X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=mixer.cpp;h=a1c038d5329abc21ad0e9a5015e0575f7658b959;hb=1202ad9167357a461366dab9cbbaa6c578423253;hp=4b92041396bb896f67fa806c171d5edc7340c88b;hpb=835e7727e1d05190d550061cb8fc8cb2daa2283b;p=nageru diff --git a/mixer.cpp b/mixer.cpp index 4b92041..a1c038d 100644 --- a/mixer.cpp +++ b/mixer.cpp @@ -4,11 +4,11 @@ #include #include -#include #include #include #include #include +#include #include #include #include @@ -26,10 +26,13 @@ #include #include #include +#include #include "bmusb/bmusb.h" #include "context.h" +#include "decklink_capture.h" #include "defs.h" +#include "flags.h" #include "h264encode.h" #include "pbo_frame_allocator.h" #include "ref_counted_gl_sync.h" @@ -47,6 +50,7 @@ namespace { void convert_fixed24_to_fp32(float *dst, size_t out_channels, const uint8_t *src, size_t in_channels, size_t num_samples) { + assert(in_channels >= out_channels); for (size_t i = 0; i < num_samples; ++i) { for (size_t j = 0; j < out_channels; ++j) { uint32_t s1 = *src++; @@ -59,6 +63,20 @@ void convert_fixed24_to_fp32(float *dst, size_t out_channels, const uint8_t *src } } +void convert_fixed32_to_fp32(float *dst, size_t out_channels, const uint8_t *src, size_t in_channels, size_t num_samples) +{ + assert(in_channels >= out_channels); + for (size_t i = 0; i < num_samples; ++i) { + for (size_t j = 0; j < out_channels; ++j) { + // Note: Assumes little-endian. + int32_t s = *(int32_t *)src; + dst[i * out_channels + j] = s * (1.0f / 4294967296.0f); + src += 4; + } + src += 4 * (in_channels - out_channels); + } +} + void insert_new_frame(RefCountedFrame frame, unsigned field_num, bool interlaced, unsigned card_index, InputState *input_state) { if (interlaced) { @@ -132,52 +150,84 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards) display_chain->set_dither_bits(0); // Don't bother. display_chain->finalize(); - h264_encoder.reset(new H264Encoder(h264_encoder_surface, WIDTH, HEIGHT, &httpd)); + h264_encoder.reset(new H264Encoder(h264_encoder_surface, global_flags.va_display, WIDTH, HEIGHT, &httpd)); - for (unsigned card_index = 0; card_index < num_cards; ++card_index) { - printf("Configuring card %d...\n", card_index); - CaptureCard *card = &cards[card_index]; - card->usb = new BMUSBCapture(card_index); - card->usb->set_frame_callback(bind(&Mixer::bm_frame, this, card_index, _1, _2, _3, _4, _5, _6, _7)); - card->frame_allocator.reset(new PBOFrameAllocator(8 << 20, WIDTH, HEIGHT)); // 8 MB. - card->usb->set_video_frame_allocator(card->frame_allocator.get()); - card->surface = create_surface(format); - card->usb->set_dequeue_thread_callbacks( - [card]{ - eglBindAPI(EGL_OPENGL_API); - card->context = create_context(card->surface); - if (!make_current(card->context, card->surface)) { - printf("failed to create bmusb context\n"); - exit(1); - } - }, - [this]{ - resource_pool->clean_context(); - }); - card->resampling_queue.reset(new ResamplingQueue(OUTPUT_FREQUENCY, OUTPUT_FREQUENCY, 2)); - card->usb->configure_card(); - } - - BMUSBCapture::start_bm_thread(); + // First try initializing the PCI devices, then USB, until we have the desired number of cards. + unsigned num_pci_devices = 0, num_usb_devices = 0; + unsigned card_index = 0; - for (unsigned card_index = 0; card_index < num_cards; ++card_index) { - cards[card_index].usb->start_bm_capture(); + IDeckLinkIterator *decklink_iterator = CreateDeckLinkIteratorInstance(); + if (decklink_iterator != nullptr) { + for ( ; card_index < num_cards; ++card_index) { + IDeckLink *decklink; + if (decklink_iterator->Next(&decklink) != S_OK) { + break; + } + + configure_card(card_index, format, new DeckLinkCapture(decklink, card_index)); + ++num_pci_devices; + } + decklink_iterator->Release(); + fprintf(stderr, "Found %d DeckLink PCI card(s).\n", num_pci_devices); + } else { + fprintf(stderr, "DeckLink drivers not found. Probing for USB cards only.\n"); + } + for ( ; card_index < num_cards; ++card_index) { + configure_card(card_index, format, new BMUSBCapture(card_index - num_pci_devices)); + ++num_usb_devices; + } + + if (num_usb_devices > 0) { + BMUSBCapture::start_bm_thread(); + } + + for (card_index = 0; card_index < num_cards; ++card_index) { + cards[card_index].capture->start_bm_capture(); } // Set up stuff for NV12 conversion. // Cb/Cr shader. - string cbcr_vert_shader = read_file("vs-cbcr.130.vert"); + string cbcr_vert_shader = + "#version 130 \n" + " \n" + "in vec2 position; \n" + "in vec2 texcoord; \n" + "out vec2 tc0; \n" + "uniform vec2 foo_chroma_offset_0; \n" + " \n" + "void main() \n" + "{ \n" + " // The result of glOrtho(0.0, 1.0, 0.0, 1.0, 0.0, 1.0) is: \n" + " // \n" + " // 2.000 0.000 0.000 -1.000 \n" + " // 0.000 2.000 0.000 -1.000 \n" + " // 0.000 0.000 -2.000 -1.000 \n" + " // 0.000 0.000 0.000 1.000 \n" + " gl_Position = vec4(2.0 * position.x - 1.0, 2.0 * position.y - 1.0, -1.0, 1.0); \n" + " vec2 flipped_tc = texcoord; \n" + " tc0 = flipped_tc + foo_chroma_offset_0; \n" + "} \n"; string cbcr_frag_shader = "#version 130 \n" "in vec2 tc0; \n" "uniform sampler2D cbcr_tex; \n" + "out vec4 FragColor; \n" "void main() { \n" - " gl_FragColor = texture2D(cbcr_tex, tc0); \n" + " FragColor = texture(cbcr_tex, tc0); \n" "} \n"; vector frag_shader_outputs; cbcr_program_num = resource_pool->compile_glsl_program(cbcr_vert_shader, cbcr_frag_shader, frag_shader_outputs); + float vertices[] = { + 0.0f, 2.0f, + 0.0f, 0.0f, + 2.0f, 0.0f + }; + cbcr_vbo = generate_vbo(2, GL_FLOAT, sizeof(vertices), vertices); + cbcr_position_attribute_index = glGetAttribLocation(cbcr_program_num, "position"); + cbcr_texcoord_attribute_index = glGetAttribLocation(cbcr_program_num, "texcoord"); + r128.init(2, OUTPUT_FREQUENCY); r128.integr_start(); @@ -193,6 +243,7 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards) Mixer::~Mixer() { resource_pool->release_glsl_program(cbcr_program_num); + glDeleteBuffers(1, &cbcr_vbo); BMUSBCapture::stop_bm_thread(); for (unsigned card_index = 0; card_index < num_cards; ++card_index) { @@ -201,12 +252,39 @@ Mixer::~Mixer() cards[card_index].should_quit = true; // Unblock thread. cards[card_index].new_data_ready_changed.notify_all(); } - cards[card_index].usb->stop_dequeue_thread(); + cards[card_index].capture->stop_dequeue_thread(); } h264_encoder.reset(nullptr); } +void Mixer::configure_card(unsigned card_index, const QSurfaceFormat &format, CaptureInterface *capture) +{ + printf("Configuring card %d...\n", card_index); + + CaptureCard *card = &cards[card_index]; + card->capture = capture; + card->capture->set_frame_callback(bind(&Mixer::bm_frame, this, card_index, _1, _2, _3, _4, _5, _6, _7)); + card->frame_allocator.reset(new PBOFrameAllocator(8 << 20, WIDTH, HEIGHT)); // 8 MB. + card->capture->set_video_frame_allocator(card->frame_allocator.get()); + card->surface = create_surface(format); + card->capture->set_dequeue_thread_callbacks( + [card]{ + eglBindAPI(EGL_OPENGL_API); + card->context = create_context(card->surface); + if (!make_current(card->context, card->surface)) { + printf("failed to create bmusb context\n"); + exit(1); + } + }, + [this]{ + resource_pool->clean_context(); + }); + card->resampling_queue.reset(new ResamplingQueue(OUTPUT_FREQUENCY, OUTPUT_FREQUENCY, 2)); + card->capture->configure_card(); +} + + namespace { int unwrap_timecode(uint16_t current_wrapped, int last) @@ -246,23 +324,38 @@ void deinterleave_samples(const vector &in, vector *out_l, vector< } // namespace void Mixer::bm_frame(unsigned card_index, uint16_t timecode, - FrameAllocator::Frame video_frame, size_t video_offset, uint16_t video_format, - FrameAllocator::Frame audio_frame, size_t audio_offset, uint16_t audio_format) + FrameAllocator::Frame video_frame, size_t video_offset, VideoFormat video_format, + FrameAllocator::Frame audio_frame, size_t audio_offset, AudioFormat audio_format) { CaptureCard *card = &cards[card_index]; - unsigned width, height, second_field_start, frame_rate_nom, frame_rate_den, extra_lines_top, extra_lines_bottom; - bool interlaced; + if (is_mode_scanning[card_index]) { + if (video_format.has_signal) { + // Found a stable signal, so stop scanning. + is_mode_scanning[card_index] = false; + } else { + static constexpr double switch_time_s = 0.5; // Should be enough time for the signal to stabilize. + timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + double sec_since_last_switch = (now.tv_sec - last_mode_scan_change[card_index].tv_sec) + + 1e-9 * (now.tv_nsec - last_mode_scan_change[card_index].tv_nsec); + if (sec_since_last_switch > switch_time_s) { + // It isn't this mode; try the next one. + mode_scanlist_index[card_index]++; + mode_scanlist_index[card_index] %= mode_scanlist[card_index].size(); + cards[card_index].capture->set_video_mode(mode_scanlist[card_index][mode_scanlist_index[card_index]]); + last_mode_scan_change[card_index] = now; + } + } + } - decode_video_format(video_format, &width, &height, &second_field_start, &extra_lines_top, &extra_lines_bottom, - &frame_rate_nom, &frame_rate_den, &interlaced); // Ignore return value for now. - int64_t frame_length = int64_t(TIMEBASE * frame_rate_den) / frame_rate_nom; + int64_t frame_length = int64_t(TIMEBASE * video_format.frame_rate_den) / video_format.frame_rate_nom; - size_t num_samples = (audio_frame.len >= audio_offset) ? (audio_frame.len - audio_offset) / 8 / 3 : 0; + size_t num_samples = (audio_frame.len >= audio_offset) ? (audio_frame.len - audio_offset) / audio_format.num_channels / (audio_format.bits_per_sample / 8) : 0; if (num_samples > OUTPUT_FREQUENCY / 10) { printf("Card %d: Dropping frame with implausible audio length (len=%d, offset=%d) [timecode=0x%04x video_len=%d video_offset=%d video_format=%x)\n", card_index, int(audio_frame.len), int(audio_offset), - timecode, int(video_frame.len), int(video_offset), video_format); + timecode, int(video_frame.len), int(video_offset), video_format.id); if (video_frame.owner) { video_frame.owner->release_frame(video_frame); } @@ -281,7 +374,17 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, // Convert the audio to stereo fp32 and add it. vector audio; audio.resize(num_samples * 2); - convert_fixed24_to_fp32(&audio[0], 2, audio_frame.data + audio_offset, 8, num_samples); + switch (audio_format.bits_per_sample) { + case 24: + convert_fixed24_to_fp32(&audio[0], 2, audio_frame.data + audio_offset, audio_format.num_channels, num_samples); + break; + case 32: + convert_fixed32_to_fp32(&audio[0], 2, audio_frame.data + audio_offset, audio_format.num_channels, num_samples); + break; + default: + fprintf(stderr, "Cannot handle audio with %u bits per sample\n", audio_format.bits_per_sample); + assert(false); + } // Add the audio. { @@ -289,7 +392,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, // Number of samples per frame if we need to insert silence. // (Could be nonintegral, but resampling will save us then.) - int silence_samples = OUTPUT_FREQUENCY * frame_rate_den / frame_rate_nom; + int silence_samples = OUTPUT_FREQUENCY * video_format.frame_rate_den / video_format.frame_rate_nom; if (dropped_frames > MAX_FPS * 2) { fprintf(stderr, "Card %d lost more than two seconds (or time code jumping around; from 0x%04x to 0x%04x), resetting resampler\n", @@ -331,7 +434,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, if (card->should_quit) return; } - size_t expected_length = width * (height + extra_lines_top + extra_lines_bottom) * 2; + size_t expected_length = video_format.width * (video_format.height + video_format.extra_lines_top + video_format.extra_lines_bottom) * 2; if (video_frame.len - video_offset == 0 || video_frame.len - video_offset != expected_length) { if (video_frame.len != 0) { @@ -359,48 +462,49 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, PBOFrameAllocator::Userdata *userdata = (PBOFrameAllocator::Userdata *)video_frame.userdata; - unsigned num_fields = interlaced ? 2 : 1; + unsigned num_fields = video_format.interlaced ? 2 : 1; timespec frame_upload_start; - if (interlaced) { + if (video_format.interlaced) { // Send the two fields along as separate frames; the other side will need to add // a deinterlacer to actually get this right. - assert(height % 2 == 0); - height /= 2; + assert(video_format.height % 2 == 0); + video_format.height /= 2; assert(frame_length % 2 == 0); frame_length /= 2; num_fields = 2; clock_gettime(CLOCK_MONOTONIC, &frame_upload_start); } - userdata->last_interlaced = interlaced; - userdata->last_frame_rate_nom = frame_rate_nom; - userdata->last_frame_rate_den = frame_rate_den; + userdata->last_interlaced = video_format.interlaced; + userdata->last_has_signal = video_format.has_signal; + userdata->last_frame_rate_nom = video_format.frame_rate_nom; + userdata->last_frame_rate_den = video_format.frame_rate_den; RefCountedFrame new_frame(video_frame); // Upload the textures. - size_t cbcr_width = width / 2; + 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; for (unsigned field = 0; field < num_fields; ++field) { - unsigned field_start_line = (field == 1) ? second_field_start : extra_lines_top + field * (height + 22); + unsigned field_start_line = (field == 1) ? video_format.second_field_start : video_format.extra_lines_top + field * (video_format.height + 22); if (userdata->tex_y[field] == 0 || userdata->tex_cbcr[field] == 0 || - width != userdata->last_width[field] || - height != userdata->last_height[field]) { + 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, height, 0, GL_RG, GL_UNSIGNED_BYTE, nullptr); + 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, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr); + 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] = width; - userdata->last_height[field] = height; + userdata->last_width[field] = video_format.width; + userdata->last_height[field] = video_format.height; } GLuint pbo = userdata->pbo; @@ -412,14 +516,16 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, glBindTexture(GL_TEXTURE_2D, userdata->tex_cbcr[field]); check_error(); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cbcr_width, height, GL_RG, GL_UNSIGNED_BYTE, BUFFER_OFFSET(cbcr_offset + cbcr_width * field_start_line * sizeof(uint16_t))); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cbcr_width, video_format.height, GL_RG, GL_UNSIGNED_BYTE, BUFFER_OFFSET(cbcr_offset + cbcr_width * field_start_line * sizeof(uint16_t))); check_error(); glBindTexture(GL_TEXTURE_2D, userdata->tex_y[field]); check_error(); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, BUFFER_OFFSET(y_offset + width * field_start_line)); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, video_format.width, video_format.height, GL_RED, GL_UNSIGNED_BYTE, BUFFER_OFFSET(y_offset + video_format.width * field_start_line)); check_error(); glBindTexture(GL_TEXTURE_2D, 0); check_error(); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); + check_error(); GLsync fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, /*flags=*/0); check_error(); assert(fence != nullptr); @@ -449,7 +555,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, card->new_frame = new_frame; card->new_frame_length = frame_length; card->new_frame_field = field; - card->new_frame_interlaced = interlaced; + card->new_frame_interlaced = video_format.interlaced; card->new_data_ready_fence = fence; card->dropped_frames = dropped_frames; card->new_data_ready_changed.notify_all(); @@ -491,7 +597,6 @@ void Mixer::thread_func() for (unsigned card_index = 0; card_index < num_cards; ++card_index) { CaptureCard *card = &cards[card_index]; - card_copy[card_index].usb = card->usb; card_copy[card_index].new_data_ready = card->new_data_ready; card_copy[card_index].new_frame = card->new_frame; card_copy[card_index].new_frame_length = card->new_frame_length; @@ -653,7 +758,7 @@ void Mixer::thread_func() h264_encoder->shutdown(); httpd.close_output_file(); httpd.open_output_file(filename.c_str()); - h264_encoder.reset(new H264Encoder(h264_encoder_surface, WIDTH, HEIGHT, &httpd)); + h264_encoder.reset(new H264Encoder(h264_encoder_surface, global_flags.va_display, WIDTH, HEIGHT, &httpd)); } #if 0 @@ -692,6 +797,11 @@ void Mixer::process_audio_one_frame(int64_t frame_pts_int, int num_samples) { vector samples_card; vector samples_out; + + // TODO: Allow mixing audio from several sources. + unsigned selected_audio_card = theme->map_signal(audio_source_channel); + assert(selected_audio_card < num_cards); + for (unsigned card_index = 0; card_index < num_cards; ++card_index) { samples_card.resize(num_samples * 2); { @@ -700,8 +810,7 @@ void Mixer::process_audio_one_frame(int64_t frame_pts_int, int num_samples) printf("Card %d reported previous underrun.\n", card_index); } } - // TODO: Allow using audio from the other card(s) as well. - if (card_index == 0) { + if (card_index == selected_audio_card) { samples_out = move(samples_card); } } @@ -853,12 +962,6 @@ void Mixer::subsample_chroma(GLuint src_tex, GLuint dst_tex) glGenVertexArrays(1, &vao); check_error(); - float vertices[] = { - 0.0f, 2.0f, - 0.0f, 0.0f, - 2.0f, 0.0f - }; - glBindVertexArray(vao); check_error(); @@ -885,17 +988,28 @@ void Mixer::subsample_chroma(GLuint src_tex, GLuint dst_tex) float chroma_offset_0[] = { -0.5f / WIDTH, 0.0f }; set_uniform_vec2(cbcr_program_num, "foo", "chroma_offset_0", chroma_offset_0); - GLuint position_vbo = fill_vertex_attribute(cbcr_program_num, "position", 2, GL_FLOAT, sizeof(vertices), vertices); - GLuint texcoord_vbo = fill_vertex_attribute(cbcr_program_num, "texcoord", 2, GL_FLOAT, sizeof(vertices), vertices); // Same as vertices. + glBindBuffer(GL_ARRAY_BUFFER, cbcr_vbo); + check_error(); + + for (GLint attr_index : { cbcr_position_attribute_index, cbcr_texcoord_attribute_index }) { + glEnableVertexAttribArray(attr_index); + check_error(); + glVertexAttribPointer(attr_index, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0)); + check_error(); + } glDrawArrays(GL_TRIANGLES, 0, 3); check_error(); - cleanup_vertex_attribute(cbcr_program_num, "position", position_vbo); - cleanup_vertex_attribute(cbcr_program_num, "texcoord", texcoord_vbo); + for (GLint attr_index : { cbcr_position_attribute_index, cbcr_texcoord_attribute_index }) { + glDisableVertexAttribArray(attr_index); + check_error(); + } glUseProgram(0); check_error(); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + check_error(); resource_pool->release_fbo(fbo); glDeleteVertexArrays(1, &vao); @@ -943,6 +1057,23 @@ void Mixer::reset_meters() correlation.reset(); } +void Mixer::start_mode_scanning(unsigned card_index) +{ + assert(card_index < num_cards); + if (is_mode_scanning[card_index]) { + return; + } + is_mode_scanning[card_index] = true; + mode_scanlist[card_index].clear(); + for (const auto &mode : cards[card_index].capture->get_available_video_modes()) { + mode_scanlist[card_index].push_back(mode.first); + } + assert(!mode_scanlist[card_index].empty()); + mode_scanlist_index[card_index] = 0; + cards[card_index].capture->set_video_mode(mode_scanlist[card_index][0]); + clock_gettime(CLOCK_MONOTONIC, &last_mode_scan_change[card_index]); +} + Mixer::OutputChannel::~OutputChannel() { if (has_current_frame) {