Android多媒体开发(八)----播放流程

       前面几篇都是视频文件播放前的准备工作,比如设置数据源,初始化解码器等等,本节我们分析MediaPlayer播放器start之后的流程。

播放流程

基本流程

       当MediaPlayer prepared成功之后,调用start就开始播放视频了。从前几篇我们知道底层会调用到StageFrightPlayer,最后到AwesomePlayer,我们继续跟踪下去:位于frameworks/av/media/libmediaplayerservice/StageFrightPlayer.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
status_t StagefrightPlayer::start() {
ALOGV("start");

return mPlayer->play();
}

StagefrightPlayer::StagefrightPlayer()
: mPlayer(new AwesomePlayer) {
ALOGV("StagefrightPlayer");

mPlayer->setListener(this);
}

       接着会调用AwesomePlayer的paly函数,位于frameworks/av/media/libstagefright/AwesomePlayer.cpp中:

1
2
3
4
5
6
7
8
9
status_t AwesomePlayer::play() {
ATRACE_CALL();

Mutex::Autolock autoLock(mLock);

modifyFlags(CACHE_UNDERRUN, CLEAR);

return play_l();
}

       play函数里有调用了play_l函数,表示是一个内部显示方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

status_t AwesomePlayer::play_l() {
modifyFlags(SEEK_PREVIEW, CLEAR);
//如果在播放,则直接返回
if (mFlags & PLAYING) {
return OK;
}

mMediaRenderingStartGeneration = ++mStartGeneration;
//如果还没有prepare,则先prepare
if (!(mFlags & PREPARED)) {
status_t err = prepare_l();

if (err != OK) {
return err;
}
}
//设置标志位PLAYING
modifyFlags(PLAYING, SET);
//设置标志位FIRST_FRAME,表示第一帧
modifyFlags(FIRST_FRAME, SET);

......

//音频解码器不为空
if (mAudioSource != NULL) {
//创建一个音频播放器,这一步还会设置播放同步时钟,即音频/视频/字幕播放的参考时间,下来我们会分析
if (mAudioPlayer == NULL) {
createAudioPlayer_l();
}

CHECK(!(mFlags & AUDIO_RUNNING));
//如果只有Audio没有Video,省略这一部分
if (mVideoSource == NULL) {

...忽略...

}

}
//如果没有设置同步时钟,则用系统的UTC时间作为播放同步时钟
//在createAudioPlayer_l里面已经设置了Audio时间轴为同步时钟,下面会讲到
if (mTimeSource == NULL && mAudioPlayer == NULL) {
mTimeSource = &mSystemTimeSource;
}
//递归消息处理
if (mVideoSource != NULL) {
// Kick off video playback
//开球,开播
postVideoEvent_l();
//解码状态回调,不重要
if (mAudioSource != NULL && mVideoSource != NULL) {
postVideoLagEvent_l();
}
}
//如果播放完了,则seek到开头
if (mFlags & AT_EOS) {
// Legacy behaviour, if a stream finishes playing and then
// is started again, we play from the start...
seekTo_l(0);
}

......
//如果是网络视频,则要一直发送bufferUpdate
if (isStreamingHTTP()) {
postBufferingEvent_l();
}

return OK;
}

       上面代码虽然多,但核心只有两件事:

  • 创建一个音频播放器,这一步还会设置播放同步时钟,即音频/视频/字幕播放的参考时间
  • 递归消息处理,这一步就是处理音频/视频/字幕的播放核心

       我们一个一个分析:
       1)创建音频播放器,设置同步时钟

