Random Quote Board

Analog Video Transmissions with hacktv and Gnu Radio

Gary Schafer, September 2025

I created and demodulated digital television signals, specifically the ATSC version 1, in a previous post. This month, I'm going to take a step back and look at analog television video, thanks to the work of Philip Heron and his "hacktv" code.

hacktv allows for sending the samples directly to a HackRF. For this post, I'm going to store the created samples as files. This will allow for transmitting with other systems besides the HackRF.

Analog Video Review

There are three versions of analog video used for broadcast (with a few special cases), and a plethora of other analog video formats such as slow scan TV. We're going to cover broadcast video in this post. Specifically, we'll look at NTSC, PAL and SECAM.

Assumptions

If you wish to follow along with this post, I'm going to assume that you have hacktv already installed.

You will also need a MP4 video which you will convert into an analog video signal. Most smartphones nowadays store their video in MP4 format. If you do not have one, then you can use ffmpeg to convert an image (JPEG, PNG, etc) into a MP4 video. NOTE: If I understand it correctly, installing hacktv also installs ffmpeg. The ffmpeg command to convert a still image into MP4 video is:

ffmpeg -loop 1 -i still-image.jpg -t 30 -r 30 video.mp4

where:

Baseband Video

I'm going to start with a baseband NTSC signal. For this, I'm using the same video of the Cornell Chimes as for the post I did on ATSC. I started by opening a terminal and navigating to the same folder containing the MP4 video file. I then entered the following command:

hacktv -o cornell-chimes-ntsc.rs16 -m ntsc -s 9000000 cornell-chimes.mp4

where:

This converts the digital .MP4 into an analog NTSC baseband signal, though only the video (no audio). This baseband video signal has the following appearance in the time domain:

Time domain display showing two lines of NTSC video with annotations for the horizontal sync, luminance signal, front porch, back porch and color burst.
Two lines of NTSC video with annotations for the horizontal sync pulse, front porch, back porch, color burst, and luminance plus the chroma signal. Single lines of video are roughly 63.556 usec long, including the two porches (front and back), and horizontal sync pulse. Note that the color burst is part of the back porch.
Graph showing a signal that is difficult to see the individual lines of video. Instead, the signal appears roughly squarish and almost completely filled in, with two large dips, one at the beginning and one near the middle, indicating the location of the vertical blanking intervals.
This shows a single frame of NTSC video. NTSC transmits 30 frames per second, with each frame divided into two fields. These two fields contain 525 lines. Even though each field is said to consist of 262.5 lines, for counting purposes, the first field is considered to have 263 lines (covering the half of a line at the end of field 1 and the first half of a line at the beginning of field 2), while the second field is considered to have the remaining 262 lines.

To make this into a proper NTSC signal contained within a 6 MHz channel, I'm going to use the following flowgraph:

Modulated NTSC

It's also possible to have hacktv create a fully-modulated NTSC signal. To do this with the same MP4 file as above, I used this code:

hacktv -o NTSC-modulated-chimes.cs16 -m m -s 13500000 cornell-chimes.mp4

where:

The modulated signal that hacktv creates does not conform with the standards for bandwidth. Here's the raw spectrum after converting the MP4 to modulated NTSC:

Spectral display showing a peak at the center, with two smaller, symmetric lumps about halfway towards each edge. A thinner spike sticks up near the right edge.
Raw spectrum of a modulated NTSC signal as created by hacktv. This is not filtered as would an actual broadcast, but is similar to the signals from a video game TV adapter (aka the "TV switch box"). In order to make this fit into the standard 6 MHz channel, it will need to be shifted and filtered (or filtered and shifted).

To adjust the spectrum so that it is similar to a broadcast station, we need to shift the spectrum and filter. We can also adjust the sample rate. Here's a flowgraph to do just that.

