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

Nuplayer 音视频同步学习笔记

程序员文章站 2022-07-01 15:37:49
...

目录

 

1. 处理解码之后的数据

(1) handleAnOutputBuffer

(2). queueBuffer 

2. AudioBuffer的处理

(1) postDrainAudioQueue_l

(2) onDrainAudioQueue

3. Video Buffer的处理

(1) postDrainVideoQueue

(2) onDrainVideoQueue

4. AVsync Audio更新锚点时间

(1) AVsync原理

(2) Audi如何更新锚点

5. AVsync Video 获取显示的时间(系统时间)

(1) nowMediaUs 当前播放媒体时间

(2) outRealUs Buffer的显示时间


1. 处理解码之后的数据

(1) handleAnOutputBuffer

    NuPlayerDecoder调用handleAnOutputBuffer处理解码之后的音视频数据.  函数最终会调用NuPlayerDecoderRenderer::queueBuffer处理这个Buffer

bool NuPlayer::Decoder::handleAnOutputBuffer(size_t index, size_t offset, size_t size, 
                                             int64_t timeUs, int32_t flags) {
    sp<MediaCodecBuffer> buffer;
    //根据Index从MediaCodec获取Buffer
    mCodec->getOutputBuffer(index, &buffer);
    mOutputBuffers.editItemAt(index) = buffer;
    // 把offset size timeUs信息添加到buffer中
    // 其中timeUs是buffer的媒体时间(Buffer在媒体文件的位置)
    buffer->setRange(offset, size);
    buffer->meta()->clear();
    buffer->meta()->setInt64("timeUs", timeUs);
    // 创建一个消息kWhatRenderBuffer, 消息会被传入到NuplayerRenderer,
    // 当Buffer被Renderer处理完成后,就会发送这个消息消息
    sp<AMessage> reply = new AMessage(kWhatRenderBuffer, this);
    reply->setSize("buffer-ix", index);
    reply->setInt32("generation", mBufferGeneration);

    // 调用NuplayerRenderer 的 queueBuffer
    mRenderer->queueBuffer(mIsAudio, buffer, reply);

(2). queueBuffer 

    把音视频的buffer添加到Render的队列 mAudioQueue(audio) mVideoQueue(!audio)
    <1> queueBuffer发送消息kWhatQueueBuffer调用Renderer::onQueueBuffer

void NuPlayer::Renderer::queueBuffer(bool audio, const sp<MediaCodecBuffer> &buffer, const sp<AMessage> &notifyConsumed) {
    int64_t mediaTimeUs = -1;
    buffer->meta()->findInt64("timeUs", &mediaTimeUs);
    // 发送消息kWhatQueueBuffer, 调用onQueueBuffer
    // 把Decoder传入的消息notifyConsumed放入到新创建的msg
    sp<AMessage> msg = new AMessage(kWhatQueueBuffer, this);
    msg->setInt32("queueGeneration", getQueueGeneration(audio));
    msg->setInt32("audio", static_cast<int32_t>(audio));
    msg->setObject("buffer", buffer);
    msg->setMessage("notifyConsumed", notifyConsumed);
    msg->post();

<2> 把音视频Buffer添加到队列mAudioQueue, mVideoQueue。调用postDrainAudioQueue_l, postDrainVideoQueue处理各自队列中的Buffer

void NuPlayer::Renderer::onQueueBuffer(const sp<AMessage> &msg)
    // 判断是Audio数据还是Video的数据
    if (audio)  mHasAudio = true;
    else        mHasVideo = true;
    //创建并初始化一个mVideoScheduler
    if (mHasVideo)
        if (mVideoScheduler == NULL)
            mVideoScheduler = new VideoFrameScheduler();
            mVideoScheduler->init();
    //创建一个队列的节点用来保存buffer的信息。
    CHECK(msg->findMessage("notifyConsumed", &notifyConsumed));
    QueueEntry entry;
    entry.mBuffer = buffer;
    // Decoder传入的消息kWhatRenderBuffer被放入到节点
    entry.mNotifyConsumed = notifyConsumed;
    entry.mOffset = 0;
    entry.mFinalResult = OK;
    entry.mBufferOrdinal = ++mTotalBuffersQueued;

    // 把创建的节点push到音频数据的队列和视频数据的队列 mAudioQueue  mVideoQueue
    // 调用postDrainAudioQueue_l 或 postDrainVideoQueue,清空音视频数据的队列
    if (audio) {
        mAudioQueue.push_back(entry);
        postDrainAudioQueue_l();
    } else {
        mVideoQueue.push_back(entry);
        postDrainVideoQueue();
    }

2. AudioBuffer的处理

(1) postDrainAudioQueue_l

调用onDrainAudioQueue来处理Auido队列的Buffer

void NuPlayer::Renderer::postDrainAudioQueue_l(int64_t delayUs)
    //发送消息 kWhatDrainAudioQueue, 调用 
    sp<AMessage> msg = new AMessage(kWhatDrainAudioQueue, this);
    msg->setInt32("drainGeneration", mAudioDrainGeneration);
    msg->post(delayUs);
case kWhatDrainAudioQueue
    // 接下来主要的工作在onDrainAudioQueue中完成
    if (onDrainAudioQueue()) {
        // 函数onDrainAudioQueue,当AudioQueue的数据没有处理完的情况下,会返回true
        // 返回true的情况下,需要延时delayUs再次调用onDrainAudioQueue处理AudioQueue
        // 1) 调用AudioTrack的getPosition获得Audio已经播放的数据帧的个数.
        uint32_t numFramesPlayed;
        mAudioSink->getPosition(&numFramesPlayed)
        // 2) 已经写入到Audio但是还没有播放的数据(主要是在Audio侧Buffer中的数据)
        uint32_t numFramesPendingPlayout =mNumFramesWritten - numFramesPlayed;
        // 3) delayUs: 需要延时delayUs之后,最新写入Audio的数据才会开始播放
        int64_t delayUs = mAudioSink->msecsPerFrame()
                          * numFramesPendingPlayout * 1000ll;
        // 4) 根据播放的速率 调整delayUs
        if (mPlaybackRate > 1.0f) delayUs /= mPlaybackRate;
        // 5) 调整delayUs,防止Audio Buffer的数据被清空
        delayUs /= 2;
        // 6) 调用postDrainAudioQueue_l处理AudioQueue的数据
        // 传入 delayUs
        postDrainAudioQueue_l(delayUs);

(2) onDrainAudioQueue

更新锚点时间, 把AudioBuffer传给AudioSink播放
     1) 循环处理mAudioQueue中的节点, 直到mAudioQueue中的buffer被清空 
     2) 尝试更新锚点时间 onNewAudioMediaTime()
     3) 把数据写入到AudioSink
     4) Buffer被处理完, 通知Decoder
     5) 更新MediaClock中的maxTimeMedia
     6) 判断mAudioQueue是否还有数据

