ffmpeg解碼流程

轉帖360doc http://www.360doc.com/content/11/1117/09/8050095_165108638.shtmlhtml

FFMPEG解碼流程緩存


1. 註冊全部容器格式和CODEC:av_register_all()ide

2. 打開文件:av_open_input_file()函數

3. 從文件中提取流信息:av_find_stream_info()ui

4. 窮舉全部的流,查找其中種類爲CODEC_TYPE_VIDEOthis

5. 查找對應的解碼器:avcodec_find_decoder()編碼

6. 打開編解碼器:avcodec_open()線程

7. 爲解碼幀分配內存:avcodec_alloc_frame()指針

8. 不停地從碼流中提取出幀數據:av_read_frame()code

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

 

 

 

 

用FFMPEG SDK進行視頻轉碼壓縮時解決音視頻不一樣步問題的方法(轉)

ffmpeg 2010-07-21 19:54:16 閱讀163 評論0 

用FFMPEG SDK進行視頻轉碼壓縮的時候,轉碼成功後去看視頻的內容,發現音視頻是不一樣步的。這個的確是一個惱火的事情。我在用FFMPEG SDK作h264格式的FLV文件編碼Filter的時候就碰到了這個問題。

        通過研究發現,FFMPEG SDK寫入視頻的時候有兩個地方用來控制寫入的時間戳,一個是AvPacket, 一個是AvFrame。 在調用avcodec_encode_video的時候須要傳入AvFrame的對象指針,也就是傳入一幀未壓縮的視頻進行壓縮處理,AvFrame包含 一個pts的參數,這個參數就是當前幀未來在還原播放的時候的時間戳。而AvPacket裏面也有pts,還有dts。提及這個就必需要說明一下 I,P,B三種視頻壓縮幀。I幀就是關鍵幀,不依賴於其餘視頻幀,P幀是向前預測的幀,只依賴於前面的視頻幀,而B幀是雙向預測視頻幀,依賴於先後視頻 幀。因爲B幀的存在,由於它是雙向的,必須知道前面的視頻幀和後面的視頻幀的詳細內容後,才能知道本B幀最終該呈現什麼圖像。而pts和dts兩個參數就 是用來控制視頻幀的顯示和解碼的順序。

      pts就是幀顯示的順序。

      dts就是幀被讀取進行解碼的順序。

     若是沒有B幀存在,dts和pts是相同的。反之,則是不相同的。關於這個的詳細介紹能夠參考一下mpeg的原理。

再說說AvPacket中包含的pts和dts兩個到底該設置什麼值?

pts和dts須要設置的就是視頻幀解碼和顯示的順序。每增長一幀就加一,並非播放視頻的時間戳。

可是實踐證實通過rmvb解碼的視頻有時候並非固定幀率的,而是變幀率的,這樣,若是每壓縮一幀,pts和dts加一的方案爲致使音視頻不一樣步。

那怎麼來解決音視頻同步的問題呢?

請看以下代碼段。

lTimeStamp 是經過directshow 獲取的當前的視頻幀的時間戳。

m_llframe_index爲當前已經通過壓縮處理的幀的數量。

首先av_rescale計算獲得當前壓縮處理已經須要處理什麼時間戳的視頻幀,若是該時間戳還沒有到達directshow當前提供的視頻幀的時間戳,則將該幀丟棄掉。

不然進行壓縮操做。並設置AVPacket的pts和dts。這裏假設B幀不存在。

由於在未來播放的時候視頻以咱們設定的固定播放幀率進行播放,因此須要根據設定的播放幀率計算獲得的視頻幀時間戳和directshow提供的當前視頻幀 的時間戳進行比較,設定是否須要進行實施延緩播放的策略。若是須要延緩播放,則將pts增長步長2,不然以普通速度播放,則設置爲1.dts與之相同。
__int64 x =av_rescale(m_llframe_index,AV_TIME_BASE*(int64_t)c->time_base.num,c->time_base.den);

if( x > lTimeStamp )
{
return TRUE;
}
m_pVideoFrame2->pts = lTimeStamp;
m_pVideoFrame2->pict_type = 0;

int out_size = avcodec_encode_video( c, m_pvideo_outbuf, video_outbuf_size,m_pVideoFrame2 );
/* if zero size, it means the image was buffered */
if (out_size > 0)
{
AVPacket pkt;
av_init_packet(&pkt);

if( x > lTimeStamp )
{
   pkt.pts = pkt.dts = m_llframe_index;
   pkt.duration = 0;
}
else
{
   pkt.duration = (lTimeStamp - x)*c->time_base.den/1000000 + 1;
   pkt.pts = m_llframe_index;
   pkt.dts = pkt.pts;
   m_llframe_index += pkt.duration;
}

//pkt.pts = lTimeStamp * (__int64)frame_rate.den / 1000;
if( c->coded_frame && c->coded_frame->key_frame )
{
    pkt.flags |= PKT_FLAG_KEY;
}

pkt.stream_index= m_pVideoStream->index;
pkt.data= m_pvideo_outbuf;
pkt.size= out_size;

/* write the compressed frame in the media file */
ret = av_interleaved_write_frame( m_pAvFormatContext, &pkt );
}
else
{
ret = 0;
}

 

請問avcodec_decode_video解碼的幀爲何後面的比前面的pts小呢?

請問以下代碼:
while( av_read_frame(pFormatCtxSource,&packet)>=0 )
{
    if( packet.stream_index==videoStream )
    {
        int out_size = avcodec_decode_video(pCodecCtxSource,pFrameSource, &bFrameFinished, packet.data, packet.size); // Decode fromsource frame

        if( bFrameFinished )
        {
            pFrameSource->pts =av_rescale_q(packet.pts, pCodecCtxSource->time_base,pStCodec->time_base);
            int out_size =avcodec_encode_video(pStCodec, video_buffer, 200000, pFrameSource); // Encodeto output
            if( out_size>0 )
            {
                // ...
            }
        }
    }

    av_free_packet(&packet);

}

在我Decode的時候,第一幀獲得的 pFrameSource->pts 是96,再解第二幀的時候,pFrameSource->pts 計算完後就成了80幾,後幾幀也是比96小,過一會又會解出來一個100多的,接下來又是比100多小的,這是爲何?在Encode的時候,先 Encode一個pts=96的,再去Encode比96小的幀就返回-1了,直到找到一個比96大的。

另外,我計算pts的方法正確嗎?

 

答覆:

Because you have B - Frame

for example:

the Inputsequence for video encoder
1  2  3   4   5    6   7
I   B   B   P  B   B   I

Let's take1,2,3.. as PTS for simplification

the out sequencefor video encoder ( this equals the decoder sequence)
1  4  2    3   7   5   6
I  P    B   B   I    B   B

you will get aPTS sequence as following:

1  4  2  3  7  5  6 

7  5 6sequence will be same as your question

 

問:

哦,那是否是個人pts不能這麼算呢?而是要每次+1,對嗎?那麼,packet中的pts和dts要用在什麼地方呢?我這樣按存儲順序進行解碼的話,顯示以前是否是要本身進行緩存呢?謝謝!

 

另外,還有個問題,既然解碼的時候,不必定是按照pts遞增的順序獲得的解碼後的畫面,那我在編碼圖像的時候,是應該按照解碼出來的幀順序進行編碼嗎?仍是把幀先緩存起來,最後嚴格接照圖像的顯示順序來編碼呢?用代碼來表示,就是:
方法一:
while(av_read_frame )
{    
    解碼;   
   pts+1;    
    編碼;   
    輸出;
}

方法二:
while(av_read_frame )
{
    解碼;
   if( pts<previous )
    {
       緩存;
    }
   else
    {
       編碼緩存的幀並寫入文件;
    }
}

這兩個方法,哪一個是正確的呢?由於我看到網上的代碼都用的是方法一,可是我以爲方法二是對的呀?

 

答:

the output of decoderis the right order for display because I/P frames will be cacheduntil next I/P

 

 

理解:

 

Decoder 後output的pts 是按正常的順序,即顯示的順序輸出的,若是有B幀,decoder會緩存。

但encoder後,輸出的是按dts輸出的。

 

 

Pts,dts並非時間戳,而更應該理解爲frame的順序序列號。因爲每幀frame的幀率並不必定是一致的,可能會變化的。轉換爲時間戳的話,應該是(pts*幀率)。爲加深理解

能夠將pts比作是第pts幀frame,假設每幀的幀率不變的話,則顯示的時間戳爲(pts*幀率),若是考慮幀率變化的,則要想辦法將(pts*當前的幀率)累加到後面。

 

在tutorial5中在decode 下增長trace後打印狀況:

