From: Steinar H. Gunderson Date: Sun, 1 May 2016 19:52:15 +0000 (+0200) Subject: When doing a cut, do the shutdown in a separate thread. X-Git-Tag: 1.3.0~42 X-Git-Url: https://git.sesse.net/?p=nageru;a=commitdiff_plain;h=ab03e5e6f24b1651b4ca7df95e20aa5786939209 When doing a cut, do the shutdown in a separate thread. 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. --- diff --git a/mux.cpp b/mux.cpp index e169438..b9860b9 100644 --- a/mux.cpp +++ b/mux.cpp @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -107,8 +108,10 @@ void Mux::add_packet(const AVPacket &pkt, int64_t pts, int64_t dts) } { - lock_guard lock(ctx_mu); - if (av_interleaved_write_frame(avctx, &pkt_copy) < 0) { + lock_guard 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 lock(mu); + ++plug_count; +} + +void Mux::unplug() +{ + lock_guard 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 c161b29..47855f7 100644 --- a/mux.h +++ b/mux.h @@ -10,6 +10,7 @@ extern "C" { } #include +#include 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 . + std::mutex mu; + AVFormatContext *avctx; // Protected by . + int plug_count = 0; // Protected by . + std::vector plugged_packets; // Protected by . + AVStream *avstream_video, *avstream_audio; KeyFrameSignalReceiver *keyframe_signal_receiver; }; diff --git a/quicksync_encoder.cpp b/quicksync_encoder.cpp index b81cb53..0019018 100644 --- a/quicksync_encoder.cpp +++ b/quicksync_encoder.cpp @@ -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 &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) diff --git a/quicksync_encoder.h b/quicksync_encoder.h index 52aaf77..25b1972 100644 --- a/quicksync_encoder.h +++ b/quicksync_encoder.h @@ -62,7 +62,8 @@ public: void add_audio(int64_t pts, std::vector audio); bool begin_frame(GLuint *y_tex, GLuint *cbcr_tex); RefCountedGLsync end_frame(int64_t pts, int64_t duration, const std::vector &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 impl; diff --git a/video_encoder.cpp b/video_encoder.cpp index 96d4932..63a7ba3 100644 --- a/video_encoder.cpp +++ b/video_encoder.cpp @@ -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 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 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 audio) { + lock_guard lock(qs_mu); quicksync_encoder->add_audio(pts, audio); } bool VideoEncoder::begin_frame(GLuint *y_tex, GLuint *cbcr_tex) { + lock_guard 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 &input_frames) { + lock_guard lock(qs_mu); return quicksync_encoder->end_frame(pts, duration, input_frames); } diff --git a/video_encoder.h b/video_encoder.h index 78162e9..617b518 100644 --- a/video_encoder.h +++ b/video_encoder.h @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -47,7 +48,8 @@ private: int write_packet(uint8_t *buf, int buf_size); AVOutputFormat *oformat; - std::unique_ptr quicksync_encoder; + std::mutex qs_mu; + std::unique_ptr quicksync_encoder; // Under . 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 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> qs_needing_cleanup; // Under . }; #endif