Block diagram showing about a dozen blocks in two lines, one over the other. The blocks start on the left of the top line, with arrows pointing towards the right. The second line does the same, and has the last block in the lower, righthand corner.
Flowgraph to convert the raw NTSC spectrum from hacktv into a filtered signal that is similar to that of a broadcast TV station. The input file is run through a "FFT Xlating Filter", which first shifts the spectrum so that the signal will be centered, then filters it with a lowpass filter. The "Rational Resampler" block adjusts the sample rate so that the final sample rate is 6 MHz, the minimum needed for a NTSC broadcast station.
Spectral display showing a large peak near the left end, and a thinner peak on the right end.
Filtered and shifted spectrum of NTSC signal. The video signal now occupies the correct position (1.25 MHz from the lower end), with the chroma 3.58 MHz higher and the audio 4.5 MHz higher, both from the video carrier.

I created a basic flowgraph outputting this filtered file into a HackRF One, and sending the output of the HackRF into a television with an analog tuner.

Block diagram consisting of three blocks flowing from left to right.
Flowgraph to transmit the video signal through a HackRF to a television set. The television was set for analog reception. This specific channel was channel 3 (60 - 66 MHz).

The video was quite good, as seen below.

Analog video as seen on a television. The video signal is the IQ file created with hacktv, modified (filtered, shifted, resampled) with Gnu Radio, and transmitted with a HackRF.

Modulated PAL

I used hactkv to also make a PAL signal. The specific command I used was:

hacktv -o PAL-modulated-chimes-16M0FS.cs16 -m i -s 16000000 cornell-chimes.mp4

where:

The raw spectrum from this PAL signal is as follows:

Display of a spectrum with a spike in the center, and two spikes equidistant from the center roughly halfway to each edge. A thin spike sticks up about three-quarters from the right edge. A rounded lobe sticks up near the right edge.
Raw spectrum of the PAL signal as created by the hacktv command with the "-m i" option. This signal has both an analog audio signal (the spike near the right end) and a NICAM digital audio signal (the rounded lobe near the right end).

Filtering this signal is slightly different from standard signals because of the NICAM audio subcarrier. It actually extends slightly into the next channel higher in the spectrum. To keep from cutting off part of this signal, I only lowered the sample rate to 10 MHz after filtering from the original 16 MHz.

