[FFmpeg-devel] [PATCH 1/7] avcodec: add s337m parser and decoder

ffnicolasg at sfr.fr ffnicolasg at sfr.fr
Wed Dec 4 16:14:03 EET 2024


From: Nicolas Gaullier <nicolas.gaullier at cji.paris>

Signed-off-by: Nicolas Gaullier <nicolas.gaullier at cji.paris>
---
 configure                                |   2 +
 libavcodec/Makefile                      |   4 +
 libavcodec/allcodecs.c                   |   2 +
 libavcodec/codec_desc.c                  |  14 +
 libavcodec/codec_id.h                    |   2 +
 libavcodec/dolby_e_parse.c               |   3 +
 libavcodec/parsers.c                     |   2 +
 libavcodec/s337m.c                       | 327 +++++++++++++++++++++++
 libavcodec/s337m_parser.c                | 133 +++++++++
 libavcodec/spdif_s337m_parse.c           | 142 ++++++++++
 libavcodec/spdif_s337m_parser_internal.h |  92 +++++++
 libavcodec/utils.c                       |   2 +
 libavcodec/version.c                     |   2 +-
 13 files changed, 726 insertions(+), 1 deletion(-)
 create mode 100644 libavcodec/s337m.c
 create mode 100644 libavcodec/s337m_parser.c
 create mode 100644 libavcodec/spdif_s337m_parse.c
 create mode 100644 libavcodec/spdif_s337m_parser_internal.h

diff --git a/configure b/configure
index d7b7b49f92..eb4eea97a9 100755
--- a/configure
+++ b/configure
@@ -3072,6 +3072,8 @@ rv20_encoder_select="h263_encoder"
 rv30_decoder_select="golomb h264pred h264qpel mpegvideodec rv34dsp"
 rv40_decoder_select="golomb h264pred h264qpel mpegvideodec rv34dsp"
 rv60_decoder_select="videodsp golomb"
+s337m_16_decoder_select="dolby_e_decoder"
+s337m_24_decoder_select="dolby_e_decoder"
 screenpresso_decoder_deps="zlib"
 shorten_decoder_select="bswapdsp"
 sipr_decoder_select="lsp"
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index a6e0e0b55e..deff288312 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -672,6 +672,8 @@ OBJS-$(CONFIG_RV60_DECODER)            += rv60dec.o rv60dsp.o
 OBJS-$(CONFIG_SAMI_DECODER)            += samidec.o ass.o htmlsubtitles.o
 OBJS-$(CONFIG_S302M_DECODER)           += s302m.o
 OBJS-$(CONFIG_S302M_ENCODER)           += s302menc.o
+OBJS-$(CONFIG_S337M_16_DECODER)        += spdif_s337m_parse.o s337m.o
+OBJS-$(CONFIG_S337M_24_DECODER)        += spdif_s337m_parse.o s337m.o
 OBJS-$(CONFIG_SANM_DECODER)            += sanm.o
 OBJS-$(CONFIG_SCPR_DECODER)            += scpr.o
 OBJS-$(CONFIG_SCREENPRESSO_DECODER)    += screenpresso.o
@@ -1233,6 +1235,8 @@ OBJS-$(CONFIG_PNG_PARSER)              += png_parser.o
 OBJS-$(CONFIG_PNM_PARSER)              += pnm_parser.o pnm.o
 OBJS-$(CONFIG_QOI_PARSER)              += qoi_parser.o
 OBJS-$(CONFIG_RV34_PARSER)             += rv34_parser.o
+OBJS-$(CONFIG_S337M_16_PARSER)         += spdif_s337m_parse.o s337m_parser.o
+OBJS-$(CONFIG_S337M_24_PARSER)         += spdif_s337m_parse.o s337m_parser.o
 OBJS-$(CONFIG_SBC_PARSER)              += sbc_parser.o
 OBJS-$(CONFIG_SIPR_PARSER)             += sipr_parser.o
 OBJS-$(CONFIG_TAK_PARSER)              += tak_parser.o tak.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 0b559dfc58..04e294734d 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -522,6 +522,8 @@ extern const FFCodec ff_ra_144_encoder;
 extern const FFCodec ff_ra_144_decoder;
 extern const FFCodec ff_ra_288_decoder;
 extern const FFCodec ff_ralf_decoder;
+extern const FFCodec ff_s337m_16_decoder;
+extern const FFCodec ff_s337m_24_decoder;
 extern const FFCodec ff_sbc_encoder;
 extern const FFCodec ff_sbc_decoder;
 extern const FFCodec ff_shorten_decoder;
diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index bc9163bf98..927d19a8f9 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -3451,6 +3451,20 @@ static const AVCodecDescriptor codec_descriptors[] = {
         .long_name = NULL_IF_CONFIG_SMALL("LC3 (Low Complexity Communication Codec)"),
         .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
     },
+    {
+        .id        = AV_CODEC_ID_S337M_16,
+        .type      = AVMEDIA_TYPE_AUDIO,
+        .name      = "s337m_16",
+        .long_name = NULL_IF_CONFIG_SMALL("S337M within 16-bit pcm"),
+        .props     = AV_CODEC_PROP_LOSSY,
+    },
+    {
+        .id        = AV_CODEC_ID_S337M_24,
+        .type      = AVMEDIA_TYPE_AUDIO,
+        .name      = "s337m_24",
+        .long_name = NULL_IF_CONFIG_SMALL("S337M within 24-bit pcm"),
+        .props     = AV_CODEC_PROP_LOSSY,
+    },
 
     /* subtitle codecs */
     {
diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h
index 6bfaa02601..60cb33eec2 100644
--- a/libavcodec/codec_id.h
+++ b/libavcodec/codec_id.h
@@ -550,6 +550,8 @@ enum AVCodecID {
     AV_CODEC_ID_OSQ,
     AV_CODEC_ID_QOA,
     AV_CODEC_ID_LC3,
+    AV_CODEC_ID_S337M_16,
+    AV_CODEC_ID_S337M_24,
 
     /* subtitle codecs */
     AV_CODEC_ID_FIRST_SUBTITLE = 0x17000,          ///< A dummy ID pointing at the start of subtitle codecs.
diff --git a/libavcodec/dolby_e_parse.c b/libavcodec/dolby_e_parse.c
index ffedcd99a4..3f6e1abc02 100644
--- a/libavcodec/dolby_e_parse.c
+++ b/libavcodec/dolby_e_parse.c
@@ -30,6 +30,9 @@ static const uint8_t nb_channels_tab[MAX_PROG_CONF + 1] = {
     8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 6, 6, 6, 6, 6, 6, 6, 4, 4, 4, 4, 8, 8
 };
 
+/* 42965 and 53706 are approximate values (rounding down):
+ * accurate video sync require muxing into s337m/aes
+ */
 static const uint16_t sample_rate_tab[16] = {
     0, 42965, 43008, 44800, 53706, 53760
 };
diff --git a/libavcodec/parsers.c b/libavcodec/parsers.c
index 8bfd2dbce0..5d78cc7969 100644
--- a/libavcodec/parsers.c
+++ b/libavcodec/parsers.c
@@ -70,6 +70,8 @@ extern const AVCodecParser ff_qoi_parser;
 extern const AVCodecParser ff_rv34_parser;
 extern const AVCodecParser ff_sbc_parser;
 extern const AVCodecParser ff_sipr_parser;
+extern const AVCodecParser ff_s337m_16_parser;
+extern const AVCodecParser ff_s337m_24_parser;
 extern const AVCodecParser ff_tak_parser;
 extern const AVCodecParser ff_vc1_parser;
 extern const AVCodecParser ff_vorbis_parser;
diff --git a/libavcodec/s337m.c b/libavcodec/s337m.c
new file mode 100644
index 0000000000..d5ac28b118
--- /dev/null
+++ b/libavcodec/s337m.c
@@ -0,0 +1,327 @@
+/*
+ * S337M decoder
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libavutil/avassert.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/opt.h"
+#include "libswresample/swresample.h"
+
+#include "avcodec.h"
+#include "codec_internal.h"
+#include "decode.h"
+#include "spdif_s337m_parser_internal.h"
+
+typedef struct S337MDecodeContext {
+    const AVClass   *class;
+    AVCodecContext  *avctx;
+    SPDIFS337MContext dectx;
+
+    int             passthrough;
+
+    int             inited;
+    int             flushed;
+
+    int             aes_start_position;
+    AVCodecContext  *codec_avctx;
+    AVFrame         *codec_frame;
+    int             codec_initial_sample_rate;
+    SwrContext      *swr;
+    int64_t         next_pts;
+    int             prev_aes_samples;
+} S337MDecodeContext;
+
+static av_cold int s337m_init(AVCodecContext *avctx)
+{
+    S337MDecodeContext *s = avctx->priv_data;
+    SPDIFS337MContext *dectx = &s->dectx;
+
+    dectx->avctx = s->avctx = avctx;
+
+    return 0;
+}
+
+static int set_codec(S337MDecodeContext *s)
+{
+    SPDIFS337MContext *dectx = &s->dectx;
+    const AVCodec *codec;
+    int ret;
+
+    if (s->codec_avctx) {
+        if (s->codec_avctx->codec_id != dectx->codec)
+            return AVERROR_INPUT_CHANGED;
+        return 0;
+    }
+
+    codec = avcodec_find_decoder(dectx->codec);
+    if (!codec)
+        return AVERROR_BUG;
+
+    s->codec_avctx = avcodec_alloc_context3(codec);
+    if (!s->codec_avctx)
+        return AVERROR(ENOMEM);
+
+    ret = avcodec_open2(s->codec_avctx, codec, NULL);
+    if (ret < 0)
+        return ret;
+
+    s->codec_frame = av_frame_alloc();
+    if (!s->codec_frame)
+        return AVERROR(ENOMEM);
+
+    return 0;
+}
+
+static int init_resample(AVCodecContext *avctx, SwrContext **swrp, int in_sample_rate)
+{
+    SwrContext *swr = *swrp = swr_alloc();
+    int ret;
+
+    if (!swr)
+        return AVERROR(ENOMEM);
+
+    av_opt_set_chlayout(swr, "in_chlayout",  &avctx->ch_layout, 0);
+    av_opt_set_chlayout(swr, "out_chlayout", &avctx->ch_layout, 0);
+    av_opt_set_int(swr, "in_sample_fmt",     avctx->sample_fmt, 0);
+    av_opt_set_int(swr, "out_sample_fmt",    avctx->sample_fmt, 0);
+    av_opt_set_int(swr, "in_sample_rate",    in_sample_rate, 0);
+    av_opt_set_int(swr, "out_sample_rate",   avctx->sample_rate, 0);
+
+    /* There are two main cases that require sync to timestamps:
+     * - dolby_e sample rate value is not accurate for drop frame video:
+     * use soft comp responsively to handle these regular single-sample drifts
+     * - in case of loss of s337m sync:
+     * use hard comp to insert whole frames of silence
+     *
+     * And one use case in between that requires NO sync:
+     * The guardband phase has to be silently ignored as the video
+     * frame is assumed to be synced with sample 0 of the aes stream.
+     */
+    av_opt_set_int(swr,    "async",          1,        0);
+    av_opt_set_double(swr, "min_comp",       1./48000, 0);
+    av_opt_set_double(swr, "max_soft_comp",  0.0001,   0);
+    av_opt_set_double(swr, "min_hard_comp",  0.02,     0);
+
+    ret = swr_init(swr);
+    if (ret < 0)
+        return ret;
+
+    return 0;
+}
+
+extern const FFCodec ff_s337m_24_decoder;
+
+/* The first input packet is usually empty: it is guard band bytes.
+ * Following packets always start with a syncword.
+ * The 1-frame (2 frames if including the usual first null packet) delay
+ * accomodates with the resampler, but the number of samples per frame is always preserved
+ * (ex: alternation of 1601/1602 audio samples per frame for Dolby E at 29.97).
+ */
+static int s337m_decode_frame(AVCodecContext *avctx, AVFrame *frame,
+                            int *got_frame, AVPacket *avpkt)
+{
+    S337MDecodeContext *s1 = avctx->priv_data;
+    SPDIFS337MContext *dectx = &s1->dectx;
+    int aes_word_bits = avctx->codec == (AVCodec *)&ff_s337m_24_decoder ? 24 : 16;
+    int aes_samples =  avpkt->size / (aes_word_bits >> 2);
+    int prev_aes_samples = s1->prev_aes_samples;
+    int ret, next;
+
+    if (s1->flushed || !avpkt->size && s1->passthrough)
+            return 0;
+
+    if (s1->passthrough) {
+        if (!s1->inited) {
+            av_channel_layout_uninit(&avctx->ch_layout);
+            avctx->ch_layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO;
+            avctx->sample_fmt = aes_word_bits == 24 ? AV_SAMPLE_FMT_S32 : AV_SAMPLE_FMT_S16;
+            s1->inited = 1;
+        }
+        frame->nb_samples = aes_samples;
+        if ((ret = ff_get_buffer(avctx, frame, 0)) < 0)
+            return ret;
+        if (aes_word_bits == 16)
+            memcpy(frame->extended_data[0], avpkt->data, aes_samples * (aes_word_bits >> 2));
+        else {
+            uint8_t *buf_in = avpkt->data;
+            uint8_t *buf_out = frame->extended_data[0];
+            for (; buf_in + 5 < avpkt->data + avpkt->size; buf_in+=6, buf_out+=8)
+                AV_WL64(buf_out,
+                     (uint64_t)AV_RL24(buf_in)   <<  8 |
+                     (uint64_t)AV_RL24(buf_in+3) << 40 );
+        }
+        *got_frame = 1;
+        return avpkt->size;
+    }
+
+    s1->prev_aes_samples = aes_samples;
+    if (s1->inited) {
+        frame->nb_samples = prev_aes_samples;
+        if ((ret = ff_get_buffer(avctx, frame, 0)) < 0)
+            return ret;
+    }
+    if (!avpkt->size) {
+        ret = swr_convert(s1->swr,
+                frame->extended_data, frame->nb_samples,
+                NULL, 0);
+        if (ret < 0)
+            return ret;
+        av_assert0(ret == frame->nb_samples);
+        *got_frame = 1;
+        s1->flushed = 1;
+        return 0;
+    }
+
+    next = avpriv_s337m_parse_header(dectx, avpkt->data, avpkt->size, aes_word_bits);
+    if (next < 0)
+        return next;
+    else if (!next) {
+        av_assert0(!s1->inited);
+        s1->aes_start_position += avpkt->size;
+        return avpkt->size;
+    }
+    ret = set_codec(s1);
+    if (ret < 0)
+        return ret;
+
+    ret = av_packet_make_writable(avpkt);
+    if (ret < 0)
+        return ret;
+    avpkt->data += next;
+    avpkt->size = dectx->frame_size;
+
+    if (aes_word_bits == 16)
+        avpriv_spdif_s337m_bswap_buf16((uint16_t *)avpkt->data, (uint16_t *)avpkt->data, avpkt->size >> 1);
+    else
+        avpriv_spdif_s337m_bswap_buf24(avpkt->data, avpkt->size);
+
+    ret = avcodec_send_packet(s1->codec_avctx, avpkt);
+    if (ret < 0) {
+        av_log(avctx, AV_LOG_ERROR, "Error submitting a packet for decoding\n");
+        return ret;
+    }
+
+    ret = avcodec_receive_frame(s1->codec_avctx, s1->codec_frame);
+    if (ret < 0)
+        return ret;
+
+    if (!s1->inited) {
+        ret = av_channel_layout_copy(&avctx->ch_layout, &s1->codec_avctx->ch_layout);
+        if (ret < 0)
+            return ret;
+        avctx->sample_fmt = s1->codec_avctx->sample_fmt;
+        s1->codec_initial_sample_rate = s1->codec_avctx->sample_rate;
+        ret = init_resample(avctx, &s1->swr, s1->codec_initial_sample_rate);
+        if (ret < 0)
+            return ret;
+        swr_next_pts(s1->swr, 0);
+        s1->inited = 1;
+        av_log(avctx, AV_LOG_VERBOSE,
+                "s337m phase: %.6fs\n", s1->aes_start_position / (aes_word_bits >> 2) / (float)avctx->sample_rate);
+        /* The small initial guard band must not be taken into account for syncing
+         * but completely missing frames must be (in that case, the guard band length is unknown, so also included).
+         */
+        if (s1->aes_start_position >= dectx->frame_size) {
+            s1->next_pts += s1->codec_initial_sample_rate * s1->aes_start_position / (aes_word_bits >> 2);
+            swr_next_pts(s1->swr, s1->next_pts);
+        }
+        ret = swr_convert(s1->swr,
+                NULL, 0,
+                (const uint8_t**)s1->codec_frame->extended_data, s1->codec_frame->nb_samples);
+        if (ret < 0)
+            return ret;
+
+        return avpkt->size;
+    } else {
+        if (av_channel_layout_compare(&avctx->ch_layout, &s1->codec_avctx->ch_layout)
+            || avctx->sample_fmt != s1->codec_avctx->sample_fmt
+            || s1->codec_initial_sample_rate != s1->codec_avctx->sample_rate)
+            return AVERROR_INPUT_CHANGED;
+
+        s1->next_pts += s1->codec_initial_sample_rate * prev_aes_samples;
+        swr_next_pts(s1->swr, s1->next_pts);
+        ret = swr_convert(s1->swr,
+                frame->extended_data, frame->nb_samples,
+                (const uint8_t**)s1->codec_frame->extended_data, s1->codec_frame->nb_samples);
+        if (ret < 0)
+            return ret;
+        av_assert0(ret == frame->nb_samples);
+    }
+
+    *got_frame = 1;
+    return avpkt->size;
+}
+
+static void s337m_flush(AVCodecContext *avctx)
+{
+    S337MDecodeContext *s = avctx->priv_data;
+    avcodec_flush_buffers(s->codec_avctx);
+}
+
+static av_cold int s337m_close(AVCodecContext *avctx)
+{
+    S337MDecodeContext *s = avctx->priv_data;
+    avcodec_free_context(&s->codec_avctx);
+    swr_free(&s->swr);
+    av_frame_free(&s->codec_frame);
+
+    return 0;
+}
+
+#define PAR (AV_OPT_FLAG_DECODING_PARAM | AV_OPT_FLAG_AUDIO_PARAM)
+static const AVOption options[] = {
+    {"passthrough", "Pass NON-PCM through unchanged", offsetof(S337MDecodeContext, passthrough), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, PAR },
+    {NULL}
+};
+
+static const AVClass s337m_decoder_class = {
+    .class_name = "s337m decoder",
+    .item_name  = av_default_item_name,
+    .option     = options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+const FFCodec ff_s337m_16_decoder = {
+    .p.name         = "s337m_16",
+    .p.long_name    = NULL_IF_CONFIG_SMALL("S337M 16-bit transport"),
+    .p.type         = AVMEDIA_TYPE_AUDIO,
+    .p.id           = AV_CODEC_ID_S337M_16,
+    .init           = s337m_init,
+    FF_CODEC_DECODE_CB(s337m_decode_frame),
+    .close          = s337m_close,
+    .flush          = s337m_flush,
+    .priv_data_size = sizeof(S337MDecodeContext),
+    .p.priv_class   = &s337m_decoder_class,
+    .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_CHANNEL_CONF | AV_CODEC_CAP_DELAY,
+    .caps_internal  = FF_CODEC_CAP_INIT_CLEANUP,
+};
+const FFCodec ff_s337m_24_decoder = {
+    .p.name         = "s337m_24",
+    .p.long_name    = NULL_IF_CONFIG_SMALL("S337M 24-bit transport"),
+    .p.type         = AVMEDIA_TYPE_AUDIO,
+    .p.id           = AV_CODEC_ID_S337M_24,
+    .init           = s337m_init,
+    FF_CODEC_DECODE_CB(s337m_decode_frame),
+    .close          = s337m_close,
+    .flush          = s337m_flush,
+    .priv_data_size = sizeof(S337MDecodeContext),
+    .p.priv_class   = &s337m_decoder_class,
+    .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_CHANNEL_CONF | AV_CODEC_CAP_DELAY,
+    .caps_internal  = FF_CODEC_CAP_INIT_CLEANUP,
+};
diff --git a/libavcodec/s337m_parser.c b/libavcodec/s337m_parser.c
new file mode 100644
index 0000000000..cb7ef7c739
--- /dev/null
+++ b/libavcodec/s337m_parser.c
@@ -0,0 +1,133 @@
+/*
+ * S337M parser
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libavutil/intreadwrite.h"
+#include "libavutil/mem.h"
+
+#include "parser.h"
+#include "spdif_s337m_parser_internal.h"
+
+static int all_zero(const uint8_t *buf, int size)
+{
+    int i = 0;
+#if HAVE_FAST_UNALIGNED
+    /* we check i < size instead of i + 3 / 7 because it is
+     * simpler and there must be AV_INPUT_BUFFER_PADDING_SIZE
+     * bytes at the end.
+     */
+#if HAVE_FAST_64BIT
+    while (i < size && !AV_RN64(buf + i))
+        i += 8;
+#else
+    while (i < size && !AV_RN32(buf + i))
+        i += 4;
+#endif
+#endif
+    for (; i < size; i++)
+        if (buf[i])
+            return 1;
+    return 0;
+}
+
+extern const AVCodecParser ff_s337m_24_parser;
+
+static int s337m_parse(AVCodecParserContext *s,
+                           AVCodecContext *avctx,
+                           const uint8_t **poutbuf, int *poutbuf_size,
+                           const uint8_t *buf, int buf_size)
+{
+    struct SPDIFS337MParseContext *pc1 = s->priv_data;
+    ParseContext *pc = &pc1->pc;
+    int aes_word_bits = s->parser == &ff_s337m_24_parser ? 24 : 16;
+    int eof = !buf_size;
+    int next;
+
+    if (s->flags & PARSER_FLAG_COMPLETE_FRAMES) {
+        next = buf_size;
+    } else {
+        next = avpriv_spdif_s337m_find_syncword(pc1, buf, buf_size, aes_word_bits);
+
+        if (!pc1->inited) {
+            /* bytes preceding the first syncword will be zeroed */
+            if (all_zero(buf, next != END_NOT_FOUND ? next : buf_size)) {
+                if (!pc1->warned_corrupted_guardband) {
+                    av_log(avctx, AV_LOG_VERBOSE,
+                            "Guard band has unexpected non-null bytes - they will be ignored.\n");
+                    pc1->warned_corrupted_guardband = 1;
+                }
+                if (buf_size > pc1->null_buf_size) {
+                    int old_buf_size = pc1->null_buf_size;
+                    int8_t *new_buf = av_realloc(pc1->null_buf, buf_size);
+                    if (!new_buf)
+                        return AVERROR(ENOMEM);
+                    pc1->null_buf = new_buf;
+                    memset(&pc1->null_buf[old_buf_size], 0, buf_size - old_buf_size);
+                    pc1->null_buf_size = buf_size;
+                }
+                buf = pc1->null_buf;
+            }
+            if (next != END_NOT_FOUND) {
+                pc1->inited = 1;
+                pc1->aes_initial_offset = pc1->aes_offset + next;
+            }
+        }
+        if (ff_combine_frame(pc, next, &buf, &buf_size) < 0) {
+            pc1->aes_offset += buf_size;
+            *poutbuf = NULL;
+            *poutbuf_size = 0;
+            return buf_size;
+        }
+    }
+    /* Contrary to the exact frame duration computed by the decoder, the packet duration
+     * computed here will reflect s337m jitter or phase change (if any),
+     * but there will not be any drift.
+     * The content/duration of the initial guard band (zeroed first packet) will be
+     * ignored by the decoder if it is less than a full audio frame, so that the overall
+     * duration may differ between the parser and the decoder.
+     */
+    pc1->aes_offset += eof ? pc1->aes_initial_offset : next;
+    s->duration = (pc1->aes_offset << 2) / aes_word_bits;
+    pc1->aes_offset = 0;
+
+    *poutbuf = buf;
+    *poutbuf_size = buf_size;
+    return next;
+}
+
+static void s337m_close(AVCodecParserContext *s)
+{
+    struct SPDIFS337MParseContext *pc1 = s->priv_data;
+
+    av_freep(&pc1->null_buf);
+    ff_parse_close(s);
+}
+
+const AVCodecParser ff_s337m_16_parser = {
+    .codec_ids      = { AV_CODEC_ID_S337M_16 },
+    .priv_data_size = sizeof(SPDIFS337MParseContext),
+    .parser_parse   = s337m_parse,
+    .parser_close   = s337m_close,
+};
+const AVCodecParser ff_s337m_24_parser = {
+    .codec_ids      = { AV_CODEC_ID_S337M_24 },
+    .priv_data_size = sizeof(SPDIFS337MParseContext),
+    .parser_parse   = s337m_parse,
+    .parser_close   = s337m_close,
+};
diff --git a/libavcodec/spdif_s337m_parse.c b/libavcodec/spdif_s337m_parse.c
new file mode 100644
index 0000000000..5e43e8b2b7
--- /dev/null
+++ b/libavcodec/spdif_s337m_parse.c
@@ -0,0 +1,142 @@
+/*
+ * SPDIF/S337M common code
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libavutil/bswap.h"
+#include "codec_id.h"
+#include "bytestream.h"
+
+#include "spdif_s337m_parser_internal.h"
+
+//TODO move to DSP
+void avpriv_spdif_s337m_bswap_buf16(uint16_t *dst, const uint16_t *src, int w)
+{
+    int i;
+
+    for (i = 0; i + 8 <= w; i += 8) {
+        dst[i + 0] = av_bswap16(src[i + 0]);
+        dst[i + 1] = av_bswap16(src[i + 1]);
+        dst[i + 2] = av_bswap16(src[i + 2]);
+        dst[i + 3] = av_bswap16(src[i + 3]);
+        dst[i + 4] = av_bswap16(src[i + 4]);
+        dst[i + 5] = av_bswap16(src[i + 5]);
+        dst[i + 6] = av_bswap16(src[i + 6]);
+        dst[i + 7] = av_bswap16(src[i + 7]);
+    }
+    for (; i < w; i++)
+        dst[i + 0] = av_bswap16(src[i + 0]);
+}
+
+void avpriv_spdif_s337m_bswap_buf24(uint8_t *data, int size)
+{
+    int i;
+
+    for (i = 0; i < size / 3; i++, data += 3)
+        FFSWAP(uint8_t, data[0], data[2]);
+}
+
+int avpriv_spdif_s337m_find_syncword(SPDIFS337MParseContext *pc1, const uint8_t *buf, int buf_size, int word_bits)
+{
+    ParseContext *pc = &pc1->pc;
+    uint64_t state = pc->state64;
+    uint64_t state_ext = pc1->state_ext;
+    int i = 0;
+
+    for (; i < buf_size; i++) {
+        state_ext = (state_ext >> 8) | state & 0xFF00000000000000;
+        state = (state << 8) | buf[i];
+        if (state_ext
+            || word_bits == 16 && state != MARKER_16LE
+            || word_bits == 24 && state != MARKER_20LE && state != MARKER_24LE)
+            continue;
+
+        pc->state64 = -1;
+        return state == MARKER_16LE ? i - 3 : i - 5;
+    }
+
+    pc->state64 = state;
+    pc1->state_ext = state_ext;
+    return END_NOT_FOUND;
+}
+
+static int s337m_get_codec(SPDIFS337MContext *dectx, uint64_t state, int data_type, int data_size, int s337m_word_bits)
+{
+    int word_bits;
+
+    if (IS_16LE_MARKER(state)) {
+        word_bits = 16;
+    } else if (IS_20LE_MARKER(state)) {
+        data_type >>= 8;
+        data_size >>= 4;
+        word_bits = 20;
+    } else if (IS_24LE_MARKER(state)) {
+        data_type >>= 8;
+        word_bits = 24;
+    } else return AVERROR_INVALIDDATA;
+
+    if (!(s337m_word_bits == 16 && word_bits == 16) &&
+        !(s337m_word_bits == 24 && word_bits == 20) &&
+        !(s337m_word_bits == 24 && word_bits == 24)) {
+        if (dectx && dectx->avctx)
+            av_log(dectx->avctx, AV_LOG_ERROR, "s337m: unexpected %d-bit payload in %d-bit container\n", word_bits, s337m_word_bits);
+        return AVERROR_INVALIDDATA;
+    }
+
+    switch(data_type & 0x1F) {
+        case 0x1C:
+            if (dectx) {
+                dectx->frame_size = (word_bits + 7 >> 3) * data_size / word_bits;
+                dectx->codec = AV_CODEC_ID_DOLBY_E;
+            }
+            break;
+
+        default:
+            /* When probing 16-bit streams, spdif codecs can be encountered. */
+            if (dectx && dectx->avctx)
+                avpriv_report_missing_feature(dectx->avctx, "Data type %#x in SMPTE 337M", data_type & 0x1F);
+            return dectx ? AVERROR_PATCHWELCOME : AVERROR_INVALIDDATA;
+    }
+
+    return 0;
+}
+
+int avpriv_s337m_parse_header(SPDIFS337MContext *dectx, const uint8_t *buf, int buf_size, int s337m_word_bits)
+{
+    uint64_t state;
+    int data_type, data_size, next = s337m_word_bits >> 1;
+    int ret = 0;
+
+    if (buf_size < next)
+        return AVERROR_BUFFER_TOO_SMALL;
+    if (s337m_word_bits == 16) {
+        state = AV_RB32(buf);
+        data_type = AV_RL16(buf + 4);
+        data_size = AV_RL16(buf + 6);
+    } else {
+        state = AV_RB48(buf);
+        data_type = AV_RL24(buf + 6);
+        data_size = AV_RL24(buf + 9);
+    }
+    if (!state)
+        return 0;
+    if (ret = s337m_get_codec(dectx, state, data_type, data_size, s337m_word_bits))
+        return ret;
+
+    return next;
+}
diff --git a/libavcodec/spdif_s337m_parser_internal.h b/libavcodec/spdif_s337m_parser_internal.h
new file mode 100644
index 0000000000..da9407ebaf
--- /dev/null
+++ b/libavcodec/spdif_s337m_parser_internal.h
@@ -0,0 +1,92 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVCODEC_SPDIF_S337M_PARSER_INTERNAL_H
+#define AVCODEC_SPDIF_S337M_PARSER_INTERNAL_H
+
+#include "parser.h"
+
+#define MARKER_16LE         0x72F81F4E
+#define MARKER_20LE         0x20876FF0E154
+#define MARKER_24LE         0x72F8961F4EA5
+
+#define IS_16LE_MARKER(state)   ((state & 0xFFFFFFFF) == MARKER_16LE)
+#define IS_20LE_MARKER(state)   ((state & 0xF0FFFFF0FFFF) == MARKER_20LE)
+#define IS_24LE_MARKER(state)   ((state & 0xFFFFFFFFFFFF) == MARKER_24LE)
+
+/**
+ * @struct SPDIFS337MContext
+ * Context for use by decoder and parser.
+ */
+typedef struct SPDIFS337MContext {
+    void        *avctx;
+
+    int         frame_size;
+    enum AVCodecID codec;
+} SPDIFS337MContext;
+
+/**
+ * @struct SPDIFS337MParseContext
+ * Context for use by parser to split packets.
+ */
+typedef struct SPDIFS337MParseContext {
+    ParseContext pc;
+    uint64_t     state_ext;
+
+    int          inited;
+    int          aes_initial_offset;
+    int          aes_offset;
+
+    uint8_t      *null_buf;
+    int          null_buf_size;
+    int          warned_corrupted_guardband;
+} SPDIFS337MParseContext;
+
+void avpriv_spdif_s337m_bswap_buf16(uint16_t *dst, const uint16_t *src, int w);
+void avpriv_spdif_s337m_bswap_buf24(uint8_t *data, int size);
+
+/**
+ * Find an 'extended sync code' as suggested by SMPTE 337M Annex A.
+ * Its length is set to 128 bits for optimization, while Annex A
+ * suggests 6 words (96 bits for 16-bit or 144 bits for 20/24-bit).
+ *
+ * Do not require sync byte alignment on container word bytes,
+ * but still, 16-bit payloads require 16-bit container
+ * and 20/24-bit payloads require 24-bit container.
+ * Note: SPDIF is always 16-bit.
+ *
+ * @param  pc1              To persist states between calls
+ * @param  buf              Buffer for reading
+ * @param  buf_size         Max available bytes to read
+ * @param  word_bits        Buffer word size: 16 or 24
+ * @return syncword byte position on success, or END_NOT_FOUND
+ **/
+int avpriv_spdif_s337m_find_syncword(SPDIFS337MParseContext *pc1, const uint8_t *buf, int buf_size, int word_bits);
+
+/**
+ * Parse s337m header: get codec and frame_size.
+ *
+ * @param  avc              If not null, codec and frame_size will be set
+ * @param  buf              Buffer for reading with s337m syncword at byte position 0
+ * @param  buf_size         Max available bytes to read
+ * @param  s337m_word_bits  Buffer word size: 16 or 24
+ * @return header size > 0 on success, 0 if the first two words are null, or < 0 on error
+ */
+int avpriv_s337m_parse_header(SPDIFS337MContext *avc, const uint8_t *buf, int buf_size, int s337m_word_bits);
+
+#endif /* AVCODEC_SPDIF_S337M_PARSER_INTERNAL_H */
diff --git a/libavcodec/utils.c b/libavcodec/utils.c
index 28023a4a4d..922c6e2417 100644
--- a/libavcodec/utils.c
+++ b/libavcodec/utils.c
@@ -497,6 +497,7 @@ int av_get_exact_bits_per_sample(enum AVCodecID codec_id)
     case AV_CODEC_ID_PCM_S16LE_PLANAR:
     case AV_CODEC_ID_PCM_U16BE:
     case AV_CODEC_ID_PCM_U16LE:
