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

Marton Balint cus at passwd.hu
Tue Mar 14 01:38:48 EET 2023



On Thu, 9 Mar 2023, 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 | 97 ++++++++++++++++++++++++++++++------
> 1 file changed, 82 insertions(+), 15 deletions(-)
>
> diff --git a/libavdevice/decklink_enc.cpp b/libavdevice/decklink_enc.cpp
> index 8d423f6b6e..51a2bb4aad 100644
> --- a/libavdevice/decklink_enc.cpp
> +++ b/libavdevice/decklink_enc.cpp
> @@ -243,19 +243,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->ch_layout.nb_channels != 2 && c->ch_layout.nb_channels != 8 && c->ch_layout.nb_channels != 16) {
> -        av_log(avctx, AV_LOG_ERROR, "Unsupported number of channels!"
> -               " Only 2, 8 or 16 channels are supported.\n");
> +
> +    if (c->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 (c->codec_id == AV_CODEC_ID_PCM_S16LE) {
> +        if (c->sample_rate != 48000) {
> +            av_log(avctx, AV_LOG_ERROR, "Unsupported sample rate!"
> +                   " Only 48kHz is supported.\n");
> +            return -1;
> +        }
> +        if (c->ch_layout.nb_channels != 2 && c->ch_layout.nb_channels != 8 && c->ch_layout.nb_channels != 16) {
> +            av_log(avctx, AV_LOG_ERROR, "Unsupported number of channels!"
> +                   " Only 2, 8 or 16 channels are supported.\n");
> +            return -1;
> +        }
> +        ctx->channels = c->ch_layout.nb_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->ch_layout.nb_channels,
> +                                    ctx->channels,
>                                     bmdAudioOutputStreamTimestamped) != S_OK) {
>         av_log(avctx, AV_LOG_ERROR, "Could not enable audio output!\n");
>         return -1;
> @@ -266,14 +279,49 @@ 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->ch_layout.nb_channels;
> +    avpriv_set_pts_info(st, 64, 1, bmdAudioSampleRate48kHz);

I'd rather not use a BMD constant for non-BMD function. Make this 48000 or 
create a non-BMD 48000 constant and use it here and when checking the 
sample rate earlier.

>
>     ctx->audio = 1;
>
>     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;
> +
> +    if (codec_id != AV_CODEC_ID_AC3)
> +        return AVERROR(EINVAL);

Use the PutByteContext for this function.

> +
> +    /* Encapsulate AC3 syncframe into SMPTE 337 packet */
> +    *outsize = pkt->size + 8;
> +    s337_payload = (uint8_t *) av_mallocz(*outsize);
> +    if (s337_payload == NULL)
> +        return AVERROR(ENOMEM);
> +
> +    /* 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 */
> +    s337_payload[4] = 0x01; /* Burst Info, including data type (1=ac3) */
> +    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;
> +    }
> +
> +    *outbuf = s337_payload;
> +    return 0;
> +}
> +
> av_cold int ff_decklink_write_trailer(AVFormatContext *avctx)
> {
>     struct decklink_cctx *cctx = (struct decklink_cctx *)avctx->priv_data;
> @@ -531,21 +579,40 @@ 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;
>     uint32_t 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 */
> +        int outbuf_size;
> +        ret = create_s337_payload(pkt, st->codecpar->codec_id,
> +                                  &outbuf, &outbuf_size);

Can't you create a buffer on the stack instead of using a dynamic 
memory allocation for each frame? I guess the S337 packet should never 
exceed the uncompressed size, but AC3 might have even more strict frame 
size limits.

> +        if (ret)
> +            return ret;
> +        sample_count = outbuf_size / 4;

How is it ensured that enough raw audio data is provided for Decklink? Or 
we provide "sparse" audio data to the decklink API, and decklink will 
pad the audio with silence based on the packet timestamps?

> +    } else {
> +        sample_count = pkt->size / (ctx->channels << 1);
> +        outbuf = pkt->data;
> +    }
> +
> +    if (ctx->dlo->ScheduleAudioSamples(outbuf, sample_count, pkt->pts,
>                                        bmdAudioSampleRate48kHz, NULL) != S_OK) {
>         av_log(avctx, AV_LOG_ERROR, "Could not schedule audio samples.\n");
> -        return AVERROR(EIO);
> +        ret = AVERROR(EIO);
>     }
>
> -    return 0;
> +    if (st->codecpar->codec_id == AV_CODEC_ID_AC3)
> +        av_freep(&outbuf);
> +
> +    return ret;
> }

Thanks,
Marton


More information about the ffmpeg-devel mailing list