Android 13 – Media Framework (10) – NuPlayer::Renderer

В этом разделе мы узнаем, как работает NuPlayer Renderer и как работает механизм avsync.

1. Создать рендерер

void NuPlayer::onStart(int64_t startPositionUs, MediaPlayerSeekMode mode) {
    
    
    if (mSource->isRealTime()) {
    
    
        flags |= Renderer::FLAG_REAL_TIME;
    }
......
    if (mOffloadAudio) {
    
    
        flags |= Renderer::FLAG_OFFLOAD_AUDIO;
    }
......
    sp<AMessage> notify = new AMessage(kWhatRendererNotify, this);
    ++mRendererGeneration;
    notify->setInt32("generation", mRendererGeneration);
    mRenderer = new Renderer(mAudioSink, mMediaClock, notify, flags);
    mRendererLooper = new ALooper;
    mRendererLooper->setName("NuPlayerRenderer");
    mRendererLooper->start(false, false, ANDROID_PRIORITY_AUDIO);
    mRendererLooper->registerHandler(mRenderer);
}

После вызова метода запуска NuPlayer будет создан Renderer, а переданными параметрами будут сообщение обратного вызова, AudioSink, MediaClock и флаги. Вы можете видеть, что NuPlayer также использует генерацию для управления состоянием Renderer. Если вы не понимаете, как используется генерация, вы можете прочитать предыдущее примечание.

Далее мы объясним значение нескольких параметров:

  • AudioSink: Это базовый класс, и на самом деле ему передается объект его подкласса AudioOutput, который реализован в MediaPlayerService.cpp. В AudioOutput инкапсулировано то AudioTrack, что если вы хотите узнать, как использовать AudioTrack, вы можете обратиться к AudioOutput. Здесь, в Renderer, после того, как декодер декодирует аудиоданные, он напрямую записывает данные в AudioOutput;
  • MediaClock: это системные часы, используемые для записи системного времени;
  • flags: Перед созданием средства рендеринга будет проанализирован используемый им флаг. Сначала будет оценено, является ли источником RealTime. Возвращаемое значение Source.isRealTime по умолчанию — false. Только RTSPSource возвращает true. Здесь вы можете догадаться, что если это является источником прямой трансляции, то в процессе avsync должны возникнуть некоторые проблемы. Там же; затем будет оценено, поддерживает ли текущий аудиопоток режим разгрузки. Режим разгрузки означает запись сжатых аудиоданных непосредственно в AudioTrack и непосредственное декодирование и воспроизведение В обычном режиме сжатые аудиоданные необходимо отправить в аудиодекодер для декодирования.Выведите данные PCM, а затем запишите их в AudioTrack. Если перейти в режим разгрузки, будет использоваться аудиодекодер NuPlayerDecoderPassThrough, поэтому процесс записи аудиоданных в рендерере тоже нужно изменить.
status_t NuPlayer::instantiateDecoder(bool audio, sp<DecoderBase> *decoder, bool checkAudioModeChange) {
    
    
    if (audio) {
    
    
        if (checkAudioModeChange) {
    
    
        	// 判断是否需要开启 offload mode
            determineAudioModeChange(format);
        }
        if (mOffloadAudio) {
    
    
            mSource->setOffloadAudio(true /* offload */);
            const bool hasVideo = (mSource->getFormat(false /*audio */) != NULL);
            format->setInt32("has-video", hasVideo);
            *decoder = new DecoderPassThrough(notify, mSource, mRenderer);
            ALOGV("instantiateDecoder audio DecoderPassThrough  hasVideo: %d", hasVideo);
        } else {
    
    
            *decoder = new Decoder(notify, mSource, mPID, mUID, mRenderer);
            ALOGV("instantiateDecoder audio Decoder");
        }
        mAudioDecoderError = false;
    }
}

