程序员人生 网站导航

ffmpeg和opencv 播放视频文件并显示

栏目:互联网时间:2014-11-05 08:48:44

   ffmpeg是基于最新版本,在官网下载http://ffmpeg.zeranoe.com/builds/。编译时VS2010配置相干头文件及库的路径便可。opencv的搭建参考上1个博客。

       首先简单介绍以下视频文件的相干知识。我们平时看到的视频文件有许多格式,比如 avi, mkv, rmvb, mov, mp4等等,这些被称为容器Container), 不同的容器格式规定了其中音视频数据的组织方式(也包括其他数据,比如字幕等)。容器中1般会封装有视频和音频轨,也称为视频流(stream)和音频 流,播放视频文件的第1步就是根据视频文件的格式,解析(demux)出其中封装的视频流、音频流和字幕(如果有的话),解析的数据读到包 (packet)中,每一个包里保存的是视频帧(frame)或音频帧,然后分别对视频帧和音频帧调用相应的解码器(decoder)进行解码,比如使用 H.264编码的视频和MP3编码的音频,会相应的调用H.264解码器和MP3解码器,解码以后得到的就是原始的图象(YUV or RGB)和声音(PCM)数据,然后根据同步好的时间将图象显示到屏幕上,将声音输出到声卡,终究就是我们看到的视频。

好好看下下面的文章,很不错的。转载1部份: 

有人会疑惑,为何解码后的pFrame不直接用于显示,而是调用swscale()转换以后进行显示?

如果不进行转换,而是直接调用SDL进行显示的话,会发现显示出来的图象是混乱的。关键问题在于解码后的pFrame的linesize里存储的不是图象的宽度,而是比宽度大1些的1个值。其缘由目前还没有仔细调查(大概是出于性能的斟酌)。例如分辨率为480x272的图象,解码后的视频的linesize[0]为512,而不是480。以第1行亮度像素(pFrame->data[0])为例,从0⑷80存储的是亮度数据,而从480⑸12则存储的是无效的数据。因此需要使用swscale()进行转换。转换后去除无效数据,linesize[0]变成480。就能够正常显示了。



100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)