bool NuPlayer::Renderer::onDrainAudioQueue()
    uint32_t prevFramesWritten = mNumFramesWritten;
    // 1) 循环处理mAudioQueue中的节点, 直到mAudioQueue中的buffer被清空
    while (!mAudioQueue.empty()) {
        // 处理当前的头节点
        QueueEntry *entry = &*mAudioQueue.begin();
        // Buffer为空的情况
        if (entry->mBuffer == NULL) {
            // EOS
        }
        // 2) 尝试更新锚点时间,之后会对锚点时间以及如何更新详细介绍
        // 第一次处理这个节点, 如果需要, 尝试通过媒体时间更新锚点时间
        // mOffset Buffer中已经有mOffset大小的数据被处理
        if (entry->mOffset == 0 && entry->mBuffer->size() > 0) {
            int64_t mediaTimeUs;
            // 从Buffer的数据中, 获得媒体的时间(当前Buffer在媒体文件中的位置)
            CHECK(entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs));
            // 尝试更新锚点时间
            onNewAudioMediaTime(mediaTimeUs);
        }

        // 3) 把数据写入到AudioSink
        // 需要写入copy大小的的数据
        size_t copy = entry->mBuffer->size() - entry->mOffset;
        // 调用AudioSink的Write把数据写到AudioTrack
        ssize_t written = mAudioSink->write(entry->mBuffer->data() + entry->mOffset,
                                            copy, false /* blocking */);
        // 根据写入的数据, 更新Offset
        entry->mOffset += written;
        // Buffer中还有remainder的数据需要处理
        size_t remainder = entry->mBuffer->size() - entry->mOffset;

        // 4) Buffer被处理完, 通知Decoder
        // 剩余的数据小于一帧(一个Audio的采样点), 当前Buffer已经被处理完
        if ((ssize_t)remainder < mAudioSink->frameSize()) {
            // 通知Decoder当前buffer已经被处理完
            // mNotifyConsumed: Decoder传入的消息kWhatRenderBuffer
            entry->mNotifyConsumed->post();
            mAudioQueue.erase(mAudioQueue.begin());
            entry = NULL;
        // 更新copiedFrames:已经写入到Audio的数据
        size_t copiedFrames = written / mAudioSink->frameSize();
        mNumFramesWritten += copiedFrames;
// 5) 更新MediaClock中的maxTimeMedia
    // (上面写入的Buffer的最后一帧的媒体时间)
    // 媒体时间, Buffer在媒体文件的位置, 可以理解为进度条的时间
    // 锚点媒体时间戳加上新写入帧数对应的时长,即为媒体时间戳最大值
    // mAnchorTimeMediaUs +   
    // 锚点媒体时间, 最新的锚点对应的buffer的MediaTime
    // (mNumFramesWritten - mAnchorNumFramesWritten)  
    // 上次锚点之后,新写入的帧数
    //  * mAudioSink->msecsPerFrame() * 1000LL        
    // 每一帧播放的时间(delay)
    int64_t maxTimeMedia;
    maxTimeMedia = mAnchorTimeMediaUs +
      (int64_t)(max((long long)mNumFramesWritten - mAnchorNumFramesWritten, 0LL)
           * 1000LL * mAudioSink->msecsPerFrame());
        mMediaClock->updateMaxTimeMedia(maxTimeMedia);

// 6) 如果mAudioQueue还有数据没有处理返回true, 需要重新调用onDrainAudioQueue处理
    bool reschedule = !mAudioQueue.empty()
            && (!mPaused || prevFramesWritten != mNumFramesWritten); 
   return reschedule;

3. Video Buffer的处理

(1) postDrainVideoQueue

计算数据应该在什么时间显示, 根据这个时间延时发送kWhatDrainVideoQueue消息, 调用onDrainVideoQueue
  1) 根据Buffer的媒体时间,获得Buffer显示的系统时间(数据应该在这个时间显示)
  2) 计算出合适的发送kWhatDrainVideoQueue消息的延时时间
  3) 发送消息kWhatDrainVideoQueue

