#include #include #include #include #define SDL_MAIN_HANDLED #include #include "util.h" #include "mediaDecoder.h" #include "shaderService.h" #include "shader.h" #include "audioDecoder.h" using std::cout; constexpr int SCREEN_WIDTH = 640; constexpr int SCREEN_HEIGHT = 480; struct OpenglVideoParam { SDL_GLContext glContext; unsigned int VAO, VBO, EBO; unsigned int texs[3]; }; int InitAudio(SDL_Window* window, SDL_Renderer* renderer, const char* targetFilePath, MediaParam& param) { InitDecoder(targetFilePath, param); window = SDL_CreateWindow("mp", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN); renderer= SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); std::jthread(RequestAudioPacket, std::ref(param)).detach(); SDL_AudioSpec des; des.freq = param.audioParam.codecCtx->sample_rate; des.channels = param.audioParam.codecCtx->ch_layout.nb_channels; des.format = AUDIO_S16SYS; des.samples = 1024; des.silence = 0; std::tuple* callbackParam = new std::tuple{window, renderer, &(param.audioParam)}; des.userdata = callbackParam; des.callback = audioCallback; if (SDL_OpenAudio(&des, nullptr) < 0) { cout << SDL_GetError() << "\n"; return -1; } SDL_PauseAudio(0); return 0; } int InitVideo(SDL_Window*& window, const char* targetFilepath, MediaParam& param, OpenglVideoParam& openglVideoParam, ShaderService*& shaderService) { InitDecoder(targetFilepath, param); //FIX: when app exited, the fmtCtx was freed, so need notify decode thread to stop decode and exit. std::jthread(RequestVideoPacket, std::ref(param)).detach(); std::jthread(RequestVideoFrame, std::ref(param)).detach(); const int client_width = param.videoParam.width / 2; const int client_height = param.videoParam.height / 2; window = SDL_CreateWindow( "MP", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, client_width, client_height, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL); if (!window) { cout << SDL_GetError() << "\n"; return -1; } SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); openglVideoParam.glContext = SDL_GL_CreateContext(window); if (!openglVideoParam.glContext) { cout << SDL_GetError() << "\n"; return -1; } if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) { auto error = glad_glGetError(); return static_cast(error); } float vertices[] = { 1.f, 1.f, 0.0, 1.0, 0.0, 1.f, -1.f, 0.0, 1.0, 1.0, -1.f, -1.f, 0.0, 0.0, 1.0, -1.f, 1.f, 0.0, 0.0, 0.0 }; unsigned int indices[]{ 0, 1, 3, 1, 2, 3, }; glGenVertexArrays(1, &openglVideoParam.VAO); glGenBuffers(1, &openglVideoParam.VBO); glGenBuffers(1, &openglVideoParam.EBO); glBindVertexArray(openglVideoParam.VAO); glBindBuffer(GL_ARRAY_BUFFER, openglVideoParam.VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, openglVideoParam.EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), reinterpret_cast(3 * sizeof(float))); glEnableVertexAttribArray(1); glGenTextures(3, openglVideoParam.texs); for (const unsigned int tex : openglVideoParam.texs) { glBindTexture(GL_TEXTURE_2D, tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); } shaderService = new ShaderService{ vSource.data(), fSource.data() }; shaderService->Use(); shaderService->SetUniform("textureY", 0); shaderService->SetUniform("textureU", 1); shaderService->SetUniform("textureV", 2); return 0; } void RenderPicture(SDL_Window* window, SDL_Renderer* renderer, SDL_Texture* texture) { SDL_SetRenderTarget(renderer, texture); SDL_SetRenderTarget(renderer, nullptr); SDL_RenderCopy(renderer, texture, nullptr, nullptr); SDL_RenderPresent(renderer); } void InitImg(SDL_Window*& window, const char* filepath, SDL_Renderer*& renderer, SDL_Surface*& surface, SDL_Texture*& texture) { const cv::Mat img = cv::imread(filepath); auto width = img.cols, height = img.rows; cv::Mat target; SDL_Rect rect; SDL_GetDisplayBounds(0, &rect); width = width > rect.w ? rect.w - 100 : width; height = height > rect.h ? rect.h - 100 : height; const cv::Size size{ width, height }; cv::resize(img, target, size, 0, 0, cv::INTER_LINEAR_EXACT); window = SDL_CreateWindow("mp", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, SDL_WINDOW_SHOWN); surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, 24, SDL_PIXELFORMAT_RGB24); memcpy(surface->pixels, target.data, static_cast(width) * height * target.channels()); renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); texture = SDL_CreateTextureFromSurface(renderer, surface); } void OpenglRenderVideo(MediaParam& param, const OpenglVideoParam& openglVideoParam, ShaderService* shaderService) { AVFrame* frame = av_frame_alloc(); param.videoParam.frameQueue.pop(frame, true, param.videoParam.quit); // TODO: TIMER glBindTexture(GL_TEXTURE_2D, openglVideoParam.texs[0]); glPixelStoref(GL_UNPACK_ROW_LENGTH, static_cast(frame->linesize[0])); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, frame->width, frame->height, 0, GL_RED, GL_UNSIGNED_BYTE, frame->data[0]); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, openglVideoParam.texs[1]); glPixelStoref(GL_UNPACK_ROW_LENGTH, static_cast(frame->linesize[1])); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, frame->width / 2, frame->height / 2, 0, GL_RED, GL_UNSIGNED_BYTE, frame->data[1]); glActiveTexture(GL_TEXTURE2); glBindTexture(GL_TEXTURE_2D, openglVideoParam.texs[2]); glPixelStoref(GL_UNPACK_ROW_LENGTH, static_cast(frame->linesize[2])); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, frame->width / 2, frame->height / 2, 0, GL_RED, GL_UNSIGNED_BYTE, frame->data[2]); av_frame_free(&frame); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); shaderService->Use(); glBindVertexArray(openglVideoParam.VAO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr); } int main(int argc, char** argv) { // Check File const char* targetFilepath = argv[1]; if (targetFilepath == nullptr || !exists(targetFilepath)) { cout << "File Not Exist\n"; return 0; } const FileType fileType = Util::GetFileType(targetFilepath); if (fileType == FileType::ERRORTYPE) { cout << "unsupported file type\n"; return 0; } // INIT SDL_Window* window = nullptr; MediaParam mediaParam{}; OpenglVideoParam openglVideoParam{}; ShaderService* shaderService = nullptr; SDL_Surface* surface = nullptr; SDL_Renderer* renderer = nullptr; SDL_Texture* texture = nullptr; double framerate = .0f; if (SDL_Init(SDL_INIT_EVERYTHING) < 0) { cout << SDL_GetError() << "\n"; return -1; } switch (fileType) { case FileType::VIDEO: { InitVideo(window, targetFilepath, mediaParam, openglVideoParam, shaderService); const auto stream_frame_rate = mediaParam.fmtCtx->streams[mediaParam.videoParam.videoStreamIndex]->avg_frame_rate; framerate = static_cast(stream_frame_rate.den) / stream_frame_rate.num; break; } case FileType::IMG: { InitImg(window, targetFilepath, renderer, surface, texture); break; } case FileType::AUDIO: { InitAudio(window, renderer, targetFilepath, mediaParam); break; } case FileType::ERRORTYPE: return -1; } bool quit = false; auto current_time = std::chrono::system_clock::now(); SDL_Event event; while (!quit) { while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_KEYDOWN: switch (event.key.keysym.scancode) { case SDL_SCANCODE_ESCAPE: quit = true; break; default: break; } break; case SDL_QUIT: quit = true; break; default: break; } } // Render switch (fileType) { case FileType::VIDEO: OpenglRenderVideo(mediaParam, openglVideoParam, shaderService); SDL_GL_SwapWindow(window); std::this_thread::sleep_until(current_time + std::chrono::milliseconds(30)); current_time = std::chrono::system_clock::now(); break; case FileType::IMG: RenderPicture(window, renderer, texture); break; case FileType::AUDIO: break; default: break; } } avcodec_close(mediaParam.videoParam.codecCtx); avformat_close_input(&(mediaParam.fmtCtx)); SDL_GL_DeleteContext(openglVideoParam.glContext); SDL_DestroyWindow(window); SDL_Quit(); return 0; }