diff mbox series

[FFmpeg-devel,v2,4/4] decklink_enc: add support for SMPTE 2038 VANC packet output

Message ID 1682707254-27604-5-git-send-email-dheitmueller@ltnglobal.com
State New
Headers show
Series Implement SMPTE 2038 output support over Decklink SDI | expand

Checks

Context Check Description
andriy/configure_x86 warning Failed to apply patch

Commit Message

Devin Heitmueller April 28, 2023, 6:40 p.m. UTC
Support decoding and embedding VANC packets delivered via SMPTE 2038
into the SDI output.  We leverage an intermediate queue because
data packets are announced separately from video but we need to embed
the data into the video frame when it is output.

Note that this patch has some additional abstraction for data
streams in general as opposed to just SMPTE 2038 packets.  This is
because subsequent patches will introduce support for other
data codecs.

Signed-off-by: Devin Heitmueller <dheitmueller@ltnglobal.com>
---
 libavdevice/decklink_common.cpp | 16 +++++++
 libavdevice/decklink_common.h   |  4 ++
 libavdevice/decklink_enc.cpp    | 99 ++++++++++++++++++++++++++++++++++++++++-
 libavdevice/decklink_enc_c.c    |  1 +
 4 files changed, 119 insertions(+), 1 deletion(-)

Comments

Devin Heitmueller June 6, 2023, 6:56 p.m. UTC | #1
Hello Marton,

On Fri, Apr 28, 2023 at 1:45 PM Devin Heitmueller
<devin.heitmueller@ltnglobal.com> wrote:
>
> Support decoding and embedding VANC packets delivered via SMPTE 2038
> into the SDI output.  We leverage an intermediate queue because
> data packets are announced separately from video but we need to embed
> the data into the video frame when it is output.
>
> Note that this patch has some additional abstraction for data
> streams in general as opposed to just SMPTE 2038 packets.  This is
> because subsequent patches will introduce support for other
> data codecs.
>
> Signed-off-by: Devin Heitmueller <dheitmueller@ltnglobal.com>

You indicated in patch 0/4 that you had some additional
feedback/comments on this patch series.  Could you please provide
them?  I've got other data formats I'm working on support for (i.e.
SCTE-104) which depend on the common changes in this patch.

Regards

Devin
Devin Heitmueller June 22, 2023, 11:54 a.m. UTC | #2
On Tue, Jun 6, 2023 at 2:56 PM Devin Heitmueller
<devin.heitmueller@ltnglobal.com> wrote:
> You indicated in patch 0/4 that you had some additional
> feedback/comments on this patch series.  Could you please provide
> them?  I've got other data formats I'm working on support for (i.e.
> SCTE-104) which depend on the common changes in this patch.

<ping>
Marton Balint June 26, 2023, 7:07 a.m. UTC | #3
On Thu, 22 Jun 2023, Devin Heitmueller wrote:

> On Tue, Jun 6, 2023 at 2:56 PM Devin Heitmueller
> <devin.heitmueller@ltnglobal.com> wrote:
>> You indicated in patch 0/4 that you had some additional
>> feedback/comments on this patch series.  Could you please provide
>> them?  I've got other data formats I'm working on support for (i.e.
>> SCTE-104) which depend on the common changes in this patch.
>
> <ping>

Sorry for the delay. Could you please rebase the patch on current master? 
Also two comments:

- I don't think you need av_packet_clone() before putting the packet into 
the queue. Yes, audio and video use it, but only because only a single 
pointer can be stored in the decklink frame. But the avpacket queue you 
are using stores whole packet structs, and the muxer is 
allowed to unref the packet it gets in write_packet() as far as I 
know.

- Could you use a more specific name for the queue size? Like 
"vanc_queue_size", not to be confused with audio/video. And please add 
documentation for it in docs/outdev.texi.

Thanks,
Marton
diff mbox series

Patch

diff --git a/libavdevice/decklink_common.cpp b/libavdevice/decklink_common.cpp
index af1b731..427751c 100644
--- a/libavdevice/decklink_common.cpp
+++ b/libavdevice/decklink_common.cpp
@@ -484,6 +484,22 @@  int ff_decklink_packet_queue_get(DecklinkPacketQueue *q, AVPacket *pkt, int bloc
     return ret;
 }
 
+int64_t ff_decklink_packet_queue_peekpts(DecklinkPacketQueue *q)
+{
+    PacketListEntry *pkt1;
+    int64_t pts = -1;
+
+    pthread_mutex_lock(&q->mutex);
+    pkt1 = q->pkt_list.head;
+    if (pkt1) {
+        pts = pkt1->pkt.pts;
+    }
+    pthread_mutex_unlock(&q->mutex);
+
+    return pts;
+}
+
+
 int ff_decklink_list_devices(AVFormatContext *avctx,
                              struct AVDeviceInfoList *device_list,
                              int show_inputs, int show_outputs)
