美文网首页android收藏
(五)Android通过ffmpeg,实现音视频同步

(五)Android通过ffmpeg,实现音视频同步

作者: 超人TIGA | 来源:发表于2022-07-02 23:54 被阅读0次

前面已经介绍过视频的解码与显示,和音频的解码与播放了。但这里会有一个问题,那就是视频和音频的同步。

不同步有什么后果?

后果就是要么视频播放太快了,音频没有跟上;或者音频播放太快了,视频没有跟上;严重影响整体的观看体验。
就好比小姐姐当面问你联系方式,小姐姐你看到了,但人家说的啥你还没听到,之后人家都走了,你才听到原来是问电话号码的,多惨。

那怎么解决呢?

方法一:以音频的解码流为主参照,视频流的解码向音频的解码时间靠拢。
方法二:以视频的解码流为主参照,音频流的解码视频的解码时间靠拢。
方法三:以手机系统时间为主参照,视频流和音频流的解码都向系统时间靠拢。

哪一种方法最好?或者说,每种方法的使用场景是什么?

我的理解是:以谁为主参照,就是看重谁。
如果声音断开一下下,我们的耳朵是很容易感觉出来的,相反如果声音连续,视频帧偶尔卡一下下,一般都影响不大。当需求是极度要求声音的连续性的,那就方法一。
相反,当需求是极度要求视频的连续性的,那就方法二。
至于方法三,就是折中的方法,感觉啥时候都可以用,我自己也大多数用方法三。

如何实现?

在这之前,先介绍几个属性。
I帧:关键帧,帧内编码帧 又称intra picture,I 帧通常是每个 GOP(MPEG 所使用的一种视频压缩技术)的第一个帧,经过适度地压缩,做为随机访问的参考点,可以当成图象。I帧可以看成是一个图像经过压缩后的产物。可独立解码。
B帧:双向预测内插编码帧 又称bi-directional interpolated prediction frame,可以大大提高压缩倍数。(与I帧相似度95%以上)
P帧:前向预测编码帧 又称predictive-frame,P 帧图像只采用前向时间预测,可以提高压缩效率和图像质量。(与I帧相似度70%以上)
DTS:帧数据的编码时间戳,这个时间戳的意义在于告诉播放器该在什么时候解码这一帧的数据。
PTS:帧数据的显示时间戳,这个时间戳用来告诉播放器该在什么时候显示这一帧的数据。
怎么理解这些东西?直接复制网络上的一张图比较直观和方便。

20220314143821.png
上图就是一个GOP内的帧数据,通过PTS和DTS,播放器可以知道在某一个时间,解码哪一帧,显示哪一帧。
同样的,当有2个流(视频流和音频流),我们需要他们的进度保持相对的一致,那么只要保证他们的PTS或者DTS都相对一致,就可以了。
具体做法

/**
 * 解码一帧数据
 * @return 0 if OK, < 0 on error or end of file
 */
int BaseDecoder::DecodeOnePacket() {
    if (m_SeekPosition > 0) {//拖动进度条

    }
    //读取一帧数据到 m_Packet 中
    int result = av_read_frame(m_AVFormatContext, m_Packet);
    while (result == 0) {
        //匹配帧的index
        if (m_Packet->stream_index == m_StreamIndex) {

            if (avcodec_send_packet(m_AVCodecContext, m_Packet) == AVERROR_EOF) {
                //解码结束
                result = -1;
                goto __EXIT;
            }

            int frameCount = 0;
            while (avcodec_receive_frame(m_AVCodecContext, m_Frame) == 0) {
                //更新时间戳
                UpdateTimeStamp();
                //同步
                AVSync();
                //渲染视频
                OnFrameAvailable(m_Frame);
                frameCount++;
            }
            //判断一个 packet 是否解码完成
            if (frameCount > 0) {
                result = 0;
                goto __EXIT;
            }
        }
        av_packet_unref(m_Packet);
        result = av_read_frame(m_AVFormatContext, m_Packet);
    }

    __EXIT:
    av_packet_unref(m_Packet);

    return result;
}

这段代码的主要功能,就是解码一帧数据(视频帧、音频帧都可以),然后交给对应的模块去显示和播放声音。其中的UpdateTimeStamp()和AVSync()就是同步的主要方法了。


