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

下載本文檔

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

文檔簡介

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

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

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

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

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

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

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

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

9、包的時候把PTS保存到一個全局變量中去。我們自己以讀到它。然后,我們把值保存到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 = global_video_pkt_pts;pic->

10、;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 中默認的申請緩沖的函數(shù)。函數(shù) av_freep 是一個內(nèi)存管理函數(shù),它不但把內(nèi)存釋放而且把指針設(shè)置為NULL?,F(xiàn)在到了我們流打

11、開的函數(shù)(stream_component_open ),我們添加這幾行來告訴ffmpeg如何去做:codecCtx->get_buffer = our_get_buffer;codecCtx->release_buffer = our_release_buffer;現(xiàn)在我們必需添加代碼來保存PTS到全局變量中,然后在需要的時候來使用它。我們的代碼現(xiàn)在看起來應該是這樣子:for(;) if(packet_queue_get(&is->videoq, packet, 1) < 0) / means we quit getting packetsbreak;pts

12、= 0;/ Save global pts to be stored in pFrame in first callglobal_video_pkt_pts = packet->pts;/ Decode video frame lenl = avcodec_decode_video(is->video_st->codec, pFrame, &frameFinished, packet->data, packet->size);if(packet->dts = AV_NOPTS_VALUE&&pFrame->opaque &

13、;& *(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。這是因為PTS是以整型來保存的。這個值是一個時間戳相當于時間的度量,用來以流的time_base為單位進行時間

14、度量。例如,如果一個流是 24幀每秒,值為42的PTS表示這一幀應該排在第42個幀的位置如果我們每秒有24幀(這里并不完全正確)。我們可以通過除以幀率來把這個值轉(zhuǎn)化為秒。流中的time_base值表示1/framerate (對于固定幀率來說),所以得到了以秒為單位的PTS ,我們需要乘以time_base 。寫代碼:使用PTS來同步現(xiàn)在我們得到了 PTS。我們要注意前面討論到的兩個同步問題。我們將定義一個函數(shù)叫做synchronize_video ,它可以更新同步的 PTS。我們可以使用內(nèi)部的反映當前視這個函數(shù)也能最終處理我們得不到PTS的情況。同時我們要知道下一幀的時間以便于正確設(shè)置刷新速

15、率。頻已經(jīng)播放時間的時鐘 video_clock來完成這個功能。我們把這些值添加到大結(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 != 0) is->video_clock = pts; else pts = is->video_clock;frame_del

16、ay = 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;你也會注意到我們也計算了重復的幀?,F(xiàn)在讓我們得到正確的PTS并且使用queue_picture來隊列化幀,添加一個新的時間戳參數(shù)pts :/ Did we get a video frame?if(frameFinished) pts = synchronize_video(i

17、s, pFrame, pts);if(queue_picture(is, pFrame, pts) < 0) break;對于queue_picture 來說唯一改變的事情就是我們把時間戳值pts保存到VideoPicture結(jié)構(gòu)體中,我們必需添加一個時間戳變量到結(jié)構(gòu)體中并且添加一行代碼:typedefstructVideoPicture .double pts; intqueue_picture(VideoState *is, AVFrame *pFrame, double pts) . stuff .if(vp->bmp) (. convert picture .vp->

18、pts = pts;. alert queue .現(xiàn)在我們的圖像隊列中的所有圖像都有了正確的時間戳值,所以讓我們看一下視頻刷新函數(shù)。你會記得上次我們用80ms的刷新時間來欺騙它。那么,現(xiàn)在我們將會算出實際的值。我們的策略是通過簡單計算前一幀和現(xiàn)在這一幀的時間戳來預測出下一個時間戳的時間。同時,我們需要同步視頻到音頻。我們將設(shè)置一個音頻時間audio clock ; 一個內(nèi)部值記錄了我們正在播放的音頻的位置。就像從任意的mp3播放器中讀出來的數(shù)字一樣。既然我們把視頻同步到音頻,視頻線程使用這個值來算出是否太快還是太慢。我們將在后面來實現(xiàn)這些代碼;現(xiàn)在我們假設(shè)我們已經(jīng)有一個可以給我們音頻時間的函數(shù)

19、get_audio_clock。一旦我們有了這個值,我們在音頻和視頻失去同步的時候應該做些什么呢?簡單而有點笨的辦法是試著用跳過正確幀或者其它的方式來解決。作為一種替代的手段,我們會調(diào)整下次刷新的值;如果時間戳太落后于音頻時間,我們加倍計算延遲。如果時間戳太領(lǐng)先于音頻時間,我們將盡可能快的刷新。既然我們有了調(diào)整過的時間和延遲,我們將把它和我們通過frame_timer計算出來的時間進行比較。這個幀時間frame_timer將會統(tǒng)計出電影播放中所有的延時。換句話說,這個 frame_timer就是指我們什么時候來顯示下一幀。我們簡單的添加新的幀定時器延時,把它和電腦 的系統(tǒng)時間進行比較,然后使用

20、那個值來調(diào)度下一次刷新。這可能有點難以理解,所以請認真研究代碼:void video_refresh_timer(void *userdata) (VideoState *is = (VideoState *)userdata;VideoPicture *vp;double actual_delay, delay, sync_threshold, rejclock, diff;if(is->video_st) if(is->pictq_size = 0) schedule_refresh(is, 1); else vp = &is->pictqis->pictq

21、_rindex;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;rejclock = get_audio_clock(is);diff = vp->pts - rejclock;sync_threshold = (delay > AV_SYNC_THRESHOLD) ? del

22、ay : 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->frame_timer - (av_gettime() /1000000.0);if(actual_delay< 0.010) actual_delay = 0.010;)sc

23、hedule_refresh(is, (int)(actual_delay * 1000 + 0.5);video_display(is);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

24、);)我們在這里做了很多檢查:首先,我們保證現(xiàn)在的時間戳和上一個時間戳之間的處以 上次的延遲。接著,我們有一個同步閾值,因為在同步的時候事情并不總是那么完美的。在 值不會比時間戳之間的間隔短。最后,我們把最小的刷新值設(shè)置為10毫秒。(這句不知道應該放在哪里)事實上這里我們應該跳過這一幀,但是我們不想為此而煩惱。我們給大結(jié)構(gòu)體添加了很多的變量,所以不要忘記檢查一下代碼。同時也不要忘記在函數(shù) frame_timer 和前面的幀延遲 frame delay :is->frame_timer = (double)av_gettime() /1000000.0;is->frame_last_

25、delay = 40e-3;同步:聲音時鐘delay是有意義的。如果不是的話,我們就猜測著用ffplay中使用0.01作為它的值。我們也保證閾streame_component_open中初始化幀時間現(xiàn)在讓我們看一下怎樣來得到聲音時鐘。我們可以在聲音解碼函數(shù)audio_decode_frame中更新時鐘時間?,F(xiàn)在,請記住我們并不是每次調(diào)用這個函數(shù)的時候都在處理新的包,所以有我們要在兩個地方更新時鐘。第一個地方是我們得到新的包的時候:我們簡單的設(shè)置聲音時 鐘為這個包的時間戳。然后,如果一個包里有許多幀,我們通過樣本數(shù)和采樣率來計算,所以當我們得到包的時候:if(pkt->pts != AV

26、_NOPTS_VALUE) (is->audio_clock = av_q2d(is->audio_st->time_base)*pkt->pts;然后當我們處理這個包的時候:pts = is->audio_clock;*pts_ptr = pts;n = 2 * is->audio_st->codec->channels;is->audio_clock += (double)data_size /(double)(n * is->audio_st->codec->sample_rate);一點細節(jié):臨時函數(shù)被改成包含pt

27、s_ptr ,所以要保證你已經(jīng)改了那些。這時的 pts_ptr是一個用來通知 audio_callback函數(shù)當前聲音包的時間戳的指針。這將在下次用來同步聲音和視頻?,F(xiàn)在我們可以最后來實現(xiàn)我們的get_audio_clock函數(shù)。它并不像得到is->audio_clock值那樣簡單。注意我們會在每次處理它的時候設(shè)置聲音時間戳,但是如果你看了audio_callback函數(shù),它花費了時間來把數(shù)據(jù)從聲音包中移到我們的輸出緩沖區(qū)中。這意味著我們聲音時鐘中記錄的時間比實際的要早太多。所以我們必須要檢查一下我們還有多少沒有寫入。下面是完整的代碼:double get_audio_clock(Vid

28、eoState *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->codec->channels * 2;if(is->audio_st) bytes_per_sec = is->audio_st->codec->sample_rate * n;)if(bytes_per_

29、sec) pts -= (double)hw_bujsize / bytes_per_sec;)return pts;)你應該知道為什么這個函數(shù)可以正常工作了;)這就是了!讓我們編譯它:gcc -o tutorial05 tutorial05.c -lavutil -lavformat -lavcodec -Iz -Im'sdl-config -cflags -libs'最后,你可以使用我們自己的電影播放器來看電影了。下次我們將看一下聲音同步,然后接下來的指導我們會討論查詢。同步音頻現(xiàn)在我們已經(jīng)有了一個比較像樣的播放器。所以讓我們看一下還有哪些零碎的東西沒處理。上次,我們掩飾了

