PR #25406 opened by Jun Zhao (mypopydev) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25406 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25406.patch
This patch set fixes G.726LE encoding tag selection in the Sun AU muxer (libavformat/au.c). Previously, au_write_header() looked up the tag via ff_codec_get_tag(), which always returned the first matching table entry (23, 4-bit / 32 kbit/s) regardless of the actual sample bit width, causing non-4-bit streams to be reopened with wrong bitrates and durations. The muxer now selects the encoding tag from bits_per_coded_sample (or derives it from bit_rate and sample_rate when unset), mapping 3/4/5-bit G.726LE to standard Sun AU encodings 25/23/26, writing the non-standard '7262' tag with a warning for 2-bit (16 kbit/s) streams, and rejecting unsupported bit widths. A new fate-auenc test is added to cover round-trip muxing and invalid bit-width rejection. >From cae778cadea63fbda13c49b3427e1bc5c60bdc0f Mon Sep 17 00:00:00 2001 From: Jun Zhao <[email protected]> Date: Sat, 24 Oct 2026 10:05:06 +0800 Subject: [PATCH 1/2] avformat/au: select G.726LE encoding tag from sample bit width au_write_header() looked up the AU encoding tag with ff_codec_get_tag(), which always returned the first AV_CODEC_ID_ADPCM_G726LE entry (23, 4-bit 32 kbit/s) regardless of the stream bit width. Remuxing 16, 24 or 40 kbit/s G.726LE into AU wrote encoding 23, so reopening the file read it back as 4-bit 32 kbit/s with the wrong duration. Select the tag from bits_per_coded_sample, falling back to bit_rate and sample_rate when bits_per_coded_sample is unset: 25 for 3-bit, 23 for 4-bit, 26 for 5-bit, and the existing '7262' tag (with a warning, since Sun AU defines no standard 2-bit code) for 2-bit. Reject other widths. Fixes: https://code.ffmpeg.org/FFmpeg/FFmpeg/issues/25401 Signed-off-by: Jun Zhao <[email protected]> --- libavformat/au.c | 52 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/libavformat/au.c b/libavformat/au.c index 22449958258..af7e7aec48c 100644 --- a/libavformat/au.c +++ b/libavformat/au.c @@ -284,6 +284,44 @@ static int au_get_annotations(AVFormatContext *s, AVBPrint *annotations) return av_bprint_is_complete(annotations) ? 0 : AVERROR(ENOMEM); } +/* Sun encodings 25, 23 and 26 are 3-, 4- and 5-bit G.726. There is no Sun + * number for 2-bit G.726; '7262' is the private tag the demuxer reads. */ +static int au_g726le_tag(AVFormatContext *s, AVCodecParameters *par) +{ + int64_t bps = par->bits_per_coded_sample; + + if (bps <= 0) { + if (par->bit_rate <= 0 || par->sample_rate <= 0) { + av_log(s, AV_LOG_ERROR, "Cannot determine G.726LE bit width\n"); + return AVERROR(EINVAL); + } + bps = (par->bit_rate + par->sample_rate / 2) / par->sample_rate; + } + + switch (bps) { + case 2: + av_log(s, AV_LOG_WARNING, + "Sun AU has no standard encoding for 2-bit G.726LE; " + "writing non-standard '7262' tag\n"); + par->codec_tag = MKBETAG('7', '2', '6', '2'); + break; + case 3: + par->codec_tag = 25; + break; + case 4: + par->codec_tag = 23; + break; + case 5: + par->codec_tag = 26; + break; + default: + av_log(s, AV_LOG_ERROR, + "Unsupported G.726LE bit width %"PRId64"\n", bps); + return AVERROR(EINVAL); + } + return 0; +} + static int au_write_header(AVFormatContext *s) { int ret; @@ -292,10 +330,16 @@ static int au_write_header(AVFormatContext *s) AVCodecParameters *par = s->streams[0]->codecpar; AVBPrint annotations; - par->codec_tag = ff_codec_get_tag(codec_au_tags, par->codec_id); - if (!par->codec_tag) { - av_log(s, AV_LOG_ERROR, "unsupported codec\n"); - return AVERROR(EINVAL); + if (par->codec_id == AV_CODEC_ID_ADPCM_G726LE) { + ret = au_g726le_tag(s, par); + if (ret < 0) + return ret; + } else { + par->codec_tag = ff_codec_get_tag(codec_au_tags, par->codec_id); + if (!par->codec_tag) { + av_log(s, AV_LOG_ERROR, "unsupported codec\n"); + return AVERROR(EINVAL); + } } av_bprint_init(&annotations, 0, INT_MAX - 24); -- 2.54.0 >From 673c0017cfee368b417c4ff7611760f8cc84d7b8 Mon Sep 17 00:00:00 2001 From: Jun Zhao <[email protected]> Date: Sat, 24 Oct 2026 10:05:06 +0800 Subject: [PATCH 2/2] tests/fate: add an AU G.726LE encoding tag round-trip test Cover 2-, 3-, 4- and 5-bit G.726LE, bit-rate fallback when bits_per_coded_sample is unset, and rejection of unsupported bit widths. Signed-off-by: Jun Zhao <[email protected]> --- libavformat/Makefile | 1 + libavformat/tests/.gitignore | 1 + libavformat/tests/auenc.c | 202 +++++++++++++++++++++++++++++++++++ tests/fate/libavformat.mak | 4 + tests/ref/fate/auenc | 8 ++ 5 files changed, 216 insertions(+) create mode 100644 libavformat/tests/auenc.c create mode 100644 tests/ref/fate/auenc diff --git a/libavformat/Makefile b/libavformat/Makefile index dbf78aa4b94..92c09714fe7 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -804,6 +804,7 @@ TESTPROGS-$(CONFIG_NETWORK) += noproxy TESTPROGS-$(CONFIG_SRTP) += srtp TESTPROGS-$(CONFIG_IMF_DEMUXER) += imf TESTPROGS-$(CONFIG_MP3_MUXER) += id3v2enc id3v2chap +TESTPROGS-$(CONFIG_AU_MUXER) += auenc TESTPROGS-$(CONFIG_OGG_MUXER) += ogg_pages TOOLS = aviocat \ diff --git a/libavformat/tests/.gitignore b/libavformat/tests/.gitignore index 6e594111121..f3640a487cc 100644 --- a/libavformat/tests/.gitignore +++ b/libavformat/tests/.gitignore @@ -1,3 +1,4 @@ +/auenc /id3v2 /id3v2enc /fifo_muxer diff --git a/libavformat/tests/auenc.c b/libavformat/tests/auenc.c new file mode 100644 index 00000000000..227a07d53a2 --- /dev/null +++ b/libavformat/tests/auenc.c @@ -0,0 +1,202 @@ +/* + * 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 <inttypes.h> +#include <stdio.h> +#include <string.h> + +#include "libavutil/mem.h" +#include "libavformat/avformat.h" +#include "libavformat/avio.h" + +#define AU_PAYLOAD 80 + +typedef struct { + const uint8_t *data; + int size; + int pos; +} MemFile; + +static int mem_read(void *opaque, uint8_t *buf, int buf_size) +{ + MemFile *m = opaque; + int left = m->size - m->pos; + + if (left <= 0) + return AVERROR_EOF; + if (buf_size > left) + buf_size = left; + memcpy(buf, m->data + m->pos, buf_size); + m->pos += buf_size; + return buf_size; +} + +static int64_t mem_seek(void *opaque, int64_t offset, int whence) +{ + MemFile *m = opaque; + + whence &= ~AVSEEK_FORCE; + if (whence == AVSEEK_SIZE) + return m->size; + if (whence == SEEK_CUR) + offset += m->pos; + else if (whence == SEEK_END) + offset += m->size; + else if (whence != SEEK_SET) + return AVERROR(EINVAL); + if (offset < 0 || offset > m->size) + return AVERROR(EINVAL); + m->pos = offset; + return offset; +} + +static void free_pb(AVIOContext **pb) +{ + if (!*pb) + return; + av_freep(&(*pb)->buffer); + avio_context_free(pb); +} + +static int run_case(int bps, int64_t bit_rate) +{ + AVFormatContext *out = NULL, *in = NULL; + AVIOContext *pb = NULL, *in_pb = NULL; + AVStream *st; + AVPacket *pkt = NULL; + MemFile mem = { 0 }; + uint8_t *buf = NULL, *io_buf = NULL; + int ret, size; + + ret = avformat_alloc_output_context2(&out, NULL, "au", NULL); + if (ret < 0) + return 1; + if (avio_open_dyn_buf(&pb) < 0) { + ret = 1; + goto done; + } + out->pb = pb; + + st = avformat_new_stream(out, NULL); + if (!st) { + ret = 1; + goto done; + } + st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; + st->codecpar->codec_id = AV_CODEC_ID_ADPCM_G726LE; + st->codecpar->sample_rate = 8000; + st->codecpar->bits_per_coded_sample = bps; + st->codecpar->bit_rate = bit_rate; + st->codecpar->ch_layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_MONO; + + ret = avformat_write_header(out, NULL); + if (ret < 0) { + printf("bps=%d bit_rate=%"PRId64" rejected\n", bps, bit_rate); + ret = 0; + goto done; + } + + pkt = av_packet_alloc(); + if (!pkt || (ret = av_new_packet(pkt, AU_PAYLOAD)) < 0) { + ret = 1; + goto done; + } + pkt->stream_index = 0; + pkt->pts = pkt->dts = 0; + if ((ret = av_write_frame(out, pkt)) < 0 || + (ret = av_write_trailer(out)) < 0) { + ret = 1; + goto done; + } + + size = avio_close_dyn_buf(pb, &buf); + pb = NULL; + if (size < 16) { + ret = 1; + goto done; + } + + mem.data = buf; + mem.size = size; + io_buf = av_malloc(4096); + if (!io_buf) { + ret = 1; + goto done; + } + in_pb = avio_alloc_context(io_buf, 4096, 0, &mem, mem_read, NULL, mem_seek); + if (!in_pb) { + av_free(io_buf); + ret = 1; + goto done; + } + + in = avformat_alloc_context(); + if (!in) { + ret = 1; + goto done; + } + in->pb = in_pb; + if ((ret = avformat_open_input(&in, NULL, NULL, NULL)) < 0 || + !in->nb_streams) { + ret = 1; + goto done; + } + + st = in->streams[0]; + printf("bps=%d bit_rate=%"PRId64" tag=%02x %02x %02x %02x " + "read_bps=%d read_bit_rate=%"PRId64" duration=%"PRId64"\n", + bps, bit_rate, buf[12], buf[13], buf[14], buf[15], + st->codecpar->bits_per_coded_sample, st->codecpar->bit_rate, + st->duration); + ret = 0; + +done: + av_packet_free(&pkt); + if (in) { + in_pb = in->pb; + avformat_close_input(&in); + } + free_pb(&in_pb); + if (pb) { + uint8_t *tmp = NULL; + avio_close_dyn_buf(pb, &tmp); + av_free(tmp); + } + av_free(buf); + avformat_free_context(out); + return ret < 0 ? 1 : ret; +} + +int main(void) +{ + static const int cases[][2] = { + { 2, 0 }, + { 3, 0 }, + { 4, 0 }, + { 5, 0 }, + { 0, 16000 }, + { 0, 40000 }, + { 1, 0 }, + { 0, 0 }, + }; + int i, ret = 0; + + for (i = 0; i < FF_ARRAY_ELEMS(cases); i++) + ret |= run_case(cases[i][0], cases[i][1]); + return ret; +} diff --git a/tests/fate/libavformat.mak b/tests/fate/libavformat.mak index 68b5f6c101b..18270bbcab9 100644 --- a/tests/fate/libavformat.mak +++ b/tests/fate/libavformat.mak @@ -10,6 +10,10 @@ FATE_LIBAVFORMAT-$(CONFIG_MP3_MUXER) += fate-id3v2chap fate-id3v2chap: libavformat/tests/id3v2chap$(EXESUF) fate-id3v2chap: CMD = run libavformat/tests/id3v2chap$(EXESUF) +FATE_LIBAVFORMAT-$(call ALLYES, AU_MUXER AU_DEMUXER) += fate-auenc +fate-auenc: libavformat/tests/auenc$(EXESUF) +fate-auenc: CMD = run libavformat/tests/auenc$(EXESUF) + FATE_LIBAVFORMAT += fate-mkdir fate-mkdir: libavformat/tests/mkdir$(EXESUF) fate-mkdir: CMD = run libavformat/tests/mkdir$(EXESUF) diff --git a/tests/ref/fate/auenc b/tests/ref/fate/auenc new file mode 100644 index 00000000000..974a1e2e8a9 --- /dev/null +++ b/tests/ref/fate/auenc @@ -0,0 +1,8 @@ +bps=2 bit_rate=0 tag=37 32 36 32 read_bps=2 read_bit_rate=16000 duration=320 +bps=3 bit_rate=0 tag=00 00 00 19 read_bps=3 read_bit_rate=24000 duration=213 +bps=4 bit_rate=0 tag=00 00 00 17 read_bps=4 read_bit_rate=32000 duration=160 +bps=5 bit_rate=0 tag=00 00 00 1a read_bps=5 read_bit_rate=40000 duration=128 +bps=0 bit_rate=16000 tag=37 32 36 32 read_bps=2 read_bit_rate=16000 duration=320 +bps=0 bit_rate=40000 tag=00 00 00 1a read_bps=5 read_bit_rate=40000 duration=128 +bps=1 bit_rate=0 rejected +bps=0 bit_rate=0 rejected -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
