一、概述
熟悉了FFMPEG的命令行工具ffmpeg.exe之后,就开始了对这个库的函数的熟悉过程,按照零基础学习FFMPEG的学习轨迹,就应当实现一个“最简单的视频播放器”来熟悉FFMPEG的工作流程和一些常用的函数。实现播放器的文章请参考最简单的视频播放器,这里仅仅记录一下自己的心得。
二、播放器的解码流程
播放器的解码流程非常重要,有利于我们去读懂代码。流程图我就不自己画了,直接取自作者的文章里的图:
对这个流程图做一些解释:
1.av_register_all
注册所有的文件格式和编解码的库。具体的来说它初始化libavformat库,注册muxer,demuxer和一些协议。libavformat库提供了一个复用/解复用音视频、字幕的框架。它包括多媒体封装格式的多个复用器和解复用器,同样支持访问多媒体源的输入输出协议。
2.avformat_open_input
打开一个输入流并读取该流的头部信息。注意,这个时候解码器还没有打开;打开的流在后面解码工作完成后需要关闭。
3.av_find_stream_info
avformat_open_input只是检测了文件的头部,所以我们还需要av_find_stream_info来读取流的信息。这一步之后将为FormatContext->streams填充上正确的信息。此外在新版的FFMPEG中,该函数已经声明为要废弃的,使用avformat_find_stream_info来代替它。
4.avcodec_find_decoder
根据输入流的信息中的解码器ID,找到合适的解码器来进行解码工作。
5.avcodec_open
打开解码器。这个时候会对解码环境(AVCodecContext)做一些初始化的工作。
6.av_read_frame
读取下一帧数据。注意该数据帧还未解码。如果读取不到,说明已经读取完毕。当读取到后,还需要判断这一帧数据是否是视频类型的数据,如果是才进行解码。
7.avcodec_decode_video2
解码数据帧。
三、代码及注释
注意:这里仅仅对解码流程做了注释,而对于SDL部分并没有做注释。
#include <stdio.h> //包含库 extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" #include "SDL/SDL.h" }; int main(int argc, char* argv[]) { //FFmpeg相关变量 AVFormatContext *pFormatCtx;//AVFormatContext主要存储视音频封装格式中包含的信息 int i; int videoindex;//视频流所在序号 AVCodecContext *pCodecCtx;//AVCodecContext,存储该视频/音频流使用解码方式的相关数据 AVCodec *pCodec;//解码器 AVFrame *pFrame,*pFrameYUV;//解码后数据 AVPacket *packet;//解码前数据 struct SwsContext *img_convert_ctx; //SDL int screen_w,screen_h;//屏幕宽、高 SDL_Surface *screen; SDL_VideoInfo *vi; SDL_Overlay *bmp; SDL_Rect rect; FILE *fp_yuv; int ret, got_picture; char* filepath = "1.rmvb"; av_register_all();//初始化libformat库和一些别的工作 avformat_network_init();//初始化网络组件 pFormatCtx = avformat_alloc_context(); //打开视频文件然后读取头部信息到pFormatCtx 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(); //SDL Begin---------------------------- 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_SetVideoMode(screen_w, screen_h, 0,0); if(!screen) { printf("SDL: could not set video mode - exiting:%s\n",SDL_GetError()); return -1; } bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height,SDL_YV12_OVERLAY, screen); rect.x = 0; rect.y = 0; rect.w = screen_w; rect.h = screen_h; //SDL End------------------------ packet=(AVPacket *)av_malloc(sizeof(AVPacket)); 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){//必须是视频流帧 //Decode ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);//解码数据帧到pFrame if(ret < 0){ printf("Decode Error.\n"); return -1; } if(got_picture){ SDL_LockYUVOverlay(bmp); pFrameYUV->data[0]=bmp->pixels[0]; pFrameYUV->data[1]=bmp->pixels[2]; pFrameYUV->data[2]=bmp->pixels[1]; pFrameYUV->linesize[0]=bmp->pitches[0]; pFrameYUV->linesize[1]=bmp->pitches[2]; pFrameYUV->linesize[2]=bmp->pitches[1]; sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize); SDL_UnlockYUVOverlay(bmp); SDL_DisplayYUVOverlay(bmp, &rect); //Delay 40ms SDL_Delay(40); } } av_free_packet(packet); } //FIX: Flush Frames remained in Codec 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); SDL_LockYUVOverlay(bmp); pFrameYUV->data[0]=bmp->pixels[0]; pFrameYUV->data[1]=bmp->pixels[2]; pFrameYUV->data[2]=bmp->pixels[1]; pFrameYUV->linesize[0]=bmp->pitches[0]; pFrameYUV->linesize[1]=bmp->pitches[2]; pFrameYUV->linesize[2]=bmp->pitches[1]; SDL_UnlockYUVOverlay(bmp); SDL_DisplayYUVOverlay(bmp, &rect); //Delay 40ms SDL_Delay(40); } sws_freeContext(img_convert_ctx); SDL_Quit(); av_free(pFrameYUV);//释放帧数据占用的内存 avcodec_close(pCodecCtx);//释放解码器环境 avformat_close_input(&pFormatCtx);//释放输入环境 return 0; }
packet->stream_index==videoindex
//找到视频流的序号 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; }
下一步便是学习常用的数据结构:http://blog.csdn.net/gameloft9/article/details/45841935
如果想要了解SDL部分的内容,可以参考:关于《最简单的视频播放器》记录二