30、一點同步問題,也就是同步 音頻到視頻而不是其它的同步方式。我們將采用和視頻一樣的方式:做一個內(nèi)部視頻時鐘來記錄視頻線程播放了多久,然后同步音頻到上 面去。后面我們也來看一下如何推而廣之把音頻和視頻都同步到外部時鐘。生成一個視頻時鐘現(xiàn)在我們要生成一個類似于上次我們的聲音時鐘的視頻時鐘:一個給出當前視頻播放時間的內(nèi)部值。開始,你可能會想這和使用上一幀的 時間戳來更新定時器一樣簡單。但是,不要忘了視頻幀之間的時間間隔是很長的,以毫秒為計量的。解決辦法是跟蹤另外一個值:我們在 設(shè)置上一幀時間戳的時候的時間值。于是當前視頻時間值就是PTS_of_last_frame + (current_time -t

31、ime_elapsed_since_PTS_value_was_set)。這種解決方式與我們在函數(shù)get_audio_clock 中的方式很類似。所在在我們的大結(jié)構(gòu)體中,我們將放上一個雙精度浮點變量video_current_pts 和一個64位寬整型變量 video_current_pts_time 。時鐘更新將被放在 video_refresh_timer 函數(shù)中。void video_refresh_timer(void *userdata) if(is->video_st) if(is->pictq_size = 0) schedule_refresh(is, 1); el

32、se 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_pts_time = av_gettime();現(xiàn)在我們需要一種得到信息的方式:double get_video_clock(VideoState *is) double delta;delta = (av_gettime() -

33、 is->video_current_pts_time) / 1000000.0;return is->video_current_pts + delta;提取時鐘但是為什么要強制使用視頻時鐘呢?我們更改視頻同步代碼以致于音頻和視頻不會試著去相互同步。想像一下我們讓它像ffplay 一樣有-個命令行參數(shù)。所以讓我們抽象一樣這件事情:我們將做一個新的封裝函數(shù)get_master_clock,用來檢測av_sync_type變量然后決定調(diào)用get_audio_clock還是get_video_clock 或者其它的想使用的獲得時鐘的函數(shù)。我們甚至可以使用電腦時鐘,這個函數(shù)我們叫做get

34、_external_clockenum AV_SYNC_AUDIO_MASTER,AV_SYNC_VIDEO_MASTER,AV_SYNC_EXTERNAL_MASTER, );#define DEFAULT_AV_SYNC_TYPE AV_SYNC_VIDEO_MASTERdouble get_master_clock(VideoState *is) if(is->av_sync_type = AV_SYNC_VIDEO_MASTER) return get_video_clock(is); else if(is->av_sync_type = AV_SYNC_AUDIO_MAS

35、TER) return get_audio_clock(is); else return get_external_clock(is);)main() is->av_sync_type = D E F AU LT_AV_S YN C_T YP E:同步音頻現(xiàn)在是最難的部分:同步音頻到視頻時鐘。我們的策略是測量聲音的位置,把它與視頻時間比較然后算出我們需要修正多少的樣本數(shù),也 就是說:我們是否需要通過丟棄樣本的方式來加速播放還是需要通過插值樣本的方式來放慢播放?我們將在每次處理聲音樣本的時候運行一個synchronize_audio 的函數(shù)來正確的收縮或者擴展聲音樣本。然而,我們不想在每次

