前面几篇都是视频文件播放前的准备工作,比如设置数据源,初始化解码器等等,本节我们分析MediaPlayer播放器start之后的流程。
播放流程
基本流程
当MediaPlayer prepared成功之后,调用start就开始播放视频了。从前几篇我们知道底层会调用到StageFrightPlayer,最后到AwesomePlayer,我们继续跟踪下去:位于frameworks/av/media/libmediaplayerservice/StageFrightPlayer.cpp:1
2
3
4
5
6
7
8
9
10
11
12status_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
9status_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
33void 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
20sp<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
6class AudioPlayer : public TimeSource {
int64_t AudioPlayer::getRealTimeUs() {
......
}
}
关于getRealTimeUs函数我们后面会分析到。
往下走play_l方法里还有这样的逻辑:1
2
3if (mTimeSource == NULL && mAudioPlayer == NULL) {
mTimeSource = &mSystemTimeSource;
}
设置同步时钟的,这里如果mAudioPlayer存在的话,以audio为基准进行播放,否则以系统时钟为基准控制播放。但我们的mAudioPlayer存在,所以已AudioPlayer作为参考基准控制播放。
注意:这里有一个重点,就是播放的同步时钟是以音频为参考时间的,后面的视频和字幕也是参考这个同步时钟来渲染和展示的。
2)递归消息处理,这一步就是处理音频/视频/字幕的播放核心
然后就是递归postVideoEvent_l()消息处理,我们看看这个方法:1
2
3
4
5
6
7
8
9
10
11void 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 | void AwesomePlayer::onVideoEvent() { |
首先是判断是否需要seek,若需要seek,则先pause住audio,先完成video的seek,后面再seek audio。
Part.2:
1 | void AwesomePlayer::onVideoEvent() { |
上述代码分两部分:
①第一部分判断是否需要seek,若需要则设置option。
②第二部分是从mVideoSource中读取一帧画面,这里读取的时候会将option传入,如果需要seek,则读取出的数据直接就是seek后的解码数据。(中间还有些小细节:如果数据读取失败,则检查宽度高度是否发生变化,否则即video播放完毕了,设置EOF标记,并触发mStreamDoneEvent消息。)
mVideoSource->read是经过omx调用底层解码器后,读取返回的已经解码过的视频帧数据。关于和omx的调用关系我们以后会讲到。
音频播放
Part.3:
1 | void AwesomePlayer::onVideoEvent() { |
上述代码也分步完:
①从上一步读取到的视频解码帧数据中取出它的时间戳。
②完成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
45void 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
44status_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 | void AwesomePlayer::onVideoEvent() { |
上面是更新时间信息:
①首先获取时钟源,系统时钟或者audio时钟。我们这里是audio时钟,即AudioPlayer。
②先要说明几个变量的意义:
- timeUs:这是下一视频帧画面的时间戳
- ts->getRealTimeUs():这是通过计算播放了多少audio帧换算出来的实际时间。这里我们可以看看AudioPlayer的getRealTimeUs函数的实现,填上上面的坑:
1 | int64_t AudioPlayer::getRealTimeUs() { |
- 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 |
|
上面代码和之前的时间处理要结合起来看,计算出来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 | void AwesomePlayer::onVideoEvent() { |
最后就是显示此帧画面了,当播放过程一切正常时,则显示此帧画面。我们可以看看初始化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
50void 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 | void AwesomePlayer::onVideoEvent() { |
上述逻辑主要是获取下一视频帧的先关时间信息,然后通过计算延时处理下一次onVideEvent回调。如果在get next frame time上述逻辑return了,就不会触发末尾的postVideoEvent_l了。
分析到这里大家应该明白,awesoemplayer的播放驱动机制即通过递归的调用postVideoEvent_l(); 来完成。而且由于postVideoEvent_l(); 里有延迟触发消息机制,因此也不会阻塞。
现在应该回到play_l函数中,处理消息递归处理最后一步:1
2
3
4
5
6
7
8if (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
59void 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
10void AwesomePlayer::onPrepareAsyncEvent() {
......
if(isStreamingHTTP()) {
postBufferingEvent_l();
}else{
finishAsyncPrepare_l();
}
}
当时网络流的时候,先缓冲一部分数据,看下具体实现:1
2
3
4
5
6
7void 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流程还没分析,这个我们以后再讲。