srcs = ['flow.cpp', 'gpu_timers.cpp']
# All the other files.
-srcs += ['main.cpp', 'player.cpp', 'httpd.cpp', 'mux.cpp', 'video_stream.cpp', 'context.cpp', 'chroma_subsampler.cpp']
+srcs += ['main.cpp', 'player.cpp', 'httpd.cpp', 'video_stream.cpp', 'context.cpp', 'chroma_subsampler.cpp']
srcs += ['vaapi_jpeg_decoder.cpp', 'db.cpp', 'disk_space_estimator.cpp', 'ycbcr_converter.cpp', 'flags.cpp']
srcs += ['mainwindow.cpp', 'jpeg_frame_view.cpp', 'clip_list.cpp', 'frame_on_disk.cpp']
srcs += moc_files
+++ /dev/null
-#include "mux.h"
-
-#include <algorithm>
-#include <assert.h>
-#include <mutex>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <string>
-#include <utility>
-#include <vector>
-
-extern "C" {
-#include <libavformat/avio.h>
-#include <libavutil/avutil.h>
-#include <libavutil/dict.h>
-#include <libavutil/mathematics.h>
-#include <libavutil/mem.h>
-#include <libavutil/pixfmt.h>
-#include <libavutil/rational.h>
-}
-
-#include "defs.h"
-#include "shared/timebase.h"
-
-using namespace std;
-
-struct PacketBefore {
- PacketBefore(const AVFormatContext *ctx) : ctx(ctx) {}
-
- bool operator() (const Mux::QueuedPacket &a_qp, const Mux::QueuedPacket &b_qp) const {
- const AVPacket *a = a_qp.pkt;
- const AVPacket *b = b_qp.pkt;
- int64_t a_dts = (a->dts == AV_NOPTS_VALUE ? a->pts : a->dts);
- int64_t b_dts = (b->dts == AV_NOPTS_VALUE ? b->pts : b->dts);
- AVRational a_timebase = ctx->streams[a->stream_index]->time_base;
- AVRational b_timebase = ctx->streams[b->stream_index]->time_base;
- if (av_compare_ts(a_dts, a_timebase, b_dts, b_timebase) != 0) {
- return av_compare_ts(a_dts, a_timebase, b_dts, b_timebase) < 0;
- } else {
- return av_compare_ts(a->pts, a_timebase, b->pts, b_timebase) < 0;
- }
- }
-
- const AVFormatContext * const ctx;
-};
-
-Mux::Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const string &video_extradata, const AVCodecParameters *audio_codecpar, int time_base, std::function<void(int64_t)> write_callback, WriteStrategy write_strategy, const vector<MuxMetrics *> &metrics)
- : write_strategy(write_strategy), avctx(avctx), write_callback(write_callback), metrics(metrics)
-{
- avstream_video = avformat_new_stream(avctx, nullptr);
- if (avstream_video == nullptr) {
- fprintf(stderr, "avformat_new_stream() failed\n");
- exit(1);
- }
- 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 {
- assert(video_codec == CODEC_MJPEG);
- avstream_video->codecpar->codec_id = AV_CODEC_ID_MJPEG;
- }
- avstream_video->codecpar->width = width;
- avstream_video->codecpar->height = height;
-
- // Colorspace details. Closely correspond to settings in EffectChain_finalize,
- // as noted in each comment.
- // Note that the H.264 stream also contains this information and depending on the
- // mux, this might simply get ignored. See sps_rbsp().
- // Note that there's no way to change this per-frame as the H.264 stream
- // would like to be able to.
- avstream_video->codecpar->color_primaries = AVCOL_PRI_BT709; // RGB colorspace (inout_format.color_space).
- avstream_video->codecpar->color_trc = AVCOL_TRC_IEC61966_2_1; // Gamma curve (inout_format.gamma_curve).
- // YUV colorspace (output_ycbcr_format.luma_coefficients).
- avstream_video->codecpar->color_space = AVCOL_SPC_BT709;
- avstream_video->codecpar->color_range = AVCOL_RANGE_MPEG; // Full vs. limited range (output_ycbcr_format.full_range).
- avstream_video->codecpar->chroma_location = AVCHROMA_LOC_LEFT; // Chroma sample location. See chroma_offset_0[] in Mixer::subsample_chroma().
- 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_size = video_extradata.size();
- memcpy(avstream_video->codecpar->extradata, video_extradata.data(), video_extradata.size());
- }
-
- avstream_audio = nullptr;
-#if 0
- avstream_audio = avformat_new_stream(avctx, nullptr);
- if (avstream_audio == nullptr) {
- fprintf(stderr, "avformat_new_stream() failed\n");
- exit(1);
- }
- 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);
- }
-#endif
-
- AVDictionary *options = NULL;
- vector<pair<string, string>> opts = MUX_OPTS;
- for (pair<string, string> 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;
- }
-
- // 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);
- }
-}
-
-Mux::~Mux()
-{
- assert(plug_count == 0);
- if (write_strategy == WRITE_BACKGROUND) {
- writer_thread_should_quit = true;
- packet_queue_ready.notify_all();
- writer_thread.join();
- }
- int64_t old_pos = avctx->pb->pos;
- av_write_trailer(avctx);
- for (MuxMetrics *metric : metrics) {
- metric->metric_written_bytes += avctx->pb->pos - old_pos;
- }
-
- if (!(avctx->oformat->flags & AVFMT_NOFILE) &&
- !(avctx->flags & AVFMT_FLAG_CUSTOM_IO)) {
- avio_closep(&avctx->pb);
- }
- avformat_free_context(avctx);
-}
-
-void Mux::add_packet(const AVPacket &pkt, int64_t pts, int64_t dts, AVRational timebase, int stream_index_override)
-{
- 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);
- }
- 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);
- }
-
- {
- lock_guard<mutex> lock(mu);
- if (write_strategy == WriteStrategy::WRITE_BACKGROUND) {
- packet_queue.push_back(QueuedPacket{ av_packet_clone(&pkt_copy), pts });
- if (plug_count == 0)
- packet_queue_ready.notify_all();
- } else if (plug_count > 0) {
- packet_queue.push_back(QueuedPacket{ av_packet_clone(&pkt_copy), pts });
- } else {
- write_packet_or_die(pkt_copy, pts);
- }
- }
-
- av_packet_unref(&pkt_copy);
-}
-
-void Mux::write_packet_or_die(const AVPacket &pkt, int64_t unscaled_pts)
-{
- for (MuxMetrics *metric : metrics) {
- if (pkt.stream_index == 0) {
- metric->metric_video_bytes += pkt.size;
- } else if (pkt.stream_index == 1) {
- metric->metric_audio_bytes += pkt.size;
- } else {
- assert(false);
- }
- }
- int64_t old_pos = avctx->pb->pos;
- if (av_interleaved_write_frame(avctx, const_cast<AVPacket *>(&pkt)) < 0) {
- fprintf(stderr, "av_interleaved_write_frame() failed\n");
- abort();
- }
- avio_flush(avctx->pb);
- for (MuxMetrics *metric : metrics) {
- metric->metric_written_bytes += avctx->pb->pos - old_pos;
- }
-
- if (pkt.stream_index == 0 && write_callback != nullptr) {
- write_callback(unscaled_pts);
- }
-}
-
-void Mux::plug()
-{
- lock_guard<mutex> lock(mu);
- ++plug_count;
-}
-
-void Mux::unplug()
-{
- lock_guard<mutex> lock(mu);
- if (--plug_count > 0) {
- return;
- }
- assert(plug_count >= 0);
-
- sort(packet_queue.begin(), packet_queue.end(), PacketBefore(avctx));
-
- if (write_strategy == WRITE_BACKGROUND) {
- packet_queue_ready.notify_all();
- } else {
- for (QueuedPacket &qp : packet_queue) {
- write_packet_or_die(*qp.pkt, qp.unscaled_pts);
- av_packet_free(&qp.pkt);
- }
- packet_queue.clear();
- }
-}
-
-void Mux::thread_func()
-{
- pthread_setname_np(pthread_self(), "Mux");
-
- unique_lock<mutex> lock(mu);
- for ( ;; ) {
- packet_queue_ready.wait(lock, [this]() {
- return writer_thread_should_quit || (!packet_queue.empty() && plug_count == 0);
- });
- if (writer_thread_should_quit && packet_queue.empty()) {
- // All done.
- break;
- }
-
- assert(!packet_queue.empty() && plug_count == 0);
- vector<QueuedPacket> packets;
- swap(packets, packet_queue);
-
- lock.unlock();
- for (QueuedPacket &qp : packets) {
- write_packet_or_die(*qp.pkt, qp.unscaled_pts);
- av_packet_free(&qp.pkt);
- }
- lock.lock();
- }
-}
-
-void MuxMetrics::init(const vector<pair<string, string>> &labels)
-{
- // TODO: See if we want to reintroduce these.
-}
#include "frame_on_disk.h"
#include "httpd.h"
#include "jpeg_frame_view.h"
-#include "mux.h"
+#include "shared/mux.h"
#include "shared/timebase.h"
#include "video_stream.h"
#include "httpd.h"
#include "jpeg_frame_view.h"
#include "movit/util.h"
-#include "mux.h"
+#include "shared/mux.h"
#include "player.h"
#include "util.h"
#include "ycbcr_converter.h"
string video_extradata;
constexpr int width = 1280, height = 720; // Doesn't matter for MJPEG.
- stream_mux.reset(new Mux(avctx, width, height, video_codec, video_extradata, /*audio_codec_parameters=*/nullptr, COARSE_TIMEBASE,
- /*write_callback=*/nullptr, Mux::WRITE_FOREGROUND, {}));
+ stream_mux.reset(new Mux(avctx, width, height, video_codec, video_extradata, /*audio_codec_parameters=*/nullptr,
+ AVCOL_SPC_BT709, Mux::WITHOUT_AUDIO,
+ COARSE_TIMEBASE, /*write_callback=*/nullptr, Mux::WRITE_FOREGROUND, {}));
encode_thread = thread(&VideoStream::encode_thread_func, this);
#include <vector>
#include "defs.h"
-#include "mux.h"
+#include "shared/mux.h"
#include "shared/timebase.h"
using namespace std;
#include "db.h"
#include "flags.h"
-#include "metrics.h"
+#include "shared/metrics.h"
#include "state.pb.h"
#include "shared/timebase.h"
#include "basic_stats.h"
-#include "metrics.h"
+#include "shared/metrics.h"
#include <assert.h>
#include <sys/resource.h>
#include "decklink_output.h"
#include "decklink_util.h"
#include "flags.h"
-#include "metrics.h"
+#include "shared/metrics.h"
#include "print_latency.h"
#include "shared/timebase.h"
#include "v210_converter.h"
#include <libavformat/version.h>
-// This flag is only supported in FFmpeg 3.3 and up, and we only require 3.1.
-#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 71, 100)
-#define MUX_SKIP_TRAILER "+skip_trailer"
-#else
-#define MUX_SKIP_TRAILER ""
-#endif
-
#define OUTPUT_FREQUENCY 48000 // Currently needs to be exactly 48000, since bmusb outputs in that.
#define MAX_FPS 60
#define FAKE_FPS 25 // Must be an integer.
#define LOCAL_DUMP_SUFFIX ".nut"
#define DEFAULT_STREAM_MUX_NAME "nut" // Only for HTTP. Local dump guesses from LOCAL_DUMP_SUFFIX.
#define DEFAULT_HTTPD_PORT 9095
-#define MUX_OPTS { \
- /* Make seekable .mov files, and keep MP4 muxer from using unlimited amounts of memory. */ \
- { "movflags", "empty_moov+frag_keyframe+default_base_moof" MUX_SKIP_TRAILER }, \
- \
- /* Make for somewhat less bursty stream output when using .mov. */ \
- { "frag_duration", "125000" }, \
- \
- /* Keep nut muxer from using unlimited amounts of memory. */ \
- { "write_index", "0" } \
-}
+
+#include "shared/mux_opts.h"
// In bytes. Beware, if too small, stream clients will start dropping data.
// For mov, you want this at 10MB or so (for the reason mentioned above),
#include <sys/statfs.h>
#include <memory>
-#include "metrics.h"
+#include "shared/metrics.h"
#include "shared/timebase.h"
DiskSpaceEstimator::DiskSpaceEstimator(DiskSpaceEstimator::callback_t callback)
#include "defs.h"
#include "shared/metacube2.h"
-#include "metrics.h"
+#include "shared/metrics.h"
struct MHD_Connection;
struct MHD_Response;
#include "flags.h"
#include "ffmpeg_capture.h"
#include "mixer.h"
-#include "mux.h"
+#include "shared/mux.h"
#include "quittable_sleeper.h"
#include "shared/timebase.h"
#include "x264_encoder.h"
string video_extradata = x264_encoder->get_global_headers();
unique_ptr<Mux> mux;
- mux.reset(new Mux(avctx, global_flags.width, global_flags.height, Mux::CODEC_H264, video_extradata, audio_encoder->get_codec_parameters().get(), COARSE_TIMEBASE,
+ mux.reset(new Mux(avctx, global_flags.width, global_flags.height, Mux::CODEC_H264, video_extradata, audio_encoder->get_codec_parameters().get(),
+ get_color_space(global_flags.ycbcr_rec709_coefficients), Mux::WITH_AUDIO, COARSE_TIMEBASE,
/*write_callback=*/nullptr, Mux::WRITE_FOREGROUND, { &stream_mux_metrics }));
stream_mux_metrics.init({{ "destination", "http" }});
return mux;
'nonlinear_fader.cpp', 'context_menus.cpp', 'vu_common.cpp', 'piecewise_interpolator.cpp', 'midi_mapper.cpp']
# Auxiliary objects used for nearly everything.
-aux_srcs = ['metrics.cpp', 'flags.cpp']
+aux_srcs = ['flags.cpp']
aux = static_library('aux', aux_srcs, dependencies: nageru_deps, include_directories: nageru_include_dirs)
nageru_link_with += aux
# Streaming and encoding objects (largely the set that is shared between Nageru and Kaeru).
stream_srcs = ['quicksync_encoder.cpp', 'x264_encoder.cpp', 'x264_dynamic.cpp', 'x264_speed_control.cpp', 'video_encoder.cpp',
- 'mux.cpp', 'audio_encoder.cpp', 'ffmpeg_util.cpp', 'httpd.cpp', 'ffmpeg_capture.cpp',
+ 'audio_encoder.cpp', 'ffmpeg_util.cpp', 'httpd.cpp', 'ffmpeg_capture.cpp',
'print_latency.cpp', 'basic_stats.cpp', 'ref_counted_frame.cpp']
stream = static_library('stream', stream_srcs, dependencies: nageru_deps, include_directories: nageru_include_dirs)
nageru_link_with += stream
#include "ffmpeg_capture.h"
#include "flags.h"
#include "input_mapping.h"
-#include "metrics.h"
+#include "shared/metrics.h"
#include "pbo_frame_allocator.h"
#include "shared/ref_counted_gl_sync.h"
#include "resampling_queue.h"
+++ /dev/null
-#ifndef _MUX_H
-#define _MUX_H 1
-
-// Wrapper around an AVFormat mux.
-
-extern "C" {
-#include <libavcodec/avcodec.h>
-#include <libavformat/avformat.h>
-}
-
-#include <sys/types.h>
-#include <atomic>
-#include <condition_variable>
-#include <functional>
-#include <mutex>
-#include <string>
-#include <utility>
-#include <thread>
-#include <vector>
-
-#include "shared/timebase.h"
-
-struct MuxMetrics {
- // “written” will usually be equal video + audio + mux overhead,
- // except that there could be buffered packets that count in audio or video
- // but not yet in written.
- std::atomic<int64_t> metric_video_bytes{0}, metric_audio_bytes{0}, metric_written_bytes{0};
-
- // Registers in global_metrics.
- void init(const std::vector<std::pair<std::string, std::string>> &labels);
-
- void reset()
- {
- metric_video_bytes = 0;
- metric_audio_bytes = 0;
- metric_written_bytes = 0;
- }
-};
-
-class Mux {
-public:
- enum Codec {
- CODEC_H264,
- CODEC_NV12, // Uncompressed 4:2:0.
- };
- enum WriteStrategy {
- // add_packet() will write the packet immediately, unless plugged.
- WRITE_FOREGROUND,
-
- // All writes will happen on a separate thread, so add_packet()
- // won't block. Use this if writing to a file and you might be
- // holding a mutex (because blocking I/O with a mutex held is
- // not good). Note that this will clone every packet, so it has
- // higher overhead.
- WRITE_BACKGROUND,
- };
-
- // Takes ownership of avctx. <write_callback> will be called every time
- // a write has been made to the video stream (id 0), with the pts of
- // the just-written frame. (write_callback can be nullptr.)
- // Does not take ownership of <metrics>; elements in there, if any,
- // will be added to.
- Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const std::string &video_extradata, const AVCodecParameters *audio_codecpar, int time_base, std::function<void(int64_t)> write_callback, WriteStrategy write_strategy, const std::vector<MuxMetrics *> &metrics);
- ~Mux();
- void add_packet(const AVPacket &pkt, int64_t pts, int64_t dts, AVRational timebase = { 1, TIMEBASE }, int stream_index_override = -1);
-
- // As long as the mux is plugged, it will not actually write anything to disk,
- // just queue the packets. Once it is unplugged, the packets are reordered by pts
- // and written. This is primarily useful if you might have two different encoders
- // writing to the mux at the same time (because one is shutting down), so that
- // pts might otherwise come out-of-order.
- //
- // You can plug and unplug multiple times; only when the plug count reaches zero,
- // something will actually happen.
- void plug();
- void unplug();
-
-private:
- // If write_strategy == WRITE_FOREGORUND, Must be called with <mu> held.
- void write_packet_or_die(const AVPacket &pkt, int64_t unscaled_pts);
- void thread_func();
-
- WriteStrategy write_strategy;
-
- std::mutex mu;
-
- // These are only in use if write_strategy == WRITE_BACKGROUND.
- std::atomic<bool> writer_thread_should_quit{false};
- std::thread writer_thread;
-
- AVFormatContext *avctx; // Protected by <mu>, iff write_strategy == WRITE_BACKGROUND.
- int plug_count = 0; // Protected by <mu>.
-
- // Protected by <mu>. If write_strategy == WRITE_FOREGROUND,
- // this is only in use when plugging.
- struct QueuedPacket {
- AVPacket *pkt;
- int64_t unscaled_pts;
- };
- std::vector<QueuedPacket> packet_queue;
- std::condition_variable packet_queue_ready;
-
- AVStream *avstream_video, *avstream_audio;
-
- std::function<void(int64_t)> write_callback;
- std::vector<MuxMetrics *> metrics;
-
- friend struct PacketBefore;
-};
-
-#endif // !defined(_MUX_H)
#include "print_latency.h"
#include "flags.h"
-#include "metrics.h"
+#include "shared/metrics.h"
#include "mixer.h"
#include <stdio.h>
#include <vector>
#include "ref_counted_frame.h"
-#include "metrics.h"
+#include "shared/metrics.h"
// Since every output frame is based on multiple input frames, we need
// more than one start timestamp; one for each input.
#include "disk_space_estimator.h"
#include "shared/ffmpeg_raii.h"
#include "flags.h"
-#include "mux.h"
+#include "shared/mux.h"
#include "print_latency.h"
#include "quicksync_encoder_impl.h"
#include "ref_counted_frame.h"
{
lock_guard<mutex> lock(file_audio_encoder_mutex);
AVCodecParametersWithDeleter audio_codecpar = file_audio_encoder->get_codec_parameters();
- file_mux.reset(new Mux(avctx, frame_width, frame_height, Mux::CODEC_H264, video_extradata, audio_codecpar.get(), TIMEBASE,
+ file_mux.reset(new Mux(avctx, frame_width, frame_height, Mux::CODEC_H264, video_extradata, audio_codecpar.get(), get_color_space(global_flags.ycbcr_rec709_coefficients), Mux::WITH_AUDIO, TIMEBASE,
std::bind(&DiskSpaceEstimator::report_write, disk_space_estimator, filename, _1),
Mux::WRITE_BACKGROUND,
{ ¤t_file_mux_metrics, &total_mux_metrics }));
#include "shared/ffmpeg_raii.h"
#include "flags.h"
#include "httpd.h"
-#include "mux.h"
+#include "shared/mux.h"
#include "quicksync_encoder.h"
#include "shared/timebase.h"
#include "x264_encoder.h"
video_extradata = x264_encoder->get_global_headers();
}
- stream_mux.reset(new Mux(avctx, width, height, video_codec, video_extradata, stream_audio_encoder->get_codec_parameters().get(), COARSE_TIMEBASE,
+ stream_mux.reset(new Mux(avctx, width, height, video_codec, video_extradata, stream_audio_encoder->get_codec_parameters().get(),
+ get_color_space(global_flags.ycbcr_rec709_coefficients),
+ Mux::WITH_AUDIO, COARSE_TIMEBASE,
/*write_callback=*/nullptr, Mux::WRITE_FOREGROUND, { &stream_mux_metrics }));
stream_mux_metrics.init({{ "destination", "http" }});
}
#include <libavformat/avio.h>
}
-#include "mux.h"
+#include "shared/mux.h"
#include "shared/ref_counted_gl_sync.h"
class AudioEncoder;
#include "defs.h"
#include "flags.h"
-#include "metrics.h"
-#include "mux.h"
+#include "shared/metrics.h"
+#include "shared/mux.h"
#include "print_latency.h"
#include "shared/timebase.h"
#include "x264_dynamic.h"
#include <movit/image_format.h>
#include "defs.h"
-#include "metrics.h"
+#include "shared/metrics.h"
#include "print_latency.h"
#include "x264_dynamic.h"
#include <type_traits>
#include "flags.h"
-#include "metrics.h"
+#include "shared/metrics.h"
using namespace std;
using namespace std::chrono;
#include <x264.h>
}
-#include "metrics.h"
+#include "shared/metrics.h"
#include "x264_dynamic.h"
class X264SpeedControl {
-srcs = ['memcpy_interleaved.cpp', 'metacube2.cpp', 'ffmpeg_raii.cpp']
+srcs = ['memcpy_interleaved.cpp', 'metacube2.cpp', 'ffmpeg_raii.cpp', 'mux.cpp', 'metrics.cpp']
shared = static_library('shared', srcs, include_directories: top_include)
shareddep = declare_dependency(
include_directories: top_include,
- link_with: shared,
- sources: srcs)
+ link_with: shared)
-#include "metrics.h"
+#include "shared/metrics.h"
#include <assert.h>
#include <math.h>
-#include "mux.h"
+#include "shared/mux.h"
+#include <algorithm>
#include <assert.h>
+#include <mutex>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <algorithm>
-#include <mutex>
#include <string>
#include <utility>
#include <vector>
#include <libavutil/rational.h>
}
-#include "defs.h"
-#include "flags.h"
-#include "metrics.h"
+#include "shared/metrics.h"
+#include "shared/mux_opts.h"
#include "shared/timebase.h"
using namespace std;
const AVFormatContext * const ctx;
};
-Mux::Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const string &video_extradata, const AVCodecParameters *audio_codecpar, int time_base, std::function<void(int64_t)> write_callback, WriteStrategy write_strategy, const vector<MuxMetrics *> &metrics)
+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<void(int64_t)> write_callback, WriteStrategy write_strategy, const vector<MuxMetrics *> &metrics)
: write_strategy(write_strategy), avctx(avctx), write_callback(write_callback), metrics(metrics)
{
avstream_video = avformat_new_stream(avctx, nullptr);
avstream_video->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
if (video_codec == CODEC_H264) {
avstream_video->codecpar->codec_id = AV_CODEC_ID_H264;
- } else {
- assert(video_codec == CODEC_NV12);
+ } 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 {
+ assert(video_codec == CODEC_MJPEG);
+ avstream_video->codecpar->codec_id = AV_CODEC_ID_MJPEG;
}
avstream_video->codecpar->width = width;
avstream_video->codecpar->height = height;
avstream_video->codecpar->color_primaries = AVCOL_PRI_BT709; // RGB colorspace (inout_format.color_space).
avstream_video->codecpar->color_trc = AVCOL_TRC_IEC61966_2_1; // Gamma curve (inout_format.gamma_curve).
// YUV colorspace (output_ycbcr_format.luma_coefficients).
- if (global_flags.ycbcr_rec709_coefficients) {
- avstream_video->codecpar->color_space = AVCOL_SPC_BT709;
- } else {
- avstream_video->codecpar->color_space = AVCOL_SPC_SMPTE170M;
- }
+ avstream_video->codecpar->color_space = color_space;
avstream_video->codecpar->color_range = AVCOL_RANGE_MPEG; // Full vs. limited range (output_ycbcr_format.full_range).
avstream_video->codecpar->chroma_location = AVCHROMA_LOC_LEFT; // Chroma sample location. See chroma_offset_0[] in Mixer::subsample_chroma().
avstream_video->codecpar->field_order = AV_FIELD_PROGRESSIVE;
memcpy(avstream_video->codecpar->extradata, video_extradata.data(), video_extradata.size());
}
- avstream_audio = avformat_new_stream(avctx, nullptr);
- if (avstream_audio == nullptr) {
- fprintf(stderr, "avformat_new_stream() failed\n");
- exit(1);
- }
- 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);
+ if (with_audio == WITH_AUDIO) {
+ avstream_audio = avformat_new_stream(avctx, nullptr);
+ if (avstream_audio == nullptr) {
+ fprintf(stderr, "avformat_new_stream() failed\n");
+ exit(1);
+ }
+ 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);
+ }
+ } else {
+ assert(with_audio == WITHOUT_AUDIO);
+ avstream_audio = nullptr;
}
AVDictionary *options = NULL;
lock_guard<mutex> lock(mu);
if (write_strategy == WriteStrategy::WRITE_BACKGROUND) {
packet_queue.push_back(QueuedPacket{ av_packet_clone(&pkt_copy), pts });
- if (plug_count == 0) packet_queue_ready.notify_all();
+ if (plug_count == 0)
+ packet_queue_ready.notify_all();
} else if (plug_count > 0) {
packet_queue.push_back(QueuedPacket{ av_packet_clone(&pkt_copy), pts });
} else {
int64_t old_pos = avctx->pb->pos;
if (av_interleaved_write_frame(avctx, const_cast<AVPacket *>(&pkt)) < 0) {
fprintf(stderr, "av_interleaved_write_frame() failed\n");
- exit(1);
+ abort();
}
avio_flush(avctx->pb);
for (MuxMetrics *metric : metrics) {
void Mux::thread_func()
{
+ pthread_setname_np(pthread_self(), "Mux");
+
unique_lock<mutex> lock(mu);
for ( ;; ) {
packet_queue_ready.wait(lock, [this]() {
}
};
+inline AVColorSpace get_color_space(bool ycbcr_rec709_coefficients)
+{
+ if (ycbcr_rec709_coefficients) {
+ return AVCOL_SPC_BT709;
+ } else {
+ return AVCOL_SPC_SMPTE170M;
+ }
+}
+
class Mux {
public:
enum Codec {
CODEC_NV12, // Uncompressed 4:2:0.
CODEC_MJPEG
};
+ enum WithAudio {
+ WITH_AUDIO,
+ WITHOUT_AUDIO
+ };
enum WriteStrategy {
// add_packet() will write the packet immediately, unless plugged.
WRITE_FOREGROUND,
// the just-written frame. (write_callback can be nullptr.)
// Does not take ownership of <metrics>; elements in there, if any,
// will be added to.
- Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const std::string &video_extradata, const AVCodecParameters *audio_codecpar, int time_base, std::function<void(int64_t)> write_callback, WriteStrategy write_strategy, const std::vector<MuxMetrics *> &metrics);
+ Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const std::string &video_extradata, const AVCodecParameters *audio_codecpar, AVColorSpace color_space, WithAudio with_audio, int time_base, std::function<void(int64_t)> write_callback, WriteStrategy write_strategy, const std::vector<MuxMetrics *> &metrics);
~Mux();
void add_packet(const AVPacket &pkt, int64_t pts, int64_t dts, AVRational timebase = { 1, TIMEBASE }, int stream_index_override = -1);
--- /dev/null
+#ifndef _MUX_OPTS_H
+#define _MUX_OPTS_H 1
+
+// This flag is only supported in FFmpeg 3.3 and up, and we only require 3.1.
+#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(57, 71, 100)
+#define MUX_SKIP_TRAILER "+skip_trailer"
+#else
+#define MUX_SKIP_TRAILER ""
+#endif
+
+#define MUX_OPTS { \
+ /* Make seekable .mov files, and keep MP4 muxer from using unlimited amounts of memory. */ \
+ { "movflags", "empty_moov+frag_keyframe+default_base_moof" MUX_SKIP_TRAILER }, \
+ \
+ /* Make for somewhat less bursty stream output when using .mov. */ \
+ { "frag_duration", "125000" }, \
+ \
+ /* Keep nut muxer from using unlimited amounts of memory. */ \
+ { "write_index", "0" } \
+}
+
+#endif // !defined(_MUX_OPTS_H)