FFmpeg再學習 -- SDL 環境搭建和視訊顯示
阿新 • • 發佈:2019-01-01
/** * 最簡單的基於FFmpeg的視訊播放器2(SDL升級版) * Simplest FFmpeg Player 2(SDL Update) * * 雷霄驊 Lei Xiaohua * [email protected] * 中國傳媒大學/數字電視技術 * Communication University of China / Digital TV Technology * http://blog.csdn.net/leixiaohua1020 * * 第2版使用SDL2.0取代了第一版中的SDL1.2 * Version 2 use SDL 2.0 instead of SDL 1.2 in version 1. * * 本程式實現了視訊檔案的解碼和顯示(支援HEVC,H.264,MPEG2等)。 * 是最簡單的FFmpeg視訊解碼方面的教程。 * 通過學習本例子可以瞭解FFmpeg的解碼流程。 * 本版本中使用SDL訊息機制重新整理視訊畫面。 * This software is a simplest video player based on FFmpeg. * Suitable for beginner of FFmpeg. * * 備註: * 標準版在播放視訊的時候,畫面顯示使用延時40ms的方式。這麼做有兩個後果: * (1)SDL彈出的視窗無法移動,一直顯示是忙碌狀態 * (2)畫面顯示並不是嚴格的40ms一幀,因為還沒有考慮解碼的時間。 * SU(SDL Update)版在視訊解碼的過程中,不再使用延時40ms的方式,而是建立了 * 一個執行緒,每隔40ms傳送一個自定義的訊息,告知主函式進行解碼顯示。這樣做之後: * (1)SDL彈出的視窗可以移動了 * (2)畫面顯示是嚴格的40ms一幀 * Remark: * Standard Version use's SDL_Delay() to control video's frame rate, it has 2 * disadvantages: * (1)SDL's Screen can't be moved and always "Busy". * (2)Frame rate can't be accurate because it doesn't consider the time consumed * by avcodec_decode_video2() * SU(SDL Update)Version solved 2 problems above. It create a thread to send SDL * Event every 40ms to tell the main loop to decode and show video frames. */ #include <stdio.h> #include "stdafx.h" #define __STDC_CONSTANT_MACROS #ifdef _WIN32 //Windows extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" #include "SDL2/SDL.h" }; #else //Linux... #ifdef __cplusplus extern "C" { #endif #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> #include <SDL2/SDL.h> #ifdef __cplusplus }; #endif #endif //Refresh Event #define SFM_REFRESH_EVENT (SDL_USEREVENT + 1) #define SFM_BREAK_EVENT (SDL_USEREVENT + 2) int thread_exit = 0; int thread_pause = 0; int sfp_refresh_thread(void *opaque) { thread_exit = 0; thread_pause = 0; while (!thread_exit) { if (!thread_pause) { SDL_Event event; event.type = SFM_REFRESH_EVENT; SDL_PushEvent(&event); //傳送一個事件 } SDL_Delay(40); //可用於調節播放速度 } thread_exit = 0; thread_pause = 0; //Break SDL_Event event; event.type = SFM_BREAK_EVENT; SDL_PushEvent(&event); return 0; } int main(int argc, char* argv[]) { AVFormatContext *pFormatCtx; int i, videoindex; AVCodecContext *pCodecCtx; AVCodec *pCodec; AVFrame *pFrame, *pFrameYUV; uint8_t *out_buffer; AVPacket *packet; int ret, got_picture; //------------SDL---------------- int screen_w, screen_h; SDL_Window *screen; SDL_Renderer* sdlRenderer; SDL_Texture* sdlTexture; SDL_Rect sdlRect; SDL_Thread *video_tid; SDL_Event event; struct SwsContext *img_convert_ctx; //輸入檔案路徑 char filepath[] = "Tai.mp4"; av_register_all(); //註冊所有元件 avformat_network_init(); //初始化網路 pFormatCtx = avformat_alloc_context(); //初始化一個 AVFormatContext if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) { //開啟輸入的視訊檔案 printf("Couldn't open input stream.\n"); return -1; } if (avformat_find_stream_info(pFormatCtx, NULL)<0) { //獲取視訊檔案資訊 printf("Couldn't find stream information.\n"); return -1; } videoindex = -1; for (i = 0; i<pFormatCtx->nb_streams; i++) if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { videoindex = i; break; } if (videoindex == -1) { printf("Didn't find a video stream.\n"); return -1; } pCodecCtx = pFormatCtx->streams[videoindex]->codec; pCodec = avcodec_find_decoder(pCodecCtx->codec_id); //查詢解碼器 if (pCodec == NULL) { printf("Codec not found.\n"); return -1; } if (avcodec_open2(pCodecCtx, pCodec, NULL)<0) { //開啟解碼器 printf("Could not open codec.\n"); return -1; } pFrame = av_frame_alloc(); pFrameYUV = av_frame_alloc(); out_buffer = (uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height)); avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height); packet = (AVPacket *)av_malloc(sizeof(AVPacket)); //Output Info----------------------------- printf("---------------- File Information ---------------\n"); av_dump_format(pFormatCtx, 0, filepath, 0); printf("-------------------------------------------------\n"); img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); //初始化 SDL 系統 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) { printf("Could not initialize SDL - %s\n", SDL_GetError()); return -1; } //SDL 2.0 Support for multiple windows screen_w = pCodecCtx->width; screen_h = pCodecCtx->height; //建立視窗 screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screen_w, screen_h, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); if (!screen) { printf("SDL: could not create window - exiting:%s\n", SDL_GetError()); return -1; } //建立渲染器 sdlRenderer = SDL_CreateRenderer(screen, -1, 0); //IYUV: Y + U + V (3 planes) //YV12: Y + V + U (3 planes) //建立紋理 sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, screen_w, screen_h); //建立一個執行緒 video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL); //------------SDL End------------ //Event Loop for (;;) { //Wait SDL_WaitEvent(&event); if (event.type == SFM_REFRESH_EVENT) { //------------------------------ if (av_read_frame(pFormatCtx, packet) >= 0) { //讀取一幀壓縮資料 if (packet->stream_index == videoindex) { ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet); //解碼一幀壓縮資料 if (ret < 0) { printf("Decode Error.\n"); return -1; } if (got_picture) { sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize); //SDL--------------------------- //設定紋理的資料 SDL_UpdateTexture(sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]); sdlRect.x = 0; sdlRect.y = 0; sdlRect.w = screen_w; sdlRect.h = screen_h; //清空紋理 SDL_RenderClear(sdlRenderer); //SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect ); //將紋理的資料拷貝給渲染器 SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL); //顯示 SDL_RenderPresent(sdlRenderer); //SDL End----------------------- } } av_free_packet(packet); } else { //Exit Thread thread_exit = 1; } } else if (event.type == SDL_WINDOWEVENT) { //If Resize SDL_GetWindowSize(screen, &screen_w, &screen_h); } else if (event.type == SDL_KEYDOWN) { //Pause if (event.key.keysym.sym == SDLK_SPACE) thread_pause = !thread_pause; } else if (event.type == SDL_QUIT) { thread_exit = 1; } else if (event.type == SFM_BREAK_EVENT) { break; } } sws_freeContext(img_convert_ctx); SDL_Quit(); //-------------- av_frame_free(&pFrameYUV); av_frame_free(&pFrame); avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx); return 0; }