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

日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

Ffmpeg和SDL如何同步音頻

系統(tǒng) 2088 0

ong>?

同步音頻

現(xiàn)在我們已經(jīng)有了一個(gè)比較像樣的播放器。所以讓我們看一下還有哪些零碎的東西沒(méi)處理。上次,我們掩飾了一點(diǎn)同步問(wèn)題,也就是同步音頻到視頻而不是其它的同步方式。我們將采用和視頻一樣的方式:做一個(gè)內(nèi)部視頻時(shí)鐘來(lái)記錄視頻線程播放了多久,然后同步音頻到上面去。后面我們也來(lái)看一下如何推而廣之把音頻和視頻都同步到外部時(shí)鐘。

?

生成一個(gè)視頻時(shí)鐘

現(xiàn)在我們要生成一個(gè)類(lèi)似于上次我們的聲音時(shí)鐘的視頻時(shí)鐘:一個(gè)給出當(dāng)前視頻播放時(shí)間的內(nèi)部值。開(kāi)始,你可能會(huì)想這和使用上一幀的時(shí)間戳來(lái)更新定時(shí)器一樣簡(jiǎn)單。但是,不要忘了視頻幀之間的時(shí)間間隔是很長(zhǎng)的,以毫秒為計(jì)量的。解決辦法是跟蹤另外一個(gè)值:我們?cè)谠O(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中的方式很類(lèi)似。

所以在我們的大結(jié)構(gòu)體中,我們將放上一個(gè)雙精度浮點(diǎn)變量video_current_pts和一個(gè)64位寬整型變量video_current_pts_time。時(shí)鐘更新將被放在video_refresh_timer函數(shù)中。

void video_refresh_timer(void *userdata) {

if(is->video_st) {

if(is->pictq_size == 0) {

schedule_refresh(is, 1);

} else {

vp = &is->pictq[is->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() - is->video_current_pts_time) / 1000000.0;

return is->video_current_pts + delta;

}

?

提取時(shí)鐘

但是為什么要強(qiáng)制使用視頻時(shí)鐘呢?我們更改視頻同步代碼以致于音頻和視頻不會(huì)試著去相互同步。想像一下我們讓它像ffplay一樣有一個(gè)命令行參數(shù)。所以讓我們抽象一樣這件事情:我們將做一個(gè)新的封裝函數(shù)get_master_clock,用來(lái)檢測(cè)av_sync_type變量然后決定調(diào)用 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 *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í)間比較然后算出我們需要修正多少的樣本數(shù),也就是說(shuō):我們是否需要通過(guò)丟棄樣本的方式來(lái)加速播放還是需要通過(guò)插值樣本的方式來(lái)放慢播放?

我們將在每次處理聲音樣本的時(shí)候運(yùn)行一個(gè)synchronize_audio的函數(shù)來(lái)正確的收縮或者擴(kuò)展聲音樣本。然而,我們不想在每次發(fā)現(xiàn)有偏差的時(shí)候都進(jìn)行同步,因?yàn)檫@樣會(huì)使同步音頻多于視頻包。所以我們?yōu)楹瘮?shù)synchronize_audio設(shè)置一個(gè)最小連續(xù)值來(lái)限定需要同步的時(shí)刻,這樣我們就不會(huì)總是在調(diào)整了。當(dāng)然,就像上次那樣,“失去同步”意味著聲音時(shí)鐘和視頻時(shí)鐘的差異大于我們的閾值。

?

所以我們將使用一個(gè)分?jǐn)?shù)系數(shù),叫c,所以現(xiàn)在可以說(shuō)我們得到了N個(gè)失去同步的聲音樣本。失去同步的數(shù)量可能會(huì)有很多變化,所以我們要計(jì)算一下失去同步的長(zhǎng)度的均值。例如,第一次調(diào)用的時(shí)候,顯示出來(lái)我們失去同步的長(zhǎng)度為40ms,下次變?yōu)?0ms等等。但是我們不會(huì)使用一個(gè)簡(jiǎn)單的均值,因?yàn)榫嚯x現(xiàn)在最近的值比靠前的值要重要的多。所以我們將使用一個(gè)分?jǐn)?shù)系統(tǒng),叫c,然后用這樣的公式來(lái)計(jì)算差異:diff_sum = new_diff + diff_sum*c。當(dāng)我們準(zhǔn)備好去找平均差異的時(shí)候,我們用簡(jiǎn)單的計(jì)算方式:avg_diff = diff_sum * (1-c)。

注意:為什么會(huì)在這里?這個(gè)公式看來(lái)很神奇!嗯,它基本上是一個(gè)使用等比級(jí)數(shù)的加權(quán)平均值。我不知道這是否有名字(我甚至查過(guò)維基百科!),但是如果想要更多的信息,這里是一個(gè)解釋http://www.dranger.com/ffmpeg/weightedmean.html 或者在http://www.dranger.com/ffmpeg/weightedmean.txt 里。

下面是我們的函數(shù):

