diff --git a/apps/video-player/src/video-player.cpp b/apps/video-player/src/video-player.cpp index 60810ad..8314392 100644 --- a/apps/video-player/src/video-player.cpp +++ b/apps/video-player/src/video-player.cpp @@ -31,20 +31,22 @@ int main(int argc, char* argv[]) { const char* videoFileName = argv[1]; - if (!initVideoPlayer()) { + VideoPlayer player; + + if (!initVideoPlayer(player)) { std::cerr << "Failed to initialize video player\n"; return -1; } - if (!loadVideo(videoFileName)) { + if (!loadVideo(player, videoFileName)) { std::cerr << "Failed to load video: " << videoFileName << "\n"; - closeVideoPlayer(); + closeVideoPlayer(player); return -1; } - playVideo(); + playVideo(player); - closeVideoPlayer(); + closeVideoPlayer(player); return 0; } diff --git a/modules/video/include/video.h b/modules/video/include/video.h index 95d6dba..eec167b 100644 --- a/modules/video/include/video.h +++ b/modules/video/include/video.h @@ -32,23 +32,26 @@ extern "C" { #include #include #endif -static AVFormatContext* formatContext = nullptr; -static AVCodecParameters* codecParameters = nullptr; -static const AVCodec* codec = nullptr; -static AVCodecContext* codecContext = nullptr; -static SDL_Window* window = nullptr; -static SDL_Renderer* renderer = nullptr; -static SDL_Texture* texture = nullptr; -static struct SwsContext* swsContext = nullptr; -static AVFrame* frame = nullptr; -static int videoStream = -1; -static AVRational timeBase; -static double frameDuration; -bool initVideoPlayer(); -bool loadVideo(const char* filename); -void playVideo(); -void closeVideoPlayer(); +struct VideoPlayer { + AVFormatContext* formatContext = nullptr; + AVCodecParameters* codecParameters = nullptr; + AVCodecContext* codecContext = nullptr; + SDL_Window* window = nullptr; + SDL_Renderer* renderer = nullptr; + SDL_Texture* texture = nullptr; + struct SwsContext* swsContext = nullptr; + AVFrame* frame = nullptr; + int videoStream = -1; + AVRational timeBase; + double frameDuration; +}; + +bool initVideoPlayer(VideoPlayer& player); +bool loadVideo(VideoPlayer& player, const char* filename); +void playVideo(VideoPlayer& player); +void closeVideoPlayer(VideoPlayer& player); + #ifdef __cplusplus } #endif \ No newline at end of file diff --git a/modules/video/src/CMakeLists.txt b/modules/video/src/CMakeLists.txt index 89211a1..0046757 100644 --- a/modules/video/src/CMakeLists.txt +++ b/modules/video/src/CMakeLists.txt @@ -26,20 +26,17 @@ project(${TARGET}) find_package(FFMPEG REQUIRED) find_package(SDL2 CONFIG REQUIRED) -include_directories(${PROJECT_SOURCE_DIR}/../include) +include_directories(${PROJECT_SOURCE_DIR}/../include + ${FFMPEG_INCLUDE_DIRS}) set(VIDEO_SRC video.cpp ) add_library(${TARGET} STATIC ${VIDEO_SRC}) -target_include_directories(${TARGET} PRIVATE ${FFMPEG_INCLUDE_DIRS}) -target_link_directories(${TARGET} PRIVATE ${FFMPEG_LIBRARY_DIRS}) target_link_libraries(${TARGET} PRIVATE ${FFMPEG_LIBRARIES} SDL2::SDL2-static) add_library(${TARGET}_wrapper SHARED ${VIDEO_SRC}) # required for bindings -target_include_directories(${TARGET}_wrapper PRIVATE ${FFMPEG_INCLUDE_DIRS}) -target_link_directories(${TARGET}_wrapper PRIVATE ${FFMPEG_LIBRARY_DIRS}) target_link_libraries(${TARGET}_wrapper PRIVATE ${FFMPEG_LIBRARIES} SDL2::SDL2-static) install(TARGETS ${TARGET} ${TARGET}_wrapper) \ No newline at end of file diff --git a/modules/video/src/video.cpp b/modules/video/src/video.cpp index a4f10ee..2cfabc8 100644 --- a/modules/video/src/video.cpp +++ b/modules/video/src/video.cpp @@ -22,147 +22,146 @@ #include -bool initVideoPlayer() { - if (SDL_Init(SDL_INIT_VIDEO) < 0) { - std::cerr << "SDL initialization failed: " << SDL_GetError() << "\n"; - return false; - } - - // av_register_all(); - if (avformat_network_init() < 0) { - std::cerr << "Failed to initialize FFmpeg network\n"; - return false; - } - - return true; +bool initVideoPlayer(VideoPlayer& player) { + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + std::cerr << "SDL initialization failed: " << SDL_GetError() << "\n"; + return false; + } + + if (avformat_network_init() < 0) { + std::cerr << "Failed to initialize FFmpeg network\n"; + return false; + } + + return true; } -bool loadVideo(const char* filename) { - if (avformat_open_input(&formatContext, filename, NULL, NULL) != 0) { - std::cerr << "Couldn't open video file\n"; - return false; - } - - if (avformat_find_stream_info(formatContext, NULL) < 0) { - std::cerr << "Couldn't find stream information\n"; - return false; - } - - videoStream = -1; - for (int i = 0; i < formatContext->nb_streams; i++) { - if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - videoStream = i; - break; +bool loadVideo(VideoPlayer& player, const char* filename) { + if (avformat_open_input(&player.formatContext, filename, NULL, NULL) != 0) { + std::cerr << "Couldn't open video file\n"; + return false; + } + + if (avformat_find_stream_info(player.formatContext, NULL) < 0) { + std::cerr << "Couldn't find stream information\n"; + return false; + } + + player.videoStream = -1; + for (int i = 0; i < player.formatContext->nb_streams; i++) { + if (player.formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { + player.videoStream = i; + break; + } + } + + if (player.videoStream == -1) { + std::cerr << "Couldn't find a video stream\n"; + return false; } - } - - if (videoStream == -1) { - std::cerr << "Couldn't find a video stream\n"; - return false; - } - - codecParameters = formatContext->streams[videoStream]->codecpar; - codec = avcodec_find_decoder(codecParameters->codec_id); - if (!codec) { - std::cerr << "Unsupported codec\n"; - return false; - } - - codecContext = avcodec_alloc_context3(codec); - if (avcodec_parameters_to_context(codecContext, codecParameters) < 0) { - std::cerr << "Failed to copy codec parameters to codec context\n"; - return false; - } - - if (avcodec_open2(codecContext, codec, NULL) < 0) { - std::cerr << "Failed to open codec\n"; - return false; - } - - window = SDL_CreateWindow("Video Player", SDL_WINDOWPOS_UNDEFINED, - SDL_WINDOWPOS_UNDEFINED, codecContext->width, - codecContext->height, - SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); - if (!window) { - std::cerr << "Failed to create SDL window: " << SDL_GetError() << "\n"; - return false; - } - - renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); - if (!renderer) { - std::cerr << "Failed to create SDL renderer: " << SDL_GetError() << "\n"; - return false; - } - - texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_YV12, - SDL_TEXTUREACCESS_STREAMING, codecContext->width, - codecContext->height); - if (!texture) { - std::cerr << "Failed to create SDL texture: " << SDL_GetError() << "\n"; - return false; - } - - swsContext = sws_getContext(codecContext->width, codecContext->height, - codecContext->pix_fmt, codecContext->width, - codecContext->height, AV_PIX_FMT_YUV420P, - SWS_BILINEAR, NULL, NULL, NULL); - if (!swsContext) { - std::cerr << "Failed to create SwsContext\n"; - return false; - } - - timeBase = formatContext->streams[videoStream]->time_base; - frameDuration = av_q2d(timeBase); - - return true; + + player.codecParameters = player.formatContext->streams[player.videoStream]->codecpar; + const AVCodec* codec = avcodec_find_decoder(player.codecParameters->codec_id); + if (!codec) { + std::cerr << "Unsupported codec\n"; + return false; + } + + player.codecContext = avcodec_alloc_context3(codec); + if (avcodec_parameters_to_context(player.codecContext, player.codecParameters) < 0) { + std::cerr << "Failed to copy codec parameters to codec context\n"; + return false; + } + + if (avcodec_open2(player.codecContext, codec, NULL) < 0) { + std::cerr << "Failed to open codec\n"; + return false; + } + + player.window = SDL_CreateWindow("Video Player", SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, player.codecContext->width, + player.codecContext->height, + SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); + if (!player.window) { + std::cerr << "Failed to create SDL window: " << SDL_GetError() << "\n"; + return false; + } + + player.renderer = SDL_CreateRenderer(player.window, -1, SDL_RENDERER_ACCELERATED); + if (!player.renderer) { + std::cerr << "Failed to create SDL renderer: " << SDL_GetError() << "\n"; + return false; + } + + player.texture = SDL_CreateTexture(player.renderer, SDL_PIXELFORMAT_YV12, + SDL_TEXTUREACCESS_STREAMING, player.codecContext->width, + player.codecContext->height); + if (!player.texture) { + std::cerr << "Failed to create SDL texture: " << SDL_GetError() << "\n"; + return false; + } + + player.swsContext = sws_getContext(player.codecContext->width, player.codecContext->height, + player.codecContext->pix_fmt, player.codecContext->width, + player.codecContext->height, AV_PIX_FMT_YUV420P, + SWS_BILINEAR, NULL, NULL, NULL); + if (!player.swsContext) { + std::cerr << "Failed to create SwsContext\n"; + return false; + } + + player.timeBase = player.formatContext->streams[player.videoStream]->time_base; + player.frameDuration = av_q2d(player.timeBase); + + return true; } -void playVideo() { - AVPacket packet; - Uint32 previousFrameTime = SDL_GetTicks(); - - while (av_read_frame(formatContext, &packet) >= 0) { - if (packet.stream_index == videoStream) { - if (avcodec_send_packet(codecContext, &packet) < 0) { - std::cerr << "Error sending a packet for decoding\n"; - continue; - } - - if (avcodec_receive_frame(codecContext, frame) == 0) { - sws_scale(swsContext, frame->data, frame->linesize, 0, frame->height, - frame->data, frame->linesize); - - SDL_UpdateYUVTexture(texture, nullptr, frame->data[0], - frame->linesize[0], frame->data[1], - frame->linesize[1], frame->data[2], - frame->linesize[2]); - SDL_RenderClear(renderer); - SDL_RenderCopy(renderer, texture, nullptr, nullptr); - SDL_RenderPresent(renderer); - - Uint32 currentFrameTime = SDL_GetTicks(); - Uint32 elapsedTime = currentFrameTime - previousFrameTime; - previousFrameTime = currentFrameTime; - - int desiredDelay = (int)(frameDuration * 1000) - elapsedTime; - - if (desiredDelay > 0) { - SDL_Delay(desiredDelay); +void playVideo(VideoPlayer& player) { + AVPacket packet; + Uint32 previousFrameTime = SDL_GetTicks(); + + while (av_read_frame(player.formatContext, &packet) >= 0) { + if (packet.stream_index == player.videoStream) { + if (avcodec_send_packet(player.codecContext, &packet) < 0) { + std::cerr << "Error sending a packet for decoding\n"; + continue; + } + + if (avcodec_receive_frame(player.codecContext, player.frame) == 0) { + sws_scale(player.swsContext, player.frame->data, player.frame->linesize, 0, player.frame->height, + player.frame->data, player.frame->linesize); + + SDL_UpdateYUVTexture(player.texture, nullptr, player.frame->data[0], + player.frame->linesize[0], player.frame->data[1], + player.frame->linesize[1], player.frame->data[2], + player.frame->linesize[2]); + SDL_RenderClear(player.renderer); + SDL_RenderCopy(player.renderer, player.texture, nullptr, nullptr); + SDL_RenderPresent(player.renderer); + + Uint32 currentFrameTime = SDL_GetTicks(); + Uint32 elapsedTime = currentFrameTime - previousFrameTime; + previousFrameTime = currentFrameTime; + + int desiredDelay = (int)(player.frameDuration * 1000) - elapsedTime; + + if (desiredDelay > 0) { + SDL_Delay(desiredDelay); + } + } } - } + av_packet_unref(&packet); } - av_packet_unref(&packet); - } } -void closeVideoPlayer() { - SDL_DestroyTexture(texture); - SDL_DestroyRenderer(renderer); - SDL_DestroyWindow(window); - SDL_Quit(); +void closeVideoPlayer(VideoPlayer& player) { + SDL_DestroyTexture(player.texture); + SDL_DestroyRenderer(player.renderer); + SDL_DestroyWindow(player.window); + SDL_Quit(); - av_frame_free(&frame); - avcodec_close(codecContext); - avformat_close_input(&formatContext); - avformat_network_deinit(); + av_frame_free(&player.frame); + avcodec_close(player.codecContext); + avformat_close_input(&player.formatContext); + avformat_network_deinit(); } \ No newline at end of file diff --git a/modules/video/test/video-test.cpp b/modules/video/test/video-test.cpp index cfb52a0..7bb649d 100644 --- a/modules/video/test/video-test.cpp +++ b/modules/video/test/video-test.cpp @@ -1,7 +1,7 @@ // The MIT License -# +// // Copyright (c) 2024 MGTheTrain -# +// // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights @@ -22,33 +22,38 @@ #include #include -// Test fixture for vide functions +// Test fixture for video functions class VideoPlaybackTest : public ::testing::Test { - protected: - virtual void SetUp() {} +protected: + virtual void SetUp() { + initVideoPlayer(player); + } - virtual void TearDown() {} + virtual void TearDown() { + closeVideoPlayer(player); + } + + VideoPlayer player; }; // Test case for loading a video file TEST_F(VideoPlaybackTest, LoadVideoFileTest) { - const char* filename = "assets/mp4/file_example_MP4_1280_10MG.mp4"; - EXPECT_TRUE(loadVideo(filename)); + const char* filename = "assets/mp4/file_example_MP4_1280_10MG.mp4"; + EXPECT_TRUE(loadVideo(player, filename)); } // Test case for starting video playback TEST_F(VideoPlaybackTest, StartPlaybackTest) { - const char* filename = "assets/mp4/file_example_MP4_1280_10MG.mp4"; - EXPECT_TRUE(loadVideo(filename)); - playVideo(); + const char* filename = "assets/mp4/file_example_MP4_1280_10MG.mp4"; + EXPECT_TRUE(loadVideo(player, filename)); + playVideo(player); } // Test case for closing video file and terminating video player TEST_F(VideoPlaybackTest, CloseVideoFileTest) { - const char* filename = "assets/mp4/file_example_MP4_1280_10MG.mp4"; - EXPECT_TRUE(loadVideo(filename)); - playVideo(); - closeVideoPlayer(); + const char* filename = "assets/mp4/file_example_MP4_1280_10MG.mp4"; + EXPECT_TRUE(loadVideo(player, filename)); + playVideo(player); } -#endif \ No newline at end of file +#endif