欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

视频播放器--音视频同步

程序员文章站 2022-07-01 17:36:14
...

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

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

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

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);
    }

}

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