int synchronize_audio(VideoState *is, short *samples,

int samples_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;

int wanted_size, min_size, max_size, nb_samples;

ref_clock = get_master_clock(is);

diff = get_audio_clock(is) - ref_clock;

if(diff < AV_NOSYNC_THRESHOLD) {

// accumulate the diffs

is->audio_diff_cum = diff + is->audio_diff_avg_coef

* is->audio_diff_cum;

if(is->audio_diff_avg_count < AUDIO_DIFF_AVG_NB) {

is->audio_diff_avg_count++;

} else {

avg_diff = is->audio_diff_cum * (1.0 - is->audio_diff_avg_coef);

}

} else {

is->audio_diff_avg_count = 0;

is->audio_diff_cum = 0;

}

}

return samples_size;

}

現(xiàn)在我們已經(jīng)做得很好;我們已經(jīng)近似的知道如何用視頻或者其它的時(shí)鐘來(lái)調(diào)整音頻了。所以讓我們來(lái)計(jì)算一下要在添加和砍掉多少樣本,并且如何在“Shrinking/expanding buffer code”部分來(lái)寫(xiě)上代碼:

if(fabs(avg_diff) >= is->audio_diff_threshold) {

wanted_size = 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) {

wanted_size = min_size;

} else if (wanted_size > max_size) {

wanted_size = max_size;

}

記住audio_length * (sample_rate * # of channels * 2)就是audio_length秒時(shí)間的聲音的樣本數(shù)。所以,我們想要的樣本數(shù)就是我們根據(jù)聲音偏移添加或者減少后的聲音樣本數(shù)。我們也可以設(shè)置一個(gè)范圍來(lái)限定我們一次進(jìn)行修正的長(zhǎng)度,因?yàn)槿绻覀兏淖兊奶啵脩魰?huì)聽(tīng)到刺耳的聲音。

?

修正樣本數(shù)

現(xiàn)在我們要真正的修正一下聲音。你可能會(huì)注意到我們的同步函數(shù)synchronize_audio返回了一個(gè)樣本數(shù),這可以告訴我們有多少個(gè)字節(jié)被送到流中。所以我們只要調(diào)整樣本數(shù)為wanted_size就可以了。這會(huì)讓樣本更小一些。但是如果我們想讓它變大,我們不能只是讓樣本大小變大,因?yàn)樵诰彌_區(qū)中沒(méi)有多余的數(shù)據(jù)!所以我們必需添加上去。但是我們?cè)鯓觼?lái)添加呢?最笨的辦法就是試著來(lái)推算聲音,所以讓我們用已有的數(shù)據(jù)在緩沖的末尾添加上最后的樣本。

if(wanted_size < samples_size) {

samples_size = wanted_size;

} else if(wanted_size > samples_size) {

uint8_t *samples_end, *q;

int nb;

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;

}

samples_size = wanted_size;

}

現(xiàn)在我們通過(guò)這個(gè)函數(shù)返回的是樣本數(shù)。我們現(xiàn)在要做的是使用它:

void audio_callback(void *userdata, Uint8 *stream, int len) {

VideoState *is = (VideoState *)userdata;

int len1, audio_size;

double pts;

while(len > 0) {

if(is->audio_buf_index >= is->audio_buf_size) {

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 = synchronize_audio(is, (int16_t *)is->audio_buf,

audio_size, pts);

is->audio_buf_size = audio_size;

我們要做的是把函數(shù)synchronize_audio插入進(jìn)去。(同時(shí),保證在初始化上面變量的時(shí)候檢查一下代碼,這些我沒(méi)有贅述)。

結(jié)束之前的最后一件事情:我們需要添加一個(gè)if語(yǔ)句來(lái)保證我們不會(huì)在視頻為主時(shí)鐘的時(shí)候也來(lái)同步視頻。

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_THRESHOLD;

if(fabs(diff) < AV_NOSYNC_THRESHOLD) {

if(diff <= -sync_threshold) {

delay = 0;

} else if(diff >= sync_threshold) {

delay = 2 * delay;

}

}

}

添加后就可以了。要保證整個(gè)程序中我沒(méi)有贅述的變量都被初始化過(guò)了。然后編譯它:

gcc -o tutorial06 tutorial06.c -lavutil -lavformat -lavcodec -lz -lm`sdl-config --cflags --libs`

然后你就可以運(yùn)行它了。

Ffmpeg和SDL如何同步音頻


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫(xiě)作最大的動(dòng)力,如果您喜歡我的文章,感覺(jué)我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長(zhǎng)會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 吉首市| 阳高县| 嵊泗县| 穆棱市| 玛沁县| 习水县| 托克逊县| 阳东县| 许昌县| 阳谷县| 峨山| 涟源市| 静海县| 盐亭县| 承德市| 广安市| 彩票| 乌拉特后旗| 大余县| 阳东县| 马边| 当阳市| 聂荣县| 沙湾县| 和顺县| 庆云县| 肥城市| 简阳市| 松潘县| 蒙城县| 庆元县| 沂水县| 新巴尔虎左旗| 集安市| 睢宁县| 灵台县| 老河口市| 萝北县| 永靖县| 湘阴县| 铁岭县|