From bf45ba2a48d75586b9999bbfe88abd21ee6e78f3 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Fri, 9 May 2025 13:50:51 +0200 Subject: [PATCH] shitty initial commit --- .idea/.gitignore | 8 + .idea/editor.xml | 344 +++++++++++++++++++++++++++ .idea/linWIN.iml | 2 + .idea/material_theme_project_new.xml | 12 + .idea/misc.xml | 7 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + CMakeLists.txt | 33 +++ src/window/VideoPlayer.cpp | 122 ++++++++++ src/window/VideoPlayer.h | 35 +++ src/window/X11Window.cpp | 49 ++++ src/window/X11Window.h | 26 ++ src/window/main.cpp | 56 +++++ 13 files changed, 708 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/editor.xml create mode 100644 .idea/linWIN.iml create mode 100644 .idea/material_theme_project_new.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 CMakeLists.txt create mode 100644 src/window/VideoPlayer.cpp create mode 100644 src/window/VideoPlayer.h create mode 100644 src/window/X11Window.cpp create mode 100644 src/window/X11Window.h create mode 100644 src/window/main.cpp diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/editor.xml b/.idea/editor.xml new file mode 100644 index 0000000..25c6c37 --- /dev/null +++ b/.idea/editor.xml @@ -0,0 +1,344 @@ + + + + + \ No newline at end of file diff --git a/.idea/linWIN.iml b/.idea/linWIN.iml new file mode 100644 index 0000000..f08604b --- /dev/null +++ b/.idea/linWIN.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml new file mode 100644 index 0000000..250ea61 --- /dev/null +++ b/.idea/material_theme_project_new.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0b76fe5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..f98a836 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..196aa62 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,33 @@ +cmake_minimum_required(VERSION 3.10) +project(linWIN) + +set(CMAKE_CXX_STANDARD 20) + +# Find and link X11 +find_package(X11 REQUIRED) +include_directories(${X11_INCLUDE_DIR}) + +# Use pkg-config to find FFmpeg libraries +find_package(PkgConfig REQUIRED) +pkg_check_modules(FFMPEG REQUIRED libavformat libavcodec libavutil libswscale) + +# Debug FFmpeg variables +message(STATUS "FFmpeg include dirs: ${FFMPEG_INCLUDE_DIRS}") +message(STATUS "FFmpeg libraries: ${FFMPEG_LIBRARIES}") + +# Include directories for both FFmpeg and X11 +include_directories(${X11_INCLUDE_DIR}) +include_directories(${FFMPEG_INCLUDE_DIRS}) + +# Add the executable and all source files +add_executable(linWIN + src/window/main.cpp + src/window/X11Window.cpp + src/window/VideoPlayer.cpp +) + +# Link FFmpeg and X11 (ensure FFmpeg libraries are linked before X11) +target_link_libraries(linWIN + ${FFMPEG_LIBRARIES} + ${X11_LIBRARIES} +) diff --git a/src/window/VideoPlayer.cpp b/src/window/VideoPlayer.cpp new file mode 100644 index 0000000..6bcf4fc --- /dev/null +++ b/src/window/VideoPlayer.cpp @@ -0,0 +1,122 @@ +#include "VideoPlayer.h" +#include + +VideoPlayer::VideoPlayer(const std::string& path) { + if (avformat_open_input(&fmtCtx, path.c_str(), nullptr, nullptr) != 0) { + throw std::runtime_error("Could not open input file"); + } + if (avformat_find_stream_info(fmtCtx, nullptr) < 0) { + avformat_close_input(&fmtCtx); + throw std::runtime_error("Could not find stream info"); + } + for (unsigned i = 0; i < fmtCtx->nb_streams; i++) { + if (fmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + videoStream = static_cast(i); + break; + } + } + if (videoStream == -1) { + avformat_close_input(&fmtCtx); + throw std::runtime_error("Could not find video stream"); + } + + AVCodecParameters* codecPar = fmtCtx->streams[videoStream]->codecpar; + codec = avcodec_find_decoder(codecPar->codec_id); + if (!codec) { + avformat_close_input(&fmtCtx); + throw std::runtime_error("Unsupported codec"); + } + + codecCtx = avcodec_alloc_context3(codec); + if (!codecCtx) { + avformat_close_input(&fmtCtx); + throw std::runtime_error("Could not allocate codec context"); + } + if (avcodec_parameters_to_context(codecCtx, codecPar) < 0) { + avcodec_free_context(&codecCtx); + avformat_close_input(&fmtCtx); + throw std::runtime_error("Could not copy codec parameters"); + } + if (avcodec_open2(codecCtx, codec, nullptr) < 0) { + avcodec_free_context(&codecCtx); + avformat_close_input(&fmtCtx); + throw std::runtime_error("Could not open codec"); + } + + frame = av_frame_alloc(); + if (!frame) { + avcodec_free_context(&codecCtx); + avformat_close_input(&fmtCtx); + throw std::runtime_error("Could not allocate frame"); + } + packet = av_packet_alloc(); + if (!packet) { + av_frame_free(&frame); + avcodec_free_context(&codecCtx); + avformat_close_input(&fmtCtx); + throw std::runtime_error("Could not allocate packet"); + } + bgraFrame = av_frame_alloc(); + if (!bgraFrame) { + av_packet_free(&packet); + av_frame_free(&frame); + avcodec_free_context(&codecCtx); + avformat_close_input(&fmtCtx); + throw std::runtime_error("Could not allocate BGRA frame"); + } + int numBytes = av_image_get_buffer_size(AV_PIX_FMT_BGRA, codecCtx->width, codecCtx->height, 1); + bgraBuffer = static_cast(av_malloc(numBytes * sizeof(uint8_t))); + if (!bgraBuffer) { + av_frame_free(&bgraFrame); + av_packet_free(&packet); + av_frame_free(&frame); + avcodec_free_context(&codecCtx); + avformat_close_input(&fmtCtx); + throw std::runtime_error("Could not allocate BGRA buffer"); + } + av_image_fill_arrays(bgraFrame->data, bgraFrame->linesize, bgraBuffer, + AV_PIX_FMT_BGRA, codecCtx->width, codecCtx->height, 1); + + swsCtx = sws_getContext(codecCtx->width, codecCtx->height, codecCtx->pix_fmt, + codecCtx->width, codecCtx->height, AV_PIX_FMT_BGRA, + SWS_BILINEAR, nullptr, nullptr, nullptr); + if (!swsCtx) { + av_free(bgraBuffer); + av_frame_free(&bgraFrame); + av_packet_free(&packet); + av_frame_free(&frame); + avcodec_free_context(&codecCtx); + avformat_close_input(&fmtCtx); + throw std::runtime_error("Could not initialize sws context"); + } +} + +bool VideoPlayer::getNextFrame(uint8_t*& outData, int& width, int& height) { + while (av_read_frame(fmtCtx, packet) >= 0) { + if (packet->stream_index == videoStream) { + int ret = avcodec_send_packet(codecCtx, packet); + av_packet_unref(packet); + if (ret < 0) continue; + ret = avcodec_receive_frame(codecCtx, frame); + if (ret == 0) { + sws_scale(swsCtx, frame->data, frame->linesize, 0, codecCtx->height, + bgraFrame->data, bgraFrame->linesize); + outData = bgraFrame->data[0]; + width = codecCtx->width; + height = codecCtx->height; + return true; + } + } + } + return false; // End of file or error +} + +VideoPlayer::~VideoPlayer() { + if (swsCtx) sws_freeContext(swsCtx); + if (bgraBuffer) av_free(bgraBuffer); + if (bgraFrame) av_frame_free(&bgraFrame); + if (packet) av_packet_free(&packet); + if (frame) av_frame_free(&frame); + if (codecCtx) avcodec_free_context(&codecCtx); + if (fmtCtx) avformat_close_input(&fmtCtx); +} diff --git a/src/window/VideoPlayer.h b/src/window/VideoPlayer.h new file mode 100644 index 0000000..ba5a0e3 --- /dev/null +++ b/src/window/VideoPlayer.h @@ -0,0 +1,35 @@ +#ifndef VIDEOPLAYER_H +#define VIDEOPLAYER_H + +#include +#include + +extern "C" { +#include +#include +#include +#include +} + +class VideoPlayer { +public: + explicit VideoPlayer(const std::string& path); + ~VideoPlayer(); + + // Returns true if a new frame is available, false if end of file. + // outData points to the BGRA data (do not free). width/height are set. + bool getNextFrame(uint8_t*& outData, int& width, int& height); + +private: + AVFormatContext* fmtCtx = nullptr; + const AVCodec* codec = nullptr; + AVCodecContext* codecCtx = nullptr; + AVFrame* frame = nullptr; + AVFrame* bgraFrame = nullptr; + AVPacket* packet = nullptr; + uint8_t* bgraBuffer = nullptr; + SwsContext* swsCtx = nullptr; + int videoStream = -1; +}; + +#endif // VIDEOPLAYER_H diff --git a/src/window/X11Window.cpp b/src/window/X11Window.cpp new file mode 100644 index 0000000..c0478ba --- /dev/null +++ b/src/window/X11Window.cpp @@ -0,0 +1,49 @@ +#include "X11Window.h" +#include +#include +#include + +// Constructor definition +X11Window::X11Window(int width, int height, RenderCallback onRender) + : renderCallback(std::move(onRender)) // Initialize the render callback +{ + // Open X display connection + display = XOpenDisplay(nullptr); + if (!display) { + std::cerr << "Unable to open X display\n"; + exit(1); + } + + // Set screen and create window + screen = DefaultScreen(display); + window = XCreateSimpleWindow(display, RootWindow(display, screen), + 100, 100, width, height, 1, + BlackPixel(display, screen), + WhitePixel(display, screen)); + + // Set event mask for Exposure and KeyPress + XSelectInput(display, window, ExposureMask | KeyPressMask); + XMapWindow(display, window); + + // Default graphics context + gc = DefaultGC(display, screen); +} + +// Destructor definition +X11Window::~X11Window() { + XDestroyWindow(display, window); + XCloseDisplay(display); +} + +// Event loop with render callback +void X11Window::run() { + XEvent event; + while (true) { + XNextEvent(display, &event); + if (event.type == Expose && renderCallback) { + renderCallback(display, window, gc); // Call the render callback + } else if (event.type == KeyPress) { + break; // Exit the loop on key press + } + } +} diff --git a/src/window/X11Window.h b/src/window/X11Window.h new file mode 100644 index 0000000..52085c9 --- /dev/null +++ b/src/window/X11Window.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +class X11Window { +public: + // Type alias for render callback + using RenderCallback = std::function; + + // Constructor declaration with three arguments + X11Window(int width, int height, RenderCallback onRender); + ~X11Window(); + + // Start the event loop + void run(); + +private: + Display* display; + Window window; + GC gc; + int screen; + + RenderCallback renderCallback; // Store the render callback +}; + diff --git a/src/window/main.cpp b/src/window/main.cpp new file mode 100644 index 0000000..0ddef83 --- /dev/null +++ b/src/window/main.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include +#include "VideoPlayer.h" +#include "X11Window.h" + +int main(int argc, char* argv[]) { + if (argc < 2) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return -1; + } + + // Initialize the VideoPlayer with the provided video path + VideoPlayer videoPlayer(argv[1]); + uint8_t* frameData = nullptr; + int width = 0, height = 0; + + // Lambda for rendering a frame + auto renderFrame = [&](Display* display, Window window, GC gc) { + if (videoPlayer.getNextFrame(frameData, width, height)) { + // Create XImage using BGRA data from VideoPlayer + XImage* xImage = XCreateImage( + display, + DefaultVisual(display, DefaultScreen(display)), + 24, // depth + ZPixmap, + 0, + reinterpret_cast(frameData), + width, + height, + 32, + width * 4 + ); + if (!xImage) { + std::cerr << "Error creating XImage from frame data." << std::endl; + return; + } + XPutImage(display, window, gc, xImage, 0, 0, 0, 0, width, height); + XFlush(display); + + // Clean up the XImage resource, not the frameData + XDestroyImage(xImage); + + // Wait ~25 FPS + usleep(40000); + } + }; + + // Create the X11 window and run the event loop + X11Window window(640, 480, renderFrame); + window.run(); + + return 0; +}