case bmusb::PixelFormat_8BitYCbCr:
first = userdata->tex_y[field] == 0 || userdata->tex_cbcr[field] == 0;
break;
- case bmusb::PixelFormat_8BitRGBA:
+ case bmusb::PixelFormat_8BitBGRA:
first = userdata->tex_rgba[field] == 0;
break;
default:
check_error();
break;
}
- case bmusb::PixelFormat_8BitRGBA:
+ case bmusb::PixelFormat_8BitBGRA:
glBindTexture(GL_TEXTURE_2D, userdata->tex_rgba[field]);
check_error();
if (global_flags.can_disable_srgb_decoder) { // See the comments in tweaked_inputs.h.
- glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, width, height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr);
} else {
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr);
}
check_error();
break;
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));
check_error();
// This nearly always should be true.
- global_flags.can_disable_srgb_decoder = epoxy_has_gl_extension("GL_EXT_texture_sRGB_decode");
+ global_flags.can_disable_srgb_decoder =
+ epoxy_has_gl_extension("GL_EXT_texture_sRGB_decode") &&
+ epoxy_has_gl_extension("GL_ARB_sampler_objects");
// Since we allow non-bouncing 4:2:2 YCbCrInputs, effective subpixel precision
// will be halved when sampling them, and we need to compensate here.
bmusb::PixelFormat pixel_format;
if (card_type == CardType::FFMPEG_INPUT) {
- pixel_format = bmusb::PixelFormat_8BitRGBA;
+ pixel_format = bmusb::PixelFormat_8BitBGRA;
} else if (global_flags.ten_bit_input) {
pixel_format = PixelFormat_10BitYCbCr;
} else {
upload_texture(userdata->tex_cbcr[field], cbcr_width, video_format.height, cbcr_width * sizeof(uint16_t), interlaced_stride, GL_RG, GL_UNSIGNED_BYTE, field_cbcr_start);
break;
}
- case bmusb::PixelFormat_8BitRGBA: {
+ case bmusb::PixelFormat_8BitBGRA: {
size_t field_start = video_offset + video_format.stride * field_start_line;
- upload_texture(userdata->tex_rgba[field], video_format.width, video_format.height, video_format.stride, interlaced_stride, GL_RGBA, GL_UNSIGNED_BYTE, field_start);
+ upload_texture(userdata->tex_rgba[field], video_format.width, video_format.height, video_format.stride, interlaced_stride, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, field_start);
break;
}
default:
printf("Timecode: '%s'\n", timecode_text.c_str());
}
+ // Update Y'CbCr settings for all cards.
+ {
+ unique_lock<mutex> lock(card_mutex);
+ for (unsigned card_index = 0; card_index < num_cards; ++card_index) {
+ 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;
+ }
+ }
+
// Get the main chain from the theme, and set its state immediately.
Theme::Chain theme_main_chain = theme->get_chain(0, pts(), global_flags.width, global_flags.height, input_state);
EffectChain *chain = theme_main_chain.chain;
theme->channel_clicked(preview_num);
}
+YCbCrInterpretation Mixer::get_input_ycbcr_interpretation(unsigned card_index) const
+{
+ unique_lock<mutex> lock(card_mutex);
+ return ycbcr_interpretation[card_index];
+}
+
+void Mixer::set_input_ycbcr_interpretation(unsigned card_index, const YCbCrInterpretation &interpretation)
+{
+ unique_lock<mutex> lock(card_mutex);
+ ycbcr_interpretation[card_index] = interpretation;
+}
+
void Mixer::start_mode_scanning(unsigned card_index)
{
assert(card_index < num_cards);
}
ready_frame = frame;
has_ready_frame = true;
- }
- if (new_frame_ready_callback) {
- new_frame_ready_callback();
+ // Call the callbacks under the mutex (they should be short),
+ // so that we don't race against a callback removal.
+ for (const auto &key_and_callback : new_frame_ready_callbacks) {
+ key_and_callback.second();
+ }
}
// Reduce the number of callbacks by filtering duplicates. The reason
return true;
}
-void Mixer::OutputChannel::set_frame_ready_callback(Mixer::new_frame_ready_callback_t callback)
+void Mixer::OutputChannel::add_frame_ready_callback(void *key, Mixer::new_frame_ready_callback_t callback)
{
- new_frame_ready_callback = callback;
+ unique_lock<mutex> lock(frame_mutex);
+ new_frame_ready_callbacks[key] = callback;
+}
+
+void Mixer::OutputChannel::remove_frame_ready_callback(void *key)
+{
+ unique_lock<mutex> lock(frame_mutex);
+ new_frame_ready_callbacks.erase(key);
}
void Mixer::OutputChannel::set_transition_names_updated_callback(Mixer::transition_names_updated_callback_t callback)