FFmpeg如何同步音視頻的解決方案_第1頁(yè)
FFmpeg如何同步音視頻的解決方案_第2頁(yè)
FFmpeg如何同步音視頻的解決方案_第3頁(yè)
FFmpeg如何同步音視頻的解決方案_第4頁(yè)
FFmpeg如何同步音視頻的解決方案_第5頁(yè)
已閱讀5頁(yè),還剩35頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

1、【轉(zhuǎn)】如何同步視頻2010-07-1610:18轉(zhuǎn)載自fandy586 最終編輯fandy586 如何同步視頻PTS 和 DTS幸運(yùn)的是,音頻和視頻流都有一些關(guān)于以多快速度和什么時(shí)間來播放它們的信息在里面。音頻流有采樣,視頻流有每秒的幀率。然而,如果我們只是簡(jiǎn)單的通過數(shù)幀和乘以幀率的方式來同步視頻,那么就很有可能會(huì)失去同步。于是作為一種補(bǔ)充,在流中的包有種叫做 DTS (解碼時(shí)間戳)和PTS (顯示時(shí)間戳)的機(jī)制。為了這兩個(gè)參數(shù),你需要了解電影存放的方式。像MPEG等格式,使用被叫做 B幀(B表示雙向bidrectional )的方式。另外兩種幀被叫做I幀和P幀(I表示關(guān)鍵幀,P表示預(yù)測(cè)幀)。

2、I幀包含了某個(gè)特定的完整圖像。P幀依賴于前面的I幀和P幀并且使用比較或者差分的方式來編碼。B幀與P幀有點(diǎn)類似,但是它是依賴于前面和后面的幀的信息的。這也就解釋了為什么我們可能在調(diào)用 avcodec_decode_video 以后會(huì)得不到一幀圖像。所以對(duì)于一個(gè)電影,幀是這樣來顯示的:I BBP?,F(xiàn)在我們需要在顯示 B幀之前知道P幀中的信息。因此,幀可能會(huì)按照這樣的方式來存儲(chǔ):IPBB。這就是為什么我們會(huì)有一個(gè)解碼時(shí)間戳和一個(gè)顯示時(shí)間戳的原因。解碼時(shí)間戳告訴我們什么時(shí)候需要解碼,顯示時(shí)間戳告訴我 們什么時(shí)候需要顯示。所以,在這種情況下,我們的流可以是這樣的:PTS: 1 4 2 3DTS: 1 2

3、 3 4Stream: I P B B通常PTS和DTS只有在流中有 B幀的時(shí)候會(huì)不同。當(dāng)我們調(diào)用av_read_frame()得到一個(gè)包的時(shí)候,PTS和DTS的信息也會(huì)保存在包中。但是我們真正想要的PTS是我們剛剛解碼出來的原始幀的PTS,這樣我們才能知道什么時(shí)候來顯示它。然而,我們從avcodec_decode_video() 函數(shù)中得到的幀只是一個(gè) AVFrame,其中并沒有包含有用的 PTS值(注意:AVFrame并沒有包含時(shí)間戳信息,但當(dāng)我們等到幀的時(shí)候并不是我們想要的樣子)。然而,ffmpeg重 新排序包以便于被 avcodec_decode_video() 函數(shù)處理的包的 DTS

4、 可以總是與其返回的 PTS 相同。但是,另外的一個(gè)警告是:我們也并不 是總能得到這個(gè)信息。不用擔(dān)心,因?yàn)橛辛硗庖环N辦法可以找到幀的 PTS ,我們可以讓程序自己來重新排序包。我們保存一幀的第一個(gè)包的 PTS :這將作為整個(gè) 這一幀的 PTS 。我們可以通過函數(shù) avcodec_decode_video() 來計(jì)算出哪個(gè)包是一幀的第一個(gè)包。怎樣實(shí)現(xiàn)呢?任何時(shí)候當(dāng)一個(gè)包開始一 幀的時(shí)候, avcodec_decode_video() 將調(diào)用一個(gè)函數(shù)來為一幀申請(qǐng)一個(gè)緩沖。當(dāng)然, ffmpeg 允許我們重新定義那個(gè)分配內(nèi)存的函數(shù)。所以 我們制作了一個(gè)新的函數(shù)來保存一個(gè)包的時(shí)間戳。當(dāng)然,盡管那樣,我們

5、可能還是得不到一個(gè)正確的時(shí)間戳。我們將在后面處理這個(gè)問題。同步現(xiàn)在,知道了什么時(shí)候來顯示一個(gè)視頻幀真好,但是我們?cè)鯓觼韺?shí)際操作呢?這里有個(gè)主意:當(dāng)我們顯示了一幀以后,我們計(jì)算出下一幀 顯示的時(shí)間。然后我們簡(jiǎn)單的設(shè)置一個(gè)新的定時(shí)器來。你可能會(huì)想,我們檢查下一幀的 PTS 值而不是系統(tǒng)時(shí)鐘來看超時(shí)是否會(huì)到。這種方 式可以工作,但是有兩種情況要處理。首先,要知道下一個(gè) PTS 是什么?,F(xiàn)在我們能添加視頻速率到我們的 PTS 中太對(duì)了!然而,有些電影需要幀重復(fù)。這意味著我們重 復(fù)播放當(dāng)前的幀。這將導(dǎo)致程序顯示下一幀太快了。所以我們需要計(jì)算它們。第二,正如程序現(xiàn)在這樣,視頻和音頻播放很歡快,一點(diǎn)也不受同

