From 934d5fa5ea39d4f20056ed2a86b62b21a261c863 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Mon, 15 May 2017 23:33:24 +0200 Subject: [PATCH] Make it possible to set input Y'CbCr interpretation on the command line. --- flags.cpp | 54 ++++++++++++++++++++++++++++++++++++++++++ flags.h | 2 ++ glwidget.cpp | 23 ++++++++---------- mixer.cpp | 25 +++++++------------ mixer.h | 12 ++++------ ycbcr_interpretation.h | 12 ++++++++++ 6 files changed, 91 insertions(+), 37 deletions(-) create mode 100644 ycbcr_interpretation.h diff --git a/flags.cpp b/flags.cpp index f7a29f7..fc1bc88 100644 --- a/flags.cpp +++ b/flags.cpp @@ -57,6 +57,7 @@ enum LongOption { OPTION_TIMECODE_STDOUT, OPTION_10_BIT_INPUT, OPTION_10_BIT_OUTPUT, + OPTION_INPUT_YCBCR_INTERPRETATION, }; void usage() @@ -134,6 +135,10 @@ void usage() fprintf(stderr, " --10-bit-input use 10-bit video input (requires compute shaders)\n"); fprintf(stderr, " --10-bit-output use 10-bit video output (requires compute shaders,\n"); fprintf(stderr, " implies --record-x264-video)\n"); + fprintf(stderr, " --input-ycbcr-interpretation=CARD,{rec601,rec709,auto}[,{limited,full}]\n"); + fprintf(stderr, " Y'CbCr coefficient standard of card CARD (default auto)\n"); + fprintf(stderr, " auto is rec601 for SD, rec709 for HD, always limited\n"); + fprintf(stderr, " limited means standard 0-240/0-235 input range (for 8-bit)\n"); } void parse_flags(int argc, char * const argv[]) @@ -193,6 +198,7 @@ void parse_flags(int argc, char * const argv[]) { "timecode-stdout", no_argument, 0, OPTION_TIMECODE_STDOUT }, { "10-bit-input", no_argument, 0, OPTION_10_BIT_INPUT }, { "10-bit-output", no_argument, 0, OPTION_10_BIT_OUTPUT }, + { "input-ycbcr-interpretation", required_argument, 0, OPTION_INPUT_YCBCR_INTERPRETATION }, { 0, 0, 0, 0 } }; vector theme_dirs; @@ -389,6 +395,54 @@ void parse_flags(int argc, char * const argv[]) global_flags.x264_video_to_http = true; global_flags.x264_bit_depth = 10; break; + case OPTION_INPUT_YCBCR_INTERPRETATION: { + char *ptr = strchr(optarg, ','); + if (ptr == nullptr) { + fprintf(stderr, "ERROR: Invalid argument '%s' to --input-ycbcr-interpretation (needs a card and an interpretation, separated by comma)\n", optarg); + exit(1); + } + *ptr = '\0'; + const int card_num = atoi(optarg); + if (card_num < 0 || card_num >= MAX_VIDEO_CARDS) { + fprintf(stderr, "ERROR: Invalid card number %d\n", card_num); + exit(1); + } + + YCbCrInterpretation interpretation; + char *interpretation_str = ptr + 1; + ptr = strchr(interpretation_str, ','); + if (ptr != nullptr) { + *ptr = '\0'; + const char *range = ptr + 1; + if (strcmp(range, "full") == 0) { + interpretation.full_range = true; + } else if (strcmp(range, "limited") == 0) { + interpretation.full_range = false; + } else { + fprintf(stderr, "ERROR: Invalid Y'CbCr range '%s' (must be “full” or “limited”)\n", range); + exit(1); + } + } + + if (strcmp(interpretation_str, "rec601") == 0) { + interpretation.ycbcr_coefficients_auto = false; + interpretation.ycbcr_coefficients = movit::YCBCR_REC_601; + } else if (strcmp(interpretation_str, "rec709") == 0) { + interpretation.ycbcr_coefficients_auto = false; + interpretation.ycbcr_coefficients = movit::YCBCR_REC_709; + } else if (strcmp(interpretation_str, "auto") == 0) { + interpretation.ycbcr_coefficients_auto = true; + if (interpretation.full_range) { + fprintf(stderr, "ERROR: Cannot use “auto” Y'CbCr coefficients with full range\n"); + exit(1); + } + } else { + fprintf(stderr, "ERROR: Invalid Y'CbCr coefficients '%s' (must be “rec601”, “rec709” or “auto”)\n", interpretation_str); + exit(1); + } + global_flags.ycbcr_interpretation[card_num] = interpretation; + break; + } case OPTION_HELP: usage(); exit(0); diff --git a/flags.h b/flags.h index 66912de..f26d4db 100644 --- a/flags.h +++ b/flags.h @@ -8,6 +8,7 @@ #include #include "defs.h" +#include "ycbcr_interpretation.h" struct Flags { int width = 1280, height = 720; @@ -57,6 +58,7 @@ struct Flags { bool display_timecode_on_stdout = false; bool ten_bit_input = false; bool ten_bit_output = false; // Implies x264_video_to_disk == true and x264_bit_depth == 10. + YCbCrInterpretation ycbcr_interpretation[MAX_VIDEO_CARDS]; int x264_bit_depth = 8; // Not user-settable. bool use_zerocopy = false; // Not user-settable. bool can_disable_srgb_decoder = false; // Not user-settable. diff --git a/glwidget.cpp b/glwidget.cpp index 73d8b7f..31018d6 100644 --- a/glwidget.cpp +++ b/glwidget.cpp @@ -193,15 +193,11 @@ void GLWidget::show_preview_context_menu(unsigned signal_num, const QPoint &pos) QMenu interpretation_submenu; QActionGroup interpretation_group(&interpretation_submenu); - bool current_ycbcr_coefficients_auto, current_full_range; - YCbCrLumaCoefficients current_ycbcr_coefficients; - global_mixer->get_input_ycbcr_interpretation( - current_card, ¤t_ycbcr_coefficients_auto, - ¤t_ycbcr_coefficients, ¤t_full_range); + YCbCrInterpretation current_interpretation = global_mixer->get_input_ycbcr_interpretation(current_card); { QAction *action = new QAction("Auto", &interpretation_group); action->setCheckable(true); - if (current_ycbcr_coefficients_auto) { + if (current_interpretation.ycbcr_coefficients_auto) { action->setChecked(true); } action->setData(QList{"interpretation", true, YCBCR_REC_709, false}); @@ -220,9 +216,9 @@ void GLWidget::show_preview_context_menu(unsigned signal_num, const QPoint &pos) } QAction *action = new QAction(QString::fromStdString(description), &interpretation_group); action->setCheckable(true); - if (!current_ycbcr_coefficients_auto && - ycbcr_coefficients == current_ycbcr_coefficients && - full_range == current_full_range) { + if (!current_interpretation.ycbcr_coefficients_auto && + ycbcr_coefficients == current_interpretation.ycbcr_coefficients && + full_range == current_interpretation.full_range) { action->setChecked(true); } action->setData(QList{"interpretation", false, ycbcr_coefficients, full_range}); @@ -357,10 +353,11 @@ void GLWidget::show_preview_context_menu(unsigned signal_num, const QPoint &pos) unsigned card_index = selected[1].toUInt(nullptr); global_mixer->set_signal_mapping(signal_num, card_index); } else if (selected[0].toString() == "interpretation") { - bool ycbcr_coefficients_auto = selected[1].toBool(); - YCbCrLumaCoefficients ycbcr_coefficients = YCbCrLumaCoefficients(selected[2].toUInt(nullptr)); - bool full_range = selected[3].toBool(); - global_mixer->set_input_ycbcr_interpretation(current_card, ycbcr_coefficients_auto, ycbcr_coefficients, full_range); + YCbCrInterpretation interpretation; + interpretation.ycbcr_coefficients_auto = selected[1].toBool(); + interpretation.ycbcr_coefficients = YCbCrLumaCoefficients(selected[2].toUInt(nullptr)); + interpretation.full_range = selected[3].toBool(); + global_mixer->set_input_ycbcr_interpretation(current_card, interpretation); } else { assert(false); } diff --git a/mixer.cpp b/mixer.cpp index 6439e58..1a76ac3 100644 --- a/mixer.cpp +++ b/mixer.cpp @@ -206,6 +206,7 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards) mixer_surface(create_surface(format)), h264_encoder_surface(create_surface(format)), decklink_output_surface(create_surface(format)), + ycbcr_interpretation(global_flags.ycbcr_interpretation), audio_mixer(num_cards) { CHECK(init_movit(MOVIT_SHADER_DIR, MOVIT_DEBUG_OFF)); @@ -1085,10 +1086,10 @@ void Mixer::render_one_frame(int64_t duration) { unique_lock lock(card_mutex); for (unsigned card_index = 0; card_index < num_cards; ++card_index) { - CaptureCard *card = &cards[card_index]; - input_state.ycbcr_coefficients_auto[card_index] = card->ycbcr_coefficients_auto; - input_state.ycbcr_coefficients[card_index] = card->ycbcr_coefficients; - input_state.full_range[card_index] = card->full_range; + YCbCrInterpretation *interpretation = &ycbcr_interpretation[card_index]; + input_state.ycbcr_coefficients_auto[card_index] = interpretation->ycbcr_coefficients_auto; + input_state.ycbcr_coefficients[card_index] = interpretation->ycbcr_coefficients; + input_state.full_range[card_index] = interpretation->full_range; } } @@ -1293,24 +1294,16 @@ void Mixer::channel_clicked(int preview_num) theme->channel_clicked(preview_num); } -void Mixer::get_input_ycbcr_interpretation(unsigned card_index, bool *ycbcr_coefficients_auto, - movit::YCbCrLumaCoefficients *ycbcr_coefficients, bool *full_range) +YCbCrInterpretation Mixer::get_input_ycbcr_interpretation(unsigned card_index) const { unique_lock lock(card_mutex); - CaptureCard *card = &cards[card_index]; - *ycbcr_coefficients_auto = card->ycbcr_coefficients_auto; - *ycbcr_coefficients = card->ycbcr_coefficients; - *full_range = card->full_range; + return ycbcr_interpretation[card_index]; } -void Mixer::set_input_ycbcr_interpretation(unsigned card_index, bool ycbcr_coefficients_auto, - movit::YCbCrLumaCoefficients ycbcr_coefficients, bool full_range) +void Mixer::set_input_ycbcr_interpretation(unsigned card_index, const YCbCrInterpretation &interpretation) { unique_lock lock(card_mutex); - CaptureCard *card = &cards[card_index]; - card->ycbcr_coefficients_auto = ycbcr_coefficients_auto; - card->ycbcr_coefficients = ycbcr_coefficients; - card->full_range = full_range; + ycbcr_interpretation[card_index] = interpretation; } void Mixer::start_mode_scanning(unsigned card_index) diff --git a/mixer.h b/mixer.h index 84ef32d..c63db64 100644 --- a/mixer.h +++ b/mixer.h @@ -37,6 +37,7 @@ #include "theme.h" #include "timebase.h" #include "video_encoder.h" +#include "ycbcr_interpretation.h" class ALSAOutput; class ChromaSubsampler; @@ -218,10 +219,8 @@ public: return theme->set_signal_mapping(signal, card); } - void get_input_ycbcr_interpretation(unsigned card_index, bool *ycbcr_coefficients_auto, - movit::YCbCrLumaCoefficients *ycbcr_coefficients, bool *full_range); - void set_input_ycbcr_interpretation(unsigned card_index, bool ycbcr_coefficients_auto, - movit::YCbCrLumaCoefficients ycbcr_coefficients, bool full_range); + YCbCrInterpretation get_input_ycbcr_interpretation(unsigned card_index) const; + void set_input_ycbcr_interpretation(unsigned card_index, const YCbCrInterpretation &interpretation); bool get_supports_set_wb(unsigned channel) const { @@ -446,12 +445,9 @@ private: QueueLengthPolicy queue_length_policy; // Refers to the "new_frames" queue. int last_timecode = -1; // Unwrapped. - - bool ycbcr_coefficients_auto = true; - movit::YCbCrLumaCoefficients ycbcr_coefficients = movit::YCBCR_REC_709; - bool full_range = false; }; CaptureCard cards[MAX_VIDEO_CARDS]; // Protected by . + YCbCrInterpretation ycbcr_interpretation[MAX_VIDEO_CARDS]; // Protected by . AudioMixer audio_mixer; // Same as global_audio_mixer (see audio_mixer.h). bool input_card_is_master_clock(unsigned card_index, unsigned master_card_index) const; struct OutputFrameInfo { diff --git a/ycbcr_interpretation.h b/ycbcr_interpretation.h new file mode 100644 index 0000000..51bad76 --- /dev/null +++ b/ycbcr_interpretation.h @@ -0,0 +1,12 @@ +#ifndef _YCBCR_INTERPRETATION_H +#define _YCBCR_INTERPRETATION_H 1 + +#include + +struct YCbCrInterpretation { + bool ycbcr_coefficients_auto = true; + movit::YCbCrLumaCoefficients ycbcr_coefficients = movit::YCBCR_REC_709; + bool full_range = false; +}; + +#endif // !defined(_YCBCR_INTERPRETATION_H) -- 2.39.2