• ffmpeg和opencv 播放视频文件和显示器


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

           首先简介以下视频文件的相关知识。

    我们平时看到的视频文件有很多格式,比方 avi。 mkv, rmvb, mov, mp4等等,这些被称为容器Container)。 不同的容器格式规定了当中音视频数据的组织方式(也包含其他数据,比方字幕等)。容器中通常会封装有视频和音频轨,也称为视频流(stream)和音频 流。播放视频文件的第一步就是依据视频文件的格式。解析(demux)出当中封装的视频流、音频流以及字幕(假设有的话)。解析的数据读到包 (packet)中。每一个包里保存的是视频帧(frame)或音频帧。然后分别对视频帧和音频帧调用对应的解码器(decoder)进行解码,比方使用 H.264编码的视频和MP3编码的音频,会对应的调用H.264解码器和MP3解码器,解码之后得到的就是原始的图像(YUV or RGB)和声音(PCM)数据,然后依据同步好的时间将图像显示到屏幕上。将声音输出到声卡,终于就是我们看到的视频。

    好好看下以下的文章,非常不错的。转载一部分: 

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

    假设不进行转换,而是直接调用SDL进行显示的话,会发现显示出来的图像是混乱的。关键问题在于解码后的pFrame的linesize里存储的不是图像的宽度。而是比宽度大一些的一个值。其原因眼下还没有细致调查(大概是出于性能的考虑)。

    比如分辨率为480x272的图像。解码后的视频的linesize[0]为512。而不是480。以第1行亮度像素(pFrame->data[0])为例,从0-480存储的是亮度数据,而从480-512则存储的是无效的数据。因此须要使用swscale()进行转换。转换后去除了无效数据,linesize[0]变为480。就能够正常显示了。




    以下直接看代码吧!

        
    /*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结构体中。
    	 *第二个參数 要打开的文件路径
    	 */
    	if(avformat_open_input(&pFormatCtx, "test.264", NULL, NULL)!=0){
    		return -1; // Couldn't open file
    	}
    
    	// 读取数据包获取流媒体文件的信息
    	if(avformat_find_stream_info(pFormatCtx,NULL)<0){
    		return -1; // Couldn't find stream information
    	}
    
    	//打印输入或输出格式的具体信息,如时间、比特率,溪流,容器,程序,元数据,基础数据,编解码器和时间。
    	av_dump_format(pFormatCtx, 0, "test.264", false);
    
    
    	//查找第一个视频流
    	videoStream=-1;
    	for(i = 0; i < pFormatCtx->nb_streams; i++){
    		if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
    			videoStream = i;
    			break;
    		}
    	}
    
    	if(videoStream == -1){//未发现视频流退出
    		return -1; 
    	}
    
    	//获得视频编解码器的上下文
    	pCodecCtx = pFormatCtx->streams[videoStream]->codec;
    
    	// 找到视频解码器
    	pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    	if(pCodec == NULL){//未发现视频编码器
    		return -1; 
    	}
    
    	// 打开视频解码器
    	if(avcodec_open2(pCodecCtx, pCodec, 0) < 0){
    		return -1; //打开失败退出
    	}
    
    	/*有些人可能会从旧的指导中记得有两个关于这些代码其他部分:
    	 *加入CODEC_FLAG_TRUNCATED到 pCodecCtx->flags和加入一个hack来粗糙的修正帧率。
    	 *这两个修正已经不在存在于ffplay.c中。因此,我必需假设它们不再必 要。

    我们移除 *了那些代码后另一个须要指出的不同点:pCodecCtx->time_base如今已经保存了帧率 *的信息。time_base是一 个结构体,它里面有一个分子和分母 (AVRational)。我们使 *用分数的方式来表示帧率是由于非常多编解码器使用非整数的帧率(比如NTSC使用29.97fps)。 * *if(pCodecCtx->time_base.num > 1000 && pCodecCtx->time_base.den == 1){ * pCodecCtx->time_base.den = 1000; *} */ // 分配保存视频帧的空间 pFrame = avcodec_alloc_frame(); // 分配一个AVFrame结构 /*准备输出保存24位RGB色的PPM文件,我们必需把帧的格式从原来的转换为RGB。

    FFMPEG将为我们做这些转换*/ pFrameRGB = avcodec_alloc_frame(); if(pFrameRGB==NULL){ return -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; //分配和返回一个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); //保存前五帧数据 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

    版权声明:本文博客原创文章,博客,未经同意,不得转载。

  • 相关阅读:
    vue.js打包后,接口安全问题
    PHP开发api接口安全验证
    DOS命令操作 规格严格
    IIS 规格严格
    Swing语法高亮 规格严格
    Jetty 规格严格
    How to avoid “Illegal type in constant pool 规格严格
    数据库表产生类 规格严格
    利用Java编写简单IIS日志清理工具 规格严格
    语法高亮 规格严格
  • 原文地址:https://www.cnblogs.com/bhlsheji/p/4638099.html
Copyright © 2020-2023  润新知