void NuPlayer::determineAudioModeChange(const sp<AMessage> &audioFormat) {
    
    
    if (canOffload) {
    
    
        if (!mOffloadAudio) {
    
    
            mRenderer->signalEnableOffloadAudio();
        }
        // open audio sink early under offload mode.
        tryOpenAudioSinkForOffload(audioFormat, audioMeta, hasVideo);
    } else {
    
    
        if (mOffloadAudio) {
    
    
            mRenderer->signalDisableOffloadAudio();
            mOffloadAudio = false;
        }
    }
}

При создании AudioDecoder будет вызван метод defineAudioModeChange, чтобы еще раз определить, поддерживается ли режим разгрузки (первое решение было кратко упомянуто в главе NuPlayer). Если он поддерживается, сначала будет использоваться режим разгрузки (для экономии производительности), а рендерер метод вызывается, чтобы попытаться открыть audio hal openAudioSinkи настроить режим разгрузки; если режим разгрузки не поддерживается, AudioTrack временно не будет создан.

В предыдущей статье о декодере мы упоминали, что декодер вызовет changeAudioFormatметод после получения события Audio Output Format Changed.Если он не находится в режиме разгрузки, здесь будет вызван openAudioSink для создания обычного AudioTrack. Другими словами, в обычном режиме AudioTrack не будет создан до тех пор, пока аудиоданные не будут фактически декодированы.

ps: Если вы хотите узнать, как использовать обычный режим AudioTrack и режим разгрузки, вы можете обратиться к NuPlayer, Renderer и NuPlayerDecoderPassThrough.

В следующем материале мы пока будем рассматривать только AudioTrack.Нормальный режим

2、очередьбуфер

У Renderer нет метода start. Avsync запускается автоматически, когда вызывается очередьBuffer для записи выходного буфера в Renderer.

void NuPlayer::Renderer::queueBuffer(
        bool audio,
        const sp<MediaCodecBuffer> &buffer,
        const sp<AMessage> &notifyConsumed) {
    
    
    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();
}

Трюк генерации также используется в Renderer.Переданные параметры будут инкапсулированы в новый AMessage и отправлены в ALooper.Наконец, сообщение обрабатывается через onQueueBuffer:

void NuPlayer::Renderer::onQueueBuffer(const sp<AMessage> &msg) {
    
    
    int32_t audio;
    CHECK(msg->findInt32("audio", &audio));
	// 判断 buffer 是否因为 generation变化要 drop
    if (dropBufferIfStale(audio, msg)) {
    
    
        return;
    }

    if (audio) {
    
    
        mHasAudio = true;
    } else {
    
    
        mHasVideo = true;
    }
	// 如果是 video 则需要创建 VideoFrameScheduler,这是用于获取 vsync,这里不做研究
    if (mHasVideo) {
    
    
        if (mVideoScheduler == NULL) {
    
    
            mVideoScheduler = new VideoFrameScheduler();
            mVideoScheduler->init();
        }
    }

    sp<RefBase> obj;
    CHECK(msg->findObject("buffer", &obj));
    sp<MediaCodecBuffer> buffer = static_cast<MediaCodecBuffer *>(obj.get());

    sp<AMessage> notifyConsumed;
    CHECK(msg->findMessage("notifyConsumed", &notifyConsumed));
	// 将 Message 中的内容重新封装到 QueueEntry
    QueueEntry entry;
    entry.mBuffer = buffer;
    entry.mNotifyConsumed = notifyConsumed;
    entry.mOffset = 0;
    entry.mFinalResult = OK;
    entry.mBufferOrdinal = ++mTotalBuffersQueued;
	// 发消息处理 Queue 中的 entry
    if (audio) {
    
    
        Mutex::Autolock autoLock(mLock);
        mAudioQueue.push_back(entry);
        postDrainAudioQueue_l();
    } else {
    
    
        mVideoQueue.push_back(entry);
        postDrainVideoQueue();
    }
	// SyncQueue
    Mutex::Autolock autoLock(mLock);
    if (!mSyncQueues || mAudioQueue.empty() || mVideoQueue.empty()) {
    
    
        return;
    }

    sp<MediaCodecBuffer> firstAudioBuffer = (*mAudioQueue.begin()).mBuffer;
    sp<MediaCodecBuffer> firstVideoBuffer = (*mVideoQueue.begin()).mBuffer;

    if (firstAudioBuffer == NULL || firstVideoBuffer == NULL) {
    
    
        // EOS signalled on either queue.
        syncQueuesDone_l();
        return;
    }

    int64_t firstAudioTimeUs;
    int64_t firstVideoTimeUs;
    CHECK(firstAudioBuffer->meta()
            ->findInt64("timeUs", &firstAudioTimeUs));
    CHECK(firstVideoBuffer->meta()
            ->findInt64("timeUs", &firstVideoTimeUs));

    int64_t diff = firstVideoTimeUs - firstAudioTimeUs;

    ALOGV("queueDiff = %.2f secs", diff / 1E6);

    if (diff > 100000LL) {
    
    
        // Audio data starts More than 0.1 secs before video.
        // Drop some audio.

        (*mAudioQueue.begin()).mNotifyConsumed->post();
        mAudioQueue.erase(mAudioQueue.begin());
        return;
    }

    syncQueuesDone_l();
}

