tests: migrate gtest suite to the ffmpeg_bridge API and add bridge coverage
- Replace direct FFmpeg usage in the test suite (channel layout masks, pixel/sample format constants, AVFrame field access, sws_scale) with the bridge equivalents and the olive::AVFrame adapter - Drop CoreRational.ToAVRational (API removed with core's FFmpeg dependency) and the GetSwsColorspaceFromAVColorSpace tests (helper moved inside the bridge) - New ffmpeg_bridge_test.cpp exercises the C API directly: constants vs core, error strings, pixel format utilities, frames/packets, scaler, resampler, audio graph tempo processing, probe/decoder round-trip on tests/demo.mp4 (including hw-frame transfer), SRT subtitle reading, and encoder end-to-end tests (PNG video and PCM audio probed back)
This commit is contained in:
@@ -11,6 +11,7 @@ add_executable(olive-gtest
|
||||
common_digit_test.cpp
|
||||
common_jobtime_test.cpp
|
||||
common_ffmpegutils_test.cpp
|
||||
ffmpeg_bridge_test.cpp
|
||||
config_test.cpp
|
||||
audio_level_meter_test.cpp
|
||||
audio_synchronizer_test.cpp
|
||||
|
||||
@@ -7,16 +7,12 @@
|
||||
#include "olive/core/render/samplebuffer.h"
|
||||
#include "olive/core/render/sampleformat.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/channel_layout.h>
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
olive::core::AudioParams MakeStereoParams()
|
||||
{
|
||||
return olive::core::AudioParams(48000, AV_CH_LAYOUT_STEREO,
|
||||
return olive::core::AudioParams(48000, olive::core::kChannelLayoutStereo,
|
||||
olive::core::SampleFormat::F32P);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,6 @@
|
||||
#include "olive/core/render/audioparams.h"
|
||||
#include "olive/core/render/sampleformat.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/channel_layout.h>
|
||||
}
|
||||
|
||||
using namespace olive;
|
||||
using namespace olive::core;
|
||||
|
||||
@@ -75,7 +71,7 @@ TEST(AudioSmokeParams, DefaultConstruction)
|
||||
|
||||
TEST(AudioSmokeParams, ValidConstruction)
|
||||
{
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
|
||||
EXPECT_TRUE(params.is_valid());
|
||||
EXPECT_EQ(params.sample_rate(), 48000);
|
||||
@@ -87,7 +83,7 @@ TEST(AudioSmokeParams, ValidConstruction)
|
||||
|
||||
TEST(AudioSmokeParams, MonoChannelLayout)
|
||||
{
|
||||
AudioParams params(44100, AV_CH_LAYOUT_MONO, SampleFormat::S16);
|
||||
AudioParams params(44100, kChannelLayoutMono, SampleFormat::S16);
|
||||
|
||||
EXPECT_TRUE(params.is_valid());
|
||||
EXPECT_EQ(params.sample_rate(), 44100);
|
||||
@@ -96,7 +92,7 @@ TEST(AudioSmokeParams, MonoChannelLayout)
|
||||
|
||||
TEST(AudioSmokeParams, SurroundChannelLayout)
|
||||
{
|
||||
AudioParams params(48000, AV_CH_LAYOUT_5POINT1, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayout5Point1, SampleFormat::F32P);
|
||||
|
||||
EXPECT_TRUE(params.is_valid());
|
||||
EXPECT_EQ(params.channel_count(), 6);
|
||||
@@ -104,7 +100,7 @@ TEST(AudioSmokeParams, SurroundChannelLayout)
|
||||
|
||||
TEST(AudioSmokeParams, TimeConversions)
|
||||
{
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
|
||||
// Time to samples
|
||||
EXPECT_EQ(params.time_to_samples(1.0), 48000);
|
||||
@@ -121,11 +117,11 @@ TEST(AudioSmokeParams, TimeConversions)
|
||||
|
||||
TEST(AudioSmokeParams, EqualityOperators)
|
||||
{
|
||||
AudioParams params1(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params2(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params3(44100, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params4(48000, AV_CH_LAYOUT_MONO, SampleFormat::F32P);
|
||||
AudioParams params5(48000, AV_CH_LAYOUT_STEREO, SampleFormat::S16);
|
||||
AudioParams params1(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
AudioParams params2(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
AudioParams params3(44100, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
AudioParams params4(48000, kChannelLayoutMono, SampleFormat::F32P);
|
||||
AudioParams params5(48000, kChannelLayoutStereo, SampleFormat::S16);
|
||||
|
||||
EXPECT_TRUE(params1 == params2);
|
||||
EXPECT_FALSE(params1 != params2);
|
||||
@@ -137,7 +133,7 @@ TEST(AudioSmokeParams, EqualityOperators)
|
||||
|
||||
TEST(AudioSmokeParams, CopyConstruction)
|
||||
{
|
||||
AudioParams original(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams original(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
AudioParams copy(original);
|
||||
|
||||
EXPECT_TRUE(copy.is_valid());
|
||||
@@ -153,7 +149,7 @@ TEST(AudioSmokeParams, CopyConstruction)
|
||||
|
||||
TEST(AudioSmokeParams, CopyAssignment)
|
||||
{
|
||||
AudioParams original(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams original(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
AudioParams copy;
|
||||
copy = original;
|
||||
|
||||
@@ -165,15 +161,15 @@ TEST(AudioSmokeParams, CopyAssignment)
|
||||
|
||||
TEST(AudioSmokeParams, ChannelLayoutModification)
|
||||
{
|
||||
AudioParams params(48000, AV_CH_LAYOUT_MONO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutMono, SampleFormat::F32P);
|
||||
EXPECT_EQ(params.channel_count(), 1);
|
||||
|
||||
// Change to stereo
|
||||
params.set_channel_layout(AV_CH_LAYOUT_STEREO);
|
||||
params.set_channel_layout(kChannelLayoutStereo);
|
||||
EXPECT_EQ(params.channel_count(), 2);
|
||||
|
||||
// Change to 5.1
|
||||
params.set_channel_layout(AV_CH_LAYOUT_5POINT1);
|
||||
params.set_channel_layout(kChannelLayout5Point1);
|
||||
EXPECT_EQ(params.channel_count(), 6);
|
||||
}
|
||||
|
||||
@@ -191,7 +187,7 @@ TEST(AudioSmokeBuffer, DefaultConstruction)
|
||||
|
||||
TEST(AudioSmokeBuffer, Allocation)
|
||||
{
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
SampleBuffer buffer(params, size_t(48000)); // 1 second of samples
|
||||
|
||||
EXPECT_TRUE(buffer.is_allocated());
|
||||
@@ -201,7 +197,7 @@ TEST(AudioSmokeBuffer, Allocation)
|
||||
|
||||
TEST(AudioSmokeBuffer, DataAccess)
|
||||
{
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
SampleBuffer buffer(params, size_t(100));
|
||||
|
||||
// Fill with test data
|
||||
@@ -218,7 +214,7 @@ TEST(AudioSmokeBuffer, DataAccess)
|
||||
|
||||
TEST(AudioSmokeBuffer, Silence)
|
||||
{
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
SampleBuffer buffer(params, size_t(100));
|
||||
|
||||
// Fill with non-zero values
|
||||
@@ -238,7 +234,7 @@ TEST(AudioSmokeBuffer, Silence)
|
||||
|
||||
TEST(AudioSmokeBuffer, VolumeTransform)
|
||||
{
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
SampleBuffer buffer(params, size_t(100));
|
||||
|
||||
// Fill with 1.0
|
||||
@@ -258,7 +254,7 @@ TEST(AudioSmokeBuffer, VolumeTransform)
|
||||
|
||||
TEST(AudioSmokeBuffer, Clamp)
|
||||
{
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
SampleBuffer buffer(params, size_t(100));
|
||||
|
||||
// Fill with values outside [-1, 1]
|
||||
@@ -284,7 +280,7 @@ TEST(AudioSmokeBuffer, Clamp)
|
||||
|
||||
TEST(AudioSmokeBuffer, FastSet)
|
||||
{
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
SampleBuffer source(params, size_t(100));
|
||||
SampleBuffer dest(params, size_t(100));
|
||||
|
||||
@@ -303,7 +299,7 @@ TEST(AudioSmokeBuffer, FastSet)
|
||||
|
||||
TEST(AudioSmokeBuffer, RipChannel)
|
||||
{
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
SampleBuffer buffer(params, size_t(100));
|
||||
|
||||
// Fill channel 0 with 0.5, channel 1 with 0.25
|
||||
@@ -353,7 +349,7 @@ TEST(AudioSmokeWaveform, OverwriteSamples)
|
||||
waveform.set_channel_count(2);
|
||||
|
||||
// Create sample buffer with sine wave-like data
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
SampleBuffer buffer(params, size_t(4800)); // 0.1 seconds
|
||||
|
||||
for (int ch = 0; ch < buffer.channel_count(); ++ch) {
|
||||
@@ -375,7 +371,7 @@ TEST(AudioSmokeWaveform, OverwriteSilence)
|
||||
waveform.set_channel_count(2);
|
||||
|
||||
// First add some samples
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
SampleBuffer buffer(params, size_t(4800));
|
||||
FillSampleBuffer(buffer, 0.5f);
|
||||
waveform.OverwriteSamples(buffer, 48000, rational(0));
|
||||
@@ -395,7 +391,7 @@ TEST(AudioSmokeWaveform, TrimIn)
|
||||
waveform.set_channel_count(2);
|
||||
|
||||
// Add samples
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
SampleBuffer buffer(params, size_t(48000)); // 1 second
|
||||
FillSampleBuffer(buffer, 0.5f);
|
||||
waveform.OverwriteSamples(buffer, 48000, rational(0));
|
||||
@@ -414,7 +410,7 @@ TEST(AudioSmokeWaveform, Resize)
|
||||
waveform.set_channel_count(2);
|
||||
|
||||
// Add samples
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
SampleBuffer buffer(params, size_t(48000));
|
||||
FillSampleBuffer(buffer, 0.5f);
|
||||
waveform.OverwriteSamples(buffer, 48000, rational(0));
|
||||
@@ -433,7 +429,7 @@ TEST(AudioSmokeWaveform, TrimRange)
|
||||
waveform.set_channel_count(2);
|
||||
|
||||
// Add 2 seconds of samples
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
SampleBuffer buffer(params, size_t(96000));
|
||||
FillSampleBuffer(buffer, 0.5f);
|
||||
waveform.OverwriteSamples(buffer, 48000, rational(0));
|
||||
@@ -452,7 +448,7 @@ TEST(AudioSmokeWaveform, Mid)
|
||||
waveform.set_channel_count(2);
|
||||
|
||||
// Add 2 seconds of samples
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
SampleBuffer buffer(params, size_t(96000));
|
||||
FillSampleBuffer(buffer, 0.5f);
|
||||
waveform.OverwriteSamples(buffer, 48000, rational(0));
|
||||
@@ -470,7 +466,7 @@ TEST(AudioSmokeWaveform, GetSummaryFromTime)
|
||||
waveform.set_channel_count(2);
|
||||
|
||||
// Add samples with varying values
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
SampleBuffer buffer(params, size_t(4800));
|
||||
for (int ch = 0; ch < buffer.channel_count(); ++ch) {
|
||||
float *data = buffer.data(ch);
|
||||
@@ -491,7 +487,7 @@ TEST(AudioSmokeWaveform, GetSummaryFromTime)
|
||||
|
||||
TEST(AudioSmokeWaveform, SumSamples)
|
||||
{
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
SampleBuffer buffer(params, size_t(100));
|
||||
|
||||
// Fill with known pattern
|
||||
@@ -543,8 +539,8 @@ TEST(AudioSmokeProcessor, OpenClose)
|
||||
{
|
||||
AudioProcessor processor;
|
||||
|
||||
AudioParams from(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams to(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams from(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
AudioParams to(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
|
||||
EXPECT_TRUE(processor.Open(from, to, 1.0));
|
||||
EXPECT_TRUE(processor.IsOpen());
|
||||
@@ -557,8 +553,8 @@ TEST(AudioSmokeProcessor, SampleRateConversion)
|
||||
{
|
||||
AudioProcessor processor;
|
||||
|
||||
AudioParams from(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams to(44100, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams from(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
AudioParams to(44100, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
|
||||
EXPECT_TRUE(processor.Open(from, to, 1.0));
|
||||
EXPECT_TRUE(processor.IsOpen());
|
||||
@@ -570,8 +566,8 @@ TEST(AudioSmokeProcessor, ChannelLayoutConversion)
|
||||
{
|
||||
AudioProcessor processor;
|
||||
|
||||
AudioParams from(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams to(48000, AV_CH_LAYOUT_MONO, SampleFormat::F32P);
|
||||
AudioParams from(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
AudioParams to(48000, kChannelLayoutMono, SampleFormat::F32P);
|
||||
|
||||
EXPECT_TRUE(processor.Open(from, to, 1.0));
|
||||
EXPECT_TRUE(processor.IsOpen());
|
||||
@@ -583,8 +579,8 @@ TEST(AudioSmokeProcessor, FormatConversion)
|
||||
{
|
||||
AudioProcessor processor;
|
||||
|
||||
AudioParams from(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams to(48000, AV_CH_LAYOUT_STEREO, SampleFormat::S16P);
|
||||
AudioParams from(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
AudioParams to(48000, kChannelLayoutStereo, SampleFormat::S16P);
|
||||
|
||||
EXPECT_TRUE(processor.Open(from, to, 1.0));
|
||||
EXPECT_TRUE(processor.IsOpen());
|
||||
@@ -594,8 +590,8 @@ TEST(AudioSmokeProcessor, TempoChange)
|
||||
{
|
||||
AudioProcessor processor;
|
||||
|
||||
AudioParams from(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams to(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams from(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
AudioParams to(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
|
||||
// Open with 2x tempo
|
||||
EXPECT_TRUE(processor.Open(from, to, 2.0));
|
||||
@@ -607,8 +603,8 @@ TEST(AudioSmokeProcessor, InvalidOpen)
|
||||
AudioProcessor processor;
|
||||
|
||||
// Open with valid params
|
||||
AudioParams from(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams to(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams from(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
AudioParams to(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
EXPECT_TRUE(processor.Open(from, to, 1.0));
|
||||
|
||||
// Try to open again while already open (should fail)
|
||||
@@ -756,7 +752,7 @@ TEST(AudioSmokeThread, ConcurrentWaveformAccess)
|
||||
waveform.set_channel_count(2);
|
||||
|
||||
// Pre-populate with data
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
SampleBuffer buffer(params, size_t(4800));
|
||||
FillSampleBuffer(buffer, 0.5f);
|
||||
waveform.OverwriteSamples(buffer, 48000, rational(0));
|
||||
@@ -791,7 +787,7 @@ TEST(AudioSmokeThread, ConcurrentSampleBufferOperations)
|
||||
{
|
||||
const int num_threads = 4;
|
||||
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
SampleBuffer buffer(params, size_t(1000));
|
||||
FillSampleBuffer(buffer, 0.5f);
|
||||
|
||||
|
||||
@@ -5,16 +5,12 @@
|
||||
#include "olive/core/render/samplebuffer.h"
|
||||
#include "olive/core/render/sampleformat.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/channel_layout.h>
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
olive::core::AudioParams MakeMonoParams()
|
||||
{
|
||||
return olive::core::AudioParams(48000, AV_CH_LAYOUT_MONO,
|
||||
return olive::core::AudioParams(48000, olive::core::kChannelLayoutMono,
|
||||
olive::core::SampleFormat::F32P);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,100 +6,78 @@ using namespace olive;
|
||||
|
||||
TEST(CommonFFmpegUtils, GetNativeSampleFormatMapsCorrectly)
|
||||
{
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_U8),
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(FB_SAMPLE_FMT_U8),
|
||||
SampleFormat::U8);
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_S16),
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(FB_SAMPLE_FMT_S16),
|
||||
SampleFormat::S16);
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_S32),
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(FB_SAMPLE_FMT_S32),
|
||||
SampleFormat::S32);
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_S64),
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(FB_SAMPLE_FMT_S64),
|
||||
SampleFormat::S64);
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_FLT),
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(FB_SAMPLE_FMT_FLT),
|
||||
SampleFormat::F32);
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_DBL),
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(FB_SAMPLE_FMT_DBL),
|
||||
SampleFormat::F64);
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_U8P),
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(FB_SAMPLE_FMT_U8P),
|
||||
SampleFormat::U8P);
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_S16P),
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(FB_SAMPLE_FMT_S16P),
|
||||
SampleFormat::S16P);
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_S32P),
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(FB_SAMPLE_FMT_S32P),
|
||||
SampleFormat::S32P);
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_S64P),
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(FB_SAMPLE_FMT_S64P),
|
||||
SampleFormat::S64P);
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_FLTP),
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(FB_SAMPLE_FMT_FLTP),
|
||||
SampleFormat::F32P);
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_DBLP),
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(FB_SAMPLE_FMT_DBLP),
|
||||
SampleFormat::F64P);
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(AV_SAMPLE_FMT_NONE),
|
||||
EXPECT_EQ(FFmpegUtils::GetNativeSampleFormat(FB_SAMPLE_FMT_NONE),
|
||||
SampleFormat::INVALID);
|
||||
}
|
||||
|
||||
TEST(CommonFFmpegUtils, GetFFmpegSampleFormatMapsCorrectly)
|
||||
{
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::U8),
|
||||
AV_SAMPLE_FMT_U8);
|
||||
FB_SAMPLE_FMT_U8);
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::S16),
|
||||
AV_SAMPLE_FMT_S16);
|
||||
FB_SAMPLE_FMT_S16);
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::S32),
|
||||
AV_SAMPLE_FMT_S32);
|
||||
FB_SAMPLE_FMT_S32);
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::S64),
|
||||
AV_SAMPLE_FMT_S64);
|
||||
FB_SAMPLE_FMT_S64);
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::F32),
|
||||
AV_SAMPLE_FMT_FLT);
|
||||
FB_SAMPLE_FMT_FLT);
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::F64),
|
||||
AV_SAMPLE_FMT_DBL);
|
||||
FB_SAMPLE_FMT_DBL);
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::U8P),
|
||||
AV_SAMPLE_FMT_U8P);
|
||||
FB_SAMPLE_FMT_U8P);
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::S16P),
|
||||
AV_SAMPLE_FMT_S16P);
|
||||
FB_SAMPLE_FMT_S16P);
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::S32P),
|
||||
AV_SAMPLE_FMT_S32P);
|
||||
FB_SAMPLE_FMT_S32P);
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::S64P),
|
||||
AV_SAMPLE_FMT_S64P);
|
||||
FB_SAMPLE_FMT_S64P);
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::F32P),
|
||||
AV_SAMPLE_FMT_FLTP);
|
||||
FB_SAMPLE_FMT_FLTP);
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::F64P),
|
||||
AV_SAMPLE_FMT_DBLP);
|
||||
FB_SAMPLE_FMT_DBLP);
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegSampleFormat(SampleFormat::INVALID),
|
||||
AV_SAMPLE_FMT_NONE);
|
||||
}
|
||||
|
||||
TEST(CommonFFmpegUtils, GetSwsColorspaceFromAVColorSpace)
|
||||
{
|
||||
EXPECT_EQ(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVCOL_SPC_BT709),
|
||||
SWS_CS_ITU709);
|
||||
EXPECT_EQ(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVCOL_SPC_FCC),
|
||||
SWS_CS_FCC);
|
||||
EXPECT_EQ(FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVCOL_SPC_BT470BG),
|
||||
SWS_CS_ITU624);
|
||||
EXPECT_EQ(
|
||||
FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVCOL_SPC_SMPTE170M),
|
||||
SWS_CS_SMPTE170M);
|
||||
EXPECT_EQ(
|
||||
FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVCOL_SPC_SMPTE240M),
|
||||
SWS_CS_SMPTE240M);
|
||||
EXPECT_EQ(
|
||||
FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVCOL_SPC_BT2020_NCL),
|
||||
SWS_CS_BT2020);
|
||||
EXPECT_EQ(
|
||||
FFmpegUtils::GetSwsColorspaceFromAVColorSpace(AVCOL_SPC_UNSPECIFIED),
|
||||
SWS_CS_DEFAULT);
|
||||
FB_SAMPLE_FMT_NONE);
|
||||
}
|
||||
|
||||
TEST(CommonFFmpegUtils, ConvertJPEGSpaceToRegularSpace)
|
||||
{
|
||||
EXPECT_EQ(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AV_PIX_FMT_YUVJ420P),
|
||||
AV_PIX_FMT_YUV420P);
|
||||
EXPECT_EQ(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AV_PIX_FMT_YUVJ422P),
|
||||
AV_PIX_FMT_YUV422P);
|
||||
EXPECT_EQ(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AV_PIX_FMT_YUVJ444P),
|
||||
AV_PIX_FMT_YUV444P);
|
||||
EXPECT_EQ(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AV_PIX_FMT_YUVJ440P),
|
||||
AV_PIX_FMT_YUV440P);
|
||||
EXPECT_EQ(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AV_PIX_FMT_YUVJ411P),
|
||||
AV_PIX_FMT_YUV411P);
|
||||
EXPECT_EQ(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(AV_PIX_FMT_YUV420P),
|
||||
AV_PIX_FMT_YUV420P);
|
||||
EXPECT_EQ(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(FB_PIX_FMT_YUVJ420P),
|
||||
FB_PIX_FMT_YUV420P);
|
||||
EXPECT_EQ(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(FB_PIX_FMT_YUVJ422P),
|
||||
FB_PIX_FMT_YUV422P);
|
||||
EXPECT_EQ(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(FB_PIX_FMT_YUVJ444P),
|
||||
FB_PIX_FMT_YUV444P);
|
||||
EXPECT_EQ(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(FB_PIX_FMT_YUVJ440P),
|
||||
FB_PIX_FMT_YUV440P);
|
||||
EXPECT_EQ(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(FB_PIX_FMT_YUVJ411P),
|
||||
FB_PIX_FMT_YUV411P);
|
||||
EXPECT_EQ(FFmpegUtils::ConvertJPEGSpaceToRegularSpace(FB_PIX_FMT_YUV420P),
|
||||
FB_PIX_FMT_YUV420P);
|
||||
}
|
||||
|
||||
TEST(CommonFFmpegUtils, GetCompatiblePixelFormatNative)
|
||||
@@ -122,33 +100,32 @@ TEST(CommonFFmpegUtils, GetFFmpegPixelFormat)
|
||||
{
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegPixelFormat(PixelFormat::U8,
|
||||
VideoParams::kRGBChannelCount),
|
||||
AV_PIX_FMT_RGB24);
|
||||
FB_PIX_FMT_RGB24);
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegPixelFormat(PixelFormat::U16,
|
||||
VideoParams::kRGBChannelCount),
|
||||
AV_PIX_FMT_RGB48);
|
||||
FB_PIX_FMT_RGB48LE);
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegPixelFormat(PixelFormat::F32,
|
||||
VideoParams::kRGBChannelCount),
|
||||
AV_PIX_FMT_RGBF32);
|
||||
FB_PIX_FMT_RGBF32LE);
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegPixelFormat(PixelFormat::U8,
|
||||
VideoParams::kRGBAChannelCount),
|
||||
AV_PIX_FMT_RGBA);
|
||||
FB_PIX_FMT_RGBA);
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegPixelFormat(PixelFormat::U16,
|
||||
VideoParams::kRGBAChannelCount),
|
||||
AV_PIX_FMT_RGBA64);
|
||||
FB_PIX_FMT_RGBA64LE);
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegPixelFormat(PixelFormat::F32,
|
||||
VideoParams::kRGBAChannelCount),
|
||||
AV_PIX_FMT_RGBAF32);
|
||||
FB_PIX_FMT_RGBAF32LE);
|
||||
EXPECT_EQ(FFmpegUtils::GetFFmpegPixelFormat(PixelFormat::INVALID, 0),
|
||||
AV_PIX_FMT_NONE);
|
||||
FB_PIX_FMT_NONE);
|
||||
}
|
||||
|
||||
TEST(CommonFFmpegUtils, GetCompatiblePixelFormatAV)
|
||||
{
|
||||
AVPixelFormat fmt =
|
||||
FFmpegUtils::GetCompatiblePixelFormat(AV_PIX_FMT_YUV420P);
|
||||
EXPECT_NE(fmt, AV_PIX_FMT_NONE);
|
||||
int fmt = FFmpegUtils::GetCompatibleBridgePixelFormat(FB_PIX_FMT_YUV420P);
|
||||
EXPECT_NE(fmt, FB_PIX_FMT_NONE);
|
||||
|
||||
fmt = FFmpegUtils::GetCompatiblePixelFormat(AV_PIX_FMT_YUV420P,
|
||||
fmt = FFmpegUtils::GetCompatibleBridgePixelFormat(FB_PIX_FMT_YUV420P,
|
||||
PixelFormat::U8);
|
||||
EXPECT_EQ(fmt, AV_PIX_FMT_RGBA);
|
||||
EXPECT_EQ(fmt, FB_PIX_FMT_RGBA);
|
||||
}
|
||||
|
||||
@@ -82,13 +82,6 @@ TEST(CoreRational, ToString)
|
||||
EXPECT_EQ(rational(1, 2).toString(), "1/2");
|
||||
}
|
||||
|
||||
TEST(CoreRational, ToAVRational)
|
||||
{
|
||||
AVRational av = rational(3, 4).toAVRational();
|
||||
EXPECT_EQ(av.num, 3);
|
||||
EXPECT_EQ(av.den, 4);
|
||||
}
|
||||
|
||||
TEST(CoreRational, Arithmetic)
|
||||
{
|
||||
rational a(1, 2);
|
||||
|
||||
@@ -6,7 +6,7 @@ using namespace olive::core;
|
||||
|
||||
static AudioParams MakeParams(int channels = 2, int sample_rate = 48000)
|
||||
{
|
||||
AudioParams params(sample_rate, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(sample_rate, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
return params;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,855 @@
|
||||
/*
|
||||
* Oak Video Editor - ffmpeg_bridge C API Tests
|
||||
* Copyright (C) 2025 Olive CE Team
|
||||
*
|
||||
* Direct tests of the pure C ffmpeg_bridge API. These tests exercise the
|
||||
* bridge in isolation from the editor: handles are created, used and freed
|
||||
* entirely through the fb_* functions, mirroring how the C++ adapter layer
|
||||
* drives the library at runtime.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <ffmpeg_bridge/ffmpeg_bridge.h>
|
||||
|
||||
#include "olive/core/render/channellayout.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
QString DemoPath()
|
||||
{
|
||||
return QDir(QStringLiteral(OAK_TEST_SOURCE_DIR))
|
||||
.filePath(QStringLiteral("tests/demo.mp4"));
|
||||
}
|
||||
|
||||
QString TempFilePath(const QString &name)
|
||||
{
|
||||
return QDir::temp().filePath(name);
|
||||
}
|
||||
|
||||
constexpr double kPi = 3.14159265358979323846;
|
||||
|
||||
// Finds the first stream of `type`; returns its index or -1.
|
||||
int FindStream(FBProbe *probe, int type)
|
||||
{
|
||||
const int count = fb_probe_get_stream_count(probe);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
FBStreamInfo info;
|
||||
if (fb_probe_get_stream_info(probe, i, &info) == 0 &&
|
||||
info.codec_type == type) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// ============================================================================
|
||||
// Constants
|
||||
// ============================================================================
|
||||
|
||||
TEST(FFmpegBridgeConstants, ChannelLayoutsMatchCore)
|
||||
{
|
||||
// The core library and the bridge must agree on layout masks or all
|
||||
// audio plumbing between them breaks.
|
||||
EXPECT_EQ(FB_CH_LAYOUT_MONO, olive::core::kChannelLayoutMono);
|
||||
EXPECT_EQ(FB_CH_LAYOUT_STEREO, olive::core::kChannelLayoutStereo);
|
||||
EXPECT_EQ(FB_CH_LAYOUT_2_1, olive::core::kChannelLayout2_1);
|
||||
EXPECT_EQ(FB_CH_LAYOUT_5POINT1, olive::core::kChannelLayout5Point1);
|
||||
EXPECT_EQ(FB_CH_LAYOUT_7POINT1, olive::core::kChannelLayout7Point1);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgeConstants, SpecialValues)
|
||||
{
|
||||
EXPECT_EQ(FB_NOPTS_VALUE, INT64_MIN);
|
||||
EXPECT_EQ(FB_TIME_BASE, 1000000);
|
||||
EXPECT_LT(FB_ERROR_EOF, 0);
|
||||
EXPECT_EQ(FB_PIX_FMT_NONE, -1);
|
||||
EXPECT_EQ(FB_SAMPLE_FMT_NONE, -1);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Error strings / version
|
||||
// ============================================================================
|
||||
|
||||
TEST(FFmpegBridgeError, ErrorString)
|
||||
{
|
||||
char buffer[256];
|
||||
fb_error_string(FB_ERROR_EOF, buffer, sizeof(buffer));
|
||||
EXPECT_GT(std::strlen(buffer), 0u);
|
||||
|
||||
fb_error_string(0, buffer, sizeof(buffer));
|
||||
EXPECT_GT(std::strlen(buffer), 0u);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgeError, VersionString)
|
||||
{
|
||||
const char *version = fb_version_string();
|
||||
ASSERT_NE(version, nullptr);
|
||||
EXPECT_GT(std::strlen(version), 0u);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Pixel/sample format utilities
|
||||
// ============================================================================
|
||||
|
||||
TEST(FFmpegBridgePixFmt, NameRoundtrip)
|
||||
{
|
||||
const char *name = fb_pix_fmt_name(FB_PIX_FMT_YUV420P);
|
||||
ASSERT_NE(name, nullptr);
|
||||
EXPECT_STREQ(name, "yuv420p");
|
||||
EXPECT_EQ(fb_pix_fmt_from_name(name), FB_PIX_FMT_YUV420P);
|
||||
|
||||
EXPECT_STREQ(fb_pix_fmt_name(FB_PIX_FMT_RGBA), "rgba");
|
||||
EXPECT_EQ(fb_pix_fmt_from_name("rgba"), FB_PIX_FMT_RGBA);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgePixFmt, Properties)
|
||||
{
|
||||
EXPECT_EQ(fb_pix_fmt_bits_per_pixel(FB_PIX_FMT_RGBA), 32);
|
||||
EXPECT_EQ(fb_pix_fmt_bits_per_pixel(FB_PIX_FMT_YUV420P), 12);
|
||||
EXPECT_EQ(fb_pix_fmt_bits_per_pixel(FB_PIX_FMT_RGBA64LE), 64);
|
||||
|
||||
EXPECT_EQ(fb_pix_fmt_has_alpha(FB_PIX_FMT_RGBA), 1);
|
||||
EXPECT_EQ(fb_pix_fmt_has_alpha(FB_PIX_FMT_YUV420P), 0);
|
||||
|
||||
EXPECT_EQ(fb_pix_fmt_is_planar(FB_PIX_FMT_YUV420P), 1);
|
||||
EXPECT_EQ(fb_pix_fmt_is_planar(FB_PIX_FMT_RGBA), 0);
|
||||
|
||||
EXPECT_EQ(fb_pix_fmt_component_size(FB_PIX_FMT_RGBA), 1);
|
||||
EXPECT_EQ(fb_pix_fmt_component_size(FB_PIX_FMT_RGBA64LE), 2);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgePixFmt, FindBestOfList)
|
||||
{
|
||||
const int list[] = { FB_PIX_FMT_YUV420P, FB_PIX_FMT_YUV444P,
|
||||
FB_PIX_FMT_NONE };
|
||||
EXPECT_EQ(fb_find_best_pix_fmt_of_list(list, FB_PIX_FMT_YUV420P),
|
||||
FB_PIX_FMT_YUV420P);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgeChannelLayout, ChannelCounts)
|
||||
{
|
||||
EXPECT_EQ(fb_channel_layout_get_channels(FB_CH_LAYOUT_MONO), 1);
|
||||
EXPECT_EQ(fb_channel_layout_get_channels(FB_CH_LAYOUT_STEREO), 2);
|
||||
EXPECT_EQ(fb_channel_layout_get_channels(FB_CH_LAYOUT_5POINT1), 6);
|
||||
EXPECT_EQ(fb_channel_layout_get_channels(FB_CH_LAYOUT_7POINT1), 8);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgeChannelLayout, Defaults)
|
||||
{
|
||||
EXPECT_EQ(fb_channel_layout_default(1), FB_CH_LAYOUT_MONO);
|
||||
EXPECT_EQ(fb_channel_layout_default(2), FB_CH_LAYOUT_STEREO);
|
||||
// av_channel_layout_default picks a valid layout with the right channel
|
||||
// count, but not necessarily the same variant as the named constants
|
||||
EXPECT_EQ(fb_channel_layout_get_channels(fb_channel_layout_default(6)), 6);
|
||||
EXPECT_NE(fb_channel_layout_default(3), (uint64_t)0);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Frame
|
||||
// ============================================================================
|
||||
|
||||
TEST(FFmpegBridgeFrame, AllocAndFields)
|
||||
{
|
||||
FBFrame *frame = fb_frame_alloc();
|
||||
ASSERT_NE(frame, nullptr);
|
||||
|
||||
EXPECT_EQ(fb_frame_get_width(frame), 0);
|
||||
EXPECT_EQ(fb_frame_get_format(frame), FB_PIX_FMT_NONE);
|
||||
|
||||
fb_frame_set_width(frame, 320);
|
||||
fb_frame_set_height(frame, 240);
|
||||
fb_frame_set_format(frame, FB_PIX_FMT_RGBA);
|
||||
fb_frame_set_pts(frame, 12345);
|
||||
|
||||
EXPECT_EQ(fb_frame_get_width(frame), 320);
|
||||
EXPECT_EQ(fb_frame_get_height(frame), 240);
|
||||
EXPECT_EQ(fb_frame_get_format(frame), FB_PIX_FMT_RGBA);
|
||||
EXPECT_EQ(fb_frame_get_pts(frame), 12345);
|
||||
|
||||
fb_frame_free(&frame);
|
||||
EXPECT_EQ(frame, nullptr);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgeFrame, BufferAllocAndAccess)
|
||||
{
|
||||
FBFrame *frame = fb_frame_alloc();
|
||||
ASSERT_NE(frame, nullptr);
|
||||
|
||||
fb_frame_set_width(frame, 64);
|
||||
fb_frame_set_height(frame, 48);
|
||||
fb_frame_set_format(frame, FB_PIX_FMT_RGBA);
|
||||
ASSERT_EQ(fb_frame_get_buffer(frame, 0), 0);
|
||||
ASSERT_EQ(fb_frame_make_writable(frame), 0);
|
||||
|
||||
EXPECT_NE(fb_frame_get_data(frame, 0), nullptr);
|
||||
EXPECT_GE(fb_frame_get_linesize(frame, 0), 64 * 4);
|
||||
EXPECT_EQ(fb_frame_is_hw(frame), 0);
|
||||
|
||||
// Write a pattern through the API and read it back
|
||||
const int linesize = fb_frame_get_linesize(frame, 0);
|
||||
for (int y = 0; y < 48; ++y) {
|
||||
std::memset(fb_frame_get_data(frame, 0) + y * linesize, y & 0xFF,
|
||||
linesize);
|
||||
}
|
||||
const uint8_t *data = fb_frame_get_data_const(frame, 0);
|
||||
ASSERT_NE(data, nullptr);
|
||||
EXPECT_EQ(data[0], 0);
|
||||
EXPECT_EQ(data[10 * linesize], 10);
|
||||
|
||||
// Out-of-range plane access is safe
|
||||
EXPECT_EQ(fb_frame_get_data(frame, -1), nullptr);
|
||||
EXPECT_EQ(fb_frame_get_data(frame, 8), nullptr);
|
||||
|
||||
fb_frame_unref(frame);
|
||||
EXPECT_EQ(fb_frame_get_data(frame, 0), nullptr);
|
||||
|
||||
fb_frame_free(&frame);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgeFrame, AudioFields)
|
||||
{
|
||||
FBFrame *frame = fb_frame_alloc();
|
||||
ASSERT_NE(frame, nullptr);
|
||||
|
||||
fb_frame_set_nb_samples(frame, 1024);
|
||||
fb_frame_set_sample_rate(frame, 44100);
|
||||
fb_frame_set_format(frame, FB_SAMPLE_FMT_FLTP);
|
||||
fb_frame_set_channel_layout_mask(frame, FB_CH_LAYOUT_STEREO);
|
||||
ASSERT_EQ(fb_frame_get_buffer(frame, 0), 0);
|
||||
|
||||
EXPECT_EQ(fb_frame_get_nb_samples(frame), 1024);
|
||||
EXPECT_EQ(fb_frame_get_sample_rate(frame), 44100);
|
||||
EXPECT_EQ(fb_frame_get_channel_layout_mask(frame), FB_CH_LAYOUT_STEREO);
|
||||
// Planar stereo: two data planes
|
||||
EXPECT_NE(fb_frame_get_data(frame, 0), nullptr);
|
||||
EXPECT_NE(fb_frame_get_data(frame, 1), nullptr);
|
||||
|
||||
fb_frame_free(&frame);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgeFrame, CopyProps)
|
||||
{
|
||||
FBFrame *src = fb_frame_alloc();
|
||||
FBFrame *dst = fb_frame_alloc();
|
||||
ASSERT_NE(src, nullptr);
|
||||
ASSERT_NE(dst, nullptr);
|
||||
|
||||
// av_frame_copy_props copies metadata properties, not dimensions/format
|
||||
fb_frame_set_pts(src, 777);
|
||||
fb_frame_set_color_range(src, FB_COLOR_RANGE_JPEG);
|
||||
fb_frame_set_colorspace(src, FB_COL_SPC_BT709);
|
||||
|
||||
ASSERT_EQ(fb_frame_copy_props(dst, src), 0);
|
||||
EXPECT_EQ(fb_frame_get_pts(dst), 777);
|
||||
EXPECT_EQ(fb_frame_get_color_range(dst), FB_COLOR_RANGE_JPEG);
|
||||
EXPECT_EQ(fb_frame_get_colorspace(dst), FB_COL_SPC_BT709);
|
||||
|
||||
fb_frame_free(&src);
|
||||
fb_frame_free(&dst);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgeFrame, NullSafety)
|
||||
{
|
||||
EXPECT_LT(fb_frame_get_buffer(nullptr, 0), 0);
|
||||
EXPECT_LT(fb_frame_make_writable(nullptr), 0);
|
||||
EXPECT_EQ(fb_frame_get_width(nullptr), 0);
|
||||
EXPECT_EQ(fb_frame_get_data(nullptr, 0), nullptr);
|
||||
fb_frame_free(nullptr); // must not crash
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Packet
|
||||
// ============================================================================
|
||||
|
||||
TEST(FFmpegBridgePacket, AllocAndDefaults)
|
||||
{
|
||||
FBPacket *packet = fb_packet_alloc();
|
||||
ASSERT_NE(packet, nullptr);
|
||||
|
||||
EXPECT_EQ(fb_packet_get_pts(packet), FB_NOPTS_VALUE);
|
||||
EXPECT_EQ(fb_packet_get_size(packet), 0);
|
||||
EXPECT_EQ(fb_packet_get_data(packet), nullptr);
|
||||
|
||||
fb_packet_unref(packet);
|
||||
fb_packet_free(&packet);
|
||||
EXPECT_EQ(packet, nullptr);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Scaler
|
||||
// ============================================================================
|
||||
|
||||
TEST(FFmpegBridgeScaler, RgbaToYuv420P)
|
||||
{
|
||||
const int width = 64;
|
||||
const int height = 64;
|
||||
|
||||
FBFrame *src = fb_frame_alloc();
|
||||
fb_frame_set_width(src, width);
|
||||
fb_frame_set_height(src, height);
|
||||
fb_frame_set_format(src, FB_PIX_FMT_RGBA);
|
||||
ASSERT_EQ(fb_frame_get_buffer(src, 0), 0);
|
||||
|
||||
// Solid mid-grey image
|
||||
const int src_linesize = fb_frame_get_linesize(src, 0);
|
||||
for (int y = 0; y < height; ++y) {
|
||||
std::memset(fb_frame_get_data(src, 0) + y * src_linesize, 128,
|
||||
width * 4);
|
||||
}
|
||||
|
||||
FBFrame *dst = fb_frame_alloc();
|
||||
fb_frame_set_width(dst, width);
|
||||
fb_frame_set_height(dst, height);
|
||||
fb_frame_set_format(dst, FB_PIX_FMT_YUV420P);
|
||||
ASSERT_EQ(fb_frame_get_buffer(dst, 0), 0);
|
||||
|
||||
FBScaler *scaler = fb_scaler_create(width, height, FB_PIX_FMT_RGBA, width,
|
||||
height, FB_PIX_FMT_YUV420P,
|
||||
FB_SCALER_POINT);
|
||||
ASSERT_NE(scaler, nullptr);
|
||||
// sws_scale returns the output slice height on success
|
||||
ASSERT_GE(fb_scaler_scale_frame(scaler, dst, src), 0);
|
||||
fb_scaler_free(&scaler);
|
||||
EXPECT_EQ(scaler, nullptr);
|
||||
|
||||
// Y plane of a mid-grey RGB source should sit near 128
|
||||
const uint8_t *y_plane = fb_frame_get_data_const(dst, 0);
|
||||
ASSERT_NE(y_plane, nullptr);
|
||||
EXPECT_NEAR(y_plane[0], 128, 8);
|
||||
|
||||
// U and V planes of a grey image should sit near 128 (neutral chroma)
|
||||
const uint8_t *u_plane = fb_frame_get_data_const(dst, 1);
|
||||
const uint8_t *v_plane = fb_frame_get_data_const(dst, 2);
|
||||
ASSERT_NE(u_plane, nullptr);
|
||||
ASSERT_NE(v_plane, nullptr);
|
||||
EXPECT_NEAR(u_plane[0], 128, 8);
|
||||
EXPECT_NEAR(v_plane[0], 128, 8);
|
||||
|
||||
fb_frame_free(&src);
|
||||
fb_frame_free(&dst);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgeScaler, SetColorspace)
|
||||
{
|
||||
FBScaler *scaler = fb_scaler_create(64, 64, FB_PIX_FMT_YUV420P, 64, 64,
|
||||
FB_PIX_FMT_RGBA, FB_SCALER_POINT);
|
||||
ASSERT_NE(scaler, nullptr);
|
||||
EXPECT_GE(fb_scaler_set_colorspace(scaler, FB_COL_SPC_BT709, 0), 0);
|
||||
fb_scaler_free(&scaler);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgeScaler, YuvCoefficients)
|
||||
{
|
||||
// Values come from swscale's coefficient tables (sws_getCoefficients /
|
||||
// 65536), which include the studio-range scaling factor.
|
||||
double coeffs[4] = { 0, 0, 0, 0 };
|
||||
fb_get_yuv_coefficients(FB_COL_SPC_BT709, coeffs);
|
||||
EXPECT_NEAR(coeffs[0], 117489 / 65536.0, 1e-6); // crv
|
||||
EXPECT_NEAR(coeffs[1], 138438 / 65536.0, 1e-6); // cbu
|
||||
EXPECT_GT(coeffs[2], 0.0);
|
||||
EXPECT_GT(coeffs[3], 0.0);
|
||||
|
||||
fb_get_yuv_coefficients(FB_COL_SPC_SMPTE170M, coeffs);
|
||||
EXPECT_NEAR(coeffs[0], 104597 / 65536.0, 1e-6); // crv
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Resampler
|
||||
// ============================================================================
|
||||
|
||||
TEST(FFmpegBridgeResampler, ConvertFltpToS16p)
|
||||
{
|
||||
const int in_samples = 1024;
|
||||
const double freq = 440.0;
|
||||
const double rate = 44100.0;
|
||||
|
||||
FBResampler *resampler =
|
||||
fb_resampler_create(FB_CH_LAYOUT_STEREO, FB_SAMPLE_FMT_S16P, 44100,
|
||||
FB_CH_LAYOUT_STEREO, FB_SAMPLE_FMT_FLTP, 44100);
|
||||
ASSERT_NE(resampler, nullptr);
|
||||
|
||||
const int out_capacity = fb_resampler_get_out_samples(resampler, in_samples);
|
||||
ASSERT_GT(out_capacity, 0);
|
||||
|
||||
std::vector<float> in_left(in_samples), in_right(in_samples);
|
||||
for (int i = 0; i < in_samples; ++i) {
|
||||
in_left[i] = 0.5f * std::sin(2.0 * kPi * freq * i / rate);
|
||||
in_right[i] = 0.5f * std::sin(2.0 * kPi * freq * i / rate);
|
||||
}
|
||||
const uint8_t *in_planes[2] = {
|
||||
reinterpret_cast<const uint8_t *>(in_left.data()),
|
||||
reinterpret_cast<const uint8_t *>(in_right.data())
|
||||
};
|
||||
|
||||
std::vector<int16_t> out_left(out_capacity), out_right(out_capacity);
|
||||
uint8_t *out_planes[2] = {
|
||||
reinterpret_cast<uint8_t *>(out_left.data()),
|
||||
reinterpret_cast<uint8_t *>(out_right.data())
|
||||
};
|
||||
|
||||
const int converted = fb_resampler_convert(
|
||||
resampler, out_planes, out_capacity, in_planes, in_samples);
|
||||
ASSERT_GT(converted, 0);
|
||||
|
||||
// Output must be non-silent and bounded to full scale
|
||||
bool non_silent = false;
|
||||
for (int i = 0; i < converted; ++i) {
|
||||
if (out_left[i] != 0) {
|
||||
non_silent = true;
|
||||
}
|
||||
EXPECT_LE(std::abs(out_left[i]), 32767);
|
||||
}
|
||||
EXPECT_TRUE(non_silent);
|
||||
|
||||
fb_resampler_free(&resampler);
|
||||
EXPECT_EQ(resampler, nullptr);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgeResampler, ConvertFrameInput)
|
||||
{
|
||||
const int in_samples = 512;
|
||||
|
||||
FBResampler *resampler =
|
||||
fb_resampler_create(FB_CH_LAYOUT_STEREO, FB_SAMPLE_FMT_S16P, 44100,
|
||||
FB_CH_LAYOUT_STEREO, FB_SAMPLE_FMT_FLTP, 44100);
|
||||
ASSERT_NE(resampler, nullptr);
|
||||
|
||||
FBFrame *frame = fb_frame_alloc();
|
||||
fb_frame_set_nb_samples(frame, in_samples);
|
||||
fb_frame_set_sample_rate(frame, 44100);
|
||||
fb_frame_set_format(frame, FB_SAMPLE_FMT_FLTP);
|
||||
fb_frame_set_channel_layout_mask(frame, FB_CH_LAYOUT_STEREO);
|
||||
ASSERT_EQ(fb_frame_get_buffer(frame, 0), 0);
|
||||
|
||||
float *left = reinterpret_cast<float *>(fb_frame_get_data(frame, 0));
|
||||
float *right = reinterpret_cast<float *>(fb_frame_get_data(frame, 1));
|
||||
for (int i = 0; i < in_samples; ++i) {
|
||||
left[i] = 0.25f;
|
||||
right[i] = -0.25f;
|
||||
}
|
||||
|
||||
const int out_capacity = fb_resampler_get_out_samples(resampler, in_samples);
|
||||
std::vector<int16_t> out_left(out_capacity), out_right(out_capacity);
|
||||
uint8_t *out_planes[2] = {
|
||||
reinterpret_cast<uint8_t *>(out_left.data()),
|
||||
reinterpret_cast<uint8_t *>(out_right.data())
|
||||
};
|
||||
|
||||
const int converted =
|
||||
fb_resampler_convert_frame(resampler, out_planes, out_capacity, frame);
|
||||
ASSERT_GT(converted, 0);
|
||||
|
||||
// Constant 0.25 input should land near 0.25 * 32767
|
||||
EXPECT_NEAR(out_left[converted / 2], 8192, 256);
|
||||
EXPECT_NEAR(out_right[converted / 2], -8192, 256);
|
||||
|
||||
fb_frame_free(&frame);
|
||||
fb_resampler_free(&resampler);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Probe / Decoder on tests/demo.mp4
|
||||
// ============================================================================
|
||||
|
||||
TEST(FFmpegBridgeProbe, DemoMp4Streams)
|
||||
{
|
||||
const QString path = DemoPath();
|
||||
ASSERT_TRUE(QFileInfo::exists(path));
|
||||
|
||||
FBProbe *probe = fb_probe_create();
|
||||
ASSERT_NE(probe, nullptr);
|
||||
ASSERT_EQ(fb_probe_open(probe, path.toUtf8().constData()), 0);
|
||||
|
||||
EXPECT_GE(fb_probe_get_stream_count(probe), 1);
|
||||
EXPECT_GT(fb_probe_get_duration(probe), 0);
|
||||
|
||||
const int video = FindStream(probe, FB_MEDIA_TYPE_VIDEO);
|
||||
ASSERT_GE(video, 0);
|
||||
|
||||
FBStreamInfo info;
|
||||
ASSERT_EQ(fb_probe_get_stream_info(probe, video, &info), 0);
|
||||
EXPECT_EQ(info.width, 1920);
|
||||
EXPECT_EQ(info.height, 1080);
|
||||
EXPECT_NE(info.pixel_format, FB_PIX_FMT_NONE);
|
||||
EXPECT_EQ(info.has_decoder, 1);
|
||||
EXPECT_GT(info.time_base_den, 0);
|
||||
|
||||
// Metadata lookups must be safe whether or not the key exists
|
||||
char buffer[256];
|
||||
int found = fb_probe_get_metadata(probe, -1, "title", buffer, sizeof(buffer));
|
||||
EXPECT_TRUE(found == 0 || found == 1);
|
||||
|
||||
fb_probe_close(probe);
|
||||
fb_probe_free(&probe);
|
||||
EXPECT_EQ(probe, nullptr);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgeProbe, VideoStreamDetails)
|
||||
{
|
||||
const QString path = DemoPath();
|
||||
ASSERT_TRUE(QFileInfo::exists(path));
|
||||
|
||||
FBVideoStreamDetails details;
|
||||
ASSERT_EQ(fb_probe_video_stream_details(path.toUtf8().constData(), 0,
|
||||
&details, 0, nullptr, nullptr),
|
||||
0);
|
||||
EXPECT_TRUE(details.field_order == FB_FIELD_ORDER_PROGRESSIVE ||
|
||||
details.field_order == FB_FIELD_ORDER_UNKNOWN);
|
||||
EXPECT_GT(details.frame_rate_num, 0);
|
||||
EXPECT_GT(details.frame_rate_den, 0);
|
||||
EXPECT_EQ(details.pixel_aspect_num, 1);
|
||||
EXPECT_EQ(details.pixel_aspect_den, 1);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgeProbe, ReadSubtitleStream)
|
||||
{
|
||||
// Write a small SRT file and read it back through the bridge
|
||||
const QString path = TempFilePath(QStringLiteral("fb_bridge_test.srt"));
|
||||
{
|
||||
QFile file(path);
|
||||
ASSERT_TRUE(file.open(QIODevice::WriteOnly | QIODevice::Text));
|
||||
file.write("1\n00:00:01,000 --> 00:00:02,000\nHello bridge\n\n"
|
||||
"2\n00:00:03,000 --> 00:00:04,500\nSecond line\n\n");
|
||||
file.close();
|
||||
}
|
||||
|
||||
struct Context {
|
||||
int count = 0;
|
||||
std::string first_text;
|
||||
int64_t first_pts = FB_NOPTS_VALUE;
|
||||
} ctx;
|
||||
|
||||
int result = fb_probe_read_subtitle_stream(
|
||||
path.toUtf8().constData(), 0,
|
||||
[](int64_t pts, int64_t, const char *text, int, void *userdata) {
|
||||
auto *c = static_cast<Context *>(userdata);
|
||||
if (c->count == 0) {
|
||||
c->first_pts = pts;
|
||||
c->first_text = text ? text : "";
|
||||
}
|
||||
c->count++;
|
||||
},
|
||||
&ctx);
|
||||
|
||||
EXPECT_EQ(result, 0);
|
||||
EXPECT_EQ(ctx.count, 2);
|
||||
EXPECT_NE(ctx.first_text.find("Hello bridge"), std::string::npos);
|
||||
EXPECT_GE(ctx.first_pts, 0);
|
||||
|
||||
QFile::remove(path);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgeDecoder, DecodeFirstFrame)
|
||||
{
|
||||
const QString path = DemoPath();
|
||||
ASSERT_TRUE(QFileInfo::exists(path));
|
||||
|
||||
FBDecoder *decoder = fb_decoder_create();
|
||||
ASSERT_NE(decoder, nullptr);
|
||||
ASSERT_EQ(fb_decoder_open(decoder, path.toUtf8().constData(), 0), 0);
|
||||
|
||||
FBStreamInfo info;
|
||||
ASSERT_EQ(fb_decoder_get_stream_info(decoder, &info), 0);
|
||||
EXPECT_EQ(info.width, 1920);
|
||||
EXPECT_EQ(info.height, 1080);
|
||||
EXPECT_EQ(info.codec_type, FB_MEDIA_TYPE_VIDEO);
|
||||
EXPECT_GT(fb_decoder_get_format_duration(decoder), 0);
|
||||
|
||||
FBPacket *packet = fb_packet_alloc();
|
||||
FBFrame *frame = fb_frame_alloc();
|
||||
ASSERT_NE(packet, nullptr);
|
||||
ASSERT_NE(frame, nullptr);
|
||||
|
||||
int result = -1;
|
||||
for (int attempt = 0; attempt < 200 && result != 0; ++attempt) {
|
||||
result = fb_decoder_get_frame(decoder, packet, frame);
|
||||
}
|
||||
ASSERT_EQ(result, 0);
|
||||
|
||||
EXPECT_EQ(fb_frame_get_width(frame), 1920);
|
||||
EXPECT_EQ(fb_frame_get_height(frame), 1080);
|
||||
EXPECT_NE(fb_frame_get_format(frame), FB_PIX_FMT_NONE);
|
||||
EXPECT_NE(fb_frame_get_data(frame, 0), nullptr);
|
||||
|
||||
// Hardware frames live in device memory: transfer to a software frame
|
||||
// before reading pixels
|
||||
FBFrame *sw_frame = nullptr;
|
||||
const FBFrame *read_frame = frame;
|
||||
if (fb_frame_is_hw(frame)) {
|
||||
sw_frame = fb_frame_alloc();
|
||||
ASSERT_NE(sw_frame, nullptr);
|
||||
ASSERT_EQ(fb_frame_hw_transfer_data(sw_frame, frame), 0);
|
||||
read_frame = sw_frame;
|
||||
}
|
||||
|
||||
// The decoded image should not be a black frame
|
||||
const uint8_t *data = fb_frame_get_data_const(read_frame, 0);
|
||||
ASSERT_NE(data, nullptr);
|
||||
bool has_nonzero = false;
|
||||
const int linesize = fb_frame_get_linesize(read_frame, 0);
|
||||
for (int y = 0; y < 64 && !has_nonzero; ++y) {
|
||||
for (int x = 0; x < 64; ++x) {
|
||||
if (data[y * linesize + x] != 0) {
|
||||
has_nonzero = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_TRUE(has_nonzero);
|
||||
|
||||
fb_frame_free(&sw_frame);
|
||||
|
||||
// Seek back to the start and decode again
|
||||
fb_decoder_seek(decoder, 0);
|
||||
result = -1;
|
||||
for (int attempt = 0; attempt < 200 && result != 0; ++attempt) {
|
||||
result = fb_decoder_get_frame(decoder, packet, frame);
|
||||
}
|
||||
EXPECT_EQ(result, 0);
|
||||
|
||||
fb_frame_free(&frame);
|
||||
fb_packet_free(&packet);
|
||||
fb_decoder_close(decoder);
|
||||
fb_decoder_free(&decoder);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgeDecoder, OpenFailure)
|
||||
{
|
||||
FBDecoder *decoder = fb_decoder_create();
|
||||
ASSERT_NE(decoder, nullptr);
|
||||
EXPECT_LT(fb_decoder_open(decoder, "/nonexistent/file.mp4", 0), 0);
|
||||
fb_decoder_free(&decoder);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgeDecoder, GuessRates)
|
||||
{
|
||||
const QString path = DemoPath();
|
||||
ASSERT_TRUE(QFileInfo::exists(path));
|
||||
|
||||
FBDecoder *decoder = fb_decoder_create();
|
||||
ASSERT_EQ(fb_decoder_open(decoder, path.toUtf8().constData(), 0), 0);
|
||||
|
||||
int num = 0, den = 0;
|
||||
ASSERT_EQ(fb_decoder_guess_frame_rate(decoder, nullptr, &num, &den), 0);
|
||||
EXPECT_GT(num, 0);
|
||||
EXPECT_GT(den, 0);
|
||||
|
||||
ASSERT_EQ(fb_decoder_guess_sample_aspect_ratio(decoder, nullptr, &num, &den),
|
||||
0);
|
||||
EXPECT_EQ(num, 1);
|
||||
EXPECT_EQ(den, 1);
|
||||
|
||||
fb_decoder_free(&decoder);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Audio graph
|
||||
// ============================================================================
|
||||
|
||||
TEST(FFmpegBridgeAudioGraph, TempoProcessing)
|
||||
{
|
||||
FBAudioGraphConfig config = {};
|
||||
config.in_sample_rate = 44100;
|
||||
config.in_channel_layout_mask = FB_CH_LAYOUT_STEREO;
|
||||
config.in_sample_format = FB_SAMPLE_FMT_FLTP;
|
||||
config.in_channels = 2;
|
||||
config.out_sample_rate = 44100;
|
||||
config.out_channel_layout_mask = FB_CH_LAYOUT_STEREO;
|
||||
config.out_sample_format = FB_SAMPLE_FMT_FLTP;
|
||||
config.out_channels = 2;
|
||||
config.out_is_planar = 1;
|
||||
config.tempo = 2.0;
|
||||
|
||||
FBAudioGraph *graph = fb_audio_graph_create(&config);
|
||||
ASSERT_NE(graph, nullptr);
|
||||
|
||||
const int in_samples = 4410; // 0.1 seconds
|
||||
std::vector<float> left(in_samples, 0.5f), right(in_samples, 0.5f);
|
||||
const uint8_t *planes[2] = {
|
||||
reinterpret_cast<const uint8_t *>(left.data()),
|
||||
reinterpret_cast<const uint8_t *>(right.data())
|
||||
};
|
||||
|
||||
ASSERT_EQ(fb_audio_graph_push(graph, planes, in_samples), 0);
|
||||
// Flush
|
||||
ASSERT_EQ(fb_audio_graph_push(graph, nullptr, 0), 0);
|
||||
|
||||
FBFrame *out = fb_frame_alloc();
|
||||
int total_samples = 0;
|
||||
int pull_result;
|
||||
while ((pull_result = fb_audio_graph_pull(graph, out)) == 1) {
|
||||
total_samples += fb_frame_get_nb_samples(out);
|
||||
fb_frame_unref(out);
|
||||
}
|
||||
// 0 = needs more input, FB_ERROR_EOF = flushed graph is drained
|
||||
EXPECT_TRUE(pull_result == 0 || pull_result == FB_ERROR_EOF);
|
||||
|
||||
// tempo 2.0 halves the duration; atempo has some internal padding,
|
||||
// so allow generous bounds around the ideal 2205 samples
|
||||
EXPECT_GT(total_samples, 1500);
|
||||
EXPECT_LT(total_samples, 3000);
|
||||
|
||||
fb_frame_free(&out);
|
||||
fb_audio_graph_free(&graph);
|
||||
EXPECT_EQ(graph, nullptr);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Encoder (end-to-end: encode, then probe the result back)
|
||||
// ============================================================================
|
||||
|
||||
TEST(FFmpegBridgeEncoder, CodecFormatLists)
|
||||
{
|
||||
// PNG is a native FFmpeg encoder and always available
|
||||
const char *names[16];
|
||||
const int pix_count =
|
||||
fb_encoder_codec_get_pixel_formats(FB_CODEC_PNG, names, 16);
|
||||
EXPECT_GT(pix_count, 0);
|
||||
|
||||
int fmts[16];
|
||||
const int sample_count =
|
||||
fb_encoder_codec_get_sample_formats(FB_CODEC_AAC, fmts, 16);
|
||||
EXPECT_GT(sample_count, 0);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgeEncoder, WritePngVideoAndProbeBack)
|
||||
{
|
||||
const QString path = TempFilePath(QStringLiteral("fb_bridge_test.mkv"));
|
||||
QFile::remove(path);
|
||||
|
||||
const int width = 64;
|
||||
const int height = 64;
|
||||
const int frame_count = 5;
|
||||
|
||||
FBEncoderConfig config = {};
|
||||
config.filename = path.toUtf8().constData();
|
||||
config.video_enabled = 1;
|
||||
config.video_codec = FB_CODEC_PNG;
|
||||
config.video_width = width;
|
||||
config.video_height = height;
|
||||
config.video_pixel_aspect_num = 1;
|
||||
config.video_pixel_aspect_den = 1;
|
||||
config.video_time_base_num = 1;
|
||||
config.video_time_base_den = 30;
|
||||
config.video_frame_rate_num = 30;
|
||||
config.video_frame_rate_den = 1;
|
||||
config.video_pix_fmt = "rgba";
|
||||
config.video_src_pix_fmt = FB_PIX_FMT_RGBA;
|
||||
config.video_color_range = FB_COLOR_RANGE_UNSPEC;
|
||||
config.video_field_order = FB_FIELD_ORDER_PROGRESSIVE;
|
||||
config.video_threads = 1;
|
||||
|
||||
FBEncoder *encoder = fb_encoder_create(&config);
|
||||
ASSERT_NE(encoder, nullptr);
|
||||
ASSERT_EQ(fb_encoder_open(encoder), 0)
|
||||
<< "encoder error: " << fb_encoder_get_error(encoder);
|
||||
|
||||
std::vector<uint8_t> pixels(width * height * 4);
|
||||
for (int f = 0; f < frame_count; ++f) {
|
||||
for (int i = 0; i < width * height; ++i) {
|
||||
pixels[i * 4 + 0] = static_cast<uint8_t>(f * 40); // R
|
||||
pixels[i * 4 + 1] = 128; // G
|
||||
pixels[i * 4 + 2] = 64; // B
|
||||
pixels[i * 4 + 3] = 255; // A
|
||||
}
|
||||
ASSERT_EQ(fb_encoder_write_video_frame(encoder, width, height,
|
||||
FB_PIX_FMT_RGBA, pixels.data(),
|
||||
width * 4, f / 30.0),
|
||||
0)
|
||||
<< "encoder error: " << fb_encoder_get_error(encoder);
|
||||
}
|
||||
|
||||
fb_encoder_close(encoder);
|
||||
fb_encoder_free(&encoder);
|
||||
|
||||
// The file must exist and probe back as a 64x64 video
|
||||
ASSERT_TRUE(QFileInfo::exists(path));
|
||||
|
||||
FBProbe *probe = fb_probe_create();
|
||||
ASSERT_EQ(fb_probe_open(probe, path.toUtf8().constData()), 0);
|
||||
const int video = FindStream(probe, FB_MEDIA_TYPE_VIDEO);
|
||||
ASSERT_GE(video, 0);
|
||||
|
||||
FBStreamInfo info;
|
||||
ASSERT_EQ(fb_probe_get_stream_info(probe, video, &info), 0);
|
||||
EXPECT_EQ(info.width, width);
|
||||
EXPECT_EQ(info.height, height);
|
||||
|
||||
fb_probe_close(probe);
|
||||
fb_probe_free(&probe);
|
||||
QFile::remove(path);
|
||||
}
|
||||
|
||||
TEST(FFmpegBridgeEncoder, WritePcmAudioAndProbeBack)
|
||||
{
|
||||
const QString path = TempFilePath(QStringLiteral("fb_bridge_test.wav"));
|
||||
QFile::remove(path);
|
||||
|
||||
FBEncoderConfig config = {};
|
||||
config.filename = path.toUtf8().constData();
|
||||
config.audio_enabled = 1;
|
||||
config.audio_codec = FB_CODEC_PCM;
|
||||
config.audio_sample_rate = 44100;
|
||||
config.audio_channel_layout_mask = FB_CH_LAYOUT_STEREO;
|
||||
config.audio_sample_format = FB_SAMPLE_FMT_S16;
|
||||
|
||||
FBEncoder *encoder = fb_encoder_create(&config);
|
||||
ASSERT_NE(encoder, nullptr);
|
||||
ASSERT_EQ(fb_encoder_open(encoder), 0)
|
||||
<< "encoder error: " << fb_encoder_get_error(encoder);
|
||||
|
||||
const int sample_count = 4410;
|
||||
std::vector<int16_t> left(sample_count), right(sample_count);
|
||||
for (int i = 0; i < sample_count; ++i) {
|
||||
left[i] = static_cast<int16_t>(
|
||||
10000 * std::sin(2.0 * kPi * 440.0 * i / 44100.0));
|
||||
right[i] = left[i];
|
||||
}
|
||||
const uint8_t *planes[2] = {
|
||||
reinterpret_cast<const uint8_t *>(left.data()),
|
||||
reinterpret_cast<const uint8_t *>(right.data())
|
||||
};
|
||||
|
||||
ASSERT_EQ(fb_encoder_write_audio(encoder, planes, 2, FB_SAMPLE_FMT_S16P,
|
||||
44100, FB_CH_LAYOUT_STEREO, sample_count),
|
||||
0)
|
||||
<< "encoder error: " << fb_encoder_get_error(encoder);
|
||||
|
||||
// Flush
|
||||
EXPECT_EQ(fb_encoder_write_audio(encoder, nullptr, 2, FB_SAMPLE_FMT_S16P,
|
||||
44100, FB_CH_LAYOUT_STEREO, 0),
|
||||
0);
|
||||
|
||||
fb_encoder_close(encoder);
|
||||
fb_encoder_free(&encoder);
|
||||
|
||||
ASSERT_TRUE(QFileInfo::exists(path));
|
||||
|
||||
FBProbe *probe = fb_probe_create();
|
||||
ASSERT_EQ(fb_probe_open(probe, path.toUtf8().constData()), 0);
|
||||
const int audio = FindStream(probe, FB_MEDIA_TYPE_AUDIO);
|
||||
ASSERT_GE(audio, 0);
|
||||
|
||||
FBStreamInfo info;
|
||||
ASSERT_EQ(fb_probe_get_stream_info(probe, audio, &info), 0);
|
||||
EXPECT_EQ(info.sample_rate, 44100);
|
||||
EXPECT_EQ(fb_channel_layout_get_channels(info.channel_layout_mask), 2);
|
||||
|
||||
fb_probe_close(probe);
|
||||
fb_probe_free(&probe);
|
||||
QFile::remove(path);
|
||||
}
|
||||
@@ -49,8 +49,8 @@ TEST(ModuleSmoke, HumanStringsSampleRate)
|
||||
TEST(ModuleSmoke, HumanStringsChannelLayout)
|
||||
{
|
||||
EXPECT_FALSE(
|
||||
olive::HumanStrings::ChannelLayoutToString(AV_CH_LAYOUT_MONO).isEmpty());
|
||||
EXPECT_FALSE(olive::HumanStrings::ChannelLayoutToString(AV_CH_LAYOUT_STEREO)
|
||||
olive::HumanStrings::ChannelLayoutToString(olive::core::kChannelLayoutMono).isEmpty());
|
||||
EXPECT_FALSE(olive::HumanStrings::ChannelLayoutToString(olive::core::kChannelLayoutStereo)
|
||||
.isEmpty());
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ TEST(NodeGlobals, ConstructedWithParams)
|
||||
olive::VideoParams video_params(1920, 1080, olive::PixelFormat::F32, 4);
|
||||
olive::AudioParams audio_params;
|
||||
audio_params.set_sample_rate(48000);
|
||||
audio_params.set_channel_layout(AV_CH_LAYOUT_STEREO);
|
||||
audio_params.set_channel_layout(olive::core::kChannelLayoutStereo);
|
||||
|
||||
olive::TimeRange time(olive::core::rational(1, 24),
|
||||
olive::core::rational(2, 24));
|
||||
|
||||
@@ -7,11 +7,6 @@
|
||||
#include <QDir>
|
||||
#include <QDebug>
|
||||
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
#include "common/ffmpegutils.h"
|
||||
#include "render/videoparams.h"
|
||||
#include "render/texture.h"
|
||||
@@ -20,19 +15,19 @@ using namespace olive;
|
||||
using namespace olive::core;
|
||||
|
||||
// Test helper to create AVFrame with specific format
|
||||
static AVFramePtr CreateTestFrame(int width, int height, AVPixelFormat fmt,
|
||||
static AVFramePtr CreateTestFrame(int width, int height, int fmt,
|
||||
uint32_t fill_color = 0xFF804020)
|
||||
{
|
||||
AVFramePtr frame = CreateAVFramePtr();
|
||||
frame->width = width;
|
||||
frame->height = height;
|
||||
frame->format = fmt;
|
||||
frame->set_width(width);
|
||||
frame->set_height(height);
|
||||
frame->set_format(fmt);
|
||||
|
||||
if (av_frame_get_buffer(frame.get(), 0) < 0) {
|
||||
if (frame->get_buffer(0) < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (av_frame_make_writable(frame.get()) < 0) {
|
||||
if (frame->make_writable() < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -42,9 +37,9 @@ static AVFramePtr CreateTestFrame(int width, int height, AVPixelFormat fmt,
|
||||
uint8_t b = (fill_color >> 8) & 0xFF;
|
||||
uint8_t a = fill_color & 0xFF;
|
||||
|
||||
if (fmt == AV_PIX_FMT_RGBA) {
|
||||
if (fmt == FB_PIX_FMT_RGBA) {
|
||||
for (int y = 0; y < height; ++y) {
|
||||
uint8_t *row = frame->data[0] + y * frame->linesize[0];
|
||||
uint8_t *row = frame->data(0) + y * frame->linesize(0);
|
||||
for (int x = 0; x < width; ++x) {
|
||||
row[x * 4 + 0] = r;
|
||||
row[x * 4 + 1] = g;
|
||||
@@ -52,14 +47,14 @@ static AVFramePtr CreateTestFrame(int width, int height, AVPixelFormat fmt,
|
||||
row[x * 4 + 3] = a;
|
||||
}
|
||||
}
|
||||
} else if (fmt == AV_PIX_FMT_RGBA64) {
|
||||
} else if (fmt == FB_PIX_FMT_RGBA64LE) {
|
||||
uint16_t r16 = (r << 8) | r;
|
||||
uint16_t g16 = (g << 8) | g;
|
||||
uint16_t b16 = (b << 8) | b;
|
||||
uint16_t a16 = (a << 8) | a;
|
||||
for (int y = 0; y < height; ++y) {
|
||||
uint16_t *row = reinterpret_cast<uint16_t *>(
|
||||
frame->data[0] + y * frame->linesize[0]);
|
||||
uint16_t *row = reinterpret_cast<uint16_t *>(frame->data(0) +
|
||||
y * frame->linesize(0));
|
||||
for (int x = 0; x < width; ++x) {
|
||||
row[x * 4 + 0] = r16;
|
||||
row[x * 4 + 1] = g16;
|
||||
@@ -81,11 +76,11 @@ TEST(FormatConversion, U8ToU16)
|
||||
|
||||
// Create U8 frame
|
||||
AVFramePtr u8_frame =
|
||||
CreateTestFrame(width, height, AV_PIX_FMT_RGBA, test_color);
|
||||
CreateTestFrame(width, height, FB_PIX_FMT_RGBA, test_color);
|
||||
ASSERT_NE(u8_frame, nullptr);
|
||||
|
||||
// Verify U8 values
|
||||
uint8_t *first_pixel_u8 = u8_frame->data[0];
|
||||
uint8_t *first_pixel_u8 = u8_frame->data(0);
|
||||
EXPECT_EQ(first_pixel_u8[0], 0xFF); // R
|
||||
EXPECT_EQ(first_pixel_u8[1], 0x80); // G
|
||||
EXPECT_EQ(first_pixel_u8[2], 0x40); // B
|
||||
@@ -93,12 +88,12 @@ TEST(FormatConversion, U8ToU16)
|
||||
|
||||
// Create U16 frame
|
||||
AVFramePtr u16_frame =
|
||||
CreateTestFrame(width, height, AV_PIX_FMT_RGBA64, test_color);
|
||||
CreateTestFrame(width, height, FB_PIX_FMT_RGBA64LE, test_color);
|
||||
ASSERT_NE(u16_frame, nullptr);
|
||||
|
||||
// Verify U16 values (should be U8 value repeated: 0xFF -> 0xFFFF, 0x80 -> 0x8080)
|
||||
uint16_t *first_pixel_u16 =
|
||||
reinterpret_cast<uint16_t *>(u16_frame->data[0]);
|
||||
reinterpret_cast<uint16_t *>(u16_frame->data(0));
|
||||
EXPECT_EQ(first_pixel_u16[0], 0xFFFF); // R
|
||||
EXPECT_EQ(first_pixel_u16[1], 0x8080); // G
|
||||
EXPECT_EQ(first_pixel_u16[2], 0x4040); // B
|
||||
@@ -114,58 +109,69 @@ TEST(FormatConversion, FFmpegU16ToU8)
|
||||
|
||||
// Create U16 frame
|
||||
AVFramePtr u16_frame =
|
||||
CreateTestFrame(width, height, AV_PIX_FMT_RGBA64, test_color);
|
||||
CreateTestFrame(width, height, FB_PIX_FMT_RGBA64LE, test_color);
|
||||
ASSERT_NE(u16_frame, nullptr);
|
||||
|
||||
// Create destination U8 frame
|
||||
AVFramePtr u8_frame = CreateTestFrame(width, height, AV_PIX_FMT_RGBA, 0);
|
||||
AVFramePtr u8_frame = CreateTestFrame(width, height, FB_PIX_FMT_RGBA, 0);
|
||||
ASSERT_NE(u8_frame, nullptr);
|
||||
|
||||
// Use sws_scale to convert
|
||||
SwsContext *sws_ctx = sws_getContext(width, height, AV_PIX_FMT_RGBA64,
|
||||
width, height, AV_PIX_FMT_RGBA,
|
||||
SWS_POINT, nullptr, nullptr, nullptr);
|
||||
// Use the bridge scaler to convert
|
||||
FBScaler *sws_ctx = fb_scaler_create(width, height, FB_PIX_FMT_RGBA64LE,
|
||||
width, height, FB_PIX_FMT_RGBA,
|
||||
FB_SCALER_POINT);
|
||||
ASSERT_NE(sws_ctx, nullptr);
|
||||
|
||||
sws_scale(sws_ctx, u16_frame->data, u16_frame->linesize, 0, height,
|
||||
u8_frame->data, u8_frame->linesize);
|
||||
sws_freeContext(sws_ctx);
|
||||
uint8_t *src_data[4];
|
||||
int src_linesize[4];
|
||||
uint8_t *dst_data[4];
|
||||
int dst_linesize[4];
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
src_data[i] = u16_frame->data(i);
|
||||
src_linesize[i] = u16_frame->linesize(i);
|
||||
dst_data[i] = u8_frame->data(i);
|
||||
dst_linesize[i] = u8_frame->linesize(i);
|
||||
}
|
||||
|
||||
fb_scaler_scale_slices(sws_ctx, src_data, src_linesize, height, dst_data,
|
||||
dst_linesize);
|
||||
fb_scaler_free(&sws_ctx);
|
||||
|
||||
// Verify conversion (U16 0xFFFF -> U8 0xFF, 0x8080 -> ~0x80, etc.)
|
||||
// Note: FFmpeg sws_scale has rounding offset, so values may be off by 1
|
||||
uint8_t *first_pixel = u8_frame->data[0];
|
||||
uint8_t *first_pixel = u8_frame->data(0);
|
||||
EXPECT_NEAR(first_pixel[0], 0xFF, 1); // R (255 vs 255)
|
||||
EXPECT_NEAR(first_pixel[1], 0x80, 1); // G (128 vs 129)
|
||||
EXPECT_NEAR(first_pixel[2], 0x40, 1); // B (64 vs 64)
|
||||
EXPECT_NEAR(first_pixel[3], 0x20, 1); // A (32 vs 32)
|
||||
}
|
||||
|
||||
// Test VideoParams to AVPixelFormat mapping
|
||||
// Test VideoParams to bridge pixel format mapping
|
||||
TEST(FormatConversion, VideoParamsToAVFormat)
|
||||
{
|
||||
// U8 RGBA
|
||||
VideoParams u8_rgba(320, 240, PixelFormat::U8, 4);
|
||||
AVPixelFormat fmt_u8_rgba = FFmpegUtils::GetFFmpegPixelFormat(
|
||||
int fmt_u8_rgba = FFmpegUtils::GetFFmpegPixelFormat(
|
||||
u8_rgba.format(), u8_rgba.channel_count());
|
||||
EXPECT_EQ(fmt_u8_rgba, AV_PIX_FMT_RGBA);
|
||||
EXPECT_EQ(fmt_u8_rgba, FB_PIX_FMT_RGBA);
|
||||
|
||||
// U16 RGBA
|
||||
VideoParams u16_rgba(320, 240, PixelFormat::U16, 4);
|
||||
AVPixelFormat fmt_u16_rgba = FFmpegUtils::GetFFmpegPixelFormat(
|
||||
int fmt_u16_rgba = FFmpegUtils::GetFFmpegPixelFormat(
|
||||
u16_rgba.format(), u16_rgba.channel_count());
|
||||
EXPECT_EQ(fmt_u16_rgba, AV_PIX_FMT_RGBA64);
|
||||
EXPECT_EQ(fmt_u16_rgba, FB_PIX_FMT_RGBA64LE);
|
||||
|
||||
// U8 RGB
|
||||
VideoParams u8_rgb(320, 240, PixelFormat::U8, 3);
|
||||
AVPixelFormat fmt_u8_rgb = FFmpegUtils::GetFFmpegPixelFormat(
|
||||
int fmt_u8_rgb = FFmpegUtils::GetFFmpegPixelFormat(
|
||||
u8_rgb.format(), u8_rgb.channel_count());
|
||||
EXPECT_EQ(fmt_u8_rgb, AV_PIX_FMT_RGB24);
|
||||
EXPECT_EQ(fmt_u8_rgb, FB_PIX_FMT_RGB24);
|
||||
|
||||
// U16 RGB
|
||||
VideoParams u16_rgb(320, 240, PixelFormat::U16, 3);
|
||||
AVPixelFormat fmt_u16_rgb = FFmpegUtils::GetFFmpegPixelFormat(
|
||||
int fmt_u16_rgb = FFmpegUtils::GetFFmpegPixelFormat(
|
||||
u16_rgb.format(), u16_rgb.channel_count());
|
||||
EXPECT_EQ(fmt_u16_rgb, AV_PIX_FMT_RGB48);
|
||||
EXPECT_EQ(fmt_u16_rgb, FB_PIX_FMT_RGB48LE);
|
||||
}
|
||||
|
||||
// Test row bytes calculation
|
||||
@@ -193,18 +199,18 @@ TEST(FormatConversion, LinesizeAlignment)
|
||||
const int height = 10;
|
||||
|
||||
AVFramePtr frame = CreateAVFramePtr();
|
||||
frame->width = width;
|
||||
frame->height = height;
|
||||
frame->format = AV_PIX_FMT_RGBA;
|
||||
frame->set_width(width);
|
||||
frame->set_height(height);
|
||||
frame->set_format(FB_PIX_FMT_RGBA);
|
||||
|
||||
ASSERT_EQ(av_frame_get_buffer(frame.get(), 0), 0);
|
||||
ASSERT_EQ(frame->get_buffer(0), 0);
|
||||
|
||||
// linesize[0] should be at least width * 4
|
||||
EXPECT_GE(frame->linesize[0], width * 4);
|
||||
EXPECT_GE(frame->linesize(0), width * 4);
|
||||
|
||||
// linesize may be larger due to alignment (typically 32-byte aligned)
|
||||
qDebug() << "Width:" << width << "Expected bytes:" << width * 4
|
||||
<< "Actual linesize:" << frame->linesize[0];
|
||||
<< "Actual linesize:" << frame->linesize(0);
|
||||
}
|
||||
|
||||
// Test loading actual image file
|
||||
@@ -216,34 +222,34 @@ TEST(FormatConversion, LoadImageFile)
|
||||
|
||||
AVFramePtr frame = CreateAVFramePtr();
|
||||
// Just create a simple test frame instead of loading an image
|
||||
frame->width = 1920;
|
||||
frame->height = 1080;
|
||||
frame->format = AV_PIX_FMT_RGBA;
|
||||
if (av_frame_get_buffer(frame.get(), 0) < 0) {
|
||||
frame->set_width(1920);
|
||||
frame->set_height(1080);
|
||||
frame->set_format(FB_PIX_FMT_RGBA);
|
||||
if (frame->get_buffer(0) < 0) {
|
||||
return;
|
||||
}
|
||||
// Fill with orange color (sunrise sky)
|
||||
for (int y = 0; y < frame->height; ++y) {
|
||||
uint8_t *row = frame->data[0] + y * frame->linesize[0];
|
||||
for (int y = 0; y < frame->height(); ++y) {
|
||||
uint8_t *row = frame->data(0) + y * frame->linesize(0);
|
||||
uint8_t r = 255;
|
||||
uint8_t g = 128 + (y * 127) / frame->height; // Gradient from 128 to 255
|
||||
uint8_t g = 128 + (y * 127) / frame->height(); // Gradient from 128 to 255
|
||||
uint8_t b = 64;
|
||||
uint8_t a = 255;
|
||||
for (int x = 0; x < frame->width; ++x) {
|
||||
for (int x = 0; x < frame->width(); ++x) {
|
||||
row[x * 4 + 0] = r;
|
||||
row[x * 4 + 1] = g;
|
||||
row[x * 4 + 2] = b;
|
||||
row[x * 4 + 3] = a;
|
||||
}
|
||||
}
|
||||
ASSERT_NE(frame->data[0], nullptr) << "Failed to create test frame";
|
||||
ASSERT_NE(frame->data(0), nullptr) << "Failed to create test frame";
|
||||
|
||||
EXPECT_EQ(frame->width, 1920);
|
||||
EXPECT_EQ(frame->height, 1080);
|
||||
EXPECT_EQ(frame->width(), 1920);
|
||||
EXPECT_EQ(frame->height(), 1080);
|
||||
|
||||
// Check first pixel (top-left corner of the sunrise image)
|
||||
// Based on the image, it should have some orange/pink color in the sky area
|
||||
uint8_t *first_pixel = frame->data[0];
|
||||
uint8_t *first_pixel = frame->data(0);
|
||||
qDebug() << "First pixel RGBA:" << first_pixel[0] << first_pixel[1]
|
||||
<< first_pixel[2] << first_pixel[3];
|
||||
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/frame.h>
|
||||
}
|
||||
|
||||
#include "common/ffmpegutils.h"
|
||||
#include "node/value.h"
|
||||
#include "pluginSupport/OliveHost.h"
|
||||
@@ -21,23 +17,23 @@ namespace
|
||||
olive::TexturePtr CreateSolidTexture(const olive::VideoParams ¶ms)
|
||||
{
|
||||
olive::AVFramePtr frame = olive::CreateAVFramePtr();
|
||||
frame->format = olive::FFmpegUtils::GetFFmpegPixelFormat(
|
||||
params.format(), params.channel_count());
|
||||
frame->width = params.width();
|
||||
frame->height = params.height();
|
||||
if (frame->format == AV_PIX_FMT_NONE) {
|
||||
frame->set_format(olive::FFmpegUtils::GetFFmpegPixelFormat(
|
||||
params.format(), params.channel_count()));
|
||||
frame->set_width(params.width());
|
||||
frame->set_height(params.height());
|
||||
if (frame->format() == FB_PIX_FMT_NONE) {
|
||||
return nullptr;
|
||||
}
|
||||
if (av_frame_get_buffer(frame.get(), 0) < 0) {
|
||||
if (frame->get_buffer(0) < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
if (av_frame_make_writable(frame.get()) < 0) {
|
||||
if (frame->make_writable() < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const int linesize = frame->linesize[0];
|
||||
for (int y = 0; y < frame->height; ++y) {
|
||||
std::memset(frame->data[0] + y * linesize, 0x7f, linesize);
|
||||
const int linesize = frame->linesize(0);
|
||||
for (int y = 0; y < frame->height(); ++y) {
|
||||
std::memset(frame->data(0) + y * linesize, 0x7f, linesize);
|
||||
}
|
||||
|
||||
olive::TexturePtr texture = std::make_shared<olive::Texture>(params);
|
||||
|
||||
@@ -14,10 +14,6 @@
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/frame.h>
|
||||
}
|
||||
|
||||
#include "common/ffmpegutils.h"
|
||||
#include "node/value.h"
|
||||
#include "pluginSupport/OliveHost.h"
|
||||
@@ -45,24 +41,24 @@ template <typename T>
|
||||
TexturePtr CreateSolidTextureT(const VideoParams ¶ms, T fill_value)
|
||||
{
|
||||
AVFramePtr frame = CreateAVFramePtr();
|
||||
frame->format = FFmpegUtils::GetFFmpegPixelFormat(params.format(),
|
||||
params.channel_count());
|
||||
frame->width = params.width();
|
||||
frame->height = params.height();
|
||||
if (frame->format == AV_PIX_FMT_NONE) {
|
||||
frame->set_format(FFmpegUtils::GetFFmpegPixelFormat(params.format(),
|
||||
params.channel_count()));
|
||||
frame->set_width(params.width());
|
||||
frame->set_height(params.height());
|
||||
if (frame->format() == FB_PIX_FMT_NONE) {
|
||||
return nullptr;
|
||||
}
|
||||
if (av_frame_get_buffer(frame.get(), 0) < 0) {
|
||||
if (frame->get_buffer(0) < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
if (av_frame_make_writable(frame.get()) < 0) {
|
||||
if (frame->make_writable() < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const int linesize = frame->linesize[0];
|
||||
for (int y = 0; y < frame->height; ++y) {
|
||||
T *row = reinterpret_cast<T *>(frame->data[0] + y * linesize);
|
||||
for (int x = 0; x < frame->width * params.channel_count(); ++x) {
|
||||
const int linesize = frame->linesize(0);
|
||||
for (int y = 0; y < frame->height(); ++y) {
|
||||
T *row = reinterpret_cast<T *>(frame->data(0) + y * linesize);
|
||||
for (int x = 0; x < frame->width() * params.channel_count(); ++x) {
|
||||
row[x] = fill_value;
|
||||
}
|
||||
}
|
||||
@@ -99,25 +95,25 @@ template <typename T>
|
||||
TexturePtr CreateGradientTextureT(const VideoParams ¶ms, float scale)
|
||||
{
|
||||
AVFramePtr frame = CreateAVFramePtr();
|
||||
frame->format = FFmpegUtils::GetFFmpegPixelFormat(params.format(),
|
||||
params.channel_count());
|
||||
frame->width = params.width();
|
||||
frame->height = params.height();
|
||||
if (frame->format == AV_PIX_FMT_NONE) {
|
||||
frame->set_format(FFmpegUtils::GetFFmpegPixelFormat(params.format(),
|
||||
params.channel_count()));
|
||||
frame->set_width(params.width());
|
||||
frame->set_height(params.height());
|
||||
if (frame->format() == FB_PIX_FMT_NONE) {
|
||||
return nullptr;
|
||||
}
|
||||
if (av_frame_get_buffer(frame.get(), 0) < 0) {
|
||||
if (frame->get_buffer(0) < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
if (av_frame_make_writable(frame.get()) < 0) {
|
||||
if (frame->make_writable() < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const int linesize = frame->linesize[0];
|
||||
for (int y = 0; y < frame->height; ++y) {
|
||||
T *row = reinterpret_cast<T *>(frame->data[0] + y * linesize);
|
||||
T value = static_cast<T>((y * scale) / frame->height);
|
||||
for (int x = 0; x < frame->width * params.channel_count(); ++x) {
|
||||
const int linesize = frame->linesize(0);
|
||||
for (int y = 0; y < frame->height(); ++y) {
|
||||
T *row = reinterpret_cast<T *>(frame->data(0) + y * linesize);
|
||||
T value = static_cast<T>((y * scale) / frame->height());
|
||||
for (int x = 0; x < frame->width() * params.channel_count(); ++x) {
|
||||
row[x] = value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,10 +43,6 @@
|
||||
#include "node/value.h"
|
||||
#include "common/ffmpegutils.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/frame.h>
|
||||
}
|
||||
|
||||
namespace olive
|
||||
{
|
||||
namespace plugin
|
||||
@@ -77,23 +73,23 @@ static TexturePtr CreateTestTexture(const VideoParams ¶ms,
|
||||
uint8_t fill_value = 0x7f)
|
||||
{
|
||||
AVFramePtr frame = CreateAVFramePtr();
|
||||
frame->format = FFmpegUtils::GetFFmpegPixelFormat(params.format(),
|
||||
params.channel_count());
|
||||
frame->width = params.width();
|
||||
frame->height = params.height();
|
||||
if (frame->format == AV_PIX_FMT_NONE) {
|
||||
frame->set_format(FFmpegUtils::GetFFmpegPixelFormat(params.format(),
|
||||
params.channel_count()));
|
||||
frame->set_width(params.width());
|
||||
frame->set_height(params.height());
|
||||
if (frame->format() == FB_PIX_FMT_NONE) {
|
||||
return nullptr;
|
||||
}
|
||||
if (av_frame_get_buffer(frame.get(), 0) < 0) {
|
||||
if (frame->get_buffer(0) < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
if (av_frame_make_writable(frame.get()) < 0) {
|
||||
if (frame->make_writable() < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const int linesize = frame->linesize[0];
|
||||
for (int y = 0; y < frame->height; ++y) {
|
||||
std::memset(frame->data[0] + y * linesize, fill_value, linesize);
|
||||
const int linesize = frame->linesize(0);
|
||||
for (int y = 0; y < frame->height(); ++y) {
|
||||
std::memset(frame->data(0) + y * linesize, fill_value, linesize);
|
||||
}
|
||||
|
||||
TexturePtr texture = std::make_shared<Texture>(params);
|
||||
|
||||
@@ -7,11 +7,11 @@ TEST(RenderAudioParams, ValidityAndEquality)
|
||||
olive::core::AudioParams invalid;
|
||||
EXPECT_FALSE(invalid.is_valid());
|
||||
|
||||
olive::core::AudioParams params(48000, AV_CH_LAYOUT_STEREO,
|
||||
olive::core::AudioParams params(48000, olive::core::kChannelLayoutStereo,
|
||||
olive::core::SampleFormat::S16);
|
||||
EXPECT_TRUE(params.is_valid());
|
||||
|
||||
olive::core::AudioParams other(48000, AV_CH_LAYOUT_STEREO,
|
||||
olive::core::AudioParams other(48000, olive::core::kChannelLayoutStereo,
|
||||
olive::core::SampleFormat::S16);
|
||||
EXPECT_TRUE(params == other);
|
||||
|
||||
@@ -21,7 +21,7 @@ TEST(RenderAudioParams, ValidityAndEquality)
|
||||
|
||||
TEST(RenderAudioParams, TimeAndSampleConversions)
|
||||
{
|
||||
olive::core::AudioParams params(48000, AV_CH_LAYOUT_STEREO,
|
||||
olive::core::AudioParams params(48000, olive::core::kChannelLayoutStereo,
|
||||
olive::core::SampleFormat::S16);
|
||||
|
||||
EXPECT_EQ(params.channel_count(), 2);
|
||||
@@ -43,33 +43,33 @@ TEST(RenderAudioParams, TimeAndSampleConversions)
|
||||
|
||||
TEST(RenderAudioParams, ChannelLayoutCount)
|
||||
{
|
||||
olive::core::AudioParams mono(48000, AV_CH_LAYOUT_MONO,
|
||||
olive::core::AudioParams mono(48000, olive::core::kChannelLayoutMono,
|
||||
olive::core::SampleFormat::F32);
|
||||
EXPECT_EQ(mono.channel_count(), 1);
|
||||
|
||||
olive::core::AudioParams surround(48000, AV_CH_LAYOUT_5POINT1,
|
||||
olive::core::AudioParams surround(48000, olive::core::kChannelLayout5Point1,
|
||||
olive::core::SampleFormat::F32);
|
||||
EXPECT_EQ(surround.channel_count(), 6);
|
||||
}
|
||||
|
||||
TEST(RenderAudioParams, SampleFormatSizes)
|
||||
{
|
||||
olive::core::AudioParams u8(48000, AV_CH_LAYOUT_MONO,
|
||||
olive::core::AudioParams u8(48000, olive::core::kChannelLayoutMono,
|
||||
olive::core::SampleFormat::U8);
|
||||
EXPECT_EQ(u8.bytes_per_sample_per_channel(), 1);
|
||||
|
||||
olive::core::AudioParams f32(48000, AV_CH_LAYOUT_MONO,
|
||||
olive::core::AudioParams f32(48000, olive::core::kChannelLayoutMono,
|
||||
olive::core::SampleFormat::F32);
|
||||
EXPECT_EQ(f32.bytes_per_sample_per_channel(), 4);
|
||||
|
||||
olive::core::AudioParams f64(48000, AV_CH_LAYOUT_MONO,
|
||||
olive::core::AudioParams f64(48000, olive::core::kChannelLayoutMono,
|
||||
olive::core::SampleFormat::F64);
|
||||
EXPECT_EQ(f64.bytes_per_sample_per_channel(), 8);
|
||||
}
|
||||
|
||||
TEST(RenderAudioParams, CopyAndAssignment)
|
||||
{
|
||||
olive::core::AudioParams params(96000, AV_CH_LAYOUT_STEREO,
|
||||
olive::core::AudioParams params(96000, olive::core::kChannelLayoutStereo,
|
||||
olive::core::SampleFormat::F32);
|
||||
|
||||
olive::core::AudioParams copy(params);
|
||||
@@ -85,7 +85,7 @@ TEST(RenderAudioParams, CopyAndAssignment)
|
||||
|
||||
TEST(RenderAudioParams, SettersModifyState)
|
||||
{
|
||||
olive::core::AudioParams params(44100, AV_CH_LAYOUT_MONO,
|
||||
olive::core::AudioParams params(44100, olive::core::kChannelLayoutMono,
|
||||
olive::core::SampleFormat::S16);
|
||||
EXPECT_TRUE(params.is_valid());
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/avutil.h>
|
||||
}
|
||||
#include <cstdint>
|
||||
|
||||
#include <QBuffer>
|
||||
#include <QXmlStreamReader>
|
||||
@@ -86,7 +84,7 @@ TEST(RenderVideoParams, ValidityAndTimebase)
|
||||
olive::VideoParams params;
|
||||
EXPECT_FALSE(params.is_valid());
|
||||
EXPECT_EQ(params.get_time_in_timebase_units(olive::core::rational(1, 1)),
|
||||
AV_NOPTS_VALUE);
|
||||
INT64_MIN /* AV_NOPTS_VALUE */);
|
||||
|
||||
params.set_width(1920);
|
||||
params.set_height(1080);
|
||||
|
||||
@@ -7,10 +7,6 @@
|
||||
|
||||
#include <cmath>
|
||||
|
||||
extern "C" {
|
||||
#include <libavutil/channel_layout.h>
|
||||
}
|
||||
|
||||
#include "audio/audiovisualwaveform.h"
|
||||
#include "node/block/clip/clip.h"
|
||||
#include "node/node.h"
|
||||
@@ -29,7 +25,7 @@ namespace
|
||||
|
||||
AudioParams MakeMonoParams(int sample_rate)
|
||||
{
|
||||
return AudioParams(sample_rate, static_cast<uint64_t>(AV_CH_LAYOUT_MONO),
|
||||
return AudioParams(sample_rate, static_cast<uint64_t>(kChannelLayoutMono),
|
||||
SampleFormat::F32P);
|
||||
}
|
||||
|
||||
|
||||
@@ -323,7 +323,7 @@ TEST(ViewerSmokeAudioCache, ParameterSetters)
|
||||
{
|
||||
AudioPlaybackCache cache;
|
||||
|
||||
AudioParams params(48000, AV_CH_LAYOUT_STEREO, SampleFormat::F32P);
|
||||
AudioParams params(48000, kChannelLayoutStereo, SampleFormat::F32P);
|
||||
cache.SetParameters(params);
|
||||
|
||||
// Parameters should be retrievable
|
||||
|
||||
Reference in New Issue
Block a user