设置同步时钟

       我们先看看createAudioPlayer_l这个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void AwesomePlayer::createAudioPlayer_l()
{
uint32_t flags = 0;
int64_t cachedDurationUs;
bool eos;
//如果是offload模式,即音频分载,音频由dsp解码,不走omx框架
if (mOffloadAudio) {
flags |= AudioPlayer::USE_OFFLOAD;
} else if (mVideoSource == NULL
&& (mDurationUs > AUDIO_SINK_MIN_DEEP_BUFFER_DURATION_US ||
(getCachedDuration_l(&cachedDurationUs, &eos) &&
cachedDurationUs > AUDIO_SINK_MIN_DEEP_BUFFER_DURATION_US))) {
flags |= AudioPlayer::ALLOW_DEEP_BUFFERING;
}
if (isStreamingHTTP()) {
flags |= AudioPlayer::IS_STREAMING;
}
if (mVideoSource != NULL) {
flags |= AudioPlayer::HAS_VIDEO;
}
//创建一个AudioPlayer,构造方法里传入了AudioSink,并设置进入音频解码器mAudioSource
mAudioPlayer = new AudioPlayer(mAudioSink, flags, this);
mAudioPlayer->setSource(mAudioSource);
//这就是设置同步时钟,是以音频时间轴为参考时间的
mTimeSource = mAudioPlayer;

// If there was a seek request before we ever started,
// honor the request now.
// Make sure to do this before starting the audio player
// to avoid a race condition.
//如果又一个seek请求在播放之前,我们要优先处理这个seek操作
seekAudioIfNecessary_l();
}

       上述函数先查看是不是offload模式,即音频分载,音频由dsp解码,不走omx框架。android L以后会支持音频的的offload播放。就高通的8994平台来看,即会把音频的编解码和后处理放到QDSP里面处理。但其实很多芯片不支持,这里我们这个不是重点,所以往下看。

       接着就是构造AudioPlayer了,这里的构造方法传入了AudioSink,即用来输出声音。这个mAudioSink传入还要追溯到之前setDataSource那里,如果忘记了可以查看Android多媒体开发(四)—-AwesomePlayer数据源处理。在最初的mediaplayerservice中,位于frameworks/av/media/libmediaplayerservice/MediaPlayerSrevice.cpp,调用setdatasource操作代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sp<MediaPlayerBase> MediaPlayerService::Client::setDataSource_pre(
player_type playerType)
{
ALOGV("player type = %d", playerType);

// create the right type of player
sp<MediaPlayerBase> p = createPlayer(playerType);
if (p == NULL) {
return p;
}
//核心在这里
if (!p->hardwareOutput()) {
mAudioOutput = new AudioOutput(mAudioSessionId, IPCThreadState::self()->getCallingUid(),
mPid, mAudioAttributes);
//这里会构造一个AudioOutput对象传入作为mAudioSink
static_cast<MediaPlayerInterface*>(p.get())->setAudioSink(mAudioOutput);
}

return p;
}

       这里会构造一个AudioOutput对象传入作为mAudioSink,实际的播放顺序是 mAudioPlayer ->mAudioSink ->mAudioTrack(不要混淆,此处类是AudioTrack)。

       最后就是设置同步时钟了,将mAudioPlayer赋值给mTimeSource 。我们可以看看AudioPlayer的继承关系,它也是继承与TimeSource类的,所以需要实现父类的getRealTimeUs这个虚函数定义位于frameworks/av/include/media/stagefright/AudioPlayer.h中,实现位于frameworks/av/media/libstagefright/AudioPlayer.cpp:

1
2
3
4
5
6
class AudioPlayer : public TimeSource {

int64_t AudioPlayer::getRealTimeUs() {
......
}
}

       关于getRealTimeUs函数我们后面会分析到。
       往下走play_l方法里还有这样的逻辑:

1
2
3
if (mTimeSource == NULL && mAudioPlayer == NULL) {
mTimeSource = &mSystemTimeSource;
}

       设置同步时钟的,这里如果mAudioPlayer存在的话,以audio为基准进行播放,否则以系统时钟为基准控制播放。但我们的mAudioPlayer存在,所以已AudioPlayer作为参考基准控制播放。

       注意:这里有一个重点,就是播放的同步时钟是以音频为参考时间的,后面的视频和字幕也是参考这个同步时钟来渲染和展示的。

       2)递归消息处理,这一步就是处理音频/视频/字幕的播放核心
       然后就是递归postVideoEvent_l()消息处理,我们看看这个方法:

1
2
3
4
5
6
7
8
9
10
11
void AwesomePlayer::postVideoEvent_l(int64_t delayUs) {//缺省参数,int64_t delayUs = -1
ATRACE_CALL();

if (mVideoEventPending) {
return;
}

mVideoEventPending = true;
//因为delayUs 缺省为-1,所以每10ms触发一下回调
mQueue.postEventWithDelay(mVideoEvent, delayUs < 0 ? 10000 : delayUs);
}

       主要就是每10ms触发mVideoEvent事件,其响应函数是AwesomePlayer::onVideoEvent。这个方法很长,所以要一步一步查看:

Part.1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void AwesomePlayer::onVideoEvent() {
ATRACE_CALL();
Mutex::Autolock autoLock(mLock);
if (!mVideoEventPending) {
// The event has been cancelled in reset_l() but had already
// been scheduled for execution at that time.
return;
}
mVideoEventPending = false;
//如果有seek操作
if (mSeeking != NO_SEEK) {
//先清空VideoBuffer视频数据缓冲区
if (mVideoBuffer) {
mVideoBuffer->release();
mVideoBuffer = NULL;
}
//如果是seek操作中,且是网络数据流
if (mSeeking == SEEK && isStreamingHTTP() && mAudioSource != NULL
&& !(mFlags & SEEK_PREVIEW)) {
// We're going to seek the video source first, followed by
// the audio source.
// In order to avoid jumps in the DataSource offset caused by
// the audio codec prefetching data from the old locations
// while the video codec is already reading data from the new
// locations, we'll "pause" the audio source, causing it to
// stop reading input data until a subsequent seek.
//我们将要seek视频的数据,然后跟着去seek音频数据
//为了避免当音频预加载数据是旧的位置取到的而视频却早就取了新的位置的数据,所以我们要先pause住audio,先完成video的seek,后面再seek audio
if (mAudioPlayer != NULL && (mFlags & AUDIO_RUNNING)) {
mAudioPlayer->pause();

modifyFlags(AUDIO_RUNNING, CLEAR);
}
mAudioSource->pause();
}
}

...未完,待续...
}

       首先是判断是否需要seek,若需要seek,则先pause住audio,先完成video的seek,后面再seek audio。