onQueueBuffer выглядит длинным, но мало что делает:

  1. Повторно инкапсулируйте содержимое сообщения в QueueEntry и добавьте его в соответствующий список;
  2. Вызовите postDrainAudioQueue_l/postDrainVideoQueue, чтобы отправить сообщение в запись в списке обработки;
  3. Определите, нужен ли SyncQueue;

2.1、Очередь синхронизации

Давайте сначала поговорим о механизме SyncQueue, он должен делатьНачать синхронизацию трансляцииИспользуется, если разрыв между точками аудио и видео слишком велик при запуске трансляции, этот механизм будет использоваться для удаления выходных данных.

В Renderer не включен механизм SyncQueue и функция не завершена.Почему вы это говорите? Потому что mSyncQueuestrue в коде не задано, и помимо выполнения SyncQueue при запуске трансляции, думаю, синхронизацию нужно еще и после промывки сделать.

Основная идея механизма SyncQueue заключается в определении точек первых элементов двух команд при поступлении и видео, и аудио данных. Если время Video позже, чем Audio, то Audio отбрасывается первым. После синхронизации SyncQueue флаг может быть установлен в значение false.

Выше мы упоминали, что onQueueBuffer сначала вызовет postDrainAudioQueue_l/postDrainVideoQueue для отправки сообщений, но если вы действительно посмотрите на код, то обнаружите, что после ввода этих двух методов вы определите, нужно ли вам делать SyncQueue. контент не будет выполнен.

2.2、postDrainAudioQueue_l

void NuPlayer::Renderer::postDrainAudioQueue_l(int64_t delayUs) {
    
    
	// 暂停、syncqueue、offload 直接退出
    if (mDrainAudioQueuePending || mSyncQueues || mUseAudioCallback) {
    
    
        return;
    }

    if (mAudioQueue.empty()) {
    
    
        return;
    }

    // FIXME: if paused, wait until AudioTrack stop() is complete before delivering data.
    if (mPaused) {
    
    
        const int64_t diffUs = mPauseDrainAudioAllowedUs - ALooper::GetNowUs();
        if (diffUs > delayUs) {
    
    
            delayUs = diffUs;
        }
    }
	// 如果暂停了就延时写入audioTrack
    mDrainAudioQueuePending = true;
    sp<AMessage> msg = new AMessage(kWhatDrainAudioQueue, this);
    msg->setInt32("drainGeneration", mAudioDrainGeneration);
    msg->post(delayUs);
}

Прежде чем использовать postDrainAudioQueue_l для отправки сообщения, оно сначала оценивается, а затем решается, отправлять ли его:

  1. Если вызывается пауза, вам нужно дождаться полной остановки AudioTrack перед записью, поэтому сообщение необходимо задержать;
  2. Если он находится в процессе обработки задержки паузы или перезаписи, или в процессе обработки SyncQueue, или в режиме разгрузки, он выйдет напрямую и больше не будет отправлять сообщения;
