[vlc-commits] [Git][videolan/vlc][master] 3 commits: mux: mp4: stop truncating high samplerates in the sample entry
Steve Lhomme (@robUx4)
gitlab at videolan.org
Fri Aug 21 17:49:41 UTC 2026
Steve Lhomme pushed to branch master at VideoLAN / VLC
Commits:
f0b9aabd by Alaric Senat at 2026-08-21T17:21:45+00:00
mux: mp4: stop truncating high samplerates in the sample entry
The AudioSampleEntry samplerate field is a 16.16 fixed point number, so
rates of 65536 Hz and above do not fit its integer part. Those are meant
to be expressed by an srat box, but that one requires a version 1 entry
and a non QuickTime brand.
Halve the rate until it fits instead, which keeps an exact power of two
relation to the real one. This is what the FLAC in ISOBMFF spec mandates
(isoflac.txt, ch. 3.3.1) and what libavformat's movenc does for every
codec.
This does not make the field usable where it is the only rate available.
It only replaces a meaningless value with a predictable one.
- - - - -
3e15ec83 by Alaric Senat at 2026-08-21T17:21:45+00:00
mux: mp4: add FLAC support
Write the 'fLaC'/'dfLa' sample entry so FLAC can be carried in
(fragmented) MP4. dfLa is a FullBox wrapping the FLAC metadata blocks,
of which only STREAMINFO is required, the specs are public and well
defined [^1].
Adding flac support to the MP4 muxer will allow the HLS stream output to
mux flac directly, which is a requirement for the chromecast refactors.
[^1]: <https://github.com/xiph/flac/blob/master/doc/isoflac.txt>
Ref: #27391
- - - - -
61ea4da0 by Alaric Senat at 2026-08-21T17:21:45+00:00
mux: mp4: add Opus support
Write the 'Opus'/'dOps' sample entry so Opus can be carried in
(fragmented) MP4. dOps almost holds the same fields as the Ogg OpusHead
identification header, the specs are public and well defined[^1].
[^1]: <https://opus-codec.org/docs/opus_in_isobmff.html>
Ref: #27391
- - - - -
2 changed files:
- modules/mux/mp4/libmp4mux.c
- modules/mux/mp4/mp4.c
Changes:
=====================================
modules/mux/mp4/libmp4mux.c
=====================================
@@ -748,6 +748,103 @@ static bo_t *GetDamrTag(es_format_t *p_fmt)
return damr;
}
+/* OpusHead identification header, mapping table excluded:
+ * magic(8) version(1) channels(1) preskip(2le) rate(4le) gain(2le) family(1) */
+#define OPUS_HEAD_SIZE 19
+#define XIPH_LACING_SIZE 2
+
+/* Opus in ISOBMFF (Opus-in-MP4 spec, ch. 4.3.2): the 'dOps' box carries the
+ * same fields as the Ogg "OpusHead" identification header, minus the magic
+ * number. */
+static bo_t *GetDOpsTag(const uint8_t *p_extra, size_t i_extra)
+{
+ if(i_extra > XIPH_LACING_SIZE + OPUS_HEAD_SIZE &&
+ !memcmp(&p_extra[XIPH_LACING_SIZE], "OpusHead", 8))
+ {
+ const size_t i_header_size = p_extra[1];
+ if(i_header_size > i_extra - XIPH_LACING_SIZE)
+ return NULL;
+ p_extra += XIPH_LACING_SIZE;
+ i_extra = i_header_size;
+ }
+ else if(i_extra < OPUS_HEAD_SIZE || memcmp(p_extra, "OpusHead", 8))
+ return NULL;
+
+ const uint8_t i_channels = p_extra[9];
+ const uint8_t i_mapping_family = p_extra[18];
+ if(i_channels == 0)
+ return NULL;
+ size_t i_maptable_size = 0;
+ /* Family 0 carries no mapping table. */
+ if(i_mapping_family != 0)
+ {
+ /* Family 1, 2 and 255 carry the stream counts followed by a table of
+ * i_channels entries. Family 3 replaces the table with a demixing
+ * matrix which the spec doesn't support. */
+ if(i_mapping_family > 2 && i_mapping_family != 255)
+ return NULL;
+ i_maptable_size = 2 + i_channels; /* streams(1) coupled(1) map(channels) */
+ if(i_extra < OPUS_HEAD_SIZE + i_maptable_size)
+ return NULL;
+ }
+
+ bo_t *dops = box_new("dOps");
+ if(!dops)
+ return NULL;
+
+ bo_add_8(dops, 0); /* Version 0 */
+ bo_add_8(dops, i_channels);
+ bo_add_16be(dops, GetWLE(&p_extra[10])); /* PreSkip */
+ bo_add_32be(dops, GetDWLE(&p_extra[12])); /* InputSampleRate */
+ bo_add_16be(dops, GetWLE(&p_extra[16])); /* OutputGain */
+ bo_add_8(dops, i_mapping_family);
+ if(i_maptable_size)
+ bo_add_mem(dops, i_maptable_size, &p_extra[OPUS_HEAD_SIZE]);
+
+ return dops;
+}
+
+#define FLAC_MAGIC_SIZE 4
+#define FLAC_BLOCK_HEADER_SIZE 4
+#define FLAC_STREAMINFO_SIZE 34
+
+static const uint8_t *FlacStreamInfo(const uint8_t *p_extra, size_t i_extra)
+{
+ if(i_extra >= FLAC_MAGIC_SIZE && !memcmp(p_extra, "fLaC", FLAC_MAGIC_SIZE))
+ {
+ p_extra += FLAC_MAGIC_SIZE;
+ i_extra -= FLAC_MAGIC_SIZE;
+ /* The first block has to be a complete STREAMINFO. */
+ if(i_extra < FLAC_BLOCK_HEADER_SIZE + FLAC_STREAMINFO_SIZE ||
+ (p_extra[0] & 0x7F) != 0 || /* BLOCK_TYPE 0 */
+ (GetDWBE(p_extra) & 0x00FFFFFF) != FLAC_STREAMINFO_SIZE)
+ return NULL;
+ return &p_extra[FLAC_BLOCK_HEADER_SIZE];
+ }
+
+ return (i_extra >= FLAC_STREAMINFO_SIZE) ? p_extra : NULL;
+}
+
+/* FLAC in ISOBMFF (isoflac.txt, ch. 3.3.2): the 'dfLa' box holds the FLAC
+ * metadata blocks, of which only STREAMINFO is required. */
+static bo_t *GetDfLaTag(const uint8_t *p_extra, size_t i_extra)
+{
+ const uint8_t *p_streaminfo = FlacStreamInfo(p_extra, i_extra);
+ if(!p_streaminfo)
+ return NULL;
+
+ bo_t *dfla = box_new("dfLa");
+ if(!dfla)
+ return NULL;
+
+ bo_add_32be(dfla, 0); /* FullBox: version 0, flags 0 */
+ bo_add_8(dfla, 0x80); /* last-metadata-block=1, BLOCK_TYPE=0 */
+ bo_add_24be(dfla, FLAC_STREAMINFO_SIZE);
+ bo_add_mem(dfla, FLAC_STREAMINFO_SIZE, p_streaminfo);
+
+ return dfla;
+}
+
static bo_t *GetD263Tag(void)
{
bo_t *d263 = box_new("d263");
@@ -1168,6 +1265,8 @@ static bo_t *GetSounBox(vlc_object_t *p_obj, mp4mux_trackinfo_t *p_track, bool b
uint16_t i_qt_version = 1;
uint16_t i_compression_id = 0xFFFE;
uint32_t i_uncompressed_bps = 0;
+ uint32_t i_rate = afmt->i_rate;
+ uint16_t i_samplesize = 16;
bo_t *srat = NULL;
/* codec specific extradata */
@@ -1226,6 +1325,22 @@ static bo_t *GetSounBox(vlc_object_t *p_obj, mp4mux_trackinfo_t *p_track, bool b
specificbox = GetWaveTag("mp4a", extraboxes, 2);
} else b_descr = true;
break;
+ case VLC_CODEC_OPUS:
+ memcpy(fcc, "Opus", 4);
+ specificbox = GetDOpsTag(p_extradata, i_extradata);
+ i_qt_version = 0;
+ i_rate = 48000;
+ break;
+ case VLC_CODEC_FLAC:
+ {
+ memcpy(fcc, "fLaC", 4);
+ specificbox = GetDfLaTag(p_extradata, i_extradata);
+ i_qt_version = 0;
+ const uint8_t *p_si = FlacStreamInfo(p_extradata, i_extradata);
+ if(p_si) /* bits per sample: 5 bits at offset 100 of STREAMINFO */
+ i_samplesize = (((p_si[12] & 0x01) << 4) | (p_si[13] >> 4)) + 1;
+ break;
+ }
case VLC_CODEC_MPGA:
case VLC_CODEC_MP2:
case VLC_CODEC_MP3:
@@ -1319,7 +1434,7 @@ static bo_t *GetSounBox(vlc_object_t *p_obj, mp4mux_trackinfo_t *p_track, bool b
if(i_qt_version == 0 && i_uncompressed_bps == 8)
bo_add_16be(soun, 8);
else
- bo_add_16be(soun, 16);
+ bo_add_16be(soun, i_samplesize);
// compression id
if(!b_mov)
bo_add_16be(soun, 0);
@@ -1327,15 +1442,19 @@ static bo_t *GetSounBox(vlc_object_t *p_obj, mp4mux_trackinfo_t *p_track, bool b
bo_add_16be(soun, i_compression_id);
bo_add_16be(soun, 0); // packet size (0)
- if(!b_mov && i_qt_version > 0 &&
- p_track->fmt.audio.i_rate >= (1<<16))
+ if(!b_mov && i_qt_version > 0 && i_rate >= (1<<16))
{
bo_add_32be(soun, 1<<16);
- srat = GetSratBox(p_track->fmt.audio.i_rate);
+ srat = GetSratBox(i_rate);
}
else
{
- bo_add_16be(soun, p_track->fmt.audio.i_rate); // sampleratehi
+ /* Halve the rate until it fits in the 16 bits constraint of the default sound box,
+ * it's a well known workaround implemented by ffmpeg and mandated by the flac spec.
+ * Decoders are expected to take the real rate from the codec configuration. */
+ while(i_rate >= (1<<16))
+ i_rate /= 2;
+ bo_add_16be(soun, i_rate); // sampleratehi
bo_add_16be(soun, 0); // sampleratelo
}
@@ -2389,6 +2508,29 @@ bool mp4mux_CanMux(vlc_object_t *p_obj, const es_format_t *p_fmt,
case VLC_CODEC_WMAP:
case VLC_CODEC_AV1:
break;
+ case VLC_CODEC_OPUS:
+ case VLC_CODEC_FLAC:
+ {
+ if(i_brand == BRAND_qt__)
+ {
+ if(p_obj)
+ msg_Err(p_obj, "%4.4s has no QuickTime mapping",
+ (const char *)&p_fmt->i_codec);
+ return false;
+ }
+ bo_t *config = (p_fmt->i_codec == VLC_CODEC_OPUS)
+ ? GetDOpsTag(p_fmt->p_extra, p_fmt->i_extra)
+ : GetDfLaTag(p_fmt->p_extra, p_fmt->i_extra);
+ if(!config)
+ {
+ if(p_obj)
+ msg_Err(p_obj, "Missing or invalid extradata for %4.4s",
+ (const char *)&p_fmt->i_codec);
+ return false;
+ }
+ bo_free(config);
+ break;
+ }
case VLC_CODEC_MP4A:
if(!p_fmt->i_extra)
{
=====================================
modules/mux/mp4/mp4.c
=====================================
@@ -513,6 +513,8 @@ static int AddStream(sout_mux_t *p_mux, sout_input_t *p_input)
p_sys->i_nb_streams );
trackfmt.audio.i_rate = 48000;
}
+ if(trackfmt.i_codec == VLC_CODEC_OPUS)
+ mp4mux_AddExtraBrand(p_sys->muxh, BRAND_iso2);
i_track_timescale = trackfmt.audio.i_rate;
break;
case VIDEO_ES:
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/bd22b5358a3f3bf5c196a87420c5fce43bb3340c...61ea4da0d5a26169cdb3db9e083ee4b56a33780b
--
View it on GitLab: https://code.videolan.org/videolan/vlc/-/compare/bd22b5358a3f3bf5c196a87420c5fce43bb3340c...61ea4da0d5a26169cdb3db9e083ee4b56a33780b
You're receiving this email because of your account on code.videolan.org. Manage all notifications: https://code.videolan.org/-/profile/notifications | Help: https://code.videolan.org/help
More information about the vlc-commits
mailing list