指導1:製做屏幕錄像
shell
概要windows
電影文件有不少基本的組成部分。首先,文件自己被稱爲容器Container,容器的類型決定了信息被存放在文件中的位置。AVI和Quicktime就是容器的例子。接着,你有一組流,例如,你常常有的是一個音頻流和一個視頻流。(一個流只是一種想像出來的詞語,用來表示一連串的經過時間來串連的數據元素)。在流中的數據元素被稱爲幀Frame。每一個流是由不一樣的編碼器來編碼生成的。編解碼器描述了實際的數據是如何被編碼Coded和解碼DECoded的,所以它的名字叫作CODEC。Divx和MP3就是編解碼器的例子。接着從流中被讀出來的叫作包Packets。包是一段數據,它包含了一段能夠被解碼成方便咱們最後在應用程序中操做的原始幀的數據。根據咱們的目的,每一個包包含了完整的幀或者對於音頻來講是許多格式完整的幀。app
基本上來講,處理視頻和音頻流是很容易的:ide
10 從video.avi文件中打開視頻流video_stream函數 20 從視頻流中讀取包到幀中測試 30 若是這個幀還不完整,跳到20ui 40 對這個幀進行一些操做this 50 跳回到20編碼 |
在這個程序中使用ffmpeg來處理多種媒體是至關容易的,雖然不少程序可能在對幀進行操做的時候很是的複雜。所以在這篇指導中,咱們將打開一個文件,讀取裏面的視頻流,並且咱們對幀的操做將是把這個幀寫到一個PPM文件中。spa
打開文件
首先,來看一下咱們如何打開一個文件。經過ffmpeg,你必需先初始化這個庫。(注意在某些系統中必需用<ffmpeg/avcodec.h>和<ffmpeg/avformat.h>來替換)
#include <avcodec.h> #include <avformat.h> ... int main(int argc, charg *argv[]) { av_register_all();
這裏註冊了全部的文件格式和編解碼器的庫,因此它們將被自動的使用在被打開的合適格式的文件上。注意你只須要調用 av_register_all()一次,所以咱們在主函數main()中來調用它。若是你喜歡,也能夠只註冊特定的格式和編解碼器,可是一般你沒有必要這樣作。
如今咱們能夠真正的打開文件:
AVFormatContext *pFormatCtx; // Open video file if(av_open_input_file(&pFormatCtx, argv[1], NULL, 0, NULL)!=0) return -1; // Couldn't open file
咱們經過第一個參數argv[1]來得到文件名。這個函數讀取文件的頭部而且把信息保存到咱們給的AVFormatContext結構體中。最後三個參數用來指定特殊的文件格式,緩衝大小和格式參數,但若是把它們設置爲空NULL或者0,libavformat將自動檢測這些參數。
這個函數只是檢測了文件的頭部,因此接着咱們須要檢查在文件中的流的信息:
// Retrieve stream information if(av_find_stream_info(pFormatCtx)<0) return -1; // Couldn't find stream information
這個函數爲pFormatCtx->streams填充上正確的信息。咱們引進一個手工調試的函數來看一下里面有什麼:
// Dump information about file onto standard error dump_format(pFormatCtx, 0, argv[1], 0);
如今pFormatCtx->streams僅僅是一組大小爲pFormatCtx->nb_streams的指針,因此讓咱們先跳過它直到咱們找到一個視頻流。
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;
流中關於編解碼器的信息就是被咱們叫作"AVCodecContext"(編解碼器上下文)的東西。這裏麪包含了流中所使用的關於編解碼器的全部信息,如今咱們有了一個指向他的指針。可是咱們必須要找到真正的編解碼器而且打開它:
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_open(pCodecCtx, pCodec)<0) return -1; // Could not open codec
有些人可能會從舊的指導中記得有兩個關於這些代碼其它部分:添加CODEC_FLAG_TRUNCATED到pCodecCtx->flags和添加一個hack來粗糙的修正幀率。這兩個修正已經不在存在於ffplay.c中。所以,我必需假設它們再也不必要。咱們移除了那些代碼後還有一個須要指出的不一樣點:pCodecCtx->time_base如今已經保存了幀率的信息。time_base是一個結構體,它裏面有一個分子和分母(AVRational)。咱們使用分數的方式來表示幀率是由於不少編解碼器使用非整數的幀率(例如NTSC使用29.97fps)。
保存數據
如今咱們須要找到一個地方來保存幀:
AVFrame *pFrame;
// Allocate video frame 爲原始幀申請內存 pFrame=avcodec_alloc_frame();
由於咱們準備輸出保存24位RGB色的PPM文件,咱們必需把幀的格式從原來的轉換爲RGB。FFMPEG將爲咱們作這些轉換。在大多數項目中(包括咱們的這個)咱們都想把原始的幀轉換成一個特定的格式。讓咱們先爲轉換來申請一幀的內存。
// Allocate an AVFrame structure 爲轉換的禎申請內存 pFrameRGB=avcodec_alloc_frame(); if(pFrameRGB==NULL) return -1;
即便咱們申請了一幀的內存,當轉換的時候,咱們仍然須要一個地方來放置原始的數據。咱們使用avpicture_get_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));
av_malloc是ffmpeg的malloc,用來實現一個簡單的malloc的包裝,這樣來保證內存地址是對齊的(4字節對齊或者2字節對齊)。它並不能保護你不被內存泄漏,重複釋放或者其它malloc的問題所困擾。
如今咱們使用avpicture_fill來把幀和咱們新申請的內存來結合。關於AVPicture的結構:AVPicture結構體是AVFrame結構體的子集――AVFrame結構體的開始部分與AVPicture結構體是同樣的。
// 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);
最後,咱們已經準備好來從流中讀取數據了。
讀取數據
咱們將要作的是經過讀取包來讀取整個視頻流,而後把它解碼成幀,最好後轉換格式而且保存。
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 avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, packet.data, packet.size); // Did we get a video frame? if(frameFinished) { // Convert the image from its native format to RGB img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24, (AVPicture*)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height); // Save the frame to disk if(++i<=5) SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i); } } // Free the packet that was allocated by av_read_frame av_free_packet(&packet); }
這個循環過程是比較簡單的:av_read_frame()讀取一個包而且把它保存到AVPacket結構體中。注意咱們僅僅申請了一個包的結構體――ffmpeg爲咱們申請了內部的數據的內存並經過packet.data指針來指向它。這些數據能夠在後面經過av_free_packet()來釋放。函數avcodec_decode_video()把包轉換爲幀。然而當解碼一個包的時候,咱們可能沒有獲得咱們須要的關於幀的信息。所以,當咱們獲得下一幀的時候,avcodec_decode_video()爲咱們設置了幀結束標誌frameFinished。最後,咱們使用img_convert()函數來把幀從原始格式(pCodecCtx->pix_fmt)轉換成爲RGB格式。要記住,你能夠把一個AVFrame結構體的指針轉換爲AVPicture結構體的指針。最後,咱們把幀和高度寬度信息傳遞給咱們的SaveFrame函數。
關於包Packets的註釋 從技術上講一個包能夠包含部分或者其它的數據,可是ffmpeg的解釋器保證了咱們獲得的包Packets包含的要麼是完整的要麼是多種完整的幀。 |
如今咱們須要作的是讓SaveFrame函數能把RGB信息定稿到一個PPM格式的文件中。咱們將生成一個簡單的PPM格式文件,請相信,它是能夠工做的。
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); }
咱們作了一些標準的文件打開動做,而後寫入RGB數據。咱們一次向文件寫入一行數據。PPM格式文件的是一種包含一長串的RGB數據的文件。若是你瞭解HTML色彩表示的方式,那麼它就相似於把每一個像素的顏色頭對頭的展開,就像#ff0000#ff0000....就表示了了個紅色的屏幕。(它被保存成二進制方式而且沒有分隔符,可是你本身是知道如何分隔的)。文件的頭部表示了圖像的寬度和高度以及最大的RGB值的大小。
如今,回顧咱們的main()函數。一旦咱們開始讀取完視頻流,咱們必需清理一切:
// 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;
你會注意到咱們使用av_free來釋放咱們使用avcode_alloc_fram和av_malloc來分配的內存。
上面的就是代碼!下面,咱們將使用Linux或者其它相似的平臺,你將運行:
gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lz -lavutil -lm
若是你使用的是老版本的ffmpeg,你能夠去掉-lavutil參數:
gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lz -lm
大多數的圖像處理函數能夠打開PPM文件。可使用一些電影文件來進行測試。
#include "stdafx.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" //#include <windows.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <math.h> #include <SDL/SDL.h> #ifdef main #undef main #endif #define SDL_AUDIO_BUFFER_SIZE 1024 static int sws_flags = SWS_BICUBIC; int main(int argc, char *argv[]) { AVFormatContext *pFormatCtx; int i, videoStream(-1); AVCodecContext *pCodecCtx; AVCodec *pCodec; AVFrame *pFrame; AVPacket packet; int frameFinished; float aspect_ratio; AVCodecContext *aCodecCtx; SDL_Overlay *bmp; SDL_Surface *screen; SDL_Rect rect; SDL_Event event; if(argc < 2) { fprintf(stderr, "Usage: test \n"); exit(1); } av_register_all(); pFormatCtx = av_alloc_format_context(); if (!pFormatCtx) { fprintf(stderr, "Memory error\n"); exit(1); } if(av_open_input_file(&pFormatCtx, argv[1], NULL, 0, NULL)!=0) return -1; // Couldn't open file if(av_find_stream_info(pFormatCtx)<0) return -1; // Couldn't find stream information // Dump information about file onto standard error dump_format(pFormatCtx, 0, argv[1], 0); // Find the first video stream for(i=0; i<pFormatCtx->nb_streams; i++) { if(pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO && videoStream<0) { videoStream=i; } } 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; pCodec=avcodec_find_decoder(pCodecCtx->codec_id); if(pCodec==NULL) { fprintf(stderr, "Unsupported codec!\n"); return -1; // Codec not found } // Open codec if(avcodec_open(pCodecCtx, pCodec)<0) return -1; // Could not open codec // Allocate video frame pFrame=avcodec_alloc_frame(); 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)); if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) { fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError()); exit(1); } #ifndef __DARWIN__ screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0); #else screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 24, 0); #endif if(!screen) { fprintf(stderr, "SDL: could not set video mode - exiting\n"); exit(1); } bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height, SDL_YV12_OVERLAY, screen); static struct SwsContext *img_convert_ctx; if (img_convert_ctx == NULL) { img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, sws_flags, NULL, NULL, NULL); if (img_convert_ctx == NULL) { fprintf(stderr, "Cannot initialize the conversion context\n"); exit(1); } } 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 avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, packet.data, packet.size); // Did we get a video frame? if(frameFinished) { // Convert the image from its native format to RGB // Save the frame to disk SDL_LockYUVOverlay(bmp); AVPicture pict; pict.data[0] = bmp->pixels[0]; pict.data[1] = bmp->pixels[2]; pict.data[2] = bmp->pixels[1]; pict.linesize[0] = bmp->pitches[0]; pict.linesize[1] = bmp->pitches[2]; pict.linesize[2] = bmp->pitches[1]; // Convert the image into YUV format that SDL uses sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pict.data, pict.linesize); SDL_UnlockYUVOverlay(bmp); rect.x = 0; rect.y = 0; rect.w = pCodecCtx->width; rect.h = pCodecCtx->height; SDL_DisplayYUVOverlay(bmp, &rect); //Sleep(60); } } // Free the packet that was allocated by av_read_frame av_free_packet(&packet); SDL_PollEvent(&event); switch(event.type) { case SDL_QUIT: SDL_Quit(); exit(0); break; default: break; } }; // 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; }