Block diagram showing about a dozen blocks in two lines, one over the other. The blocks start on the left of the top line, with arrows pointing towards the right. The second line does the same, and has the last block in the lower, righthand corner.
Gnu Radio Companion flowgraph to input the raw PAL signal created with the hacktv command, filter it, shift the spectrum, and resample to a 10 MHz sample rate. The reason that the sample rate is only 10 MHz (as opposed to 8 MHz, which would just fit the 8 MHz channel bandwidth, is due to the presence of the NICAM audio subcarrier. Reducing the sample rate to 8 MHz will cut off part of this signal. Therefore, the sample rate is only reduced to 10 MHz.
Spectral display showing a noisy signal that peaks roughly 2/3 of the way from the center towards the left end. A smaller, thinner peak is seen about 3/4 of the way towards the right end from the center. A smaller, rounded with roughly the shape of a half-oval can be seen near the right edge.
Spectral display of the filtered and shifted PAL signal. The video carrier is on the left. The analog audio carrier is the thin spike towards the right end. The rounded lobe to the right of the analog audio carrier is the NICAM digital stereo signal. This signal actually extends slightly into the next channel higher in the spectrum.

You can create a PAL signal without the NICAM subcarrier using the "--nonicam" option, as follows:

hacktv -o PAL-modulated-chimes-nonicam-16M0FS.cs16 -m i -s 16000000 --nonicam cornell-chimes.mp4

where:

Spectral display showing a noisy signal that peaks roughly 2/3 of the way from the center towards the left end. A smaller, thinner peak is seen about 3/4 of the way towards the right end from the center.
Filtered and shifted PAL spectrum without the NICAM subcarrier. Without this subcarrier, the spectrum will fit properly within the 8 MHz channel bandwidth.

Modulated SECAM

Making a modulated SECAM signal is similar to the others. I used the following command to make a basic SECAM signal:

hacktv -o SECAM-modulated-chimes-16M0FS.cs16 -m l -s 16000000 cornell-chimes.mp4

where:

This creates a raw spectrum that is somewhat similar to the PAL spectrum, except the NICAM subcarrier is lower in frequency than the analog audio subcarrier.

Display of a spectrum with a spike in the center, and two triangular spikes equidistant from the center roughly halfway to each edge. A rounded lobe roughly looking like a half-oval sticks up just to the right of the right-most triangular signal. Just to the right of the half-oval is a thin spike sticking up. This last spike is roughly 4/5 towards the right edge from the center.
Spectrum of the raw SECAM signal as created by hacktv. The noticeable differences between this signal and the PAL signal are that the chroma subcarriers appear to have twin peaks, the NICAM subcarrier is lower in frequency (to the left of) the analog audio subcarrier, and the analog audio subcarrier resides 6.5 MHz up from the video carrier at the center.

I used the following flowgraph to filter, shift and resample the signal. (NOTE: I could have simplified this flowgraph, but modifying the flowgraph would have been more work than just changing some values in the flowgraph I used for the PAL. So I went with "lazy".)

Block diagram showing about a dozen blocks in two lines, one over the other. The blocks start on the left of the top line, with arrows pointing towards the right. The second line does the same, and has the last block in the lower, righthand corner.
Gnu Radio Companion flowgraph to filter, shift, and resample the SECAM signal so that it fits within an 8 MHz channel.
Spectral display showing a noisy signal that peaks near the left (lower) end. Just to the right of center the signal climbs up into two peaks. Near the right end is a half-oval-shaped signal, followed by a thin spike at the right end.
Spectral display of filtered, shifted, and resampled SECAM signal. Because the NICAM signal is lower in frequency than the analog audio carrier, the entire spectrum will fit within the 8 MHz channel bandwidth.

Demodulating with GRC

As I'm an American (not to be confused with a "Murican"), I don't have ready access to either PAL or SECAM demodulators. Both the TVs I have in my house only work on NTSC. This means that, in order to test the PAL or SECAM signals, I'll have to make a "tuner" using Gnu Radio.

Fortunately, I've already done that. All we need to do is to modify the flowgraph for amplitude demodulation and add an audio demodulator.

Block diagram consisting of seven rows of blocks. The top three rows consist of blocks with variables used to control the processing; the bottom four rows consist of blocks that process the audio and video from the modulation analog video carrier.
GRC flowgraph to demodulate the audio and video from either NTSC, PAL or SECAM signals. The various parameters, such as the filter limits, the input sample rate, and the audio demodulation (SECAM uses AM, NTSC and PAL use FM), have to be adjusted manually.
A window containing 5 rows of various controls along the top, and a display underneath showing two fields of single video frame.
Display of controls and video frame (two fields) of PAL video.
Grayscale display showing a woman in front of a row of levers, with people standing around. Cables lead upwards from the levers.
Display from the PAL video signal.

Setting the Sample Rate

TL;DR: The video clock rate should be 13.5 MHz for NTSC, and 16 MHz for PAL or SECAM.

Video is displayed one horizontal line at a time. Each line has a certain time length. The inverse is that a certain number of lines are drawn each second. This is the horizontal line rate. This horizontal line rate is based on the specific video standard, with PAL and SECAM having identical line rates. In order to display an analog video signal on a digital display, each horizontal line has to be drawn with an integer number of samples. This is where it gets tricky. The number of samples displayed on the screen is the sample rate divided by the horizontal line rate. If this number is not an integer, then the signal will not line up properly.

The question becomes, "What clock rate should we use?"

A quick discussion about how NTSC signals are clocked. According to The Television Engineering Handbook (my emphasis added):

In color systems, the horizontal and vertical rates are rigidly locked to (actually divided down from) the color subcarrier frequency. ("Television Engineering Handbook: Revised Edition", K. Blair Benson (editor and coauthor), McGraw-Hill, Inc, New York, 1992)

The color subcarrier frequency is approximately 3.58 MHz, and can be calculated as follows:

\( \Large f_{chroma} = \text{5 MHz} \times \frac{63}{88} = \text{3.5795455 MHz} \)

Both the horizontal and vertical frequencies can be calculated from this as follows:

\( \Large f_{horz} = f_{chroma} \times \frac{2}{455} = \text{15734.2657 Hz} \)

\( \Large f_{vert} = f_{horz} \times \frac{2}{525} = \text{59.94 Hz} \)

Another way to calculate these various clock rates is to start with the horizontal sync rate, which can also be calculated as follows:

\( \Large f_{horz} = \text{2.25 MHz} \div 143 = \text{15734.2657 Hz} \)

The fact that this clock rate is a frequency (such as could be created by a crystal oscillator) decimated by a single integer simplifies our problem. That's because, if we use an integer value of this sample rate (for example, 2.25 MHz, 4.5 MHz, 6.75 MHz, 9 MHz, etc), then the number of pixels we need to display the image will also be an integer value. That works very well in Gnu Radio with the Video SDL Sink block. For example, if we use a 4.5 MHz sample rate, then we would need 286 pixels to display one line of video. However, since the sample rate also sets the maximum bandwidth allowable combined with the fact that NTSC is a 6 MHz channel, then we need a sample rate of at least 6 MHz. Given we need an integer multiple of 2.25 MHz, this means the lowest sample rate we should use for a modulated NTSC signal should be 6.75 MHz. You may have noticed that the sample rate used for the hacktv command above was a very specific value, 13.5 MHz. That's 6 x 2.25 MHz. To display this video signal, we could use a Video SDL Sink with a horizontal width of 6 x 143 = 858 pixels. In the same vein, PAL and SECAM should be 16 MHz, which requires 1024 samples for each horizontal line.

hacktv Sample Rates

hacktv requires that the video sample rate be an integer multiple of the horizontal sync rate. Even if you try to set the sample rate to a value that is not an integer multiple of the horizontal rate, hacktv will set the sample rate to the closest value to one of the integer values. For example, what happens if you try to set the sample rate to 11 MHz? This value would require a multiple of the horizontal sync rate of (11 MHz/15834.2657 Hz) = 699.11. The closest integer is 699. Hence, hacktv will set the sample rate to (15734.2657 Hz)(699) = 10.998251 MHz.

Summary

Between this and the previous post, you should be set for making a video receiver for pretty much any broadcast analog video standard (NTSC, PAL or SECAM). I'm not certain what my next post will be, but perhaps I'll cover slow scan TV signals. Or something else. Guess we'll find out!

Rastering Analog Video with Gnu Radio

Gary Schafer, September 2025

I attended Shmoocon a few years ago, and was able to take part in my first "capture the flag". One of the "flags" required viewing an analog video signal. This was before I'd begun playing around with SDRangel, so I needed some way to raster the video signal without requiring a TV set (which wasn't handy at the time). I came up with a Gnu Radio flowgraph to do it. I was in a hurry at the time, so my first flowgraph was barebones. I've since enhanced it. That's what I'll cover here.