+    case AV_CODEC_ID_S337M_16:
         return 16;
     case AV_CODEC_ID_PCM_S24DAUD:
     case AV_CODEC_ID_PCM_S24BE:
@@ -504,6 +505,7 @@ int av_get_exact_bits_per_sample(enum AVCodecID codec_id)
     case AV_CODEC_ID_PCM_S24LE_PLANAR:
     case AV_CODEC_ID_PCM_U24BE:
     case AV_CODEC_ID_PCM_U24LE:
+    case AV_CODEC_ID_S337M_24:
         return 24;
     case AV_CODEC_ID_PCM_S32BE:
     case AV_CODEC_ID_PCM_S32LE:
diff --git a/libavcodec/version.c b/libavcodec/version.c
index 03dd95e5ba..b4917b4e29 100644
--- a/libavcodec/version.c
+++ b/libavcodec/version.c
@@ -35,7 +35,7 @@ unsigned avcodec_version(void)
                   AV_CODEC_ID_PCM_SGA      == 65572 &&
                   AV_CODEC_ID_ADPCM_XMD    == 69683 &&
                   AV_CODEC_ID_CBD2_DPCM    == 81928 &&
-                  AV_CODEC_ID_QOA          == 86121 &&
+                  AV_CODEC_ID_S337M_24     == 86124 &&
                   AV_CODEC_ID_ARIB_CAPTION == 94233 &&
                   AV_CODEC_ID_SMPTE_2038   == 98315,
                   "Don't insert new codec ids in the middle of a list");
-- 
2.30.2



More information about the ffmpeg-devel mailing list