6、步的影響。如果一切都工作得很好的話,我們不必?fù)?dān)心。但是,你的電 腦并不是最好的,很多視頻文件也不是完好的。所以,我們有三種選擇:同步音頻到視頻,同步視頻到音頻,或者都同步到外部時(shí)鐘(例 如你的電腦時(shí)鐘)。從現(xiàn)在開始,我們將同步視頻到音頻。寫代碼:獲得幀的時(shí)間戳現(xiàn)在讓我們到代碼中來做這些事情。我們將需要為我們的大結(jié)構(gòu)體添加一些成員,但是我們會(huì)根據(jù)需要來做。首先,讓我們看一下視頻線 程。記住,在這里我們得到了解碼線程輸出到隊(duì)列中的包。這里我們需要的是從 avcodec_decode_video 函數(shù)中得到幀的時(shí)間戳。我們討 論的第一種方式是從上次處理的包中得到 DTS ,這是很容易的:double

7、 pts;for(;) if(packet_queue_get(&is->videoq, packet, 1) < 0) / means we quit getting packetsbreak;pts = 0;/ Decode video framelerd = avcodec_decode_video(is->video_st->codec,pFrame, &frameFinished,packet->data, packet->size);if(packet->dts != AV_NOPTS_VALUE) pts = packet

8、->dts; else pts = 0;pts *= av_q2d(is->video_st->time_base);如果我們得不到 PTS就把它設(shè)置為0o好,那是很容易的。但是我們所說的如果包的 DTS 不能幫到我們,我們需要使用這一幀的第一個(gè)包的 自己的申請(qǐng)幀程序來實(shí)現(xiàn)。下面的是函數(shù)的格式:PTS 。我們通過讓 ffmpeg 使用我們intget_buffer(structAVCodecContext *c, AVFrame *pic);void release_buffer(structAVCodecContext *c, AVFrame *pic);申請(qǐng)函數(shù)沒有告訴我

9、們關(guān)于包的任何事情, 所以我們要自己每次在得到一個(gè)包的時(shí)候把 PTS 保存到一個(gè)全局變量中去。 然后,我們把值保存到 AVFrame 結(jié)構(gòu)體難理解的變量中去。所以一開始,這就是我們的函數(shù):我們自己以讀到它。uint64_t global_video_pkt_pts= AV_NOPTS_VALUE;intour_get_buffer(structAVCodecContext *c, AVFrame *pic) int ret = avcodec_default_get_buffer(c, pic);uint64_t *pts = av_malloc(sizeof(uint64_t);*pts =

10、 global_video_pkt_pts;pic->opaque = pts;return ret;void our_release_buffer(structAVCodecContext *c, AVFrame *pic) if(pic) av_freep(&pic->opaque);avcodec_default_release_buffer(c, pic);函數(shù) avcodec_default_get_buffer 和 avcodec_default_release_buffer 是 ffmpeg 中默認(rèn)的申請(qǐng)緩沖的函數(shù)。 函數(shù) av_freep 是一個(gè)內(nèi)存管理 函

11、數(shù),它不但把內(nèi)存釋放而且把指針設(shè)置為NULL ?,F(xiàn)在到了我們流打開的函數(shù)( stream_component_open ),我們添加這幾行來告訴 ffmpeg 如何去做:codecCtx->get_buffer = our_get_buffer;codecCtx->release_buffer = our_release_buffer;現(xiàn)在我們必需添加代碼來保存 PTS 到全局變量中,然后在需要的時(shí)候來使用它。我們的代碼現(xiàn)在看起來應(yīng)該是這樣子:for(;) if(packet_queue_get(&is->videoq, packet, 1) < 0) / me

12、ans we quit getting packetsbreak;pts = 0;/ Save global pts to be stored in pFrame in first callglobal_video_pkt_pts = packet->pts;/ Decode video framelen1 = avcodec_decode_video(is->video_st->codec, pFrame, &frameFinished, packet->data, packet->size);if(packet->dts = AV_NOPTS_V

13、ALUE&&pFrame->opaque && *(uint64_t*)pFrame->opaque != AV_NOPTS_VALUE) pts = *(uint64_t *)pFrame->opaque; else if(packet->dts != AV_NOPTS_VALUE) pts = packet->dts; else pts = 0;pts *= av_q2d(is->video_st->time_base);技術(shù)提示:你可能已經(jīng)注意到我們使用 int64 來表示 PTS 。這是因?yàn)?PTS 是以整型來保

