]> git.sesse.net Git - nageru/blobdiff - mixer.cpp
Release Nageru 1.3.1.
[nageru] / mixer.cpp
index dc9eea63fc0473f99d9260bffb8c99bb10aa485d..1934ee9429bcae271b3c6d22a9df64e2820a49fc 100644 (file)
--- a/mixer.cpp
+++ b/mixer.cpp
@@ -110,9 +110,9 @@ void QueueLengthPolicy::update_policy(int queue_length)
                if (queue_length >= int(safe_queue_length)) {
                        been_at_safe_point_since_last_starvation = true;
                }
-               if (++frames_with_at_least_one >= 50 && safe_queue_length > 0) {
+               if (++frames_with_at_least_one >= 1000 && safe_queue_length > 0) {
                        --safe_queue_length;
-                       fprintf(stderr, "Card %u: Spare frames for more than 50 frames, reducing safe limit to %u frames\n",
+                       fprintf(stderr, "Card %u: Spare frames for more than 1000 frames, reducing safe limit to %u frames\n",
                                card_index, safe_queue_length);
                        frames_with_at_least_one = 0;
                }
@@ -142,6 +142,7 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
        theme.reset(new Theme(global_flags.theme_filename.c_str(), resource_pool.get(), num_cards));
        for (unsigned i = 0; i < NUM_OUTPUTS; ++i) {
                output_channel[i].parent = this;
+               output_channel[i].channel = i;
        }
 
        ImageFormat inout_format;
@@ -157,7 +158,7 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
        display_chain->set_dither_bits(0);  // Don't bother.
        display_chain->finalize();
 
-       video_encoder.reset(new VideoEncoder(h264_encoder_surface, global_flags.va_display, WIDTH, HEIGHT, &httpd));
+       video_encoder.reset(new VideoEncoder(resource_pool.get(), h264_encoder_surface, global_flags.va_display, WIDTH, HEIGHT, &httpd));
 
        // Start listening for clients only once VideoEncoder has written its header, if any.
        httpd.start(9095);
@@ -248,6 +249,7 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
        // except the final makeup gain.
        if (global_flags.flat_audio) {
                set_locut_enabled(false);
+               set_gain_staging_auto(false);
                set_limiter_enabled(false);
                set_compressor_enabled(false);
        }
@@ -256,7 +258,9 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
        // and there's a limit to how important the peak meter is.
        peak_resampler.setup(OUTPUT_FREQUENCY, OUTPUT_FREQUENCY * 4, /*num_channels=*/2, /*hlen=*/16, /*frel=*/1.0);
 
-       alsa.reset(new ALSAOutput(OUTPUT_FREQUENCY, /*num_channels=*/2));
+       if (global_flags.enable_alsa_output) {
+               alsa.reset(new ALSAOutput(OUTPUT_FREQUENCY, /*num_channels=*/2));
+       }
 }
 
 Mixer::~Mixer()
@@ -287,19 +291,7 @@ void Mixer::configure_card(unsigned card_index, const QSurfaceFormat &format, Ca
        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->resampling_queue.reset(new ResamplingQueue(card_index, OUTPUT_FREQUENCY, OUTPUT_FREQUENCY, 2));
        card->capture->configure_card();
 }
 
@@ -368,7 +360,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode,
                }
        }
 
-       int64_t frame_length = int64_t(TIMEBASE * video_format.frame_rate_den) / video_format.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) / audio_format.num_channels / (audio_format.bits_per_sample / 8) : 0;
        if (num_samples > OUTPUT_FREQUENCY / 10) {
@@ -419,7 +411,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode,
                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",
                                card_index, card->last_timecode, timecode);
-                       card->resampling_queue.reset(new ResamplingQueue(OUTPUT_FREQUENCY, OUTPUT_FREQUENCY, 2));
+                       card->resampling_queue.reset(new ResamplingQueue(card_index, OUTPUT_FREQUENCY, OUTPUT_FREQUENCY, 2));
                        dropped_frames = 0;
                } else if (dropped_frames > 0) {
                        // Insert silence as needed.
@@ -501,59 +493,64 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode,
        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) ? 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 ||
-                   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.
+               // Put the actual texture upload in a lambda that is executed in the main thread.
+               // It is entirely possible to do this in the same thread (and it might even be
+               // faster, depending on the GPU and driver), but it appears to be trickling
+               // driver bugs very easily.
+               //
+               // Note that this means we must hold on to the actual frame data in <userdata>
+               // until the upload command is run, but we hold on to <frame> 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, userdata]() {
+                       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 ||
+                           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;
+                       }
+
+                       GLuint pbo = userdata->pbo;
+                       check_error();
+                       glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 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.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();
+                       }
+
                        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);
+                       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();
-                       glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, video_format.width, video_format.height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
+                       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();
-                       userdata->last_width[field] = video_format.width;
-                       userdata->last_height[field] = video_format.height;
-               }
-
-               GLuint pbo = userdata->pbo;
-               check_error();
-               glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 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.flush_pbos) {
-                       glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, field_y_start, video_format.width * video_format.height);
+                       glBindTexture(GL_TEXTURE_2D, 0);
                        check_error();
-                       glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, field_cbcr_start, cbcr_width * video_format.height * sizeof(uint16_t));
+                       glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
                        check_error();
