]> git.sesse.net Git - nageru/commitdiff
Support AV1 streaming over HTTP, via SVT-AV1.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Thu, 21 Jul 2022 14:45:02 +0000 (16:45 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Thu, 21 Jul 2022 14:45:02 +0000 (16:45 +0200)
This is optional, and pretty rough currently. But it might be interesting
for certain use cases, in particular around 1080p or 4K streaming.

13 files changed:
README
meson.build
nageru/av1_encoder.cpp [new file with mode: 0644]
nageru/av1_encoder.h [new file with mode: 0644]
nageru/defs.h
nageru/flags.cpp
nageru/flags.h
nageru/quicksync_encoder.cpp
nageru/quicksync_encoder_impl.h
nageru/video_encoder.cpp
nageru/video_encoder.h
shared/mux.cpp
shared/mux.h

diff --git a/README b/README
index a10fe94f441137014b8e31fcd6318368e6ea0291..a565b3984775a0417e732b67c8e37a1eabeb4d1e 100644 (file)
--- a/README
+++ b/README
@@ -98,6 +98,10 @@ Nageru currently needs:
    If you build with libsrt, make sure it is not linked to OpenSSL,
    for license reasons.
 
    If you build with libsrt, make sure it is not linked to OpenSSL,
    for license reasons.
 
+ - Optional: SVT-AV1, for encoding high-quality video suitable for streaming to
+   end users (higher quality than using x264, but not nearly as mature).
+   You will need at least version 1.0.0.
+
 
 Futatabi also needs:
 
 
 Futatabi also needs:
 
@@ -125,6 +129,9 @@ Exceptions as of July 2022:
 
      meson obj -Dcef_dir=/usr/lib/x86_64-linux-gnu/cef -Dcef_build_type=system -Dcef_no_icudtl=true
 
 
      meson obj -Dcef_dir=/usr/lib/x86_64-linux-gnu/cef -Dcef_build_type=system -Dcef_no_icudtl=true
 
+  - Debian's SVT-AV1 is too old, so you will need to compile it yourself
+    if you wish to use it for streaming.
+
 
 The patches/ directory contains a patch that helps zita-resampler performance.
 It is meant for upstream, but was not in at the time Nageru was released.
 
 The patches/ directory contains a patch that helps zita-resampler performance.
 It is meant for upstream, but was not in at the time Nageru was released.
index 4f96702dbad8e980e4fc744e7f15ba568f57a442..a7ea6ae2eb770ebc4cb266eb3d00cc79da495868 100644 (file)
@@ -27,6 +27,7 @@ qt5deps = dependency('qt5', modules: ['Core', 'Gui', 'Widgets', 'OpenGLExtension
 sdl2_imagedep = dependency('SDL2_image', required: false)
 sdl2dep = dependency('sdl2', required: false)
 srtdep = dependency('srt', required: false)
 sdl2_imagedep = dependency('SDL2_image', required: false)
 sdl2dep = dependency('sdl2', required: false)
 srtdep = dependency('srt', required: false)
+svtav1dep = dependency('SvtAv1Enc', required: false, version: '>=1.0.0')
 sqlite3dep = dependency('sqlite3')
 threaddep = dependency('threads')
 vadrmdep = dependency('libva-drm')
 sqlite3dep = dependency('sqlite3')
 threaddep = dependency('threads')
 vadrmdep = dependency('libva-drm')
@@ -63,6 +64,9 @@ if srtdep.found()
        # or gnutls libsrt, so we cannot check license compatibility here.
        add_project_arguments('-DHAVE_SRT=1', language: 'cpp')
 endif
        # or gnutls libsrt, so we cannot check license compatibility here.
        add_project_arguments('-DHAVE_SRT=1', language: 'cpp')
 endif
+if svtav1dep.found()
+       add_project_arguments('-DHAVE_AV1=1', language: 'cpp')
+endif
 
 top_include = include_directories('.')
 
 
 top_include = include_directories('.')
 
@@ -73,7 +77,7 @@ subdir('shared')
 nageru_srcs = []
 nageru_deps = [shareddep, qt5deps, libjpegdep, movitdep, protobufdep,
        vax11dep, vadrmdep, x11dep, libavformatdep, libswresampledep, libavcodecdep, libavutildep,
 nageru_srcs = []
 nageru_deps = [shareddep, qt5deps, libjpegdep, movitdep, protobufdep,
        vax11dep, vadrmdep, x11dep, libavformatdep, libswresampledep, libavcodecdep, libavutildep,
-       libswscaledep, libusbdep, luajitdep, dldep, x264dep, alsadep, zitaresamplerdep,
+       libswscaledep, libusbdep, luajitdep, dldep, x264dep, svtav1dep, alsadep, zitaresamplerdep,
        qcustomplotdep, threaddep, eigendep, srtdep, libdrmdep]
 nageru_include_dirs = [include_directories('nageru')]
 nageru_link_with = []
        qcustomplotdep, threaddep, eigendep, srtdep, libdrmdep]
 nageru_include_dirs = [include_directories('nageru')]
 nageru_link_with = []
@@ -212,10 +216,14 @@ nageru_srcs += ['nageru/chroma_subsampler.cpp', 'nageru/v210_converter.cpp', 'na
        'nageru/timecode_renderer.cpp', 'nageru/tweaked_inputs.cpp', 'nageru/mjpeg_encoder.cpp']
 
 # Streaming and encoding objects (largely the set that is shared between Nageru and Kaeru).
        'nageru/timecode_renderer.cpp', 'nageru/tweaked_inputs.cpp', 'nageru/mjpeg_encoder.cpp']
 
 # Streaming and encoding objects (largely the set that is shared between Nageru and Kaeru).
-stream_srcs = ['nageru/quicksync_encoder.cpp', 'nageru/x264_encoder.cpp', 'nageru/x264_dynamic.cpp', 'nageru/x264_speed_control.cpp', 'nageru/video_encoder.cpp',
+stream_srcs = ['nageru/quicksync_encoder.cpp', 'nageru/video_encoder.cpp',
+       'nageru/x264_encoder.cpp', 'nageru/x264_dynamic.cpp', 'nageru/x264_speed_control.cpp',
        'nageru/audio_encoder.cpp', 'nageru/ffmpeg_util.cpp', 'nageru/ffmpeg_capture.cpp',
        'nageru/print_latency.cpp', 'nageru/basic_stats.cpp', 'nageru/ref_counted_frame.cpp',
        'nageru/v4l_output.cpp']
        'nageru/audio_encoder.cpp', 'nageru/ffmpeg_util.cpp', 'nageru/ffmpeg_capture.cpp',
        'nageru/print_latency.cpp', 'nageru/basic_stats.cpp', 'nageru/ref_counted_frame.cpp',
        'nageru/v4l_output.cpp']
+if svtav1dep.found()
+       stream_srcs += 'nageru/av1_encoder.cpp'
+endif
 stream = static_library('stream', stream_srcs, dependencies: nageru_deps, include_directories: nageru_include_dirs)
 nageru_link_with += stream
 
 stream = static_library('stream', stream_srcs, dependencies: nageru_deps, include_directories: nageru_include_dirs)
 nageru_link_with += stream
 
diff --git a/nageru/av1_encoder.cpp b/nageru/av1_encoder.cpp
new file mode 100644 (file)
index 0000000..a0dbdd1
--- /dev/null
@@ -0,0 +1,375 @@
+#include "av1_encoder.h"
+
+#include <assert.h>
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <atomic>
+#include <cstdint>
+#include <functional>
+#include <mutex>
+
+#include <EbSvtAv1.h>
+#include <EbSvtAv1Enc.h>
+
+#include "defs.h"
+#include "flags.h"
+#include "shared/metrics.h"
+#include "shared/mux.h"
+#include "print_latency.h"
+#include "shared/timebase.h"
+#include "shared/memcpy_interleaved.h"
+
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+}
+
+using namespace movit;
+using namespace std;
+using namespace std::chrono;
+using namespace std::placeholders;
+
+namespace {
+
+// AV1Encoder can be restarted if --record-av1-video is set, so make these
+// metrics global.
+atomic<int64_t> metric_av1_queued_frames{0};
+atomic<int64_t> metric_av1_max_queued_frames{AV1_QUEUE_LENGTH};
+atomic<int64_t> metric_av1_dropped_frames{0};
+atomic<int64_t> metric_av1_output_frames_i{0};
+atomic<int64_t> metric_av1_output_frames_p{0};
+Histogram metric_av1_qp;
+LatencyHistogram av1_latency_histogram;
+
+once_flag av1_metrics_inited;
+
+}  // namespace
+
+AV1Encoder::AV1Encoder(const AVOutputFormat *oformat)
+       : wants_global_headers(oformat->flags & AVFMT_GLOBALHEADER)
+{
+               call_once(av1_metrics_inited, []{
+                       global_metrics.add("av1_queued_frames",  {}, &metric_av1_queued_frames, Metrics::TYPE_GAUGE);
+                       global_metrics.add("av1_max_queued_frames", {},  &metric_av1_max_queued_frames, Metrics::TYPE_GAUGE);
+                       global_metrics.add("av1_dropped_frames", {},  &metric_av1_dropped_frames);
+                       global_metrics.add("av1_output_frames", {{ "type", "i" }}, &metric_av1_output_frames_i);
+                       global_metrics.add("av1_output_frames", {{ "type", "p" }}, &metric_av1_output_frames_p);
+
+                       metric_av1_qp.init_uniform(50);
+                       global_metrics.add("av1_qp", {}, &metric_av1_qp);
+                       av1_latency_histogram.init("av1");
+               });
+
+       const size_t bytes_per_pixel = 1;  // TODO: 10-bit support.
+       frame_pool.reset(new uint8_t[global_flags.width * global_flags.height * 2 * bytes_per_pixel * AV1_QUEUE_LENGTH]);
+       for (unsigned i = 0; i < AV1_QUEUE_LENGTH; ++i) {
+               free_frames.push(frame_pool.get() + i * (global_flags.width * global_flags.height * 2 * bytes_per_pixel));
+       }
+       encoder_thread = thread(&AV1Encoder::encoder_thread_func, this);
+}
+
+AV1Encoder::~AV1Encoder()
+{
+       should_quit = true;
+       queued_frames_nonempty.notify_all();
+       encoder_thread.join();
+}
+
+void AV1Encoder::add_frame(int64_t pts, int64_t duration, YCbCrLumaCoefficients ycbcr_coefficients, const uint8_t *data, const ReceivedTimestamps &received_ts)
+{
+       assert(!should_quit);
+
+       QueuedFrame qf;
+       qf.pts = pts;
+       qf.duration = duration;
+       qf.ycbcr_coefficients = ycbcr_coefficients;
+       qf.received_ts = received_ts;
+
+       {
+               lock_guard<mutex> lock(mu);
+               if (free_frames.empty()) {
+                       fprintf(stderr, "WARNING: AV1 queue full, dropping frame with pts %" PRId64 "\n", pts);
+                       ++metric_av1_dropped_frames;
+                       return;
+               }
+
+               qf.data = free_frames.front();
+               free_frames.pop();
+       }
+
+       // Since we're copying anyway, we can unpack from NV12 to fully planar on the fly.
+       // SVT-AV1 makes its own copy, though, and it would have been nice to avoid the
+       // double-copy.
+       size_t bytes_per_pixel = 1;  // TODO: 10-bit support.
+       size_t frame_size = global_flags.width * global_flags.height * bytes_per_pixel;
+       assert(global_flags.width % 2 == 0);
+       assert(global_flags.height % 2 == 0);
+       uint8_t *y = qf.data;   
+       uint8_t *cb = y + frame_size;
+       uint8_t *cr = cb + frame_size / 4;
+       memcpy(y, data, frame_size);
+       memcpy_interleaved(cb, cr, data + frame_size, frame_size / 2);
+
+       {
+               lock_guard<mutex> lock(mu);
+               queued_frames.push(qf);
+               queued_frames_nonempty.notify_all();
+               metric_av1_queued_frames = queued_frames.size();
+       }
+}
+       
+void AV1Encoder::init_av1()
+{
+       EbSvtAv1EncConfiguration config;
+       EbErrorType ret = svt_av1_enc_init_handle(&encoder, nullptr, &config);
+       if (ret != EB_ErrorNone) {
+               fprintf(stderr, "Error initializing SVT-AV1 handle (error %08x)\n", ret);
+               exit(EXIT_FAILURE);
+       }
+
+       config.enc_mode = global_flags.av1_preset;
+       config.intra_period_length = 63;  // Approx. one second, conforms to the (n % 8) - 1 == 0 rule.
+       config.source_width = global_flags.width;
+       config.source_height = global_flags.height;
+       config.frame_rate_numerator = global_flags.av1_fps_num;
+       config.frame_rate_denominator = global_flags.av1_fps_den;
+       config.encoder_bit_depth = 8;  // TODO: 10-bit support.
+       config.rate_control_mode = 2;  // CBR.
+       config.pred_structure = 1;  // PRED_LOW_DELAY_B (needed for CBR).
+       config.target_bit_rate = global_flags.av1_bitrate * 1000;
+
+       // NOTE: These should be in sync with the ones in quicksync_encoder.cpp (sps_rbsp()).
+       config.color_primaries = EB_CICP_CP_BT_709;
+       config.transfer_characteristics = EB_CICP_TC_SRGB;
+       if (global_flags.ycbcr_rec709_coefficients) {
+               config.matrix_coefficients = EB_CICP_MC_BT_709;
+       } else {
+               config.matrix_coefficients = EB_CICP_MC_BT_601;
+       }
+       config.color_range = EB_CR_STUDIO_RANGE;
+#if SVT_AV1_CHECK_VERSION(1, 0, 0)
+       config.chroma_sample_position = EB_CSP_VERTICAL;
+#endif
+
+       const vector<string> &extra_param = global_flags.av1_extra_param;
+       for (const string &str : extra_param) {
+               const size_t pos = str.find(',');
+               if (pos == string::npos) {
+                       if (svt_av1_enc_parse_parameter(&config, str.c_str(), nullptr) != EB_ErrorNone) {
+                               fprintf(stderr, "ERROR: SVT-AV1 rejected parameter '%s' with no value\n", str.c_str());
+                               exit(EXIT_FAILURE);
+                       }
+               } else {
+                       const string key = str.substr(0, pos);
+                       const string value = str.substr(pos + 1);
+                       if (svt_av1_enc_parse_parameter(&config, key.c_str(), value.c_str()) != EB_ErrorNone) {
+                               fprintf(stderr, "ERROR: SVT-AV1 rejected parameter '%s' set to '%s'\n",
+                                       key.c_str(), value.c_str());
+                               exit(EXIT_FAILURE);
+                       }
+               }
+       }
+       
+       ret = svt_av1_enc_set_parameter(encoder, &config);
+       if (ret != EB_ErrorNone) {
+               fprintf(stderr, "Error configuring SVT-AV1 (error %08x)\n", ret);
+               exit(EXIT_FAILURE);
+       }
+
+       ret = svt_av1_enc_init(encoder);
+       if (ret != EB_ErrorNone) {
+               fprintf(stderr, "Error initializing SVT-AV1 (error %08x)\n", ret);
+               exit(EXIT_FAILURE);
+       }
+
+       if (wants_global_headers) {
+               EbBufferHeaderType *header = NULL;
+
+               ret = svt_av1_enc_stream_header(encoder, &header);
+               if (ret != EB_ErrorNone) {
+                       fprintf(stderr, "Error building SVT-AV1 header (error %08x)\n", ret);
+                       exit(EXIT_FAILURE);
+               }
+               
+               global_headers = string(reinterpret_cast<const char *>(header->p_buffer), header->n_filled_len);
+
+               svt_av1_enc_stream_header_release(header);  // Don't care about errors.
+          }
+}
+
+void AV1Encoder::encoder_thread_func()
+{
+       if (nice(5) == -1) {
+               perror("nice()");
+               // No exit; it's not fatal.
+       }
+       pthread_setname_np(pthread_self(), "AV1_encode");
+       init_av1();
+       av1_init_done = true;
+
+       bool frames_left;
+
+       do {
+               QueuedFrame qf;
+
+               // Wait for a queued frame, then dequeue it.
+               {
+                       unique_lock<mutex> lock(mu);
+                       queued_frames_nonempty.wait(lock, [this]() { return !queued_frames.empty() || should_quit; });
+                       if (!queued_frames.empty()) {
+                               qf = queued_frames.front();
+                               queued_frames.pop();
+                       } else {
+                               qf.pts = -1;
+                               qf.duration = -1;
+                               qf.data = nullptr;
+                       }
+
+                       metric_av1_queued_frames = queued_frames.size();
+                       frames_left = !queued_frames.empty();
+               }
+
+               encode_frame(qf);
+               
+               {
+                       lock_guard<mutex> lock(mu);
+                       free_frames.push(qf.data);
+               }
+
+               // We should quit only if the should_quit flag is set _and_ we have nothing
+               // in our queue.
+       } while (!should_quit || frames_left);
+
+       // Signal end of stream.
+       EbBufferHeaderType hdr;
+       hdr.n_alloc_len   = 0;
+       hdr.n_filled_len  = 0;
+       hdr.n_tick_count  = 0;
+       hdr.p_app_private = nullptr;
+       hdr.pic_type      = EB_AV1_INVALID_PICTURE;
+       hdr.p_buffer      = nullptr;
+       hdr.metadata      = nullptr;
+       hdr.flags         = EB_BUFFERFLAG_EOS;
+       svt_av1_enc_send_picture(encoder, &hdr);
+
+       bool seen_eof = false;
+       do {
+               EbBufferHeaderType *buf;
+               EbErrorType ret = svt_av1_enc_get_packet(encoder, &buf, /*pic_send_done=*/true);
+               if (ret == EB_NoErrorEmptyQueue) {
+                       assert(false);
+               }
+               seen_eof = (buf->flags & EB_BUFFERFLAG_EOS);
+               process_packet(buf);
+       } while (!seen_eof);
+
+       svt_av1_enc_deinit(encoder);
+       svt_av1_enc_deinit_handle(encoder);
+}
+
+void AV1Encoder::encode_frame(AV1Encoder::QueuedFrame qf)
+{
+       if (qf.data) {
+               EbSvtIOFormat pic;
+               pic.luma = qf.data;     
+               pic.cb = pic.luma + global_flags.width * global_flags.height;
+               pic.cr = pic.cb + global_flags.width * global_flags.height / 4;
+               pic.y_stride = global_flags.width;
+               pic.cb_stride = global_flags.width / 2;
+               pic.cr_stride = global_flags.width / 2;
+               pic.width = global_flags.width;
+               pic.height = global_flags.height;
+               pic.origin_x = 0;
+               pic.origin_y = 0;
+               pic.color_fmt = EB_YUV420;
+               pic.bit_depth = EB_EIGHT_BIT;  // TODO: 10-bit.
+
+               EbBufferHeaderType hdr;
+               hdr.p_buffer      = reinterpret_cast<uint8_t *>(&pic);
+               hdr.n_alloc_len   = global_flags.width * global_flags.height * 3 / 2;  // TODO: 10-bit.
+               hdr.n_filled_len  = hdr.n_alloc_len;
+               hdr.n_tick_count  = 0;
+               hdr.p_app_private = reinterpret_cast<void *>(intptr_t(qf.duration));
+               hdr.pic_type      = EB_AV1_INVALID_PICTURE;  // Actually means auto, according to FFmpeg.
+               hdr.metadata      = nullptr;
+               hdr.flags         = 0;
+               hdr.pts           = av_rescale_q(qf.pts, AVRational{ 1, TIMEBASE }, AVRational{ global_flags.av1_fps_den, global_flags.av1_fps_num });
+               if (hdr.pts <= last_pts) {
+                       fprintf(stderr, "WARNING: Receiving frames faster than given --av1-fps value (%d/%d); dropping frame.\n",
+                               global_flags.av1_fps_num, global_flags.av1_fps_den);
+               } else {
+                       svt_av1_enc_send_picture(encoder, &hdr);
+                       frames_being_encoded[hdr.pts] = qf.received_ts;
+                       last_pts = hdr.pts;
+               }
+       }
+
+       for ( ;; ) {
+               EbBufferHeaderType *buf;
+               EbErrorType ret = svt_av1_enc_get_packet(encoder, &buf, /*pic_send_done=*/false);
+               if (ret == EB_NoErrorEmptyQueue) {
+                       return;
+               }
+               process_packet(buf);
+       }
+}
+
+void AV1Encoder::process_packet(EbBufferHeaderType *buf)
+{
+       if (buf->n_filled_len == 0) {
+               // TODO: Can this ever happen?
+               svt_av1_enc_release_out_buffer(&buf);
+               return;
+       }
+
+       switch (buf->pic_type) {
+               case EB_AV1_KEY_PICTURE:
+               case EB_AV1_INTRA_ONLY_PICTURE:
+                       ++metric_av1_output_frames_i;
+                       break;
+               case EB_AV1_INTER_PICTURE:  // We don't really know whether it's P or B.
+                       ++metric_av1_output_frames_p;
+                       break;
+               default:
+                       break;
+       }
+       metric_av1_qp.count_event(buf->qp);
+
+       if (frames_being_encoded.count(buf->pts)) {
+               ReceivedTimestamps received_ts = frames_being_encoded[buf->pts];
+               frames_being_encoded.erase(buf->pts);
+
+               static int frameno = 0;
+               print_latency("Current AV1 latency (video inputs → network mux):",
+                               received_ts, /*b_frame=*/false, &frameno, &av1_latency_histogram);
+       } else {
+               assert(false);
+       }
+
+       AVPacket pkt;
+       memset(&pkt, 0, sizeof(pkt));
+       pkt.buf = nullptr;
+       pkt.data = buf->p_buffer;
+       pkt.size = buf->n_filled_len;
+       pkt.stream_index = 0;
+       if (buf->pic_type == EB_AV1_KEY_PICTURE) {
+               pkt.flags = AV_PKT_FLAG_KEY;
+       } else if (buf->pic_type == EB_AV1_NON_REF_PICTURE) {
+               // I have no idea if this does anything in practice,
+               // but the libavcodec plugin does it.
+               pkt.flags = AV_PKT_FLAG_DISPOSABLE;
+       } else {
+               pkt.flags = 0;
+       }
+       pkt.pts = av_rescale_q(buf->pts, AVRational{ global_flags.av1_fps_den, global_flags.av1_fps_num }, AVRational{ 1, TIMEBASE });
+       pkt.dts = av_rescale_q(buf->dts, AVRational{ global_flags.av1_fps_den, global_flags.av1_fps_num }, AVRational{ 1, TIMEBASE });
+
+       for (Mux *mux : muxes) {
+               mux->add_packet(pkt, pkt.pts, pkt.dts);
+       }
+
+       svt_av1_enc_release_out_buffer(&buf);
+}
diff --git a/nageru/av1_encoder.h b/nageru/av1_encoder.h
new file mode 100644 (file)
index 0000000..3431595
--- /dev/null
@@ -0,0 +1,120 @@
+// An AV1 encoder using the SVT-AV1 encoder library. (libaom does not seem
+// to be suitable for real-time streaming as of 2022, as it is not using
+// threads particularly efficiently.) AV1 is a newer format than H.264,
+// obviously both for better and for worse: Higher coding efficiency
+// (with sufficient amount of cores, SVT-AV1 is even better than x264
+// at higher frame rates), but generally smaller ecosystem, no speed
+// control, less sophisticated rate control, etc..
+//
+// We don't support storing AV1 to disk currently, only to HTTP.
+// We also don't support hardware AV1 encoding, as hardware supporting it
+// is very rare currently.
+//
+// Since SVT-AV1 does not support VFR, you need to declare the frame rate
+// up-front, using the --av1-framerate flag. If the given frame rate is
+// too high (ie., you are producing frames too slowly), rate control will get
+// confused and use too little bitrate. If it is too low, Nageru will need to
+// drop frames before input to the encoder.
+
+#ifndef _AV1_ENCODER_H
+#define _AV1_ENCODER_H 1
+
+#include <sched.h>
+#include <stdint.h>
+#include <atomic>
+#include <condition_variable>
+#include <memory>
+#include <mutex>
+#include <queue>
+#include <string>
+#include <thread>
+#include <unordered_map>
+#include <vector>
+
+extern "C" {
+#include <libavformat/avformat.h>
+}
+
+#include <movit/image_format.h>
+
+#include "defs.h"
+#include "shared/metrics.h"
+#include "print_latency.h"
+#include "video_codec_interface.h"
+
+class Mux;
+struct EbBufferHeaderType;
+struct EbComponentType;
+
+class AV1Encoder : public VideoCodecInterface {
+public:
+       AV1Encoder(const AVOutputFormat *oformat);  // Does not take ownership.
+
+       // Called after the last frame. Will block; once this returns,
+       // the last data is flushed.
+       ~AV1Encoder() override;
+
+       // Must be called before first frame. Does not take ownership.
+       void add_mux(Mux *mux) override { muxes.push_back(mux); }
+
+       // <data> is taken to be raw NV12 data of WIDTHxHEIGHT resolution.
+       // Does not block.
+       void add_frame(int64_t pts, int64_t duration, movit::YCbCrLumaCoefficients ycbcr_coefficients, const uint8_t *data, const ReceivedTimestamps &received_ts) override;
+
+       std::string get_global_headers() const override {
+               while (!av1_init_done) {
+                       sched_yield();
+               }
+               return global_headers;
+       }
+
+private:
+       struct QueuedFrame {
+               int64_t pts, duration;
+               movit::YCbCrLumaCoefficients ycbcr_coefficients;
+               uint8_t *data;
+               ReceivedTimestamps received_ts;
+       };
+       void encoder_thread_func();
+       void init_av1();
+       void encode_frame(QueuedFrame qf);
+       void process_packet(EbBufferHeaderType *buf);  // Takes ownership.
+
+       // One big memory chunk of all 50 (or whatever) frames, allocated in
+       // the constructor. All data functions just use pointers into this
+       // pool.
+       std::unique_ptr<uint8_t[]> frame_pool;
+
+       std::vector<Mux *> muxes;
+       const bool wants_global_headers;
+
+       std::string global_headers;
+
+       std::thread encoder_thread;
+       std::atomic<bool> av1_init_done{false};
+       std::atomic<bool> should_quit{false};
+       EbComponentType *encoder;
+
+       int64_t last_pts = -1;
+
+       // Protects everything below it.
+       std::mutex mu;
+
+       // Frames that are not being encoded or waiting to be encoded,
+       // so that add_frame() can use new ones.
+       // TODO: Do we actually need this queue, or is SVT-AV1's queue
+       // non-blocking so that we can drop it?
+       std::queue<uint8_t *> free_frames;
+
+       // Frames that are waiting to be encoded (ie., add_frame() has been
+       // called, but they are not picked up for encoding yet).
+       std::queue<QueuedFrame> queued_frames;
+
+       // Whenever the state of <queued_frames> changes.
+       std::condition_variable queued_frames_nonempty;
+
+       // Key is the pts of the frame.
+       std::unordered_map<int64_t, ReceivedTimestamps> frames_being_encoded;
+};
+
+#endif  // !defined(_AV1_ENCODER_H)
index eb664d0ab113dc13f11982bb6446ff51ff0f26ec..41ee96a617a45fa452ce3e2c0681bf14a56a358c 100644 (file)
@@ -15,6 +15,7 @@
 #define AUDIO_OUTPUT_CODEC_NAME "pcm_s32le"
 #define DEFAULT_AUDIO_OUTPUT_BIT_RATE 0
 #define DEFAULT_X264_OUTPUT_BIT_RATE 4500  // 5 Mbit after making room for some audio and TCP overhead.
 #define AUDIO_OUTPUT_CODEC_NAME "pcm_s32le"
 #define DEFAULT_AUDIO_OUTPUT_BIT_RATE 0
 #define DEFAULT_X264_OUTPUT_BIT_RATE 4500  // 5 Mbit after making room for some audio and TCP overhead.
+#define DEFAULT_AV1_OUTPUT_BIT_RATE 4500  // Same.
 
 #define LOCAL_DUMP_PREFIX "record-"
 #define LOCAL_DUMP_SUFFIX ".nut"
 
 #define LOCAL_DUMP_PREFIX "record-"
 #define LOCAL_DUMP_SUFFIX ".nut"
 // In number of frames. Comes in addition to any internal queues in x264
 // (frame threading, lookahead, etc.).
 #define X264_QUEUE_LENGTH 50
 // In number of frames. Comes in addition to any internal queues in x264
 // (frame threading, lookahead, etc.).
 #define X264_QUEUE_LENGTH 50
+#define AV1_QUEUE_LENGTH 50
 
 #define X264_DEFAULT_PRESET "ultrafast"
 #define X264_DEFAULT_TUNE "film"
 
 
 #define X264_DEFAULT_PRESET "ultrafast"
 #define X264_DEFAULT_TUNE "film"
 
+#define DEFAULT_AV1_PRESET 12
+#define DEFAULT_AV1_FPS_NUM 60000
+#define DEFAULT_AV1_FPS_DEN 1001
+
 #endif  // !defined(_DEFS_H)
 #endif  // !defined(_DEFS_H)
index 42fdb88b22a1b2639d8cf7c954b1284f7ec7ed8d..2bd0eeca0aa183b01f13c4ba03dfbb1ecf831d17 100644 (file)
@@ -23,6 +23,7 @@ enum LongOption {
        OPTION_V4L_OUTPUT,
        OPTION_HTTP_UNCOMPRESSED_VIDEO,
        OPTION_HTTP_X264_VIDEO,
        OPTION_V4L_OUTPUT,
        OPTION_HTTP_UNCOMPRESSED_VIDEO,
        OPTION_HTTP_X264_VIDEO,
+       OPTION_HTTP_AV1_VIDEO,
        OPTION_RECORD_X264_VIDEO,
        OPTION_SEPARATE_X264_DISK_ENCODE,
        OPTION_X264_PRESET,
        OPTION_RECORD_X264_VIDEO,
        OPTION_SEPARATE_X264_DISK_ENCODE,
        OPTION_X264_PRESET,
@@ -39,6 +40,10 @@ enum LongOption {
        OPTION_X264_SEPARATE_DISK_BITRATE,
        OPTION_X264_SEPARATE_DISK_CRF,
        OPTION_X264_SEPARATE_DISK_PARAM,
        OPTION_X264_SEPARATE_DISK_BITRATE,
        OPTION_X264_SEPARATE_DISK_CRF,
        OPTION_X264_SEPARATE_DISK_PARAM,
+       OPTION_AV1_PRESET,
+       OPTION_AV1_BITRATE,
+       OPTION_AV1_FPS,
+       OPTION_AV1_PARAM,
        OPTION_HTTP_MUX,
        OPTION_HTTP_COARSE_TIMEBASE,
        OPTION_HTTP_AUDIO_CODEC,
        OPTION_HTTP_MUX,
        OPTION_HTTP_COARSE_TIMEBASE,
        OPTION_HTTP_AUDIO_CODEC,
@@ -185,6 +190,16 @@ void usage(Program program)
                fprintf(stderr, "                                  incompatible with --x264-separate-disk-bitrate\n");
                fprintf(stderr, "      --x264-separate-disk-param=NAME[,VALUE] set any x264 parameter, for fine tuning\n");
        }
                fprintf(stderr, "                                  incompatible with --x264-separate-disk-bitrate\n");
                fprintf(stderr, "      --x264-separate-disk-param=NAME[,VALUE] set any x264 parameter, for fine tuning\n");
        }
+#ifdef HAVE_AV1
+       fprintf(stderr, "      --http-av1-video            send AV1-compressed video to HTTP clients\n");
+       fprintf(stderr, "      --av1-preset                SVT-AV1 quality preset (default %d, from -2 to 13)\n",
+               DEFAULT_AV1_PRESET);
+       fprintf(stderr, "      --av1-bitrate               AV1 bitrate (in kilobit/sec, default %d)\n",
+               DEFAULT_AV1_OUTPUT_BIT_RATE);
+       fprintf(stderr, "      --av1-fps=NUM[/DEN]         AV1 encoded frame rate (default %d/%d)\n",
+               DEFAULT_AV1_FPS_NUM, DEFAULT_AV1_FPS_DEN);
+       fprintf(stderr, "      --av1-param=NAME[,VALUE]    set any SVT-AV1 parameter, for fine tuning\n");
+#endif
        fprintf(stderr, "      --http-mux=NAME             mux to use for HTTP streams (default " DEFAULT_STREAM_MUX_NAME ")\n");
        fprintf(stderr, "      --http-audio-codec=NAME     audio codec to use for HTTP streams\n");
        fprintf(stderr, "                                  (default is to use the same as for the recording)\n");
        fprintf(stderr, "      --http-mux=NAME             mux to use for HTTP streams (default " DEFAULT_STREAM_MUX_NAME ")\n");
        fprintf(stderr, "      --http-audio-codec=NAME     audio codec to use for HTTP streams\n");
        fprintf(stderr, "                                  (default is to use the same as for the recording)\n");
@@ -289,6 +304,13 @@ void parse_flags(Program program, int argc, char * const argv[])
                { "x264-separate-disk-bitrate", required_argument, 0, OPTION_X264_SEPARATE_DISK_BITRATE },
                { "x264-separate-disk-crf", required_argument, 0, OPTION_X264_SEPARATE_DISK_CRF },
                { "x264-separate-disk-param", required_argument, 0, OPTION_X264_SEPARATE_DISK_PARAM },
                { "x264-separate-disk-bitrate", required_argument, 0, OPTION_X264_SEPARATE_DISK_BITRATE },
                { "x264-separate-disk-crf", required_argument, 0, OPTION_X264_SEPARATE_DISK_CRF },
                { "x264-separate-disk-param", required_argument, 0, OPTION_X264_SEPARATE_DISK_PARAM },
+#ifdef HAVE_AV1
+               { "http-av1-video", no_argument, 0, OPTION_HTTP_AV1_VIDEO },
+               { "av1-preset", required_argument, 0, OPTION_AV1_PRESET },
+               { "av1-bitrate", required_argument, 0, OPTION_AV1_BITRATE },
+               { "av1-fps", required_argument, 0, OPTION_AV1_FPS },
+               { "av1-param", required_argument, 0, OPTION_AV1_PARAM },
+#endif
                { "http-mux", required_argument, 0, OPTION_HTTP_MUX },
                { "http-audio-codec", required_argument, 0, OPTION_HTTP_AUDIO_CODEC },
                { "http-audio-bitrate", required_argument, 0, OPTION_HTTP_AUDIO_BITRATE },
                { "http-mux", required_argument, 0, OPTION_HTTP_MUX },
                { "http-audio-codec", required_argument, 0, OPTION_HTTP_AUDIO_CODEC },
                { "http-audio-bitrate", required_argument, 0, OPTION_HTTP_AUDIO_BITRATE },
@@ -445,6 +467,9 @@ void parse_flags(Program program, int argc, char * const argv[])
                        global_flags.x264_video_to_http = true;
                        global_flags.x264_separate_disk_encode = true;
                        break;
                        global_flags.x264_video_to_http = true;
                        global_flags.x264_separate_disk_encode = true;
                        break;
+               case OPTION_HTTP_AV1_VIDEO:
+                       global_flags.av1_video_to_http = true;
+                       break;
                case OPTION_X264_PRESET:
                        global_flags.x264_preset = optarg;
                        break;
                case OPTION_X264_PRESET:
                        global_flags.x264_preset = optarg;
                        break;
@@ -487,6 +512,29 @@ void parse_flags(Program program, int argc, char * const argv[])
                case OPTION_X264_SEPARATE_DISK_PARAM:
                        global_flags.x264_separate_disk_extra_param.push_back(optarg);
                        break;
                case OPTION_X264_SEPARATE_DISK_PARAM:
                        global_flags.x264_separate_disk_extra_param.push_back(optarg);
                        break;
+               case OPTION_AV1_PRESET:
+                       global_flags.av1_preset = atoi(optarg);
+                       break;
+               case OPTION_AV1_BITRATE:
+                       global_flags.av1_bitrate = atoi(optarg);
+                       break;
+               case OPTION_AV1_FPS: {
+                       string str = optarg;
+                       const size_t pos = str.find('/');
+                       if (pos == string::npos) {
+                               global_flags.av1_fps_num = stoi(str);
+                               global_flags.av1_fps_den = 1;
+                       } else {
+                               const string num = str.substr(0, pos);
+                               const string den = str.substr(pos + 1);
+                               global_flags.av1_fps_num = stoi(num);
+                               global_flags.av1_fps_den = stoi(den);
+                       }
+                       break;
+               }
+               case OPTION_AV1_PARAM:
+                       global_flags.av1_extra_param.push_back(optarg);
+                       break;
                case OPTION_FLAT_AUDIO:
                        // If --flat-audio is given, turn off everything that messes with the sound,
                        // except the final makeup gain.
                case OPTION_FLAT_AUDIO:
                        // If --flat-audio is given, turn off everything that messes with the sound,
                        // except the final makeup gain.
@@ -570,8 +618,6 @@ void parse_flags(Program program, int argc, char * const argv[])
                        break;
                case OPTION_10_BIT_OUTPUT:
                        global_flags.ten_bit_output = true;
                        break;
                case OPTION_10_BIT_OUTPUT:
                        global_flags.ten_bit_output = true;
-                       global_flags.x264_video_to_disk = true;
-                       global_flags.x264_video_to_http = true;
                        global_flags.x264_bit_depth = 10;
                        break;
                case OPTION_INPUT_YCBCR_INTERPRETATION: {
                        global_flags.x264_bit_depth = 10;
                        break;
                case OPTION_INPUT_YCBCR_INTERPRETATION: {
@@ -645,11 +691,21 @@ void parse_flags(Program program, int argc, char * const argv[])
                }
        }
 
                }
        }
 
