2011-10-11 08:44:27 -05:00
|
|
|
/**
|
2013-09-09 23:32:16 -05:00
|
|
|
* @file
|
2013-09-12 17:52:10 -05:00
|
|
|
* @brief Source file for the FrameMapper class
|
|
|
|
|
* @author Jonathan Thomas <jonathan@openshot.org>
|
|
|
|
|
*
|
2019-06-09 08:31:04 -04:00
|
|
|
* @ref License
|
|
|
|
|
*/
|
|
|
|
|
|
2021-10-16 01:26:26 -04:00
|
|
|
// Copyright (c) 2008-2019 OpenShot Studios, LLC
|
|
|
|
|
//
|
|
|
|
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
2011-10-11 08:44:27 -05:00
|
|
|
|
2021-04-19 20:57:47 -04:00
|
|
|
#include <cmath>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <iomanip>
|
|
|
|
|
|
2020-10-18 07:43:37 -04:00
|
|
|
#include "FrameMapper.h"
|
2021-01-26 10:52:04 -05:00
|
|
|
#include "Exceptions.h"
|
2020-10-19 16:28:49 -04:00
|
|
|
#include "Clip.h"
|
2021-11-01 11:04:31 -04:00
|
|
|
#include "ZmqLogger.h"
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
using namespace openshot;
|
|
|
|
|
|
2020-10-13 17:08:27 -05:00
|
|
|
FrameMapper::FrameMapper(ReaderBase *reader, Fraction target, PulldownType target_pulldown, int target_sample_rate, int target_channels, ChannelLayout target_channel_layout) :
|
2021-04-08 22:34:48 -05:00
|
|
|
reader(reader), target(target), pulldown(target_pulldown), is_dirty(true), avr(NULL), parent_position(0.0)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
2012-10-14 23:24:27 -05:00
|
|
|
// Set the original frame rate from the reader
|
2014-01-05 23:12:56 -06:00
|
|
|
original = Fraction(reader->info.fps.num, reader->info.fps.den);
|
2012-10-14 23:24:27 -05:00
|
|
|
|
2012-10-17 09:57:02 -05:00
|
|
|
// Set all info struct members equal to the internal reader
|
|
|
|
|
info = reader->info;
|
2014-01-05 23:12:56 -06:00
|
|
|
info.fps.num = target.num;
|
|
|
|
|
info.fps.den = target.den;
|
|
|
|
|
info.video_timebase.num = target.den;
|
|
|
|
|
info.video_timebase.den = target.num;
|
2012-10-17 09:57:02 -05:00
|
|
|
info.video_length = round(info.duration * info.fps.ToDouble());
|
2015-03-01 22:36:39 -06:00
|
|
|
info.sample_rate = target_sample_rate;
|
|
|
|
|
info.channels = target_channels;
|
|
|
|
|
info.channel_layout = target_channel_layout;
|
2012-11-20 16:22:50 -06:00
|
|
|
info.width = reader->info.width;
|
|
|
|
|
info.height = reader->info.height;
|
2011-10-11 08:44:27 -05:00
|
|
|
|
|
|
|
|
// Used to toggle odd / even fields
|
|
|
|
|
field_toggle = true;
|
|
|
|
|
|
2016-07-27 13:18:55 -05:00
|
|
|
// Adjust cache size based on size of frame and audio
|
|
|
|
|
final_cache.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels);
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
2015-12-24 16:44:45 -06:00
|
|
|
// Destructor
|
|
|
|
|
FrameMapper::~FrameMapper() {
|
2020-03-22 12:33:59 -04:00
|
|
|
|
|
|
|
|
// Auto Close if not already
|
|
|
|
|
Close();
|
2019-05-08 14:53:23 -07:00
|
|
|
|
|
|
|
|
reader = NULL;
|
2015-12-24 16:44:45 -06:00
|
|
|
}
|
|
|
|
|
|
2017-03-10 00:51:08 -06:00
|
|
|
/// Get the current reader
|
2017-10-26 18:44:35 -05:00
|
|
|
ReaderBase* FrameMapper::Reader()
|
2017-03-10 00:51:08 -06:00
|
|
|
{
|
|
|
|
|
if (reader)
|
|
|
|
|
return reader;
|
|
|
|
|
else
|
|
|
|
|
// Throw error if reader not initialized
|
2019-08-27 15:47:39 -04:00
|
|
|
throw ReaderClosed("No Reader has been initialized for FrameMapper. Call Reader(*reader) before calling this method.");
|
2017-03-10 00:51:08 -06:00
|
|
|
}
|
|
|
|
|
|
2017-09-28 16:03:01 -05:00
|
|
|
void FrameMapper::AddField(int64_t frame)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
|
|
|
|
// Add a field, and toggle the odd / even field
|
2012-10-15 17:45:20 -05:00
|
|
|
AddField(Field(frame, field_toggle));
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FrameMapper::AddField(Field field)
|
|
|
|
|
{
|
|
|
|
|
// Add a field to the end of the field list
|
|
|
|
|
fields.push_back(field);
|
|
|
|
|
|
|
|
|
|
// toggle the odd / even flag
|
|
|
|
|
field_toggle = (field_toggle ? false : true);
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-02 18:32:47 -06:00
|
|
|
// Clear both the fields & frames lists
|
|
|
|
|
void FrameMapper::Clear() {
|
|
|
|
|
// Clear the fields & frames lists
|
|
|
|
|
fields.clear();
|
|
|
|
|
fields.shrink_to_fit();
|
|
|
|
|
frames.clear();
|
|
|
|
|
frames.shrink_to_fit();
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Use the original and target frame rates and a pull-down technique to create
|
|
|
|
|
// a mapping between the original fields and frames or a video to a new frame rate.
|
|
|
|
|
// This might repeat or skip fields and frames of the original video, depending on
|
|
|
|
|
// whether the frame rate is increasing or decreasing.
|
|
|
|
|
void FrameMapper::Init()
|
|
|
|
|
{
|
2019-07-03 14:14:02 -04:00
|
|
|
ZmqLogger::Instance()->AppendDebugMethod("FrameMapper::Init (Calculate frame mappings)");
|
2015-03-01 22:36:39 -06:00
|
|
|
|
2015-09-28 22:05:50 -05:00
|
|
|
// Do not initialize anything if just a picture with no audio
|
|
|
|
|
if (info.has_video and !info.has_audio and info.has_single_image)
|
|
|
|
|
// Skip initialization
|
|
|
|
|
return;
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Clear the fields & frames lists
|
2022-02-02 18:32:47 -06:00
|
|
|
Clear();
|
2011-10-11 08:44:27 -05:00
|
|
|
|
2021-04-08 22:34:48 -05:00
|
|
|
// Find parent position (if any)
|
|
|
|
|
Clip *parent = (Clip *) ParentClip();
|
|
|
|
|
if (parent) {
|
|
|
|
|
parent_position = parent->Position();
|
|
|
|
|
parent_start = parent->Start();
|
|
|
|
|
} else {
|
|
|
|
|
parent_position = 0.0;
|
|
|
|
|
parent_start = 0.0;
|
|
|
|
|
}
|
|
|
|
|
|
2015-02-26 17:33:09 -06:00
|
|
|
// Mark as not dirty
|
|
|
|
|
is_dirty = false;
|
|
|
|
|
|
|
|
|
|
// Clear cache
|
|
|
|
|
final_cache.Clear();
|
|
|
|
|
|
2013-10-17 17:46:58 -05:00
|
|
|
// Some framerates are handled special, and some use a generic Keyframe curve to
|
|
|
|
|
// map the framerates. These are the special framerates:
|
2016-07-27 13:18:55 -05:00
|
|
|
if ((fabs(original.ToFloat() - 24.0) < 1e-7 || fabs(original.ToFloat() - 25.0) < 1e-7 || fabs(original.ToFloat() - 30.0) < 1e-7) &&
|
|
|
|
|
(fabs(target.ToFloat() - 24.0) < 1e-7 || fabs(target.ToFloat() - 25.0) < 1e-7 || fabs(target.ToFloat() - 30.0) < 1e-7)) {
|
2012-10-30 18:53:26 -05:00
|
|
|
|
2013-10-17 17:46:58 -05:00
|
|
|
// Get the difference (in frames) between the original and target frame rates
|
2014-01-05 23:12:56 -06:00
|
|
|
float difference = target.ToInt() - original.ToInt();
|
2011-10-11 08:44:27 -05:00
|
|
|
|
2013-10-17 17:46:58 -05:00
|
|
|
// Find the number (i.e. interval) of fields that need to be skipped or repeated
|
|
|
|
|
int field_interval = 0;
|
|
|
|
|
int frame_interval = 0;
|
|
|
|
|
|
|
|
|
|
if (difference != 0)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
2014-01-05 23:12:56 -06:00
|
|
|
field_interval = round(fabs(original.ToInt() / difference));
|
2011-10-11 08:44:27 -05:00
|
|
|
|
2013-10-17 17:46:58 -05:00
|
|
|
// Get frame interval (2 fields per frame)
|
|
|
|
|
frame_interval = field_interval * 2.0f;
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
2013-10-17 17:46:58 -05:00
|
|
|
|
|
|
|
|
// Calculate # of fields to map
|
2017-09-28 16:03:01 -05:00
|
|
|
int64_t frame = 1;
|
|
|
|
|
int64_t number_of_fields = reader->info.video_length * 2;
|
2013-10-17 17:46:58 -05:00
|
|
|
|
|
|
|
|
// Loop through all fields in the original video file
|
2017-09-28 16:03:01 -05:00
|
|
|
for (int64_t field = 1; field <= number_of_fields; field++)
|
2013-10-17 17:46:58 -05:00
|
|
|
{
|
|
|
|
|
|
|
|
|
|
if (difference == 0) // Same frame rate, NO pull-down or special techniques required
|
|
|
|
|
{
|
|
|
|
|
// Add fields
|
|
|
|
|
AddField(frame);
|
|
|
|
|
}
|
|
|
|
|
else if (difference > 0) // Need to ADD fake fields & frames, because original video has too few frames
|
|
|
|
|
{
|
|
|
|
|
// Add current field
|
|
|
|
|
AddField(frame);
|
|
|
|
|
|
|
|
|
|
if (pulldown == PULLDOWN_CLASSIC && field % field_interval == 0)
|
|
|
|
|
{
|
|
|
|
|
// Add extra field for each 'field interval
|
|
|
|
|
AddField(frame);
|
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
if (frame + 1 <= info.video_length)
|
|
|
|
|
// add field for next frame (if the next frame exists)
|
|
|
|
|
AddField(Field(frame + 1, field_toggle));
|
|
|
|
|
}
|
|
|
|
|
else if (pulldown == PULLDOWN_NONE && field % frame_interval == 0)
|
|
|
|
|
{
|
|
|
|
|
// No pull-down technique needed, just repeat this frame
|
|
|
|
|
AddField(frame);
|
|
|
|
|
AddField(frame);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (difference < 0) // Need to SKIP fake fields & frames, because we want to return to the original film frame rate
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
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 (pulldown == PULLDOWN_ADVANCED && field % field_interval == 0 && field % frame_interval != 0)
|
|
|
|
|
{
|
|
|
|
|
// skip this field, plus the next field
|
|
|
|
|
field++;
|
|
|
|
|
}
|
|
|
|
|
else if (pulldown == PULLDOWN_NONE && frame % field_interval == 0)
|
|
|
|
|
{
|
|
|
|
|
// skip this field, plus the next one
|
|
|
|
|
field++;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// No skipping needed, so add the field
|
|
|
|
|
AddField(frame);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// increment frame number (if field is divisible by 2)
|
|
|
|
|
if (field % 2 == 0 && field > 0)
|
|
|
|
|
frame++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} else {
|
2019-03-06 15:35:03 -06:00
|
|
|
// Map the remaining framerates using a linear algorithm
|
2017-05-26 01:08:20 -05:00
|
|
|
double rate_diff = target.ToDouble() / original.ToDouble();
|
2017-09-28 16:03:01 -05:00
|
|
|
int64_t new_length = reader->info.video_length * rate_diff;
|
2013-10-17 17:46:58 -05:00
|
|
|
|
2019-03-06 15:35:03 -06:00
|
|
|
// Calculate the value difference
|
2022-06-25 17:42:30 -05:00
|
|
|
double value_increment = reader->info.video_length / (double) (new_length);
|
2013-10-17 17:46:58 -05:00
|
|
|
|
|
|
|
|
// Loop through curve, and build list of frames
|
2019-03-06 15:35:03 -06:00
|
|
|
double original_frame_num = 1.0f;
|
2017-09-28 16:03:01 -05:00
|
|
|
for (int64_t frame_num = 1; frame_num <= new_length; frame_num++)
|
2013-10-17 17:46:58 -05:00
|
|
|
{
|
|
|
|
|
// Add 2 fields per frame
|
2019-03-06 15:35:03 -06:00
|
|
|
AddField(round(original_frame_num));
|
|
|
|
|
AddField(round(original_frame_num));
|
|
|
|
|
|
|
|
|
|
// Increment original frame number
|
|
|
|
|
original_frame_num += value_increment;
|
2013-10-17 17:46:58 -05:00
|
|
|
}
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Loop through the target frames again (combining fields into frames)
|
|
|
|
|
Field Odd(0, true); // temp field used to track the ODD field
|
|
|
|
|
Field Even(0, true); // temp field used to track the EVEN field
|
|
|
|
|
|
2012-10-15 17:45:20 -05:00
|
|
|
// Variables used to remap audio samples
|
2017-09-28 16:03:01 -05:00
|
|
|
int64_t start_samples_frame = 1;
|
2012-10-15 17:45:20 -05:00
|
|
|
int start_samples_position = 0;
|
|
|
|
|
|
2019-12-15 14:22:59 -05:00
|
|
|
for (std::vector<Field>::size_type field = 1; field <= fields.size(); field++)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
|
|
|
|
// Get the current field
|
|
|
|
|
Field f = fields[field - 1];
|
|
|
|
|
|
|
|
|
|
// Is field divisible by 2?
|
|
|
|
|
if (field % 2 == 0 && field > 0)
|
|
|
|
|
{
|
2012-10-15 17:45:20 -05:00
|
|
|
// New frame number
|
2018-05-30 03:20:31 -05:00
|
|
|
int64_t frame_number = field / 2;
|
2012-10-15 17:45:20 -05:00
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Set the bottom frame
|
|
|
|
|
if (f.isOdd)
|
|
|
|
|
Odd = f;
|
|
|
|
|
else
|
|
|
|
|
Even = f;
|
|
|
|
|
|
2015-03-01 22:36:39 -06:00
|
|
|
// Determine the range of samples (from the original rate). Resampling happens in real-time when
|
|
|
|
|
// calling the GetFrame() method. So this method only needs to redistribute the original samples with
|
|
|
|
|
// the original sample rate.
|
2017-09-28 16:03:01 -05:00
|
|
|
int64_t end_samples_frame = start_samples_frame;
|
2012-10-15 17:45:20 -05:00
|
|
|
int end_samples_position = start_samples_position;
|
2020-10-20 13:03:10 -05:00
|
|
|
int remaining_samples = Frame::GetSamplesPerFrame(AdjustFrameNumber(frame_number), target, reader->info.sample_rate, reader->info.channels);
|
2012-10-15 17:45:20 -05:00
|
|
|
|
|
|
|
|
while (remaining_samples > 0)
|
|
|
|
|
{
|
2021-04-08 22:34:48 -05:00
|
|
|
// Get original samples (with NO framerate adjustments)
|
|
|
|
|
// This is the original reader's frame numbers
|
|
|
|
|
int original_samples = Frame::GetSamplesPerFrame(end_samples_frame, original, reader->info.sample_rate, reader->info.channels) - end_samples_position;
|
2012-10-15 17:45:20 -05:00
|
|
|
|
|
|
|
|
// Enough samples
|
|
|
|
|
if (original_samples >= remaining_samples)
|
|
|
|
|
{
|
|
|
|
|
// Take all that we need, and break loop
|
2017-01-06 20:48:47 -05:00
|
|
|
end_samples_position += remaining_samples - 1;
|
2012-10-15 17:45:20 -05:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-10-17 17:46:58 -05:00
|
|
|
|
|
|
|
|
|
2012-10-15 17:45:20 -05:00
|
|
|
// Create the sample mapping struct
|
2020-10-20 13:03:10 -05:00
|
|
|
SampleRange Samples = {start_samples_frame, start_samples_position, end_samples_frame, end_samples_position, Frame::GetSamplesPerFrame(AdjustFrameNumber(frame_number), target, reader->info.sample_rate, reader->info.channels)};
|
2012-10-15 17:45:20 -05:00
|
|
|
|
|
|
|
|
// Reset the audio variables
|
|
|
|
|
start_samples_frame = end_samples_frame;
|
|
|
|
|
start_samples_position = end_samples_position + 1;
|
2020-10-20 13:03:10 -05:00
|
|
|
if (start_samples_position >= Frame::GetSamplesPerFrame(AdjustFrameNumber(start_samples_frame), original, reader->info.sample_rate, reader->info.channels))
|
2012-10-15 17:45:20 -05:00
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Create a frame and ADD it to the frames collection
|
2012-10-15 17:45:20 -05:00
|
|
|
MappedFrame frame = {Odd, Even, Samples};
|
2011-10-11 08:44:27 -05:00
|
|
|
frames.push_back(frame);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Set the top field
|
|
|
|
|
if (f.isOdd)
|
|
|
|
|
Odd = f;
|
|
|
|
|
else
|
|
|
|
|
Even = f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clear the internal fields list (no longer needed)
|
|
|
|
|
fields.clear();
|
2022-02-02 18:32:47 -06:00
|
|
|
fields.shrink_to_fit();
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
2017-10-26 18:44:35 -05:00
|
|
|
MappedFrame FrameMapper::GetMappedFrame(int64_t TargetFrameNumber)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
2019-03-06 15:35:03 -06:00
|
|
|
// Check if mappings are dirty (and need to be recalculated)
|
|
|
|
|
if (is_dirty)
|
|
|
|
|
// Recalculate mappings
|
|
|
|
|
Init();
|
|
|
|
|
|
2015-09-28 22:05:50 -05:00
|
|
|
// Ignore mapping on single image readers
|
|
|
|
|
if (info.has_video and !info.has_audio and info.has_single_image) {
|
|
|
|
|
// Return the same number
|
|
|
|
|
MappedFrame frame;
|
|
|
|
|
frame.Even.Frame = TargetFrameNumber;
|
|
|
|
|
frame.Odd.Frame = TargetFrameNumber;
|
|
|
|
|
frame.Samples.frame_start = 0;
|
|
|
|
|
frame.Samples.frame_end = 0;
|
|
|
|
|
frame.Samples.sample_start = 0;
|
|
|
|
|
frame.Samples.sample_end = 0;
|
|
|
|
|
frame.Samples.total = 0;
|
|
|
|
|
return frame;
|
|
|
|
|
}
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Check if frame number is valid
|
2014-01-06 00:04:40 -06:00
|
|
|
if(TargetFrameNumber < 1 || frames.size() == 0)
|
2013-11-04 17:30:14 -06:00
|
|
|
// frame too small, return error
|
2011-10-11 08:44:27 -05:00
|
|
|
throw OutOfBoundsFrame("An invalid frame was requested.", TargetFrameNumber, frames.size());
|
2013-11-04 17:30:14 -06:00
|
|
|
|
2019-12-15 14:22:59 -05:00
|
|
|
else if (TargetFrameNumber > (int64_t)frames.size())
|
2013-11-04 17:30:14 -06:00
|
|
|
// frame too large, set to end frame
|
|
|
|
|
TargetFrameNumber = frames.size();
|
2011-10-11 08:44:27 -05:00
|
|
|
|
2016-04-24 15:37:47 -05:00
|
|
|
// Debug output
|
2022-01-12 10:53:58 -05:00
|
|
|
ZmqLogger::Instance()->AppendDebugMethod(
|
|
|
|
|
"FrameMapper::GetMappedFrame",
|
|
|
|
|
"TargetFrameNumber", TargetFrameNumber,
|
|
|
|
|
"frames.size()", frames.size(),
|
|
|
|
|
"frames[...].Odd", frames[TargetFrameNumber - 1].Odd.Frame,
|
|
|
|
|
"frames[...].Even", frames[TargetFrameNumber - 1].Even.Frame);
|
2016-04-24 15:37:47 -05:00
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Return frame
|
|
|
|
|
return frames[TargetFrameNumber - 1];
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-24 16:44:45 -06:00
|
|
|
// Get or generate a blank frame
|
2017-09-28 16:03:01 -05:00
|
|
|
std::shared_ptr<Frame> FrameMapper::GetOrCreateFrame(int64_t number)
|
2015-12-24 16:44:45 -06:00
|
|
|
{
|
2017-08-20 17:37:39 -05:00
|
|
|
std::shared_ptr<Frame> new_frame;
|
2015-12-24 16:44:45 -06:00
|
|
|
|
2016-07-27 13:18:55 -05:00
|
|
|
// Init some basic properties about this frame (keep sample rate and # channels the same as the original reader for now)
|
2020-10-20 13:03:10 -05:00
|
|
|
int samples_in_frame = Frame::GetSamplesPerFrame(AdjustFrameNumber(number), target, reader->info.sample_rate, reader->info.channels);
|
2015-12-24 16:44:45 -06:00
|
|
|
|
|
|
|
|
try {
|
2016-04-24 15:37:47 -05:00
|
|
|
// Debug output
|
2022-01-12 10:53:58 -05:00
|
|
|
ZmqLogger::Instance()->AppendDebugMethod(
|
|
|
|
|
"FrameMapper::GetOrCreateFrame (from reader)",
|
|
|
|
|
"number", number,
|
|
|
|
|
"samples_in_frame", samples_in_frame);
|
2016-04-24 15:37:47 -05:00
|
|
|
|
2015-12-24 16:44:45 -06:00
|
|
|
// Attempt to get a frame (but this could fail if a reader has just been closed)
|
|
|
|
|
new_frame = reader->GetFrame(number);
|
|
|
|
|
|
|
|
|
|
// Return real frame
|
|
|
|
|
return new_frame;
|
|
|
|
|
|
|
|
|
|
} catch (const ReaderClosed & e) {
|
|
|
|
|
// ...
|
|
|
|
|
} catch (const OutOfBoundsFrame & e) {
|
|
|
|
|
// ...
|
|
|
|
|
}
|
|
|
|
|
|
2016-04-24 15:37:47 -05:00
|
|
|
// Debug output
|
2022-01-12 10:53:58 -05:00
|
|
|
ZmqLogger::Instance()->AppendDebugMethod(
|
|
|
|
|
"FrameMapper::GetOrCreateFrame (create blank)",
|
|
|
|
|
"number", number,
|
|
|
|
|
"samples_in_frame", samples_in_frame);
|
2016-04-24 15:37:47 -05:00
|
|
|
|
2015-12-24 16:44:45 -06:00
|
|
|
// Create blank frame
|
2017-08-20 17:37:39 -05:00
|
|
|
new_frame = std::make_shared<Frame>(number, info.width, info.height, "#000000", samples_in_frame, reader->info.channels);
|
2016-07-27 13:18:55 -05:00
|
|
|
new_frame->SampleRate(reader->info.sample_rate);
|
2015-12-24 16:44:45 -06:00
|
|
|
new_frame->ChannelsLayout(info.channel_layout);
|
2018-09-11 00:40:31 -05:00
|
|
|
new_frame->AddAudioSilence(samples_in_frame);
|
2015-12-24 16:44:45 -06:00
|
|
|
return new_frame;
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-17 09:57:02 -05:00
|
|
|
// Get an openshot::Frame object for a specific frame number of this reader.
|
2017-10-26 18:44:35 -05:00
|
|
|
std::shared_ptr<Frame> FrameMapper::GetFrame(int64_t requested_frame)
|
2012-10-17 09:57:02 -05:00
|
|
|
{
|
2015-06-01 00:20:14 -07:00
|
|
|
// Check final cache, and just return the frame (if it's available)
|
2017-08-20 17:37:39 -05:00
|
|
|
std::shared_ptr<Frame> final_frame = final_cache.GetFrame(requested_frame);
|
2015-08-05 23:40:58 -05:00
|
|
|
if (final_frame) return final_frame;
|
2015-06-01 00:20:14 -07:00
|
|
|
|
|
|
|
|
// Create a scoped lock, allowing only a single thread to run the following code at one time
|
2021-10-27 14:34:05 -04:00
|
|
|
const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
|
2015-06-01 00:20:14 -07:00
|
|
|
|
2021-04-08 22:34:48 -05:00
|
|
|
// Find parent properties (if any)
|
|
|
|
|
Clip *parent = (Clip *) ParentClip();
|
|
|
|
|
if (parent) {
|
|
|
|
|
float position = parent->Position();
|
|
|
|
|
float start = parent->Start();
|
|
|
|
|
if (parent_position != position || parent_start != start) {
|
|
|
|
|
// Force dirty if parent clip has moved or been trimmed
|
|
|
|
|
// since this heavily affects frame #s and audio mappings
|
|
|
|
|
is_dirty = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if mappings are dirty (and need to be recalculated)
|
2015-02-26 17:33:09 -06:00
|
|
|
if (is_dirty)
|
|
|
|
|
Init();
|
|
|
|
|
|
2015-06-01 00:20:14 -07:00
|
|
|
// Check final cache a 2nd time (due to potential lock already generating this frame)
|
2015-08-05 23:40:58 -05:00
|
|
|
final_frame = final_cache.GetFrame(requested_frame);
|
|
|
|
|
if (final_frame) return final_frame;
|
2012-10-17 09:57:02 -05:00
|
|
|
|
2015-03-08 21:42:53 -05:00
|
|
|
// Minimum number of frames to process (for performance reasons)
|
2017-07-19 16:05:07 -05:00
|
|
|
// Dialing this down to 1 for now, as it seems to improve performance, and reduce export crashes
|
|
|
|
|
int minimum_frames = 1;
|
2015-02-26 17:33:09 -06:00
|
|
|
|
2016-04-11 17:43:56 -05:00
|
|
|
// Debug output
|
2022-01-12 10:53:58 -05:00
|
|
|
ZmqLogger::Instance()->AppendDebugMethod(
|
|
|
|
|
"FrameMapper::GetFrame (Loop through frames)",
|
|
|
|
|
"requested_frame", requested_frame,
|
|
|
|
|
"minimum_frames", minimum_frames);
|
2016-04-11 17:43:56 -05:00
|
|
|
|
2017-06-22 15:26:40 -05:00
|
|
|
// Loop through all requested frames
|
2017-09-28 16:03:01 -05:00
|
|
|
for (int64_t frame_number = requested_frame; frame_number < requested_frame + minimum_frames; frame_number++)
|
2012-10-17 09:57:02 -05:00
|
|
|
{
|
2017-06-22 15:26:40 -05:00
|
|
|
|
|
|
|
|
// Debug output
|
2022-01-12 10:53:58 -05:00
|
|
|
ZmqLogger::Instance()->AppendDebugMethod(
|
|
|
|
|
"FrameMapper::GetFrame (inside omp for loop)",
|
|
|
|
|
"frame_number", frame_number,
|
|
|
|
|
"minimum_frames", minimum_frames,
|
|
|
|
|
"requested_frame", requested_frame);
|
2017-06-22 15:26:40 -05:00
|
|
|
|
|
|
|
|
// Get the mapped frame
|
|
|
|
|
MappedFrame mapped = GetMappedFrame(frame_number);
|
2017-08-20 17:37:39 -05:00
|
|
|
std::shared_ptr<Frame> mapped_frame;
|
2017-06-22 15:26:40 -05:00
|
|
|
|
|
|
|
|
// Get the mapped frame (keeping the sample rate and channels the same as the original... for the moment)
|
|
|
|
|
mapped_frame = GetOrCreateFrame(mapped.Odd.Frame);
|
|
|
|
|
|
|
|
|
|
// Get # of channels in the actual frame
|
|
|
|
|
int channels_in_frame = mapped_frame->GetAudioChannelsCount();
|
2020-10-20 13:03:10 -05:00
|
|
|
int samples_in_frame = Frame::GetSamplesPerFrame(AdjustFrameNumber(frame_number), target, mapped_frame->SampleRate(), channels_in_frame);
|
2017-06-22 15:26:40 -05:00
|
|
|
|
|
|
|
|
// Determine if mapped frame is identical to source frame
|
|
|
|
|
// including audio sample distribution according to mapped.Samples,
|
|
|
|
|
// and frame_number. In some cases such as end of stream, the reader
|
|
|
|
|
// will return a frame with a different frame number. In these cases,
|
|
|
|
|
// we cannot use the frame as is, nor can we modify the frame number,
|
|
|
|
|
// otherwise the reader's cache object internals become invalid.
|
|
|
|
|
if (info.sample_rate == mapped_frame->SampleRate() &&
|
|
|
|
|
info.channels == mapped_frame->GetAudioChannelsCount() &&
|
|
|
|
|
info.channel_layout == mapped_frame->ChannelsLayout() &&
|
|
|
|
|
mapped.Samples.total == mapped_frame->GetAudioSamplesCount() &&
|
|
|
|
|
mapped.Samples.frame_start == mapped.Odd.Frame &&
|
|
|
|
|
mapped.Samples.sample_start == 0 &&
|
|
|
|
|
mapped_frame->number == frame_number &&// in some conditions (e.g. end of stream)
|
|
|
|
|
info.fps.num == reader->info.fps.num &&
|
|
|
|
|
info.fps.den == reader->info.fps.den) {
|
|
|
|
|
// Add original frame to cache, and skip the rest (for performance reasons)
|
|
|
|
|
final_cache.Add(mapped_frame);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create a new frame
|
2020-08-20 16:50:12 -04:00
|
|
|
auto frame = std::make_shared<Frame>(
|
|
|
|
|
frame_number, 1, 1, "#000000", samples_in_frame, channels_in_frame);
|
2017-06-22 15:26:40 -05:00
|
|
|
frame->SampleRate(mapped_frame->SampleRate());
|
|
|
|
|
frame->ChannelsLayout(mapped_frame->ChannelsLayout());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Copy the image from the odd field
|
2017-08-20 17:37:39 -05:00
|
|
|
std::shared_ptr<Frame> odd_frame;
|
2017-06-22 15:26:40 -05:00
|
|
|
odd_frame = GetOrCreateFrame(mapped.Odd.Frame);
|
|
|
|
|
|
|
|
|
|
if (odd_frame)
|
2020-08-20 16:50:12 -04:00
|
|
|
frame->AddImage(std::make_shared<QImage>(*odd_frame->GetImage()), true);
|
2017-06-22 15:26:40 -05:00
|
|
|
if (mapped.Odd.Frame != mapped.Even.Frame) {
|
|
|
|
|
// Add even lines (if different than the previous image)
|
2017-08-20 17:37:39 -05:00
|
|
|
std::shared_ptr<Frame> even_frame;
|
2017-06-22 15:26:40 -05:00
|
|
|
even_frame = GetOrCreateFrame(mapped.Even.Frame);
|
|
|
|
|
if (even_frame)
|
2020-08-20 16:50:12 -04:00
|
|
|
frame->AddImage(
|
|
|
|
|
std::make_shared<QImage>(*even_frame->GetImage()), false);
|
2017-06-22 15:26:40 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Resample audio on frame (if needed)
|
|
|
|
|
bool need_resampling = false;
|
|
|
|
|
if (info.has_audio &&
|
|
|
|
|
(info.sample_rate != frame->SampleRate() ||
|
|
|
|
|
info.channels != frame->GetAudioChannelsCount() ||
|
|
|
|
|
info.channel_layout != frame->ChannelsLayout()))
|
|
|
|
|
// Resample audio and correct # of channels if needed
|
|
|
|
|
need_resampling = true;
|
|
|
|
|
|
|
|
|
|
// create a copy of mapped.Samples that will be used by copy loop
|
|
|
|
|
SampleRange copy_samples = mapped.Samples;
|
|
|
|
|
|
|
|
|
|
if (need_resampling)
|
2012-10-17 09:57:02 -05:00
|
|
|
{
|
2017-06-22 15:26:40 -05:00
|
|
|
// 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.
|
2020-09-14 03:13:54 -05:00
|
|
|
const int EXTRA_INPUT_SAMPLES = 100;
|
2016-04-24 15:37:47 -05:00
|
|
|
|
2018-01-06 02:22:05 -06:00
|
|
|
// Extend end sample count by an additional EXTRA_INPUT_SAMPLES samples
|
2017-06-22 15:26:40 -05:00
|
|
|
copy_samples.sample_end += EXTRA_INPUT_SAMPLES;
|
|
|
|
|
int samples_per_end_frame =
|
|
|
|
|
Frame::GetSamplesPerFrame(copy_samples.frame_end, original,
|
2021-05-07 23:05:16 -04:00
|
|
|
reader->info.sample_rate, reader->info.channels);
|
2017-06-22 15:26:40 -05:00
|
|
|
if (copy_samples.sample_end >= samples_per_end_frame)
|
2017-01-06 20:48:47 -05:00
|
|
|
{
|
2017-06-22 15:26:40 -05:00
|
|
|
// check for wrapping
|
|
|
|
|
copy_samples.frame_end++;
|
|
|
|
|
copy_samples.sample_end -= samples_per_end_frame;
|
|
|
|
|
}
|
|
|
|
|
copy_samples.total += EXTRA_INPUT_SAMPLES;
|
2017-01-06 20:48:47 -05:00
|
|
|
|
2017-06-22 15:26:40 -05:00
|
|
|
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,
|
2021-05-07 23:05:16 -04:00
|
|
|
reader->info.sample_rate, reader->info.channels);
|
2017-06-22 15:26:40 -05:00
|
|
|
if (copy_samples.sample_start >= samples_per_start_frame)
|
2017-01-06 20:48:47 -05:00
|
|
|
{
|
|
|
|
|
// check for wrapping
|
2017-06-22 15:26:40 -05:00
|
|
|
copy_samples.frame_start++;
|
|
|
|
|
copy_samples.sample_start -= samples_per_start_frame;
|
2017-01-06 20:48:47 -05:00
|
|
|
}
|
2017-06-22 15:26:40 -05:00
|
|
|
copy_samples.total -= EXTRA_INPUT_SAMPLES;
|
2017-01-06 20:48:47 -05:00
|
|
|
}
|
2017-06-22 15:26:40 -05:00
|
|
|
}
|
2017-01-06 20:48:47 -05:00
|
|
|
|
2017-06-22 15:26:40 -05:00
|
|
|
// Copy the samples
|
|
|
|
|
int samples_copied = 0;
|
2017-09-28 16:03:01 -05:00
|
|
|
int64_t starting_frame = copy_samples.frame_start;
|
2017-06-22 15:26:40 -05:00
|
|
|
while (info.has_audio && samples_copied < copy_samples.total)
|
|
|
|
|
{
|
|
|
|
|
// Init number of samples to copy this iteration
|
|
|
|
|
int remaining_samples = copy_samples.total - samples_copied;
|
|
|
|
|
int number_to_copy = 0;
|
|
|
|
|
|
2017-07-19 16:05:07 -05:00
|
|
|
// number of original samples on this frame
|
2017-08-20 17:37:39 -05:00
|
|
|
std::shared_ptr<Frame> original_frame = GetOrCreateFrame(starting_frame);
|
2017-07-19 16:05:07 -05:00
|
|
|
int original_samples = original_frame->GetAudioSamplesCount();
|
|
|
|
|
|
2017-06-22 15:26:40 -05:00
|
|
|
// Loop through each channel
|
|
|
|
|
for (int channel = 0; channel < channels_in_frame; channel++)
|
2012-10-17 09:57:02 -05:00
|
|
|
{
|
2017-06-22 15:26:40 -05:00
|
|
|
if (starting_frame == copy_samples.frame_start)
|
2015-03-08 21:42:53 -05:00
|
|
|
{
|
2017-06-22 15:26:40 -05:00
|
|
|
// 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;
|
2015-03-08 21:42:53 -05:00
|
|
|
|
2017-06-22 15:26:40 -05:00
|
|
|
// Add samples to new frame
|
|
|
|
|
frame->AddAudio(true, channel, samples_copied, original_frame->GetAudioSamples(channel) + copy_samples.sample_start, number_to_copy, 1.0);
|
2015-03-08 21:42:53 -05:00
|
|
|
}
|
2017-06-22 15:26:40 -05:00
|
|
|
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;
|
2015-03-08 21:42:53 -05:00
|
|
|
|
2017-06-22 15:26:40 -05:00
|
|
|
// 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);
|
|
|
|
|
}
|
2012-10-17 09:57:02 -05:00
|
|
|
}
|
|
|
|
|
|
2017-06-22 15:26:40 -05:00
|
|
|
// increment frame
|
|
|
|
|
samples_copied += number_to_copy;
|
|
|
|
|
starting_frame++;
|
|
|
|
|
}
|
2015-03-01 22:36:39 -06:00
|
|
|
|
2017-06-22 15:26:40 -05:00
|
|
|
// Resample audio on frame (if needed)
|
|
|
|
|
if (need_resampling)
|
|
|
|
|
// Resample audio and correct # of channels if needed
|
|
|
|
|
ResampleMappedAudio(frame, mapped.Odd.Frame);
|
2015-03-08 21:42:53 -05:00
|
|
|
|
2017-06-22 15:26:40 -05:00
|
|
|
// Add frame to final cache
|
|
|
|
|
final_cache.Add(frame);
|
|
|
|
|
|
|
|
|
|
} // for loop
|
2012-10-17 09:57:02 -05:00
|
|
|
|
2015-03-09 15:17:56 -05:00
|
|
|
// Return processed openshot::Frame
|
2015-03-08 21:42:53 -05:00
|
|
|
return final_cache.GetFrame(requested_frame);
|
2012-10-17 09:57:02 -05:00
|
|
|
}
|
|
|
|
|
|
2021-04-19 20:57:47 -04:00
|
|
|
void FrameMapper::PrintMapping(std::ostream* out)
|
2011-10-11 08:44:27 -05:00
|
|
|
{
|
2016-07-27 13:18:55 -05:00
|
|
|
// Check if mappings are dirty (and need to be recalculated)
|
|
|
|
|
if (is_dirty)
|
|
|
|
|
// Recalculate mappings
|
|
|
|
|
Init();
|
|
|
|
|
|
2011-10-11 08:44:27 -05:00
|
|
|
// Loop through frame mappings
|
|
|
|
|
for (float map = 1; map <= frames.size(); map++)
|
|
|
|
|
{
|
|
|
|
|
MappedFrame frame = frames[map - 1];
|
2021-04-19 20:57:47 -04:00
|
|
|
*out << "Target frame #: " << map
|
|
|
|
|
<< " mapped to original frame #:\t("
|
2021-05-07 23:05:16 -04:00
|
|
|
<< frame.Odd.Frame << " odd, "
|
|
|
|
|
<< frame.Even.Frame << " even)" << std::endl;
|
2021-04-19 20:57:47 -04:00
|
|
|
|
|
|
|
|
*out << " - Audio samples mapped to frame "
|
|
|
|
|
<< frame.Samples.frame_start << ":"
|
2021-05-07 23:05:16 -04:00
|
|
|
<< frame.Samples.sample_start << " to frame "
|
|
|
|
|
<< frame.Samples.frame_end << ":"
|
|
|
|
|
<< frame.Samples.sample_end << endl;
|
2011-10-11 08:44:27 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
2012-10-17 09:57:02 -05:00
|
|
|
|
2013-12-18 21:55:43 -06:00
|
|
|
// Determine if reader is open or closed
|
|
|
|
|
bool FrameMapper::IsOpen() {
|
|
|
|
|
if (reader)
|
|
|
|
|
return reader->IsOpen();
|
|
|
|
|
else
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-17 09:57:02 -05:00
|
|
|
// Open the internal reader
|
2017-10-26 18:44:35 -05:00
|
|
|
void FrameMapper::Open()
|
2012-10-17 09:57:02 -05:00
|
|
|
{
|
|
|
|
|
if (reader)
|
|
|
|
|
{
|
2019-07-03 14:14:02 -04:00
|
|
|
ZmqLogger::Instance()->AppendDebugMethod("FrameMapper::Open");
|
2015-03-01 22:36:39 -06:00
|
|
|
|
2012-10-17 09:57:02 -05:00
|
|
|
// Open the reader
|
|
|
|
|
reader->Open();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Close the internal reader
|
|
|
|
|
void FrameMapper::Close()
|
|
|
|
|
{
|
|
|
|
|
if (reader)
|
2015-03-01 22:36:39 -06:00
|
|
|
{
|
2015-12-24 16:44:45 -06:00
|
|
|
// Create a scoped lock, allowing only a single thread to run the following code at one time
|
2021-10-27 14:34:05 -04:00
|
|
|
const std::lock_guard<std::recursive_mutex> lock(getFrameMutex);
|
2015-12-24 16:44:45 -06:00
|
|
|
|
2019-07-03 14:14:02 -04:00
|
|
|
ZmqLogger::Instance()->AppendDebugMethod("FrameMapper::Close");
|
2015-03-01 22:36:39 -06:00
|
|
|
|
2015-03-08 21:42:53 -05:00
|
|
|
// Close internal reader
|
|
|
|
|
reader->Close();
|
2015-03-01 22:36:39 -06:00
|
|
|
}
|
2022-01-26 17:56:33 -06:00
|
|
|
|
|
|
|
|
// Clear the fields & frames lists
|
2022-02-02 18:32:47 -06:00
|
|
|
Clear();
|
2022-01-26 17:56:33 -06:00
|
|
|
|
|
|
|
|
// Mark as dirty
|
|
|
|
|
is_dirty = true;
|
|
|
|
|
|
|
|
|
|
// Clear cache
|
|
|
|
|
final_cache.Clear();
|
|
|
|
|
|
|
|
|
|
// Deallocate resample buffer
|
|
|
|
|
if (avr) {
|
|
|
|
|
SWR_CLOSE(avr);
|
|
|
|
|
SWR_FREE(&avr);
|
|
|
|
|
avr = NULL;
|
|
|
|
|
}
|
2012-10-17 09:57:02 -05:00
|
|
|
}
|
2013-12-07 21:09:55 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
// Generate JSON string of this object
|
2019-12-27 08:51:51 -05:00
|
|
|
std::string FrameMapper::Json() const {
|
2013-12-07 21:09:55 -06:00
|
|
|
|
|
|
|
|
// Return formatted string
|
|
|
|
|
return JsonValue().toStyledString();
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-27 08:51:51 -05:00
|
|
|
// Generate Json::Value for this object
|
|
|
|
|
Json::Value FrameMapper::JsonValue() const {
|
2013-12-07 21:09:55 -06:00
|
|
|
|
|
|
|
|
// Create root json object
|
|
|
|
|
Json::Value root = ReaderBase::JsonValue(); // get parent properties
|
|
|
|
|
root["type"] = "FrameMapper";
|
|
|
|
|
|
|
|
|
|
// return JsonValue
|
|
|
|
|
return root;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load JSON string into this object
|
2019-12-27 08:51:51 -05:00
|
|
|
void FrameMapper::SetJson(const std::string value) {
|
2013-12-07 21:09:55 -06:00
|
|
|
|
|
|
|
|
// Parse JSON string into JSON objects
|
|
|
|
|
try
|
|
|
|
|
{
|
2019-12-27 08:51:51 -05:00
|
|
|
const Json::Value root = openshot::stringToJson(value);
|
2013-12-07 21:09:55 -06:00
|
|
|
// Set all values that match
|
|
|
|
|
SetJsonValue(root);
|
|
|
|
|
}
|
2019-07-03 12:58:02 -04:00
|
|
|
catch (const std::exception& e)
|
2013-12-07 21:09:55 -06:00
|
|
|
{
|
|
|
|
|
// Error parsing JSON (or missing keys)
|
2019-08-27 15:47:39 -04:00
|
|
|
throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
|
2013-12-07 21:09:55 -06:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-27 08:51:51 -05:00
|
|
|
// Load Json::Value into this object
|
|
|
|
|
void FrameMapper::SetJsonValue(const Json::Value root) {
|
2013-12-07 21:09:55 -06:00
|
|
|
|
|
|
|
|
// Set parent data
|
|
|
|
|
ReaderBase::SetJsonValue(root);
|
|
|
|
|
|
|
|
|
|
// Re-Open path, and re-init everything (if needed)
|
|
|
|
|
if (reader) {
|
|
|
|
|
|
|
|
|
|
Close();
|
|
|
|
|
Open();
|
|
|
|
|
}
|
|
|
|
|
}
|
2015-02-26 17:33:09 -06:00
|
|
|
|
|
|
|
|
// Change frame rate or audio mapping details
|
2020-10-13 17:08:27 -05:00
|
|
|
void FrameMapper::ChangeMapping(Fraction target_fps, PulldownType target_pulldown, int target_sample_rate, int target_channels, ChannelLayout target_channel_layout)
|
2015-02-26 17:33:09 -06:00
|
|
|
{
|
2021-05-07 23:05:16 -04:00
|
|
|
ZmqLogger::Instance()->AppendDebugMethod(
|
|
|
|
|
"FrameMapper::ChangeMapping",
|
|
|
|
|
"target_fps.num", target_fps.num,
|
|
|
|
|
"target_fps.den", target_fps.den,
|
|
|
|
|
"target_pulldown", target_pulldown,
|
|
|
|
|
"target_sample_rate", target_sample_rate,
|
|
|
|
|
"target_channels", target_channels,
|
|
|
|
|
"target_channel_layout", target_channel_layout);
|
2015-03-01 22:36:39 -06:00
|
|
|
|
2015-02-26 17:33:09 -06:00
|
|
|
// Mark as dirty
|
|
|
|
|
is_dirty = true;
|
|
|
|
|
|
|
|
|
|
// Update mapping details
|
2017-05-26 01:08:20 -05:00
|
|
|
target.num = target_fps.num;
|
|
|
|
|
target.den = target_fps.den;
|
|
|
|
|
info.fps.num = target_fps.num;
|
|
|
|
|
info.fps.den = target_fps.den;
|
|
|
|
|
info.video_timebase.num = target_fps.den;
|
|
|
|
|
info.video_timebase.den = target_fps.num;
|
2022-08-03 16:14:00 -05:00
|
|
|
info.video_length = round(info.duration * info.fps.ToDouble());
|
2015-02-26 17:33:09 -06:00
|
|
|
pulldown = target_pulldown;
|
2015-03-01 22:36:39 -06:00
|
|
|
info.sample_rate = target_sample_rate;
|
|
|
|
|
info.channels = target_channels;
|
|
|
|
|
info.channel_layout = target_channel_layout;
|
2015-03-04 15:26:08 -06:00
|
|
|
|
2015-03-08 22:22:40 -05:00
|
|
|
// Clear cache
|
|
|
|
|
final_cache.Clear();
|
|
|
|
|
|
2016-07-27 13:18:55 -05:00
|
|
|
// Adjust cache size based on size of frame and audio
|
|
|
|
|
final_cache.SetMaxBytesFromInfo(OPEN_MP_NUM_PROCESSORS * 2, info.width, info.height, info.sample_rate, info.channels);
|
|
|
|
|
|
2015-03-08 22:22:40 -05:00
|
|
|
// Deallocate resample buffer
|
|
|
|
|
if (avr) {
|
2018-09-11 00:40:31 -05:00
|
|
|
SWR_CLOSE(avr);
|
|
|
|
|
SWR_FREE(&avr);
|
2015-03-08 22:22:40 -05:00
|
|
|
avr = NULL;
|
|
|
|
|
}
|
2015-02-26 17:33:09 -06:00
|
|
|
}
|
|
|
|
|
|
2015-03-01 22:36:39 -06:00
|
|
|
// Resample audio and map channels (if needed)
|
2017-09-28 16:03:01 -05:00
|
|
|
void FrameMapper::ResampleMappedAudio(std::shared_ptr<Frame> frame, int64_t original_frame_number)
|
2015-03-01 22:36:39 -06:00
|
|
|
{
|
2019-03-06 15:35:03 -06:00
|
|
|
// Check if mappings are dirty (and need to be recalculated)
|
|
|
|
|
if (is_dirty)
|
|
|
|
|
// Recalculate mappings
|
|
|
|
|
Init();
|
|
|
|
|
|
2015-03-01 22:36:39 -06:00
|
|
|
// Init audio buffers / variables
|
|
|
|
|
int total_frame_samples = 0;
|
|
|
|
|
int channels_in_frame = frame->GetAudioChannelsCount();
|
|
|
|
|
int sample_rate_in_frame = frame->SampleRate();
|
|
|
|
|
int samples_in_frame = frame->GetAudioSamplesCount();
|
|
|
|
|
ChannelLayout channel_layout_in_frame = frame->ChannelsLayout();
|
|
|
|
|
|
2021-05-07 23:05:16 -04:00
|
|
|
ZmqLogger::Instance()->AppendDebugMethod(
|
|
|
|
|
"FrameMapper::ResampleMappedAudio",
|
|
|
|
|
"frame->number", frame->number,
|
|
|
|
|
"original_frame_number", original_frame_number,
|
|
|
|
|
"channels_in_frame", channels_in_frame,
|
|
|
|
|
"samples_in_frame", samples_in_frame,
|
|
|
|
|
"sample_rate_in_frame", sample_rate_in_frame);
|
2015-03-08 21:42:53 -05:00
|
|
|
|
2015-03-01 22:36:39 -06:00
|
|
|
// Get audio sample array
|
|
|
|
|
float* frame_samples_float = NULL;
|
|
|
|
|
// Get samples interleaved together (c1 c2 c1 c2 c1 c2)
|
|
|
|
|
frame_samples_float = frame->GetInterleavedAudioSamples(sample_rate_in_frame, NULL, &samples_in_frame);
|
|
|
|
|
|
|
|
|
|
// Calculate total samples
|
|
|
|
|
total_frame_samples = samples_in_frame * channels_in_frame;
|
|
|
|
|
|
2015-03-04 15:26:08 -06:00
|
|
|
// Create a new array (to hold all S16 audio samples for the current queued frames)
|
2016-07-27 13:18:55 -05:00
|
|
|
int16_t* frame_samples = (int16_t*) av_malloc(sizeof(int16_t)*total_frame_samples);
|
2015-03-04 15:26:08 -06:00
|
|
|
|
2020-03-26 10:17:22 +02:00
|
|
|
// Translate audio sample values back to 16 bit integers with saturation
|
|
|
|
|
float valF;
|
|
|
|
|
int16_t conv;
|
|
|
|
|
const int16_t max16 = 32767;
|
|
|
|
|
const int16_t min16 = -32768;
|
|
|
|
|
for (int s = 0; s < total_frame_samples; s++) {
|
|
|
|
|
valF = frame_samples_float[s] * (1 << 15);
|
|
|
|
|
if (valF > max16)
|
|
|
|
|
conv = max16;
|
|
|
|
|
else if (valF < min16)
|
|
|
|
|
conv = min16;
|
|
|
|
|
else
|
|
|
|
|
conv = int(valF + 32768.5) - 32768; // +0.5 is for rounding
|
|
|
|
|
|
|
|
|
|
// Copy into buffer
|
|
|
|
|
frame_samples[s] = conv;
|
|
|
|
|
}
|
2015-03-01 22:36:39 -06:00
|
|
|
|
2015-03-09 15:17:56 -05:00
|
|
|
|
2015-03-01 22:36:39 -06:00
|
|
|
// Deallocate float array
|
|
|
|
|
delete[] frame_samples_float;
|
|
|
|
|
frame_samples_float = NULL;
|
|
|
|
|
|
2021-05-07 23:05:16 -04:00
|
|
|
ZmqLogger::Instance()->AppendDebugMethod(
|
|
|
|
|
"FrameMapper::ResampleMappedAudio (got sample data from frame)",
|
|
|
|
|
"frame->number", frame->number,
|
|
|
|
|
"total_frame_samples", total_frame_samples,
|
|
|
|
|
"target channels", info.channels,
|
|
|
|
|
"channels_in_frame", channels_in_frame,
|
|
|
|
|
"target sample_rate", info.sample_rate,
|
|
|
|
|
"samples_in_frame", samples_in_frame);
|
2015-03-01 22:36:39 -06:00
|
|
|
|
|
|
|
|
|
|
|
|
|
// Create input frame (and allocate arrays)
|
2015-09-23 00:27:28 -05:00
|
|
|
AVFrame *audio_frame = AV_ALLOCATE_FRAME();
|
|
|
|
|
AV_RESET_FRAME(audio_frame);
|
2015-03-01 22:36:39 -06:00
|
|
|
audio_frame->nb_samples = total_frame_samples / channels_in_frame;
|
|
|
|
|
|
2021-05-07 23:05:16 -04:00
|
|
|
int buf_size = audio_frame->nb_samples * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) * channels_in_frame;
|
|
|
|
|
int error_code = avcodec_fill_audio_frame(
|
|
|
|
|
audio_frame, channels_in_frame, AV_SAMPLE_FMT_S16,
|
|
|
|
|
(uint8_t *) frame_samples, buf_size, 1);
|
2015-03-01 22:36:39 -06:00
|
|
|
|
2015-10-01 13:00:50 -05:00
|
|
|
if (error_code < 0)
|
2015-03-01 22:36:39 -06:00
|
|
|
{
|
2021-08-11 03:58:45 -04:00
|
|
|
ZmqLogger::Instance()->AppendDebugMethod(
|
|
|
|
|
"FrameMapper::ResampleMappedAudio ERROR [" + av_err2string(error_code) + "]",
|
|
|
|
|
"error_code", error_code);
|
2015-03-01 22:36:39 -06:00
|
|
|
throw ErrorEncodingVideo("Error while resampling audio in frame mapper", frame->number);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update total samples & input frame size (due to bigger or smaller data types)
|
2020-10-20 13:03:10 -05:00
|
|
|
total_frame_samples = Frame::GetSamplesPerFrame(AdjustFrameNumber(frame->number), target, info.sample_rate, info.channels);
|
2015-03-08 21:42:53 -05:00
|
|
|
|
2021-05-07 23:05:16 -04:00
|
|
|
ZmqLogger::Instance()->AppendDebugMethod(
|
|
|
|
|
"FrameMapper::ResampleMappedAudio (adjust # of samples)",
|
|
|
|
|
"total_frame_samples", total_frame_samples,
|
|
|
|
|
"info.sample_rate", info.sample_rate,
|
|
|
|
|
"sample_rate_in_frame", sample_rate_in_frame,
|
|
|
|
|
"info.channels", info.channels,
|
|
|
|
|
"channels_in_frame", channels_in_frame,
|
|
|
|
|
"original_frame_number", original_frame_number);
|
2015-03-01 22:36:39 -06:00
|
|
|
|
|
|
|
|
// Create output frame (and allocate arrays)
|
2015-09-23 00:27:28 -05:00
|
|
|
AVFrame *audio_converted = AV_ALLOCATE_FRAME();
|
|
|
|
|
AV_RESET_FRAME(audio_converted);
|
2015-03-08 21:42:53 -05:00
|
|
|
audio_converted->nb_samples = total_frame_samples;
|
|
|
|
|
av_samples_alloc(audio_converted->data, audio_converted->linesize, info.channels, total_frame_samples, AV_SAMPLE_FMT_S16, 0);
|
2015-03-01 22:36:39 -06:00
|
|
|
|
2021-05-07 23:05:16 -04:00
|
|
|
ZmqLogger::Instance()->AppendDebugMethod(
|
|
|
|
|
"FrameMapper::ResampleMappedAudio (preparing for resample)",
|
|
|
|
|
"in_sample_fmt", AV_SAMPLE_FMT_S16,
|
|
|
|
|
"out_sample_fmt", AV_SAMPLE_FMT_S16,
|
|
|
|
|
"in_sample_rate", sample_rate_in_frame,
|
|
|
|
|
"out_sample_rate", info.sample_rate,
|
|
|
|
|
"in_channels", channels_in_frame,
|
|
|
|
|
"out_channels", info.channels);
|
2015-03-01 22:36:39 -06:00
|
|
|
|
|
|
|
|
int nb_samples = 0;
|
|
|
|
|
|
2017-06-22 15:26:40 -05:00
|
|
|
// setup resample context
|
|
|
|
|
if (!avr) {
|
2018-09-11 00:40:31 -05:00
|
|
|
avr = SWR_ALLOC();
|
2021-05-07 23:05:16 -04:00
|
|
|
av_opt_set_int(avr, "in_channel_layout", channel_layout_in_frame, 0);
|
|
|
|
|
av_opt_set_int(avr, "out_channel_layout", info.channel_layout, 0);
|
|
|
|
|
av_opt_set_int(avr, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
|
|
|
|
|
av_opt_set_int(avr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
|
|
|
|
|
av_opt_set_int(avr, "in_sample_rate", sample_rate_in_frame, 0);
|
|
|
|
|
av_opt_set_int(avr, "out_sample_rate", info.sample_rate, 0);
|
|
|
|
|
av_opt_set_int(avr, "in_channels", channels_in_frame, 0);
|
|
|
|
|
av_opt_set_int(avr, "out_channels", info.channels, 0);
|
2018-09-11 00:40:31 -05:00
|
|
|
SWR_INIT(avr);
|
2017-06-22 15:26:40 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert audio samples
|
2021-05-07 23:05:16 -04:00
|
|
|
nb_samples = SWR_CONVERT(avr, // audio resample context
|
|
|
|
|
audio_converted->data, // output data pointers
|
|
|
|
|
audio_converted->linesize[0], // output plane size, in bytes. (0 if unknown)
|
|
|
|
|
audio_converted->nb_samples, // maximum number of samples that the output buffer can hold
|
|
|
|
|
audio_frame->data, // input data pointers
|
|
|
|
|
audio_frame->linesize[0], // input plane size, in bytes (0 if unknown)
|
|
|
|
|
audio_frame->nb_samples); // number of input samples to convert
|
2015-03-01 22:36:39 -06:00
|
|
|
|
2015-03-04 15:26:08 -06:00
|
|
|
// Create a new array (to hold all resampled S16 audio samples)
|
2016-01-30 17:12:41 -06:00
|
|
|
int16_t* resampled_samples = new int16_t[(nb_samples * info.channels)];
|
2015-03-01 22:36:39 -06:00
|
|
|
|
|
|
|
|
// Copy audio samples over original samples
|
2015-06-01 00:20:14 -07:00
|
|
|
memcpy(resampled_samples, audio_converted->data[0], (nb_samples * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16) * info.channels));
|
2015-03-01 22:36:39 -06:00
|
|
|
|
|
|
|
|
// Free frames
|
2016-07-27 13:18:55 -05:00
|
|
|
av_freep(&audio_frame->data[0]);
|
2015-09-23 00:27:28 -05:00
|
|
|
AV_FREE_FRAME(&audio_frame);
|
2016-07-27 13:18:55 -05:00
|
|
|
av_freep(&audio_converted->data[0]);
|
2015-09-23 00:27:28 -05:00
|
|
|
AV_FREE_FRAME(&audio_converted);
|
2015-03-04 15:26:08 -06:00
|
|
|
frame_samples = NULL;
|
2015-03-01 22:36:39 -06:00
|
|
|
|
|
|
|
|
// Resize the frame to hold the right # of channels and samples
|
|
|
|
|
int channel_buffer_size = nb_samples;
|
|
|
|
|
frame->ResizeAudio(info.channels, channel_buffer_size, info.sample_rate, info.channel_layout);
|
|
|
|
|
|
2021-05-07 23:05:16 -04:00
|
|
|
ZmqLogger::Instance()->AppendDebugMethod(
|
|
|
|
|
"FrameMapper::ResampleMappedAudio (Audio successfully resampled)",
|
|
|
|
|
"nb_samples", nb_samples,
|
|
|
|
|
"total_frame_samples", total_frame_samples,
|
|
|
|
|
"info.sample_rate", info.sample_rate,
|
|
|
|
|
"channels_in_frame", channels_in_frame,
|
|
|
|
|
"info.channels", info.channels,
|
|
|
|
|
"info.channel_layout", info.channel_layout);
|
2015-03-01 22:36:39 -06:00
|
|
|
|
|
|
|
|
// Array of floats (to hold samples for each channel)
|
|
|
|
|
float *channel_buffer = new float[channel_buffer_size];
|
|
|
|
|
|
|
|
|
|
// Divide audio into channels. Loop through each channel
|
|
|
|
|
for (int channel_filter = 0; channel_filter < info.channels; channel_filter++)
|
|
|
|
|
{
|
|
|
|
|
// Init array
|
|
|
|
|
for (int z = 0; z < channel_buffer_size; z++)
|
|
|
|
|
channel_buffer[z] = 0.0f;
|
|
|
|
|
|
|
|
|
|
// Loop through all samples and add them to our Frame based on channel.
|
|
|
|
|
// Toggle through each channel number, since channel data is stored like (left right left right)
|
|
|
|
|
int channel = 0;
|
|
|
|
|
int position = 0;
|
|
|
|
|
for (int sample = 0; sample < (nb_samples * info.channels); sample++)
|
|
|
|
|
{
|
|
|
|
|
// Only add samples for current channel
|
|
|
|
|
if (channel_filter == channel)
|
|
|
|
|
{
|
|
|
|
|
// Add sample (convert from (-32768 to 32768) to (-1.0 to 1.0))
|
2015-03-04 15:26:08 -06:00
|
|
|
channel_buffer[position] = resampled_samples[sample] * (1.0f / (1 << 15));
|
2015-03-01 22:36:39 -06:00
|
|
|
|
|
|
|
|
// Increment audio position
|
|
|
|
|
position++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// increment channel (if needed)
|
|
|
|
|
if ((channel + 1) < info.channels)
|
|
|
|
|
// move to next channel
|
|
|
|
|
channel ++;
|
|
|
|
|
else
|
|
|
|
|
// reset channel
|
|
|
|
|
channel = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add samples to frame for this channel
|
2015-03-08 21:42:53 -05:00
|
|
|
frame->AddAudio(true, channel_filter, 0, channel_buffer, position, 1.0f);
|
2015-03-01 22:36:39 -06:00
|
|
|
|
2021-05-07 23:05:16 -04:00
|
|
|
ZmqLogger::Instance()->AppendDebugMethod(
|
|
|
|
|
"FrameMapper::ResampleMappedAudio (Add audio to channel)",
|
|
|
|
|
"number of samples", position,
|
|
|
|
|
"channel_filter", channel_filter);
|
2015-03-01 22:36:39 -06:00
|
|
|
}
|
|
|
|
|
|
2015-06-01 00:20:14 -07:00
|
|
|
// Update frame's audio meta data
|
|
|
|
|
frame->SampleRate(info.sample_rate);
|
|
|
|
|
frame->ChannelsLayout(info.channel_layout);
|
|
|
|
|
|
2015-03-01 22:36:39 -06:00
|
|
|
// clear channel buffer
|
|
|
|
|
delete[] channel_buffer;
|
|
|
|
|
channel_buffer = NULL;
|
|
|
|
|
|
|
|
|
|
// Delete arrays
|
2015-03-04 15:26:08 -06:00
|
|
|
delete[] resampled_samples;
|
|
|
|
|
resampled_samples = NULL;
|
2015-03-01 22:36:39 -06:00
|
|
|
}
|
2020-10-10 17:01:24 -03:00
|
|
|
|
2020-10-13 17:08:27 -05:00
|
|
|
// Adjust frame number for Clip position and start (which can result in a different number)
|
2020-10-20 13:03:10 -05:00
|
|
|
int64_t FrameMapper::AdjustFrameNumber(int64_t clip_frame_number) {
|
2020-10-10 17:01:24 -03:00
|
|
|
|
2020-10-20 13:03:10 -05:00
|
|
|
// Get clip position from parent clip (if any)
|
|
|
|
|
float position = 0.0;
|
|
|
|
|
float start = 0.0;
|
|
|
|
|
Clip *parent = (Clip *) ParentClip();
|
|
|
|
|
if (parent) {
|
|
|
|
|
position = parent->Position();
|
|
|
|
|
start = parent->Start();
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-07 23:05:16 -04:00
|
|
|
// Adjust start frame and position based on parent clip.
|
|
|
|
|
// This ensures the same frame # is used by mapped readers and clips,
|
|
|
|
|
// when calculating samples per frame.
|
|
|
|
|
// Thus, this prevents gaps and mismatches in # of samples.
|
2020-10-10 17:01:24 -03:00
|
|
|
int64_t clip_start_frame = (start * info.fps.ToDouble()) + 1;
|
|
|
|
|
int64_t clip_start_position = round(position * info.fps.ToDouble()) + 1;
|
|
|
|
|
int64_t frame_number = clip_frame_number + clip_start_position - clip_start_frame;
|
|
|
|
|
|
|
|
|
|
return frame_number;
|
2020-10-19 16:28:49 -04:00
|
|
|
}
|