]> git.sesse.net Git - nageru/blobdiff - mixer.cpp
Start pulling video orchestration logic into VideoEncoder.
[nageru] / mixer.cpp
index 0817ff9a15b49829c27bb7e80346d25faf52256f..dc9eea63fc0473f99d9260bffb8c99bb10aa485d 100644 (file)
--- a/mixer.cpp
+++ b/mixer.cpp
@@ -33,7 +33,7 @@
 #include "decklink_capture.h"
 #include "defs.h"
 #include "flags.h"
-#include "h264encode.h"
+#include "video_encoder.h"
 #include "pbo_frame_allocator.h"
 #include "ref_counted_gl_sync.h"
 #include "timebase.h"
@@ -92,23 +92,6 @@ void insert_new_frame(RefCountedFrame frame, unsigned field_num, bool interlaced
        }
 }
 
-string generate_local_dump_filename(int frame)
-{
-       time_t now = time(NULL);
-       tm now_tm;
-       localtime_r(&now, &now_tm);
-
-       char timestamp[256];
-       strftime(timestamp, sizeof(timestamp), "%F-%T%z", &now_tm);
-
-       // Use the frame number to disambiguate between two cuts starting
-       // on the same second.
-       char filename[256];
-       snprintf(filename, sizeof(filename), "%s%s-f%02d%s",
-               LOCAL_DUMP_PREFIX, timestamp, frame % 100, LOCAL_DUMP_SUFFIX);
-       return filename;
-}
-
 }  // namespace
 
 void QueueLengthPolicy::update_policy(int queue_length)
@@ -139,7 +122,7 @@ void QueueLengthPolicy::update_policy(int queue_length)
 }
 
 Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
-       : httpd(WIDTH, HEIGHT),
+       : httpd(),
          num_cards(num_cards),
          mixer_surface(create_surface(format)),
          h264_encoder_surface(create_surface(format)),
@@ -148,9 +131,6 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
          limiter(OUTPUT_FREQUENCY),
          compressor(OUTPUT_FREQUENCY)
 {
-       httpd.open_output_file(generate_local_dump_filename(/*frame=*/0).c_str());
-       httpd.start(9095);
-
        CHECK(init_movit(MOVIT_SHADER_DIR, MOVIT_DEBUG_OFF));
        check_error();
 
@@ -159,7 +139,7 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
        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;
        }
@@ -177,7 +157,10 @@ 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, global_flags.va_display, WIDTH, HEIGHT, &httpd));
+       video_encoder.reset(new VideoEncoder(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);
 
        // 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;
@@ -261,6 +244,14 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
 
        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);
@@ -283,7 +274,7 @@ Mixer::~Mixer()
                cards[card_index].capture->stop_dequeue_thread();
        }
 
-       h264_encoder.reset(nullptr);
+       video_encoder.reset(nullptr);
 }
 
 void Mixer::configure_card(unsigned card_index, const QSurfaceFormat &format, CaptureInterface *capture)
@@ -535,16 +526,24 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode,
                check_error();
                glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
                check_error();
-               glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, video_frame.size);
-               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();
@@ -553,6 +552,8 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode,
                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
@@ -657,9 +658,10 @@ void Mixer::thread_func()
                        }
                }
 
-               render_one_frame();
+               int64_t duration = new_frames[master_card_index].length;
+               render_one_frame(duration);
                ++frame;
-               pts_int += new_frames[master_card_index].length;
+               pts_int += duration;
 
                clock_gettime(CLOCK_MONOTONIC, &now);
                double elapsed = now.tv_sec - start.tv_sec +
@@ -672,12 +674,7 @@ void Mixer::thread_func()
                }
 
                if (should_cut.exchange(false)) {  // Test and clear.
-                       string filename = generate_local_dump_filename(frame);
-                       printf("Starting new recording: %s\n", filename.c_str());
-                       h264_encoder->shutdown();
-                       httpd.close_output_file();
-                       httpd.open_output_file(filename.c_str());
-                       h264_encoder.reset(new H264Encoder(h264_encoder_surface, global_flags.va_display, WIDTH, HEIGHT, &httpd));
+                       video_encoder->do_cut(frame);
                }
 
 #if 0
@@ -755,7 +752,7 @@ void Mixer::schedule_audio_resampling_tasks(unsigned dropped_frames, int num_sam
        }
 }
 
-void Mixer::render_one_frame()
+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);
@@ -764,7 +761,7 @@ void Mixer::render_one_frame()
        //theme_main_chain.chain->enable_phase_timing(true);
 
        GLuint y_tex, cbcr_tex;
-       bool got_frame = h264_encoder->begin_frame(&y_tex, &cbcr_tex);
+       bool got_frame = video_encoder->begin_frame(&y_tex, &cbcr_tex);
        assert(got_frame);
 
        // Render main chain.
@@ -785,11 +782,8 @@ void Mixer::render_one_frame()
        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);
+       RefCountedGLsync fence = video_encoder->end_frame(pts_int + av_delay, duration, theme_main_chain.input_frames);
 
        // The live frame just shows the RGBA texture we just rendered.
        // It owns rgba_tex now.
@@ -1010,7 +1004,7 @@ void Mixer::process_audio_one_frame(int64_t frame_pts_int, int num_samples)
        }
 
        // And finally add them to the output.
-       h264_encoder->add_audio(frame_pts_int, move(samples_out));
+       video_encoder->add_audio(frame_pts_int, move(samples_out));
 }
 
 void Mixer::subsample_chroma(GLuint src_tex, GLuint dst_tex)
@@ -1189,3 +1183,5 @@ void Mixer::OutputChannel::set_frame_ready_callback(Mixer::new_frame_ready_callb
        new_frame_ready_callback = callback;
        has_new_frame_ready_callback = true;
 }
+
+mutex RefCountedGLsync::fence_lock;