14、存的。這個(gè)值是一個(gè)時(shí)間戳相當(dāng)于時(shí)間的度量,用 來以流的 time_base 為單位進(jìn)行時(shí)間度量。例如,如果一個(gè)流是 24 幀每秒,值為 42 的 PTS 表示這一幀應(yīng)該排在第 42 個(gè)幀的位置如果我 們每秒有 24 幀(這里并不完全正確)。我們可以通過除以幀率來把這個(gè)值轉(zhuǎn)化為秒。 流中的 time_base 值表示 1/framerate (對(duì)于固定幀率來說) ,所以得到了以秒為單位的 PTS , 我們需要乘以 time_base 。寫代碼:使用 PTS 來同步synchronize_video ,它可以更新同步的 PTS ?,F(xiàn)在我們得到了 PTS 。我們要注意前面討論到的兩個(gè)同步問題。我們將

15、定義一個(gè)函數(shù)叫做我們可以使用內(nèi)部的反映當(dāng)前視這個(gè)函數(shù)也能最終處理我們得不到 PTS 的情況。 同時(shí)我們要知道下一幀的時(shí)間以便于正確設(shè)置刷新速率。 頻已經(jīng)播放時(shí)間的時(shí)鐘 video_clock 來完成這個(gè)功能。我們把這些值添加到大結(jié)構(gòu)體中。typedefstructVideoState double video_clock; /下面的是函數(shù) synchronize_video ,它可以很好的自我注釋:double synchronize_video(VideoState *is, AVFrame *src_frame, double pts) double frame_delay;if(pts

16、!= 0) is->video_clock = pts; else pts = is->video_clock;frame_delay = av_q2d(is->video_st->codec->time_base);frame_delay += src_frame->repeat_pict * (frame_delay * 0.5); is->video_clock += frame_delay;return pts;你也會(huì)注意到我們也計(jì)算了重復(fù)的幀。現(xiàn)在讓我們得到正確的 PTS 并且使用 queue_picture 來隊(duì)列化幀,添加一個(gè)新的時(shí)間戳參

17、數(shù) pts :/ Did we get a video frame?if(frameFinished) pts = synchronize_video(is, pFrame, pts);if(queue_picture(is, pFrame, pts) < 0) break;對(duì)于 queue_picture 來說唯一改變的事情就是我們把時(shí)間戳值 pts 保存到 VideoPicture 結(jié)構(gòu)體中,我們必需添加一個(gè)時(shí)間戳變量到結(jié)構(gòu)體 中并且添加一行代碼:typedefstructVideoPicture double pts;intqueue_picture(VideoState *is,

18、 AVFrame *pFrame, double pts) . stuff .if(vp->bmp) . convert picture .vp->pts = pts;. alert queue .現(xiàn)在我們的圖像隊(duì)列中的所有圖像都有了正確的時(shí)間戳值,所以讓我們看一下視頻刷新函數(shù)。你會(huì)記得上次我們用 80ms 的刷新時(shí)間來欺 騙它。那么,現(xiàn)在我們將會(huì)算出實(shí)際的值。我們的策略是通過簡(jiǎn)單計(jì)算前一幀和現(xiàn)在這一幀的時(shí)間戳來預(yù)測(cè)出下一個(gè)時(shí)間戳的時(shí)間。同時(shí),我們需要同步視頻到音頻。我們將設(shè)置一個(gè)音頻時(shí)間 audio clock ;一個(gè)內(nèi)部值記錄了我們正在播放的音頻的位置。就像從任意的 mp3 播

19、放器中讀出來的數(shù)字一樣。既然我們把視 頻同步到音頻,視頻線程使用這個(gè)值來算出是否太快還是太慢。我們將在后面來實(shí)現(xiàn)這些代碼; 現(xiàn)在我們假設(shè)我們已經(jīng)有一個(gè)可以給我們音頻時(shí)間的函數(shù) get_audio_clock 。一旦我們有了這個(gè)值,我們?cè)?音頻和視頻失去同步的時(shí)候應(yīng)該做些什么呢?簡(jiǎn)單而有點(diǎn)笨的辦法是試著用跳過正確幀或者其它的方式來解決。作為一種替代的手段,我 們會(huì)調(diào)整下次刷新的值;如果時(shí)間戳太落后于音頻時(shí)間,我們加倍計(jì)算延遲。如果時(shí)間戳太領(lǐng)先于音頻時(shí)間,我們將盡可能快的刷新。既 然我們有了調(diào)整過的時(shí)間和延遲,我們將把它和我們通過 frame_timer 計(jì)算出來的時(shí)間進(jìn)行比較。這個(gè)幀時(shí)間 fra