Part.2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
void AwesomePlayer::onVideoEvent() {

...Part.1...

if (!mVideoBuffer) {//已经清空了VideoBuffer
MediaSource::ReadOptions options;
//如果需要拖动
if (mSeeking != NO_SEEK) {
ALOGV("seeking to %" PRId64 " us (%.2f secs)", mSeekTimeUs, mSeekTimeUs / 1E6);
//设置拖动标志
options.setSeekTo(
mSeekTimeUs,
mSeeking == SEEK_VIDEO_ONLY
? MediaSource::ReadOptions::SEEK_NEXT_SYNC
: MediaSource::ReadOptions::SEEK_CLOSEST_SYNC);
}
for (;;) {
//读取一个视频帧的画面,这个是调用omx解码后的数据,传入VideoBuffer,这个我们以后再讲
status_t err = mVideoSource->read(&mVideoBuffer, &options);
options.clearSeekTo();
//如果读取失败的处理,ignore
if (err != OK) {
CHECK(mVideoBuffer == NULL);

if (err == INFO_FORMAT_CHANGED) {
ALOGV("VideoSource signalled format change.");
//检查宽度高度是否发生变化
notifyVideoSize_l();

if (mVideoRenderer != NULL) {
mVideoRendererIsPreview = false;
initRenderer_l();
}
continue;
}

// So video playback is complete, but we may still have
// a seek request pending that needs to be applied
// to the audio track.
if (mSeeking != NO_SEEK) {
ALOGV("video stream ended while seeking!");
}
finishSeekIfNecessary(-1);

if (mAudioPlayer != NULL
&& !(mFlags & (AUDIO_RUNNING | SEEK_PREVIEW))) {
startAudioPlayer_l();
}
//否则即video播放完毕了,设置EOF标记
modifyFlags(VIDEO_AT_EOS, SET);
//并触发mStreamDoneEvent消息
postStreamDoneEvent_l(err);
return;
}
/*如果读取一个视频帧成功,go on*/
//检查读取到mVideoBuffer长度,如果长度小于0,则继续读取
if (mVideoBuffer->range_length() == 0) {
// Some decoders, notably the PV AVC software decoder
// return spurious empty buffers that we just want to ignore.
//一些解码器,尤其是MEPG4 h264的返回假的空buffers,我们需要忽略这些
mVideoBuffer->release();
mVideoBuffer = NULL;
continue;
}
//如果读取一个视频帧成功了,则跳出for(;;)循环
break;
}

{
Mutex::Autolock autoLock(mStatsLock);
//将解码的视频帧数+1
++mStats.mNumVideoFramesDecoded;
}
}

...未完,待续...

}

       上述代码分两部分:
       ①第一部分判断是否需要seek,若需要则设置option。
       ②第二部分是从mVideoSource中读取一帧画面,这里读取的时候会将option传入,如果需要seek,则读取出的数据直接就是seek后的解码数据。(中间还有些小细节:如果数据读取失败,则检查宽度高度是否发生变化,否则即video播放完毕了,设置EOF标记,并触发mStreamDoneEvent消息。)

       mVideoSource->read是经过omx调用底层解码器后,读取返回的已经解码过的视频帧数据。关于和omx的调用关系我们以后会讲到。

音频播放

Part.3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
void AwesomePlayer::onVideoEvent() {

...Part.1...
...Part.2...

int64_t timeUs;//从上一步读取到的视频解码帧数据中取出它的时间戳
CHECK(mVideoBuffer->meta_data()->findInt64(kKeyTime, &timeUs));
//因为这一帧即将播放,所以赋给mLastVideoTimeUs
mLastVideoTimeUs = timeUs;
//如果仅仅seek视频,ignore
if (mSeeking == SEEK_VIDEO_ONLY) {
if (mSeekTimeUs > timeUs) {
ALOGI("XXX mSeekTimeUs = %" PRId64 " us, timeUs = %" PRId64 " us",
mSeekTimeUs, timeUs);
}
}

{
Mutex::Autolock autoLock(mMiscStateLock);
//将视频帧时间戳赋值
mVideoTimeUs = timeUs;
}

SeekType wasSeeking = mSeeking;
////完成audio的seek。之前说如果有seek请求,则先pause住audio,读取seek的video数据,拿到第一帧数据后,以此数据为标准,来seek audio,此处finishSeekIfNecessary便是完成此功能
finishSeekIfNecessary(timeUs);
//如果音频没有开始播放,则开始播放音频
if (mAudioPlayer != NULL && !(mFlags & (AUDIO_RUNNING | SEEK_PREVIEW))) {
//播放音频
status_t err = startAudioPlayer_l();
if (err != OK) {
ALOGE("Starting the audio player failed w/ err %d", err);
return;
}
}
//如果字幕没有开播,则播放字幕
if ((mFlags & TEXTPLAYER_INITIALIZED)
&& !(mFlags & (TEXT_RUNNING | SEEK_PREVIEW))) {
//播放字幕
mTextDriver->start();
modifyFlags(TEXT_RUNNING, SET);
}

...未完,待续...
}

       上述代码也分步完:
       ①从上一步读取到的视频解码帧数据中取出它的时间戳。
       ②完成audio的seek。之前说如果有seek请求,则先pause住audio,读取seek的video数据,拿到第一帧数据后,以此数据为标准,来seek audio,此处finishSeekIfNecessary便是完成此功能。我们可以看看finishSeekIfNecessary方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
