Android萬能視頻播放器05-音視頻同步

1、概念

由于視頻播放器中音頻和視頻是分別播放和渲染的,就會(huì)出現(xiàn)聲音和畫面不同步的現(xiàn)象。為了使同一時(shí)刻聲音和畫面的一致性,我們就需要音視頻同步來實(shí)現(xiàn),這就是音視頻同步。

2、播放時(shí)間

2.1、音頻播放時(shí)間

音頻播放的時(shí)長是PCM數(shù)據(jù)決定的,根據(jù)數(shù)據(jù)大小和采樣率、通道數(shù)和位深度就能計(jì)算出播放的時(shí)長。只要采樣率、通道數(shù)、位深度不變,揚(yáng)聲器播放同一段PCM數(shù)據(jù)的時(shí)間就是固定不變的。

2.2、視頻播放時(shí)間

視頻其實(shí)沒有播放時(shí)長的概念,只有相鄰視頻畫面幀直接的時(shí)間間隔,調(diào)整時(shí)間間隔就能改變視頻畫面的渲染速度,來實(shí)現(xiàn)視頻的快慢控制。

3、音視頻同步方法:

  • 第一種:音頻線性播放,視頻同步到音頻上。

  • 第二種:視頻線性播放,音頻同步到視頻上。

  • 第三種:用一個(gè)外部線性時(shí)間,音頻和視頻都同步到這個(gè)外部時(shí)間上。

由于人們對聲音更敏感,視頻畫面的一會(huì)兒快一會(huì)兒慢是察覺不出來的。而
聲音的節(jié)奏變化是很容易察覺的。所以我們這里采用第一種方式來同步音視頻。

4、音視頻同步實(shí)現(xiàn):

4.1、PTS和time_base

PTS即顯示時(shí)間戳,這個(gè)時(shí)間戳用來告訴播放器該在什么時(shí)候顯示這一幀的數(shù)據(jù)。
time_base即時(shí)間度量單位(時(shí)間基),可以類比:米、千克這種單位。

4.2、分別獲取音頻和視頻的PTS(播放時(shí)間戳):

PTS = avFrame->pts * av_q2d(avStream->time_base);

4.3、獲取音視頻PTS差值,根據(jù)差值來設(shè)置視頻的睡眠時(shí)間達(dá)到和音頻的相對同步。

視頻快了就休眠久點(diǎn),視頻慢了就休眠少點(diǎn),來達(dá)到同步。

5、實(shí)現(xiàn)

5.1、JfVideo.h在Video中拿到Audio對象

class JfVideo {
public:
    ...
    JfAudio *audio;
    ...

public:
    ...
};

初始化時(shí)賦值

