OPTION_TIMECODE_STDOUT,
OPTION_10_BIT_INPUT,
OPTION_10_BIT_OUTPUT,
+ OPTION_INPUT_YCBCR_INTERPRETATION,
};
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[])
{ "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<string> theme_dirs;
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);
#include <vector>
#include "defs.h"
+#include "ycbcr_interpretation.h"
struct Flags {
int width = 1280, height = 720;
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.
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<QVariant>{"interpretation", true, YCBCR_REC_709, false});
}
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<QVariant>{"interpretation", false, ycbcr_coefficients, full_range});
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);
}
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));
{
unique_lock<mutex> 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;
}
}
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<mutex> 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<mutex> 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)
#include "theme.h"
#include "timebase.h"
#include "video_encoder.h"
+#include "ycbcr_interpretation.h"
class ALSAOutput;
class ChromaSubsampler;
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
{
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 <card_mutex>.
+ YCbCrInterpretation ycbcr_interpretation[MAX_VIDEO_CARDS]; // Protected by <card_mutex>.
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 {
--- /dev/null
+#ifndef _YCBCR_INTERPRETATION_H
+#define _YCBCR_INTERPRETATION_H 1
+
+#include <movit/image_format.h>
+
+struct YCbCrInterpretation {
+ bool ycbcr_coefficients_auto = true;
+ movit::YCbCrLumaCoefficients ycbcr_coefficients = movit::YCBCR_REC_709;
+ bool full_range = false;
+};
+
+#endif // !defined(_YCBCR_INTERPRETATION_H)