diff --git a/src/FrameMapper.cpp b/src/FrameMapper.cpp
index 1057358c..756d1f85 100644
--- a/src/FrameMapper.cpp
+++ b/src/FrameMapper.cpp
@@ -549,40 +549,18 @@ std::shared_ptr FrameMapper::GetFrame(int64_t requested_frame)
}
}
- // Resampling needed, modify copy of SampleRange object that
- // includes some additional input samples on first iteration,
- // and continues the offset to ensure that the sample rate
- // converter isn't input limited.
- const int EXTRA_INPUT_SAMPLES = 100;
+ // Resampling needed, modify copy of SampleRange object that includes some additional input samples on
+ // first iteration, and continues the offset to ensure that the resampler is not input limited.
+ const int EXTRA_INPUT_SAMPLES = 48;
- // Extend end sample count by an additional EXTRA_INPUT_SAMPLES samples
- copy_samples.sample_end += EXTRA_INPUT_SAMPLES;
- int samples_per_end_frame =
- Frame::GetSamplesPerFrame(copy_samples.frame_end, original,
- reader->info.sample_rate, reader->info.channels);
- if (copy_samples.sample_end >= samples_per_end_frame)
- {
- // check for wrapping
- copy_samples.frame_end++;
- copy_samples.sample_end -= samples_per_end_frame;
- }
- copy_samples.total += EXTRA_INPUT_SAMPLES;
-
- if (avr) {
- // Sample rate conversion has been allocated on this clip, so
- // this is not the first iteration. Extend start position by
- // EXTRA_INPUT_SAMPLES to keep step with previous frame
- copy_samples.sample_start += EXTRA_INPUT_SAMPLES;
- int samples_per_start_frame =
- Frame::GetSamplesPerFrame(copy_samples.frame_start, original,
- reader->info.sample_rate, reader->info.channels);
- if (copy_samples.sample_start >= samples_per_start_frame)
- {
- // check for wrapping
- copy_samples.frame_start++;
- copy_samples.sample_start -= samples_per_start_frame;
- }
- copy_samples.total -= EXTRA_INPUT_SAMPLES;
+ if (!avr) {
+ // This is the first iteration, and we need to extend # of samples for this frame
+ // Extend sample count range by an additional EXTRA_INPUT_SAMPLES
+ copy_samples.Extend(EXTRA_INPUT_SAMPLES, original, reader->info.sample_rate, reader->info.channels, is_increasing);
+ } else {
+ // Sample rate conversion has already been allocated on this clip, so
+ // this is not the first iteration. Shift position by EXTRA_INPUT_SAMPLES to correctly align samples
+ copy_samples.Shift(EXTRA_INPUT_SAMPLES, original, reader->info.sample_rate, reader->info.channels, is_increasing);
}
}
diff --git a/src/FrameMapper.h b/src/FrameMapper.h
index c955a3cd..9e37664a 100644
--- a/src/FrameMapper.h
+++ b/src/FrameMapper.h
@@ -80,6 +80,86 @@ namespace openshot
int64_t frame_end;
int sample_end;
+ /// Extend SampleRange on either side
+ void Extend(int64_t samples, openshot::Fraction fps, int sample_rate, int channels, bool right_side) {
+ int remaining_samples = samples;
+ while (remaining_samples > 0) {
+ if (right_side) {
+ // Extend range to the right
+ int samples_per_frame = Frame::GetSamplesPerFrame(frame_end, fps, sample_rate, channels);
+ if (remaining_samples + sample_end < samples_per_frame) {
+ sample_end += remaining_samples;
+ remaining_samples = 0;
+ } else {
+ frame_end++;
+ remaining_samples -= (samples_per_frame - sample_end);
+ sample_end = 0;
+ }
+ } else {
+ // Extend range to the left
+ if (sample_start - remaining_samples >= 0) {
+ sample_start -= remaining_samples;
+ remaining_samples = 0;
+ } else {
+ frame_start--;
+ remaining_samples -= (sample_start + 1);
+ sample_start = Frame::GetSamplesPerFrame(frame_start, fps, sample_rate, channels) - 1;
+ }
+ }
+ }
+
+ // Increase total
+ total += samples;
+ }
+
+ /// Shrink SampleRange on either side
+ void Shrink(int64_t samples, openshot::Fraction fps, int sample_rate, int channels, bool right_side) {
+ int remaining_samples = samples;
+ while (remaining_samples > 0) {
+ if (right_side) {
+ // Shrink range on the right
+ if (sample_end - remaining_samples >= 0) {
+ sample_end -= remaining_samples;
+ remaining_samples = 0;
+ } else {
+ frame_end--;
+ int samples_per_frame = Frame::GetSamplesPerFrame(frame_end, fps, sample_rate, channels);
+ remaining_samples -= (sample_end + 1);
+ sample_end = samples_per_frame - 1;
+ }
+ } else {
+ // Shrink range on the left
+ int samples_per_frame = Frame::GetSamplesPerFrame(frame_start, fps, sample_rate, channels);
+ if (sample_start + remaining_samples < samples_per_frame) {
+ sample_start += remaining_samples;
+ remaining_samples = 0;
+ } else {
+ frame_start++;
+ remaining_samples -= (samples_per_frame - sample_start);
+ sample_start = 0;
+ }
+ }
+ }
+
+ // Reduce total
+ total -= samples;
+ }
+
+ void Shift(int64_t samples, openshot::Fraction fps, int sample_rate, int channels, bool right_side) {
+ // Extend each side of the range (to SHIFT the range) by adding (or subtracting) from both sides
+ // For example: [ range ]
+ // For example: [ range ]
+ if (right_side) {
+ // SHIFT both sides to the right
+ Extend(samples, fps, sample_rate, channels, true);
+ Shrink(samples, fps, sample_rate, channels, false);
+ } else {
+ // SHIFT both sides to the left
+ Extend(samples, fps, sample_rate, channels, false);
+ Shrink(samples, fps, sample_rate, channels, true);
+ }
+ }
+
int total;
};
@@ -128,7 +208,7 @@ namespace openshot
CacheMemory final_cache; // Cache of actual Frame objects
bool is_dirty; // When this is true, the next call to GetFrame will re-init the mapping
float parent_position; // Position of parent clip (which is used to generate the audio mapping)
- float parent_start; // Start of parent clip (which is used to generate the audio mapping)
+ float parent_start; // Start of parent clip (which is used to generate the audio mapping)
int64_t previous_frame; // Used during resampling, to determine when a large gap is detected
SWRCONTEXT *avr; // Audio resampling context object
diff --git a/tests/Clip.cpp b/tests/Clip.cpp
index 28399a4f..a6e3e629 100644
--- a/tests/Clip.cpp
+++ b/tests/Clip.cpp
@@ -387,17 +387,9 @@ TEST_CASE( "time remapping", "[libopenshot][clip]" )
clip.Start(0.0);
// Set time keyframe (4X speed REVERSE)
- //clip.time.AddPoint(1, original_video_length, openshot::LINEAR);
- //clip.time.AddPoint(original_video_length, 1.0, openshot::LINEAR);
-
- // Set time keyframe (4X speed FORWARD)
- //clip.time.AddPoint(1, 1.0, openshot::LINEAR);
- //clip.time.AddPoint(original_video_length / 2, original_video_length, openshot::LINEAR);
-
- // Set time keyframe (1/4X speed FORWARD)
- //clip.time.AddPoint(1, 1.0, openshot::LINEAR);
- //clip.time.AddPoint(original_video_length * 4, original_video_length, openshot::LINEAR);
-
+ clip.time.AddPoint(1, original_video_length, openshot::LINEAR);
+ clip.time.AddPoint(original_video_length, 1.0, openshot::LINEAR);
+
// TODO: clip.Duration() != clip.Reader->info.duration
// Set clip length based on time-values
if (clip.time.GetLength() > 1) {
@@ -424,9 +416,7 @@ TEST_CASE( "time remapping", "[libopenshot][clip]" )
t1.info.channels);
std::shared_ptr f = t1.GetFrame(frame);
- if (expected_sample_count != f->GetAudioSamplesCount()) {
- CHECK(expected_sample_count == f->GetAudioSamplesCount());
- }
+ CHECK(expected_sample_count == f->GetAudioSamplesCount());
}
// Clear cache
@@ -440,9 +430,7 @@ TEST_CASE( "time remapping", "[libopenshot][clip]" )
t1.info.channels);
std::shared_ptr f = t1.GetFrame(frame);
- if (expected_sample_count != f->GetAudioSamplesCount()) {
- CHECK(expected_sample_count == f->GetAudioSamplesCount());
- }
+ CHECK(expected_sample_count == f->GetAudioSamplesCount());
}
t1.Close();
@@ -480,9 +468,7 @@ TEST_CASE( "resample_audio_8000_to_48000_reverse", "[libopenshot][clip]" )
map.info.channels);
std::shared_ptr f = clip.GetFrame(frame);
- if (expected_sample_count != f->GetAudioSamplesCount()) {
- CHECK(expected_sample_count == f->GetAudioSamplesCount());
- }
+ CHECK(expected_sample_count == f->GetAudioSamplesCount());
}
// Clear clip cache
diff --git a/tests/FrameMapper.cpp b/tests/FrameMapper.cpp
index 64cd67ee..5dd501d2 100644
--- a/tests/FrameMapper.cpp
+++ b/tests/FrameMapper.cpp
@@ -472,136 +472,136 @@ TEST_CASE( "redistribute_samples_per_frame", "[libopenshot][framemapper]" ) {
}
TEST_CASE( "Distribute samples", "[libopenshot][framemapper]" ) {
- // This test verifies that audio data can be redistributed correctly
- // between common and uncommon frame rates
- int sample_rate = 48000;
- int channels = 2;
- int num_seconds = 1;
+ // This test verifies that audio data can be redistributed correctly
+ // between common and uncommon frame rates
+ int sample_rate = 48000;
+ int channels = 2;
+ int num_seconds = 1;
- // Source frame rates (varies the # of samples per frame)
- std::vector rates = {
- openshot::Fraction(30,1),
- openshot::Fraction(24,1) ,
- openshot::Fraction(119,4),
- openshot::Fraction(30000,1001)
- };
+ // Source frame rates (varies the # of samples per frame)
+ std::vector rates = {
+ openshot::Fraction(30,1),
+ openshot::Fraction(24,1) ,
+ openshot::Fraction(119,4),
+ openshot::Fraction(30000,1001)
+ };
- for (auto& frame_rate : rates) {
- // Init sin wave variables
- const int OFFSET = 0;
- const float AMPLITUDE = 0.75;
- const int NUM_SAMPLES = 100;
- double angle = 0.0;
+ for (auto& frame_rate : rates) {
+ // Init sin wave variables
+ const int OFFSET = 0;
+ const float AMPLITUDE = 0.75;
+ const int NUM_SAMPLES = 100;
+ double angle = 0.0;
- // Create cache object to hold test frames
- openshot::CacheMemory cache;
+ // Create cache object to hold test frames
+ openshot::CacheMemory cache;
- // Let's create some test frames
- for (int64_t frame_number = 1; frame_number <= (frame_rate.ToFloat() * num_seconds * 2); ++frame_number) {
- // Create blank frame (with specific frame #, samples, and channels)
- int sample_count = openshot::Frame::GetSamplesPerFrame(frame_number, frame_rate, sample_rate, channels);
- auto f = std::make_shared(frame_number, sample_count, channels);
- f->SampleRate(sample_rate);
+ // Let's create some test frames
+ for (int64_t frame_number = 1; frame_number <= (frame_rate.ToFloat() * num_seconds * 2); ++frame_number) {
+ // Create blank frame (with specific frame #, samples, and channels)
+ int sample_count = openshot::Frame::GetSamplesPerFrame(frame_number, frame_rate, sample_rate, channels);
+ auto f = std::make_shared(frame_number, sample_count, channels);
+ f->SampleRate(sample_rate);
- // Create test samples with sin wave (predictable values)
- float *audio_buffer = new float[sample_count * 2];
- for (int sample_number = 0; sample_number < sample_count; sample_number++) {
- // Calculate sin wave
- float sample_value = float(AMPLITUDE * sin(angle) + OFFSET);
- audio_buffer[sample_number] = abs(sample_value);
- angle += (2 * M_PI) / NUM_SAMPLES;
- }
+ // Create test samples with sin wave (predictable values)
+ float *audio_buffer = new float[sample_count * 2];
+ for (int sample_number = 0; sample_number < sample_count; sample_number++) {
+ // Calculate sin wave
+ float sample_value = float(AMPLITUDE * sin(angle) + OFFSET);
+ audio_buffer[sample_number] = abs(sample_value);
+ angle += (2 * M_PI) / NUM_SAMPLES;
+ }
- // Add custom audio samples to Frame (bool replaceSamples, int destChannel, int destStartSample, const float* source,
- f->AddAudio(true, 0, 0, audio_buffer, sample_count, 1.0); // add channel 1
- f->AddAudio(true, 1, 0, audio_buffer, sample_count, 1.0); // add channel 2
+ // Add custom audio samples to Frame (bool replaceSamples, int destChannel, int destStartSample, const float* source,
+ f->AddAudio(true, 0, 0, audio_buffer, sample_count, 1.0); // add channel 1
+ f->AddAudio(true, 1, 0, audio_buffer, sample_count, 1.0); // add channel 2
- // Add test frame to dummy reader
- cache.Add(f);
+ // Add test frame to dummy reader
+ cache.Add(f);
- delete[] audio_buffer;
- }
+ delete[] audio_buffer;
+ }
- openshot::DummyReader r(frame_rate, 1920, 1080, sample_rate, channels, 30.0, &cache);
- r.Open();
+ openshot::DummyReader r(frame_rate, 1920, 1080, sample_rate, channels, 30.0, &cache);
+ r.Open();
- // Target frame rates
+ // Target frame rates
std::vector mapped_rates = {
- openshot::Fraction(30,1),
- openshot::Fraction(24,1),
- openshot::Fraction(119,4),
- openshot::Fraction(30000,1001)
- };
- for (auto &mapped_rate : mapped_rates) {
- // Reset SIN wave
- angle = 0.0;
+ openshot::Fraction(30,1),
+ openshot::Fraction(24,1),
+ openshot::Fraction(119,4),
+ openshot::Fraction(30000,1001)
+ };
+ for (auto &mapped_rate : mapped_rates) {
+ // Reset SIN wave
+ angle = 0.0;
- // Map to different fps
- FrameMapper map(&r, mapped_rate, PULLDOWN_NONE, sample_rate, channels, LAYOUT_STEREO);
- map.info.has_audio = true;
- map.Open();
+ // Map to different fps
+ FrameMapper map(&r, mapped_rate, PULLDOWN_NONE, sample_rate, channels, LAYOUT_STEREO);
+ map.info.has_audio = true;
+ map.Open();
- // Loop through samples, and verify FrameMapper didn't mess up individual sample values
- int num_samples = 0;
- for (int frame_index = 1; frame_index <= (map.info.fps.ToInt() * num_seconds); frame_index++) {
- int sample_count = map.GetFrame(frame_index)->GetAudioSamplesCount();
- for (int sample_index = 0; sample_index < sample_count; sample_index++) {
+ // Loop through samples, and verify FrameMapper didn't mess up individual sample values
+ int num_samples = 0;
+ for (int frame_index = 1; frame_index <= (map.info.fps.ToInt() * num_seconds); frame_index++) {
+ int sample_count = map.GetFrame(frame_index)->GetAudioSamplesCount();
+ for (int sample_index = 0; sample_index < sample_count; sample_index++) {
- // Calculate sin wave
- float predicted_value = abs(float(AMPLITUDE * sin(angle) + OFFSET));
- angle += (2 * M_PI) / NUM_SAMPLES;
+ // Calculate sin wave
+ float predicted_value = abs(float(AMPLITUDE * sin(angle) + OFFSET));
+ angle += (2 * M_PI) / NUM_SAMPLES;
- // Verify each mapped sample value is correct (after being redistributed by the FrameMapper)
- float mapped_value = map.GetFrame(frame_index)->GetAudioSample(0, sample_index, 1.0);
- CHECK(predicted_value == Approx(mapped_value).margin(0.001));
- }
- // Increment sample value
- num_samples += map.GetFrame(frame_index)->GetAudioSamplesCount();
- }
+ // Verify each mapped sample value is correct (after being redistributed by the FrameMapper)
+ float mapped_value = map.GetFrame(frame_index)->GetAudioSample(0, sample_index, 1.0);
+ CHECK(predicted_value == Approx(mapped_value).margin(0.001));
+ }
+ // Increment sample value
+ num_samples += map.GetFrame(frame_index)->GetAudioSamplesCount();
+ }
- float clip_position = 3.77;
- int starting_clip_frame = round(clip_position * map.info.fps.ToFloat()) + 1;
+ float clip_position = 3.77;
+ int starting_clip_frame = round(clip_position * map.info.fps.ToFloat()) + 1;
- // Create Timeline (same specs as reader)
- Timeline t1(map.info.width, map.info.height, map.info.fps, map.info.sample_rate, map.info.channels,
- map.info.channel_layout);
+ // Create Timeline (same specs as reader)
+ Timeline t1(map.info.width, map.info.height, map.info.fps, map.info.sample_rate, map.info.channels,
+ map.info.channel_layout);
- Clip c1;
- c1.Reader(&map);
- c1.Layer(1);
- c1.Position(clip_position);
- c1.Start(0.0);
- c1.End(10.0);
+ Clip c1;
+ c1.Reader(&map);
+ c1.Layer(1);
+ c1.Position(clip_position);
+ c1.Start(0.0);
+ c1.End(10.0);
- // Add clips
- t1.AddClip(&c1);
- t1.Open();
+ // Add clips
+ t1.AddClip(&c1);
+ t1.Open();
- // Reset SIN wave
- angle = 0.0;
+ // Reset SIN wave
+ angle = 0.0;
- for (int frame_index = starting_clip_frame; frame_index < (starting_clip_frame + (t1.info.fps.ToFloat() * num_seconds)); frame_index++) {
- for (int sample_index = 0; sample_index < t1.GetFrame(frame_index)->GetAudioSamplesCount(); sample_index++) {
- // Calculate sin wave
- float predicted_value = abs(float(AMPLITUDE * sin(angle) + OFFSET));
- angle += (2 * M_PI) / NUM_SAMPLES;
+ for (int frame_index = starting_clip_frame; frame_index < (starting_clip_frame + (t1.info.fps.ToFloat() * num_seconds)); frame_index++) {
+ for (int sample_index = 0; sample_index < t1.GetFrame(frame_index)->GetAudioSamplesCount(); sample_index++) {
+ // Calculate sin wave
+ float predicted_value = abs(float(AMPLITUDE * sin(angle) + OFFSET));
+ angle += (2 * M_PI) / NUM_SAMPLES;
- // Verify each mapped sample value is correct (after being redistributed by the FrameMapper)
- float timeline_value = t1.GetFrame(frame_index)->GetAudioSample(0, sample_index, 1.0);
- CHECK(predicted_value == Approx(timeline_value).margin(0.001));
- }
- }
+ // Verify each mapped sample value is correct (after being redistributed by the FrameMapper)
+ float timeline_value = t1.GetFrame(frame_index)->GetAudioSample(0, sample_index, 1.0);
+ CHECK(predicted_value == Approx(timeline_value).margin(0.001));
+ }
+ }
- // Close mapper
- map.Close();
- t1.Close();
- }
+ // Close mapper
+ map.Close();
+ t1.Close();
+ }
- // Clean up reader
- r.Close();
- cache.Clear();
+ // Clean up reader
+ r.Close();
+ cache.Clear();
- } // for rates
+ } // for rates
}
TEST_CASE( "PrintMapping", "[libopenshot][framemapper]" )
@@ -651,3 +651,101 @@ TEST_CASE( "Json", "[libopenshot][framemapper]" )
CHECK(map.info.sample_rate == 48000);
CHECK(map.info.fps.num == 30);
}
+
+TEST_CASE( "SampleRange", "[libopenshot][framemapper]")
+{
+ openshot::Fraction fps(30, 1);
+ int sample_rate = 44100;
+ int channels = 2;
+
+ int64_t start_frame = 10;
+ int start_sample = 0;
+ int total_samples = Frame::GetSamplesPerFrame(start_frame, fps, sample_rate, channels);
+
+ int64_t end_frame = 10;
+ int end_sample = (total_samples - 1);
+
+ SampleRange Samples = {start_frame, start_sample, end_frame, end_sample, total_samples };
+ CHECK(Samples.frame_start == 10);
+ CHECK(Samples.sample_start == 0);
+ CHECK(Samples.frame_end == 10);
+ CHECK(Samples.sample_end == 1469);
+
+ // ------ RIGHT -------
+ // Extend range to the RIGHT
+ Samples.Extend(50, fps, sample_rate, channels, true);
+ CHECK(Samples.frame_start == 10);
+ CHECK(Samples.sample_start == 0);
+ CHECK(Samples.frame_end == 11);
+ CHECK(Samples.sample_end == 49);
+ CHECK(Samples.total == total_samples + 50);
+
+ // Shrink range from the RIGHT
+ Samples.Shrink(50, fps, sample_rate, channels, true);
+ CHECK(Samples.frame_start == 10);
+ CHECK(Samples.sample_start == 0);
+ CHECK(Samples.frame_end == 10);
+ CHECK(Samples.sample_end == 1469);
+ CHECK(Samples.total == total_samples);
+
+
+ // ------ LEFT -------
+ // Extend range to the LEFT
+ Samples.Extend(50, fps, sample_rate, channels, false);
+ CHECK(Samples.frame_start == 9);
+ CHECK(Samples.sample_start == 1420);
+ CHECK(Samples.frame_end == 10);
+ CHECK(Samples.sample_end == 1469);
+ CHECK(Samples.total == total_samples + 50);
+
+ // Shrink range from the LEFT
+ Samples.Shrink(50, fps, sample_rate, channels, false);
+ CHECK(Samples.frame_start == 10);
+ CHECK(Samples.sample_start == 0);
+ CHECK(Samples.frame_end == 10);
+ CHECK(Samples.sample_end == 1469);
+ CHECK(Samples.total == total_samples);
+
+
+ // ------ SHIFT -------
+ // Shift range to the RIGHT
+ Samples.Shift(50, fps, sample_rate, channels, true);
+ CHECK(Samples.frame_start == 10);
+ CHECK(Samples.sample_start == 50);
+ CHECK(Samples.frame_end == 11);
+ CHECK(Samples.sample_end == 49);
+ CHECK(Samples.total == total_samples);
+
+ // Shift range to the LEFT
+ Samples.Shift(50, fps, sample_rate, channels, false);
+ CHECK(Samples.frame_start == 10);
+ CHECK(Samples.sample_start == 0);
+ CHECK(Samples.frame_end == 10);
+ CHECK(Samples.sample_end == 1469);
+ CHECK(Samples.total == total_samples);
+
+
+ // Shift range to the RIGHT
+ Samples.Shift(50, fps, sample_rate, channels, true);
+ CHECK(Samples.frame_start == 10);
+ CHECK(Samples.sample_start == 50);
+ CHECK(Samples.frame_end == 11);
+ CHECK(Samples.sample_end == 49);
+ CHECK(Samples.total == total_samples);
+
+ // Shift range to the LEFT
+ Samples.Shift(75, fps, sample_rate, channels, false);
+ CHECK(Samples.frame_start == 9);
+ CHECK(Samples.sample_start == 1445);
+ CHECK(Samples.frame_end == 10);
+ CHECK(Samples.sample_end == 1444);
+ CHECK(Samples.total == total_samples);
+
+ // Shift range to the RIGHT
+ Samples.Shift(25, fps, sample_rate, channels, true);
+ CHECK(Samples.frame_start == 10);
+ CHECK(Samples.sample_start == 0);
+ CHECK(Samples.frame_end == 10);
+ CHECK(Samples.sample_end == 1469);
+ CHECK(Samples.total == total_samples);
+}
\ No newline at end of file