FFmpeg开发(四):音视频同步与音视频播放器

封装格式
压缩未压缩
音频AACPCM
视频H.264Yuv
时间戳

1、I 帧/P 帧/B 帧 I 帧:
I 帧(Intra-coded picture, 帧内编码帧,常称为关键帧)包含一幅完整的图像信息,属于帧内编码图像,不含运动矢量,在解码时不需要参考其他帧图像。因此在 I
帧图像处可以切换频道,而不会导致图像丢失或无法解码。I 帧图像用于阻止误差的累积和扩散。在闭合式 GOP(Graphic output Protocol) 中,每个 GOP 的第一个帧一定是
I 帧,且当前 GOP 的数据不会参考前后 GOP 的数据。

P 帧:P 帧(Predictive-coded picture, 预测编码图像帧)是帧间编码帧,利用之前的 I 帧或 P 帧进行预测编码。

B 帧:B 帧(Bi-directionally predicted picture,
双向预测编码图像帧)是帧间编码帧,利用之前和(或)之后的 I 帧或 P 帧进行双向预测编码。B 帧不可以作为参考帧。 B帧具有更高的压缩率,但需要更多的缓冲时间以及更高的 CPU 占用率,因此 B 帧适合本地存储以及视频点播,而不适用对实时性要求较高的直播系统。

2、DTS 和 PTS DTS(Decoding Time Stamp, 解码时间戳),表示压缩帧的解码时间。 PTS(Presentation Time Stamp, 显示时间戳),表示将压缩帧解码后得到的原始帧的显示时间。 音频中 DTS 和
PTS 是相同的。视频中由于 B 帧需要双向预测,B 帧依赖于其前和其后的帧,因此含 B 帧的视频解码顺序与显示顺序不同,即 DTS 与
PTS 不同。当然,不含 B 帧的视频,其 DTS 和 PTS 是相同的。 编码顺序指编码器编码后图像帧的顺序。存储到磁盘的本地视频文件中图像帧的顺序与编码顺序相同。
传输顺序指编码后的流在网络中传输过程中图像帧的顺序。 解码顺序指解码器解码图像帧的顺序。 显示顺序指图像帧在显示器上显示的顺序。
采集顺序与显示顺序相同。编码顺序、传输顺序和解码顺序相同。

视频pts的计算
  1. 双向预测帧比需要参考后面采集的帧才能解码,后采集的帧需要被优先解码,所以就会导致解码顺序和显示顺序不一样。
  2. 例如 time_base = {1,1000}(1/1000s),dts = 40,pts = 160,可以把dts和pts看作时钟脉冲,那么可以计算出该视频帧的解码时刻为40ms,显示时刻为160ms。pts就是该视频帧在视频中的时间位置。同理,计算duration的方法也是一样的。
音频pts的计算
  1. 音频audioCodecCtx->sample_rate表示音频的频率(Hz),但是从计算方式上来说是和视频编码的计算方式是一样的。

时间戳转换代码为:
pts = (int64_t)(frame->pts * av_q2d(audioStream->time_base) * 1000);
av_q2d的函数定义为:

/**
 * Convert an AVRational to a `double`.
 */
static inline double av_q2d(AVRational a){
    return a.num / (double) a.den;
}

音视频同步

一般有三种方式:

  1. 音视频向系统时钟同步
  2. 音频向视频同步
  3. 视频向音频同步

我的工程里是视频向音频同步,结构图如下:
在这里插入图片描述

音频解码和视频解码分别占用一个独立线程,线程里有一个解码循环,解码循环里不断对音视频编码数据进行解码,解码后的数据都放入缓冲区中。
音频按照固定的采样率播放,利用同步回调函数为视频提供对齐基准,当视频时间戳大于音频时间戳时,渲染器不进行渲染或者重复渲染上一帧,反之,进行跳帧渲染。

代码逻辑
  1. 以音频为基准,调用回调函数:
void OpenSLESPlayer::processAudio() {
    unique_lock<mutex> providerLock(providerMu);
    if(provider != NULL)
    {
        AudioFrame *data = provider->getAudioFrame();
        if(data == NULL)
        {
            LOGE("get a NULL audio frame");
            (*playerBufferQueue)->Enqueue(playerBufferQueue, emptyBuffer, EMPTY_BUFFER_SAMPLES * 2 * sizeof(int16_t));
        } else
        {
//            LOGD("audio frame sample count = %d, pts = %ld", data->sampleCount, data->pts);
            memcpy(emptyBuffer, data->data, data->sampleCount * 2 * sizeof(int16_t));
            (*playerBufferQueue)->Enqueue(playerBufferQueue, emptyBuffer, data->sampleCount * 2 * sizeof(int16_t));
            provider->putBackUsed(data);
        }
    }
    providerLock.unlock();
}

getAudioFrame()函数逻辑:

AudioFrame *MediaPlayerController::getAudioFrame() {
    LOGD("audio frame,");
    AudioFrame *frame = audioQueue->get();
    if(!frame)
    {
        //播放完成
        LOGD("audio play end");
        return nullptr;
    }
    currentPositionMS = frame->pts;
    LOGV("audio frame, currentPositionMS = %ld", currentPositionMS);
    while(1)
    {
        if(waitVFrame == nullptr)
        {
            waitVFrame = videoQueue->get();
            if(waitVFrame == nullptr)
            {
                break;
            }

            LOGV("audio frame, pts = %ld, videoQueue.size = %d", waitVFrame->pts, videoQueue->getSize());
        }

        LOGV("audio frame, pts = %ld", waitVFrame->pts);

        if(waitVFrame->pts - currentPositionMS < -50)
        {
            ///this video too late, discard it. And continue this loop until get a AVFrame is not too late
            LOGV("audio frame video too late, discard");
            videoQueue->putToUsed(waitVFrame);
            waitVFrame = nullptr;
            continue;
        } else if(waitVFrame->pts - currentPositionMS > 50)
        {
            //this video is still too early, wait. break this loop to let the AVFrame return. And will check this video frame at next getAVFrame call.
            LOGV("audio frame video too early, wait. video is early %ld ms than audio", waitVFrame->pts - frame->pts);
            break;
        } else
        {
            ///it is time to refresh this AVFrame.
            if(videoPlayer->isReady())
            {
                LOGV("audio frame :refresh image");
                unique_lock<mutex> AVFrameLock(videoMu);
                nextVFrame = waitVFrame;
                waitVFrame = nullptr;
                //videoPlayer->renderSurface(nextVFrame->data);
                videoQueue->putToUsed(nextVFrame);
                AVFrameLock.unlock();
            } else
            {
                ///videoPlayer not ready(caused window not set), discard this video frame
                LOGE("audio frame ,videoPlayer not prepared, discard this AVFrame");
                videoQueue->putToUsed(waitVFrame);
                waitVFrame = nullptr;
            }
            break;
        }
    }
    return frame;
}

GitHub:https://github.com/PushedChris/Android_Media/tree/player_opengl_AV
csdn:https://editor.csdn.net/md/?articleId=113884706

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值