Skip to content

Commit

Permalink
test: add tests for gpacmp4mx
Browse files Browse the repository at this point in the history
Tests cross-validate agains cmafmux
  • Loading branch information
DenizUgur committed Jan 31, 2025
1 parent 0cc8b61 commit 50c453d
Show file tree
Hide file tree
Showing 8 changed files with 542 additions and 7 deletions.
19 changes: 19 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,25 @@
"targetArchitecture": "arm64",
"internalConsoleOptions": "openOnSessionStart"
}
},
{
"name": "Run tests",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/build/tests/gstgpacplugin_test",
"preLaunchTask": "build",
"cwd": "/tmp",
"environment": [
{
"name": "GST_PLUGIN_PATH",
"value": "${workspaceFolder}/build"
}
],
"osx": {
"MIMode": "lldb",
"targetArchitecture": "arm64",
"internalConsoleOptions": "openOnSessionStart"
}
}
]
}
2 changes: 0 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
{
"python.terminal.activateEnvironment": true,
"python.defaultInterpreterPath": "./testsuite/.venv/bin/python",
"[c]": {
"editor.defaultFormatter": "xaver.clang-format"
},
Expand Down
4 changes: 1 addition & 3 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@
{
"label": "test",
"type": "shell",
"command": "${command:python.interpreterPath}",
"args": ["./test_runner.py", "--backend", "gst", "--level", "detailed"],
"command": "${workspaceFolder}/build/tests/gstgpacplugin_test",
"options": {
"cwd": "${workspaceFolder}/testsuite",
"env": {
"GST_DEBUG": "3",
"GST_PLUGIN_PATH": "${workspaceFolder}/build"
Expand Down
27 changes: 25 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.16)
project(gpac_plugin VERSION 0.1.0 LANGUAGES C) # x-release-please-version

# Options
option(ENABLE_TESTS "Enable and build tests" OFF)

# Set default build type to Release
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type (e.g., Debug, Release)" FORCE)
endif()

# Define allowed values for CMAKE_BUILD_TYPE
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS Debug Release)

# Source
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
file(GLOB_RECURSE SOURCES
Expand All @@ -19,7 +30,6 @@ file(GLOB_RECURSE SOURCES

add_library(${PROJECT_NAME} SHARED ${SOURCES})
target_compile_options(${PROJECT_NAME} PRIVATE
-g
-Wall -Wextra -Werror
-Wcast-align
-Wno-unused-parameter
Expand All @@ -28,6 +38,14 @@ target_compile_options(${PROJECT_NAME} PRIVATE
-Wno-cast-function-type
)

if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
message(STATUS "Building in Debug mode")
target_compile_options(${PROJECT_NAME} PRIVATE -g)
else()
message(STATUS "Building in Release mode")
target_compile_options(${PROJECT_NAME} PRIVATE -O3)
endif()

# GStreamer
find_package(PkgConfig REQUIRED)
pkg_check_modules(GSTREAMER REQUIRED gstreamer-1.0)
Expand Down Expand Up @@ -62,3 +80,8 @@ if(GST_LAUNCH_EXECUTABLE)
# Define a custom target that uses this command
add_custom_target(create_gst_link ALL DEPENDS ${CMAKE_BINARY_DIR}/gst-launch)
endif()

# Tests
if(ENABLE_TESTS)
add_subdirectory(tests)
endif()
50 changes: 50 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Configure test project
project(gstgpacplugin_test LANGUAGES CXX)
enable_testing()

# GoogleTest requires at least C++14
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Avoid warning about DOWNLOAD_EXTRACT_TIMESTAMP in CMake 3.24:
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24.0")
cmake_policy(SET CMP0135 NEW)
endif()

# Fetch GoogleTest
include(FetchContent)
FetchContent_Declare(
googletest
URL https://github.com/google/googletest/archive/2b6b042a77446ff322cd7522ca068d9f2a21c1d1.zip
DOWNLOAD_EXTRACT_TIMESTAMP
)

# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

# Sources
file(GLOB_RECURSE SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/*.cc
${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp
)

# Test executable
add_executable(${PROJECT_NAME} ${SOURCES})
target_compile_options(${PROJECT_NAME} PRIVATE
-g
-Wall -Wextra -Werror
-Wcast-align
-Wno-unused-parameter
-Wno-unused-variable
-Wno-missing-field-initializers
-Wno-cast-function-type
)

# Link libraries
include_directories(${GSTREAMER_INCLUDE_DIRS} ${GSTREAMER_BASE_INCLUDE_DIRS})
target_link_libraries(${PROJECT_NAME} GTest::gtest_main ${GSTREAMER_LIBRARIES} ${GSTREAMER_BASE_LIBRARIES})

# Discover tests
include(GoogleTest)
gtest_discover_tests(${PROJECT_NAME})
23 changes: 23 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Tests

## Setup

From the root of the repository, configure the project to build the tests:

```bash
cmake -S . -B build -DENABLE_TESTS=ON
cmake --build build
```

Some tests require [`gst-plugin-fmp4`](https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs) to be built and installed on your system. Follow the instructions in the repository to build and install the plugin. Specifically, the `cmafmux` element is used to cross-validate the output of the `gpacmp4mx` element.

> [!NOTE]
> GStreamer documentation describes the plugin search path in detail. You can refer to the [GStreamer documentation](https://gstreamer.freedesktop.org/documentation/gstreamer/gstregistry.html?gi-language=c) to understand how GStreamer searches for plugins.
## Running tests

To run the tests, execute the following command:

```bash
./build/tests/gstgpacplugin_test
```
182 changes: 182 additions & 0 deletions tests/src/common.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
#pragma once

#include <gst/gst.h>
#include <gtest/gtest.h>
#include <thread>

class GstAppSink
{
private:
std::mutex buffer_queue_mutex;
std::condition_variable buffer_queue_cv;
GQueue* buffer_queue;
GstElement* appsink;

protected:
static GstFlowReturn NewSample(GstElement* sink, GstAppSink* self)
{
GstSample* sample;
g_signal_emit_by_name(sink, "pull-sample", &sample);
if (sample == NULL)
return GST_FLOW_ERROR;

// Push the sample into the buffer queue
GstBufferList* buffer = gst_sample_get_buffer_list(sample);
gst_buffer_list_ref(buffer);
g_queue_push_tail(self->buffer_queue, buffer);
gst_sample_unref(sample);
self->buffer_queue_cv.notify_one();
return GST_FLOW_OK;
}

public:
GstAppSink(GstElement* test_element, GstElement* tee, GstElement* pipeline)
{
// Create the elements
GstElement* queue = gst_element_factory_make("queue", NULL);
appsink = gst_element_factory_make("appsink", NULL);
if (!queue || !appsink) {
g_error("Failed to create elements");
return;
}

// Set the appsink properties
g_object_set(
appsink, "emit-signals", TRUE, "sync", FALSE, "buffer-list", TRUE, NULL);
g_signal_connect(appsink, "new-sample", G_CALLBACK(NewSample), this);
buffer_queue = g_queue_new();

// Add the appsink to the test element
gst_bin_add_many(GST_BIN(pipeline), queue, test_element, appsink, NULL);

// Link the elements
if (!gst_element_link(tee, queue) ||
!gst_element_link(queue, test_element) ||
!gst_element_link(test_element, appsink)) {
g_error("Failed to link elements");
return;
}
}

GstBufferList* PopBuffer()
{
// Wait until we have at least one buffer in the queue
std::unique_lock<std::mutex> lock(buffer_queue_mutex);
buffer_queue_cv.wait(lock, [&]() {
gboolean eos = false;
g_object_get(G_OBJECT(this->appsink), "eos", &eos, NULL);
if (eos)
return true;
return !g_queue_is_empty(this->buffer_queue);
});

return (GstBufferList*)g_queue_pop_head(buffer_queue);
}

~GstAppSink()
{
// Flush the buffer queue
while (!g_queue_is_empty(buffer_queue)) {
GstBufferList* buffer = (GstBufferList*)g_queue_pop_head(buffer_queue);
gst_buffer_list_unref(buffer);
}

// Free resources
gst_object_unref(appsink);
g_queue_free(buffer_queue);
}

int GetBufferCount() { return g_queue_get_length(buffer_queue); }
};

class GstTestFixture : public ::testing::Test
{
protected:
GstElement* tee;
GstElement* pipeline;

static void SetUpTestSuite() { gst_init(NULL, NULL); }
static void TearDownTestSuite() { gst_deinit(); }

void SetUp() override
{
GstElement* source = gst_element_factory_make_full(
"videotestsrc", "num-buffers", 60, "do-timestamp", TRUE, NULL);
GstElement* capsfilter = gst_element_factory_make_full(
"capsfilter",
"caps",
gst_caps_from_string("video/x-raw, framerate=30/1"),
NULL);
GstElement* encoder =
gst_element_factory_make_full("x264enc", "key-int-max", 30, NULL);
tee = gst_element_factory_make("tee", NULL);

// Create the pipeline
pipeline = gst_pipeline_new("test-pipeline");
if (!pipeline || !source || !capsfilter || !encoder || !tee) {
g_error("Failed to create elements");
return;
}

// Build the pipeline
gst_bin_add_many(GST_BIN(pipeline), source, capsfilter, encoder, tee, NULL);
if (!gst_element_link(source, capsfilter) ||
!gst_element_link(capsfilter, encoder) ||
!gst_element_link(encoder, tee)) {
g_error("Failed to link elements");
return;
}
}

void TearDown() override
{
// Wait until error or EOS
GstBus* bus = gst_element_get_bus(pipeline);
GstMessage* msg = gst_bus_timed_pop_filtered(
bus,
GST_CLOCK_TIME_NONE,
(GstMessageType)(GST_MESSAGE_ERROR | GST_MESSAGE_EOS));

// Parse message
if (msg != NULL) {
GError* err;
gchar* debug_info;

switch (GST_MESSAGE_TYPE(msg)) {
case GST_MESSAGE_ERROR:
gst_message_parse_error(msg, &err, &debug_info);
g_printerr("Error received from element %s: %s\n",
GST_OBJECT_NAME(msg->src),
err->message);
g_printerr("Debugging information: %s\n",
debug_info ? debug_info : "none");
g_clear_error(&err);
g_free(debug_info);
break;
case GST_MESSAGE_EOS:
break;
default:
// We should not reach here because we only asked for ERRORs and EOS
g_printerr("Unexpected message received.\n");
break;
}
gst_message_unref(msg);
}

// Free resources
gst_object_unref(bus);
gst_element_set_state(pipeline, GST_STATE_NULL);
gst_object_unref(pipeline);
}

bool StartPipeline()
{
GstStateChangeReturn ret =
gst_element_set_state(pipeline, GST_STATE_PLAYING);
if (ret == GST_STATE_CHANGE_FAILURE) {
g_error("Failed to start pipeline");
return false;
}
return true;
}
};
Loading

0 comments on commit 50c453d

Please sign in to comment.