len1 = avcodec_decode_video(is->video_st->codec,pFrame, &frameFinished,

                      packet->data,packet->size);

      printf("-----------------------------------------------------------------------------\n");

      printf("avcodec_decode_videopacket->pts:%x,packet->dts:%x\n",packet->pts,packet->dts);

      printf("avcodec_decode_videopFrame->pkt_pts:%x,pFrame->pkt_dts:%x,pFrame->pts:%x\n",pFrame->pkt_pts,pFrame->pkt_dts,pFrame->pts);

      if(pFrame->opaque)

           printf("avcodec_decode_video*(uint64_t *)pFrame->opaque:%x\n",*(uint64_t *)pFrame->opaque);

 

其中播一個mp4文件的打印狀況:

-----------------------------------------------------------------------------

avcodec_decode_video packet->pts:1ae,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:1ae

-----------------------------------------------------------------------------

avcodec_decode_video packet->pts:1af,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:1af

-----------------------------------------------------------------------------

avcodec_decode_video packet->pts:24c,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:1ac

-----------------------------------------------------------------------------

avcodec_decode_video packet->pts:24d,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:24d

-----------------------------------------------------------------------------

avcodec_decode_video packet->pts:24e,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video*(uint64_t *)pFrame->opaque:24e

 

如下爲播放rm文件的狀況:

-----------------------------------------------------------------------------

avcodec_decode_videopacket->pts:1831b,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:1831b

-----------------------------------------------------------------------------

avcodec_decode_videopacket->pts:18704,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:18704

-----------------------------------------------------------------------------

avcodec_decode_videopacket->pts:18aed,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:18aed

-----------------------------------------------------------------------------

avcodec_decode_videopacket->pts:18ed6,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:18ed6

-----------------------------------------------------------------------------

avcodec_decode_videopacket->pts:192bf,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:192bf

-----------------------------------------------------------------------------

avcodec_decode_videopacket->pts:196a8,packet->dts:0

avcodec_decode_videopFrame->pkt_pts:0,pFrame->pkt_dts:80000000,pFrame->pts:0

avcodec_decode_video *(uint64_t *)pFrame->opaque:196a8

 

 

能夠看出有的pts是+1 累加,有的是加了不少,但都是按順序累加的。當傳人decoder前的packet有pts時,則decoder後獲取的frame將會賦值packet 的pts;當傳人的packet 只是一幀的部分數據或是B幀,因爲decoder出來的frame要按正常的pts順序輸出,有可能decoder不會獲取到frame ,或decoder內部會緩存也不會輸出frame,即frame的pts會爲空。Frame pts(即opaque) 爲空的話則會看frame->dts,dts都沒有的話才認爲frame->pts爲0.

 

對於:

pts *= av_q2d(is->video_st->time_base);////即pts*幀率

 

    // Did we get avideo frame?

   if(frameFinished) {

      pts =synchronize_video(is, pFrame, pts);

///// synchronize_video考慮了3中狀況:

1.    pts拿到的話就用該pts

2.    pts沒有拿到的話就用前一幀的pts時間

3.    若是該幀要重複顯示,則將顯示的數量*幀率,再加到前面的pts中。

     if(queue_picture(is, pFrame, pts) < 0) {/////傳人decoder後的幀隊列中,以便後續去獲取show。

 

 

static double synchronize_video(VideoState *is, AVFrame*src_frame, double pts) {

 

  doubleframe_delay;

 

  if(pts != 0) {

    /* if we havepts, set video clock to it */

    is->video_clock = pts;

  } else {

    /* if we aren'tgiven a pts, set it to the clock */

    pts =is->video_clock;

  }

  /* update thevideo clock */

/////很關鍵:前面傳進來的pts已是時間戳了,是當前frame開始播放的時間戳,

/////下面frame_delay是該幀顯示完將要花費的時間,(pts+frame_delay)也便是/////預測的下一幀將要播放的時間戳。

  frame_delay =av_q2d(is->video_st->codec->time_base);

  /* if we arerepeating a frame, adjust clock accordingly */

 

//////重複多幀的話要累加上

  frame_delay +=src_frame->repeat_pict * (frame_delay * 0.5);

 is->video_clock += frame_delay;

  return pts;/////此時返回的值即爲下一幀將要開始顯示的時間戳。

}

 

 

 

///////開定時器去顯示幀隊列中的已經decode過的數據,按前面的分析咱們已經知道幀隊列中的數據已是按pts順序插入到隊列中的。 Timer的做用就是有幀率不一致及重複幀的狀況形成時間戳不是線性的,有快有慢,從而tutorial5纔有timer的方式來播放:追趕

如下是一個網友很直觀淺顯的例子解釋:

ccq(183892517) 17:05:21 if(packet->dts ==AV_NOPTS_VALUE 是否是就是沒有獲取到dts的狀況?

David Cen(3727567) 17:06:44 就是有一把尺子 一隻螞蟻跟着一個標杆走  David Cen(3727567) 17:06:58 標杆是勻速的 螞蟻或快或慢 DavidCen(3727567) 17:07:18 慢了你就抽它 讓他跑起來 快了就拽它  David Cen(3727567) 17:07:38 這樣音(標杆)視頻(螞蟻)就能同步了 DavidCen(3727567) 17:08:00 這裏最大的問題就是音頻是勻速的 視頻是非線性的

 

另外:此時vp–>pts獲取到的pts已經轉化爲時間戳了,這個時間戳爲就是當前幀顯示結束的時間戳,也便是下一幀將顯示的預測時間戳。

static void video_refresh_timer(void *userdata) {

 

  VideoState *is = (VideoState*)userdata;

  VideoPicture *vp;

  double actual_delay, delay,sync_threshold, ref_clock, diff;

 

  if(is->video_st) {

    if(is->pictq_size == 0) {

      schedule_refresh(is, 1);

    } else {

      vp =&is->pictq[is->pictq_rindex];

 

      delay = vp->pts -is->frame_last_pts; /* the pts from last time */  ////這是當前要顯示的frame和下一 副                                                        //////將要顯示的 frame的間隔時間

      if(delay <= 0 || delay>= 1.0) {

         /* if incorrect delay, useprevious one */

         delay =is->frame_last_delay;

      }

      /* save for next time */

      is->frame_last_delay =delay;

      is->frame_last_pts =vp->pts;

 

      /* update delay to sync toaudio */

      ref_clock = get_audio_clock(is);/////獲取到聲音當前播放的時間戳。

      diff = vp->pts -ref_clock;////// vp->pts其實是預測的下一幀將要播放的開始時間,

 

//////////也就是說在diff這段時間中聲音是勻速發生的,可是在delay這段時間frame的顯示可能就會有快//////////慢的區別。

        

 

          

 

      /* Skip or repeat the frame.Take delay into account

          FFPlay still doesn't "know if this is thebest guess." */

      sync_threshold = (delay >AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD;

        

      if(fabs(diff) < AV_NOSYNC_THRESHOLD) {

         if(diff <=-sync_threshold) {

           delay = 0;//////下一幀畫面顯示的時間和當前的聲音很近的話加快顯示下一幀(即後面video_display顯示完當前幀後開啓定時器很快去顯示下一幀)

         } else if(diff >=sync_threshold) {

           delay = 2 * delay;//////下一幀開始顯示的時間和當前聲音的時間隔的比較長則延緩,即兩幀畫面間話的顯示的時間長度大於兩幀畫面間的聲音播放的時間,則我 們將兩幀畫顯示的時候加倍拖長點,好比幀1和幀2的時間顯示間隔爲40ms,但幀1和幀2的聲音播放時間爲55ms,怎麼辦呢?咱們不可能去打亂聲音的質 量的,則咱們採用的方法是:將兩幀畫面的播放間隔加大,原本是過30ms就要開始播下一幀的,咱們改爲60ms後才播下一幀。

         }

      }/////

////固然若是diff大於AV_NOSYNC_THRESHOLD,即快進的模式了,畫面跳動太大,不存在音視頻同步的問題了。

 

      is->frame_timer += delay;

      /* computer the REAL delay*/

      actual_delay =is->frame_timer - (av_gettime() / 1000000.0);

      if(actual_delay < 0.010){

         /* Really it should skipthe picture instead */

         actual_delay = 0.010;

      }

      schedule_refresh(is,(int)(actual_delay * 1000 + 0.5));////開定時器去顯示下一幀

      /* show the picture! */

      video_display(is);////立馬顯示當前幀

     

      /* update queue for nextpicture! */

      if(++is->pictq_rindex ==VIDEO_PICTURE_QUEUE_SIZE) {

         is->pictq_rindex = 0;

      }

     SDL_LockMutex(is->pictq_mutex);

      is->pictq_size--;

     SDL_CondSignal(is->pictq_cond);

     SDL_UnlockMutex(is->pictq_mutex);

    }

  } else {

    schedule_refresh(is, 100);

  }

相關文章
相關標籤/搜索