]> git.sesse.net Git - nageru/commitdiff
When doing a cut, do the shutdown in a separate thread.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 1 May 2016 19:52:15 +0000 (21:52 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sun, 1 May 2016 20:11:17 +0000 (22:11 +0200)
Alleviates problems with dropped frames in the mixer because shutdown
takes too long. We still have a problem with audio going to a somewhat
random of the two files, though.

mux.cpp
mux.h
quicksync_encoder.cpp
quicksync_encoder.h
video_encoder.cpp
video_encoder.h

diff --git a/mux.cpp b/mux.cpp
index e16943828fcccaa8f9d5e65ad9008cc9bb1456dd..b9860b943c402f08f8263de63ccd40aca3719c3b 100644 (file)
--- a/mux.cpp
+++ b/mux.cpp
@@ -1,5 +1,6 @@
 #include <assert.h>
 
+#include <algorithm>
 #include <mutex>
 #include <string>
 #include <vector>
@@ -107,8 +108,10 @@ void Mux::add_packet(const AVPacket &pkt, int64_t pts, int64_t dts)
        }
 
        {
-               lock_guard<mutex> lock(ctx_mu);
-               if (av_interleaved_write_frame(avctx, &pkt_copy) < 0) {
+               lock_guard<mutex> lock(mu);
+               if (plug_count > 0) {
+                       plugged_packets.push_back(av_packet_clone(&pkt_copy));
+               } else if (av_interleaved_write_frame(avctx, &pkt_copy) < 0) {
                        fprintf(stderr, "av_interleaved_write_frame() failed\n");
                        exit(1);
                }
@@ -116,3 +119,37 @@ void Mux::add_packet(const AVPacket &pkt, int64_t pts, int64_t dts)
 
        av_packet_unref(&pkt_copy);
 }
+
+void Mux::plug()
+{
+       lock_guard<mutex> lock(mu);
+       ++plug_count;
+}
+
+void Mux::unplug()
+{
+       lock_guard<mutex> lock(mu);
+       if (--plug_count > 0) {
+               return;
+       }
+       assert(plug_count >= 0);
+
+       sort(plugged_packets.begin(), plugged_packets.end(), [](const AVPacket *a, const AVPacket *b) {
+               int64_t a_dts = (a->dts == AV_NOPTS_VALUE ? a->pts : a->dts);
+               int64_t b_dts = (b->dts == AV_NOPTS_VALUE ? b->pts : b->dts);
+               if (a_dts != b_dts) {
+                       return a_dts < b_dts;
+               } else {
+                       return a->pts < b->pts;
+               }
+       });
+
+       for (AVPacket *pkt : plugged_packets) {
+               if (av_interleaved_write_frame(avctx, pkt) < 0) {
+                       fprintf(stderr, "av_interleaved_write_frame() failed\n");
+                       exit(1);
+               }
+               av_packet_free(&pkt);
+       }
+       plugged_packets.clear();
+}
diff --git a/mux.h b/mux.h
index c161b298d2e27408b4bbb23fc3f596abfd456515..47855f728ae82b5d09d9a180c0035fc59d770027 100644 (file)
--- a/mux.h
+++ b/mux.h
@@ -10,6 +10,7 @@ extern "C" {
 }
 
 #include <mutex>
+#include <vector>
 
 class KeyFrameSignalReceiver {
 public:
@@ -29,9 +30,23 @@ public:
        ~Mux();
        void add_packet(const AVPacket &pkt, int64_t pts, int64_t dts);
 
+       // As long as the mux is plugged, it will not actually write anything to disk,
+       // just queue the packets. Once it is unplugged, the packets are reordered by pts
+       // and written. This is primarily useful if you might have two different encoders
+       // writing to the mux at the same time (because one is shutting down), so that
+       // pts might otherwise come out-of-order.
+       //
+       // You can plug and unplug multiple times; only when the plug count reaches zero,
+       // something will actually happen.
+       void plug();
+       void unplug();
+
 private:
-       std::mutex ctx_mu;
-       AVFormatContext *avctx;  // Protected by <ctx_mu>.
+       std::mutex mu;
+       AVFormatContext *avctx;  // Protected by <mu>.
+       int plug_count = 0;  // Protected by <mu>.
+       std::vector<AVPacket *> plugged_packets;  // Protected by <mu>.
+
        AVStream *avstream_video, *avstream_audio;
        KeyFrameSignalReceiver *keyframe_signal_receiver;
 };
index b81cb53dba2173448ff8fb06e31e8dc85816bfc0..001901873b7fd641a43183f0c7a2b15b45e46ed8 100644 (file)
@@ -200,6 +200,7 @@ public:
        bool begin_frame(GLuint *y_tex, GLuint *cbcr_tex);
        RefCountedGLsync end_frame(int64_t pts, int64_t duration, const vector<RefCountedFrame> &input_frames);
        void shutdown();
+       void release_gl_resources();
        void set_stream_mux(Mux *mux)
        {
                stream_mux = mux;
@@ -251,11 +252,12 @@ private:
        VADisplay va_open_display(const string &va_display);
        void va_close_display(VADisplay va_dpy);
        int setup_encode();
-       int release_encode();
+       void release_encode();
        void update_ReferenceFrames(int frame_type);
        int update_RefPicList(int frame_type);
 
        bool is_shutdown = false;
+       bool has_released_gl_resources = false;
        bool use_zerocopy;
        int drm_fd = -1;
 
@@ -1695,13 +1697,26 @@ void QuickSyncEncoderImpl::storage_task_thread()
        }
 }
 