void NuPlayer::Renderer::onMessageReceived(const sp<AMessage> &msg) {
    
    
	switch (msg->what()) {
    
    
        case kWhatDrainAudioQueue:
        {
    
    
            mDrainAudioQueuePending = false;
			// 检查 generation
            int32_t generation;
            CHECK(msg->findInt32("drainGeneration", &generation));
            if (generation != getDrainGeneration(true /* audio */)) {
    
    
                break;
            }
			// 写入 audiotrack
            if (onDrainAudioQueue()) {
    
    
                uint32_t numFramesPlayed;
                CHECK_EQ(mAudioSink->getPosition(&numFramesPlayed),
                         (status_t)OK);
				// ......
				// 重写处理
                postDrainAudioQueue_l(delayUs);
            }
            break;
        }
    }
}

Основные шаги kWhatDrainAudioQueue следующие:

  1. Проверить генерацию, чтобы определить, нужно ли прекращать запись данных в AudioTrack;
  2. Вызовите onDrainAudioQueue, чтобы записать все данные из списка в AudioTrack;
  3. Если все данные в List не были записаны (кольцевой буфер заполнен), то вычисляем время задержки, вызываем postDrainAudioQueue_l для повторной отправки сообщения о задержке и ждем записи.
bool NuPlayer::Renderer::onDrainAudioQueue() {
    
    
    // do not drain audio during teardown as queued buffers may be invalid.
    if (mAudioTornDown) {
    
    
        return false;
    }
    // 获取当前已经播放的帧数
	uint32_t numFramesPlayed;
	mAudioSink->getPosition(&numFramesPlayed);
    uint32_t prevFramesWritten = mNumFramesWritten;
    while (!mAudioQueue.empty()) {
    
    
        QueueEntry *entry = &*mAudioQueue.begin();

        if (entry->mBuffer == NULL) {
    
    
        	// buffer 等于 null 会有两种情况,一种是 mNotifyConsumed 不等 null,另一种是 等于 null
            if (entry->mNotifyConsumed != nullptr) {
    
    
                // TAG for re-open audio sink.
                onChangeAudioFormat(entry->mMeta, entry->mNotifyConsumed);
                mAudioQueue.erase(mAudioQueue.begin());
                continue;
            }

            // EOS
            if (mPaused) {
    
    
                // Do not notify EOS when paused.
                // This is needed to avoid switch to next clip while in pause.
                ALOGV("onDrainAudioQueue(): Do not notify EOS when paused");
                return false;
            }

            int64_t postEOSDelayUs = 0;
            if (mAudioSink->needsTrailingPadding()) {
    
    
                postEOSDelayUs = getPendingAudioPlayoutDurationUs(ALooper::GetNowUs());
            }
            notifyEOS(true /* audio */, entry->mFinalResult, postEOSDelayUs);
            mLastAudioMediaTimeUs = getDurationUsIfPlayedAtSampleRate(mNumFramesWritten);

            mAudioQueue.erase(mAudioQueue.begin());
            entry = NULL;
            if (mAudioSink->needsTrailingPadding()) {
    
    
                mAudioSink->stop();
                mNumFramesWritten = 0;
            }
            return false;
        }

        mLastAudioBufferDrained = entry->mBufferOrdinal;

		// 如果偏移量为 0,说明是一个全新的ouput buffer
        if (entry->mOffset == 0 && entry->mBuffer->size() > 0) {
    
    
            int64_t mediaTimeUs;
            CHECK(entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs));
            // 更新 AudioMediaTime
            onNewAudioMediaTime(mediaTimeUs);
        }

        size_t copy = entry->mBuffer->size() - entry->mOffset;
		// 将数据写入到 AudioTrack
        ssize_t written = mAudioSink->write(entry->mBuffer->data() + entry->mOffset,
                                            copy, false /* blocking */);
        // 计算写入长度,并且做一些判断
        entry->mOffset += written;
        size_t remainder = entry->mBuffer->size() - entry->mOffset;
        if ((ssize_t)remainder < mAudioSink->frameSize()) {
    
    
        	// 如果剩余的数据大于0,并且小于一帧音频的大小,那么就丢弃剩下的数据
            if (remainder > 0) {
    
    
                ALOGW("Corrupted audio buffer has fractional frames, discarding %zu bytes.",
                        remainder);
                entry->mOffset += remainder;
                copy -= remainder;
            }

            entry->mNotifyConsumed->post();
            mAudioQueue.erase(mAudioQueue.begin());

            entry = NULL;
        }
		// 记录写入的帧数
        size_t copiedFrames = written / mAudioSink->frameSize();
        mNumFramesWritten += copiedFrames;

        {
    
    
        	// 计算最大可播放时间
            Mutex::Autolock autoLock(mLock);
            int64_t maxTimeMedia;
            maxTimeMedia =
                mAnchorTimeMediaUs +
                        (int64_t)(max((long long)mNumFramesWritten - mAnchorNumFramesWritten, 0LL)
                                * 1000LL * mAudioSink->msecsPerFrame());
            mMediaClock->updateMaxTimeMedia(maxTimeMedia);

            notifyIfMediaRenderingStarted_l();
        }

        if (written != (ssize_t)copy) {
    
    
            CHECK_EQ(copy % mAudioSink->frameSize(), 0u);
            ALOGV("AudioSink write short frame count %zd < %zu", written, copy);
            break;
        }
    }

    // calculate whether we need to reschedule another write.
    // 当 List 数据不为空,并且 没有暂停或者是AudioTrack还没写满,尝试再次调用 postDrainAudioQueue_l 写入
    bool reschedule = !mAudioQueue.empty()
            && (!mPaused
                || prevFramesWritten != mNumFramesWritten);
    return reschedule;
}

