diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index d43b4fae0..a63085e22 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -3159,7 +3159,7 @@ bool Monitor::Decode() { (decoding == DECODING_ALWAYS) || (decoding == DECODING_KEYFRAMES) || ((decoding == DECODING_ONDEMAND) && (hasViewers() || shared_data->last_write_index == image_buffer_count)) || - ((decoding == DECODING_KEYFRAMESONDEMAND) && hasViewers()); + ((decoding == DECODING_KEYFRAMESONDEMAND) && (hasViewers() || decoder_requires_next_packet)); if (!needs_decoding) { Debug(1, "Flushing decoder in phase 1: %zu packets queued but decoding no longer needed", @@ -3180,11 +3180,16 @@ bool Monitor::Decode() { auto front_packet = front_lock.packet_; int ret = front_packet->receive_frame(context); + Debug(2, "RECV packet=%d ret=%d", front_packet->image_index, ret); if (ret > 0) { // Success - got a decoded frame, take ownership and process it packet_lock = std::move(decoder_queue.front()); decoder_queue.pop_front(); + Debug(2, "QUEUE POP size=%zu", decoder_queue.size()); packet = front_packet; + if ((decoding == DECODING_KEYFRAMES || (decoding == DECODING_KEYFRAMESONDEMAND && !hasViewers())) && decoder_requires_next_packet ) { + decoder_requires_next_packet = false; + } Debug(2, "Received frame for packet %d", packet->image_index); // Continue to PHASE 3 (frame processing) } else if (ret < 0) { @@ -3230,12 +3235,16 @@ bool Monitor::Decode() { } // Check if this packet needs to be sent to the decoder + if ((decoding == DECODING_KEYFRAMES || (decoding == DECODING_KEYFRAMESONDEMAND && !hasViewers())) && packet->keyframe) { + decoder_requires_next_packet = true; + Debug(2, "Decoder requires follow-up packets after keyframe %d", packet->image_index); + } bool already_decoded = packet->image || packet->in_frame || !packet->packet->size; bool should_decode = !already_decoded && ( (decoding == DECODING_ALWAYS) || ((decoding == DECODING_ONDEMAND) && (hasViewers() || shared_data->last_write_index == image_buffer_count)) || - ((decoding == DECODING_KEYFRAMES) && packet->keyframe) || - ((decoding == DECODING_KEYFRAMESONDEMAND) && (hasViewers() || packet->keyframe)) + ((decoding == DECODING_KEYFRAMES) && (packet->keyframe || decoder_requires_next_packet)) || + ((decoding == DECODING_KEYFRAMESONDEMAND) && (hasViewers() || packet->keyframe || decoder_requires_next_packet)) ); if (!should_decode && !decoder_queue.empty()) { @@ -3256,10 +3265,21 @@ bool Monitor::Decode() { } if (should_decode) { - Debug(2, "Sending packet %d to decoder", packet->image_index); - + Debug(2, + "Sending packet=%d to decoder " + "key=%d " + "flags=0x%x " + "pts=%lld " + "dts=%lld", + packet->image_index, + packet->keyframe, + packet->packet->flags, + (long long)packet->packet->pts, + (long long)packet->packet->dts + ); SystemTimePoint starttime = std::chrono::system_clock::now(); int ret = packet->send_packet(context); + Debug(2, "SEND RESULT packet=%d ret=%d", packet->image_index, ret); SystemTimePoint endtime = std::chrono::system_clock::now(); // Warn if send_packet is taking too long @@ -3285,6 +3305,7 @@ bool Monitor::Decode() { // Success - packet sent to decoder, queue it for receive later decoder_queue.push_back(std::move(packet_lock)); + Debug(2, "QUEUE PUSH size=%zu", decoder_queue.size()); packetqueue.increment_it(decoder_it, false); return true; // Frame will be received on a future call } @@ -3299,24 +3320,6 @@ bool Monitor::Decode() { // between keyframe decodes. if (packet->codec_type == AVMEDIA_TYPE_VIDEO) { shared_data->last_write_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); - - // Update last_write_index to point to the most recent keyframe - // even when the frame hasn't been fully decoded to the image buffer. - // This allows mode=single snapshots to always return the latest available - // image (a keyframe), while only decoding full frames when needed. - if ((decoding == DECODING_KEYFRAMES || decoding == DECODING_KEYFRAMESONDEMAND) && packet->keyframe) { - unsigned int kf_index = packet->image_index % image_buffer_count; - // Only update if this is a newer keyframe than what last_write_index currently points to - int current_write_index = shared_data->last_write_index % image_buffer_count; - if (kf_index != current_write_index) { - Debug(2, "Updating last_write_index to keyframe index %u (was %u, packet %d)", - kf_index, current_write_index, packet->image_index); - // Update timestamp to signal freshness - shared_timestamps[kf_index] = zm::chrono::duration_cast(packet->timestamp.time_since_epoch()); - // Update index as atomic final step - shared_data->last_write_index = kf_index; - } - } } } @@ -3433,18 +3436,13 @@ bool Monitor::Decode() { // Write to shared image buffer. unsigned int index = (shared_data->last_write_index + 1) % image_buffer_count; decoding_image_count++; + Debug(2, "SHM WRITE packet=%d index=%d", packet->image_index, packet->image_index % image_buffer_count); WriteShmFrame(index, capture_image); shared_timestamps[index] = zm::chrono::duration_cast(packet->timestamp.time_since_epoch()); shared_data->signal = signal_check_points ? CheckSignal(capture_image) : true; shared_data->last_write_index = index; shared_data->last_write_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); - // NEW: Track last decoded keyframe for mode=single snapshots - if (packet->keyframe) { - last_keyframe_index = index; - Debug(2, "Updated last_keyframe_index to %d (packet %d)", index, packet->image_index); - } - // Warn if falling behind auto lag = std::chrono::system_clock::now() - packet->timestamp; if (lag > Seconds(ZM_WATCH_MAX_DELAY)) { diff --git a/src/zm_monitor.h b/src/zm_monitor.h index c61d1e2a4..27af2aab2 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -1079,7 +1079,11 @@ class Monitor : public std::enable_shared_from_this { int StartupDelay() const { return startup_delay; } private: - int last_keyframe_index = -1; // Track which slot contains the last decoded keyframe + // True after a keyframe is sent until the decoder outputs the first frame. + // Used by keyframe-based decoding modes to feed any required follow-up packets. + // A future improvement could eliminate mode-specific checks by relying solely + // on this flag to track the decoder state. + bool decoder_requires_next_packet = false; }; #define MOD_ADD( var, delta, limit ) (((var)+(limit)+(delta))%(limit)) diff --git a/src/zm_monitorstream.cpp b/src/zm_monitorstream.cpp index 711c26d65..3a79e9447 100644 --- a/src/zm_monitorstream.cpp +++ b/src/zm_monitorstream.cpp @@ -1009,18 +1009,9 @@ void MonitorStream::SingleImage(int scale) { return; } - // Use the most recent keyframe if available, otherwise use last_write_index int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; - - // Validate the timestamp to ensure it's a real frame - if (!monitor->shared_data->valid || !monitor->shared_timestamps[index].tv_sec) { - Debug(1, "Image slot %d is invalid or not yet decoded. Waiting...", index); - sendTextFrame("Image slot not ready"); - return; - } - AVPixelFormat pixformat = monitor->image_pixelformats[index]; - Debug(1, "Sending image index %d, pix format is %d %s", index, pixformat, zm_get_pix_fmt_name(pixformat)); + Debug(1, "Sending regular image index %d, pix format is %d %s", index, pixformat, zm_get_pix_fmt_name(pixformat)); Image *snap_image = monitor->ReadShmFrame(index); if (!config.timestamp_on_capture) { monitor->TimestampImage(snap_image,