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:
2026-07-15 21:55:11 +08:00
parent 3be4294e15
commit 0f41620a0b
10 changed files with 480 additions and 287 deletions
+5 -87
View File
@@ -40,16 +40,15 @@ const std::vector<int> AudioParams::kSupportedSampleRates = {
};
const std::vector<uint64_t> AudioParams::kSupportedChannelLayouts = {
AV_CH_LAYOUT_MONO, AV_CH_LAYOUT_STEREO, AV_CH_LAYOUT_2_1,
AV_CH_LAYOUT_5POINT1, AV_CH_LAYOUT_7POINT1
kChannelLayoutMono, kChannelLayoutStereo, kChannelLayout2_1,
kChannelLayout5Point1, kChannelLayout7Point1
};
bool AudioParams::operator==(const AudioParams &other) const
{
return format() == other.format() && sample_rate() == other.sample_rate() &&
time_base() == other.time_base() &&
av_channel_layout_compare(&channel_layout_,
&other.channel_layout()) == 0;
channel_layout_mask_ == other.channel_layout_mask_;
}
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
{
assert(is_valid());
return samples_to_time(bytes_to_samples(bytes));
}
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()));
}
@@ -148,90 +143,13 @@ int AudioParams::bits_per_sample() const
bool AudioParams::is_valid() const
{
return (!time_base().isNull() &&
av_channel_layout_check(&channel_layout_) &&
return (!time_base().isNull() && channel_layout_mask_ != 0 &&
format_ > SampleFormat::INVALID && format_ < SampleFormat::COUNT);
}
void AudioParams::calculate_channel_count()
{
channel_count_ = channel_layout().nb_channels;
}
/**
* @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_);
channel_count_ = ChannelLayoutMaskChannelCount(channel_layout_mask_);
}
}