libavformat/oggenc.c: re-initialize stream on new metadata.

This commit is contained in:
Romain Beauxis
2025-03-14 00:27:04 -05:00
committed by Lynne
parent 5ec37f61b2
commit 0e89d993c5
5 changed files with 115 additions and 14 deletions

View File

@@ -66,6 +66,7 @@ typedef struct OGGStreamContext {
OGGPage page; ///< current page
unsigned serial_num; ///< serial number
int64_t last_granule; ///< last packet granule
int packet_seen; ///< true when packets have been submitted
} OGGStreamContext;
typedef struct OGGPageList {
@@ -81,11 +82,14 @@ typedef struct OGGContext {
#endif
int64_t pref_duration; ///< preferred page duration (0 => fill all segments)
int serial_offset;
int failed; // if true all packet submission will fail.
} OGGContext;
#define OFFSET(x) offsetof(OGGContext, x)
#define PARAM AV_OPT_FLAG_ENCODING_PARAM
static int ogg_write_trailer(AVFormatContext *s);
static const AVOption options[] = {
{ "serial_offset", "serial number offset",
OFFSET(serial_offset), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, PARAM },
@@ -107,6 +111,20 @@ static const AVClass ogg_muxer_class = {
.version = LIBAVUTIL_VERSION_INT,
};
static void ogg_cleanup_stream(AVStream *st)
{
OGGStreamContext *oggstream = st->priv_data;
if (st->codecpar->codec_id == AV_CODEC_ID_FLAC ||
st->codecpar->codec_id == AV_CODEC_ID_SPEEX ||
st->codecpar->codec_id == AV_CODEC_ID_OPUS ||
st->codecpar->codec_id == AV_CODEC_ID_VP8) {
av_freep(&oggstream->header[0]);
}
av_freep(&oggstream->header[1]);
}
static void ogg_write_page(AVFormatContext *s, OGGPage *page, int extra_flags)
{
OGGStreamContext *oggstream = s->streams[page->stream_index]->priv_data;
@@ -482,6 +500,8 @@ static void ogg_write_pages(AVFormatContext *s, int flush)
ogg->page_list = p;
}
// This function can be used on an initialized context to reinitialize the
// streams.
static int ogg_init(AVFormatContext *s)
{
OGGContext *ogg = s->priv_data;
@@ -515,9 +535,17 @@ static int ogg_init(AVFormatContext *s)
av_log(s, AV_LOG_ERROR, "No extradata present\n");
return AVERROR_INVALIDDATA;
}
oggstream = av_mallocz(sizeof(*oggstream));
if (!oggstream)
return AVERROR(ENOMEM);
oggstream = st->priv_data;
if (!oggstream) {
oggstream = av_mallocz(sizeof(*oggstream));
if (!oggstream)
return AVERROR(ENOMEM);
st->priv_data = oggstream;
} else {
ogg_cleanup_stream(st);
memset(oggstream, 0, sizeof(*oggstream));
}
oggstream->page.stream_index = i;
@@ -534,7 +562,6 @@ static int ogg_init(AVFormatContext *s)
av_dict_copy(&st->metadata, s->metadata, AV_DICT_DONT_OVERWRITE);
st->priv_data = oggstream;
if (st->codecpar->codec_id == AV_CODEC_ID_FLAC) {
int err = ogg_build_flac_headers(st->codecpar, oggstream,
s->flags & AVFMT_FLAG_BITEXACT,
@@ -642,13 +669,69 @@ static int ogg_write_header(AVFormatContext *s)
return 0;
}
static int ogg_check_new_metadata(AVFormatContext *s, AVPacket *pkt)
{
int ret = 0;
size_t size;
OGGContext *oggcontext = s->priv_data;
AVStream *st = s->streams[pkt->stream_index];
OGGStreamContext *oggstream = st->priv_data;
const uint8_t *side_metadata = av_packet_get_side_data(pkt, AV_PKT_DATA_STRINGS_METADATA, &size);
if (!side_metadata)
return 0;
// Don't restart on first packet.
if (!oggstream->packet_seen)
return 0;
if (s->nb_streams > 1) {
av_log(s, AV_LOG_WARNING, "Multiple streams present: cannot insert new metadata!\n");
return 0;
}
if (st->codecpar->codec_id != AV_CODEC_ID_VORBIS &&
st->codecpar->codec_id != AV_CODEC_ID_FLAC &&
st->codecpar->codec_id != AV_CODEC_ID_OPUS) {
av_log(s, AV_LOG_WARNING, "Inserting metadata is only supported for vorbis, flac and opus streams!\n");
return 0;
}
ret = ogg_write_trailer(s);
if (ret < 0)
goto end;
av_dict_free(&st->metadata);
ret = av_packet_unpack_dictionary(side_metadata, size, &st->metadata);
if (ret < 0)
goto end;
ret = ogg_init(s);
if (ret < 0)
goto end;
ret = ogg_write_header(s);
end:
oggcontext->failed = ret < 0;
return ret;
}
static int ogg_write_packet_internal(AVFormatContext *s, AVPacket *pkt)
{
AVStream *st = s->streams[pkt->stream_index];
OGGContext *oggcontext = s->priv_data;
OGGStreamContext *oggstream = st->priv_data;
int ret;
int64_t granule;
if (oggcontext->failed)
return AVERROR_INVALIDDATA;
ret = ogg_check_new_metadata(s, pkt);
if (ret < 0)
return ret;
if (st->codecpar->codec_id == AV_CODEC_ID_THEORA) {
int64_t pts = oggstream->vrev < 1 ? pkt->pts : pkt->pts + pkt->duration;
int pframe_count;
@@ -690,6 +773,7 @@ static int ogg_write_packet_internal(AVFormatContext *s, AVPacket *pkt)
ogg_write_pages(s, 0);
oggstream->last_granule = granule;
oggstream->packet_seen = 1;
return 0;
}
@@ -736,16 +820,8 @@ static void ogg_free(AVFormatContext *s)
for (i = 0; i < s->nb_streams; i++) {
AVStream *st = s->streams[i];
OGGStreamContext *oggstream = st->priv_data;
if (!oggstream)
continue;
if (st->codecpar->codec_id == AV_CODEC_ID_FLAC ||
st->codecpar->codec_id == AV_CODEC_ID_SPEEX ||
st->codecpar->codec_id == AV_CODEC_ID_OPUS ||
st->codecpar->codec_id == AV_CODEC_ID_VP8) {
av_freep(&oggstream->header[0]);
}
av_freep(&oggstream->header[1]);
if (st->priv_data)
ogg_cleanup_stream(st);
}
while (p) {

View File

@@ -103,6 +103,16 @@ runecho(){
$target_exec $target_path/"$@" >&3
}
run_with_temp(){
create_tmp=$1
process_tmp=$2
filext=$3
tmpfile=${outdir}/$test.$filext
cleanfiles="$cleanfiles $tmpfile"
run $create_tmp $tmpfile || return 1
run $process_tmp $tmpfile
}
probefmt(){
run ffprobe${PROGSUF}${EXECSUF} -bitexact -threads $threads -show_entries format=format_name -print_format default=nw=1:nk=1 "$@"
}

View File

@@ -2,6 +2,11 @@ FATE_OGG_FLAC += fate-ogg-flac-chained-meta
fate-ogg-flac-chained-meta: REF = $(SRC_PATH)/tests/ref/fate/ogg-flac-chained-meta.txt
fate-ogg-flac-chained-meta: CMD = run $(APITESTSDIR)/api-dump-stream-meta-test$(EXESUF) $(TARGET_SAMPLES)/ogg-flac/chained-meta.ogg
FATE_OGG_FLAC += fate-ogg-flac-copy-chained-meta
fate-ogg-flac-copy-chained-meta: $(APITESTSDIR)/api-dump-stream-meta-test$(EXESUF) $(FFMPEG)
fate-ogg-flac-copy-chained-meta: REF = $(SRC_PATH)/tests/ref/fate/ogg-flac-chained-meta.txt
fate-ogg-flac-copy-chained-meta: CMD = run_with_temp "$(FFMPEG) -nostdin -hide_banner -loglevel quiet -i $(TARGET_SAMPLES)/ogg-flac/chained-meta.ogg -c copy -f ogg -y" "$(APITESTSDIR)/api-dump-stream-meta-test$(EXESUF)" ogg
FATE_OGG_FLAC-$(call DEMDEC, OGG, FLAC, FLAC_PARSER) += $(FATE_OGG_FLAC)
FATE_SAMPLES_DUMP_STREAM_META += $(FATE_OGG_FLAC-yes)

View File

@@ -2,6 +2,11 @@ FATE_OGG_OPUS += fate-ogg-opus-chained-meta
fate-ogg-opus-chained-meta: REF = $(SRC_PATH)/tests/ref/fate/ogg-opus-chained-meta.txt
fate-ogg-opus-chained-meta: CMD = run $(APITESTSDIR)/api-dump-stream-meta-test$(EXESUF) $(TARGET_SAMPLES)/ogg-opus/chained-meta.ogg
FATE_OGG_OPUS += fate-ogg-opus-copy-chained-meta
fate-ogg-opus-copy-chained-meta: $(APITESTSDIR)/api-dump-stream-meta-test$(EXESUF) $(FFMPEG)
fate-ogg-opus-copy-chained-meta: REF = $(SRC_PATH)/tests/ref/fate/ogg-opus-chained-meta.txt
fate-ogg-opus-copy-chained-meta: CMD = run_with_temp "$(FFMPEG) -nostdin -hide_banner -loglevel quiet -i $(TARGET_SAMPLES)/ogg-opus/chained-meta.ogg -c copy -f ogg -y" "$(APITESTSDIR)/api-dump-stream-meta-test$(EXESUF)" ogg
FATE_OGG_OPUS-$(call DEMDEC, OGG, OPUS) += $(FATE_OGG_OPUS)
FATE_SAMPLES_DUMP_STREAM_META += $(FATE_OGG_OPUS-yes)

View File

@@ -2,6 +2,11 @@ FATE_OGG_VORBIS += fate-ogg-vorbis-chained-meta
fate-ogg-vorbis-chained-meta: REF = $(SRC_PATH)/tests/ref/fate/ogg-vorbis-chained-meta.txt
fate-ogg-vorbis-chained-meta: CMD = run $(APITESTSDIR)/api-dump-stream-meta-test$(EXESUF) $(TARGET_SAMPLES)/ogg-vorbis/chained-meta.ogg
FATE_OGG_VORBIS += fate-ogg-vorbis-copy-chained-meta
fate-ogg-vorbis-copy-chained-meta: $(APITESTSDIR)/api-dump-stream-meta-test$(EXESUF) $(FFMPEG)
fate-ogg-vorbis-copy-chained-meta: REF = $(SRC_PATH)/tests/ref/fate/ogg-vorbis-chained-meta.txt
fate-ogg-vorbis-copy-chained-meta: CMD = run_with_temp "$(FFMPEG) -nostdin -hide_banner -loglevel quiet -i $(TARGET_SAMPLES)/ogg-vorbis/chained-meta.ogg -c copy -f ogg -y" "$(APITESTSDIR)/api-dump-stream-meta-test$(EXESUF)" ogg
FATE_OGG_VORBIS-$(call DEMDEC, OGG, VORBIS) += $(FATE_OGG_VORBIS)
FATE_SAMPLES_DUMP_STREAM_META += $(FATE_OGG_VORBIS-yes)