3 # Copyright 2005,2006,2007 Free Software Foundation, Inc.
5 # This file is part of GNU Radio
7 # GNU Radio is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3, or (at your option)
12 # GNU Radio is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with GNU Radio; see the file COPYING. If not, write to
19 # the Free Software Foundation, Inc., 51 Franklin Street,
20 # Boston, MA 02110-1301, USA.
24 Realtime capture and display of analog Tv stations.
25 Can also use a file as source or sink
26 When you use an output file you can show the results frame-by-frame using ImageMagick
27 When you want to use the realtime sdl display window you must first install gr-video-sdl (is in gnuradio cvs).
28 When you use a file source, in stead of the usrp, make sure you capture interleaved shorts.
29 (Use usrp_rx_file.py, or use usrp_rx_cfile.py --output-shorts if you have a recent enough usrp_rx_cfile.py)
30 There is no synchronisation yet. The sync blocks are in development but not yet in cvs.
33 from gnuradio import gr, gru, eng_notation, optfir
35 from gnuradio import video_sdl
37 print "FYI: gr-video-sdl is not installed"
38 print "realtime SDL video output window will not be available"
39 from gnuradio import usrp
40 from gnuradio.eng_option import eng_option
41 from gnuradio.wxgui import slider, powermate
42 from gnuradio.wxgui import stdgui2, fftsink2, form
43 from optparse import OptionParser
44 from usrpm import usrp_dbid
49 # To debug, insert this in your test code...
51 #print 'Blocked waiting for GDB attach (pid = %d)' % (os.getpid(),)
52 #raw_input ('Press Enter to continue: ')
53 # remainder of your test code follows...
55 def pick_subdevice(u):
57 The user didn't specify a subdevice on the command line.
58 Try for one of these, in order: TV_RX, BASIC_RX, whatever is on side A.
62 return usrp.pick_subdev(u, (usrp_dbid.TV_RX,
63 usrp_dbid.TV_RX_REV_2,
64 usrp_dbid.TV_RX_REV_3,
68 class tv_rx_block (stdgui2.std_top_block):
69 def __init__(self,frame,panel,vbox,argv):
70 stdgui2.std_top_block.__init__ (self,frame,panel,vbox,argv)
72 usage="%prog: [options] [input_filename]. \n If you don't specify an input filename the usrp will be used as source\n " \
73 "Make sure your input capture file containes interleaved shorts not complex floats"
74 parser=OptionParser(option_class=eng_option)
75 parser.add_option("-R", "--rx-subdev-spec", type="subdev", default=None,
76 help="select USRP Rx side A or B (default=A)")
77 parser.add_option("-d", "--decim", type="int", default=64,
78 help="set fgpa decimation rate to DECIM [default=%default]")
79 parser.add_option("-f", "--freq", type="eng_float", default=519.25e6,
80 help="set frequency to FREQ", metavar="FREQ")
81 parser.add_option("-g", "--gain", type="eng_float", default=None,
82 help="set gain in dB (default is midpoint)")
83 parser.add_option("-c", "--contrast", type="eng_float", default=1.0,
84 help="set contrast (default is 1.0)")
85 parser.add_option("-b", "--brightness", type="eng_float", default=0.0,
86 help="set brightness (default is 0)")
87 parser.add_option("-8", "--width-8", action="store_true", default=False,
88 help="Enable 8-bit samples across USB")
89 parser.add_option("-p", "--pal", action="store_true", default=False,
90 help="PAL video format (this is the default)")
91 parser.add_option("-n", "--ntsc", action="store_true", default=False,
92 help="NTSC video format")
93 parser.add_option("-o", "--out-filename", type="string", default="sdl",
94 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)")
95 parser.add_option("-r", "--repeat", action="store_false", default=True,
96 help="repeat file in a loop")
97 parser.add_option("-N", "--no-hb", action="store_true", default=False,
98 help="don't use halfband filter in usrp")
100 (options, args) = parser.parse_args()
101 if not ((len(args) == 1) or (len(args) == 0)):
113 self.contrast = options.contrast
114 self.brightness = options.brightness
122 usrp_decim = options.decim # 32
124 if not (options.out_filename=="sdl"):
127 if not ((filename is None) or (filename=="usrp")):
128 self.filesource = gr.file_source(gr.sizeof_short,filename,options.repeat) # file is data source
129 self.istoc = gr.interleaved_short_to_complex()
130 self.connect(self.filesource,self.istoc)
136 if options.no_hb or (options.decim<8):
137 self.fpga_filename="std_4rx_0tx.rbf" #contains 4 Rx paths without halfbands and 0 tx paths
139 self.fpga_filename="std_2rxhb_2tx.rbf" # contains 2 Rx paths with halfband filters and 2 tx paths (the default)
140 self.u = usrp.source_c(0,fpga_filename=self.fpga_filename) # usrp is data source
144 format = self.u.make_format(sample_width, sample_shift)
145 r = self.u.set_format(format)
146 adc_rate = self.u.adc_rate() # 64 MS/s
147 self.u.set_decim_rate(usrp_decim)
148 if options.rx_subdev_spec is None:
149 options.rx_subdev_spec = pick_subdevice(self.u)
150 self.u.set_mux(usrp.determine_rx_mux_value(self.u, options.rx_subdev_spec))
151 self.subdev = usrp.selected_subdev(self.u, options.rx_subdev_spec)
152 print "Using RX d'board %s" % (self.subdev.side_and_name(),)
153 if options.gain is None:
154 # if no gain was specified, use the mid-point in dB
155 g = self.subdev.gain_range()
156 options.gain = float(g[0]+g[1])/2
159 usrp_rate = adc_rate / usrp_decim # 320 kS/s
161 f2uc=gr.float_to_uchar()
162 # sdl window as final sink
163 if not (options.pal or options.ntsc):
164 options.pal=True #set default to PAL
166 lines_per_frame=625.0
170 lines_per_frame=525.0
171 frames_per_sec=29.97002997
173 width=int(usrp_rate/(lines_per_frame*frames_per_sec))
174 height=int(lines_per_frame)
176 if (options.out_filename=="sdl"):
177 #Here comes the tv screen, you have to build and install gr-video-sdl for this (subproject of gnuradio, only in cvs for now)
179 video_sink = video_sdl.sink_uc ( frames_per_sec, width, height,0,show_width,height)
181 print "gr-video-sdl is not installed"
182 print "realtime \"sdl\" video output window is not available"
186 print "You can use the imagemagick display tool to show the resulting imagesequence"
187 print "use the following line to show the demodulated TV-signal:"
188 print "display -depth 8 -size " +str(width)+ "x" + str(height) + " gray:" + options.out_filename
189 print "(Use the spacebar to advance to next frames)"
191 file_sink=gr.file_sink(gr.sizeof_char, options.out_filename)
194 self.agc=gr.agc_cc(1e-7,1.0,1.0) #1e-7
195 self.am_demod = gr.complex_to_mag ()
196 self.set_blacklevel=gr.add_const_ff(0.0)
197 self.invert_and_scale = gr.multiply_const_ff (0.0) #-self.contrast *128.0*255.0/(200.0)
199 # now wire it all together
200 #sample_rate=options.width*options.height*options.framerate
202 process_type='do_no_sync'
203 if process_type=='do_no_sync':
204 self.connect (self.src, self.agc,self.am_demod,self.invert_and_scale, self.set_blacklevel,f2uc,self.dst)
205 elif process_type=='do_tv_sync_adv':
206 #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)
207 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
208 self.connect (self.src, self.am_demod,self.invert_and_scale,self.tv_sync_adv,s2f,f2uc,self.dst)
209 elif process_type=='do_nullsink':
210 #self.connect (self.src, self.am_demod,self.invert_and_scale,f2uc,video_sink)
211 c2r=gr.complex_to_real()
212 nullsink=gr.null_sink(gr.sizeof_float)
213 self.connect (self.src, c2r,nullsink) #video_sink)
214 elif process_type=='do_tv_sync_corr':
215 frame_size=width*height #int(usrp_rate/25.0)
217 search_window=20*nframes
221 tv_corr=gr.tv_correlator_ff(frame_size,nframes, search_window, video_alpha, corr_alpha,debug) #Note: this block is not yet in cvs
222 shift=gr.add_const_ff(-0.7)
223 self.connect (self.src, self.agc,self.am_demod,tv_corr,self.invert_and_scale, self.set_blacklevel,f2uc,self.dst) #self.agc,
224 else: # process_type=='do_test_image':
225 src_vertical_bars = gr.sig_source_f (usrp_rate, gr.GR_SIN_WAVE, 10.0 *usrp_rate/320, 255,128)
226 self.connect(src_vertical_bars,f2uc,self.dst)
228 self._build_gui(vbox, usrp_rate, usrp_rate, usrp_rate)
231 if abs(options.freq) < 1e6:
235 self.set_gain(options.gain)
236 self.set_contrast(self.contrast)
237 self.set_brightness(options.brightness)
238 if not(self.set_freq(options.freq)):
239 self._set_status_msg("Failed to set initial frequency")
242 def _set_status_msg(self, msg, which=0):
243 self.frame.GetStatusBar().SetStatusText(msg, which)
246 def _build_gui(self, vbox, usrp_rate, demod_rate, audio_rate):
248 def _form_set_freq(kv):
249 return self.set_freq(kv['freq'])
253 self.src_fft = fftsink.fft_sink_c (self, self.panel, title="Data from USRP",
254 fft_size=512, sample_rate=usrp_rate)
255 self.connect (self.src, self.src_fft)
256 vbox.Add (self.src_fft.win, 4, wx.EXPAND)
259 post_demod_fft = fftsink.fft_sink_f (self, self.panel, title="Post Demod",
260 fft_size=512, sample_rate=demod_rate,
261 y_per_div=10, ref_level=-40)
262 self.connect (self.am_demod, post_demod_fft)
263 vbox.Add (post_demod_fft.win, 4, wx.EXPAND)
266 post_filt_fft = fftsink.fft_sink_f (self, self.panel, title="Post Filter",
267 fft_size=512, sample_rate=audio_rate,
268 y_per_div=10, ref_level=-40)
269 self.connect (self.set_blacklevel, post_filt)
270 vbox.Add (fft_win4, 4, wx.EXPAND)
273 # control area form at bottom
274 self.myform = myform = form.form()
276 if not (self.u is None):
277 hbox = wx.BoxSizer(wx.HORIZONTAL)
279 myform['freq'] = form.float_field(
280 parent=self.panel, sizer=hbox, label="Freq", weight=1,
281 callback=myform.check_input_and_call(_form_set_freq, self._set_status_msg))
284 myform['freq_slider'] = \
285 form.quantized_slider_field(parent=self.panel, sizer=hbox, weight=3,
286 range=(50.25e6, 900.25e6, 0.25e6),
287 callback=self.set_freq)
289 vbox.Add(hbox, 0, wx.EXPAND)
291 hbox = wx.BoxSizer(wx.HORIZONTAL)
294 myform['contrast'] = \
295 form.quantized_slider_field(parent=self.panel, sizer=hbox, label="Contrast",
296 weight=3, range=(-2.0, 2.0, 0.1),
297 callback=self.set_contrast)
300 myform['brightness'] = \
301 form.quantized_slider_field(parent=self.panel, sizer=hbox, label="Brightness",
302 weight=3, range=(-255.0, 255.0, 1.0),
303 callback=self.set_brightness)
306 if not (self.u is None):
308 form.quantized_slider_field(parent=self.panel, sizer=hbox, label="Gain",
309 weight=3, range=self.subdev.gain_range(),
310 callback=self.set_gain)
312 vbox.Add(hbox, 0, wx.EXPAND)
315 self.knob = powermate.powermate(self.frame)
317 powermate.EVT_POWERMATE_ROTATE (self.frame, self.on_rotate)
318 powermate.EVT_POWERMATE_BUTTON (self.frame, self.on_button)
320 print "FYI: No Powermate or Contour Knob found"
323 def on_rotate (self, event):
324 self.rot += event.delta
325 if (self.state == "FREQ"):
327 self.set_freq(self.freq + .1e6)
330 self.set_freq(self.freq - .1e6)
332 elif (self.state == "CONTRAST"):
335 self.set_contrast(self.contrast + step)
338 self.set_contrast(self.contrast - step)
343 self.set_brightness(self.brightness + step)
346 self.set_brightness(self.brightness - step)
349 def on_button (self, event):
350 if event.value == 0: # button up
353 if self.state == "FREQ":
354 self.state = "CONTRAST"
355 elif self.state == "CONTRAST":
356 self.state = "BRIGHTNESS"
359 self.update_status_bar ()
362 def set_contrast (self, contrast):
363 self.contrast = contrast
364 self.invert_and_scale.set_k(-self.contrast *128.0*255.0/(200.0))
365 self.myform['contrast'].set_value(self.contrast)
366 self.update_status_bar ()
368 def set_brightness (self, brightness):
369 self.brightness = brightness
370 self.set_blacklevel.set_k(self.brightness +255.0)
371 self.myform['brightness'].set_value(self.brightness)
372 self.update_status_bar ()
374 def set_freq(self, target_freq):
376 Set the center frequency we're interested in.
378 @param target_freq: frequency in Hz
381 Tuning is a two step process. First we ask the front-end to
382 tune as close to the desired frequency as it can. Then we use
383 the result of that operation and our target_frequency to
384 determine the value for the digital down converter.
386 if not (self.u is None):
387 r = usrp.tune(self.u, 0, self.subdev, target_freq)
389 self.freq = target_freq
390 self.myform['freq'].set_value(target_freq) # update displayed value
391 self.myform['freq_slider'].set_value(target_freq) # update displayed value
392 self.update_status_bar()
393 self._set_status_msg("OK", 0)
396 self._set_status_msg("Failed", 0)
399 def set_gain(self, gain):
400 if not (self.u is None):
402 self.myform['gain'].set_value(gain) # update displayed value
403 self.subdev.set_gain(gain)
404 self.update_status_bar()
406 def update_status_bar (self):
407 msg = "Setting:%s Contrast:%r Brightness:%r Gain: %r" % (self.state, self.contrast,self.brightness,self.gain)
408 self._set_status_msg(msg, 1)
409 #self.src_fft.set_baseband_freq(self.freq)
413 if __name__ == '__main__':
414 app = stdgui2.stdapp (tv_rx_block, "USRP TV RX black-and-white")