目錄
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> ¬ifyConsumed) {
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", ¬ifyConsumed));
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;