
#include <stdio.h>
#include <SDL.h>
#include <libavutil/log.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
int main(int argc,char* argv[]){
char* in_file = NULL;
int ret = -1; //返回值
int len; //存放读取的数据大小
int stream_idx; //存放视频流的索引
int frameFin; //标志packet中数据帧是否读取完成
AVFormatContext* fmt_cxt = NULL; //格式上下文
AVCodecContext* codec_cxt = NULL; //编解码器上下文
struct SwsContext* sws_cxt = NULL; //图像处理上下文
AVCodec* codec = NULL; //编解码器
AVFrame* frame = NULL; //帧
AVPicture* pict = NULL; //YUV图像存放
AVPacket packet; //包
SDL_Window* win = NULL; //SDL窗口指针
SDL_Renderer* rend = NULL; //SDL渲染器指针
SDL_Texture* text = NULL; //纹理
SDL_Rect rect; //用于显示数据的矩阵区域
SDL_Event event; //SDL事件
int w_width = 640; //设置的默认窗口大小,后面会进行调整
int w_height = 480;
if(argc<2){ //检查是否设置了播放的文件名称
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,"Usage: command <file>");
return ret;
}
in_file = argv[1];
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)){
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,"Failed to initialize SDL - %s!",SDL_GetError());
return ret;
}
//------------------------配置所有的FFmpeg信息
av_register_all(); //注册所有协议
//获取格式上下文
if(avformat_open_input(&fmt_cxt,in_file,NULL,NULL)<0){
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,"Failed to open video!");
goto __INIT;
}
//获取流的信息,这里换一种方式获取,不直接使用av_find_best_stream获取
stream_idx = -1;
for(int i=0;i<fmt_cxt->nb_streams;i++){
if(fmt_cxt->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
stream_idx = i;
break;
}
}
if(stream_idx==-1){
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,"Failed to get video stream!");
goto __FORMAT;
}
//打印输入视频流的详细信息
if(avformat_find_stream_info(fmt_cxt,0)<0){
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,"Failed to get video stream information!");
goto __FORMAT;
}
av_dump_format(fmt_cxt,stream_idx,in_file,0); //打印信息
//开始获取解码器,对视频流进行解码获取YUV数据
//根据输入流的参数获取对于的解码器
codec = avcodec_find_decoder(fmt_cxt->streams[stream_idx]->codec->codec_id);
if(codec==NULL){
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,"Failed to find video decoder!");
goto __FORMAT;
}
//获取对应上下文
codec_cxt = avcodec_alloc_context3(codec);
if(!codec_cxt){
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,"Failed to find video decoder context!");
goto __FORMAT;
}
//开始拷贝输入流参数到解码器中
if(avcodec_copy_context(codec_cxt,fmt_cxt->streams[stream_idx]->codec)<0){
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,"Failed to copy video decoder context!");
goto __CODEC;
}
//打开编解码器
if(avcodec_open2(codec_cxt,codec,NULL)<0){
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,"Failed to opn video decoder!");
goto __CODEC;
}
frame = av_frame_alloc(); //分配帧
if(!frame){
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,"Failed to alloc video frame!");
goto __CODEC;
}
//创建图片转换上下文(在上面参数拷贝后面)-----------------
//源图像宽、高、像素格式;目标图像宽、高、像素格式;以及图像拉伸使用的算法
sws_cxt = sws_getContext(codec_cxt->width,codec_cxt->height,codec_cxt->pix_fmt,
codec_cxt->width,codec_cxt->height,AV_PIX_FMT_YUV420P, //输出像素格式YUV
SWS_BILINEAR,NULL,NULL,NULL); //获取图像处理上下文
pict = (AVPicture*)malloc(sizeof(AVPicture));
if(!pict){
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,"Failed to alloc video picture frame!");
goto __FRAME;
}
avpicture_alloc(pict,
AV_PIX_FMT_YUV420P,
codec_cxt->width,
codec_cxt->height); //为图像存放分配空间
//------------------------开始设置SDL数据
w_width = codec_cxt->width;
w_height = codec_cxt->height;
//创建窗口
win = SDL_CreateWindow("Media Player",
SDL_WINDOWPOS_UNDEFINED,SDL_WINDOWPOS_UNDEFINED, //不指定左上位置
w_width,w_height, //设置窗口宽高,与视频一般或者大些都可以
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); //窗口可用于OpenGL上下文 窗口可以调整大小
if(!win){
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,"Failed to create window to SDL");
goto __PICT;
}
//创建渲染器
rend = SDL_CreateRenderer(win,-1,0);
if(!rend){
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,"Failed to create renderer to SDL");
goto __WIN;
}
//创建纹理
text = SDL_CreateTexture(rend,SDL_PIXELFORMAT_IYUV,
SDL_TEXTUREACCESS_STREAMING, //视频流,连续的
w_width,w_height);
if(!text){
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,"Failed to create texture to SDL");
goto __RENDER;
}
rect.x = 0; //显示矩阵的左上点
rect.y = 0;
//------------------------开始读取数据,结合SDL与FFmpeg
av_init_packet(&packet); //初始化数据包
while(av_read_frame(fmt_cxt,&packet)>=0){
if(packet.stream_index==stream_idx){ //属于视频流
len = avcodec_decode_video2(codec_cxt,frame,&frameFin,&packet); //作用是解码一帧视频数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame。
if(len<0){
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,"Fail to decode video frame");
goto __INIT;
}
if(frameFin){ //表示读取完成了一帧,开始进行转换,解码数据已经放入了frame中
sws_scale(sws_cxt,(uint8_t const * const *)frame->data,frame->linesize, //当前处理区域的每个通道数据指针,每个通道行字节数
0,codec_cxt->height, //参数int srcSliceY, int srcSliceH,定义在输入图像上处理区域,srcSliceY是起始位置,srcSliceH是处理多少行。如果srcSliceY=0,srcSliceH=height,表示一次性处理完整个图像。
pict->data,pict->linesize); //定义输出图像信息(输出的每个通道数据指针,每个通道行字节数)
//可以渲染YUV数据
SDL_UpdateYUVTexture(text,NULL,
pict->data[0],pict->linesize[0],
pict->data[1],pict->linesize[1],
pict->data[2],pict->linesize[2]);
//更新矩阵的信息
rect.w = codec_cxt->width;
rect.h = codec_cxt->height;
SDL_RenderClear(rend); //清空渲染器缓冲区
SDL_RenderCopy(rend,text,NULL,&rect); //拷贝纹理到显卡,选择渲染目标的一块矩形区域作为输出
SDL_RenderPresent(rend); //将结果显示到窗口
}
}
av_packet_unref(&packet); //注意:减少引用不会释放空间,因为本函数还在引用这个packet结构体;
//所以我们在解码函数中剩余的其他帧数据,还是保留在packet中的,后面根据av_read_frame继续向内部添加数据
//设置事件轮询
SDL_PollEvent(&event);
switch(event.type){
case SDL_QUIT:
goto __QUIT;
break;
default:
break;
}
}
__QUIT:
ret = 0;
av_packet_unref(&packet); //减少引用,使得自己释放空间
SDL_DestroyTexture(text);
__RENDER:
SDL_DestroyRenderer(rend);
__WIN:
SDL_DestroyWindow(win);
__PICT:
avpicture_free(pict);
free(pict);
__FRAME:
av_frame_free(&frame);
__CODEC:
avcodec_close(codec_cxt);
__FORMAT:
avformat_close_input(&fmt_cxt);
__INIT:
SDL_Quit();
return ret;
}