X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;ds=sidebyside;f=mixer.cpp;h=a5ceeaf052b66edb95966446c3d5741ce925fb14;hb=a29cde44ae406abc5047fce5e09c63671aa10f38;hp=c7a4659c8a66a486a85345310f897cdf49e601c4;hpb=508e627eaad81db60cee79b596a800ba3d49a325;p=nageru diff --git a/mixer.cpp b/mixer.cpp index c7a4659..a5ceeaf 100644 --- a/mixer.cpp +++ b/mixer.cpp @@ -45,6 +45,8 @@ #include "ref_counted_gl_sync.h" #include "resampling_queue.h" #include "timebase.h" +#include "timecode_renderer.h" +#include "v210_converter.h" #include "video_encoder.h" class IDeckLink; @@ -76,12 +78,83 @@ void insert_new_frame(RefCountedFrame frame, unsigned field_num, bool interlaced } } +void ensure_texture_resolution(PBOFrameAllocator::Userdata *userdata, unsigned field, unsigned width, unsigned height) +{ + bool first; + if (global_flags.ten_bit_input) { + first = userdata->tex_v210[field] == 0 || userdata->tex_444[field] == 0; + } else { + first = userdata->tex_y[field] == 0 || userdata->tex_cbcr[field] == 0; + } + + if (first || + width != userdata->last_width[field] || + height != userdata->last_height[field]) { + // We changed resolution since last use of this texture, so we need to create + // a new object. Note that this each card has its own PBOFrameAllocator, + // we don't need to worry about these flip-flopping between resolutions. + if (global_flags.ten_bit_input) { + const size_t v210_width = v210Converter::get_minimum_v210_texture_width(width); + + glBindTexture(GL_TEXTURE_2D, userdata->tex_v210[field]); + check_error(); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB10_A2, v210_width, height, 0, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, nullptr); + check_error(); + glBindTexture(GL_TEXTURE_2D, userdata->tex_444[field]); + check_error(); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB10_A2, width, height, 0, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, nullptr); + check_error(); + } else { + size_t cbcr_width = width / 2; + + glBindTexture(GL_TEXTURE_2D, userdata->tex_cbcr[field]); + check_error(); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, cbcr_width, height, 0, GL_RG, GL_UNSIGNED_BYTE, nullptr); + check_error(); + glBindTexture(GL_TEXTURE_2D, userdata->tex_y[field]); + check_error(); + glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr); + check_error(); + } + userdata->last_width[field] = width; + userdata->last_height[field] = height; + } +} + +void upload_texture(GLuint tex, GLuint width, GLuint height, GLuint stride, bool interlaced_stride, GLenum format, GLenum type, GLintptr offset) +{ + if (interlaced_stride) { + stride *= 2; + } + if (global_flags.flush_pbos) { + glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, offset, stride * height); + check_error(); + } + + glBindTexture(GL_TEXTURE_2D, tex); + check_error(); + if (interlaced_stride) { + glPixelStorei(GL_UNPACK_ROW_LENGTH, width * 2); + check_error(); + } else { + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + check_error(); + } + + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, format, type, BUFFER_OFFSET(offset)); + check_error(); + glBindTexture(GL_TEXTURE_2D, 0); + check_error(); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + check_error(); +} + } // namespace void QueueLengthPolicy::update_policy(unsigned queue_length) { if (queue_length == 0) { // Starvation. - if (been_at_safe_point_since_last_starvation && safe_queue_length < global_flags.max_input_queue_frames) { + if (been_at_safe_point_since_last_starvation && safe_queue_length < unsigned(global_flags.max_input_queue_frames)) { ++safe_queue_length; fprintf(stderr, "Card %u: Starvation, increasing safe limit to %u frame(s)\n", card_index, safe_queue_length); @@ -199,6 +272,28 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards) chroma_subsampler.reset(new ChromaSubsampler(resource_pool.get())); + if (global_flags.ten_bit_input) { + if (!v210Converter::has_hardware_support()) { + fprintf(stderr, "ERROR: --ten-bit-input requires support for OpenGL compute shaders\n"); + fprintf(stderr, " (OpenGL 4.3, or GL_ARB_compute_shader + GL_ARB_shader_image_load_store).\n"); + exit(1); + } + v210_converter.reset(new v210Converter()); + + // These are all the widths listed in the Blackmagic SDK documentation + // (section 2.7.3, “Display Modes”). + v210_converter->precompile_shader(720); + v210_converter->precompile_shader(1280); + v210_converter->precompile_shader(1920); + v210_converter->precompile_shader(2048); + v210_converter->precompile_shader(3840); + v210_converter->precompile_shader(4096); + } + + timecode_renderer.reset(new TimecodeRenderer(resource_pool.get(), global_flags.width, global_flags.height)); + display_timecode_in_stream = global_flags.display_timecode_in_stream; + display_timecode_on_stdout = global_flags.display_timecode_on_stdout; + if (global_flags.enable_alsa_output) { alsa.reset(new ALSAOutput(OUTPUT_FREQUENCY, /*num_channels=*/2)); } @@ -251,6 +346,7 @@ void Mixer::configure_card(unsigned card_index, CaptureInterface *capture, bool } while (!card->new_frames.empty()) card->new_frames.pop_front(); card->last_timecode = -1; + card->capture->set_pixel_format(global_flags.ten_bit_input ? PixelFormat_10BitYCbCr : PixelFormat_8BitYCbCr); card->capture->configure_card(); // NOTE: start_bm_capture() happens in thread_func(). @@ -393,7 +489,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, card->last_timecode = timecode; - size_t expected_length = video_format.width * (video_format.height + video_format.extra_lines_top + video_format.extra_lines_bottom) * 2; + size_t expected_length = video_format.stride * (video_format.height + video_format.extra_lines_top + video_format.extra_lines_bottom); if (video_frame.len - video_offset == 0 || video_frame.len - video_offset != expected_length) { if (video_frame.len != 0) { @@ -413,6 +509,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, new_frame.length = frame_length; new_frame.interlaced = false; new_frame.dropped_frames = dropped_frames; + new_frame.received_timestamp = video_frame.received_timestamp; card->new_frames.push_back(move(new_frame)); card->new_frames_changed.notify_all(); } @@ -445,9 +542,9 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, RefCountedFrame frame(video_frame); // Upload the textures. - size_t cbcr_width = video_format.width / 2; - size_t cbcr_offset = video_offset / 2; - size_t y_offset = video_frame.size / 2 + video_offset / 2; + const size_t cbcr_width = video_format.width / 2; + const size_t cbcr_offset = video_offset / 2; + const size_t y_offset = video_frame.size / 2 + video_offset / 2; for (unsigned field = 0; field < num_fields; ++field) { // Put the actual texture upload in a lambda that is executed in the main thread. @@ -458,7 +555,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, // Note that this means we must hold on to the actual frame data in // until the upload command is run, but we hold on to much longer than that // (in fact, all the way until we no longer use the texture in rendering). - auto upload_func = [field, video_format, y_offset, cbcr_offset, cbcr_width, interlaced_stride, userdata]() { + auto upload_func = [this, field, video_format, y_offset, video_offset, cbcr_offset, cbcr_width, interlaced_stride, userdata]() { unsigned field_start_line; if (field == 1) { field_start_line = video_format.second_field_start; @@ -466,68 +563,26 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, field_start_line = video_format.extra_lines_top; } - if (userdata->tex_y[field] == 0 || - userdata->tex_cbcr[field] == 0 || - video_format.width != userdata->last_width[field] || - video_format.height != userdata->last_height[field]) { - // We changed resolution since last use of this texture, so we need to create - // a new object. Note that this each card has its own PBOFrameAllocator, - // we don't need to worry about these flip-flopping between resolutions. - glBindTexture(GL_TEXTURE_2D, userdata->tex_cbcr[field]); - check_error(); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, cbcr_width, video_format.height, 0, GL_RG, GL_UNSIGNED_BYTE, nullptr); - check_error(); - glBindTexture(GL_TEXTURE_2D, userdata->tex_y[field]); - check_error(); - glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, video_format.width, video_format.height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr); - check_error(); - userdata->last_width[field] = video_format.width; - userdata->last_height[field] = video_format.height; - } + ensure_texture_resolution(userdata, field, video_format.width, video_format.height); - GLuint pbo = userdata->pbo; - check_error(); - glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, userdata->pbo); check_error(); - size_t field_y_start = y_offset + video_format.width * field_start_line; - size_t field_cbcr_start = cbcr_offset + cbcr_width * field_start_line * sizeof(uint16_t); + if (global_flags.ten_bit_input) { + size_t field_start = video_offset + video_format.stride * field_start_line; + upload_texture(userdata->tex_v210[field], video_format.stride / sizeof(uint32_t), video_format.height, video_format.stride, interlaced_stride, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, field_start); + v210_converter->convert(userdata->tex_v210[field], userdata->tex_444[field], video_format.width, video_format.height); + } else { + size_t field_y_start = y_offset + video_format.width * field_start_line; + size_t field_cbcr_start = cbcr_offset + cbcr_width * field_start_line * sizeof(uint16_t); - if (global_flags.flush_pbos) { - glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, field_y_start, video_format.width * video_format.height); - check_error(); - glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, field_cbcr_start, cbcr_width * video_format.height * sizeof(uint16_t)); - check_error(); + // Make up our own strides, since we are interleaving. + upload_texture(userdata->tex_y[field], video_format.width, video_format.height, video_format.width, interlaced_stride, GL_RED, GL_UNSIGNED_BYTE, field_y_start); + 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); } - glBindTexture(GL_TEXTURE_2D, userdata->tex_cbcr[field]); - check_error(); - if (interlaced_stride) { - glPixelStorei(GL_UNPACK_ROW_LENGTH, cbcr_width * 2); - check_error(); - } else { - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - check_error(); - } - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cbcr_width, video_format.height, GL_RG, GL_UNSIGNED_BYTE, BUFFER_OFFSET(field_cbcr_start)); - check_error(); - glBindTexture(GL_TEXTURE_2D, userdata->tex_y[field]); - check_error(); - if (interlaced_stride) { - glPixelStorei(GL_UNPACK_ROW_LENGTH, video_format.width * 2); - check_error(); - } else { - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - check_error(); - } - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, video_format.width, video_format.height, GL_RED, GL_UNSIGNED_BYTE, BUFFER_OFFSET(field_y_start)); - check_error(); - glBindTexture(GL_TEXTURE_2D, 0); - check_error(); glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); check_error(); - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - check_error(); }; if (field == 1) { @@ -591,7 +646,6 @@ void Mixer::thread_func() steady_clock::time_point start, now; start = steady_clock::now(); - int frame = 0; int stats_dropped_frames = 0; while (!should_quit) { @@ -665,15 +719,15 @@ void Mixer::thread_func() int64_t frame_duration = output_frame_info.frame_duration; render_one_frame(frame_duration); - ++frame; + ++frame_num; pts_int += frame_duration; now = steady_clock::now(); double elapsed = duration(now - start).count(); - if (frame % 100 == 0) { + if (frame_num % 100 == 0) { printf("%d frames (%d dropped) in %.3f seconds = %.1f fps (%.1f ms/frame)", - frame, stats_dropped_frames, elapsed, frame / elapsed, - 1e3 * elapsed / frame); + frame_num, stats_dropped_frames, elapsed, frame_num / elapsed, + 1e3 * elapsed / frame_num); // chain->print_phase_timing(); // Check our memory usage, to see if we are close to our mlockall() @@ -710,7 +764,7 @@ void Mixer::thread_func() if (should_cut.exchange(false)) { // Test and clear. - video_encoder->do_cut(frame); + video_encoder->do_cut(frame_num); } #if 0 @@ -929,6 +983,12 @@ void Mixer::schedule_audio_resampling_tasks(unsigned dropped_frames, int num_sam void Mixer::render_one_frame(int64_t duration) { + // Determine the time code for this frame before we start rendering. + string timecode_text = timecode_renderer->get_timecode_text(double(pts_int) / TIMEBASE, frame_num); + if (display_timecode_on_stdout) { + printf("Timecode: '%s'\n", timecode_text.c_str()); + } + // 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; @@ -945,6 +1005,12 @@ void Mixer::render_one_frame(int64_t duration) GLuint fbo = resource_pool->create_fbo(y_tex, cbcr_full_tex, rgba_tex); check_error(); chain->render_to_fbo(fbo, global_flags.width, global_flags.height); + + if (display_timecode_in_stream) { + // Render the timecode on top. + timecode_renderer->render_timecode(fbo, timecode_text); + } + resource_pool->release_fbo(fbo); chroma_subsampler->subsample_chroma(cbcr_full_tex, global_flags.width, global_flags.height, cbcr_tex);