Using ffmpeg to generate a transport stream – more details and how to add text overlays

Generating a valid transport stream for DATV Express without one of the listed PVR type of capture/encoder cards is well within the capabilities of modern computers so why are we reliant upon hardware encoders?

I first became aware of this hardware encoder approach with the launch of Digilite a few years ago. At that time software encoding in real time was very challenging so using a hardware encoder was a sensible way to go. More recently the availability of suitable encoder cards has become limited as the PVR market has moved on to embrace H264 and other hardware codecs. Cards do come up on popular auction sites however they can be other than advertised (as I found) and are not guaranteed to be in full working order. Further there is limited value investing in a digital television transmission system if you’re going to feed it with an analogue signal in the first place!

A solution was sought and experimentation done with tools like VLC, ffmpeg and avconv. In the event I found that ffmpeg provided the best early results so have developed a solution to the point where I can reliably generate a valid transport stream for DVB-S mode using Charles G4GUO DATV Express Server.

VLC appears perfectly capable of generating a suitable transport stream however I found it very difficult to locate specific documentation for things like setting PIDs and meta data and also experienced stability problems with it silently crashing mid-stream – not an ideal situation in the middle of a QSO!

I also found that if you’re using ffmpeg to generate a transport stream for DATV Express you can very simply add text and simple graphics overlays to the video feed using filters. This is important for DATV enthusiasts as we are required to give our call sign at intervals and what could be more convenient than a text overlay on screen. Of course if we move to a purely digital format we cannot add the overlay using a conventional overlay processor as the analogue signal is never available. I’ve tagged on a section at the end of this discussion detailing how to get started doing this.

Anatomy of the ffmpeg command line:

Take my earlier post “DATV Express with vMix using ffmpeg to generate the Transport Stream” This included a batch file to run on the Windows machine which launches ffmpeg to do the video and audio compression and assemble the transport stream.

The last line of the batch file:

start "Video feed to DATV Express" /high c:\ffmpeg\bin\ffmpeg -f dshow -i video="vMix Video" -f dshow -i audio="vMix Audio" -f mpeg2video -pix_fmt yuv420p -r 25 -s 720x576 -aspect 4:3 -qmin 2 -qmax 35 -b:v %VIDRATE%k -minrate %VIDRATE%k -maxrate %VIDRATE%k -bufsize %BUFSIZE%k -acodec mp2 -ab 128k -ac 2 -f mpegts -mpegts_original_network_id 1 -mpegts_transport_stream_id 1 -mpegts_service_id 1 -mpegts_pmt_start_pid 4096 -streamid 0:289 -streamid 1:337 -metadata service_provider="MYCALL" -metadata service_name="My Station ID" -y udp://

…looks pretty horrendous doesn’t it. Let’s break it down a bit!

start "Video feed to DATV Express" /high c:\ffmpeg\bin\ffmpeg

We could simply run ffmpeg but using Start makes the window prettier and allows us to set the process priority.

Type start /? into a Windows command prompt and it will tell you “Starts a separate window to run a specified program or command.”

“Video feed to DATV Express” becomes the window title

/high specifies that the program should run at high priority – we need this to ensure ffmpeg keeps the data flowing while Windows runs vMix in the foreground and checks for updates etc. etc. etc. in the background!

The rest of the line is the ffmpeg command. In a nutshell it tells ffmpeg to receive two data streams – one video, one audio, how to encode each of those streams, and what wrapper to put them in – the Transport Stream.

-f dshow -i video="vMix Video"

Tells ffmpeg to receive a video feed in DirectShow format  from the DirectShow device called “vMix Video”

-f dshow -i audio="vMix Audio"

Tells ffmpeg to receive a second feed – audio this time – from the DirectShow input called “vMix Audio”

-f mpeg2video -pix_fmt yuv420p -r 25 -s 720x576 -aspect 4:3 -qmin 2 -qmax 35 -b:v %VIDRATE%k -minrate %VIDRATE%k -maxrate %VIDRATE%k -bufsize %BUFSIZE%k

This lot specifies the video encoder to use – mpeg2video and various controlling parameters

-pix_fmt yuv420p specifies the pixel format otherwise ffmpeg will use yuv422p which won’t work!
 -r 25 frame rate 25 fps
 -s 720×576 frame size 720 wide by 576 high
 -aspect 4:3 aspect ratio 4:3
 -qmin minimum q value to use – higher values make the picture more blocky
 -qmax maximum q value to use – lower values keep the picture quality up at the expense of possibly running out of bandwidth causing total picture breakdown