-int QuickSyncEncoderImpl::release_encode()
+void QuickSyncEncoderImpl::release_encode()
 {
        for (unsigned i = 0; i < SURFACE_NUM; i++) {
                vaDestroyBuffer(va_dpy, gl_surfaces[i].coded_buf);
                vaDestroySurfaces(va_dpy, &gl_surfaces[i].src_surface, 1);
                vaDestroySurfaces(va_dpy, &gl_surfaces[i].ref_surface, 1);
+       }
 
+       vaDestroyContext(va_dpy, context_id);
+       vaDestroyConfig(va_dpy, config_id);
+}
+
+void QuickSyncEncoderImpl::release_gl_resources()
+{
+       assert(is_shutdown);
+       if (has_released_gl_resources) {
+               return;
+       }
+
+       for (unsigned i = 0; i < SURFACE_NUM; i++) {
                if (!use_zerocopy) {
                        glBindBuffer(GL_PIXEL_PACK_BUFFER, gl_surfaces[i].pbo);
                        glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
@@ -1712,10 +1727,7 @@ int QuickSyncEncoderImpl::release_encode()
                resource_pool->release_2d_texture(gl_surfaces[i].cbcr_tex);
        }
 
-       vaDestroyContext(va_dpy, context_id);
-       vaDestroyConfig(va_dpy, config_id);
-
-       return 0;
+       has_released_gl_resources = true;
 }
 
 int QuickSyncEncoderImpl::deinit_va()
@@ -1782,6 +1794,7 @@ QuickSyncEncoderImpl::QuickSyncEncoderImpl(const std::string &filename, movit::R
 QuickSyncEncoderImpl::~QuickSyncEncoderImpl()
 {
        shutdown();
+       release_gl_resources();
 }
 
 bool QuickSyncEncoderImpl::begin_frame(GLuint *y_tex, GLuint *cbcr_tex)
index 52aaf77dd48d4ac4007c121189ce3d295dd14640..25b19725dee6841faf83c453af49fab38a279ca3 100644 (file)
@@ -62,7 +62,8 @@ public:
        void add_audio(int64_t pts, std::vector<float> audio);
        bool begin_frame(GLuint *y_tex, GLuint *cbcr_tex);
        RefCountedGLsync end_frame(int64_t pts, int64_t duration, const std::vector<RefCountedFrame> &input_frames);
-       void shutdown();  // Blocking.
+       void shutdown();  // Blocking. Does not require an OpenGL context.
+       void release_gl_resources();  // Requires an OpenGL context. Must be run after shutdown.
 
 private:
        std::unique_ptr<QuickSyncEncoderImpl> impl;
index 96d493252a02e1946363d552bf9908326803b7ee..63a7ba3b1e315363e4888039ac48158770b3b4b8 100644 (file)
@@ -61,6 +61,9 @@ VideoEncoder::VideoEncoder(ResourcePool *resource_pool, QSurface *surface, const
 VideoEncoder::~VideoEncoder()
 {
        quicksync_encoder.reset(nullptr);
+       while (quicksync_encoders_in_shutdown.load() > 0) {
+               usleep(10000);
+       }
        close_output_stream();
 }
 
@@ -68,23 +71,46 @@ void VideoEncoder::do_cut(int frame)
 {
        string filename = generate_local_dump_filename(frame);
        printf("Starting new recording: %s\n", filename.c_str());
-       quicksync_encoder->shutdown();
+
+       // Do the shutdown of the old encoder in a separate thread, since it can
+       // take some time (it needs to wait for all the frames in the queue to be
+       // done encoding, for one) and we are running on the main mixer thread.
+       // However, since this means both encoders could be sending packets at
+       // the same time, it means pts could come out of order to the stream mux,
+       // and we need to plug it until the shutdown is complete.
+       stream_mux->plug();
+       lock_guard<mutex> lock(qs_mu);
+       QuickSyncEncoder *old_encoder = quicksync_encoder.release();  // When we go C++14, we can use move capture instead.
+       thread([old_encoder, this]{
+               old_encoder->shutdown();
+               stream_mux->unplug();
+
+               // We cannot delete the encoder here, as this thread has no OpenGL context.
+               // We'll deal with it in begin_frame().
+               lock_guard<mutex> lock(qs_mu);
+               qs_needing_cleanup.emplace_back(old_encoder);
+       }).detach();
+
        quicksync_encoder.reset(new QuickSyncEncoder(filename, resource_pool, surface, va_display, width, height, oformat, stream_audio_encoder.get(), x264_encoder.get()));
        quicksync_encoder->set_stream_mux(stream_mux.get());
 }
 
 void VideoEncoder::add_audio(int64_t pts, std::vector<float> audio)
 {
+       lock_guard<mutex> lock(qs_mu);
        quicksync_encoder->add_audio(pts, audio);
 }
 
 bool VideoEncoder::begin_frame(GLuint *y_tex, GLuint *cbcr_tex)
 {
+       lock_guard<mutex> lock(qs_mu);
+       qs_needing_cleanup.clear();  // Since we have an OpenGL context here, and are called regularly.
        return quicksync_encoder->begin_frame(y_tex, cbcr_tex);
 }
 
 RefCountedGLsync VideoEncoder::end_frame(int64_t pts, int64_t duration, const std::vector<RefCountedFrame> &input_frames)
 {
+       lock_guard<mutex> lock(qs_mu);
        return quicksync_encoder->end_frame(pts, duration, input_frames);
 }
 
index 78162e9c91badb1f18273d7b27ee0f430c3734da..617b51842554b5db5f5325e4c1cfeafff94c591f 100644 (file)
@@ -7,6 +7,7 @@
 
 #include <stdint.h>
 #include <memory>
+#include <set>
 #include <string>
 #include <vector>
 
@@ -47,7 +48,8 @@ private:
        int write_packet(uint8_t *buf, int buf_size);
 
        AVOutputFormat *oformat;
-       std::unique_ptr<QuickSyncEncoder> quicksync_encoder;
+       std::mutex qs_mu;
+       std::unique_ptr<QuickSyncEncoder> quicksync_encoder;  // Under <qs_mu>.
        movit::ResourcePool *resource_pool;
        QSurface *surface;
        std::string va_display;
@@ -64,6 +66,12 @@ private:
        std::string stream_mux_header;
 
        bool stream_mux_writing_keyframes = false;
+
+       std::atomic<int> quicksync_encoders_in_shutdown{0};
+
+       // Encoders that are shutdown, but need to call release_gl_resources()
+       // (or be deleted) from some thread with an OpenGL context.
+       std::vector<std::unique_ptr<QuickSyncEncoder>> qs_needing_cleanup;  // Under <qs_mu>.
 };
 
 #endif