[FFmpeg-devel] [PATCH 1/1] avcodec: Add A2DP LATM audio support.
cenzhanquan2 at gmail.com
cenzhanquan2 at gmail.com
Fri Apr 25 13:41:59 EEST 2025
From: zhanquan cen <cenzhanquan2 at gmail.com>
This commit introduces three key components for Bluetooth A2DP LATM streams:
1.A2DP-specific LATM decoder (aac_latm_a2dp)
New codec ID AV_CODEC_ID_AAC_LATM_A2DP
Inherits LATMContext with A2DP extensions
Attaches "a2dp_rechunk" bitstream filter.
2.Parser enhancement
Extend latm_parser to handle A2DP variant
Support rechunking fragmented A2DP packets.
3.Bitstream filter implementation (a2dp_rechunk)
Reassembles A2DP packets into valid LATM frames
Handles dynamic MTU fragmentation (672 bytes typical)
Supports codecs: SBC, AAC, AAC_LATM & AAC_LATM_A2DP
This enables end-to-end processing of Bluetooth A2DP audio streams,
including: packet rechunking -> LATM syntax parsing -> AAC decoding.
Signed-off-by: zhanquan cen <cenzhanquan2 at gmail.com>
---
libavcodec/Makefile | 1 +
libavcodec/a2dp_rechunk_bsf.c | 153 +++++++++++++++++++++++++++++++++
libavcodec/aac/aacdec_latm.h | 26 +++++-
libavcodec/allcodecs.c | 1 +
libavcodec/bitstream_filters.c | 1 +
libavcodec/codec_desc.c | 8 ++
libavcodec/codec_id.h | 1 +
libavcodec/latm_parser.c | 2 +-
8 files changed, 191 insertions(+), 2 deletions(-)
create mode 100644 libavcodec/a2dp_rechunk_bsf.c
diff --git a/libavcodec/Makefile b/libavcodec/Makefile
index 7bd1dbec9a..e74d722669 100644
--- a/libavcodec/Makefile
+++ b/libavcodec/Makefile
@@ -1266,6 +1266,7 @@ OBJS-$(CONFIG_HAPQA_EXTRACT_BSF) += hap.o
OBJS-$(CONFIG_HEVC_METADATA_BSF) += h265_profile_level.o h2645data.o
OBJS-$(CONFIG_REMOVE_EXTRADATA_BSF) += av1_parse.o
OBJS-$(CONFIG_TRUEHD_CORE_BSF) += mlp_parse.o mlp.o
+OBJS-$(CONFIG_A2DP_RECHUNK_BSF) += a2dp_rechunk_bsf.o
# thread libraries
OBJS-$(HAVE_LIBC_MSVCRT) += file_open.o
diff --git a/libavcodec/a2dp_rechunk_bsf.c b/libavcodec/a2dp_rechunk_bsf.c
new file mode 100644
index 0000000000..59c16f152a
--- /dev/null
+++ b/libavcodec/a2dp_rechunk_bsf.c
@@ -0,0 +1,153 @@
+/*
+* A2DP rechunk bitstream filter
+*
+* 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 "avcodec.h"
+#include "bsf_internal.h"
+#include "libavutil/avassert.h"
+#include "libavutil/opt.h"
+#include <libavcodec/avcodec.h>
+typedef struct A2DPContext {
+ AVCodecContext *avctx;
+ AVCodecParserContext *parser;
+ AVPacket *in_pkt;
+} A2DPContext;
+
+static int a2dp_rechunk_init(AVBSFContext *ctx)
+{
+ A2DPContext *s = ctx->priv_data;
+ const AVCodec *codec;
+ int ret;
+
+ s->in_pkt = av_packet_alloc();
+ if (!s->in_pkt)
+ return AVERROR(ENOMEM);
+
+ s->parser = av_parser_init(ctx->par_in->codec_id);
+ if (!s->parser) {
+ ret = AVERROR(EINVAL);
+ goto error;
+ }
+
+ /* find the audio decoder */
+ codec = avcodec_find_decoder(ctx->par_in->codec_id);
+ if (!codec) {
+ ret = AVERROR_DECODER_NOT_FOUND;
+ goto error;
+ }
+
+ s->avctx = avcodec_alloc_context3(codec);
+ if (!s->avctx) {
+ ret = AVERROR(ENOMEM);
+ goto error;
+ }
+
+ return 0;
+
+ error:
+ av_packet_free(&s->in_pkt);
+ av_parser_close(s->parser);
+ return ret;
+}
+
+static void a2dp_rechunk_uninit(AVBSFContext *ctx)
+{
+ A2DPContext *s = ctx->priv_data;
+
+ av_packet_free(&s->in_pkt);
+ av_parser_close(s->parser);
+ avcodec_free_context(&s->avctx);
+}
+
+static void a2dp_rechunk_flush(AVBSFContext *ctx)
+{
+ A2DPContext *s = ctx->priv_data;
+
+ av_packet_unref(s->in_pkt);
+}
+
+static int a2dp_rechunk_filter(AVBSFContext *ctx, AVPacket *pkt)
+{
+ A2DPContext *s = ctx->priv_data;
+ uint8_t *outbuf;
+ int outbuf_size;
+ int ret = 0;
+
+ while (1) {
+ if (!s->in_pkt->size) {
+ ret = ff_bsf_get_packet_ref(ctx, s->in_pkt);
+ if (ret < 0)
+ break;
+ }
+
+ ret = av_parser_parse2(s->parser, s->avctx, &outbuf, &outbuf_size,
+ s->in_pkt->data, s->in_pkt->size,
+ AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
+ /* if parser header error, return error */
+ if (ret < 0) {
+ av_log(ctx, AV_LOG_ERROR, "[A2DP] Parser error: %d\n", ret);
+ break;
+ }
+
+ s->in_pkt->data += ret;
+ s->in_pkt->size -= ret;
+
+ /* if not found end, parser had cached remaining data, unref current pkt */
+ if (!outbuf_size || !outbuf) {
+ av_assert0(!s->in_pkt->size);
+ av_packet_unref(s->in_pkt);
+ continue;
+ }
+
+ /* note: outbuf_size >= ret */
+ /* get one packet from in_pkt or */
+ /* cache combine with part in_pkt data */
+ if (!s->in_pkt->size) {
+ if (outbuf_size == ret) {
+ av_packet_move_ref(pkt, s->in_pkt);
+ } else {
+ av_packet_unref(s->in_pkt);
+ }
+ }
+
+ pkt->data = outbuf;
+ pkt->size = outbuf_size;
+ break;
+ };
+
+ return ret;
+ }
+
+static const enum AVCodecID codec_ids[] = {
+ AV_CODEC_ID_SBC,
+ AV_CODEC_ID_AAC,
+ AV_CODEC_ID_AAC_LATM,
+ AV_CODEC_ID_AAC_LATM_A2DP,
+ AV_CODEC_ID_NONE,
+};
+
+const FFBitStreamFilter ff_a2dp_rechunk_bsf = {
+ .p.name = "a2dp_rechunk",
+ .p.codec_ids = codec_ids,
+ .priv_data_size = sizeof(A2DPContext),
+ .filter = a2dp_rechunk_filter,
+ .init = a2dp_rechunk_init,
+ .flush = a2dp_rechunk_flush,
+ .close = a2dp_rechunk_uninit,
+};
\ No newline at end of file
diff --git a/libavcodec/aac/aacdec_latm.h b/libavcodec/aac/aacdec_latm.h
index 398d40741b..b0aca3c331 100644
--- a/libavcodec/aac/aacdec_latm.h
+++ b/libavcodec/aac/aacdec_latm.h
@@ -347,4 +347,28 @@ const FFCodec ff_aac_latm_decoder = {
.p.profiles = NULL_IF_CONFIG_SMALL(ff_aac_profiles),
};
-#endif /* AVCODEC_AAC_AACDEC_LATM_H */
+/*
+* Note: This decoder filter is intended to decode A2DP LATM streams transferred,
+* frame received from socket, need do rechunk process
+*/
+const FFCodec ff_aac_latm_a2dp_decoder = {
+ .p.name = "aac_latm_a2dp",
+ CODEC_LONG_NAME("AAC LATM (Advanced Audio Coding LATM syntax) for A2DP"),
+ .p.type = AVMEDIA_TYPE_AUDIO,
+ .p.id = AV_CODEC_ID_AAC_LATM_A2DP,
+ .priv_data_size = sizeof(struct LATMContext),
+ .init = latm_decode_init,
+ .close = decode_close,
+ FF_CODEC_DECODE_CB(latm_decode_frame),
+ .p.sample_fmts = (const enum AVSampleFormat[]) {
+ AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_NONE
+ },
+ .p.capabilities = AV_CODEC_CAP_CHANNEL_CONF | AV_CODEC_CAP_DR1,
+ .caps_internal = FF_CODEC_CAP_INIT_CLEANUP,
+ CODEC_CH_LAYOUTS_ARRAY(ff_aac_ch_layout),
+ .flush = flush,
+ .p.profiles = NULL_IF_CONFIG_SMALL(ff_aac_profiles),
+ .bsfs = "a2dp_rechunk",
+};
+
+#endif /* AVCODEC_AAC_AACDEC_LATM_H */
\ No newline at end of file
diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c
index f10519617e..2cc2c87dc6 100644
--- a/libavcodec/allcodecs.c
+++ b/libavcodec/allcodecs.c
@@ -427,6 +427,7 @@ extern const FFCodec ff_aac_encoder;
extern const FFCodec ff_aac_decoder;
extern const FFCodec ff_aac_fixed_decoder;
extern const FFCodec ff_aac_latm_decoder;
+extern const FFCodec ff_aac_latm_a2dp_decoder;
extern const FFCodec ff_ac3_encoder;
extern const FFCodec ff_ac3_decoder;
extern const FFCodec ff_ac3_fixed_encoder;
diff --git a/libavcodec/bitstream_filters.c b/libavcodec/bitstream_filters.c
index f923411bee..5d9d7a1ec3 100644
--- a/libavcodec/bitstream_filters.c
+++ b/libavcodec/bitstream_filters.c
@@ -24,6 +24,7 @@
#include "bsf.h"
#include "bsf_internal.h"
+extern const FFBitStreamFilter ff_a2dp_rechunk_bsf;
extern const FFBitStreamFilter ff_aac_adtstoasc_bsf;
extern const FFBitStreamFilter ff_av1_frame_merge_bsf;
extern const FFBitStreamFilter ff_av1_frame_split_bsf;
diff --git a/libavcodec/codec_desc.c b/libavcodec/codec_desc.c
index 9fb190e35a..ca12f5cf64 100644
--- a/libavcodec/codec_desc.c
+++ b/libavcodec/codec_desc.c
@@ -3466,6 +3466,14 @@ static const AVCodecDescriptor codec_descriptors[] = {
.long_name = NULL_IF_CONFIG_SMALL("LC3 (Low Complexity Communication Codec)"),
.props = AV_CODEC_PROP_INTRA_ONLY | AV_CODEC_PROP_LOSSY,
},
+ {
+ .id = AV_CODEC_ID_AAC_LATM_A2DP,
+ .type = AVMEDIA_TYPE_AUDIO,
+ .name = "aac_latm_a2dp",
+ .long_name = NULL_IF_CONFIG_SMALL("AAC LATM (Advanced Audio Coding LATM syntax) for A2DP"),
+ .props = AV_CODEC_PROP_LOSSY,
+ .profiles = NULL_IF_CONFIG_SMALL(ff_aac_profiles),
+ },
/* subtitle codecs */
{
diff --git a/libavcodec/codec_id.h b/libavcodec/codec_id.h
index 2f6efe8261..dc566acc3a 100644
--- a/libavcodec/codec_id.h
+++ b/libavcodec/codec_id.h
@@ -552,6 +552,7 @@ enum AVCodecID {
AV_CODEC_ID_OSQ,
AV_CODEC_ID_QOA,
AV_CODEC_ID_LC3,
+ AV_CODEC_ID_AAC_LATM_A2DP,
/* subtitle codecs */
AV_CODEC_ID_FIRST_SUBTITLE = 0x17000, ///< A dummy ID pointing at the start of subtitle codecs.
diff --git a/libavcodec/latm_parser.c b/libavcodec/latm_parser.c
index 8cc2024c4f..0af587115c 100644
--- a/libavcodec/latm_parser.c
+++ b/libavcodec/latm_parser.c
@@ -105,7 +105,7 @@ static int latm_parse(AVCodecParserContext *s1, AVCodecContext *avctx,
}
const AVCodecParser ff_aac_latm_parser = {
- .codec_ids = { AV_CODEC_ID_AAC_LATM },
+ .codec_ids = { AV_CODEC_ID_AAC_LATM , AV_CODEC_ID_AAC_LATM_A2DP},
.priv_data_size = sizeof(LATMParseContext),
.parser_parse = latm_parse,
.parser_close = ff_parse_close
--
2.34.1
More information about the ffmpeg-devel
mailing list