void AwesomePlayer::finishSeekIfNecessary(int64_t videoTimeUs) {
ATRACE_CALL();
//如果是仅仅seek视频,则return
if (mSeeking == SEEK_VIDEO_ONLY) {
mSeeking = NO_SEEK;
return;
}
//如果没有拖动或者是预览时候
if (mSeeking == NO_SEEK || (mFlags & SEEK_PREVIEW)) {
return;
}

// If we paused, then seeked, then resumed, it is possible that we have
// signaled SEEK_COMPLETE at a copmletely different media time than where
// we are now resuming. Signal new position to media time provider.
// Cannot signal another SEEK_COMPLETE, as existing clients may not expect
// multiple SEEK_COMPLETE responses to a single seek() request.
//一个seek请求在恢复拖动后可能会发送多个SEEK_COMPLETE消息,客户端当然不希望这样,所以需要处理掉多余的SEEK_COMPLETE
if (mSeekNotificationSent && abs(mSeekTimeUs - videoTimeUs) > 10000) {
// notify if we are resuming more than 10ms away from desired seek time
notifyListener_l(MEDIA_SKIPPED);
}

if (mAudioPlayer != NULL) {
ALOGV("seeking audio to %" PRId64 " us (%.2f secs).", videoTimeUs, videoTimeUs / 1E6);

// If we don't have a video time, seek audio to the originally
// requested seek time instead.
//如果指定了拖动时间点mSeekTimeUs且它大于0,则拖到这个位置;如果没有则拖到原始请求拖动时候的时间点
mAudioPlayer->seekTo(videoTimeUs < 0 ? mSeekTimeUs : videoTimeUs);
mWatchForAudioSeekComplete = true;
mWatchForAudioEOS = true;
} else if (!mSeekNotificationSent) {
// If we're playing video only, report seek complete now,
// otherwise audio player will notify us later.
//若果仅仅是播放视频,则不用拖动音频
notifyListener_l(MEDIA_SEEK_COMPLETE);
mSeekNotificationSent = true;
}
//拖动完后要设置第一帧标志位FIRST_FRAME
modifyFlags(FIRST_FRAME, SET);
mSeeking = NO_SEEK;

......
}

       ③seek完audio后,然后开始播放音频。我们看看startAudioPlayer_l:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
status_t AwesomePlayer::startAudioPlayer_l(bool sendErrorNotification) {

......

if (!(mFlags & AUDIOPLAYER_STARTED)) {//如果还没有开始播放
bool wasSeeking = mAudioPlayer->isSeeking();

// We've already started the MediaSource in order to enable
// the prefetcher to read its data.
//开始播放
err = mAudioPlayer->start(
true /* sourceAlreadyStarted */);
//错误处理
if (err != OK) {
if (sendErrorNotification) {
notifyListener_l(MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, err);
}

return err;
}
//修改标志位
modifyFlags(AUDIOPLAYER_STARTED, SET);
//如果是拖动
if (wasSeeking) {
CHECK(!mAudioPlayer->isSeeking());

// We will have finished the seek while starting the audio player.
//发送完成拖动回调
postAudioSeekComplete();
} else {
notifyIfMediaStarted_l();
}
} else {//如果已经开始了,则恢复播放
err = mAudioPlayer->resume();
}
//播放成功,设置标志位
if (err == OK) {
modifyFlags(AUDIO_RUNNING, SET);

mWatchForAudioEOS = true;
}

return err;
}

       这样就开始播放音频了,关于AudioPlayer的playback流程我们以后再讲。

       ④然后播放字幕。

音视频同步

Part.4:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void AwesomePlayer::onVideoEvent() {

...Part.1...
...Part.2...
...Part.3...

//同步时钟,是mTimeSource,即AudioPlayer
TimeSource *ts =
((mFlags & AUDIO_AT_EOS) || !(mFlags & AUDIOPLAYER_STARTED))
? &mSystemTimeSource : mTimeSource;
int64_t systemTimeUs = mSystemTimeSource.getRealTimeUs();
int64_t looperTimeUs = ALooper::GetNowUs();
//如果是第一帧
if (mFlags & FIRST_FRAME) {
modifyFlags(FIRST_FRAME, CLEAR);
mSinceLastDropped = 0;
mClockEstimator->reset();
//在4.4以及往前的版本是 mTimeSourceDeltaUs = ts->getRealTimeUs() - timeUs;
//5.0只是更精准了而已,使用了一个WindowedLinearFitEstimator工具类,有兴趣的可以研究。其实和4.4结果差别不大
mTimeSourceDeltaUs = estimateRealTimeUs(ts, systemTimeUs) - timeUs;
}

int64_t realTimeUs, mediaTimeUs;
//如果不是第一帧,mAudioPlayer->getMediaTimeMapping(&realTimeUs, &mediaTimeUs)返回true,而在第一帧返回false
if (!(mFlags & AUDIO_AT_EOS) && mAudioPlayer != NULL
&& mAudioPlayer->getMediaTimeMapping(&realTimeUs, &mediaTimeUs)) {
ALOGV("updating TSdelta (%" PRId64 " => %" PRId64 " change %" PRId64 ")",
mTimeSourceDeltaUs, realTimeUs - mediaTimeUs,
mTimeSourceDeltaUs - (realTimeUs - mediaTimeUs));
ATRACE_INT("TS delta change (ms)", (mTimeSourceDeltaUs - (realTimeUs - mediaTimeUs)) / 1E3);
mTimeSourceDeltaUs = realTimeUs - mediaTimeUs;
}
//如果是仅仅拖动视频
if (wasSeeking == SEEK_VIDEO_ONLY) {
int64_t nowUs = estimateRealTimeUs(ts, systemTimeUs) - mTimeSourceDeltaUs;

int64_t latenessUs = nowUs - timeUs;

ATRACE_INT("Video Lateness (ms)", latenessUs / 1E3);

if (latenessUs > 0) {
ALOGI("after SEEK_VIDEO_ONLY we're late by %.2f secs", latenessUs / 1E6);
}
}

...未完,待续...
}

       上面是更新时间信息:
       ①首先获取时钟源,系统时钟或者audio时钟。我们这里是audio时钟,即AudioPlayer。
       ②先要说明几个变量的意义:

  • timeUs:这是下一视频帧画面的时间戳
  • ts->getRealTimeUs():这是通过计算播放了多少audio帧换算出来的实际时间。这里我们可以看看AudioPlayer的getRealTimeUs函数的实现,填上上面的坑:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
