diff mbox

[FFmpeg-devel,1/2] aptx: implement the aptX bluetooth codec

Message ID 20171107222613.26435-1-aurel@gnuage.org
State Superseded
Headers show

Commit Message

Aurelien Jacobs Nov. 7, 2017, 10:26 p.m. UTC
The encoder was reverse engineered from binary library and from
EP0398973B1 patent (long expired).
The decoder was simply deduced from the encoder.
---
 doc/general.texi        |   2 +
 libavcodec/Makefile     |   2 +
 libavcodec/allcodecs.c  |   1 +
 libavcodec/aptx.c       | 826 ++++++++++++++++++++++++++++++++++++++++++++++++
 libavcodec/avcodec.h    |   1 +
 libavcodec/codec_desc.c |   7 +
 6 files changed, 839 insertions(+)
 create mode 100644 libavcodec/aptx.c

Comments

Rostislav Pehlivanov Nov. 8, 2017, 12:03 a.m. UTC | #1
On 7 November 2017 at 22:26, Aurelien Jacobs <aurel@gnuage.org> wrote:

> The encoder was reverse engineered from binary library and from
> EP0398973B1 patent (long expired).
> The decoder was simply deduced from the encoder.
> ---
>  doc/general.texi        |   2 +
>  libavcodec/Makefile     |   2 +
>  libavcodec/allcodecs.c  |   1 +
>  libavcodec/aptx.c       | 826 ++++++++++++++++++++++++++++++
> ++++++++++++++++++
>  libavcodec/avcodec.h    |   1 +
>  libavcodec/codec_desc.c |   7 +
>  6 files changed, 839 insertions(+)
>  create mode 100644 libavcodec/aptx.c
>
>
LGTM, will apply tomorrow unless there are any complains



interval *= -(sample_difference < 0) | 1;
>

Neat, I'll keep this trick in mind
Rostislav Pehlivanov Nov. 8, 2017, 5:24 p.m. UTC | #2
On 7 November 2017 at 22:26, Aurelien Jacobs <aurel@gnuage.org> wrote:

> ---
>  doc/general.texi         |  1 +
>  libavformat/Makefile     |  2 ++
>  libavformat/allformats.c |  1 +
>  libavformat/aptxdec.c    | 58 ++++++++++++++++++++++++++++++
> ++++++++++++++++++
>  libavformat/rawenc.c     | 13 +++++++++++
>  libavformat/utils.c      |  1 +
>  6 files changed, 76 insertions(+)
>  create mode 100644 libavformat/aptxdec.c
>
> diff --git a/doc/general.texi b/doc/general.texi
> index 4a89531c47..79e0bd0993 100644
> --- a/doc/general.texi
> +++ b/doc/general.texi
> @@ -439,6 +439,7 @@ library:
>  @item QCP                       @tab   @tab X
>  @item raw ADTS (AAC)            @tab X @tab X
>  @item raw AC-3                  @tab X @tab X
> +@item raw aptX                  @tab X @tab X
>  @item raw Chinese AVS video     @tab X @tab X
>  @item raw CRI ADX               @tab X @tab X
>  @item raw Dirac                 @tab X @tab X
> diff --git a/libavformat/Makefile b/libavformat/Makefile
> index caebe5b146..21fb892a81 100644
> --- a/libavformat/Makefile
> +++ b/libavformat/Makefile
> @@ -92,6 +92,8 @@ OBJS-$(CONFIG_APC_DEMUXER)               += apc.o
>  OBJS-$(CONFIG_APE_DEMUXER)               += ape.o apetag.o img2.o
>  OBJS-$(CONFIG_APNG_DEMUXER)              += apngdec.o
>  OBJS-$(CONFIG_APNG_MUXER)                += apngenc.o
> +OBJS-$(CONFIG_APTX_DEMUXER)              += aptxdec.o rawdec.o
> +OBJS-$(CONFIG_APTX_MUXER)                += rawenc.o
>  OBJS-$(CONFIG_AQTITLE_DEMUXER)           += aqtitledec.o subtitles.o
>  OBJS-$(CONFIG_ASF_DEMUXER)               += asfdec_f.o asf.o asfcrypt.o \
>                                              avlanguage.o
> diff --git a/libavformat/allformats.c b/libavformat/allformats.c
> index 405ddb5ad9..40964a0df0 100644
> --- a/libavformat/allformats.c
> +++ b/libavformat/allformats.c
> @@ -67,6 +67,7 @@ static void register_all(void)
>      REGISTER_DEMUXER (APC,              apc);
>      REGISTER_DEMUXER (APE,              ape);
>      REGISTER_MUXDEMUX(APNG,             apng);
> +    REGISTER_MUXDEMUX(APTX,             aptx);
>      REGISTER_DEMUXER (AQTITLE,          aqtitle);
>      REGISTER_MUXDEMUX(ASF,              asf);
>      REGISTER_DEMUXER (ASF_O,            asf_o);
> diff --git a/libavformat/aptxdec.c b/libavformat/aptxdec.c
> new file mode 100644
> index 0000000000..90ce789454
> --- /dev/null
> +++ b/libavformat/aptxdec.c
> @@ -0,0 +1,58 @@
> +/*
> + * RAW aptX demuxer
> + *
> + * Copyright (C) 2017  Aurelien Jacobs <aurel@gnuage.org>
> + *
> + * 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 "avformat.h"
> +#include "rawdec.h"
> +
> +#define APTX_BLOCK_SIZE   4
> +#define APTX_PACKET_SIZE  (256*APTX_BLOCK_SIZE)
> +
> +static int aptx_read_header(AVFormatContext *s)
> +{
> +    AVStream *st = avformat_new_stream(s, NULL);
> +    if (!st)
> +        return AVERROR(ENOMEM);
> +    st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
> +    st->codecpar->codec_id = AV_CODEC_ID_APTX;
> +    st->codecpar->format = AV_SAMPLE_FMT_S32P;
> +    st->codecpar->channels = 2;
> +    st->codecpar->sample_rate = 44100;
> +    st->codecpar->bits_per_coded_sample = 4;
> +    st->codecpar->block_align = APTX_BLOCK_SIZE;
> +    st->codecpar->frame_size = APTX_PACKET_SIZE;
> +    st->start_time = 0;
> +    return 0;
> +}
> +
> +static int aptx_read_packet(AVFormatContext *s, AVPacket *pkt)
> +{
> +    return av_get_packet(s->pb, pkt, APTX_PACKET_SIZE);
> +}
> +
> +AVInputFormat ff_aptx_demuxer = {
> +    .name           = "aptx",
> +    .long_name      = NULL_IF_CONFIG_SMALL("raw aptX"),
> +    .extensions     = "aptx",
> +    .read_header    = aptx_read_header,
> +    .read_packet    = aptx_read_packet,
> +    .flags          = AVFMT_GENERIC_INDEX,
> +};
> diff --git a/libavformat/rawenc.c b/libavformat/rawenc.c
> index f640121cb4..aa3ef76fbf 100644
> --- a/libavformat/rawenc.c
> +++ b/libavformat/rawenc.c
> @@ -91,6 +91,19 @@ AVOutputFormat ff_adx_muxer = {
>  };
>  #endif
>
> +#if CONFIG_APTX_MUXER
> +AVOutputFormat ff_aptx_muxer = {
> +    .name              = "aptx",
> +    .long_name         = NULL_IF_CONFIG_SMALL("raw aptX (Audio Processing
> Technology for Bluetooth)"),
> +    .extensions        = "aptx",
> +    .audio_codec       = AV_CODEC_ID_APTX,
> +    .video_codec       = AV_CODEC_ID_NONE,
> +    .write_header      = force_one_stream,
> +    .write_packet      = ff_raw_write_packet,
> +    .flags             = AVFMT_NOTIMESTAMPS,
> +};
> +#endif
> +
>  #if CONFIG_CAVSVIDEO_MUXER
>  AVOutputFormat ff_cavsvideo_muxer = {
>      .name              = "cavsvideo",
> diff --git a/libavformat/utils.c b/libavformat/utils.c
> index cbfb78bf4d..33145e7e90 100644
> --- a/libavformat/utils.c
> +++ b/libavformat/utils.c
> @@ -324,6 +324,7 @@ static int set_codec_from_probe_data(AVFormatContext
> *s, AVStream *st,
>      } fmt_id_type[] = {
>          { "aac",       AV_CODEC_ID_AAC,        AVMEDIA_TYPE_AUDIO },
>          { "ac3",       AV_CODEC_ID_AC3,        AVMEDIA_TYPE_AUDIO },
> +        { "aptx",      AV_CODEC_ID_APTX,       AVMEDIA_TYPE_AUDIO },
>          { "dts",       AV_CODEC_ID_DTS,        AVMEDIA_TYPE_AUDIO },
>          { "dvbsub",    AV_CODEC_ID_DVB_SUBTITLE,AVMEDIA_TYPE_SUBTITLE },
>          { "dvbtxt",    AV_CODEC_ID_DVB_TELETEXT,AVMEDIA_TYPE_SUBTITLE },
> --
> 2.15.0
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>



Patch doesn't apply

>
> Applying: aptx: add raw muxer and demuxer for aptX
> error: sha1 information is lacking or useless (doc/general.texi).
> error: could not build fake ancestor
> Patch failed at 0001 aptx: add raw muxer and demuxer for aptX
>
Michael Niedermayer Nov. 8, 2017, 5:26 p.m. UTC | #3
On Wed, Nov 08, 2017 at 02:06:09PM +0100, Aurelien Jacobs wrote:
[...]
> +typedef const struct {
> +    const int32_t *quantize_intervals;
> +    const int32_t *invert_quantize_dither_factors;
> +    const int32_t *quantize_dither_factors;

> +    const int32_t *quantize_factor_select_offset;

this would fit in int16_t *


> +    int tables_size;
> +    int32_t quantized_bits;
> +    int32_t prediction_order;
> +} ConstTables;
> +
> +static ConstTables tables[NB_SUBBANDS] = {
> +    [LF]  = { quantize_intervals_LF,
> +              invert_quantize_dither_factors_LF,
> +              quantize_dither_factors_LF,
> +              quantize_factor_select_offset_LF,
> +              FF_ARRAY_ELEMS(quantize_intervals_LF),
> +              7, 24 },
> +    [MLF] = { quantize_intervals_MLF,
> +              invert_quantize_dither_factors_MLF,
> +              quantize_dither_factors_MLF,
> +              quantize_factor_select_offset_MLF,
> +              FF_ARRAY_ELEMS(quantize_intervals_MLF),
> +              4, 12 },
> +    [MHF] = { quantize_intervals_MHF,
> +              invert_quantize_dither_factors_MHF,
> +              quantize_dither_factors_MHF,
> +              quantize_factor_select_offset_MHF,
> +              FF_ARRAY_ELEMS(quantize_intervals_MHF),
> +              2, 6 },
> +    [HF]  = { quantize_intervals_HF,
> +              invert_quantize_dither_factors_HF,
> +              quantize_dither_factors_HF,
> +              quantize_factor_select_offset_HF,
> +              FF_ARRAY_ELEMS(quantize_intervals_HF),
> +              3, 12 },
> +};
> +

> +static const int32_t quantization_factors[32] = {
> +    2048, 2093, 2139, 2186, 2233, 2282, 2332, 2383,
> +    2435, 2489, 2543, 2599, 2656, 2714, 2774, 2834,
> +    2896, 2960, 3025, 3091, 3158, 3228, 3298, 3371,
> +    3444, 3520, 3597, 3676, 3756, 3838, 3922, 4008,
> +};

this too would fir in int16_t

[...]
> +/*
> + * Push one sample into a circular signal buffer.
> + */
> +av_always_inline
> +static void aptx_qmf_filter_signal_push(FilterSignal *signal, int32_t sample)
> +{
> +    signal->buffer[signal->pos            ] = sample;
> +    signal->buffer[signal->pos+FILTER_TAPS] = sample;
> +    signal->pos = (signal->pos + 1) % FILTER_TAPS;

% could be replaced by &


> +}
> +
> +/*
> + * Compute the convolution of the signal with the coefficients, and reduce
> + * to 24 bits by applying the specified right shifting.
> + */
> +av_always_inline
> +static int32_t aptx_qmf_convolution(FilterSignal *signal,
> +                                    const int32_t coeffs[FILTER_TAPS],
> +                                    int shift)
> +{
> +    int32_t *sig = &signal->buffer[signal->pos];
> +    int64_t e = 0;
> +

> +    for (int i = 0; i < FILTER_TAPS; i++)

"for (int" is something we avoided as some comilers didnt like it,
iam not sure if this is still true but there are none in the codebase

also a fate test for this would be a good idea

[...]
Rostislav Pehlivanov Nov. 8, 2017, 5:31 p.m. UTC | #4
On 8 November 2017 at 17:26, Michael Niedermayer <michael@niedermayer.cc>
wrote:

> On Wed, Nov 08, 2017 at 02:06:09PM +0100, Aurelien Jacobs wrote:
> [...]
> > +typedef const struct {
> > +    const int32_t *quantize_intervals;
> > +    const int32_t *invert_quantize_dither_factors;
> > +    const int32_t *quantize_dither_factors;
>
> > +    const int32_t *quantize_factor_select_offset;
>
> this would fit in int16_t *
>
>
> > +    int tables_size;
> > +    int32_t quantized_bits;
> > +    int32_t prediction_order;
> > +} ConstTables;
> > +
> > +static ConstTables tables[NB_SUBBANDS] = {
> > +    [LF]  = { quantize_intervals_LF,
> > +              invert_quantize_dither_factors_LF,
> > +              quantize_dither_factors_LF,
> > +              quantize_factor_select_offset_LF,
> > +              FF_ARRAY_ELEMS(quantize_intervals_LF),
> > +              7, 24 },
> > +    [MLF] = { quantize_intervals_MLF,
> > +              invert_quantize_dither_factors_MLF,
> > +              quantize_dither_factors_MLF,
> > +              quantize_factor_select_offset_MLF,
> > +              FF_ARRAY_ELEMS(quantize_intervals_MLF),
> > +              4, 12 },
> > +    [MHF] = { quantize_intervals_MHF,
> > +              invert_quantize_dither_factors_MHF,
> > +              quantize_dither_factors_MHF,
> > +              quantize_factor_select_offset_MHF,
> > +              FF_ARRAY_ELEMS(quantize_intervals_MHF),
> > +              2, 6 },
> > +    [HF]  = { quantize_intervals_HF,
> > +              invert_quantize_dither_factors_HF,
> > +              quantize_dither_factors_HF,
> > +              quantize_factor_select_offset_HF,
> > +              FF_ARRAY_ELEMS(quantize_intervals_HF),
> > +              3, 12 },
> > +};
> > +
>
> > +static const int32_t quantization_factors[32] = {
> > +    2048, 2093, 2139, 2186, 2233, 2282, 2332, 2383,
> > +    2435, 2489, 2543, 2599, 2656, 2714, 2774, 2834,
> > +    2896, 2960, 3025, 3091, 3158, 3228, 3298, 3371,
> > +    3444, 3520, 3597, 3676, 3756, 3838, 3922, 4008,
> > +};
>
> this too would fir in int16_t
>
> [...]
> > +/*
> > + * Push one sample into a circular signal buffer.
> > + */
> > +av_always_inline
> > +static void aptx_qmf_filter_signal_push(FilterSignal *signal, int32_t
> sample)
> > +{
> > +    signal->buffer[signal->pos            ] = sample;
> > +    signal->buffer[signal->pos+FILTER_TAPS] = sample;
> > +    signal->pos = (signal->pos + 1) % FILTER_TAPS;
>
> % could be replaced by &
>
>
> > +}
> > +
> > +/*
> > + * Compute the convolution of the signal with the coefficients, and
> reduce
> > + * to 24 bits by applying the specified right shifting.
> > + */
> > +av_always_inline
> > +static int32_t aptx_qmf_convolution(FilterSignal *signal,
> > +                                    const int32_t coeffs[FILTER_TAPS],
> > +                                    int shift)
> > +{
> > +    int32_t *sig = &signal->buffer[signal->pos];
> > +    int64_t e = 0;
> > +
>
> > +    for (int i = 0; i < FILTER_TAPS; i++)
>
> "for (int" is something we avoided as some comilers didnt like it,
> iam not sure if this is still true but there are none in the codebase
>
> also a fate test for this would be a good idea
>
>
No, I completely reject this idea.

I want to use for (int loops and have always wanted to use them.
Show me a comiler which we support which doesn't accept those. Then I'll
suggest we drop support for it.

Its time we bring the codebase to the 21st century and this is my main
issue - being unable to use for (int loops.
Aurelien Jacobs Nov. 9, 2017, 1:39 p.m. UTC | #5
On Thu, Nov 09, 2017 at 03:22:21AM +0100, Michael Niedermayer wrote:
> On Wed, Nov 08, 2017 at 11:41:16PM +0100, Aurelien Jacobs wrote:
> > On Wed, Nov 08, 2017 at 06:26:03PM +0100, Michael Niedermayer wrote:
> > > On Wed, Nov 08, 2017 at 02:06:09PM +0100, Aurelien Jacobs wrote:
> > > [...]
> > > > +}
> > > > +
> > > > +/*
> > > > + * Compute the convolution of the signal with the coefficients, and reduce
> > > > + * to 24 bits by applying the specified right shifting.
> > > > + */
> > > > +av_always_inline
> > > > +static int32_t aptx_qmf_convolution(FilterSignal *signal,
> > > > +                                    const int32_t coeffs[FILTER_TAPS],
> > > > +                                    int shift)
> > > > +{
> > > > +    int32_t *sig = &signal->buffer[signal->pos];
> > > > +    int64_t e = 0;
> > > > +
> > > 
> > > > +    for (int i = 0; i < FILTER_TAPS; i++)
> > > 
> > > "for (int" is something we avoided as some comilers didnt like it,
> > > iam not sure if this is still true but there are none in the codebase
> > 
> > If you insist on this I will of course change it, but hey, we require
> 
> me personally, not really, i used "for (int" style as long as
> i can remember outside ffmpeg but i also would like to keep supportng
> all platforms ...

OK, got it.
Rostislav Pehlivanov Nov. 9, 2017, 2:32 p.m. UTC | #6
On 9 November 2017 at 13:37, Aurelien Jacobs <aurel@gnuage.org> wrote:

> On Thu, Nov 09, 2017 at 12:52:34AM +0000, Rostislav Pehlivanov wrote:
> > On 8 November 2017 at 22:41, Aurelien Jacobs <aurel@gnuage.org> wrote:
> >
> > > On Wed, Nov 08, 2017 at 06:26:03PM +0100, Michael Niedermayer wrote:
> > > > On Wed, Nov 08, 2017 at 02:06:09PM +0100, Aurelien Jacobs wrote:
> > > > [...]
> > > > > +}
> > > > > +
> > > > > +/*
> > > > > + * Compute the convolution of the signal with the coefficients,
> and
> > > reduce
> > > > > + * to 24 bits by applying the specified right shifting.
> > > > > + */
> > > > > +av_always_inline
> > > > > +static int32_t aptx_qmf_convolution(FilterSignal *signal,
> > > > > +                                    const int32_t
> coeffs[FILTER_TAPS],
> > > > > +                                    int shift)
> > > > > +{
> > > > > +    int32_t *sig = &signal->buffer[signal->pos];
> > > > > +    int64_t e = 0;
> > > > > +
> > > >
> > > > > +    for (int i = 0; i < FILTER_TAPS; i++)
> > > >
> > > > "for (int" is something we avoided as some comilers didnt like it,
> > > > iam not sure if this is still true but there are none in the codebase
> > >
> > > If you insist on this I will of course change it, but hey, we require
> > > a C99 compiler since a long time and we use so many features of C99
> > > that I really don't get why we couldn't use "for (int".
> > > I can't imagine that any relevant C compiler would not support this
> > > nowadays !
> > > What I propose is to get this in as is, and see if anyone encounter
> > > issue with any compiler. If any issue arise, I will of course send a
> > > patch to fix it.
> > >
> > > Here is an updated patch.
> > >
> > >
> > Send another patch because some people are beyond convincing and its
> really
> > pissing me off. Really. In particular maybe those compiler writers at
> > Microsoft who seem to think shipping something that doesn't support such
> a
> > simple yet important feature is important.
> > Or maybe users who don't want to update a 6 year old version of msvc.
> > Or maybe us because we support compiling with msvc at all when its
> clearly
> > _not_ a C compiler and this project is written in C.
>
> Here you go.
>
> Just for reference, splitting the déclaration out of the for loop added
> 19 lines in this file, which is a 2.3 % increase of the line count.
> (I'm not sure this file is representative of the rest of the code base)
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
>
Still some issues:

1.) You need to set AVCodec->supported_samplerates for the encoder, since
in the raw aptx muxer you always set the samplerate as 44100. Does the
codec/container support other samplerates?

2.) "Frame must have a multiple of 4 samples" gets printed out when trying
to resample a 48000 file and encode it to aptx.
What you need to do is to remove AV_CODEC_CAP_VARIABLE_FRAME_SIZE from the
capabilities and set some sane default for avctx->frame_size if
avctx->frame_size is unset. Users can override it prior to encoder init and
you can do it through the command line using -frame_size. If the user sets
the frame_size, check if its a multiple of 4 and use it. Else, error out.
If it isn't set (e.g. its 0), use the default (1024 is a good value).

3.) The packet timestamps on the encoder side are missing. Use the
AudioFrameQueue API (libavcodec/audio_frame_queue.h) which at every frame
counts how many samples you're given via the avframe and calculates what
dts/pts to set on the avpacket.
Just call ff_af_queue_init at the end of your init function (after the
frame size is set and checked), call ff_af_queue_add at the start of your
encode function and ff_af_queue_remove with the number of samples you
encoded and pointers to the packet pts/duration fields. And make sure to
call ff_af_queue_close on uninit, which will print out an error if you've
encoded more samples than you've received or less samples than you've
recevied.
Aurelien Jacobs Nov. 9, 2017, 10:48 p.m. UTC | #7
On Thu, Nov 09, 2017 at 02:32:44PM +0000, Rostislav Pehlivanov wrote:
> On 9 November 2017 at 13:37, Aurelien Jacobs <aurel@gnuage.org> wrote:
> 
> > On Thu, Nov 09, 2017 at 12:52:34AM +0000, Rostislav Pehlivanov wrote:
> > > On 8 November 2017 at 22:41, Aurelien Jacobs <aurel@gnuage.org> wrote:
> > >
> > > > On Wed, Nov 08, 2017 at 06:26:03PM +0100, Michael Niedermayer wrote:
> > > > > On Wed, Nov 08, 2017 at 02:06:09PM +0100, Aurelien Jacobs wrote:
> > > > > [...]
> > > > > > +}
> > > > > > +
> > > > > > +/*
> > > > > > + * Compute the convolution of the signal with the coefficients,
> > and
> > > > reduce
> > > > > > + * to 24 bits by applying the specified right shifting.
> > > > > > + */
> > > > > > +av_always_inline
> > > > > > +static int32_t aptx_qmf_convolution(FilterSignal *signal,
> > > > > > +                                    const int32_t
> > coeffs[FILTER_TAPS],
> > > > > > +                                    int shift)
> > > > > > +{
> > > > > > +    int32_t *sig = &signal->buffer[signal->pos];
> > > > > > +    int64_t e = 0;
> > > > > > +
> > > > >
> > > > > > +    for (int i = 0; i < FILTER_TAPS; i++)
> > > > >
> > > > > "for (int" is something we avoided as some comilers didnt like it,
> > > > > iam not sure if this is still true but there are none in the codebase
> > > >
> > > > If you insist on this I will of course change it, but hey, we require
> > > > a C99 compiler since a long time and we use so many features of C99
> > > > that I really don't get why we couldn't use "for (int".
> > > > I can't imagine that any relevant C compiler would not support this
> > > > nowadays !
> > > > What I propose is to get this in as is, and see if anyone encounter
> > > > issue with any compiler. If any issue arise, I will of course send a
> > > > patch to fix it.
> > > >
> > > > Here is an updated patch.
> > > >
> > > >
> > > Send another patch because some people are beyond convincing and its
> > really
> > > pissing me off. Really. In particular maybe those compiler writers at
> > > Microsoft who seem to think shipping something that doesn't support such
> > a
> > > simple yet important feature is important.
> > > Or maybe users who don't want to update a 6 year old version of msvc.
> > > Or maybe us because we support compiling with msvc at all when its
> > clearly
> > > _not_ a C compiler and this project is written in C.
> >
> > Here you go.
> >
> > Just for reference, splitting the déclaration out of the for loop added
> > 19 lines in this file, which is a 2.3 % increase of the line count.
> > (I'm not sure this file is representative of the rest of the code base)
> >
> > _______________________________________________
> > ffmpeg-devel mailing list
> > ffmpeg-devel@ffmpeg.org
> > http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> >
> >
> Still some issues:
> 
> 1.) You need to set AVCodec->supported_samplerates for the encoder, since
> in the raw aptx muxer you always set the samplerate as 44100. Does the
> codec/container support other samplerates?

The codec is actually samplerate agnostic.
It only convert each group of 4 samples to a 16 bit integer.
There is no other information than the samples themselves in the
encoded stream. No header, no frame size, no samplerate.
And there is no standard container used to store aptX along with
the needed metadata such as the samplerate. It is meant to be streamed
thru bluetooth using the A2DP "protocol" which includes its own metadata
signaling with samplerate.
The raw muxer/demuxer that I implemented is mostly for testing purpose,
not really for practical use.
So with this in mind, I don't think that it make any sense to set
AVCodec->supported_samplerates.
I don't think I set the samplerate anywhere in the encoder.
I only set it in the demuxer to a default 44100 just to be able to
playback test files even though there is no way to know its actual
samplerate.

> 2.) "Frame must have a multiple of 4 samples" gets printed out when trying
> to resample a 48000 file and encode it to aptx.
> What you need to do is to remove AV_CODEC_CAP_VARIABLE_FRAME_SIZE from the
> capabilities and set some sane default for avctx->frame_size if
> avctx->frame_size is unset. Users can override it prior to encoder init and
> you can do it through the command line using -frame_size. If the user sets
> the frame_size, check if its a multiple of 4 and use it. Else, error out.
> If it isn't set (e.g. its 0), use the default (1024 is a good value).

OK, I will try this.
And I should probably document that for acheiving the lowest possible
latency for realtime bluetooth streaming, frame_size shoud be set to
4 samples.

> 3.) The packet timestamps on the encoder side are missing. Use the
> AudioFrameQueue API (libavcodec/audio_frame_queue.h) which at every frame
> counts how many samples you're given via the avframe and calculates what
> dts/pts to set on the avpacket.
> Just call ff_af_queue_init at the end of your init function (after the
> frame size is set and checked), call ff_af_queue_add at the start of your
> encode function and ff_af_queue_remove with the number of samples you
> encoded and pointers to the packet pts/duration fields. And make sure to
> call ff_af_queue_close on uninit, which will print out an error if you've
> encoded more samples than you've received or less samples than you've
> recevied.

