Debugging DVDStyler - A maintainer's point of view

DVD StylerAs the FreeBSD maintainer for DVDStyler, it's my privilege to ensure it runs successfully on my favourite OS. Although stability is important, so too is moving forward with new features and testing beta releases is part of that process.

A new release was available, and it compiled successfully. Only when it ran, using a particular feature, did a fatal bug surface. A simple SIGSEGV was easy to pinpoint, but why? My ffmpeg foo needed upgrading because the code looked good, yet the error made no sense. Links to the code, documentation and tools are listed below.

FFmpeg is a free and open-source software project consisting of a suite of libraries and programs for handling video, audio, and other multimedia files and streams. It is very stable, has excellent documentation, used in many software projects, and has been used by Perseverance rover on Mars.

What is the code trying to do?

The process is creating / copying a stream: create an av context, set it to stereo, then initialise it.

The process in a bit more detail is straightforward:

A new empty stream is created with avformat_new_stream().

avcodec_alloc_context3() creates an AVCodecContext*.

Set a few things such as bit rate, sample rate, format, number of channels, and then set the channel layout to stereo.

Use avcodec_parameters_from_context() to copy parameters to the new stream, open the encoder with avcodec_open2(), allocate an audio frame with av_frame_alloc(), set a few things (nb_samples, format and channel_layout), allocate memory with av_frame_get_buffer(), set it to be writeable with av_frame_make_writable(), then finally use memset() to copy data.

Would you like to follow the code? It's in the following function in mediaenc_ffmpeg.cpp, see dvdstyler link above.

bool wxFfmpegMediaEncoder::addAudioStream(int codecId) {

The lldb debugger is straightforward to use and has good documentation including a page for those familiar with gdb.

The Bug

* thread #1, name = 'dvdstyler', stop reason = signal SIGSEGV: invalid address (fault address: 0x0)

Fortunately DVDStyler is well engineered and ffmpeg is very well documented.

The simple yet sophisticated ffmpeg search box takes you to where you need to go. Clearly laid out, with examples to copy and paste. Eat your heart out ChatGPT.

Setting channels to 1 failed to open the encoder, worth a try as the second data channel was empty. DVDStyler is well written with each function call return tested making it easy and quick to evaluate.

Omitting to set channels had no effect as setting channel_layout to  AV_CH_LAYOUT_STEREO set it to two anyway.

If there are two channels but only the first data[] is initialised, then the only option left is to test for data during the loop using memset to copy data. Here is the patch adding two extra lines:

The Patch

     for (int i = 0; i < c->channels; i++) {
         uint16_t *samples = (uint16_t*)m_audioFrame->data[i];
+        if (samples) {
             memset(samples, 0, c->frame_size * av_get_bytes_per_sample(c->sample_fmt));
+        }
     }

Did it work? YES!

Maintainer hat on

Did it work properly?

As a maintainer, it's also my responsibility to ensure an app works as expected. Clang does an excellent job of identifying issues during the build process and there were no new issues with the proposed patch. C++ has the tools to evaluate how code runs and valgrind does an excellent job of identifying runtime issues. Thankfully there were no issues relating to the patch or code paths I tested. The patch is ready for submission upstream.

Why FreeBSD?

Built from solid engineering principles, Poudriere on FreeBSD is the predictable and easy to configure and use build system to reliably and repeatedly build debug and production builds with fully staged and tested packages with complete build log archive. It uses BSD Jails to construct a clean environment for each build.

The FreeBSD ports system makes building and testing ports straightforward, simply add a couple of lines to the FreeBSD port Makefile and it adds appropriate flags to build an app with debug symbols for debugging. -g for debug, -O0 for no optimisation, Jason Turner's C++ Best Practices recommends -Wall and -Wextra. There's always more that can be done, but this is an exercise to fix a bug, not rewrite dvdstyler.

WITH_DEBUG=     yes
DEBUG_FLAGS=    -g -O0 -Wall -Wextra

This predictable and easy to configure build makes it easy to use lldb for debugging. At the lldb prompt, use the following commands to start debugging:

Set the program to be debugged

file /usr/local/bin/dvdstyler

Let lldb know where to find your source code. All poudriere builds start from /wrkdirs/usr/ports/ with a category, in this case multimedia, followed by the port dvdstyler, then the path to the code.

settings set target.source-map "/wrkdirs/usr/ports/multimedia/dvdstyler/work/DVDStyler-3.3b3/src" "/path/to/your/src/"

After letting lldb know where to find source files, set a breakpoint to stop at an appropriate part of your code, in this case where AVCodecContext is initialised.

breakpoint set --file mediaenc_ffmpeg.cpp --line 295

Start the application

process launch

Then when the breakpoint is reached, use step to step through the code, and frame variable to inspect variables.

Repeat until you have found and fixed the bug.

Summary

Patch successfully tested and submitted upstream.

Get involved and adopt a port today

If you feel you could do something similar, there are many projects looking for maintainers just waiting for you to say hello. Or you could start your own. There's plenty of help including people, documentation and online groups, see links above.