Let's get a couple of things out of the way right now about what this flowgraph will (and will not) do.

  1. This flowgraph will work on NTSC, PAL or SECAM signals.
  2. I'm just going for video. I didn't add audio demodulation (if any) on the signal.
  3. This flowgraph will only show the image in black and white. Getting to the color means properly and precisely demodulating the chroma subcarrier signal, plus figuring out how to separate out the Y, U and V signals. That's a bit more than I want to do and, frankly, I'm not certain how I would do it in Gnu Radio.
  4. For the purposes of this flowgraph, I consider PAL and SECAM to be the same thing. Since I'm not dealing with color (see previous comment), the timing is my only concern. The timing for these two types are equivalent.
  5. Gnu Radio does not currently have a way to sync with the video signal. Instead, we'll create our own sync that should be good enough.
  6. I didn't setup up the screen controls and display for neatness. I didn't bother adding values to the "GUI Hint" properties to any of the control or display blocks.

I'm going to jump into the flow of rastering, with the assumption that you, the reader, already has a basic understanding of analog video rastering. If not, well, go ahead and try it anyway if you have a known analog video transmitter near you! What have you got to lose?

General Flow of Rastering with Gnu Radio

Image showing a wide DVD player with a small, gray box on top. The gray box roughly 5 cm square and 3 cm high has three colored connectors plugged into it, a yellow, red, and white one. Out of the top of the gray box extends a thin wire antenna roughly 20 cm long.
This is the transmitter, the gray box, on top of the video source feeding it, an old Panasonic DVD player. In the player is a DVD of the old 1950s movie, "The Crimson Pirate", starring Burt Lancaster. The gray box is an analog, wireless video extender. It uses FM to transmit its signal to a receiver nearby. That receiver will be plugged into the actual display.
Spectral trace showing a signal that starts low on the left, rises to a peak in the middle, then slopes down towards the right.
As it rises on the left, it seems to bounce up like a bouncing ball that initially bounces higher, then hits a peak, then drops again. This bouncing occurs before the signal peaks at the center of the display. After the peak, the signal waves up and down seemingly random til it reachs the bottom of the display on the right.
Spectral trace of a FM video signal. This was captured with a 20 MHz sample rate, so that span on this display is also 20 MHz. I captured this with a Signal Hound BB60C and Signal Hound's "Spike" software.
Block diagram showing a signal flow from the top left to the bottom right. Each line ends with a block that connects to the left end of the line below. There are three lines total. Along the top are blocks for entering variables and controls for adjusting certain parameters.
Gnu Radio flowgraph for rastering video. This particular flowgraph uses the Ettus Research USRP B200-mini for input. It would work just as well with a HackRF One or even a file containing samples of a modulated video signal. The "Quadrature Demod" block is due to the transmitter using FM. If it had been an actual, over-the-air analog video broadcast, I would probably have used a "Complex to Mag" block.