20、me_timer 將會(huì)統(tǒng)計(jì)出電 影播放中所有的延時(shí)。換句話說,這個(gè) frame_timer 就是指我們什么時(shí)候來顯示下一幀。我們簡(jiǎn)單的添加新的幀定時(shí)器延時(shí),把它和電腦 的系統(tǒng)時(shí)間進(jìn)行比較,然后使用那個(gè)值來調(diào)度下一次刷新。這可能有點(diǎn)難以理解,所以請(qǐng)認(rèn)真研究代碼:void video_refresh_timer(void *userdata) VideoState *is = (VideoState *)userdata;VideoPicture *vp;double actual_delay, delay, sync_threshold, reLclock, diff;if(is->vid

21、eo_st) if(is->pictq_size = 0) schedule_refresh(is, 1); else vp = &is->pictqis->pictq_ri ndex;delay = vp->pts - is->frame_last_pts;if(delay <= 0 | delay >= 1.0) delay = is->frame_last_delay;is->frame_last_delay = delay;is->frame_last_pts = vp->pts;reLclock = get_a

22、udio_clock(is);diff = vp->pts - reLclock;sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD;if(fabs(diff) < AV_NOSYNC_THRESHOLD) if(diff <= -sync_threshold) delay = 0; else if(diff >= sync_threshold) delay = 2 * delay;is->frame_timer += delay;actual_delay = is

23、->frame_timer - (av_gettime() /1000000.0);if(actual_delay< 0.010) actual_delay = 0.010;schedule_refresh(is, (int)(actual_delay * 1000 + 0.5);video_display(is);if(+is->pictq_rindex = VIDEO_PICTURE_QUEUE_SIZE) is->pictq_ri ndex = 0;SDL_LockMutex(is->pictq_mutex);is->pictq_size-;SDL_C

24、on dSig nal(is->pictq_c ond);SDL_ Un lockMutex(is->pictq_mutex); else schedule_refresh(is, 100);我們?cè)谶@里做了很多檢查:首先,我們保證現(xiàn)在的時(shí)間戳和上一個(gè)時(shí)間戳之間的處以 上次的延遲。接著,我們有一個(gè)同步閾值,因?yàn)樵谕降臅r(shí)候事情并不總是那么完美的。在 值不會(huì)比時(shí)間戳之間的間隔短。最后,我們把最小的刷新值設(shè)置為10毫秒。(這句不知道應(yīng)該放在哪里)事實(shí)上這里我們應(yīng)該跳過這一幀,但是我們不想為此而煩惱。我們給大結(jié)構(gòu)體添加了很多的變量,所以不要忘記檢查一下代碼。同時(shí)也不要忘記在函數(shù) frame

25、_timer禾廿前面的幀延遲 frame delay :is->frame_timer = (double)av_gettime() /1000000.0;is->frame_last_delay = 40e-3;同步:聲音時(shí)鐘delay是有意義的。如果不是的話,我們就猜測(cè)著用ffplay中使用0.01作為它的值。我們也保證閾streame_comp onen t_ope n中初始化幀時(shí)間現(xiàn)在讓我們看一下怎樣來得到聲音時(shí)鐘。我們可以在聲音解碼函數(shù) audio_decode_frame 中更新時(shí)鐘時(shí)間。現(xiàn)在,請(qǐng)記住我們并不是每次 調(diào)用這個(gè)函數(shù)的時(shí)候都在處理新的包,所以有我們要在兩個(gè)地

26、方更新時(shí)鐘。第一個(gè)地方是我們得到新的包的時(shí)候:我們簡(jiǎn)單的設(shè)置聲音時(shí) 鐘為這個(gè)包的時(shí)間戳。然后,如果一個(gè)包里有許多幀,我們通過樣本數(shù)和采樣率來計(jì)算,所以當(dāng)我們得到包的時(shí)候:if(pkt->pts != AV_NOPTS_VALUE) is->audio_clock = av_q2d(is->audio_st->time_base)*pkt->pts;然后當(dāng)我們處理這個(gè)包的時(shí)候:pts = is->audio_clock;*pts_ptr = pts;n = 2 * is->audio_st->codec->channels;is->au

27、dio_clock += (double)data_size /(double)(n * is->audio_st->codec->sample_rate);一點(diǎn)細(xì)節(jié):臨時(shí)函數(shù)被改成包含 pts_ptr ,所以要保證你已經(jīng)改了那些。這時(shí)的 pts_ptr 是一個(gè)用來通知 audio_callback 函數(shù)當(dāng)前聲音包的 時(shí)間戳的指針。這將在下次用來同步聲音和視頻?,F(xiàn)在我們可以最后來實(shí)現(xiàn)我們的 get_audio_clock 函數(shù)。它并不像得到 is->audio_clock 值那樣簡(jiǎn)單。注意我們會(huì)在每次處理它的時(shí)候設(shè)置 聲音時(shí)間戳,但是如果你看了 audio_callba

28、ck 函數(shù),它花費(fèi)了時(shí)間來把數(shù)據(jù)從聲音包中移到我們的輸出緩沖區(qū)中。這意味著我們聲音時(shí)鐘 中記錄的時(shí)間比實(shí)際的要早太多。所以我們必須要檢查一下我們還有多少?zèng)]有寫入。下面是完整的代碼:double get_audio_clock(VideoState *is) double pts;inthw_buLsize, bytes_per_sec, n;pts = is->audio_clock;hw_buLsize = is->audio_bu1size - is->audio_buMndex;bytes_per_sec = 0;n = is->audio_st->code