Вот список того, что делает onDrainAudioQueue:

  1. Вызовите AudioSink.getPosition, чтобы получить количество воспроизводимых в данный момент аудиокадров. Используйте этот номер кадра и количество записанных в данный момент кадров, чтобы вычислить, сколько кадров аудиоданных можно записать сейчас;
  2. Если значение Buffer в QueueEntry равно NULL, это означает, что EOS был получен:
    • 2.1.Если mNotifyConsumed не NULL, это означает, что получено время прерывания потока кода, и AudioTrack необходимо перезапустить с новым форматом;
    • 2.2.Если mNotifyConsumed имеет значение NULL, это означает, что очередьEOS вызывается верхним уровнем.В это время необходимо рассчитать, как долго аудио может воспроизводиться, а затем отправить сообщение о задержке для уведомления NuPlayer об EOS;
  3. Если значение Buffer не NULL, данные необходимо скопировать в AudioTrack. Здесь возможны две ситуации:
    • 4.1.Если смещение Буфера не равно 0, это означает, что копирование не было завершено в прошлый раз, и копирование нужно продолжить здесь;
    • 4.2. Если смещение равно 0, это означает, что это новый выходной буфер, и его необходимо вызвать для onNewAudioMediaTimeобновления некоторого содержимого с использованием новой временной метки;
  4. Вызовите AudioSink.write, чтобы записать данные в AudioTrack.Если оставшиеся данные меньше размера одного кадра аудиоданных, оставшиеся данные будут удалены напрямую, в противном случае они будут скопированы в следующий раз;
  5. Обновите максимальную продолжительность мультимедиа MediaClock и отправьте событие kWhatMediaRenderingStart, чтобы начать рендеринг в NuPlayer;
  6. Если список не пуст, пауза отсутствует или данные AudioTrack не заполнены, верните true и повторите попытку записи.

2.3、OnNewAudioMediaTime

Причина, по которой эта функция выделена, заключается в том, что она является одной из основных функций, используемых механизмом NuPlayer Avsync. Avsync делится на следующие четыре типа:

  • свободный запуск: не делайте Avsync;
  • Audio Master: видео синхронизируется со звуком;
  • Video Master: синхронизация звука с видео;
  • System Master: аудио и видео синхронизируются с системными часами;

NuPlayer использует Audio Master.

void NuPlayer::Renderer::onNewAudioMediaTime(int64_t mediaTimeUs) {
    
    
    Mutex::Autolock autoLock(mLock);
    // TRICKY: vorbis decoder generates multiple frames with the same
    // timestamp, so only update on the first frame with a given timestamp
    if (mediaTimeUs == mAnchorTimeMediaUs) {
    
    
        return;
    }
    // 设置 MediaClock 的开始媒体时间;
    setAudioFirstAnchorTimeIfNeeded_l(mediaTimeUs);

    // mNextAudioClockUpdateTimeUs is -1 if we're waiting for audio sink to start
    // 等待 AudioTrack 启动,获取到第一帧pts
    if (mNextAudioClockUpdateTimeUs == -1) {
    
    
        AudioTimestamp ts;
        if (mAudioSink->getTimestamp(ts) == OK && ts.mPosition > 0) {
    
    
            mNextAudioClockUpdateTimeUs = 0; // start our clock updates
        }
    }
    int64_t nowUs = ALooper::GetNowUs();
    if (mNextAudioClockUpdateTimeUs >= 0) {
    
    
    	// 到达更新时间
        if (nowUs >= mNextAudioClockUpdateTimeUs) {
    
    
        	// 获取当前剩余帧数,计算当前已播时长
            int64_t nowMediaUs = mediaTimeUs - getPendingAudioPlayoutDurationUs(nowUs);
            mMediaClock->updateAnchor(nowMediaUs, nowUs, mediaTimeUs);
            mUseVirtualAudioSink = false;
            mNextAudioClockUpdateTimeUs = nowUs + kMinimumAudioClockUpdatePeriodUs;
        }
    } 
    mAnchorNumFramesWritten = mNumFramesWritten;
    mAnchorTimeMediaUs = mediaTimeUs;
}
  1. Установите время начала мультимедиа MediaClock;
  2. Подождите, пока запустится AudioTrack и получите очки первого кадра;
  3. Рассчитайте общую продолжительность на основе текущего количества записанных кадров, вычтите текущую продолжительность воспроизведения AudioTrack и получите оставшуюся продолжительность воспроизведения;
  4. Вычтите продолжительность воспроизведения из временной метки, записанной на этот раз, чтобы получить текущее время воспроизведения мультимедиа;
  5. Обновите привязку MediaClock, указав вычисленное текущее время мультимедиа nowMediaUs, текущую записанную метку времени и текущее системное время;

Здесь задействованы три времени:

  • nowMediaUs: медиа-время текущей игровой позиции;
  • mediaTimeUs: время мультимедиа текущего аудиокадра;
  • nowUs: текущее системное время;

Основная цель getPendingAudioPlayoutDurationUs — получить время мультимедиа текущей воспроизводимой позиции через AudioTrack.

Наконец, updateAnchor вызывается для обновления времени привязки в MediaClock, что обновит три значения:

  • mAnchorTimeMediaUs: время мультимедиа текущей позиции воспроизведения;
  • mAnchorTimeRealUs: системное время, соответствующее текущему времени мультимедиа;
  • mPlaybackRate:Текущая скорость воспроизведения;

Время привязки не обновляется каждый раз при получении новой метки времени звука,
но обновляется каждый интервал mNextAudioClockUpdateTimeUs.

2.4、postDrainVideoQueue

void NuPlayer::Renderer::postDrainVideoQueue() {
    
    
	// 当前正在处理 video output buffer、syncqueue、暂停 直接退出
    if (mDrainVideoQueuePending
            || getSyncQueues()
            || (mPaused && mVideoSampleReceived)) {
    
    
        return;
    }

    if (mVideoQueue.empty()) {
    
    
        return;
    }

    QueueEntry &entry = *mVideoQueue.begin();

    sp<AMessage> msg = new AMessage(kWhatDrainVideoQueue, this);
    msg->setInt32("drainGeneration", getDrainGeneration(false /* audio */));
	// 收到 EOS 直接发送消息
    if (entry.mBuffer == NULL) {
    
    
        // EOS doesn't carry a timestamp.
        msg->post();
        mDrainVideoQueuePending = true;
        return;
    }

    int64_t nowUs = ALooper::GetNowUs();
    // 直播流的avsync
    if (mFlags & FLAG_REAL_TIME) {
    
    
        int64_t realTimeUs;
        CHECK(entry.mBuffer->meta()->findInt64("timeUs", &realTimeUs));

        realTimeUs = mVideoScheduler->schedule(realTimeUs * 1000) / 1000;

        int64_t twoVsyncsUs = 2 * (mVideoScheduler->getVsyncPeriod() / 1000);

        int64_t delayUs = realTimeUs - nowUs;

        ALOGW_IF(delayUs > 500000, "unusually high delayUs: %lld", (long long)delayUs);
        // post 2 display refreshes before rendering is due
        msg->post(delayUs > twoVsyncsUs ? delayUs - twoVsyncsUs : 0);

        mDrainVideoQueuePending = true;
        return;
    }

    int64_t mediaTimeUs;
    CHECK(entry.mBuffer->meta()->findInt64("timeUs", &mediaTimeUs));

    {
    
    
        Mutex::Autolock autoLock(mLock);
        // 如果 anchor time 小于0,则使用 video timestamp 更新 anchor time
        if (mAnchorTimeMediaUs < 0) {
    
    
            mMediaClock->updateAnchor(mediaTimeUs, nowUs, mediaTimeUs);
            mAnchorTimeMediaUs = mediaTimeUs;
        }
    }
    mNextVideoTimeMediaUs = mediaTimeUs;
    // 如果没有 audio 则用 video 来计算最大可播放时间
    if (!mHasAudio) {
    
    
        // smooth out videos >= 10fps
        mMediaClock->updateMaxTimeMedia(mediaTimeUs + kDefaultVideoFrameIntervalUs);
    }
	// 第一帧 video 到达 或者是 video pts 小于 audio 第一帧 pts,直接post
    if (!mVideoSampleReceived || mediaTimeUs < mAudioFirstAnchorTimeMediaUs) {
    
    
        msg->post();
    } else {
    
    
        int64_t twoVsyncsUs = 2 * (mVideoScheduler->getVsyncPeriod() / 1000);
		// 等到 2倍vsync时间前post消息
        // post 2 display refreshes before rendering is due
        mMediaClock->addTimer(msg, mediaTimeUs, -twoVsyncsUs);
    }

    mDrainVideoQueuePending = true;
}

Обработка буфера вывода видео намного сложнее:

  1. Если в данный момент он ожидает обработки предыдущего выходного буфера, выполняет синхронизацию или был приостановлен, он вернется напрямую, не обрабатывая текущее сообщение;
  2. После получения EOS опубликуйте сообщение напрямую;
  3. Если источником является RTSPSource, который представляет собой поток в реальном времени, его собственные точки будут использоваться в качестве ссылки для расчета момента рендеринга;
  4. Если есть только видео, время привязки будет обновлено с учетом временной метки первого кадра видео;
  5. Если это обычный поток:
    • 4.1. Когда приходит первый кадр видео или количество точек видео меньше, чем количество точек первого кадра аудио, опубликуйте сообщение напрямую;
    • 4.2. В других случаях необходимо использовать MediaClock для расчета времени отправки сообщения, и сообщение будет опубликовано после достижения этого времени;

Зачем использовать MediaClock для расчета времени отправки сообщения? Это связано с тем, что необходимо учитывать воспроизведение с двойной скоростью. Если существует двойная скорость, время отправки сообщения не может быть получено простым сложением и вычитанием.

Выше сказано, что Рендерер синхронизируется по аудио, где это можно проверить?

При использовании MediaClock для расчета времени обработки сообщения используется метод getMediaTime_l, который используется для получения текущего времени воспроизведения мультимедиа:

