3 Realtime capture and display of analog Tv stations.
4 Can also use a file as source or sink
5 When you use an output file you can show the results frame-by-frame using ImageMagick
6 When you want to use the realtime sdl display window you must first install gr-video-sdl (is in gnuradio cvs).
7 When you use a file source, in stead of the usrp, make sure you capture interleaved shorts.
8 (Use usrp_rx_file.py, or use usrp_rx_cfile.py --output-shorts if you have a recent enough usrp_rx_cfile.py)
9 There is no synchronisation yet. The sync blocks are in development but not yet in cvs.
12 from gnuradio import gr, gru, eng_notation, optfir
14 from gnuradio import video_sdl
16 print "FYI: gr-video-sdl is not installed"
17 print "realtime SDL video output window will not be available"
18 from gnuradio import usrp
19 from gnuradio import blks
20 from gnuradio.eng_option import eng_option
21 from gnuradio.wxgui import slider, powermate
22 from gnuradio.wxgui import stdgui, fftsink, form
23 from optparse import OptionParser
29 # To debug, insert this in your test code...
31 #print 'Blocked waiting for GDB attach (pid = %d)' % (os.getpid(),)
32 #raw_input ('Press Enter to continue: ')
33 # remainder of your test code follows...
35 def pick_subdevice(u):
37 The user didn't specify a subdevice on the command line.
38 Try for one of these, in order: TV_RX, BASIC_RX, whatever is on side A.
42 return usrp.pick_subdev(u, (usrp_dbid.TV_RX,
43 usrp_dbid.TV_RX_REV_2,
47 class tv_rx_graph (stdgui.gui_flow_graph):
48 def __init__(self,frame,panel,vbox,argv):
49 stdgui.gui_flow_graph.__init__ (self,frame,panel,vbox,argv)
51 usage="%prog: [options] [input_filename]. \n If you don't specify an input filename the usrp will be used as source\n " \
52 "Make sure your input capture file containes interleaved shorts not complex floats"
53 parser=OptionParser(option_class=eng_option)
54 parser.add_option("-R", "--rx-subdev-spec", type="subdev", default=None,
55 help="select USRP Rx side A or B (default=A)")
56 parser.add_option("-d", "--decim", type="int", default=64,
57 help="set fgpa decimation rate to DECIM [default=%default]")
58 parser.add_option("-f", "--freq", type="eng_float", default=519.25e6,
59 help="set frequency to FREQ", metavar="FREQ")
60 parser.add_option("-g", "--gain", type="eng_float", default=None,
61 help="set gain in dB (default is midpoint)")
62 parser.add_option("-c", "--contrast", type="eng_float", default=1.0,
63 help="set contrast (default is 1.0)")
64 parser.add_option("-b", "--brightness", type="eng_float", default=0.0,
65 help="set brightness (default is 0)")
66 parser.add_option("-8", "--width-8", action="store_true", default=False,
67 help="Enable 8-bit samples across USB")
68 parser.add_option("-p", "--pal", action="store_true", default=False,
69 help="PAL video format (this is the default)")
70 parser.add_option("-n", "--ntsc", action="store_true", default=False,
71 help="NTSC video format")
72 parser.add_option("-o", "--out-filename", type="string", default="sdl",
73 help="For example out_raw_uchar.gray. If you don't specify an output filename you will get a video_sink_sdl realtime output window. You then need to have gr-video-sdl installed)")
74 parser.add_option("-r", "--repeat", action="store_false", default=True,
75 help="repeat file in a loop")
76 parser.add_option("-N", "--no-hb", action="store_true", default=False,
77 help="don't use halfband filter in usrp")
79 (options, args) = parser.parse_args()
80 if not ((len(args) == 1) or (len(args) == 0)):
92 self.contrast = options.contrast
93 self.brightness = options.brightness
101 usrp_decim = options.decim # 32
103 if not (options.out_filename=="sdl"):
106 if not ((filename is None) or (filename=="usrp")):
107 self.filesource = gr.file_source(gr.sizeof_short,filename,options.repeat) # file is data source
108 self.istoc = gr.interleaved_short_to_complex()
109 self.connect(self.filesource,self.istoc)
115 if options.no_hb or (options.decim<8):
116 self.fpga_filename="std_4rx_0tx.rbf" #contains 4 Rx paths without halfbands and 0 tx paths
118 self.fpga_filename="std_2rxhb_2tx.rbf" # contains 2 Rx paths with halfband filters and 2 tx paths (the default)
119 self.u = usrp.source_c(0,fpga_filename=self.fpga_filename) # usrp is data source
123 format = self.u.make_format(sample_width, sample_shift)
124 r = self.u.set_format(format)
125 adc_rate = self.u.adc_rate() # 64 MS/s
126 self.u.set_decim_rate(usrp_decim)
127 if options.rx_subdev_spec is None:
128 options.rx_subdev_spec = pick_subdevice(self.u)
129 self.u.set_mux(usrp.determine_rx_mux_value(self.u, options.rx_subdev_spec))
130 self.subdev = usrp.selected_subdev(self.u, options.rx_subdev_spec)
131 print "Using RX d'board %s" % (self.subdev.side_and_name(),)
132 if options.gain is None:
133 # if no gain was specified, use the mid-point in dB
134 g = self.subdev.gain_range()
135 options.gain = float(g[0]+g[1])/2
138 usrp_rate = adc_rate / usrp_decim # 320 kS/s
140 f2uc=gr.float_to_uchar()
141 # sdl window as final sink
142 if not (options.pal or options.ntsc):
143 options.pal=True #set default to PAL
145 lines_per_frame=625.0
149 lines_per_frame=525.0
150 frames_per_sec=29.97002997
152 width=int(usrp_rate/(lines_per_frame*frames_per_sec))
153 height=int(lines_per_frame)
155 if (options.out_filename=="sdl"):
156 #Here comes the tv screen, you have to build and install gr-video-sdl for this (subproject of gnuradio, only in cvs for now)
158 video_sink = video_sdl.sink_uc ( frames_per_sec, width, height,0,show_width,height)
160 print "gr-video-sdl is not installed"
161 print "realtime \"sdl\" video output window is not available"
165 print "You can use the imagemagick display tool to show the resulting imagesequence"
166 print "use the following line to show the demodulated TV-signal:"
167 print "display -depth 8 -size " +str(width)+ "x" + str(height) + " gray:" + options.out_filename
168 print "(Use the spacebar to advance to next frames)"
170 file_sink=gr.file_sink(gr.sizeof_char, options.out_filename)
173 self.agc=gr.agc_cc(1e-7,1.0,1.0) #1e-7
174 self.am_demod = gr.complex_to_mag ()
175 self.set_blacklevel=gr.add_const_ff(0.0)
176 self.invert_and_scale = gr.multiply_const_ff (0.0) #-self.contrast *128.0*255.0/(200.0)
178 # now wire it all together
179 #sample_rate=options.width*options.height*options.framerate
181 process_type='do_no_sync'
182 if process_type=='do_no_sync':
183 self.connect (self.src, self.agc,self.am_demod,self.invert_and_scale, self.set_blacklevel,f2uc,self.dst)
184 elif process_type=='do_tv_sync_adv':
185 #defaults: gr.tv_sync_adv (double sampling_freq, unsigned int tv_format,bool output_active_video_only=false, bool do_invert=false, double wanted_black_level=0.0, double wanted_white_level=255.0, double avg_alpha=0.1, double initial_gain=1.0, double initial_offset=0.0,bool debug=false)
186 self.tv_sync_adv=gr.tv_sync_adv(usrp_rate,0,False,False,0.0,255.0,0.01,1.0,0.0,False) #note, this block is not yet in cvs
187 self.connect (self.src, self.am_demod,self.invert_and_scale,self.tv_sync_adv,s2f,f2uc,self.dst)
188 elif process_type=='do_nullsink':
189 #self.connect (self.src, self.am_demod,self.invert_and_scale,f2uc,video_sink)
190 c2r=gr.complex_to_real()
191 nullsink=gr.null_sink(gr.sizeof_float)
192 self.connect (self.src, c2r,nullsink) #video_sink)
193 elif process_type=='do_tv_sync_corr':
194 frame_size=width*height #int(usrp_rate/25.0)
196 search_window=20*nframes
200 tv_corr=gr.tv_correlator_ff(frame_size,nframes, search_window, video_alpha, corr_alpha,debug) #Note: this block is not yet in cvs
201 shift=gr.add_const_ff(-0.7)
202 self.connect (self.src, self.agc,self.am_demod,tv_corr,self.invert_and_scale, self.set_blacklevel,f2uc,self.dst) #self.agc,
203 else: # process_type=='do_test_image':
204 src_vertical_bars = gr.sig_source_f (usrp_rate, gr.GR_SIN_WAVE, 10.0 *usrp_rate/320, 255,128)
205 self.connect(src_vertical_bars,f2uc,self.dst)
207 self._build_gui(vbox, usrp_rate, usrp_rate, usrp_rate)
210 if abs(options.freq) < 1e6:
214 self.set_gain(options.gain)
215 self.set_contrast(self.contrast)
216 self.set_brightness(options.brightness)
217 if not(self.set_freq(options.freq)):
218 self._set_status_msg("Failed to set initial frequency")
221 def _set_status_msg(self, msg, which=0):
222 self.frame.GetStatusBar().SetStatusText(msg, which)
225 def _build_gui(self, vbox, usrp_rate, demod_rate, audio_rate):
227 def _form_set_freq(kv):
228 return self.set_freq(kv['freq'])
232 self.src_fft = fftsink.fft_sink_c (self, self.panel, title="Data from USRP",
233 fft_size=512, sample_rate=usrp_rate)
234 self.connect (self.src, self.src_fft)
235 vbox.Add (self.src_fft.win, 4, wx.EXPAND)
238 post_demod_fft = fftsink.fft_sink_f (self, self.panel, title="Post Demod",
239 fft_size=512, sample_rate=demod_rate,
240 y_per_div=10, ref_level=-40)
241 self.connect (self.am_demod, post_demod_fft)
242 vbox.Add (post_demod_fft.win, 4, wx.EXPAND)
245 post_filt_fft = fftsink.fft_sink_f (self, self.panel, title="Post Filter",
246 fft_size=512, sample_rate=audio_rate,
247 y_per_div=10, ref_level=-40)
248 self.connect (self.set_blacklevel, post_filt)
249 vbox.Add (fft_win4, 4, wx.EXPAND)
252 # control area form at bottom
253 self.myform = myform = form.form()
255 if not (self.u is None):
256 hbox = wx.BoxSizer(wx.HORIZONTAL)
258 myform['freq'] = form.float_field(
259 parent=self.panel, sizer=hbox, label="Freq", weight=1,
260 callback=myform.check_input_and_call(_form_set_freq, self._set_status_msg))
263 myform['freq_slider'] = \
264 form.quantized_slider_field(parent=self.panel, sizer=hbox, weight=3,
265 range=(50.25e6, 900.25e6, 0.25e6),
266 callback=self.set_freq)
268 vbox.Add(hbox, 0, wx.EXPAND)
270 hbox = wx.BoxSizer(wx.HORIZONTAL)
273 myform['contrast'] = \
274 form.quantized_slider_field(parent=self.panel, sizer=hbox, label="Contrast",
275 weight=3, range=(-2.0, 2.0, 0.1),
276 callback=self.set_contrast)
279 myform['brightness'] = \
280 form.quantized_slider_field(parent=self.panel, sizer=hbox, label="Brightness",
281 weight=3, range=(-255.0, 255.0, 1.0),
282 callback=self.set_brightness)
285 if not (self.u is None):
287 form.quantized_slider_field(parent=self.panel, sizer=hbox, label="Gain",
288 weight=3, range=self.subdev.gain_range(),
289 callback=self.set_gain)
291 vbox.Add(hbox, 0, wx.EXPAND)
294 self.knob = powermate.powermate(self.frame)
296 powermate.EVT_POWERMATE_ROTATE (self.frame, self.on_rotate)
297 powermate.EVT_POWERMATE_BUTTON (self.frame, self.on_button)
299 print "FYI: No Powermate or Contour Knob found"
302 def on_rotate (self, event):
303 self.rot += event.delta
304 if (self.state == "FREQ"):
306 self.set_freq(self.freq + .1e6)
309 self.set_freq(self.freq - .1e6)
311 elif (self.state == "CONTRAST"):
314 self.set_contrast(self.contrast + step)
317 self.set_contrast(self.contrast - step)
322 self.set_brightness(self.brightness + step)
325 self.set_brightness(self.brightness - step)
328 def on_button (self, event):
329 if event.value == 0: # button up
332 if self.state == "FREQ":
333 self.state = "CONTRAST"
334 elif self.state == "CONTRAST":
335 self.state = "BRIGHTNESS"
338 self.update_status_bar ()
341 def set_contrast (self, contrast):
342 self.contrast = contrast
343 self.invert_and_scale.set_k(-self.contrast *128.0*255.0/(200.0))
344 self.myform['contrast'].set_value(self.contrast)
345 self.update_status_bar ()
347 def set_brightness (self, brightness):
348 self.brightness = brightness
349 self.set_blacklevel.set_k(self.brightness +255.0)
350 self.myform['brightness'].set_value(self.brightness)
351 self.update_status_bar ()
353 def set_freq(self, target_freq):
355 Set the center frequency we're interested in.
357 @param target_freq: frequency in Hz
360 Tuning is a two step process. First we ask the front-end to
361 tune as close to the desired frequency as it can. Then we use
362 the result of that operation and our target_frequency to
363 determine the value for the digital down converter.
365 if not (self.u is None):
366 r = usrp.tune(self.u, 0, self.subdev, target_freq)
368 self.freq = target_freq
369 self.myform['freq'].set_value(target_freq) # update displayed value
370 self.myform['freq_slider'].set_value(target_freq) # update displayed value
371 self.update_status_bar()
372 self._set_status_msg("OK", 0)
375 self._set_status_msg("Failed", 0)
378 def set_gain(self, gain):
379 if not (self.u is None):
381 self.myform['gain'].set_value(gain) # update displayed value
382 self.subdev.set_gain(gain)
383 self.update_status_bar()
385 def update_status_bar (self):
386 msg = "Setting:%s Contrast:%r Brightness:%r Gain: %r" % (self.state, self.contrast,self.brightness,self.gain)
387 self._set_status_msg(msg, 1)
388 #self.src_fft.set_baseband_freq(self.freq)
392 if __name__ == '__main__':
393 app = stdgui.stdapp (tv_rx_graph, "USRP TV RX black-and-white")