void QueueLengthPolicy::update_policy(int queue_length)
{
if (queue_length < 0) { // Starvation.
- if (safe_queue_length < 5) {
+ if (been_at_safe_point_since_last_starvation && safe_queue_length < 5) {
++safe_queue_length;
fprintf(stderr, "Card %u: Starvation, increasing safe limit to %u frames\n",
card_index, safe_queue_length);
}
frames_with_at_least_one = 0;
+ been_at_safe_point_since_last_starvation = false;
return;
}
if (queue_length > 0) {
- if (++frames_with_at_least_one >= 50) {
+ 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) {
--safe_queue_length;
fprintf(stderr, "Card %u: Spare frames for more than 50 frames, reducing safe limit to %u frames\n",
card_index, safe_queue_length);
movit_texel_subpixel_precision /= 2.0;
resource_pool.reset(new ResourcePool);
- theme.reset(new Theme("theme.lua", resource_pool.get(), 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;
}
locut.init(FILTER_HPF, 2);
+ // If --flat-audio is given, turn off everything that messes with the sound,
+ // except the final makeup gain.
+ if (global_flags.flat_audio) {
+ set_locut_enabled(false);
+ set_limiter_enabled(false);
+ set_compressor_enabled(false);
+ }
+
// hlen=16 is pretty low quality, but we use quite a bit of CPU otherwise,
// 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);
new_frame.frame = RefCountedFrame(FrameAllocator::Frame());
new_frame.length = frame_length;
new_frame.interlaced = false;
- new_frame.ready_fence = nullptr;
new_frame.dropped_frames = dropped_frames;
card->new_frames.push(move(new_frame));
card->new_frames_changed.notify_all();
GLuint pbo = userdata->pbo;
check_error();
- glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo);
- check_error();
- glMemoryBarrier(GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT);
+ 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();
- 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)));
+ 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(y_offset + video_format.width * field_start_line));
+ 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_ARB, 0);
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
check_error();
- GLsync fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, /*flags=*/0);
+ 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();
- assert(fence != nullptr);
if (field == 1) {
// Don't upload the second field as fast as we can; wait until
bool has_new_frame[MAX_CARDS] = { false };
int num_samples[MAX_CARDS] = { 0 };
- // The first card is the master timer, so wait for it to have a new frame.
- // TODO: Make configurable, and with a timeout.
- unsigned master_card_index = 0;
-
- {
- unique_lock<mutex> lock(bmusb_mutex);
-
- cards[master_card_index].new_frames_changed.wait(lock, [this, master_card_index]{ return !cards[master_card_index].new_frames.empty(); });
-
- for (unsigned card_index = 0; card_index < num_cards; ++card_index) {
- CaptureCard *card = &cards[card_index];
- if (card->new_frames.empty()) {
- assert(card_index != master_card_index);
- card->queue_length_policy.update_policy(-1);
- continue;
- }
- new_frames[card_index] = move(card->new_frames.front());
- has_new_frame[card_index] = true;
- card->new_frames.pop();
- card->new_frames_changed.notify_all();
-
- int num_samples_times_timebase = OUTPUT_FREQUENCY * new_frames[card_index].length + card->fractional_samples;
- num_samples[card_index] = num_samples_times_timebase / TIMEBASE;
- card->fractional_samples = num_samples_times_timebase % TIMEBASE;
- assert(num_samples[card_index] >= 0);
-
- if (card_index != master_card_index) {
- // If we have excess frames compared to the policy for this card,
- // drop frames from the head.
- card->queue_length_policy.update_policy(card->new_frames.size());
- while (card->new_frames.size() > card->queue_length_policy.get_safe_queue_length()) {
- card->new_frames.pop();
- }
- }
- }
- }
-
- // Resample the audio as needed, including from previously dropped frames.
- assert(num_cards > 0);
- for (unsigned frame_num = 0; frame_num < new_frames[master_card_index].dropped_frames + 1; ++frame_num) {
- {
- // Signal to the audio thread to process this frame.
- unique_lock<mutex> lock(audio_mutex);
- audio_task_queue.push(AudioTask{pts_int, num_samples[master_card_index]});
- audio_task_queue_changed.notify_one();
- }
- if (frame_num != new_frames[master_card_index].dropped_frames) {
- // For dropped frames, increase the pts. Note that if the format changed
- // in the meantime, we have no way of detecting that; we just have to
- // assume the frame length is always the same.
- ++stats_dropped_frames;
- pts_int += new_frames[master_card_index].length;
- }
- }
+ // TODO: Add a timeout.
+ unsigned master_card_index = theme->map_signal(master_clock_channel);
+ assert(master_card_index < num_cards);
- if (audio_level_callback != nullptr) {
- unique_lock<mutex> lock(compressor_mutex);
- double loudness_s = r128.loudness_S();
- double loudness_i = r128.integrated();
- double loudness_range_low = r128.range_min();
- double loudness_range_high = r128.range_max();
-
- audio_level_callback(loudness_s, 20.0 * log10(peak),
- loudness_i, loudness_range_low, loudness_range_high,
- gain_staging_db, 20.0 * log10(final_makeup_gain),
- correlation.get_correlation());
- }
+ get_one_frame_from_each_card(master_card_index, new_frames, has_new_frame, num_samples);
+ schedule_audio_resampling_tasks(new_frames[master_card_index].dropped_frames, num_samples[master_card_index], new_frames[master_card_index].length);
+ stats_dropped_frames += new_frames[master_card_index].dropped_frames;
+ send_audio_level_callback();
for (unsigned card_index = 0; card_index < num_cards; ++card_index) {
if (card_index == master_card_index || !has_new_frame[card_index]) {
// 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, /*flags=*/0, GL_TIMEOUT_IGNORED);
+ glWaitSync(new_frame->ready_fence.get(), /*flags=*/0, GL_TIMEOUT_IGNORED);
check_error();
- glDeleteSync(new_frame->ready_fence);
+ new_frame->ready_fence.reset();
check_error();
}
}
- // Get the main chain from the theme, and set its state immediately.
- Theme::Chain theme_main_chain = theme->get_chain(0, pts(), WIDTH, HEIGHT, input_state);
- EffectChain *chain = theme_main_chain.chain;
- theme_main_chain.setup_chain();
- //theme_main_chain.chain->enable_phase_timing(true);
-
- GLuint y_tex, cbcr_tex;
- bool got_frame = h264_encoder->begin_frame(&y_tex, &cbcr_tex);
- assert(got_frame);
-
- // Render main chain.
- GLuint cbcr_full_tex = resource_pool->create_2d_texture(GL_RG8, WIDTH, HEIGHT);
- GLuint rgba_tex = resource_pool->create_2d_texture(GL_RGB565, WIDTH, HEIGHT); // Saves texture bandwidth, although dithering gets messed up.
- GLuint fbo = resource_pool->create_fbo(y_tex, cbcr_full_tex, rgba_tex);
- check_error();
- chain->render_to_fbo(fbo, WIDTH, HEIGHT);
- resource_pool->release_fbo(fbo);
-
- subsample_chroma(cbcr_full_tex, cbcr_tex);
- resource_pool->release_2d_texture(cbcr_full_tex);
-
- // Set the right state for rgba_tex.
- glBindFramebuffer(GL_FRAMEBUFFER, 0);
- glBindTexture(GL_TEXTURE_2D, rgba_tex);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
- RefCountedGLsync fence(GL_SYNC_GPU_COMMANDS_COMPLETE, /*flags=*/0);
- check_error();
-
- const int64_t av_delay = TIMEBASE / 10; // Corresponds to the fixed delay in resampling_queue.h. TODO: Make less hard-coded.
- h264_encoder->end_frame(fence, pts_int + av_delay, theme_main_chain.input_frames);
+ render_one_frame();
++frame;
pts_int += new_frames[master_card_index].length;
- // The live frame just shows the RGBA texture we just rendered.
- // It owns rgba_tex now.
- DisplayFrame live_frame;
- live_frame.chain = display_chain.get();
- live_frame.setup_chain = [this, rgba_tex]{
- display_input->set_texture_num(rgba_tex);
- };
- live_frame.ready_fence = fence;
- live_frame.input_frames = {};
- live_frame.temp_textures = { rgba_tex };
- output_channel[OUTPUT_LIVE].output_frame(live_frame);
-
- // Set up preview and any additional channels.
- for (int i = 1; i < theme->get_num_channels() + 2; ++i) {
- DisplayFrame display_frame;
- Theme::Chain chain = theme->get_chain(i, pts(), WIDTH, HEIGHT, input_state); // FIXME: dimensions
- display_frame.chain = chain.chain;
- display_frame.setup_chain = chain.setup_chain;
- display_frame.ready_fence = fence;
- display_frame.input_frames = chain.input_frames;
- display_frame.temp_textures = {};
- output_channel[i].output_frame(display_frame);
- }
-
clock_gettime(CLOCK_MONOTONIC, &now);
double elapsed = now.tv_sec - start.tv_sec +
1e-9 * (now.tv_nsec - start.tv_nsec);
resource_pool->clean_context();
}
+void Mixer::get_one_frame_from_each_card(unsigned master_card_index, CaptureCard::NewFrame new_frames[MAX_CARDS], bool has_new_frame[MAX_CARDS], int num_samples[MAX_CARDS])
+{
+ // The first card is the master timer, so wait for it to have a new frame.
+ unique_lock<mutex> lock(bmusb_mutex);
+ cards[master_card_index].new_frames_changed.wait(lock, [this, master_card_index]{ return !cards[master_card_index].new_frames.empty(); });
+
+ for (unsigned card_index = 0; card_index < num_cards; ++card_index) {
+ CaptureCard *card = &cards[card_index];
+ if (card->new_frames.empty()) {
+ assert(card_index != master_card_index);
+ card->queue_length_policy.update_policy(-1);
+ continue;
+ }
+ new_frames[card_index] = move(card->new_frames.front());
+ has_new_frame[card_index] = true;
+ card->new_frames.pop();
+ card->new_frames_changed.notify_all();
+
+ int num_samples_times_timebase = OUTPUT_FREQUENCY * new_frames[card_index].length + card->fractional_samples;
+ num_samples[card_index] = num_samples_times_timebase / TIMEBASE;
+ card->fractional_samples = num_samples_times_timebase % TIMEBASE;
+ assert(num_samples[card_index] >= 0);
+
+ if (card_index == master_card_index) {
+ // We don't use the queue length policy for the master card,
+ // but we will if it stops being the master. Thus, clear out
+ // the policy in case we switch in the future.
+ card->queue_length_policy.reset(card_index);
+ } else {
+ // If we have excess frames compared to the policy for this card,
+ // drop frames from the head.
+ card->queue_length_policy.update_policy(card->new_frames.size());
+ while (card->new_frames.size() > card->queue_length_policy.get_safe_queue_length()) {
+ card->new_frames.pop();
+ }
+ }
+ }
+}
+
+void Mixer::schedule_audio_resampling_tasks(unsigned dropped_frames, int num_samples_per_frame, int length_per_frame)
+{
+ // Resample the audio as needed, including from previously dropped frames.
+ assert(num_cards > 0);
+ for (unsigned frame_num = 0; frame_num < dropped_frames + 1; ++frame_num) {
+ {
+ // Signal to the audio thread to process this frame.
+ unique_lock<mutex> lock(audio_mutex);
+ audio_task_queue.push(AudioTask{pts_int, num_samples_per_frame});
+ audio_task_queue_changed.notify_one();
+ }
+ if (frame_num != dropped_frames) {
+ // For dropped frames, increase the pts. Note that if the format changed
+ // in the meantime, we have no way of detecting that; we just have to
+ // assume the frame length is always the same.
+ pts_int += length_per_frame;
+ }
+ }
+}
+
+void Mixer::render_one_frame()
+{
+ // Get the main chain from the theme, and set its state immediately.
+ Theme::Chain theme_main_chain = theme->get_chain(0, pts(), WIDTH, HEIGHT, input_state);
+ EffectChain *chain = theme_main_chain.chain;
+ theme_main_chain.setup_chain();
+ //theme_main_chain.chain->enable_phase_timing(true);
+
+ GLuint y_tex, cbcr_tex;
+ bool got_frame = h264_encoder->begin_frame(&y_tex, &cbcr_tex);
+ assert(got_frame);
+
+ // Render main chain.
+ GLuint cbcr_full_tex = resource_pool->create_2d_texture(GL_RG8, WIDTH, HEIGHT);
+ GLuint rgba_tex = resource_pool->create_2d_texture(GL_RGB565, WIDTH, HEIGHT); // Saves texture bandwidth, although dithering gets messed up.
+ GLuint fbo = resource_pool->create_fbo(y_tex, cbcr_full_tex, rgba_tex);
+ check_error();
+ chain->render_to_fbo(fbo, WIDTH, HEIGHT);
+ resource_pool->release_fbo(fbo);
+
+ subsample_chroma(cbcr_full_tex, cbcr_tex);
+ resource_pool->release_2d_texture(cbcr_full_tex);
+
+ // Set the right state for rgba_tex.
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glBindTexture(GL_TEXTURE_2D, rgba_tex);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ const int64_t av_delay = TIMEBASE / 10; // Corresponds to the fixed delay in resampling_queue.h. TODO: Make less hard-coded.
+ RefCountedGLsync fence = h264_encoder->end_frame(pts_int + av_delay, theme_main_chain.input_frames);
+
+ // The live frame just shows the RGBA texture we just rendered.
+ // It owns rgba_tex now.
+ DisplayFrame live_frame;
+ live_frame.chain = display_chain.get();
+ live_frame.setup_chain = [this, rgba_tex]{
+ display_input->set_texture_num(rgba_tex);
+ };
+ live_frame.ready_fence = fence;
+ live_frame.input_frames = {};
+ live_frame.temp_textures = { rgba_tex };
+ output_channel[OUTPUT_LIVE].output_frame(live_frame);
+
+ // Set up preview and any additional channels.
+ for (int i = 1; i < theme->get_num_channels() + 2; ++i) {
+ DisplayFrame display_frame;
+ Theme::Chain chain = theme->get_chain(i, pts(), WIDTH, HEIGHT, input_state); // FIXME: dimensions
+ display_frame.chain = chain.chain;
+ display_frame.setup_chain = chain.setup_chain;
+ display_frame.ready_fence = fence;
+ display_frame.input_frames = chain.input_frames;
+ display_frame.temp_textures = {};
+ output_channel[i].output_frame(display_frame);
+ }
+}
+
+void Mixer::send_audio_level_callback()
+{
+ if (audio_level_callback == nullptr) {
+ return;
+ }
+
+ unique_lock<mutex> lock(compressor_mutex);
+ double loudness_s = r128.loudness_S();
+ double loudness_i = r128.integrated();
+ double loudness_range_low = r128.range_min();
+ double loudness_range_high = r128.range_max();
+
+ audio_level_callback(loudness_s, 20.0 * log10(peak),
+ loudness_i, loudness_range_low, loudness_range_high,
+ gain_staging_db, 20.0 * log10(final_makeup_gain),
+ correlation.get_correlation());
+}
+
void Mixer::audio_thread_func()
{
while (!should_quit) {