From 0556d8cb8416bdc7b432a432c3d58239a94358d2 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Mon, 13 Mar 2017 21:55:02 +0100 Subject: [PATCH] Support loading 10-bit x264 dynamically. --- Makefile | 2 +- flags.cpp | 6 +++ flags.h | 1 + x264_dynamic.cpp | 85 ++++++++++++++++++++++++++++++++++++++++++ x264_dynamic.h | 28 ++++++++++++++ x264_encoder.cpp | 36 +++++++++++------- x264_encoder.h | 2 + x264_speed_control.cpp | 13 +++++-- x264_speed_control.h | 6 ++- 9 files changed, 160 insertions(+), 19 deletions(-) create mode 100644 x264_dynamic.cpp create mode 100644 x264_dynamic.h diff --git a/Makefile b/Makefile index 6d46bc3..5733c47 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ AUDIO_MIXER_OBJS = audio_mixer.o alsa_input.o alsa_pool.o ebu_r128_proc.o stereo OBJS += chroma_subsampler.o v210_converter.o mixer.o pbo_frame_allocator.o context.o ref_counted_frame.o theme.o httpd.o flags.o image_input.o alsa_output.o disk_space_estimator.o print_latency.o timecode_renderer.o $(AUDIO_MIXER_OBJS) # Streaming and encoding objects -OBJS += quicksync_encoder.o x264_encoder.o x264_speed_control.o video_encoder.o metacube2.o mux.o audio_encoder.o ffmpeg_raii.o +OBJS += quicksync_encoder.o x264_encoder.o x264_dynamic.o x264_speed_control.o video_encoder.o metacube2.o mux.o audio_encoder.o ffmpeg_raii.o # DeckLink OBJS += decklink_capture.o decklink_util.o decklink_output.o decklink/DeckLinkAPIDispatch.o diff --git a/flags.cpp b/flags.cpp index 62028df..e6c306b 100644 --- a/flags.cpp +++ b/flags.cpp @@ -27,6 +27,7 @@ enum LongOption { OPTION_X264_BITRATE, OPTION_X264_VBV_BUFSIZE, OPTION_X264_VBV_MAX_BITRATE, + OPTION_X264_10_BIT, OPTION_X264_PARAM, OPTION_HTTP_MUX, OPTION_HTTP_COARSE_TIMEBASE, @@ -89,6 +90,7 @@ void usage() fprintf(stderr, " default: same as --x264-bitrate, that is, one-second VBV)\n"); fprintf(stderr, " --x264-vbv-max-bitrate x264 local max bitrate (in kilobit/sec per --vbv-bufsize,\n"); fprintf(stderr, " 0 = no limit, default: same as --x264-bitrate, i.e., CBR)\n"); + fprintf(stderr, " --x264-10-bit enable 10-bit x264 encoding\n"); fprintf(stderr, " --x264-param=NAME[,VALUE] set any x264 parameter, for fine tuning\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"); @@ -156,6 +158,7 @@ void parse_flags(int argc, char * const argv[]) { "x264-bitrate", required_argument, 0, OPTION_X264_BITRATE }, { "x264-vbv-bufsize", required_argument, 0, OPTION_X264_VBV_BUFSIZE }, { "x264-vbv-max-bitrate", required_argument, 0, OPTION_X264_VBV_MAX_BITRATE }, + { "x264-10-bit", no_argument, 0, OPTION_X264_10_BIT }, { "x264-param", required_argument, 0, OPTION_X264_PARAM }, { "http-mux", required_argument, 0, OPTION_HTTP_MUX }, { "http-coarse-timebase", no_argument, 0, OPTION_HTTP_COARSE_TIMEBASE }, @@ -287,6 +290,9 @@ void parse_flags(int argc, char * const argv[]) case OPTION_X264_VBV_BUFSIZE: global_flags.x264_vbv_buffer_size = atoi(optarg); break; + case OPTION_X264_10_BIT: + global_flags.x264_bit_depth = 10; + break; case OPTION_X264_VBV_MAX_BITRATE: global_flags.x264_vbv_max_bitrate = atoi(optarg); break; diff --git a/flags.h b/flags.h index 97d2fe6..b840c15 100644 --- a/flags.h +++ b/flags.h @@ -35,6 +35,7 @@ struct Flags { int x264_bitrate = DEFAULT_X264_OUTPUT_BIT_RATE; // In kilobit/sec. int x264_vbv_max_bitrate = -1; // In kilobits. 0 = no limit, -1 = same as (CBR). int x264_vbv_buffer_size = -1; // In kilobits. 0 = one-frame VBV, -1 = same as (one-second VBV). + int x264_bit_depth = 8; std::vector x264_extra_param; // In “key[,value]” format. bool enable_alsa_output = true; std::map default_stream_mapping; diff --git a/x264_dynamic.cpp b/x264_dynamic.cpp new file mode 100644 index 0000000..6aa649e --- /dev/null +++ b/x264_dynamic.cpp @@ -0,0 +1,85 @@ +#include "x264_dynamic.h" + +#include +#include +#include +#include +#include +#include + +#include + +using namespace std; + +X264Dynamic load_x264_for_bit_depth(unsigned depth) +{ + X264Dynamic dyn; + if (unsigned(x264_bit_depth) >= depth) { + // Just use the one we are linked to. + dyn.handle = nullptr; + dyn.x264_encoder_close = x264_encoder_close; + dyn.x264_encoder_delayed_frames = x264_encoder_delayed_frames; + dyn.x264_encoder_encode = x264_encoder_encode; + dyn.x264_encoder_headers = x264_encoder_headers; + dyn.x264_encoder_open = x264_encoder_open; + dyn.x264_encoder_parameters = x264_encoder_parameters; + dyn.x264_encoder_reconfig = x264_encoder_reconfig; + dyn.x264_param_apply_profile = x264_param_apply_profile; + dyn.x264_param_default_preset = x264_param_default_preset; + dyn.x264_param_parse = x264_param_parse; + dyn.x264_picture_init = x264_picture_init; + return dyn; + } + + // Uh-oh, our currently loaded library doesn't have the required support. + // Let's try to dynamically load a 10-bit version; in particular, Debian + // has a version in /usr/lib/x86_64-linux-gnu/x264-10bit/libx264.so., + // so we try to figure out where our libx264 comes from, and modify that path. + string x264_dir, x264_suffix; + void *handle = dlopen(nullptr, RTLD_NOW); + link_map *m; + int err = dlinfo(handle, RTLD_DI_LINKMAP, &m); + assert(err != -1); + for ( ; m != nullptr; m = m->l_next) { + if (m->l_name == nullptr) { + continue; + } + const char *ptr = strstr(m->l_name, "/libx264.so."); + if (ptr != nullptr) { + x264_dir = string(m->l_name, ptr - m->l_name); + x264_suffix = string(ptr, (m->l_name + strlen(m->l_name)) - ptr); + break; + } + } + dlclose(handle); + + if (x264_dir.empty()) { + fprintf(stderr, "ERROR: Requested %d-bit x264, but is not linked to such an x264, and could not find one.\n", + depth); + exit(1); + } + + string x264_10b_string = x264_dir + "/x264-10bit" + x264_suffix; + void *x264_dlhandle = dlopen(x264_10b_string.c_str(), RTLD_NOW); + if (x264_dlhandle == nullptr) { + fprintf(stderr, "ERROR: Requested %d-bit x264, but is not linked to such an x264, and %s would not load.\n", + depth, x264_10b_string.c_str()); + exit(1); + } + + dyn.handle = x264_dlhandle; + dyn.x264_encoder_close = (decltype(::x264_encoder_close) *)dlsym(x264_dlhandle, "x264_encoder_close"); + dyn.x264_encoder_delayed_frames = (decltype(::x264_encoder_delayed_frames) *)dlsym(x264_dlhandle, "x264_encoder_delayed_frames"); + dyn.x264_encoder_encode = (decltype(::x264_encoder_encode) *)dlsym(x264_dlhandle, "x264_encoder_encode"); + dyn.x264_encoder_headers = (decltype(::x264_encoder_headers) *)dlsym(x264_dlhandle, "x264_encoder_headers"); + char x264_encoder_open_symname[256]; + snprintf(x264_encoder_open_symname, sizeof(x264_encoder_open_symname), "x264_encoder_open_%d", X264_BUILD); + dyn.x264_encoder_open = (decltype(::x264_encoder_open) *)dlsym(x264_dlhandle, x264_encoder_open_symname); + dyn.x264_encoder_parameters = (decltype(::x264_encoder_parameters) *)dlsym(x264_dlhandle, "x264_encoder_parameters"); + dyn.x264_encoder_reconfig = (decltype(::x264_encoder_reconfig) *)dlsym(x264_dlhandle, "x264_encoder_reconfig"); + dyn.x264_param_apply_profile = (decltype(::x264_param_apply_profile) *)dlsym(x264_dlhandle, "x264_param_apply_profile"); + dyn.x264_param_default_preset = (decltype(::x264_param_default_preset) *)dlsym(x264_dlhandle, "x264_param_default_preset"); + dyn.x264_param_parse = (decltype(::x264_param_parse) *)dlsym(x264_dlhandle, "x264_param_parse"); + dyn.x264_picture_init = (decltype(::x264_picture_init) *)dlsym(x264_dlhandle, "x264_picture_init"); + return dyn; +} diff --git a/x264_dynamic.h b/x264_dynamic.h new file mode 100644 index 0000000..27e5202 --- /dev/null +++ b/x264_dynamic.h @@ -0,0 +1,28 @@ +#ifndef _X264_DYNAMIC_H +#define _X264_DYNAMIC_H 1 + +// A helper to load 10-bit x264 if needed. + +#include + +extern "C" { +#include +} + +struct X264Dynamic { + void *handle; // If not nullptr, needs to be dlclose()d. + decltype(::x264_encoder_close) *x264_encoder_close; + decltype(::x264_encoder_delayed_frames) *x264_encoder_delayed_frames; + decltype(::x264_encoder_encode) *x264_encoder_encode; + decltype(::x264_encoder_headers) *x264_encoder_headers; + decltype(::x264_encoder_open) *x264_encoder_open; + decltype(::x264_encoder_parameters) *x264_encoder_parameters; + decltype(::x264_encoder_reconfig) *x264_encoder_reconfig; + decltype(::x264_param_apply_profile) *x264_param_apply_profile; + decltype(::x264_param_default_preset) *x264_param_default_preset; + decltype(::x264_param_parse) *x264_param_parse; + decltype(::x264_picture_init) *x264_picture_init; +}; +X264Dynamic load_x264_for_bit_depth(unsigned depth); + +#endif // !defined(_X264_DYNAMIC_H) diff --git a/x264_encoder.cpp b/x264_encoder.cpp index 8e3b567..c8f7b81 100644 --- a/x264_encoder.cpp +++ b/x264_encoder.cpp @@ -1,6 +1,7 @@ #include "x264_encoder.h" #include +#include #include #include #include @@ -13,6 +14,7 @@ #include "mux.h" #include "print_latency.h" #include "timebase.h" +#include "x264_dynamic.h" #include "x264_speed_control.h" extern "C" { @@ -43,7 +45,8 @@ void update_vbv_settings(x264_param_t *param) } // namespace X264Encoder::X264Encoder(AVOutputFormat *oformat) - : wants_global_headers(oformat->flags & AVFMT_GLOBALHEADER) + : wants_global_headers(oformat->flags & AVFMT_GLOBALHEADER), + dyn(load_x264_for_bit_depth(global_flags.x264_bit_depth)) { frame_pool.reset(new uint8_t[global_flags.width * global_flags.height * 2 * X264_QUEUE_LENGTH]); for (unsigned i = 0; i < X264_QUEUE_LENGTH; ++i) { @@ -57,6 +60,9 @@ X264Encoder::~X264Encoder() should_quit = true; queued_frames_nonempty.notify_all(); encoder_thread.join(); + if (dyn.handle) { + dlclose(dyn.handle); + } } void X264Encoder::add_frame(int64_t pts, int64_t duration, YCbCrLumaCoefficients ycbcr_coefficients, const uint8_t *data, const ReceivedTimestamps &received_ts) @@ -92,7 +98,7 @@ void X264Encoder::add_frame(int64_t pts, int64_t duration, YCbCrLumaCoefficients void X264Encoder::init_x264() { x264_param_t param; - x264_param_default_preset(¶m, global_flags.x264_preset.c_str(), global_flags.x264_tune.c_str()); + dyn.x264_param_default_preset(¶m, global_flags.x264_preset.c_str(), global_flags.x264_tune.c_str()); param.i_width = global_flags.width; param.i_height = global_flags.height; @@ -154,24 +160,28 @@ void X264Encoder::init_x264() for (const string &str : global_flags.x264_extra_param) { const size_t pos = str.find(','); if (pos == string::npos) { - if (x264_param_parse(¶m, str.c_str(), nullptr) != 0) { + if (dyn.x264_param_parse(¶m, str.c_str(), nullptr) != 0) { fprintf(stderr, "ERROR: x264 rejected parameter '%s'\n", str.c_str()); } } else { const string key = str.substr(0, pos); const string value = str.substr(pos + 1); - if (x264_param_parse(¶m, key.c_str(), value.c_str()) != 0) { + if (dyn.x264_param_parse(¶m, key.c_str(), value.c_str()) != 0) { fprintf(stderr, "ERROR: x264 rejected parameter '%s' set to '%s'\n", key.c_str(), value.c_str()); } } } - x264_param_apply_profile(¶m, "high"); + if (global_flags.x264_bit_depth > 8) { + dyn.x264_param_apply_profile(¶m, "high10"); + } else { + dyn.x264_param_apply_profile(¶m, "high"); + } param.b_repeat_headers = !wants_global_headers; - x264 = x264_encoder_open(¶m); + x264 = dyn.x264_encoder_open(¶m); if (x264 == nullptr) { fprintf(stderr, "ERROR: x264 initialization failed.\n"); exit(1); @@ -185,7 +195,7 @@ void X264Encoder::init_x264() x264_nal_t *nal; int num_nal; - x264_encoder_headers(x264, &nal, &num_nal); + dyn.x264_encoder_headers(x264, &nal, &num_nal); for (int i = 0; i < num_nal; ++i) { if (nal[i].i_type == NAL_SEI) { @@ -237,9 +247,9 @@ void X264Encoder::encoder_thread_func() // We should quit only if the should_quit flag is set _and_ we have nothing // in either queue. - } while (!should_quit || frames_left || x264_encoder_delayed_frames(x264) > 0); + } while (!should_quit || frames_left || dyn.x264_encoder_delayed_frames(x264) > 0); - x264_encoder_close(x264); + dyn.x264_encoder_close(x264); } void X264Encoder::encode_frame(X264Encoder::QueuedFrame qf) @@ -250,7 +260,7 @@ void X264Encoder::encode_frame(X264Encoder::QueuedFrame qf) x264_picture_t *input_pic = nullptr; if (qf.data) { - x264_picture_init(&pic); + dyn.x264_picture_init(&pic); pic.i_pts = qf.pts; pic.img.i_csp = X264_CSP_NV12; @@ -293,18 +303,18 @@ void X264Encoder::encode_frame(X264Encoder::QueuedFrame qf) }); } else { x264_param_t param; - x264_encoder_parameters(x264, ¶m); + dyn.x264_encoder_parameters(x264, ¶m); if (bitrate_override_func) { bitrate_override_func(¶m); } ycbcr_coefficients_override_func(¶m); - x264_encoder_reconfig(x264, ¶m); + dyn.x264_encoder_reconfig(x264, ¶m); } if (speed_control) { speed_control->before_frame(float(free_frames.size()) / X264_QUEUE_LENGTH, X264_QUEUE_LENGTH, 1e6 * qf.duration / TIMEBASE); } - x264_encoder_encode(x264, &nal, &num_nal, input_pic, &pic); + dyn.x264_encoder_encode(x264, &nal, &num_nal, input_pic, &pic); if (speed_control) { speed_control->after_frame(); } diff --git a/x264_encoder.h b/x264_encoder.h index 34cf702..455bb1e 100644 --- a/x264_encoder.h +++ b/x264_encoder.h @@ -33,6 +33,7 @@ extern "C" { #include #include "print_latency.h" +#include "x264_dynamic.h" class Mux; class X264SpeedControl; @@ -88,6 +89,7 @@ private: std::thread encoder_thread; std::atomic x264_init_done{false}; std::atomic should_quit{false}; + X264Dynamic dyn; x264_t *x264; std::unique_ptr speed_control; diff --git a/x264_speed_control.cpp b/x264_speed_control.cpp index 1418132..4708682 100644 --- a/x264_speed_control.cpp +++ b/x264_speed_control.cpp @@ -1,5 +1,6 @@ #include "x264_speed_control.h" +#include #include #include #include @@ -15,10 +16,11 @@ using namespace std; using namespace std::chrono; X264SpeedControl::X264SpeedControl(x264_t *x264, float f_speed, int i_buffer_size, float f_buffer_init) - : x264(x264), f_speed(f_speed) + : dyn(load_x264_for_bit_depth(global_flags.x264_bit_depth)), + x264(x264), f_speed(f_speed) { x264_param_t param; - x264_encoder_parameters(x264, ¶m); + dyn.x264_encoder_parameters(x264, ¶m); float fps = (float)param.i_fps_num / param.i_fps_den; uspf = 1e6 / fps; @@ -43,6 +45,9 @@ X264SpeedControl::~X264SpeedControl() (float)stat.min_buffer / buffer_size, (float)stat.max_buffer / buffer_size ); // x264_log( x264, X264_LOG_INFO, "speedcontrol: avg cplx=%.5f\n", cplx_num / cplx_den ); + if (dyn.handle) { + dlclose(dyn.handle); + } } typedef struct @@ -302,7 +307,7 @@ void X264SpeedControl::apply_preset(int new_preset) const sc_preset_t *s = &presets[new_preset]; x264_param_t p; - x264_encoder_parameters(x264, &p); + dyn.x264_encoder_parameters(x264, &p); p.i_frame_reference = s->refs; p.i_bframe_adaptive = s->badapt; @@ -317,6 +322,6 @@ void X264SpeedControl::apply_preset(int new_preset) if (override_func) { override_func(&p); } - x264_encoder_reconfig(x264, &p); + dyn.x264_encoder_reconfig(x264, &p); preset = new_preset; } diff --git a/x264_speed_control.h b/x264_speed_control.h index b45e6c6..de88f66 100644 --- a/x264_speed_control.h +++ b/x264_speed_control.h @@ -48,9 +48,11 @@ #include extern "C" { -#include "x264.h" +#include } +#include "x264_dynamic.h" + class X264SpeedControl { public: // x264: Encoding object we are using; must be opened. Assumed to be @@ -95,6 +97,8 @@ private: int dither_preset(float f); void apply_preset(int new_preset); + X264Dynamic dyn; + // Not owned by us. x264_t *x264; -- 2.39.2