最簡單的基于FFMPEG+SDL的視頻播放器 ver2 (采用SDL2.0)
來源:程序員人生 發布時間:2014-09-18 03:43:59 閱讀次數:4579次
簡介
之前做過一個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版本。
SourceForge項目主頁:
https://sourceforge.net/projects/simplestffmpegplayer/
嘗試了一下開源中國的代碼托管,建立了一個項目:
http://git.oschina.net/leixiaohua1020/Simplest_ffmpeg_player_2
CSDN完整工程下載地址:
http://download.csdn.net/detail/leixiaohua1020/7826277
流程圖
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(標準版)代碼
一句話介紹:最基礎的版本,學習的開始。
/**
* 最簡單的基于FFmpeg的視頻播放器 2
* Simplest FFmpeg Player 2
*
* 雷霄驊 Lei Xiaohua
* leixiaohua1020@126.com
* 中國傳媒大學/數字電視技術
* 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的解碼流程。
* This software is a simplest video player based on FFmpeg.
* Suitable for beginner of FFmpeg.
*
* Version:2
*/
#include "stdafx.h"
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
//SDL
#include "sdl/SDL.h"
#include "sdl/SDL_thread.h"
};
//Output YUV420P data as a file
#define OUTPUT_YUV420P 0
int _tmain(int argc, _TCHAR* argv[])
{
AVFormatContext *pFormatCtx;
int i, videoindex;
AVCodecContext *pCodecCtx;
AVCodec *pCodec;
char filepath[]="src01_480x272_22.h265";
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.(無法打開輸入流)
");
return -1;
}
if(av_find_stream_info(pFormatCtx)<0)
{
printf("Couldn't find stream information.(無法獲取流信息)
");
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.(沒有找到視頻流)
");
return -1;
}
pCodecCtx=pFormatCtx->streams[videoindex]->codec;
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL)
{
printf("Codec not found.(沒有找到解碼器)
");
return -1;
}
if(avcodec_open2(pCodecCtx, pCodec,NULL)<0)
{
printf("Could not open codec.(無法打開解碼器)
");
return -1;
}
AVFrame *pFrame,*pFrameYUV;
pFrame=avcodec_alloc_frame();
pFrameYUV=avcodec_alloc_frame();
uint8_t *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);
//SDL---------------------------
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
printf( "Could not initialize SDL - %s
", SDL_GetError());
return -1;
}
int screen_w=0,screen_h=0;
//SDL 2.0 Support for multiple windows
SDL_Window *screen;
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
",SDL_GetError());
return -1;
}
SDL_Renderer* sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
//IYUV: Y + U + V (3 planes)
//YV12: Y + V + U (3 planes)
SDL_Texture* sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING,pCodecCtx->width,pCodecCtx->height);
SDL_Rect sdlRect;
sdlRect.x = 0;
sdlRect.y = 0;
sdlRect.w = screen_w;
sdlRect.h = screen_h;
//SDL End----------------------
int ret, got_picture;
AVPacket *packet=(AVPacket *)av_malloc(sizeof(AVPacket));
//Output Info-----------------------------
printf("File Information(文件信息)---------------------
");
av_dump_format(pFormatCtx,0,filepath,0);
printf("-------------------------------------------------
");
#if OUTPUT_YUV420P
FILE *fp_yuv=fopen("output.yuv","wb+");
#endif
struct SwsContext *img_convert_ctx;
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
//------------------------------
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.(解碼錯誤)
");
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);
#if OUTPUT_YUV420P
int y_size=pCodecCtx->width*pCodecCtx->height;
fwrite(pFrameYUV->data[0],1,y_size,fp_yuv); //Y
fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv); //U
fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv); //V
#endif
//SDL---------------------------
SDL_UpdateTexture( sdlTexture, &sdlRect, pFrameYUV->data[0], pFrameYUV->linesize[0] );
SDL_RenderClear( sdlRenderer );
SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );
SDL_RenderPresent( sdlRenderer );
//SDL End-----------------------
//Delay 40ms
SDL_Delay(40);
}
}
av_free_packet(packet);
}
sws_freeContext(img_convert_ctx);
#if OUTPUT_YUV420P
fclose(fp_yuv);
#endif
SDL_Quit();
av_free(out_buffer);
av_free(pFrameYUV);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
return 0;
}
simplest_ffmpeg_player_su(SU版)代碼
一句話介紹:標準版的基礎之上引入了SDL的Event。
效果如下:
(1)SDL彈出的窗口可以移動了
(2)畫面顯示是嚴格的40ms一幀
/**
* 最簡單的基于FFmpeg的視頻播放器2(SDL升級版)
* Simplest FFmpeg Player 2(SDL Update)
*
* 雷霄驊 Lei Xiaohua
* leixiaohua1020@126.com
* 中國傳媒大學/數字電視技術
* 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.
*
* Version: 2
*/
#include "stdafx.h"
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
//SDL
#include "sdl/SDL.h"
#include "sdl/SDL_thread.h"
};
//Refresh Event
#define SFM_REFRESH_EVENT (SDL_USEREVENT + 1)
int thread_exit=0;
int sfp_refresh_thread(void *opaque)
{
while (thread_exit==0) {
SDL_Event event;
event.type = SFM_REFRESH_EVENT;
SDL_PushEvent(&event);
SDL_Delay(40);
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
AVFormatContext *pFormatCtx;
int i, videoindex;
AVCodecContext *pCodecCtx;
AVCodec *pCodec;
char filepath[]="src01_480x272_22.h265";
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.(無法打開輸入流)
");
return -1;
}
if(av_find_stream_info(pFormatCtx)<0)
{
printf("Couldn't find stream information.(無法獲取流信息)
");
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.(沒有找到視頻流)
");
return -1;
}
pCodecCtx=pFormatCtx->streams[videoindex]->codec;
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL)
{
printf("Codec not found.(沒有找到解碼器)
");
return -1;
}
if(avcodec_open2(pCodecCtx, pCodec,NULL)<0)
{
printf("Could not open codec.(無法打開解碼器)
");
return -1;
}
AVFrame *pFrame,*pFrameYUV;
pFrame=avcodec_alloc_frame();
pFrameYUV=avcodec_alloc_frame();
uint8_t *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);
//------------SDL----------------
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
printf( "Could not initialize SDL - %s
", SDL_GetError());
return -1;
}
int screen_w=0,screen_h=0;
SDL_Window *screen;
//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);
if(!screen) {
printf("SDL: could not create window - exiting:%s
",SDL_GetError());
return -1;
}
SDL_Renderer* sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
//IYUV: Y + U + V (3 planes)
//YV12: Y + V + U (3 planes)
SDL_Texture* sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING,pCodecCtx->width,pCodecCtx->height);
SDL_Rect sdlRect;
sdlRect.x = 0;
sdlRect.y = 0;
sdlRect.w = screen_w;
sdlRect.h = screen_h;
int ret, got_picture;
AVPacket *packet=(AVPacket *)av_malloc(sizeof(AVPacket));
//Output Info-----------------------------
printf("File Information(文件信息)---------------------
");
av_dump_format(pFormatCtx,0,filepath,0);
printf("-------------------------------------------------
");
struct SwsContext *img_convert_ctx;
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_Thread *video_tid = SDL_CreateThread(sfp_refresh_thread,NULL,NULL);
//
//Event Loop
SDL_Event event;
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.(解碼錯誤)
");
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, &sdlRect, pFrameYUV->data[0], pFrameYUV->linesize[0] );
SDL_RenderClear( sdlRenderer );
SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );
SDL_RenderPresent( sdlRenderer );
//SDL End-----------------------
}
}
av_free_packet(packet);
}else{
//Exit Thread
thread_exit=1;
break;
}
}
}
sws_freeContext(img_convert_ctx);
SDL_Quit();
//--------------
av_free(out_buffer);
av_free(pFrameYUV);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
return 0;
}
運行結果

??
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