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; 下一步,我們需要取出包含在文件中的流信息: AVFormatContext結構體 dump_format(pFormatCtx, 0, filename, false);//我們可以使用這個函數把獲取到得參數全部輸出。 for(i=0; i<pFormatCtx->nb_streams; i++) //區分視頻流和音頻流 接下來就需要尋找解碼器 AVCodec *pCodec; avcodec_open(pCodecCtx, pCodec); //打開解碼器 AVFrame *pFrame; /////////////////////////////////////////開始解碼/////////////////////////////////////////// 第一步當然是讀數據: 我們將要做的是通過讀取包來讀取整個視頻流,然後把它解碼成幀,最後轉換格式並且保存。 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的時候就碰到了這個問題。
請問avcodec_decode_video解碼的幀爲什麼後面的比前面的pts小呢? 請問如下代碼:
答覆: Because you have B - Frame
問: 哦,那是不是我的pts不能這麼算呢?而是要每次+1,對嗎?那麼,packet中的pts和dts要用在什麼地方呢?我這樣按存儲順序進行解碼的話,顯示之前是不是要自己進行緩存呢?謝謝!
另外,還有個問題,既然解碼的時候,不一定是按照pts遞增的順序得到的解碼後的畫面,那我在編碼圖像的時候,是應該按照解碼出來的幀順序進行編碼嗎?還是把幀先緩存起來,最後嚴格接照圖像的顯示順序來編碼呢?用代碼來表示,就是:
答: 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); } |