+Mixer *global_mixer = nullptr;
+
+namespace {
+
+void convert_fixed24_to_fp32(float *dst, size_t out_channels, const uint8_t *src, size_t in_channels, size_t num_samples)
+{
+ for (size_t i = 0; i < num_samples; ++i) {
+ for (size_t j = 0; j < out_channels; ++j) {
+ uint32_t s1 = *src++;
+ uint32_t s2 = *src++;
+ uint32_t s3 = *src++;
+ uint32_t s = s1 | (s1 << 8) | (s2 << 16) | (s3 << 24);
+ dst[i * out_channels + j] = int(s) * (1.0f / 4294967296.0f);
+ }
+ src += 3 * (in_channels - out_channels);
+ }
+}
+
+} // namespace
+
+Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
+ : httpd(LOCAL_DUMP_FILE_NAME, WIDTH, HEIGHT),
+ num_cards(num_cards),
+ mixer_surface(create_surface(format)),
+ h264_encoder_surface(create_surface(format)),
+ level_compressor(OUTPUT_FREQUENCY),
+ limiter(OUTPUT_FREQUENCY),
+ compressor(OUTPUT_FREQUENCY)
+{
+ httpd.start(9095);
+
+ CHECK(init_movit(MOVIT_SHADER_DIR, MOVIT_DEBUG_OFF));
+ check_error();
+
+ // Since we allow non-bouncing 4:2:2 YCbCrInputs, effective subpixel precision
+ // will be halved when sampling them, and we need to compensate here.
+ movit_texel_subpixel_precision /= 2.0;
+
+ resource_pool.reset(new ResourcePool);
+ theme.reset(new Theme("theme.lua", resource_pool.get(), num_cards));
+ for (unsigned i = 0; i < NUM_OUTPUTS; ++i) {
+ output_channel[i].parent = this;
+ }
+
+ ImageFormat inout_format;
+ inout_format.color_space = COLORSPACE_sRGB;
+ 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()));
+ check_error();
+ display_input = new FlatInput(inout_format, FORMAT_RGB, GL_UNSIGNED_BYTE, WIDTH, 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();
+
+ h264_encoder.reset(new H264Encoder(h264_encoder_surface, 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();
+
+ for (unsigned card_index = 0; card_index < num_cards; ++card_index) {
+ cards[card_index].usb->start_bm_capture();
+ }
+
+ //chain->enable_phase_timing(true);
+
+ // Set up stuff for NV12 conversion.
+
+ // Cb/Cr shader.
+ string cbcr_vert_shader = read_file("vs-cbcr.130.vert");
+ string cbcr_frag_shader =
+ "#version 130 \n"
+ "in vec2 tc0; \n"
+ "uniform sampler2D cbcr_tex; \n"
+ "void main() { \n"
+ " gl_FragColor = texture2D(cbcr_tex, tc0); \n"
+ "} \n";
+ cbcr_program_num = resource_pool->compile_glsl_program(cbcr_vert_shader, cbcr_frag_shader);