From e1d3019659b608c0f636222709d17e6b9fa215ca Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Wed, 15 Jul 2026 23:00:13 +0800 Subject: [PATCH] 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) --- tests/gtest/CMakeLists.txt | 1 + tests/gtest/audio_level_meter_test.cpp | 6 +- tests/gtest/audio_smoke_test.cpp | 90 +- tests/gtest/audio_waveform_sync_test.cpp | 6 +- tests/gtest/common_ffmpegutils_test.cpp | 121 +-- tests/gtest/core_rational_test.cpp | 7 - tests/gtest/core_samplebuffer_test.cpp | 2 +- tests/gtest/ffmpeg_bridge_test.cpp | 855 ++++++++++++++++++ tests/gtest/module_smoke_test.cpp | 4 +- tests/gtest/node_globals_test.cpp | 2 +- tests/gtest/plugin_format_conversion_test.cpp | 120 +-- tests/gtest/plugin_ofx_integration_test.cpp | 24 +- tests/gtest/plugin_ofx_misc_test.cpp | 50 +- tests/gtest/plugin_smoke_test.cpp | 24 +- .../gtest/render_audioparams_branch_test.cpp | 20 +- .../gtest/render_videoparams_branch_test.cpp | 6 +- tests/gtest/timeline_waveform_sync_test.cpp | 6 +- tests/gtest/viewer_smoke_test.cpp | 2 +- 18 files changed, 1074 insertions(+), 272 deletions(-) create mode 100644 tests/gtest/ffmpeg_bridge_test.cpp diff --git a/tests/gtest/CMakeLists.txt b/tests/gtest/CMakeLists.txt index 67b887b41..c946f54dc 100644 --- a/tests/gtest/CMakeLists.txt +++ b/tests/gtest/CMakeLists.txt @@ -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 diff --git a/tests/gtest/audio_level_meter_test.cpp b/tests/gtest/audio_level_meter_test.cpp index 740f7cd6b..18945f1c3 100644 --- a/tests/gtest/audio_level_meter_test.cpp +++ b/tests/gtest/audio_level_meter_test.cpp @@ -7,16 +7,12 @@ #include "olive/core/render/samplebuffer.h" #include "olive/core/render/sampleformat.h" -extern "C" { -#include -} - 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); } diff --git a/tests/gtest/audio_smoke_test.cpp b/tests/gtest/audio_smoke_test.cpp index 13206635c..5a49e2436 100644 --- a/tests/gtest/audio_smoke_test.cpp +++ b/tests/gtest/audio_smoke_test.cpp @@ -26,10 +26,6 @@ #include "olive/core/render/audioparams.h" #include "olive/core/render/sampleformat.h" -extern "C" { -#include -} - 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); diff --git a/tests/gtest/audio_waveform_sync_test.cpp b/tests/gtest/audio_waveform_sync_test.cpp index e6ebb4c98..26acc60ec 100644 --- a/tests/gtest/audio_waveform_sync_test.cpp +++ b/tests/gtest/audio_waveform_sync_test.cpp @@ -5,16 +5,12 @@ #include "olive/core/render/samplebuffer.h" #include "olive/core/render/sampleformat.h" -extern "C" { -#include -} - 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); } diff --git a/tests/gtest/common_ffmpegutils_test.cpp b/tests/gtest/common_ffmpegutils_test.cpp index 5eb4c67d9..5ac6e974c 100644 --- a/tests/gtest/common_ffmpegutils_test.cpp +++ b/tests/gtest/common_ffmpegutils_test.cpp @@ -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); } diff --git a/tests/gtest/core_rational_test.cpp b/tests/gtest/core_rational_test.cpp index aaffe0f14..98ee9e549 100644 --- a/tests/gtest/core_rational_test.cpp +++ b/tests/gtest/core_rational_test.cpp @@ -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); diff --git a/tests/gtest/core_samplebuffer_test.cpp b/tests/gtest/core_samplebuffer_test.cpp index ea6808fb0..de67911bc 100644 --- a/tests/gtest/core_samplebuffer_test.cpp +++ b/tests/gtest/core_samplebuffer_test.cpp @@ -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; } diff --git a/tests/gtest/ffmpeg_bridge_test.cpp b/tests/gtest/ffmpeg_bridge_test.cpp new file mode 100644 index 000000000..3e309881e --- /dev/null +++ b/tests/gtest/ffmpeg_bridge_test.cpp @@ -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 + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#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 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(in_left.data()), + reinterpret_cast(in_right.data()) + }; + + std::vector out_left(out_capacity), out_right(out_capacity); + uint8_t *out_planes[2] = { + reinterpret_cast(out_left.data()), + reinterpret_cast(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(fb_frame_get_data(frame, 0)); + float *right = reinterpret_cast(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 out_left(out_capacity), out_right(out_capacity); + uint8_t *out_planes[2] = { + reinterpret_cast(out_left.data()), + reinterpret_cast(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(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 left(in_samples, 0.5f), right(in_samples, 0.5f); + const uint8_t *planes[2] = { + reinterpret_cast(left.data()), + reinterpret_cast(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 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(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 left(sample_count), right(sample_count); + for (int i = 0; i < sample_count; ++i) { + left[i] = static_cast( + 10000 * std::sin(2.0 * kPi * 440.0 * i / 44100.0)); + right[i] = left[i]; + } + const uint8_t *planes[2] = { + reinterpret_cast(left.data()), + reinterpret_cast(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); +} diff --git a/tests/gtest/module_smoke_test.cpp b/tests/gtest/module_smoke_test.cpp index 8c8295340..7b3e01fc0 100644 --- a/tests/gtest/module_smoke_test.cpp +++ b/tests/gtest/module_smoke_test.cpp @@ -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()); } diff --git a/tests/gtest/node_globals_test.cpp b/tests/gtest/node_globals_test.cpp index 708d237c4..fbde9ac09 100644 --- a/tests/gtest/node_globals_test.cpp +++ b/tests/gtest/node_globals_test.cpp @@ -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)); diff --git a/tests/gtest/plugin_format_conversion_test.cpp b/tests/gtest/plugin_format_conversion_test.cpp index d550d6e73..073a65e94 100644 --- a/tests/gtest/plugin_format_conversion_test.cpp +++ b/tests/gtest/plugin_format_conversion_test.cpp @@ -7,11 +7,6 @@ #include #include -extern "C" { -#include -#include -} - #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( - frame->data[0] + y * frame->linesize[0]); + uint16_t *row = reinterpret_cast(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(u16_frame->data[0]); + reinterpret_cast(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]; diff --git a/tests/gtest/plugin_ofx_integration_test.cpp b/tests/gtest/plugin_ofx_integration_test.cpp index 55d386a3c..321f0b54a 100644 --- a/tests/gtest/plugin_ofx_integration_test.cpp +++ b/tests/gtest/plugin_ofx_integration_test.cpp @@ -2,10 +2,6 @@ #include -extern "C" { -#include -} - #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(params); diff --git a/tests/gtest/plugin_ofx_misc_test.cpp b/tests/gtest/plugin_ofx_misc_test.cpp index c6461beb6..d636cfec0 100644 --- a/tests/gtest/plugin_ofx_misc_test.cpp +++ b/tests/gtest/plugin_ofx_misc_test.cpp @@ -14,10 +14,6 @@ #include -extern "C" { -#include -} - #include "common/ffmpegutils.h" #include "node/value.h" #include "pluginSupport/OliveHost.h" @@ -45,24 +41,24 @@ template 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(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(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 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(frame->data[0] + y * linesize); - T value = static_cast((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(frame->data(0) + y * linesize); + T value = static_cast((y * scale) / frame->height()); + for (int x = 0; x < frame->width() * params.channel_count(); ++x) { row[x] = value; } } diff --git a/tests/gtest/plugin_smoke_test.cpp b/tests/gtest/plugin_smoke_test.cpp index 588b7ee05..1ae28db2c 100644 --- a/tests/gtest/plugin_smoke_test.cpp +++ b/tests/gtest/plugin_smoke_test.cpp @@ -43,10 +43,6 @@ #include "node/value.h" #include "common/ffmpegutils.h" -extern "C" { -#include -} - 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(params); diff --git a/tests/gtest/render_audioparams_branch_test.cpp b/tests/gtest/render_audioparams_branch_test.cpp index e7716cd90..c5b6d4f7b 100644 --- a/tests/gtest/render_audioparams_branch_test.cpp +++ b/tests/gtest/render_audioparams_branch_test.cpp @@ -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()); diff --git a/tests/gtest/render_videoparams_branch_test.cpp b/tests/gtest/render_videoparams_branch_test.cpp index a4016d673..1b2780ef5 100644 --- a/tests/gtest/render_videoparams_branch_test.cpp +++ b/tests/gtest/render_videoparams_branch_test.cpp @@ -1,8 +1,6 @@ #include -extern "C" { -#include -} +#include #include #include @@ -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); diff --git a/tests/gtest/timeline_waveform_sync_test.cpp b/tests/gtest/timeline_waveform_sync_test.cpp index 56a50cd51..76e241992 100644 --- a/tests/gtest/timeline_waveform_sync_test.cpp +++ b/tests/gtest/timeline_waveform_sync_test.cpp @@ -7,10 +7,6 @@ #include -extern "C" { -#include -} - #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(AV_CH_LAYOUT_MONO), + return AudioParams(sample_rate, static_cast(kChannelLayoutMono), SampleFormat::F32P); } diff --git a/tests/gtest/viewer_smoke_test.cpp b/tests/gtest/viewer_smoke_test.cpp index 7b80ec972..2d8b65c84 100644 --- a/tests/gtest/viewer_smoke_test.cpp +++ b/tests/gtest/viewer_smoke_test.cpp @@ -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