29、c->channels * 2;if(is->audio_st) bytes_per_sec = is->audio_st->codec->sample_rate * n;if(bytes_per_sec) pts -= (double)hw_buLsize / bytes_per_sec;return pts;你應(yīng)該知道為什么這個(gè)函數(shù)可以正常工作了;) 這就是了!讓我們編譯它:gcc -o tutorial05 tutorial05.c -lavutil -lavformat -lavcodec -Iz -lm'sdl-c onfig -cflags -

30、libs'最后,你可以使用我們自己的電影播放器來看電影了。下次我們將看一下聲音同步,然后接下來的指導(dǎo)我們會(huì)討論查詢。同步音頻現(xiàn)在我們已經(jīng)有了一個(gè)比較像樣的播放器。所以讓我們看一下還有哪些零碎的東西沒處理。上次,我們掩飾了一點(diǎn)同步問題,也就是同步 音頻到視頻而不是其它的同步方式。我們將采用和視頻一樣的方式:做一個(gè)內(nèi)部視頻時(shí)鐘來記錄視頻線程播放了多久,然后同步音頻到上 面去。后面我們也來看一下如何推而廣之把音頻和視頻都同步到外部時(shí)鐘。生成一個(gè)視頻時(shí)鐘現(xiàn)在我們要生成一個(gè)類似于上次我們的聲音時(shí)鐘的視頻時(shí)鐘:一個(gè)給出當(dāng)前視頻播放時(shí)間的內(nèi)部值。開始,你可能會(huì)想這和使用上一幀的 時(shí)間戳來更新定時(shí)器一

31、樣簡(jiǎn)單。但是,不要忘了視頻幀之間的時(shí)間間隔是很長(zhǎng)的,以毫秒為計(jì)量的。解決辦法是跟蹤另外一個(gè)值:我們?cè)?設(shè)置上一幀時(shí)間戳的時(shí)候的時(shí)間值。于是當(dāng)前視頻時(shí)間值就是PTS_of_last_frame + (current_time -time_elapsed_since_PTS_value_was_set) 。這種解決方式與我們?cè)诤瘮?shù) get_audio_clock 中的方式很類似。時(shí)鐘更所在在我們的大結(jié)構(gòu)體中,我們將放上一個(gè)雙精度浮點(diǎn)變量video_current_pts 和一個(gè) 64 位寬整型變量 video_current_pts_time新將被放在 video_refresh_timer 函數(shù)

32、中。void video_refresh_timer(void *userdata) if(is->video_st) if(is->pictq_size = 0) schedule_refresh(is, 1); else vp = &is->pictqis->pictq_rindex;is->video_current_pts = vp->pts;is->video_current_pts_time = av_gettime();不要忘記在 stream_component_open 函數(shù)中初始化它:is->video_current

33、_pts_time = av_gettime();現(xiàn)在我們需要一種得到信息的方式:double get_video_clock(VideoState *is) double delta;delta = (av_gettime() - is->video_current_pts_time) / 1000000.0;return is->video_current_pts + delta;提取時(shí)鐘ffplay 一樣有一變量然后決定調(diào)但是為什么要強(qiáng)制使用視頻時(shí)鐘呢?我們更改視頻同步代碼以致于音頻和視頻不會(huì)試著去相互同步。想像一下我們讓它像 個(gè)命令行參數(shù)。所以讓我們抽象一樣這件事情:我們將

34、做一個(gè)新的封裝函數(shù) get_master_clock ,用來檢測(cè) av_sync_type 用 get_audio_clock 還是 get_video_clock 或者其它的想使用的獲得時(shí)鐘的函數(shù)。我們甚至可以使用電腦時(shí)鐘,這個(gè)函數(shù)我們叫做 get_external_clock :enum AV_SYNC_AUDIO_MASTER,AV_SYNC_VIDEO_MASTER,AV_SYNC_EXTERNAL_MASTER, ;#define DEFAULT_AV_SYNC_TYPE AV_SYNC_VIDEO_MASTER double get_master_clock(VideoState

35、*is) if(is->av_sync_type = AV_SYNC_VIDEO_MASTER) return get_video_clock(is); else if(is->av_sync_type = AV_SYNC_AUDIO_MASTER) return get_audio_clock(is); else return get_external_clock(is);main() is->av_sync_type = DEFAULT_AV_SYNC_TYPE;同步音頻現(xiàn)在是最難的部分:同步音頻到視頻時(shí)鐘。我們的策略是測(cè)量聲音的位置,把它與視頻時(shí)間比較然后算出我們需要修