下面直接看代码吧!

    
/*File : playvideo.cpp *Auth : sjin *Date : 20141029 *Mail : 413977243@qq.com */ #include <stdio.h> #include <cv.h> #include <cxcore.h> #include <highgui.h> #ifdef __cplusplus extern "C" { #endif #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libswscale/swscale.h> #pragma comment (lib, "Ws2_32.lib") #pragma comment (lib, "avcodec.lib") #pragma comment (lib, "avdevice.lib") #pragma comment (lib, "avfilter.lib") #pragma comment (lib, "avformat.lib") #pragma comment (lib, "avutil.lib") #pragma comment (lib, "swresample.lib") #pragma comment (lib, "swscale.lib") #ifdef __cplusplus } #endif static void CopyDate(AVFrame *pFrame,int width,int height,int time) { if(time <=0 ) time = 1; int nChannels; int stepWidth; uchar * pData; cv::Mat frameImage(cv::Size(width, height), CV_8UC3, cv::Scalar(0)); stepWidth = frameImage.step; nChannels = frameImage.channels(); pData = frameImage.data; for(int i = 0; i < height; i++){ for(int j = 0; j < width; j++){ pData[i * stepWidth + j * nChannels + 0] = pFrame->data[0][i * pFrame->linesize[0] + j * nChannels + 2]; pData[i * stepWidth + j * nChannels + 1] = pFrame->data[0][i * pFrame->linesize[0] + j * nChannels + 1]; pData[i * stepWidth + j * nChannels + 2] = pFrame->data[0][i * pFrame->linesize[0] + j * nChannels + 0]; } } cv::namedWindow("Video", CV_WINDOW_AUTOSIZE); cv::imshow("Video", frameImage); cv::waitKey(time); } static void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) { FILE *pFile; char szFilename[32]; int y; // Open file sprintf(szFilename, "frame%d.ppm", iFrame); pFile=fopen(szFilename, "wb"); if(pFile==NULL) return; // Write header fprintf(pFile, "P6 %d %d 255 ", width, height); // Write pixel data for(y=0; y<height; y++) fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile); // Close file fclose(pFile); } int main(int argc, char * argv[]) { AVFormatContext *pFormatCtx = NULL; int i, videoStream; AVCodecContext *pCodecCtx; AVCodec *pCodec; AVFrame *pFrame; AVFrame *pFrameRGB; AVPacket packet; int frameFinished; int numBytes; uint8_t *buffer; /*注册了所有的文件格式和编解码器的库*/ av_register_all(); // 打开视频文件 /*这个函数读取文件的头部并且把信息保存到我们给的AVFormatContext结构体中。 *第2个参数 要打开的文件路径 */ if(avformat_open_input(&pFormatCtx, "test.264", NULL, NULL)!=0){ return ⑴; // Couldn't open file } // 读取数据包获得流媒体文件的信息 if(avformat_find_stream_info(pFormatCtx,NULL)<0){ return ⑴; // Couldn't find stream information } //打印输入或输出格式的详细信息,如时间、比特率,溪流,容器,程序,元数据,基础数据,编解码器和时间。 av_dump_format(pFormatCtx, 0, "test.264", false); //查找第1个视频流 videoStream=⑴; for(i = 0; i < pFormatCtx->nb_streams; i++){ if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){ videoStream = i; break; } } if(videoStream == ⑴){//未发现视频流退出 return ⑴; } //取得视频编解码器的上下文 pCodecCtx = pFormatCtx->streams[videoStream]->codec; // 找到视频解码器 pCodec = avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec == NULL){//未发现视频编码器 return ⑴; } // 打开视频解码器 if(avcodec_open2(pCodecCtx, pCodec, 0) < 0){ return ⑴; //打开失溃退出 } /*有些人可能会从旧的指点中记得有两个关于这些代码其它部份: *添加CODEC_FLAG_TRUNCATED到 pCodecCtx->flags和添加1个hack来粗糙的修正帧率。 *这两个修正已不在存在于ffplay.c中。因此,我必须假定它们不再必 要。我们移除 *了那些代码后还有1个需要指出的不同点:pCodecCtx->time_base现在已保存了帧率 *的信息。time_base是1 个结构体,它里面有1个份子和分母 (AVRational)。我们使 *用分数的方式来表示帧率是由于很多编解码器使用非整数的帧率(例如NTSC使用29.97fps)。 * *if(pCodecCtx->time_base.num > 1000 && pCodecCtx->time_base.den == 1){ * pCodecCtx->time_base.den = 1000; *} */ // 分配保存视频帧的空间 pFrame = avcodec_alloc_frame(); // 分配1个AVFrame结构 /*准备输出保存24位RGB色的PPM文件,我们必须把帧的格式从原来的转换为RGB。FFMPEG将为我们做这些转换*/ pFrameRGB = avcodec_alloc_frame(); if(pFrameRGB==NULL){ return ⑴; } /*即便我们申请了1帧的内存,当转换的时候,我们依然需要1个地方来放置原始的数据。 *我们使用avpicture_get_size来取得我们需要的大小,然后手工申请内存空间: */ numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height); buffer = (uint8_t *)av_malloc( numBytes*sizeof(uint8_t)); // 基于指定的图象参数,设置图片字段所提供的图象数据缓冲区。 avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,pCodecCtx->width, pCodecCtx->height); /*读取数据帧 *通过读取包来读取全部视频流,然后把它解码成帧,最好后转换格式并且保存。 */ i=0; long prepts = 0; while(av_read_frame(pFormatCtx, &packet) >= 0){ if(packet.stream_index == videoStream){/*判断读取的是不是为需要的视频帧数据*/ // 解码视频帧 avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet); if(frameFinished){ static struct SwsContext *img_convert_ctx; #if 0 //就的转换模式已废除 img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24, (AVPicture*)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height); #endif if(img_convert_ctx == NULL) { int w = pCodecCtx->width; int h = pCodecCtx->height; //分配和返回1个SwsContext。您需要履行缩放/转换操作使用sws_scale()。 img_convert_ctx = sws_getContext(w, h, pCodecCtx->pix_fmt, w, h, PIX_FMT_RGB24, SWS_BICUBIC,NULL, NULL, NULL); if(img_convert_ctx == NULL) { fprintf(stderr, "Cannot initialize the conversion context! "); exit(1); } } ////转换图象格式,将解压出来的YUV420P的图象转换为BRG24的图象 sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize); //保存前5帧数据 if(i++ <= 5){ SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i); } CopyDate(pFrameRGB, pCodecCtx->width, pCodecCtx->height,packet.pts-prepts); prepts = packet.pts; } } //释放空间 av_free_packet(&packet); } //释放空间 av_free(buffer); av_free(pFrameRGB); // 释放 YUV frame av_free(pFrame); //关闭解码器 avcodec_close(pCodecCtx); //关闭视频文件 avformat_close_input(&pFormatCtx); system("Pause"); return 0; }
运行以下图:



参考资料:
http://www.cnblogs.com/dyllove98/archive/2013/07/03/3170272.html
http://blog.csdn.net/hsp494980719/article/details/7986400
------分隔线----------------------------
------分隔线----------------------------

最新技术推荐