Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
From: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
To: ffmpeg-devel@ffmpeg.org
Subject: Re: [FFmpeg-devel] [PATCH] avformat/lc3: Add file format for LC3/LC3plus transport
Date: Wed, 10 Apr 2024 20:26:01 +0200
Message-ID: <AS8P250MB07443711134687B6AD72AD1C8F062@AS8P250MB0744.EURP250.PROD.OUTLOOK.COM> (raw)
In-Reply-To: <CA+wrVdYFNv7E4NEh5s+Fkbih+fQEdY3hVkgyvPi9AeLuzN7z7w@mail.gmail.com>

Antoine Soulier via ffmpeg-devel:
> Sorry for that, I missed the rebasing.
> I have followed your recommendations, and put the muxer/demuxer in the same
> file.
> Here is the patch:
> 

Send your patch either with git send-email or attach it to your mail; do
not copy the file produced by git format-patch into your mail.

> From f85989288a99130eb3583d5ae9c5bf441e961ed4 Mon Sep 17 00:00:00 2001
> From: Antoine SOULIER <asoulier@google.com>
> Date: Thu, 4 Apr 2024 22:38:03 +0000
> Subject: [PATCH] avformat/lc3: Add file format for LC3/LC3plus transport
> 
> A file format is described in Bluetooth SIG LC3 and ETSI TS 103 634, for
> test purpose. This is the format implemented here.
> ---
>  Changelog                |   1 +
>  doc/muxers.texi          |   8 ++
>  libavformat/Makefile     |   2 +
>  libavformat/allformats.c |   2 +
>  libavformat/lc3.c        | 248 +++++++++++++++++++++++++++++++++++++++
>  5 files changed, 261 insertions(+)
>  create mode 100644 libavformat/lc3.c
> 
> diff --git a/Changelog b/Changelog
> index b7a1af4083..5c8f505211 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -5,6 +5,7 @@ version <next>:
>  - Raw Captions with Time (RCWT) closed caption demuxer
>  - LC3/LC3plus decoding/encoding using external library liblc3
>  - ffmpeg CLI filtergraph chaining
> +- LC3/LC3plus demuxer and muxer
> 
> 
>  version 7.0:
> diff --git a/doc/muxers.texi b/doc/muxers.texi
> index 4b30970b78..032f48410e 100644
> --- a/doc/muxers.texi
> +++ b/doc/muxers.texi
> @@ -2703,6 +2703,14 @@ This muxer accepts a single audio stream containing
> PCM data.
>  @section ivf
>  On2 IVF muxer.
> 
> +@section lc3
> +Bluetooth SIG Low Complexity Communication Codec audio (LC3), or
> +ETSI TS 103 634 Low Complexity Communication Codec plus (LC3plus).
> +
> +This muxer accepts a single @code{lc3} audio stream.
> +
> +@section matroska

Nonsense. You are adding this in the middle of the description of the
IVF muxer.

