FFmpeg and SDL Tutorial
在網路上看到一些教學,雖然好像有點過時了
但我覺得還是可以參考
就在這邊做翻譯邊記錄下來
原本的連結 http://dranger.com/ffmpeg/
前言:
前面先介紹了一些 FFmpeg 的特色跟優點
當我們跟著指示完成時,就會有一隻少於一千行的視訊播放器
而在製作撥放器的時候,會用到SDL來輸出
第一步:Making Screencaps
Overview
影片的檔案有一些基本成份,檔案的本身叫做container
container的例子有 AVI 跟 Quicktime
再來檔案通常會有 video stream 跟 audio stream,而 stream 的元素稱為 frame
每一個 stream,都被編碼成各種不同類型的codec,而codec 決定了怎麼編碼跟解碼
codec 的例子有 DivX 跟 MP3
Packets 是從 stream 讀出來,再解碼成 raw frames,是我們最終拿來應用的資料集合
簡單來說
10 OPEN video_stream FROM video.avi
20 READ packet FROM video_stream INTO frame
30 IF frame NOT COMPLETE GOTO 20
40 DO SOMETHING WITH frame
50 GOTO 20
用 FFmpeg 處理多媒體大部份就像這樣的流程,雖然有些程式 "DO SOMETHING" 非常複雜
Opening the File
首先我們要開啟檔案,開啟之前我們要先初始化 library
#include <avcodec.h>
#include <avformat.h>
int main(int argc, charg *argv[]){
av_register_all();
這會註冊所有在 library 裡面所有可用的 file formats and codecs
當檔案有對應的 format/codec 被開啟,他們就會被自動使用
要注意的是,註冊只需要做一次就可以了,所以我們在 main() 裡面呼叫
接下來就要開啟檔案了
AVFormatContext *pFormatCtx;
// Open video file
if(av_open_input_file(&pFormatCtx, argv[1], NULL, 0, NULL) != 0)
return -1; // Couldn't open file
第一個引數會得到我們的檔案名稱
然後這個函式會讀取標頭檔以及儲存檔案格式 AVFormatContext 的結構
最後三個引數用來指定 file format, buffer size, and format options
如果設定成 0 或 NULL,libavformat 會自動檢測
這個函式只看標頭檔,接下來我們要檢查 stream 的資訊
// 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 是陣列大小
所以我們可以走訪這個陣列,直到我們發現 video stream
int i;
AVCodecContext *pCodeCtx;
// 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
pCodeCtx=pFormatCtx->streams[videoStream]->codec;
關於 codec 的 stream 資訊,我們稱為 "codec context"
這包含了所有我們使用的 stream 資訊
但我們仍然必須找真正的 codec 並且開啟他
AVCodec *pCodec;
// Find the decoder for the video stream
pCodec = avcodec_find_decoder(pCodeCtx->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
Storing the Data
現在我們要找地方來存 frame
AVFrame *pFrame;
// Allocate video frame
pFrame = avcodec_alloc_frame();
因為我們計畫要輸出 PPM 的檔案,所以我們要把原生的 frame 轉成 RGB
// Allocate an AVFrame structure
pFrameRGB = avcodec_alloc_frame();
if( pFrameRGB == NULL )
return -1;
雖然我們分配了 frame,但我們還是需要地方來放轉換好的 raw data
我們用 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
現在我們要使用 avpicture_fill 來連結我們的 frame 跟新的 buffer
// Assign appropriate parts of buffer to image planes in pFrameRGB
// Note that pFrameRGB ia an AVFrame, but AVFrame is a superset of AVPicture
avpicture_fill( (AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24, pCodecCtx->width,
pCodecCtx->height);
終於,我們已經準備好讀取 stream
Reading the Data
現在我們要用讀取 packet 的方式來讀取完整的 video stream
再解碼到我們的 frame,一旦完成之後,就去轉換並存檔
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(pCodeCtx, 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);
}//if
}//if
// Free the packet that was allocated by av_read_frame
av_free_packet(&packet);
}//while
av_read_frame() 讀取 packet 並儲存到 AVPacket 結構裡
要注意我們只有分配 packet 結構
FFmpeg 為我們分配內部的資料指向 packet.data
而這等一下會被 av_free_packet() 給 free掉
avcodec_decode_video() 幫我們從 packet 轉換到 frame
總之在做完解碼 packet 後,我們可能沒有我們所需 frame 的所有資訊
所以當我們有下一張 frame 時,avcodec_decode_video()可以幫我們設定 frameFinished
再來我們使用 img_convert() 把原生 format 轉換成 RGB (pCodecCtx->pix_fmt)
要記得我們可以把 AVFrame 轉換成 AVPicture
最後我們就把 frame 的寬跟高存進我們的 SaveFrame 函式
現在我們只要讓 SaveFrame 函式把 RGB 的資訊輸出成 PPM
video SaveFrame( AVFrame *pFrame, int width, int height, intframe) {
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\n225\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 資訊
標頭檔指出了圖案的長跟寬還有 RGB 值的最大尺寸
現在回到main()
當我們讀取完 video stream,我們必須清除所有東西
// Free the RGB image
av_free(buffer);
av_free(pFrameRGB);
// Free the YUV frame
av_free(pFrame)
// Close the codec
avcodec_close(pCodeCtx);
// Close the video file
av_close_input_file(pFormatCtx);
return 0;
可以用以下的命令來執行
gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lz -lavutil -lm
但我覺得還是可以參考
就在這邊做翻譯邊記錄下來
原本的連結 http://dranger.com/ffmpeg/
前言:
前面先介紹了一些 FFmpeg 的特色跟優點
當我們跟著指示完成時,就會有一隻少於一千行的視訊播放器
而在製作撥放器的時候,會用到SDL來輸出
第一步:Making Screencaps
Overview
影片的檔案有一些基本成份,檔案的本身叫做container
container的例子有 AVI 跟 Quicktime
再來檔案通常會有 video stream 跟 audio stream,而 stream 的元素稱為 frame
每一個 stream,都被編碼成各種不同類型的codec,而codec 決定了怎麼編碼跟解碼
codec 的例子有 DivX 跟 MP3
Packets 是從 stream 讀出來,再解碼成 raw frames,是我們最終拿來應用的資料集合
簡單來說
10 OPEN video_stream FROM video.avi
20 READ packet FROM video_stream INTO frame
30 IF frame NOT COMPLETE GOTO 20
40 DO SOMETHING WITH frame
50 GOTO 20
用 FFmpeg 處理多媒體大部份就像這樣的流程,雖然有些程式 "DO SOMETHING" 非常複雜
Opening the File
首先我們要開啟檔案,開啟之前我們要先初始化 library
#include <avcodec.h>
#include <avformat.h>
int main(int argc, charg *argv[]){
av_register_all();
這會註冊所有在 library 裡面所有可用的 file formats and codecs
當檔案有對應的 format/codec 被開啟,他們就會被自動使用
要注意的是,註冊只需要做一次就可以了,所以我們在 main() 裡面呼叫
接下來就要開啟檔案了
AVFormatContext *pFormatCtx;
// Open video file
if(av_open_input_file(&pFormatCtx, argv[1], NULL, 0, NULL) != 0)
return -1; // Couldn't open file
第一個引數會得到我們的檔案名稱
然後這個函式會讀取標頭檔以及儲存檔案格式 AVFormatContext 的結構
最後三個引數用來指定 file format, buffer size, and format options
如果設定成 0 或 NULL,libavformat 會自動檢測
這個函式只看標頭檔,接下來我們要檢查 stream 的資訊
// 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 是陣列大小
所以我們可以走訪這個陣列,直到我們發現 video stream
int i;
AVCodecContext *pCodeCtx;
// 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
pCodeCtx=pFormatCtx->streams[videoStream]->codec;
關於 codec 的 stream 資訊,我們稱為 "codec context"
這包含了所有我們使用的 stream 資訊
但我們仍然必須找真正的 codec 並且開啟他
AVCodec *pCodec;
// Find the decoder for the video stream
pCodec = avcodec_find_decoder(pCodeCtx->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
Storing the Data
現在我們要找地方來存 frame
AVFrame *pFrame;
// Allocate video frame
pFrame = avcodec_alloc_frame();
因為我們計畫要輸出 PPM 的檔案,所以我們要把原生的 frame 轉成 RGB
// Allocate an AVFrame structure
pFrameRGB = avcodec_alloc_frame();
if( pFrameRGB == NULL )
return -1;
雖然我們分配了 frame,但我們還是需要地方來放轉換好的 raw data
我們用 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
現在我們要使用 avpicture_fill 來連結我們的 frame 跟新的 buffer
// Assign appropriate parts of buffer to image planes in pFrameRGB
// Note that pFrameRGB ia an AVFrame, but AVFrame is a superset of AVPicture
avpicture_fill( (AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24, pCodecCtx->width,
pCodecCtx->height);
終於,我們已經準備好讀取 stream
Reading the Data
現在我們要用讀取 packet 的方式來讀取完整的 video stream
再解碼到我們的 frame,一旦完成之後,就去轉換並存檔
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(pCodeCtx, 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);
}//if
}//if
// Free the packet that was allocated by av_read_frame
av_free_packet(&packet);
}//while
av_read_frame() 讀取 packet 並儲存到 AVPacket 結構裡
要注意我們只有分配 packet 結構
FFmpeg 為我們分配內部的資料指向 packet.data
而這等一下會被 av_free_packet() 給 free掉
avcodec_decode_video() 幫我們從 packet 轉換到 frame
總之在做完解碼 packet 後,我們可能沒有我們所需 frame 的所有資訊
所以當我們有下一張 frame 時,avcodec_decode_video()可以幫我們設定 frameFinished
再來我們使用 img_convert() 把原生 format 轉換成 RGB (pCodecCtx->pix_fmt)
要記得我們可以把 AVFrame 轉換成 AVPicture
最後我們就把 frame 的寬跟高存進我們的 SaveFrame 函式
現在我們只要讓 SaveFrame 函式把 RGB 的資訊輸出成 PPM
video SaveFrame( AVFrame *pFrame, int width, int height, intframe) {
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\n225\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 資訊
標頭檔指出了圖案的長跟寬還有 RGB 值的最大尺寸
現在回到main()
當我們讀取完 video stream,我們必須清除所有東西
// Free the RGB image
av_free(buffer);
av_free(pFrameRGB);
// Free the YUV frame
av_free(pFrame)
// Close the codec
avcodec_close(pCodeCtx);
// Close the video file
av_close_input_file(pFormatCtx);
return 0;
可以用以下的命令來執行
gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lz -lavutil -lm
留言
張貼留言