Skip to content

Commit 5ee9d1b

Browse files
committed
Fixing a bug where a non-EOF frame is otherwise ready but still has no image data: instead of stalling indefinitely, it reuses the most recent non-future image and finalizes the frame. Added new unit tests to verify the fix.
1 parent 6213935 commit 5ee9d1b

File tree

2 files changed

+72
-0
lines changed

2 files changed

+72
-0
lines changed

src/FFmpegReader.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2827,6 +2827,28 @@ void FFmpegReader::CheckWorkingFrames(int64_t requested_frame) {
28272827
}
28282828
}
28292829

2830+
// If both streams have advanced past this frame but the decoder never
2831+
// produced image data for it, reuse the most recent non-future image.
2832+
// This avoids stalling indefinitely on sparse/missing decoded frames.
2833+
if (!f->has_image_data && is_video_ready && is_audio_ready) {
2834+
std::shared_ptr<Frame> previous_frame_instance = final_cache.GetFrame(f->number - 1);
2835+
if (previous_frame_instance && previous_frame_instance->has_image_data) {
2836+
f->AddImage(std::make_shared<QImage>(previous_frame_instance->GetImage()->copy()));
2837+
}
2838+
if (!f->has_image_data
2839+
&& last_final_video_frame
2840+
&& last_final_video_frame->has_image_data
2841+
&& last_final_video_frame->number <= f->number) {
2842+
f->AddImage(std::make_shared<QImage>(last_final_video_frame->GetImage()->copy()));
2843+
}
2844+
if (!f->has_image_data
2845+
&& last_video_frame
2846+
&& last_video_frame->has_image_data
2847+
&& last_video_frame->number <= f->number) {
2848+
f->AddImage(std::make_shared<QImage>(last_video_frame->GetImage()->copy()));
2849+
}
2850+
}
2851+
28302852
// Do not finalize non-EOF video frames without decoded image data.
28312853
// This prevents repeated previous-frame fallbacks being cached as real frames.
28322854
if (!f->has_image_data) {

tests/FFmpegReader.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222

2323
#include "openshot_catch.h"
2424

25+
#define private public
2526
#include "FFmpegReader.h"
27+
#undef private
2628
#include "Exceptions.h"
2729
#include "Frame.h"
2830
#include "Timeline.h"
@@ -528,6 +530,54 @@ TEST_CASE( "Attached_Picture_Audio_Does_Not_Stall_Early_Frames", "[libopenshot][
528530
std::remove(fixture_path.str().c_str());
529531
}
530532

533+
TEST_CASE( "Missing_Image_Frame_Finalizes_Using_Previous_Image", "[libopenshot][ffmpegreader]" )
534+
{
535+
FFmpegReader r("synthetic-missing-image", DurationStrategy::VideoPreferred, false);
536+
537+
r.info.has_video = true;
538+
r.info.has_audio = true;
539+
r.info.has_single_image = false;
540+
r.info.width = 320;
541+
r.info.height = 240;
542+
r.info.fps = Fraction(30, 1);
543+
r.info.sample_rate = 48000;
544+
r.info.channels = 2;
545+
r.info.channel_layout = LAYOUT_STEREO;
546+
r.info.video_length = 120;
547+
r.info.video_timebase = Fraction(1, 30);
548+
r.info.audio_timebase = Fraction(1, 48000);
549+
550+
r.pts_offset_seconds = 0.0;
551+
r.last_frame = 58;
552+
r.video_pts_seconds = 2.233333;
553+
r.audio_pts_seconds = 3.100000;
554+
r.packet_status.video_eof = false;
555+
r.packet_status.audio_eof = false;
556+
r.packet_status.packets_eof = false;
557+
r.packet_status.end_of_file = false;
558+
559+
const int samples_per_frame = Frame::GetSamplesPerFrame(
560+
58, r.info.fps, r.info.sample_rate, r.info.channels);
561+
auto previous = std::make_shared<Frame>(
562+
58, r.info.width, r.info.height, "#112233", samples_per_frame, r.info.channels);
563+
previous->AddColor(r.info.width, r.info.height, "#112233");
564+
r.final_cache.Add(previous);
565+
r.last_final_video_frame = previous;
566+
567+
auto missing = r.CreateFrame(59);
568+
r.working_cache.Add(missing);
569+
REQUIRE(missing != nullptr);
570+
REQUIRE_FALSE(missing->has_image_data);
571+
572+
r.CheckWorkingFrames(59);
573+
574+
auto finalized = r.final_cache.GetFrame(59);
575+
REQUIRE(finalized != nullptr);
576+
CHECK(finalized->has_image_data);
577+
CHECK(finalized->CheckPixel(0, 0, 17, 34, 51, 255, 0));
578+
CHECK(r.final_cache.GetFrame(58) != nullptr);
579+
}
580+
531581
TEST_CASE( "HardwareDecodeSuccessful_IsFalse_WhenHardwareDecodeIsDisabled", "[libopenshot][ffmpegreader][hardware]" )
532582
{
533583
HardwareDecoderSettingsGuard guard;

0 commit comments

Comments
 (0)