I've also made the flowgraph available if you wish to download it.

Here's how to work with this flowgraph. Before running it, enter the appropriate values in the blocks marked with the comment, "USER ENTERED VARIABLE". These are:

When you run it, the flowgraph:

  1. Performs calculations based on the video type (NTSC, PAL/SECAM). These include calculating the horizontal line rate, the number of pixels in the width of the Video SDL Sink, the limits for the horizontal line rate adjustment, and the maximum limits for the horizontal and vertical shift.
  2. Inputs the signal. This can be from a SDR, from a file, or from a network connection. For this post, I used the USRP B200-mini.
  3. Filters the signal. This will either be based on the signal and SDR (either with the sample rate or with a manual setting for the SDR's input bandwidth), or with an external filter. I'd use an external filter if I was going after an AM signal that used VSB. If there was an audio signal on the next channel down (meaning within a few MHz of the video carrier), I'd throw in a filter to get rid of that before demodulation. But for FM video? Those are wide enough bandwidth that by setting the sample rate properly, the signal will already be filtered just based on the sample rate.
  4. Demodulates the signal. For those outside of the USA, you may still have broadcast analog video signals. If that is what you're going for, then you'll need an AM demodulator (a "Complex to Mag" block works nicely). If you're going after an analog video transmitter such as a wireless video extender or perhaps a drone, those transmissions are FM (a "Quadrature Demod" block works here). For this post, I'm using an analog wireless video extender. It uses FM. Hence, the "Quadrature Demod" block.
  5. Filters the baseband signal. NTSC, for example, uses the bottom 4.2 MHz for video. This includes a chroma subcarrier at roughly 3.58 MHz. For FM video, audio subcarriers exist at 6 and 6.5 MHz, while for AM broadcast video, the audio subcarrier exists at 4.5 MHz. This filter gets rid of any audio subcarriers.
  6. Adjusts the sample rate. There are two parts to this. The first is to get to the approximate sample rate required by the specific video standard (namely either NTSC or PAL/SECAM). The second step is to get the precise timing so that the image will not drift on the display. I do both steps with the "Polyphase Arbitrary Resampler".
  7. Adjusts the amplitude of the video signal. The Video SDL Sink (the block we'll use to display the video signal) will use an unsigned 8-bit integer (uchar). This means we need to have amplitudes between 0 (black) and 255 (white). I'm performing this in two blocks. The first is a multiplication that takes place in the "Gain" setting of the "Quadrature Demod" block. This multiplication is essentially a contrast adjustment. The other block is an "Add Const" (add a constant) to adjust the level of the overall signal and keep it within the 0 - 255 limits. This is essentially a "brightness" adjustment.
  8. Shifts the video display so that it is centered horizontally and vertically. Once you have the timing set juuuuust right, the display should not be moving, but it doesn't mean it will be centered, either horizontally or vertically. Therefore, I'm using a "Delay" block to adjust these positions. NOTE: This block is not absolutely required. You can get away without it by using the timing adjustment earlier. But this is tricky. However, if you're operating at the very edge of your system (seeing overflows from the SDR, primarily), then you can delete this block and just use the timing.
  9. Interlaces the lines. As I said, broadcast analog video is interlaced. I'm using two blocks to interlace the video, a "Delay" block in combination with a "Stream Mux" block. The delay adds a half-frame (one field) delay, then multiplexes the non-delayed and delayed to provide for the interlacing.
  10. Finally, displays the video. The Video SDL Sink display ratio (say, widescreen or 4:3) can be set using the "Display Width" and "Display Height" values. I'm using a relatively large size because I'm fortunate to have multiple monitors. If you're operating on a small laptop or tablet screen, you may wish to decrease these values to a size that works for your screen. This will probably take some trial-and-error.

Once the flowgraph is truly running, you'll need to make a few adjustments. I'll give the general flow here.

  1. If needed, adjust the center frequency of your SDR to that of the video transmitter, then adjust the appropriate gain values of that SDR so that you have a decent signal strength.
  2. Once you have a video signal on the time display, adjust the contrast and brightness controls so that the signal amplitude extends as much between the top and bottom of the display as possible.
  3. Adjust the horizontal rate. This is what sets the sync for the signal. The rate is in Hz, but the step size is 1/1000th of a Hz. This means it will change slooooowly. You can try sliding the slider manually. I tend to use the up / down arrows. Anyway, once the signal stops drifting, you're set. I've set the limits for the timing to be within +/- 10 Hz of the video type horizontal rate. This has worked on pretty much every transmitter I've seen. HOWEVER, given the possibility that this will be used by many more people, if you adjust the sliders to the limit and it is still drifting, you'll need to increase those limits. ALSO, the video display will probably continue to drift very slowly over time. So this may need to be adjusted periodically.
  4. Adjust the position of the display using the horizontal and vertical shift sliders to center the display.
Screen showing several horizontal slider bars along the top and a screen showing a filled blue screen along the bottom.
Screen showing the various controls along the top and the time domain display along the bottom. The time display shows a full frame (two fields) in order to set the amplitudes correctly.
Video screen showing a display with the actual bottom of the screen centered vertically and the right edge roughly centered horizontally.
The initial display after setting the timing correct.
Video screen centered properly.
The properly-centered video display. After adjusting the timing for drift, I used the horizontal and vertical shift controls to move the display so that it was centered.

Final Notes

Here are a couple of other things I learned while I was putting together this flowgraph and this post:

I normally only do one post a month, but I decided to throw this together now in order to have it for reference for my next post on hacktv. Til then!

Here's a Random Fact...