X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=mixer.cpp;h=31328212a24dc8a571a758794a82305504d5719c;hb=56ebeafc49f5e8786adde7365cce2249ae7980f4;hp=80426c2c8db8b62afaa03d8957102f14024016c6;hpb=dc508afd3b72b3c4212753c9a3e90f14d21f460e;p=nageru diff --git a/mixer.cpp b/mixer.cpp index 80426c2..3132821 100644 --- a/mixer.cpp +++ b/mixer.cpp @@ -39,6 +39,7 @@ #include "decklink_output.h" #include "defs.h" #include "disk_space_estimator.h" +#include "ffmpeg_capture.h" #include "flags.h" #include "input_mapping.h" #include "pbo_frame_allocator.h" @@ -88,7 +89,7 @@ void ensure_texture_resolution(PBOFrameAllocator::Userdata *userdata, unsigned f 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: @@ -121,13 +122,13 @@ void ensure_texture_resolution(PBOFrameAllocator::Userdata *userdata, unsigned f 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; @@ -211,7 +212,9 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards) 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. @@ -306,10 +309,23 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards) fprintf(stderr, "Initialized %u fake cards.\n", num_fake_cards); } + // Initialize all video inputs the theme asked for. Note that these are + // all put _after_ the regular cards, which stop at - 1. + std::vector video_inputs = theme->get_video_inputs(); + for (unsigned video_card_index = 0; video_card_index < video_inputs.size(); ++card_index, ++video_card_index) { + if (card_index >= MAX_VIDEO_CARDS) { + fprintf(stderr, "ERROR: Not enough card slots available for the videos the theme requested.\n"); + exit(1); + } + configure_card(card_index, video_inputs[video_card_index], CardType::FFMPEG_INPUT, /*output=*/nullptr); + video_inputs[video_card_index]->set_card_index(card_index); + } + num_video_inputs = video_inputs.size(); + BMUSBCapture::set_card_connected_callback(bind(&Mixer::bm_hotplug_add, this, _1)); BMUSBCapture::start_bm_thread(); - for (unsigned card_index = 0; card_index < num_cards; ++card_index) { + for (unsigned card_index = 0; card_index < num_cards + num_video_inputs; ++card_index) { cards[card_index].queue_length_policy.reset(card_index); } @@ -357,7 +373,7 @@ Mixer::~Mixer() { BMUSBCapture::stop_bm_thread(); - for (unsigned card_index = 0; card_index < num_cards; ++card_index) { + for (unsigned card_index = 0; card_index < num_cards + num_video_inputs; ++card_index) { { unique_lock lock(card_mutex); cards[card_index].should_quit = true; // Unblock thread. @@ -387,7 +403,14 @@ void Mixer::configure_card(unsigned card_index, CaptureInterface *capture, CardT card->output.reset(output); } - bmusb::PixelFormat pixel_format = global_flags.ten_bit_input ? PixelFormat_10BitYCbCr : PixelFormat_8BitYCbCr; + bmusb::PixelFormat pixel_format; + if (card_type == CardType::FFMPEG_INPUT) { + pixel_format = bmusb::PixelFormat_8BitBGRA; + } else if (global_flags.ten_bit_input) { + pixel_format = PixelFormat_10BitYCbCr; + } else { + pixel_format = PixelFormat_8BitYCbCr; + } card->capture->set_frame_callback(bind(&Mixer::bm_frame, this, card_index, _1, _2, _3, _4, _5, _6, _7)); if (card->frame_allocator == nullptr) { @@ -639,9 +662,9 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, 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: @@ -704,7 +727,7 @@ void Mixer::thread_func() // Start the actual capture. (We don't want to do it before we're actually ready // to process output frames.) - for (unsigned card_index = 0; card_index < num_cards; ++card_index) { + for (unsigned card_index = 0; card_index < num_cards + num_video_inputs; ++card_index) { if (int(card_index) != output_card_index) { cards[card_index].capture->start_bm_capture(); } @@ -747,7 +770,7 @@ void Mixer::thread_func() handle_hotplugged_cards(); - for (unsigned card_index = 0; card_index < num_cards; ++card_index) { + for (unsigned card_index = 0; card_index < num_cards + num_video_inputs; ++card_index) { if (card_index == master_card_index || !has_new_frame[card_index]) { continue; } @@ -768,7 +791,7 @@ void Mixer::thread_func() continue; } - for (unsigned card_index = 0; card_index < num_cards; ++card_index) { + for (unsigned card_index = 0; card_index < num_cards + num_video_inputs; ++card_index) { if (!has_new_frame[card_index] || new_frames[card_index].frame->len == 0) continue; @@ -935,7 +958,7 @@ start: cards[master_card_index].new_frames.front().received_timestamp; } - for (unsigned card_index = 0; card_index < num_cards; ++card_index) { + for (unsigned card_index = 0; card_index < num_cards + num_video_inputs; ++card_index) { CaptureCard *card = &cards[card_index]; if (input_card_is_master_clock(card_index, master_card_index)) { // We don't use the queue length policy for the master card, @@ -1064,6 +1087,12 @@ void Mixer::render_one_frame(int64_t duration) theme_main_chain.setup_chain(); //theme_main_chain.chain->enable_phase_timing(true); + // The theme can't (or at least shouldn't!) call connect_signal() on + // each FFmpeg input, so we'll do it here. + for (const pair &conn : theme->get_signal_connections()) { + conn.first->connect_signal_raw(conn.second->get_card_index()); + } + // If HDMI/SDI output is active and the user has requested auto mode, // its mode overrides the existing Y'CbCr setting for the chain. YCbCrLumaCoefficients ycbcr_output_coefficients; @@ -1298,10 +1327,12 @@ void Mixer::OutputChannel::output_frame(DisplayFrame frame) } 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 @@ -1368,9 +1399,16 @@ bool Mixer::OutputChannel::get_display_frame(DisplayFrame *frame) 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 lock(frame_mutex); + new_frame_ready_callbacks[key] = callback; +} + +void Mixer::OutputChannel::remove_frame_ready_callback(void *key) +{ + unique_lock lock(frame_mutex); + new_frame_ready_callbacks.erase(key); } void Mixer::OutputChannel::set_transition_names_updated_callback(Mixer::transition_names_updated_callback_t callback)