]> git.sesse.net Git - nageru/blobdiff - nageru/mixer.cpp
Fix a segfault that could happen with FFmpeg inputs and slow startup.
[nageru] / nageru / mixer.cpp
index deaa8e73079710b7f13b8dffd6a9f084f0ae9b4c..d1b52ef2dc54b4a9db8ad84e755bbadc4ac76411 100644 (file)
 #include "cef_capture.h"
 #endif
 #include "chroma_subsampler.h"
-#include "context.h"
+#include "shared/context.h"
 #include "decklink_capture.h"
 #include "decklink_output.h"
 #include "defs.h"
-#include "disk_space_estimator.h"
+#include "shared/disk_space_estimator.h"
 #include "ffmpeg_capture.h"
 #include "flags.h"
 #include "input_mapping.h"
-#include "metrics.h"
+#include "shared/metrics.h"
+#include "mjpeg_encoder.h"
 #include "pbo_frame_allocator.h"
-#include "ref_counted_gl_sync.h"
+#include "shared/ref_counted_gl_sync.h"
 #include "resampling_queue.h"
-#include "timebase.h"
+#include "shared/timebase.h"
 #include "timecode_renderer.h"
 #include "v210_converter.h"
+#include "va_display_with_cleanup.h"
 #include "video_encoder.h"
 
 #undef Status
@@ -356,6 +358,9 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
        display_chain->finalize();
 
        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));
+       if (!global_flags.card_to_mjpeg_stream_export.empty()) {
+               mjpeg_encoder.reset(new MJPEGEncoder(&httpd, global_flags.va_display));
+       }
 
        // Must be instantiated after VideoEncoder has initialized global_flags.use_zerocopy.
        theme.reset(new Theme(global_flags.theme_filename, global_flags.theme_dirs, resource_pool.get(), num_cards));
@@ -499,6 +504,9 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
 
 Mixer::~Mixer()
 {
+       if (mjpeg_encoder != nullptr) {
+               mjpeg_encoder->stop();
+       }
        httpd.stop();
        BMUSBCapture::stop_bm_thread();
 
@@ -771,12 +779,12 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode,
 
                bool success;
                do {
-                       success = audio_mixer->add_silence(device, silence_samples, dropped_frames, frame_length);
+                       success = audio_mixer->add_silence(device, silence_samples, dropped_frames);
                } while (!success);
        }
 
        if (num_samples > 0) {
-               audio_mixer->add_audio(device, audio_frame.data + audio_offset, num_samples, audio_format, frame_length, audio_frame.received_timestamp);
+               audio_mixer->add_audio(device, audio_frame.data + audio_offset, num_samples, audio_format, audio_frame.received_timestamp);
        }
 
        // Done with the audio, so release it.
@@ -787,6 +795,11 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode,
        card->last_timecode = timecode;
 
        PBOFrameAllocator::Userdata *userdata = (PBOFrameAllocator::Userdata *)video_frame.userdata;
+       if (card->type == CardType::FFMPEG_INPUT && userdata != nullptr) {
+               FFmpegCapture *ffmpeg_capture = static_cast<FFmpegCapture *>(card->capture.get());
+               userdata->has_last_subtitle = ffmpeg_capture->get_has_last_subtitle();
+               userdata->last_subtitle = ffmpeg_capture->get_last_subtitle();
+       }
 
        size_t cbcr_width, cbcr_height, cbcr_offset, y_offset;
        size_t expected_length = video_format.stride * (video_format.height + video_format.extra_lines_top + video_format.extra_lines_bottom);
@@ -821,7 +834,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode,
                // Still send on the information that we _had_ a frame, even though it's corrupted,
                // so that pts can go up accordingly.
                {
-                       unique_lock<mutex> lock(card_mutex);
+                       lock_guard<mutex> lock(card_mutex);
                        CaptureCard::NewFrame new_frame;
                        new_frame.frame = RefCountedFrame(FrameAllocator::Frame());
                        new_frame.length = frame_length;
@@ -852,6 +865,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode,
                }
                frame_upload_start = steady_clock::now();
        }
+       assert(userdata != nullptr);
        userdata->last_interlaced = video_format.interlaced;
        userdata->last_has_signal = video_format.has_signal;
        userdata->last_is_connected = video_format.is_connected;
@@ -945,7 +959,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode,
                }
 
                {
-                       unique_lock<mutex> lock(card_mutex);
+                       lock_guard<mutex> lock(card_mutex);
                        CaptureCard::NewFrame new_frame;
                        new_frame.frame = frame;
                        new_frame.length = frame_length;
@@ -954,6 +968,9 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode,
                        new_frame.upload_func = upload_func;
                        new_frame.dropped_frames = dropped_frames;
                        new_frame.received_timestamp = video_frame.received_timestamp;  // Ignore the audio timestamp.
+                       new_frame.video_format = video_format;
+                       new_frame.y_offset = y_offset;
+                       new_frame.cbcr_offset = cbcr_offset;
                        card->new_frames.push_back(move(new_frame));
                        card->jitter_history.frame_arrived(video_frame.received_timestamp, frame_length, dropped_frames);
                        card->may_have_dropped_last_frame = false;
@@ -1063,6 +1080,15 @@ void Mixer::thread_func()
                                new_frame->upload_func();
                                new_frame->upload_func = nullptr;
                        }
