diff mbox series

[FFmpeg-devel,1/2] avcodec/liblc3: Add encoding/decoding support of LC3 audio codec

Message ID 20240401213205.2039901-1-asoulier@google.com
State New
Headers show
Series [FFmpeg-devel,1/2] avcodec/liblc3: Add encoding/decoding support of LC3 audio codec | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Antoine Soulier April 1, 2024, 9:32 p.m. UTC
The LC3 audio codec is the default codec of Bluetooth LE audio.
This is a wrapper over the liblc3 library (https://github.com/google/liblc3).

Signed-off-by: Antoine Soulier <asoulier@google.com>
---
 Changelog                 |   4 +
 configure                 |   6 ++
 doc/encoders.texi         |  57 +++++++++++
 doc/general_contents.texi |  11 +-
 libavcodec/Makefile       |   2 +
 libavcodec/allcodecs.c    |   2 +
 libavcodec/codec_desc.c   |   7 ++
 libavcodec/codec_id.h     |   1 +
 libavcodec/liblc3dec.c    | 145 ++++++++++++++++++++++++++
 libavcodec/liblc3enc.c    | 210 ++++++++++++++++++++++++++++++++++++++
 10 files changed, 444 insertions(+), 1 deletion(-)
 create mode 100644 libavcodec/liblc3dec.c
 create mode 100644 libavcodec/liblc3enc.c

Comments

Stefano Sabatini April 4, 2024, 3:39 p.m. UTC | #1
On date Monday 2024-04-01 21:32:04 +0000, ffmpeg-devel Mailing List wrote:
> The LC3 audio codec is the default codec of Bluetooth LE audio.
> This is a wrapper over the liblc3 library (https://github.com/google/liblc3).
> 
> Signed-off-by: Antoine Soulier <asoulier@google.com>
> ---
>  Changelog                 |   4 +
>  configure                 |   6 ++
>  doc/encoders.texi         |  57 +++++++++++
>  doc/general_contents.texi |  11 +-
>  libavcodec/Makefile       |   2 +
>  libavcodec/allcodecs.c    |   2 +
>  libavcodec/codec_desc.c   |   7 ++
>  libavcodec/codec_id.h     |   1 +
>  libavcodec/liblc3dec.c    | 145 ++++++++++++++++++++++++++
>  libavcodec/liblc3enc.c    | 210 ++++++++++++++++++++++++++++++++++++++
>  10 files changed, 444 insertions(+), 1 deletion(-)
>  create mode 100644 libavcodec/liblc3dec.c
>  create mode 100644 libavcodec/liblc3enc.c

will apply this soon (after a rebase)

Note: I had to add the libavutil/mem.h includes to fix local
compilation.

Also, liblc3 meson will install the .pc file in
lib/x86_64-linux-gnu/pkgconfig/ which is a bit unexpected, as the
PKG_CONFIG_PATH is usually lib/pkgconfig.

Thanks.
James Almer April 4, 2024, 4:22 p.m. UTC | #2
On 4/1/2024 6:32 PM, Antoine Soulier via ffmpeg-devel wrote:
> The LC3 audio codec is the default codec of Bluetooth LE audio.
> This is a wrapper over the liblc3 library (https://github.com/google/liblc3).
> 
> Signed-off-by: Antoine Soulier <asoulier@google.com>
> ---
>   Changelog                 |   4 +
>   configure                 |   6 ++
>   doc/encoders.texi         |  57 +++++++++++
>   doc/general_contents.texi |  11 +-
>   libavcodec/Makefile       |   2 +
>   libavcodec/allcodecs.c    |   2 +
>   libavcodec/codec_desc.c   |   7 ++
>   libavcodec/codec_id.h     |   1 +
>   libavcodec/liblc3dec.c    | 145 ++++++++++++++++++++++++++
>   libavcodec/liblc3enc.c    | 210 ++++++++++++++++++++++++++++++++++++++
>   10 files changed, 444 insertions(+), 1 deletion(-)
>   create mode 100644 libavcodec/liblc3dec.c
>   create mode 100644 libavcodec/liblc3enc.c
> 
> diff --git a/Changelog b/Changelog
> index e83a00e35c..83a4cf7888 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -1,6 +1,10 @@
>   Entries are sorted chronologically from oldest to youngest within each release,
>   releases are sorted from youngest to oldest.
>   
> +version <next>:
> +- LC3/LC3plus decoding/encoding using external library liblc3
> +
> +
>   version 7.0:
>   - DXV DXT1 encoder
>   - LEAD MCMP decoder
> diff --git a/configure b/configure
> index 2d46ef0b9c..e5d9ba9f53 100755
> --- a/configure
> +++ b/configure
> @@ -244,6 +244,7 @@ External library support:
>     --enable-libjxl          enable JPEG XL de/encoding via libjxl [no]
>     --enable-libklvanc       enable Kernel Labs VANC processing [no]
>     --enable-libkvazaar      enable HEVC encoding via libkvazaar [no]
> +  --enable-liblc3          enable LC3 de/encoding via liblc3 [no]
>     --enable-liblensfun      enable lensfun lens correction [no]
>     --enable-libmodplug      enable ModPlug via libmodplug [no]
>     --enable-libmp3lame      enable MP3 encoding via libmp3lame [no]
> @@ -1926,6 +1927,7 @@ EXTERNAL_LIBRARY_LIST="
>       libjxl
>       libklvanc
>       libkvazaar
> +    liblc3
>       libmodplug
>       libmp3lame
>       libmysofa
> @@ -3499,6 +3501,9 @@ libilbc_encoder_deps="libilbc"
>   libjxl_decoder_deps="libjxl libjxl_threads"
>   libjxl_encoder_deps="libjxl libjxl_threads"
>   libkvazaar_encoder_deps="libkvazaar"
> +liblc3_decoder_deps="liblc3"
> +liblc3_encoder_deps="liblc3"
> +liblc3_encoder_select="audio_frame_queue"
>   libmodplug_demuxer_deps="libmodplug"
>   libmp3lame_encoder_deps="libmp3lame"
>   libmp3lame_encoder_select="audio_frame_queue mpegaudioheader"
> @@ -6869,6 +6874,7 @@ enabled libjxl            && require_pkg_config libjxl "libjxl >= 0.7.0" jxl/dec
>                                require_pkg_config libjxl_threads "libjxl_threads >= 0.7.0" jxl/thread_parallel_runner.h JxlThreadParallelRunner
>   enabled libklvanc         && require libklvanc libklvanc/vanc.h klvanc_context_create -lklvanc
>   enabled libkvazaar        && require_pkg_config libkvazaar "kvazaar >= 2.0.0" kvazaar.h kvz_api_get
> +enabled liblc3            && require_pkg_config liblc3 "lc3 >= 1.1.0" lc3.h lc3_hr_setup_encoder
>   enabled liblensfun        && require_pkg_config liblensfun lensfun lensfun.h lf_db_create
>   
>   if enabled libmfx && enabled libvpl; then
> diff --git a/doc/encoders.texi b/doc/encoders.texi
> index 7c223ed74c..66847191e1 100644
> --- a/doc/encoders.texi
> +++ b/doc/encoders.texi
> @@ -814,6 +814,63 @@ ffmpeg -i input.wav -c:a libfdk_aac -profile:a aac_he -b:a 64k output.m4a
>   @end example
>   @end itemize
>   
> +@anchor{liblc3-enc}
> +@section liblc3
> +
> +liblc3 LC3 (Low Complexity Communication Codec) encoder wrapper.
> +
> +Requires the presence of the liblc3 headers and library during configuration.
> +You need to explicitly configure the build with @code{--enable-liblc3}.
> +
> +This encoder has support for the Bluetooth SIG LC3 codec for the LE Audio
> +protocol, and the following features of LC3plus:
> +@itemize
> +@item
> +Frame duration of 2.5 and 5ms.
> +@item
> +High-Resolution mode, 48 KHz, and 96 kHz sampling rates.
> +@end itemize
> +
> +For more information see the liblc3 project at
> +@url{https://github.com/google/liblc3}.
> +
> +@subsection Options
> +
> +The following options are mapped on the shared FFmpeg codec options.
> +
> +@table @option
> +@item b @var{bitrate}
> +Set the bit rate in bits/s. This will determine the fixed size of the encoded
> +frames, for a selected frame duration.
> +
> +@item ar @var{frequency}
> +Set the audio sampling rate (in Hz).
> +
> +@item channels
> +Set the number of audio channels.
> +
> +@item frame_duration
> +Set the audio frame duration in milliseconds. Default value is 10ms.
> +Allowed frame durations are 2.5ms, 5ms, 7.5ms and 10ms.
> +LC3 (Bluetooth LE Audio), allows 7.5ms and 10ms; and LC3plus 2.5ms, 5ms
> +and 10ms.
> +
> +The 10ms frame duration is available in LC3 and LC3 plus standard.
> +In this mode, the produced bitstream can be referenced either as LC3 or LC3plus.
> +
> +@item high_resolution @var{boolean}
> +Enable the high-resolution mode if set to 1. The high-resolution mode is
> +available with all LC3plus frame durations and for a sampling rate of 48 KHz,
> +and 96 KHz.
> +
> +The encoder automatically turns off this mode at lower sampling rates and
> +activates it at 96 KHz.
> +
> +This mode should be preferred at high bitrates. In this mode, the audio
> +bandwidth is always up to the Nyquist frequency, compared to LC3 at 48 KHz,
> +which limits the bandwidth to 20 KHz.
> +@end table
> +
>   @anchor{libmp3lame}
>   @section libmp3lame
>   
> diff --git a/doc/general_contents.texi b/doc/general_contents.texi
> index f269cbd1a9..e7cf4f8239 100644
> --- a/doc/general_contents.texi
> +++ b/doc/general_contents.texi
> @@ -237,6 +237,14 @@ Go to @url{http://sourceforge.net/projects/opencore-amr/} and follow the
>   instructions for installing the library.
>   Then pass @code{--enable-libfdk-aac} to configure to enable it.
>   
> +@subsection LC3 library
> +
> +FFmpeg can make use of the Google LC3 library for LC3 decoding & encoding.
> +
> +Go to @url{https://github.com/google/liblc3/} and follow the instructions for
> +installing the library.
> +Then pass @code{--enable-liblc3} to configure to enable it.
> +
>   @section OpenH264
>   
>   FFmpeg can make use of the OpenH264 library for H.264 decoding and encoding.
> @@ -1300,7 +1308,8 @@ following image formats are supported:
>       @tab encoding and decoding supported through external library libilbc
>   @item IMC (Intel Music Coder)  @tab     @tab  X
>   @item Interplay ACM            @tab     @tab  X
> -@item MACE (Macintosh Audio Compression/Expansion) 3:1  @tab     @tab  X
> +@item LC3                    @tab E  @tab  E
> +    @tab supported through external library liblc3
>   @item MACE (Macintosh Audio Compression/Expansion) 6:1  @tab     @tab  X
>   @item Marian's A-pac audio     @tab     @tab  X
>   @item MI-SC4 (Micronas SC-4 Audio)  @tab     @tab  X
> diff --git a/libavcodec/Makefile b/libavcodec/Makefile
> index eef936944d..6323c98add 100644
> --- a/libavcodec/Makefile
> +++ b/libavcodec/Makefile
> @@ -1122,6 +1122,8 @@ OBJS-$(CONFIG_LIBILBC_ENCODER)            += libilbc.o
>   OBJS-$(CONFIG_LIBJXL_DECODER)             += libjxldec.o libjxl.o
>   OBJS-$(CONFIG_LIBJXL_ENCODER)             += libjxlenc.o libjxl.o
>   OBJS-$(CONFIG_LIBKVAZAAR_ENCODER)         += libkvazaar.o
> +OBJS-$(CONFIG_LIBLC3_ENCODER)             += liblc3enc.o
> +OBJS-$(CONFIG_LIBLC3_DECODER)             += liblc3dec.o
>   OBJS-$(CONFIG_LIBMP3LAME_ENCODER)         += libmp3lame.o
>   OBJS-$(CONFIG_LIBOPENCORE_AMRNB_DECODER)  += libopencore-amr.o
>   OBJS-$(CONFIG_LIBOPENCORE_AMRNB_ENCODER)  += libopencore-amr.o
> diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
> index 2386b450a6..f4705651fb 100644
> --- a/libavcodec/allcodecs.c
> +++ b/libavcodec/allcodecs.c
> @@ -776,6 +776,8 @@ extern const FFCodec ff_libilbc_encoder;
>   extern const FFCodec ff_libilbc_decoder;
>   extern const FFCodec ff_libjxl_decoder;
>   extern const FFCodec ff_libjxl_encoder;
> +extern const FFCodec ff_liblc3_encoder;
> +extern const FFCodec ff_liblc3_decoder;
>   extern const FFCodec ff_libmp3lame_encoder;
>   extern const FFCodec ff_libopencore_amrnb_encoder;
>   extern const FFCodec ff_libopencore_amrnb_decoder;
> diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
> index 3bab86db62..7dba61dc8b 100644
> --- a/libavcodec/codec_desc.c
> +++ b/libavcodec/codec_desc.c
> @@ -3425,6 +3425,13 @@ static const AVCodecDescriptor codec_descriptors[] = {
>           .long_name = NULL_IF_CONFIG_SMALL("QOA (Quite OK Audio)"),
>           .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
>       },
> +    {
> +        .id        = AV_CODEC_ID_LC3,
> +        .type      = AVMEDIA_TYPE_AUDIO,
> +        .name      = "lc3",
> +        .long_name = NULL_IF_CONFIG_SMALL("LC3 (Low Complexity Communication Codec)"),
> +        .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
> +    },
>   
>       /* subtitle codecs */
>       {
> diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h
> index c8dc21da74..0ab1e34a61 100644
> --- a/libavcodec/codec_id.h
> +++ b/libavcodec/codec_id.h
> @@ -543,6 +543,7 @@ enum AVCodecID {
>       AV_CODEC_ID_AC4,
>       AV_CODEC_ID_OSQ,
>       AV_CODEC_ID_QOA,
> +    AV_CODEC_ID_LC3,
>   
>       /* subtitle codecs */
>       AV_CODEC_ID_FIRST_SUBTITLE = 0x17000,          ///< A dummy ID pointing at the start of subtitle codecs.
> diff --git a/libavcodec/liblc3dec.c b/libavcodec/liblc3dec.c
> new file mode 100644
> index 0000000000..75ab90f377
> --- /dev/null
> +++ b/libavcodec/liblc3dec.c
> @@ -0,0 +1,145 @@
> +/*
> + * LC3 decoder wrapper
> + * Copyright (C) 2024  Antoine Soulier <asoulier@google.com>
> + *
> + * This file is part of FFmpeg.
> + *
> + * Permission to use, copy, modify, and/or distribute this software for any
> + * purpose with or without fee is hereby granted, provided that the above
> + * copyright notice and this permission notice appear in all copies.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
> + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
> + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
> + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
> + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
> + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> + */
> +
> +#include <lc3.h>
> +
> +#include "libavutil/intreadwrite.h"
> +
> +#include "avcodec.h"
> +#include "codec.h"
> +#include "codec_internal.h"
> +#include "decode.h"
> +#include "internal.h"
> +
> +#define DECODER_MAX_CHANNELS  2
> +
> +typedef struct LibLC3DecContext {
> +    int frame_us, srate_hz, hr_mode;
> +    void *decoder_mem;
> +    lc3_decoder_t decoder[DECODER_MAX_CHANNELS];
> +    int64_t length;
> +} LibLC3DecContext;
> +
> +static av_cold int liblc3_decode_init(AVCodecContext *avctx)
> +{
> +    LibLC3DecContext *liblc3 = avctx->priv_data;
> +    int channels = avctx->ch_layout.nb_channels;
> +    int ep_mode;
> +    unsigned decoder_size;
> +
> +    if (avctx->extradata_size < 10)
> +        return AVERROR_INVALIDDATA;
> +
> +    liblc3->frame_us = AV_RL16(avctx->extradata + 0) * 10;
> +    liblc3->srate_hz = avctx->sample_rate;
> +    ep_mode          = AV_RL16(avctx->extradata + 2);
> +    liblc3->hr_mode  = AV_RL16(avctx->extradata + 4);
> +    liblc3->length   = AV_RL32(avctx->extradata + 6);
> +    if (ep_mode != 0) {
> +        av_log(avctx, AV_LOG_ERROR,
> +               "Error protection mode is not supported.\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    av_log(avctx, AV_LOG_INFO,
> +           "Decoding %.1f ms frames.\n", liblc3->frame_us / 1000.f);
> +    if (liblc3->hr_mode)
> +        av_log(avctx, AV_LOG_INFO, "High-resolution mode enabled.\n");
> +
> +    decoder_size = lc3_hr_decoder_size(
> +        liblc3->hr_mode, liblc3->frame_us, liblc3->srate_hz);
> +    if (!decoder_size)
> +        return AVERROR_INVALIDDATA;
> +
> +    liblc3->decoder_mem = av_malloc_array(channels, decoder_size);
> +    if (!liblc3->decoder_mem)
> +        return AVERROR(ENOMEM);
> +
> +    for (int ch = 0; ch < channels; ch++) {
> +        liblc3->decoder[ch] = lc3_hr_setup_decoder(
> +            liblc3->hr_mode, liblc3->frame_us, liblc3->srate_hz, 0,
> +            (char *)liblc3->decoder_mem + ch * decoder_size);
> +    }
> +
> +    avctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
> +    avctx->delay = lc3_hr_delay_samples(
> +        liblc3->hr_mode, liblc3->frame_us, liblc3->srate_hz);
> +    avctx->internal->skip_samples = avctx->delay;
> +
> +    return 0;
> +}
> +
> +static av_cold int liblc3_decode_close(AVCodecContext *avctx)
> +{
> +    LibLC3DecContext *liblc3 = avctx->priv_data;
> +
> +    av_freep(&liblc3->decoder_mem);
> +
> +    return 0;
> +}
> +
> +static int liblc3_decode(AVCodecContext *avctx, AVFrame *frame,
> +                         int *got_frame_ptr, AVPacket *avpkt)
> +{
> +    LibLC3DecContext *liblc3 = avctx->priv_data;
> +    int channels = avctx->ch_layout.nb_channels;
> +    uint8_t *in = avpkt->data;
> +    int block_bytes, ret;
> +
> +    frame->nb_samples = av_rescale(
> +        liblc3->frame_us, liblc3->srate_hz, 1000*1000);
> +    if ((ret = ff_get_buffer(avctx, frame, 0)) < 0)
> +        return ret;
> +
> +    block_bytes = avpkt->size;
> +    for (int ch = 0; ch < channels; ch++) {
> +        int nbytes = block_bytes / channels + (ch < block_bytes % channels);
> +
> +        ret = lc3_decode(liblc3->decoder[ch], in, nbytes,
> +                         LC3_PCM_FORMAT_FLOAT, frame->data[ch], 1);
> +        if (ret < 0)
> +            return AVERROR_INVALIDDATA;
> +
> +        in += nbytes;
> +    }
> +
> +    if (liblc3->length > 0) {
> +        int64_t end_pts = liblc3->length + avctx->delay;
> +        frame->nb_samples = FFMIN(frame->nb_samples,
> +                                  FFMAX(end_pts - frame->pts, 0));
> +    }
> +
> +    *got_frame_ptr = 1;
> +
> +    return avpkt->size;
> +}
> +
> +const FFCodec ff_liblc3_decoder = {
> +    .p.name         = "liblc3",
> +    CODEC_LONG_NAME("LC3 (Low Complexity Communication Codec)"),
> +    .p.type         = AVMEDIA_TYPE_AUDIO,
> +    .p.id           = AV_CODEC_ID_LC3,
> +    .p.capabilities = AV_CODEC_CAP_DR1,
> +    .p.wrapper_name = "liblc3",
> +    .priv_data_size = sizeof(LibLC3DecContext),
> +    .caps_internal  = FF_CODEC_CAP_INIT_CLEANUP,
> +    .init           = liblc3_decode_init,
> +    .close          = liblc3_decode_close,
> +    FF_CODEC_DECODE_CB(liblc3_decode),
> +};
> diff --git a/libavcodec/liblc3enc.c b/libavcodec/liblc3enc.c
> new file mode 100644
> index 0000000000..9731229e50
> --- /dev/null
> +++ b/libavcodec/liblc3enc.c
> @@ -0,0 +1,210 @@
> +/*
> + * LC3 encoder wrapper
> + * Copyright (C) 2024  Antoine Soulier <asoulier@google.com>
> + *
> + * This file is part of FFmpeg.
> + *
> + * Permission to use, copy, modify, and/or distribute this software for any
> + * purpose with or without fee is hereby granted, provided that the above
> + * copyright notice and this permission notice appear in all copies.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
> + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
> + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
> + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
> + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
> + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> + */
> +
> +#include <lc3.h>
> +
> +#include "libavutil/intreadwrite.h"
> +#include "libavutil/opt.h"
> +
> +#include "avcodec.h"
> +#include "codec.h"
> +#include "codec_internal.h"
> +#include "encode.h"
> +
> +#define ENCODER_MAX_CHANNELS  2
> +
> +typedef struct LibLC3EncOpts {
> +    float frame_duration;
> +    int hr_mode;
> +} LibLC3EncOpts;
> +
> +typedef struct LibLC3EncContext {
> +    const AVClass *av_class;
> +    LibLC3EncOpts opts;
> +    int block_bytes;
> +    void *encoder_mem;
> +    lc3_encoder_t encoder[ENCODER_MAX_CHANNELS];
> +    int delay_samples;
> +    int remaining_samples;
> +} LibLC3EncContext;
> +
> +static av_cold int liblc3_encode_init(AVCodecContext *avctx)
> +{
> +    LibLC3EncContext *liblc3 = avctx->priv_data;
> +    bool hr_mode = liblc3->opts.hr_mode;
> +    int frame_us = liblc3->opts.frame_duration * 1000;
> +    int srate_hz = avctx->sample_rate;
> +    int channels = avctx->ch_layout.nb_channels;
> +    int effective_bit_rate;
> +    unsigned encoder_size;
> +
> +    if (frame_us != 2500 && frame_us !=  5000 &&
> +        frame_us != 7500 && frame_us != 10000   ) {
> +        av_log(avctx, AV_LOG_ERROR,
> +               "Unsupported frame duration %.1f ms.\n", frame_us / 1000.f);
> +        return AVERROR(EINVAL);
> +    }
> +
> +    hr_mode |= srate_hz > 48000;
> +    hr_mode &= srate_hz >= 48000;
> +
> +    if (frame_us == 7500 && hr_mode) {
> +        av_log(avctx, AV_LOG_ERROR,
> +               "High-resolution mode is not supported with 7.5 ms frames.\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    av_log(avctx, AV_LOG_INFO, "Encoding %.1f ms frames.\n", frame_us / 1000.f);
> +    if (hr_mode)
> +        av_log(avctx, AV_LOG_INFO, "High-resolution mode is enabled.\n");
> +
> +    liblc3->block_bytes = lc3_hr_frame_block_bytes(
> +        hr_mode, frame_us, srate_hz, channels, avctx->bit_rate);
> +
> +    effective_bit_rate = lc3_hr_resolve_bitrate(
> +        hr_mode, frame_us, srate_hz, liblc3->block_bytes);
> +
> +    if (avctx->bit_rate != effective_bit_rate)
> +        av_log(avctx, AV_LOG_WARNING,
> +               "Bitrate changed to %d bps.\n", effective_bit_rate);
> +    avctx->bit_rate = effective_bit_rate;
> +
> +    encoder_size = lc3_hr_encoder_size(hr_mode, frame_us, srate_hz);
> +    if (!encoder_size)
> +        return AVERROR(EINVAL);
> +
> +    liblc3->encoder_mem = av_malloc_array(channels, encoder_size);
> +    if (!liblc3->encoder_mem)
> +        return AVERROR(ENOMEM);
> +
> +    for (int ch = 0; ch < channels; ch++) {
> +        liblc3->encoder[ch] = lc3_hr_setup_encoder(
> +            hr_mode, frame_us, srate_hz, 0,
> +            (char *)liblc3->encoder_mem + ch * encoder_size);
> +    }
> +
> +    avctx->extradata = av_mallocz(6 + AV_INPUT_BUFFER_PADDING_SIZE);

You're allocating 6 bytes for extradata but then the decoder expects it 
to be at least 10.
Even if you make the muxer expect that, it's not correct. The proper way 
to handle this is allocating all 10 bytes during init(), leaving the 
last 4 for length as 0, then at the end of encoding propagate a side 
data only packet with a NEW_EXTRADATA type side data entry where the 
length value is filled. The muxer should then look for it during 
write_trailer() and update the header with the new value if the output 
is seekable.
This will also let you write the values in extradata in the same order 
they are in the container.

> +    if (!avctx->extradata)
> +        return AVERROR(ENOMEM);
> +
> +    AV_WL16(avctx->extradata + 0, frame_us / 10);
> +    AV_WL16(avctx->extradata + 2, 0);
> +    AV_WL16(avctx->extradata + 4, hr_mode);
> +    avctx->extradata_size = 6;
> +
> +    avctx->frame_size = av_rescale(frame_us, srate_hz, 1000*1000);
> +    liblc3->delay_samples = lc3_hr_delay_samples(hr_mode, frame_us, srate_hz);
> +    liblc3->remaining_samples = 0;
> +
> +    return 0;
> +}
> +
> +static av_cold int liblc3_encode_close(AVCodecContext *avctx)
> +{
> +    LibLC3EncContext *liblc3 = avctx->priv_data;
> +
> +    av_freep(&liblc3->encoder_mem);
> +
> +    return 0;
> +}
> +
> +static int liblc3_encode(AVCodecContext *avctx, AVPacket *pkt,
> +                         const AVFrame *frame, int *got_packet_ptr)
> +{
> +    LibLC3EncContext *liblc3 = avctx->priv_data;
> +    int block_bytes = liblc3->block_bytes;
> +    int channels = avctx->ch_layout.nb_channels;
> +    void *zero_frame = NULL;
> +    uint8_t *data_ptr;
> +    int ret;
> +
> +    if ((ret = ff_get_encode_buffer(avctx, pkt, block_bytes, 0)) < 0)
> +        return ret;
> +
> +    if (frame) {
> +        int padding = frame->nb_samples - frame->duration;
> +        liblc3->remaining_samples = FFMAX(liblc3->delay_samples - padding, 0);
> +    } else {
> +        if (!liblc3->remaining_samples)
> +            return 0;
> +
> +        liblc3->remaining_samples = 0;
> +        zero_frame = av_mallocz(avctx->frame_size * sizeof(float));
> +        if (!zero_frame)
> +            return AVERROR(ENOMEM);
> +    }
> +
> +    data_ptr = pkt->data;
> +    for (int ch = 0; ch < channels; ch++) {
> +        const float *pcm = zero_frame ? zero_frame : frame->data[ch];
> +        int nbytes = block_bytes / channels + (ch < block_bytes % channels);
> +
> +        lc3_encode(liblc3->encoder[ch],
> +                   LC3_PCM_FORMAT_FLOAT, pcm, 1, nbytes, data_ptr);
> +
> +        data_ptr += nbytes;
> +    }
> +
> +    if (zero_frame)
> +        av_free(zero_frame);
> +
> +    *got_packet_ptr = 1;
> +
> +    return 0;
> +}
> +
> +#define OFFSET(x) offsetof(LibLC3EncContext, opts.x)
> +#define FLAGS AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
> +static const AVOption options[] = {
> +    { "frame_duration", "Duration of a frame in milliseconds",
> +        OFFSET(frame_duration), AV_OPT_TYPE_FLOAT,
> +        { .dbl = 10.0 }, 2.5, 10.0, FLAGS },
> +    { "high_resolution", "Enable High-Resolution mode (48 KHz or 96 KHz)",
> +        OFFSET(hr_mode), AV_OPT_TYPE_BOOL,
> +        { .i64 = 0 }, 0, 1, FLAGS },
> +    { NULL }
> +};
> +
> +static const AVClass class = {
> +    .class_name = "liblc3 encoder",
> +    .item_name  = av_default_item_name,
> +    .option     = options,
> +    .version    = LIBAVUTIL_VERSION_INT,
> +};
> +
> +const FFCodec ff_liblc3_encoder = {
> +    .p.name         = "liblc3",
> +    CODEC_LONG_NAME("LC3 (Low Complexity Communication Codec)"),
> +    .p.type         = AVMEDIA_TYPE_AUDIO,
> +    .p.id           = AV_CODEC_ID_LC3,
> +    .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY,
> +    .p.ch_layouts = (const AVChannelLayout[])
> +        { { AV_CHANNEL_ORDER_UNSPEC, 1 },
> +          { AV_CHANNEL_ORDER_UNSPEC, 2 }, { 0 } },
> +    .p.supported_samplerates = (const int [])
> +        { 96000, 48000, 32000, 24000, 16000, 8000, 0 },
> +    .p.sample_fmts = (const enum AVSampleFormat[])
> +        { AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_NONE },
> +    .p.priv_class   = &class,
> +    .p.wrapper_name = "liblc3",
> +    .priv_data_size = sizeof(LibLC3EncContext),
> +    .init           = liblc3_encode_init,
> +    .close          = liblc3_encode_close,
> +    FF_CODEC_ENCODE_CB(liblc3_encode),
> +};
Andreas Rheinhardt April 4, 2024, 4:27 p.m. UTC | #3
Antoine Soulier via ffmpeg-devel:
> The LC3 audio codec is the default codec of Bluetooth LE audio.
> This is a wrapper over the liblc3 library (https://github.com/google/liblc3).
> 
> Signed-off-by: Antoine Soulier <asoulier@google.com>
> ---
>  Changelog                 |   4 +
>  configure                 |   6 ++
>  doc/encoders.texi         |  57 +++++++++++
>  doc/general_contents.texi |  11 +-
>  libavcodec/Makefile       |   2 +
>  libavcodec/allcodecs.c    |   2 +
>  libavcodec/codec_desc.c   |   7 ++
>  libavcodec/codec_id.h     |   1 +
>  libavcodec/liblc3dec.c    | 145 ++++++++++++++++++++++++++
>  libavcodec/liblc3enc.c    | 210 ++++++++++++++++++++++++++++++++++++++
>  10 files changed, 444 insertions(+), 1 deletion(-)
>  create mode 100644 libavcodec/liblc3dec.c
>  create mode 100644 libavcodec/liblc3enc.c
> 
> diff --git a/Changelog b/Changelog
> index e83a00e35c..83a4cf7888 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -1,6 +1,10 @@
>  Entries are sorted chronologically from oldest to youngest within each release,
>  releases are sorted from youngest to oldest.
>  
> +version <next>:
> +- LC3/LC3plus decoding/encoding using external library liblc3
> +
> +
>  version 7.0:
>  - DXV DXT1 encoder
>  - LEAD MCMP decoder
> diff --git a/configure b/configure
> index 2d46ef0b9c..e5d9ba9f53 100755
> --- a/configure
> +++ b/configure
> @@ -244,6 +244,7 @@ External library support:
>    --enable-libjxl          enable JPEG XL de/encoding via libjxl [no]
>    --enable-libklvanc       enable Kernel Labs VANC processing [no]
>    --enable-libkvazaar      enable HEVC encoding via libkvazaar [no]
> +  --enable-liblc3          enable LC3 de/encoding via liblc3 [no]
>    --enable-liblensfun      enable lensfun lens correction [no]
>    --enable-libmodplug      enable ModPlug via libmodplug [no]
>    --enable-libmp3lame      enable MP3 encoding via libmp3lame [no]
> @@ -1926,6 +1927,7 @@ EXTERNAL_LIBRARY_LIST="
>      libjxl
>      libklvanc
>      libkvazaar
> +    liblc3
>      libmodplug
>      libmp3lame
>      libmysofa
> @@ -3499,6 +3501,9 @@ libilbc_encoder_deps="libilbc"
>  libjxl_decoder_deps="libjxl libjxl_threads"
>  libjxl_encoder_deps="libjxl libjxl_threads"
>  libkvazaar_encoder_deps="libkvazaar"
> +liblc3_decoder_deps="liblc3"
> +liblc3_encoder_deps="liblc3"
> +liblc3_encoder_select="audio_frame_queue"
>  libmodplug_demuxer_deps="libmodplug"
>  libmp3lame_encoder_deps="libmp3lame"
>  libmp3lame_encoder_select="audio_frame_queue mpegaudioheader"
> @@ -6869,6 +6874,7 @@ enabled libjxl            && require_pkg_config libjxl "libjxl >= 0.7.0" jxl/dec
>                               require_pkg_config libjxl_threads "libjxl_threads >= 0.7.0" jxl/thread_parallel_runner.h JxlThreadParallelRunner
>  enabled libklvanc         && require libklvanc libklvanc/vanc.h klvanc_context_create -lklvanc
>  enabled libkvazaar        && require_pkg_config libkvazaar "kvazaar >= 2.0.0" kvazaar.h kvz_api_get
> +enabled liblc3            && require_pkg_config liblc3 "lc3 >= 1.1.0" lc3.h lc3_hr_setup_encoder
>  enabled liblensfun        && require_pkg_config liblensfun lensfun lensfun.h lf_db_create
>  
>  if enabled libmfx && enabled libvpl; then
> diff --git a/doc/encoders.texi b/doc/encoders.texi
> index 7c223ed74c..66847191e1 100644
> --- a/doc/encoders.texi
> +++ b/doc/encoders.texi
> @@ -814,6 +814,63 @@ ffmpeg -i input.wav -c:a libfdk_aac -profile:a aac_he -b:a 64k output.m4a
>  @end example
>  @end itemize
>  
> +@anchor{liblc3-enc}
> +@section liblc3
> +
> +liblc3 LC3 (Low Complexity Communication Codec) encoder wrapper.
> +
> +Requires the presence of the liblc3 headers and library during configuration.
> +You need to explicitly configure the build with @code{--enable-liblc3}.
> +
> +This encoder has support for the Bluetooth SIG LC3 codec for the LE Audio
> +protocol, and the following features of LC3plus:
> +@itemize
> +@item
> +Frame duration of 2.5 and 5ms.
> +@item
> +High-Resolution mode, 48 KHz, and 96 kHz sampling rates.
> +@end itemize
> +
> +For more information see the liblc3 project at
> +@url{https://github.com/google/liblc3}.
> +
> +@subsection Options
> +
> +The following options are mapped on the shared FFmpeg codec options.
> +
> +@table @option
> +@item b @var{bitrate}
> +Set the bit rate in bits/s. This will determine the fixed size of the encoded
> +frames, for a selected frame duration.
> +
> +@item ar @var{frequency}
> +Set the audio sampling rate (in Hz).
> +
> +@item channels
> +Set the number of audio channels.
> +
> +@item frame_duration
> +Set the audio frame duration in milliseconds. Default value is 10ms.
> +Allowed frame durations are 2.5ms, 5ms, 7.5ms and 10ms.
> +LC3 (Bluetooth LE Audio), allows 7.5ms and 10ms; and LC3plus 2.5ms, 5ms
> +and 10ms.
> +
> +The 10ms frame duration is available in LC3 and LC3 plus standard.
> +In this mode, the produced bitstream can be referenced either as LC3 or LC3plus.
> +
> +@item high_resolution @var{boolean}
> +Enable the high-resolution mode if set to 1. The high-resolution mode is
> +available with all LC3plus frame durations and for a sampling rate of 48 KHz,
> +and 96 KHz.
> +
> +The encoder automatically turns off this mode at lower sampling rates and
> +activates it at 96 KHz.
> +
> +This mode should be preferred at high bitrates. In this mode, the audio
> +bandwidth is always up to the Nyquist frequency, compared to LC3 at 48 KHz,
> +which limits the bandwidth to 20 KHz.
> +@end table
> +
>  @anchor{libmp3lame}
>  @section libmp3lame
>  
> diff --git a/doc/general_contents.texi b/doc/general_contents.texi
> index f269cbd1a9..e7cf4f8239 100644
> --- a/doc/general_contents.texi
> +++ b/doc/general_contents.texi
> @@ -237,6 +237,14 @@ Go to @url{http://sourceforge.net/projects/opencore-amr/} and follow the
>  instructions for installing the library.
>  Then pass @code{--enable-libfdk-aac} to configure to enable it.
>  
> +@subsection LC3 library
> +
> +FFmpeg can make use of the Google LC3 library for LC3 decoding & encoding.
> +
> +Go to @url{https://github.com/google/liblc3/} and follow the instructions for
> +installing the library.
> +Then pass @code{--enable-liblc3} to configure to enable it.
> +
>  @section OpenH264
>  
>  FFmpeg can make use of the OpenH264 library for H.264 decoding and encoding.
> @@ -1300,7 +1308,8 @@ following image formats are supported:
>      @tab encoding and decoding supported through external library libilbc
>  @item IMC (Intel Music Coder)  @tab     @tab  X
>  @item Interplay ACM            @tab     @tab  X
> -@item MACE (Macintosh Audio Compression/Expansion) 3:1  @tab     @tab  X
> +@item LC3                    @tab E  @tab  E
> +    @tab supported through external library liblc3
>  @item MACE (Macintosh Audio Compression/Expansion) 6:1  @tab     @tab  X
>  @item Marian's A-pac audio     @tab     @tab  X
>  @item MI-SC4 (Micronas SC-4 Audio)  @tab     @tab  X
> diff --git a/libavcodec/Makefile b/libavcodec/Makefile
> index eef936944d..6323c98add 100644
> --- a/libavcodec/Makefile
> +++ b/libavcodec/Makefile
> @@ -1122,6 +1122,8 @@ OBJS-$(CONFIG_LIBILBC_ENCODER)            += libilbc.o
>  OBJS-$(CONFIG_LIBJXL_DECODER)             += libjxldec.o libjxl.o
>  OBJS-$(CONFIG_LIBJXL_ENCODER)             += libjxlenc.o libjxl.o
>  OBJS-$(CONFIG_LIBKVAZAAR_ENCODER)         += libkvazaar.o
> +OBJS-$(CONFIG_LIBLC3_ENCODER)             += liblc3enc.o
> +OBJS-$(CONFIG_LIBLC3_DECODER)             += liblc3dec.o
>  OBJS-$(CONFIG_LIBMP3LAME_ENCODER)         += libmp3lame.o
>  OBJS-$(CONFIG_LIBOPENCORE_AMRNB_DECODER)  += libopencore-amr.o
>  OBJS-$(CONFIG_LIBOPENCORE_AMRNB_ENCODER)  += libopencore-amr.o
> diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
> index 2386b450a6..f4705651fb 100644
> --- a/libavcodec/allcodecs.c
> +++ b/libavcodec/allcodecs.c
> @@ -776,6 +776,8 @@ extern const FFCodec ff_libilbc_encoder;
>  extern const FFCodec ff_libilbc_decoder;
>  extern const FFCodec ff_libjxl_decoder;
>  extern const FFCodec ff_libjxl_encoder;
> +extern const FFCodec ff_liblc3_encoder;
> +extern const FFCodec ff_liblc3_decoder;
>  extern const FFCodec ff_libmp3lame_encoder;
>  extern const FFCodec ff_libopencore_amrnb_encoder;
>  extern const FFCodec ff_libopencore_amrnb_decoder;
> diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
> index 3bab86db62..7dba61dc8b 100644
> --- a/libavcodec/codec_desc.c
> +++ b/libavcodec/codec_desc.c
> @@ -3425,6 +3425,13 @@ static const AVCodecDescriptor codec_descriptors[] = {
>          .long_name = NULL_IF_CONFIG_SMALL("QOA (Quite OK Audio)"),
>          .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
>      },
> +    {
> +        .id        = AV_CODEC_ID_LC3,
> +        .type      = AVMEDIA_TYPE_AUDIO,
> +        .name      = "lc3",
> +        .long_name = NULL_IF_CONFIG_SMALL("LC3 (Low Complexity Communication Codec)"),
> +        .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
> +    },
>  
>      /* subtitle codecs */
>      {
> diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h
> index c8dc21da74..0ab1e34a61 100644
> --- a/libavcodec/codec_id.h
> +++ b/libavcodec/codec_id.h
> @@ -543,6 +543,7 @@ enum AVCodecID {
>      AV_CODEC_ID_AC4,
>      AV_CODEC_ID_OSQ,
>      AV_CODEC_ID_QOA,
> +    AV_CODEC_ID_LC3,
>  
>      /* subtitle codecs */
>      AV_CODEC_ID_FIRST_SUBTITLE = 0x17000,          ///< A dummy ID pointing at the start of subtitle codecs.
> diff --git a/libavcodec/liblc3dec.c b/libavcodec/liblc3dec.c
> new file mode 100644
> index 0000000000..75ab90f377
> --- /dev/null
> +++ b/libavcodec/liblc3dec.c
> @@ -0,0 +1,145 @@
> +/*
> + * LC3 decoder wrapper
> + * Copyright (C) 2024  Antoine Soulier <asoulier@google.com>
> + *
> + * This file is part of FFmpeg.
> + *
> + * Permission to use, copy, modify, and/or distribute this software for any
> + * purpose with or without fee is hereby granted, provided that the above
> + * copyright notice and this permission notice appear in all copies.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
> + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
> + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
> + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
> + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
> + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> + */
> +
> +#include <lc3.h>
> +
> +#include "libavutil/intreadwrite.h"
> +
> +#include "avcodec.h"
> +#include "codec.h"
> +#include "codec_internal.h"
> +#include "decode.h"
> +#include "internal.h"
> +
> +#define DECODER_MAX_CHANNELS  2
> +
> +typedef struct LibLC3DecContext {
> +    int frame_us, srate_hz, hr_mode;
> +    void *decoder_mem;
> +    lc3_decoder_t decoder[DECODER_MAX_CHANNELS];
> +    int64_t length;
> +} LibLC3DecContext;
> +
> +static av_cold int liblc3_decode_init(AVCodecContext *avctx)
> +{
> +    LibLC3DecContext *liblc3 = avctx->priv_data;
> +    int channels = avctx->ch_layout.nb_channels;
> +    int ep_mode;
> +    unsigned decoder_size;
> +
> +    if (avctx->extradata_size < 10)
> +        return AVERROR_INVALIDDATA;
> +
> +    liblc3->frame_us = AV_RL16(avctx->extradata + 0) * 10;
> +    liblc3->srate_hz = avctx->sample_rate;
> +    ep_mode          = AV_RL16(avctx->extradata + 2);
> +    liblc3->hr_mode  = AV_RL16(avctx->extradata + 4);
> +    liblc3->length   = AV_RL32(avctx->extradata + 6);

Transporting length via extradata is a horrible design. We already have
a mechanism to do it properly: AV_PKT_DATA_SKIP_SAMPLES.

> +    if (ep_mode != 0) {
> +        av_log(avctx, AV_LOG_ERROR,
> +               "Error protection mode is not supported.\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    av_log(avctx, AV_LOG_INFO,
> +           "Decoding %.1f ms frames.\n", liblc3->frame_us / 1000.f);
> +    if (liblc3->hr_mode)
> +        av_log(avctx, AV_LOG_INFO, "High-resolution mode enabled.\n");
> +
> +    decoder_size = lc3_hr_decoder_size(
> +        liblc3->hr_mode, liblc3->frame_us, liblc3->srate_hz);
> +    if (!decoder_size)
> +        return AVERROR_INVALIDDATA;
> +
> +    liblc3->decoder_mem = av_malloc_array(channels, decoder_size);
> +    if (!liblc3->decoder_mem)
> +        return AVERROR(ENOMEM);
> +
> +    for (int ch = 0; ch < channels; ch++) {
> +        liblc3->decoder[ch] = lc3_hr_setup_decoder(
> +            liblc3->hr_mode, liblc3->frame_us, liblc3->srate_hz, 0,
> +            (char *)liblc3->decoder_mem + ch * decoder_size);
> +    }
> +
> +    avctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
> +    avctx->delay = lc3_hr_delay_samples(
> +        liblc3->hr_mode, liblc3->frame_us, liblc3->srate_hz);
> +    avctx->internal->skip_samples = avctx->delay;
> +
> +    return 0;
> +}
> +
> +static av_cold int liblc3_decode_close(AVCodecContext *avctx)
> +{
> +    LibLC3DecContext *liblc3 = avctx->priv_data;
> +
> +    av_freep(&liblc3->decoder_mem);
> +
> +    return 0;
> +}
> +
> +static int liblc3_decode(AVCodecContext *avctx, AVFrame *frame,
> +                         int *got_frame_ptr, AVPacket *avpkt)
> +{
> +    LibLC3DecContext *liblc3 = avctx->priv_data;
> +    int channels = avctx->ch_layout.nb_channels;
> +    uint8_t *in = avpkt->data;
> +    int block_bytes, ret;
> +
> +    frame->nb_samples = av_rescale(
> +        liblc3->frame_us, liblc3->srate_hz, 1000*1000);
> +    if ((ret = ff_get_buffer(avctx, frame, 0)) < 0)
> +        return ret;
> +
> +    block_bytes = avpkt->size;
> +    for (int ch = 0; ch < channels; ch++) {
> +        int nbytes = block_bytes / channels + (ch < block_bytes % channels);
> +
> +        ret = lc3_decode(liblc3->decoder[ch], in, nbytes,
> +                         LC3_PCM_FORMAT_FLOAT, frame->data[ch], 1);
> +        if (ret < 0)
> +            return AVERROR_INVALIDDATA;
> +
> +        in += nbytes;
> +    }
> +
> +    if (liblc3->length > 0) {
> +        int64_t end_pts = liblc3->length + avctx->delay;
> +        frame->nb_samples = FFMIN(frame->nb_samples,
> +                                  FFMAX(end_pts - frame->pts, 0));
> +    }
> +
> +    *got_frame_ptr = 1;
> +
> +    return avpkt->size;
> +}
> +
> +const FFCodec ff_liblc3_decoder = {
> +    .p.name         = "liblc3",
> +    CODEC_LONG_NAME("LC3 (Low Complexity Communication Codec)"),
> +    .p.type         = AVMEDIA_TYPE_AUDIO,
> +    .p.id           = AV_CODEC_ID_LC3,
> +    .p.capabilities = AV_CODEC_CAP_DR1,
> +    .p.wrapper_name = "liblc3",
> +    .priv_data_size = sizeof(LibLC3DecContext),
> +    .caps_internal  = FF_CODEC_CAP_INIT_CLEANUP,
> +    .init           = liblc3_decode_init,
> +    .close          = liblc3_decode_close,
> +    FF_CODEC_DECODE_CB(liblc3_decode),
> +};
> diff --git a/libavcodec/liblc3enc.c b/libavcodec/liblc3enc.c
> new file mode 100644
> index 0000000000..9731229e50
> --- /dev/null
> +++ b/libavcodec/liblc3enc.c
> @@ -0,0 +1,210 @@
> +/*
> + * LC3 encoder wrapper
> + * Copyright (C) 2024  Antoine Soulier <asoulier@google.com>
> + *
> + * This file is part of FFmpeg.
> + *
> + * Permission to use, copy, modify, and/or distribute this software for any
> + * purpose with or without fee is hereby granted, provided that the above
> + * copyright notice and this permission notice appear in all copies.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
> + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
> + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
> + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
> + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
> + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> + */
> +
> +#include <lc3.h>
> +
> +#include "libavutil/intreadwrite.h"
> +#include "libavutil/opt.h"
> +
> +#include "avcodec.h"
> +#include "codec.h"
> +#include "codec_internal.h"
> +#include "encode.h"
> +
> +#define ENCODER_MAX_CHANNELS  2
> +
> +typedef struct LibLC3EncOpts {
> +    float frame_duration;
> +    int hr_mode;
> +} LibLC3EncOpts;
> +
> +typedef struct LibLC3EncContext {
> +    const AVClass *av_class;
> +    LibLC3EncOpts opts;
> +    int block_bytes;
> +    void *encoder_mem;
> +    lc3_encoder_t encoder[ENCODER_MAX_CHANNELS];
> +    int delay_samples;
> +    int remaining_samples;
> +} LibLC3EncContext;
> +
> +static av_cold int liblc3_encode_init(AVCodecContext *avctx)
> +{
> +    LibLC3EncContext *liblc3 = avctx->priv_data;
> +    bool hr_mode = liblc3->opts.hr_mode;
> +    int frame_us = liblc3->opts.frame_duration * 1000;
> +    int srate_hz = avctx->sample_rate;
> +    int channels = avctx->ch_layout.nb_channels;
> +    int effective_bit_rate;
> +    unsigned encoder_size;
> +
> +    if (frame_us != 2500 && frame_us !=  5000 &&
> +        frame_us != 7500 && frame_us != 10000   ) {
> +        av_log(avctx, AV_LOG_ERROR,
> +               "Unsupported frame duration %.1f ms.\n", frame_us / 1000.f);
> +        return AVERROR(EINVAL);
> +    }
> +
> +    hr_mode |= srate_hz > 48000;
> +    hr_mode &= srate_hz >= 48000;
> +
> +    if (frame_us == 7500 && hr_mode) {
> +        av_log(avctx, AV_LOG_ERROR,
> +               "High-resolution mode is not supported with 7.5 ms frames.\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    av_log(avctx, AV_LOG_INFO, "Encoding %.1f ms frames.\n", frame_us / 1000.f);
> +    if (hr_mode)
> +        av_log(avctx, AV_LOG_INFO, "High-resolution mode is enabled.\n");
> +
> +    liblc3->block_bytes = lc3_hr_frame_block_bytes(
> +        hr_mode, frame_us, srate_hz, channels, avctx->bit_rate);
> +
> +    effective_bit_rate = lc3_hr_resolve_bitrate(
> +        hr_mode, frame_us, srate_hz, liblc3->block_bytes);
> +
> +    if (avctx->bit_rate != effective_bit_rate)
> +        av_log(avctx, AV_LOG_WARNING,
> +               "Bitrate changed to %d bps.\n", effective_bit_rate);
> +    avctx->bit_rate = effective_bit_rate;
> +
> +    encoder_size = lc3_hr_encoder_size(hr_mode, frame_us, srate_hz);
> +    if (!encoder_size)
> +        return AVERROR(EINVAL);
> +
> +    liblc3->encoder_mem = av_malloc_array(channels, encoder_size);
> +    if (!liblc3->encoder_mem)
> +        return AVERROR(ENOMEM);
> +
> +    for (int ch = 0; ch < channels; ch++) {
> +        liblc3->encoder[ch] = lc3_hr_setup_encoder(
> +            hr_mode, frame_us, srate_hz, 0,
> +            (char *)liblc3->encoder_mem + ch * encoder_size);
> +    }
> +
> +    avctx->extradata = av_mallocz(6 + AV_INPUT_BUFFER_PADDING_SIZE);
> +    if (!avctx->extradata)
> +        return AVERROR(ENOMEM);

You are creating extradata that the decoder won't accept.

> +
> +    AV_WL16(avctx->extradata + 0, frame_us / 10);
> +    AV_WL16(avctx->extradata + 2, 0);
> +    AV_WL16(avctx->extradata + 4, hr_mode);
> +    avctx->extradata_size = 6;
> +
> +    avctx->frame_size = av_rescale(frame_us, srate_hz, 1000*1000);
> +    liblc3->delay_samples = lc3_hr_delay_samples(hr_mode, frame_us, srate_hz);
> +    liblc3->remaining_samples = 0;
> +
> +    return 0;
> +}
> +
> +static av_cold int liblc3_encode_close(AVCodecContext *avctx)
> +{
> +    LibLC3EncContext *liblc3 = avctx->priv_data;
> +
> +    av_freep(&liblc3->encoder_mem);
> +
> +    return 0;
> +}
> +
> +static int liblc3_encode(AVCodecContext *avctx, AVPacket *pkt,
> +                         const AVFrame *frame, int *got_packet_ptr)
> +{
> +    LibLC3EncContext *liblc3 = avctx->priv_data;
> +    int block_bytes = liblc3->block_bytes;
> +    int channels = avctx->ch_layout.nb_channels;
> +    void *zero_frame = NULL;
> +    uint8_t *data_ptr;
> +    int ret;
> +
> +    if ((ret = ff_get_encode_buffer(avctx, pkt, block_bytes, 0)) < 0)
> +        return ret;
> +
> +    if (frame) {
> +        int padding = frame->nb_samples - frame->duration;
> +        liblc3->remaining_samples = FFMAX(liblc3->delay_samples - padding, 0);
> +    } else {
> +        if (!liblc3->remaining_samples)
> +            return 0;
> +
> +        liblc3->remaining_samples = 0;
> +        zero_frame = av_mallocz(avctx->frame_size * sizeof(float));
> +        if (!zero_frame)
> +            return AVERROR(ENOMEM);
> +    }
> +
> +    data_ptr = pkt->data;
> +    for (int ch = 0; ch < channels; ch++) {
> +        const float *pcm = zero_frame ? zero_frame : frame->data[ch];
> +        int nbytes = block_bytes / channels + (ch < block_bytes % channels);
> +
> +        lc3_encode(liblc3->encoder[ch],
> +                   LC3_PCM_FORMAT_FLOAT, pcm, 1, nbytes, data_ptr);
> +
> +        data_ptr += nbytes;
> +    }
> +
> +    if (zero_frame)
> +        av_free(zero_frame);
> +
> +    *got_packet_ptr = 1;
> +
> +    return 0;
> +}
> +
> +#define OFFSET(x) offsetof(LibLC3EncContext, opts.x)
> +#define FLAGS AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
> +static const AVOption options[] = {
> +    { "frame_duration", "Duration of a frame in milliseconds",
> +        OFFSET(frame_duration), AV_OPT_TYPE_FLOAT,
> +        { .dbl = 10.0 }, 2.5, 10.0, FLAGS },
> +    { "high_resolution", "Enable High-Resolution mode (48 KHz or 96 KHz)",
> +        OFFSET(hr_mode), AV_OPT_TYPE_BOOL,
> +        { .i64 = 0 }, 0, 1, FLAGS },
> +    { NULL }
> +};
> +
> +static const AVClass class = {
> +    .class_name = "liblc3 encoder",
> +    .item_name  = av_default_item_name,
> +    .option     = options,
> +    .version    = LIBAVUTIL_VERSION_INT,
> +};
> +
> +const FFCodec ff_liblc3_encoder = {
> +    .p.name         = "liblc3",
> +    CODEC_LONG_NAME("LC3 (Low Complexity Communication Codec)"),
> +    .p.type         = AVMEDIA_TYPE_AUDIO,
> +    .p.id           = AV_CODEC_ID_LC3,
> +    .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY,
> +    .p.ch_layouts = (const AVChannelLayout[])
> +        { { AV_CHANNEL_ORDER_UNSPEC, 1 },
> +          { AV_CHANNEL_ORDER_UNSPEC, 2 }, { 0 } },
> +    .p.supported_samplerates = (const int [])
> +        { 96000, 48000, 32000, 24000, 16000, 8000, 0 },
> +    .p.sample_fmts = (const enum AVSampleFormat[])
> +        { AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_NONE },
> +    .p.priv_class   = &class,
> +    .p.wrapper_name = "liblc3",
> +    .priv_data_size = sizeof(LibLC3EncContext),
> +    .init           = liblc3_encode_init,
> +    .close          = liblc3_encode_close,
> +    FF_CODEC_ENCODE_CB(liblc3_encode),
> +};
diff mbox series

Patch

diff --git a/Changelog b/Changelog
index e83a00e35c..83a4cf7888 100644
--- a/Changelog
+++ b/Changelog
@@ -1,6 +1,10 @@ 
 Entries are sorted chronologically from oldest to youngest within each release,
 releases are sorted from youngest to oldest.
 
+version <next>:
+- LC3/LC3plus decoding/encoding using external library liblc3
+
+
 version 7.0:
 - DXV DXT1 encoder
 - LEAD MCMP decoder
diff --git a/configure b/configure
index 2d46ef0b9c..e5d9ba9f53 100755
--- a/configure
+++ b/configure
@@ -244,6 +244,7 @@  External library support:
   --enable-libjxl          enable JPEG XL de/encoding via libjxl [no]
   --enable-libklvanc       enable Kernel Labs VANC processing [no]
   --enable-libkvazaar      enable HEVC encoding via libkvazaar [no]
+  --enable-liblc3          enable LC3 de/encoding via liblc3 [no]
   --enable-liblensfun      enable lensfun lens correction [no]
   --enable-libmodplug      enable ModPlug via libmodplug [no]
   --enable-libmp3lame      enable MP3 encoding via libmp3lame [no]
@@ -1926,6 +1927,7 @@  EXTERNAL_LIBRARY_LIST="
     libjxl
     libklvanc
     libkvazaar
+    liblc3
     libmodplug
     libmp3lame
     libmysofa
@@ -3499,6 +3501,9 @@  libilbc_encoder_deps="libilbc"
 libjxl_decoder_deps="libjxl libjxl_threads"
 libjxl_encoder_deps="libjxl libjxl_threads"
 libkvazaar_encoder_deps="libkvazaar"
+liblc3_decoder_deps="liblc3"
+liblc3_encoder_deps="liblc3"
+liblc3_encoder_select="audio_frame_queue"
 libmodplug_demuxer_deps="libmodplug"
 libmp3lame_encoder_deps="libmp3lame"
 libmp3lame_encoder_select="audio_frame_queue mpegaudioheader"
@@ -6869,6 +6874,7 @@  enabled libjxl            && require_pkg_config libjxl "libjxl >= 0.7.0" jxl/dec
                              require_pkg_config libjxl_threads "libjxl_threads >= 0.7.0" jxl/thread_parallel_runner.h JxlThreadParallelRunner
 enabled libklvanc         && require libklvanc libklvanc/vanc.h klvanc_context_create -lklvanc
 enabled libkvazaar        && require_pkg_config libkvazaar "kvazaar >= 2.0.0" kvazaar.h kvz_api_get
+enabled liblc3            && require_pkg_config liblc3 "lc3 >= 1.1.0" lc3.h lc3_hr_setup_encoder
 enabled liblensfun        && require_pkg_config liblensfun lensfun lensfun.h lf_db_create
 
 if enabled libmfx && enabled libvpl; then
diff --git a/doc/encoders.texi b/doc/encoders.texi
index 7c223ed74c..66847191e1 100644
--- a/doc/encoders.texi
+++ b/doc/encoders.texi
@@ -814,6 +814,63 @@  ffmpeg -i input.wav -c:a libfdk_aac -profile:a aac_he -b:a 64k output.m4a
 @end example
 @end itemize
 
+@anchor{liblc3-enc}
+@section liblc3
+
+liblc3 LC3 (Low Complexity Communication Codec) encoder wrapper.
+
+Requires the presence of the liblc3 headers and library during configuration.
+You need to explicitly configure the build with @code{--enable-liblc3}.
+
+This encoder has support for the Bluetooth SIG LC3 codec for the LE Audio
+protocol, and the following features of LC3plus:
+@itemize
+@item
+Frame duration of 2.5 and 5ms.
+@item
+High-Resolution mode, 48 KHz, and 96 kHz sampling rates.
+@end itemize
+
+For more information see the liblc3 project at
+@url{https://github.com/google/liblc3}.
+
+@subsection Options
+
+The following options are mapped on the shared FFmpeg codec options.
+
+@table @option
+@item b @var{bitrate}
+Set the bit rate in bits/s. This will determine the fixed size of the encoded
+frames, for a selected frame duration.
+
+@item ar @var{frequency}
+Set the audio sampling rate (in Hz).
+
+@item channels
+Set the number of audio channels.
+
+@item frame_duration
+Set the audio frame duration in milliseconds. Default value is 10ms.
+Allowed frame durations are 2.5ms, 5ms, 7.5ms and 10ms.
+LC3 (Bluetooth LE Audio), allows 7.5ms and 10ms; and LC3plus 2.5ms, 5ms
+and 10ms.
+
+The 10ms frame duration is available in LC3 and LC3 plus standard.
+In this mode, the produced bitstream can be referenced either as LC3 or LC3plus.
+
+@item high_resolution @var{boolean}
+Enable the high-resolution mode if set to 1. The high-resolution mode is
+available with all LC3plus frame durations and for a sampling rate of 48 KHz,
+and 96 KHz.
+
+The encoder automatically turns off this mode at lower sampling rates and
+activates it at 96 KHz.
+
+This mode should be preferred at high bitrates. In this mode, the audio
+bandwidth is always up to the Nyquist frequency, compared to LC3 at 48 KHz,
+which limits the bandwidth to 20 KHz.
+@end table
+
 @anchor{libmp3lame}
 @section libmp3lame
 
diff --git a/doc/general_contents.texi b/doc/general_contents.texi
index f269cbd1a9..e7cf4f8239 100644
--- a/doc/general_contents.texi
+++ b/doc/general_contents.texi
@@ -237,6 +237,14 @@  Go to @url{http://sourceforge.net/projects/opencore-amr/} and follow the
 instructions for installing the library.
 Then pass @code{--enable-libfdk-aac} to configure to enable it.
 
+@subsection LC3 library
+
+FFmpeg can make use of the Google LC3 library for LC3 decoding & encoding.
+
+Go to @url{https://github.com/google/liblc3/} and follow the instructions for
+installing the library.
+Then pass @code{--enable-liblc3} to configure to enable it.
+
 @section OpenH264
 
 FFmpeg can make use of the OpenH264 library for H.264 decoding and encoding.
@@ -1300,7 +1308,8 @@  following image formats are supported:
     @tab encoding and decoding supported through external library libilbc
 @item IMC (Intel Music Coder)  @tab     @tab  X
 @item Interplay ACM            @tab     @tab  X
-@item MACE (Macintosh Audio Compression/Expansion) 3:1  @tab     @tab  X
+@item LC3                    @tab E  @tab  E
+    @tab supported through external library liblc3
 @item MACE (Macintosh Audio Compression/Expansion) 6:1  @tab     @tab  X
 @item Marian's A-pac audio     @tab     @tab  X
 @item MI-SC4 (Micronas SC-4 Audio)  @tab     @tab  X
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index eef936944d..6323c98add 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -1122,6 +1122,8 @@  OBJS-$(CONFIG_LIBILBC_ENCODER)            += libilbc.o
 OBJS-$(CONFIG_LIBJXL_DECODER)             += libjxldec.o libjxl.o
 OBJS-$(CONFIG_LIBJXL_ENCODER)             += libjxlenc.o libjxl.o
 OBJS-$(CONFIG_LIBKVAZAAR_ENCODER)         += libkvazaar.o
+OBJS-$(CONFIG_LIBLC3_ENCODER)             += liblc3enc.o
+OBJS-$(CONFIG_LIBLC3_DECODER)             += liblc3dec.o
 OBJS-$(CONFIG_LIBMP3LAME_ENCODER)         += libmp3lame.o
 OBJS-$(CONFIG_LIBOPENCORE_AMRNB_DECODER)  += libopencore-amr.o
 OBJS-$(CONFIG_LIBOPENCORE_AMRNB_ENCODER)  += libopencore-amr.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 2386b450a6..f4705651fb 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -776,6 +776,8 @@  extern const FFCodec ff_libilbc_encoder;
 extern const FFCodec ff_libilbc_decoder;
 extern const FFCodec ff_libjxl_decoder;
 extern const FFCodec ff_libjxl_encoder;
+extern const FFCodec ff_liblc3_encoder;
+extern const FFCodec ff_liblc3_decoder;
 extern const FFCodec ff_libmp3lame_encoder;
 extern const FFCodec ff_libopencore_amrnb_encoder;
 extern const FFCodec ff_libopencore_amrnb_decoder;
diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index 3bab86db62..7dba61dc8b 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -3425,6 +3425,13 @@  static const AVCodecDescriptor codec_descriptors[] = {
         .long_name = NULL_IF_CONFIG_SMALL("QOA (Quite OK Audio)"),
         .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
     },
+    {
+        .id        = AV_CODEC_ID_LC3,
+        .type      = AVMEDIA_TYPE_AUDIO,
+        .name      = "lc3",
+        .long_name = NULL_IF_CONFIG_SMALL("LC3 (Low Complexity Communication Codec)"),
+        .props     = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
+    },
 
     /* subtitle codecs */
     {
diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h
index c8dc21da74..0ab1e34a61 100644
--- a/libavcodec/codec_id.h
+++ b/libavcodec/codec_id.h
@@ -543,6 +543,7 @@  enum AVCodecID {
     AV_CODEC_ID_AC4,
     AV_CODEC_ID_OSQ,
     AV_CODEC_ID_QOA,
+    AV_CODEC_ID_LC3,
 
     /* subtitle codecs */
     AV_CODEC_ID_FIRST_SUBTITLE = 0x17000,          ///< A dummy ID pointing at the start of subtitle codecs.
diff --git a/libavcodec/liblc3dec.c b/libavcodec/liblc3dec.c
new file mode 100644
index 0000000000..75ab90f377
--- /dev/null
+++ b/libavcodec/liblc3dec.c
@@ -0,0 +1,145 @@ 
+/*
+ * LC3 decoder wrapper
+ * Copyright (C) 2024  Antoine Soulier <asoulier@google.com>
+ *
+ * This file is part of FFmpeg.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <lc3.h>
+
+#include "libavutil/intreadwrite.h"
+
+#include "avcodec.h"
+#include "codec.h"
+#include "codec_internal.h"
+#include "decode.h"
+#include "internal.h"
+
+#define DECODER_MAX_CHANNELS  2
+
+typedef struct LibLC3DecContext {
+    int frame_us, srate_hz, hr_mode;
+    void *decoder_mem;
+    lc3_decoder_t decoder[DECODER_MAX_CHANNELS];
+    int64_t length;
+} LibLC3DecContext;
+
+static av_cold int liblc3_decode_init(AVCodecContext *avctx)
+{
+    LibLC3DecContext *liblc3 = avctx->priv_data;
+    int channels = avctx->ch_layout.nb_channels;
+    int ep_mode;
+    unsigned decoder_size;
+
+    if (avctx->extradata_size < 10)
+        return AVERROR_INVALIDDATA;
+
+    liblc3->frame_us = AV_RL16(avctx->extradata + 0) * 10;
+    liblc3->srate_hz = avctx->sample_rate;
+    ep_mode          = AV_RL16(avctx->extradata + 2);
+    liblc3->hr_mode  = AV_RL16(avctx->extradata + 4);
+    liblc3->length   = AV_RL32(avctx->extradata + 6);
+    if (ep_mode != 0) {
+        av_log(avctx, AV_LOG_ERROR,
+               "Error protection mode is not supported.\n");
+        return AVERROR(EINVAL);
+    }
+
+    av_log(avctx, AV_LOG_INFO,
+           "Decoding %.1f ms frames.\n", liblc3->frame_us / 1000.f);
+    if (liblc3->hr_mode)
+        av_log(avctx, AV_LOG_INFO, "High-resolution mode enabled.\n");
+
+    decoder_size = lc3_hr_decoder_size(
+        liblc3->hr_mode, liblc3->frame_us, liblc3->srate_hz);
+    if (!decoder_size)
+        return AVERROR_INVALIDDATA;
+
+    liblc3->decoder_mem = av_malloc_array(channels, decoder_size);
+    if (!liblc3->decoder_mem)
+        return AVERROR(ENOMEM);
+
+    for (int ch = 0; ch < channels; ch++) {
+        liblc3->decoder[ch] = lc3_hr_setup_decoder(
+            liblc3->hr_mode, liblc3->frame_us, liblc3->srate_hz, 0,
+            (char *)liblc3->decoder_mem + ch * decoder_size);
+    }
+
+    avctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
+    avctx->delay = lc3_hr_delay_samples(
+        liblc3->hr_mode, liblc3->frame_us, liblc3->srate_hz);
+    avctx->internal->skip_samples = avctx->delay;
+
+    return 0;
+}
+
+static av_cold int liblc3_decode_close(AVCodecContext *avctx)
+{
+    LibLC3DecContext *liblc3 = avctx->priv_data;
+
+    av_freep(&liblc3->decoder_mem);
+
+    return 0;
+}
+
+static int liblc3_decode(AVCodecContext *avctx, AVFrame *frame,
+                         int *got_frame_ptr, AVPacket *avpkt)
+{
+    LibLC3DecContext *liblc3 = avctx->priv_data;
+    int channels = avctx->ch_layout.nb_channels;
+    uint8_t *in = avpkt->data;
+    int block_bytes, ret;
+
+    frame->nb_samples = av_rescale(
+        liblc3->frame_us, liblc3->srate_hz, 1000*1000);
+    if ((ret = ff_get_buffer(avctx, frame, 0)) < 0)
+        return ret;
+
+    block_bytes = avpkt->size;
+    for (int ch = 0; ch < channels; ch++) {
+        int nbytes = block_bytes / channels + (ch < block_bytes % channels);
+
+        ret = lc3_decode(liblc3->decoder[ch], in, nbytes,
+                         LC3_PCM_FORMAT_FLOAT, frame->data[ch], 1);
+        if (ret < 0)
+            return AVERROR_INVALIDDATA;
+
+        in += nbytes;
+    }
+
+    if (liblc3->length > 0) {
+        int64_t end_pts = liblc3->length + avctx->delay;
+        frame->nb_samples = FFMIN(frame->nb_samples,
+                                  FFMAX(end_pts - frame->pts, 0));
+    }
+
+    *got_frame_ptr = 1;
+
+    return avpkt->size;
+}
+
+const FFCodec ff_liblc3_decoder = {
+    .p.name         = "liblc3",
+    CODEC_LONG_NAME("LC3 (Low Complexity Communication Codec)"),
+    .p.type         = AVMEDIA_TYPE_AUDIO,
+    .p.id           = AV_CODEC_ID_LC3,
+    .p.capabilities = AV_CODEC_CAP_DR1,
+    .p.wrapper_name = "liblc3",
+    .priv_data_size = sizeof(LibLC3DecContext),
+    .caps_internal  = FF_CODEC_CAP_INIT_CLEANUP,
+    .init           = liblc3_decode_init,
+    .close          = liblc3_decode_close,
+    FF_CODEC_DECODE_CB(liblc3_decode),
+};
diff --git a/libavcodec/liblc3enc.c b/libavcodec/liblc3enc.c
new file mode 100644
index 0000000000..9731229e50
--- /dev/null
+++ b/libavcodec/liblc3enc.c
@@ -0,0 +1,210 @@ 
+/*
+ * LC3 encoder wrapper
+ * Copyright (C) 2024  Antoine Soulier <asoulier@google.com>
+ *
+ * This file is part of FFmpeg.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <lc3.h>
+
+#include "libavutil/intreadwrite.h"
+#include "libavutil/opt.h"
+
+#include "avcodec.h"
+#include "codec.h"
+#include "codec_internal.h"
+#include "encode.h"
+
+#define ENCODER_MAX_CHANNELS  2
+
+typedef struct LibLC3EncOpts {
+    float frame_duration;
+    int hr_mode;
+} LibLC3EncOpts;
+
+typedef struct LibLC3EncContext {
+    const AVClass *av_class;
+    LibLC3EncOpts opts;
+    int block_bytes;
+    void *encoder_mem;
+    lc3_encoder_t encoder[ENCODER_MAX_CHANNELS];
+    int delay_samples;
+    int remaining_samples;
+} LibLC3EncContext;
+
+static av_cold int liblc3_encode_init(AVCodecContext *avctx)
+{
+    LibLC3EncContext *liblc3 = avctx->priv_data;
+    bool hr_mode = liblc3->opts.hr_mode;
+    int frame_us = liblc3->opts.frame_duration * 1000;
+    int srate_hz = avctx->sample_rate;
+    int channels = avctx->ch_layout.nb_channels;
+    int effective_bit_rate;
+    unsigned encoder_size;
+
+    if (frame_us != 2500 && frame_us !=  5000 &&
+        frame_us != 7500 && frame_us != 10000   ) {
+        av_log(avctx, AV_LOG_ERROR,
+               "Unsupported frame duration %.1f ms.\n", frame_us / 1000.f);
+        return AVERROR(EINVAL);
+    }
+
+    hr_mode |= srate_hz > 48000;
+    hr_mode &= srate_hz >= 48000;
+
+    if (frame_us == 7500 && hr_mode) {
+        av_log(avctx, AV_LOG_ERROR,
+               "High-resolution mode is not supported with 7.5 ms frames.\n");
+        return AVERROR(EINVAL);
+    }
+
+    av_log(avctx, AV_LOG_INFO, "Encoding %.1f ms frames.\n", frame_us / 1000.f);
+    if (hr_mode)
+        av_log(avctx, AV_LOG_INFO, "High-resolution mode is enabled.\n");
+
+    liblc3->block_bytes = lc3_hr_frame_block_bytes(
+        hr_mode, frame_us, srate_hz, channels, avctx->bit_rate);
+
+    effective_bit_rate = lc3_hr_resolve_bitrate(
+        hr_mode, frame_us, srate_hz, liblc3->block_bytes);
+
+    if (avctx->bit_rate != effective_bit_rate)
+        av_log(avctx, AV_LOG_WARNING,
+               "Bitrate changed to %d bps.\n", effective_bit_rate);
+    avctx->bit_rate = effective_bit_rate;
+
+    encoder_size = lc3_hr_encoder_size(hr_mode, frame_us, srate_hz);
+    if (!encoder_size)
+        return AVERROR(EINVAL);
+
+    liblc3->encoder_mem = av_malloc_array(channels, encoder_size);
+    if (!liblc3->encoder_mem)
+        return AVERROR(ENOMEM);
+
+    for (int ch = 0; ch < channels; ch++) {
+        liblc3->encoder[ch] = lc3_hr_setup_encoder(
+            hr_mode, frame_us, srate_hz, 0,
+            (char *)liblc3->encoder_mem + ch * encoder_size);
+    }
+
+    avctx->extradata = av_mallocz(6 + AV_INPUT_BUFFER_PADDING_SIZE);
+    if (!avctx->extradata)
+        return AVERROR(ENOMEM);
+
+    AV_WL16(avctx->extradata + 0, frame_us / 10);
+    AV_WL16(avctx->extradata + 2, 0);
+    AV_WL16(avctx->extradata + 4, hr_mode);
+    avctx->extradata_size = 6;
+
+    avctx->frame_size = av_rescale(frame_us, srate_hz, 1000*1000);
+    liblc3->delay_samples = lc3_hr_delay_samples(hr_mode, frame_us, srate_hz);
+    liblc3->remaining_samples = 0;
+
+    return 0;
+}
+
+static av_cold int liblc3_encode_close(AVCodecContext *avctx)
+{
+    LibLC3EncContext *liblc3 = avctx->priv_data;
+
+    av_freep(&liblc3->encoder_mem);
+
+    return 0;
+}
+
+static int liblc3_encode(AVCodecContext *avctx, AVPacket *pkt,
+                         const AVFrame *frame, int *got_packet_ptr)
+{
+    LibLC3EncContext *liblc3 = avctx->priv_data;
+    int block_bytes = liblc3->block_bytes;
+    int channels = avctx->ch_layout.nb_channels;
+    void *zero_frame = NULL;
+    uint8_t *data_ptr;
+    int ret;
+
+    if ((ret = ff_get_encode_buffer(avctx, pkt, block_bytes, 0)) < 0)
+        return ret;
+
+    if (frame) {
+        int padding = frame->nb_samples - frame->duration;
+        liblc3->remaining_samples = FFMAX(liblc3->delay_samples - padding, 0);
+    } else {
+        if (!liblc3->remaining_samples)
+            return 0;
+
+        liblc3->remaining_samples = 0;
+        zero_frame = av_mallocz(avctx->frame_size * sizeof(float));
+        if (!zero_frame)
+            return AVERROR(ENOMEM);
+    }
+
+    data_ptr = pkt->data;
+    for (int ch = 0; ch < channels; ch++) {
+        const float *pcm = zero_frame ? zero_frame : frame->data[ch];
+        int nbytes = block_bytes / channels + (ch < block_bytes % channels);
+
+        lc3_encode(liblc3->encoder[ch],
+                   LC3_PCM_FORMAT_FLOAT, pcm, 1, nbytes, data_ptr);
+
+        data_ptr += nbytes;
+    }
+
+    if (zero_frame)
+        av_free(zero_frame);
+
+    *got_packet_ptr = 1;
+
+    return 0;
+}
+
+#define OFFSET(x) offsetof(LibLC3EncContext, opts.x)
+#define FLAGS AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_ENCODING_PARAM
+static const AVOption options[] = {
+    { "frame_duration", "Duration of a frame in milliseconds",
+        OFFSET(frame_duration), AV_OPT_TYPE_FLOAT,
+        { .dbl = 10.0 }, 2.5, 10.0, FLAGS },
+    { "high_resolution", "Enable High-Resolution mode (48 KHz or 96 KHz)",
+        OFFSET(hr_mode), AV_OPT_TYPE_BOOL,
+        { .i64 = 0 }, 0, 1, FLAGS },
+    { NULL }
+};
+
+static const AVClass class = {
+    .class_name = "liblc3 encoder",
+    .item_name  = av_default_item_name,
+    .option     = options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+const FFCodec ff_liblc3_encoder = {
+    .p.name         = "liblc3",
+    CODEC_LONG_NAME("LC3 (Low Complexity Communication Codec)"),
+    .p.type         = AVMEDIA_TYPE_AUDIO,
+    .p.id           = AV_CODEC_ID_LC3,
+    .p.capabilities = AV_CODEC_CAP_DR1 | AV_CODEC_CAP_DELAY,
+    .p.ch_layouts = (const AVChannelLayout[])
+        { { AV_CHANNEL_ORDER_UNSPEC, 1 },
+          { AV_CHANNEL_ORDER_UNSPEC, 2 }, { 0 } },
+    .p.supported_samplerates = (const int [])
+        { 96000, 48000, 32000, 24000, 16000, 8000, 0 },
+    .p.sample_fmts = (const enum AVSampleFormat[])
+        { AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_NONE },
+    .p.priv_class   = &class,
+    .p.wrapper_name = "liblc3",
+    .priv_data_size = sizeof(LibLC3EncContext),
+    .init           = liblc3_encode_init,
+    .close          = liblc3_encode_close,
+    FF_CODEC_ENCODE_CB(liblc3_encode),
+};