=====================================================
最簡單的基于FFmpeg的視頻播放器系列文章列表:
100行代碼實現最簡單的基于FFMPEG+SDL的視頻播放器(SDL1.x)
最簡單的基于FFMPEG+SDL的視頻播放器 ver2 (采用SDL2.0)
最簡單的基于FFmpeg的解碼器-純凈版(不包含libavformat)
最簡單的基于FFMPEG+SDL的視頻播放器:拆分-解碼器和播放器
最簡單的基于FFMPEG的Helloworld程序
=====================================================
簡介
之前做過一個FFMPEG+SDL的簡單播放器:《100行代碼實現最簡單的基于FFMPEG+SDL的視頻播放器》。該播放器采用SDL1.2顯示視頻。最近有不少人反映SDL已經升級到2.0版本了,甚至官網的Wiki上都只有SDL2.0的文檔了,因此下載了SDL 2.0 并且進行了簡單的研究。隨后對此前的播放器進行了修改,將SDL1.2換成了SDL2.0。
注:《100行代碼實現最簡單的基于FFMPEG+SDL的視頻播放器》文章中提到的很多知識這里不再重復記錄。本文重點記錄SDL1.2與SDL2.0的不同。
平臺使用了VC2010,FFmpeg類庫使用了最近的版本,SDL使用2.0版本。
Simplest FFmpeg Player 2
項目主頁
SourceForge:https://sourceforge.net/projects/simplestffmpegplayer/
Github:https://github.com/leixiaohua1020/simplest_ffmpeg_player
開源中國:http://git.oschina.net/leixiaohua1020/simplest_ffmpeg_player
流程圖
FFmpeg解碼一個視頻流程如下圖所示:
SDL2.0顯示YUV的流程圖:
對比SDL1.2的流程圖,發現變化還是很大的。幾乎所有的API都發生了變化。但是函數和變量有一定的對應關系:
SDL_SetVideoMode()————SDL_CreateWindow()
SDL_Surface————SDL_Window
SDL_CreateYUVOverlay()————SDL_CreateTexture()
SDL_Overlay————SDL_Texture
不再一一例舉。
下圖為SDL1.x顯示YUV的流程圖。
簡單解釋各個變量的作用:
SDL_Window就是使用SDL的時候彈出的那個窗口。在SDL1.x版本中,只可以創建一個一個窗口。在SDL2.0版本中,可以創建多個窗口。
SDL_Texture用于顯示YUV數據。一個SDL_Texture對應一幀YUV數據。
SDL_Renderer用于渲染SDL_Texture至SDL_Window。
SDL_Rect用于確定SDL_Texture顯示的位置。注意:一個SDL_Texture可以指定多個不同的SDL_Rect,這樣就可以在SDL_Window不同位置顯示相同的內容(使用SDL_RenderCopy()函數)。
它們的關系如下圖所示:
下圖舉了個例子,指定了4個SDL_Rect,可以實現4分屏的顯示。
simplest_ffmpeg_player(標準版)代碼
最基礎的版本,學習的開始。
[cpp]?view plaincopy
???????????????????????????#include?<stdio.h>????#define?__STDC_CONSTANT_MACROS????#ifdef?_WIN32????extern?"C"??{??#include?"libavcodec/avcodec.h"??#include?"libavformat/avformat.h"??#include?"libswscale/swscale.h"??#include?"libavutil/imgutils.h"??#include?"SDL2/SDL.h"??};??#else????#ifdef?__cplusplus??extern?"C"??{??#endif??#include?<libavcodec/avcodec.h>??#include?<libavformat/avformat.h>??#include?<libswscale/swscale.h>??#include?<SDL2/SDL.h>??#include?<libavutil/imgutils.h>??#ifdef?__cplusplus??};??#endif??#endif??????#define?OUTPUT_YUV420P?0????int?main(int?argc,?char*?argv[])??{??????AVFormatContext?*pFormatCtx;??????int?????????????i,?videoindex;??????AVCodecContext??*pCodecCtx;??????AVCodec?????????*pCodec;??????AVFrame?*pFrame,*pFrameYUV;??????unsigned?char?*out_buffer;??????AVPacket?*packet;??????int?y_size;??????int?ret,?got_picture;??????struct?SwsContext?*img_convert_ctx;????????char?filepath[]="bigbuckbunny_480x272.h265";????????????int?screen_w=0,screen_h=0;??????SDL_Window?*screen;???????SDL_Renderer*?sdlRenderer;??????SDL_Texture*?sdlTexture;??????SDL_Rect?sdlRect;????????FILE?*fp_yuv;????????av_register_all();??????avformat_network_init();??????pFormatCtx?=?avformat_alloc_context();????????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=(unsigned?char?*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P,??pCodecCtx->width,?pCodecCtx->height,1));??????av_image_fill_arrays(pFrameYUV->data,?pFrameYUV->linesize,out_buffer,??????????AV_PIX_FMT_YUV420P,pCodecCtx->width,?pCodecCtx->height,1);????????????packet=(AVPacket?*)av_malloc(sizeof(AVPacket));????????????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,?AV_PIX_FMT_YUV420P,?SWS_BICUBIC,?NULL,?NULL,?NULL);?????#if?OUTPUT_YUV420P???????fp_yuv=fopen("output.yuv","wb+");????#endif??????????????if(SDL_Init(SDL_INIT_VIDEO?|?SDL_INIT_AUDIO?|?SDL_INIT_TIMER))?{????????????printf(?"Could?not?initialize?SDL?-?%s\n",?SDL_GetError());???????????return?-1;??????}?????????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);????????if(!screen)?{????????????printf("SDL:?could?not?create?window?-?exiting:%s\n",SDL_GetError());????????????return?-1;??????}????????sdlRenderer?=?SDL_CreateRenderer(screen,?-1,?0);????????????????????sdlTexture?=?SDL_CreateTexture(sdlRenderer,?SDL_PIXELFORMAT_IYUV,?SDL_TEXTUREACCESS_STREAMING,pCodecCtx->width,pCodecCtx->height);??????????sdlRect.x=0;??????sdlRect.y=0;??????sdlRect.w=screen_w;??????sdlRect.h=screen_h;??????????????while(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?unsigned?char*?const*)pFrame->data,?pFrame->linesize,?0,?pCodecCtx->height,???????????????????????pFrameYUV->data,?pFrameYUV->linesize);????????????????????#if?OUTPUT_YUV420P??????????????????y_size=pCodecCtx->width*pCodecCtx->height;????????????????????fwrite(pFrameYUV->data[0],1,y_size,fp_yuv);??????????????????????fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv);????????????????????fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv);????#endif????????????????????#if?0??????????????????SDL_UpdateTexture(?sdlTexture,?NULL,?pFrameYUV->data[0],?pFrameYUV->linesize[0]?);????#else??????????????????SDL_UpdateYUVTexture(sdlTexture,?&sdlRect,??????????????????pFrameYUV->data[0],?pFrameYUV->linesize[0],??????????????????pFrameYUV->data[1],?pFrameYUV->linesize[1],??????????????????pFrameYUV->data[2],?pFrameYUV->linesize[2]);??#endif??????????????????????????????????????SDL_RenderClear(?sdlRenderer?);????????????????????SDL_RenderCopy(?sdlRenderer,?sdlTexture,??NULL,?&sdlRect);????????????????????SDL_RenderPresent(?sdlRenderer?);????????????????????????????????????????????????????????SDL_Delay(40);??????????????}??????????}??????????av_free_packet(packet);??????}??????????????????while?(1)?{??????????ret?=?avcodec_decode_video2(pCodecCtx,?pFrame,?&got_picture,?packet);??????????if?(ret?<?0)??????????????break;??????????if?(!got_picture)??????????????break;??????????sws_scale(img_convert_ctx,?(const?unsigned?char*?const*)pFrame->data,?pFrame->linesize,?0,?pCodecCtx->height,???????????????pFrameYUV->data,?pFrameYUV->linesize);??#if?OUTPUT_YUV420P??????????int?y_size=pCodecCtx->width*pCodecCtx->height;????????????fwrite(pFrameYUV->data[0],1,y_size,fp_yuv);??????????????fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv);????????????fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv);????#endif????????????????????SDL_UpdateTexture(?sdlTexture,?&sdlRect,?pFrameYUV->data[0],?pFrameYUV->linesize[0]?);????????????SDL_RenderClear(?sdlRenderer?);????????????SDL_RenderCopy(?sdlRenderer,?sdlTexture,??NULL,?&sdlRect);????????????SDL_RenderPresent(?sdlRenderer?);????????????????????????????????SDL_Delay(40);??????}????????sws_freeContext(img_convert_ctx);????#if?OUTPUT_YUV420P???????fclose(fp_yuv);??#endif?????????SDL_Quit();????????av_frame_free(&pFrameYUV);??????av_frame_free(&pFrame);??????avcodec_close(pCodecCtx);??????avformat_close_input(&pFormatCtx);????????return?0;??}??
simplest_ffmpeg_player_su(SU版)代碼
標準版的基礎之上引入了SDL的Event。
效果如下:
(1)SDL彈出的窗口可以移動了
(2)畫面顯示是嚴格的40ms一幀
[cpp]?view plaincopy
????????????????????????????????????????#include?<stdio.h>????#define?__STDC_CONSTANT_MACROS????#ifdef?_WIN32????extern?"C"??{??#include?"libavcodec/avcodec.h"??#include?"libavformat/avformat.h"??#include?"libswscale/swscale.h"??#include?"libavutil/imgutils.h"??#include?"SDL2/SDL.h"??};??#else????#ifdef?__cplusplus??extern?"C"??{??#endif??#include?<libavcodec/avcodec.h>??#include?<libavformat/avformat.h>??#include?<libswscale/swscale.h>??#include?<libavutil/imgutils.h>??#include?<SDL2/SDL.h>??#ifdef?__cplusplus??};??#endif??#endif??????#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;????????????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;??????unsigned?char?*out_buffer;??????AVPacket?*packet;??????int?ret,?got_picture;??????????????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[]="Titanic.ts";????????av_register_all();??????avformat_network_init();??????pFormatCtx?=?avformat_alloc_context();????????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=(unsigned?char?*)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P,??pCodecCtx->width,?pCodecCtx->height,1));??????av_image_fill_arrays(pFrameYUV->data,?pFrameYUV->linesize,out_buffer,??????????AV_PIX_FMT_YUV420P,pCodecCtx->width,?pCodecCtx->height,1);??????????????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,?AV_PIX_FMT_YUV420P,?SWS_BICUBIC,?NULL,?NULL,?NULL);???????????????if(SDL_Init(SDL_INIT_VIDEO?|?SDL_INIT_AUDIO?|?SDL_INIT_TIMER))?{????????????printf(?"Could?not?initialize?SDL?-?%s\n",?SDL_GetError());???????????return?-1;??????}?????????????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);????????if(!screen)?{????????????printf("SDL:?could?not?create?window?-?exiting:%s\n",SDL_GetError());????????????return?-1;??????}??????sdlRenderer?=?SDL_CreateRenderer(screen,?-1,?0);????????????????????sdlTexture?=?SDL_CreateTexture(sdlRenderer,?SDL_PIXELFORMAT_IYUV,?SDL_TEXTUREACCESS_STREAMING,pCodecCtx->width,pCodecCtx->height);??????????sdlRect.x=0;??????sdlRect.y=0;??????sdlRect.w=screen_w;??????sdlRect.h=screen_h;????????packet=(AVPacket?*)av_malloc(sizeof(AVPacket));????????video_tid?=?SDL_CreateThread(sfp_refresh_thread,NULL,NULL);????????????????????????for?(;;)?{????????????????????SDL_WaitEvent(&event);??????????if(event.type==SFM_REFRESH_EVENT){??????????????while(1){??????????????????if(av_read_frame(pFormatCtx,?packet)<0)??????????????????????thread_exit=1;????????????????????if(packet->stream_index==videoindex)??????????????????????break;??????????????}??????????????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?unsigned?char*?const*)pFrame->data,?pFrame->linesize,?0,?pCodecCtx->height,?pFrameYUV->data,?pFrameYUV->linesize);????????????????????????????????????SDL_UpdateTexture(?sdlTexture,?NULL,?pFrameYUV->data[0],?pFrameYUV->linesize[0]?);????????????????????SDL_RenderClear(?sdlRenderer?);??????????????????????????????????????SDL_RenderCopy(?sdlRenderer,?sdlTexture,?NULL,?NULL);????????????????????SDL_RenderPresent(?sdlRenderer?);??????????????????????????????????}??????????????av_free_packet(packet);??????????}else?if(event.type==SDL_KEYDOWN){????????????????????????????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;??}??
運行結果
程序運行后,會在命令行窗口打印一些視頻信息,同時會彈出一個窗口播放視頻內容。
下載
CSDN完整工程下載地址:
http://download.csdn.net/detail/leixiaohua1020/7826277
更新(2014.10.5)==============================
版本升級至2.2。
1.新版本在原版本的基礎上增加了“flush_decoder”功能。當av_read_frame()循環退出的時候,實際上解碼器中可能還包含剩余的幾幀數據。因此需要通過“flush_decoder”將這幾幀數據輸出。“flush_decoder”功能簡而言之即直接調用avcodec_decode_video2()獲得AVFrame,而不再向解碼器傳遞AVPacket。參考代碼如下:
[cpp]?view plaincopy
??while?(1)?{??????ret?=?avcodec_decode_video2(pCodecCtx,?pFrame,?&got_picture,?packet);??????if?(ret?<?0)??????????break;??????if?(!got_picture)??????????break;??????sws_scale(img_convert_ctx,?(const?uint8_t*?const*)pFrame->data,?pFrame->linesize,?0,?pCodecCtx->height,?pFrameYUV->data,?pFrameYUV->linesize);????????}??
2.為了更好地適應Linux等其他操作系統,做到可以跨平臺,去除掉了VC特有的一些函數。比如“#include "stdafx.h"”,“_tmain()”等等。
具體信息參見文章:avcodec_decode_video2()解碼視頻后丟幀的問題解決
2.2版下載地址:
http://download.csdn.net/detail/leixiaohua1020/8002337
更新-2.3(2015.1.03)==============================
新增一個工程:最簡單的基于FFmpeg的解碼器-純凈版(不包含libavformat)
2.3版CSDN下載地址:
http://download.csdn.net/detail/leixiaohua1020/8322307
更新-2.4(2015.2.13)==============================
這次考慮到了跨平臺的要求,調整了源代碼。經過這次調整之后,源代碼可以在以下平臺編譯通過:
VC++:打開sln文件即可編譯,無需配置。
cl.exe:打開compile_cl.bat即可命令行下使用cl.exe進行編譯,注意可能需要按照VC的安裝路徑調整腳本里面的參數。編譯命令如下。
[plain]?view plaincopy
::VS2010?Environment??call?"D:\Program?Files\Microsoft?Visual?Studio?10.0\VC\vcvarsall.bat"??::include??@set?INCLUDE=include;%INCLUDE%??::lib??@set?LIB=lib;%LIB%??::compile?and?link??cl?simplest_ffmpeg_player.cpp?/MD?/link?SDL2.lib?SDL2main.lib?avcodec.lib?^??avformat.lib?avutil.lib?avdevice.lib?avfilter.lib?postproc.lib?swresample.lib?swscale.lib?^??/SUBSYSTEM:WINDOWS?/OPT:NOREF??MinGW:MinGW命令行下運行compile_mingw.sh即可使用MinGW的g++進行編譯。編譯命令如下。
[plain]?view plaincopy
g++?simplest_ffmpeg_player.cpp?-g?-o?simplest_ffmpeg_player.exe?\??-I?/usr/local/include?-L?/usr/local/lib?\??-lmingw32?-lSDL2main?-lSDL2?-lavformat?-lavcodec?-lavutil?-lswscale??GCC:Linux或者MacOS命令行下運行compile_gcc.sh即可使用GCC進行編譯。編譯命令如下。
[plain]?view plaincopy
gcc?simplest_ffmpeg_player.cpp?-g?-o?simplest_ffmpeg_player.out?\??-I?/usr/local/include?-L?/usr/local/lib?-lSDL2main?-lSDL2?-lavformat?-lavcodec?-lavutil?-lswscale??PS:相關的編譯命令已經保存到了工程文件夾中
CSDN項目下載地址:http://download.csdn.net/detail/leixiaohua1020/8443943
SourceForge、Github等上面已經更新。
更新-2.5(2015.7.17)==============================
增加了下列工程:
simplest_ffmpeg_decoder:一個包含了封裝格式處理功能的解碼器。使用了libavcodec和libavformat。simplest_video_play_sdl2:使用SDL2播放YUV的例子。simplest_ffmpeg_helloworld:輸出FFmpeg類庫的信息。
CSDN項目下載地址:http://download.csdn.net/detail/leixiaohua1020/8924321
SourceForge、Github等上面已經更新。
總結
以上是生活随笔為你收集整理的最简单的基于FFMPEG+SDL的视频播放器 ver2 (采用SDL2.0)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。