int64_t AudioPlayer::getRealTimeUs() {
Mutex::Autolock autoLock(mLock);
if (useOffload()) {//如果支持音频分载
if (mSeeking) {
return mSeekTimeUs;
}
//则从mAudioSink或者mAudioTrack处获取已经播放帧数消耗时间
mPositionTimeRealUs = getOutputPlayPositionUs_l();
return mPositionTimeRealUs;
}
//直接计算播放了多少帧消耗时间
return getRealTimeUsLocked();
}

int64_t AudioPlayer::getRealTimeUsLocked() const {
CHECK(mStarted);
CHECK_NE(mSampleRate, 0);
//已经播放的帧数 * 1s / 帧率 = 已经播放帧数消耗时间
int64_t result = -mLatencyUs + (mNumFramesPlayed * 1000000) / mSampleRate;

// Compensate for large audio buffers, updates of mNumFramesPlayed
// are less frequent, therefore to get a "smoother" notion of time we
// compensate using system time.
int64_t diffUs;
if (mPinnedTimeUs >= 0ll) {//-1ll,小于0
diffUs = mPinnedTimeUs;
} else {
diffUs = ALooper::GetNowUs();
}
//mNumFramesPlayedSysTimeUs = ALooper::GetNowUs()
diffUs -= mNumFramesPlayedSysTimeUs;
//上面都是障眼法
return result + diffUs;
}

int64_t AudioPlayer::getOutputPlayPositionUs_l()
{
uint32_t playedSamples = 0;
uint32_t sampleRate;
if (mAudioSink != NULL) {//mAudioSink 不为空,setDataSource时已经设置了
mAudioSink->getPosition(&playedSamples);
sampleRate = mAudioSink->getSampleRate();
} else {//如果为空
mAudioTrack->getPosition(&playedSamples);
sampleRate = mAudioTrack->getSampleRate();
}
if (sampleRate != 0) {
mSampleRate = sampleRate;
}

int64_t playedUs;
//一共播放时间
if (mSampleRate != 0) {
playedUs = (static_cast<int64_t>(playedSamples) * 1000000 ) / mSampleRate;
} else {
playedUs = 0;
}

// HAL position is relative to the first buffer we sent at mStartPosUs
//计算出总的渲染时间
const int64_t renderedDuration = mStartPosUs + playedUs;
ALOGV("getOutputPlayPositionUs_l %" PRId64, renderedDuration);
return renderedDuration;
}
  • realTimeUs: 这是从mAudioplayer中获取的信息(如果有audio的话),是当前播放位置的时间
  • mediaTimeUs:下一音频帧的时间戳

       附注:音频跟视频很不一样,视频每一帧就是一张图像,而从上面的正玄波可以看出,音频数据是流式的,本身没有明确的一帧帧的概念,在实际的应用中,为了音频算法处理/传输的方便,一般约定俗成取2.5ms~60ms为单位的数据量为一帧音频。
       这个时间被称之为“采样时间”,其长度没有特别的标准,它是根据编解码器和具体应用的需求来决定的,我们可以计算一下一帧音频帧的大小:
       假设某通道的音频信号是采样率为8kHz,位宽为16bit,20ms一帧,双通道,则一帧音频数据的大小为:
       int size = 8000 x 16bit x 0.02s x 2 = 5120 bit = 640 byte

       这两个变量都是根据mAudioPlayer->getMediaTimeMapping(&realTimeUs, &mediaTimeUs)方法获取的,我们进入其中看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    /*在构造函数里这两个变量初始赋值为-1
mPositionTimeMediaUs(-1), //这是从mAudioplayer中获取的信息(如果有audio的话),是当前播放位置的时间
mPositionTimeRealUs(-1), //下一音频帧的时间戳
*/

bool AudioPlayer::getMediaTimeMapping(
int64_t *realtime_us, int64_t *mediatime_us) {
Mutex::Autolock autoLock(mLock);

if (useOffload()) {
mPositionTimeRealUs = getOutputPlayPositionUs_l();
*realtime_us = mPositionTimeRealUs;
*mediatime_us = mPositionTimeRealUs;
} else {
*realtime_us = mPositionTimeRealUs;
*mediatime_us = mPositionTimeMediaUs;
}
//如果是第一帧,则两个变量都是-1,则返回false;如果不是第一帧,则返回true
return mPositionTimeRealUs != -1 && mPositionTimeMediaUs != -1;
}

       ③这正好应对了Part.4的逻辑:

  • 如果是第一帧画面则mTimeSourceDeltaUs=ts->getRealTimeUs() - timeUs;
  • 如果不是第一帧画面则:mTimeSourceDeltaUs = realTimeUs - mediaTimeUs;

        对于第一帧的处理,在4.4以及往前的版本是 mTimeSourceDeltaUs = ts->getRealTimeUs() - timeUs; 5.0只是更精准了而已,使用了一个WindowedLinearFitEstimator工具类,有兴趣的可以研究。其实和4.4结果差别不大,我为了方便分析,所以取这个值。

       下面wasSeeking == SEEK_VIDEO_ONLY先忽略掉,我们继续往下:

Part.5:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78

void AwesomePlayer::onVideoEvent() {

...Part.1...
...Part.2...
...Part.3...
...Part.4...

int64_t latenessUs = 0;//时间偏差latency
//播放视频
if (wasSeeking == NO_SEEK) {
// Let's display the first frame after seeking right away.
//同4.4处理, int64_t nowUs = ts->getRealTimeUs() - mTimeSourceDeltaUs;
int64_t nowUs = estimateRealTimeUs(ts, systemTimeUs) - mTimeSourceDeltaUs;

latenessUs = nowUs - timeUs;

ATRACE_INT("Video Lateness (ms)", latenessUs / 1E3);
//如果latency过大,则要跳帧,seek到固定为止
if (latenessUs > 500000ll
&& mAudioPlayer != NULL
&& mAudioPlayer->getMediaTimeMapping(
&realTimeUs, &mediaTimeUs)) {
if (mWVMExtractor == NULL) {//如果不是google的私货
ALOGI("we're much too late (%.2f secs), video skipping ahead",
latenessUs / 1E6);
//清空VideoBuffer,为下一次读取解码帧做准备
mVideoBuffer->release();
mVideoBuffer = NULL;
//跳帧仅仅拖动视频
mSeeking = SEEK_VIDEO_ONLY;
mSeekTimeUs = mediaTimeUs;
//postVideoEvent_l函数参数缺省为-1,所以10ms后下一次onVideoEvent
postVideoEvent_l();
return;
} else {//是google的私货,good bye
// The widevine extractor doesn't deal well with seeking
// audio and video independently. We'll just have to wait
// until the decoder catches up, which won't be long at all.
ALOGI("we're very late (%.2f secs)", latenessUs / 1E6);
}
}
//如果latency稍大,则要丢帧
if (latenessUs > 40000) {
// We're more than 40ms late.
ALOGV("we're late by %" PRId64 " us (%.2f secs)",
latenessUs, latenessUs / 1E6);

if (!(mFlags & SLOW_DECODER_HACK)
|| mSinceLastDropped > FRAME_DROP_FREQ)
{
ALOGV("we're late by %" PRId64 " us (%.2f secs) dropping "
"one after %d frames",
latenessUs, latenessUs / 1E6, mSinceLastDropped);

mSinceLastDropped = 0;
mVideoBuffer->release();
mVideoBuffer = NULL;

{
Mutex::Autolock autoLock(mStatsLock);
//丢帧数+1
++mStats.mNumVideoFramesDropped;
}
//丢帧后立刻下一次onVideoEvent
postVideoEvent_l(0);
return;
}
}
//如果latency超前了,则要等待延时下一次onVideoEvent
if (latenessUs < -30000) {
// We're more than 30ms early, schedule at most 20 ms before time due
postVideoEvent_l(latenessUs < -60000 ? 30000 : -latenessUs - 20000);
return;
}
}
...未完,待续...
}

       上面代码和之前的时间处理要结合起来看,计算出来mTimeSourceDeltaUs之后,就可以分析播放信息如:

  • 当前播放进度,即int64_t nowUs = ts->getRealTimeUs() - mTimeSourceDeltaUs;
  • 播放的latency: int64_t latenessUs = nowUs - timeUs; (这里timeUs是下一帧的时间戳)

       下面是处理latency过大的情况:这里比对参考是audio或者系统时钟,即与音视频同步的处理:

  • 超过500000ll US,则seek到对应位置,跳帧
  • 超过40000 则丢帧处理
  • 当比参考时钟早了30ms,则通过postVideoEvent_l(latenessUs < -60000 ? 30000 : -latenessUs - 20000);延迟触发下一次的mVideoEvent

       音视频同步就分析到这里,自我感觉挺重要的。

视频播放

