avformat/matroskaenc: Parse Opus packet durations ourselves

This avoids avpriv functions from lavc/opus/parse.c
(which parse way more than we need, necessitating
parsing the extradata).
It furthermore makes the output of the muxer consistent,
i.e. no longer depending upon whether the Opus parser
or decoder are enabled (the avpriv functions would just
return AVERROR(ENOSYS)).

Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
This commit is contained in:
Andreas Rheinhardt
2026-02-06 13:41:45 +01:00
parent 853843d86f
commit 12747e6296
5 changed files with 57 additions and 23 deletions

View File

@@ -29,5 +29,7 @@ OBJS-$(CONFIG_OPUS_ENCODER) += \
opus/rc.o \
opus/tab.o \
STLIBOBJS-$(CONFIG_MATROSKA_MUXER) += opus/frame_duration_tab.o
STLIBOBJS-$(CONFIG_WEBM_MUXER) += opus/frame_duration_tab.o
libavcodec/opus/%.o: CPPFLAGS += -I$(SRC_PATH)/libavcodec/

View File

@@ -161,7 +161,7 @@ extern const float ff_celt_postfilter_taps[3][3];
extern const float ff_celt_window2[120];
extern const float ff_celt_window_padded[];
static const float *const ff_celt_window = &ff_celt_window_padded[8];
#define ff_celt_window (ff_celt_window_padded + 8)
extern const float ff_opus_deemph_weights[];

View File

@@ -751,6 +751,7 @@ SHLIBOBJS-$(CONFIG_IMAGE_JPEGXL_PIPE_DEMUXER) += jpegxl_parse.o
SHLIBOBJS-$(CONFIG_JNI) += ffjni.o
SHLIBOBJS-$(CONFIG_JPEGXL_ANIM_DEMUXER) += jpegxl_parse.o
SHLIBOBJS-$(CONFIG_MATROSKA_DEMUXER) += mpeg4audio_sample_rates.o
SHLIBOBJS-$(CONFIG_MATROSKA_MUXER) += opus_frame_duration_tab.o
SHLIBOBJS-$(CONFIG_MOV_DEMUXER) += ac3_channel_layout_tab.o
SHLIBOBJS-$(CONFIG_MP3_MUXER) += mpegaudiotabs.o
SHLIBOBJS-$(CONFIG_MXF_MUXER) += golomb_tab.o \
@@ -760,6 +761,7 @@ SHLIBOBJS-$(CONFIG_RTPDEC) += jpegtables.o
SHLIBOBJS-$(CONFIG_RTP_MUXER) += golomb_tab.o jpegtables.o \
mpeg4audio_sample_rates.o
SHLIBOBJS-$(CONFIG_SPDIF_MUXER) += dca_sample_rate_tab.o
SHLIBOBJS-$(CONFIG_WEBM_MUXER) += opus_frame_duration_tab.o
# libavdevice dependencies

View File

@@ -69,7 +69,7 @@
#include "libavcodec/itut35.h"
#include "libavcodec/xiph.h"
#include "libavcodec/mpeg4audio.h"
#include "libavcodec/opus/parse.h"
#include "libavcodec/opus/tab.h"
/* Level 1 elements we create a SeekHead entry for:
* Info, Tracks, Chapters, Attachments, Tags (potentially twice) and Cues */
@@ -210,10 +210,6 @@ typedef struct mkv_track {
* The callback shall not return an error on the second call. */
int (*reformat)(struct MatroskaMuxContext *, AVIOContext *,
const AVPacket *, int *size);
// Opus specific
OpusParseContext *opus;
OpusPacket *opus_pkt;
} mkv_track;
typedef struct MatroskaMuxContext {
@@ -286,6 +282,34 @@ typedef struct MatroskaMuxContext {
/** Seek preroll value for opus */
#define OPUS_SEEK_PREROLL 80000000
/**
* Returns the duration of an Opus packet in samples.
*/
static int parse_opus_packet_duration(const uint8_t *buf, int buf_size)
{
int code = buf[0] & 0x3;
int config = buf[0] >> 3;
int frame_count;
switch (code) {
default:
av_unreachable("code is in 0..3");
case 0:
frame_count = 1;
break;
case 1:
case 2:
frame_count = 2;
break;
case 3:
if (buf_size <= 1)
return AVERROR_INVALIDDATA;
frame_count = buf[1] & 0x3F;
break;
}
return frame_count * ff_opus_frame_duration[config];
}
static int ebml_id_size(uint32_t id)
{
return (av_log2(id) + 7U) / 8;
@@ -865,13 +889,6 @@ static void mkv_deinit(AVFormatContext *s)
av_freep(&mkv->cur_block.h2645_nalu_list.nalus);
av_freep(&mkv->cues.entries);
for (int i = 0; i < s->nb_streams; i++) {
mkv_track *track = &mkv->tracks[i];
if (track->opus)
avpriv_opus_parse_uninit_context(track->opus);
av_freep(&track->opus);
av_freep(&track->opus_pkt);
}
av_freep(&mkv->tracks);
}
@@ -2843,14 +2860,12 @@ static int mkv_write_block(void *logctx, MatroskaMuxContext *mkv,
duration != track->default_duration_high &&
duration != track->default_duration_low))
ebml_writer_add_uint(&writer, MATROSKA_ID_BLOCKDURATION, duration);
else if (track->opus) {
ret = avpriv_opus_parse_packet(&track->opus_pkt, pkt->data, pkt->size,
track->opus->nb_streams > 1, logctx);
if (!ret) {
else if (par->codec_id == AV_CODEC_ID_OPUS) {
ret = parse_opus_packet_duration(pkt->data, pkt->size);
if (ret >= 0) {
/* If the packet's duration is inconsistent with the coded duration,
* add an explicit duration element. */
uint64_t parsed_duration = av_rescale_q(track->opus_pkt->frame_count * track->opus_pkt->frame_duration,
(AVRational){1, par->sample_rate},
uint64_t parsed_duration = av_rescale_q(ret, (AVRational){1, 48000},
st->time_base);
if (parsed_duration != duration)
ebml_writer_add_uint(&writer, MATROSKA_ID_BLOCKDURATION, duration);
@@ -3495,10 +3510,6 @@ static int mkv_init(struct AVFormatContext *s)
case AV_CODEC_ID_WEBVTT:
track->reformat = webm_reformat_vtt;
break;
case AV_CODEC_ID_OPUS:
avpriv_opus_parse_extradata(&track->opus, par->extradata, par->extradata_size,
par->ch_layout.nb_channels, s);
break;
}
if (s->flags & AVFMT_FLAG_BITEXACT) {

View File

@@ -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/opus/frame_duration_tab.c"