void BaseDecoder::UpdateTimeStamp() {
    LOGE("DecoderBase::UpdateTimeStamp");
    std::unique_lock<std::mutex> lock(m_Mutex);
    if(m_Frame->pkt_dts != AV_NOPTS_VALUE) {
        m_CurTimeStamp = m_Frame->pkt_dts;
    } else if (m_Frame->pts != AV_NOPTS_VALUE) {
        m_CurTimeStamp = m_Frame->pts;
    } else {
        m_CurTimeStamp = 0;
    }

    m_CurTimeStamp = (int64_t)((m_CurTimeStamp * av_q2d(m_AVFormatContext->streams[m_StreamIndex]->time_base)) * 1000);

    if(m_SeekPosition > 0 && m_SeekSuccess)
    {
        m_StartTimeStamp = GetSysCurrentTime() - m_CurTimeStamp;
        m_SeekPosition = 0;
        m_SeekSuccess = false;
    }
}

long BaseDecoder::AVSync() {
    LOGD("BaseDecoder::AVSync");
    long curSysTime = GetSysCurrentTime();
    //基于系统时钟计算从开始播放流逝的时间
    long elapsedTime = curSysTime - m_StartTimeStamp;
    long delay = 0;
    //向系统时钟同步
    if(m_CurTimeStamp > elapsedTime) {
        //休眠时间
        auto sleepTime = static_cast<unsigned int>(m_CurTimeStamp - elapsedTime);//ms
        //限制休眠时间不能过长
        sleepTime = sleepTime > DELAY_THRESHOLD ? DELAY_THRESHOLD :  sleepTime;
        av_usleep(sleepTime * 1000);
    }
    delay = elapsedTime - m_CurTimeStamp;
    return delay;
}

每次解码都更新一下时间,然后跟系统时间做对比,根据时间差进行判断,要么睡眠等待,要么继续执行解码。

还有需要注意的不?

首先,睡眠等待的时间,需要一个阈值,不然很影响体验。
然后,如果输入流是网络,输入数据是不稳定的,有可能会丢帧之类的情况出现,这些情况需要根据具体需求去建立方案解决,这里就不写了。

相关文章

  • (五)Android通过ffmpeg,实现音视频同步

    前面已经介绍过视频的解码与显示,和音频的解码与播放了。但这里会有一个问题,那就是视频和音频的同步。 不同步有什么后...

  • FFmpeg 音视频同步

    FFmpeg简易播放器的实现5-音视频同步 写的很不错 : https://www.cnblogs.com/lei...

  • Android集成FFmpeg并实现视频转码

    通过编译FFmpeg并集成到Android App中实现视频转码,也可以直接执行FFmpeg命令实现视频拼接、剪切...

  • Ffmpeg音视频常用命令

    播放器架构 同时还有音视频同步,这个是很重要的。 渲染流程 1、FFmpeg常用命令实战 FFmpeg音视频处理流...

  • FFmpeg (五)音视频同步

    I\P\B帧 I 帧:帧内编码帧 ,一个图像经过压缩后的产物,包含一幅完整的图像信息; P 帧: 前向预测编码帧,...

  • 音视频开发—FFMpeg编码&解码

    FFMpeg 作为音视频领域的开源工具,它几乎可以实现所有针对音视频的处理,本文主要利用 FFMpeg 官方提供的...

  • FFmpeg工具

    ffmpeg在做音视频编解码时非常方便,所以很多场景下转码使用的是ffmpeg,通过ffmpeg –-help可以...

  • FFMPEG进阶系列02-ffmpeg命令详解3

    更多音视频知识请点击:专注音视频开发 ffmpeg 的封装转换 FFmpeg的封装转换部分,主要是通过libavf...

  • 基于Qt和FFmpeg的简易视频播放器

    1.前言 这里实现了简易的视频解码,后面会陆续发布音视频同步等相关文章 2.视频解码播放流程 FFmpeg解码视频...

  • FFmpeg滤镜(12)

    FFmpeg生成测试元数据 FFmpeg不但可以处理音视频文件,还可以生成音视频文件,可以通过lavfi设备虚拟音...

网友评论

    本文标题:(五)Android通过ffmpeg,实现音视频同步

    本文链接:https://www.haomeiwen.com/subject/kpkkbrtx.html