Part.6:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void AwesomePlayer::onVideoEvent() {

...Part.1...
...Part.2...
...Part.3...
...Part.4...
...Part.5...

if ((mNativeWindow != NULL)
&& (mVideoRendererIsPreview || mVideoRenderer == NULL)) {
mVideoRendererIsPreview = false;
//初始化视窗
initRenderer_l();
}

if (mVideoRenderer != NULL) {
mSinceLastDropped++;
mVideoBuffer->meta_data()->setInt64(kKeyTime, looperTimeUs - latenessUs);
//开始渲染画面
mVideoRenderer->render(mVideoBuffer);
if (!mVideoRenderingStarted) {
mVideoRenderingStarted = true;
notifyListener_l(MEDIA_INFO, MEDIA_INFO_RENDERING_START);
}

if (mFlags & PLAYING) {
notifyIfMediaStarted_l();
}
}

mVideoBuffer->release();
mVideoBuffer = NULL;

if (wasSeeking != NO_SEEK && (mFlags & SEEK_PREVIEW)) {
modifyFlags(SEEK_PREVIEW, CLEAR);
return;
}
...未完,待续...
}

       最后就是显示此帧画面了,当播放过程一切正常时,则显示此帧画面。我们可以看看初始化Renderer的方法initRenderer_l:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
void AwesomePlayer::initRenderer_l() {
ATRACE_CALL();

if (mNativeWindow == NULL) {
return;
}
//先获取视频元数据
sp<MetaData> meta = mVideoSource->getFormat();

int32_t format;
const char *component;
int32_t decodedWidth, decodedHeight;
CHECK(meta->findInt32(kKeyColorFormat, &format));//颜色编码
CHECK(meta->findCString(kKeyDecoderComponent, &component));//解码组件
CHECK(meta->findInt32(kKeyWidth, &decodedWidth));//视频宽度
CHECK(meta->findInt32(kKeyHeight, &decodedHeight));//视频高度

int32_t rotationDegrees;//获取旋转角度
if (!mVideoTrack->getFormat()->findInt32(
kKeyRotation, &rotationDegrees)) {
rotationDegrees = 0;
}

mVideoRenderer.clear();

// Must ensure that mVideoRenderer's destructor is actually executed
// before creating a new one.
IPCThreadState::self()->flushCommands();

// Even if set scaling mode fails, we will continue anyway
setVideoScalingMode_l(mVideoScalingMode);
//如果是硬解
if (USE_SURFACE_ALLOC
&& !strncmp(component, "OMX.", 4)
&& strncmp(component, "OMX.google.", 11)) {
// Hardware decoders avoid the CPU color conversion by decoding
// directly to ANativeBuffers, so we must use a renderer that
// just pushes those buffers to the ANativeWindow.
mVideoRenderer =
new AwesomeNativeWindowRenderer(mNativeWindow, rotationDegrees);
} else {//如果是软解
// Other decoders are instantiated locally and as a consequence
// allocate their buffers in local address space. This renderer
// then performs a color conversion and copy to get the data
// into the ANativeBuffer.
sp<AMessage> format;
convertMetaDataToMessage(meta, &format);
mVideoRenderer = new AwesomeLocalRenderer(mNativeWindow, format);
}
}

       就是对硬解和软解选择不同的Renderer。

Part.7:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
void AwesomePlayer::onVideoEvent() {

...Part.1...
...Part.2...
...Part.3...
...Part.4...
...Part.5...
...Part.6...

/* get next frame time 获取下一视频帧的时间*/
if (wasSeeking == NO_SEEK) {
MediaSource::ReadOptions options;
for (;;) {
//同上,读取下一视频帧数据
status_t err = mVideoSource->read(&mVideoBuffer, &options);
if (err != OK) {
// deal with any errors next time
CHECK(mVideoBuffer == NULL);
postVideoEvent_l(0);
return;
}

if (mVideoBuffer->range_length() != 0) {
break;
}

// Some decoders, notably the PV AVC software decoder
// return spurious empty buffers that we just want to ignore.

mVideoBuffer->release();
mVideoBuffer = NULL;
}

{
Mutex::Autolock autoLock(mStatsLock);
++mStats.mNumVideoFramesDecoded;
}

int64_t nextTimeUs;
//下一视频帧时间戳
CHECK(mVideoBuffer->meta_data()->findInt64(kKeyTime, &nextTimeUs));
systemTimeUs = mSystemTimeSource.getRealTimeUs();
//延时时间
int64_t delayUs = nextTimeUs - estimateRealTimeUs(ts, systemTimeUs) + mTimeSourceDeltaUs;
ATRACE_INT("Frame delta (ms)", (nextTimeUs - timeUs) / 1E3);
ALOGV("next frame in %" PRId64, delayUs);
// try to schedule 30ms before time due
//延时处理
postVideoEvent_l(delayUs > 60000 ? 30000 : (delayUs < 30000 ? 0 : delayUs - 30000));
return;
}
//如果在get next frame time上述逻辑return了,就不会触发这个了
postVideoEvent_l();
}

       上述逻辑主要是获取下一视频帧的先关时间信息,然后通过计算延时处理下一次onVideEvent回调。如果在get next frame time上述逻辑return了,就不会触发末尾的postVideoEvent_l了。

       分析到这里大家应该明白,awesoemplayer的播放驱动机制即通过递归的调用postVideoEvent_l(); 来完成。而且由于postVideoEvent_l(); 里有延迟触发消息机制,因此也不会阻塞。

       现在应该回到play_l函数中,处理消息递归处理最后一步:

1
2
3
4
5
6
7
8
if (mVideoSource != NULL) {
// Kick off video playback
postVideoEvent_l();//这个已经处理过了

if (mAudioSource != NULL && mVideoSource != NULL) {
postVideoLagEvent_l();//然后是这个消息处理
}
}

       postVideoLagEvent看下此事件的处理方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

