/*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;
/*注冊(cè)了所有的文件格式和編解碼器的庫*/
av_register_all();
// 打開視頻文件
/*這個(gè)函數(shù)讀取文件的頭部并且把信息保存到我們給的AVFormatContext結(jié)構(gòu)體中。
*第2個(gè)參數(shù) 要打開的文件路徑
*/
if(avformat_open_input(&pFormatCtx, "test.264", NULL, NULL)!=0){
return ⑴; // Couldn't open file
}
// 讀取數(shù)據(jù)包獲得流媒體文件的信息
if(avformat_find_stream_info(pFormatCtx,NULL)<0){
return ⑴; // Couldn't find stream information
}
//打印輸入或輸出格式的詳細(xì)信息,如時(shí)間、比特率,溪流,容器,程序,元數(shù)據(jù),基礎(chǔ)數(shù)據(jù),編解碼器和時(shí)間。
av_dump_format(pFormatCtx, 0, "test.264", false);
//查找第1個(gè)視頻流
videoStream=⑴;
for(i = 0; i < pFormatCtx->nb_streams; i++){
if(pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
videoStream = i;
break;
}
}
if(videoStream == ⑴){//未發(fā)現(xiàn)視頻流退出
return ⑴;
}
//取得視頻編解碼器的上下文
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
// 找到視頻解碼器
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec == NULL){//未發(fā)現(xiàn)視頻編碼器
return ⑴;
}
// 打開視頻解碼器
if(avcodec_open2(pCodecCtx, pCodec, 0) < 0){
return ⑴; //打開失潰退出
}
/*有些人可能會(huì)從舊的指點(diǎn)中記得有兩個(gè)關(guān)于這些代碼其它部份:
*添加CODEC_FLAG_TRUNCATED到 pCodecCtx->flags和添加1個(gè)hack來粗糙的修正幀率。
*這兩個(gè)修正已不在存在于ffplay.c中。因此,我必須假定它們不再必 要。我們移除
*了那些代碼后還有1個(gè)需要指出的不同點(diǎn):pCodecCtx->time_base現(xiàn)在已保存了幀率
*的信息。time_base是1 個(gè)結(jié)構(gòu)體,它里面有1個(gè)份子和分母 (AVRational)。我們使
*用分?jǐn)?shù)的方式來表示幀率是由于很多編解碼器使用非整數(shù)的幀率(例如NTSC使用29.97fps)。
*
*if(pCodecCtx->time_base.num > 1000 && pCodecCtx->time_base.den == 1){
* pCodecCtx->time_base.den = 1000;
*}
*/
// 分配保存視頻幀的空間
pFrame = avcodec_alloc_frame();
// 分配1個(gè)AVFrame結(jié)構(gòu)
/*準(zhǔn)備輸出保存24位RGB色的PPM文件,我們必須把幀的格式從原來的轉(zhuǎn)換為RGB。FFMPEG將為我們做這些轉(zhuǎn)換*/
pFrameRGB = avcodec_alloc_frame();
if(pFrameRGB==NULL){
return ⑴;
}
/*即便我們申請(qǐng)了1幀的內(nèi)存,當(dāng)轉(zhuǎn)換的時(shí)候,我們依然需要1個(gè)地方來放置原始的數(shù)據(jù)。
*我們使用avpicture_get_size來取得我們需要的大小,然后手工申請(qǐng)內(nèi)存空間:
*/
numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);
buffer = (uint8_t *)av_malloc( numBytes*sizeof(uint8_t));
// 基于指定的圖象參數(shù),設(shè)置圖片字段所提供的圖象數(shù)據(jù)緩沖區(qū)。
avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,pCodecCtx->width, pCodecCtx->height);
/*讀取數(shù)據(jù)幀
*通過讀取包來讀取全部視頻流,然后把它解碼成幀,最好后轉(zhuǎn)換格式并且保存。
*/
i=0;
long prepts = 0;
while(av_read_frame(pFormatCtx, &packet) >= 0){
if(packet.stream_index == videoStream){/*判斷讀取的是不是為需要的視頻幀數(shù)據(jù)*/
// 解碼視頻幀
avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
if(frameFinished){
static struct SwsContext *img_convert_ctx;
#if 0
//就的轉(zhuǎn)換模式已廢除
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個(gè)SwsContext。您需要履行縮放/轉(zhuǎn)換操作使用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);
}
}
////轉(zhuǎn)換圖象格式,將解壓出來的YUV420P的圖象轉(zhuǎn)換為BRG24的圖象
sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);
//保存前5幀數(shù)據(jù)
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);
//關(guān)閉解碼器
avcodec_close(pCodecCtx);
//關(guān)閉視頻文件
avformat_close_input(&pFormatCtx);
system("Pause");
return 0;
}
運(yùn)行以下圖: