日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

ffplay.c学习-7-以音频同步为基准

發布時間:2024/4/11 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 ffplay.c学习-7-以音频同步为基准 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

ffplay.c學習-7-以音頻同步為基準


目錄

  • 以音頻為基準
  • ?頻主流程
  • 視頻主流程
  • delay的計算
  • 以視頻為基準
  • 視頻主流程
  • ?頻主流程
  • synchronize_audio
  • swr_set_compensation
  • 以外部時鐘為基準
  • 回顧
  • 分析

  • 1. 以音頻為基準

    1. ?頻主流程

  • ffplay默認也是采?的這種同步策略。
  • 此時?頻的時鐘設置在sdl_audio_callback:
  • audio_callback_time = av_gettime_relative();.../* Let's assume the audio driver that is used by SDL has two periods. */if (!isnan(is->audio_clock)) {set_clock_at(&is->audclk, is->audio_clock -(double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size)/ is->audio_tgt.bytes_per_sec,is->audio_clock_serial,audio_callback_time / 1000000.0);sync_clock_to_slave(&is->extclk, &is->audclk);}
  • ?頻時鐘的維護
  • 我們先來is->audio_clock是在audio_decode_frame賦值:is->audio_clock = af->pts + (double) af->frame->nb_samples / af->frame->sample_rate;從這?可以看出來,這?的時間戳是audio_buf結束位置的時間戳,?不是audio_buf起始位置的時間戳,所以當audio_buf有剩余時(剩余的?度記錄在audio_write_buf_size),那實際數據的pts就變成is->audio_clock - (double)(is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec,即是
  • 再考慮到,實質上audio_hw_buf_size*2這些數據實際都沒有播放出去,所以就有is->audio_clock - (double)(2 * is->audio_hw_buf_size + is->audio_write_buf_size) / is->audio_tgt.bytes_per_sec。
  • 再加上我們在SDL回調進?填充,實際上 是有開始被播放,所以我們這?采?的相對時間是,剛回調產?的,就是內部 在播放的時候,那相對時間實際也在?。
  • static void sdl_audio_callback(void *opaque, Uint8 *stream, int len) {VideoState *is = opaque;int audio_size, len1;audio_callback_time = av_gettime_relative();
  • 最終
  • set_clock_at(&is->audclk, is->audio_clock - (double)(2 * is->audio_hw_buf_size + is- >audio_write_buf_size) / is->audio_tgt.bytes_per_sec, is->audio_clock_serial,audio_callback_time / 1000000.0);

    2. 視頻主流程

  • ffplay中將視頻同步到?頻的主要?案是,如果視頻播放過快,則重復播放上?幀,以等待?頻;如果視頻播放過慢,則丟幀追趕?頻。
  • 這?部分的邏輯實現在視頻輸出函數 video_refresh 中,分析代碼前,我們先來回顧下這個函數的流程圖:
  • 在這個流程中,“計算上?幀顯示時?”這?步驟?關重要。先來看下代碼:
  • /* called to display each frame */ /* 非暫停或強制刷新的時候,循環調用video_refresh */ static void video_refresh(void *opaque, double *remaining_time) {..../* compute nominal last_duration *///lastvp上一幀,vp當前幀 ,nextvp下一幀//last_duration 計算上一幀應顯示的時長last_duration = vp_duration(is, lastvp, vp);// 經過compute_target_delay方法,計算出待顯示幀vp需要等待的時間// 如果以video同步,則delay直接等于last_duration。// 如果以audio或外部時鐘同步,則需要比對主時鐘調整待顯示幀vp要等待的時間。delay = compute_target_delay(last_duration, is);time= av_gettime_relative()/1000000.0;// is->frame_timer 實際上就是上一幀lastvp的播放時間,// is->frame_timer + delay 是待顯示幀vp該播放的時間if (time < is->frame_timer + delay) { //判斷是否繼續顯示上一幀// 當前系統時刻還未到達上一幀的結束時刻,那么還應該繼續顯示上一幀。// 計算出最小等待時間*remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);goto display;}// 走到這一步,說明已經到了或過了該顯示的時間,待顯示幀vp的狀態變更為當前要顯示的幀is->frame_timer += delay; // 更新當前幀播放的時間if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX) {is->frame_timer = time; //如果和系統時間差距太大,就糾正為系統時間}SDL_LockMutex(is->pictq.mutex);if (!isnan(vp->pts))update_video_pts(is, vp->pts, vp->pos, vp->serial); // 更新video時鐘SDL_UnlockMutex(is->pictq.mutex);//丟幀邏輯if (frame_queue_nb_remaining(&is->pictq) > 1) {//有nextvp才會檢測是否該丟幀Frame *nextvp = frame_queue_peek_next(&is->pictq);duration = vp_duration(is, vp, nextvp);if(!is->step // 非逐幀模式才檢測是否需要丟幀 is->step==1 為逐幀播放&& (framedrop>0 || // cpu解幀過慢(framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) // 非視頻同步方式&& time > is->frame_timer + duration // 確實落后了一幀數據) {printf("%s(%d) dif:%lfs, drop frame\n", __FUNCTION__, __LINE__,(is->frame_timer + duration) - time);is->frame_drops_late++; // 統計丟幀情況frame_queue_next(&is->pictq); // 這里實現真正的丟幀//(這里不能直接while丟幀,因為很可能audio clock重新對時了,這樣delay值需要重新計算)goto retry; //回到函數開始位置,繼續重試}}...}
  • 這段代碼的邏輯在上述流程圖中有包含。主要思路就是?開始提到的:
  • 如果視頻播放過快,則重復播放上?幀,以等待?頻;
  • 如果視頻播放過慢,則丟幀追趕?頻。實現的?式是,參考audio clock,計算上?幀(在屏幕上的那個畫?)還應顯示多久(含幀本身時?),然后與系統時刻對?,是否該顯示下?幀了。
  • 這?與系統時刻的對?,引?了另?個概念——frame_timer。可以理解為幀顯示時刻,如更新前,是上?幀lastvp的顯示時刻;對于更新后( is->frame_timer += delay ),則為當前幀vp顯示時刻。上?幀顯示時刻加上delay(還應顯示多久(含幀本身時?))即為上?幀應結束顯示的時刻。具體原理看如下示意圖:
  • 這?給出了3種情況的示意圖:
  • time1:系統時刻?于lastvp結束顯示的時刻(frame_timer+dealy),即虛線圓圈位置。此時應該繼續顯示lastvp
  • time2:系統時刻?于lastvp的結束顯示時刻,但?于vp的結束顯示時刻(vp的顯示時間開始于虛線圓圈,結束于??圓圈)。此時既不重復顯示lastvp,也不丟棄vp,即應顯示vp
  • time3:系統時刻?于vp結束顯示時刻(??圓圈位置,也是nextvp預計的開始顯示時刻)。此時應該丟棄vp。

  • 3. delay的計算

  • 那么接下來就要看最關鍵的lastvp的顯示時?delay(不是很好理解,要反復體會)是如何計算的。這在函數compute_target_delay中實現:
  • /*** @brief 計算正在顯示幀需要持續播放的時間。* @param delay 該參數實際傳遞的是當前顯示幀和待播放幀的間隔。* @param is* @return 返回當前顯示幀要持續播放的時間。為什么要調整返回的delay?為什么不支持使用相鄰間隔幀時間?*/ static double compute_target_delay(double delay, VideoState *is) {double sync_threshold, diff = 0;/* update delay to follow master synchronisation source *//* 如果發現當前主Clock源不是video,則計算當前視頻時鐘與主時鐘的差值 */if (get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER) {/* if video is slave, we try to correct big delays byduplicating or deleting a frame通過重復幀或者刪除幀來糾正延遲*/diff = get_clock(&is->vidclk) - get_master_clock(is);/* skip or repeat frame. We take into account thedelay to compute the threshold. I still don't knowif it is the best guess */sync_threshold = FFMAX(AV_SYNC_THRESHOLD_MIN,FFMIN(AV_SYNC_THRESHOLD_MAX, delay));if (!isnan(diff) && fabs(diff) < is->max_frame_duration) { // diff在最大幀duration內if (diff <= -sync_threshold) { // 視頻已經落后了delay = FFMAX(0, delay + diff); //}else if (diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD) {// 視頻超前//AV_SYNC_FRAMEDUP_THRESHOLD是0.1,此時如果delay>0.1, 如果2*delay時間就有點久delay = delay + diff;}else if (diff >= sync_threshold) {delay = 2 * delay; // 保持在 2 * AV_SYNC_FRAMEDUP_THRESHOLD內, 即是2*0.1 = 0.2秒內} else {// 其他條件就是delay = delay; 維持原來的delay, 依靠frame_timer+duration和當前時間進行對比}}} else {// 如果是以video為同步,則直接返回last_duration}av_log(NULL, AV_LOG_TRACE, "video: delay=%0.3f A-V=%f\n",delay, -diff);return delay; }
  • 這段代碼中最難理解的是sync_threshold,sync_threshold值范圍:FFMAX(AV_SYNC_THRESHOLD_MIN, FFMIN(AV_SYNC_THRESHOLD_MAX, delay)),其中delay為傳?的上?幀播放需要持續的時間(本質是幀持續時間 frame duration),即是分以下3種情況:
  • delay >AV_SYNC_THRESHOLD_MAX=0.1秒,則sync_threshold = 0.1秒
  • delay <AV_SYNC_THRESHOLD_MIN=0.04秒,則sync_threshold = 0.04秒
  • AV_SYNC_THRESHOLD_MIN = 0.0.4秒 <= delay <= AV_SYNC_THRESHOLD_MAX=0.1秒,則sync_threshold為delay本身。
  • 從這?分析也可以看出來,sync_threshold 最?值為0.1秒,最?值為0.04秒。這?說明?個說明問題呢?
  • 同步精度最好的范圍是:-0.0.4秒~+0.04秒;
  • 同步精度最差的范圍是:-0.1秒~+0.1秒;
  • 和具體視頻的幀率有關系,delay幀間隔(frame duration)落在0.04~0.1秒時,則同步精度為正負1幀。
  • 畫個圖幫助理解:
  • 圖中:
  • 坐標軸是diff值??,diff為0表示video clock與audio clock完全相同,完美同步。
  • 坐標軸下??塊,表示要返回的值,?塊值的delay指傳?參數,結合上?節代碼,即lastvp的顯示時?(frame duration)。
    從圖上可以看出來sync_threshold是建??塊區域,在這塊區域內?需調整lastvp的顯示時?,直接返回delay即可。也就是在這塊區域內認為是準同步的(sync_threshold也是最?允許同步誤差)。
  • 1. 同步判斷結果:
  • diff <= -sync_threshold:如果?于-sync_threshold,那就是視頻播放較慢,需要適當丟幀。具體是返回?個最?為0的值。根據前?frame_timer的圖,?少應更新畫?為vp。
  • diff >= sync_threshold && delay > AV_SYNC_FRAMEDUP_THRESHOLD:如果不僅?于sync_threshold,?且超過了AV_SYNC_FRAMEDUP_THRESHOLD,那么返回delay+diff,由具體diff決定還要顯示多久(這?不是很明?代碼意圖,按我理解,統?處理為返回2*delay,或者delay+diff即可,沒有區分的必要)
  • 此邏輯幀間隔delay > AV_SYNC_FRAMEDUP_THRESHOLD =0.1秒,此時sync_threshold =0.1秒,那delay + diff > 0.1 + diff >= 0.1 + 0.1 = 0.2秒。
  • diff >= sync_threshold:如果?于sync_threshold,那么視頻播放太快,需要適當重復顯示lastvp。具體是返回2delay,也就是2倍的lastvp顯示時?,也就是讓lastvp再顯示?幀。此邏輯?定是 delay <= 0.1時秒,2delay <= 0.2秒
  • -sync_threshold <diff < +sync_threshold:允許誤差內,按frame duration去顯示視頻,即返回delay
  • ?此,基本上分析完了視頻同步?頻的過程,簡單總結下:
  • 基本策略是:如果視頻播放過快,則重復播放上?幀,以等待?頻;如果視頻播放過慢,則丟幀追趕?頻。
  • 這?策略的實現?式是:引?frame_timer概念,標記幀的顯示時刻和應結束顯示的時刻,再與系統時刻對?,決定重復還是丟幀。
  • lastvp的應結束顯示的時刻,除了考慮這?幀本身的顯示時?,還應考慮了video clock與audio clock的差值。
  • 并不是每時每刻都在同步,?是有?個“準同步”的差值區域。

  • 2. 以視頻為基準

  • 媒體流??只有視頻成分,這個時候才會?以視頻為基準。
  • 在“視頻同步?頻”的策略中,我們是通過丟幀或重復顯示的?法來達到追趕或等待?頻時鐘的?的,但在“?頻同步視頻”時,卻不能這樣簡單處理。
  • 在?頻輸出時,最?單位是“樣本”。?頻?般以數字采樣值保存,?般常?的采樣頻率有44.1K,48K等,也就是每秒鐘有44100或48000個樣本。視頻輸出中與“樣本”概念最為接近的畫?幀,如?個24fps(frame per second)的視頻,?秒鐘有24個畫?輸出,這?的?個畫?和?頻中的?個樣本是等效的。可以想?,如果對?頻使??樣的丟幀(丟樣本)和重復顯示?案,是不科學的。(?頻的連續性遠?于視頻,通過重復?百個樣本或者丟棄?百個樣本來達到同步,會在聽覺有很明顯的不連貫)
  • ?頻本質上來講:就是做重采樣補償,?頻慢了,重采樣后的樣本就?正常的減少,以趕緊播放下?幀;?頻快了,重采樣后的樣本就?正常的增加,從?播放慢?些。
  • 1. 視頻主流程

  • video_refresh()-> update_video_pts() 按照著視頻幀間隔去播放,并實時地重新矯正video時鐘。重點主要在audio的播放。
  • 2. ?頻主流程

  • 在分析具體的補償?法的之前,先回顧下?頻輸出的流程。
  • ?頻輸出的主要模型是:
  • 在 audio_buf 緩沖不?時, audio_decode_frame 會從FrameQueue中取出數據放? audio_buf .audio_decode_frame 函數有?視頻同步相關的控制代碼:
  • /*** Decode one audio frame and return its uncompressed size.** The processed audio frame is decoded, converted if required, and* stored in is->audio_buf, with size in bytes given by the return* value.*/ static int audio_decode_frame(VideoState *is) {...// 獲取樣本數校正值:若同步時鐘是音頻,則不調整樣本數;否則根據同步需要調整樣本數wanted_nb_samples = synchronize_audio(is, af->frame->nb_samples);// is->audio_tgt是SDL可接受的音頻幀數,是audio_open()中取得的參數// 在audio_open()函數中又有"is->audio_src = is->audio_tgt""// 此處表示:如果frame中的音頻參數 == is->audio_src == is->audio_tgt,// 那音頻重采樣的過程就免了(因此時is->swr_ctr是NULL)// 否則使用frame(源)和is->audio_tgt(目標)中的音頻參數來設置is->swr_ctx,// 并使用frame中的音頻參數來賦值is->audio_srcif (af->frame->format != is->audio_src.fmt || // 采樣格式dec_channel_layout != is->audio_src.channel_layout || // 通道布局af->frame->sample_rate != is->audio_src.freq || // 采樣率// 第4個條件, 要改變樣本數量, 那就是需要初始化重采樣(wanted_nb_samples != af->frame->nb_samples && !is->swr_ctx) // samples不同且swr_ctx沒有初始化) {swr_free(&is->swr_ctx);is->swr_ctx = swr_alloc_set_opts(NULL,is->audio_tgt.channel_layout, // 目標輸出is->audio_tgt.fmt,is->audio_tgt.freq,dec_channel_layout, // 數據源af->frame->format,af->frame->sample_rate,0, NULL);if (!is->swr_ctx || swr_init(is->swr_ctx) < 0) {av_log(NULL, AV_LOG_ERROR,"Cannot create sample rate converter for conversion of %d Hz %s %d channels to %d Hz %s %d channels!\n",af->frame->sample_rate, av_get_sample_fmt_name(af->frame->format), af->frame->channels,is->audio_tgt.freq, av_get_sample_fmt_name(is->audio_tgt.fmt), is->audio_tgt.channels);swr_free(&is->swr_ctx);return -1;}is->audio_src.channel_layout = dec_channel_layout;is->audio_src.channels = af->frame->channels;is->audio_src.freq = af->frame->sample_rate;is->audio_src.fmt = af->frame->format;}if (is->swr_ctx) {// 重采樣輸入參數1:輸入音頻樣本數是af->frame->nb_samples// 重采樣輸入參數2:輸入音頻緩沖區const uint8_t **in = (const uint8_t **)af->frame->extended_data; // data[0] data[1]// 重采樣輸出參數1:輸出音頻緩沖區尺寸uint8_t **out = &is->audio_buf1; //真正分配緩存audio_buf1,指向是用audio_buf// 重采樣輸出參數2:輸出音頻緩沖區int out_count = (int64_t)wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate+ 256;int out_size = av_samples_get_buffer_size(NULL, is->audio_tgt.channels,out_count, is->audio_tgt.fmt, 0);int len2;if (out_size < 0) {av_log(NULL, AV_LOG_ERROR, "av_samples_get_buffer_size() failed\n");return -1;}// 如果frame中的樣本數經過校正,則條件成立if (wanted_nb_samples != af->frame->nb_samples) {int sample_delta = (wanted_nb_samples - af->frame->nb_samples) * is->audio_tgt.freq/ af->frame->sample_rate;int compensation_distance = wanted_nb_samples * is->audio_tgt.freq / af->frame->sample_rate;// swr_set_compensationif (swr_set_compensation(is->swr_ctx,sample_delta,compensation_distance) < 0) {av_log(NULL, AV_LOG_ERROR, "swr_set_compensation() failed\n");return -1;}}av_fast_malloc(&is->audio_buf1, &is->audio_buf1_size, out_size);if (!is->audio_buf1)return AVERROR(ENOMEM);// 音頻重采樣:返回值是重采樣后得到的音頻數據中單個聲道的樣本數len2 = swr_convert(is->swr_ctx, out, out_count, in, af->frame->nb_samples);if (len2 < 0) {av_log(NULL, AV_LOG_ERROR, "swr_convert() failed\n");return -1;}if (len2 == out_count) {av_log(NULL, AV_LOG_WARNING, "audio buffer is probably too small\n");if (swr_init(is->swr_ctx) < 0)swr_free(&is->swr_ctx);}// 重采樣返回的一幀音頻數據大小(以字節為單位)is->audio_buf = is->audio_buf1;resampled_data_size = len2 * is->audio_tgt.channels * av_get_bytes_per_sample(is->audio_tgt.fmt);} else {// 未經重采樣,則將指針指向frame中的音頻數據is->audio_buf = af->frame->data[0]; // s16交錯模式data[0], fltp data[0] data[1]resampled_data_size = data_size;}audio_clock0 = is->audio_clock;/* update the audio clock with the pts */if (!isnan(af->pts))is->audio_clock = af->pts + (double) af->frame->nb_samples / af->frame->sample_rate;elseis->audio_clock = NAN;is->audio_clock_serial = af->serial; #ifdef DEBUG{static double last_clock;printf("audio: delay=%0.3f clock=%0.3f clock0=%0.3f\n",is->audio_clock - last_clock,is->audio_clock, audio_clock0);last_clock = is->audio_clock;} #endifreturn resampled_data_size; }
  • 主要分3個步驟:
  • 根據與vidoe clock的差值,計算應該輸出的樣本數。由函數 synchronize_audio 完成:
  • ?頻慢了則樣本數減少
  • ?頻快了則樣本數增加
  • 判斷是否需要重采樣:如果要輸出的樣本數與frame的樣本數不相等,也就是需要適當減少或增加樣本。
  • 重采樣——利?重采樣庫進?樣本的插?或剔除
  • 可以看到,與視頻的處理略有不同,視頻的同步控制主要體現在上?幀顯示時?的控制,即對frame_timer的控制;??頻是直接體現在輸出樣本上的控制。
  • 前?提到如果單純判斷某個時刻應該重復樣本或丟棄樣本,然后對輸出?頻進?修改,??會很容易感知到這?不連貫,體驗不好。
  • 這?的處理?式是利?重采樣庫進?平滑地樣本剔除或添加。即在獲知要調整的?標樣本數wanted_nb_samples 后,通過 swr_set_compensation 和 swr_convert 的函數組合完成”重采樣“。
  • 需要注意的是,因為增加或刪除了樣本,樣本總數發?了變化,?采樣率不變,那么假設原先1s的聲?將被以?于1s或?于1s的時?進?播放,這會導致聲?整體頻率被拉低或拉?。直觀感受,就是聲?變粗或變尖了。ffplay也考慮到了這點影響,其做法是設定?個最?、最?調整范圍,避免?幅度的?調變化。
  • 3. synchronize_audio

  • 在了解了整體流程后, 就來看下關鍵函數: synchronize_audio
  • synchronize_audio 負責根據與video clock的差值計算出合適的?標樣本數,通過樣本數控制?頻輸出速度。
  • 現在讓我們看看當 N 組?頻采樣已經不同步的情況。?這些?頻采樣不同步的程度也有很?的不同,所以我們要取平均值來衡量每個采樣的不同步情況。?如,第?次調?時顯示我們不同步了 40ms,下?次是50ms,等等。但是我們不會采取簡單的平均計算,因為最近的值?之前的值更重要也更有意義,這時候我們會使??個?數系數 audio_diff_cum,并對不同步的延時求和:is->audio_diff_cum = diff + is->audio_diff_avg_coef * is->audio_diff_cum;。當我們找到平均差異值時,我們就簡單的計算 avg_diff= is->audio_diff_cum * (1.0 - is->audio_diff_avg_coef);。我們代碼如下:
  • /* return the wanted number of samples to get better sync if sync_type is video* or external master clock */ /*** @brief 如果sync_type是視頻或外部主時鐘,則返回所需樣本數以獲得更好的同步* @param is* @param nb_samples 正常播放的采樣數量* @return*/ static int synchronize_audio(VideoState *is, int nb_samples) {int wanted_nb_samples = nb_samples;/* if not master, then we try to remove or add samples to correct the clock */if (get_master_sync_type(is) != AV_SYNC_AUDIO_MASTER) {double diff, avg_diff;int min_nb_samples, max_nb_samples;diff = get_clock(&is->audclk) - get_master_clock(is);if (!isnan(diff) && fabs(diff) < AV_NOSYNC_THRESHOLD) {// 誤差在AV_NOSYNC_THRESHOLD 范圍再來看看要不要調整is->audio_diff_cum = diff + is->audio_diff_avg_coef * is->audio_diff_cum;if (is->audio_diff_avg_count < AUDIO_DIFF_AVG_NB) {/* not enough measures to have a correct estimate */is->audio_diff_avg_count++; // 連續20次不同步才進行校正} else {/* estimate the A-V difference */avg_diff = is->audio_diff_cum * (1.0 - is->audio_diff_avg_coef); // avg_diff = diff;if (fabs(avg_diff) >= is->audio_diff_threshold) {wanted_nb_samples = nb_samples + (int)(diff * is->audio_src.freq);min_nb_samples = ((nb_samples * (100 - SAMPLE_CORRECTION_PERCENT_MAX) / 100));max_nb_samples = ((nb_samples * (100 + SAMPLE_CORRECTION_PERCENT_MAX) / 100));// av_clip 用來限制wanted_nb_samples最終落在 min_nb_samples或者max_nb_sampleswanted_nb_samples = av_clip(wanted_nb_samples, min_nb_samples, max_nb_samples);}av_log(NULL, AV_LOG_INFO, "diff=%f adiff=%f sample_diff=%d apts=%0.3f %f\n",diff, avg_diff, wanted_nb_samples - nb_samples,is->audio_clock, is->audio_diff_threshold);}} else {// > AV_NOSYNC_THRESHOLD 閾值,該干嘛就干嘛/* too big difference : may be initial PTS errors, soreset A-V filter */is->audio_diff_avg_count = 0;is->audio_diff_cum = 0; // 恢復正常后重置為0}}return wanted_nb_samples; }
  • 和 compute_target_delay ?樣,這個函數的源碼注釋也是ffplay?算多的。這??先得先理解?個”神奇的算法“。
  • 這?有?組變量 audio_diff_avg_coef 、audio_diff_avg_count 、 audio_diff_cum 、 avg_diff .我們會發現在開始播放的AUDIO_DIFF_AVG_NB(20)個幀內,都是在通過公式 is->audio_diff_cum = diff + is->audio_diff_avg_coef * is->audio_diff_cum; 計算累加值 audio_diff_cum 。按注釋的意思是為了得到?個準確的估計值。接著在后?計算與主時鐘的差值時,并不是直接求當前時刻的差值,?是根據累加值計算?個平均值: avg_diff = is->audio_diff_cum * (1.0 - is->audio_diff_avg_coef); ,然后通過這個均值進?校正。
  • 這個公式的?的應該是為了讓越靠近當前時刻的diff值在平均值中的權重越?
  • 繼續看在計算得到 avg_diff 后,如何確定要輸出的樣本數:
  • wanted_nb_samples = nb_samples + (int)(diff * is->audio_src.freq);min_nb_samples = ((nb_samples * (100 - SAMPLE_CORRECTION_PERCENT_MAX) / 100));max_nb_samples = ((nb_samples * (100 + SAMPLE_CORRECTION_PERCENT_MAX) / 100));// av_clip 用來限制wanted_nb_samples最終落在 min_nb_samples或者max_nb_sampleswanted_nb_samples = av_clip(wanted_nb_samples, min_nb_samples, max_nb_samples);
  • 時間差值乘以采樣率可以得到?于補償的樣本數,加之原樣本數,即應輸出樣本數。另外考慮到上?節提到的?頻?調變化問題,這?限制了調節范圍在正負10%以內。
  • 所以如果?視頻不同步的差值較?,并不會?即完全同步,最多只調節當前幀樣本數的10%,剩余會在下次調節時繼續校正。
  • 最后,是與視頻同步?頻時類似地,有?個準同步的區間,在這個區間內不去做同步校正,其??是audio_diff_threshold:
  • is->audio_diff_threshold = (double)(is->audio_hw_buf_size) / is->audio_tgt.bytes_per_sec;
  • 即?頻輸出設備內緩沖的?頻時?。
  • 以上,就是?頻去同步視頻時的主要邏輯。簡單總結如下:
  • ?頻追趕、等待視頻采樣的?法是直接調整輸出樣本數量
  • 調整輸出樣本時為避免聽覺上不連貫的體驗,使?了重采樣庫進??頻的剔除和添加
  • 計算校正后輸出的樣本數量,使?了?個”神奇的公式“
  • 4. swr_set_compensation

    /*** @}** @name Low-level option setting functions* These functons provide a means to set low-level options that is not possible* with the AVOption API.* @{*//*** Activate resampling compensation ("soft" compensation). This function is* internally called when needed in swr_next_pts().** @param[in,out] s allocated Swr context. If it is not initialized,* or SWR_FLAG_RESAMPLE is not set, swr_init() is* called with the flag set.* @param[in] sample_delta delta in PTS per sample* @param[in] compensation_distance number of samples to compensate for* @return >= 0 on success, AVERROR error codes if:* @li @c s is NULL,* @li @c compensation_distance is less than 0,* @li @c compensation_distance is 0 but sample_delta is not,* @li compensation unsupported by resampler, or* @li swr_init() fails when called.*/ int swr_set_compensation(struct SwrContext *s, int sample_delta, int compensation_distance);
  • 激活重采樣補償(“軟”補償)。
  • 在swr_next_pts()中需要時,內部調?此函數。
  • 參數:s:分配Swr上下?。 如果未初始化,或未設置SWR_FLAG_RESAMPLE,則會使?標志集調?swr_init()。
  • sample_delta:每個樣本PTS的delta
  • compensation_distance:要補償的樣品數量
  • 返回:> = 0成功,AVERROR錯誤代碼如果:
  • s為null
  • compensation_distance?于0,
  • compensation_distance是0,但是sample_delta不是,
  • 補償不?持重采樣器,或
  • 調?時,swr_init()失敗。
  • 超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生

    總結

    以上是生活随笔為你收集整理的ffplay.c学习-7-以音频同步为基准的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。