void AwesomePlayer::onVideoLagUpdate() {
Mutex::Autolock autoLock(mLock);
if (!mVideoLagEventPending) {
return;
}
mVideoLagEventPending = false;

int64_t audioTimeUs = mAudioPlayer->getMediaTimeUs();
int64_t videoLateByUs = audioTimeUs - mVideoTimeUs;

if (!(mFlags & VIDEO_AT_EOS) && videoLateByUs > 300000ll) {
ALOGV("video late by %lld ms.", videoLateByUs / 1000ll);

notifyListener_l(
MEDIA_INFO,
MEDIA_INFO_VIDEO_TRACK_LAGGING,
videoLateByUs / 1000ll);
}

postVideoLagEvent_l();
}

       这一段其实没什么多大意义,主要是为了更新信息。我们可以看看MEDIA_INFO_VIDEO_TRACK_LAGGING 这个标志位的意义:

1
2
3
// The video is too complex for the decoder: it can't decode frames fast
// enough. Possibly only the audio plays fine at this stage.
MEDIA_INFO_VIDEO_TRACK_LAGGING = 700,

       可以知道当视频解码速度不够时,会通知上层,decoder不给力。

       上述就是播放流程的大概过程。

其他回调事件分析

mStreamDoneEvent

       这里是当vidoe播放结束后会触发,在onVideoEvent中当读取帧数据失败时。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
void AwesomePlayer::onStreamDone() {
// Posted whenever any stream finishes playing.
ATRACE_CALL();

Mutex::Autolock autoLock(mLock);
//mStreamDoneEvent 事件是否存在
if (!mStreamDoneEventPending) {
return;
}
mStreamDoneEventPending = false;
//错误处理
if (mStreamDoneStatus != ERROR_END_OF_STREAM) {
ALOGV("MEDIA_ERROR %d", mStreamDoneStatus);

notifyListener_l(
MEDIA_ERROR, MEDIA_ERROR_UNKNOWN, mStreamDoneStatus);

pause_l(true /* at eos */);

modifyFlags(AT_EOS, SET);
return;
}
//是否播放完了
const bool allDone =
(mVideoSource == NULL || (mFlags & VIDEO_AT_EOS))
&& (mAudioSource == NULL || (mFlags & AUDIO_AT_EOS));

if (!allDone) {
return;
}
//如果是循环播放
if ((mFlags & LOOPING)
|| ((mFlags & AUTO_LOOPING)
&& (mAudioSink == NULL || mAudioSink->realtime()))) {
// Don't AUTO_LOOP if we're being recorded, since that cannot be
// turned off and recording would go on indefinitely.
//seek到起始位置
seekTo_l(0);

if (mVideoSource != NULL) {
postVideoEvent_l();
}
} else {
ALOGV("MEDIA_PLAYBACK_COMPLETE");
//通知上层播放完毕
notifyListener_l(MEDIA_PLAYBACK_COMPLETE);
//播放完毕,暂停
pause_l(true /* at eos */);

// If audio hasn't completed MEDIA_SEEK_COMPLETE yet,
// notify MEDIA_SEEK_COMPLETE to observer immediately for state persistence.
if (mWatchForAudioSeekComplete) {
notifyListener_l(MEDIA_SEEK_COMPLETE);
mWatchForAudioSeekComplete = false;
}

modifyFlags(AT_EOS, SET);
}
}

       主要做了这几件事:

  • 判断是否真的播放完毕了
  • 若播放完毕了,是否需要循环,若需要则调用seekTo_l(0)继续播放
  • 否则,通知上层本次播放结束,发送MEDIA_PLAYBACK_COMPLETE给调用者

mBufferingEvent

       awesomeplayer中通过调用postBufferingEvent_l来触发此事件,作用是缓冲数据。调用的位置有:

1
2
3
4
5
6
7
8
9
10
void AwesomePlayer::onPrepareAsyncEvent() {  

......

if(isStreamingHTTP()) {
postBufferingEvent_l();
}else{
finishAsyncPrepare_l();
}
}

       当时网络流的时候,先缓冲一部分数据,看下具体实现:

1
2
3
4
5
6
7
void AwesomePlayer::postBufferingEvent_l() {  
if(mBufferingEventPending) {
return;
}
mBufferingEventPending =true;
mQueue.postEventWithDelay(mBufferingEvent, 1000000ll);
}

       首先修改标志位mBufferingEventPending,之后触发消息。
       这里就不贴代码了,说说原理:当需要cache数据的时候,在onPrepareAsyncEvent调用postBufferingEvent_l 后onPrepareAsyncEvent 就结束了。由于此时解码器已经开始解码,即数据链路已经建立。因此会不断的进行 读数据-解码的操作,而在onBufferingUpdate响应函数中,会先pause住输出,等数据缓存足够了之后,调用finishAsyncPrepare_l等完成prepareAsync的操作。

结语

       到这里播放流程就分析完了,最复杂的还是递归消息处理那一段。流程中对于Video Buffer传入流程和Audio Playback流程还没分析,这个我们以后再讲。
妹子

坚持技术分享,您的支持将鼓励我继续创作!