diff --git a/include/FrameMapper.h b/include/FrameMapper.h index 83990740..af21d8f2 100644 --- a/include/FrameMapper.h +++ b/include/FrameMapper.h @@ -61,13 +61,11 @@ namespace openshot */ struct SampleRange { - int starting_frame; - int starting_sample; + int frame_start; + int sample_start; - int ending_frame; - int ending_sample; - - SampleRange() : starting_frame(0), starting_sample(0), ending_frame(0), ending_sample(0) { }; + int frame_end; + int sample_end; }; /** @@ -105,10 +103,10 @@ namespace openshot vector fields; // List of all fields vector frames; // List of all frames bool field_toggle; // Internal odd / even toggle (used when building the mapping) - Framerate m_original; // The original frame rate - Framerate m_target; // The target frame rate - Pulldown_Method m_pulldown; // The pull-down technique - FileReaderBase *m_reader; // The source video reader + Framerate original; // The original frame rate + Framerate target; // The target frame rate + Pulldown_Method pulldown; // The pull-down technique + FileReaderBase *reader; // The source video reader // Internal methods used by init void AddField(int frame); @@ -120,6 +118,9 @@ namespace openshot // whether the frame rate is increasing or decreasing. void Init(); + /// Calculate the # of samples per video frame (for a specific frame number) + int GetSamplesPerFrame(int frame_number, Fraction rate); + public: /// Default constructor for FrameMapper class FrameMapper(FileReaderBase *reader, Framerate target, Pulldown_Method pulldown); @@ -128,16 +129,16 @@ namespace openshot MappedFrame GetFrame(int TargetFrameNumber) throw(OutOfBoundsFrame); /// Get the target framerate - Framerate TargetFPS() { return m_target; }; + Framerate TargetFPS() { return target; }; /// Get the source framerate - Framerate SourceFPS() { return m_original; }; + Framerate SourceFPS() { return original; }; /// Set the target framerate - void TargetFPS(Framerate new_fps) { m_target = new_fps; }; + void TargetFPS(Framerate new_fps) { target = new_fps; }; /// Set the source framerate - void SourceFPS(Framerate new_fps) { m_original = new_fps; }; + void SourceFPS(Framerate new_fps) { original = new_fps; }; /** * \brief Re-map time to slow down, speed up, or reverse a clip based on a Keyframe. diff --git a/src/FrameMapper.cpp b/src/FrameMapper.cpp index f98e04f8..e1762d4f 100644 --- a/src/FrameMapper.cpp +++ b/src/FrameMapper.cpp @@ -9,18 +9,14 @@ using namespace std; using namespace openshot; -FrameMapper::FrameMapper(FileReaderBase *reader, Framerate target, Pulldown_Method pulldown) +FrameMapper::FrameMapper(FileReaderBase *reader, Framerate target, Pulldown_Method pulldown) : + reader(reader), target(target), pulldown(pulldown) { - // init position - m_target = target; - m_pulldown = pulldown; - m_reader = reader; - // Set the original frame rate from the reader - m_original = Framerate(m_reader->info.fps.num, m_reader->info.fps.den); + original = Framerate(reader->info.fps.num, reader->info.fps.den); // Set length from reader - m_length = m_reader->info.video_length; + m_length = reader->info.video_length; // Used to toggle odd / even fields field_toggle = true; @@ -32,8 +28,7 @@ FrameMapper::FrameMapper(FileReaderBase *reader, Framerate target, Pulldown_Meth void FrameMapper::AddField(int frame) { // Add a field, and toggle the odd / even field - Field f(frame, field_toggle); - AddField(f); + AddField(Field(frame, field_toggle)); } void FrameMapper::AddField(Field field) @@ -52,7 +47,7 @@ void FrameMapper::AddField(Field field) void FrameMapper::Init() { // Get the difference (in frames) between the original and target frame rates - float difference = m_target.GetRoundedFPS() - m_original.GetRoundedFPS(); + float difference = target.GetRoundedFPS() - original.GetRoundedFPS(); // Find the number (i.e. interval) of fields that need to be skipped or repeated int field_interval = 0; @@ -60,7 +55,7 @@ void FrameMapper::Init() if (difference != 0) { - field_interval = round(fabs(m_original.GetRoundedFPS() / difference)); + field_interval = round(fabs(original.GetRoundedFPS() / difference)); // Get frame interval (2 fields per frame) frame_interval = field_interval * 2.0f; @@ -86,12 +81,12 @@ void FrameMapper::Init() // Add current field AddField(frame); - if (m_pulldown == PULLDOWN_CLASSIC && field % field_interval == 0) + if (pulldown == PULLDOWN_CLASSIC && field % field_interval == 0) { // Add extra field for each 'field interval AddField(frame); } - else if (m_pulldown == PULLDOWN_ADVANCED && field % field_interval == 0 && field % frame_interval != 0) + else if (pulldown == PULLDOWN_ADVANCED && field % field_interval == 0 && field % frame_interval != 0) { // Add both extra fields in the middle 'together' (i.e. 2:3:3:2 technique) AddField(frame); // add field for current frame @@ -100,7 +95,7 @@ void FrameMapper::Init() // add field for next frame (if the next frame exists) AddField(Field(frame + 1, field_toggle)); } - else if (m_pulldown == PULLDOWN_NONE && field % frame_interval == 0) + else if (pulldown == PULLDOWN_NONE && field % frame_interval == 0) { // No pull-down technique needed, just repeat this frame AddField(frame); @@ -110,17 +105,17 @@ void FrameMapper::Init() else if (difference < 0) // Need to SKIP fake fields & frames, because we want to return to the original film frame rate { - if (m_pulldown == PULLDOWN_CLASSIC && field % field_interval == 0) + if (pulldown == PULLDOWN_CLASSIC && field % field_interval == 0) { // skip current field and toggle the odd/even flag field_toggle = (field_toggle ? false : true); } - else if (m_pulldown == PULLDOWN_ADVANCED && field % field_interval == 0 && field % frame_interval != 0) + else if (pulldown == PULLDOWN_ADVANCED && field % field_interval == 0 && field % frame_interval != 0) { // skip this field, plus the next field field++; } - else if (m_pulldown == PULLDOWN_NONE && frame % field_interval == 0) + else if (pulldown == PULLDOWN_NONE && frame % field_interval == 0) { // skip this field, plus the next one field++; @@ -141,6 +136,10 @@ void FrameMapper::Init() Field Odd(0, true); // temp field used to track the ODD field Field Even(0, true); // temp field used to track the EVEN field + // Variables used to remap audio samples + int start_samples_frame = 1; + int start_samples_position = 0; + for (unsigned int field = 1; field <= fields.size(); field++) { // Get the current field @@ -149,14 +148,54 @@ void FrameMapper::Init() // Is field divisible by 2? if (field % 2 == 0 && field > 0) { + // New frame number + int frame_number = field / 2; + // Set the bottom frame if (f.isOdd) Odd = f; else Even = f; + // Determine the range of samples (from the original rate, to the new rate) + int end_samples_frame = start_samples_frame; + int end_samples_position = start_samples_position; + int remaining_samples = GetSamplesPerFrame(frame_number, target.GetFraction()); + + while (remaining_samples > 0) + { + // get original samples + int original_samples = GetSamplesPerFrame(end_samples_frame, original.GetFraction()) - end_samples_position; + + // Enough samples + if (original_samples >= remaining_samples) + { + // Take all that we need, and break loop + end_samples_position = remaining_samples; + remaining_samples = 0; + } else + { + // Not enough samples (take them all, and keep looping) + end_samples_frame += 1; // next frame + end_samples_position = 0; // next frame, starting on 1st sample + remaining_samples -= original_samples; // reduce the remaining amount + } + } + + // Create the sample mapping struct + SampleRange Samples = {start_samples_frame, start_samples_position, end_samples_frame, end_samples_position}; + + // Reset the audio variables + start_samples_frame = end_samples_frame; + start_samples_position = end_samples_position + 1; + if (start_samples_position >= GetSamplesPerFrame(start_samples_frame, original.GetFraction())) + { + start_samples_frame += 1; // increment the frame (since we need to wrap onto the next one) + start_samples_position = 0; // reset to 0, since we wrapped + } + // Create a frame and ADD it to the frames collection - MappedFrame frame = {Odd, Even}; + MappedFrame frame = {Odd, Even, Samples}; frames.push_back(frame); } else @@ -220,10 +259,24 @@ void FrameMapper::MapTime(Keyframe new_time) throw(OutOfBoundsFrame) } +// Calculate the # of samples per video frame (for a specific frame number) +int FrameMapper::GetSamplesPerFrame(int frame_number, Fraction rate) +{ + // Get the total # of samples for the previous frame, and the current frame (rounded) + double fps = rate.Reciprocal().ToDouble(); + double previous_samples = round((reader->info.sample_rate * fps) * (frame_number - 1)); + double total_samples = round((reader->info.sample_rate * fps) * frame_number); + + // Subtract the previous frame's total samples with this frame's total samples. Not all sample rates can + // be evenly divided into frames, so each frame can have have different # of samples. + double samples_per_frame = total_samples - previous_samples; + return samples_per_frame; +} + void FrameMapper::PrintMapping() { // Get the difference (in frames) between the original and target frame rates - float difference = m_target.GetRoundedFPS() - m_original.GetRoundedFPS(); + float difference = target.GetRoundedFPS() - original.GetRoundedFPS(); int field_interval = 0; int frame_interval = 0; @@ -231,14 +284,14 @@ void FrameMapper::PrintMapping() if (difference != 0) { // Find the number (i.e. interval) of fields that need to be skipped or repeated - field_interval = round(fabs(m_original.GetRoundedFPS() / difference)); + field_interval = round(fabs(original.GetRoundedFPS() / difference)); // Get frame interval (2 fields per frame) frame_interval = field_interval * 2.0f; } // print header - cout << "Convert " << m_original.GetRoundedFPS() << " fps to " << m_target.GetRoundedFPS() << " fps" << endl; + cout << "Convert " << original.GetRoundedFPS() << " fps to " << target.GetRoundedFPS() << " fps" << endl; cout << "Difference of " << difference << " frames per second" << endl; cout << "Field Interval: " << field_interval << "th field" << endl; cout << "Frame Interval: " << frame_interval << "th field" << endl << endl; @@ -248,7 +301,7 @@ void FrameMapper::PrintMapping() { MappedFrame frame = frames[map - 1]; cout << "Target frame #: " << map << " mapped to original frame #:\t(" << frame.Odd.Frame << " odd, " << frame.Even.Frame << " even)" << endl; - + cout << " - Audio samples mapped to frame " << frame.Samples.frame_start << ":" << frame.Samples.sample_start << " to frame " << frame.Samples.frame_end << ":" << frame.Samples.sample_end << endl; } } diff --git a/src/Main.cpp b/src/Main.cpp index 78a53a8c..8a878552 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -16,6 +16,15 @@ void FrameReady(int number) int main() { + + openshot::FFmpegReader r1("/home/jonathan/Videos/sintel_trailer-720p.mp4"); + r1.Open(); + FrameMapper map(&r1, Framerate(24,1), PULLDOWN_NONE); + map.PrintMapping(); + + return 0; + + // Create timeline Timeline t(640, 360, Framerate(24,1));