36、正多少的樣本數(shù),也 就是說:我們是否需要通過丟棄樣本的方式來加速播放還是需要通過插值樣本的方式來放慢播放?我們將在每次處理聲音樣本的時(shí)候運(yùn)行一個(gè) synchronize_audio 的函數(shù)來正確的收縮或者擴(kuò)展聲音樣本。然而,我們不想在每次發(fā)現(xiàn)有偏 差的時(shí)候都進(jìn)行同步,因?yàn)檫@樣會(huì)使同步音頻多于視頻包。所以我們?yōu)楹瘮?shù) synchronize_audio 設(shè)置一個(gè)最小連續(xù)值來限定需要同步的時(shí) 刻,這樣我們就不會(huì)總是在調(diào)整了。當(dāng)然,就像上次那樣, “失去同步 ”意味著聲音時(shí)鐘和視頻時(shí)鐘的差異大于我們的閾值。所以我們將使用一個(gè)分?jǐn)?shù)系數(shù),叫c ,所以現(xiàn)在可以說我們得到了 N 個(gè)失去同步的聲音樣本。失去同步

37、的數(shù)量可能會(huì)有很多變化,所以我們要計(jì)算一下失去同步的長(zhǎng)度的均值。例如,第一次調(diào)用的時(shí)候,顯示出來我們失去同步的長(zhǎng)度為40ms ,下次變?yōu)?50ms 等等。但是我們不會(huì)使用一個(gè)簡(jiǎn)單的均值,因?yàn)榫嚯x現(xiàn)在最近的值比靠前的值要重要的多。所以我們將使用一個(gè)分?jǐn)?shù)系統(tǒng),叫c ,然后用這樣的公式來計(jì)算差異: diff_sum = new_diff + diff_sum*c 。當(dāng)我們準(zhǔn)備好去找平均差異的時(shí)候,我們用簡(jiǎn)單的計(jì)算方式:avg_diff = diff_sum * (1-c) 。注意:為什么會(huì)在這里?這個(gè)公式看來很神奇!嗯,它基本上是一個(gè)使用等比級(jí)數(shù)的加權(quán)平均值。我不知道這是否有名字(我甚至查過維 基百

38、科!),但是如果想要更多的信息,這里是一個(gè)解釋 或者在 里。下面是我們的函數(shù):intsynchronize_audio(VideoState *is, short *samples,intsamples_size, double pts) int n;double ref_clock;n = 2 * is->audio_st->codec->channels;if(is->av_sync_type != AV_SYNC_AUDIO_MASTER) double diff, avg_diff;intwanted_size, min_size, max_size, nb_s

39、amples;reLclock = get_master_clock(is);diff = get_audio_clock(is) - relclock;if(diff < AV_NOSYNC_THRESHOLD) / accumulate the diffsis->audio diff cum = diff + is->audio diff avg coef* is->audio_diffcum;if(is->audio_difLavg_count< AUDIO_DIFF_AVG_NB) is->audio_difLavg_co un t+; els

40、e avg_diff = is->audio_dif1cum * (1.0 - is->audio_diffavg_coef); else is->audio_difLavg_co unt = 0;is->audio_dif1cum = 0;return samples_size;現(xiàn)在我們已經(jīng)做得很好;我們已經(jīng)近似的知道如何用視頻或者其它的時(shí)鐘來調(diào)整音頻了 C并且如何在aShrinking/expanding buffer code咅E分來寫上代碼:if(fabs(avg_diff) >= is->audio_diffthreshold) wan ted_s

41、ize = samples_size +(int)(diff * is->audio_st->codec->sample_rate) * n);min_size = samples_size * (100 - SAMPLE_CORRECTION_PERCENT_MAX)/100);max_size = samples_size * (100 + SAMPLE_CORRECTION_PERCENT_MAX)/100);if(wanted_size<min_size) wan ted_size = min_size;所以讓我們來計(jì)算一下要在添加和砍掉多少樣本, else i

42、f (wanted_size>max_size) wanted_size = max_size;記住 audio_length * (sample_rate * # of channels * 2)就是 audio_length 秒時(shí)間的聲音的樣本數(shù)。所以,我們想要的樣本數(shù)就是我們根據(jù)聲音偏移添加或者減少后的聲音樣本數(shù)。我們也可以設(shè)置一個(gè)范圍來限定我們一次進(jìn)行修正的長(zhǎng)度,因?yàn)槿绻覀兏淖兊奶?,用戶?huì)聽 到刺耳的聲音。修正樣本數(shù)現(xiàn)在我們要真正的修正一下聲音。你可能會(huì)注意到我們的同步函數(shù) synchronize_audio 返回了一個(gè)樣本數(shù),這可以告訴我們有多少個(gè)字節(jié) 被送到流中。所以我們

43、只要調(diào)整樣本數(shù)為 wanted_size 就可以了。這會(huì)讓樣本更小一些。但是如果我們想讓它變大,我們不能只是讓樣本 大小變大,因?yàn)樵诰彌_區(qū)中沒有多余的數(shù)據(jù)!所以我們必需添加上去。但是我們?cè)鯓觼硖砑幽兀孔畋康霓k法就是試著來推算聲音,所以讓 我們用已有的數(shù)據(jù)在緩沖的末尾添加上最后的樣本。if(wanted_size<samples_size) samples_size = wanted_size; else if(wanted_size>samples_size) uint8_t *samples_end, *q;intnb;nb = (samples_size - wanted_si

44、ze);samples_end = (uint8_t *)samples + samples_size - n;q = samples_end + n;while(nb> 0) memcpy(q, samples_end, n);q += n;nb -= n;samples_size = wan ted_size;現(xiàn)在我們通過這個(gè)函數(shù)返回的是樣本數(shù)。我們現(xiàn)在要做的是使用它:void audio_callback(void *userdata, Uint8 *stream, intlen) VideoState *is = (VideoState *)userdata;int lerd,

