之前已经完成了音频和视频的播放,但是发现音频的播放速度要明显快于视频。由于音频和视频解码时间、渲染时间的不同,所以要进行音视频同步。

由于人耳的听觉灵敏度是要高度视觉的。假如音频少一帧的话是能够明显感觉到的。但是视频少一帧是很难察觉到的。所以所以视频同步的关键是以音频播放为准,视频不断的去减少两者之间的相对播放时间。

一、记录音频的相对播放时间

1、声明变量clock(相对开始的播放时间),这个clock值是不断变化的。这里的重新赋值实在音频转pcm数据的方法中进行的。代码如下:

/**
 * 转换为pcm数据
 */
int AudioChannel::changeFrameToPcm() {
       ...//省略代码
        //数量*单位 pts是数量
        clock = frame->pts*av_q2d(time_base);
        break;
    }
    releaseAvFrame(frame);
    return data_size;
}

2、这里的pts与dts需要了解一下:

pts:显示时间戳,指从packet解码出来的数据的显示顺序

dts:解码时间戳,告诉解码器packet的解码顺序

音频中两者是相同的,但是视频有图B帧的存在,会造成解码顺序与显示顺序并不相同,也就是pts与dts不一定相同。

单位:av_q2d(time_base)计算出来相当于帧率,单位是时间。计算得到的clock是实际的相对于之前一帧的进度时间。

3、由于是以音频为准。所以在视频代码中需要进行播放的精确延时。代码如下:

void VideoChannel2::goPlayFrame() {
    ...//省略代码  
    while (isPlaying) {
           ...//省略代码  

        //帧率,解码速度,渲染速度都会影响音视频同步
        //根据帧率求得延迟时间
        double fps_delay = 1 / fps;
        //渲染时间
        double decode_delay = frame->repeat_pict / (2 * fps);
        //总的延迟时间 解码时间也要算进去 配置差的手机解码比较慢
        double totalDelay = fps_delay + decode_delay;
        //视频的相对时间
        clock = frame->pts * av_q2d(time_base);
        //音频的相对时间
        double audioClock = audioChannel->clock;
        //视频时间刻度-音频时间刻度
        double diff = clock - audioClock;
        LOGE("音视频时间差--%d", diff);
        if (clock > audioClock) {
            //视频超前 休眠时间长一点 不然会卡很久
            if (diff > 1) {
                //相差比较大,延迟时间加大,加快同步时间
                av_usleep((totalDelay * 2) * 1000000);
            } else {
                //相差较小
                av_usleep((totalDelay + diff) * 1000000);

            }

        } else {
            //视频延后 音频超前
            if (diff > 1) {
                //休眠
            } else if (diff >= 0.05) {
                //视频需要追赶,丢非关键帧 同步
                releaseAvFrame(frame);
                //丢frame队列 调用该方法直接调用dropFrame()方法
                frame_queue.sync();
            } else {


            }
        }


        //16ms
        //av_usleep(frame_delay * 1000 * 1000);
        //释放frame
        releaseAvFrame(frame);
    }

    //资源释放
    av_free(&dst_data[0]);
    isPlaying = false;
    releaseAvFrame(frame);
    sws_freeContext(swsContext);
}

4、这里通过计算得到视频和音频的相对播放时间,判断两者的差异,差异较小时可以通过延迟的方法进行追赶。但是如果视频落后音频很多时,就需要采用丢帧的形式经行追赶。丢帧的方式有两种。丢packet和丢frame。两者对比如下:

由于视频有I帧和P,B帧。丢帧不能丢I帧,否则导致后面的画面看不到。所以需要进行判断。另外丢packet帧有时候是没有效果的。当我们的frame队列是满的时候。无论怎么操作packet队列都不会影响结果。因为直接和视频播放相关的是frame队列。所以这里采用丢frame帧的策略。

5:丢帧代码如下:

VideoChannel2::VideoChannel2(int id, JavaCallHelper *javaCallHelper,
                             AVCodecContext *avCodecContext, AVRational time_base) : BaseChannel(id,
                                                                                                 javaCallHelper,
                                                                                                 avCodecContext,
                                                                                                 time_base) {
    //设置丢帧策略
    frame_queue.setReleaseHandle(releaseAvFrame);
    frame_queue.setSyncHandle(dropFrame);
}

/**
 * 丢真可分为两种 丢packet 丢frame
 * 丢packet不能丢关键帧 假如丢packet帧,但是frame队列还是满的,f因为rame队列是直接与渲染相关的,丢packet不会有效果
 * 选择丢frame帧 不需要判断I,P,B帧,并且直接有效
 * @param q
 */
void dropFrame(queue<AVFrame *> &q) {
    if (!q.empty()) {
        AVFrame *frame = q.front();
        q.pop();
        BaseChannel::releaseAvFrame(frame);
    }

}

完成以后步骤,就可以达到音视频的同步了。