Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 32 additions & 23 deletions src/FrameMapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -558,12 +558,12 @@ std::shared_ptr<Frame> FrameMapper::GetFrame(int64_t requested_frame)
frame->AddImage(std::make_shared<QImage>(*even_frame->GetImage()), false);
}

// Determine if the wrapped reader actually supplied audio on this frame.
// Video-only readers can still be mapped onto an audio-enabled timeline,
// which yields frames with default audio metadata but no sample data.
const bool reader_has_audio = mapped_frame->SampleRate() > 0 &&
mapped_frame->GetAudioChannelsCount() > 0 &&
mapped_frame->GetAudioSamplesCount() > 0;
// Only treat the reader as audio-capable when the wrapped reader reports
// audio. Individual source frames can still be empty near boundaries, but
// those cases should pad with silence instead of dropping this mapped frame.
const bool reader_has_audio = reader->info.has_audio &&
mapped_frame->SampleRate() > 0 &&
mapped_frame->GetAudioChannelsCount() > 0;

// Resample audio on frame (if needed)
bool need_resampling = false;
Expand Down Expand Up @@ -604,6 +604,10 @@ std::shared_ptr<Frame> FrameMapper::GetFrame(int64_t requested_frame)
}
}

// Preserve the target frame duration even when a source frame has no samples.
if (reader_has_audio && samples_in_frame > 0)
frame->AddAudioSilence(samples_in_frame);

// Copy the samples
int samples_copied = 0;
int64_t starting_frame = copy_samples.frame_start;
Expand All @@ -620,40 +624,45 @@ std::shared_ptr<Frame> FrameMapper::GetFrame(int64_t requested_frame)
}

int original_samples = original_frame->GetAudioSamplesCount();
if (original_samples <= 0)
break;
if (original_samples <= 0) {
if (starting_frame >= copy_samples.frame_end)
break;
starting_frame++;
continue;
}

if (starting_frame == copy_samples.frame_start)
number_to_copy = original_samples - copy_samples.sample_start;
else if (starting_frame > copy_samples.frame_start && starting_frame < copy_samples.frame_end)
number_to_copy = original_samples;
else
number_to_copy = copy_samples.sample_end + 1;

if (number_to_copy <= 0) {
if (starting_frame >= copy_samples.frame_end)
break;
starting_frame++;
continue;
}
if (number_to_copy > remaining_samples)
number_to_copy = remaining_samples;

// Loop through each channel
for (int channel = 0; channel < channels_in_frame; channel++)
{
if (starting_frame == copy_samples.frame_start)
{
// Starting frame (take the ending samples)
number_to_copy = original_samples - copy_samples.sample_start;
if (number_to_copy > remaining_samples)
number_to_copy = remaining_samples;

// Add samples to new frame
frame->AddAudio(true, channel, samples_copied, original_frame->GetAudioSamples(channel) + copy_samples.sample_start, number_to_copy, 1.0);
}
else if (starting_frame > copy_samples.frame_start && starting_frame < copy_samples.frame_end)
{
// Middle frame (take all samples)
number_to_copy = original_samples;
if (number_to_copy > remaining_samples)
number_to_copy = remaining_samples;

// Add samples to new frame
frame->AddAudio(true, channel, samples_copied, original_frame->GetAudioSamples(channel), number_to_copy, 1.0);
}
else
{
// Ending frame (take the beginning samples)
number_to_copy = copy_samples.sample_end + 1;
if (number_to_copy > remaining_samples)
number_to_copy = remaining_samples;

// Add samples to new frame
frame->AddAudio(false, channel, samples_copied, original_frame->GetAudioSamples(channel), number_to_copy, 1.0);
}
}
Expand Down
43 changes: 42 additions & 1 deletion tests/FrameMapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,47 @@ TEST_CASE( "Invalid_Frame_Too_Small", "[libopenshot][framemapper]" )

}

TEST_CASE( "zero_sample_source_frame_preserves_mapped_audio_duration", "[libopenshot][framemapper][audio]" )
{
CacheMemory cache;
const Fraction fps(24, 1);
const int sample_rate = 48000;
const int channels = 2;
const int samples_per_frame = Frame::GetSamplesPerFrame(1, fps, sample_rate, channels);

auto empty_frame = std::make_shared<Frame>(1, 1, 1, "#000000", 0, channels);
empty_frame->SampleRate(sample_rate);
empty_frame->ChannelsLayout(LAYOUT_STEREO);
cache.Add(empty_frame);

auto audio_frame = std::make_shared<Frame>(2, 1, 1, "#000000", samples_per_frame, channels);
audio_frame->SampleRate(sample_rate);
audio_frame->ChannelsLayout(LAYOUT_STEREO);
std::vector<float> samples(samples_per_frame, 0.5f);
for (int channel = 0; channel < channels; ++channel)
audio_frame->AddAudio(true, channel, 0, samples.data(), samples_per_frame, 1.0f);
cache.Add(audio_frame);

DummyReader reader(fps, 1, 1, sample_rate, channels, 2.0 / fps.ToDouble(), &cache);
reader.info.has_audio = true;
reader.Open();

FrameMapper map(&reader, fps, PULLDOWN_NONE, sample_rate, channels, LAYOUT_STEREO);
map.Open();

auto first = map.GetFrame(1);
CHECK(first->GetAudioSamplesCount() == samples_per_frame);
for (int i = 0; i < samples_per_frame; ++i)
CHECK(first->GetAudioSample(0, i, 1.0) == Approx(0.0f).margin(0.0001f));

auto second = map.GetFrame(2);
CHECK(second->GetAudioSamplesCount() == samples_per_frame);
CHECK(second->GetAudioSample(0, 0, 1.0) == Approx(0.5f).margin(0.0001f));

map.Close();
reader.Close();
}

TEST_CASE( "24_fps_to_30_fps_Pulldown_Classic", "[libopenshot][framemapper]" )
{
// Create a reader
Expand Down Expand Up @@ -746,4 +787,4 @@ TEST_CASE( "SampleRange", "[libopenshot][framemapper]")
CHECK(Samples.frame_end == 10);
CHECK(Samples.sample_end == 1469);
CHECK(Samples.total == total_samples);
}
}
Loading