X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=shared%2Fmux.cpp;h=b8feacd6907460cedb017a07626c64dca5ed10a7;hb=2f92c975a3cf9f4803a58267fd2a12765e34a69e;hp=da618e0c6bbc1066ef4c3db110f935c48c0bde45;hpb=131a051c4cd3719a9be415386fdf0f4e15da7c66;p=nageru diff --git a/shared/mux.cpp b/shared/mux.cpp index da618e0..b8feacd 100644 --- a/shared/mux.cpp +++ b/shared/mux.cpp @@ -22,7 +22,7 @@ extern "C" { } #include "shared/metrics.h" -#include "shared/mux_opts.h" +#include "shared/shared_defs.h" #include "shared/timebase.h" using namespace std; @@ -47,21 +47,27 @@ struct PacketBefore { const AVFormatContext * const ctx; }; -Mux::Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const string &video_extradata, const AVCodecParameters *audio_codecpar, AVColorSpace color_space, WithAudio with_audio, int time_base, function write_callback, WriteStrategy write_strategy, const vector &metrics) +Mux::Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const string &video_extradata, const AVCodecParameters *audio_codecpar, AVColorSpace color_space, int time_base, function write_callback, WriteStrategy write_strategy, const vector &metrics, WithSubtitles with_subtitles) : write_strategy(write_strategy), avctx(avctx), write_callback(write_callback), metrics(metrics) { - avstream_video = avformat_new_stream(avctx, nullptr); + // MPEG-TS ostensibly needs some conversions (e.g. for differing start codes), + // so let FFmpeg insert them as needed in case we are muxing to that. + // Curiously enough, things actually seem to go quite fine without + // (and it also seems FFmpeg's MPEG-TS muxer automatically does stuff like + // repeat PPS/SPS before keyframes for us), but it can't hurt. + avctx->flags |= AVFMT_FLAG_AUTO_BSF; + + AVStream *avstream_video = avformat_new_stream(avctx, nullptr); if (avstream_video == nullptr) { fprintf(stderr, "avformat_new_stream() failed\n"); - exit(1); + abort(); } avstream_video->time_base = AVRational{1, time_base}; avstream_video->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; if (video_codec == CODEC_H264) { avstream_video->codecpar->codec_id = AV_CODEC_ID_H264; - } else if (video_codec == CODEC_NV12) { - avstream_video->codecpar->codec_id = AV_CODEC_ID_RAWVIDEO; - avstream_video->codecpar->codec_tag = avcodec_pix_fmt_to_codec_tag(AV_PIX_FMT_NV12); + } else if (video_codec == CODEC_AV1) { + avstream_video->codecpar->codec_id = AV_CODEC_ID_AV1; } else { assert(video_codec == CODEC_MJPEG); avstream_video->codecpar->codec_id = AV_CODEC_ID_MJPEG; @@ -84,45 +90,44 @@ Mux::Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const avstream_video->codecpar->field_order = AV_FIELD_PROGRESSIVE; if (!video_extradata.empty()) { - avstream_video->codecpar->extradata = (uint8_t *)av_malloc(video_extradata.size()); + avstream_video->codecpar->extradata = (uint8_t *)av_malloc(video_extradata.size() + AV_INPUT_BUFFER_PADDING_SIZE); avstream_video->codecpar->extradata_size = video_extradata.size(); memcpy(avstream_video->codecpar->extradata, video_extradata.data(), video_extradata.size()); } + streams.push_back(avstream_video); - if (with_audio == WITH_AUDIO) { - avstream_audio = avformat_new_stream(avctx, nullptr); + if (audio_codecpar != nullptr) { + AVStream *avstream_audio = avformat_new_stream(avctx, nullptr); if (avstream_audio == nullptr) { fprintf(stderr, "avformat_new_stream() failed\n"); - exit(1); + abort(); } avstream_audio->time_base = AVRational{1, time_base}; if (avcodec_parameters_copy(avstream_audio->codecpar, audio_codecpar) < 0) { fprintf(stderr, "avcodec_parameters_copy() failed\n"); - exit(1); + abort(); } - } else { - assert(with_audio == WITHOUT_AUDIO); - avstream_audio = nullptr; + streams.push_back(avstream_audio); } - AVDictionary *options = NULL; - vector> opts = MUX_OPTS; - for (pair opt : opts) { - av_dict_set(&options, opt.first.c_str(), opt.second.c_str(), 0); - } - if (avformat_write_header(avctx, &options) < 0) { - fprintf(stderr, "avformat_write_header() failed\n"); - exit(1); - } - for (MuxMetrics *metric : metrics) { - metric->metric_written_bytes += avctx->pb->pos; + if (with_subtitles == WITH_SUBTITLES) { + AVStream *avstream_subtitles = avformat_new_stream(avctx, nullptr); + if (avstream_subtitles == nullptr) { + fprintf(stderr, "avformat_new_stream() failed\n"); + abort(); + } + avstream_subtitles->time_base = AVRational{1, time_base}; + avstream_subtitles->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE; + avstream_subtitles->codecpar->codec_id = AV_CODEC_ID_WEBVTT; + avstream_subtitles->disposition = AV_DISPOSITION_METADATA; + streams.push_back(avstream_subtitles); + subtitle_stream_idx = streams.size() - 1; } - // Make sure the header is written before the constructor exits. - avio_flush(avctx->pb); - if (write_strategy == WRITE_BACKGROUND) { writer_thread = thread(&Mux::thread_func, this); + } else { + write_header(); } } @@ -149,26 +154,21 @@ Mux::~Mux() void Mux::add_packet(const AVPacket &pkt, int64_t pts, int64_t dts, AVRational timebase, int stream_index_override) { + assert(pts >= dts); + AVPacket pkt_copy; - av_init_packet(&pkt_copy); if (av_packet_ref(&pkt_copy, &pkt) < 0) { fprintf(stderr, "av_copy_packet() failed\n"); - exit(1); + abort(); } if (stream_index_override != -1) { pkt_copy.stream_index = stream_index_override; } - if (pkt_copy.stream_index == 0) { - pkt_copy.pts = av_rescale_q(pts, timebase, avstream_video->time_base); - pkt_copy.dts = av_rescale_q(dts, timebase, avstream_video->time_base); - pkt_copy.duration = av_rescale_q(pkt.duration, timebase, avstream_video->time_base); - } else if (pkt_copy.stream_index == 1) { - pkt_copy.pts = av_rescale_q(pts, timebase, avstream_audio->time_base); - pkt_copy.dts = av_rescale_q(dts, timebase, avstream_audio->time_base); - pkt_copy.duration = av_rescale_q(pkt.duration, timebase, avstream_audio->time_base); - } else { - assert(false); - } + assert(size_t(pkt_copy.stream_index) < streams.size()); + AVRational time_base = streams[pkt_copy.stream_index]->time_base; + pkt_copy.pts = av_rescale_q(pts, timebase, time_base); + pkt_copy.dts = av_rescale_q(dts, timebase, time_base); + pkt_copy.duration = av_rescale_q(pkt.duration, timebase, time_base); { lock_guard lock(mu); @@ -198,9 +198,12 @@ void Mux::write_packet_or_die(const AVPacket &pkt, int64_t unscaled_pts) } } int64_t old_pos = avctx->pb->pos; - if (av_interleaved_write_frame(avctx, const_cast(&pkt)) < 0) { - fprintf(stderr, "av_interleaved_write_frame() failed\n"); - abort(); + int err = av_interleaved_write_frame(avctx, const_cast(&pkt)); + if (err < 0) { + char errbuf[AV_ERROR_MAX_STRING_SIZE]; + av_strerror(err, errbuf, sizeof(errbuf)); + fprintf(stderr, "av_interleaved_write_frame() failed: %s\n", errbuf); + exit(EXIT_FAILURE); } avio_flush(avctx->pb); for (MuxMetrics *metric : metrics) { @@ -243,6 +246,8 @@ void Mux::thread_func() { pthread_setname_np(pthread_self(), "Mux"); + write_header(); + unique_lock lock(mu); for ( ;; ) { packet_queue_ready.wait(lock, [this]() { @@ -266,6 +271,31 @@ void Mux::thread_func() } } +void Mux::write_header() +{ + AVDictionary *options = NULL; + vector> opts = MUX_OPTS; + for (pair opt : opts) { + av_dict_set(&options, opt.first.c_str(), opt.second.c_str(), 0); + } + + int err = avformat_write_header(avctx, &options); + if (err < 0) { + char errbuf[AV_ERROR_MAX_STRING_SIZE]; + av_strerror(err, errbuf, sizeof(errbuf)); + fprintf(stderr, "avformat_write_header() failed: %s\n", errbuf); + exit(EXIT_FAILURE); + } + for (MuxMetrics *metric : metrics) { + metric->metric_written_bytes += avctx->pb->pos; + } + + // Make sure the header is written before the constructor exits + // (assuming we are in WRITE_FOREGROUND mode). + avio_flush(avctx->pb); + +} + void MuxMetrics::init(const vector> &labels) { vector> labels_video = labels;