diff --git a/libavdevice/decklink_common.h b/libavdevice/decklink_common.h
index 1cc6d9c..6762607 100644
--- a/libavdevice/decklink_common.h
+++ b/libavdevice/decklink_common.h
@@ -115,6 +115,9 @@  struct decklink_ctx {
 
     AVCCFifo *cc_fifo;      ///< closed captions
 
+    /* Output VANC queue */
+    DecklinkPacketQueue vanc_queue;
+
     /* Streams present */
     int audio;
     int video;
@@ -241,5 +244,6 @@  void ff_decklink_packet_queue_end(DecklinkPacketQueue *q);
 unsigned long long ff_decklink_packet_queue_size(DecklinkPacketQueue *q);
 int ff_decklink_packet_queue_put(DecklinkPacketQueue *q, AVPacket *pkt);
 int ff_decklink_packet_queue_get(DecklinkPacketQueue *q, AVPacket *pkt, int block);
+int64_t ff_decklink_packet_queue_peekpts(DecklinkPacketQueue *q);
 
 #endif /* AVDEVICE_DECKLINK_COMMON_H */
diff --git a/libavdevice/decklink_enc.cpp b/libavdevice/decklink_enc.cpp
index 616e9c7..82e5ed1 100644
--- a/libavdevice/decklink_enc.cpp
+++ b/libavdevice/decklink_enc.cpp
@@ -345,6 +345,25 @@  static int decklink_setup_subtitle(AVFormatContext *avctx, AVStream *st)
     return ret;
 }
 
+static int decklink_setup_data(AVFormatContext *avctx, AVStream *st)
+{
+    int ret = -1;
+
+    switch(st->codecpar->codec_id) {
+#if CONFIG_LIBKLVANC
+    case AV_CODEC_ID_SMPTE_2038:
+        /* No specific setup required */
+        ret = 0;
+        break;
+#endif
+    default:
+        av_log(avctx, AV_LOG_ERROR, "Unsupported data codec specified\n");
+        break;
+    }
+
+    return ret;
+}
+
 av_cold int ff_decklink_write_trailer(AVFormatContext *avctx)
 {
     struct decklink_cctx *cctx = (struct decklink_cctx *)avctx->priv_data;
@@ -370,6 +389,7 @@  av_cold int ff_decklink_write_trailer(AVFormatContext *avctx)
 #if CONFIG_LIBKLVANC
     klvanc_context_destroy(ctx->vanc_ctx);
 #endif
+    ff_decklink_packet_queue_end(&ctx->vanc_queue);
 
     ff_ccfifo_freep(&ctx->cc_fifo);
     av_freep(&cctx->ctx);
@@ -554,6 +574,58 @@  static int decklink_construct_vanc(AVFormatContext *avctx, struct decklink_ctx *
     construct_cc(avctx, ctx, pkt, &vanc_lines);
     construct_afd(avctx, ctx, pkt, &vanc_lines, st);
 
+    /* See if there any pending data packets to process */
+    while (ff_decklink_packet_queue_size(&ctx->vanc_queue) > 0) {
+        AVStream *vanc_st;
+        AVPacket vanc_pkt;
+        int64_t pts;
+
+        pts = ff_decklink_packet_queue_peekpts(&ctx->vanc_queue);
+        if (pts > ctx->last_pts) {
+            /* We haven't gotten to the video frame we are supposed to inject
+               the oldest VANC packet into yet, so leave it on the queue... */
+            break;
+        }
+
+        ret = ff_decklink_packet_queue_get(&ctx->vanc_queue, &vanc_pkt, 1);
+        if (vanc_pkt.pts + 1 < ctx->last_pts) {
+            av_log(avctx, AV_LOG_WARNING, "VANC packet too old, throwing away\n");
+            av_packet_unref(&vanc_pkt);
+            continue;
+        }
+
+        vanc_st = avctx->streams[vanc_pkt.stream_index];
+        if (vanc_st->codecpar->codec_id == AV_CODEC_ID_SMPTE_2038) {
+            struct klvanc_smpte2038_anc_data_packet_s *pkt_2038 = 0;
+
+            klvanc_smpte2038_parse_pes_payload(vanc_pkt.data, vanc_pkt.size, &pkt_2038);
+            if (pkt_2038 == NULL) {
+                av_log(avctx, AV_LOG_ERROR, "failed to decode SMPTE 2038 PES packet");
+                av_packet_unref(&vanc_pkt);
+                continue;
+            }
+            for (int i = 0; i < pkt_2038->lineCount; i++) {
+                struct klvanc_smpte2038_anc_data_line_s *l = &pkt_2038->lines[i];
+                uint16_t *vancWords = NULL;
+                uint16_t vancWordCount;
+
+                if (klvanc_smpte2038_convert_line_to_words(l, &vancWords,
+                                                           &vancWordCount) < 0)
+                    break;
+
+                ret = klvanc_line_insert(ctx->vanc_ctx, &vanc_lines, vancWords,
+                                         vancWordCount, l->line_number, 0);
+                free(vancWords);
+                if (ret != 0) {
+                    av_log(avctx, AV_LOG_ERROR, "VANC line insertion failed\n");
+                    break;
+                }
+            }
+            klvanc_smpte2038_anc_data_packet_free(pkt_2038);
+        }
+        av_packet_unref(&vanc_pkt);
+    }
+
     IDeckLinkVideoFrameAncillary *vanc;
     int result = ctx->dlo->CreateAncillaryData(bmdFormat10BitYUV, &vanc);
     if (result != S_OK) {
@@ -752,6 +824,23 @@  static int decklink_write_subtitle_packet(AVFormatContext *avctx, AVPacket *pkt)
     return 0;
 }
 
+static int decklink_write_data_packet(AVFormatContext *avctx, AVPacket *pkt)
+{
+    struct decklink_cctx *cctx = (struct decklink_cctx *)avctx->priv_data;
+    struct decklink_ctx *ctx = (struct decklink_ctx *)cctx->ctx;
+
+    AVPacket *avpacket = av_packet_clone(pkt);
+    if (!avpacket) {
+        av_log(avctx, AV_LOG_ERROR, "Could not clone data packet.\n");
+        return AVERROR(EIO);
+    }
+    if (ff_decklink_packet_queue_put(&ctx->vanc_queue, avpacket) < 0) {
+        av_log(avctx, AV_LOG_WARNING, "Failed to queue DATA packet\n");
+    }
+
+    return 0;
+}
+
 extern "C" {
 
 av_cold int ff_decklink_write_header(AVFormatContext *avctx)
@@ -817,6 +906,9 @@  av_cold int ff_decklink_write_header(AVFormatContext *avctx)
         } else if (c->codec_type == AVMEDIA_TYPE_VIDEO) {
             if (decklink_setup_video(avctx, st))
                 goto error;
+        } else if (c->codec_type == AVMEDIA_TYPE_DATA) {
+            if (decklink_setup_data(avctx, st))
+                goto error;
         } else if (c->codec_type == AVMEDIA_TYPE_SUBTITLE) {
             if (decklink_setup_subtitle(avctx, st))
                 goto error;
@@ -826,13 +918,16 @@  av_cold int ff_decklink_write_header(AVFormatContext *avctx)
         }
     }
 
+    /* Reconfigure the data/subtitle stream clocks to match the video */
     for (n = 0; n < avctx->nb_streams; n++) {
         AVStream *st = avctx->streams[n];
         AVCodecParameters *c = st->codecpar;
 
-        if(c->codec_type == AVMEDIA_TYPE_SUBTITLE)
+        if(c->codec_type == AVMEDIA_TYPE_DATA ||
+           c->codec_type == AVMEDIA_TYPE_SUBTITLE)
             avpriv_set_pts_info(st, 64, ctx->bmd_tb_num, ctx->bmd_tb_den);
     }
+    ff_decklink_packet_queue_init(avctx, &ctx->vanc_queue);
 
     frame_rate = av_make_q(ctx->bmd_tb_den, ctx->bmd_tb_num);
     if (!(ctx->cc_fifo = ff_ccfifo_alloc(&frame_rate, ctx)))
@@ -853,6 +948,8 @@  int ff_decklink_write_packet(AVFormatContext *avctx, AVPacket *pkt)
         return decklink_write_video_packet(avctx, pkt);
     else if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
         return decklink_write_audio_packet(avctx, pkt);
+    else if (st->codecpar->codec_type == AVMEDIA_TYPE_DATA)
+        return decklink_write_data_packet(avctx, pkt);
     else if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE)
         return decklink_write_subtitle_packet(avctx, pkt);
 