45、audio_size;double pts;while(len> 0) if(is->audio_buMndex>= is->audio_bu1size) audio_size = audio_decode_frame(is, is->audio_buf, sizeof(is->audio_buf), &pts);if(audio_size< 0) is->audio_buf_size = 1024;memset(is->audio_buf, 0, is->audio_buf_size); else audio_size =

46、synchronize_audio(is, (int16_t *)is->audio_buf,audio_size, pts);is->audio_buf_size = audio_size;我們要做的是把函數(shù) synchronize_audio 插入進(jìn)去。(同時(shí),保證在初始化上面變量的時(shí)候檢查一下代碼,這些我沒有贅述)。 結(jié)束之前的最后一件事情:我們需要添加一個(gè) if 語(yǔ)句來保證我們不會(huì)在視頻為主時(shí)鐘的時(shí)候也來同步視頻。if(is->av_sync_type != AV_SYNC_VIDEO_MASTER) ref_clock = get_master_clock(is);

47、diff = vp->pts - ref_clock;sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay :AV_SYNC_THRESHOLD;if(fabs(diff) < AV_NOSYNC_THRESHOLD) if(diff <= -sync_threshold) delay = 0; else if(diff >= sync_threshold) delay = 2 * delay;添加后就可以了。要保證整個(gè)程序中我沒有贅述的變量都被初始化過了。然后編譯它:gcc -o tutorial06 tuto

48、rial06.c -lavutil -lavformat -lavcodec -Iz -lm'sdl-c onfig -cflags -libs'然后你就可以運(yùn)行它了??爝M(jìn)快退處理快進(jìn)快退命令現(xiàn)在我們來為我們的播放器加入一些快進(jìn)和快退的功能,因?yàn)槿绻悴荒苋炙阉饕徊侩娪笆呛茏屓擞憛挼?。同時(shí),這將告訴你 av_seek_frame 函數(shù)是多么容易使用。10 frame我們將在電影播放中使用左方向鍵和右方向鍵來表示向后和向前一小段,使用向上和向下鍵來表示向前和向后一大段。這里一小段是 秒,一大段是 60 秒。所以我們需要設(shè)置我們的主循環(huán)來捕捉鍵盤事件。然而當(dāng)我們捕捉到鍵盤事件后我

49、們不能直接調(diào)用av_seek函數(shù)。我們要主要的解碼線程 decode_thread 的循環(huán)中做這些。所以,我們要添加一些變量到大結(jié)構(gòu)體中,用來包含新的跳轉(zhuǎn)位置和一些 跳轉(zhuǎn)標(biāo)志:intseek_req;in tseek_flags;in t64_t seek_pos;現(xiàn)在讓我們?cè)谥餮h(huán)中捕捉按鍵:for(;)double incr, pos;SDL_WaitEvent(&event);switch (eve nt.type) case SDL_KEYDOWN:switch (event, key. keysym .sym) case SDLK_LEFT:incr = -10.0;goto

50、do_seek;case SDLK_RIGHT:incr = 10.0;gotodo_seek;case SDLK UP:incr = 60.0;gotodo_seek;case SDLK_DOWN:incr = -60.0;gotodo_seek;do_seek:if(global_video_state) pos = get_master_clock(global_video_state);pos += incr;stream_seek(global_video_state,(int64_t)(pos * AV_TIME_BASE), incr);break;default:break;b

51、reak;為了檢測(cè)按鍵,我們先查了一下是否有 SDL_KEYDOWN 事件。然后我們使用 event.key.keysym.sym 來判斷哪個(gè)按鍵被按下。一旦我們 知道了如何來跳轉(zhuǎn),我們就來計(jì)算新的時(shí)間,方法為把增加的時(shí)間值加到從函數(shù) get_master_clock 中得到的時(shí)間值上。然后我們調(diào)用 stream_seek 函數(shù)來設(shè)置 seek_pos 等變量。我們把新的時(shí)間轉(zhuǎn)換成為 avcodec 中的內(nèi)部時(shí)間戳單位。在流中調(diào)用那個(gè)時(shí)間戳將使用幀而 不是用秒來計(jì)算, 公式為 seconds = frames * time_base(fps) 。默認(rèn)的 avcodec 值為 1,000,000

