core: remove direct FFmpeg dependency from libolivecore
- rational: store num/den natively instead of AVRational; math operators re-implemented natively (ported av_reduce/av_d2q/av_cmp_q semantics, verified bit-exact against FFmpeg) - AudioParams: replace AVChannelLayout member with a plain uint64_t mask (new render/channellayout.h constants mirror AV_CH_LAYOUT_* values) - Timecode: native rescale (av_rescale_q/av_rescale_q_rnd equivalents) with 128-bit intermediate precision - core no longer finds or links FFMPEG::avutil Part of the FFmpeg isolation effort: all FFmpeg access is being moved behind a dedicated shared library (ffmpeg_bridge).
This commit is contained in:
+1
-7
@@ -27,11 +27,6 @@ set(CMAKE_CXX_EXTENSIONS OFF)
|
|||||||
|
|
||||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||||
|
|
||||||
# Link avutil
|
|
||||||
find_package(FFMPEG 6.0 REQUIRED
|
|
||||||
COMPONENTS avutil
|
|
||||||
)
|
|
||||||
|
|
||||||
# Link Imath
|
# Link Imath
|
||||||
find_package(Imath REQUIRED CONFIG)
|
find_package(Imath REQUIRED CONFIG)
|
||||||
|
|
||||||
@@ -46,6 +41,7 @@ add_library(olivecore
|
|||||||
src/render/samplebuffer.cpp
|
src/render/samplebuffer.cpp
|
||||||
src/util/bezier.cpp
|
src/util/bezier.cpp
|
||||||
src/util/color.cpp
|
src/util/color.cpp
|
||||||
|
src/util/fractionutils.cpp
|
||||||
src/util/rational.cpp
|
src/util/rational.cpp
|
||||||
src/util/stringutils.cpp
|
src/util/stringutils.cpp
|
||||||
src/util/tests.cpp
|
src/util/tests.cpp
|
||||||
@@ -55,7 +51,6 @@ add_library(olivecore
|
|||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(olivecore PRIVATE
|
target_include_directories(olivecore PRIVATE
|
||||||
${FFMPEG_INCLUDE_DIRS}
|
|
||||||
"${CMAKE_CURRENT_SOURCE_DIR}/include/olive/core"
|
"${CMAKE_CURRENT_SOURCE_DIR}/include/olive/core"
|
||||||
"${CMAKE_SOURCE_DIR}/third_party/openfx/include/"
|
"${CMAKE_SOURCE_DIR}/third_party/openfx/include/"
|
||||||
)
|
)
|
||||||
@@ -63,7 +58,6 @@ target_include_directories(olivecore PRIVATE
|
|||||||
target_link_libraries(olivecore PRIVATE
|
target_link_libraries(olivecore PRIVATE
|
||||||
OpenGL::GL
|
OpenGL::GL
|
||||||
Imath::Imath
|
Imath::Imath
|
||||||
FFMPEG::avutil
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Link OpenTimelineIO (optional)
|
# Link OpenTimelineIO (optional)
|
||||||
|
|||||||
@@ -23,13 +23,10 @@
|
|||||||
#define LIBOLIVECORE_AUDIOPARAMS_H
|
#define LIBOLIVECORE_AUDIOPARAMS_H
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
#include <libavutil/channel_layout.h>
|
|
||||||
}
|
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "channellayout.h"
|
||||||
#include "sampleformat.h"
|
#include "sampleformat.h"
|
||||||
#include "../util/rational.h"
|
#include "../util/rational.h"
|
||||||
|
|
||||||
@@ -38,21 +35,10 @@ namespace olive::core
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Audio parameters class managing audio stream configuration
|
* @brief Audio parameters class managing audio stream configuration
|
||||||
*
|
*
|
||||||
* CRITICAL NOTE: This class manages AVChannelLayout which contains dynamic memory
|
* Channel layouts are stored as plain 64-bit masks (see channellayout.h).
|
||||||
* (custom channel maps via u.map pointer). Prior to the Rule of Three implementation,
|
* Because the mask is a simple value type, AudioParams has value semantics
|
||||||
* shallow copies could occur when:
|
* and can be copied freely.
|
||||||
* - AudioParams stored in QVector (QVector reallocations)
|
|
||||||
* - AudioParams passed by value to RenderVideoParams
|
|
||||||
* - AudioParams copied during Node graph duplication in ProjectCopier
|
|
||||||
*
|
|
||||||
* When shallow copies occurred, one copy's set_channel_layout() could free the
|
|
||||||
* shared u.map pointer, corrupting other copies. This manifested as:
|
|
||||||
* - channel_layouts=0x0 errors in AudioProcessor::Open()
|
|
||||||
* - is_valid() returning false unexpectedly
|
|
||||||
*
|
|
||||||
* The Rule of Three (copy ctor, copy assignment, destructor) was added to ensure
|
|
||||||
* proper deep copies of AVChannelLayout using av_channel_layout_copy().
|
|
||||||
*/
|
*/
|
||||||
class AudioParams {
|
class AudioParams {
|
||||||
public:
|
public:
|
||||||
@@ -62,59 +48,28 @@ public:
|
|||||||
*/
|
*/
|
||||||
AudioParams()
|
AudioParams()
|
||||||
: sample_rate_(0)
|
: sample_rate_(0)
|
||||||
, channel_layout_{}
|
, channel_layout_mask_(0)
|
||||||
, channel_count_(0)
|
, channel_count_(0)
|
||||||
, format_(SampleFormat::INVALID)
|
, format_(SampleFormat::INVALID)
|
||||||
{
|
{
|
||||||
set_default_footage_parameters();
|
set_default_footage_parameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Constructor from AVChannelLayout (deep copy)
|
|
||||||
* @param sample_rate Audio sample rate (e.g., 48000)
|
|
||||||
* @param channel_layout FFmpeg channel layout (copied via av_channel_layout_copy)
|
|
||||||
* @param format Sample format (e.g., SampleFormat::F32P)
|
|
||||||
*
|
|
||||||
* NOTE: The channel_layout parameter is deep-copied. The original can be
|
|
||||||
* safely uninit'd after this constructor returns.
|
|
||||||
*/
|
|
||||||
AudioParams(const int &sample_rate, const AVChannelLayout &channel_layout,
|
|
||||||
const SampleFormat &format)
|
|
||||||
: sample_rate_(sample_rate)
|
|
||||||
, channel_layout_{}
|
|
||||||
, channel_count_(0)
|
|
||||||
, format_(format)
|
|
||||||
{
|
|
||||||
set_default_footage_parameters();
|
|
||||||
timebase_ = sample_rate_as_time_base();
|
|
||||||
av_channel_layout_uninit(&channel_layout_);
|
|
||||||
av_channel_layout_copy(&channel_layout_, &channel_layout);
|
|
||||||
|
|
||||||
// Cache channel count from the copied layout
|
|
||||||
calculate_channel_count();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Constructor from channel layout mask
|
* @brief Constructor from channel layout mask
|
||||||
* @param sample_rate Audio sample rate
|
* @param sample_rate Audio sample rate
|
||||||
* @param channel_layout Channel layout mask (e.g., AV_CH_LAYOUT_STEREO)
|
* @param channel_layout Channel layout mask (e.g., kChannelLayoutStereo)
|
||||||
* @param format Sample format
|
* @param format Sample format
|
||||||
*
|
|
||||||
* This is the most common constructor used in Olive. The mask is converted
|
|
||||||
* to AVChannelLayout via av_channel_layout_from_mask().
|
|
||||||
*/
|
*/
|
||||||
AudioParams(const int &sample_rate, uint64_t channel_layout,
|
AudioParams(const int &sample_rate, uint64_t channel_layout,
|
||||||
const SampleFormat &format)
|
const SampleFormat &format)
|
||||||
: sample_rate_(sample_rate)
|
: sample_rate_(sample_rate)
|
||||||
, channel_layout_{}
|
, channel_layout_mask_(channel_layout)
|
||||||
, channel_count_(0)
|
, channel_count_(0)
|
||||||
, format_(format)
|
, format_(format)
|
||||||
{
|
{
|
||||||
set_default_footage_parameters();
|
set_default_footage_parameters();
|
||||||
timebase_ = sample_rate_as_time_base();
|
timebase_ = sample_rate_as_time_base();
|
||||||
av_channel_layout_uninit(&channel_layout_);
|
|
||||||
av_channel_layout_from_mask(&channel_layout_, channel_layout);
|
|
||||||
// Cache channel count
|
|
||||||
calculate_channel_count();
|
calculate_channel_count();
|
||||||
}
|
}
|
||||||
int sample_rate() const
|
int sample_rate() const
|
||||||
@@ -127,37 +82,21 @@ public:
|
|||||||
sample_rate_ = sample_rate;
|
sample_rate_ = sample_rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AVChannelLayout &channel_layout() const
|
|
||||||
{
|
|
||||||
return channel_layout_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set channel layout from AVChannelLayout (deep copy)
|
* @brief Channel layout as a 64-bit mask (0 if unspecified)
|
||||||
* @param channel_layout Source channel layout to copy
|
|
||||||
*
|
|
||||||
* CRITICAL: This function first uninitializes the current layout (freeing any
|
|
||||||
* dynamic memory), then deep-copies the new layout. This is safe only if
|
|
||||||
* copies are properly managed via Rule of Three.
|
|
||||||
*
|
|
||||||
* If called on a shallow-copied AudioParams, this would corrupt other copies
|
|
||||||
* that share the same u.map pointer.
|
|
||||||
*/
|
*/
|
||||||
void set_channel_layout(const AVChannelLayout &channel_layout)
|
const uint64_t &channel_layout() const
|
||||||
{
|
{
|
||||||
av_channel_layout_uninit(&channel_layout_);
|
return channel_layout_mask_;
|
||||||
av_channel_layout_copy(&channel_layout_, &channel_layout);
|
|
||||||
calculate_channel_count();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Set channel layout from mask
|
* @brief Set channel layout from mask
|
||||||
* @param mask Channel layout mask (e.g., AV_CH_LAYOUT_STEREO)
|
* @param mask Channel layout mask (e.g., kChannelLayoutStereo)
|
||||||
*/
|
*/
|
||||||
void set_channel_layout(uint64_t mask)
|
void set_channel_layout(uint64_t mask)
|
||||||
{
|
{
|
||||||
av_channel_layout_uninit(&channel_layout_);
|
channel_layout_mask_ = mask;
|
||||||
av_channel_layout_from_mask(&channel_layout_, mask);
|
|
||||||
calculate_channel_count();
|
calculate_channel_count();
|
||||||
}
|
}
|
||||||
rational time_base() const
|
rational time_base() const
|
||||||
@@ -235,32 +174,6 @@ public:
|
|||||||
bool operator==(const AudioParams &other) const;
|
bool operator==(const AudioParams &other) const;
|
||||||
bool operator!=(const AudioParams &other) const;
|
bool operator!=(const AudioParams &other) const;
|
||||||
|
|
||||||
/**
|
|
||||||
* @name Rule of Three Implementation
|
|
||||||
*
|
|
||||||
* These are required because AVChannelLayout (FFmpeg >= 5.0) contains a union
|
|
||||||
* with a pointer member (u.map for custom channel maps). Without proper
|
|
||||||
* deep copy management:
|
|
||||||
*
|
|
||||||
* 1. Default copy constructor: Shallow copies u.map pointer, leading to
|
|
||||||
* double-free when original and copy are destroyed
|
|
||||||
* 2. Default copy assignment: Same issue as copy constructor
|
|
||||||
* 3. Default destructor: Doesn't free u.map, causing memory leaks
|
|
||||||
*
|
|
||||||
* The implementations use av_channel_layout_copy() and av_channel_layout_uninit()
|
|
||||||
* for proper FFmpeg-managed memory handling.
|
|
||||||
*
|
|
||||||
* Context where this matters:
|
|
||||||
* - QVector<AudioParams> in FootageDescription (vector reallocations)
|
|
||||||
* - RenderVideoParams passing AudioParams by value
|
|
||||||
* - ProjectCopier duplicating node graphs with audio parameters
|
|
||||||
*/
|
|
||||||
///@{
|
|
||||||
AudioParams(const AudioParams &other);
|
|
||||||
AudioParams &operator=(const AudioParams &other);
|
|
||||||
~AudioParams();
|
|
||||||
///@}
|
|
||||||
|
|
||||||
static const std::vector<uint64_t> kSupportedChannelLayouts;
|
static const std::vector<uint64_t> kSupportedChannelLayouts;
|
||||||
static const std::vector<int> kSupportedSampleRates;
|
static const std::vector<int> kSupportedSampleRates;
|
||||||
|
|
||||||
@@ -273,7 +186,7 @@ private:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Updates channel_count_ from the current channel_layout_
|
* @brief Updates channel_count_ from the current channel_layout_mask_
|
||||||
* Called after any channel layout modification.
|
* Called after any channel layout modification.
|
||||||
*/
|
*/
|
||||||
void calculate_channel_count();
|
void calculate_channel_count();
|
||||||
@@ -281,23 +194,12 @@ private:
|
|||||||
int sample_rate_; ///< Audio sample rate in Hz (e.g., 48000)
|
int sample_rate_; ///< Audio sample rate in Hz (e.g., 48000)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief FFmpeg channel layout structure
|
* @brief Channel layout mask (0 if unspecified)
|
||||||
*
|
*
|
||||||
* WARNING: This struct contains a union with a pointer member (u.map) when
|
* Plain value type mirroring FFmpeg's AV_CH_LAYOUT_* masks; no dynamic
|
||||||
* using custom channel layouts (order == AV_CHANNEL_ORDER_CUSTOM). The pointer
|
* memory is involved, so copies are trivially safe.
|
||||||
* must be properly managed via av_channel_layout_copy/uninit.
|
|
||||||
*
|
|
||||||
* Layout variants:
|
|
||||||
* - order == AV_CHANNEL_ORDER_UNSPEC: u.mask is undefined, nb_channels valid
|
|
||||||
* - order == AV_CHANNEL_ORDER_NATIVE: u.mask contains channel bitmask
|
|
||||||
* - order == AV_CHANNEL_ORDER_CUSTOM: u.map points to AVChannelCustom array
|
|
||||||
*
|
|
||||||
* Corruption symptoms:
|
|
||||||
* - u.mask == 0 when order should be NATIVE
|
|
||||||
* - av_channel_layout_check() returns false
|
|
||||||
* - is_valid() returns false
|
|
||||||
*/
|
*/
|
||||||
AVChannelLayout channel_layout_;
|
uint64_t channel_layout_mask_;
|
||||||
|
|
||||||
int channel_count_; ///< Cached channel count from layout
|
int channel_count_; ///< Cached channel count from layout
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#ifndef LIBOLIVECORE_CHANNELLAYOUT_H
|
||||||
|
#define LIBOLIVECORE_CHANNELLAYOUT_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Channel layout masks used throughout Olive
|
||||||
|
*
|
||||||
|
* Audio channel layouts are represented as plain 64-bit masks. The values
|
||||||
|
* deliberately mirror FFmpeg's AV_CH_LAYOUT_* constants so they can be passed
|
||||||
|
* straight through to the FFmpeg bridge library; the bridge unit tests
|
||||||
|
* static_assert each value against the real FFmpeg headers.
|
||||||
|
*/
|
||||||
|
inline constexpr uint64_t kChannelLayoutMono = 0x4; ///< AV_CH_LAYOUT_MONO
|
||||||
|
inline constexpr uint64_t kChannelLayoutStereo = 0x3; ///< AV_CH_LAYOUT_STEREO
|
||||||
|
inline constexpr uint64_t kChannelLayout2_1 = 0x103; ///< AV_CH_LAYOUT_2_1
|
||||||
|
inline constexpr uint64_t kChannelLayout5Point1 = 0x60F; ///< AV_CH_LAYOUT_5POINT1
|
||||||
|
inline constexpr uint64_t kChannelLayout7Point1 = 0x63F; ///< AV_CH_LAYOUT_7POINT1
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of channels in a layout mask (population count)
|
||||||
|
*/
|
||||||
|
inline int ChannelLayoutMaskChannelCount(uint64_t mask)
|
||||||
|
{
|
||||||
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
return __builtin_popcountll(mask);
|
||||||
|
#else
|
||||||
|
int count = 0;
|
||||||
|
while (mask) {
|
||||||
|
mask &= mask - 1;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LIBOLIVECORE_CHANNELLAYOUT_H
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#ifndef LIBOLIVECORE_FRACTIONUTILS_H
|
||||||
|
#define LIBOLIVECORE_FRACTIONUTILS_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Rounding modes for RescaleRnd()
|
||||||
|
*
|
||||||
|
* Mirrors the FFmpeg AVRounding modes that this codebase used before the
|
||||||
|
* FFmpeg dependency was removed from core.
|
||||||
|
*/
|
||||||
|
enum class FractionRounding {
|
||||||
|
/**
|
||||||
|
* Round to the nearest value; halfway cases are rounded away from zero.
|
||||||
|
* Equivalent to FFmpeg's AV_ROUND_NEAR_INF.
|
||||||
|
*/
|
||||||
|
kNearInf,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Round toward positive infinity. Equivalent to FFmpeg's AV_ROUND_UP.
|
||||||
|
*/
|
||||||
|
kUp
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reduce a fraction so that numerator and denominator fit within `max`
|
||||||
|
*
|
||||||
|
* Native re-implementation of FFmpeg's av_reduce(): divides out the greatest
|
||||||
|
* common divisor and, if the values still do not fit within `max`, finds the
|
||||||
|
* closest approximation using continued fractions.
|
||||||
|
*
|
||||||
|
* A zero denominator is preserved (with the numerator set to zero).
|
||||||
|
*/
|
||||||
|
void ReduceFraction(int64_t &num, int64_t &den, int64_t max);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Compare two fractions
|
||||||
|
*
|
||||||
|
* Native re-implementation of FFmpeg's av_cmp_q(): returns -1 if a < b,
|
||||||
|
* 0 if a == b, 1 if a > b, and INT_MIN when the comparison is meaningless
|
||||||
|
* (degenerate zero-denominator fractions).
|
||||||
|
*/
|
||||||
|
int CompareFractions(int an, int ad, int bn, int bd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Rescale `a` by the fraction b/c: returns a * b / c
|
||||||
|
*
|
||||||
|
* Native re-implementation of FFmpeg's av_rescale_rnd(). The intermediate
|
||||||
|
* product is computed with 128-bit arithmetic where available so that no
|
||||||
|
* precision is lost for large timestamps.
|
||||||
|
*/
|
||||||
|
int64_t RescaleRnd(int64_t a, int64_t b, int64_t c, FractionRounding rnd);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // LIBOLIVECORE_FRACTIONUTILS_H
|
||||||
@@ -22,10 +22,7 @@
|
|||||||
#ifndef LIBOLIVECORE_RATIONAL_H
|
#ifndef LIBOLIVECORE_RATIONAL_H
|
||||||
#define LIBOLIVECORE_RATIONAL_H
|
#define LIBOLIVECORE_RATIONAL_H
|
||||||
|
|
||||||
extern "C" {
|
#include <climits>
|
||||||
#include <libavutil/rational.h>
|
|
||||||
}
|
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#ifdef USE_OTIO
|
#ifdef USE_OTIO
|
||||||
@@ -39,14 +36,14 @@ class rational {
|
|||||||
public:
|
public:
|
||||||
rational(const int &numerator = 0)
|
rational(const int &numerator = 0)
|
||||||
{
|
{
|
||||||
r_.num = numerator;
|
num_ = numerator;
|
||||||
r_.den = 1;
|
den_ = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
rational(const int &numerator, const int &denominator)
|
rational(const int &numerator, const int &denominator)
|
||||||
{
|
{
|
||||||
r_.num = numerator;
|
num_ = numerator;
|
||||||
r_.den = denominator;
|
den_ = denominator;
|
||||||
|
|
||||||
fix_signs();
|
fix_signs();
|
||||||
reduce();
|
reduce();
|
||||||
@@ -54,13 +51,6 @@ public:
|
|||||||
|
|
||||||
rational(const rational &rhs) = default;
|
rational(const rational &rhs) = default;
|
||||||
|
|
||||||
rational(const AVRational &r)
|
|
||||||
{
|
|
||||||
r_ = r;
|
|
||||||
|
|
||||||
fix_signs();
|
|
||||||
}
|
|
||||||
|
|
||||||
static rational fromDouble(const double &flt, bool *ok = nullptr);
|
static rational fromDouble(const double &flt, bool *ok = nullptr);
|
||||||
static rational fromString(const std::string &str, bool *ok = nullptr);
|
static rational fromString(const std::string &str, bool *ok = nullptr);
|
||||||
|
|
||||||
@@ -94,18 +84,16 @@ public:
|
|||||||
}
|
}
|
||||||
rational operator-() const
|
rational operator-() const
|
||||||
{
|
{
|
||||||
return rational(r_.num, -r_.den);
|
return rational(num_, -den_);
|
||||||
}
|
}
|
||||||
bool operator!() const
|
bool operator!() const
|
||||||
{
|
{
|
||||||
return !r_.num;
|
return !num_;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Function: convert to double
|
//Function: convert to double
|
||||||
double toDouble() const;
|
double toDouble() const;
|
||||||
|
|
||||||
AVRational toAVRational() const;
|
|
||||||
|
|
||||||
#ifdef USE_OTIO
|
#ifdef USE_OTIO
|
||||||
static rational fromRationalTime(const opentime::RationalTime &t)
|
static rational fromRationalTime(const opentime::RationalTime &t)
|
||||||
{
|
{
|
||||||
@@ -126,29 +114,29 @@ public:
|
|||||||
// A NaN is always a null, but a null is not always a NaN
|
// A NaN is always a null, but a null is not always a NaN
|
||||||
bool isNull() const
|
bool isNull() const
|
||||||
{
|
{
|
||||||
return r_.num == 0;
|
return num_ == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns whether this rational is not a valid number (denominator == 0)
|
// Returns whether this rational is not a valid number (denominator == 0)
|
||||||
bool isNaN() const
|
bool isNaN() const
|
||||||
{
|
{
|
||||||
return r_.den == 0;
|
return den_ == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int &numerator() const
|
const int &numerator() const
|
||||||
{
|
{
|
||||||
return r_.num;
|
return num_;
|
||||||
}
|
}
|
||||||
const int &denominator() const
|
const int &denominator() const
|
||||||
{
|
{
|
||||||
return r_.den;
|
return den_;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string toString() const;
|
std::string toString() const;
|
||||||
|
|
||||||
friend std::ostream &operator<<(std::ostream &out, const rational &value)
|
friend std::ostream &operator<<(std::ostream &out, const rational &value)
|
||||||
{
|
{
|
||||||
out << value.r_.num << '/' << value.r_.den;
|
out << value.num_ << '/' << value.den_;
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@@ -157,7 +145,8 @@ private:
|
|||||||
void fix_signs();
|
void fix_signs();
|
||||||
void reduce();
|
void reduce();
|
||||||
|
|
||||||
AVRational r_;
|
int num_;
|
||||||
|
int den_;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define RATIONAL_MIN rational(INT_MIN)
|
#define RATIONAL_MIN rational(INT_MIN)
|
||||||
|
|||||||
@@ -40,16 +40,15 @@ const std::vector<int> AudioParams::kSupportedSampleRates = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const std::vector<uint64_t> AudioParams::kSupportedChannelLayouts = {
|
const std::vector<uint64_t> AudioParams::kSupportedChannelLayouts = {
|
||||||
AV_CH_LAYOUT_MONO, AV_CH_LAYOUT_STEREO, AV_CH_LAYOUT_2_1,
|
kChannelLayoutMono, kChannelLayoutStereo, kChannelLayout2_1,
|
||||||
AV_CH_LAYOUT_5POINT1, AV_CH_LAYOUT_7POINT1
|
kChannelLayout5Point1, kChannelLayout7Point1
|
||||||
};
|
};
|
||||||
|
|
||||||
bool AudioParams::operator==(const AudioParams &other) const
|
bool AudioParams::operator==(const AudioParams &other) const
|
||||||
{
|
{
|
||||||
return format() == other.format() && sample_rate() == other.sample_rate() &&
|
return format() == other.format() && sample_rate() == other.sample_rate() &&
|
||||||
time_base() == other.time_base() &&
|
time_base() == other.time_base() &&
|
||||||
av_channel_layout_compare(&channel_layout_,
|
channel_layout_mask_ == other.channel_layout_mask_;
|
||||||
&other.channel_layout()) == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AudioParams::operator!=(const AudioParams &other) const
|
bool AudioParams::operator!=(const AudioParams &other) const
|
||||||
@@ -119,15 +118,11 @@ int64_t AudioParams::bytes_to_samples(const int64_t &bytes) const
|
|||||||
|
|
||||||
rational AudioParams::bytes_to_time(const int64_t &bytes) const
|
rational AudioParams::bytes_to_time(const int64_t &bytes) const
|
||||||
{
|
{
|
||||||
assert(is_valid());
|
|
||||||
|
|
||||||
return samples_to_time(bytes_to_samples(bytes));
|
return samples_to_time(bytes_to_samples(bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
rational AudioParams::bytes_per_channel_to_time(const int64_t &bytes) const
|
rational AudioParams::bytes_per_channel_to_time(const int64_t &bytes) const
|
||||||
{
|
{
|
||||||
assert(is_valid());
|
|
||||||
|
|
||||||
return samples_to_time(bytes_to_samples(bytes * channel_count()));
|
return samples_to_time(bytes_to_samples(bytes * channel_count()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,90 +143,13 @@ int AudioParams::bits_per_sample() const
|
|||||||
|
|
||||||
bool AudioParams::is_valid() const
|
bool AudioParams::is_valid() const
|
||||||
{
|
{
|
||||||
return (!time_base().isNull() &&
|
return (!time_base().isNull() && channel_layout_mask_ != 0 &&
|
||||||
av_channel_layout_check(&channel_layout_) &&
|
|
||||||
format_ > SampleFormat::INVALID && format_ < SampleFormat::COUNT);
|
format_ > SampleFormat::INVALID && format_ < SampleFormat::COUNT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioParams::calculate_channel_count()
|
void AudioParams::calculate_channel_count()
|
||||||
{
|
{
|
||||||
channel_count_ = channel_layout().nb_channels;
|
channel_count_ = ChannelLayoutMaskChannelCount(channel_layout_mask_);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Copy constructor - deep copies AVChannelLayout
|
|
||||||
*
|
|
||||||
* This is critical because AVChannelLayout::u.map is a pointer for custom
|
|
||||||
* channel layouts. Default copy would share the pointer, leading to double-free.
|
|
||||||
*
|
|
||||||
* The member initializer list initializes channel_layout_ to zero ({}),
|
|
||||||
* then av_channel_layout_copy performs the deep copy from other.
|
|
||||||
*
|
|
||||||
* @param other Source AudioParams to copy from
|
|
||||||
*/
|
|
||||||
AudioParams::AudioParams(const AudioParams &other)
|
|
||||||
: sample_rate_(other.sample_rate_)
|
|
||||||
, channel_layout_{} // Zero-initialize before FFmpeg copy
|
|
||||||
, channel_count_(other.channel_count_)
|
|
||||||
, format_(other.format_)
|
|
||||||
, enabled_(other.enabled_)
|
|
||||||
, stream_index_(other.stream_index_)
|
|
||||||
, duration_(other.duration_)
|
|
||||||
, timebase_(other.timebase_)
|
|
||||||
{
|
|
||||||
// Deep copy AVChannelLayout using FFmpeg API
|
|
||||||
// This handles all layout types: unspecified, native (mask), and custom (map)
|
|
||||||
av_channel_layout_copy(&channel_layout_, &other.channel_layout_);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Copy assignment - cleans up existing layout before copying
|
|
||||||
*
|
|
||||||
* CRITICAL ORDER OF OPERATIONS:
|
|
||||||
* 1. Check for self-assignment (this != &other)
|
|
||||||
* 2. Copy all scalar members
|
|
||||||
* 3. Uninitialize current channel_layout_ (frees old u.map if present)
|
|
||||||
* 4. Deep copy from other's channel_layout_
|
|
||||||
*
|
|
||||||
* Step 3 must happen before step 4 to avoid memory leaks. If we copied first,
|
|
||||||
* we'd lose the pointer to the old u.map that needs to be freed.
|
|
||||||
*
|
|
||||||
* @param other Source AudioParams to copy from
|
|
||||||
* @return Reference to this for chaining
|
|
||||||
*/
|
|
||||||
AudioParams &AudioParams::operator=(const AudioParams &other)
|
|
||||||
{
|
|
||||||
if (this != &other) {
|
|
||||||
// Copy scalar members first (no dependencies)
|
|
||||||
sample_rate_ = other.sample_rate_;
|
|
||||||
format_ = other.format_;
|
|
||||||
channel_count_ = other.channel_count_;
|
|
||||||
enabled_ = other.enabled_;
|
|
||||||
stream_index_ = other.stream_index_;
|
|
||||||
duration_ = other.duration_;
|
|
||||||
timebase_ = other.timebase_;
|
|
||||||
|
|
||||||
// Free current layout's dynamic memory (u.map if custom)
|
|
||||||
av_channel_layout_uninit(&channel_layout_);
|
|
||||||
|
|
||||||
// Deep copy from other (includes allocating new u.map if needed)
|
|
||||||
av_channel_layout_copy(&channel_layout_, &other.channel_layout_);
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Destructor - frees AVChannelLayout dynamic memory
|
|
||||||
*
|
|
||||||
* av_channel_layout_uninit() handles all cases:
|
|
||||||
* - Unspecified/Native: No-op (no dynamic memory)
|
|
||||||
* - Custom: Frees u.map array
|
|
||||||
*
|
|
||||||
* Without this, custom channel layouts would leak memory.
|
|
||||||
*/
|
|
||||||
AudioParams::~AudioParams()
|
|
||||||
{
|
|
||||||
av_channel_layout_uninit(&channel_layout_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,10 +56,7 @@ SampleBuffer::SampleBuffer(const AudioParams &audio_params,
|
|||||||
SampleBuffer SampleBuffer::rip_channel(int channel) const
|
SampleBuffer SampleBuffer::rip_channel(int channel) const
|
||||||
{
|
{
|
||||||
AudioParams p = this->audio_params_;
|
AudioParams p = this->audio_params_;
|
||||||
AVChannelLayout layout;
|
p.set_channel_layout(kChannelLayoutMono);
|
||||||
av_channel_layout_from_mask(&layout, AV_CH_LAYOUT_MONO);
|
|
||||||
p.set_channel_layout(layout);
|
|
||||||
av_channel_layout_uninit(&layout);
|
|
||||||
|
|
||||||
SampleBuffer b(p, this->sample_count_per_channel_);
|
SampleBuffer b(p, this->sample_count_per_channel_);
|
||||||
b.fast_set(*this, 0, channel);
|
b.fast_set(*this, 0, channel);
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
/***
|
||||||
|
|
||||||
|
Olive - Non-Linear Video Editor
|
||||||
|
Copyright (C) 2023 Olive Studios LLC
|
||||||
|
Modifications Copyright (C) 2025 mikesolar
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
***/
|
||||||
|
|
||||||
|
#include "util/fractionutils.h"
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace olive::core
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
int64_t i64_gcd(int64_t a, int64_t b)
|
||||||
|
{
|
||||||
|
if (a < 0) {
|
||||||
|
a = -a;
|
||||||
|
}
|
||||||
|
if (b < 0) {
|
||||||
|
b = -b;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (b) {
|
||||||
|
int64_t t = a % b;
|
||||||
|
a = b;
|
||||||
|
b = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void ReduceFraction(int64_t &num, int64_t &den, int64_t max)
|
||||||
|
{
|
||||||
|
if (den == 0) {
|
||||||
|
num = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sign = (num < 0) != (den < 0);
|
||||||
|
|
||||||
|
int64_t gcd = i64_gcd(num, den);
|
||||||
|
if (gcd) {
|
||||||
|
num = (num < 0 ? -num : num) / gcd;
|
||||||
|
den = (den < 0 ? -den : den) / gcd;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num <= max && den <= max) {
|
||||||
|
num = sign ? -num : num;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continued fraction approximation (ported from FFmpeg's av_reduce)
|
||||||
|
int64_t a0n = 0, a0d = 1;
|
||||||
|
int64_t a1n = 1, a1d = 0;
|
||||||
|
|
||||||
|
while (den) {
|
||||||
|
int64_t x = num / den;
|
||||||
|
int64_t next_den = num - den * x;
|
||||||
|
int64_t a2n = x * a1n + a0n;
|
||||||
|
int64_t a2d = x * a1d + a0d;
|
||||||
|
|
||||||
|
if (a2n > max || a2d > max) {
|
||||||
|
if (a1n) {
|
||||||
|
x = (max - a0n) / a1n;
|
||||||
|
}
|
||||||
|
if (a1d && (max - a0d) / a1d < x) {
|
||||||
|
x = (max - a0d) / a1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (den * (2 * x * a1d + a0d) > num * a1d) {
|
||||||
|
a1n = x * a1n + a0n;
|
||||||
|
a1d = x * a1d + a0d;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
a0n = a1n;
|
||||||
|
a0d = a1d;
|
||||||
|
a1n = a2n;
|
||||||
|
a1d = a2d;
|
||||||
|
num = den;
|
||||||
|
den = next_den;
|
||||||
|
}
|
||||||
|
|
||||||
|
num = sign ? -a1n : a1n;
|
||||||
|
den = a1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
int CompareFractions(int an, int ad, int bn, int bd)
|
||||||
|
{
|
||||||
|
const int64_t tmp = an * int64_t(bd) - bn * int64_t(ad);
|
||||||
|
|
||||||
|
if (tmp) {
|
||||||
|
return int(((tmp ^ ad ^ bd) >> 63) | 1);
|
||||||
|
} else if (bd && ad) {
|
||||||
|
return 0;
|
||||||
|
} else if (an && bn) {
|
||||||
|
return (an >> 31) - (bn >> 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
return INT_MIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t RescaleRnd(int64_t a, int64_t b, int64_t c, FractionRounding rnd)
|
||||||
|
{
|
||||||
|
// Normalize so that the divisor is positive; the sign is carried by the
|
||||||
|
// dividend instead.
|
||||||
|
if (c < 0) {
|
||||||
|
c = -c;
|
||||||
|
b = -b;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(__SIZEOF_INT128__)
|
||||||
|
// 128-bit intermediate: exact for all 64-bit inputs
|
||||||
|
__int128 r = __int128(a) * __int128(b);
|
||||||
|
bool negative = r < 0;
|
||||||
|
unsigned __int128 ur = negative ? -r : r;
|
||||||
|
unsigned __int128 uc = static_cast<unsigned __int128>(c);
|
||||||
|
|
||||||
|
unsigned __int128 q;
|
||||||
|
if (rnd == FractionRounding::kNearInf) {
|
||||||
|
// Round to nearest, ties away from zero
|
||||||
|
q = (ur + uc / 2) / uc;
|
||||||
|
} else {
|
||||||
|
// Round toward positive infinity
|
||||||
|
if (!negative) {
|
||||||
|
q = (ur + uc - 1) / uc;
|
||||||
|
} else {
|
||||||
|
q = ur / uc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t res = int64_t(q);
|
||||||
|
return negative ? -res : res;
|
||||||
|
#else
|
||||||
|
// Portable fallback: cross-reduce to keep the intermediate product in
|
||||||
|
// 64-bit range, then divide with the requested rounding.
|
||||||
|
int64_t g = i64_gcd(b, c);
|
||||||
|
if (g) {
|
||||||
|
b /= g;
|
||||||
|
c /= g;
|
||||||
|
}
|
||||||
|
g = i64_gcd(a, c);
|
||||||
|
if (g) {
|
||||||
|
a /= g;
|
||||||
|
c /= g;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool negative = (a < 0) != (b < 0);
|
||||||
|
int64_t ua = a < 0 ? -a : a;
|
||||||
|
int64_t ub = b < 0 ? -b : b;
|
||||||
|
|
||||||
|
int64_t q;
|
||||||
|
if (ua != 0 && ub > std::numeric_limits<int64_t>::max() / ua) {
|
||||||
|
// Extremely unlikely: the product still overflows int64, fall back
|
||||||
|
// to floating point (may lose precision for huge values).
|
||||||
|
long double v = (long double)a * (long double)b / (long double)c;
|
||||||
|
if (rnd == FractionRounding::kNearInf) {
|
||||||
|
v = v >= 0 ? floorl(v + 0.5L) : ceill(v - 0.5L);
|
||||||
|
} else {
|
||||||
|
v = ceill(v);
|
||||||
|
}
|
||||||
|
return int64_t(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t u = ua * ub;
|
||||||
|
if (rnd == FractionRounding::kNearInf) {
|
||||||
|
q = (u + c / 2) / c;
|
||||||
|
} else {
|
||||||
|
if (!negative) {
|
||||||
|
q = (u + c - 1) / c;
|
||||||
|
} else {
|
||||||
|
q = u / c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return negative ? -q : q;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+81
-37
@@ -22,7 +22,13 @@
|
|||||||
#include "util/rational.h"
|
#include "util/rational.h"
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <climits>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include "util/fractionutils.h"
|
||||||
#include "util/stringutils.h"
|
#include "util/stringutils.h"
|
||||||
|
|
||||||
namespace olive::core
|
namespace olive::core
|
||||||
@@ -39,22 +45,45 @@ rational rational::fromDouble(const double &flt, bool *ok)
|
|||||||
return NaN;
|
return NaN;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use FFmpeg function for the time being
|
if (fabs(flt) > double(INT_MAX) + 3.0) {
|
||||||
AVRational r = av_d2q(flt, INT_MAX);
|
// Value is out of range for a rational, return NaN
|
||||||
|
if (ok) {
|
||||||
|
*ok = false;
|
||||||
|
}
|
||||||
|
return NaN;
|
||||||
|
}
|
||||||
|
|
||||||
if (r.den == 0) {
|
// Continued fraction conversion (ported from FFmpeg's av_d2q)
|
||||||
|
int exponent;
|
||||||
|
frexp(flt, &exponent);
|
||||||
|
exponent = std::max(exponent - 1, 0);
|
||||||
|
int64_t den = 1LL << (62 - exponent);
|
||||||
|
int64_t num = int64_t(floor(flt * den + 0.5));
|
||||||
|
|
||||||
|
int64_t rnum = num, rden = den;
|
||||||
|
ReduceFraction(rnum, rden, INT_MAX);
|
||||||
|
|
||||||
|
if ((!rnum || !rden) && flt) {
|
||||||
|
// Value was too small to represent above, retry with maximum precision
|
||||||
|
rnum = int64_t(flt * double(INT64_MAX));
|
||||||
|
rden = INT64_MAX;
|
||||||
|
ReduceFraction(rnum, rden, INT_MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rden == 0) {
|
||||||
// If den == 0, we were unable to convert to a rational
|
// If den == 0, we were unable to convert to a rational
|
||||||
if (ok) {
|
if (ok) {
|
||||||
*ok = false;
|
*ok = false;
|
||||||
}
|
}
|
||||||
} else {
|
return NaN;
|
||||||
// Otherwise, assume we received a real rational
|
|
||||||
if (ok) {
|
|
||||||
*ok = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return r;
|
// Otherwise, assume we received a real rational
|
||||||
|
if (ok) {
|
||||||
|
*ok = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return rational(int(rnum), int(rden));
|
||||||
}
|
}
|
||||||
|
|
||||||
rational rational::fromString(const std::string &str, bool *ok)
|
rational rational::fromString(const std::string &str, bool *ok)
|
||||||
@@ -80,25 +109,20 @@ rational rational::fromString(const std::string &str, bool *ok)
|
|||||||
|
|
||||||
double rational::toDouble() const
|
double rational::toDouble() const
|
||||||
{
|
{
|
||||||
if (r_.den != 0) {
|
if (den_ != 0) {
|
||||||
return av_q2d(r_);
|
return double(num_) / double(den_);
|
||||||
} else {
|
} else {
|
||||||
return std::numeric_limits<double>::quiet_NaN();
|
return std::numeric_limits<double>::quiet_NaN();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AVRational rational::toAVRational() const
|
|
||||||
{
|
|
||||||
return r_;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef USE_OTIO
|
#ifdef USE_OTIO
|
||||||
opentime::RationalTime rational::toRationalTime(double framerate) const
|
opentime::RationalTime rational::toRationalTime(double framerate) const
|
||||||
{
|
{
|
||||||
// Is this the best way of doing this?
|
// Is this the best way of doing this?
|
||||||
// Olive can store rationals as 0/0 which causes errors in OTIO
|
// Olive can store rationals as 0/0 which causes errors in OTIO
|
||||||
opentime::RationalTime time =
|
opentime::RationalTime time =
|
||||||
opentime::RationalTime(r_.num, r_.den == 0 ? 1 : r_.den);
|
opentime::RationalTime(num_, den_ == 0 ? 1 : den_);
|
||||||
return time.rescaled_to(framerate);
|
return time.rescaled_to(framerate);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -113,41 +137,45 @@ rational rational::flipped() const
|
|||||||
void rational::flip()
|
void rational::flip()
|
||||||
{
|
{
|
||||||
if (!isNull()) {
|
if (!isNull()) {
|
||||||
std::swap(r_.den, r_.num);
|
std::swap(den_, num_);
|
||||||
fix_signs();
|
fix_signs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string rational::toString() const
|
std::string rational::toString() const
|
||||||
{
|
{
|
||||||
return StringUtils::format("%d/%d", r_.num, r_.den);
|
return StringUtils::format("%d/%d", num_, den_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void rational::fix_signs()
|
void rational::fix_signs()
|
||||||
{
|
{
|
||||||
if (r_.den < 0) {
|
if (den_ < 0) {
|
||||||
// Normalize so that denominator is always positive
|
// Normalize so that denominator is always positive
|
||||||
r_.den = -r_.den;
|
den_ = -den_;
|
||||||
r_.num = -r_.num;
|
num_ = -num_;
|
||||||
} else if (r_.den == 0) {
|
} else if (den_ == 0) {
|
||||||
// Normalize to 0/0 (aka NaN) if denominator is zero
|
// Normalize to 0/0 (aka NaN) if denominator is zero
|
||||||
r_.num = 0;
|
num_ = 0;
|
||||||
} else if (r_.num == 0) {
|
} else if (num_ == 0) {
|
||||||
// Normalize to 0/1 if numerator is zero
|
// Normalize to 0/1 if numerator is zero
|
||||||
r_.den = 1;
|
den_ = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void rational::reduce()
|
void rational::reduce()
|
||||||
{
|
{
|
||||||
av_reduce(&r_.num, &r_.den, r_.num, r_.den, INT_MAX);
|
int64_t n = num_, d = den_;
|
||||||
|
ReduceFraction(n, d, INT_MAX);
|
||||||
|
num_ = int(n);
|
||||||
|
den_ = int(d);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Assignment Operators
|
//Assignment Operators
|
||||||
|
|
||||||
const rational &rational::operator=(const rational &rhs)
|
const rational &rational::operator=(const rational &rhs)
|
||||||
{
|
{
|
||||||
r_ = rhs.r_;
|
num_ = rhs.num_;
|
||||||
|
den_ = rhs.den_;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,7 +188,11 @@ const rational &rational::operator+=(const rational &rhs)
|
|||||||
if (rhs.isNaN()) {
|
if (rhs.isNaN()) {
|
||||||
*this = NaN;
|
*this = NaN;
|
||||||
} else {
|
} else {
|
||||||
r_ = av_add_q(r_, rhs.r_);
|
int64_t n = num_ * int64_t(rhs.den_) + rhs.num_ * int64_t(den_);
|
||||||
|
int64_t d = den_ * int64_t(rhs.den_);
|
||||||
|
ReduceFraction(n, d, INT_MAX);
|
||||||
|
num_ = int(n);
|
||||||
|
den_ = int(d);
|
||||||
fix_signs();
|
fix_signs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,7 +209,11 @@ const rational &rational::operator-=(const rational &rhs)
|
|||||||
if (rhs.isNaN()) {
|
if (rhs.isNaN()) {
|
||||||
*this = NaN;
|
*this = NaN;
|
||||||
} else {
|
} else {
|
||||||
r_ = av_sub_q(r_, rhs.r_);
|
int64_t n = num_ * int64_t(rhs.den_) - rhs.num_ * int64_t(den_);
|
||||||
|
int64_t d = den_ * int64_t(rhs.den_);
|
||||||
|
ReduceFraction(n, d, INT_MAX);
|
||||||
|
num_ = int(n);
|
||||||
|
den_ = int(d);
|
||||||
fix_signs();
|
fix_signs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,7 +230,11 @@ const rational &rational::operator*=(const rational &rhs)
|
|||||||
if (rhs.isNaN()) {
|
if (rhs.isNaN()) {
|
||||||
*this = NaN;
|
*this = NaN;
|
||||||
} else {
|
} else {
|
||||||
r_ = av_mul_q(r_, rhs.r_);
|
int64_t n = num_ * int64_t(rhs.num_);
|
||||||
|
int64_t d = den_ * int64_t(rhs.den_);
|
||||||
|
ReduceFraction(n, d, INT_MAX);
|
||||||
|
num_ = int(n);
|
||||||
|
den_ = int(d);
|
||||||
fix_signs();
|
fix_signs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,7 +251,11 @@ const rational &rational::operator/=(const rational &rhs)
|
|||||||
if (rhs.isNaN()) {
|
if (rhs.isNaN()) {
|
||||||
*this = NaN;
|
*this = NaN;
|
||||||
} else {
|
} else {
|
||||||
r_ = av_div_q(r_, rhs.r_);
|
int64_t n = num_ * int64_t(rhs.den_);
|
||||||
|
int64_t d = den_ * int64_t(rhs.num_);
|
||||||
|
ReduceFraction(n, d, INT_MAX);
|
||||||
|
num_ = int(n);
|
||||||
|
den_ = int(d);
|
||||||
fix_signs();
|
fix_signs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,29 +297,29 @@ rational rational::operator*(const rational &rhs) const
|
|||||||
|
|
||||||
bool rational::operator<(const rational &rhs) const
|
bool rational::operator<(const rational &rhs) const
|
||||||
{
|
{
|
||||||
return av_cmp_q(r_, rhs.r_) == -1;
|
return CompareFractions(num_, den_, rhs.num_, rhs.den_) == -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool rational::operator<=(const rational &rhs) const
|
bool rational::operator<=(const rational &rhs) const
|
||||||
{
|
{
|
||||||
int cmp = av_cmp_q(r_, rhs.r_);
|
int cmp = CompareFractions(num_, den_, rhs.num_, rhs.den_);
|
||||||
return cmp == 0 || cmp == -1;
|
return cmp == 0 || cmp == -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool rational::operator>(const rational &rhs) const
|
bool rational::operator>(const rational &rhs) const
|
||||||
{
|
{
|
||||||
return av_cmp_q(r_, rhs.r_) == 1;
|
return CompareFractions(num_, den_, rhs.num_, rhs.den_) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool rational::operator>=(const rational &rhs) const
|
bool rational::operator>=(const rational &rhs) const
|
||||||
{
|
{
|
||||||
int cmp = av_cmp_q(r_, rhs.r_);
|
int cmp = CompareFractions(num_, den_, rhs.num_, rhs.den_);
|
||||||
return cmp == 0 || cmp == 1;
|
return cmp == 0 || cmp == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool rational::operator==(const rational &rhs) const
|
bool rational::operator==(const rational &rhs) const
|
||||||
{
|
{
|
||||||
return av_cmp_q(r_, rhs.r_) == 0;
|
return CompareFractions(num_, den_, rhs.num_, rhs.den_) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool rational::operator!=(const rational &rhs) const
|
bool rational::operator!=(const rational &rhs) const
|
||||||
|
|||||||
@@ -21,10 +21,10 @@
|
|||||||
|
|
||||||
#include "util/timecodefunctions.h"
|
#include "util/timecodefunctions.h"
|
||||||
|
|
||||||
extern "C" {
|
#include <climits>
|
||||||
#include <libavutil/mathematics.h>
|
#include <cmath>
|
||||||
}
|
|
||||||
|
|
||||||
|
#include "util/fractionutils.h"
|
||||||
#include "util/stringutils.h"
|
#include "util/stringutils.h"
|
||||||
|
|
||||||
namespace olive::core
|
namespace olive::core
|
||||||
@@ -334,11 +334,9 @@ rational Timecode::timestamp_to_time(const int64_t ×tamp,
|
|||||||
int64_t num = int64_t(timebase.numerator()) * timestamp;
|
int64_t num = int64_t(timebase.numerator()) * timestamp;
|
||||||
int64_t den = timebase.denominator();
|
int64_t den = timebase.denominator();
|
||||||
|
|
||||||
int num_r, den_r;
|
ReduceFraction(num, den, INT_MAX);
|
||||||
|
|
||||||
av_reduce(&num_r, &den_r, num, den, INT_MAX);
|
return rational(int(num), int(den));
|
||||||
|
|
||||||
return rational(num_r, den_r);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Timecode::timebase_is_drop_frame(const rational &timebase)
|
bool Timecode::timebase_is_drop_frame(const rational &timebase)
|
||||||
@@ -389,7 +387,9 @@ int64_t Timecode::rescale_timestamp(const int64_t &ts, const rational &source,
|
|||||||
return ts;
|
return ts;
|
||||||
}
|
}
|
||||||
|
|
||||||
return av_rescale_q(ts, source.toAVRational(), dest.toAVRational());
|
return RescaleRnd(ts, source.numerator() * int64_t(dest.denominator()),
|
||||||
|
source.denominator() * int64_t(dest.numerator()),
|
||||||
|
FractionRounding::kNearInf);
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t Timecode::rescale_timestamp_ceil(const int64_t &ts,
|
int64_t Timecode::rescale_timestamp_ceil(const int64_t &ts,
|
||||||
@@ -400,8 +400,9 @@ int64_t Timecode::rescale_timestamp_ceil(const int64_t &ts,
|
|||||||
return ts;
|
return ts;
|
||||||
}
|
}
|
||||||
|
|
||||||
return av_rescale_q_rnd(ts, source.toAVRational(), dest.toAVRational(),
|
return RescaleRnd(ts, source.numerator() * int64_t(dest.denominator()),
|
||||||
AV_ROUND_UP);
|
source.denominator() * int64_t(dest.numerator()),
|
||||||
|
FractionRounding::kUp);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user