ffmpeg編解碼詳細過程


 

 

 

 

 

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播放器中讀出來的數字一樣。既然我們把視頻同步到音頻,視頻線程使用這個值來算出是否太快還是太慢。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM