PR #24804 opened by Lynne URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24804 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24804.patch
This series of commits adds support for AC-4 muxing into MPEG containers. It also adds support for packed audio segs into HLS. I used https://ott.dolby.com/OnDelKits/AC-4_Online_Delivery_Kit_1.6/Documentation/Dolby_AC4_spec/help_files/topics/c_ov_intro.html as the documentation. This series overlaps somewhat with https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24680 >From ea848e8ea9723f4b9f4965b862538484f7699448 Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Sun, 27 Sep 2026 20:22:00 +0900 Subject: [PATCH 01/15] avcodec/ac4_parse: add AC-4 table of contents parsing Parse the ac4_toc() of raw AC-4 frames (ETSI TS 103 190-1 and 190-2), and derive the ac4_dsi_v1() decoder specific information from it, as stored in the AC4SpecificBox of ISOBMFF files (ETSI TS 103 190-2 V1.3.1, annex E). Besides the table of contents, the presentation substreams and the metadata of the audio substreams are inspected for the immersive_audio_indicator and de_indicator. For immersive stereo presentations, an additional regular stereo presentation is signalled, as recommended by Dolby. The generated DSIs are identical to the ones in Dolby's AC-4 online delivery test streams. Functions to parse DSIs and to map AC-4 channel masks to channel layouts and ISO/IEC 23091-3 channel configurations are provided for muxers and manifest writers. --- libavcodec/Makefile | 1 + libavcodec/ac4_parse.c | 2011 ++++++++++++++++++++++++++++++++++++++++ libavcodec/ac4_parse.h | 145 +++ 3 files changed, 2157 insertions(+) create mode 100644 libavcodec/ac4_parse.c create mode 100644 libavcodec/ac4_parse.h diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 7785638c77..3922e84812 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -28,6 +28,7 @@ HEADERS = ac3_parser.h \ vorbis_parser.h \ OBJS = ac3_parser.o \ + ac4_parse.o \ adts_parser.o \ allcodecs.o \ avcodec.o \ diff --git a/libavcodec/ac4_parse.c b/libavcodec/ac4_parse.c new file mode 100644 index 0000000000..9c322e3b85 --- /dev/null +++ b/libavcodec/ac4_parse.c @@ -0,0 +1,2011 @@ +/* + * AC-4 table of contents and decoder specific information parsing + * + * 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 + */ + +/** + * @file + * AC-4 table of contents parsing and ISOBMFF decoder specific information + * (ETSI TS 103 190-1 and ETSI TS 103 190-2, annex E) generation. + */ + +#include <string.h> + +#include "libavutil/avassert.h" +#include "libavutil/internal.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/log.h" +#include "libavutil/mem.h" +#include "ac4_parse.h" +#include "defs.h" +#include "get_bits.h" +#include "put_bits.h" + +#define MAX_PRESENTATIONS 32 +#define MAX_GROUPS 32 +#define MAX_PRES_GROUPS 16 +#define MAX_GROUP_SUBSTREAMS 8 +#define MAX_SUBSTREAMS 64 +#define MAX_EMDF 32 +#define MAX_TARGETS 16 + +/* Channel modes, ETSI TS 103 190-2 table 56 */ +enum { + CH_MODE_MONO = 0, + CH_MODE_STEREO, + CH_MODE_3_0, + CH_MODE_5_0, + CH_MODE_5_1, + CH_MODE_7_0_34, + CH_MODE_7_1_34, + CH_MODE_7_0_52, + CH_MODE_7_1_52, + CH_MODE_7_0_322, + CH_MODE_7_1_322, + CH_MODE_7_0_4, + CH_MODE_7_1_4, + CH_MODE_9_0_4, + CH_MODE_9_1_4, + CH_MODE_22_2, + CH_MODE_NB, +}; + +/* Channel groups, ETSI TS 103 190-2 table A.27 */ +#define G_LR (1 << 0) +#define G_C (1 << 1) +#define G_LSRS (1 << 2) +#define G_LBRB (1 << 3) +#define G_TFLR (1 << 4) +#define G_TBLR (1 << 5) +#define G_LFE (1 << 6) +#define G_TSLR (1 << 7) +#define G_TFC (1 << 9) +#define G_TBC (1 << 10) +#define G_TC (1 << 11) +#define G_LFE2 (1 << 12) +#define G_BFLR (1 << 13) +#define G_BFC (1 << 14) +#define G_CB (1 << 15) +#define G_SCR (1 << 16) +#define G_LWRW (1 << 17) + +/* All channel groups of each channel mode */ +static const uint32_t ch_mode_groups[CH_MODE_NB] = { + [CH_MODE_MONO] = G_C, + [CH_MODE_STEREO] = G_LR, + [CH_MODE_3_0] = G_LR | G_C, + [CH_MODE_5_0] = G_LR | G_C | G_LSRS, + [CH_MODE_5_1] = G_LR | G_C | G_LSRS | G_LFE, + [CH_MODE_7_0_34] = G_LR | G_C | G_LSRS | G_LBRB, + [CH_MODE_7_1_34] = G_LR | G_C | G_LSRS | G_LBRB | G_LFE, + [CH_MODE_7_0_52] = G_LR | G_C | G_LSRS | G_LWRW, + [CH_MODE_7_1_52] = G_LR | G_C | G_LSRS | G_LWRW | G_LFE, + [CH_MODE_7_0_322] = G_LR | G_C | G_LSRS | G_TFLR, + [CH_MODE_7_1_322] = G_LR | G_C | G_LSRS | G_TFLR | G_LFE, + [CH_MODE_7_0_4] = G_LR | G_C | G_LSRS | G_LBRB | G_TFLR | G_TBLR, + [CH_MODE_7_1_4] = G_LR | G_C | G_LSRS | G_LBRB | G_TFLR | G_TBLR | G_LFE, + [CH_MODE_9_0_4] = G_LR | G_C | G_LSRS | G_LBRB | G_TFLR | G_TBLR | G_SCR, + [CH_MODE_9_1_4] = G_LR | G_C | G_LSRS | G_LBRB | G_TFLR | G_TBLR | G_SCR | + G_LFE, + [CH_MODE_22_2] = G_LR | G_C | G_LSRS | G_LBRB | G_TFLR | G_TBLR | G_LFE | + G_TSLR | G_TFC | G_TBC | G_TC | G_LFE2 | G_BFLR | + G_BFC | G_CB | G_LWRW, +}; + +/* Core channel modes, ETSI TS 103 190-2 table 71 */ +enum { + CORE_5_0 = 3, + CORE_5_1 = 4, + CORE_5_0_2 = 5, + CORE_5_1_2 = 6, +}; + +static const uint32_t core_mode_groups[] = { + [CORE_5_0] = G_LR | G_C | G_LSRS, + [CORE_5_1] = G_LR | G_C | G_LSRS | G_LFE, + [CORE_5_0_2] = G_LR | G_C | G_LSRS | G_TSLR, + [CORE_5_1_2] = G_LR | G_C | G_LSRS | G_TSLR | G_LFE, +}; + +typedef struct AC4ContentType { + int present; + int classifier; + int language_indicator; + int n_language_tag_bytes; + uint8_t language_tag[64]; +} AC4ContentType; + +typedef struct AC4Substream { + int ch_mode; ///< -1 if object coded + int ims_atmos; ///< immersive stereo channel_mode 0b1111001 + int has_ch_flags; + int b_4_back_channels_present; + int b_centre_present; + int top_channels_present; + int sf_multiplier; ///< dsi_sf_multiplier + int b_bitrate_info; + int bitrate_indicator; ///< brate_ind + int add_ch_base; + int b_ajoc; + int b_lfe; + int b_static_dmx; + int n_dmx_objects_minus1; + int n_umx_objects_minus1; + int bed_objects; + int dynamic_objects; + int isf_objects; + int sus_ver; + int substream_index; ///< -1 if not present in this stream + AC4ContentType ct; ///< only for ac4_substream_info() +} AC4Substream; + +typedef struct AC4SubstreamGroup { + int b_substreams_present; + int b_hsf_ext; + int b_channel_coded; + int n_substreams; + AC4Substream ss[MAX_GROUP_SUBSTREAMS]; + AC4ContentType ct; +} AC4SubstreamGroup; + +typedef struct AC4Presentation { + int presentation_version; + int single; ///< b_single_substream(_group) + int presentation_config; + int md_compat; + int b_presentation_id; + int presentation_id; + int dsi_frame_rate_multiply_info; + int dsi_frame_rate_fraction_info; + int frame_rate_factor; + int emdf_version; + int key_id; + int b_presentation_filter; + int b_enable_presentation; + int b_multi_pid; + int b_hsf_ext; + int b_pre_virtualized; + int b_add_emdf_substreams; + int n_add_emdf_substreams; + int add_emdf_version[MAX_EMDF]; + int add_key_id[MAX_EMDF]; + + /* ac4_presentation_v1_info() */ + int n_sgi; ///< number of ac4_sgi_specifier() + int group_index[MAX_PRES_GROUPS]; + int b_alternative; + int pres_substream_index; + + /* ac4_presentation_info() */ + int n_substreams; + AC4Substream ss[4]; + + /* from ac4_presentation_substream() */ + int immersive_audio_indicator; + int name_len; + uint8_t name[32]; + int n_targets; + int target_md_compat[MAX_TARGETS]; + int target_device_category[MAX_TARGETS]; + + /* derived */ + int ch_mode; + int ch_mode_core; + uint32_t channel_groups; + int b_4_back_channels_present; + int top_channel_pairs; + int has_ajoc; + int de_indicator; +} AC4Presentation; + +typedef struct AC4TOC { + AC4FrameInfo fi; + int n_presentations; + int payload_base; + int b_program_id; + int short_program_id; + int b_program_uuid_present; + uint8_t program_uuid[16]; + AC4Presentation pres[MAX_PRESENTATIONS]; + int n_groups; + AC4SubstreamGroup groups[MAX_GROUPS]; + int n_substreams; + int b_size_present; + int substream_size[MAX_SUBSTREAMS]; + int toc_size; +} AC4TOC; + +static const struct { + int timescale; + int duration; +} frame_durations_48k[14] = { + { 48000, 2002 }, { 48000, 2000 }, { 48000, 1920 }, { 240000, 8008 }, + { 48000, 1600 }, { 48000, 1001 }, { 48000, 1000 }, { 48000, 960 }, + { 240000, 4004 }, { 48000, 800 }, { 48000, 480 }, { 240000, 2002 }, + { 48000, 400 }, { 48000, 2048 }, +}; + +/** + * Read a variable_bits() field. Values are clamped to 2^15, larger than any + * sane value, to prevent overflows. + */ +static int variable_bits(GetBitContext *gb, int bits) +{ + int value = 0; + + while (1) { + value += get_bits(gb, bits); + if (!get_bits1(gb) || get_bits_left(gb) < 0 || value >= 1 << 15) + break; + value <<= bits; + value += 1 << bits; + } + + return FFMIN(value, 1 << 15); +} + +static int parse_frame_info(AC4FrameInfo *fi, GetBitContext *gb) +{ + fi->bitstream_version = get_bits(gb, 2); + if (fi->bitstream_version == 3) + fi->bitstream_version += variable_bits(gb, 2); + fi->sequence_counter = get_bits(gb, 10); + fi->wait_frames = -1; + if (get_bits1(gb)) { + fi->wait_frames = get_bits(gb, 3); + if (fi->wait_frames > 0) + skip_bits(gb, 2); /* br_code */ + } + fi->fs_index = get_bits1(gb); + fi->frame_rate_index = get_bits(gb, 4); + fi->iframe = get_bits1(gb); + + if (fi->fs_index) { + if (fi->frame_rate_index >= FF_ARRAY_ELEMS(frame_durations_48k)) + return AVERROR_INVALIDDATA; + fi->sample_rate = 48000; + fi->timescale = frame_durations_48k[fi->frame_rate_index].timescale; + fi->frame_duration = frame_durations_48k[fi->frame_rate_index].duration; + } else { + if (fi->frame_rate_index != 13) + return AVERROR_INVALIDDATA; + fi->sample_rate = 44100; + fi->timescale = 44100; + fi->frame_duration = 2048; + } + + return 0; +} + +int ff_ac4_parse_frame_info(AC4FrameInfo *info, const uint8_t *buf, int size) +{ + uint8_t tmp[16 + AV_INPUT_BUFFER_PADDING_SIZE] = { 0 }; + GetBitContext gb; + int ret; + + size = FFMIN(size, 16); + if (size > 0) + memcpy(tmp, buf, size); + ret = init_get_bits8(&gb, tmp, size); + if (ret < 0) + return ret; + ret = parse_frame_info(info, &gb); + if (ret < 0) + return ret; + return get_bits_left(&gb) >= 0 ? 0 : AVERROR_INVALIDDATA; +} + +int ff_ac4_update_bit_rate_mode(int prev_mode, const AC4FrameInfo *info) +{ + int mode = info->wait_frames <= 0 ? 1 : info->wait_frames <= 6 ? 2 : 3; + if (!prev_mode) + return mode; + return prev_mode == mode ? mode : 3; +} + +static int superset(int a, int b, const uint32_t *groups, int start, int nb) +{ + uint32_t mask; + + if (a < 0) + return b; + if (b < 0) + return a; + if (FFMIN(a, b) == CH_MODE_MONO && FFMAX(a, b) == CH_MODE_STEREO) + return CH_MODE_STEREO; + + mask = groups[a] | groups[b]; + for (int i = start; i < nb; i++) + if ((groups[i] & mask) == mask) + return i; + return FFMAX(a, b); +} + +/** + * Channel groups present in the original content of a channel coded substream. + */ +static uint32_t substream_channel_groups(const AC4Substream *ss) +{ + uint32_t groups; + + if (ss->ch_mode < 0 || ss->ch_mode >= CH_MODE_NB) + return 0; + + groups = ch_mode_groups[ss->ch_mode]; + if (ss->has_ch_flags) { + groups &= ~(G_C | G_LBRB | G_TFLR | G_TBLR); + if (ss->b_centre_present) + groups |= G_C; + if (ss->b_4_back_channels_present) + groups |= G_LBRB; + if (ss->top_channels_present == 1 || ss->top_channels_present == 2) + groups |= G_TSLR; + else if (ss->top_channels_present == 3) + groups |= G_TFLR | G_TBLR; + } + + return groups; +} + +static int read_channel_mode(GetBitContext *gb, int presentation_version) +{ + int v; + + if (!get_bits1(gb)) + return CH_MODE_MONO; + if (!get_bits1(gb)) + return CH_MODE_STEREO; + v = get_bits(gb, 2); + if (v < 3) + return CH_MODE_3_0 + v; + v = get_bits(gb, 3); + if (v < 6) + return CH_MODE_7_0_34 + v; + if (!presentation_version) { + /* ETSI TS 103 190-1, table 88 */ + if (v == 6) + return 11; + return 12 + variable_bits(gb, 2); + } + if (v == 6) + return CH_MODE_7_0_4 + get_bits1(gb); + v = get_bits(gb, 2); + if (v < 3) + return CH_MODE_9_0_4 + v; + return 16 + variable_bits(gb, 2); +} + +static int read_bitrate_indicator(GetBitContext *gb) +{ + int v = get_bits(gb, 3); + if (!(v & 1)) + return v >> 1; + v = (v << 2) | get_bits(gb, 2); + return 4 + ((v >> 3) & 3) * 4 + (v & 3); +} + +static void read_sf_multiplier(GetBitContext *gb, const AC4FrameInfo *fi, + AC4Substream *ss) +{ + ss->sf_multiplier = 0; + if (fi->fs_index == 1 && get_bits1(gb)) + ss->sf_multiplier = 1 + get_bits1(gb); +} + +static void read_bitrate_info(GetBitContext *gb, AC4Substream *ss) +{ + ss->b_bitrate_info = get_bits1(gb); + if (ss->b_bitrate_info) + ss->bitrate_indicator = read_bitrate_indicator(gb); +} + +static int read_substream_index(GetBitContext *gb) +{ + int idx = get_bits(gb, 2); + if (idx == 3) + idx += variable_bits(gb, 2); + return idx; +} + +static void read_content_type(GetBitContext *gb, AC4ContentType *ct) +{ + ct->present = 1; + ct->classifier = get_bits(gb, 3); + ct->language_indicator = get_bits1(gb); + ct->n_language_tag_bytes = 0; + if (ct->language_indicator) { + if (get_bits1(gb)) { + /* b_serialized_language_tag: the tag is spread over multiple + * frames, which are not available here. */ + skip_bits(gb, 1 + 16); + } else { + ct->n_language_tag_bytes = get_bits(gb, 6); + for (int i = 0; i < ct->n_language_tag_bytes; i++) + ct->language_tag[i] = get_bits(gb, 8); + } + } +} + +static void read_emdf_info(GetBitContext *gb, int *version, int *key_id) +{ + int v, k, primary, secondary; + + v = get_bits(gb, 2); + if (v == 3) + v += variable_bits(gb, 2); + k = get_bits(gb, 3); + if (k == 7) + k += variable_bits(gb, 3); + if (get_bits1(gb)) /* b_emdf_payloads_substream_info */ + read_substream_index(gb); + /* emdf_protection() */ + primary = get_bits(gb, 2); + secondary = get_bits(gb, 2); + if (primary) + skip_bits_long(gb, 8 << (2 * (primary - 1))); + if (secondary) + skip_bits_long(gb, 8 << (2 * (secondary - 1))); + + if (version) + *version = v; + if (key_id) + *key_id = k; +} + +static void read_frame_rate_multiply_info(GetBitContext *gb, const AC4FrameInfo *fi, + AC4Presentation *p) +{ + p->frame_rate_factor = 1; + p->dsi_frame_rate_multiply_info = 0; + + switch (fi->frame_rate_index) { + case 2: case 3: case 4: + if (get_bits1(gb)) { + int multiplier_bit = get_bits1(gb); + p->frame_rate_factor = multiplier_bit ? 4 : 2; + p->dsi_frame_rate_multiply_info = multiplier_bit ? 2 : 1; + } + break; + case 0: case 1: case 7: case 8: case 9: + if (get_bits1(gb)) { + p->frame_rate_factor = 2; + p->dsi_frame_rate_multiply_info = 1; + } + break; + } +} + +static void read_frame_rate_fractions_info(GetBitContext *gb, const AC4FrameInfo *fi, + AC4Presentation *p) +{ + p->dsi_frame_rate_fraction_info = 0; + + if (fi->frame_rate_index >= 5 && fi->frame_rate_index <= 9) { + if (p->frame_rate_factor == 1 && get_bits1(gb)) + p->dsi_frame_rate_fraction_info = 1; + } else if (fi->frame_rate_index >= 10 && fi->frame_rate_index <= 12) { + if (get_bits1(gb)) + p->dsi_frame_rate_fraction_info = 1 + get_bits1(gb); + } +} + +static void skip_presentation_config_ext_info(GetBitContext *gb) +{ + int n_skip_bytes = get_bits(gb, 5); + if (get_bits1(gb)) + n_skip_bytes += variable_bits(gb, 2) << 5; + skip_bits_long(gb, 8 * n_skip_bytes); +} + +static int read_add_emdf_substreams(GetBitContext *gb, AC4Presentation *p) +{ + int n = get_bits(gb, 2); + if (!n) + n = variable_bits(gb, 2) + 4; + if (n > MAX_EMDF) + return AVERROR_PATCHWELCOME; + + p->n_add_emdf_substreams = n; + for (int i = 0; i < n; i++) + read_emdf_info(gb, &p->add_emdf_version[i], &p->add_key_id[i]); + + return 0; +} + +/* ETSI TS 103 190-1, clause 4.2.3.6 */ +static int read_substream_info(GetBitContext *gb, const AC4FrameInfo *fi, + AC4Presentation *p, AC4Substream *ss) +{ + ss->ch_mode = read_channel_mode(gb, 0); + read_sf_multiplier(gb, fi, ss); + read_bitrate_info(gb, ss); + if (ss->ch_mode >= CH_MODE_7_0_52 && ss->ch_mode <= CH_MODE_7_1_322) + ss->add_ch_base = get_bits1(gb); + if (get_bits1(gb)) + read_content_type(gb, &ss->ct); + skip_bits(gb, p->frame_rate_factor); /* b_iframe */ + ss->substream_index = read_substream_index(gb); + ss->sus_ver = 0; + + return 0; +} + +/* ETSI TS 103 190-2, clause 6.2.1.2 */ +static int read_presentation_info(GetBitContext *gb, const AC4FrameInfo *fi, + AC4Presentation *p) +{ + int n, ret; + + p->single = get_bits1(gb); + p->presentation_config = 0x1F; + if (!p->single) { + p->presentation_config = get_bits(gb, 3); + if (p->presentation_config == 7) + p->presentation_config += variable_bits(gb, 2); + } + while (get_bits1(gb) && get_bits_left(gb) > 0) + p->presentation_version++; + + if (!p->single && p->presentation_config == 6) { + p->b_add_emdf_substreams = 1; + } else { + p->md_compat = get_bits(gb, 3); + p->b_presentation_id = get_bits1(gb); + if (p->b_presentation_id) + p->presentation_id = variable_bits(gb, 2); + read_frame_rate_multiply_info(gb, fi, p); + read_emdf_info(gb, &p->emdf_version, &p->key_id); + + if (p->single) { + n = 1; + } else { + p->b_hsf_ext = get_bits1(gb); + switch (p->presentation_config) { + case 0: case 1: case 2: n = 2; break; + case 3: case 4: n = 3; break; + case 5: n = 1; break; + default: n = 0; break; + } + } + for (int i = 0; i < n; i++) { + ret = read_substream_info(gb, fi, p, &p->ss[i]); + if (ret < 0) + return ret; + if (!i && p->b_hsf_ext) + read_substream_index(gb); /* ac4_hsf_ext_substream_info() */ + } + p->n_substreams = n; + if (!p->single && p->presentation_config > 5) + skip_presentation_config_ext_info(gb); + + p->b_pre_virtualized = get_bits1(gb); + p->b_add_emdf_substreams = get_bits1(gb); + } + if (p->b_add_emdf_substreams) { + ret = read_add_emdf_substreams(gb, p); + if (ret < 0) + return ret; + } + + return 0; +} + +static int read_sgi_specifier(GetBitContext *gb, AC4Presentation *p) +{ + int idx = get_bits(gb, 3); + if (idx == 7) + idx += variable_bits(gb, 2); + if (idx >= MAX_GROUPS || p->n_sgi >= MAX_PRES_GROUPS) + return AVERROR_PATCHWELCOME; + p->group_index[p->n_sgi++] = idx; + return 0; +} + +/* ETSI TS 103 190-2, clause 6.2.1.3 */ +static int read_presentation_v1_info(GetBitContext *gb, const AC4FrameInfo *fi, + AC4Presentation *p) +{ + int n, ret; + + p->single = get_bits1(gb); + p->presentation_config = 0x1F; + if (!p->single) { + p->presentation_config = get_bits(gb, 3); + if (p->presentation_config == 7) + p->presentation_config += variable_bits(gb, 2); + } + while (get_bits1(gb) && get_bits_left(gb) > 0) + p->presentation_version++; + + p->pres_substream_index = -1; + if (!p->single && p->presentation_config == 6) { + p->b_add_emdf_substreams = 1; + } else { + p->md_compat = get_bits(gb, 3); + p->b_presentation_id = get_bits1(gb); + if (p->b_presentation_id) + p->presentation_id = variable_bits(gb, 2); + read_frame_rate_multiply_info(gb, fi, p); + read_frame_rate_fractions_info(gb, fi, p); + read_emdf_info(gb, &p->emdf_version, &p->key_id); + p->b_presentation_filter = get_bits1(gb); + if (p->b_presentation_filter) + p->b_enable_presentation = get_bits1(gb); + + if (p->single) { + n = 1; + } else { + p->b_multi_pid = get_bits1(gb); + switch (p->presentation_config) { + case 0: case 1: case 2: n = 2; break; + case 3: case 4: n = 3; break; + case 5: + n = get_bits(gb, 2) + 2; + if (n == 5) + n += variable_bits(gb, 2); + break; + default: + n = 0; + skip_presentation_config_ext_info(gb); + break; + } + } + for (int i = 0; i < n; i++) { + ret = read_sgi_specifier(gb, p); + if (ret < 0) + return ret; + } + + p->b_pre_virtualized = get_bits1(gb); + p->b_add_emdf_substreams = get_bits1(gb); + + /* ac4_presentation_substream_info() */ + p->b_alternative = get_bits1(gb); + skip_bits1(gb); /* b_pres_ndot */ + p->pres_substream_index = read_substream_index(gb); + } + if (p->b_add_emdf_substreams) { + ret = read_add_emdf_substreams(gb, p); + if (ret < 0) + return ret; + } + + return 0; +} + +/* Returns the number of static objects */ +static int read_bed_dyn_obj_assignment(GetBitContext *gb, int n_signals, + AC4Substream *ss) +{ + static const uint8_t isf_objects[8] = { 4, 8, 10, 14, 15, 30 }; + static const uint8_t bed_objects[8] = { 2, 3, 5, 7, 9, 7, 9, 11 }; + static const uint8_t std_objects[10] = { 2, 1, 0, 2, 2, 2, 2, 2, 2, 0 }; + int n = 0; + + if (get_bits1(gb)) /* b_dyn_objects_only */ + return 0; + + if (get_bits1(gb)) { /* b_isf */ + ss->isf_objects = 1; + return isf_objects[get_bits(gb, 3)]; + } + + ss->bed_objects = 1; + if (get_bits1(gb)) { /* b_ch_assign_code */ + n = bed_objects[get_bits(gb, 3)]; + } else if (get_bits1(gb)) { /* b_chan_assign_mask */ + if (get_bits1(gb)) { /* b_nonstd_bed_channel_assignment */ + int mask = get_bits(gb, 17); + for (int i = 0; i < 17; i++) + n += i != 3 && i != 16 && (mask >> (16 - i)) & 1; + } else { + int mask = get_bits(gb, 10); + for (int i = 0; i < 10; i++) + if ((mask >> (9 - i)) & 1) + n += std_objects[i]; + } + } else { + int n_bed_signals = 1; + if (n_signals > 1) + n_bed_signals = get_bits(gb, av_ceil_log2(n_signals)) + 1; + for (int i = 0; i < n_bed_signals; i++) + n += get_bits(gb, 4) != 3; + } + + return n; +} + +static void skip_oamd_common_data(GetBitContext *gb) +{ + if (!get_bits1(gb)) /* b_default_screen_size_ratio */ + skip_bits(gb, 5); + skip_bits1(gb); /* b_bed_object_chan_distribute */ + if (get_bits1(gb)) { /* b_additional_data */ + int add_data_bytes = get_bits1(gb) + 1; + if (add_data_bytes == 2) + add_data_bytes += variable_bits(gb, 2); + skip_bits_long(gb, 8 * add_data_bytes); + } +} + +static void read_substream_info_chan(GetBitContext *gb, const AC4FrameInfo *fi, + int presentation_version, int frame_rate_factor, + int b_substreams_present, AC4Substream *ss) +{ + ss->ch_mode = read_channel_mode(gb, 1); + if (ss->ch_mode >= CH_MODE_7_0_4 && ss->ch_mode <= CH_MODE_9_1_4) { + ss->has_ch_flags = 1; + ss->b_4_back_channels_present = get_bits1(gb); + ss->b_centre_present = get_bits1(gb); + ss->top_channels_present = get_bits(gb, 2); + } + read_sf_multiplier(gb, fi, ss); + read_bitrate_info(gb, ss); + if (ss->ch_mode >= CH_MODE_7_0_52 && ss->ch_mode <= CH_MODE_7_1_322) + ss->add_ch_base = get_bits1(gb); + skip_bits(gb, frame_rate_factor); /* b_audio_ndot */ + ss->substream_index = b_substreams_present ? read_substream_index(gb) : -1; + + /* Immersive stereo is signalled with the 7.X 3/4/0 channel modes */ + if (presentation_version == 2 && + (ss->ch_mode == CH_MODE_7_0_34 || ss->ch_mode == CH_MODE_7_1_34)) { + ss->ims_atmos = ss->ch_mode == CH_MODE_7_1_34; + ss->ch_mode = CH_MODE_STEREO; + } +} + +static void read_substream_info_ajoc(GetBitContext *gb, const AC4FrameInfo *fi, + int frame_rate_factor, int b_substreams_present, + AC4Substream *ss) +{ + int n_umx, n_static; + + ss->b_lfe = get_bits1(gb); + ss->b_static_dmx = get_bits1(gb); + if (!ss->b_static_dmx) { + AC4Substream dmx = { 0 }; + ss->n_dmx_objects_minus1 = get_bits(gb, 4); + read_bed_dyn_obj_assignment(gb, ss->n_dmx_objects_minus1 + 1, &dmx); + } + if (get_bits1(gb)) /* b_oamd_common_data_present */ + skip_oamd_common_data(gb); + n_umx = get_bits(gb, 4) + 1; + if (n_umx == 16) + n_umx += variable_bits(gb, 3); + ss->n_umx_objects_minus1 = n_umx - 1; + n_static = read_bed_dyn_obj_assignment(gb, n_umx, ss); + ss->dynamic_objects = n_static < n_umx; + read_sf_multiplier(gb, fi, ss); + read_bitrate_info(gb, ss); + skip_bits(gb, frame_rate_factor); /* b_audio_ndot */ + ss->substream_index = b_substreams_present ? read_substream_index(gb) : -1; +} + +static void read_substream_info_obj(GetBitContext *gb, const AC4FrameInfo *fi, + int frame_rate_factor, int b_substreams_present, + AC4Substream *ss) +{ + skip_bits(gb, 3); /* n_objects_code */ + if (get_bits1(gb)) { /* b_dynamic_objects */ + ss->dynamic_objects = 1; + ss->b_lfe = get_bits1(gb); + } else if (get_bits1(gb)) { /* b_bed_objects */ + ss->bed_objects = 1; + if (get_bits1(gb)) { /* b_bed_start */ + if (get_bits1(gb)) /* b_ch_assign_code */ + skip_bits(gb, 3); + else + skip_bits(gb, get_bits1(gb) ? 17 : 10); + } + } else if (get_bits1(gb)) { /* b_isf */ + ss->isf_objects = 1; + if (get_bits1(gb)) /* b_isf_start */ + skip_bits(gb, 3); + } else { + skip_bits_long(gb, 8 * get_bits(gb, 4)); + } + read_sf_multiplier(gb, fi, ss); + read_bitrate_info(gb, ss); + skip_bits(gb, frame_rate_factor); /* b_audio_ndot */ + ss->substream_index = b_substreams_present ? read_substream_index(gb) : -1; +} + +/* ETSI TS 103 190-2, clause 6.2.1.6 */ +static int read_substream_group_info(GetBitContext *gb, const AC4FrameInfo *fi, + int presentation_version, int frame_rate_factor, + AC4SubstreamGroup *g) +{ + int n; + + g->b_substreams_present = get_bits1(gb); + g->b_hsf_ext = get_bits1(gb); + if (get_bits1(gb)) { + n = 1; + } else { + n = get_bits(gb, 2) + 2; + if (n == 5) + n += variable_bits(gb, 2); + } + if (n > MAX_GROUP_SUBSTREAMS) + return AVERROR_PATCHWELCOME; + g->n_substreams = n; + + g->b_channel_coded = get_bits1(gb); + if (g->b_channel_coded) { + for (int i = 0; i < n; i++) { + AC4Substream *ss = &g->ss[i]; + ss->sus_ver = fi->bitstream_version == 1 ? get_bits1(gb) : 1; + read_substream_info_chan(gb, fi, presentation_version, frame_rate_factor, + g->b_substreams_present, ss); + if (g->b_hsf_ext && g->b_substreams_present) + read_substream_index(gb); + } + } else { + if (get_bits1(gb)) { /* b_oamd_substream */ + skip_bits1(gb); /* b_oamd_ndot */ + if (g->b_substreams_present) + read_substream_index(gb); + } + for (int i = 0; i < n; i++) { + AC4Substream *ss = &g->ss[i]; + ss->ch_mode = -1; + ss->sus_ver = 1; + ss->b_ajoc = get_bits1(gb); + if (ss->b_ajoc) + read_substream_info_ajoc(gb, fi, frame_rate_factor, + g->b_substreams_present, ss); + else + read_substream_info_obj(gb, fi, frame_rate_factor, + g->b_substreams_present, ss); + if (g->b_hsf_ext && g->b_substreams_present) + read_substream_index(gb); + } + } + if (get_bits1(gb)) + read_content_type(gb, &g->ct); + + return 0; +} + +static int read_toc(void *logctx, AC4TOC *toc, GetBitContext *gb) +{ + AC4FrameInfo *fi = &toc->fi; + int ret; + + ret = parse_frame_info(fi, gb); + if (ret < 0) + return ret; + + if (fi->bitstream_version > 2) { + avpriv_request_sample(logctx, "AC-4 bitstream version %d", fi->bitstream_version); + return AVERROR_PATCHWELCOME; + } + + if (get_bits1(gb)) { + toc->n_presentations = 1; + } else if (get_bits1(gb)) { + toc->n_presentations = variable_bits(gb, 2) + 2; + } + if (toc->n_presentations > MAX_PRESENTATIONS) { + avpriv_request_sample(logctx, "%d AC-4 presentations", toc->n_presentations); + return AVERROR_PATCHWELCOME; + } + + if (get_bits1(gb)) { + toc->payload_base = get_bits(gb, 5) + 1; + if (toc->payload_base == 0x20) + toc->payload_base += variable_bits(gb, 3); + } + + if (fi->bitstream_version <= 1) { + for (int i = 0; i < toc->n_presentations; i++) { + ret = read_presentation_info(gb, fi, &toc->pres[i]); + if (ret < 0) + return ret; + } + } else { + toc->b_program_id = get_bits1(gb); + if (toc->b_program_id) { + toc->short_program_id = get_bits(gb, 16); + toc->b_program_uuid_present = get_bits1(gb); + if (toc->b_program_uuid_present) + for (int i = 0; i < 16; i++) + toc->program_uuid[i] = get_bits(gb, 8); + } + + for (int i = 0; i < toc->n_presentations; i++) { + ret = read_presentation_v1_info(gb, fi, &toc->pres[i]); + if (ret < 0) + return ret; + for (int j = 0; j < toc->pres[i].n_sgi; j++) + toc->n_groups = FFMAX(toc->n_groups, toc->pres[i].group_index[j] + 1); + } + + for (int i = 0; i < toc->n_groups; i++) { + const AC4Presentation *p = NULL; + + /* Some fields depend on the presentation the group belongs to */ + for (int j = 0; j < toc->n_presentations && !p; j++) + for (int k = 0; k < toc->pres[j].n_sgi; k++) + if (toc->pres[j].group_index[k] == i) + p = &toc->pres[j]; + + ret = read_substream_group_info(gb, fi, + p ? p->presentation_version : 1, + p ? p->frame_rate_factor : 1, + &toc->groups[i]); + if (ret < 0) + return ret; + } + } + + /* substream_index_table() */ + toc->n_substreams = get_bits(gb, 2); + if (!toc->n_substreams) + toc->n_substreams = variable_bits(gb, 2) + 4; + if (toc->n_substreams > MAX_SUBSTREAMS) + return AVERROR_PATCHWELCOME; + toc->b_size_present = toc->n_substreams == 1 ? get_bits1(gb) : 1; + if (toc->b_size_present) { + for (int i = 0; i < toc->n_substreams; i++) { + int more = get_bits1(gb); + toc->substream_size[i] = get_bits(gb, 10); + if (more) + toc->substream_size[i] += variable_bits(gb, 2) << 10; + } + } + align_get_bits(gb); + + if (get_bits_left(gb) < 0) + return AVERROR_INVALIDDATA; + + toc->toc_size = get_bits_count(gb) >> 3; + return 0; +} + +/** + * Get the offset and size of the data of a substream within the frame. + */ +static int get_substream(const AC4TOC *toc, int frame_size, int idx, + int *offset, int *size) +{ + int pos = toc->toc_size + toc->payload_base; + + if (idx < 0 || idx >= toc->n_substreams) + return AVERROR_INVALIDDATA; + + for (int i = 0; i < idx; i++) + pos += toc->substream_size[i]; + *offset = pos; + *size = toc->b_size_present ? toc->substream_size[idx] : frame_size - pos; + + if (*offset > frame_size || *size <= 0 || *size > frame_size - *offset) + return AVERROR_INVALIDDATA; + return 0; +} + +static void skip_further_loudness_info(GetBitContext *gb, int sus_ver) +{ + if (sus_ver == 0) { + int loudness_version = get_bits(gb, 2); + if (loudness_version == 3) + skip_bits(gb, 4); + if (get_bits(gb, 4)) { /* loud_prac_type */ + if (get_bits1(gb)) /* b_loudcorr_dialgate */ + skip_bits(gb, 3); + skip_bits1(gb); /* b_loudcorr_type */ + } + } else { + skip_bits1(gb); /* b_loudcorr_dialgate */ + } + if (get_bits1(gb)) /* b_loudrelgat */ + skip_bits(gb, 11); + if (get_bits1(gb)) /* b_loudspchgat */ + skip_bits(gb, 11 + 3); + for (int i = 0; i < 4; i++) /* loudstrm3s, max_loudstrm3s, truepk, max_truepk */ + if (get_bits1(gb)) + skip_bits(gb, 11); + if (sus_ver == 0) { + if (get_bits1(gb)) { /* b_prgmbndy */ + for (int i = 0; i < 32 && !get_bits1(gb); i++) + ; + skip_bits1(gb); /* b_end_or_start */ + if (get_bits1(gb)) + skip_bits(gb, 11); + } + } + if (get_bits1(gb)) /* b_lra */ + skip_bits(gb, 10 + 3); + for (int i = 0; i < 2; i++) /* loudmntry, max_loudmntry */ + if (get_bits1(gb)) + skip_bits(gb, 11); + if (sus_ver >= 1) { + if (get_bits1(gb)) /* b_rtllcomp */ + skip_bits(gb, 8); + if (get_bits1(gb)) { /* b_extension */ + int e_bits_size = get_bits(gb, 5); + if (e_bits_size == 31) + e_bits_size += variable_bits(gb, 4); + skip_bits_long(gb, e_bits_size); + } + } else if (get_bits1(gb)) { /* b_extension */ + int e_bits_size = get_bits(gb, 5); + if (e_bits_size == 31) + e_bits_size += variable_bits(gb, 4); + skip_bits_long(gb, e_bits_size); + } +} + +/** + * Determine whether a substream carries dialogue enhancement data, + * i.e. whether b_de_data_present or b_dialog_max_gain are set. + * + * @return 1 if it does, 0 if it does not, a negative value if unknown + */ +static int substream_has_de(const AC4TOC *toc, const uint8_t *frame, int frame_size, + const AC4Presentation *p, const AC4Substream *ss) +{ + GetBitContext gb; + int offset, size, audio_size, ch_mode, has_de = 0; + + /* The older substream syntax contains a lot more metadata we do not need */ + if (ss->sus_ver != 1 || ss->substream_index < 0) + return -1; + /* oamd_dyndata_single() would have to be parsed */ + if (p->b_alternative && ss->ch_mode < 0 && !ss->b_ajoc) + return -1; + if (get_substream(toc, frame_size, ss->substream_index, &offset, &size) < 0) + return -1; + if (init_get_bits8(&gb, frame + offset, size) < 0) + return -1; + + audio_size = get_bits(&gb, 15); + if (get_bits1(&gb)) + audio_size += variable_bits(&gb, 7) << 15; + if (audio_size >= get_bits_left(&gb) >> 3) + return -1; + skip_bits_long(&gb, 8 * audio_size); + + ch_mode = ss->ch_mode; + + /* basic_metadata() */ + if (get_bits1(&gb)) { /* b_more_basic_metadata */ + if (get_bits1(&gb)) { /* b_substream_loudness_info */ + skip_bits(&gb, 8); + if (get_bits1(&gb)) + skip_further_loudness_info(&gb, 1); + } + if (ch_mode == CH_MODE_STEREO) { + if (get_bits1(&gb)) /* b_prev_dmx_info */ + skip_bits(&gb, 3 + 2); + } else if (ch_mode > CH_MODE_STEREO) { + if (ch_mode == CH_MODE_5_0 || ch_mode == CH_MODE_5_1) { + if (get_bits1(&gb)) /* b_predmixtyp_5ch */ + skip_bits(&gb, 3); + if (get_bits1(&gb)) /* b_preupmixtyp_5ch */ + skip_bits(&gb, 4); + } else if (ch_mode >= CH_MODE_7_0_34 && ch_mode <= CH_MODE_7_1_322) { + if (get_bits1(&gb)) { /* b_upmixtyp_7ch */ + if (ch_mode <= CH_MODE_7_1_34) + skip_bits(&gb, 2); + else if (ch_mode >= CH_MODE_7_0_322) + skip_bits(&gb, 1); + } + } + skip_bits(&gb, 2 + 1 + 1); + } + if (get_bits1(&gb)) /* b_dc_blocking */ + skip_bits1(&gb); + } + + /* extended_metadata() */ + if (get_bits1(&gb)) { /* b_dialog */ + if (get_bits1(&gb)) { /* b_dialog_max_gain */ + has_de = 1; + skip_bits(&gb, 2); + } + if (get_bits1(&gb)) /* b_pan_dialog_present */ + skip_bits(&gb, ch_mode == CH_MODE_MONO ? 8 : 8 + 8 + 2); + } + if (get_bits1(&gb)) { /* b_channels_classifier */ + if (ch_mode == CH_MODE_MONO || (ch_mode >= CH_MODE_3_0 && ch_mode < CH_MODE_NB)) + if (get_bits1(&gb)) + skip_bits1(&gb); + if (ch_mode >= CH_MODE_STEREO && ch_mode < CH_MODE_NB) { + for (int i = 0; i < 2; i++) + if (get_bits1(&gb)) + skip_bits1(&gb); + } + if (ch_mode >= CH_MODE_5_0 && ch_mode < CH_MODE_NB) + skip_bits(&gb, 2); + if (ch_mode == CH_MODE_7_0_34 || ch_mode == CH_MODE_7_1_34 || + (ch_mode >= CH_MODE_7_0_4 && ch_mode < CH_MODE_NB)) + skip_bits(&gb, 2); + if (ch_mode == CH_MODE_7_0_52 || ch_mode == CH_MODE_7_1_52 || + ch_mode == CH_MODE_22_2) + skip_bits(&gb, 2); + if (ch_mode == CH_MODE_7_0_322 || ch_mode == CH_MODE_7_1_322) + skip_bits(&gb, 2); + if (ch_mode >= 0 && ch_mode < CH_MODE_NB && + (ch_mode_groups[ch_mode] & G_LFE)) + skip_bits1(&gb); + } + if (get_bits1(&gb)) /* b_event_probability */ + skip_bits(&gb, 4); + + /* tools_metadata_size */ + skip_bits(&gb, 7); + if (get_bits1(&gb)) + variable_bits(&gb, 3); + + /* dialog_enhancement() */ + has_de |= get_bits1(&gb); /* b_de_data_present */ + + if (get_bits_left(&gb) < 0) + return -1; + + return has_de; +} + +static void parse_presentation_substream(const AC4TOC *toc, const uint8_t *frame, + int frame_size, AC4Presentation *p, + int n_substreams) +{ + GetBitContext gb; + int offset, size, n_targets, immersive = 0; + + if (get_substream(toc, frame_size, p->pres_substream_index, &offset, &size) < 0) + return; + if (init_get_bits8(&gb, frame + offset, size) < 0) + return; + + if (p->b_alternative) { + if (get_bits1(&gb)) { /* b_name_present */ + p->name_len = get_bits1(&gb) ? get_bits(&gb, 5) : 32; + for (int i = 0; i < p->name_len; i++) + p->name[i] = get_bits(&gb, 8); + } + n_targets = get_bits(&gb, 2) + 1; + if (n_targets == 4) + n_targets += variable_bits(&gb, 2); + if (n_targets > MAX_TARGETS) + return; + p->n_targets = n_targets; + for (int t = 0; t < p->n_targets; t++) { + p->target_md_compat[t] = get_bits(&gb, 3); + p->target_device_category[t] = get_bits(&gb, 4); + if (get_bits1(&gb)) /* b_tdc_extension */ + skip_bits(&gb, 4); + if (get_bits1(&gb)) /* b_ducking_depth_present */ + skip_bits(&gb, 6); + if (get_bits1(&gb)) /* b_loud_corr_target */ + skip_bits(&gb, 5); + for (int s = 0; s < n_substreams; s++) { + if (get_bits1(&gb)) /* b_active */ + if (get_bits1(&gb)) /* alt_data_set_index */ + variable_bits(&gb, 2); + } + } + } + + if (get_bits1(&gb)) { /* b_additional_data */ + int add_data_bytes = get_bits(&gb, 4) + 1; + if (add_data_bytes == 16) + add_data_bytes += variable_bits(&gb, 2); + align_get_bits(&gb); + immersive = get_bits1(&gb); + } + + if (get_bits_left(&gb) >= 0) + p->immersive_audio_indicator = immersive; +} + +/** + * Derive the presentation information needed for the DSI which is not + * directly transmitted in the ac4_toc(). + */ +static void derive_presentation_v1(const AC4TOC *toc, const uint8_t *frame, + int frame_size, AC4Presentation *p) +{ + int ch_mode = -1, core = -1, obj = 0, adaptive = 0, de = 0, de_unknown = 0; + int n_substreams = 0, ims = -1; + + p->top_channel_pairs = 0; + p->b_4_back_channels_present = 0; + p->channel_groups = 0; + + for (int i = 0; i < p->n_sgi; i++) { + const AC4SubstreamGroup *g = &toc->groups[p->group_index[i]]; + /* The dialogue enhancement substream group does not contribute channels */ + int is_de = i == 1 && (p->presentation_config == 1 || p->presentation_config == 4); + + n_substreams += g->n_substreams; + for (int j = 0; j < g->n_substreams; j++) { + const AC4Substream *ss = &g->ss[j]; + + if (g->b_substreams_present) { + int ret = substream_has_de(toc, frame, frame_size, p, ss); + if (ret < 0) + de_unknown = 1; + else + de |= ret; + } + if (is_de) + continue; + + if (g->b_channel_coded) { + int ss_core = -1; + + if (ss->ch_mode >= CH_MODE_NB) + continue; + + ch_mode = superset(ch_mode, ss->ch_mode, ch_mode_groups, 0, CH_MODE_NB); + if (ss->ch_mode == CH_MODE_7_0_4 || ss->ch_mode == CH_MODE_9_0_4) + ss_core = CORE_5_0_2; + else if (ss->ch_mode == CH_MODE_7_1_4 || ss->ch_mode == CH_MODE_9_1_4) + ss_core = CORE_5_1_2; + core = superset(core, ss_core, core_mode_groups, CORE_5_0, CORE_5_1_2 + 1); + + p->channel_groups |= substream_channel_groups(ss); + if (ss->has_ch_flags) { + p->b_4_back_channels_present |= ss->b_4_back_channels_present; + if (ss->top_channels_present == 3) + p->top_channel_pairs = 2; + else if (ss->top_channels_present) + p->top_channel_pairs = FFMAX(p->top_channel_pairs, 1); + } + if (p->presentation_version == 2) + ims = FFMAX(ims, ss->ims_atmos); + } else { + obj = 1; + if (ss->b_ajoc) { + p->has_ajoc = 1; + if (ss->b_static_dmx) + core = superset(core, ss->b_lfe ? CORE_5_1 : CORE_5_0, + core_mode_groups, CORE_5_0, CORE_5_1_2 + 1); + else + adaptive = 1; + } else { + adaptive = 1; + } + } + } + } + + if (obj) + ch_mode = -1; + if (adaptive || core == ch_mode) + core = -1; + p->ch_mode = ch_mode; + p->ch_mode_core = core; + + /* Without dialogue enhancement information, follow the common practice + * of signalling it as available. */ + p->de_indicator = de || de_unknown; + + p->immersive_audio_indicator = 0; + if (p->pres_substream_index >= 0) + parse_presentation_substream(toc, frame, frame_size, p, n_substreams); + if (ims >= 0) + p->immersive_audio_indicator = ims; +} + +static void derive_presentation_v0(AC4Presentation *p) +{ + int ch_mode = -1; + + p->channel_groups = 0; + for (int i = 0; i < p->n_substreams; i++) { + const AC4Substream *ss = &p->ss[i]; + if (i == 1 && (p->presentation_config == 1 || p->presentation_config == 4)) + continue; + if (ss->ch_mode >= CH_MODE_NB) + continue; + ch_mode = superset(ch_mode, ss->ch_mode, ch_mode_groups, 0, CH_MODE_NB); + p->channel_groups |= substream_channel_groups(ss); + } + p->ch_mode = ch_mode; +} + +static void put_content_type(PutBitContext *pb, const AC4ContentType *ct) +{ + put_bits(pb, 1, ct->present); + if (!ct->present) + return; + put_bits(pb, 3, ct->classifier); + put_bits(pb, 1, ct->language_indicator); + if (ct->language_indicator) { + put_bits(pb, 6, ct->n_language_tag_bytes); + for (int i = 0; i < ct->n_language_tag_bytes; i++) + put_bits(pb, 8, ct->language_tag[i]); + } +} + +static void put_bitrate_dsi(PutBitContext *pb, int mode, uint32_t bit_rate) +{ + put_bits(pb, 2, mode); + put_bits32(pb, bit_rate); + put_bits32(pb, 0xFFFFFFFF); /* bit_rate_precision: unknown */ +} + +static void put_substream_group_dsi(PutBitContext *pb, const AC4SubstreamGroup *g) +{ + put_bits(pb, 1, g->b_substreams_present); + put_bits(pb, 1, g->b_hsf_ext); + put_bits(pb, 1, g->b_channel_coded); + put_bits(pb, 8, g->n_substreams); + for (int i = 0; i < g->n_substreams; i++) { + const AC4Substream *ss = &g->ss[i]; + put_bits(pb, 2, ss->sf_multiplier); + put_bits(pb, 1, ss->b_bitrate_info); + if (ss->b_bitrate_info) + put_bits(pb, 5, ss->bitrate_indicator); + if (g->b_channel_coded) { + put_bits(pb, 24, substream_channel_groups(ss)); + } else { + put_bits(pb, 1, ss->b_ajoc); + if (ss->b_ajoc) { + put_bits(pb, 1, ss->b_static_dmx); + if (!ss->b_static_dmx) + put_bits(pb, 4, ss->n_dmx_objects_minus1); + put_bits(pb, 6, FFMIN(ss->n_umx_objects_minus1, 63)); + } + put_bits(pb, 1, ss->bed_objects); + put_bits(pb, 1, ss->dynamic_objects); + put_bits(pb, 1, ss->isf_objects); + put_bits(pb, 1, 0); + } + } + put_content_type(pb, &g->ct); +} + +static void put_add_emdf_substreams(PutBitContext *pb, const AC4Presentation *p) +{ + put_bits(pb, 7, p->n_add_emdf_substreams); + for (int i = 0; i < p->n_add_emdf_substreams; i++) { + put_bits(pb, 5, FFMIN(p->add_emdf_version[i], 31)); + put_bits(pb, 10, FFMIN(p->add_key_id[i], 1023)); + } +} + +/* ETSI TS 103 190-2, clause E.10 */ +static void put_presentation_v1_dsi(PutBitContext *pb, const AC4TOC *toc, + const AC4Presentation *p, int legacy) +{ + int start = put_bits_count(pb); + int pres_id = p->b_presentation_id ? p->presentation_id : 0; + int config = p->single ? 0x1F : FFMIN(p->presentation_config, 0x1E); + int n_sgi = FFMIN(p->n_sgi, 9); + + put_bits(pb, 5, config); + if (config != 6) { + put_bits(pb, 3, p->md_compat); + put_bits(pb, 1, p->b_presentation_id); + if (p->b_presentation_id) + put_bits(pb, 5, pres_id > 31 ? 0 : pres_id); + put_bits(pb, 2, p->dsi_frame_rate_multiply_info); + put_bits(pb, 2, p->dsi_frame_rate_fraction_info); + put_bits(pb, 5, FFMIN(p->emdf_version, 31)); + put_bits(pb, 10, FFMIN(p->key_id, 1023)); + + put_bits(pb, 1, p->ch_mode >= 0 && p->ch_mode < CH_MODE_NB); + if (p->ch_mode >= 0 && p->ch_mode < CH_MODE_NB) { + put_bits(pb, 5, p->ch_mode); + if (p->ch_mode >= CH_MODE_7_0_4 && p->ch_mode <= CH_MODE_9_1_4) { + put_bits(pb, 1, p->b_4_back_channels_present); + put_bits(pb, 2, p->top_channel_pairs); + } + put_bits(pb, 24, p->channel_groups); + } + + put_bits(pb, 1, p->ch_mode_core >= 0 || p->has_ajoc); + if (p->ch_mode_core >= 0 || p->has_ajoc) { + put_bits(pb, 1, p->ch_mode_core >= 0); + if (p->ch_mode_core >= 0) + put_bits(pb, 2, p->ch_mode_core - CORE_5_0); + } + + put_bits(pb, 1, p->b_presentation_filter); + if (p->b_presentation_filter) { + put_bits(pb, 1, p->b_enable_presentation); + put_bits(pb, 8, 0); /* n_filter_bytes */ + } + + if (config == 0x1F) { + put_substream_group_dsi(pb, &toc->groups[p->group_index[0]]); + } else { + put_bits(pb, 1, p->b_multi_pid); + if (config <= 5) { + if (config == 5) + put_bits(pb, 3, n_sgi - 2); + for (int i = 0; i < n_sgi; i++) + put_substream_group_dsi(pb, &toc->groups[p->group_index[i]]); + } else { + put_bits(pb, 7, 0); /* n_skip_bytes */ + } + } + + /* Immersive stereo presentations are always pre-virtualized, but can + * be decoded as regular stereo by legacy decoders. */ + put_bits(pb, 1, p->presentation_version == 2 ? !legacy : p->b_pre_virtualized); + put_bits(pb, 1, p->b_add_emdf_substreams); + } + if (p->b_add_emdf_substreams) + put_add_emdf_substreams(pb, p); + + put_bits(pb, 1, 0); /* b_presentation_bitrate_info */ + + put_bits(pb, 1, p->b_alternative); + if (p->b_alternative) { + align_put_bits(pb); + put_bits(pb, 16, p->name_len); + for (int i = 0; i < p->name_len; i++) + put_bits(pb, 8, p->name[i]); + put_bits(pb, 5, p->n_targets); + for (int i = 0; i < p->n_targets; i++) { + put_bits(pb, 3, p->target_md_compat[i]); + put_bits(pb, 8, p->target_device_category[i]); + } + } + while ((put_bits_count(pb) - start) & 7) + put_bits(pb, 1, 0); + + put_bits(pb, 1, p->de_indicator); + put_bits(pb, 1, legacy ? 0 : p->immersive_audio_indicator); + put_bits(pb, 4, 0); + put_bits(pb, 1, pres_id > 31); + put_bits(pb, pres_id > 31 ? 9 : 1, pres_id > 31 ? FFMIN(pres_id, 511) : 0); +} + +static void put_substream_dsi(PutBitContext *pb, const AC4Substream *ss) +{ + put_bits(pb, 5, FFMIN(ss->ch_mode, 31)); + put_bits(pb, 2, ss->sf_multiplier); + put_bits(pb, 1, ss->b_bitrate_info); + if (ss->b_bitrate_info) + put_bits(pb, 5, ss->bitrate_indicator); + if (ss->ch_mode >= CH_MODE_7_0_52 && ss->ch_mode <= CH_MODE_7_1_322) + put_bits(pb, 1, ss->add_ch_base); + put_content_type(pb, &ss->ct); +} + +/* ETSI TS 103 190-1, clause E.4a */ +static void put_presentation_v0_dsi(PutBitContext *pb, const AC4Presentation *p) +{ + int start = put_bits_count(pb); + int config = p->single ? 0x1F : FFMIN(p->presentation_config, 0x1E); + + put_bits(pb, 5, config); + if (config != 6) { + put_bits(pb, 3, p->md_compat); + put_bits(pb, 1, p->b_presentation_id); + if (p->b_presentation_id) + put_bits(pb, 5, FFMIN(p->presentation_id, 31)); + put_bits(pb, 2, p->dsi_frame_rate_multiply_info); + put_bits(pb, 5, FFMIN(p->emdf_version, 31)); + put_bits(pb, 10, FFMIN(p->key_id, 1023)); + put_bits(pb, 24, p->channel_groups); + if (p->single) { + put_substream_dsi(pb, &p->ss[0]); + } else { + put_bits(pb, 1, p->b_hsf_ext); + if (config <= 5) { + for (int i = 0; i < p->n_substreams; i++) + put_substream_dsi(pb, &p->ss[i]); + } else { + put_bits(pb, 7, 0); /* n_skip_bytes */ + } + } + put_bits(pb, 1, p->b_pre_virtualized); + put_bits(pb, 1, p->b_add_emdf_substreams); + } + if (p->b_add_emdf_substreams) + put_add_emdf_substreams(pb, p); + while ((put_bits_count(pb) - start) & 7) + put_bits(pb, 1, 0); +} + +int ff_ac4_frame_to_dsi(void *logctx, const uint8_t *frame, int frame_size, + int bit_rate_mode, uint32_t bit_rate, + uint8_t **dsi, int *dsi_size) +{ + GetBitContext gb; + PutBitContext pb; + AC4TOC *toc; + uint8_t *buf = NULL, *pres_buf = NULL, *padded; + int ret, n_pres = 0, buf_size, size; + + *dsi = NULL; + *dsi_size = 0; + + if (frame_size <= 0 || frame_size > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE) + return AVERROR_INVALIDDATA; + + /* The input is not necessarily padded */ + padded = av_malloc(frame_size + AV_INPUT_BUFFER_PADDING_SIZE); + toc = av_mallocz(sizeof(*toc)); + if (!padded || !toc) { + ret = AVERROR(ENOMEM); + goto end; + } + memcpy(padded, frame, frame_size); + memset(padded + frame_size, 0, AV_INPUT_BUFFER_PADDING_SIZE); + frame = padded; + + ret = init_get_bits8(&gb, frame, frame_size); + if (ret < 0) + goto end; + + ret = read_toc(logctx, toc, &gb); + if (ret < 0) { + av_log(logctx, AV_LOG_ERROR, "Error parsing AC-4 table of contents\n"); + goto end; + } + + for (int i = 0; i < toc->n_presentations; i++) { + AC4Presentation *p = &toc->pres[i]; + if (toc->fi.bitstream_version <= 1) { + derive_presentation_v0(p); + n_pres++; + } else { + derive_presentation_v1(toc, frame, frame_size, p); + n_pres += 1 + (p->presentation_version == 2); + } + } + + if (bit_rate_mode < 0) + bit_rate_mode = ff_ac4_update_bit_rate_mode(0, &toc->fi); + + buf_size = 64 + 8 * n_pres; + buf = av_malloc(buf_size + AV_INPUT_BUFFER_PADDING_SIZE); + pres_buf = av_malloc(16384); + if (!buf || !pres_buf) { + ret = AVERROR(ENOMEM); + goto end; + } + + init_put_bits(&pb, buf, buf_size); + put_bits(&pb, 3, 1); /* ac4_dsi_version */ + put_bits(&pb, 7, toc->fi.bitstream_version); + put_bits(&pb, 1, toc->fi.fs_index); + put_bits(&pb, 4, toc->fi.frame_rate_index); + put_bits(&pb, 9, n_pres); + if (toc->fi.bitstream_version > 1) { + put_bits(&pb, 1, toc->b_program_id); + if (toc->b_program_id) { + put_bits(&pb, 16, toc->short_program_id); + put_bits(&pb, 1, toc->b_program_uuid_present); + if (toc->b_program_uuid_present) + for (int i = 0; i < 16; i++) + put_bits(&pb, 8, toc->program_uuid[i]); + } + } + put_bitrate_dsi(&pb, bit_rate_mode, bit_rate); + align_put_bits(&pb); + flush_put_bits(&pb); + size = put_bytes_output(&pb); + + for (int i = 0; i < toc->n_presentations; i++) { + const AC4Presentation *p = &toc->pres[i]; + int nb = toc->fi.bitstream_version > 1 && p->presentation_version == 2 ? 2 : 1; + + for (int j = 0; j < nb; j++) { + PutBitContext ppb; + int version, pres_bytes; + + init_put_bits(&ppb, pres_buf, 16384); + if (toc->fi.bitstream_version <= 1) { + put_presentation_v0_dsi(&ppb, p); + version = 0; + } else { + put_presentation_v1_dsi(&ppb, toc, p, j); + /* The second instance of immersive stereo is regular stereo */ + version = p->presentation_version == 2 && !j ? 2 : 1; + } + flush_put_bits(&ppb); + pres_bytes = put_bytes_output(&ppb); + + ret = av_reallocp(&buf, size + 4 + pres_bytes + AV_INPUT_BUFFER_PADDING_SIZE); + if (ret < 0) + goto end; + buf[size++] = version; + if (pres_bytes >= 255) { + buf[size++] = 255; + AV_WB16(buf + size, pres_bytes - 255); + size += 2; + } else { + buf[size++] = pres_bytes; + } + memcpy(buf + size, pres_buf, pres_bytes); + size += pres_bytes; + } + } + + memset(buf + size, 0, AV_INPUT_BUFFER_PADDING_SIZE); + *dsi = buf; + *dsi_size = size; + buf = NULL; + ret = 0; + +end: + av_free(buf); + av_free(pres_buf); + av_free(toc); + av_free(padded); + return ret; +} + +static void parse_substream_group_dsi(GetBitContext *gb, AC4DSIPresentation *p) +{ + int channel_coded, n_substreams; + + skip_bits(gb, 2); /* b_substreams_present, b_hsf_ext */ + channel_coded = get_bits1(gb); + n_substreams = get_bits(gb, 8); + for (int i = 0; i < n_substreams; i++) { + skip_bits(gb, 2); + if (get_bits1(gb)) + skip_bits(gb, 5); + if (channel_coded) { + skip_bits(gb, 24); + } else { + if (get_bits1(gb)) { /* b_ajoc */ + if (!get_bits1(gb)) + skip_bits(gb, 4); + p->n_objects += get_bits(gb, 6) + 1; + } + skip_bits(gb, 4); + } + } + if (get_bits1(gb)) { /* b_content_type */ + skip_bits(gb, 3); + if (get_bits1(gb)) { + int n = get_bits(gb, 6), keep = !p->language[0]; + for (int i = 0; i < n; i++) { + int c = get_bits(gb, 8); + if (keep) + p->language[i] = c; + } + } + } +} + +static int parse_presentation_v1_dsi(GetBitContext *gb, AC4DSIPresentation *p, + int pres_bytes) +{ + int start = get_bits_count(gb), config, n_sg = 0, add_emdf = 0; + + p->presentation_id = -1; + p->ch_mode = -1; + config = get_bits(gb, 5); + if (config == 6) { + add_emdf = 1; + } else { + p->md_compat = get_bits(gb, 3); + if (get_bits1(gb)) + p->presentation_id = get_bits(gb, 5); + skip_bits(gb, 2 + 2 + 5 + 10); + p->channel_coded = get_bits1(gb); + if (p->channel_coded) { + p->ch_mode = get_bits(gb, 5); + if (p->ch_mode >= CH_MODE_7_0_4 && p->ch_mode <= CH_MODE_9_1_4) + skip_bits(gb, 3); + p->channel_mask = get_bits(gb, 24); + } else { + p->channel_mask = AC4_CHANNEL_MASK_OBJECTS; + } + if (get_bits1(gb)) /* b_presentation_core_differs */ + if (get_bits1(gb)) + skip_bits(gb, 2); + if (get_bits1(gb)) { /* b_presentation_filter */ + skip_bits1(gb); + skip_bits_long(gb, 8 * get_bits(gb, 8)); + } + if (config == 0x1F) { + n_sg = 1; + } else { + skip_bits1(gb); /* b_multi_pid */ + if (config <= 2) + n_sg = 2; + else if (config <= 4) + n_sg = 3; + else if (config == 5) + n_sg = get_bits(gb, 3) + 2; + else + skip_bits_long(gb, 8 * get_bits(gb, 7)); + } + for (int i = 0; i < n_sg; i++) + parse_substream_group_dsi(gb, p); + p->pre_virtualized = get_bits1(gb); + add_emdf = get_bits1(gb); + } + if (add_emdf) + skip_bits_long(gb, 15 * get_bits(gb, 7)); + if (get_bits1(gb)) /* b_presentation_bitrate_info */ + skip_bits_long(gb, 66); + if (get_bits1(gb)) { /* b_alternative */ + align_get_bits(gb); + skip_bits_long(gb, 8 * get_bits(gb, 16)); + skip_bits_long(gb, 11 * get_bits(gb, 5)); + } + while ((get_bits_count(gb) - start) & 7) + skip_bits1(gb); + if (get_bits_count(gb) - start <= (pres_bytes - 1) * 8) { + p->de_indicator = get_bits1(gb); + p->immersive_audio_indicator = get_bits1(gb); + skip_bits(gb, 4); + if (get_bits1(gb)) + p->presentation_id = get_bits(gb, 9); + } + + return get_bits_left(gb) >= 0 ? 0 : AVERROR_INVALIDDATA; +} + +static int parse_presentation_v0_dsi(GetBitContext *gb, AC4DSIPresentation *p, + int dsi_version) +{ + int single = 0, config, n_ss = 0, add_emdf = 0; + + p->presentation_id = -1; + if (!dsi_version) { + single = get_bits1(gb); + config = get_bits(gb, 5); + p->presentation_version = get_bits(gb, 5); + } else { + config = get_bits(gb, 5); + single = config == 0x1F; + } + if (!single && config == 6) { + add_emdf = 1; + } else { + p->md_compat = get_bits(gb, 3); + if (get_bits1(gb)) + p->presentation_id = get_bits(gb, 5); + skip_bits(gb, 2 + 5 + 10); + if (dsi_version) { + skip_bits(gb, 6); + p->channel_mask = get_bits(gb, 18); + } + if (single) { + n_ss = 1; + } else { + skip_bits1(gb); /* b_hsf_ext */ + if (config <= 2) + n_ss = 2; + else if (config <= 4) + n_ss = 3; + else if (config == 5) + n_ss = 1; + else + skip_bits_long(gb, 8 * get_bits(gb, 7)); + } + for (int i = 0; i < n_ss; i++) { + int ch_mode = get_bits(gb, 5); + if (!i) { + p->channel_coded = 1; + p->ch_mode = ch_mode; + if (!dsi_version && ch_mode < CH_MODE_7_0_4) + p->channel_mask = ch_mode_groups[ch_mode]; + } + skip_bits(gb, 2); + if (get_bits1(gb)) + skip_bits(gb, 5); + if (ch_mode >= CH_MODE_7_0_52 && ch_mode <= CH_MODE_7_1_322) + skip_bits1(gb); + if (get_bits1(gb)) { + skip_bits(gb, 3); + if (get_bits1(gb)) + skip_bits_long(gb, 8 * get_bits(gb, 6)); + } + } + p->pre_virtualized = get_bits1(gb); + add_emdf = get_bits1(gb); + } + if (add_emdf) + skip_bits_long(gb, 15 * get_bits(gb, 7)); + align_get_bits(gb); + + return get_bits_left(gb) >= 0 ? 0 : AVERROR_INVALIDDATA; +} + +static int parse_dsi(AC4DSIInfo *info, const uint8_t *buf, int size) +{ + GetBitContext gb; + int ret; + + ret = init_get_bits8(&gb, buf, size); + if (ret < 0) + return ret; + + info->dsi_version = get_bits(&gb, 3); + if (info->dsi_version > 1) + return AVERROR_PATCHWELCOME; + info->bitstream_version = get_bits(&gb, 7); + info->fs_index = get_bits1(&gb); + info->frame_rate_index = get_bits(&gb, 4); + info->n_presentations = get_bits(&gb, 9); + info->sample_rate = info->fs_index ? 48000 : 44100; + if (info->fs_index && info->frame_rate_index < FF_ARRAY_ELEMS(frame_durations_48k) && + frame_durations_48k[info->frame_rate_index].timescale == 48000) + info->frame_size = frame_durations_48k[info->frame_rate_index].duration; + else if (!info->fs_index && info->frame_rate_index == 13) + info->frame_size = 2048; + + if (info->dsi_version == 0) { + for (int i = 0; i < info->n_presentations && i < AC4_DSI_MAX_PRESENTATIONS; i++) { + ret = parse_presentation_v0_dsi(&gb, &info->pres[i], 0); + if (ret < 0) + return ret; + info->nb_presentations_parsed++; + } + return 0; + } + + if (info->bitstream_version > 1) { + if (get_bits1(&gb)) { /* b_program_id */ + skip_bits(&gb, 16); + if (get_bits1(&gb)) + skip_bits_long(&gb, 128); + } + } + info->bit_rate_mode = get_bits(&gb, 2); + info->bit_rate = get_bits_long(&gb, 32); + skip_bits_long(&gb, 32); + align_get_bits(&gb); + + for (int i = 0; i < info->n_presentations && i < AC4_DSI_MAX_PRESENTATIONS; i++) { + AC4DSIPresentation *p = &info->pres[i]; + int version, pres_bytes, start; + + if (get_bits_left(&gb) < 16) + return AVERROR_INVALIDDATA; + version = get_bits(&gb, 8); + pres_bytes = get_bits(&gb, 8); + if (pres_bytes == 255) + pres_bytes += get_bits(&gb, 16); + if (pres_bytes > get_bits_left(&gb) >> 3) + return AVERROR_INVALIDDATA; + + start = get_bits_count(&gb); + ret = 0; + p->presentation_version = version; + p->presentation_id = -1; + p->ch_mode = -1; + if (version == 0) { + ret = parse_presentation_v0_dsi(&gb, p, 1); + } else if (version <= 2) { + ret = parse_presentation_v1_dsi(&gb, p, pres_bytes); + } + if (ret < 0) + return ret; + info->nb_presentations_parsed++; + + skip_bits_long(&gb, start + 8 * pres_bytes - get_bits_count(&gb)); + } + + return 0; +} + +int ff_ac4_parse_dsi(AC4DSIInfo *info, const uint8_t *buf, int size) +{ + uint8_t *tmp; + int ret; + + memset(info, 0, sizeof(*info)); + + if (size <= 0 || size > INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE) + return AVERROR_INVALIDDATA; + + /* The input is not necessarily padded */ + tmp = av_malloc(size + AV_INPUT_BUFFER_PADDING_SIZE); + if (!tmp) + return AVERROR(ENOMEM); + memcpy(tmp, buf, size); + memset(tmp + size, 0, AV_INPUT_BUFFER_PADDING_SIZE); + + ret = parse_dsi(info, tmp, size); + av_free(tmp); + return ret; +} + +static const struct { + uint32_t group; + uint64_t mask; +} group_channels[] = { + { G_LR, AV_CH_FRONT_LEFT | AV_CH_FRONT_RIGHT }, + { G_C, AV_CH_FRONT_CENTER }, + { G_LSRS, AV_CH_SIDE_LEFT | AV_CH_SIDE_RIGHT }, + { G_LBRB, AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT }, + { G_TFLR, AV_CH_TOP_FRONT_LEFT | AV_CH_TOP_FRONT_RIGHT }, + { G_TBLR, AV_CH_TOP_BACK_LEFT | AV_CH_TOP_BACK_RIGHT }, + { G_LFE, AV_CH_LOW_FREQUENCY }, + { G_TSLR, AV_CH_TOP_SIDE_LEFT | AV_CH_TOP_SIDE_RIGHT }, + { 1 << 8, AV_CH_TOP_SIDE_LEFT | AV_CH_TOP_SIDE_RIGHT }, /* deprecated Tsl/Tsr group */ + { G_TFC, AV_CH_TOP_FRONT_CENTER }, + { G_TBC, AV_CH_TOP_BACK_CENTER }, + { G_TC, AV_CH_TOP_CENTER }, + { G_LFE2, AV_CH_LOW_FREQUENCY_2 }, + { G_BFLR, AV_CH_BOTTOM_FRONT_LEFT | AV_CH_BOTTOM_FRONT_RIGHT }, + { G_BFC, AV_CH_BOTTOM_FRONT_CENTER }, + { G_CB, AV_CH_BACK_CENTER }, + { G_SCR, AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER }, + { G_LWRW, AV_CH_WIDE_LEFT | AV_CH_WIDE_RIGHT }, + { 1 << 18, AV_CH_TOP_FRONT_LEFT | AV_CH_TOP_FRONT_RIGHT }, /* Vhl/Vhr, TS 103 190-2 V1.2.1 */ +}; + +int ff_ac4_channel_mask_to_nb_channels(uint32_t mask) +{ + uint64_t layout = 0; + + if (mask & AC4_CHANNEL_MASK_OBJECTS) + return 0; + + for (int i = 0; i < FF_ARRAY_ELEMS(group_channels); i++) + if (mask & group_channels[i].group) + layout |= group_channels[i].mask; + + return av_popcount64(layout); +} + +static int channel_mask_to_layout(AVChannelLayout *layout, uint32_t mask) +{ + uint64_t ch = 0; + + if (!mask || (mask & AC4_CHANNEL_MASK_OBJECTS)) + return AVERROR(EINVAL); + + for (int i = 0; i < FF_ARRAY_ELEMS(group_channels); i++) + if (mask & group_channels[i].group) + ch |= group_channels[i].mask; + + av_channel_layout_uninit(layout); + return av_channel_layout_from_mask(layout, ch); +} + +int ff_ac4_presentation_to_layout(AVChannelLayout *layout, const AC4DSIPresentation *p) +{ + if (p->channel_coded && !channel_mask_to_layout(layout, p->channel_mask)) + return 0; + + av_channel_layout_uninit(layout); + layout->order = AV_CHANNEL_ORDER_UNSPEC; + layout->nb_channels = p->n_objects > 0 ? p->n_objects : 2; + return 0; +} + +int ff_ac4_channel_mask_to_cicp(uint32_t mask) +{ + /* Dolby AC-4 online delivery kit, AudioChannelConfiguration descriptor */ + static const struct { + uint32_t mask; + int cicp; + } map[] = { + { 0x000002, 1 }, { 0x000001, 2 }, { 0x000003, 3 }, { 0x008003, 4 }, + { 0x000007, 5 }, { 0x000047, 6 }, { 0x020047, 7 }, { 0x008001, 9 }, + { 0x000005, 10 }, { 0x008047, 11 }, { 0x00004F, 12 }, { 0x02FF7F, 13 }, + { 0x02FEFF, 13 }, { 0x06FF6F, 13 }, { 0x000057, 14 }, { 0x040047, 14 }, + { 0x00145F, 15 }, { 0x04144F, 15 }, { 0x000077, 16 }, { 0x040067, 16 }, + { 0x000A77, 17 }, { 0x040A67, 17 }, { 0x000A7F, 18 }, { 0x040A6F, 18 }, + { 0x00007F, 19 }, { 0x04006F, 19 }, { 0x01007F, 20 }, { 0x05006F, 20 }, + }; + + for (int i = 0; i < FF_ARRAY_ELEMS(map); i++) + if (map[i].mask == mask) + return map[i].cicp; + return 0; +} diff --git a/libavcodec/ac4_parse.h b/libavcodec/ac4_parse.h new file mode 100644 index 0000000000..cf55880634 --- /dev/null +++ b/libavcodec/ac4_parse.h @@ -0,0 +1,145 @@ +/* + * AC-4 table of contents and decoder specific information parsing + * + * 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 + */ + +#ifndef AVCODEC_AC4_PARSE_H +#define AVCODEC_AC4_PARSE_H + +#include <stdint.h> + +#include "libavutil/channel_layout.h" + +#define AC4_DSI_MAX_PRESENTATIONS 16 + +/** + * Bit 23 of an AC-4 channel mask, signalling object based content. + */ +#define AC4_CHANNEL_MASK_OBJECTS (1 << 23) + +/** + * Speaker groups of an AC-4 channel mask above or below the listener: + * Tfl/Tfr, Tbl/Tbr, Tsl/Tsr, Tfc, Tbc, Tc, Bfl/Bfr, Bfc and Vhl/Vhr. + */ +#define AC4_CHANNEL_MASK_HEIGHTS 0x046FB0 + +/** + * Fields of the fixed-length part of an ac4_toc(). + */ +typedef struct AC4FrameInfo { + int bitstream_version; + int sequence_counter; + int wait_frames; ///< -1 if b_wait_frames is not set + int fs_index; + int frame_rate_index; + int iframe; ///< b_iframe_global + int sample_rate; ///< base sampling frequency + int timescale; ///< media time scale, as per ETSI TS 103 190-2, table E.1 + int frame_duration; ///< duration of a frame, in units of timescale +} AC4FrameInfo; + +typedef struct AC4DSIPresentation { + int presentation_version; + int md_compat; + int presentation_id; ///< -1 if absent + int channel_coded; + int ch_mode; ///< -1 if not channel coded + /** + * Speaker groups present (ETSI TS 103 190-2 table A.27), or + * AC4_CHANNEL_MASK_OBJECTS for object based presentations. + */ + uint32_t channel_mask; + int n_objects; ///< number of A-JOC upmix objects, 0 if unknown + int pre_virtualized; + int de_indicator; + int immersive_audio_indicator; + char language[64]; ///< first language tag found, BCP 47 +} AC4DSIPresentation; + +typedef struct AC4DSIInfo { + int dsi_version; + int bitstream_version; + int fs_index; + int frame_rate_index; + int sample_rate; + int frame_size; ///< samples per frame, 0 if not an integer + int n_presentations; + int bit_rate_mode; + uint32_t bit_rate; + int nb_presentations_parsed; + AC4DSIPresentation pres[AC4_DSI_MAX_PRESENTATIONS]; +} AC4DSIInfo; + +/** + * Parse the fixed-length part of the ac4_toc() at the start of a raw_ac4_frame(). + * + * @return 0 on success, a negative error code on failure + */ +int ff_ac4_parse_frame_info(AC4FrameInfo *info, const uint8_t *buf, int size); + +/** + * Create an ac4_dsi_v1() (the payload of an AC4SpecificBox) describing the + * AC-4 stream the given raw_ac4_frame() is part of. + * + * The DSI is followed by AV_INPUT_BUFFER_PADDING_SIZE zeroed padding bytes. + * + * @param bit_rate_mode ac4_bitrate_dsi()/bit_rate_mode to write, or -1 to + * derive it from the frame + * @param bit_rate average bit rate of the stream, 0 if unknown + * @param[out] dsi newly allocated buffer containing the DSI + * @param[out] dsi_size size of the DSI + * @return 0 on success, a negative error code on failure + */ +int ff_ac4_frame_to_dsi(void *logctx, const uint8_t *frame, int frame_size, + int bit_rate_mode, uint32_t bit_rate, + uint8_t **dsi, int *dsi_size); + +/** + * Parse an ac4_dsi_v1() (or the older ac4_dsi()). + * + * @return 0 on success, a negative error code on failure + */ +int ff_ac4_parse_dsi(AC4DSIInfo *info, const uint8_t *buf, int size); + +/** + * Derive a bit_rate_mode for an ac4_bitrate_dsi() from the wait_frames of all + * frames of a stream. Call with prev_mode 0 for the first frame. + */ +int ff_ac4_update_bit_rate_mode(int prev_mode, const AC4FrameInfo *info); + +/** + * Get the number of channels of an AC-4 channel mask (i.e. speaker groups). + */ +int ff_ac4_channel_mask_to_nb_channels(uint32_t mask); + +/** + * Set a channel layout describing a presentation from an ac4_dsi_v1(). + * Object based presentations are described with an unspecified channel order + * and the number of objects, if known. + */ +int ff_ac4_presentation_to_layout(AVChannelLayout *layout, const AC4DSIPresentation *p); + +/** + * Get the ISO/IEC 23091-3 ChannelConfiguration value corresponding to an + * AC-4 channel mask. + * + * @return the ChannelConfiguration, or 0 if there is none + */ +int ff_ac4_channel_mask_to_cicp(uint32_t mask); + +#endif /* AVCODEC_AC4_PARSE_H */ -- 2.52.0 >From 46036b612ea07ceb288cb4df7f43974378d88a73 Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Sun, 27 Sep 2026 20:22:00 +0900 Subject: [PATCH 02/15] avcodec: add AC-4 parser Split AC-4 sync frames (ETSI TS 103 190-1, annex G), as carried in MPEG-TS, and output the raw frames they contain, which is what the raw AC-4 demuxer and ISOBMFF already return. This keeps a single packet format for AC-4; raw frames cannot be reliably told apart from sync frames. Complete raw frames are passed through. The sampling rate, frame durations, I-frames and the channel layout of the first presentation are exported. --- libavcodec/Makefile | 1 + libavcodec/ac4_parser.c | 206 ++++++++++++++++++++++++++++++++++++++++ libavcodec/parsers.c | 1 + 3 files changed, 208 insertions(+) create mode 100644 libavcodec/ac4_parser.c diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 3922e84812..a7e7283cb9 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -1254,6 +1254,7 @@ OBJS-$(CONFIG_AAC_LATM_PARSER) += latm_parser.o OBJS-$(CONFIG_AAC_PARSER) += aac_parser.o aac_ac3_parser.o OBJS-$(CONFIG_AC3_PARSER) += aac_ac3_parser.o ac3tab.o \ ac3_channel_layout_tab.o +OBJS-$(CONFIG_AC4_PARSER) += ac4_parser.o OBJS-$(CONFIG_ADX_PARSER) += adx_parser.o OBJS-$(CONFIG_AHX_PARSER) += ahx_parser.o OBJS-$(CONFIG_AMR_PARSER) += amr_parser.o diff --git a/libavcodec/ac4_parser.c b/libavcodec/ac4_parser.c new file mode 100644 index 0000000000..307a904ae4 --- /dev/null +++ b/libavcodec/ac4_parser.c @@ -0,0 +1,206 @@ +/* + * AC-4 parser + * + * 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 + */ + +/** + * @file + * AC-4 parser + * + * Splits a stream of AC-4 sync frames (ETSI TS 103 190-1, annex G), as carried + * in MPEG-TS, into raw_ac4_frame()s, which is the packet format used for + * AC-4. Complete raw frames are passed through. + */ + +#include "libavutil/intreadwrite.h" +#include "libavutil/mem.h" + +#include "ac4_parse.h" +#include "get_bits.h" +#include "parser.h" +#include "parser_internal.h" + +typedef struct AC4ParseContext { + ParseContext pc; + int header_bytes; ///< number of bytes of the header search window + int in_frame; + int64_t remaining; ///< bytes left in the current sync frame + int raw_size; ///< size of the raw frame of the current sync frame + int crc; + int frame_done; + int out_raw_size; ///< size of the raw frame of the last complete sync frame + int out_crc; + unsigned frame_num; ///< used to distribute fractional frame durations +} AC4ParseContext; + +/* Frame durations in 1/240000 units for a 48 kHz base sampling frequency */ +static const uint16_t frame_duration_48k[14] = { + 10010, 10000, 9600, 8008, 8000, 5005, 5000, 4800, 4004, 4000, 2400, 2002, 2000, 10240, +}; + +static int find_frame_end(AC4ParseContext *p, const uint8_t *buf, int buf_size) +{ + uint64_t state = p->pc.state64; + int i = 0; + + p->frame_done = 0; + + while (i < buf_size) { + if (p->in_frame) { + int64_t n = FFMIN(p->remaining, buf_size - i); + i += n; + p->remaining -= n; + if (!p->remaining) { + p->in_frame = 0; + p->frame_done = 1; + p->out_raw_size = p->raw_size; + p->out_crc = p->crc; + p->pc.state64 = 0; + p->header_bytes = 0; + return i; + } + continue; + } + + state = (state << 8) | buf[i++]; + p->header_bytes = FFMIN(p->header_bytes + 1, 8); + + if (p->header_bytes >= 4 && ((state >> 16) & 0xFFFE) == 0xAC40 && + (state & 0xFFFF) != 0xFFFF && (state & 0xFFFF)) { + p->raw_size = state & 0xFFFF; + p->crc = (state >> 16) & 1; + } else if (p->header_bytes >= 7 && ((state >> 40) & 0xFFFE) == 0xAC40 && + ((state >> 24) & 0xFFFF) == 0xFFFF && (state & 0xFFFFFF)) { + p->raw_size = state & 0xFFFFFF; + p->crc = (state >> 40) & 1; + } else { + continue; + } + + p->in_frame = 1; + p->remaining = p->raw_size + 2 * p->crc; + state = 0; + } + + p->pc.state64 = state; + return END_NOT_FOUND; +} + +static void parse_frame_info(AVCodecParserContext *s, AVCodecContext *avctx, + AC4ParseContext *p, const uint8_t *buf, int buf_size) +{ + GetBitContext gb; + int fs_index, frame_rate_index; + + s->key_frame = -1; + s->duration = 0; + + if (init_get_bits8(&gb, buf, FFMIN(buf_size, 8)) < 0) + return; + + if (get_bits(&gb, 2) == 3) { /* bitstream_version */ + do { + skip_bits(&gb, 2); + } while (get_bits1(&gb) && get_bits_left(&gb) > 0); + } + skip_bits(&gb, 10); /* sequence_counter */ + if (get_bits1(&gb)) { /* b_wait_frames */ + if (get_bits(&gb, 3)) + skip_bits(&gb, 2); + } + fs_index = get_bits1(&gb); + frame_rate_index = get_bits(&gb, 4); + if (get_bits_left(&gb) < 1) + return; + s->key_frame = get_bits1(&gb); + + if (fs_index) { + unsigned d; + if (frame_rate_index >= FF_ARRAY_ELEMS(frame_duration_48k)) + return; + /* Distribute fractional numbers of samples per frame evenly */ + d = frame_duration_48k[frame_rate_index]; + s->duration = ((p->frame_num + 1) * d) / 5 - (p->frame_num * d) / 5; + p->frame_num = (p->frame_num + 1) % 5; + avctx->sample_rate = 48000; + avctx->frame_size = d % 5 ? 0 : d / 5; + } else if (frame_rate_index == 13) { + s->duration = 2048; + avctx->sample_rate = 44100; + avctx->frame_size = 2048; + } + + /* The channel layout of the first presentation */ + if (s->key_frame == 1 || !avctx->ch_layout.nb_channels) { + uint8_t *dsi; + int dsi_size; + + if (ff_ac4_frame_to_dsi(avctx, buf, buf_size, -1, 0, &dsi, &dsi_size) >= 0) { + AC4DSIInfo info; + if (ff_ac4_parse_dsi(&info, dsi, dsi_size) >= 0 && info.nb_presentations_parsed) + ff_ac4_presentation_to_layout(&avctx->ch_layout, &info.pres[0]); + av_free(dsi); + } + } +} + +static int ac4_parse(AVCodecParserContext *s, AVCodecContext *avctx, + const uint8_t **poutbuf, int *poutbuf_size, + const uint8_t *buf, int buf_size) +{ + AC4ParseContext *p = s->priv_data; + const uint8_t *frame = buf; + int next, frame_size = buf_size, in_size = buf_size; + + *poutbuf = NULL; + *poutbuf_size = 0; + + if (s->flags & PARSER_FLAG_COMPLETE_FRAMES) { + next = buf_size; + } else { + next = find_frame_end(p, buf, buf_size); + if (ff_combine_frame(&p->pc, next, &buf, &buf_size) < 0) + return in_size; + /* Discard incomplete data at the end of the stream */ + if (!p->frame_done) + return next; + + /* The combined buffer may contain garbage before the sync frame, + * the raw frame is at its end. */ + frame_size = p->out_raw_size; + if (frame_size + 2 * p->out_crc > buf_size) + return next; + frame = buf + buf_size - 2 * p->out_crc - frame_size; + } + + if (!frame_size) + return next; + + parse_frame_info(s, avctx, p, frame, frame_size); + + *poutbuf = frame; + *poutbuf_size = frame_size; + return next; +} + +const FFCodecParser ff_ac4_parser = { + PARSER_CODEC_LIST(AV_CODEC_ID_AC4), + .priv_data_size = sizeof(AC4ParseContext), + .parse = ac4_parse, + .close = ff_parse_close, +}; diff --git a/libavcodec/parsers.c b/libavcodec/parsers.c index b54442a0ea..89b4c8f4ea 100644 --- a/libavcodec/parsers.c +++ b/libavcodec/parsers.c @@ -24,6 +24,7 @@ extern const FFCodecParser ff_aac_parser; extern const FFCodecParser ff_aac_latm_parser; extern const FFCodecParser ff_ac3_parser; +extern const FFCodecParser ff_ac4_parser; extern const FFCodecParser ff_adx_parser; extern const FFCodecParser ff_ahx_parser; extern const FFCodecParser ff_amr_parser; -- 2.52.0 >From d8dbed160d25b0ca9e32781628c184b50f29190f Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Sun, 27 Sep 2026 20:22:00 +0900 Subject: [PATCH 03/15] avcodec/bsf/extract_extradata: support AC-4 Derive the decoder specific information of AC-4 streams from the table of contents of I-frames, which allows streams from MPEG-TS and raw files to have the same extradata as those demuxed from ISOBMFF. The DSI is only signalled again when it changes, as some fields may vary between frames. --- doc/bitstream_filters.texi | 6 +++- libavcodec/bsf/extract_extradata.c | 51 ++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/doc/bitstream_filters.texi b/doc/bitstream_filters.texi index a00757d315..787535d94b 100644 --- a/doc/bitstream_filters.texi +++ b/doc/bitstream_filters.texi @@ -274,10 +274,14 @@ container level). This latter form is called "extradata" in FFmpeg terminology. This bitstream filter detects the in-band headers and makes them available as extradata. +For AC-4, the decoder specific information (as stored in an ISOBMFF +AC4SpecificBox) is derived from the table of contents of I-frames. It is only +signalled again when it changes. + @table @option @item remove When this option is enabled, the long-term headers are removed from the -bitstream after extraction. +bitstream after extraction. This has no effect for AC-4. @end table @section filter_units diff --git a/libavcodec/bsf/extract_extradata.c b/libavcodec/bsf/extract_extradata.c index 3c66f392b2..e94372a27b 100644 --- a/libavcodec/bsf/extract_extradata.c +++ b/libavcodec/bsf/extract_extradata.c @@ -22,6 +22,7 @@ #include "libavutil/mem.h" #include "libavutil/opt.h" +#include "libavcodec/ac4_parse.h" #include "libavcodec/av1.h" #include "libavcodec/av1_parse.h" #include "libavcodec/bsf.h" @@ -49,6 +50,11 @@ typedef struct ExtractExtradataContext { /* H264/HEVC specific fields */ H2645Packet h2645_pkt; + /* AC-4 specific fields */ + uint8_t *ac4_dsi; + int ac4_dsi_size; + int ac4_bit_rate_mode; + /* AVOptions */ int remove; } ExtractExtradataContext; @@ -488,6 +494,48 @@ static int extract_extradata_lcevc(AVBSFContext *ctx, AVPacket *pkt, return 0; } +static int extract_extradata_ac4(AVBSFContext *ctx, AVPacket *pkt, + uint8_t **data, int *size) +{ + ExtractExtradataContext *s = ctx->priv_data; + AC4FrameInfo info; + uint8_t *dsi; + int dsi_size; + + /* The decoder specific information is derived from the table of contents, + * which cannot be removed. Some of its information is only present in + * I-frames. */ + if (ff_ac4_parse_frame_info(&info, pkt->data, pkt->size) < 0 || !info.iframe) + return 0; + + if (!s->ac4_dsi) + s->ac4_bit_rate_mode = ff_ac4_update_bit_rate_mode(0, &info); + + if (ff_ac4_frame_to_dsi(ctx, pkt->data, pkt->size, s->ac4_bit_rate_mode, 0, + &dsi, &dsi_size) < 0) + return 0; + + /* Only signal configuration changes */ + if (s->ac4_dsi && s->ac4_dsi_size == dsi_size && + !memcmp(s->ac4_dsi, dsi, dsi_size)) { + av_free(dsi); + return 0; + } + + av_freep(&s->ac4_dsi); + s->ac4_dsi = av_memdup(dsi, dsi_size); + if (!s->ac4_dsi) { + av_free(dsi); + return AVERROR(ENOMEM); + } + s->ac4_dsi_size = dsi_size; + + *data = dsi; + *size = dsi_size; + + return 0; +} + static int extract_extradata_vc1(AVBSFContext *ctx, AVPacket *pkt, uint8_t **data, int *size) { @@ -586,6 +634,7 @@ static const struct { int (*extract)(AVBSFContext *ctx, AVPacket *pkt, uint8_t **data, int *size); } extract_tab[] = { + { AV_CODEC_ID_AC4, extract_extradata_ac4 }, { AV_CODEC_ID_AV1, extract_extradata_av1 }, { AV_CODEC_ID_AVS2, extract_extradata_mpeg4 }, { AV_CODEC_ID_AVS3, extract_extradata_mpeg4 }, @@ -654,9 +703,11 @@ static void extract_extradata_close(AVBSFContext *ctx) ExtractExtradataContext *s = ctx->priv_data; ff_av1_packet_uninit(&s->av1_pkt); ff_h2645_packet_uninit(&s->h2645_pkt); + av_freep(&s->ac4_dsi); } static const enum AVCodecID codec_ids[] = { + AV_CODEC_ID_AC4, AV_CODEC_ID_AV1, AV_CODEC_ID_AVS2, AV_CODEC_ID_AVS3, -- 2.52.0 >From f89a52bf0238bc1732c8f87951e7a9b26bb609a2 Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Sun, 27 Sep 2026 20:22:00 +0900 Subject: [PATCH 04/15] avformat/ac4enc: compute the CRC over the frame size too The crc_word covers frame_size() as well as the raw frame (ETSI TS 103 190-1, clause G.4.2). --- libavformat/ac4enc.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/libavformat/ac4enc.c b/libavformat/ac4enc.c index 0505b05147..c8bbfa0546 100644 --- a/libavformat/ac4enc.c +++ b/libavformat/ac4enc.c @@ -21,6 +21,7 @@ #include "libavcodec/codec_id.h" #include "libavcodec/packet.h" #include "libavutil/crc.h" +#include "libavutil/intreadwrite.h" #include "libavutil/opt.h" #include "avformat.h" #include "mux.h" @@ -34,26 +35,30 @@ static int ac4_write_packet(AVFormatContext *s, AVPacket *pkt) { AC4Context *ac4 = s->priv_data; AVIOContext *pb = s->pb; + uint8_t frame_size[5]; + int frame_size_len; if (!pkt->size) return 0; - if (ac4->write_crc) - avio_wb16(pb, 0xAC41); - else - avio_wb16(pb, 0xAC40); - if (pkt->size >= 0xffff) { - avio_wb16(pb, 0xffff); - avio_wb24(pb, pkt->size); + AV_WB16(frame_size, 0xffff); + AV_WB24(frame_size + 2, pkt->size); + frame_size_len = 5; } else { - avio_wb16(pb, pkt->size); + AV_WB16(frame_size, pkt->size); + frame_size_len = 2; } + avio_wb16(pb, ac4->write_crc ? 0xAC41 : 0xAC40); + avio_write(pb, frame_size, frame_size_len); avio_write(pb, pkt->data, pkt->size); if (ac4->write_crc) { - uint16_t crc = av_crc(av_crc_get_table(AV_CRC_16_ANSI), 0, pkt->data, pkt->size); + /* The CRC protects frame_size() and raw_ac4_frame() */ + const AVCRC *crc_tab = av_crc_get_table(AV_CRC_16_ANSI); + uint16_t crc = av_crc(crc_tab, 0, frame_size, frame_size_len); + crc = av_crc(crc_tab, crc, pkt->data, pkt->size); avio_wl16(pb, crc); } -- 2.52.0 >From 4db642559f2c946ab4a5593527b9fc78f26c282c Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Sun, 27 Sep 2026 20:22:00 +0900 Subject: [PATCH 05/15] avformat/ac4dec: export timestamps and I-frames Set the time base to the media time scale of ETSI TS 103 190-2, table E.1, which represents frame durations of all frame rates exactly, and export packet durations and I-frames as keyframes. The sampling rate and channel layout are now exported as well, via the AC-4 parser. Resynchronize on invalid data, and skip ID3v2 tags, which HTTP Live Streaming packed audio segments start with. --- libavformat/Makefile | 2 +- libavformat/ac4_parse.c | 19 ++++++++ libavformat/ac4dec.c | 100 ++++++++++++++++++++++++++++++++++------ 3 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 libavformat/ac4_parse.c diff --git a/libavformat/Makefile b/libavformat/Makefile index 437c3edb5a..b3adbde7fd 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -752,7 +752,7 @@ OBJS-$(CONFIG_LIBSSH_PROTOCOL) += libssh.o OBJS-$(CONFIG_LIBZMQ_PROTOCOL) += libzmq.o # Objects duplicated from other libraries for shared builds -SHLIBOBJS += log2_tab.o to_upper4.o \ +SHLIBOBJS += ac4_parse.o log2_tab.o to_upper4.o \ h2645_parse.o lcevctab.o SHLIBOBJS-$(CONFIG_ISO_MEDIA) += mpegaudiotabs.o SHLIBOBJS-$(CONFIG_ISO_WRITER) += golomb_tab.o diff --git a/libavformat/ac4_parse.c b/libavformat/ac4_parse.c new file mode 100644 index 0000000000..72b236686a --- /dev/null +++ b/libavformat/ac4_parse.c @@ -0,0 +1,19 @@ +/* + * 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 "libavcodec/ac4_parse.c" diff --git a/libavformat/ac4dec.c b/libavformat/ac4dec.c index dc6638de3a..53d4354828 100644 --- a/libavformat/ac4dec.c +++ b/libavformat/ac4dec.c @@ -19,11 +19,13 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "libavutil/avassert.h" -#include "libavutil/crc.h" +#include "libavutil/intreadwrite.h" +#include "libavcodec/ac4_parse.h" #include "avformat.h" +#include "avio_internal.h" #include "demux.h" -#include "rawdec.h" +#include "id3v2.h" +#include "internal.h" static int ac4_probe(const AVProbeData *p) { @@ -56,9 +58,47 @@ static int ac4_probe(const AVProbeData *p) return FFMIN(AVPROBE_SCORE_MAX, max_frames * 7); } +/** + * Skip to the next sync frame, skipping ID3v2 tags (as found at the start of + * HTTP Live Streaming audio segments) and garbage. + */ +static int ac4_resync(AVFormatContext *s) +{ + AVIOContext *pb = s->pb; + + while (1) { + uint8_t buf[ID3v2_HEADER_SIZE]; + int64_t pos = avio_tell(pb); + int ret; + + ffio_ensure_seekback(pb, sizeof(buf)); + ret = avio_read(pb, buf, sizeof(buf)); + if (ret < 2) + return ret < 0 ? ret : AVERROR_EOF; + + if ((AV_RB16(buf) & 0xFFFE) == 0xAC40) { + ret = avio_seek(pb, pos, SEEK_SET); + return ret < 0 ? ret : 0; + } + + if (ret == sizeof(buf) && ff_id3v2_match(buf, ID3v2_DEFAULT_MAGIC)) { + avio_skip(pb, ff_id3v2_tag_len(buf) - sizeof(buf)); + } else { + ret = avio_seek(pb, pos + 1, SEEK_SET); + if (ret < 0) + return ret; + } + } +} + static int ac4_read_header(AVFormatContext *s) { + AVIOContext *pb = s->pb; + AC4FrameInfo info; AVStream *st; + uint8_t buf[7 + 16]; + int64_t pos; + int hdr_size, size, ret; st = avformat_new_stream(s, NULL); if (!st) @@ -66,6 +106,28 @@ static int ac4_read_header(AVFormatContext *s) st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; st->codecpar->codec_id = AV_CODEC_ID_AC4; + /* For the channel layout; the extradata is derived when probing */ + ffstream(st)->need_parsing = AVSTREAM_PARSE_HEADERS; + + /* The time base depends on the frame rate */ + ret = ac4_resync(s); + if (ret < 0) + return ret; + pos = avio_tell(pb); + + ffio_ensure_seekback(pb, sizeof(buf)); + size = avio_read(pb, buf, sizeof(buf)); + if (avio_seek(pb, pos, SEEK_SET) < 0) + return AVERROR(EIO); + + hdr_size = size >= 4 && AV_RB16(buf + 2) == 0xFFFF ? 7 : 4; + if (ff_ac4_parse_frame_info(&info, buf + hdr_size, size - hdr_size) < 0) { + av_log(s, AV_LOG_WARNING, "Could not parse the first AC-4 frame\n"); + info.sample_rate = info.timescale = 48000; + } + + st->codecpar->sample_rate = info.sample_rate; + avpriv_set_pts_info(st, 64, 1, info.timescale); return 0; } @@ -73,26 +135,38 @@ static int ac4_read_header(AVFormatContext *s) static int ac4_read_packet(AVFormatContext *s, AVPacket *pkt) { AVIOContext *pb = s->pb; + AVStream *st = s->streams[0]; + AC4FrameInfo info; int64_t pos; - uint16_t sync; - int ret, size; + int sync, size, ret; - if (avio_feof(s->pb)) - return AVERROR_EOF; - - pos = avio_tell(s->pb); - sync = avio_rb16(pb); - size = avio_rb16(pb); - if (size == 0xffff) - size = avio_rb24(pb); + do { + ret = ac4_resync(s); + if (ret < 0) + return ret; + pos = avio_tell(pb); + sync = avio_rb16(pb); + size = avio_rb16(pb); + if (size == 0xFFFF) + size = avio_rb24(pb); + } while (!size); ret = av_get_packet(pb, pkt, size); + if (ret < 0) + return ret; pkt->pos = pos; pkt->stream_index = 0; if (sync == 0xAC41) avio_skip(pb, 2); + if (ff_ac4_parse_frame_info(&info, pkt->data, pkt->size) >= 0) { + pkt->duration = av_rescale(info.frame_duration, st->time_base.den, + (int64_t)info.timescale * st->time_base.num); + if (info.iframe) + pkt->flags |= AV_PKT_FLAG_KEY; + } + return ret; } -- 2.52.0 >From 14087c5df0fda623ae55940defb52f940c1fa438 Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Sun, 27 Sep 2026 20:22:00 +0900 Subject: [PATCH 06/15] avformat/mov: parse the AC-4 specific box Export the ac4_dsi_v1() as extradata, and the sampling rate, frame size and channel layout of the first presentation. --- libavformat/mov.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/libavformat/mov.c b/libavformat/mov.c index 24b178649a..d8c0500859 100644 --- a/libavformat/mov.c +++ b/libavformat/mov.c @@ -53,6 +53,7 @@ #include "libavutil/timecode.h" #include "libavutil/uuid.h" #include "libavcodec/ac3tab.h" +#include "libavcodec/ac4_parse.h" #include "libavcodec/exif.h" #include "libavcodec/flac.h" #include "libavcodec/hevc/hevc.h" @@ -1168,6 +1169,38 @@ static int mov_read_dec3(MOVContext *c, AVIOContext *pb, MOVAtom atom) return 0; } +static int mov_read_dac4(MOVContext *c, AVIOContext *pb, MOVAtom atom) +{ + AVStream *st; + AC4DSIInfo info; + int ret; + + if (c->fc->nb_streams < 1) + return 0; + st = c->fc->streams[c->fc->nb_streams-1]; + + if ((uint64_t)atom.size > (1 << 20) || atom.size < 3) + return AVERROR_INVALIDDATA; + + av_freep(&st->codecpar->extradata); + st->codecpar->extradata_size = 0; + ret = ff_get_extradata(c->fc, st->codecpar, pb, atom.size); + if (ret < 0) + return ret; + + /* The DSI is informative only; the stream itself is authoritative. */ + if (ff_ac4_parse_dsi(&info, st->codecpar->extradata, + st->codecpar->extradata_size) < 0 || + !info.nb_presentations_parsed) + return 0; + + st->codecpar->sample_rate = info.sample_rate; + st->codecpar->frame_size = info.frame_size; + ff_ac4_presentation_to_layout(&st->codecpar->ch_layout, &info.pres[0]); + + return 0; +} + /* Maps the DTS `StreamConstruction` field (ETSI TS 102 114 Table E-2) to the * coarse FFmpeg DTS profile enum. Extension-substream XLL maps to HD_MA; * extension-substream XXCH, X96, or XBR maps to HD_HRA; core-substream XCH or @@ -10147,6 +10180,7 @@ static const MOVParseTableEntry mov_default_parse_table[] = { { MKTAG('e','s','d','s'), mov_read_esds }, { MKTAG('d','a','c','3'), mov_read_dac3 }, /* AC-3 info */ { MKTAG('d','e','c','3'), mov_read_dec3 }, /* EAC-3 info */ +{ MKTAG('d','a','c','4'), mov_read_dac4 }, /* AC-4 info */ { MKTAG('d','d','t','s'), mov_read_ddts }, /* DTS audio descriptor */ { MKTAG('w','i','d','e'), mov_read_wide }, /* place holder */ { MKTAG('w','f','e','x'), mov_read_wfex }, -- 2.52.0 >From a433ac4f40c1fa15dcf9b309c4fef667ba600fc7 Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Sun, 27 Sep 2026 20:22:00 +0900 Subject: [PATCH 07/15] avformat/movenc: add AC-4 support Write AC4SampleEntry and AC4SpecificBox as specified by ETSI TS 103 190-2, annex E. The DSI is taken from the extradata, or derived from the first sync sample. Sync samples are determined from the bitstream, the media time scale follows table E.1 of the specification, and the dby1 brand is signalled. --- libavformat/movenc.c | 126 +++++++++++++++++++++++++++++++++++++++++-- libavformat/movenc.h | 9 ++++ 2 files changed, 132 insertions(+), 3 deletions(-) diff --git a/libavformat/movenc.c b/libavformat/movenc.c index 6c2a009ac3..0134631199 100644 --- a/libavformat/movenc.c +++ b/libavformat/movenc.c @@ -41,6 +41,7 @@ #include "apv.h" #include "lcevc.h" #include "libavcodec/ac3_parser_internal.h" +#include "libavcodec/ac4_parse.h" #include "libavcodec/dnxhddata.h" #include "libavcodec/flac.h" #include "libavcodec/mpegaudiodata.h" @@ -642,6 +643,65 @@ end: return ret; } +static int mov_get_ac4_dsi(AVFormatContext *s, MOVTrack *track, + const uint8_t **dsi, int *dsi_size) +{ + int ret; + + if (track->extradata_size[track->last_stsd_index]) { + *dsi = track->extradata[track->last_stsd_index]; + *dsi_size = track->extradata_size[track->last_stsd_index]; + return 0; + } + + if (!track->ac4.frame) { + av_log(s, AV_LOG_ERROR, + "Cannot write moov atom before AC-4 packets parsed.\n"); + return AVERROR(EINVAL); + } + + av_freep(&track->ac4.dsi); + ret = ff_ac4_frame_to_dsi(s, track->ac4.frame, track->ac4.frame_size, + track->ac4.bit_rate_mode, 0, + &track->ac4.dsi, &track->ac4.dsi_size); + if (ret < 0) + return ret; + + *dsi = track->ac4.dsi; + *dsi_size = track->ac4.dsi_size; + return 0; +} + +static int mov_get_ac4_channels(AVFormatContext *s, MOVTrack *track) +{ + AC4DSIInfo info; + const uint8_t *dsi; + int size; + + if (mov_get_ac4_dsi(s, track, &dsi, &size) < 0 || + ff_ac4_parse_dsi(&info, dsi, size) < 0 || !info.nb_presentations_parsed || + !info.pres[0].channel_coded) + return 2; + + return ff_ac4_channel_mask_to_nb_channels(info.pres[0].channel_mask); +} + +static int mov_write_dac4_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) +{ + const uint8_t *dsi; + int size, ret; + + ret = mov_get_ac4_dsi(s, track, &dsi, &size); + if (ret < 0) + return ret; + + avio_wb32(pb, 8 + size); + ffio_wfourcc(pb, "dac4"); + avio_write(pb, dsi, size); + + return 0; +} + static int mov_write_eac3_tag(AVFormatContext *s, AVIOContext *pb, MOVTrack *track) { PutBitContext pbc; @@ -1516,8 +1576,11 @@ static int mov_write_audio_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex avio_wb16(pb, 16); avio_wb16(pb, track->audio_vbr ? -2 : 0); /* compression ID */ } else { /* reserved for mp4/3gp */ - avio_wb16(pb, track->tag == MKTAG('i', 'a', 'm', 'f') ? - 0 : track->par->ch_layout.nb_channels); + if (track->par->codec_id == AV_CODEC_ID_AC4) + avio_wb16(pb, mov_get_ac4_channels(s, track)); + else + avio_wb16(pb, track->tag == MKTAG('i', 'a', 'm', 'f') ? + 0 : track->par->ch_layout.nb_channels); if (track->par->codec_id == AV_CODEC_ID_FLAC || track->par->codec_id == AV_CODEC_ID_ALAC) { avio_wb16(pb, track->par->bits_per_raw_sample); @@ -1579,6 +1642,8 @@ static int mov_write_audio_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContex ret = mov_write_ac3_tag(s, pb, track); else if (track->par->codec_id == AV_CODEC_ID_EAC3) ret = mov_write_eac3_tag(s, pb, track); + else if (track->par->codec_id == AV_CODEC_ID_AC4) + ret = mov_write_dac4_tag(s, pb, track); else if (track->par->codec_id == AV_CODEC_ID_ALAC) ret = mov_write_extradata_tag(pb, track); else if (track->par->codec_id == AV_CODEC_ID_WMAPRO) @@ -3488,6 +3553,7 @@ static int mov_write_stbl_tag(AVFormatContext *s, AVIOContext *pb, MOVMuxContext mov_write_stts_tag(pb, track); if ((track->par->codec_type == AVMEDIA_TYPE_VIDEO || track->par->codec_id == AV_CODEC_ID_TRUEHD || + track->par->codec_id == AV_CODEC_ID_AC4 || track->par->codec_id == AV_CODEC_ID_MPEGH_3D_AUDIO || (track->par->codec_id == AV_CODEC_ID_AAC && track->par->profile == AV_PROFILE_AAC_USAC) || track->par->codec_tag == MKTAG('r','t','p',' ')) && @@ -6350,6 +6416,7 @@ static int mov_write_ftyp_tag(AVIOContext *pb, AVFormatContext *s) has_av1 = 1; if (st->codecpar->codec_id == AV_CODEC_ID_AC3 || st->codecpar->codec_id == AV_CODEC_ID_EAC3 || + st->codecpar->codec_id == AV_CODEC_ID_AC4 || st->codecpar->codec_id == AV_CODEC_ID_TRUEHD || av_packet_side_data_get(st->codecpar->coded_side_data, st->codecpar->nb_coded_side_data, @@ -6623,6 +6690,38 @@ static void mov_parse_vc1_frame(AVPacket *pkt, MOVTrack *trk) } } +static int mov_parse_ac4_frame(AVFormatContext *s, AVPacket *pkt, MOVTrack *trk) +{ + AC4FrameInfo info; + + if (ff_ac4_parse_frame_info(&info, pkt->data, pkt->size) < 0) { + av_log(s, AV_LOG_WARNING, "Invalid AC-4 frame\n"); + return 0; + } + + trk->ac4.bit_rate_mode = ff_ac4_update_bit_rate_mode(trk->ac4.bit_rate_mode, &info); + if (info.iframe) { + trk->cluster[trk->entry].flags |= MOV_SYNC_SAMPLE; + trk->has_keyframes++; + } else if (!trk->entry) { + av_log(s, AV_LOG_WARNING, "First AC-4 sample is not a sync sample\n"); + } + + /* Keep the first sync sample around to create the DSI from */ + if (!trk->ac4.frame || (info.iframe && !trk->ac4.frame_is_sync)) { + av_freep(&trk->ac4.frame); + trk->ac4.frame = av_malloc(pkt->size + AV_INPUT_BUFFER_PADDING_SIZE); + if (!trk->ac4.frame) + return AVERROR(ENOMEM); + memcpy(trk->ac4.frame, pkt->data, pkt->size); + memset(trk->ac4.frame + pkt->size, 0, AV_INPUT_BUFFER_PADDING_SIZE); + trk->ac4.frame_size = pkt->size; + trk->ac4.frame_is_sync = info.iframe; + } + + return 0; +} + static void mov_parse_truehd_frame(AVPacket *pkt, MOVTrack *trk) { int length; @@ -7483,6 +7582,10 @@ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt) mov_parse_vc1_frame(pkt, trk); } else if (par->codec_id == AV_CODEC_ID_TRUEHD) { mov_parse_truehd_frame(pkt, trk); + } else if (par->codec_id == AV_CODEC_ID_AC4) { + ret = mov_parse_ac4_frame(s, pkt, trk); + if (ret < 0) + goto err; } else if (pkt->flags & AV_PKT_FLAG_KEY) { if (mov->mode == MODE_MOV && par->codec_id == AV_CODEC_ID_MPEG2VIDEO && trk->entry > 0) { // force sync sample for the first key frame @@ -8096,6 +8199,8 @@ static void mov_free(AVFormatContext *s) av_packet_free(&info->pkt); av_freep(&track->eac3_priv); } + av_freep(&track->ac4.frame); + av_freep(&track->ac4.dsi); for (int j = 0; j < track->stsd_count; j++) av_freep(&track->extradata[j]); av_freep(&track->extradata); @@ -8681,7 +8786,21 @@ static int mov_init(AVFormatContext *s) } } else if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { track->timescale = st->codecpar->sample_rate; - if (!st->codecpar->frame_size && !av_get_bits_per_sample(st->codecpar->codec_id)) { + if (st->codecpar->codec_id == AV_CODEC_ID_AC4) { + AC4DSIInfo info; + /* ETSI TS 103 190-2, table E.1 */ + track->timescale = st->codecpar->sample_rate == 44100 ? 44100 : 48000; + if (st->codecpar->extradata_size && + ff_ac4_parse_dsi(&info, st->codecpar->extradata, + st->codecpar->extradata_size) >= 0) { + track->timescale = info.sample_rate; + if (info.fs_index && (info.frame_rate_index == 3 || + info.frame_rate_index == 8 || + info.frame_rate_index == 11)) + track->timescale = 240000; + } + track->audio_vbr = 1; + } else if (!st->codecpar->frame_size && !av_get_bits_per_sample(st->codecpar->codec_id)) { av_log(s, AV_LOG_WARNING, "track %d: codec frame size is not set\n", i); track->audio_vbr = 1; }else if (st->codecpar->codec_id == AV_CODEC_ID_ADPCM_MS || @@ -9422,6 +9541,7 @@ static const AVCodecTag codec_mp4_tags[] = { { AV_CODEC_ID_MP2, MKTAG('m', 'p', '4', 'a') }, { AV_CODEC_ID_AC3, MKTAG('a', 'c', '-', '3') }, { AV_CODEC_ID_EAC3, MKTAG('e', 'c', '-', '3') }, + { AV_CODEC_ID_AC4, MKTAG('a', 'c', '-', '4') }, { AV_CODEC_ID_DTS, MKTAG('m', 'p', '4', 'a') }, { AV_CODEC_ID_TRUEHD, MKTAG('m', 'l', 'p', 'a') }, { AV_CODEC_ID_FLAC, MKTAG('f', 'L', 'a', 'C') }, diff --git a/libavformat/movenc.h b/libavformat/movenc.h index c375659c93..a5dbc13a78 100644 --- a/libavformat/movenc.h +++ b/libavformat/movenc.h @@ -177,6 +177,15 @@ typedef struct MOVTrack { void *eac3_priv; + struct { + uint8_t *frame; ///< first AC-4 sync sample, to derive a DSI from + int frame_size; + int frame_is_sync; + int bit_rate_mode; + uint8_t *dsi; ///< DSI derived from frame + int dsi_size; + } ac4; + MOVMuxCencContext cenc; uint32_t palette[AVPALETTE_COUNT]; -- 2.52.0 >From 168ec9eb105423d0bedc6b75eecddfae5e432b91 Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Sun, 27 Sep 2026 20:22:00 +0900 Subject: [PATCH 08/15] avformat/mpegtsenc: add AC-4 support Mux AC-4 as sync frames with an AC-4_descriptor (ETSI EN 300 468), one frame per PES packet, as is done by Dolby's reference streams. --- libavformat/mpegtsenc.c | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/libavformat/mpegtsenc.c b/libavformat/mpegtsenc.c index c7733f65e3..58996c0ae4 100644 --- a/libavformat/mpegtsenc.c +++ b/libavformat/mpegtsenc.c @@ -421,6 +421,9 @@ static int get_dvb_stream_type(AVFormatContext *s, AVStream *st) ? STREAM_TYPE_PRIVATE_DATA : STREAM_TYPE_ATSC_AUDIO_EAC3; break; + case AV_CODEC_ID_AC4: + stream_type = STREAM_TYPE_PRIVATE_DATA; + break; case AV_CODEC_ID_DTS: stream_type = STREAM_TYPE_BLURAY_AUDIO_DTS; // should be STREAM_TYPE_PRIVATE_DATA (ETSI TS 101 154), needs a DTS_descriptor() (ETSI EN 300 468) break; @@ -610,6 +613,13 @@ static int mpegts_write_pmt(AVFormatContext *s, MpegTSService *service) *q++=0; // omit all fields... } } + if (codec_id == AV_CODEC_ID_AC4) { + /* AC-4_descriptor, ETSI EN 300 468 */ + *q++ = DVB_EXTENSION_DESCRIPTOR; + *q++ = 2; + *q++ = AC4_DESCRIPTOR; + *q++ = 0; /* ac4_config_flag, ac4_toc_flag */ + } if (codec_id == AV_CODEC_ID_S302M) put_registration_descriptor(&q, MKTAG('B', 'S', 'S', 'D')); if (codec_id == AV_CODEC_ID_OPUS) { @@ -1726,7 +1736,8 @@ static void mpegts_write_pes(AVFormatContext *s, AVStream *st, *q++ = len; val = 0x80; /* data alignment indicator is required for subtitle and data streams */ - if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE || st->codecpar->codec_type == AVMEDIA_TYPE_DATA) + if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE || st->codecpar->codec_type == AVMEDIA_TYPE_DATA || + st->codecpar->codec_id == AV_CODEC_ID_AC4) val |= 0x04; *q++ = val; *q++ = flags; @@ -2241,6 +2252,23 @@ static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt) ts_st->dvb_ac3_desc = dvb_ac3_desc; } av_free(hdr); + } else if (st->codecpar->codec_id == AV_CODEC_ID_AC4) { + /* AC-4 is carried as sync frames (ETSI TS 103 190-1, annex G) */ + int hdr_size = size >= 0xFFFF ? 7 : 4; + + data = av_malloc(size + hdr_size); + if (!data) + return AVERROR(ENOMEM); + AV_WB16(data, 0xAC40); + if (size >= 0xFFFF) { + AV_WB16(data + 2, 0xFFFF); + AV_WB24(data + 4, size); + } else { + AV_WB16(data + 2, size); + } + memcpy(data + hdr_size, buf, size); + buf = data; + size += hdr_size; } else if (st->codecpar->codec_id == AV_CODEC_ID_PCM_BLURAY && ts->m2ts_mode) { mpegts_write_pes(s, st, buf, size, pts, dts, pkt->flags & AV_PKT_FLAG_KEY, stream_id); @@ -2258,9 +2286,10 @@ static int mpegts_write_packet_internal(AVFormatContext *s, AVPacket *pkt) ts_st->opus_queued_samples = 0; } - if (st->codecpar->codec_type != AVMEDIA_TYPE_AUDIO || size > ts->pes_payload_size) { + if (st->codecpar->codec_type != AVMEDIA_TYPE_AUDIO || size > ts->pes_payload_size || + st->codecpar->codec_id == AV_CODEC_ID_AC4) { av_assert0(!ts_st->payload_size); - // for video and subtitle, write a single pes packet + // for video, subtitle and AC-4, write a single pes packet mpegts_write_pes(s, st, buf, size, pts, dts, pkt->flags & AV_PKT_FLAG_KEY, stream_id); ts_st->opus_queued_samples = 0; -- 2.52.0 >From 4dd41b392e136ea922e8d4c2a07b6844cf4bb15b Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Sun, 27 Sep 2026 20:22:00 +0900 Subject: [PATCH 09/15] avformat/codecstring: add AC-4 Create the codecs parameter from the DSI, as specified in ETSI TS 103 190-2, clause E.13, signalling the presentation with the lowest decoder compatibility level. --- libavformat/codecstring.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/libavformat/codecstring.c b/libavformat/codecstring.c index 6ecec13ed5..1adc2e2fac 100644 --- a/libavformat/codecstring.c +++ b/libavformat/codecstring.c @@ -24,6 +24,7 @@ #include "libavutil/intreadwrite.h" #include "libavutil/mem.h" #include "libavutil/rational.h" +#include "libavcodec/ac4_parse.h" #include "av1.h" #include "avc.h" @@ -224,6 +225,22 @@ int ff_make_codec_str(void *logctx, const AVCodecParameters *par, av_bprintf(out, "ac-3"); } else if (par->codec_id == AV_CODEC_ID_EAC3) { av_bprintf(out, "ec-3"); + } else if (par->codec_id == AV_CODEC_ID_AC4) { + // ETSI TS 103 190-2, clause E.13 + AC4DSIInfo info; + if (par->extradata_size && + ff_ac4_parse_dsi(&info, par->extradata, par->extradata_size) >= 0 && + info.nb_presentations_parsed) { + /* Signal the presentation with the lowest decoder compatibility level */ + const AC4DSIPresentation *p = &info.pres[0]; + for (i = 1; i < info.nb_presentations_parsed; i++) + if (info.pres[i].md_compat < p->md_compat) + p = &info.pres[i]; + av_bprintf(out, "ac-4.%02x.%02x.%02x", info.bitstream_version, + p->presentation_version, p->md_compat); + } else { + av_bprintf(out, "ac-4"); + } } else { return AVERROR(EINVAL); } -- 2.52.0 >From 79708687263d55282f2becbaf1d01b9341afc942 Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Sun, 27 Sep 2026 20:22:00 +0900 Subject: [PATCH 10/15] avformat/dashenc: drop codec tags incompatible with the segment format Codec tags from the input, e.g. MPEG-TS stream types, made the segment muxer fail, so remuxing E-AC-3 from MPEG-TS failed for instance. --- libavformat/dashenc.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c index 6b4ab8eeb9..337749577b 100644 --- a/libavformat/dashenc.c +++ b/libavformat/dashenc.c @@ -1436,6 +1436,12 @@ static int dash_init(AVFormatContext *s) if (!(st = avformat_new_stream(ctx, NULL))) return AVERROR(ENOMEM); avcodec_parameters_copy(st->codecpar, s->streams[i]->codecpar); + /* Drop codec tags from the input, e.g. MPEG-TS stream types, not + * valid for the segment format */ + if (ctx->oformat->codec_tag && + av_codec_get_id(ctx->oformat->codec_tag, st->codecpar->codec_tag) != st->codecpar->codec_id && + av_codec_get_tag(ctx->oformat->codec_tag, st->codecpar->codec_id) > 0) + st->codecpar->codec_tag = 0; st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio; st->time_base = s->streams[i]->time_base; st->avg_frame_rate = s->streams[i]->avg_frame_rate; -- 2.52.0 >From b338b9fe29992596d2afa4de2042ccee2b13b3ce Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Sun, 27 Sep 2026 20:22:00 +0900 Subject: [PATCH 11/15] avformat/dashenc: signal AC-4 channel configurations Signal the channel configuration of AC-4 streams with the MPEG channel configuration scheme where possible, and with Dolby's scheme, and signal immersive stereo content, as specified in Dolby's AC-4 online delivery specification. --- libavformat/dashenc.c | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c index 337749577b..9659141ec7 100644 --- a/libavformat/dashenc.c +++ b/libavformat/dashenc.c @@ -40,6 +40,7 @@ #include "libavutil/time.h" #include "libavutil/time_internal.h" +#include "libavcodec/ac4_parse.h" #include "libavcodec/avcodec.h" #include "avformat.h" @@ -663,6 +664,31 @@ static void format_date(char *buf, int size, int64_t time_us) } } +static int write_ac4_audio_properties(AVIOContext *out, const AVCodecParameters *par) +{ + const AC4DSIPresentation *p; + AC4DSIInfo info; + uint32_t mask; + int cicp; + + if (!par->extradata_size || + ff_ac4_parse_dsi(&info, par->extradata, par->extradata_size) < 0 || + !info.nb_presentations_parsed) + return AVERROR(EINVAL); + + /* Signalling is based on the first presentation */ + p = &info.pres[0]; + mask = p->channel_coded ? p->channel_mask : AC4_CHANNEL_MASK_OBJECTS; + cicp = ff_ac4_channel_mask_to_cicp(mask); + if (cicp) + avio_printf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:mpegB:cicp:ChannelConfiguration\" value=\"%d\" />\n", cicp); + avio_printf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"tag:dolby.com,2015:dash:audio_channel_configuration:2015\" value=\"%X\" />\n", mask); + if (p->pre_virtualized) + avio_printf(out, "\t\t\t\t<SupplementalProperty schemeIdUri=\"tag:dolby.com,2016:dash:virtualized_content:2016\" value=\"1\" />\n"); + + return 0; +} + static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_index, int final) { @@ -734,8 +760,10 @@ static int write_adaptation_set(AVFormatContext *s, AVIOContext *out, int as_ind } else { avio_printf(out, "\t\t\t<Representation id=\"%d\" mimeType=\"audio/%s\" codecs=\"%s\"%s audioSamplingRate=\"%d\">\n", i, os->format_name, os->codec_str, bandwidth_str, s->streams[i]->codecpar->sample_rate); - avio_printf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n", - s->streams[i]->codecpar->ch_layout.nb_channels); + if (st->codecpar->codec_id != AV_CODEC_ID_AC4 || + write_ac4_audio_properties(out, os->ctx->streams[0]->codecpar) < 0) + avio_printf(out, "\t\t\t\t<AudioChannelConfiguration schemeIdUri=\"urn:mpeg:dash:23003:3:audio_channel_configuration:2011\" value=\"%d\" />\n", + s->streams[i]->codecpar->ch_layout.nb_channels); } if (!final && c->write_prft && os->producer_reference_time_str[0]) { avio_printf(out, "\t\t\t\t<ProducerReferenceTime id=\"%d\" inband=\"true\" type=\"%s\" wallClockTime=\"%s\" presentationTime=\"%"PRId64"\">\n", -- 2.52.0 >From 970a1c5f4f8bef2db0aa3420d41bf50c7aeb12bc Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Sun, 27 Sep 2026 20:22:00 +0900 Subject: [PATCH 12/15] avformat/hlsplaylist: signal AC-4 channel configurations Pass the codec parameters to ff_hls_write_audio_rendition(), and write the CHANNELS attribute of AC-4 streams as specified in Dolby's AC-4 online delivery specification, which signals immersive and object based content. --- libavformat/dashenc.c | 2 +- libavformat/hlsenc.c | 16 +++++++++------- libavformat/hlsplaylist.c | 39 ++++++++++++++++++++++++++++++++++++--- libavformat/hlsplaylist.h | 3 ++- 4 files changed, 48 insertions(+), 12 deletions(-) diff --git a/libavformat/dashenc.c b/libavformat/dashenc.c index 9659141ec7..4513551c45 100644 --- a/libavformat/dashenc.c +++ b/libavformat/dashenc.c @@ -1197,7 +1197,7 @@ static int write_manifest(AVFormatContext *s, int final) get_hls_playlist_name(playlist_file, sizeof(playlist_file), NULL, i); ff_hls_write_audio_rendition(c->m3u8_out, audio_group, playlist_file, NULL, i, is_default, - s->streams[i]->codecpar->ch_layout.nb_channels); + os->ctx->streams[0]->codecpar); max_audio_bitrate = FFMAX(st->codecpar->bit_rate + os->muxer_overhead, max_audio_bitrate); if (!av_strnstr(audio_codec_str, os->codec_str, sizeof(audio_codec_str))) { diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c index e35af34d23..e08ba5be61 100644 --- a/libavformat/hlsenc.c +++ b/libavformat/hlsenc.c @@ -1389,7 +1389,7 @@ static int create_master_playlist(AVFormatContext *s, int is_file_proto = proto && !strcmp(proto, "file"); int use_temp_file = is_file_proto && ((hls->flags & HLS_TEMP_FILE) || hls->master_publish_rate); char temp_filename[MAX_URL_SIZE]; - int nb_channels; + const AVCodecParameters *apar; input_vs->m3u8_created = 1; if (!hls->master_m3u8_created) { @@ -1438,13 +1438,15 @@ static int create_master_playlist(AVFormatContext *s, av_log(s, AV_LOG_ERROR, "Unable to find relative URL\n"); goto fail; } - nb_channels = 0; - for (j = 0; j < vs->nb_streams; j++) - if (vs->streams[j]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) - if (vs->streams[j]->codecpar->ch_layout.nb_channels > nb_channels) - nb_channels = vs->streams[j]->codecpar->ch_layout.nb_channels; + apar = NULL; + for (j = 0; j < vs->nb_streams; j++) { + const AVCodecParameters *par = vs->streams[j]->codecpar; + if (par->codec_type == AVMEDIA_TYPE_AUDIO && + (!apar || par->ch_layout.nb_channels > apar->ch_layout.nb_channels)) + apar = par; + } - ff_hls_write_audio_rendition(hls->m3u8_out, vs->agroup, m3u8_rel_name, vs->language, i, hls->has_default_key ? vs->is_default : 1, nb_channels); + ff_hls_write_audio_rendition(hls->m3u8_out, vs->agroup, m3u8_rel_name, vs->language, i, hls->has_default_key ? vs->is_default : 1, apar); } /* For variant streams with video add #EXT-X-STREAM-INF tag with attributes*/ diff --git a/libavformat/hlsplaylist.c b/libavformat/hlsplaylist.c index 774bc5ac84..cc8937f1f4 100644 --- a/libavformat/hlsplaylist.c +++ b/libavformat/hlsplaylist.c @@ -25,6 +25,7 @@ #include <time.h> #include "libavutil/time_internal.h" +#include "libavcodec/ac4_parse.h" #include "avformat.h" #include "hlsplaylist.h" @@ -37,9 +38,40 @@ void ff_hls_write_playlist_version(AVIOContext *out, int version) avio_printf(out, "#EXT-X-VERSION:%d\n", version); } +/* Dolby AC-4 online delivery kit, CHANNELS attribute */ +static int write_ac4_channels(AVIOContext *out, const AVCodecParameters *par) +{ + const AC4DSIPresentation *p; + AC4DSIInfo info; + int nb_channels; + + if (!par->extradata_size || + ff_ac4_parse_dsi(&info, par->extradata, par->extradata_size) < 0 || + !info.nb_presentations_parsed) + return AVERROR(EINVAL); + + p = &info.pres[0]; + if (!p->channel_coded) { + nb_channels = p->n_objects ? p->n_objects : par->ch_layout.nb_channels; + if (!nb_channels) + return AVERROR(EINVAL); + avio_printf(out, "CHANNELS=\"%d/JOC\",", nb_channels); + } else if (p->pre_virtualized) { + avio_printf(out, "CHANNELS=\"2/%s\",", p->immersive_audio_indicator ? "IMSA" : "IMS"); + } else { + nb_channels = ff_ac4_channel_mask_to_nb_channels(p->channel_mask); + avio_printf(out, "CHANNELS=\"%d%s\",", nb_channels, + p->immersive_audio_indicator || + (p->channel_mask & AC4_CHANNEL_MASK_HEIGHTS) ? "/IMSA" : ""); + } + + return 0; +} + void ff_hls_write_audio_rendition(AVIOContext *out, const char *agroup, const char *filename, const char *language, - int name_id, int is_default, int nb_channels) + int name_id, int is_default, + const AVCodecParameters *par) { if (!out || !agroup || !filename) return; @@ -49,8 +81,9 @@ void ff_hls_write_audio_rendition(AVIOContext *out, const char *agroup, if (language) { avio_printf(out, "LANGUAGE=\"%s\",", language); } - if (nb_channels) { - avio_printf(out, "CHANNELS=\"%d\",", nb_channels); + if (par && (par->codec_id != AV_CODEC_ID_AC4 || write_ac4_channels(out, par) < 0) && + par->ch_layout.nb_channels) { + avio_printf(out, "CHANNELS=\"%d\",", par->ch_layout.nb_channels); } avio_printf(out, "URI=\"%s\"\n", filename); } diff --git a/libavformat/hlsplaylist.h b/libavformat/hlsplaylist.h index a9f8bab263..5d513d5b9c 100644 --- a/libavformat/hlsplaylist.h +++ b/libavformat/hlsplaylist.h @@ -38,7 +38,8 @@ typedef enum { void ff_hls_write_playlist_version(AVIOContext *out, int version); void ff_hls_write_audio_rendition(AVIOContext *out, const char *agroup, const char *filename, const char *language, - int name_id, int is_default, int nb_channels); + int name_id, int is_default, + const AVCodecParameters *par); void ff_hls_write_subtitle_rendition(AVIOContext *out, const char *sgroup, const char *filename, const char *language, const char *sname, int name_id, int is_default); -- 2.52.0 >From 471a204a31134073355084d6d6476efa959367ae Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Sun, 27 Sep 2026 20:22:00 +0900 Subject: [PATCH 13/15] avformat/hlsenc: start AC-4 segments with I-frames Audio packets are usually all keyframes, but AC-4 segments are required to start with an I-frame. --- Changelog | 1 + libavformat/hlsenc.c | 3 +++ 2 files changed, 4 insertions(+) diff --git a/Changelog b/Changelog index 56ecd0b5d6..66b0f8556f 100644 --- a/Changelog +++ b/Changelog @@ -15,6 +15,7 @@ version <next>: - H.264 data partitioning support - DSD (dsd_msbf) encoder - Rhetorex ADPCM decoder +- AC-4 parser, and AC-4 support in the MP4, MPEG-TS, DASH and HLS muxers version 9.0: diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c index e08ba5be61..92ed768e99 100644 --- a/libavformat/hlsenc.c +++ b/libavformat/hlsenc.c @@ -2502,6 +2502,9 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) ((pkt->flags & AV_PKT_FLAG_KEY) || (hls->flags & HLS_SPLIT_BY_TIME)); is_ref_pkt = (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) && (pkt->stream_index == vs->reference_stream_index); } + /* AC-4 segments have to start with an I-frame */ + if (!vs->has_video && st->codecpar->codec_id == AV_CODEC_ID_AC4) + can_split = !!(pkt->flags & AV_PKT_FLAG_KEY); if (st->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) is_ref_pkt = can_split = 0; if (pkt->pts == AV_NOPTS_VALUE) -- 2.52.0 >From a2f07622c50ef987cd10fbf7b48c244198671c0b Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Sun, 27 Sep 2026 20:22:00 +0900 Subject: [PATCH 14/15] avformat/hls: allow AC-4 segments --- libavformat/hls.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libavformat/hls.c b/libavformat/hls.c index 38d2a99fb2..b8390ed00c 100644 --- a/libavformat/hls.c +++ b/libavformat/hls.c @@ -3143,7 +3143,7 @@ static const AVOption hls_options[] = { OFFSET(prefer_x_start), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS}, {"allowed_extensions", "List of file extensions that hls is allowed to access", OFFSET(allowed_extensions), AV_OPT_TYPE_STRING, - {.str = "3gp,aac,avi,ac3,eac3,flac,mkv,m3u8,m4a,m4s,m4v,mpg,mov,mp2,mp3,mp4,mpeg,mpegts,ogg,ogv,oga,ts,vob,vtt,wav,webvtt" + {.str = "3gp,aac,avi,ac3,ac4,eac3,flac,mkv,m3u8,m4a,m4s,m4v,mpg,mov,mp2,mp3,mp4,mpeg,mpegts,ogg,ogv,oga,ts,vob,vtt,wav,webvtt" ",cmfv,cmfa" // Ticket11526 www.nicovideo.jp ",ec3" // part of Ticket11435 (Elisa Viihde (Finnish online recording service)) ",fmp4" // https://github.com/yt-dlp/yt-dlp/issues/12700 @@ -3151,7 +3151,7 @@ static const AVOption hls_options[] = { INT_MIN, INT_MAX, FLAGS}, {"allowed_segment_extensions", "List of file extensions that hls is allowed to access", OFFSET(allowed_segment_extensions), AV_OPT_TYPE_STRING, - {.str = "3gp,aac,avi,ac3,eac3,flac,mkv,m3u8,m4a,m4s,m4v,mpg,mov,mp2,mp3,mp4,mpeg,mpegts,ogg,ogv,oga,ts,vob,vtt,wav,webvtt" + {.str = "3gp,aac,avi,ac3,ac4,eac3,flac,mkv,m3u8,m4a,m4s,m4v,mpg,mov,mp2,mp3,mp4,mpeg,mpegts,ogg,ogv,oga,ts,vob,vtt,wav,webvtt" ",cmfv,cmfa" // Ticket11526 www.nicovideo.jp ",ec3" // part of Ticket11435 (Elisa Viihde (Finnish online recording service)) ",fmp4" // https://github.com/yt-dlp/yt-dlp/issues/12700 -- 2.52.0 >From 71f4c32a9eeccdc8d5015824c8a2e3bac9f0f67c Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Sun, 27 Sep 2026 20:22:00 +0900 Subject: [PATCH 15/15] avformat/hlsenc: add packed audio segments Add the packed_audio segment type, which writes audio elementary streams starting with an ID3 tag carrying the timestamp of the first sample, as specified by HTTP Live Streaming, for AAC, AC-3, E-AC-3, MP3 and AC-4. --- Changelog | 1 + doc/muxers.texi | 5 +++ libavformat/hlsenc.c | 95 ++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 97 insertions(+), 4 deletions(-) diff --git a/Changelog b/Changelog index 66b0f8556f..649b17ec72 100644 --- a/Changelog +++ b/Changelog @@ -16,6 +16,7 @@ version <next>: - DSD (dsd_msbf) encoder - Rhetorex ADPCM decoder - AC-4 parser, and AC-4 support in the MP4, MPEG-TS, DASH and HLS muxers +- HLS packed audio segments version 9.0: diff --git a/doc/muxers.texi b/doc/muxers.texi index af599893b2..0374cab70a 100644 --- a/doc/muxers.texi +++ b/doc/muxers.texi @@ -2205,6 +2205,11 @@ compatible with all HLS versions. @item fmp4 Output segment files in fragmented MP4 format, similar to MPEG-DASH. fmp4 files may be used in HLS version 7 and above. + +@item packed_audio +Output segment files as audio elementary streams, starting with an ID3 tag +signalling the timestamp of the first sample. Each variant stream must consist +of a single AAC, AC-3, E-AC-3, MP3 or AC-4 audio stream. @end table @item hls_fmp4_init_filename @var{filename} diff --git a/libavformat/hlsenc.c b/libavformat/hlsenc.c index 92ed768e99..8deb4ea68e 100644 --- a/libavformat/hlsenc.c +++ b/libavformat/hlsenc.c @@ -118,8 +118,21 @@ typedef enum HLSFlags { typedef enum { SEGMENT_TYPE_MPEGTS, SEGMENT_TYPE_FMP4, + SEGMENT_TYPE_PACKED_AUDIO, } SegmentType; +/* Muxers for packed audio segments */ +static const struct { + enum AVCodecID codec_id; + const char *muxer; +} packed_audio_muxers[] = { + { AV_CODEC_ID_AAC, "adts" }, + { AV_CODEC_ID_AC3, "ac3" }, + { AV_CODEC_ID_EAC3, "eac3" }, + { AV_CODEC_ID_MP3, "mp3" }, + { AV_CODEC_ID_AC4, "ac4" }, +}; + typedef struct VariantStream { unsigned var_stream_idx; unsigned number; @@ -183,6 +196,7 @@ typedef struct VariantStream { AVStream **streams; char codec_attr[128]; CodecAttributeStatus attr_status; + int write_id3_timestamp; /* packed audio segment start pending */ unsigned int nb_streams; int m3u8_created; /* status of media play-list creation */ int is_default; /* default status of audio group */ @@ -884,7 +898,14 @@ static int hls_mux_init(AVFormatContext *s, VariantStream *vs) if (hls->segment_type == SEGMENT_TYPE_FMP4) { av_dict_set(&options, "fflags", "-autobsf", 0); av_dict_set(&options, "movflags", "+frag_custom+dash+delay_moov", AV_DICT_APPEND); - } else { + } else if (hls->segment_type == SEGMENT_TYPE_PACKED_AUDIO) { + /* Segments must start with the timestamp ID3 tag */ + if (!strcmp(oc->oformat->name, "mp3")) { + av_dict_set(&options, "id3v2_version", "0", AV_DICT_DONT_OVERWRITE); + av_dict_set(&options, "write_xing", "0", AV_DICT_DONT_OVERWRITE); + } + vs->write_id3_timestamp = 1; + } else if (hls->segment_type == SEGMENT_TYPE_MPEGTS) { /* We only require one PAT/PMT per segment. */ char period[21]; snprintf(period, sizeof(period), "%d", (INT_MAX / 2) - 1); @@ -1824,7 +1845,8 @@ static int hls_start(AVFormatContext *s, VariantStream *vs) } } if (c->segment_type != SEGMENT_TYPE_FMP4) { - if (oc->oformat->priv_class && oc->priv_data) { + if (c->segment_type == SEGMENT_TYPE_MPEGTS && + oc->oformat->priv_class && oc->priv_data) { av_opt_set(oc->priv_data, "mpegts_flags", "resend_headers", 0); } if (c->flags & HLS_SINGLE_FILE) { @@ -2431,6 +2453,29 @@ static int64_t append_single_file(AVFormatContext *s, VariantStream *vs) return ret; } + +/** + * Write the ID3 tag signalling the timestamp of the first sample in a packed + * audio segment, as specified by HTTP Live Streaming. + */ +static void write_id3_timestamp(AVIOContext *pb, int64_t pts) +{ + static const char owner[] = "com.apple.streaming.transportStreamTimestamp"; + + /* ID3v2.4 header; the sizes are small enough to not need syncsafe coding */ + avio_write(pb, "ID3", 3); + avio_wb16(pb, 0x0400); + avio_w8(pb, 0); + avio_wb32(pb, 10 + sizeof(owner) + 8); + + /* PRIV frame, containing the 33 bit MPEG-2 TS timestamp */ + ffio_wfourcc(pb, "PRIV"); + avio_wb32(pb, sizeof(owner) + 8); + avio_wb16(pb, 0); + avio_write(pb, owner, sizeof(owner)); + avio_wb64(pb, pts & ((1ULL << 33) - 1)); +} + static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) { HLSContext *hls = s->priv_data; @@ -2706,6 +2751,8 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) } vs->number++; av_freep(&old_filename); + if (hls->segment_type == SEGMENT_TYPE_PACKED_AUDIO) + vs->write_id3_timestamp = 1; if (ret < 0) { return ret; @@ -2714,6 +2761,11 @@ static int hls_write_packet(AVFormatContext *s, AVPacket *pkt) vs->packets_written++; if (oc->pb) { + if (vs->write_id3_timestamp && pkt->pts != AV_NOPTS_VALUE) { + write_id3_timestamp(oc->pb, av_rescale_q(pkt->pts, st->time_base, + (AVRational){ 1, 90000 })); + vs->write_id3_timestamp = 0; + } ret = ff_write_chained(oc, stream_index, pkt, s, 0); if (hls->ignore_io_errors) ret = 0; @@ -2899,6 +2951,28 @@ failed: } +static int packed_audio_init_format(AVFormatContext *s, VariantStream *vs) +{ + const AVCodecParameters *par = vs->streams[0]->codecpar; + + if (vs->nb_streams != 1 || par->codec_type != AVMEDIA_TYPE_AUDIO) { + av_log(s, AV_LOG_ERROR, "Packed audio segments require a single audio " + "stream per variant stream\n"); + return AVERROR(EINVAL); + } + + for (int i = 0; i < FF_ARRAY_ELEMS(packed_audio_muxers); i++) { + if (packed_audio_muxers[i].codec_id != par->codec_id) + continue; + vs->oformat = av_guess_format(packed_audio_muxers[i].muxer, NULL, NULL); + return vs->oformat ? 0 : AVERROR_MUXER_NOT_FOUND; + } + + av_log(s, AV_LOG_ERROR, "Codec %s is not supported in packed audio segments\n", + avcodec_get_name(par->codec_id)); + return AVERROR(EINVAL); +} + static int hls_init(AVFormatContext *s) { int ret = 0; @@ -3026,6 +3100,10 @@ static int hls_init(AVFormatContext *s) #else return AVERROR_MUXER_NOT_FOUND; #endif + } else if (hls->segment_type == SEGMENT_TYPE_PACKED_AUDIO) { + ret = packed_audio_init_format(s, vs); + if (ret < 0) + return ret; } else { EXTERN const FFOutputFormat ff_mpegts_muxer; vs->oformat = &ff_mpegts_muxer.p; @@ -3040,7 +3118,15 @@ static int hls_init(AVFormatContext *s) if (p) *p = '\0'; - vs->basename = av_asprintf("%s%s", vs->m3u8_name, pattern); + if (hls->segment_type == SEGMENT_TYPE_PACKED_AUDIO) { + /* Use the extension of the elementary stream format */ + const char *ext = vs->oformat->extensions; + int prefix_len = strrchr(pattern, '.') + 1 - pattern; + vs->basename = av_asprintf("%s%.*s%.*s", vs->m3u8_name, prefix_len, pattern, + (int)strcspn(ext, ","), ext); + } else { + vs->basename = av_asprintf("%s%s", vs->m3u8_name, pattern); + } if (!vs->basename) return AVERROR(ENOMEM); @@ -3172,9 +3258,10 @@ static const AVOption options[] = { {"hls_enc_key_url", "url to access the key to decrypt the segments", OFFSET(key_url), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, {"hls_enc_iv", "hex-coded 16 byte initialization vector", OFFSET(iv), AV_OPT_TYPE_STRING, .flags = E}, {"hls_subtitle_path", "set path of hls subtitles", OFFSET(subtitle_filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E}, - {"hls_segment_type", "set hls segment files type", OFFSET(segment_type), AV_OPT_TYPE_INT, {.i64 = SEGMENT_TYPE_MPEGTS }, 0, SEGMENT_TYPE_FMP4, E, .unit = "segment_type"}, + {"hls_segment_type", "set hls segment files type", OFFSET(segment_type), AV_OPT_TYPE_INT, {.i64 = SEGMENT_TYPE_MPEGTS }, 0, SEGMENT_TYPE_PACKED_AUDIO, E, .unit = "segment_type"}, {"mpegts", "make segment file to mpegts files in m3u8", 0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_TYPE_MPEGTS }, 0, UINT_MAX, E, .unit = "segment_type"}, {"fmp4", "make segment file to fragment mp4 files in m3u8", 0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_TYPE_FMP4 }, 0, UINT_MAX, E, .unit = "segment_type"}, + {"packed_audio", "make segment file to packed audio (timestamped elementary stream) files in m3u8", 0, AV_OPT_TYPE_CONST, {.i64 = SEGMENT_TYPE_PACKED_AUDIO }, 0, UINT_MAX, E, .unit = "segment_type"}, {"hls_fmp4_init_filename", "set fragment mp4 file init filename", OFFSET(fmp4_init_filename), AV_OPT_TYPE_STRING, {.str = "init.mp4"}, 0, 0, E}, {"hls_fmp4_init_resend", "resend fragment mp4 init file after refresh m3u8 every time", OFFSET(resend_init_file), AV_OPT_TYPE_BOOL, {.i64 = 0 }, 0, 1, E }, {"hls_flags", "set flags affecting HLS playlist and media file generation", OFFSET(flags), AV_OPT_TYPE_FLAGS, {.i64 = 0 }, 0, UINT_MAX, E, .unit = "flags"}, -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
