最简单的基于FFMPEG+SDL的视频播放器(新版ffmpeg接口)

博客原文地址

https://blog.csdn.net/leixiaohua1020/article/details/38868499

//因旧的ffmpeg部分函数已弃用,修改后的版本

//运行环境vs2015  +sdl 2+ffmpeg20200426

//代码的注释某位网友的,博主很懒就不找了

/**

* 最简单的基于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.

*

* 备注:

* 标准版在播放视频的时候,画面显示使用延时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>



#define __STDC_CONSTANT_MACROS



#ifdef _WIN32

//Windows

extern "C"

{

#include "libavcodec/avcodec.h"

#include "libavformat/avformat.h"

#include "libswscale/swscale.h"

#include "libavutil/imgutils.h"

#include "SDL.h"

};

#else

//Linux...

#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



//Refresh Event

#define SFM_REFRESH_EVENT  (SDL_USEREVENT + 1)



#define SFM_BREAK_EVENT  (SDL_USEREVENT + 2)



int thread_exit = 0;

int thread_pause = 0;



/**

* 每隔40ms发送一次消息,eventType为SFM_REFRESH_EVENT

*

* thread_pause:暂停

* thread_exit:退出

*/

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;



// 每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体

AVCodec *pCodec;



// 存储一帧解码后像素(采样)数据

AVFrame *pFrame, *pFrameYUV;



// 存储图像数据

unsigned char *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_Rect srcsdlRect;

SDL_Rect dstsdlRect;

// 线程

SDL_Thread *video_tid;



// 事件

SDL_Event event;



struct SwsContext *img_convert_ctx;



//char filepath[]="bigbuckbunny_480x272.h265";

char filepath[] = "d:/test.264";

// 注册复用器,编码器等(参考FFmpeg解码流程图)

//av_register_all();



// 进行网络组件的全局初始化(详细代码请参考第三篇文章中代码对应位置的描述)

avformat_network_init();



/**

* Allocate an AVFormatContext.

* avformat_free_context() can be used to free the context and everything

* allocated by the framework within it.

* 分配AVFormatContext。

* avformat_free_context()可用于释放上下文以及框架在其中分配的所有内容。

*/

pFormatCtx = avformat_alloc_context();



// 打开多媒体数据并且获得一些相关的信息(参考FFmpeg解码流程图)

if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) {

printf("Couldn't open input stream.\n");

return -1;

}



// 读取一部分视音频数据并且获得一些相关的信息(参考FFmpeg解码流程图)

if (avformat_find_stream_info(pFormatCtx, NULL)<0) {

printf("Couldn't find stream information.\n");

return -1;

}



// 每个视频文件中有多个流(视频流、音频流、字幕流等,而且可有多个),循环遍历找到视频流

// 判断方式:AVFormatContext->AVStream->AVCodecContext->codec_type是否为AVMEDIA_TYPE_VIDEO

videoindex = -1;

for (i = 0; i<pFormatCtx->nb_streams; i++)

if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {

videoindex = i;

break;

}



// 如果没有视频流,返回

if (videoindex == -1) {

printf("Didn't find a video stream.\n");

return -1;

}



// 保存视频流中的AVCodecContext

//pCodecCtx = pFormatCtx->streams[videoindex]->codecpar;

pCodecCtx = avcodec_alloc_context3(NULL);

avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoindex]->codecpar);



// 用于查找FFmpeg的解码器(参考FFmpeg解码流程图)

pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

if (pCodec == NULL) {

printf("Codec not found.\n");

return -1;

}



// 初始化一个视音频编解码器的AVCodecContext(参考FFmpeg解码流程图)

if (avcodec_open2(pCodecCtx, pCodec, NULL)<0) {

printf("Could not open codec.\n");

return -1;

}



// 创建AVFrame,用来存放解码后的一帧的数据

pFrame = av_frame_alloc();

pFrameYUV = av_frame_alloc();



// av_image_get_buffer_size:返回使用给定参数存储图像所需的数据量的字节大小

out_buffer = (unsigned char *)av_malloc(av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1));



// 根据指定的图像参数和提供的数组设置数据指针和线条(data pointers and linesizes)

av_image_fill_arrays(pFrameYUV->data, pFrameYUV->linesize, out_buffer,

AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);



//Output Info-----------------------------

printf("---------------- File Information ---------------\n");

av_dump_format(pFormatCtx, 0, filepath, 0);

printf("-------------------------------------------------\n");



// sws_getContext():初始化一个SwsContext

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);



// 初始化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;



// 创建窗口SDL_CreateWindow

//screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,

// screen_w, screen_h, SDL_WINDOW_OPENGL);

screen = SDL_CreateWindow("Simplest ffmpeg player's Window", 10, 10,

screen_w/4, screen_h/4, SDL_WINDOW_OPENGL);



if (!screen) {

printf("SDL: could not create window - exiting:%s\n", 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);



//源rect

sdlRect.x = 0;

sdlRect.y = 0;

sdlRect.w = screen_w;

sdlRect.h = screen_h;

//sdlRect.w = screen_w/8;

//sdlRect.h = screen_h/8;



//显示rect

dstsdlRect.x = 0;

dstsdlRect.y = 0;

dstsdlRect.w = screen_w / 8;

dstsdlRect.h = screen_h / 8;

// 创建一个AVPacket,用来存放下面循环获取到的未解码帧

packet = (AVPacket *)av_malloc(sizeof(AVPacket));



// 创建线程sfp_refresh_thread,进行视频数据的读取

video_tid = SDL_CreateThread(sfp_refresh_thread, NULL, NULL);

//------------SDL End------------

//Event Loop



for (;;) {

//Wait

SDL_WaitEvent(&event);

if (event.type == SFM_REFRESH_EVENT) {

// sfp_refresh_thread中定义的事件,视频播放的事件

while (1) {

// 读取到的数据为空,则停止播放

if (av_read_frame(pFormatCtx, packet)<0)

thread_exit = 1;



// 循环找到视频流

if (packet->stream_index == videoindex)

break;

}



// 解码一帧视频数据:输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame

//ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);

ret = avcodec_send_packet(pCodecCtx, packet);

got_picture = avcodec_receive_frame(pCodecCtx, pFrame);

if (ret < 0) {

printf("Decode Error.\n");

return -1;

}



if (got_picture == 0) {

sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);

//SDL---------------------------

// 设置纹理数据

SDL_UpdateTexture(sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0]);

// 清理渲染器

SDL_RenderClear(sdlRenderer);



// 将纹理数据copy到渲染器

//将sdlRect区域的图像显示到dstsdlRect区域

//SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );

SDL_RenderCopy(sdlRenderer, sdlTexture, &sdlRect, &dstsdlRect);

//SDL_RenderCopy(sdlRenderer, sdlTexture, NULL, NULL);

// 显示

SDL_RenderPresent(sdlRenderer);

//SDL End-----------------------

}

av_packet_unref(packet);

}

else if (event.type == SDL_KEYDOWN) {

//Pause

if (event.key.keysym.sym == SDLK_SPACE)

// 空格键

thread_pause = !thread_pause;

}

else if (event.type == SDL_QUIT) {

// 系统event,点击关闭时返回该event

// thread_exit置为1,退出监听事件的循环

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;

}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值