> +
>  IVF was developed by On2 Technologies (formerly known as Duck
>  Corporation), to store internally developed codecs.
> 
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index 9981799cc9..8efe26b6df 100644
> --- a/libavformat/Makefile
> +++ b/libavformat/Makefile
> @@ -332,6 +332,8 @@ OBJS-$(CONFIG_KVAG_DEMUXER)              += kvag.o
>  OBJS-$(CONFIG_KVAG_MUXER)                += kvag.o rawenc.o
>  OBJS-$(CONFIG_LAF_DEMUXER)               += lafdec.o
>  OBJS-$(CONFIG_LATM_MUXER)                += latmenc.o rawenc.o
> +OBJS-$(CONFIG_LC3_DEMUXER)               += lc3.o
> +OBJS-$(CONFIG_LC3_MUXER)                 += lc3.o
>  OBJS-$(CONFIG_LMLM4_DEMUXER)             += lmlm4.o
>  OBJS-$(CONFIG_LOAS_DEMUXER)              += loasdec.o rawdec.o
>  OBJS-$(CONFIG_LUODAT_DEMUXER)            += luodatdec.o
> diff --git a/libavformat/allformats.c b/libavformat/allformats.c
> index ae925dcf60..305fa46532 100644
> --- a/libavformat/allformats.c
> +++ b/libavformat/allformats.c
> @@ -252,6 +252,8 @@ extern const FFInputFormat  ff_kvag_demuxer;
>  extern const FFOutputFormat ff_kvag_muxer;
>  extern const FFInputFormat  ff_laf_demuxer;
>  extern const FFOutputFormat ff_latm_muxer;
> +extern const FFInputFormat  ff_lc3_demuxer;
> +extern const FFOutputFormat ff_lc3_muxer;
>  extern const FFInputFormat  ff_lmlm4_demuxer;
>  extern const FFInputFormat  ff_loas_demuxer;
>  extern const FFInputFormat  ff_luodat_demuxer;
> diff --git a/libavformat/lc3.c b/libavformat/lc3.c
> new file mode 100644
> index 0000000000..8b1ec62589
> --- /dev/null
> +++ b/libavformat/lc3.c
> @@ -0,0 +1,248 @@
> +/*
> + * LC3 demuxer
> + * Copyright (C) 2024  Antoine Soulier <asoulier@google.com>
> + *
> + * 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
> + */
> +
> +/**
> + * @file
> + * Based on the file format specified by :
> + *
> + * - Bluetooth SIG - Low Complexity Communication Codec Test Suite
> + *
> https://www.bluetooth.org/docman/handlers/downloaddoc.ashx?doc_id=502301
> + *   3.2.8.2 Reference LC3 Codec Bitstream Format
> + *
> + * - ETSI TI 103 634 V1.4.1 - Low Complexity Communication Codec plus
> + *
> https://www.etsi.org/deliver/etsi_ts/103600_103699/103634/01.04.01_60/ts_103634v010401p.pdf
> + *   LC3plus conformance script package
> + */
> +
> +#include "config_components.h"
> +
> +#include "libavcodec/packet.h"
> +#include "libavutil/intreadwrite.h"
> +
> +#include "avformat.h"
> +#include "avio.h"
> +#include "demux.h"
> +#include "internal.h"
> +#include "mux.h"
> +
> +typedef struct LC3DemuxContext {
> +    int frame_samples;
> +    int64_t end_dts;
> +} LC3DemuxContext;
> +
> +static int check_frame_length(int srate_hz, int frame_us)
> +{
> +    if (srate_hz !=  8000 && srate_hz != 16000 && srate_hz != 24000 &&
> +        srate_hz != 32000 && srate_hz != 48000 && srate_hz != 96000)
> +        return -1;
> +
> +    if (frame_us != 2500 && frame_us !=  5000 &&
> +        frame_us != 7500 && frame_us != 10000)
> +        return -1;
> +
> +    return 0;
> +}
> +
> +static int lc3_read_probe(const AVProbeData *p)
> +{
> +    int frame_us, srate_hz;
> +
> +    if (p->buf_size < 12)
> +        return 0;
> +
> +    if (AV_RB16(p->buf + 0) != 0x1ccc ||
> +        AV_RL16(p->buf + 2) <  9 * sizeof(uint16_t))
> +        return 0;
> +
> +    srate_hz = AV_RL16(p->buf + 4) * 100;
> +    frame_us = AV_RL16(p->buf + 10) * 10;
> +    if (check_frame_length(srate_hz, frame_us) < 0)
> +        return 0;
> +
> +    return AVPROBE_SCORE_MAX;
> +}
> +
> +static int lc3_read_header(AVFormatContext *s)
> +{
> +    LC3DemuxContext *lc3 = s->priv_data;
> +    AVStream *st = NULL;
> +    uint16_t tag, hdr_size;
> +    uint32_t length;
> +    int srate_hz, frame_us, channels, bit_rate;
> +    int ep_mode, hr_mode;
> +    int num_extra_params;
> +    int delay, ret;
> +
> +    tag = avio_rb16(s->pb);
> +    hdr_size = avio_rl16(s->pb);
> +
> +    if (tag != 0x1ccc || hdr_size < 9 * sizeof(uint16_t))
> +        return AVERROR_INVALIDDATA;
> +
> +    num_extra_params = hdr_size / sizeof(uint16_t) - 9;
> +
> +    srate_hz = avio_rl16(s->pb) * 100;
> +    bit_rate = avio_rl16(s->pb) * 100;
> +    channels = avio_rl16(s->pb);
> +    frame_us = avio_rl16(s->pb) * 10;
> +    ep_mode  = avio_rl16(s->pb) != 0;
> +    length   = avio_rl32(s->pb);
> +    hr_mode  = num_extra_params >= 1 && avio_rl16(s->pb);
> +
> +    if (check_frame_length(srate_hz, frame_us) < 0) {
> +        av_log(s, AV_LOG_ERROR, "Invalid LC3 sample rate: %d Hz, "
> +                                "frame duration: %.1f ms.\n",
> +               srate_hz, frame_us / 1000.f);
> +        return AVERROR_INVALIDDATA;
> +    }
> +
> +    st = avformat_new_stream(s, NULL);
> +    if (!st)
> +        return AVERROR(ENOMEM);
> +
> +    avpriv_set_pts_info(st, 64, 1, srate_hz);
> +    avpriv_update_cur_dts(s, st, 0);
> +    st->duration = length;
> +
> +    st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
> +    st->codecpar->codec_id = AV_CODEC_ID_LC3;
> +    st->codecpar->sample_rate = srate_hz;
> +    st->codecpar->bit_rate = bit_rate;
> +    st->codecpar->ch_layout.nb_channels = channels;
> +
> +    if ((ret = ff_alloc_extradata(st->codecpar, 6)) < 0)
> +        return ret;
> +
> +    AV_WL16(st->codecpar->extradata + 0, frame_us / 10);
> +    AV_WL16(st->codecpar->extradata + 2, ep_mode);
> +    AV_WL16(st->codecpar->extradata + 4, hr_mode);
> +
> +    lc3->frame_samples = av_rescale(frame_us, srate_hz, 1000*1000);
> +
> +    delay = av_rescale(frame_us == 7500 ? 4000 : 2500, srate_hz,
> 1000*1000);
> +    lc3->end_dts = length ? length + delay : -1;
> +
> +    return 0;
> +}
> +
> +static int lc3_read_packet(AVFormatContext *s, AVPacket *pkt)
> +{
> +    LC3DemuxContext *lc3 = s->priv_data;
> +    AVStream *st = s->streams[0];
> +    AVIOContext *pb = s->pb;
> +    int64_t pos = avio_tell(pb);
> +    int64_t remaining_samples;
> +    int ret;
> +
> +    ret = av_get_packet(s->pb, pkt, avio_rl16(pb));
> +    if (ret < 0)
> +        return ret;
> +
> +    pkt->pos = pos;
> +
> +    remaining_samples = lc3->end_dts < 0 ? lc3->frame_samples :
> +                        FFMAX(lc3->end_dts - ffstream(st)->cur_dts, 0);
> +    pkt->duration = FFMIN(lc3->frame_samples, remaining_samples);
> +
> +    return 0;
> +}
> +
> +static av_cold int lc3_muxer_init(AVFormatContext *s)
> +{
> +    if (s->nb_streams != 1) {
> +        av_log(s, AV_LOG_ERROR, "This muxer only supports a single
> stream.\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    return 0;
> +}
> +
> +static int lc3_write_header(AVFormatContext *s)
> +{
> +    AVStream *st = s->streams[0];
> +    int channels = st->codecpar->ch_layout.nb_channels;
> +    int srate_hz = st->codecpar->sample_rate;
> +    int bit_rate = st->codecpar->bit_rate;
> +    int frame_us, ep_mode, hr_mode;
> +    uint32_t nb_samples = av_rescale_q(
> +        st->duration, st->time_base, (AVRational){ 1, srate_hz });
> +
> +    if (st->codecpar->extradata_size < 6)
> +        return AVERROR_INVALIDDATA;
> +
> +    frame_us = AV_RL16(st->codecpar->extradata + 0) * 10;
> +    ep_mode = AV_RL16(st->codecpar->extradata + 2) != 0;
> +    hr_mode = AV_RL16(st->codecpar->extradata + 4) != 0;
> +
> +    if (check_frame_length(srate_hz, frame_us) < 0) {
> +        av_log(s, AV_LOG_ERROR, "Invalid LC3 sample rate: %d Hz, "
> +                                "frame duration: %.1f ms.\n",
> +               srate_hz, frame_us / 1000.f);
> +        return AVERROR_INVALIDDATA;
> +    }
> +
> +    avio_wb16(s->pb, 0x1ccc);
> +    avio_wl16(s->pb, (9 + hr_mode) * sizeof(uint16_t));
> +    avio_wl16(s->pb, srate_hz / 100);
> +    avio_wl16(s->pb, bit_rate / 100);
> +    avio_wl16(s->pb, channels);
> +    avio_wl16(s->pb, frame_us / 10);
> +    avio_wl16(s->pb, ep_mode);
> +    avio_wl32(s->pb, nb_samples);
> +    if (hr_mode)
> +        avio_wl16(s->pb, hr_mode);
> +
> +    return 0;
> +}
> +
> +static int lc3_write_packet(AVFormatContext *s, AVPacket *pkt)
> +{
> +    avio_wl16(s->pb, pkt->size);
> +    avio_write(s->pb, pkt->data, pkt->size);
> +    return 0;
> +}
> +
> +#if CONFIG_LC3_DEMUXER
> +const FFInputFormat ff_lc3_demuxer = {
> +    .p.name         = "lc3",
> +    .p.long_name    = NULL_IF_CONFIG_SMALL("LC3 (Low Complexity
> Communication Codec)"),
> +    .p.extensions   = "lc3",
> +    .p.flags        = AVFMT_GENERIC_INDEX,
> +    .priv_data_size = sizeof(LC3DemuxContext),
> +    .read_probe     = lc3_read_probe,
> +    .read_header    = lc3_read_header,
> +    .read_packet    = lc3_read_packet,
> +};
> +#endif
> +
> +#if CONFIG_LC3_MUXER
> +const FFOutputFormat ff_lc3_muxer = {
> +    .p.name        = "lc3",
> +    .p.long_name   = NULL_IF_CONFIG_SMALL("LC3 (Low Complexity
> Communication Codec)"),
> +    .p.extensions  = "lc3",
> +    .p.audio_codec = AV_CODEC_ID_LC3,
> +    .p.video_codec = AV_CODEC_ID_NONE,
> +    .p.flags       = AVFMT_NOTIMESTAMPS,
> +    .init          = lc3_muxer_init,
> +    .write_header  = lc3_write_header,
> +    .write_packet  = lc3_write_packet,
> +};
> +#endif

You only put the muxer and demuxer inside #if guards. If only one of
these two is enabled, the other's functions will not be used and lead to
compiler warnings. This can be fixed by putting all the stuff for only
the muxer/demuxer inside the #if (see argo_cvg.c for an example).

- Andreas

_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".

  reply	other threads:[~2024-04-10 18:26 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-04-08 22:27 Antoine Soulier via ffmpeg-devel
2024-04-10  9:11 ` Stefano Sabatini
2024-04-10 17:41   ` Antoine Soulier via ffmpeg-devel
2024-04-10 18:26     ` Andreas Rheinhardt [this message]
2024-04-10 21:22       ` Stefano Sabatini
2024-04-10 23:46         ` Antoine Soulier via ffmpeg-devel
2024-04-12 13:05           ` Stefano Sabatini
2024-04-12 23:46             ` Antoine Soulier via ffmpeg-devel
2024-04-13  8:54               ` Stefano Sabatini
2024-04-15 16:40                 ` Stefano Sabatini
  -- strict thread matches above, loose matches on Subject: below --
2024-04-06 21:08 Antoine Soulier via ffmpeg-devel
2024-04-08 14:30 ` Stefano Sabatini

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=AS8P250MB07443711134687B6AD72AD1C8F062@AS8P250MB0744.EURP250.PROD.OUTLOOK.COM \
    --to=andreas.rheinhardt@outlook.com \
    --cc=ffmpeg-devel@ffmpeg.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git