36、發(fā)現(xiàn)有偏差的時候都進行同步,因為這樣會使同步音頻多于視頻包。所以我們?yōu)楹瘮?shù)synchronize_audio 設(shè)置一個最小連續(xù)值來限定需要同步的時刻,這樣我們就不會總是在調(diào)整了。當然,就像上次那樣,失去同步”意味著聲音時鐘和視頻時鐘的差異大于我們的閾值。所以我們將使用一個分數(shù)系數(shù),叫c,所以現(xiàn)在可以說我們得到了N個失去同步的聲音樣本。失去同步的數(shù)量可能會有很多變化,所以我們要計算一下失去同步的長度的均值。例如,第一次調(diào)用的時候,顯示出來我們失去同步的長度為40ms,下次變?yōu)?0ms等等。但是我們不會使用一個簡單的均值,因為距離現(xiàn)在最近的值比靠前的值要重要的多。所以我們將使用一個分數(shù)系統(tǒng),叫c,

37、然后用這樣的公式來計算差異:diff_sum = new_diff + diff_sum*c。當我們準備好去找平均差異的時候,我們用簡單的計算方式:avg_diff = diff_sum * (1-c)。注意:為什么會在這里?這個公式看來很神奇!嗯,它基本上是一個使用等比級數(shù)的加權(quán)平均值。我不知道這是否有名字(我甚至查過維 基百科!),但是如果想要更多的信息,這里是一個解釋 或者在 里。下面是我們的函數(shù):intsynchronize_audio(VideoState *is, short *samples, intsamples_size, double pts) int n;double r

38、ef_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_samples;rejclock = get_master_clock(is);diff = get_audio_clock(is) - rejclock;if(diff < AV_NOSYNC_THRESHOLD) / accumulate the diffsi

39、s->audio diff cum = diff + is->audio diff avg coef* is->audio_diffcum;if(is->audio_difLavg_count< AUDIO_DIFF_AVG_NB) is->audio_dif1avg_count+; else avg_diff = is->audio_dif1cum * (1.0 - is->audio_diffavg_coef);) else is->audio_dif1avg_count = 0;is->audio_dif1cum = 0;)re

40、turn samples_size;)現(xiàn)在我們已經(jīng)做得很好;我們已經(jīng)近似的知道如何用視頻或者其它的時鐘來調(diào)整音頻了 C并且如何在aShrinking/expanding buffer code 部分來寫上代碼:if(fabs(avg_diff) >= is->audio_diffthreshold) wanted_size = samples_size +(int)(diff * is->audio_st->codec->sample_rate) * n);min_size = samples_size * (100 - SAMPLE_CORRECTION_PER

41、CENT_MAX)/100);max_size = samples_size * (100 + SAMPLE_CORRECTION_PERCENT_MAX)/100);if(wanted_size<min_size) wanted_size = min_size;所以讓我們來計算一下要在添加和砍掉多少樣本, else if (wanted_size>max_size) ( wanted_size = max_size;記住audio_length * (sample_rate * # of channels * 2) 就是audio_length 秒時間的聲音的樣本數(shù)。所以,我們想

42、要的樣本數(shù)就是我們根據(jù) 聲音偏移添加或者減少后的聲音樣本數(shù)。我們也可以設(shè)置一個范圍來限定我們一次進行修正的長度,因為如果我們改變的太多,用戶會聽 到刺耳的聲音。修正樣本數(shù)現(xiàn)在我們要真正的修正一下聲音。你可能會注意到我們的同步函數(shù)synchronize_audio 返回了一個樣本數(shù),這可以告訴我們有多少個字節(jié)被送到流中。所以我們只要調(diào)整樣本數(shù)為wanted_size就可以了。這會讓樣本更小一些。但是如果我們想讓它變大,我們不能只是讓樣本大小變大,因為在緩沖區(qū)中沒有多余的數(shù)據(jù)!所以我們必需添加上去。但是我們怎樣來添加呢?最笨的辦法就是試著來推算聲音,所以讓 我們用已有的數(shù)據(jù)在緩沖的末尾添加上最后的

43、樣本。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_size);samples_end = (uint8_t *)samples + samples_size - n;q = samples_end + n;while(nb> 0) memcpy(q, samples_end, n);q += n;nb -= n;)sam