diff --git a/libavdevice/decklink_enc_c.c b/libavdevice/decklink_enc_c.c
index 0a3984b..d593cca 100644
--- a/libavdevice/decklink_enc_c.c
+++ b/libavdevice/decklink_enc_c.c
@@ -32,6 +32,7 @@  static const AVOption options[] = {
     { "list_devices", "use ffmpeg -sinks decklink instead", OFFSET(list_devices), AV_OPT_TYPE_BOOL, { .i64 = 0   }, 0, 1, ENC | AV_OPT_FLAG_DEPRECATED},
     { "list_formats", "list supported formats"  , OFFSET(list_formats), AV_OPT_TYPE_INT   , { .i64 = 0   }, 0, 1, ENC },
     { "preroll"     , "video preroll in seconds", OFFSET(preroll     ), AV_OPT_TYPE_DOUBLE, { .dbl = 0.5 }, 0, 5, ENC },
+    { "queue_size",   "output queue buffer size", OFFSET(queue_size  ), AV_OPT_TYPE_INT64, { .i64 = (1024 * 1024)}, 0, INT64_MAX, ENC },
 #if BLACKMAGIC_DECKLINK_API_VERSION >= 0x0b000000
     { "duplex_mode" , "duplex mode"             , OFFSET(duplex_mode ), AV_OPT_TYPE_INT   , { .i64 = 0   }, 0, 5, ENC, "duplex_mode"},
 #else