I will look at this.
Rostislav Pehlivanov Nov. 10, 2017, 12:25 a.m. UTC | #8
On 9 November 2017 at 22:48, Aurelien Jacobs <aurel@gnuage.org> wrote:

> On Thu, Nov 09, 2017 at 02:32:44PM +0000, Rostislav Pehlivanov wrote:
> > On 9 November 2017 at 13:37, Aurelien Jacobs <aurel@gnuage.org> wrote:
> >
> > > On Thu, Nov 09, 2017 at 12:52:34AM +0000, Rostislav Pehlivanov wrote:
> > > > On 8 November 2017 at 22:41, Aurelien Jacobs <aurel@gnuage.org>
> wrote:
> > > >
> > > > > On Wed, Nov 08, 2017 at 06:26:03PM +0100, Michael Niedermayer
> wrote:
> > > > > > On Wed, Nov 08, 2017 at 02:06:09PM +0100, Aurelien Jacobs wrote:
> > > > > > [...]
> > > > > > > +}
> > > > > > > +
> > > > > > > +/*
> > > > > > > + * Compute the convolution of the signal with the
> coefficients,
> > > and
> > > > > reduce
> > > > > > > + * to 24 bits by applying the specified right shifting.
> > > > > > > + */
> > > > > > > +av_always_inline
> > > > > > > +static int32_t aptx_qmf_convolution(FilterSignal *signal,
> > > > > > > +                                    const int32_t
> > > coeffs[FILTER_TAPS],
> > > > > > > +                                    int shift)
> > > > > > > +{
> > > > > > > +    int32_t *sig = &signal->buffer[signal->pos];
> > > > > > > +    int64_t e = 0;
> > > > > > > +
> > > > > >
> > > > > > > +    for (int i = 0; i < FILTER_TAPS; i++)
> > > > > >
> > > > > > "for (int" is something we avoided as some comilers didnt like
> it,
> > > > > > iam not sure if this is still true but there are none in the
> codebase
> > > > >
> > > > > If you insist on this I will of course change it, but hey, we
> require
> > > > > a C99 compiler since a long time and we use so many features of C99
> > > > > that I really don't get why we couldn't use "for (int".
> > > > > I can't imagine that any relevant C compiler would not support this
> > > > > nowadays !
> > > > > What I propose is to get this in as is, and see if anyone encounter
> > > > > issue with any compiler. If any issue arise, I will of course send
> a
> > > > > patch to fix it.
> > > > >
> > > > > Here is an updated patch.
> > > > >
> > > > >
> > > > Send another patch because some people are beyond convincing and its
> > > really
> > > > pissing me off. Really. In particular maybe those compiler writers at
> > > > Microsoft who seem to think shipping something that doesn't support
> such
> > > a
> > > > simple yet important feature is important.
> > > > Or maybe users who don't want to update a 6 year old version of msvc.
> > > > Or maybe us because we support compiling with msvc at all when its
> > > clearly
> > > > _not_ a C compiler and this project is written in C.
> > >
> > > Here you go.
> > >
> > > Just for reference, splitting the déclaration out of the for loop added
> > > 19 lines in this file, which is a 2.3 % increase of the line count.
> > > (I'm not sure this file is representative of the rest of the code base)
> > >
> > > _______________________________________________
> > > ffmpeg-devel mailing list
> > > ffmpeg-devel@ffmpeg.org
> > > http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> > >
> > >
> > Still some issues:
> >
> > 1.) You need to set AVCodec->supported_samplerates for the encoder, since
> > in the raw aptx muxer you always set the samplerate as 44100. Does the
> > codec/container support other samplerates?
>
> The codec is actually samplerate agnostic.
> It only convert each group of 4 samples to a 16 bit integer.
> There is no other information than the samples themselves in the
> encoded stream. No header, no frame size, no samplerate.
> And there is no standard container used to store aptX along with
> the needed metadata such as the samplerate. It is meant to be streamed
> thru bluetooth using the A2DP "protocol" which includes its own metadata
> signaling with samplerate.
> The raw muxer/demuxer that I implemented is mostly for testing purpose,
> not really for practical use.
> So with this in mind, I don't think that it make any sense to set
> AVCodec->supported_samplerates.
> I don't think I set the samplerate anywhere in the encoder.
> I only set it in the demuxer to a default 44100 just to be able to
> playback test files even though there is no way to know its actual
> samplerate.
>
>
Ah, I see.
In this case set the allowed samplerate to 48000 on the encoder side and
the output samplerate in the demuxer to the same. Pretty much all systems,
be it bluetooth, I2S or some other interface run at 48khz so that's what we
should set it so its as compatible as possible. Nothing but systems which
handle redbook cds use 44100hz.
Aurelien Jacobs Nov. 10, 2017, 9:09 p.m. UTC | #9
On Fri, Nov 10, 2017 at 12:25:12AM +0000, Rostislav Pehlivanov wrote:
> On 9 November 2017 at 22:48, Aurelien Jacobs <aurel@gnuage.org> wrote:
> 
> > On Thu, Nov 09, 2017 at 02:32:44PM +0000, Rostislav Pehlivanov wrote:
> > > On 9 November 2017 at 13:37, Aurelien Jacobs <aurel@gnuage.org> wrote:
> > >
> > > > On Thu, Nov 09, 2017 at 12:52:34AM +0000, Rostislav Pehlivanov wrote:
> > > > > On 8 November 2017 at 22:41, Aurelien Jacobs <aurel@gnuage.org>
> > wrote:
> > > > >
> > > > > > On Wed, Nov 08, 2017 at 06:26:03PM +0100, Michael Niedermayer
> > wrote:
> > > > > > > On Wed, Nov 08, 2017 at 02:06:09PM +0100, Aurelien Jacobs wrote:
> > > > > > > [...]
> > > > > > > > +}
> > > > > > > > +
> > > > > > > > +/*
> > > > > > > > + * Compute the convolution of the signal with the
> > coefficients,
> > > > and
> > > > > > reduce
> > > > > > > > + * to 24 bits by applying the specified right shifting.
> > > > > > > > + */
> > > > > > > > +av_always_inline
> > > > > > > > +static int32_t aptx_qmf_convolution(FilterSignal *signal,
> > > > > > > > +                                    const int32_t
> > > > coeffs[FILTER_TAPS],
> > > > > > > > +                                    int shift)
> > > > > > > > +{
> > > > > > > > +    int32_t *sig = &signal->buffer[signal->pos];
> > > > > > > > +    int64_t e = 0;
> > > > > > > > +
> > > > > > >
> > > > > > > > +    for (int i = 0; i < FILTER_TAPS; i++)
> > > > > > >
> > > > > > > "for (int" is something we avoided as some comilers didnt like
> > it,
> > > > > > > iam not sure if this is still true but there are none in the
> > codebase
> > > > > >
> > > > > > If you insist on this I will of course change it, but hey, we
> > require
> > > > > > a C99 compiler since a long time and we use so many features of C99
> > > > > > that I really don't get why we couldn't use "for (int".
> > > > > > I can't imagine that any relevant C compiler would not support this
> > > > > > nowadays !
> > > > > > What I propose is to get this in as is, and see if anyone encounter
> > > > > > issue with any compiler. If any issue arise, I will of course send
> > a
> > > > > > patch to fix it.
> > > > > >
> > > > > > Here is an updated patch.
> > > > > >
> > > > > >
> > > > > Send another patch because some people are beyond convincing and its
> > > > really
> > > > > pissing me off. Really. In particular maybe those compiler writers at
> > > > > Microsoft who seem to think shipping something that doesn't support
> > such
> > > > a
> > > > > simple yet important feature is important.
> > > > > Or maybe users who don't want to update a 6 year old version of msvc.
> > > > > Or maybe us because we support compiling with msvc at all when its
> > > > clearly
> > > > > _not_ a C compiler and this project is written in C.
> > > >
> > > > Here you go.
> > > >
> > > > Just for reference, splitting the déclaration out of the for loop added
> > > > 19 lines in this file, which is a 2.3 % increase of the line count.
> > > > (I'm not sure this file is representative of the rest of the code base)
> > > >
> > > > _______________________________________________
> > > > ffmpeg-devel mailing list
> > > > ffmpeg-devel@ffmpeg.org
> > > > http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
> > > >
> > > >
> > > Still some issues:
> > >
> > > 1.) You need to set AVCodec->supported_samplerates for the encoder, since
> > > in the raw aptx muxer you always set the samplerate as 44100. Does the
> > > codec/container support other samplerates?
> >
> > The codec is actually samplerate agnostic.
> > It only convert each group of 4 samples to a 16 bit integer.
> > There is no other information than the samples themselves in the
> > encoded stream. No header, no frame size, no samplerate.
> > And there is no standard container used to store aptX along with
> > the needed metadata such as the samplerate. It is meant to be streamed
> > thru bluetooth using the A2DP "protocol" which includes its own metadata
> > signaling with samplerate.
> > The raw muxer/demuxer that I implemented is mostly for testing purpose,
> > not really for practical use.
> > So with this in mind, I don't think that it make any sense to set
> > AVCodec->supported_samplerates.
> > I don't think I set the samplerate anywhere in the encoder.
> > I only set it in the demuxer to a default 44100 just to be able to
> > playback test files even though there is no way to know its actual
> > samplerate.
> >
> >
> Ah, I see.
> In this case set the allowed samplerate to 48000 on the encoder side and
> the output samplerate in the demuxer to the same. Pretty much all systems,
> be it bluetooth, I2S or some other interface run at 48khz so that's what we
> should set it so its as compatible as possible. Nothing but systems which
> handle redbook cds use 44100hz.

I found a list of supported samplerate in an aptX commercial
documentation, so I think it makes sense to list them in the encoder.
Regarding the demuxer, I set it 48000 by default, but I added an
AVOption to allow user to set -sample_rate.

I will submit an updated patch set.
diff mbox

Patch

diff --git a/doc/general.texi b/doc/general.texi
index 9e6ae13435..4a89531c47 100644
--- a/doc/general.texi
+++ b/doc/general.texi
@@ -991,6 +991,8 @@  following image formats are supported:
 @item Amazing Studio PAF Audio @tab     @tab  X
 @item Apple lossless audio   @tab  X  @tab  X
     @tab QuickTime fourcc 'alac'
+@item aptX                   @tab  X  @tab  X
+    @tab Used in Bluetooth A2DP
 @item ATRAC1                 @tab     @tab  X
 @item ATRAC3                 @tab     @tab  X
 @item ATRAC3+                @tab     @tab  X
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 3a33361f33..25706a263d 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -188,6 +188,8 @@  OBJS-$(CONFIG_AMV_ENCODER)             += mjpegenc.o mjpegenc_common.o \
 OBJS-$(CONFIG_ANM_DECODER)             += anm.o
 OBJS-$(CONFIG_ANSI_DECODER)            += ansi.o cga_data.o
 OBJS-$(CONFIG_APE_DECODER)             += apedec.o
+OBJS-$(CONFIG_APTX_DECODER)            += aptx.o
+OBJS-$(CONFIG_APTX_ENCODER)            += aptx.o
 OBJS-$(CONFIG_APNG_DECODER)            += png.o pngdec.o pngdsp.o
 OBJS-$(CONFIG_APNG_ENCODER)            += png.o pngenc.o
 OBJS-$(CONFIG_SSA_DECODER)             += assdec.o ass.o
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index 98655ddd7c..61abe9939c 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -406,6 +406,7 @@  static void register_all(void)
     REGISTER_DECODER(AMRNB,             amrnb);
     REGISTER_DECODER(AMRWB,             amrwb);
     REGISTER_DECODER(APE,               ape);
+    REGISTER_ENCDEC (APTX,              aptx);
     REGISTER_DECODER(ATRAC1,            atrac1);
     REGISTER_DECODER(ATRAC3,            atrac3);
     REGISTER_DECODER(ATRAC3AL,          atrac3al);
diff --git a/libavcodec/aptx.c b/libavcodec/aptx.c
new file mode 100644
index 0000000000..b2879ea370
--- /dev/null
+++ b/libavcodec/aptx.c
@@ -0,0 +1,826 @@ 
+/*
+ * Audio Processing Technology codec for Bluetooth (aptX)
+ *
+ * Copyright (C) 2017  Aurelien Jacobs <aurel@gnuage.org>
+ *
+ * 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 "avcodec.h"
+#include "internal.h"
+#include "mathops.h"
+
+
+enum channels {
+    LEFT,
+    RIGHT,
+    NB_CHANNELS
+};
+
+enum subbands {
+    LF,  // Low Frequency (0-5.5 kHz)
+    MLF, // Medium-Low Frequency (5.5-11kHz)
+    MHF, // Medium-High Frequency (11-16.5kHz)
+    HF,  // High Frequency (16.5-22kHz)
+    NB_SUBBANDS
+};
+
+#define NB_FILTERS 2
+#define FILTER_TAPS 16
+
+typedef struct {
+    int pos;
+    int32_t buffer[2*FILTER_TAPS];
+} FilterSignal;
+
+typedef struct {
+    FilterSignal outer_filter_signal[NB_FILTERS];
+    FilterSignal inner_filter_signal[NB_FILTERS][NB_FILTERS];
+} QMFAnalysis;
+
+typedef struct {
+    int32_t quantized_sample;
+    int32_t quantized_sample_parity_change;
+    int32_t error;
+} Quantize;
+
+typedef struct {
+    int32_t quantization_factor;
+    int32_t factor_select;
+    int32_t reconstructed_difference;
+} InvertQuantize;
+
+typedef struct {
+    int32_t prev_sign[2];
+    int32_t s_weight[2];
+    int32_t d_weight[24];
+    int32_t pos;
+    int32_t reconstructed_differences[48];
+    int32_t previous_reconstructed_sample;
+    int32_t predicted_difference;
+    int32_t predicted_sample;
+} Prediction;
+
+typedef struct {
+    int32_t codeword_history;
+    int32_t dither_parity;
+    int32_t dither[NB_SUBBANDS];
+
+    QMFAnalysis qmf;
+    Quantize quantize[NB_SUBBANDS];
+    InvertQuantize invert_quantize[NB_SUBBANDS];
+    Prediction prediction[NB_SUBBANDS];
+} Channel;
+
+typedef struct {
+    int32_t sync_idx;
+    Channel channels[NB_CHANNELS];
+} AptXContext;
+
+
+static const int32_t quantize_intervals_LF[65] = {
+      -9948,    9948,   29860,   49808,   69822,   89926,  110144,  130502,
+     151026,  171738,  192666,  213832,  235264,  256982,  279014,  301384,
+     324118,  347244,  370790,  394782,  419250,  444226,  469742,  495832,
+     522536,  549890,  577936,  606720,  636290,  666700,  698006,  730270,
+     763562,  797958,  833538,  870398,  908640,  948376,  989740, 1032874,
+    1077948, 1125150, 1174700, 1226850, 1281900, 1340196, 1402156, 1468282,
+    1539182, 1615610, 1698514, 1789098, 1888944, 2000168, 2125700, 2269750,
+    2438670, 2642660, 2899462, 3243240, 3746078, 4535138, 5664098, 7102424,
+    8897462,
+};
+static const int32_t invert_quantize_dither_factors_LF[65] = {
+       9948,   9948,   9962,   9988,  10026,  10078,  10142,  10218,
+      10306,  10408,  10520,  10646,  10784,  10934,  11098,  11274,
+      11462,  11664,  11880,  12112,  12358,  12618,  12898,  13194,
+      13510,  13844,  14202,  14582,  14988,  15422,  15884,  16380,
+      16912,  17484,  18098,  18762,  19480,  20258,  21106,  22030,
+      23044,  24158,  25390,  26760,  28290,  30008,  31954,  34172,
+      36728,  39700,  43202,  47382,  52462,  58762,  66770,  77280,
+      91642, 112348, 144452, 199326, 303512, 485546, 643414, 794914,
+    1000124,
+};
+static const int32_t quantize_dither_factors_LF[65] = {
+        0,     4,     7,    10,    13,    16,    19,    22,
+       26,    28,    32,    35,    38,    41,    44,    47,
+       51,    54,    58,    62,    65,    70,    74,    79,
+       84,    90,    95,   102,   109,   116,   124,   133,
+      143,   154,   166,   180,   195,   212,   231,   254,
+      279,   308,   343,   383,   430,   487,   555,   639,
+      743,   876,  1045,  1270,  1575,  2002,  2628,  3591,
+     5177,  8026, 13719, 26047, 45509, 39467, 37875, 51303,
+        0,
+};
+static const int32_t quantize_factor_select_offset_LF[65] = {
+      0, -21, -19, -17, -15, -12, -10,  -8,
+     -6,  -4,  -1,   1,   3,   6,   8,  10,
+     13,  15,  18,  20,  23,  26,  29,  31,
+     34,  37,  40,  43,  47,  50,  53,  57,
+     60,  64,  68,  72,  76,  80,  85,  89,
+     94,  99, 105, 110, 116, 123, 129, 136,
+    144, 152, 161, 171, 182, 194, 207, 223,
+    241, 263, 291, 328, 382, 467, 522, 522,
+    522,
+};
+
+
+static const int32_t quantize_intervals_MLF[9] = {
+    -89806, 89806, 278502, 494338, 759442, 1113112, 1652322, 2720256, 5190186,
+};
+static const int32_t invert_quantize_dither_factors_MLF[9] = {
+    89806, 89806, 98890, 116946, 148158, 205512, 333698, 734236, 1735696,
+};
+static const int32_t quantize_dither_factors_MLF[9] = {
+    0, 2271, 4514, 7803, 14339, 32047, 100135, 250365, 0,
+};
+static const int32_t quantize_factor_select_offset_MLF[9] = {
+    0, -14, 6, 29, 58, 96, 154, 270, 521,
+};
+
+
+static const int32_t quantize_intervals_MHF[3] = {
+    -194080, 194080, 890562,
+};
+static const int32_t invert_quantize_dither_factors_MHF[3] = {
+    194080, 194080, 502402,
+};
+static const int32_t quantize_dither_factors_MHF[3] = {
+    0, 77081, 0,
+};
+static const int32_t quantize_factor_select_offset_MHF[3] = {
+    0, -33, 136,
+};
+
+
+static const int32_t quantize_intervals_HF[5] = {
+    -163006, 163006, 542708, 1120554, 2669238,
+};
+static const int32_t invert_quantize_dither_factors_HF[5] = {
+    163006, 163006, 216698, 361148, 1187538,
+};
+static const int32_t quantize_dither_factors_HF[5] = {
+    0, 13423, 36113, 206598, 0,
+};
+static const int32_t quantize_factor_select_offset_HF[5] = {
+    0, -8, 33, 95, 262,
+};
+
+typedef const struct {
+    const int32_t *quantize_intervals;
+    const int32_t *invert_quantize_dither_factors;
+    const int32_t *quantize_dither_factors;
+    const int32_t *quantize_factor_select_offset;
+    int tables_size;
+    int32_t quantized_bits;
+    int32_t prediction_order;
+} ConstTables;
+
+static ConstTables tables[NB_SUBBANDS] = {
+    [LF]  = { quantize_intervals_LF,
+              invert_quantize_dither_factors_LF,
+              quantize_dither_factors_LF,
+              quantize_factor_select_offset_LF,
+              FF_ARRAY_ELEMS(quantize_intervals_LF),
+              7, 24 },
+    [MLF] = { quantize_intervals_MLF,
+              invert_quantize_dither_factors_MLF,
+              quantize_dither_factors_MLF,
+              quantize_factor_select_offset_MLF,
+              FF_ARRAY_ELEMS(quantize_intervals_MLF),
+              4, 12 },
+    [MHF] = { quantize_intervals_MHF,
+              invert_quantize_dither_factors_MHF,
+              quantize_dither_factors_MHF,
+              quantize_factor_select_offset_MHF,
+              FF_ARRAY_ELEMS(quantize_intervals_MHF),
+              2, 6 },
+    [HF]  = { quantize_intervals_HF,
+              invert_quantize_dither_factors_HF,
+              quantize_dither_factors_HF,
+              quantize_factor_select_offset_HF,
+              FF_ARRAY_ELEMS(quantize_intervals_HF),
+              3, 12 },
+};
+
+static const int32_t quantization_factors[32] = {
+    2048, 2093, 2139, 2186, 2233, 2282, 2332, 2383,
+    2435, 2489, 2543, 2599, 2656, 2714, 2774, 2834,
+    2896, 2960, 3025, 3091, 3158, 3228, 3298, 3371,
+    3444, 3520, 3597, 3676, 3756, 3838, 3922, 4008,
+};
+
+
+/* Rounded right shift with optionnal clipping */
+#define RSHIFT_SIZE(size)                                                     \
+av_always_inline                                                              \
+static int##size##_t rshift##size(int##size##_t value, int shift)             \
+{                                                                             \
+    int##size##_t rounding = (int##size##_t)1 << (shift - 1);                 \
+    int##size##_t mask = ((int##size##_t)1 << (shift + 1)) - 1;               \
+    return ((value + rounding) >> shift) - ((value & mask) == rounding);      \
+}                                                                             \
+av_always_inline                                                              \
+static int##size##_t rshift##size##_clip24(int##size##_t value, int shift)    \
+{                                                                             \
+    return av_clip_intp2(rshift##size(value, shift), 23);                     \
+}
+RSHIFT_SIZE(32)
+RSHIFT_SIZE(64)
+
+
+av_always_inline
+static void aptx_update_codeword_history(Channel *channel)
+{
+    int32_t cw = ((channel->quantize[0].quantized_sample & 3) << 0) +
+                 ((channel->quantize[1].quantized_sample & 2) << 1) +
+                 ((channel->quantize[2].quantized_sample & 1) << 3);
+    channel->codeword_history = (cw << 8) + (channel->codeword_history << 4);
+}
+
+static void aptx_generate_dither(Channel *channel)
+{
+    int64_t m;
+    int32_t d;
+
+    aptx_update_codeword_history(channel);
+
+    m = (int64_t)5184443 * (channel->codeword_history >> 7);
+    d = (m << 2) + (m >> 22);
+    for (int subband = 0; subband < NB_SUBBANDS; subband++)
+        channel->dither[subband] = d << (23 - 5*subband);
+    channel->dither_parity = (d >> 25) & 1;
+}
+
+/*
+ * Convolution filter coefficients for the outer QMF of the QMF tree.
+ * The 2 sets are a mirror of each other.
+ */
+static const int32_t aptx_qmf_outer_coeffs[NB_FILTERS][FILTER_TAPS] = {
+    {
+        730, -413, -9611, 43626, -121026, 269973, -585547, 2801966,
+        697128, -160481, 27611, 8478, -10043, 3511, 688, -897,
+    },
+    {
+        -897, 688, 3511, -10043, 8478, 27611, -160481, 697128,
+        2801966, -585547, 269973, -121026, 43626, -9611, -413, 730,
+    },
+};
+
+/*
+ * Convolution filter coefficients for the inner QMF of the QMF tree.
+ * The 2 sets are a mirror of each other.
+ */
+static const int32_t aptx_qmf_inner_coeffs[NB_FILTERS][FILTER_TAPS] = {
+    {
+       1033, -584, -13592, 61697, -171156, 381799, -828088, 3962579,
+       985888, -226954, 39048, 11990, -14203, 4966, 973, -1268,
+    },
+    {
+      -1268, 973, 4966, -14203, 11990, 39048, -226954, 985888,
+      3962579, -828088, 381799, -171156, 61697, -13592, -584, 1033,
+    },
+};
+
+/*
+ * Push one sample into a circular signal buffer.
+ */
+av_always_inline
+static void aptx_qmf_filter_signal_push(FilterSignal *signal, int32_t sample)
+{
+    signal->buffer[signal->pos            ] = sample;
+    signal->buffer[signal->pos+FILTER_TAPS] = sample;
+    signal->pos = (signal->pos + 1) % FILTER_TAPS;
+}
+
+/*
+ * Compute the convolution of the signal with the coefficients, and reduce
+ * to 24 bits by applying the specified right shifting.
+ */
+av_always_inline
+static int32_t aptx_qmf_convolution(FilterSignal *signal,
+                                    const int32_t coeffs[FILTER_TAPS],
+                                    int shift)
+{
+    int32_t *sig = &signal->buffer[signal->pos];
+    int64_t e = 0;
+
+    for (int i = 0; i < FILTER_TAPS; i++)
+        e += MUL64(sig[i], coeffs[i]);
+
+    return rshift64_clip24(e, shift);
+}
+
+/*
+ * Half-band QMF analysis filter realized with a polyphase FIR filter.
+ * Split into 2 subbands and downsample by 2.
+ * So for each pair of samples that goes in, one sample goes out,
+ * split into 2 separate subbands.
+ */
+av_always_inline
+static void aptx_qmf_polyphase_analysis(FilterSignal signal[NB_FILTERS],
+                                        const int32_t coeffs[NB_FILTERS][FILTER_TAPS],
+                                        int shift,
+                                        int32_t samples[NB_FILTERS],
+                                        int32_t *low_subband_output,
+                                        int32_t *high_subband_output)
+{
+    int32_t subbands[NB_FILTERS];
+
+    for (int i = 0; i < NB_FILTERS; i++) {
+        aptx_qmf_filter_signal_push(&signal[i], samples[NB_FILTERS-1-i]);
+        subbands[i] = aptx_qmf_convolution(&signal[i], coeffs[i], shift);
+    }
+
+    *low_subband_output  = av_clip_intp2(subbands[0] + subbands[1], 23);
+    *high_subband_output = av_clip_intp2(subbands[0] - subbands[1], 23);
+}
+
+/*
+ * Two stage QMF analysis tree.
+ * Split 4 input samples into 4 subbands and downsample by 4.
+ * So for each group of 4 samples that goes in, one sample goes out,
+ * split into 4 separate subbands.
+ */
+static void aptx_qmf_tree_analysis(QMFAnalysis *qmf,
+                                   int32_t samples[4],
+                                   int32_t subband_samples[4])
+{
+    int32_t intermediate_samples[4];
+
+    /* Split 4 input samples into 2 intermediate subbands downsampled to 2 samples */
+    for (int i = 0; i < 2; i++)
+        aptx_qmf_polyphase_analysis(qmf->outer_filter_signal,
+                                    aptx_qmf_outer_coeffs, 23,
+                                    &samples[2*i],
+                                    &intermediate_samples[0+i],
+                                    &intermediate_samples[2+i]);
+
+    /* Split 2 intermediate subband samples into 4 final subbands downsampled to 1 sample */
+    for (int i = 0; i < 2; i++)
+        aptx_qmf_polyphase_analysis(qmf->inner_filter_signal[i],
+                                    aptx_qmf_inner_coeffs, 23,
+                                    &intermediate_samples[2*i],
+                                    &subband_samples[2*i+0],
+                                    &subband_samples[2*i+1]);
+}
+
+/*
+ * Half-band QMF synthesis filter realized with a polyphase FIR filter.
+ * Join 2 subbands and upsample by 2.
+ * So for each 2 subbands sample that goes in, a pair of samples goes out.
+ */
+av_always_inline
+static void aptx_qmf_polyphase_synthesis(FilterSignal signal[NB_FILTERS],
+                                         const int32_t coeffs[NB_FILTERS][FILTER_TAPS],
+                                         int shift,
+                                         int32_t low_subband_input,
+                                         int32_t high_subband_input,
+                                         int32_t samples[NB_FILTERS])
+{
+    int32_t subbands[NB_FILTERS];
+
+    subbands[0] = low_subband_input + high_subband_input;
+    subbands[1] = low_subband_input - high_subband_input;
+
+    for (int i = 0; i < NB_FILTERS; i++) {
+        aptx_qmf_filter_signal_push(&signal[i], subbands[1-i]);
+        samples[i] = aptx_qmf_convolution(&signal[i], coeffs[i], shift);
+    }
+}
+
+/*
+ * Two stage QMF synthesis tree.
+ * Join 4 subbands and upsample by 4.
+ * So for each 4 subbands sample that goes in, a group of 4 samples goes out.
+ */
+static void aptx_qmf_tree_synthesis(QMFAnalysis *qmf,
+                                    int32_t subband_samples[4],
+                                    int32_t samples[4])
+{
+    int32_t intermediate_samples[4];
+
+    /* Join 4 subbands into 2 intermediate subbands upsampled to 2 samples. */
+    for (int i = 0; i < 2; i++)
+        aptx_qmf_polyphase_synthesis(qmf->inner_filter_signal[i],
+                                     aptx_qmf_inner_coeffs, 22,
+                                     subband_samples[2*i+0],
+                                     subband_samples[2*i+1],
+                                     &intermediate_samples[2*i]);
+
+    /* Join 2 samples from intermediate subbands upsampled to 4 samples. */
+    for (int i = 0; i < 2; i++)
+        aptx_qmf_polyphase_synthesis(qmf->outer_filter_signal,
+                                     aptx_qmf_outer_coeffs, 21,
+                                     intermediate_samples[0+i],
+                                     intermediate_samples[2+i],
+                                     &samples[2*i]);
+}
+
+
+av_always_inline
+static int32_t aptx_bin_search(int32_t value, int32_t factor,
+                               const int32_t *intervals, int32_t nb_intervals)
+{
+    int32_t idx = 0;
+
+    for (int i = nb_intervals >> 1; i > 0; i >>= 1)
+        if (MUL64(factor, intervals[idx + i]) <= ((int64_t)value << 24))
+            idx += i;
+
+    return idx;
+}
+
+static void aptx_quantize_difference(Quantize *quantize,
+                                     int32_t sample_difference,
+                                     int32_t dither,
+                                     int32_t quantization_factor,
+                                     ConstTables *tables)
+{
+    const int32_t *intervals = tables->quantize_intervals;
+    int32_t quantized_sample, dithered_sample, parity_change;
+    int32_t d, mean, interval, inv;
+    int64_t error;
+
+    quantized_sample = aptx_bin_search(FFABS(sample_difference) >> 4,
+                                       quantization_factor,
+                                       intervals, tables->tables_size);
+
+    d = rshift32_clip24(MULH(dither, dither), 7) - (1 << 23);
+    d = rshift64(MUL64(d, tables->quantize_dither_factors[quantized_sample]), 23);
+
+    intervals += quantized_sample;
+    mean = (intervals[1] + intervals[0]) / 2;
+    interval = (intervals[1] - intervals[0]) * (-(sample_difference < 0) | 1);
+
+    dithered_sample = rshift64_clip24(MUL64(dither, interval) + ((int64_t)(mean + d) << 32), 32);
+    error = ((int64_t)FFABS(sample_difference) << 20) - MUL64(dithered_sample, quantization_factor);
+    quantize->error = FFABS(rshift64(error, 23));
+
+    parity_change = quantized_sample;
+    if (error < 0)
+        quantized_sample--;
+    else
+        parity_change--;
+
+    inv = -(sample_difference < 0);
+    quantize->quantized_sample               = quantized_sample ^ inv;
+    quantize->quantized_sample_parity_change = parity_change    ^ inv;
+}
+
+static void aptx_encode_channel(Channel *channel, int32_t samples[4])
+{
+    int32_t subband_samples[4];
+    aptx_qmf_tree_analysis(&channel->qmf, samples, subband_samples);
+    aptx_generate_dither(channel);
+    for (int subband = 0; subband < NB_SUBBANDS; subband++) {
+        int32_t diff = av_clip_intp2(subband_samples[subband] - channel->prediction[subband].predicted_sample, 23);
+        aptx_quantize_difference(&channel->quantize[subband], diff,
+                                 channel->dither[subband],
+                                 channel->invert_quantize[subband].quantization_factor,
+                                 &tables[subband]);
+    }
+}
+
+static void aptx_decode_channel(Channel *channel, int32_t samples[4])
+{
+    int32_t subband_samples[4];
+    for (int subband = 0; subband < NB_SUBBANDS; subband++)
+        subband_samples[subband] = channel->prediction[subband].previous_reconstructed_sample;
+    aptx_qmf_tree_synthesis(&channel->qmf, subband_samples, samples);
+}
+
+
+static void aptx_invert_quantization(InvertQuantize *invert_quantize,
+                                     int32_t quantized_sample, int32_t dither,
+                                     ConstTables *tables)
+{
+    int32_t qr, idx, shift, factor_select;
+
+    idx = (quantized_sample ^ -(quantized_sample < 0)) + 1;
+    qr = tables->quantize_intervals[idx] / 2;
+    if (quantized_sample < 0)
+        qr = -qr;
+
+    qr = rshift64_clip24(((int64_t)qr<<32) + MUL64(dither, tables->invert_quantize_dither_factors[idx]), 32);
+    invert_quantize->reconstructed_difference = MUL64(invert_quantize->quantization_factor, qr) >> 19;
+
+    shift = 24 - tables->quantized_bits;
+
+    /* update factor_select */
+    factor_select = 32620 * invert_quantize->factor_select;
+    factor_select = rshift32(factor_select + (tables->quantize_factor_select_offset[idx] << 15), 15);
+    invert_quantize->factor_select = av_clip(factor_select, 0, (shift << 8) | 0xFF);
+
+    /* update quantization factor */
+    idx = (invert_quantize->factor_select & 0xFF) >> 3;
+    shift -= invert_quantize->factor_select >> 8;
+    invert_quantize->quantization_factor = (quantization_factors[idx] << 11) >> shift;
+}
+
+static int32_t *aptx_reconstructed_differences_update(Prediction *prediction,
+                                                      int32_t reconstructed_difference,
+                                                      int order)
+{
+    int32_t *rd1 = prediction->reconstructed_differences, *rd2 = rd1 + order;
+    int p = prediction->pos;
+
+    rd1[p] = rd2[p];
+    prediction->pos = p = (p + 1) % order;
+    rd2[p] = reconstructed_difference;
+    return &rd2[p];
+}
+
+static void aptx_prediction_filtering(Prediction *prediction,
+                                      int32_t reconstructed_difference,
+                                      int order)
+{
+    int32_t reconstructed_sample, predictor, srd0;
+    int32_t *reconstructed_differences;
+    int64_t predicted_difference = 0;
+
+    reconstructed_sample = av_clip_intp2(reconstructed_difference + prediction->predicted_sample, 23);
+    predictor = av_clip_intp2((MUL64(prediction->s_weight[0], prediction->previous_reconstructed_sample)
+                             + MUL64(prediction->s_weight[1], reconstructed_sample)) >> 22, 23);
+    prediction->previous_reconstructed_sample = reconstructed_sample;
+
+    reconstructed_differences = aptx_reconstructed_differences_update(prediction, reconstructed_difference, order);
+    srd0 = FFDIFFSIGN(reconstructed_difference, 0) << 23;
+    for (int i = 0; i < order; i++) {
+        int32_t srd = FF_SIGNBIT(reconstructed_differences[-i-1]) | 1;
+        prediction->d_weight[i] -= rshift32(prediction->d_weight[i] - srd*srd0, 8);
+        predicted_difference += MUL64(reconstructed_differences[-i], prediction->d_weight[i]);
+    }
+
+    prediction->predicted_difference = av_clip_intp2(predicted_difference >> 22, 23);
+    prediction->predicted_sample = av_clip_intp2(predictor + prediction->predicted_difference, 23);
+}
+
+static void aptx_process_subband(InvertQuantize *invert_quantize,
+                                 Prediction *prediction,
+                                 int32_t quantized_sample, int32_t dither,
+                                 ConstTables *tables)
+{
+    int32_t sign, same_sign[2], weight[2], sw1, range;
+
+    aptx_invert_quantization(invert_quantize, quantized_sample, dither, tables);
+
+    sign = FFDIFFSIGN(invert_quantize->reconstructed_difference,
+                      -prediction->predicted_difference);
+    same_sign[0] = sign * prediction->prev_sign[0];
+    same_sign[1] = sign * prediction->prev_sign[1];
+    prediction->prev_sign[0] = prediction->prev_sign[1];
+    prediction->prev_sign[1] = sign | 1;
+
+    range = 0x100000;
+    sw1 = rshift32(-same_sign[1] * prediction->s_weight[1], 1);
+    sw1 = (av_clip(sw1, -range, range) & ~0xF) << 4;
+
+    range = 0x300000;
+    weight[0] = 254 * prediction->s_weight[0] + 0x800000*same_sign[0] + sw1;
+    prediction->s_weight[0] = av_clip(rshift32(weight[0], 8), -range, range);
+
+    range = 0x3C0000 - prediction->s_weight[0];
+    weight[1] = 255 * prediction->s_weight[1] + 0xC00000*same_sign[1];
+    prediction->s_weight[1] = av_clip(rshift32(weight[1], 8), -range, range);
+
+    aptx_prediction_filtering(prediction,
+                              invert_quantize->reconstructed_difference,
+                              tables->prediction_order);
+}
+
+static void aptx_invert_quantize_and_prediction(Channel *channel)
+{
+    for (int subband = 0; subband < NB_SUBBANDS; subband++)
+        aptx_process_subband(&channel->invert_quantize[subband],
+                             &channel->prediction[subband],
+                             channel->quantize[subband].quantized_sample,
+                             channel->dither[subband],
+                             &tables[subband]);
+}
+
+static int32_t aptx_quantized_parity(Channel *channel)
+{
+    int32_t parity = channel->dither_parity;
+
+    for (int subband = 0; subband < NB_SUBBANDS; subband++)
+        parity ^= channel->quantize[subband].quantized_sample;
+
+    return parity & 1;
+}
+
+/* For each sample, ensure that the parity of all subbands of all channels
+ * is 0 except once every 8 samples where the parity is forced to 1. */
+static int aptx_check_parity(Channel channels[NB_CHANNELS], int32_t *idx)
+{
+    int32_t parity = aptx_quantized_parity(&channels[LEFT])
+                   ^ aptx_quantized_parity(&channels[RIGHT]);
+
+    int eighth = *idx == 7;
+    *idx = (*idx + 1) % 8;
+
+    return parity ^ eighth;
+}
+
+static void aptx_insert_sync(Channel channels[NB_CHANNELS], int32_t *idx)
+{
+    if (aptx_check_parity(channels, idx)) {
+        static const int map[] = { 1, 2, 0, 3 };
+        Quantize *min = &channels[NB_CHANNELS-1].quantize[map[0]];
+        for (Channel *c = &channels[NB_CHANNELS-1]; c >= channels; c--)
+            for (int i = 0; i < NB_SUBBANDS; i++)
+                if (c->quantize[map[i]].error < min->error)
+                    min = &c->quantize[map[i]];
+
+        /* Forcing the desired parity is done by offsetting by 1 the quantized
+         * sample from the subband featuring the smallest quantization error. */
+        min->quantized_sample = min->quantized_sample_parity_change;
+    }
+}
+
+static uint16_t aptx_pack_codeword(Channel *channel)
+{
+    int32_t parity = aptx_quantized_parity(channel);
+    return (((channel->quantize[3].quantized_sample & 0x06) | parity) << 13)
+         | (((channel->quantize[2].quantized_sample & 0x03)         ) << 11)
+         | (((channel->quantize[1].quantized_sample & 0x0F)         ) <<  7)
+         | (((channel->quantize[0].quantized_sample & 0x7F)         ) <<  0);
+}
+
+static void aptx_unpack_codeword(Channel *channel, uint16_t codeword)
+{
+    channel->quantize[0].quantized_sample = sign_extend(codeword >>  0, 7);
+    channel->quantize[1].quantized_sample = sign_extend(codeword >>  7, 4);
+    channel->quantize[2].quantized_sample = sign_extend(codeword >> 11, 2);
+    channel->quantize[3].quantized_sample = sign_extend(codeword >> 13, 3);
+    channel->quantize[3].quantized_sample = (channel->quantize[3].quantized_sample & ~1)
+                                          | aptx_quantized_parity(channel);
+}
+
+static void aptx_encode_samples(AptXContext *ctx,
+                                int32_t samples[NB_CHANNELS][4],
+                                uint8_t output[2*NB_CHANNELS])
+{
+    for (int channel = 0; channel < NB_CHANNELS; channel++)
+        aptx_encode_channel(&ctx->channels[channel], samples[channel]);
+
+    aptx_insert_sync(ctx->channels, &ctx->sync_idx);
+
+    for (int channel = 0; channel < NB_CHANNELS; channel++) {
+        aptx_invert_quantize_and_prediction(&ctx->channels[channel]);
+        AV_WB16(output + 2*channel, aptx_pack_codeword(&ctx->channels[channel]));
+    }
+}
+
+static int aptx_decode_samples(AptXContext *ctx,
+                                const uint8_t input[2*NB_CHANNELS],
+                                int32_t samples[NB_CHANNELS][4])
+{
+    int ret;
+
+    for (int channel = 0; channel < NB_CHANNELS; channel++) {
+        uint16_t codeword;
+        aptx_generate_dither(&ctx->channels[channel]);
+
+        codeword = AV_RB16(input + 2*channel);
+        aptx_unpack_codeword(&ctx->channels[channel], codeword);
+        aptx_invert_quantize_and_prediction(&ctx->channels[channel]);
+    }
+
+    ret = aptx_check_parity(ctx->channels, &ctx->sync_idx);
+
+    for (int channel = 0; channel < NB_CHANNELS; channel++)
+        aptx_decode_channel(&ctx->channels[channel], samples[channel]);
+
+    return ret;
+}
+
+
+static av_cold int aptx_init(AVCodecContext *avctx)
+{
+    AptXContext *s = avctx->priv_data;
+
+    for (int chan = 0; chan < NB_CHANNELS; chan++) {
+        Channel *channel = &s->channels[chan];
+        for (int subband = 0; subband < NB_SUBBANDS; subband++) {
+            Prediction *prediction = &channel->prediction[subband];
+            prediction->prev_sign[0] = 1;
+            prediction->prev_sign[1] = 1;
+        }
+    }
+
+    return 0;
+}
+
+static int aptx_decode_frame(AVCodecContext *avctx, void *data,
+                             int *got_frame_ptr, AVPacket *avpkt)
+{
+    AptXContext *s = avctx->priv_data;
+    AVFrame *frame = data;
+    int ret;
+
+    if (avpkt->size < 4) {
+        av_log(avctx, AV_LOG_ERROR, "Packet is too small\n");
+        return AVERROR_INVALIDDATA;
+    }
+
+    /* get output buffer */
+    frame->channels = NB_CHANNELS;
+    frame->format = AV_SAMPLE_FMT_S32P;
+    frame->nb_samples = avpkt->size & ~3;
+    if ((ret = ff_get_buffer(avctx, frame, 0)) < 0)
+        return ret;
+
+    for (int pos = 0; pos < frame->nb_samples; pos += 4) {
+        int32_t samples[NB_CHANNELS][4];
+
+        if (aptx_decode_samples(s, &avpkt->data[pos], samples)) {
+            av_log(avctx, AV_LOG_ERROR, "Synchronization error\n");
+            return AVERROR_INVALIDDATA;
+        }
+
+        for (int channel = 0; channel < NB_CHANNELS; channel++)
+            for (int sample = 0; sample < 4; sample++)
+                AV_WN32A(&frame->data[channel][4*(sample+pos)],
+                         samples[channel][sample] << 8);
+    }
+
+    *got_frame_ptr = 1;
+    return frame->nb_samples;
+}
+
+static int aptx_encode_frame(AVCodecContext *avctx, AVPacket *avpkt,
+                             const AVFrame *frame, int *got_packet_ptr)
+{
+    AptXContext *s = avctx->priv_data;
+    int ret;
+
+    /* input must contain a multiple of 4 samples */
+    if (frame->nb_samples & 3 || frame->nb_samples == 0) {
+        av_log(avctx, AV_LOG_ERROR, "Frame must have a multiple of 4 samples\n");
+        return 0;
+    }
+
+    if ((ret = ff_alloc_packet2(avctx, avpkt, frame->nb_samples, 0)) < 0)
+        return ret;
+
+    for (int pos = 0; pos < frame->nb_samples; pos += 4) {
+        int32_t samples[NB_CHANNELS][4];
+
+        for (int channel = 0; channel < NB_CHANNELS; channel++)
+            for (int sample = 0; sample < 4; sample++)
+                samples[channel][sample] = (int32_t)AV_RN32A(&frame->data[channel][4*(sample+pos)]) >> 8;
+
+        aptx_encode_samples(s, samples, avpkt->data + pos);
+    }
+
+    *got_packet_ptr = 1;
+    return 0;
+}
+
+
+#if CONFIG_APTX_DECODER
+AVCodec ff_aptx_decoder = {
+    .name                  = "aptx",
+    .long_name             = NULL_IF_CONFIG_SMALL("aptX (Audio Processing Technology for Bluetooth)"),
+    .type                  = AVMEDIA_TYPE_AUDIO,
+    .id                    = AV_CODEC_ID_APTX,
+    .priv_data_size        = sizeof(AptXContext),
+    .init                  = aptx_init,
+    .decode                = aptx_decode_frame,
+    .capabilities          = AV_CODEC_CAP_DR1,
+    .channel_layouts       = (const uint64_t[]) { AV_CH_LAYOUT_STEREO, 0},
+    .sample_fmts           = (const enum AVSampleFormat[]) { AV_SAMPLE_FMT_S32P,
+                                                             AV_SAMPLE_FMT_NONE },
+};
+#endif
+
+#if CONFIG_APTX_ENCODER
+AVCodec ff_aptx_encoder = {
+    .name                  = "aptx",
+    .long_name             = NULL_IF_CONFIG_SMALL("aptX (Audio Processing Technology for Bluetooth)"),
+    .type                  = AVMEDIA_TYPE_AUDIO,
+    .id                    = AV_CODEC_ID_APTX,
+    .priv_data_size        = sizeof(AptXContext),
+    .init                  = aptx_init,
+    .encode2               = aptx_encode_frame,
+    .capabilities          = AV_CODEC_CAP_VARIABLE_FRAME_SIZE,
+    .channel_layouts       = (const uint64_t[]) { AV_CH_LAYOUT_STEREO, 0},
+    .sample_fmts           = (const enum AVSampleFormat[]) { AV_SAMPLE_FMT_S32P,
+                                                             AV_SAMPLE_FMT_NONE },
+};
+#endif
diff --git a/libavcodec/avcodec.h b/libavcodec/avcodec.h
index c4134424f0..36a99f4162 100644
--- a/libavcodec/avcodec.h
+++ b/libavcodec/avcodec.h
@@ -614,6 +614,7 @@  enum AVCodecID {
     AV_CODEC_ID_PAF_AUDIO,
     AV_CODEC_ID_ON2AVC,
     AV_CODEC_ID_DSS_SP,
+    AV_CODEC_ID_APTX,
 
     AV_CODEC_ID_FFWAVESYNTH = 0x15800,
     AV_CODEC_ID_SONIC,
diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index 92bf1d2681..c3688de1d6 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -2859,6 +2859,13 @@  static const AVCodecDescriptor codec_descriptors[] = {
         .long_name = NULL_IF_CONFIG_SMALL("ADPCM MTAF"),
         .props     = AV_CODEC_PROP_LOSSY,
     },
+    {
+        .id        = AV_CODEC_ID_APTX,
+        .type      = AVMEDIA_TYPE_AUDIO,
+        .name      = "aptx",
+        .long_name = NULL_IF_CONFIG_SMALL("aptX (Audio Processing Technology for Bluetooth)"),
+        .props     = AV_CODEC_PROP_LOSSY,
+    },
 
     /* subtitle codecs */
     {