diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml new file mode 100644 index 0000000..c207f12 --- /dev/null +++ b/.github/workflows/cmake-multi-platform.yml @@ -0,0 +1,170 @@ +# This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. +# See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml +name: CMake on multiple platforms + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + runs-on: ${{ matrix.os }} + permissions: + id-token: write + contents: read + attestations: write + strategy: + # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. + fail-fast: false + + # Set up a matrix to run the following 3 configurations: + # 1. + # 2. + # 3. + # + # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. + matrix: + os: [ubuntu-26.04, windows-latest] + build_type: [Release] + c_compiler: [gcc, clang, cl] + ffmpeg_from_vcpkg: [ON, OFF] + ffmpeg_from_pkgconfig: [ON, OFF] + include: + - os: windows-latest + c_compiler: cl + cpp_compiler: cl + use_pthreads: ON + - os: ubuntu-26.04 + c_compiler: gcc + cpp_compiler: g++ + use_pthreads: OFF + - os: ubuntu-26.04 + c_compiler: clang + cpp_compiler: clang++ + use_pthreads: OFF + exclude: + - os: windows-latest + c_compiler: gcc + - os: windows-latest + c_compiler: clang + - os: ubuntu-26.04 + c_compiler: cl + - ffmpeg_from_vcpkg: ON + ffmpeg_from_pkgconfig: ON + - ffmpeg_from_vcpkg: OFF + ffmpeg_from_pkgconfig: OFF + os: ubuntu-26.04 + # VCPKG is fucking broken on this pos github runner. + - ffmpeg_from_vcpkg: ON + - os: windows-latest + ffmpeg_from_pkgconfig: ON + + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - name: Set reusable strings + # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. + id: strings + shell: bash + run: | + echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" + echo "vcpkg-toolchain=$VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" >> "$GITHUB_OUTPUT" + + - name: Install prereqs on linux + if: ${{ contains(matrix.os, 'ubuntu') }} + id: install + run: > + sudo apt-get update + + sudo apt-get install libavutil-dev libavcodec-dev + libswresample-dev libavformat-dev nasm libasound2-dev libx11-dev libxrandr-dev + libxi-dev libgl1-mesa-dev libglu1-mesa-dev libxcursor-dev libxinerama-dev + libxkbcommon-dev autoconf autoconf-archive automake libtool + + # git clone https://github.com/microsoft/vcpkg.git + # + # cd vcpkg + # + # echo "vcpkg-toolchain=$(pwd)/scripts/buildsystems/vcpkg.cmake" >> "$GITHUB_OUTPUT" + # + # ./bootstrap-vcpkg.sh + # - name: Install ffmpeg binaries on windows + # if: ${{ contains(matrix.os, 'windows') }} + # id: ffmp + # shell: powershell + # run: | + # Invoke-WebRequest -Uri https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-full-shared.7z -OutFile ffmpeg-shared.7z + # & 7z x .\ffmpeg-shared.7z -y + # (Get-ChildItem -Directory | Where-Object {$_.Name -like "*ffmpeg*"} | Select-Object -First 1 | ForEach-Object { "ffmp-dir=$($_.Name)" }) >> $GITHUB_OUTPUT + + # - name: Update vcpkg root + # shell: bash + # run: | + # cd $VCPKG_INSTALLATION_ROOT + # git reset --hard + # git pull + # cd - + + - name: download ffmpeg from github + uses: robinraju/release-downloader@v1 + if: ${{ contains(matrix.os, 'windows') }} + id: ffmp + with: + repository: 'GyanD/codexffmpeg' + tag: '9.0.1' + fileName: 'ffmpeg-9.0.1-full_build-shared.zip' + extract: true + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: > + cmake -B ${{ steps.strings.outputs.build-output-dir }} + -DCMAKE_TOOLCHAIN_FILE=${{ steps.strings.outputs.vcpkg-toolchain }} + -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} + -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} + -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + ${{ case ( contains(matrix.os, 'windows'), '-DFFMPEG_DIR="./ffmpeg-9.0.1-full_build-shared"', '') }} + -DUSE_FFMPEG_FROM_VCPKG=${{ matrix.ffmpeg_from_vcpkg }} + -DUSE_FFMPEG_FROM_PKGCONFIG=${{ matrix.ffmpeg_from_pkgconfig }} + -DUSE_PTHREADS4W=${{ matrix.use_pthreads }} + -DVCPKG_TARGET_TRIPLET=${{ case( matrix.os == 'ubuntu-26.04', 'x64-linux', + matrix.os == 'windows-latest', 'x64-windows-static-md-release', + '' ) }} + -S ${{ github.workspace }} + + - name: Build + # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + run: | + cmake --build ${{ steps.strings.outputs.build-output-dir }} --config Release + + - name: Generate attestation + if: ${{ contains(matrix.os, 'windows') }} + uses: actions/attest@v4 + with: + subject-path: ${{ steps.strings.outputs.build-output-dir }}\Release\player.exe + + - name: move files into publish folder + if: ${{ contains(matrix.os, 'windows') }} + shell: bash + run: | + mkdir publish + mv "${{ steps.strings.outputs.build-output-dir }}"/Release/*.exe publish/ + mv ffmpeg-9.0.1-full_build-shared/bin/{avcodec-63,avformat-63,avutil-61,swresample-7}.dll publish/ + + - name: Upload windows build + if: ${{ contains(matrix.os, 'windows') }} + uses: actions/upload-artifact@v7 + with: + name: avrays + path: publish/ + +# - name: Test +# working-directory: ${{ steps.strings.outputs.build-output-dir }} + # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail +# run: ctest --build-config ${{ matrix.build_type }} diff --git a/.gitignore b/.gitignore index a608b91..2bd527c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,10 @@ *.mkv *.webm *.mp4 +.vs player* -build/ +/build*/ +vcpkg_installed/ +CMakeFiles/ +build_tests/ +CMakeUserPresets.json diff --git a/.gitmodules b/.gitmodules index 0bc225e..43a5a0e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "vendor/raygui"] path = vendor/raygui url = git@github.com:raysan5/raygui.git +[submodule "vendor/raylib"] + path = vendor/raylib + url = git@github.com:raysan5/raylib.git diff --git a/CMakeLists.txt b/CMakeLists.txt index c05b6ba..2944d8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,14 +1,85 @@ -cmake_minimum_required(VERSION 4.4) +# 4.3 is what VS ships with now, we use it as a baseline +# (but will probably work with lower versions, too) +cmake_minimum_required(VERSION 4.2) + +# The options logic has to be made before project(...) +# since that's when vcpkg install runs. +option(USE_FFMPEG_FROM_VCPKG "Install ffmpeg from vcpkg" ON) +option(USE_FFMPEG_FROM_PKGCONFIG "Use ffmpeg installed on the system" OFF) +option(LIBRARY_ONLY "Don't build player exe" OFF) +if("${USE_FFMPEG_FROM_VCPKG}") + list(APPEND VCPKG_MANIFEST_FEATURES "ffmpeg") +endif() +if (MSVC) + option(USE_PTHREADS4W "Use pthreads4w lib" ON) +else() + option(USE_PTHREADS4W "Use pthreads4w lib" OFF) +endif() + +set(CACHE{RAYLIB_DIR} HELP "raylib base directory to use" TYPE PATH VALUE "./vendor/raylib") +set(CACHE{FFMPEG_DIR} HELP "base directory of libav pre-compiled files" TYPE PATH VALUE "") +message(STATUS "Raylib dir is set to ${RAYLIB_DIR}") +message(STATUS "FFMPEG base dir is set to ${FFMPEG_DIR}") + +if(USE_PTHREADS4W) + message(STATUS "Activating pthreads4w") + list(APPEND VCPKG_MANIFEST_FEATURES "pthreads") +endif() project(avrays) -cmake_pkg_config(IMPORT libavcodec REQUIRED) -cmake_pkg_config(IMPORT libavutil REQUIRED) -cmake_pkg_config(IMPORT libavformat REQUIRED) -cmake_pkg_config(IMPORT libswresample REQUIRED) + +# We want a release build of Raylib no matter what. +set(OLD_BUILD_TYPE "${CMAKE_BUILD_TYPE}") +set(CMAKE_BUILD_TYPE Release) +add_subdirectory("${RAYLIB_DIR}") +set(CMAKE_BUILD_TYPE "${OLD_BUILD_TYPE}") + +# Pthreads4w is only for microsoft's compiler, mingw has its own implementation of +# pthreads calls. +if(USE_PTHREADS4W) + find_package(PThreads4W REQUIRED) +endif() + +# Now try to find FFMPEG library. +if("${USE_FFMPEG_FROM_VCPKG}") + find_package(FFMPEG REQUIRED) +elseif("${USE_FFMPEG_FROM_PKGCONFIG}") + message(STATUS "Using ffmpeg from pkgconfig") + cmake_pkg_config(IMPORT libavcodec REQUIRED) + cmake_pkg_config(IMPORT libavutil REQUIRED) + cmake_pkg_config(IMPORT libavformat REQUIRED) + cmake_pkg_config(IMPORT libswresample REQUIRED) + list(APPEND FFMPEG_LIBRARIES PkgConfig::libavcodec PkgConfig::libavutil PkgConfig::libavformat PkgConfig::libswresample) +elseif(FFMPEG_DIR) + if(NOT EXISTS "${FFMPEG_DIR}/include/libavcodec/avcodec.h") + message(FATAL_ERROR "ffmpeg's directory doesn't seem to have necessary files") + endif() + list(APPEND FFMPEG_INCLUDE_DIRS "${FFMPEG_DIR}/include") + list(APPEND FFMPEG_LIBRARY_DIRS "${FFMPEG_DIR}/lib") + list(APPEND FFMPEG_LIBRARIES "${FFMPEG_DIR}/lib/avcodec.lib" "${FFMPEG_DIR}/lib/avutil.lib" "${FFMPEG_DIR}/lib/avformat.lib" "${FFMPEG_DIR}/lib/swresample.lib") +else() + message(FATAL_ERROR "ffmpeg's libav libraries are required to build this. + Set USE_FFMPEG_FROM_PKGCONFIG or USE_FFMPEG_FROM_VCPKG variable to ON, or provide FFMPEG_DIR.") +endif() + add_library(avrays STATIC) target_sources(avrays PUBLIC avlib.h PRIVATE avlib.c) -target_link_libraries(avrays PRIVATE PkgConfig::libavcodec PkgConfig::libavutil PkgConfig::libavformat PkgConfig::libswresample) +target_include_directories(avrays PRIVATE ${FFMPEG_INCLUDE_DIRS}) +target_link_directories(avrays PRIVATE ${FFMPEG_LIBRARY_DIRS}) +target_link_libraries(avrays PRIVATE raylib ${FFMPEG_LIBRARIES}) +if(USE_PTHREADS4W) + target_link_libraries(avrays PRIVATE PThreads4W::PThreads4W) +endif() -add_executable(player) -target_sources(player PRIVATE main.c) -target_link_libraries(player PRIVATE avrays raylib m X11) +if(NOT LIBRARY_ONLY) + add_executable(player) + target_sources(player PRIVATE main.c) + if(MSVC) + target_sources(player PRIVATE winstuff.c) + endif() + target_include_directories(player PRIVATE ${FFMPEG_INCLUDE_DIRS}) + target_link_libraries(player PRIVATE avrays raylib) + if(USE_PTHREADS4W) + target_link_libraries(player PRIVATE PThreads4W::PThreads4W) + endif() +endif() diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000..10f178b --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,13 @@ +{ + "version": 2, + "configurePresets": [ + { + "name": "vcpkg", + "generator": "Ninja", + "binaryDir": "${sourceDir}/build", + "cacheVariables": { + "CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" + } + } + ] +} diff --git a/README.md b/README.md index e92f74a..991df35 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,14 @@ If you don't wish to use stb_avray.h, then you will need the following files: `a - make_stb is a bash script that generates stb_avray.h file. - stb_avray.h is a single header version of the library. You can include it instead of the avlib.c/avlib.h/utils.h combo. +## Where do I get ffmpeg/libav from? + +On linux, look for packages named `ffmpeg`, `libav*-dev` or similar. Run `pkg-config --list-package-names | grep -E 'lib(av|sw)'` to see which of the libav* libraries you have installed. At the minimum, you need libavcodec, libavutil, libavformat and libswresample to compile avray. + +On windows, you can download and use precompiled binaries gyan [gyan](https://www.gyan.dev/ffmpeg/builds/) or [BtbN](https://github.com/BtbN/FFmpeg-Builds/releases). Make sure that what you downloaded actually contains dev files(headers and `*.lib`s) and not just executables for ffmpeg and friends. + +As an alternative, you can download and compile these libraries with vcpkg. If you go with this route, make sure you have enough space on your hard drive (10 Gb at the very least) + ## How to pause, or note about how video/audio sync works AVRay assumes that the audio stream will be continously pulled in raylib's audio stream callback function, and calculates current timestamps/syncs video playback based on that. This has a couple of consequences: @@ -51,6 +59,66 @@ The player has the following controls: - Double click switches borderless fullscreen mode on/off - `Esc` closes the window, as is tradition in raylib. +### CMake presets and options + +cmake builds can be configured with the following options: + +```bash +-DUSE_FFMPEG_FROM_VCPKG=ON # uses vcpkg to download and build libav libraries, default ON +-DUSE_FFMPEG_FROM_PKGCONFIG=OFF # uses pkgconfig to find libav on your system +-LIBRARY_ONLY=OFF # build only the library .a .lib +``` + +It's recommended to create `CMakeUserPresets.json` file to store build configurations. + +It could look like this + +```json +{ + "version": 2, + "configurePresets": [ + { + "name": "linux", + "inherits": "vcpkg", + "cacheVariables": { + "VCPKG_TARGET_TRIPLET": "x64-linux", + "USE_FFMPEG_FROM_VCPKG": "OFF", + "USE_FFMPEG_FROM_PKGCONFIG": "ON" + } + }, + { + "name": "windows", + "inherits": "vcpkg", + "binaryDir": "${sourceDir}/build_win", + "cacheVariables": { + "VCPKG_TARGET_TRIPLET": "x64-windows-static-md-release", + "USE_FFMPEG_FROM_VCPKG": "OFF", + "CMAKE_BUILD_TYPE": , + "FFMPEG_DIR": , + "USE_PTHREADS4W": "ON" + } + } + ], + "buildPresets": [ + { + "name": "windows", + "configurePreset": "windows" + }, + { + "name": "linux", + "configurePreset": "linux" + } + ] +} +``` + +With `CMakeUserPresets.json` file the configure and compile steps can be as easy as: + +```bash +cmake --preset=windows +cmake --build --preset=windows +``` + ## Minimal example A minimal program that uses AVRay could look something like this(see main_minimal.c): @@ -85,7 +153,7 @@ int main(int argc, char **argv) { DecoderState state = ctx.state; // Exit when the playback is finished if (state == DS_FINISHED) { - break; + break; } // Update textures checks the current timestamp(driven by the audio stream) // and updates the textures if it's time to show the next video frame. @@ -208,3 +276,5 @@ static void thread_create(ThreadType *t, void *(*thread_proc)(void *), static void thread_join(ThreadType *t) { pthread_join(*t, NULL); } static void thread_sleep_ms(unsigned int ms) { usleep(ms * 1000); } ``` + +See also open issues on the github's issue tracker. diff --git a/avlib.c b/avlib.c index ba0f9b0..1bad8fd 100644 --- a/avlib.c +++ b/avlib.c @@ -28,6 +28,7 @@ * distribution. * **********************************************************************************************/ +#include #include #include #include @@ -35,7 +36,10 @@ #include #include #include +#ifndef _MSC_VER +// for usleep() #include +#endif #include "avlib.h" #include "utils.h" @@ -72,7 +76,12 @@ static void thread_create(ThreadType *t, void *(*thread_proc)(void *), pthread_create(t, NULL, thread_proc, arg); } static void thread_join(ThreadType *t) { pthread_join(*t, NULL); } + +#ifdef _MSC_VER +static void thread_sleep_ms(unsigned int ms) { Sleep(ms); } +#else static void thread_sleep_ms(unsigned int ms) { usleep(ms * 1000); } +#endif int time_to_str(double seconds, char *buf, size_t n) { int sec = floor(seconds); @@ -397,6 +406,7 @@ int avray_open_file(DecoderContext *ctx, const char *file_name) { } assert(ctx->state == DS_READY); struct DecoderPrivate *p = ctx->p; + TraceLog(LOG_INFO, "Trying to open file: %s", file_name); result = avformat_open_input(&p->fmt_ctx, file_name, NULL, NULL); if (result != 0) { return RESULT_CANT_OPEN; @@ -404,7 +414,7 @@ int avray_open_file(DecoderContext *ctx, const char *file_name) { ERRCHECK("Can't open file"); result = avformat_find_stream_info(p->fmt_ctx, NULL); - ERRCHECK("Cant find stream info"); + ERRCHECK2R(result >= 0, RESULT_CANT_OPEN, "Cant find stream info"); p->video_stream = av_find_best_stream(p->fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); @@ -867,10 +877,12 @@ static void try_unlock_decoder_sem(DecoderContext *ctx) { int sem_value; struct DecoderPrivate *p = ctx->p; semaphore_get_value(&p->sem, &sem_value); - if (sem_value == 0) { + // TraceLog(LOG_DEBUG, "current semaphore value is %d", sem_value); + if (sem_value <= 0) { // we can advance semaphore if both buffers are less than 80% empty if (ringbuffer_len(&p->image_buffer) < 0.8 * p->image_buffer.buf_size && ringbuffer_len(&p->audio_buffer) < 0.8 * p->audio_buffer.buf_size) { + // TraceLog(LOG_INFO, "trying to advance the semaphore..."); semaphore_incr(&p->sem); } } @@ -1117,55 +1129,56 @@ void avray_shutdown(DecoderContext *ctx) { // Raylib specific things #include -const char *fs_yuv420p10 = "#version 130\n" - - "in vec2 fragTexCoord;" - "out vec4 finalColor;" - "uniform sampler2D tex_luma;" - "uniform sampler2D tex_u;" - "uniform sampler2D tex_v;" - - "float samplet(sampler2D t) {" - " vec4 smp = texture(t, fragTexCoord);" - " float res = smp.r * 0.25 + smp.a * 64.0;" - " return res; }" - - "void main() {" - " float smp = samplet(tex_luma);" - " float luma_f = smp;" - " smp = samplet(tex_u);" - " float u_f = smp - 0.5;" - " smp = samplet(tex_v);" - " float v_f = smp - 0.5;" - - " luma_f = 1.1643*(luma_f - 0.0625);" - " float r=luma_f+1.5958*v_f;" - " float g=luma_f-0.39173*u_f-0.81290*v_f;" - " float b=luma_f+2.017*u_f;" - " finalColor=vec4(r, g, b, 1.0);" - "}"; +static const char *fs_yuv420p10 = "#version 130\n" + + "in vec2 fragTexCoord;" + "out vec4 finalColor;" + "uniform sampler2D tex_luma;" + "uniform sampler2D tex_u;" + "uniform sampler2D tex_v;" + + "float samplet(sampler2D t) {" + " vec4 smp = texture(t, fragTexCoord);" + " float res = smp.r * 0.25 + smp.a * 64.0;" + " return res; }" + + "void main() {" + " float smp = samplet(tex_luma);" + " float luma_f = smp;" + " smp = samplet(tex_u);" + " float u_f = smp - 0.5;" + " smp = samplet(tex_v);" + " float v_f = smp - 0.5;" + + " luma_f = 1.1643*(luma_f - 0.0625);" + " float r=luma_f+1.5958*v_f;" + " float g=luma_f-0.39173*u_f-0.81290*v_f;" + " float b=luma_f+2.017*u_f;" + " finalColor=vec4(r, g, b, 1.0);" + "}"; // shader code adopted from // https://stackoverflow.com/questions/30191911/is-it-possible-to-draw-yuv422-and-yuv420-texture-using-opengl -const char *fs_yuv420p = "#version 130 \n" - "in vec2 fragTexCoord;" - "in vec4 fragColor;" - "out vec4 finalColor;" - "uniform sampler2D tex_luma;" - "uniform sampler2D tex_u;" - "uniform sampler2D tex_v;" - "void main() {" - " float luma = texture(tex_luma, fragTexCoord).r;" - " float u = texture(tex_u, fragTexCoord).r;" - " float v = texture(tex_v, fragTexCoord).r;" - " luma=1.1643*(luma-0.0625);" - " u=u-0.5;" - " v=v-0.5;" - " float r=luma+1.5958*v;" - " float g=luma-0.39173*u-0.81290*v;" - " float b=luma+2.017*u;" - " finalColor=vec4(r, g, b, 1.0);" - "}"; +static const char *fs_yuv420p = + "#version 130 \n" + "in vec2 fragTexCoord;" + "in vec4 fragColor;" + "out vec4 finalColor;" + "uniform sampler2D tex_luma;" + "uniform sampler2D tex_u;" + "uniform sampler2D tex_v;" + "void main() {" + " float luma = texture(tex_luma, fragTexCoord).r;" + " float u = texture(tex_u, fragTexCoord).r;" + " float v = texture(tex_v, fragTexCoord).r;" + " luma=1.1643*(luma-0.0625);" + " u=u-0.5;" + " v=v-0.5;" + " float r=luma+1.5958*v;" + " float g=luma-0.39173*u-0.81290*v;" + " float b=luma+2.017*u;" + " finalColor=vec4(r, g, b, 1.0);" + "}"; int avray_init_graphics_objects(DecoderContext *ctx, RaylibObjects *objs) { *objs = (RaylibObjects){}; @@ -1212,10 +1225,22 @@ int avray_init_graphics_objects(DecoderContext *ctx, RaylibObjects *objs) { } int avray_free_graphics_objects(RaylibObjects *objs) { - UnloadShader(objs->video_shader); - UnloadTexture(objs->tex_luma); - UnloadTexture(objs->tex_u); - UnloadTexture(objs->tex_v); + if (IsShaderValid(objs->video_shader)) { + UnloadShader(objs->video_shader); + objs->video_shader.id = 0; + } + if (IsTextureValid(objs->tex_luma)) { + UnloadTexture(objs->tex_luma); + objs->tex_luma.id = 0; + } + if (IsTextureValid(objs->tex_u)) { + UnloadTexture(objs->tex_u); + objs->tex_u.id = 0; + } + if (IsTextureValid(objs->tex_v)) { + UnloadTexture(objs->tex_v); + objs->tex_v.id = 0; + } return RESULT_OK; } @@ -1372,15 +1397,15 @@ Vector2 avray_draw_debug_overlay(DecoderContext *ctx, RaylibObjects *objs, text_width = tw; DrawText(msg, x, y, FONTSIZE, WHITE); y += LINEHEIGHT; - snprintf(msg, sizeof(msg), "abytes: %ld written: %ld", ctx->abytes_pulled, - ctx->abytes_written); + snprintf(msg, sizeof(msg), "abytes: %" PRIu64 ", written: %" PRIu64, + ctx->abytes_pulled, ctx->abytes_written); tw = MeasureText(msg, FONTSIZE); if (tw > text_width) text_width = tw; DrawText(msg, x, y, FONTSIZE, WHITE); y += LINEHEIGHT; - snprintf(msg, sizeof(msg), "vbytes: %ld, written: %ld", ctx->vbytes_pulled, - ctx->vbytes_written); + snprintf(msg, sizeof(msg), "vbytes: %" PRIu64 ", written: %" PRIu64, + ctx->vbytes_pulled, ctx->vbytes_written); tw = MeasureText(msg, FONTSIZE); if (tw > text_width) text_width = tw; diff --git a/avlib.h b/avlib.h index 8aba2d5..08815fb 100644 --- a/avlib.h +++ b/avlib.h @@ -97,8 +97,8 @@ typedef struct DecoderContext { TimeLine vbuffer_timeline; TimeLine abuffer_timeline; - unsigned long int abytes_pulled, vbytes_pulled; - unsigned long int abytes_written, vbytes_written; + uint64_t abytes_pulled, vbytes_pulled; + uint64_t abytes_written, vbytes_written; DecoderState state; } DecoderContext; diff --git a/main.c b/main.c index 2ca1712..a08b61a 100644 --- a/main.c +++ b/main.c @@ -1,13 +1,27 @@ -#include #include #include #include #include #include #include -#include #include +#ifdef __MINGW32__ +#include +#include +#elif (defined _MSC_VER) +// definded in winstuff.c +double get_monotonic_time(); +#else +#include #include +double get_monotonic_time() { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + double time = (double)ts.tv_nsec / 1000000000; + time += ts.tv_sec; + return time; +} +#endif #include "avlib.h" #define RAYGUI_IMPLEMENTATION @@ -20,13 +34,13 @@ DecoderContext dc; FILE *log_file; void open_log() { log_file = fopen("raylib.log", "w"); } -void close_log() { +void close_log(void) { if (log_file) fclose(log_file); } void tracelog_impl(FILE *log_stream, TraceLogLevel logLevel, double time, - const char *text, va_list args) { + const char *text) { const char *log_type = ""; switch (logLevel) { case LOG_INFO: @@ -52,28 +66,26 @@ void tracelog_impl(FILE *log_stream, TraceLogLevel logLevel, double time, return; break; } - fprintf(log_stream, "%s (%.3lf): ", log_type, time); - vfprintf(log_stream, text, args); - fputc('\n', log_stream); + fprintf(log_stream, "%s (%.3lf): %s\n", log_type, time, text); } void tracelog_cb(int logLevel, const char *text, va_list args) { FILE *log_stream = stdout; - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - double time = (double)ts.tv_nsec / 1000000000; - time += ts.tv_sec; + // Even in multibyte strings, 2 kb buffer should be enough + char buffer[2048]; + double time = get_monotonic_time(); time -= start_clock_time; if (logLevel >= LOG_ERROR) { log_stream = stderr; } - va_list aq; + /* va_list aq; va_copy(aq, args); - va_end(aq); - tracelog_impl(log_stream, logLevel, time, text, args); + va_end(aq); */ + vsnprintf(buffer, sizeof(buffer), text, args); + tracelog_impl(log_stream, logLevel, time, buffer); if (log_file) { - tracelog_impl(log_file, logLevel, time, text, aq); + tracelog_impl(log_file, logLevel, time, buffer); } } @@ -147,10 +159,7 @@ void print_argv(char **argv, int argc) { int main(int argc, char **argv) { // tempfile = fopen("tempfile.out", "wb"); - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - start_clock_time = (double)ts.tv_nsec / 1000000000; - start_clock_time += ts.tv_sec; + start_clock_time = get_monotonic_time(); open_log(); atexit(&close_log); @@ -158,28 +167,35 @@ int main(int argc, char **argv) { SetTraceLogCallback(tracelog_cb); float current_volume = 0.2; if (argc < 2) { + fprintf(stderr, "No input files specified\n"); return 1; } int result = avray_init_decoder(&dc); - if (result != 0) + if (result != 0) { + fprintf(stderr, "Can't initialize decoder\n"); exit(EXIT_FAILURE); + } unsigned int file_index = 1; int current_argc = argc; char *file_name = argv[file_index]; while ((result = avray_open_file(&dc, file_name)) == RESULT_CANT_OPEN) { + fprintf(stderr, "Can't open %s\n", file_name); remove_file_from_argv(argv, ¤t_argc, file_index); // print_argv(argv, current_argc); if (file_index == current_argc) break; file_name = argv[file_index]; } - if (result != 0) + if (result != 0) { + fprintf(stderr, "File open failed\n"); exit(EXIT_FAILURE); + } float scale_factor = 1.0; int scaled_width = 1, scaled_height = 1; Vector2 letterbox = {}; + printf("Start init window\n"); InitWindow(1024, 768, file_name); rescale_window(&scale_factor, &scaled_width, &scaled_height, &letterbox, false); @@ -220,6 +236,8 @@ int main(int argc, char **argv) { } StopAudioStream(stream); UnloadAudioStream(stream); + // Set this so that IsAudioStreamValid could detect "deleted" stream. + stream.buffer = NULL; avray_free_graphics_objects(&video_tex); file_name = argv[file_index]; @@ -353,7 +371,12 @@ int main(int argc, char **argv) { avray_free_graphics_objects(&video_tex); avray_shutdown(&dc); - UnloadAudioStream(stream); + // Assume that if the stream is already unloaded, its buffer would be set to + // NULL and the IsAudioStreamValid will return false + if (IsAudioStreamValid(stream)) { + UnloadAudioStream(stream); + stream.buffer = NULL; // being pedantic + } CloseAudioDevice(); CloseWindow(); diff --git a/stb_avray.h b/stb_avray.h index 3a36735..1d2a0f7 100644 --- a/stb_avray.h +++ b/stb_avray.h @@ -44,7 +44,7 @@ #include #include -#define UTILFUNC [[maybe_unused]] static inline +#define UTILFUNC static inline typedef struct CleanupStack { struct CleanupStack *pnext; @@ -250,20 +250,20 @@ UTILFUNC void avray__ringbuffer_flush(avray__RingBuffer *rb) { } typedef struct TimeLine { - void *buffer; + char *buffer; size_t elem_size; unsigned int cursor; size_t len; } TimeLine; UTILFUNC TimeLine make_timeline(size_t elem_size, size_t length) { - void *buf = malloc(elem_size * length); + char *buf = malloc(elem_size * length); memset(buf, 0, elem_size * length); return (TimeLine){buf, elem_size, 0, length}; } UTILFUNC void *timeline_push(TimeLine *self) { - void *result = self->buffer + self->elem_size * self->cursor; + char *result = self->buffer + self->elem_size * self->cursor; self->cursor += 1; if (self->cursor >= self->len) { self->cursor = 0; @@ -428,8 +428,8 @@ typedef struct DecoderContext { TimeLine vbuffer_timeline; TimeLine abuffer_timeline; - unsigned long int abytes_pulled, vbytes_pulled; - unsigned long int abytes_written, vbytes_written; + uint64_t abytes_pulled, vbytes_pulled; + uint64_t abytes_written, vbytes_written; DecoderState state; } DecoderContext; @@ -470,6 +470,7 @@ Vector2 avray_draw_debug_overlay(DecoderContext *ctx, RaylibObjects *objs, bool draw_background); #endif // !AVLIB_H #ifdef AVRAY_IMPLEMENTATION +#include #include #include #include @@ -477,7 +478,10 @@ Vector2 avray_draw_debug_overlay(DecoderContext *ctx, RaylibObjects *objs, #include #include #include +#ifndef _MSC_VER +// for usleep() #include +#endif // Size of the video buffer if the framerate is not available @@ -512,7 +516,12 @@ static void avray__thread_create(ThreadType *t, void *(*thread_proc)(void *), pthread_create(t, NULL, thread_proc, arg); } static void thread_join(ThreadType *t) { pthread_join(*t, NULL); } + +#ifdef _MSC_VER +static void thread_sleep_ms(unsigned int ms) { Sleep(ms); } +#else static void thread_sleep_ms(unsigned int ms) { usleep(ms * 1000); } +#endif int time_to_str(double seconds, char *buf, size_t n) { int sec = floor(seconds); @@ -837,6 +846,7 @@ int avray_open_file(DecoderContext *ctx, const char *file_name) { } assert(ctx->state == DS_READY); struct DecoderPrivate *p = ctx->p; + TraceLog(LOG_INFO, "Trying to open file: %s", file_name); result = avformat_open_input(&p->fmt_ctx, file_name, NULL, NULL); if (result != 0) { return RESULT_CANT_OPEN; @@ -844,7 +854,7 @@ int avray_open_file(DecoderContext *ctx, const char *file_name) { ERRCHECK("Can't open file"); result = avformat_find_stream_info(p->fmt_ctx, NULL); - ERRCHECK("Cant find stream info"); + ERRCHECK2R(result >= 0, RESULT_CANT_OPEN, "Cant find stream info"); p->video_stream = av_find_best_stream(p->fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); @@ -1307,10 +1317,12 @@ static void avray__try_unlock_decoder_sem(DecoderContext *ctx) { int sem_value; struct DecoderPrivate *p = ctx->p; avray__semaphore_get_value(&p->sem, &sem_value); - if (sem_value == 0) { + // TraceLog(LOG_DEBUG, "current semaphore value is %d", sem_value); + if (sem_value <= 0) { // we can advance semaphore if both buffers are less than 80% empty if (avray__ringbuffer_len(&p->image_buffer) < 0.8 * p->image_buffer.buf_size && avray__ringbuffer_len(&p->audio_buffer) < 0.8 * p->audio_buffer.buf_size) { + // TraceLog(LOG_INFO, "trying to advance the semaphore..."); avray__semaphore_incr(&p->sem); } } @@ -1557,55 +1569,56 @@ void avray_shutdown(DecoderContext *ctx) { // Raylib specific things #include -const char *fs_yuv420p10 = "#version 130\n" - - "in vec2 fragTexCoord;" - "out vec4 finalColor;" - "uniform sampler2D tex_luma;" - "uniform sampler2D tex_u;" - "uniform sampler2D tex_v;" - - "float samplet(sampler2D t) {" - " vec4 smp = texture(t, fragTexCoord);" - " float res = smp.r * 0.25 + smp.a * 64.0;" - " return res; }" - - "void main() {" - " float smp = samplet(tex_luma);" - " float luma_f = smp;" - " smp = samplet(tex_u);" - " float u_f = smp - 0.5;" - " smp = samplet(tex_v);" - " float v_f = smp - 0.5;" - - " luma_f = 1.1643*(luma_f - 0.0625);" - " float r=luma_f+1.5958*v_f;" - " float g=luma_f-0.39173*u_f-0.81290*v_f;" - " float b=luma_f+2.017*u_f;" - " finalColor=vec4(r, g, b, 1.0);" - "}"; +static const char *fs_yuv420p10 = "#version 130\n" + + "in vec2 fragTexCoord;" + "out vec4 finalColor;" + "uniform sampler2D tex_luma;" + "uniform sampler2D tex_u;" + "uniform sampler2D tex_v;" + + "float samplet(sampler2D t) {" + " vec4 smp = texture(t, fragTexCoord);" + " float res = smp.r * 0.25 + smp.a * 64.0;" + " return res; }" + + "void main() {" + " float smp = samplet(tex_luma);" + " float luma_f = smp;" + " smp = samplet(tex_u);" + " float u_f = smp - 0.5;" + " smp = samplet(tex_v);" + " float v_f = smp - 0.5;" + + " luma_f = 1.1643*(luma_f - 0.0625);" + " float r=luma_f+1.5958*v_f;" + " float g=luma_f-0.39173*u_f-0.81290*v_f;" + " float b=luma_f+2.017*u_f;" + " finalColor=vec4(r, g, b, 1.0);" + "}"; // shader code adopted from // https://stackoverflow.com/questions/30191911/is-it-possible-to-draw-yuv422-and-yuv420-texture-using-opengl -const char *fs_yuv420p = "#version 130 \n" - "in vec2 fragTexCoord;" - "in vec4 fragColor;" - "out vec4 finalColor;" - "uniform sampler2D tex_luma;" - "uniform sampler2D tex_u;" - "uniform sampler2D tex_v;" - "void main() {" - " float luma = texture(tex_luma, fragTexCoord).r;" - " float u = texture(tex_u, fragTexCoord).r;" - " float v = texture(tex_v, fragTexCoord).r;" - " luma=1.1643*(luma-0.0625);" - " u=u-0.5;" - " v=v-0.5;" - " float r=luma+1.5958*v;" - " float g=luma-0.39173*u-0.81290*v;" - " float b=luma+2.017*u;" - " finalColor=vec4(r, g, b, 1.0);" - "}"; +static const char *fs_yuv420p = + "#version 130 \n" + "in vec2 fragTexCoord;" + "in vec4 fragColor;" + "out vec4 finalColor;" + "uniform sampler2D tex_luma;" + "uniform sampler2D tex_u;" + "uniform sampler2D tex_v;" + "void main() {" + " float luma = texture(tex_luma, fragTexCoord).r;" + " float u = texture(tex_u, fragTexCoord).r;" + " float v = texture(tex_v, fragTexCoord).r;" + " luma=1.1643*(luma-0.0625);" + " u=u-0.5;" + " v=v-0.5;" + " float r=luma+1.5958*v;" + " float g=luma-0.39173*u-0.81290*v;" + " float b=luma+2.017*u;" + " finalColor=vec4(r, g, b, 1.0);" + "}"; int avray_init_graphics_objects(DecoderContext *ctx, RaylibObjects *objs) { *objs = (RaylibObjects){}; @@ -1652,10 +1665,22 @@ int avray_init_graphics_objects(DecoderContext *ctx, RaylibObjects *objs) { } int avray_free_graphics_objects(RaylibObjects *objs) { - UnloadShader(objs->video_shader); - UnloadTexture(objs->tex_luma); - UnloadTexture(objs->tex_u); - UnloadTexture(objs->tex_v); + if (IsShaderValid(objs->video_shader)) { + UnloadShader(objs->video_shader); + objs->video_shader.id = 0; + } + if (IsTextureValid(objs->tex_luma)) { + UnloadTexture(objs->tex_luma); + objs->tex_luma.id = 0; + } + if (IsTextureValid(objs->tex_u)) { + UnloadTexture(objs->tex_u); + objs->tex_u.id = 0; + } + if (IsTextureValid(objs->tex_v)) { + UnloadTexture(objs->tex_v); + objs->tex_v.id = 0; + } return RESULT_OK; } @@ -1812,15 +1837,15 @@ Vector2 avray_draw_debug_overlay(DecoderContext *ctx, RaylibObjects *objs, text_width = tw; DrawText(msg, x, y, FONTSIZE, WHITE); y += LINEHEIGHT; - snprintf(msg, sizeof(msg), "abytes: %ld written: %ld", ctx->abytes_pulled, - ctx->abytes_written); + snprintf(msg, sizeof(msg), "abytes: %" PRIu64 ", written: %" PRIu64, + ctx->abytes_pulled, ctx->abytes_written); tw = MeasureText(msg, FONTSIZE); if (tw > text_width) text_width = tw; DrawText(msg, x, y, FONTSIZE, WHITE); y += LINEHEIGHT; - snprintf(msg, sizeof(msg), "vbytes: %ld, written: %ld", ctx->vbytes_pulled, - ctx->vbytes_written); + snprintf(msg, sizeof(msg), "vbytes: %" PRIu64 ", written: %" PRIu64, + ctx->vbytes_pulled, ctx->vbytes_written); tw = MeasureText(msg, FONTSIZE); if (tw > text_width) text_width = tw; diff --git a/utils.h b/utils.h index 021d80b..585e285 100644 --- a/utils.h +++ b/utils.h @@ -38,7 +38,7 @@ #include #include -#define UTILFUNC [[maybe_unused]] static inline +#define UTILFUNC static inline typedef struct CleanupStack { struct CleanupStack *pnext; @@ -244,20 +244,20 @@ UTILFUNC void ringbuffer_flush(RingBuffer *rb) { } typedef struct TimeLine { - void *buffer; + char *buffer; size_t elem_size; unsigned int cursor; size_t len; } TimeLine; UTILFUNC TimeLine make_timeline(size_t elem_size, size_t length) { - void *buf = malloc(elem_size * length); + char *buf = malloc(elem_size * length); memset(buf, 0, elem_size * length); return (TimeLine){buf, elem_size, 0, length}; } UTILFUNC void *timeline_push(TimeLine *self) { - void *result = self->buffer + self->elem_size * self->cursor; + char *result = self->buffer + self->elem_size * self->cursor; self->cursor += 1; if (self->cursor >= self->len) { self->cursor = 0; diff --git a/vcpkg-configuration.json b/vcpkg-configuration.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/vcpkg-configuration.json @@ -0,0 +1 @@ +{} diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 0000000..f6790f7 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,24 @@ +{ + "dependencies": [ + ], + "features": + { + "ffmpeg": { + "description": "FFMPEG's libav library set", + "dependencies": [ + { + "name": "ffmpeg", + "features": [ + "all" + ] + } + ] + }, + "pthreads": { + "description": "PThreads4W pthreads implementation. Only needed on MSVC", + "dependencies": [ + "pthreads" + ] + } + } +} diff --git a/vendor/raylib b/vendor/raylib new file mode 160000 index 0000000..9f3cadf --- /dev/null +++ b/vendor/raylib @@ -0,0 +1 @@ +Subproject commit 9f3cadf1e618f125bd9b282c7759f8cb26ce17fc diff --git a/winstuff.c b/winstuff.c new file mode 100644 index 0000000..0ade591 --- /dev/null +++ b/winstuff.c @@ -0,0 +1,28 @@ +#ifdef _MSC_VER +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#include +#include +#include +// Function to get monotonic clock time in seconds +double get_monotonic_time() { + static LARGE_INTEGER frequency = { 0 }; + LARGE_INTEGER counter; + + // Initialize frequency only once + if (frequency.QuadPart == 0) { + if (!QueryPerformanceFrequency(&frequency)) { + fprintf(stderr, "High-resolution performance counter not supported.\n"); + return -1.0; + } + } + + // Get current counter value + if (!QueryPerformanceCounter(&counter)) { + fprintf(stderr, "Failed to query performance counter.\n"); + return -1.0; + } + + // Convert to seconds + return (double)counter.QuadPart / (double)frequency.QuadPart; +} +#endif \ No newline at end of file