status_t MediaClock::getMediaTime_l(
        int64_t realUs, int64_t *outMediaUs, bool allowPastMaxTime) const {
    
    
    if (mAnchorTimeRealUs == -1) {
    
    
        return NO_INIT;
    }

    int64_t mediaUs = mAnchorTimeMediaUs
            + (realUs - mAnchorTimeRealUs) * (double)mPlaybackRate;
    *outMediaUs = mediaUs;
    return OK;
}

当前媒体播放时间= 上次记录的媒体播放时间+ 系统走过时间*倍速

mAnchorTimeMediaUs и mAnchorTimeRealUs обновляются с использованием времени воспроизведения звука, поэтому время сообщения вывода видео рассчитывается на основе времени звука, а это означает, что видео синхронизируется со звуком.

Если вы хотите узнать более подробную информацию о MediaClock, прочтите ее самостоятельно.

void NuPlayer::Renderer::onDrainVideoQueue() {
    
    
    if (mVideoQueue.empty()) {
    
    
        return;
    }

    QueueEntry *entry = &*mVideoQueue.begin();
	// 通知 NuPlayer EOS
    if (entry->mBuffer == NULL) {
    
    
        // EOS
        notifyEOS(false /* audio */, entry->mFinalResult);
        mVideoQueue.erase(mVideoQueue.begin());
        entry = NULL;
        setVideoLateByUs(0);
        return;
    }

	// 获取render的系统时间
    int64_t nowUs = ALooper::GetNowUs();
    int64_t realTimeUs;
    int64_t mediaTimeUs = -1;
    if (mFlags & FLAG_REAL_TIME) {
    
    
        CHECK(entry->mBuffer->meta()->findInt64("timeUs", &realTimeUs));
    } else {
    
    
        CHECK(entry->mBuffer->meta()->findInt64("timeUs", &mediaTimeUs));
		// 计算 render 的系统时间
        realTimeUs = getRealTimeUs(mediaTimeUs, nowUs);
    }
    realTimeUs = mVideoScheduler->schedule(realTimeUs * 1000) / 1000;

    bool tooLate = false;
	// 判断video是否晚到
    if (!mPaused) {
    
    
        setVideoLateByUs(nowUs - realTimeUs);
        tooLate = (mVideoLateByUs > 40000);
    } 
	// 总是渲染出第一帧
    // Always render the first video frame while keeping stats on A/V sync.
    if (!mVideoSampleReceived) {
    
    
        realTimeUs = nowUs;
        tooLate = false;
    }
	// 将消息发送给个 NuPlayer Decoder 处理
    entry->mNotifyConsumed->setInt64("timestampNs", realTimeUs * 1000LL);
    entry->mNotifyConsumed->setInt32("render", !tooLate);
    entry->mNotifyConsumed->post();
    mVideoQueue.erase(mVideoQueue.begin());
    entry = NULL;

    mVideoSampleReceived = true;

    if (!mPaused) {
    
    
    	// 通知 NuPlayer video render 开始
        if (!mVideoRenderingStarted) {
    
    
            mVideoRenderingStarted = true;
            notifyVideoRenderingStart();
        }
        Mutex::Autolock autoLock(mLock);
        notifyIfMediaRenderingStarted_l();
    }
}
  1. Если буфер равен NULL, уведомите NuPlayer EOS;
  2. Получить или вычислить системное время, когда должен быть отображен текущий буфер;
  3. Определить, опаздывает ли буфер, на основе текущего системного времени;
  4. Отправьте сообщение, чтобы уведомить NuPlayer Decoder о рендеринге;

На этом общее понимание NuPlayer Renderer заканчивается. Часто используемые пауза, возобновление, сброс, getCurrentPosition, setPlaybackSettings, setSyncSettings и режим разгрузки здесь не будут подробно объясняться.

Je suppose que tu aimes

Origine blog.csdn.net/qq_41828351/article/details/132644378
conseillé
Classement