#include "mixer.h"
#include <assert.h>
-#include <endian.h>
#include <epoxy/egl.h>
#include <movit/effect_chain.h>
#include <movit/effect_util.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/resource.h>
-#include <sys/time.h>
-#include <time.h>
#include <algorithm>
#include <chrono>
-#include <cmath>
#include <condition_variable>
#include <cstddef>
+#include <cstdint>
#include <memory>
#include <mutex>
+#include <ratio>
#include <string>
#include <thread>
#include <utility>
#include <vector>
+#include "DeckLinkAPI.h"
+#include "LinuxCOM.h"
+#include "alsa_output.h"
#include "bmusb/bmusb.h"
#include "bmusb/fake_capture.h"
#include "context.h"
-#include "db.h"
#include "decklink_capture.h"
#include "defs.h"
#include "disk_space_estimator.h"
#include "flags.h"
+#include "input_mapping.h"
#include "pbo_frame_allocator.h"
#include "ref_counted_gl_sync.h"
+#include "resampling_queue.h"
#include "timebase.h"
#include "video_encoder.h"
+class IDeckLink;
class QOpenGLContext;
using namespace movit;
num_cards(num_cards),
mixer_surface(create_surface(format)),
h264_encoder_surface(create_surface(format)),
- audio_mixer(num_cards),
- correlation(OUTPUT_FREQUENCY)
+ audio_mixer(num_cards)
{
CHECK(init_movit(MOVIT_SHADER_DIR, MOVIT_DEBUG_OFF));
check_error();
inout_format.gamma_curve = GAMMA_sRGB;
// Display chain; shows the live output produced by the main chain (its RGBA version).
- display_chain.reset(new EffectChain(WIDTH, HEIGHT, resource_pool.get()));
+ display_chain.reset(new EffectChain(global_flags.width, global_flags.height, resource_pool.get()));
check_error();
- display_input = new FlatInput(inout_format, FORMAT_RGB, GL_UNSIGNED_BYTE, WIDTH, HEIGHT); // FIXME: GL_UNSIGNED_BYTE is really wrong.
+ display_input = new FlatInput(inout_format, FORMAT_RGB, GL_UNSIGNED_BYTE, global_flags.width, global_flags.height); // FIXME: GL_UNSIGNED_BYTE is really wrong.
display_chain->add_input(display_input);
display_chain->add_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
display_chain->set_dither_bits(0); // Don't bother.
display_chain->finalize();
- video_encoder.reset(new VideoEncoder(resource_pool.get(), h264_encoder_surface, global_flags.va_display, WIDTH, HEIGHT, &httpd, global_disk_space_estimator));
+ video_encoder.reset(new VideoEncoder(resource_pool.get(), h264_encoder_surface, global_flags.va_display, global_flags.width, global_flags.height, &httpd, global_disk_space_estimator));
// Start listening for clients only once VideoEncoder has written its header, if any.
httpd.start(9095);
unsigned num_fake_cards = 0;
for ( ; card_index < num_cards; ++card_index, ++num_fake_cards) {
- FakeCapture *capture = new FakeCapture(WIDTH, HEIGHT, FAKE_FPS, OUTPUT_FREQUENCY, card_index, global_flags.fake_cards_audio);
+ FakeCapture *capture = new FakeCapture(global_flags.width, global_flags.height, FAKE_FPS, OUTPUT_FREQUENCY, card_index, global_flags.fake_cards_audio);
configure_card(card_index, capture, /*is_fake_capture=*/true);
}
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();
-
- // 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);
-
if (global_flags.enable_alsa_output) {
alsa.reset(new ALSAOutput(OUTPUT_FREQUENCY, /*num_channels=*/2));
}
card->is_fake_capture = is_fake_capture;
card->capture->set_frame_callback(bind(&Mixer::bm_frame, this, card_index, _1, _2, _3, _4, _5, _6, _7));
if (card->frame_allocator == nullptr) {
- card->frame_allocator.reset(new PBOFrameAllocator(8 << 20, WIDTH, HEIGHT)); // 8 MB.
+ card->frame_allocator.reset(new PBOFrameAllocator(8 << 20, global_flags.width, global_flags.height)); // 8 MB.
}
card->capture->set_video_frame_allocator(card->frame_allocator.get());
if (card->surface == nullptr) {
DeviceSpec device{InputSourceType::CAPTURE_CARD, card_index};
audio_mixer.reset_resampler(device);
- audio_mixer.set_name(device, card->capture->get_description());
+ audio_mixer.set_display_name(device, card->capture->get_description());
+ audio_mixer.trigger_state_changed_callback();
}
}
}
-float find_peak(const float *samples, size_t num_samples)
-{
- float m = fabs(samples[0]);
- for (size_t i = 1; i < num_samples; ++i) {
- m = max(m, fabs(samples[i]));
- }
- return m;
-}
-
-void deinterleave_samples(const vector<float> &in, vector<float> *out_l, vector<float> *out_r)
-{
- size_t num_samples = in.size() / 2;
- out_l->resize(num_samples);
- out_r->resize(num_samples);
-
- const float *inptr = in.data();
- float *lptr = &(*out_l)[0];
- float *rptr = &(*out_r)[0];
- for (size_t i = 0; i < num_samples; ++i) {
- *lptr++ = *inptr++;
- *rptr++ = *inptr++;
- }
-}
-
} // namespace
void Mixer::bm_frame(unsigned card_index, uint16_t timecode,
fprintf(stderr, "Card %d dropped %d frame(s) (before timecode 0x%04x), inserting silence.\n",
card_index, dropped_frames, timecode);
- audio_mixer.add_silence(device, silence_samples, dropped_frames, frame_length);
+ bool success;
+ do {
+ success = audio_mixer.add_silence(device, silence_samples, dropped_frames, frame_length);
+ } while (!success);
}
audio_mixer.add_audio(device, audio_frame.data + audio_offset, num_samples, audio_format, frame_length);
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();
handle_hotplugged_cards();
CaptureCard *card = &cards[card_index];
if (card->capture->get_disconnected()) {
fprintf(stderr, "Card %u went away, replacing with a fake card.\n", card_index);
- FakeCapture *capture = new FakeCapture(WIDTH, HEIGHT, FAKE_FPS, OUTPUT_FREQUENCY, card_index, global_flags.fake_cards_audio);
+ FakeCapture *capture = new FakeCapture(global_flags.width, global_flags.height, FAKE_FPS, OUTPUT_FREQUENCY, card_index, global_flags.fake_cards_audio);
configure_card(card_index, capture, /*is_fake_capture=*/true);
card->queue_length_policy.reset(card_index);
card->capture->start_bm_capture();
void Mixer::render_one_frame(int64_t duration)
{
// 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);
+ Theme::Chain theme_main_chain = theme->get_chain(0, pts(), global_flags.width, global_flags.height, input_state);
EffectChain *chain = theme_main_chain.chain;
theme_main_chain.setup_chain();
//theme_main_chain.chain->enable_phase_timing(true);
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 cbcr_full_tex = resource_pool->create_2d_texture(GL_RG8, global_flags.width, global_flags.height);
+ GLuint rgba_tex = resource_pool->create_2d_texture(GL_RGB565, global_flags.width, global_flags.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);
+ chain->render_to_fbo(fbo, global_flags.width, global_flags.height);
resource_pool->release_fbo(fbo);
subsample_chroma(cbcr_full_tex, cbcr_tex);
// 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
+ Theme::Chain chain = theme->get_chain(i, pts(), global_flags.width, global_flags.height, input_state); // FIXME: dimensions
display_frame.chain = chain.chain;
display_frame.setup_chain = chain.setup_chain;
display_frame.ready_fence = fence;
}
}
-void Mixer::send_audio_level_callback()
-{
- if (audio_level_callback == nullptr) {
- return;
- }
-
- unique_lock<mutex> lock(audio_measure_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, to_db(peak),
- loudness_i, loudness_range_low, loudness_range_high,
- audio_mixer.get_gain_staging_db(),
- audio_mixer.get_final_makeup_gain_db(),
- correlation.get_correlation());
-}
-
void Mixer::audio_thread_func()
{
while (!should_quit) {
ResamplingQueue::RateAdjustmentPolicy rate_adjustment_policy =
task.adjust_rate ? ResamplingQueue::ADJUST_RATE : ResamplingQueue::DO_NOT_ADJUST_RATE;
- process_audio_one_frame(task.pts_int, task.num_samples, rate_adjustment_policy);
- }
-}
-
-void Mixer::process_audio_one_frame(int64_t frame_pts_int, int num_samples, ResamplingQueue::RateAdjustmentPolicy rate_adjustment_policy)
-{
- vector<float> samples_out = audio_mixer.get_output(double(frame_pts_int) / TIMEBASE, num_samples, rate_adjustment_policy);
-
- // Upsample 4x to find interpolated peak.
- peak_resampler.inp_data = samples_out.data();
- peak_resampler.inp_count = samples_out.size() / 2;
-
- vector<float> interpolated_samples_out;
- interpolated_samples_out.resize(samples_out.size());
- {
- unique_lock<mutex> lock(audio_measure_mutex);
-
- while (peak_resampler.inp_count > 0) { // About four iterations.
- peak_resampler.out_data = &interpolated_samples_out[0];
- peak_resampler.out_count = interpolated_samples_out.size() / 2;
- peak_resampler.process();
- size_t out_stereo_samples = interpolated_samples_out.size() / 2 - peak_resampler.out_count;
- peak = max<float>(peak, find_peak(interpolated_samples_out.data(), out_stereo_samples * 2));
- peak_resampler.out_data = nullptr;
+ vector<float> samples_out = audio_mixer.get_output(
+ double(task.pts_int) / TIMEBASE,
+ task.num_samples,
+ rate_adjustment_policy);
+
+ // Send the samples to the sound card, then add them to the output.
+ if (alsa) {
+ alsa->write(samples_out);
}
+ video_encoder->add_audio(task.pts_int, move(samples_out));
}
-
- // Find R128 levels and L/R correlation.
- vector<float> left, right;
- deinterleave_samples(samples_out, &left, &right);
- float *ptrs[] = { left.data(), right.data() };
- {
- unique_lock<mutex> lock(audio_measure_mutex);
- r128.process(left.size(), ptrs);
- audio_mixer.set_current_loudness(r128.loudness_M());
- correlation.process_samples(samples_out);
- }
-
- // Send the samples to the sound card.
- if (alsa) {
- alsa->write(samples_out);
- }
-
- // And finally add them to the output.
- video_encoder->add_audio(frame_pts_int, move(samples_out));
}
void Mixer::subsample_chroma(GLuint src_tex, GLuint dst_tex)
// Extract Cb/Cr.
GLuint fbo = resource_pool->create_fbo(dst_tex);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
- glViewport(0, 0, WIDTH/2, HEIGHT/2);
+ glViewport(0, 0, global_flags.width/2, global_flags.height/2);
check_error();
glUseProgram(cbcr_program_num);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
check_error();
- float chroma_offset_0[] = { -0.5f / WIDTH, 0.0f };
+ float chroma_offset_0[] = { -0.5f / global_flags.width, 0.0f };
set_uniform_vec2(cbcr_program_num, "foo", "chroma_offset_0", chroma_offset_0);
glBindBuffer(GL_ARRAY_BUFFER, cbcr_vbo);
theme->channel_clicked(preview_num);
}
-void Mixer::reset_meters()
-{
- unique_lock<mutex> lock(audio_measure_mutex);
- peak_resampler.reset();
- peak = 0.0f;
- r128.reset();
- r128.integr_start();
- correlation.reset();
-}
-
void Mixer::start_mode_scanning(unsigned card_index)
{
assert(card_index < num_cards);