+
+                       // Only bother doing MJPEG encoding if there are any connected clients
+                       // that want the stream. FIXME: We should also stop memcpy-ing if there are none!
+                       if (httpd.get_num_connected_multicam_clients() > 0) {
+                               auto stream_it = global_flags.card_to_mjpeg_stream_export.find(card_index);
+                               if (stream_it != global_flags.card_to_mjpeg_stream_export.end()) {
+                                       mjpeg_encoder->upload_frame(pts_int, stream_it->second, new_frame->frame, new_frame->video_format, new_frame->y_offset, new_frame->cbcr_offset);
+                               }
+                       }
                }
 
                int64_t frame_duration = output_frame_info.frame_duration;
@@ -1328,7 +1354,7 @@ void Mixer::schedule_audio_resampling_tasks(unsigned dropped_frames, int num_sam
                        // non-dropped frame; perhaps we should just discard that as well,
                        // since dropped frames are expected to be rare, and it might be
                        // better to just wait until we have a slightly more normal situation).
-                       unique_lock<mutex> lock(audio_mutex);
+                       lock_guard<mutex> lock(audio_mutex);
                        bool adjust_rate = !dropped_frame && !is_preroll;
                        audio_task_queue.push(AudioTask{pts_int, num_samples_per_frame, adjust_rate, frame_timestamp});
                        audio_task_queue_changed.notify_one();
@@ -1352,7 +1378,7 @@ void Mixer::render_one_frame(int64_t duration)
 
        // Update Y'CbCr settings for all cards.
        {
-               unique_lock<mutex> lock(card_mutex);
+               lock_guard<mutex> lock(card_mutex);
                for (unsigned card_index = 0; card_index < num_cards; ++card_index) {
                        YCbCrInterpretation *interpretation = &ycbcr_interpretation[card_index];
                        input_state.ycbcr_coefficients_auto[card_index] = interpretation->ycbcr_coefficients_auto;
@@ -1558,13 +1584,13 @@ void Mixer::channel_clicked(int preview_num)
 
 YCbCrInterpretation Mixer::get_input_ycbcr_interpretation(unsigned card_index) const
 {
-       unique_lock<mutex> lock(card_mutex);
+       lock_guard<mutex> lock(card_mutex);
        return ycbcr_interpretation[card_index];
 }
 
 void Mixer::set_input_ycbcr_interpretation(unsigned card_index, const YCbCrInterpretation &interpretation)
 {
-       unique_lock<mutex> lock(card_mutex);
+       lock_guard<mutex> lock(card_mutex);
        ycbcr_interpretation[card_index] = interpretation;
 }
 
@@ -1588,7 +1614,7 @@ void Mixer::start_mode_scanning(unsigned card_index)
 map<uint32_t, VideoMode> Mixer::get_available_output_video_modes() const
 {
        assert(desired_output_card_index != -1);
-       unique_lock<mutex> lock(card_mutex);
+       lock_guard<mutex> lock(card_mutex);
        return cards[desired_output_card_index].output->get_available_video_modes();
 }
 
@@ -1626,7 +1652,7 @@ void Mixer::OutputChannel::output_frame(DisplayFrame &&frame)
        // Store this frame for display. Remove the ready frame if any
        // (it was seemingly never used).
        {
-               unique_lock<mutex> lock(frame_mutex);
+               lock_guard<mutex> lock(frame_mutex);
                if (has_ready_frame) {
                        parent->release_display_frame(&ready_frame);
                }
@@ -1681,7 +1707,7 @@ void Mixer::OutputChannel::output_frame(DisplayFrame &&frame)
 
 bool Mixer::OutputChannel::get_display_frame(DisplayFrame *frame)
 {
-       unique_lock<mutex> lock(frame_mutex);
+       lock_guard<mutex> lock(frame_mutex);
        if (!has_current_frame && !has_ready_frame) {
                return false;
        }
@@ -1706,13 +1732,13 @@ bool Mixer::OutputChannel::get_display_frame(DisplayFrame *frame)
 
 void Mixer::OutputChannel::add_frame_ready_callback(void *key, Mixer::new_frame_ready_callback_t callback)
 {
-       unique_lock<mutex> lock(frame_mutex);
+       lock_guard<mutex> lock(frame_mutex);
        new_frame_ready_callbacks[key] = callback;
 }
 
 void Mixer::OutputChannel::remove_frame_ready_callback(void *key)
 {
-       unique_lock<mutex> lock(frame_mutex);
+       lock_guard<mutex> lock(frame_mutex);
        new_frame_ready_callbacks.erase(key);
 }