From f7ccf916d9755700c655499afb0c8c636be07242 Mon Sep 17 00:00:00 2001 From: Mole Shang <135e2@135e2.dev> Date: Wed, 9 Aug 2023 22:39:07 +0800 Subject: utils/ffmpeg: support remux --- src/utils/ffmpeg.c | 192 +++++++++++++++++++++++++++++++++++++++++++++++++++-- src/utils/ffmpeg.h | 2 + 2 files changed, 187 insertions(+), 7 deletions(-) (limited to 'src/utils') diff --git a/src/utils/ffmpeg.c b/src/utils/ffmpeg.c index e4d9e44..694400c 100644 --- a/src/utils/ffmpeg.c +++ b/src/utils/ffmpeg.c @@ -9,6 +9,25 @@ #include "../logger.h" #include "ffmpeg.h" +#ifdef DEBUG +static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt, + const char *tag) { + AVRational *time_base = &fmt_ctx->streams[pkt->stream_index]->time_base; + + printf("%s: pts:%s pts_time:%s dts:%s dts_time:%s duration:%s " + "duration_time:%s stream_index:%d\n", + tag, av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, time_base), + av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, time_base), + av_ts2str(pkt->duration), av_ts2timestr(pkt->duration, time_base), + pkt->stream_index); +} +#else +static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt, + const char *tag) { + return; +} +#endif + int merge_av(const char *videofn, const char *audiofn, const char *outfn) { AVFormatContext *input1_format_context = NULL, *input2_format_context = NULL, *output_format_context = NULL; @@ -18,6 +37,16 @@ int merge_av(const char *videofn, const char *audiofn, const char *outfn) { int *streams_list = NULL; int number_of_streams = 0; + AVDictionary *opts = NULL; + /* Support fragmented MP4 + * https://developer.mozilla.org/en-US/docs/Web/API/Media_Source_Extensions_API/Transcoding_assets_for_MSE + */ + av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov+default_base_moof", + 0); + /* Enable all protocols support */ + av_dict_set(&opts, "protocol_whitelist", + "file,crypto,data,http,https,tcp,tls", 0); + if ((ret = avformat_open_input(&input1_format_context, videofn, NULL, NULL)) < 0) { append_log("Could not open input file '%s'\n", videofn); @@ -112,12 +141,7 @@ int merge_av(const char *videofn, const char *audiofn, const char *outfn) { goto end; } } - AVDictionary *opts = NULL; - /* Support fragmented MP4 - * https://developer.mozilla.org/en-US/docs/Web/API/Media_Source_Extensions_API/Transcoding_assets_for_MSE - */ - av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov+default_base_moof", - 0); + ret = avformat_write_header(output_format_context, &opts); if (ret < 0) { append_log("Error occurred when opening output file\n"); @@ -128,7 +152,7 @@ int merge_av(const char *videofn, const char *audiofn, const char *outfn) { ret = av_read_frame(input1_format_context, &packet); if (ret < 0) break; - in_stream = input1_format_context->streams[packet.stream_index]; + in_stream = input1_format_context->streams[stream_index]; if (packet.stream_index >= number_of_streams || streams_list[packet.stream_index] < 0) { av_packet_unref(&packet); @@ -137,6 +161,7 @@ int merge_av(const char *videofn, const char *audiofn, const char *outfn) { packet.stream_index = streams_list[packet.stream_index]; out_stream = output_format_context->streams[packet.stream_index]; /* copy packet */ + log_packet(input1_format_context, &packet, "in"); packet.pts = av_rescale_q_rnd(packet.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); @@ -146,6 +171,7 @@ int merge_av(const char *videofn, const char *audiofn, const char *outfn) { packet.duration = av_rescale_q(packet.duration, in_stream->time_base, out_stream->time_base); packet.pos = -1; + log_packet(output_format_context, &packet, "out"); ret = av_interleaved_write_frame(output_format_context, &packet); if (ret < 0) { @@ -170,6 +196,7 @@ int merge_av(const char *videofn, const char *audiofn, const char *outfn) { streams_list[packet.stream_index + input1_format_context->nb_streams]; out_stream = output_format_context->streams[packet.stream_index]; /* copy packet */ + log_packet(input2_format_context, &packet, "in"); packet.pts = av_rescale_q_rnd(packet.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX); @@ -179,6 +206,7 @@ int merge_av(const char *videofn, const char *audiofn, const char *outfn) { packet.duration = av_rescale_q(packet.duration, in_stream->time_base, out_stream->time_base); packet.pos = -1; + log_packet(output_format_context, &packet, "out"); ret = av_interleaved_write_frame(output_format_context, &packet); if (ret < 0) { @@ -211,3 +239,153 @@ end: } return 0; } + +int remux(const char *in_filename, const char *out_filename) { + const AVOutputFormat *ofmt = NULL; + AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL; + AVPacket *pkt = NULL; + int ret, i; + int stream_index = 0; + int *stream_mapping = NULL; + int stream_mapping_size = 0; + + pkt = av_packet_alloc(); + if (!pkt) { + fprintf(stderr, "Could not allocate AVPacket\n"); + return 1; + } + + AVDictionary *opts = NULL; + av_dict_set(&opts, "protocol_whitelist", + "concat,file,http,https,tcp,tls,crypto", 0); + + if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, &opts)) < 0) { + fprintf(stderr, "Could not open input file '%s'\n", in_filename); + goto end; + } + + if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) { + fprintf(stderr, "Failed to retrieve input stream information.\n"); + goto end; + } + + av_dump_format(ifmt_ctx, 0, in_filename, 0); + + avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename); + if (!ofmt_ctx) { + fprintf(stderr, "Could not create output context.\n"); + ret = AVERROR_UNKNOWN; + goto end; + } + + stream_mapping_size = ifmt_ctx->nb_streams; + stream_mapping = av_calloc(stream_mapping_size, sizeof(*stream_mapping)); + if (!stream_mapping) { + ret = AVERROR(ENOMEM); + goto end; + } + + ofmt = ofmt_ctx->oformat; + + for (i = 0; i < ifmt_ctx->nb_streams; i++) { + AVStream *out_stream; + AVStream *in_stream = ifmt_ctx->streams[i]; + AVCodecParameters *in_codecpar = in_stream->codecpar; + + if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO && + in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO && + in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) { + stream_mapping[i] = -1; + continue; + } + + stream_mapping[i] = stream_index++; + + out_stream = avformat_new_stream(ofmt_ctx, NULL); + if (!out_stream) { + fprintf(stderr, "Failed allocating output stream\n"); + ret = AVERROR_UNKNOWN; + goto end; + } + + ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar); + if (ret < 0) { + fprintf(stderr, "Failed to copy codec parameters\n"); + goto end; + } + out_stream->codecpar->codec_tag = 0; + } + av_dump_format(ofmt_ctx, 0, out_filename, 1); + + if (!(ofmt->flags & AVFMT_NOFILE)) { + ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE); + if (ret < 0) { + fprintf(stderr, "Could not open output file '%s'", out_filename); + goto end; + } + } + + ret = avformat_write_header(ofmt_ctx, &opts); + if (ret < 0) { + fprintf(stderr, "Error occurred when opening output file\n"); + goto end; + } + + while (1) { + AVStream *in_stream, *out_stream; + + ret = av_read_frame(ifmt_ctx, pkt); + if (ret < 0) + break; + + in_stream = ifmt_ctx->streams[pkt->stream_index]; + if (pkt->stream_index >= stream_mapping_size || + stream_mapping[pkt->stream_index] < 0) { + av_packet_unref(pkt); + continue; + } + + pkt->stream_index = stream_mapping[pkt->stream_index]; + out_stream = ofmt_ctx->streams[pkt->stream_index]; + log_packet(ifmt_ctx, pkt, "in"); + + /* copy packet */ + av_packet_rescale_ts(pkt, in_stream->time_base, out_stream->time_base); + pkt->pos = -1; + log_packet(ofmt_ctx, pkt, "out"); + + ret = av_interleaved_write_frame(ofmt_ctx, pkt); + /* pkt is now blank (av_interleaved_write_frame() takes ownership of + * its contents and resets pkt), so that no unreferencing is necessary. + * This would be different if one used av_write_frame(). */ + if (ret < 0) { + fprintf(stderr, "Error muxing packet\n"); + break; + } + } + + av_write_trailer(ofmt_ctx); +end: + av_packet_free(&pkt); + + avformat_close_input(&ifmt_ctx); + + /* close output */ + if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE)) + avio_closep(&ofmt_ctx->pb); + avformat_free_context(ofmt_ctx); + + av_freep(&stream_mapping); + + if (ret < 0 && ret != AVERROR_EOF) { + fprintf(stderr, "Error occurred: %s\n", av_err2str(ret)); + return 1; + } + + // Delete seperate files + if (remove(in_filename) != 0) { + append_log("Error deleting partial file %s\n", in_filename); + } + + return 0; +} diff --git a/src/utils/ffmpeg.h b/src/utils/ffmpeg.h index 91d79df..1ca949d 100644 --- a/src/utils/ffmpeg.h +++ b/src/utils/ffmpeg.h @@ -3,4 +3,6 @@ int merge_av(const char *videofn, const char *audiofn, const char *outfn); +int remux(const char *inputfn, const char *outfn); + #endif -- cgit v1.2.3