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\n%d %d\n255\n", 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!\n"); 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; }

版權聲明:本文博客原創文章,博客,未經同意,不得轉載。