void JfFFmpeg::start() {
    if (audio == NULL) {
        if (LOG_DEBUG){
            LOGE("AUDIO == NULL");
        }
    }
    if (video == NULL) {
        if (LOG_DEBUG){
            LOGE("VIDEO == NULL");
        }
    }

    video->audio = audio;

    audio->play();
    video->play();
    ...

5.2、獲取音視頻PTS差值

double getFrameDiffTime(AVFrame *avFrame);

/**
 * 當(dāng)前幀的AVFrame
 */
double JfVideo::getFrameDiffTime(AVFrame *avFrame) {
    //獲取當(dāng)前幀的pts
    double pts = av_frame_get_best_effort_timestamp(avFrame);
    
    if (pts == AV_NOPTS_VALUE){//判斷是否有效
        pts = 0;
    }

    double timestamp = pts * av_q2d(time_base);//time_base是流的time_base,用來計(jì)算這幀在整個(gè)視頻中的時(shí)間位置
    
    if (timestamp > 0){
        clock = timestamp;
    }

    double diff = audio->clock - clock;

    return diff;
}

5.3、實(shí)現(xiàn)同步

double JfVideo::getDelayTime(double diff) {
    if (diff > 0.003){//音頻播放比視頻快0.003,讓視頻播放快點(diǎn)
        delayTime = delayTime * 2 / 3;//讓視頻渲染時(shí)sleep略長的時(shí)間,但是可能會(huì)導(dǎo)致視頻播放的越來越快

        if (delayTime < defaultDelayTime / 2){
            delayTime = defaultDelayTime * 2 / 3;
        } else if (delayTime > defaultDelayTime * 2){
            delayTime = defaultDelayTime * 2;
        }

    } else if (diff < -0.003){//音頻播放比視頻慢0.003,讓視頻播放慢點(diǎn)
        delayTime = delayTime * 3 / 2;//讓視頻渲染時(shí)sleep略長的時(shí)間,但是可能會(huì)導(dǎo)致視頻播放的越來越慢

        if (delayTime < defaultDelayTime / 2){
            delayTime = defaultDelayTime * 2 / 3;
        } else if (delayTime > defaultDelayTime * 2){
            delayTime = defaultDelayTime * 2;
        }

    } else if (diff == 0.003){

    }

    if (diff >= 0.5){
        delayTime = 0;
    } else if (diff <= -0.5){
        delayTime = defaultDelayTime * 2;
    }

    if (fabs(diff) >= 10){//音頻不存在
        delayTime = defaultDelayTime;
    }
    return delayTime;
}

5.4、視頻渲染速度調(diào)節(jié)

void *playVideo(void *data){
    JfVideo *video = (JfVideo *)data;

    while (video->playStatus != NULL && !video->playStatus->exit){
        if (video->playStatus->seeking){
            av_usleep(1000 * 100);
            continue;
        }
        if (video->queue->getQueueSize() == 0){//加載狀態(tài)
            if (!video->playStatus->loading){
                video->playStatus->loading = true;
                video->callJava->onCallLoading(CHILD_THREAD, true);
                LOGD("VIDEO加載狀態(tài)");
            }
            av_usleep(1000 * 100);
            continue;
        } else {
            if (video->playStatus->loading){
                video->playStatus->loading = false;
                video->callJava->onCallLoading(CHILD_THREAD, false);
                LOGD("VIDEO播放狀態(tài)");
            }
        }

        /*AVPacket *avPacket = av_packet_alloc();
        if (video->queue->getAVPacket(avPacket) == 0){
            //解碼渲染
            LOGD("線程中獲取視頻AVPacket");
        }
        av_packet_free(&avPacket);//AVPacket中的第一個(gè)參數(shù),就是引用,減到0才真正釋放
        av_free(avPacket);
        avPacket = NULL;*/

        AVPacket *avPacket = av_packet_alloc();
        if (video->queue->getAVPacket(avPacket) != 0){
            av_packet_free(&avPacket);//AVPacket中的第一個(gè)參數(shù),就是引用,減到0才真正釋放
            av_free(avPacket);
            avPacket = NULL;
            continue;
        }

        if (avcodec_send_packet(video->pVCodecCtx,avPacket) != 0){

            av_packet_free(&avPacket);//AVPacket中的第一個(gè)參數(shù),就是引用,減到0才真正釋放
            av_free(avPacket);
            avPacket = NULL;
            continue;
        }
        AVFrame *avFrame = av_frame_alloc();
        if (avcodec_receive_frame(video->pVCodecCtx,avFrame) != 0){
            av_frame_free(&avFrame);
            av_free(avFrame);
            avFrame = NULL;
            av_packet_free(&avPacket);//AVPacket中的第一個(gè)參數(shù),就是引用,減到0才真正釋放
            av_free(avPacket);
            avPacket = NULL;
            continue;
        }

        if (LOG_DEBUG){
            LOGD("子線程解碼一個(gè)AVFrame成功");
        }

        if (avFrame->format == AV_PIX_FMT_YUV420P){
            //直接渲染
            LOGD("YUV420P");

            double diff = video->getFrameDiffTime(avFrame);

            LOGD("DIFF IS %f",diff);

            av_usleep(video->getDelayTime(diff) * 1000000);

            video->callJava->onCallRenderYUV(
                    CHILD_THREAD,
                    video->pVCodecCtx->width,
                    video->pVCodecCtx->height,
                    avFrame->data[0],
                    avFrame->data[1],
                    avFrame->data[2]);
        } else {
            //轉(zhuǎn)成YUV420P
            AVFrame *pFrameYUV420P = av_frame_alloc();
            int num = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,video->pVCodecCtx->width,video->pVCodecCtx->height,1);
            uint8_t *buffer = (uint8_t *)(av_malloc(num * sizeof(uint8_t)));
            av_image_fill_arrays(
                    pFrameYUV420P->data,
                    pFrameYUV420P->linesize,
                    buffer,
                    AV_PIX_FMT_YUV420P,
                    video->pVCodecCtx->width,
                    video->pVCodecCtx->height,
                    1);
            SwsContext *sws_ctx = sws_getContext(
                    video->pVCodecCtx->width,
                    video->pVCodecCtx->height,
                    video->pVCodecCtx->pix_fmt,
                    video->pVCodecCtx->width,
                    video->pVCodecCtx->height,
                    AV_PIX_FMT_YUV420P,
                    SWS_BICUBIC,
                    NULL,NULL,NULL
                    );

            if (!sws_ctx){
                av_frame_free(&pFrameYUV420P);
                av_free(pFrameYUV420P);
                av_free(buffer);
                continue;
            }

            sws_scale(
                    sws_ctx,
                    avFrame->data,
                    avFrame->linesize,
                    0,
                    avFrame->height,
                    pFrameYUV420P->data,
                    pFrameYUV420P->linesize);//這里得到Y(jié)UV數(shù)據(jù)
            LOGD("NO_YUV420P");
            //渲染

            double diff = video->getFrameDiffTime(avFrame);

            LOGD("DIFF IS %f",diff);

            av_usleep(video->getDelayTime(diff) * 1000000);


            video->callJava->onCallRenderYUV(
                    CHILD_THREAD,
                    video->pVCodecCtx->width,
                    video->pVCodecCtx->height,
                    pFrameYUV420P->data[0],
                    pFrameYUV420P->data[1],
                    pFrameYUV420P->data[2]);

            av_frame_free(&pFrameYUV420P);
            av_free(pFrameYUV420P);
            av_free(buffer);
            sws_freeContext(sws_ctx);
        }

        av_frame_free(&avFrame);
        av_free(avFrame);
        avFrame = NULL;
        av_packet_free(&avPacket);//AVPacket中的第一個(gè)參數(shù),就是引用,減到0才真正釋放
        av_free(avPacket);
        avPacket = NULL;
    }
    pthread_exit(&video->thread_play);
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,250評論 6 530
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 97,923評論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,041評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,475評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,253評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,801評論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,882評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,023評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,530評論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,494評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,639評論 1 366
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,177評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,890評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,289評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,552評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,242評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,626評論 2 370

推薦閱讀更多精彩內(nèi)容

  • 心若蓮花,處處蓮花, 吐氣如蘭,空谷有蘭, 愛在心中,愛有回響, 你若芬芳,花亦芬芳。
    明月映我心閱讀 171評論 4 16
  • 加入挑好果已經(jīng)10天了,這10天給了我太多的感悟,很多朋友在問我你怎么做微商了,怎么去賣水果了,利潤很低的,很辛苦...
    果先森傅梁閱讀 255評論 1 0
  • 煙中云,水中月,畫中景,存在于真假之中 雨中淚,城中樹,人中情,體現(xiàn)在善惡之間
    吾愛從心閱讀 187評論 0 4
  • 沒有完全的正確和錯(cuò)誤,只有在過程中不斷把結(jié)果趨于正確。
    timingkiss閱讀 92評論 0 0