全新系列文章已更新:
- Android Media Framework - 开篇
- Android Media Framework(一)OpenMAX 框架简介
- Android Media Framework(二)OpenMAX 类型阅读与分析
- Android Media Framework(三)OpenMAX API阅读与分析
- Android Media Framework(四)Non-Tunneled组件的状态转换与buffer分配过程分析
- Android Media Framework(五)Tunnel Mode
- Android Media Framework(六)插件式编程与OMXStore
- Android Media Framework(七)MediaCodecService
- Android Media Framework(八)OMXNodeInstance - Ⅰ
- Android Media Framework(九)OMXNodeInstance - Ⅱ
- Android Media Framework(十)OMXNodeInstance - Ⅲ
Source 在播放器中起着拉流(Streaming)和解复用(demux)的作用,Source 设计的好坏直接影响到播放器的基础功能,我们这一节将会了解 NuPlayer 中的通用 Source(GenericSource)关注本地播放架构,直播流暂时先不研究。
1、NuPlayer::Source
NuPlayer::Source 是一个抽象类,定义了 Source 实现所需要的基本接口,例如 prepareAsync,start,dequeueAccessUnit等,除此之外还包含了一些共有的方法,例如 Callback发送 等。
android 为我们提供了5种 Source 实现,分别为:
HTTPLiveSource
:url 为 m3u8 结尾的 http 链接时,NuPlayer 将创建 HTTPLiveSource;RTSPSource
:url 为 rtsp 开头,或者以sdp结尾时,将创建 RTSPSource;RTPSource
:GenericSource
:通用 Source,当 url 不符合以上创建条件时会创建该 Source,一般用于本地播放;StreamingSource
:NuPlayer 的 setDataSource 提供了一个以IStreamSource
为参数的版本,意为由上层自己实现 Source,参数 IStreamSource 将被封装在 StreamingSource 中供 NuPlayer 使用。如果我们想在 native 自定义 Source,可以实现 IStreamSource 接口,然后调用这个版本的 setDataSource 方法。
NuPlayer::Source 提供有如下基本播放控制接口,具体实现可根据需求覆写这些实现:
virtual void prepareAsync() = 0;
virtual void start() = 0;
virtual void stop() {}
virtual void pause() {}
virtual void resume() {}
virtual void disconnect() {}
virtual status_t feedMoreTSData() = 0;
virtual status_t dequeueAccessUnit(bool audio, sp<ABuffer> *accessUnit) = 0;
virtual status_t seekTo(int64_t /* seekTimeUs */,MediaPlayerSeekMode /* mode */ = MediaPlayerSeekMode::SEEK_PREVIOUS_SYNC) ;
NuPlayer::Source 提供有名为 Flags 的枚举类型,Flags 标记了当前播放码流所支持的操作,执行完 prepareAsync 后会将 Flags 信息上抛,最终打开或关闭上层的一些功能:
enum Flags {
FLAG_CAN_PAUSE = 1,
FLAG_CAN_SEEK_BACKWARD = 2, // the "10 sec back button"
FLAG_CAN_SEEK_FORWARD = 4, // the "10 sec forward button"
FLAG_CAN_SEEK = 8, // the "seek bar"
FLAG_DYNAMIC_DURATION = 16,
FLAG_SECURE = 32, // Secure codec is required.
FLAG_PROTECTED = 64, // The screen needs to be protected (screenshot is disabled).
};
Source 运行过程中可能会有如下事件上抛给 NuPlayer,上抛所调用的函数实现在 NuPlayer.cpp 中:
kWhatPrepared
:Source prepare 完成通知 NuPlayer;kWhatFlagsChanged
:prepare 过程中获取的码流支持的操作,prepare 过程中上抛给 NuPlayer;kWhatVideoSizeChanged
:prepare 过程中获取的码流宽高等信息,prepare 过程中上抛给 NuPlayer;kWhatBufferingUpdate
:上抛当前 buffering 的百分比;kWhatPauseOnBufferingStart
:上抛 buffering 开始事件;kWhatResumeOnBufferingEnd
:上抛 buffering 结束事件;kWhatCacheStats
:上抛当前的缓存带宽信息;kWhatInstantiateSecureDecoders
:上抛信息创建 secure decoder;
2、GenericSource
GenericSource 名为通用 source,但是往往它会被用来当作本地播放的 Source,由于本地播放的码流文件会有形形色色的封装格式,所以这个 source 会依赖解封装(demux)服务 media.extractor。除了依赖解封装外,source 还需要一个 IO 来读取码流,这个 IO 被封装在 DataSource中。Source、DataSource、Extractor三者的关系如下:
2.1、prepareAsync
void NuPlayer::GenericSource::prepareAsync() {
Mutex::Autolock _l(mLock);
ALOGV("prepareAsync: (looper: %d)", (mLooper != NULL));
if (mLooper == NULL) {
mLooper = new ALooper;
mLooper->setName("generic");
mLooper->start();
mLooper->registerHandler(this);
}
sp<AMessage> msg = new AMessage(kWhatPrepareAsync, this);
msg->post();
}
GenericSource 的 ALooper 包含在它的类内部,在 prepareAsync 中完成创建和注册。prepareAsync 的处理比较长,这里对代码进行精简阅读:
void NuPlayer::GenericSource::onPrepareAsync() {
mDisconnectLock.lock();
// delayed data source creation
if (mDataSource == NULL) {
mIsSecure = false;
if (!mUri.empty()) {
const char* uri = mUri.c_str();
String8 contentType;
// ......
// 1. 使用 DataSource 工厂创建一个 datasource
sp<DataSource> dataSource = PlayerServiceDataSourceFactory::getInstance()
->CreateFromURI(mHTTPService, uri, &mUriHeaders, &contentType,
static_cast<HTTPBase *>(mHttpSource.get()));
// ......
if (!mDisconnected) {
mDataSource = dataSource;
}
}
// ......
}
// 这里的 mIsStreaming 表示当前 Source 是不是网络串流的
if (mDataSource->flags() & DataSource::kIsCachingDataSource) {
mCachedSource = static_cast<NuCachedSource2 *>(mDataSource.get());
}
// For cached streaming cases, we need to wait for enough
// buffering before reporting prepared.
mIsStreaming = (mCachedSource != NULL);
// 2、使用 DataSource 创建 Extractor
// init extractor from data source
status_t err = initFromDataSource();
// 3、获取 Extractor 解析到的 video track 信息
if (mVideoTrack.mSource != NULL) {
sp<MetaData> meta = getFormatMeta_l(false /* audio */);
sp<AMessage> msg = new AMessage;
err = convertMetaDataToMessage(meta, &msg);
if(err != OK) {
notifyPreparedAndCleanup(err);
return;
}
notifyVideoSizeChanged(msg);
}
// 4、将source flag 上抛
notifyFlagsChanged(
// FLAG_SECURE will be known if/when prepareDrm is called by the app
// FLAG_PROTECTED will be known if/when prepareDrm is called by the app
FLAG_CAN_PAUSE |
FLAG_CAN_SEEK_BACKWARD |
FLAG_CAN_SEEK_FORWARD |
FLAG_CAN_SEEK);
// 5、将prepareAsync完成消息上抛
finishPrepareAsync();
}
onPrepareAsync 主要做了如下几件事情:
- 使用 DataSource 工厂创建合适的 datasource;
- 调用 media.extractor 服务,将 DataSource 作为参数传入,创建 Extractor;
- 获取 Extractor 解析到的 video track 信息,将信息上抛;
- 将source flag 上抛;
- 将prepareAsync完成消息上抛。
status_t NuPlayer::GenericSource::initFromDataSource() {
sp<IMediaExtractor> extractor;
sp<DataSource> dataSource;
{
Mutex::Autolock _l_d(mDisconnectLock);
dataSource = mDataSource;
}
// 1、创建 IExtractor
// This might take long time if data source is not reliable.
extractor = MediaExtractorFactory::Create(dataSource, NULL);
// 2、获取码流信息,包含码流时长等信息
sp<MetaData> fileMeta = extractor->getMetaData();
// 3、获取 track 数量
size_t numtracks = extractor->countTracks();
mFileMeta = fileMeta;
if (mFileMeta != NULL) {
int64_t duration;
if (mFileMeta->findInt64(kKeyDuration, &duration)) {
mDurationUs = duration;
}
}
int32_t totalBitrate = 0;
mMimes.clear();
// 4、遍历所有的 track
for (size_t i = 0; i < numtracks; ++i) {
sp<IMediaSource> track = extractor->getTrack(i);
if (track == NULL) {
continue;
}
// 获取 track 的信息,包含 mime type
sp<MetaData> meta = extractor->getTrackMetaData(i);
const char *mime;
CHECK(meta->findCString(kKeyMIMEType, &mime));
// 创建一个 Track 来封装获取到的 track source,以及 track buffer pool
if (!strncasecmp(mime, "audio/", 6)) {
if (mAudioTrack.mSource == NULL) {
mAudioTrack.mIndex = i;
mAudioTrack.mSource = track;
mAudioTrack.mPackets =
new AnotherPacketSource(mAudioTrack.mSource->getFormat());
if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) {
mAudioIsVorbis = true;
} else {
mAudioIsVorbis = false;
}
// 将 mime 加入到 vector 中
mMimes.add(String8(mime));
}
} else if (!strncasecmp(mime, "video/", 6)) {
if (mVideoTrack.mSource == NULL) {
mVideoTrack.mIndex = i;
mVideoTrack.mSource = track;
mVideoTrack.mPackets =
new AnotherPacketSource(mVideoTrack.mSource->getFormat());
// 将 video mime 放在容器第一个
mMimes.insertAt(String8(mime), 0);
}
}
// 将 track source 存储到 vector 中
mSources.push(track);
int64_t durationUs;
if (meta->findInt64(kKeyDuration, &durationUs)) {
if (durationUs > mDurationUs) {
mDurationUs = durationUs;
}
}
// 获取 bitrate
int32_t bitrate;
if (totalBitrate >= 0 && meta->findInt32(kKeyBitRate, &bitrate)) {
totalBitrate += bitrate;
} else {
totalBitrate = -1;
}
}
if (mSources.size() == 0) {
ALOGE("b/23705695");
return UNKNOWN_ERROR;
}
mBitrate = totalBitrate;
return OK;
}
initFromDataSource 的关键是调用 media.extractor 服务创建 IMediaExtractor 对象来解析 DataSource 读取到的内容。IMediaExtractor 创建完成,解析过程也就完成了,使用 extractor 可以获取到流的基本信息,track数量,创建 IMediaSource 对象。
- 使用 DataSource 创建 IMediaExtractor;
- 获取 Extractor MetaData,时长信息、track count、track metadata 这类流信息是存储的 extractor 中的,所以需要从 Extractor 中获取到;
- 使用 Extractor 为 每个 track 创建 IMediaSource,并存储在 vector 中,后续读取指定 track 的数据通过该 IMediaSource 来完成;
- 将 IMediaSource,track id,以及一个 BufferPool 组织成一个
Track
结构体,后续调用就使用该结构体。
void NuPlayer::GenericSource::finishPrepareAsync() {
ALOGV("finishPrepareAsync");
status_t err = startSources();
if (err != OK) {
ALOGE("Failed to init start data source!");
notifyPreparedAndCleanup(err);
return;
}
if (mIsStreaming) {
mCachedSource->resumeFetchingIfNecessary();
mPreparing = true;
schedulePollBuffering();
} else {
notifyPrepared();
}
if (mAudioTrack.mSource != NULL) {
postReadBuffer(MEDIA_TRACK_TYPE_AUDIO);
}
if (mVideoTrack.mSource != NULL) {
postReadBuffer(MEDIA_TRACK_TYPE_VIDEO);
}
}
prepareAsync 的最后会调用 finishPrepareAsync,这里首先会调用已选择的 IMediaSource 的 start 方法,接着上抛 prepare 完成的消息,最后调用 postReadBuffer,让 GenericSource 开始使用 IMediaSource 读取 demux 后的数据。
2.2、数据读取
上面说到调用 postReadBuffer 读取数据,GenericSource 只有一个 ALooper 线程,所以不能同时读取音频和视频数据,那我们应该按什么顺序读取呢?
void NuPlayer::GenericSource::postReadBuffer(media_track_type trackType) {
if ((mPendingReadBufferTypes & (1 << trackType)) == 0) {
mPendingReadBufferTypes |= (1 << trackType);
sp<AMessage> msg = new AMessage(kWhatReadBuffer, this);
msg->setInt32("trackType", trackType);
msg->post();
}
}
postReadBuffer 用成员 mPendingReadBufferTypes 来管理当前应该读取的 track。调用 postReadBuffer 后函数会检查 mPendingReadBufferTypes 对应 trackType 位置的值是否为 0 时,如果是就去读取该 track 的码流,如果不是说明当前正在读取该 track 的码流,不对当前调用做任何操作。当前 track 的数据读取完毕,mPendingReadBufferTypes 对应位置的码流会被重置为 0。
我觉得使用以上机制有两种作用:
- 可以尽量保证在碰到需要同时读取 audio 和 video 数据的情况时,两种数据都能够读取到,数据次数相对均匀;
- 可以保证消息队列中同时只有一条读取 audio 和 video 数据的 Message,避免其他消息无法被处理。
接下来看 readBuffer 是如何读取数据的,同上面一样,这里会删减掉 Streaming 部分的代码:
void NuPlayer::GenericSource::readBuffer(
media_track_type trackType, int64_t seekTimeUs, MediaPlayerSeekMode mode,
int64_t *actualTimeUs, bool formatChange) {
Track *track;
size_t maxBuffers = 1;
// 1、根据 tracktype 获取一次要读取的buffer的数量
switch (trackType) {
case MEDIA_TRACK_TYPE_VIDEO:
track = &mVideoTrack;
maxBuffers = 8; // too large of a number may influence seeks
break;
case MEDIA_TRACK_TYPE_AUDIO:
track = &mAudioTrack;
maxBuffers = 64;
break;
case MEDIA_TRACK_TYPE_SUBTITLE:
track = &mSubtitleTrack;
break;
case MEDIA_TRACK_TYPE_TIMEDTEXT:
track = &mTimedTextTrack;
break;
default:
TRESPASS();
}
// 设定本次读取的位置
if (actualTimeUs) {
*actualTimeUs = seekTimeUs;
}
MediaSource::ReadOptions options;
bool seeking = false;
if (seekTimeUs >= 0) {
options.setSeekTo(seekTimeUs, mode);
seeking = true;
}
// 是否支持一次读取多包数据,默认是支持的
const bool couldReadMultiple = (track->mSource->supportReadMultiple());
if (couldReadMultiple) {
options.setNonBlocking();
}
// 2、获取当前 track 的 generation
int32_t generation = getDataGeneration(trackType);
// 3、循环读取,直到读到指定数量的 buffer
for (size_t numBuffers = 0; numBuffers < maxBuffers; ) {
Vector<MediaBufferBase *> mediaBuffers;
status_t err = NO_ERROR;
sp<IMediaSource> source = track->mSource;
mLock.unlock();
// 4、读取buffer
if (couldReadMultiple) {
err = source->readMultiple(
&mediaBuffers, maxBuffers - numBuffers, &options);
}
mLock.lock();
options.clearNonPersistent();
size_t id = 0;
size_t count = mediaBuffers.size();
// 5、检查 generation 的值,如果不同于之前的 generation 那么就销毁所有数据并退出数据读取
// in case track has been changed since we don't have lock for some time.
if (generation != getDataGeneration(trackType)) {
for (; id < count; ++id) {
mediaBuffers[id]->release();
}
break;
}
// 解析读到的每一个buffer
for (; id < count; ++id) {
int64_t timeUs;
MediaBufferBase *mbuf = mediaBuffers[id];
// 6、查找 buffer 中的 pts 信息
if (!mbuf->meta_data().findInt64(kKeyTime, &timeUs)) {
mbuf->meta_data().dumpToLog();
track->mPackets->signalEOS(ERROR_MALFORMED);
break;
}
if (trackType == MEDIA_TRACK_TYPE_AUDIO) {
mAudioTimeUs = timeUs;
} else if (trackType == MEDIA_TRACK_TYPE_VIDEO) {
mVideoTimeUs = timeUs;
}
// 7、将一个不连续的标志位加入到 buffer pool 中
queueDiscontinuityIfNeeded(seeking, formatChange, trackType, track);
// 8、将数据转换为 ABuffer
sp<ABuffer> buffer = mediaBufferToABuffer(mbuf, trackType);
if (numBuffers == 0 && actualTimeUs != nullptr) {
*actualTimeUs = timeUs;
}
// 9、给buffer附上一些额外信息
if (seeking && buffer != nullptr) {
sp<AMessage> meta = buffer->meta();
if (meta != nullptr && mode == MediaPlayerSeekMode::SEEK_CLOSEST
&& seekTimeUs > timeUs) {
sp<AMessage> extra = new AMessage;
extra->setInt64("resume-at-mediaTimeUs", seekTimeUs);
meta->setMessage("extra", extra);
}
}
// 10、将buffer加入到对应 track 的buffer pool 中
track->mPackets->queueAccessUnit(buffer);
formatChange = false;
seeking = false;
++numBuffers;
}
// 11、销毁没有被解析的 buffer
if (id < count) {
// Error, some mediaBuffer doesn't have kKeyTime.
for (; id < count; ++id) {
mediaBuffers[id]->release();
}
break;
}
// 12、当返回值为WOULD_BLOCK时,退出当前track的读取
if (err == WOULD_BLOCK) {
break;
} else if (err == INFO_FORMAT_CHANGED) {
} else if (err != OK) {
queueDiscontinuityIfNeeded(seeking, formatChange, trackType, track);
track->mPackets->signalEOS(err);
break;
}
}
}
- 根据 tracktype 决定一次要读取的buffer的数量,video buffer size 可能比较大,所以读取的数量比较小,audio 的情况反之;
- 如果需要seek,那么读取的时候需要带下去 seek 信息;
- 开始读取之前先获取当前的 generation 信息,generation 的作用是标记当前执行的操作是否已经过时了(对于读取 audio 和 video 是用不到的);
- 循环读取,直到读到指定数量的 buffer,这里要注意读取的时候是没有加锁的;
- 检查 generation 的值,如果不同于之前的 generation 那么就销毁所有数据并退出数据读取(对于读取 audio 和 video 是用不到的);
- 重新选择了 track(会附带 seek动作),向 buffer pool 添加一个不连续信息;
- 解析每一个 buffer,读取 pts 信息,将 buffer 以 ABuffer 的形式存储,加入到 buffer pool;
- 销毁未被解析的 buffer(解析过的 buffer 会直接释放);
- 当返回值为WOULD_BLOCK时,退出当前 track 的读取。
由于读取是一个比较耗时的工作,可能会影响其他 cmd 的执行,所以这里设计在读取过程中没有给 IMediaSource 加锁,我们可以控制 IMediaSource 让它中断读取,返回 WOULD_BLOCK 之类的异常。
Android Media 有很多地方用了 generation 技巧,例如在 Renderer、ACodec 中也用到了。它的作用我认为有两点:
- 方便 debug;
- 在任务执行过程中检查是否有其他更高优先级的任务需要执行,优先级高的任务会直接修改 generation 的数值,从而中断当前任务的执行。
selectTrack 可能会改变当前正在播放的 mime type,decoder 需要被释放再重新创建,所以需要在 Buffer Pool 中添加一条 Discontinuity
信息,用来侦测当前写入 decoder 的数据是否已经到达 selectTrack 后读取的位置。
所有读到的 buffer 将会拷贝到 ABuffer 当中,ABuffer 存储有buffer data、buffer length、metadata 以及 pts 等信息,这些都是向 decoder 中写入时所需要的。