-       if (global_flags.uncompressed_video_to_http &&
-           global_flags.x264_video_to_http) {
-               fprintf(stderr, "ERROR: --http-uncompressed-video and --http-x264-video are mutually incompatible\n");
+       if (global_flags.uncompressed_video_to_http +
+           global_flags.x264_video_to_http +
+           global_flags.av1_video_to_http > 1) {
+               fprintf(stderr, "ERROR: --http-{uncompressed,x264,av1}-video are mutually incompatible\n");
                exit(1);
        }
                exit(1);
        }
+       if (global_flags.ten_bit_output) {
+               global_flags.x264_video_to_disk = true;  // No 10-bit Quick Sync support.
+               if (global_flags.av1_video_to_http) {
+                       fprintf(stderr, "ERROR: 10-bit AV1 output is not supported yet\n");
+                       exit(1);
+               } else {
+                       global_flags.x264_video_to_http = true;
+               }
+       }
        if (global_flags.min_num_cards <= 0) {
                fprintf(stderr, "ERROR: --num-cards must be at least 1\n");
                exit(1);
        if (global_flags.min_num_cards <= 0) {
                fprintf(stderr, "ERROR: --num-cards must be at least 1\n");
                exit(1);
index 734e290420bb995652ecfd9910003a7f40c57e82..96a4160f80515c4a2c815f0084f60f08d5b21f50 100644 (file)
@@ -20,6 +20,7 @@ struct Flags {
        bool x264_video_to_http = false;
        bool x264_video_to_disk = false;  // Disables Quick Sync entirely. Implies x264_video_to_http == true.
        bool x264_separate_disk_encode = false;  // Disables Quick Sync entirely. Implies x264_video_to_disk == true.
        bool x264_video_to_http = false;
        bool x264_video_to_disk = false;  // Disables Quick Sync entirely. Implies x264_video_to_http == true.
        bool x264_separate_disk_encode = false;  // Disables Quick Sync entirely. Implies x264_video_to_disk == true.
+       bool av1_video_to_http = false;
        std::vector<std::string> theme_dirs { ".", PREFIX "/share/nageru" };
        std::string recording_dir = ".";
        std::string theme_filename = "theme.lua";
        std::vector<std::string> theme_dirs { ".", PREFIX "/share/nageru" };
        std::string recording_dir = ".";
        std::string theme_filename = "theme.lua";
@@ -44,6 +45,11 @@ struct Flags {
        int x264_vbv_buffer_size = -1;  // In kilobits. 0 = one-frame VBV, -1 = same as <x264_bitrate> (one-second VBV).
        std::vector<std::string> x264_extra_param;  // In “key[,value]” format.
 
        int x264_vbv_buffer_size = -1;  // In kilobits. 0 = one-frame VBV, -1 = same as <x264_bitrate> (one-second VBV).
        std::vector<std::string> x264_extra_param;  // In “key[,value]” format.
 
+       int av1_preset = DEFAULT_AV1_PRESET;
+       int av1_bitrate = DEFAULT_AV1_OUTPUT_BIT_RATE;
+       int av1_fps_num = DEFAULT_AV1_FPS_NUM, av1_fps_den = DEFAULT_AV1_FPS_DEN;
+       std::vector<std::string> av1_extra_param;  // In “key[,value]” format.
+
        std::string x264_separate_disk_preset;  // Empty will be overridden by X264_DEFAULT_PRESET, unless speedcontrol is set.
        std::string x264_separate_disk_tune = X264_DEFAULT_TUNE;
        int x264_separate_disk_bitrate = -1;
        std::string x264_separate_disk_preset;  // Empty will be overridden by X264_DEFAULT_PRESET, unless speedcontrol is set.
        std::string x264_separate_disk_tune = X264_DEFAULT_TUNE;
        int x264_separate_disk_bitrate = -1;
index 19565ad506263112aae7768ef169c0b059fbc127..84bfaac80b9c6e3b5c4e731c92a674b71510e7b8 100644 (file)
@@ -719,6 +719,9 @@ void QuickSyncEncoderImpl::enable_zerocopy_if_possible()
        } else if (global_flags.x264_video_to_http) {
                fprintf(stderr, "Disabling zerocopy H.264 encoding due to --http-x264-video.\n");
                use_zerocopy = false;
        } else if (global_flags.x264_video_to_http) {
                fprintf(stderr, "Disabling zerocopy H.264 encoding due to --http-x264-video.\n");
                use_zerocopy = false;
+       } else if (global_flags.av1_video_to_http) {
+               fprintf(stderr, "Disabling zerocopy H.264 encoding due to --http-av1-video.\n");
+               use_zerocopy = false;
        } else if (!global_flags.v4l_output_device.empty()) {
                fprintf(stderr, "Disabling zerocopy H.264 encoding due to --v4l-output.\n");
                use_zerocopy = false;
        } else if (!global_flags.v4l_output_device.empty()) {
                fprintf(stderr, "Disabling zerocopy H.264 encoding due to --v4l-output.\n");
                use_zerocopy = false;
@@ -1342,7 +1345,8 @@ void QuickSyncEncoderImpl::save_codeddata(GLSurface *surf, storage_task task)
                        file_mux->add_packet(pkt, task.pts + global_delay(), task.dts + global_delay());
                }
                if (!global_flags.uncompressed_video_to_http &&
                        file_mux->add_packet(pkt, task.pts + global_delay(), task.dts + global_delay());
                }
                if (!global_flags.uncompressed_video_to_http &&
-                   !global_flags.x264_video_to_http) {
+                   !global_flags.x264_video_to_http &&
+                   !global_flags.av1_video_to_http) {
                        stream_mux->add_packet(pkt, task.pts + global_delay(), task.dts + global_delay());
                }
        }
                        stream_mux->add_packet(pkt, task.pts + global_delay(), task.dts + global_delay());
                }
        }
