1. 注冊所有容器格式和CODEC:av_register_all()
2. 打開文件:av_open_input_file()
3. 從文件中提取流信息:av_find_stream_info()
4. 窮舉所有的流,查找其中種類為CODEC_TYPE_VIDEO
5. 查找對應的解碼器:avcodec_find_decoder()
6. 打開編解碼器:avcodec_open()
7. 為解碼幀分配內存:avcodec_alloc_frame()
8. 不停地從碼流中提取出幀數據:av_read_frame()
9. 判斷幀的類型,對於視頻幀調用:avcodec_decode_video()
10. 解碼完后,釋放解碼器:avcodec_close()
11. 關閉輸入文件:av_close_input_file()
首先第一件事情就是開一個視頻文件並從中得到流。我們要做的第一件事情就是使用av_register_all();來初始化libavformat/libavcodec:
這一步注冊庫中含有的所有可用的文件格式和編碼器,這樣當打開一個文件時,它們才能夠自動選擇相應的文件格式和編碼器。av_register_all()只需調用一次,所以,要放在初始化代碼中。也可以僅僅注冊個人的文件格式和編碼。
下一步,打開文件:
AVFormatContext *pFormatCtx;
const char *filename="myvideo.mpg";
av_open_input_file(&pFormatCtx, filename, NULL, 0, NULL); // 打開視頻文件
最后三個參數描述了文件格式,緩沖區大小(size)和格式參數;我們通過簡單地指明NULL或0告訴 libavformat 去自動探測文件格式並且使用默認的緩沖區大小。這里的格式參數指的是視頻輸出參數,比如寬高的坐標。
下一步,我們需要取出包含在文件中的流信息:
av_find_stream_info(pFormatCtx); // 取出流信息
AVFormatContext 結構體
dump_format(pFormatCtx, 0, filename, false);//我們可以使用這個函數把獲取到得參數全部輸出。
for(i=0; i<pFormatCtx->nb_streams; i++) //區分視頻流和音頻流
if(pFormatCtx->streams->codec.codec_type==CODEC_TYPE_VIDEO) //找到視頻流,這里也可以換成音頻
{
videoStream=i;
break;
}
接下來就需要尋找解碼器
AVCodec *pCodec;
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
avcodec_open(pCodecCtx, pCodec); // 打開解碼器
給視頻幀分配空間以便存儲解碼后的圖片:
AVFrame *pFrame;
pFrame=avcodec_alloc_frame();
/////////////////////////////////////////開始解碼///////////////////////////////////////////
第一步當然是讀數據:
我們將要做的是通過讀取包來讀取整個視頻流,然后把它解碼成幀,最后轉換格式並且保存。
while(av_read_frame(pFormatCtx, &packet)>=0) { //讀數據
if(packet.stream_index==videoStream){ //判斷是否視頻流
avcodec_decode_video(pCodecCtx,pFrame, &frameFinished,
packet.data, packet.size); //解碼
if(frameFinished) {
img_convert((AVPicture *)pFrameRGB, PIX_FMT_RGB24,(AVPicture*)pFrame, pCodecCtx->pix_fmt, pCodecCtx->width,pCodecCtx->height);//轉換 }
SaveFrame(pFrameRGB, pCodecCtx->width,pCodecCtx->height, i); //保存數據
av_free_packet(&packet); //釋放
av_read_frame()讀取一個包並且把它保存到AVPacket結構體中。這些數據可以在后面通過av_free_packet()來釋放。函數avcodec_decode_video()把包轉換為幀。然而當解碼一個包的時候,我們可能沒有得到我們需要的關於幀的信息。因此,當我們得到下一幀的時候,avcodec_decode_video()為我們設置了幀結束標志frameFinished。最后,我們使用 img_convert()函數來把幀從原始格式(pCodecCtx->pix_fmt)轉換成為RGB格式。要記住,你可以把一個 AVFrame結構體的指針轉換為AVPicture結構體的指針。最后,我們把幀和高度寬度信息傳遞給我們的SaveFrame函數。
到此解碼完畢,顯示過程使用SDL完成考慮到我們以后會使用firmware進行顯示操作,SDL忽略不講。
音視頻同步
DTS(解碼時間戳)和PTS(顯示時間戳)
當我們調用av_read_frame()得到一個包的時候,PTS和DTS的信息也會保存在包中。但是我們真正想要的PTS是我們剛剛解碼出來的原始幀的PTS,這樣我們才能知道什么時候來顯示它。然而,我們從avcodec_decode_video()函數中得到的幀只是一個AVFrame,其中並沒有包含有用的PTS值(注意:AVFrame並沒有包含時間戳信息,但當我們等到幀的時候並不是我們想要的樣子)。。我們保存一幀的第一個包的PTS:這將作為整個這一幀的PTS。我們可以通過函數avcodec_decode_video()來計算出哪個包是一幀的第一個包。怎樣實現呢?任何時候當一個包開始一幀的時候,avcodec_decode_video()將調用一個函數來為一幀申請一個緩沖。當然,ffmpeg允許我們重新定義那個分配內存的函數。計算前一幀和現在這一幀的時間戳來預測出下一個時間戳的時間。同時,我們需要同步視頻到音頻。我們將設置一個音頻時間audioclock;一個內部值記錄了我們正在播放的音頻的位置。就像從任意的mp3播放器中讀出來的數字一樣。既然我們把視頻同步到音頻,視頻線程使用這個值來算出是否太快還是太慢。