-               }
-
-               glBindTexture(GL_TEXTURE_2D, userdata->tex_cbcr[field]);
-               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();
-               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();
-               RefCountedGLsync fence(GL_SYNC_GPU_COMMANDS_COMPLETE, /*flags=*/0);
-               check_error();
-               assert(fence.get() != nullptr);
-               glFlush();  // Make sure the main thread doesn't have to wait until we push out enough frames to make a new command buffer.
-               check_error();
+               };
 
                if (field == 1) {
                        // Don't upload the second field as fast as we can; wait until
@@ -581,7 +578,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode,
                        new_frame.length = frame_length;
                        new_frame.field = field;
                        new_frame.interlaced = video_format.interlaced;
-                       new_frame.ready_fence = fence;
+                       new_frame.upload_func = upload_func;
                        new_frame.dropped_frames = dropped_frames;
                        card->new_frames.push(move(new_frame));
                        card->new_frames_changed.notify_all();
@@ -648,13 +645,10 @@ void Mixer::thread_func()
                        insert_new_frame(new_frame->frame, new_frame->field, new_frame->interlaced, card_index, &input_state);
                        check_error();
 
-                       // The new texture might still be uploaded,
-                       // tell the GPU to wait until it's there.
-                       if (new_frame->ready_fence) {
-                               glWaitSync(new_frame->ready_fence.get(), /*flags=*/0, GL_TIMEOUT_IGNORED);
-                               check_error();
-                               new_frame->ready_fence.reset();
-                               check_error();
+                       // The new texture might need uploading before use.
+                       if (new_frame->upload_func) {
+                               new_frame->upload_func();
+                               new_frame->upload_func = nullptr;
                        }
                }
 
@@ -835,7 +829,10 @@ void Mixer::audio_thread_func()
 
                {
                        unique_lock<mutex> lock(audio_mutex);
-                       audio_task_queue_changed.wait(lock, [this]{ return !audio_task_queue.empty(); });
+                       audio_task_queue_changed.wait(lock, [this]{ return should_quit || !audio_task_queue.empty(); });
+                       if (should_quit) {
+                               return;
+                       }
                        task = audio_task_queue.front();
                        audio_task_queue.pop();
                }
@@ -857,9 +854,7 @@ void Mixer::process_audio_one_frame(int64_t frame_pts_int, int num_samples)
                samples_card.resize(num_samples * 2);
                {
                        unique_lock<mutex> lock(cards[card_index].audio_mutex);
-                       if (!cards[card_index].resampling_queue->get_output_samples(double(frame_pts_int) / TIMEBASE, &samples_card[0], num_samples)) {
-                               printf("Card %d reported previous underrun.\n", card_index);
-                       }
+                       cards[card_index].resampling_queue->get_output_samples(double(frame_pts_int) / TIMEBASE, &samples_card[0], num_samples);
                }
                if (card_index == selected_audio_card) {
                        samples_out = move(samples_card);
@@ -1085,6 +1080,7 @@ void Mixer::start()
 void Mixer::quit()
 {
        should_quit = true;
+       audio_task_queue_changed.notify_one();
        mixer_thread.join();
        audio_thread.join();
 }
@@ -1148,9 +1144,47 @@ void Mixer::OutputChannel::output_frame(DisplayFrame frame)
                has_ready_frame = true;
        }
 
-       if (has_new_frame_ready_callback) {
+       if (new_frame_ready_callback) {
                new_frame_ready_callback();
        }
+
+       // Reduce the number of callbacks by filtering duplicates. The reason
+       // why we bother doing this is that Qt seemingly can get into a state
+       // where its builds up an essentially unbounded queue of signals,
+       // consuming more and more memory, and there's no good way of collapsing
+       // user-defined signals or limiting the length of the queue.
+       if (transition_names_updated_callback) {
+               vector<string> transition_names = global_mixer->get_transition_names();
+               bool changed = false;
+               if (transition_names.size() != last_transition_names.size()) {
+                       changed = true;
+               } else {
+                       for (unsigned i = 0; i < transition_names.size(); ++i) {
+                               if (transition_names[i] != last_transition_names[i]) {
+                                       changed = true;
+                                       break;
+                               }
+                       }
+               }
+               if (changed) {
+                       transition_names_updated_callback(transition_names);
+                       last_transition_names = transition_names;
+               }
+       }
+       if (name_updated_callback) {
+               string name = global_mixer->get_channel_name(channel);
+               if (name != last_name) {
+                       name_updated_callback(name);
+                       last_name = name;
+               }
+       }
+       if (color_updated_callback) {
+               string color = global_mixer->get_channel_color(channel);
+               if (color != last_color) {
+                       color_updated_callback(color);
+                       last_color = color;
+               }
+       }
 }
 
 bool Mixer::OutputChannel::get_display_frame(DisplayFrame *frame)
@@ -1181,7 +1215,21 @@ bool Mixer::OutputChannel::get_display_frame(DisplayFrame *frame)
 void Mixer::OutputChannel::set_frame_ready_callback(Mixer::new_frame_ready_callback_t callback)
 {
        new_frame_ready_callback = callback;
-       has_new_frame_ready_callback = true;
+}
+
+void Mixer::OutputChannel::set_transition_names_updated_callback(Mixer::transition_names_updated_callback_t callback)
+{
+       transition_names_updated_callback = callback;
+}
+
+void Mixer::OutputChannel::set_name_updated_callback(Mixer::name_updated_callback_t callback)
+{
+       name_updated_callback = callback;
+}
+
+void Mixer::OutputChannel::set_color_updated_callback(Mixer::color_updated_callback_t callback)
+{
+       color_updated_callback = callback;
 }
 
 mutex RefCountedGLsync::fence_lock;