52、fps (所以 2 秒的內(nèi)部時(shí)間戳為 2,000,000 )。 在后面我們來看一下為什么要把這個(gè)值進(jìn)行一下轉(zhuǎn)換。這就是我們的 stream_seek 函數(shù)。請(qǐng)注意我們?cè)O(shè)置了一個(gè)標(biāo)志為后退服務(wù):void stream_seek(VideoState *is, int64_t pos, intrel) if(!is->seek_req) is->seek_pos = pos;is->seek_flags = rel< 0 ? AVSEEK_FLAG_BACKWARD : 0;is->seek_req = 1;現(xiàn)在讓我們看一下如果在 decode_thread 中實(shí)現(xiàn)跳

53、轉(zhuǎn)。你會(huì)注意到我們已經(jīng)在源文件中標(biāo)記了一個(gè)叫做“ seek stuff goes here 的”部分?,F(xiàn)在我們將把代碼寫在這里。跳轉(zhuǎn)是圍繞著 av_seek_frame 函數(shù)的。這個(gè)函數(shù)用到了一個(gè)格式上下文,一個(gè)流,一個(gè)時(shí)間戳和一組標(biāo)記來作為它的參數(shù)。這個(gè)函數(shù)將會(huì) 跳轉(zhuǎn)到你所給的時(shí)間戳的位置。時(shí)間戳的單位是你傳遞給函數(shù)的流的時(shí)基 time_base 。然而,你并不是必需要傳給它一個(gè)流(流可以用 -1 來代替)。如果你這樣做了, 時(shí)基 time_base 將會(huì)是 avcodec 中的內(nèi)部時(shí)間戳單位, 或者是 1000000fps 。這就是為什么我們?cè)谠O(shè)置 seek_pos 的時(shí)候會(huì)把位置乘以 A

54、V_TIME_BASER 的原因。但是,如果給 av_seek_frame 函數(shù)的 stream 參數(shù)傳遞傳 -1, 你有時(shí)會(huì)在播放某些文件的時(shí)候遇到問題(比較少見),所以我們會(huì)取文件中 的第一個(gè)流并且把它傳遞到 av_seek_frame 函數(shù)。不要忘記我們也要把時(shí)間戳 timestamp 的單位進(jìn)行轉(zhuǎn)化。if(is->seek_req) intstream_index= -1;int64_t seek_target = is->seek_pos;if (is->videoStream>= 0) stream_index = is->videoStream;el

55、se if(is->audioStream>= 0) stream_index = is->audioStream; if(stream_index>=0)seek_target= av_rescale_q(seek_target, AV_TIME_BASE_Q, pFormatCtx->streamsstream_index->time_base); if(av_seek_frame(is->pFormatCtx, stream_index, seek_target, is->seek_flags) < 0) fprintf(stderr

56、, "%s: error while seekingn", is->pFormatCtx->filename);它基本的動(dòng)作是計(jì)算 a*b/c ,但是這個(gè)函數(shù)還是必 else 這里 av_rescale_q(a,b,c) 是用來把時(shí)間戳從一個(gè)時(shí)基調(diào)整到另外一個(gè)時(shí)基時(shí)候用的函數(shù)。需的,因?yàn)橹苯佑?jì)算會(huì)有溢出的情況發(fā)生。 AV_TIME_BASE_Q 是 AV_TIME_BASE 作為分母后的版本。它們是很不相同的: AV_TIME_BASE * time_in_seconds = avcodec_timestamp而 AV_TIME_BASE_Q * avcodec

57、_timestamp = time_in_seconds(注意AV_TIME_BASE_Q 實(shí)際上是一個(gè) AVRational 對(duì)象,所以你必需使用 avcodec 中特定的 q 函數(shù)來處理它)。清空我們的緩沖我們已經(jīng)正確設(shè)定了跳轉(zhuǎn)位置,但是我們還沒有結(jié)束。記住我們有一個(gè)堆放了很多包的隊(duì)列。既然我們跳到了不同的位置,我們必需把隊(duì) 列中的內(nèi)容清空否則電影是不會(huì)跳轉(zhuǎn)的。不僅如此, avcodec 也有它自己的內(nèi)部緩沖,也需要每次被清空。要實(shí)現(xiàn)這個(gè),我們需要首先寫一個(gè)函數(shù)來清空我們的包隊(duì)列。然后我們需要一種命令聲音和視頻線程來清空 avcodec 內(nèi)部緩沖的辦法。我 們可以在清空隊(duì)列后把特定的包放入到隊(duì)列中,然后當(dāng)它們檢測(cè)到特定的包的時(shí)候,它們就會(huì)把自己的內(nèi)部緩沖清空。讓我們開始寫清空函數(shù)。其實(shí)很簡(jiǎn)單的,所以我直接把代碼寫在下面:static void packet_queue_flush(PacketQueue *q) AVPacketList *pkt, *pkt1;SDL_LockMutex(q->mutex);for(pkt = q->first_pkt; pkt != NULL; pkt = pkt1) pkt1 = pkt->next;av_free_packet(&pkt->pkt);av_freep(&a

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論