diff --git a/CMakeLists.txt b/CMakeLists.txt index d5bc6d7..0c2f5bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required (VERSION 3.9) +include(CMakePrintHelpers) set(FFMPEG_NUGET_NAME "ffmpeg-lgpl") set(FFMPEG_NUGET_VERSION "6.1.0.2") @@ -19,6 +20,12 @@ endif(NOT WIN32) file(READ "VERSION" PROJECT_FILE_VERSION) project(Griffeye.VideoProcessor.Native VERSION ${PROJECT_FILE_VERSION} DESCRIPTION "Video frame extraction library using ffmpeg") +option(BUILD_EXAMPLES "build examples" OFF) + +if(CMAKE_BUILD_TYPE STREQUAL "") + set(CMAKE_BUILD_TYPE "Debug") +endif() + add_library(${PROJECT_NAME} SHARED src/libvx.c src/filter.c @@ -42,16 +49,15 @@ if(WIN32) PROPERTY VS_PACKAGE_REFERENCES "${FFMPEG_NUGET_NAME}_${FFMPEG_NUGET_VERSION}" ) else() - string(TOLOWER ${CMAKE_BUILD_TYPE} build_type) + string(TOLOWER ${CMAKE_BUILD_TYPE} BUILD_TYPE) SET(CMAKE_FIND_LIBRARY_SUFFIXES ".a") target_include_directories(${PROJECT_NAME} PUBLIC - "${CMAKE_CURRENT_SOURCE_DIR}/Packages/${FFMPEG_NUGET_NAME}/${FFMPEG_NUGET_VERSION}/build/native/Unix/include/x64" + "${CMAKE_CURRENT_SOURCE_DIR}/Packages/${FFMPEG_NUGET_NAME}/${FFMPEG_NUGET_VERSION}/build/native/Unix/include/x64" ) # Assumes FFMpeg NuGet package have been explicitly/externally restored to ${CMAKE_CURRENT_SOURCE_DIR}/Packages - set(FFMPEG_LIB_PATH "${CMAKE_CURRENT_SOURCE_DIR}/Packages/${FFMPEG_NUGET_NAME}/${FFMPEG_NUGET_VERSION}/build/native/Unix/lib/${build_type}/x64") - include(CMakePrintHelpers) + set(FFMPEG_LIB_PATH "${CMAKE_CURRENT_SOURCE_DIR}/Packages/${FFMPEG_NUGET_NAME}/${FFMPEG_NUGET_VERSION}/build/native/Unix/lib/${BUILD_TYPE}/x64") cmake_print_variables(FFMPEG_LIB_PATH) find_library(AVUTIL_LIBRARY NAMES avutil PATHS ${FFMPEG_LIB_PATH} REQUIRED NO_DEFAULT_PATH NO_CACHE) @@ -65,11 +71,10 @@ else() find_library(PKGCONF_LIBRARY NAMES pkgconf PATHS ${FFMPEG_LIB_PATH} REQUIRED NO_DEFAULT_PATH NO_CACHE) find_library(DAV1D_LIBRARY NAMES dav1d PATHS ${FFMPEG_LIB_PATH} REQUIRED NO_DEFAULT_PATH NO_CACHE) find_library(Z_LIBRARY NAMES z PATHS ${FFMPEG_LIB_PATH} REQUIRED NO_DEFAULT_PATH NO_CACHE) - set(FFMPEG_LIBS ${AVDEVICE_LIBRARY} ${AVFILTER_LIBRARY} ${AVFORMAT_LIBRARY} ${AVCODEC_LIBRARY} ${SWRESAMPLE_LIBRARY} ${SWSCALE_LIBRARY} ${AVUTIL_LIBRARY} ${VPX_LIBRARY} ${PKGCONF_LIBRARY} ${DAV1D_LIBRARY} ${Z_LIBRARY}) - cmake_print_variables(FFMPEG_LIBS) - + + # Provide source code for gdb set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fdebug-prefix-map=/build/build_linux_debug=/usr/src/griffeye.videoprocessor") set(CMAKE_SHARED_LINKER_FLAGS "-Wl,-Bsymbolic") @@ -82,6 +87,9 @@ set_target_properties(${PROJECT_NAME} PROPERTIES PUBLIC_HEADER include/libvx.h) # Include sub-projects. add_subdirectory("src") +if (BUILD_EXAMPLES) + add_subdirectory("examples") +endif() # Install library -install(TARGETS ${PROJECT_NAME} DESTINATION lib/${PROJECT_NAME}) +install(TARGETS ${PROJECT_NAME} DESTINATION lib/${PROJECT_NAME}) \ No newline at end of file diff --git a/README.md b/README.md index e55cb7d..c7059a3 100644 --- a/README.md +++ b/README.md @@ -23,10 +23,21 @@ Need to have dotnet CLI installed and some build utils `RUN apt update && DEBIAN_FRONTEND=noninteractive apt install -y build-essential cmake zip` ``` -dotnet restore Griffeye.VideoProcessor.Native.csproj --packages Packages +dotnet restore Griffeye.VideoProcessor.Native.csproj --configfile NuGet.Config --packages Packages cmake -S . -B build_linux/ cmake --build build_linux/ --config [Release|Debug] +``` +### Examples +Projects from the `/examples` directory can be built by adding `-DBUILD_EXAMPLES=ON` to the above cmake command: +``` +cmake -S . -B build_linux/ -DBUILD_EXAMPLES=ON +``` + +### Testing +The project can be run with Valgrind via the CLI example project: +``` +valgrind --leak-check=full libvxcli video.mp4 ``` ## Architecture diff --git a/VERSION b/VERSION index afaf360..e6d5cb8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.0 \ No newline at end of file +1.0.2 \ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..03c5747 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required (VERSION 3.9) + +set(TARGET libvxcli) + +include_directories("${CMAKE_SOURCE_DIR}/src") + +add_executable(${TARGET} cli.c) + +target_link_libraries(${TARGET} PRIVATE ${FFMPEG_LIBS}) +target_link_libraries(${TARGET} PRIVATE Griffeye.VideoProcessor.Native) +target_link_libraries(${TARGET} PRIVATE m) \ No newline at end of file diff --git a/examples/cli.c b/examples/cli.c new file mode 100644 index 0000000..8ae6f7b --- /dev/null +++ b/examples/cli.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "libvx.h" + +int main(int argc, char* argv[]) { + int result = 1; + char* video_path = argc > 1 ? argv[1] : ""; + vx_video* video = NULL; + vx_video_info* video_info = calloc(1, sizeof(vx_video_info)); + vx_frame_info* frame_info = calloc(1, sizeof(vx_frame_info)); + vx_frame* frame = NULL; + + vx_audio_params audio_params = { + .channels = 2, + .sample_format = VX_SAMPLE_FMT_FLT, + .sample_rate = 44100 + }; + const vx_video_options options = { + .audio_params = audio_params, + .autorotate = true, + .crop_area = {0}, + .hw_criteria = VX_HW_ACCEL_ALL, + .scene_threshold = 0.5 + }; + + if (vx_open(video_path, options, &video, video_info) != VX_ERR_SUCCESS) { + printf("Failed to open video: %s\n", video_path); + goto cleanup; + } + + // Initialize an empty frame that will be reused throughout the video + frame = vx_frame_create(video, video_info->width, video_info->height, VX_PIX_FMT_RGB32); + if (!frame) { + printf("Failed to create frame\n"); + goto cleanup; + } + + // Iterate over all frames in the video. The frame is decoded when stepping + while (vx_frame_step(video, frame_info) == VX_ERR_SUCCESS) { + bool is_video_frame = frame_info->flags & VX_FF_HAS_IMAGE; + + printf("Frame: %ld, %dx%d\n", video->frame_count, frame_info->width, frame_info->height); + + // Perform processing and tranfer from GPU to CPU if necessary + if (vx_frame_transfer_data(video, frame) != VX_ERR_SUCCESS) { + printf("Failed to transfer frame data\n"); + goto cleanup; + } + else { + printf("Frame type: %s\n", is_video_frame ? "video" : "audio"); + // The frame image could be accessed via frame->buffer + // Alternatively for audio frames, the audio samples could be accessed via frame->audio_data + } + } + + result = 0; + +cleanup: + vx_close(video); + if (video_info) { + free(video_info); + } + if (frame) { + vx_frame_destroy(frame); + } + if (frame_info) { + free(frame_info); + } + + return result; +} \ No newline at end of file diff --git a/src/filtergraph.h b/src/filtergraph.h index c6768d8..5660810 100644 --- a/src/filtergraph.h +++ b/src/filtergraph.h @@ -6,7 +6,7 @@ extern "C" { #endif vx_error vx_filtergraph_init(struct AVFilterGraph** filter_graph, enum AVMediaType type, const char* args); - vx_error vx_filtergraph_configure(struct AVFilterGraph** filter_graph, enum AVMediaType mediaType, struct AVFilterContext** last_filter, int* pad_index); + vx_error vx_filtergraph_configure(struct AVFilterGraph** filter_graph, enum AVMediaType type, struct AVFilterContext** last_filter, int* pad_index); vx_error vx_filtergraph_insert_filter(struct AVFilterContext** last_filter, int* pad_index, const char* filter_name, const char* filter_label, const char* args); vx_error vx_filtergraph_process_frame(struct AVFilterGraph** filter_graph, AVFrame* av_frame); diff --git a/src/libvx.c b/src/libvx.c index fa7aff6..3246628 100644 --- a/src/libvx.c +++ b/src/libvx.c @@ -31,69 +31,32 @@ static bool initialized = false; static vx_log_callback log_cb = NULL; -struct vx_audio_info -{ - double peak_level; - double rms_level; - double rms_peak; -}; - -struct vx_scene_info -{ - double difference; - double scene_score; - bool new_scene; -}; - -struct vx_frame -{ - int width; - int height; - vx_pix_fmt pix_fmt; - int sample_count; - int max_samples; - - vx_audio_info audio_info; - vx_scene_info scene_info; - - uint8_t** audio_buffer; - void* buffer; -}; - -struct vx_frame_info -{ - int width; - int height; - double timestamp; - vx_frame_flag flags; -}; - static vx_log_level av_to_vx_log_level(const int level) { // See: lavu_log_constants switch (level) { - case AV_LOG_QUIET: - return VX_LOG_NONE; + case AV_LOG_QUIET: + return VX_LOG_NONE; - case AV_LOG_PANIC: - case AV_LOG_FATAL: - return VX_LOG_FATAL; + case AV_LOG_PANIC: + case AV_LOG_FATAL: + return VX_LOG_FATAL; - case AV_LOG_ERROR: - return VX_LOG_ERROR; + case AV_LOG_ERROR: + return VX_LOG_ERROR; - case AV_LOG_WARNING: - return VX_LOG_WARNING; + case AV_LOG_WARNING: + return VX_LOG_WARNING; - case AV_LOG_INFO: - case AV_LOG_VERBOSE: - return VX_LOG_INFO; + case AV_LOG_INFO: + case AV_LOG_VERBOSE: + return VX_LOG_INFO; - case AV_LOG_DEBUG: - return VX_LOG_DEBUG; + case AV_LOG_DEBUG: + return VX_LOG_DEBUG; - default: - return VX_LOG_NONE; + default: + return VX_LOG_NONE; } } @@ -558,13 +521,22 @@ void vx_close(vx_video* video) swr_free(&video->swr_ctx); if (video->fmt_ctx) - avformat_free_context(video->fmt_ctx); + avformat_close_input(&video->fmt_ctx); for (int i = 0; i < video->frame_queue_count; i++) { av_frame_unref(video->frame_queue[i]); av_frame_free(&video->frame_queue[i]); } + if (video->hw_device_ctx) + av_buffer_unref(&video->hw_device_ctx); + + if (video->audio_codec_ctx) + avcodec_free_context(&video->audio_codec_ctx); + + if (video->video_codec_ctx) + avcodec_free_context(&video->video_codec_ctx); + free(video); } diff --git a/src/libvx.h b/src/libvx.h index 0f0c94c..63c757c 100644 --- a/src/libvx.h +++ b/src/libvx.h @@ -6,13 +6,13 @@ extern "C" { #endif #ifdef _MSC_VER - // Microsoft +// Microsoft #define VX_DECLSPEC __declspec(dllexport) #define VX_CDECL __cdecl #else - // GCC +// GCC #define VX_DECLSPEC __attribute__((visibility("default"))) -#define VX_CDECL __attribute__((__cdecl__)) +#define VX_CDECL #endif #define FRAME_QUEUE_SIZE 32 @@ -120,6 +120,43 @@ struct av_audio_params AVRational time_base; }; +struct vx_audio_info +{ + double peak_level; + double rms_level; + double rms_peak; +}; + +struct vx_scene_info +{ + double difference; + double scene_score; + bool new_scene; +}; + +struct vx_frame_info +{ + int width; + int height; + double timestamp; + vx_frame_flag flags; +}; + +struct vx_frame +{ + int width; + int height; + vx_pix_fmt pix_fmt; + int sample_count; + int max_samples; + + vx_audio_info audio_info; + vx_scene_info scene_info; + + uint8_t** audio_buffer; + void* buffer; +}; + struct vx_video_options { vx_audio_params audio_params;