44、ples_size = wanted_size;)現(xiàn)在我們通過這個函數(shù)返回的是樣本數(shù)。我們現(xiàn)在要做的是使用它:void audio_callback(void *userdata, Uint8 *stream, intlen) VideoState *is = (VideoState *)userdata;int Ien1, audio_size;double pts;while(len> 0) if(is->audio_buMndex>= is->audio_bu1size) audio_size = audio_decode_frame(is, is->aud

45、io_buf, sizeof(is->audio_buf), &pts);if(audio_size< 0) else if(diff >= sync_threshold) (is->audio_buf_size = 1024;memset(is->audio_buf, 0, is->audio_buf_size); else audio_size = synchronize_audio(is, (int16_t *)is->audio_buf,audio_size, pts);is->audio_buf_size = audio_siz

46、e;我們要做的是把函數(shù) synchronize_audio 插入進去。(同時,保證在初始化上面變量的時候檢查一下代碼,這些我沒有贅述)。結(jié)束之前的最后一件事情:我們需要添加一個if語句來保證我們不會在視頻為主時鐘的時候也來同步視頻。if(is->av_sync_type != AV_SYNC_VIDEO_MASTER) ref_clock = get_master_clock(is);diff = vp->pts - ref_clock;sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay :AV_SYNC_THRESHOL

47、D;if(fabs(diff) < AV_NOSYNC_THRESHOLD) if(diff <= -sync_threshold) delay = 0;delay = 2 * delay;添加后就可以了。要保證整個程序中我沒有贅述的變量都被初始化過了。然后編譯它:gcc -o tutorial06 tutorial06.c -lavutil -lavformat -lavcodec -lz -lm'sdl-config -cflags -libs'然后你就可以運行它了。快進快退 處理快進快退命令現(xiàn)在我們來為我們的播放器加入一些快進和快退的功能,因為如果你不能全局搜

48、索一部電影是很讓人討厭的。同時,這將告訴你 av_seek_frame 函數(shù)是多么容易使用。我們將在電影播放中使用左方向鍵和右方向鍵來表示向后和向前一小段,使用向上和向下鍵來表示向前和向后一大段。這里一小段是10秒,一大段是60秒。所以我們需要設(shè)置我們的主循環(huán)來捕捉鍵盤事件。然而當我們捕捉到鍵盤事件后我們不能直接調(diào)用av_seek_frame函數(shù)。我們要主要的解碼線程decode_thread的循環(huán)中做這些。所以,我們要添加一些變量到大結(jié)構(gòu)體中,用來包含新的跳轉(zhuǎn)位置和一些跳轉(zhuǎn)標志:intseek_req;intseek_flags;int64_t seek_pos;現(xiàn)在讓我們在主循環(huán)中捕捉按鍵

49、:for(;)double incr, pos;SDL_WaitEvent(&event);switch(event.type) case SDL_KEYDOWN:switch (event, key. keysym .sym) case SDLK_LEFT:incr = -10.0;gotodo_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_

50、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;break;為了檢測按鍵,我們先查了一下是否有SDL_KEYDOWN 事件。然后我們使用event.key.keysym.sym 來判斷哪個按鍵被按下。一旦我們知道了如何來跳轉(zhuǎn),我們就來計算新的時間,方法為把增加的時間值加到從函數(shù)get_master_clock中得到的時間值上。然后我們調(diào)用str

51、eam_seek函數(shù)來設(shè)置seek_pos等變量。我們把新的時間轉(zhuǎn)換成為avcodec中的內(nèi)部時間戳單位。在流中調(diào)用那個時間戳將使用幀而不是用秒來計算, 公式為seconds = frames * time_base(fps)。默認的avcodec值為1,000,000fps (所以2秒的內(nèi)部時間戳為 2,000,000 )。在后面我們來看一下為什么要把這個值進行一下轉(zhuǎn)換。這就是我們的stream_seek函數(shù)。請注意我們設(shè)置了一個標志為后退服務:void stream_seek(VideoState *is, int64_t pos, intrel) (if(!is->seek_req

52、) (is->seek_pos = pos;is->seek_flags = rel< 0 ? AVSEEK_FLAG_BACKWARD : 0;is->seek_req = 1;現(xiàn)在讓我們看一下如果在decode_thread 中實現(xiàn)跳轉(zhuǎn)。你會注意到我們已經(jīng)在源文件中標記了一個叫做"seek stuff goes here的部分?,F(xiàn)在我們將把代碼寫在這里。跳轉(zhuǎn)是圍繞著av_seek_frame函數(shù)的。這個函數(shù)用到了一個格式上下文,一個流,一個時間戳和一組標記來作為它的參數(shù)。這個函數(shù)將會 跳轉(zhuǎn)到你所給的時間戳的位置。時間戳的單位是你傳遞給函數(shù)的流的時基time

53、_base。然而,你并不是必需要傳給它一個流(流可以用-1來代替)。如果你這樣做了,時基time_base將會是avcodec中的內(nèi)部時間戳單位, 或者是1000000fps。這就是為什么我們在設(shè)置seek_pos的時候會把位置乘以 AV_TIME_BASER 的原因。但是,如果給av_seek_frame函數(shù)的stream參數(shù)傳遞傳-1,你有時會在播放某些文件的時候遇到問題(比較少見),所以我們會取文件中 的第一個流并且把它傳遞到av_seek_frame 函數(shù)。不要忘記我們也要把時間戳timestamp的單位進行轉(zhuǎn)化。if(is->seek_req) (intstream_index

54、= -1;int64_t seek_target = is->seek_pos;if (is->videoStream>= 0) stream_index = is->videoStream;else 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_ba

55、se);if(av_seek_frame(is->pFormatCtx, stream_index,seek_target, is->seek_flags) < 0) (fprintf(stderr, "%s: error while seekingn",is->pFormatCtx->filename); else (它基本的動作是計算a*b/c ,但是這個函數(shù)還是必這里av_rescale_q(a,b,c)是用來把時間戳從一個時基調(diào)整到另外一個時基時候用的函數(shù)。需的,因為直接計算會有溢出的情況發(fā)生。AV_TIME_BASE_Q 是AV_TI

56、ME_BASE 作為分母后的版本。它們是很不相同的:AV_TIME_BASE * time_in_seconds = avcodec_timestamp 而 AV_TIME_BASE_Q * avcodec_timestamp = time_in_seconds (注意 AV_TIME_BASE_Q 實際上是一個 AVRational對象,所以你必需使用avcodec中特定的q函數(shù)來處理它)。清空我們的緩沖我們已經(jīng)正確設(shè)定了跳轉(zhuǎn)位置,但是我們還沒有結(jié)束。記住我們有一個堆放了很多包的隊列。既然我們跳到了不同的位置,我們必需把隊 列中的內(nèi)容清空否則電影是不會跳轉(zhuǎn)的。不僅如此,avcodec也有它自

57、己的內(nèi)部緩沖,也需要每次被清空。要實現(xiàn)這個,我們需要首先寫一個函數(shù)來清空我們的包隊列。然后我們需要一種命令聲音和視頻線程來清空avcodec內(nèi)部緩沖的辦法。我們可以在清空隊列后把特定的包放入到隊列中,然后當它們檢測到特定的包的時候,它們就會把自己的內(nèi)部緩沖清空。讓我們開始寫清空函數(shù)。其實很簡單的,所以我直接把代碼寫在下面: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(&pkt); q->last_pkt = NULL;q->first_pkt = NULL;q->nb_packets = 0;q->size = 0;SDL_UnlockMutex(q->mutex);)既然隊列已經(jīng)清空了,我們放入 滑空包”。但是開始我們要定義和創(chuàng)建這個包:AVPacketflush_pkt;main() av_in it_packet(&fl ush_p k

溫馨提示

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

評論

0/150

提交評論