From: zhanquan cen <cenzhanqu...@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 <cenzhanqu...@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 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-requ...@ffmpeg.org with subject "unsubscribe".