X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;ds=sidebyside;f=nageru%2Fmixer.cpp;h=9a86682daa91ca19b2325e3858093f55e80a0a27;hb=6cf87ad853439f8565c575fb29dc539a15fdba87;hp=ef73b613c0643bae638a3f6533a645ece146ea10;hpb=95a7a58f73728eb027d2bbd3cb819eede4d2ba79;p=nageru diff --git a/nageru/mixer.cpp b/nageru/mixer.cpp index ef73b61..9a86682 100644 --- a/nageru/mixer.cpp +++ b/nageru/mixer.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include @@ -44,6 +45,7 @@ #include "shared/disk_space_estimator.h" #include "ffmpeg_capture.h" #include "flags.h" +#include "image_input.h" #include "input_mapping.h" #include "shared/metrics.h" #include "mjpeg_encoder.h" @@ -152,11 +154,8 @@ void ensure_texture_resolution(PBOFrameAllocator::Userdata *userdata, unsigned f case 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_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr); - } else { - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr); - } + // NOTE: sRGB may be disabled by sRGBSwitchingFlatInput. + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8_ALPHA8, width, height, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr); check_error(); break; default: @@ -306,16 +305,18 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards) num_cards(num_cards), mixer_surface(create_surface(format)), h264_encoder_surface(create_surface(format)), - decklink_output_surface(create_surface(format)) + decklink_output_surface(create_surface(format)), + image_update_surface(create_surface(format)) { memcpy(ycbcr_interpretation, global_flags.ycbcr_interpretation, sizeof(ycbcr_interpretation)); 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") && - epoxy_has_gl_extension("GL_ARB_sampler_objects"); + if (!epoxy_has_gl_extension("GL_EXT_texture_sRGB_decode") || + !epoxy_has_gl_extension("GL_ARB_sampler_objects")) { + fprintf(stderr, "Nageru requires GL_EXT_texture_sRGB_decode and GL_ARB_sampler_objects to run.\n"); + exit(1); + } // Since we allow non-bouncing 4:2:2 YCbCrInputs, effective subpixel precision // will be halved when sampling them, and we need to compensate here. @@ -347,6 +348,11 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards) ycbcr_format.cb_y_position = 0.5f; ycbcr_format.cr_y_position = 0.5f; + // Initialize the neutral colors to sane values. + for (unsigned i = 0; i < MAX_VIDEO_CARDS; ++i) { + last_received_neutral_color[i] = RGBTriplet(1.0f, 1.0f, 1.0f); + } + // Display chain; shows the live output produced by the main chain (or rather, a copy of it). display_chain.reset(new EffectChain(global_flags.width, global_flags.height, resource_pool.get())); check_error(); @@ -500,10 +506,14 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards) } output_jitter_history.register_metrics({{ "card", "output" }}); + + ImageInput::start_update_thread(image_update_surface); } Mixer::~Mixer() { + ImageInput::end_update_thread(); + if (mjpeg_encoder != nullptr) { mjpeg_encoder->stop(); } @@ -999,6 +1009,10 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, new_frame.video_format = video_format; new_frame.y_offset = y_offset; new_frame.cbcr_offset = cbcr_offset; + if (card->type == CardType::FFMPEG_INPUT) { + FFmpegCapture *ffmpeg_capture = static_cast(card->capture.get()); + new_frame.neutral_color = ffmpeg_capture->get_last_neutral_color(); + } card->new_frames.push_back(move(new_frame)); card->jitter_history.frame_arrived(video_frame.received_timestamp, frame_length, dropped_frames); card->may_have_dropped_last_frame = false; @@ -1110,12 +1124,27 @@ void Mixer::thread_func() new_frame->upload_func = nullptr; } + // Only set the white balance if it actually changed. This means that the user + // is free to override the white balance in a video with no white balance information + // actually set (ie. r=g=b=1 all the time), or one where the white point is wrong, + // but frame-to-frame decisions will be heeded. We do this pretty much as late + // as possible (ie., after picking out the frame from the buffer), so that we are sure + // that the change takes effect on exactly the right frame. + if (fabs(new_frame->neutral_color.r - last_received_neutral_color[card_index].r) > 1e-3 || + fabs(new_frame->neutral_color.g - last_received_neutral_color[card_index].g) > 1e-3 || + fabs(new_frame->neutral_color.b - last_received_neutral_color[card_index].b) > 1e-3) { + theme->set_wb_for_signal(card_index, new_frame->neutral_color.r, new_frame->neutral_color.g, new_frame->neutral_color.b); + last_received_neutral_color[card_index] = new_frame->neutral_color; + } + if (new_frame->frame->data_copy != nullptr) { int mjpeg_card_index = mjpeg_encoder->get_mjpeg_stream_for_card(card_index); if (mjpeg_card_index != -1) { - mjpeg_encoder->upload_frame(pts_int, mjpeg_card_index, new_frame->frame, new_frame->video_format, new_frame->y_offset, new_frame->cbcr_offset, move(raw_audio[card_index])); + RGBTriplet neutral_color = theme->get_white_balance_for_signal(card_index); + mjpeg_encoder->upload_frame(pts_int, mjpeg_card_index, new_frame->frame, new_frame->video_format, new_frame->y_offset, new_frame->cbcr_offset, move(raw_audio[card_index]), neutral_color); } } + } int64_t frame_duration = output_frame_info.frame_duration; @@ -1266,7 +1295,7 @@ start: // we dropped. (may_have_dropped_last_frame is set whenever we // trim the queue completely away, and cleared when we actually // get a new frame.) - ((CEFCapture *)card->capture.get())->request_new_frame(); + ((CEFCapture *)card->capture.get())->request_new_frame(/*ignore_if_locked=*/true); } #endif } else {