diff --git a/libavformat/oggenc.c b/libavformat/oggenc.c index be85386a6a..605c9d20e1 100644 --- a/libavformat/oggenc.c +++ b/libavformat/oggenc.c @@ -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) { diff --git a/tests/fate-run.sh b/tests/fate-run.sh index 0e0c11ac3b..cd66cd059c 100755 --- a/tests/fate-run.sh +++ b/tests/fate-run.sh @@ -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 "$@" } diff --git a/tests/fate/ogg-flac.mak b/tests/fate/ogg-flac.mak index 07bdb232cb..3b48f32d2d 100644 --- a/tests/fate/ogg-flac.mak +++ b/tests/fate/ogg-flac.mak @@ -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) diff --git a/tests/fate/ogg-opus.mak b/tests/fate/ogg-opus.mak index 54b6fbabde..d8a30dd058 100644 --- a/tests/fate/ogg-opus.mak +++ b/tests/fate/ogg-opus.mak @@ -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) diff --git a/tests/fate/ogg-vorbis.mak b/tests/fate/ogg-vorbis.mak index 74805d591e..1bca373fea 100644 --- a/tests/fate/ogg-vorbis.mak +++ b/tests/fate/ogg-vorbis.mak @@ -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)