轉自:http://blog.chinaunix.net/uid-20846214-id-4193590.html
注:本文參考http://dranger.com/ffmpeg/tutorial01.html,但是這篇比較老舊了,文中用的最新版的FFmpeg,很多API都跟老版的不同,請大家注意。
在最簡單的情況下,其實處理Video和Audio的步驟是非常簡單的:
1:open video_stream從video.avi中
2:從video_stream中讀取packet到frame裏面
3:如果frame不完整就goto到第2步繼續
4:對frame做些處理
5:跳到第2步重複
packet包含了要被解碼成原始數據幀frame的數據塊。每個packet都包含了完整的frames。
這章,我們會打開一個媒體文件,從文件裏面讀取Video Stream,然後把幀frame寫入一個PPM文件,PPM(Portable Pixelmap)文件是一種linux圖片格式,它很簡單,只包含格式,圖像寬高,bit數等信息以及圖像數據。
一:打開文件
要用FFmpeg庫中的支持,必須包含它的頭文件:
- #include <avcodec.h>
- #include <avformat.h>
- ...
- ...
- int main(int argc, char **argv)
- {
- ...
- av_register_all();
- ...
- }
接下來打開媒體文件:
- AVFormatContext *pFormatCtx;
- // Open video file
- if(avformat_open_input(&pFormatCtx, argv[1],
NULL,
NULL)!=0)
- return -1; // Couldn't open file
avformat_open_input函數會讀取媒體文件的頭並且把這些信息保存到AVFormatContext結構體中,最後2個參數是用來指定文件格式,buffer大小和格式參數,設置成NULL的話,libavformat庫會自動去探測它們。
接下來我們需要Check Out這個文件的stream信息:
- // Retrieve stream information
- if (av_find_stream_info(pFormatCtx) < 0)
- return -1; // Couldn't find stream information
- // Dump information about file onto standard
error
- av_dump_format(pFormatCtx, 0, argv[1], 0);
- int i;
- AVCodecContext *pCodecCtx;
- // Find the first video stream
- videoStream = -1;
- for (i=0; i<pFormatCtx->nb_streams;
i++) {
- if (pFormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO)
{
- videoStream = i;
- break;
- }
- }
- if(videoStream == -1)
- return -1;
// Didn't find a video stream
- //
Get a pointer to the codec context
for the video stream
- pCodecCtx = pFormatCtx->streams[videoStream]->codec;
- AVCodec *pCodec;
- // Find the decoder
for the video stream
- pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
- if (pCodec == NULL)
{
- fprintf(stderr,
"Unsupported codec!\n");
- return -1;
// Codec
not found
- }
- // Open codec
- if(avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
- return -1; // Could not open codec
二:存儲數據
現在我們需要一個地方來存放從媒體文件中解碼出的原始數據幀frame:
- AVFrame *pFrame;
- // Allocate video frame
- pFrame = avcodec_alloc_frame();
- // Allocate an AVFrame structure
- pFrameRGB = avcodec_alloc_frame();
- if(pFrameRGB == NULL)
- return -1;
別急,還需要一個內存buffer,用來存放媒體文件的中即將要被轉換的原始數據,我們用avpicture_get_size函數來獲取需要的size:
- uint8_t *buffer;
- int numBytes;
- // Determine required buffer size
and allocate buffer
- numBytes = avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
- pCodecCtx->height);
- buffer = (uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
下面將pFrameRGB與buffer關聯起來,這裏的AVPicture 結構是AVFrame 結構的子集,AVPicture 結構與AVFrame 結構的開始部分一模一樣:
- // Assign appropriate parts of buffer
to image planes in pFrameRGB
- // Note that pFrameRGB
is an AVFrame, but AVFrame
is a superset
- // of AVPicture
- avpicture_fill((AVPicture
*)pFrameRGB, buffer, PIX_FMT_RGB24,
- pCodecCtx->width, pCodecCtx->height);
三:讀數據
下面通過函數av_read_frame讀取Video Stream到packet中。然後用解碼器pCodecCtx從packet.data中解碼出原始數據幀並存放到pFrame中。參數frameFinished判斷轉換的結果。如果轉換完成,調用img_convert函數把原始數據幀pFrame轉換成我們要的RGB幀pFrameRGB,並調用SaveFrame函數把RGB幀保存成一個個的PPM圖片(這裏我們只保存了Video Stream的前15張圖片,可以根據個人需要修改)。while循環最後會調用av_free_packet函數清除av_read_frame函數中讀入packet的數據,然後循環繼續讀packet,繼續解碼,繼續轉換,繼續保存成PPM圖片,直到讀完整個媒體文件。注意這裏自己準備一個pSwsCtx結構,這個結構比較靈活,可以對即將要生成的PPM圖片進行操作配置,如反轉圖片。
- int frameFinished;
- AVPacket packet;
- i=0;
- while(av_read_frame(pFormatCtx,
&packet)>=0)
{
- //
Is this a packet from the video stream?
- if(packet.stream_index==videoStream)
{
- // Decode video frame pCodecCtx, pFrame, &frameFinished, &packet
- avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
-
- // Did we
get a video frame?
- if(frameFinished)
{
- // Convert the image from its native format
to RGB
- sws_scale(pSwsCtx, pFrame->data, pFrame->linesize, 0,
pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize); -
- // Save the frame
to disk
- if(++i<=15)
- SaveFrame(pFrameRGB, pCodecCtx->width,
- pCodecCtx->height, i);
- }
- }
-
- // Free the packet that was allocated by av_read_frame
- av_free_packet(&packet);
- }
ppm的第一部分由三行ASCII碼組成:
第一行是P2 or P3 or P6,我們寫的P6
第二行是圖像的大小,先是列像素數,後是行像素數,中間有一個空格,我們寫的%d %d
第三行是一個介於1和65535之間的整數,而且必須是文本的,用來表示每一個像素的一個分量用幾個比特表示,我們寫的255,即8bit表示一個像素分量,那一個像素就是24-bit了。
三行之後是圖像的純數據流,從左到右,從上到下。我們這裏寫數據部分,pFrame->data[0]是數據頭,y是目前寫入的行數,pFrame->linesize[0]是每行的字節數,pFrame->data[0]+y*pFrame->linesize[0]就是每行數據開頭的地址。width是每行像素個數,width*3就是每行要寫的數據個數,以像素分量爲單位。
- 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);
- }
- // Free the RGB image
- av_free(buffer);
- av_free(pFrameRGB);
- // Free the YUV frame
- av_free(pFrame);
- // Close the codec
- avcodec_close(pCodecCtx);
- // Close the video file
- av_close_input_file(pFormatCtx);
- return 0;
OK,到此完成了!編譯,運行,會發現當前目錄下有15長PPM圖片: