diff --git a/src/zm_decoder_thread.cpp b/src/zm_decoder_thread.cpp index 6b08efd39..e5b6e61d9 100644 --- a/src/zm_decoder_thread.cpp +++ b/src/zm_decoder_thread.cpp @@ -42,4 +42,10 @@ void DecoderThread::Run() { } } } + + // Release any packets we sent to the codec but never received frames for. + // The codec context is about to be (or has been) torn down for Pause / + // reconnect; leaving stale entries would create a permanent latency + // offset against the next codec context on resume. + monitor_->flushDecoderQueue(); } diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 8bb33f4e2..89cc72b64 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2892,6 +2892,21 @@ bool Monitor::applyDeinterlacing(std::shared_ptr &packet, Image *captu return true; } +void Monitor::flushDecoderQueue() { + // Called from DecoderThread::Run() as the decoder thread exits, so no + // concurrent access to decoder_queue: the thread that mutates it is us. + if (decoder_queue.empty()) return; + Debug(1, "Flushing %zu in-flight entries from decoder_queue", decoder_queue.size()); + for (auto &lock : decoder_queue) { + if (lock.packet_) { + lock.packet_->decoded = true; + lock.packet_->notify_all(); + } + } + decoder_queue.clear(); + packetqueue.notify_all(); // wake the analysis thread if it's waiting +} + bool Monitor::Decode() { AVCodecContext *context = camera->getVideoCodecContext(); ZMPacketLock packet_lock; @@ -3606,6 +3621,23 @@ unsigned int Monitor::Colours() const { return camera ? camera->Colours() : colo unsigned int Monitor::SubpixelOrder() const { return camera ? camera->SubpixelOrder() : 0; } int Monitor::PrimeCapture() { + // Stop the decoder before tearing the codec context down. The decoder + // thread holds a raw AVCodecContext* it got from + // camera->getVideoCodecContext(); camera->PrimeCapture() will Close() the + // camera (freeing that context) and OpenFfmpeg() a new one. Running the + // decoder against the dying context is unsafe; equally important, on + // exit the decoder thread releases the in-flight packet locks in + // decoder_queue (see DecoderThread::Run). Without that, stale entries + // survive the reconnect and create a permanent latency offset against + // the new codec context — the analysis thread blocks on + // !packet->decoded for those packets and the packetqueue fills to + // max_video_packet_count and stays there. + if (decoder) { + decoder->Stop(); + packetqueue.notify_all(); // wake the thread if it's blocked on wait_for + decoder->Join(); + } + int ret = camera->PrimeCapture(); if (ret <= 0) return ret; diff --git a/src/zm_monitor.h b/src/zm_monitor.h index d4daf6db2..4778d25e9 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -767,6 +767,13 @@ class Monitor : public std::enable_shared_from_this { RecordingOption Recording() const { return recording; } inline PacketQueue * GetPacketQueue() { return &packetqueue; } + + // Called by the decoder thread as it exits. Releases packet locks for + // anything it sent to the codec context but never received as a frame + // (codec context is about to be torn down for Pause/reconnect, so those + // packets will never produce output). Marks them decoded so the analysis + // thread can advance past them. + void flushDecoderQueue(); inline bool Enabled() const { return shared_data->capturing; }