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:
@@ -23,13 +23,10 @@
|
||||
#define LIBOLIVECORE_AUDIOPARAMS_H
|
||||
#include <cstring>
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/channel_layout.h>
|
||||
}
|
||||
|
||||
#include <assert.h>
|
||||
#include <vector>
|
||||
|
||||
#include "channellayout.h"
|
||||
#include "sampleformat.h"
|
||||
#include "../util/rational.h"
|
||||
|
||||
@@ -38,21 +35,10 @@ namespace olive::core
|
||||
|
||||
/**
|
||||
* @brief Audio parameters class managing audio stream configuration
|
||||
*
|
||||
* CRITICAL NOTE: This class manages AVChannelLayout which contains dynamic memory
|
||||
* (custom channel maps via u.map pointer). Prior to the Rule of Three implementation,
|
||||
* shallow copies could occur when:
|
||||
* - 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().
|
||||
*
|
||||
* Channel layouts are stored as plain 64-bit masks (see channellayout.h).
|
||||
* Because the mask is a simple value type, AudioParams has value semantics
|
||||
* and can be copied freely.
|
||||
*/
|
||||
class AudioParams {
|
||||
public:
|
||||
@@ -62,59 +48,28 @@ public:
|
||||
*/
|
||||
AudioParams()
|
||||
: sample_rate_(0)
|
||||
, channel_layout_{}
|
||||
, channel_layout_mask_(0)
|
||||
, channel_count_(0)
|
||||
, format_(SampleFormat::INVALID)
|
||||
{
|
||||
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
|
||||
* @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
|
||||
*
|
||||
* 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,
|
||||
const SampleFormat &format)
|
||||
: sample_rate_(sample_rate)
|
||||
, channel_layout_{}
|
||||
, channel_layout_mask_(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_from_mask(&channel_layout_, channel_layout);
|
||||
// Cache channel count
|
||||
calculate_channel_count();
|
||||
}
|
||||
int sample_rate() const
|
||||
@@ -127,37 +82,21 @@ public:
|
||||
sample_rate_ = sample_rate;
|
||||
}
|
||||
|
||||
const AVChannelLayout &channel_layout() const
|
||||
{
|
||||
return channel_layout_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set channel layout from AVChannelLayout (deep copy)
|
||||
* @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.
|
||||
* @brief Channel layout as a 64-bit mask (0 if unspecified)
|
||||
*/
|
||||
void set_channel_layout(const AVChannelLayout &channel_layout)
|
||||
const uint64_t &channel_layout() const
|
||||
{
|
||||
av_channel_layout_uninit(&channel_layout_);
|
||||
av_channel_layout_copy(&channel_layout_, &channel_layout);
|
||||
calculate_channel_count();
|
||||
return channel_layout_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)
|
||||
{
|
||||
av_channel_layout_uninit(&channel_layout_);
|
||||
av_channel_layout_from_mask(&channel_layout_, mask);
|
||||
channel_layout_mask_ = mask;
|
||||
calculate_channel_count();
|
||||
}
|
||||
rational time_base() const
|
||||
@@ -235,32 +174,6 @@ public:
|
||||
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<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.
|
||||
*/
|
||||
void calculate_channel_count();
|
||||
@@ -281,23 +194,12 @@ private:
|
||||
int sample_rate_; ///< Audio sample rate in Hz (e.g., 48000)
|
||||
|
||||
/**
|
||||
* @brief FFmpeg channel layout structure
|
||||
*
|
||||
* WARNING: This struct contains a union with a pointer member (u.map) when
|
||||
* using custom channel layouts (order == AV_CHANNEL_ORDER_CUSTOM). The pointer
|
||||
* 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
|
||||
* @brief Channel layout mask (0 if unspecified)
|
||||
*
|
||||
* Plain value type mirroring FFmpeg's AV_CH_LAYOUT_* masks; no dynamic
|
||||
* memory is involved, so copies are trivially safe.
|
||||
*/
|
||||
AVChannelLayout channel_layout_;
|
||||
uint64_t channel_layout_mask_;
|
||||
|
||||
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
|
||||
#define LIBOLIVECORE_RATIONAL_H
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/rational.h>
|
||||
}
|
||||
|
||||
#include <climits>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef USE_OTIO
|
||||
@@ -39,14 +36,14 @@ class rational {
|
||||
public:
|
||||
rational(const int &numerator = 0)
|
||||
{
|
||||
r_.num = numerator;
|
||||
r_.den = 1;
|
||||
num_ = numerator;
|
||||
den_ = 1;
|
||||
}
|
||||
|
||||
rational(const int &numerator, const int &denominator)
|
||||
{
|
||||
r_.num = numerator;
|
||||
r_.den = denominator;
|
||||
num_ = numerator;
|
||||
den_ = denominator;
|
||||
|
||||
fix_signs();
|
||||
reduce();
|
||||
@@ -54,13 +51,6 @@ public:
|
||||
|
||||
rational(const rational &rhs) = default;
|
||||
|
||||
rational(const AVRational &r)
|
||||
{
|
||||
r_ = r;
|
||||
|
||||
fix_signs();
|
||||
}
|
||||
|
||||
static rational fromDouble(const double &flt, bool *ok = nullptr);
|
||||
static rational fromString(const std::string &str, bool *ok = nullptr);
|
||||
|
||||
@@ -94,18 +84,16 @@ public:
|
||||
}
|
||||
rational operator-() const
|
||||
{
|
||||
return rational(r_.num, -r_.den);
|
||||
return rational(num_, -den_);
|
||||
}
|
||||
bool operator!() const
|
||||
{
|
||||
return !r_.num;
|
||||
return !num_;
|
||||
}
|
||||
|
||||
//Function: convert to double
|
||||
double toDouble() const;
|
||||
|
||||
AVRational toAVRational() const;
|
||||
|
||||
#ifdef USE_OTIO
|
||||
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
|
||||
bool isNull() const
|
||||
{
|
||||
return r_.num == 0;
|
||||
return num_ == 0;
|
||||
}
|
||||
|
||||
// Returns whether this rational is not a valid number (denominator == 0)
|
||||
bool isNaN() const
|
||||
{
|
||||
return r_.den == 0;
|
||||
return den_ == 0;
|
||||
}
|
||||
|
||||
const int &numerator() const
|
||||
{
|
||||
return r_.num;
|
||||
return num_;
|
||||
}
|
||||
const int &denominator() const
|
||||
{
|
||||
return r_.den;
|
||||
return den_;
|
||||
}
|
||||
|
||||
std::string toString() const;
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &out, const rational &value)
|
||||
{
|
||||
out << value.r_.num << '/' << value.r_.den;
|
||||
out << value.num_ << '/' << value.den_;
|
||||
|
||||
return out;
|
||||
}
|
||||
@@ -157,7 +145,8 @@ private:
|
||||
void fix_signs();
|
||||
void reduce();
|
||||
|
||||
AVRational r_;
|
||||
int num_;
|
||||
int den_;
|
||||
};
|
||||
|
||||
#define RATIONAL_MIN rational(INT_MIN)
|
||||
|
||||
Reference in New Issue
Block a user