void NuPlayer::Renderer::postDrainVideoQueue()
    QueueEntry &entry = *mVideoQueue.begin();
    // 准备发送消息kWhatDrainVideoQueue,
    sp<AMessage> msg = new AMessage(kWhatDrainVideoQueue, this);
    msg->setInt32("drainGeneration", getDrainGeneration(false /* audio */));
    // 1) 根据Buffer的媒体时间,获得Buffer显示的系统时间(数据应该在这个时间显示)
    // 获得当前系统的时间
    int64_t nowUs = ALooper::GetNowUs();
    // 获取当前Buffer的媒体时间(当前Buffer在媒体文件的位置)
    entry.mBuffer->meta()->findInt64("timeUs", &mediaTimeUs);
    // 获取当前Buffer应该在什么时间显示(数据显示的系统的时间)
    // 主要根据mediaTimeUs来计算, 需要依赖Audio侧更新的锚点时间
    // 如果获得realTimeUs和delayUs有问题
    // 通常需要检查Audio侧更新的锚点时间和Audio getTimestamp或getPosition的返回值
    realTimeUs = getRealTimeUs(mediaTimeUs, nowUs);

    // 2) 计算出合适的发送kWhatDrainVideoQueue消息的延时时间, 
    // delayUs, 当前Buffer在delayUs之后显示
    delayUs = realTimeUs - nowUs;
    // Video的Buffer来的太早, 或锚点时间有问题,延时重新调用postDrainVideoQueue
    if (delayUs > 500000) {
        postDelayUs = 500000;
    if (postDelayUs >= 0) {
        msg->setWhat(kWhatPostDrainVideoQueue);
        msg->post(postDelayUs);
        mVideoScheduler->restart();

    // 利用VideoScheduler更新realTimeUs和delayUs
    realTimeUs = mVideoScheduler->schedule(realTimeUs * 1000) / 1000;
    int64_t twoVsyncsUs = 2 * (mVideoScheduler->getVsyncPeriod() / 1000);
    delayUs = realTimeUs - nowUs;
    // 3) 发送消息kWhatDrainVideoQueue调用onDrainVideoQueue
    //如果 delayUs大于2倍的Vsync, 延时delayUs减去2倍的Vsync的时间发送kWhatDrainVideoQueue
    //否则立即发送kWhatDrainVideoQueue, 立即发送kWhatDrainVideoQueue,处理buffer
    msg->post(delayUs > twoVsyncsUs ? delayUs - twoVsyncsUs : 0);

(2) onDrainVideoQueue

重新计算buffer显示的系统时间realTimeUs, 通知Decoder Buffer已经处理完
发送realTimeUs 和 tooLate的信息

void NuPlayer::Renderer::onDrainVideoQueue() {
    // 取出第一个Buffer
    QueueEntry *entry = &*mVideoQueue.begin();
    // 当前Real系统的时间
    int64_t nowUs = ALooper::GetNowUs();
    // 获取媒体时间
    entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs);
    // 显示的Real系统时间
    realTimeUs = getRealTimeUs(mediaTimeUs, nowUs);

    bool tooLate = false;
    if (!mPaused) {
        // 视频的数据来晚了nowUs - realTimeUs的时间
            // mVideoLateByUs = nowUs - realTimeUs
        setVideoLateByUs(nowUs - realTimeUs);
        // 视频晚了40ms
        tooLate = (mVideoLateByUs > 40000);

    // 通知Decoder当前buffer已经被处理完, 发送realTimeUs和tooLate
    entry->mNotifyConsumed->setInt64("timestampNs", realTimeUs * 1000ll);
    entry->mNotifyConsumed->setInt32("render", !tooLate);
    entry->mNotifyConsumed->post();
    mVideoQueue.erase(mVideoQueue.begin());
    entry = NULL;

4. AVsync Audio更新锚点时间

(1) AVsync原理

系统时间和媒体时间应该是线性关系: (mediaTimeUs - anchorTimeMediaUs) = PlaybackRate*(nowUs - anchorTimeMediaUs)。

所以理论上,我们可以根据锚点, 计算出任意一点的媒体时间对应的系统时间(Buffer应该播放的时间). AVsync 的进本机理就是通过Audio每隔一段时间更新锚点, Video的Buffer根据锚点和媒体时间计算出应该播放的时(系统时间)

更新锚点时间 anchorTimeMediaUs anchorTimeRealUs
anchorTimeRealUs  更新锚点时的 系统时间
anchorTimeMediaUs 更新锚点时的 媒体时间(正在播放的媒体的时间)
下面我们主要来看一下Audi如何更新锚点

(2) Audi如何更新锚点

anchorTimeRealUs = nowUs
锚点系统时间被设置为当前系统时间, 所以 anchorTimeMediaUs 就应该当前正在播放的媒体时间,接下来重点是如何确定anchorTimeMediaUs. 

<1> 如何计算当前正在播放的媒体时间

当前正在播放的媒体时间 = 正在写入的媒体时间 - 已经写入但是没有播放的数据需要播放的时间
pendingAudioPlayoutDurationUs: 已经写入但是没有播放的数据需要播放的时间(主要是在AudioBuffer里面的数据)
anchorTimeMediaUs = mediaTimeUs - pendingAudioPlayoutDurationUs;

<2> 计算已经写入但是没有播放的数据需要播放的时间
writtenAudioDurationUs: 已经写入的数据的持续时间
PlayedOutDurationUs: 当前已经播放的数据的持续时间
两者相减就是pendingAudioPlayoutDurationUs
pendingAudioPlayoutDurationUs = writtenAudioDurationUs - PlayedOutDurationUs
<3> 已经写入的数据的持续时间
mNumFramesWritten 已经写入的数据的帧的个数 * 每一帧多少时间(1000000LL / sampleRate)
writtenAudioDurationUs = mNumFramesWritten * (1000000LL / sampleRate)
 <4> 计算当前已经播放的数据的持续时间
ts.mPosition * 1000000LL/mSampleRateHz 在 ts.mTime时间已经播放的数据的时间
因为nowUs与ts.mTime不相等, 最后需要根据nowUs进行调整
PlayedOutDurationUs = ts.mPosition * 1000000LL/mSampleRateHz + nowUs - ts.mTime

void NuPlayer::Renderer::onNewAudioMediaTime(int64_t mediaTimeUs)
    // 设置初始锚点媒体时间
    setAudioFirstAnchorTimeIfNeeded_l(mediaTimeUs);

    int64_t nowUs = ALooper::GetNowUs();
    if (mNextAudioClockUpdateTimeUs >= 0)
        //是否需要更新锚点时间, 根据kMinimumAudioClockUpdatePeriodUs的时间
        if (nowUs >= mNextAudioClockUpdateTimeUs) 
            //  (1) nowMediaUs:  当前正在播放的媒体时间
            //  mediaTimeUs: 当前正在写入到Audio的数据的媒体时间
            //  getPendingAudioPlayoutDurationUs 
            //  已经写入到Audio但是还没有播放的数据持续时间
            int64_t nowMediaUs = mediaTimeUs - 
                                 getPendingAudioPlayoutDurationUs(nowUs);
            //  根据nowMediaUs和mediaTimeUs更新锚点时间
            mMediaClock->updateAnchor(nowMediaUs, nowUs, mediaTimeUs);
            mNextAudioClockUpdateTimeUs = nowUs + kMinimumAudioClockUpdatePeriodUs;
 
// (2) 需要分析一下getPendingAudioPlayoutDurationUs
// 计算方法使用 writtenAudioDurationUs - PlayedOutDurationUs
int64_t NuPlayer::Renderer::getPendingAudioPlayoutDurationUs(int64_t nowUs)
    // (3) writtenAudioDurationUs 已经写入的数据的持续时间
    // mNumFramesWritten * (1000000LL / sampleRate)
    // 写入的帧的个数 * 每一帧的持续时间(us)
    // (1000000LL / sampleRate): sampleRate一秒的采样数, 取倒数每一个采样的持续时间
    int64_t writtenAudioDurationUs = 
             getDurationUsIfPlayedAtSampleRate(mNumFramesWritten);
    // PlayedOutDurationUs 已经播放时间, 需要从Audio侧获取
    return writtenAudioDurationUs - mAudioSink->getPlayedOutDurationUs(nowUs);

//(4) 当前已经播放的数据的持续时间
int64_t MediaPlayerService::AudioOutput::getPlayedOutDurationUs(int64_t nowUs)
    // 从Audio侧获取已经获取当前播放的帧,和对应的系统时间
    // 注意ts.mPosition并不是正在播放的帧的位置, 
    // 应该是ts.mTime这个系统时间点正在播放的帧的位置
    // ts.mTime与当前时间nowUs并不相等,会有ms级的差别
    status_t res = mTrack->getTimestamp(ts);
    if (res == OK)
        numFramesPlayed = ts.mPosition;
        numFramesPlayedAt = ts.mTime.tv_sec * 1000000LL + ts.mTime.tv_nsec / 1000;
    //  numFramesPlayed * 1000000LL / mSampleRateHz: ts.mTime时间点已经播放的时间
    // 最后计算的时候需要考虑ts.mTime与当前时间nowUs之间的差异
    // durationUs  nowUs时间点已经播放的时间(正在播放的媒体时间)
    int64_t durationUs = (int64_t)((int32_t)numFramesPlayed * 1000000LL / 
                         mSampleRateHz) + nowUs - numFramesPlayedAt;


// (5) 调用MediaClock::updateAnchor更新锚点
//    传入参数:
//    anchorTimeMediaUs     正在播放的媒体时间
//    anchorTimeRealUs      anchorTimeMediaUs对应的系统时间
void MediaClock::updateAnchor(int64_t anchorTimeMediaUs, 
                              int64_t anchorTimeRealUs, int64_t maxTimeMediaUs)
    // 获得当前的系统时间, 可能与anchorTimeRealUs有差别
    int64_t nowUs = ALooper::GetNowUs();
    // 获得当前正在播放的媒体时间 nowMediaUs
    int64_t nowMediaUs =
        anchorTimeMediaUs + (nowUs - anchorTimeRealUs) * (double)mPlaybackRate;
    // 更新当前播放的媒体时间为锚点媒体时间
    // 更新当前系统时间为锚点系统时间
    mAnchorTimeRealUs = nowUs;
    mAnchorTimeMediaUs = nowMediaUs;

5. AVsync Video 获取显示的时间(系统时间)

     AVsync的目的是获得Buffer显示的时间(buffer的系统时间).我们可以根据媒体时间和系统时间的线性关系计算出显示的时间
(mediaTimeUs - anchorTimeMediaUs) = PlaybackRate*(nowUs - anchorTimeMediaUs)

(1) nowMediaUs 当前播放媒体时间

根据当前的系统时间和锚点时间计算出当前播放媒体时间
nowMediaUs = mAnchorTimeMediaUs + (realUs - mAnchorTimeRealUs) * mPlaybackRate

(2) outRealUs Buffer的显示时间

根据当前播放的媒体时间和系统时间, 计算出Buffer的显示时间(系统时间)
outRealUs = (targetMediaUs - nowMediaUs) / (double)mPlaybackRate + nowUs

// outRealUs 获得当前Buffer播放的系统时间(应该在这个时间点播放)
// 传入参数 targetMediaUs. 当前Buffer的媒体时间
status_t MediaClock::getRealTimeFor(int64_t targetMediaUs, int64_t *outRealUs)
    int64_t nowUs = ALooper::GetNowUs();
    // (1) nowMediaUs video正在播放的媒体时间, nowUs对应的媒体时间
    getMediaTime_l(nowUs, &nowMediaUs, true /* allowPastMaxTime */);
    // (2) 计算出Buffer的显示时间
    *outRealUs = (targetMediaUs - nowMediaUs) / (double)mPlaybackRate + nowUs;

// outMediaUs video正在播放的媒体时间, nowUs对应的媒体时间
// realUs  当前系统时间
status_t MediaClock::getMediaTime_l(
        int64_t realUs, int64_t *outMediaUs, bool allowPastMaxTime)
    //mediaUs 当前Audio正在播放的媒体时间, 对应video正在播放的媒体时间
    int64_t mediaUs = mAnchorTimeMediaUs
            + (realUs - mAnchorTimeRealUs) * (double)mPlaybackRate;
    *outMediaUs = mediaUs;