@@ -1450,6 +1454,8 @@ QuickSyncEncoderImpl::QuickSyncEncoderImpl(const std::string &filename, Resource
        if (global_flags.x264_video_to_http || global_flags.x264_video_to_disk) {
                assert(http_encoder != nullptr);
                assert(disk_encoder != nullptr);
        if (global_flags.x264_video_to_http || global_flags.x264_video_to_disk) {
                assert(http_encoder != nullptr);
                assert(disk_encoder != nullptr);
+       } else if (global_flags.av1_video_to_http) {
+               assert(http_encoder != nullptr);
        } else {
                assert(http_encoder == nullptr);
                assert(disk_encoder == nullptr);
        } else {
                assert(http_encoder == nullptr);
                assert(disk_encoder == nullptr);
@@ -1911,10 +1917,9 @@ void QuickSyncEncoderImpl::pass_frame(QuickSyncEncoderImpl::PendingFrame frame,
        uint8_t *data = reinterpret_cast<uint8_t *>(surf->y_ptr);
        if (global_flags.uncompressed_video_to_http) {
                add_packet_for_uncompressed_frame(pts, duration, data);
        uint8_t *data = reinterpret_cast<uint8_t *>(surf->y_ptr);
        if (global_flags.uncompressed_video_to_http) {
                add_packet_for_uncompressed_frame(pts, duration, data);
-       } else if (global_flags.x264_video_to_http || global_flags.x264_video_to_disk) {
+       } else if (http_encoder != nullptr) {
                http_encoder->add_frame(pts, duration, frame.ycbcr_coefficients, data, received_ts);
                http_encoder->add_frame(pts, duration, frame.ycbcr_coefficients, data, received_ts);
-       }
-       if (global_flags.x264_separate_disk_encode) {
+       } if (disk_encoder != nullptr && disk_encoder != http_encoder) {
                disk_encoder->add_frame(pts, duration, frame.ycbcr_coefficients, data, received_ts);
        }
 
                disk_encoder->add_frame(pts, duration, frame.ycbcr_coefficients, data, received_ts);
        }
 
index 3d777dcf578034756f36c7fd44e8e169f8a58c04..4c82131468002153455ae3dc0d1ac65a339c7546 100644 (file)
@@ -171,7 +171,7 @@ private:
        std::mutex file_audio_encoder_mutex;
        std::unique_ptr<AudioEncoder> file_audio_encoder;
 
        std::mutex file_audio_encoder_mutex;
        std::unique_ptr<AudioEncoder> file_audio_encoder;
 
-       VideoCodecInterface *http_encoder;  // nullptr if not using x264.
+       VideoCodecInterface *http_encoder;  // nullptr if not using x264/SVT-AV1.
        VideoCodecInterface *disk_encoder;
        std::unique_ptr<V4LOutput> v4l_output;  // nullptr if not using V4L2 output.
 
        VideoCodecInterface *disk_encoder;
        std::unique_ptr<V4LOutput> v4l_output;  // nullptr if not using V4L2 output.
 
index c75c4e3f365f851a0aa056fdebb3bacd7701bf9a..3342cfb0c21b9d460c209067312029a1a83b78d4 100644 (file)
@@ -12,6 +12,9 @@ extern "C" {
 }
 
 #include "audio_encoder.h"
 }
 
 #include "audio_encoder.h"
+#ifdef HAVE_AV1
+#include "av1_encoder.h"
+#endif
 #include "defs.h"
 #include "shared/ffmpeg_raii.h"
 #include "flags.h"
 #include "defs.h"
 #include "shared/ffmpeg_raii.h"
 #include "flags.h"
@@ -61,8 +64,14 @@ VideoEncoder::VideoEncoder(ResourcePool *resource_pool, QSurface *surface, const
        if (global_flags.x264_video_to_http || global_flags.x264_video_to_disk) {
                x264_encoder.reset(new X264Encoder(oformat, /*use_separate_disk_params=*/false));
        }
        if (global_flags.x264_video_to_http || global_flags.x264_video_to_disk) {
                x264_encoder.reset(new X264Encoder(oformat, /*use_separate_disk_params=*/false));
        }
-       X264Encoder *http_encoder = x264_encoder.get();
-       X264Encoder *disk_encoder = x264_encoder.get();
+       VideoCodecInterface *http_encoder = x264_encoder.get();
+       VideoCodecInterface *disk_encoder = x264_encoder.get();
+#ifdef HAVE_AV1
+       if (global_flags.av1_video_to_http) {
+               av1_encoder.reset(new AV1Encoder(oformat));
+               http_encoder = av1_encoder.get();
+       }
+#endif
        if (global_flags.x264_separate_disk_encode) {
                x264_disk_encoder.reset(new X264Encoder(oformat, /*use_separate_disk_params=*/true));
                disk_encoder = x264_disk_encoder.get();
        if (global_flags.x264_separate_disk_encode) {
                x264_disk_encoder.reset(new X264Encoder(oformat, /*use_separate_disk_params=*/true));
                disk_encoder = x264_disk_encoder.get();
@@ -77,6 +86,11 @@ VideoEncoder::VideoEncoder(ResourcePool *resource_pool, QSurface *surface, const
        if (global_flags.x264_video_to_http) {
                x264_encoder->add_mux(stream_mux.get());
        }
        if (global_flags.x264_video_to_http) {
                x264_encoder->add_mux(stream_mux.get());
        }
+#ifdef HAVE_AV1
+       if (global_flags.av1_video_to_http) {
+               av1_encoder->add_mux(stream_mux.get());
+       }
+#endif
 }
 
 VideoEncoder::~VideoEncoder()
 }
 
 VideoEncoder::~VideoEncoder()
@@ -201,6 +215,8 @@ void VideoEncoder::open_output_stream()
        Mux::Codec video_codec;
        if (global_flags.uncompressed_video_to_http) {
                video_codec = Mux::CODEC_NV12;
        Mux::Codec video_codec;
        if (global_flags.uncompressed_video_to_http) {
                video_codec = Mux::CODEC_NV12;
+       } else if (global_flags.av1_video_to_http) {
+               video_codec = Mux::CODEC_AV1;
        } else {
                video_codec = Mux::CODEC_H264;
        }
        } else {
                video_codec = Mux::CODEC_H264;
        }
@@ -210,6 +226,10 @@ void VideoEncoder::open_output_stream()
        string video_extradata;
        if (global_flags.x264_video_to_http || global_flags.x264_video_to_disk) {
                video_extradata = x264_encoder->get_global_headers();
        string video_extradata;
        if (global_flags.x264_video_to_http || global_flags.x264_video_to_disk) {
                video_extradata = x264_encoder->get_global_headers();
+#ifdef HAVE_AV1
+       } else if (global_flags.av1_video_to_http) {
+               video_extradata = av1_encoder->get_global_headers();
+#endif
        }
 
        stream_mux.reset(new Mux(avctx, width, height, video_codec, video_extradata, stream_audio_encoder->get_codec_parameters().get(),
        }
 
        stream_mux.reset(new Mux(avctx, width, height, video_codec, video_extradata, stream_audio_encoder->get_codec_parameters().get(),
index 3c82c00d609317a6d32344fb0bcf47378b6bdb11..7a5fef1ac4dfd9cadc07bc6492b1281213a327f4 100644 (file)
@@ -24,6 +24,7 @@ extern "C" {
 #include "shared/ref_counted_gl_sync.h"
 
 class AudioEncoder;
 #include "shared/ref_counted_gl_sync.h"
 
 class AudioEncoder;
+class AV1Encoder;
 class DiskSpaceEstimator;
 class HTTPD;
 class Mux;
 class DiskSpaceEstimator;
 class HTTPD;
 class Mux;
@@ -94,6 +95,9 @@ private:
        std::unique_ptr<AudioEncoder> stream_audio_encoder;
        std::unique_ptr<X264Encoder> x264_encoder;  // nullptr if not using x264.
        std::unique_ptr<X264Encoder> x264_disk_encoder;  // nullptr if not using x264, or if not having separate disk encodes.
        std::unique_ptr<AudioEncoder> stream_audio_encoder;
        std::unique_ptr<X264Encoder> x264_encoder;  // nullptr if not using x264.
        std::unique_ptr<X264Encoder> x264_disk_encoder;  // nullptr if not using x264, or if not having separate disk encodes.
+#ifdef HAVE_AV1
+       std::unique_ptr<AV1Encoder> av1_encoder;  // nullptr if not using SVT-AV1.
+#endif
 
        std::string stream_mux_header;
        MuxMetrics stream_mux_metrics;
 
        std::string stream_mux_header;
        MuxMetrics stream_mux_metrics;
index b9ca553ee1b2ac1731d1345cd7df8e0e162f308a..f6452795f4ab80ff51b6b9e391191c3c6194e90d 100644 (file)
@@ -59,6 +59,8 @@ Mux::Mux(AVFormatContext *avctx, int width, int height, Codec video_codec, const
        avstream_video->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
        if (video_codec == CODEC_H264) {
                avstream_video->codecpar->codec_id = AV_CODEC_ID_H264;
        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_AV1) {
+               avstream_video->codecpar->codec_id = AV_CODEC_ID_AV1;
        } 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_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);
index 1b9fe93216aba26c364f628cd2ce325fa187c263..075da1688f76d4dcba6a1760913657602463638d 100644 (file)
@@ -50,6 +50,7 @@ class Mux {
 public:
        enum Codec {
                CODEC_H264,
 public:
        enum Codec {
                CODEC_H264,
+               CODEC_AV1,
                CODEC_NV12,  // Uncompressed 4:2:0.
                CODEC_MJPEG
        };
                CODEC_NV12,  // Uncompressed 4:2:0.
                CODEC_MJPEG
        };