-b:v %VIDRATE%k the nominal bitrate for the video stream. %VIDRATE% is substituted in the script for a calculated value depending on symbol rate and forward error correction selected. The trailing k just specifies * 1000 as we calculate it in kbps
-minrate %VIDRATE%k the minimum bitrate – set the same as nominal to give constant bitrate
-maxrate %VIDRATE%k the maximum bitrate – set the same as nominal to give constant bitrate
-bufsize %BUFSIZE%k buffer size to use for the encoder. If this is too big ffmpeg uses variable bitrate regardless of the min/maxrate settings so I choose to use 70% of the bitrate value.

There seems to be little documentation out there on exactly how these parameters should be set but these seem to give reasonable results.

-acodec mp2 -ab 128k -ac 2

This specifies the audio codec to use – mp2, the audio bitrate 128kbps and two channels.

-f mpegts -mpegts_original_network_id 1 -mpegts_transport_stream_id 1 -mpegts_service_id 1 -mpegts_pmt_start_pid 4096 -streamid 0:289 -streamid 1:337 -metadata service_provider="MYCALL" -metadata service_name="My Station ID"

This is the wrapper – we could probably get away with just the -f mpegts but the remaining parameters give us complete control over the various IDs and metadata to be carried in the stream.

 -mpegts_original_network_id 1
-mpegts_transport_stream_id 1
-mpegts_service_id 1
 -mpegts_pmt_start_pid 4096
 -streamid 0:289
 -streamid 1:337
 -metadata service_provider=”MYCALL”
 -metadata service_name=”My Station ID”

You’ll want to change these to suit your station. The stream ID’s for the two streams are important. I have found that matching the video stream ID is necessary to ensure the receiver sees the channel, matching the audio stream ID reduces the chance of the receiver muting the audio if two stations change over a bit quickly on a repeater input.


Just tells ffmpeg that I want to answer yes to any questions it might want to ask me – this is handy in a batch file but not strictly necessary in an interactive situation.


This specifies where we want to send the transport stream – in this case across the network via UDP with a specific packet size – which by the way is 7 * 188 but could have been any multiple of 188 up to 255 * 188 from what Charles G4GUO tells me.

Documentation on these and many other parameters are available in the ffmpeg / avconv man pages – type “man ffmpeg” at a linux command prompt or search online for “ffmpeg man”.

Text overlays

It’s a condition of the amateur licence that you give your call sign at start, end and periodically during transmission. ATV operators usually have their call sign on display either somewhere adjacent to them in their operating position or as a text overlay added to the video signal.

In your digital ATV station you will probably have a digital video camera, feeding your computer or transmitter via firwire, SDI or HDMI. There is no analogue video available to which a text overlay can be added.

Without an analogue video signal this has to be achieved by other means. Using software like vMix is one solution. This provides a very easy way to composite several layers into one picture including text overlays, graphics and even virtual sets. It is however, frustrating to have to devote an entire input to produce a simple call sign overlay!

Ffmpeg allows each of the streams to be processed through one or more “Filters” before encoding and packaging them into the final transport stream. This is achieved through the use of the -vf option.

One or more filters with their parameters are passed to the filter processor in a single quoted string:

... -vf "filter1=param1=value1:param2='stringvalue2', filter2=param3=value3" ...

string values need to be enclosed in single quotes: ‘stringvalue’
each parameter is separated by colon characters: :

So how to add a call sign? Here’s the line that I use:

-vf "drawtext=fontfile='C\:\\Windows\\Fonts\\verdanab.ttf':text='MW0LLK':x=60:y=34:fontsize=20:fontcolor=0xffffff7f"

Notice that we have to specify the font file to use. This example is for use on a Windows system – on Linux it will be something like:

-vf "drawtext=fontfile='/usr/share/fonts/truetype/freefont/FreeSans.ttf':text='MW0LLK':x=60:y=34:fontsize=20:fontcolor=0xffffff7f"

The key difference being the font path and file name – you can choose whatever font you want although there may be limitations of which I am unaware.

The remaining examples will use the Windows format – mainly because that is the ffmpeg command that I have to hand as I write. All the other parameters used work unchanged on both systems.

The position and size parameters are all in pixels. These will need to be changed to suit the particular screen size and aspect ratio in use. I positioned the text in a little from top and left to allow for overscan on television receivers. It also looks better not crammed into the corner!

The fontcolor parameter will accept colour names e.g. green, white etc. or hexadecimal colour values as either six digit 0xrrggbb or eight digit 0xrrggbbaa where rrggbb specify the rgb values and aa specifies the alpha or transparency.

Values for r, g and b range from 00 to ff where 00 is black and ff is full on.

Alpha values range from 00 (totally transparent) to ff (opaque)

Drop Shadows

To make the text visible even against a similar coloured background I chose to add a drop shadow. Alternatives could include a solid background colour box, or placing the text twice at different sizes and colours with the smaller size on top of the larger so it presents a coloured outline to the small text.

Here’s the version with the drop shadow added:

-vf "drawtext=fontfile='C\:\\Windows\\Fonts\\verdanab.ttf':text='MW0LLK':x=60:y=34:fontsize=20:fontcolor=0xffffff7f:shadowcolor=0x003f007f:shadowx=2:shadowy=2"

For the drop shadow you need to specify the shadow colour and an x and y offset.

Finally here’s the actual filter specification that I currently use when working through our local ATV repeater:

-vf "drawtext=fontfile='C\:\\Windows\\Fonts\\verdanab.ttf':text='MW0LLK':x=60:y=34:fontsize=20:fontcolor=0xffffff7f:shadowcolor=0x003f007f:shadowx=2:shadowy=2, drawtext=fontfile='C\:\\Windows\\Fonts\\verdanab.ttf':text='70cm DVB-S':x=60:y=52:fontsize=12:fontcolor=0xffffff9f:shadowcolor=0x00007f9f:shadowx=2:shadowy=2, drawtext=fontfile='C\:\\Windows\\Fonts\\verdanab.ttf':text='via GB3TM':x=60:y=65:fontsize=12:fontcolor=0xffffff9f:shadowcolor=0x00007f9f:shadowx=2:shadowy=2"

This is added to the middle of the ffmpeg command after the inputs have been fully specified and before the encoder specifications thus:

start "Video feed to DATV Express" /high c:\ffmpeg\bin\ffmpeg -f dshow -i video="vMix Video" -f dshow -i audio="vMix Audio" -vf "drawtext=fontfile='C\:\\Windows\\Fonts\\verdanab.ttf':text='MW0LLK':x=60:y=34:fontsize=20:fontcolor=0xffffff7f:shadowcolor=0x003f007f:shadowx=2:shadowy=2, drawtext=fontfile='C\:\\Windows\\Fonts\\verdanab.ttf':text='70cm DVB-S':x=60:y=52:fontsize=12:fontcolor=0xffffff9f:shadowcolor=0x00007f9f:shadowx=2:shadowy=2, drawtext=fontfile='C\:\\Windows\\Fonts\\verdanab.ttf':text='via GB3TM':x=60:y=65:fontsize=12:fontcolor=0xffffff9f:shadowcolor=0x00007f9f:shadowx=2:shadowy=2" -f mpeg2video -pix_fmt yuv420p -r 25 -s 720x576  -aspect 4:3 -qmin 2 -qmax 35 -b:v %VIDRATE%k -minrate %VIDRATE%k -maxrate %VIDRATE%k -bufsize %BUFSIZE%k -acodec mp2 -ab 128k -ac 2 -f mpegts -mpegts_original_network_id 1 -mpegts_transport_stream_id 1 -mpegts_service_id 1 -mpegts_pmt_start_pid 4096 -streamid 0:289 -streamid 1:337 -metadata service_provider="MW0LLK" -metadata service_name="MW0LLK-Express" -y udp://

You can replace the command in the batch file with this one – suitably modified to suit your call sign etc. stream IDs, meta data and output specification. Alternatively grab everything from -vf to the closing double quote at the end of the filters and paste it into the command in your batch file.

I have to add a disclaimer – all of the above is based upon much reading of the limited documentation available for ffmpeg / avconv and experimentation. I don’t have access to a proper stream analyser or Tutione software so there may well be errors in the stream that I am unaware of! The streams generated with the above commands do work with my satellite receiver and the one on GB3TM so they must be reasonably ok.

I hope this proves useful to somebody. I’ve broken down the ffmpeg command used to generate the TS for DATV Express and detailed how to add text overlays. Next time…. well I’ll have to think what to cover next. I am currently playing with Raspberry Pi V2 and DATV Express. It would be nice to box up the DATV Express with either a Raspberry Pi or Odroid as a stand-alone transmitter with web interface for settings and control.

6 thoughts on “Using ffmpeg to generate a transport stream – more details and how to add text overlays”

  1. Hi Chris, thank you for the FFMPEG information. I recently found FFMPEG as a part of VBTPlayer that has the configuration switches preset in a text file and works really well. Figuring out all the required switches could be a real headache! I printed the ffmpeg full and found it is 35 pages using 10 point font!

    Question: Various videos being used to create a transport stream file seem to have random PID’s. Is there any program you know of that will modify (transcode) the PID’s so it matches the receiver requirement?

  2. Sorry – I thought I replied to this already but it’s not there now!
    ffmpeg will do what you want. Assuming you don’t want to change the streams just use -vcodec copy -acodec copy and tweak the PIDs in the mpegts section: where I have -streamid 0:289 -streamid 1:337 just set your desired ids.
    ffmpeg documentation gives more details on what else can be done with meta data and various ids in the transport stream.

  3. Can these scripts be modified to work on H264/265?
    I think Rob M0DTS has had some success on 264.
    Looking forward to your talk at CAT15.
    73. Ian.

Leave a Reply

Your email address will not be published.