このセクションでは、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 開始メソッドを呼び出した後、レンダラーが作成され、渡されるパラメーターはコールバック メッセージ、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 を作成するとき、determineAudioModeChange が呼び出され、オフロード モードがサポートされているかどうかを再度判断します (最初の判断については NuPlayer の章で簡単に説明しました)。サポートされている場合は、(パフォーマンスを節約するために) オフロード モードが最初に使用され、レンダラーはaudio hal を開こうとメソッドが呼び出されopenAudioSink
、オフロード モードを設定します。オフロード モードがサポートされていない場合、AudioTrack は一時的に作成されません。
Decoder に関する前回の記事で、Decoder はchangeAudioFormat
Audio Output Format Changed イベントを受信した後にメソッドを呼び出すと述べましたが、オフロード モードでない場合は、ここで openAudioSink が呼び出され、通常の AudioTrack が作成されます。つまり、通常モードでは、オーディオ データが実際にデコードされるまで、AudioTrack は作成されません。
ps: AudioTrack の通常モードとオフロード モードの使用方法を知りたい場合は、NuPlayer、Renderer、および NuPlayerDecoderPassThrough を参照してください。
以下の内容では、当面はAudioTrackのみを見ていきます。ノーマルモード。
2、キューバッファ
Renderer には start メソッドがありません。出力バッファを Renderer に書き込むために queueBuffer が呼び出されるときに、Avsync が自動的に開始されます。
void NuPlayer::Renderer::queueBuffer(
bool audio,
const sp<MediaCodecBuffer> &buffer,
const sp<AMessage> ¬ifyConsumed) {
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", ¬ifyConsumed));
// 将 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 は長く見えますが、大したことは行いません。
- メッセージ内のコンテンツを QueueEntry に再カプセル化し、対応するリストに追加します。
- postDrainAudioQueue_l / postDrainVideoQueue を呼び出して、処理リストのエントリにメッセージを送信します。
- SyncQueue が必要かどうかを判断します。
2.1、同期キュー
まず SyncQueue メカニズムについて話しましょう。ブロードキャスト同期を開始するブロードキャストの開始時にオーディオ ポイントとビデオ ポイントの間のギャップが大きすぎる場合、このメカニズムを使用して出力データがドロップされます。
SyncQueue メカニズムが Renderer で有効になっておらず、機能が完了していません。なぜこのように言えるのでしょうか? コード内で true が設定されていないためmSyncQueues
、ブロードキャスト開始時に SyncQueue を行うことに加えて、フラッシュ後に同期も行う必要があると思います。
SyncQueue メカニズムの主なアイデアは、ビデオ データとオーディオ データの両方が到着したときに 2 つのチームの最初の要素のポイントを決定することです。ビデオ時間がオーディオよりも遅い場合は、オーディオが最初にドロップされます。同期後、SyncQueueフラグは false に設定できます。
onQueueBuffer はメッセージを送信するために最初に postDrainAudioQueue_l / postDrainVideoQueue を呼び出すと上で説明しましたが、実際にコードを見ると、これら 2 つのメソッドを入力すると、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 を使用してメッセージを送信する前に、まずメッセージが判断されてから、送信するかどうかが決定されます。
- 一時停止が呼び出された場合は、書き込む前に AudioTrack が完全に停止するまで待つ必要があるため、メッセージを遅らせる必要があります。
- 一時停止遅延または再書き込み処理中、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 の主な手順は次のとおりです。
- 世代をチェックして、AudioTrack へのデータの書き込みを停止する必要があるかどうかを判断します。
- onDrainAudioQueue を呼び出して、リスト内のすべてのデータを AudioTrack に書き込みます。
- 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 の機能のリストは次のとおりです。
- 現在再生されているオーディオ フレーム数を取得するには、AudioSink.getPosition を呼び出します。このフレーム番号と現在書き込まれているフレーム数を使用して、現在書き込めるオーディオ データのフレーム数を計算します。
- QueueEntry の Buffer が NULL の場合、EOS が受信されたことを意味します。
- 2.1. mNotifyConsumed が NULL でない場合は、コード ストリームの不連続時間を受信したことを意味し、AudioTrack を新しい形式で再起動する必要があります。
- 2.2. mNotifyConsumed が NULL の場合は、上位層によって queueEOS が呼び出されたことを意味しますが、このとき、オーディオを再生できる時間を計算し、NuPlayer に EOS を通知するための遅延メッセージを送信する必要があります。
- Buffer が NULL でない場合、データを AudioTrack にコピーする必要があります。ここには 2 つの状況が考えられます。
- 4.1. バッファのオフセットが 0 でない場合は、前回コピーが完了していないため、ここでコピーを続行する必要があることを意味します。
- 4.2. オフセットが 0 の場合、それは新しい出力バッファーであり、
onNewAudioMediaTime
一部のコンテンツを新しいタイムスタンプで更新するために呼び出す必要があることを意味します。
- AudioSink.write を呼び出して、データを AudioTrack に書き込みます。残りのデータがオーディオ データの 1 フレームのサイズより小さい場合、残りのデータは直接削除され、それ以外の場合は次回コピーされます。
- MediaClock の最大メディア継続時間を更新し、イベント kWhatMediaRenderingStart を送信して、NuPlayer へのレンダリングを開始します。
- List が空でなく、一時停止がない場合、または AudioTrack データがいっぱいでない場合は、true を返し、再度書き込みを試みます。
2.3、NewAudioMediaTime について
この関数が取り上げられている理由は、これが NuPlayer Avsync メカニズムで使用されるコア関数の 1 つであるためです。Avsync は次の 4 種類に分類されます。
- フリーラン: Avsync を実行しません。
- オーディオ マスター: ビデオはオーディオと同期されます。
- ビデオマスター: オーディオをビデオに同期します。
- システム マスター: オーディオとビデオはシステム クロックに同期されます。
NuPlayer はオーディオ マスターを使用します。
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;
}
- MediaClock の開始メディア時間を設定します。
- AudioTrack が開始して最初のフレーム ポイントを取得するまで待ちます。
- 現在書き込まれているフレーム数に基づいて合計期間を計算し、現在の AudioTrack 再生期間を減算して、残りの再生可能期間を取得します。
- 今回書き込まれたタイムスタンプから再生可能時間を減算して、現在のメディア再生時間を取得します。
- MediaClock アンカーを、計算された現在のメディア時刻 nowMediaUs、現在書き込まれているタイムスタンプ、および現在のシステム時刻で更新します。
ここには 3 つの時間が関係します。
- nowMediaUs: 現在の再生位置のメディア時間。
- mediaTimeUs: 現在のオーディオ フレームのメディア時間。
- nowUs: 現在のシステム時間。
getPendingAudioPlayoutDurationUs の主な目的は、AudioTrack を通じて現在再生されている位置のメディア時間を取得することです。
最後に、updateAnchor を呼び出して MediaClock のアンカー時間を更新します。これにより、次の 3 つの値が更新されます。
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;
}
ビデオ出力バッファの処理はさらに複雑です。
- 現在、前の出力バッファの処理を待機している場合、同期キューを実行している場合、または一時停止している場合は、現在のメッセージを処理せずに直接戻ります。
- EOS を受信したら、メッセージを直接投稿します。
- ソースがリアルタイム ストリームである RTSPSource の場合、いつレンダリングするかを計算するための参照として独自の pts が使用されます。
- ビデオのみがある場合、アンカー時間はビデオの最初のフレームのタイムスタンプで更新されます。
- 通常のストリームの場合:
- 4.1. ビデオの最初のフレームが到着するか、ビデオのポイントがオーディオの最初のフレームのポイントより小さい場合、メッセージを直接投稿します。
- 4.2. 他の場合には、MediaClock を使用してメッセージ送信時刻を計算する必要があり、メッセージは時刻に達した後に投稿されます。
メッセージ送信時間の計算に MediaClock を使用するのはなぜですか? これは、倍速再生を考慮する必要があるためであり、倍速が存在する場合、単純な加減算ではメッセージ送信時間を求めることができない。
上では、レンダラーはオーディオを使用して同期されていると言われていますが、これはどこで確認できますか?
getMediaTime_l
MediaClock を使用してメッセージ処理時間を計算する場合、現在のメディア再生時間を取得するために使用されるメソッドが使用されます。
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();
}
}
- バッファが NULL の場合は、NuPlayer EOS に通知します。
- 現在のバッファをレンダリングする必要があるシステム時間を取得または計算します。
- 現在のシステム時間に基づいてバッファが遅れているかどうかを判断します。
- NuPlayer Decoder にレンダリングするよう通知するメッセージを送信します。
これで、NuPlayer Renderer の一般的な理解は終わりました。一般的に使用される一時停止、再開、フラッシュ、getCurrentPosition、setPlaybackSettings、setSyncSettings、およびオフロード モードについては、ここではあまり説明しません。