Source 在播放器中起着拉流(Streaming)和解复用(demux)的作用,Source 设计的好坏直接影响到播放器的基础功能,我们这一节将会了解 NuPlayer 中的通用 Source(GenericSource)关注本地播放架构,直播流暂时先不研究。
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;GenericSource 名为通用 source,但是往往它会被用来当作本地播放的 Source,由于本地播放的码流文件会有形形色色的封装格式,所以这个 source 会依赖解封装(demux)服务 media.extractor。除了依赖解封装外,source 还需要一个 IO 来读取码流,这个 IO 被封装在 DataSource中。Source、DataSource、Extractor三者的关系如下:
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 主要做了如下几件事情:
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 对象。
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 后的数据。
上面说到调用 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。
我觉得使用以上机制有两种作用:
接下来看 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;
}
}
}
由于读取是一个比较耗时的工作,可能会影响其他 cmd 的执行,所以这里设计在读取过程中没有给 IMediaSource 加锁,我们可以控制 IMediaSource 让它中断读取,返回 WOULD_BLOCK 之类的异常。
Android Media 有很多地方用了 generation 技巧,例如在 Renderer、ACodec 中也用到了。它的作用我认为有两点:
selectTrack 可能会改变当前正在播放的 mime type,decoder 需要被释放再重新创建,所以需要在 Buffer Pool 中添加一条 Discontinuity
信息,用来侦测当前写入 decoder 的数据是否已经到达 selectTrack 后读取的位置。
所有读到的 buffer 将会拷贝到 ABuffer 当中,ABuffer 存储有buffer data、buffer length、metadata 以及 pts 等信息,这些都是向 decoder 中写入时所需要的。
调用 start 后 GenericSource 会分别去读取一次 audio 和 video data,如上一小节所述,如果当前正在读取,那这里的 postReadBuffer 将不会生效。
start 更重要的是将 mStarted
标志置为 true,有了它才能从 GenericSource 获取数据向 decoder 写入。
void NuPlayer::GenericSource::start() {
Mutex::Autolock _l(mLock);
ALOGI("start");
if (mAudioTrack.mSource != NULL) {
postReadBuffer(MEDIA_TRACK_TYPE_AUDIO);
}
if (mVideoTrack.mSource != NULL) {
postReadBuffer(MEDIA_TRACK_TYPE_VIDEO);
}
mStarted = true;
}
这里将 pause、stop 和 resume 放在一起,他们核心的作用是修改 mStarted 的值,从而控制是否能从 GenericSource 拿到 demux 后的数据。
void NuPlayer::GenericSource::stop() {
Mutex::Autolock _l(mLock);
mStarted = false;
}
void NuPlayer::GenericSource::pause() {
Mutex::Autolock _l(mLock);
mStarted = false;
}
void NuPlayer::GenericSource::resume() {
Mutex::Autolock _l(mLock);
mStarted = true;
}
disconnect 主要是为了网络连接所设计的,里面调用的是 Streaming Source 的断开方法。
void NuPlayer::GenericSource::disconnect() {
sp<DataSource> dataSource, httpSource;
{
Mutex::Autolock _l_d(mDisconnectLock);
dataSource = mDataSource;
httpSource = mHttpSource;
mDisconnected = true;
}
if (dataSource != NULL) {
// disconnect data source
if (dataSource->flags() & DataSource::kIsCachingDataSource) {
static_cast<NuCachedSource2 *>(dataSource.get())->disconnect();
}
} else if (httpSource != NULL) {
static_cast<HTTPBase *>(httpSource.get())->disconnect();
}
}
调用 seek 方法会等当前读取的工作完成,自己以 seekTime,seekMode 作为参数,去调用 readBuffer 分别读取一次 audio/video data:
status_t NuPlayer::GenericSource::doSeek(int64_t seekTimeUs, MediaPlayerSeekMode mode) {
if (mVideoTrack.mSource != NULL) {
++mVideoDataGeneration;
int64_t actualTimeUs;
readBuffer(MEDIA_TRACK_TYPE_VIDEO, seekTimeUs, mode, &actualTimeUs);
if (mode != MediaPlayerSeekMode::SEEK_CLOSEST) {
seekTimeUs = std::max<int64_t>(0, actualTimeUs);
}
mVideoLastDequeueTimeUs = actualTimeUs;
}
if (mAudioTrack.mSource != NULL) {
++mAudioDataGeneration;
readBuffer(MEDIA_TRACK_TYPE_AUDIO, seekTimeUs, MediaPlayerSeekMode::SEEK_CLOSEST);
mAudioLastDequeueTimeUs = seekTimeUs;
}
return OK;
}
读取流程和正常流程大致相同,但是不同的是会调用到 AnotherPacketSource.queueDiscontinuity
,这里很好理解,seek 后需要丢弃 buffer pool 里之前的所有数据。queueDiscontinuity 就是在向 bufferPool 写入数据时添加 flag,从而实现清空之前的数据的目的。
void NuPlayer::GenericSource::queueDiscontinuityIfNeeded(
bool seeking, bool formatChange, media_track_type trackType, Track *track) {
if ((seeking || formatChange)
&& (trackType == MEDIA_TRACK_TYPE_AUDIO
|| trackType == MEDIA_TRACK_TYPE_VIDEO)) {
ATSParser::DiscontinuityType type = (formatChange && seeking)
? ATSParser::DISCONTINUITY_FORMATCHANGE
: ATSParser::DISCONTINUITY_NONE;
track->mPackets->queueDiscontinuity(type, NULL /* extra */, true /* discard */);
}
}
GenericSource 读取到的 demux 后的数据都存储在 AnotherPacketSource 这个buffer pool 中,decoder 调用 dequeueAccessUnit 实际就时从 AnotherPacketSource 获取 buffer。
status_t NuPlayer::GenericSource::dequeueAccessUnit(
bool audio, sp<ABuffer> *accessUnit) {
Mutex::Autolock _l(mLock);
// 1、如果 start 为 false 直接退出
if (!mStarted && mIsDrmReleased) {
return -EWOULDBLOCK;
}
Track *track = audio ? &mAudioTrack : &mVideoTrack;
if (track->mSource == NULL) {
return -EWOULDBLOCK;
}
// 2、判断是 bufferpool 是否为空,如果为空则尝试读取并直接退出
status_t finalResult;
if (!track->mPackets->hasBufferAvailable(&finalResult)) {
if (finalResult == OK) {
postReadBuffer(
audio ? MEDIA_TRACK_TYPE_AUDIO : MEDIA_TRACK_TYPE_VIDEO);
return -EWOULDBLOCK;
}
return finalResult;
}
// 3、从 bufferpool 中 dequeue buffer,(阻塞等待)
status_t result = track->mPackets->dequeueAccessUnit(accessUnit);
// 4、检查 bufferpool 中的数据,如果不够了就尝试读取
if (!mIsStreaming) {
if (track->mPackets->getAvailableBufferCount(&finalResult) < 2) {
postReadBuffer(audio? MEDIA_TRACK_TYPE_AUDIO : MEDIA_TRACK_TYPE_VIDEO);
}
}
return result;
}
AnotherPacketSource.dequeueAccessUnit
会阻塞等待,但是在这之前已经判断了是否为空,所以这里并不会出现阻塞的情况;最后要看读取事件驱动的问题,我们可以发现执行 prepare、start、seek、selectTrack 时都会调用一次 postReadBuffer,难道执行一次 read 就没下文了吗?
看了 dequeueAccessUnit 我们就可以知道,本地文件的读取是依赖 decoder 的需求的,decoder 要多少就读多少;而 streaming 是不一样的,它有一个自己 post 自己的过程,从而实现自身不断去拉流的效果。