From d389fbd6d5a002d24a35c89e37a6e0e71981afa0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hamal=20Dvo=C5=99=C3=A1k?= <mordae@anilinux.org> Date: Sun, 16 Mar 2025 19:00:26 +0100 Subject: [PATCH] Initial import --- .clang-format | 112 +++++++++++++++++++ .clangd | 2 + .gitignore | 1 + src/CMakeLists.txt | 25 +++++ src/main.c | 266 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 406 insertions(+) create mode 100644 .clang-format create mode 100644 .clangd create mode 100644 .gitignore create mode 100644 src/CMakeLists.txt create mode 100644 src/main.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..54a2757 --- /dev/null +++ b/.clang-format @@ -0,0 +1,112 @@ +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 100 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false + +ForEachMacros: + - 'for_each' + +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentGotoLabels: false +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +# Taken from git's rules +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 + +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatementsExceptForEachMacros +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp03 +TabWidth: 4 +UseTab: Always +... diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..ec0b890 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + CompilationDatabase: build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..84c7387 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.21) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +project(lazier-player) + +find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(mpv REQUIRED IMPORTED_TARGET mpv) + +if(CMAKE_BUILD_TYPE MATCHES "Debug") + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) +endif() + +add_executable( + lazier-player + main.c +) + +target_link_libraries(lazier-player PRIVATE SDL3::SDL3 mpv m) +set_property(TARGET lazier-player PROPERTY C_STANDARD 23) +target_compile_options(lazier-player PRIVATE -Wall -Wextra -Wnull-dereference) +target_include_directories(lazier-player PRIVATE include) +install(TARGETS lazier-player DESTINATION bin) diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..4719db1 --- /dev/null +++ b/src/main.c @@ -0,0 +1,266 @@ +#include <SDL3/SDL.h> +#include <mpv/client.h> +#include <mpv/render.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +typedef struct { + mpv_handle *mpv; + mpv_render_context *render_ctx; + SDL_Window *window; + SDL_Renderer *renderer; + SDL_Texture *texture; + int width; + int height; + int running; + int needs_redraw; +} App; + +static void SDL_PrintError(const char *msg) +{ + fprintf(stderr, "sdl: %s: %s\n", msg, SDL_GetError()); +} + +static void mpv_print_error(int code, const char *msg) +{ + fprintf(stderr, "mpv: %s: %s\n", msg, mpv_error_string(code)); +} + +static void handle_mpv_events(App *app) +{ + while (true) { + mpv_event *event = mpv_wait_event(app->mpv, 0); + + if (event->event_id == MPV_EVENT_NONE) + break; + + printf("mpv: event=%s\n", mpv_event_name(event->event_id)); + + if (event->event_id == MPV_EVENT_PROPERTY_CHANGE) { + mpv_event_property *prop = event->data; + + if (!strcmp(prop->name, "width") || !strcmp(prop->name, "height")) { + int64_t width = 0; + int64_t height = 0; + + mpv_get_property(app->mpv, "width", MPV_FORMAT_INT64, &width); + mpv_get_property(app->mpv, "height", MPV_FORMAT_INT64, &height); + + if (width > 0 && height > 0 && (width != app->width || height != app->height)) { + app->width = width; + app->height = height; + + if (app->texture) + SDL_DestroyTexture(app->texture); + + app->texture = SDL_CreateTexture(app->renderer, SDL_PIXELFORMAT_XBGR8888, + SDL_TEXTUREACCESS_STREAMING, width, height); + + if (!app->texture) { + SDL_PrintError("SDL_CreateTexture"); + } else { + // Resize window to match video dimensions + SDL_SetWindowSize(app->window, app->width, app->height); + } + } + } + } else if (event->event_id == MPV_EVENT_END_FILE) { + app->running = 0; + } + } +} + +static void render_video(App *app) +{ + int err; + + // Clear the renderer + if (!SDL_SetRenderDrawColor(app->renderer, 0, 0, 0, 255)) + SDL_PrintError("SDL_SetRenderDrawColor"); + + if (!SDL_RenderClear(app->renderer)) + SDL_PrintError("SDL_RenderClear"); + + // Render MPV frame + if (app->texture && app->render_ctx) { + void *pixels; + int stride = 0; + + if (!SDL_LockTexture(app->texture, NULL, &pixels, &stride)) { + SDL_PrintError("SDL_LockTexture"); + return; + } + + float w, h; + if (!SDL_GetTextureSize(app->texture, &w, &h)) + SDL_PrintError("SDL_GetTextureSize"); + + int size[] = { w, h }; + + mpv_render_param params[] = { + { MPV_RENDER_PARAM_SW_SIZE, &size }, // + { MPV_RENDER_PARAM_SW_FORMAT, "rgb0" }, // + { MPV_RENDER_PARAM_SW_STRIDE, &stride }, // + { MPV_RENDER_PARAM_SW_POINTER, pixels }, // + { MPV_RENDER_PARAM_INVALID, NULL }, + }; + + if ((err = mpv_render_context_render(app->render_ctx, params))) + mpv_print_error(err, "mpv_render_context_render"); + + SDL_UnlockTexture(app->texture); + + if (!SDL_RenderTexture(app->renderer, app->texture, NULL, NULL)) { + SDL_PrintError("SDL_RenderTexture"); + } + } + + // Draw shapes over the video + // 1. Red rectangle + if (!SDL_SetRenderDrawColor(app->renderer, 255, 0, 0, 128)) + SDL_PrintError("SDL_SetRenderDrawColor"); + + SDL_FRect rect = { 50, 50, 100, 80 }; + + if (!SDL_RenderFillRect(app->renderer, &rect)) + SDL_PrintError("SDL_RenderFillRect"); + + // 2. Blue circle (approximated with points) + if (!SDL_SetRenderDrawColor(app->renderer, 0, 0, 255, 128)) + SDL_PrintError("SDL_SetRenderDrawColor"); + + int centerX = app->width - 100; + int centerY = app->height - 100; + int radius = 40; + + for (int i = 0; i < 360; i++) { + float angle = i * 3.14159f / 180.0f; + float x = centerX + radius * SDL_cosf(angle); + float y = centerY + radius * SDL_sinf(angle); + + if (!SDL_RenderPoint(app->renderer, x, y)) + SDL_PrintError("SDL_RenderPoint"); + } + + // 3. Green line + if (!SDL_SetRenderDrawColor(app->renderer, 0, 255, 0, 200)) + SDL_PrintError("SDL_SetRenderDrawColor"); + + if (!SDL_RenderLine(app->renderer, 0, 0, app->width, app->height)) + SDL_PrintError("SDL_RenderLine"); + + if (!SDL_RenderPresent(app->renderer)) + SDL_PrintError("SDL_RenderPresent"); +} + +int main(int argc, char *argv[]) +{ + int err; + + if (argc < 2) { + printf("Usage: %s <video_file>\n", argv[0]); + return 1; + } + + static App app = { 0 }; + app.running = 1; + app.width = 800; + app.height = 600; + + // Initialize SDL + if (!SDL_Init(SDL_INIT_VIDEO)) { + SDL_PrintError("SDL_Init"); + return 1; + } + + // Create window + app.window = SDL_CreateWindow("SDL3 + MPV Video Player", app.width, app.height, 0); + if (!app.window) { + SDL_PrintError("SDL_CreateWindow"); + SDL_Quit(); + return 1; + } + + // Create renderer + app.renderer = SDL_CreateRenderer(app.window, "software"); + if (!app.renderer) { + SDL_PrintError("SDL_CreateRenderer"); + SDL_DestroyWindow(app.window); + SDL_Quit(); + return 1; + } + + // Initialize MPV + app.mpv = mpv_create(); + if (!app.mpv) { + fprintf(stderr, "mpv: mpv_create: returned NULL\n"); + SDL_DestroyRenderer(app.renderer); + SDL_DestroyWindow(app.window); + SDL_Quit(); + return 1; + } + + // Configure MPV + if ((err = mpv_set_option_string(app.mpv, "video-sync", "display-resample"))) + mpv_print_error(err, "mpv_set_option_string"); + + if ((err = mpv_set_option_string(app.mpv, "vo", "libmpv"))) + mpv_print_error(err, "mpv_set_option_string"); + + if ((err = mpv_initialize(app.mpv))) + mpv_print_error(err, "mpv_initialize"); + + // Initialize MPV render context + mpv_render_param params[] = { + { MPV_RENDER_PARAM_API_TYPE, MPV_RENDER_API_TYPE_SW }, + { MPV_RENDER_PARAM_INVALID, NULL }, + }; + + if ((err = mpv_render_context_create(&app.render_ctx, app.mpv, params))) + mpv_print_error(err, "mpv_render_context_create"); + + // Load the video file + const char *cmd[] = { "loadfile", argv[1], NULL }; + + if ((err = mpv_command(app.mpv, cmd))) + mpv_print_error(err, "mpv_command"); + + // Observe properties + if ((err = mpv_observe_property(app.mpv, 0, "width", MPV_FORMAT_INT64))) + mpv_print_error(err, "mpv_observe_property"); + + if ((err = mpv_observe_property(app.mpv, 0, "height", MPV_FORMAT_INT64))) + mpv_print_error(err, "mpv_observe_property"); + + // Main loop + while (app.running) { + SDL_Event event; + while (SDL_PollEvent(&event)) { + if (event.type == SDL_EVENT_QUIT) { + app.running = 0; + } + } + + handle_mpv_events(&app); + render_video(&app); + + SDL_Delay(16); // ~60fps + } + + // Cleanup + if (app.texture) + SDL_DestroyTexture(app.texture); + + if (app.render_ctx) + mpv_render_context_free(app.render_ctx); + + mpv_terminate_destroy(app.mpv); + SDL_DestroyRenderer(app.renderer); + SDL_DestroyWindow(app.window); + SDL_Quit(); + + return 0; +} + +// vim:set ts=4 sw=4 et: