[FFmpeg-devel] [PATCH 8/8] decklink: Add support for compressed AC-3 output over SDI

Aaron Levinson alevinsn_dev at levland.net
Sat Dec 30 10:29:00 EET 2017


On 12/29/2017 10:12 AM, Devin Heitmueller wrote:
> Extend the decklink output to include support for compressed AC-3,
> encapsulated using the SMPTE ST 377:2015 standard.
> 
> This functionality can be exercised by using the "copy" codec when
> the input audio stream is AC-3.  For example:
> 
> ./ffmpeg -i ~/foo.ts -codec:a copy -f decklink 'UltraStudio Mini Monitor'
> 
> Note that the default behavior continues to be to do PCM output,
> which means without specifying the copy codec a stream containing
> AC-3 will be decoded and downmixed to stereo audio before output.
> 
> Signed-off-by: Devin Heitmueller <dheitmueller at ltnglobal.com>
> ---
>   libavdevice/decklink_enc.cpp | 101 ++++++++++++++++++++++++++++++++++++-------
>   1 file changed, 86 insertions(+), 15 deletions(-)
> 
> diff --git a/libavdevice/decklink_enc.cpp b/libavdevice/decklink_enc.cpp
> index 59a4181e19..aca3c13861 100644
> --- a/libavdevice/decklink_enc.cpp
> +++ b/libavdevice/decklink_enc.cpp
> @@ -224,19 +224,32 @@ static int decklink_setup_audio(AVFormatContext *avctx, AVStream *st)
>           av_log(avctx, AV_LOG_ERROR, "Only one audio stream is supported!\n");
>           return -1;
>       }
> -    if (c->sample_rate != 48000) {
> -        av_log(avctx, AV_LOG_ERROR, "Unsupported sample rate!"
> -               " Only 48kHz is supported.\n");
> -        return -1;
> -    }
> -    if (c->channels != 2 && c->channels != 8 && c->channels != 16) {
> -        av_log(avctx, AV_LOG_ERROR, "Unsupported number of channels!"
> -               " Only 2, 8 or 16 channels are supported.\n");
> +
> +    if (st->codecpar->codec_id == AV_CODEC_ID_AC3) {
> +        /* Regardless of the number of channels in the codec, we're only
> +           using 2 SDI audio channels at 48000Hz */
> +        ctx->channels = 2;
> +    } else if (st->codecpar->codec_id == AV_CODEC_ID_PCM_S16LE) {
> +        if (c->channels != 2 && c->channels != 8 && c->channels != 16) {
> +            av_log(avctx, AV_LOG_ERROR, "Unsupported number of channels!"
> +                   " Only 2, 8 or 16 channels are supported.\n");
> +            return -1;
> +        }
> +        if (c->sample_rate != bmdAudioSampleRate48kHz) {
> +            av_log(avctx, AV_LOG_ERROR, "Unsupported sample rate!"
> +                   " Only 48kHz is supported.\n");
> +            return -1;
> +        }
> +        ctx->channels = c->channels;
> +    } else {
> +        av_log(avctx, AV_LOG_ERROR, "Unsupported codec specified!"
> +               " Only PCM_S16LE and AC-3 are supported.\n");
>           return -1;
>       }
> +
>       if (ctx->dlo->EnableAudioOutput(bmdAudioSampleRate48kHz,
>                                       bmdAudioSampleType16bitInteger,
> -                                    c->channels,
> +                                    ctx->channels,
>                                       bmdAudioOutputStreamTimestamped) != S_OK) {
>           av_log(avctx, AV_LOG_ERROR, "Could not enable audio output!\n");
>           return -1;
> @@ -247,8 +260,7 @@ static int decklink_setup_audio(AVFormatContext *avctx, AVStream *st)
>       }
>   
>       /* The device expects the sample rate to be fixed. */
> -    avpriv_set_pts_info(st, 64, 1, c->sample_rate);
> -    ctx->channels = c->channels;
> +    avpriv_set_pts_info(st, 64, 1, bmdAudioSampleRate48kHz);
>   
>       ctx->audio = 1;
>   
> @@ -536,25 +548,84 @@ static int decklink_write_video_packet(AVFormatContext *avctx, AVPacket *pkt)
>       return 0;
>   }
>   
> +static int create_s337_payload(AVPacket *pkt, enum AVCodecID codec_id, uint8_t **outbuf, int *outsize)
> +{
> +    uint8_t *s337_payload;
> +    uint8_t *s337_payload_start;
> +    int i;
> +
> +    /* Encapsulate AC3 syncframe into SMPTE 337 packet */
> +    *outsize = (pkt->size + 4) * sizeof(uint16_t);
> +    s337_payload = (uint8_t *) av_mallocz(*outsize);
> +    if (s337_payload == NULL)
> +        return AVERROR(ENOMEM);
> +
> +    *outbuf = s337_payload;

Should not store s337_payload in outbuf until the end of the function 
and returning success.  If it fails prematurely (say with the call to 
AVERROR(EINVAL)), the caller may rightfully interpret this to mean that 
outbuf has not been filled in and let outbuf leak.  In the case of 
failure, this function should call av_free() on s337_payload.  Also 
technically true for outsize as well--best to only modify output 
parameters when success is guaranteed.

> +
> +    /* Construct SMPTE S337 Burst preamble */
> +    s337_payload[0] = 0x72; /* Sync Word 1 */
> +    s337_payload[1] = 0xf8; /* Sync Word 1 */
> +    s337_payload[2] = 0x1f; /* Sync Word 1 */
> +    s337_payload[3] = 0x4e; /* Sync Word 1 */
> +
> +    if (codec_id == AV_CODEC_ID_AC3) {
> +        s337_payload[4] = 0x01;
> +    } else {
> +        return AVERROR(EINVAL);
> +    }
> +
> +    s337_payload[5] = 0x00;
> +    uint16_t bitcount = pkt->size * 8;
> +    s337_payload[6] = bitcount & 0xff; /* Length code */
> +    s337_payload[7] = bitcount >> 8; /* Length code */
> +    s337_payload_start = &s337_payload[8];
> +    for (i = 0; i < pkt->size; i += 2) {
> +        s337_payload_start[0] = pkt->data[i+1];
> +        s337_payload_start[1] = pkt->data[i];
> +        s337_payload_start += 2;
> +    }
> +
> +    return 0;
> +}
> +
>   static int decklink_write_audio_packet(AVFormatContext *avctx, AVPacket *pkt)
>   {
>       struct decklink_cctx *cctx = (struct decklink_cctx *)avctx->priv_data;
>       struct decklink_ctx *ctx = (struct decklink_ctx *)cctx->ctx;
> -    int sample_count = pkt->size / (ctx->channels << 1);
> +    AVStream *st = avctx->streams[pkt->stream_index];
> +    int sample_count;
>       buffercount_type buffered;
> +    uint8_t *outbuf = NULL;
> +    int ret = 0;
>   
>       ctx->dlo->GetBufferedAudioSampleFrameCount(&buffered);
>       if (pkt->pts > 1 && !buffered)
>           av_log(avctx, AV_LOG_WARNING, "There's no buffered audio."
>                  " Audio will misbehave!\n");
>   
> -    if (ctx->dlo->ScheduleAudioSamples(pkt->data, sample_count, pkt->pts,
> +    if (st->codecpar->codec_id == AV_CODEC_ID_AC3) {
> +        /* Encapsulate AC3 syncframe into SMPTE 337 packet */
> +        ret = create_s337_payload(pkt, st->codecpar->codec_id,
> +                                  &outbuf, &sample_count);
> +        if (ret != 0)

Assuming that you make the change discussed above to 
create_s337_payload(), can change to return ret here in case of failure.

> +            goto done;
> +    } else {
> +        sample_count = pkt->size / (ctx->channels << 1);
> +        outbuf = pkt->data;
> +    }
> +
> +    if (ctx->dlo->ScheduleAudioSamples(outbuf, sample_count, pkt->pts,
>                                          bmdAudioSampleRate48kHz, NULL) != S_OK) {

Simple approach that eliminates the need for using goto and the done 
label:  store the return value of ScheduleAudioSamples() in a local 
variable.  Then, free outbuf if appropriate.  Then handle the 
ScheduleAudioSamples() failure case.  Eliminating the goto will also 
make the code easier to understand, and it will result in the memory 
being deallocated immediately after it is no longer needed.

>           av_log(avctx, AV_LOG_ERROR, "Could not schedule audio samples.\n");
> -        return AVERROR(EIO);
> +        ret = AVERROR(EIO);
> +        goto done;
>       }
>   
> -    return 0;
> +done:
> +    if (st->codecpar->codec_id == AV_CODEC_ID_AC3)
> +        av_free(outbuf);
> +
> +    return ret;
>   }
>   
>   extern "C" {
> 

Aaron Levinson


More information about the ffmpeg-devel mailing list