关注公众号免费阅读全文,进入音视频开发技术分享群!

上一篇我们了解了 GenericSource 需要依赖 IMediaExtractor 完成 demux 工作,这一篇我们就来学习 android media 框架中的第二个服务 media.extractor,看看 IMediaExtractor 是如何创建与工作的。

1、MediaExtractorService

media.extractor 和 media.player 是属于同一层级的 binder service,都用于提供媒体播放支持,media.player 用于提供播放器功能,而 media.extractor 主要用于本地文件播放的解封装工作。

MediaExtractorService 相关代码位于:
frameworks/av/services/mediaextractor
frameworks/av/media/libmedia
frameworks/av/media/libstagefright

media.extractor 中提供服务的主体是 MediaExtractorService ,创建 IMediaExtractor 需要借助 MediaExtractorService 的力量,这一点和 media.player 的设计是一致的,创建 IMediaPlayer 需要借助 MediaPlayerService 的力量。这两个 service 起着创建和管理实际工作实例的功能。

MediaExtractorService 所继承的 BnMediaExtractorService 由 aidl 文件生成,IMediaExtractorService.aidl 在 libmedia 目录下,总共有3个方法可供使用:

interface IMediaExtractorService {
IMediaExtractor makeExtractor(IDataSource source, @nullable @utf8InCpp String mime);
IDataSource makeIDataSource(in FileDescriptor fd, long offset, long length);
@utf8InCpp String[] getSupportedTypes();
}

接下来看 MediaExtractorService 中到底包含哪些内容。首先来看构造函数:

MediaExtractorService::MediaExtractorService() {
MediaExtractorFactory::LoadExtractors();
}

构造函数调用了 MediaExtractorFactory::LoadExtractors 将平台上所有的 extractor lib 加载到 MediaExtractorService 进程:

void MediaExtractorFactory::LoadExtractors() {
Mutex::Autolock autoLock(gPluginMutex); std::shared_ptr<std::list<sp<ExtractorPlugin>>> newList(new std::list<sp<ExtractorPlugin>>()); RegisterExtractors("/apex/com.android.media/lib"
#ifdef __LP64__
"64"
#endif
"/extractors", &dlextinfo, *newList); } else {
ALOGE("couldn't find media namespace.");
} RegisterExtractors("/system/lib"
#ifdef __LP64__
"64"
#endif
"/extractors", NULL, *newList); RegisterExtractors("/system_ext/lib"
#ifdef __LP64__
"64"
#endif
"/extractors", NULL, *newList); newList->sort(compareFunc);
gPlugins = newList; for (auto it = gPlugins->begin(); it != gPlugins->end(); ++it) {
if ((*it)->def.def_version == EXTRACTORDEF_VERSION_NDK_V2) {
for (size_t i = 0;; i++) {
const char* ext = (*it)->def.u.v3.supported_types[i];
if (ext == nullptr) {
break;
}
gSupportedExtensions.push_back(std::string(ext));
}
}
}
}

lib 加载总共分为2步:

  1. /apex/com.android.media/lib/extractor /system/lib/lib /system_ext/lib 这三个目录下寻找名字中包含 extractor.so 的 lib,打开这些 lib 并用 dlsym 找到 lib 中的 GETEXTRACTORDEF 函数,最后将 libHandle,GETEXTRACTORDEF 的返回值 ExtractorDef 以及 path 封装成一个 ExtractorPlugin 存到 list 当中;
  2. 遍历 ExtractorPlugin 读取 ExtractorDef 中定义的可以支持的扩展名,存储到一个列表中。

初学的时候看这里觉得晕晕乎乎的,一层套一层让人很难理解,但是从框架的角度来看就很清除很多。

所有的 extractor 都要遵循设计规则,实现 GETEXTRACTORDEF 函数,函数的返回值是一个 ExtractorDef 对象,ExtractorDef 声明在 MediaExtractorPluginApi.h 中:

struct ExtractorDef {
// version number of this structure
const uint32_t def_version; // A unique identifier for this extractor.
// See below for a convenience macro to create this from a string.
media_uuid_t extractor_uuid; // Version number of this extractor. When two extractors with the same
// uuid are encountered, the one with the largest version number will
// be used.
const uint32_t extractor_version; // a human readable name
const char *extractor_name; union {
struct {
SnifferFunc sniff;
} v2;
struct {
SnifferFunc sniff;
// a NULL terminated list of container mime types and/or file extensions
// that this extractor supports
const char **supported_types;
} v3;
} u;
};

以下是结构体中成员的含义:

  • def_version:这个结构体的版本;
  • extractor_uuid:当前 extractor 独一无二的 id,可以使用 constUUID 方法来生成 ,使用方法查看该方法注释;
  • extractor_version:extractor 的版本,如果两个 extractor uuid 相同,那么加载时只会存储高版本的那个;
  • extractor_name:extractor 的名字,同样可以由我们自己定义;
  • u:它是一个联合体,我们只关注 v3,v3 中有一个 SnifferFunc,这就是后面选择 extractor 的依据;supported_types 表示当前 extractor 所支持的 mine type 或者 文件后缀,必须要以 NULL结尾。

MediaExtractorFactory::LoadExtractors 那么多步骤核心就是或者每个 extractor lib 所定义的 ExtractorDef。

使用 dumpsys media.extractor 查看服务的信息,可以看到加载到的 extractor 的名称,版本,路径和支持解析的文件类型,这些都是从 ExtractorDef 中获取到的。

generic_x86:/ $ dumpsys media.extractor
Available extractors:
AAC Extractor: plugin_version(3), uuid(4fd80eae03d24d729eb948fa6bb54613), version(1), path(/apex/com.android.media/lib/extractors/libaacextractor.so), supports: aac
AMR Extractor: plugin_version(3), uuid(c86639c92f3140aca715fa01b4493aaf), version(1), path(/apex/com.android.media/lib/extractors/libamrextractor.so), supports: amr awb
FLAC Extractor: plugin_version(3), uuid(1364b048cc454fda9934327d0ebf9829), version(1), path(/apex/com.android.media/lib/extractors/libflacextractor.so), supports: flac fl
MIDI Extractor: plugin_version(3), uuid(ef6cca0af8a243e6ba5fdfcd7c9a7ef2), version(1), path(/apex/com.android.media/lib/extractors/libmidiextractor.so), supports: imy mid midi mxmf ota rtttl rt
x smf xmf
MP3 Extractor: plugin_version(3), uuid(812a3f6cc8cf46deb5293774b14103d4), version(1), path(/apex/com.android.media/lib/extractors/libmp3extractor.so), supports: mp2 mp3 mpeg mpg mpga
MP4 Extractor: plugin_version(3), uuid(27575c6744174c548d3d8e626985a164), version(2), path(/apex/com.android.media/lib/extractors/libmp4extractor.so), supports: 3g2 3ga 3gp 3gpp 3gpp2 m4a m4r
m4v mov mp4 qt
MPEG2-PS/TS Extractor: plugin_version(3), uuid(3d1dcfebe40a436da574c2438a555e5f), version(1), path(/apex/com.android.media/lib/extractors/libmpeg2extractor.so), supports: m2p m2ts mts ts
Matroska Extractor: plugin_version(3), uuid(abbedd9238c44904a4c1b3f45f899980), version(1), path(/apex/com.android.media/lib/extractors/libmkvextractor.so), supports: mka mkv webm
Ogg Extractor: plugin_version(3), uuid(8cc5cd06f772495e8a62cba9649374e9), version(1), path(/apex/com.android.media/lib/extractors/liboggextractor.so), supports: oga ogg opus
WAV Extractor: plugin_version(3), uuid(7d61385858374a3884c5332d1cddee27), version(1), path(/apex/com.android.media/lib/extractors/libwavextractor.so), supports: wav

看到这里我会有一些疑问:为什么 MediaExtractor 要以服务的形式提供呢?

我想到的可能有这些:

  • 如果要有一个进程要使用 extractor,按照 MediaExtractorFactory 的加载方法,每个进程都要加载所有的 lib,这是很耗费时间的。放到 MediaExtractorService 一个地方加载,其他地方就都可以使用了。
  • 创建的所有的 IMediaExtractor 都可以用 MediaExtractorService 管理;
  • 如果要新增 extractor,只要按照设计规则实现,编译push到指定目录下就可以生效,不用修改加载部分的代码;

但是,我觉得 extractor 不需要以服务的形式存在,以服务的形式提供反而会损失性能,按照 ffmpeg 的方式,用一个函数指针列表来存储 ExtractorDef 效率应该也很高。

带着这个问题我们一起来看下面的内容。


2、MediaExtractorFactory

我们在上文已经对 MediaExtractorFactory 有了部分解,接下来我们将继续了解它的其他作用:

  1. 在 service 进程搜索加载所有的 extractor lib;
  2. 提供 Create 方法给 client 进程,封装 binder 调用;
  3. 帮助 service 进程创建 IMediaExtractor 实例;

第一点我们在上文已经了解过了,主要是通过 LoadExtractors 方法来加载 lib,获取 lib 中的GETEXTRACTORDEF 函数,这里就不再多做了解。

接下来回到 GenericSource 中看 Create 方法是如何使用的:

status_t NuPlayer::GenericSource::initFromDataSource() {
sp<IMediaExtractor> extractor;
sp<DataSource> dataSource;
{
Mutex::Autolock _l_d(mDisconnectLock);
dataSource = mDataSource;
}
extractor = MediaExtractorFactory::Create(dataSource, NULL);
}

可以发现,并没有直接调用 MediaExtractorService::makeExtractor 来创建 IMediaExtractor 对象,而是调用的 MediaExtractorFactory::Create

sp<IMediaExtractor> MediaExtractorFactory::Create(
const sp<DataSource> &source, const char *mime) {
// 1. 在调用进程创建 extractor
if (!property_get_bool("media.stagefright.extractremote", true)) {
// local extractor
ALOGW("creating media extractor in calling process");
return CreateFromService(source, mime);
} else {
// 2. 创建远程 extractor
// remote extractor
ALOGV("get service manager");
sp<IBinder> binder = defaultServiceManager()->getService(String16("media.extractor")); if (binder != 0) {
sp<IMediaExtractorService> mediaExService(
interface_cast<IMediaExtractorService>(binder));
sp<IMediaExtractor> ex;
mediaExService->makeExtractor(
CreateIDataSourceFromDataSource(source),
mime ? std::optional<std::string>(mime) : std::nullopt,
&ex);
return ex;
} else {
ALOGE("extractor service not running");
return NULL;
}
}
return NULL;
}

Create 方法提供了创建 extractor 的两种方式:

  1. 本地 extractor:在调用进程创建 IMediaExtractor;
  2. 远程 extractor:调用 MediaExtractorService 创建远程 IMediaExtractor。

第一点说明我们在上一节最后的猜想是多余的,Android 已经提供了在调用进程创建 MediaExtractor 的方式,但是可能为了更方便管理,默认使用远程的版本。

两种模式的选择使用 media.stagefright.extractremote 属性来控制,若值为 或者 true 就创建远程 extractor,否则创建本地 extractor。

这里还要提一下 DataSource,在 GenericSource::onPrepareAsync 中,如果上层传下来的是 url 不为空,那么就创建本地 DataSource;如果传递的是 fd,要用是远程 extractor 就创建远程 DataSource,否则创建本地 DataSource。

接下来我们只去关注平时用的比较多的情况:extractor 为远程创建,datasource 为本地创建。

回到 MediaExtractorFactory 中来,Create 方法最后会调用 MediaExtractorService 的 makeExtractor 创建远程 IMediaExtractor 对象:

::android::binder::Status MediaExtractorService::makeExtractor(
const ::android::sp<::android::IDataSource>& remoteSource,
const ::std::optional< ::std::string> &mime,
::android::sp<::android::IMediaExtractor>* _aidl_return) {
ALOGV("@@@ MediaExtractorService::makeExtractor for %s", mime ? mime->c_str() : nullptr); sp<DataSource> localSource = CreateDataSourceFromIDataSource(remoteSource); MediaBuffer::useSharedMemory();
// 1、创建 Extractor
sp<IMediaExtractor> extractor = MediaExtractorFactory::CreateFromService(
localSource,
mime ? mime->c_str() : nullptr); ALOGV("extractor service created %p (%s)",
extractor.get(),
extractor == nullptr ? "" : extractor->name());
// 2、存储 Extractor
if (extractor != nullptr) {
registerMediaExtractor(extractor, localSource, mime ? mime->c_str() : nullptr);
}
*_aidl_return = extractor;
return binder::Status::ok();
}

makeExtractor 又会回到 MediaExtractorFactory 的调用,但是这时候已经进到到 Service 进程当中,CreateFromService 很简单,就是遍历加载的内容,调用 sniff 函数选择最合适的 extractor。

ExtractorDef.u.v3.sniff 函数返回的是函数指针,并不是一个具体的 extractor 对象。这是因为执行 sniff 函数会返回置信度,遍历完成后会选择置信度最高的那个 extractor,如果 sniff 执行完就返回一个具体的对象,那么遍历一次就会创建多个对象,创建多个对象再释放,这样的效率是很低的。

IMediaExtractor 创建完成后会调用 registerMediaExtractor 注册到一个容器中,以弱引用的形式存储,不会影响实例的生命周期。这个容器存储有最近十次播放用到的 extractor 信息,我们可以用 dumpsys media.extractor 来查看,如果对象没有释放那么 extractor 和 track 会输出 active,否则输出 deleted。

console:/ # dumpsys media.extractor
......
Recent extractors, most recent first:
08-07 04:01:43: MPEG4Extractor for mime NULL, source TinyCacheSource(CallbackDataSource(486->490, RemoteDataSource(FileSource(fd(/storage/42BA-A9CA/Video/cd1.mp4), 0, 263428780)))), pid 490: active
track {trID: (int32_t) 2, srte: (int32_t) 44100, mxBr: (int32_t) 40268, mime: (char*) audio/mp4a-latm, lang: (char*) und, inpS: (int32_t) 416, esds: (unknown type 1702061171, size 30), dura: (int64_t) 2601842358, aaot: (int32_t) 2, #chn: (int32_t) 2} : active
track {widt: (int32_t) 1280, trID: (int32_t) 1, nfrm: (int32_t) 65041, mime: (char*) video/avc, lang: (char*) und, inpS: (int32_t) 79222, heig: (int32_t) 720, frmR: (int32_t) 25, dura: (int64_t) 2601640000, dWid: (int32_t) 1280, dHgt: (int32_t) 720, avcc: (unknown type 1635148643, size 46)} : active

3、IDataSource

我们都知道要通过 binder 传递对象,这个对象必须要实现 binder 接口(Bp/Bn那套)。由于 GenericSource 中创建的 DataSource 只是一个普通对象,所以是不能通过 binder 传递的。为了让 MediaExtractorService 进程能够使用这个 DataSource对象,MediaExtractorFactory 帮我们做了一层封装。

sp<IMediaExtractor> MediaExtractorFactory::Create(
...
mediaExService->makeExtractor(CreateIDataSourceFromDataSource(source), mime ? std::optional<std::string>(mime) : std::nullopt, &ex);
...
}

MediaExtractorFactory 调用 CreateIDataSourceFromDataSource 把 DataSource 封装到 RemoteDataSource 中,再将 RemoteDataSource 通过 binder 传递给 MediaExtractorService。

sp<IDataSource> CreateIDataSourceFromDataSource(const sp<DataSource> &source) {
if (source == nullptr) {
return nullptr;
}
return RemoteDataSource::wrap(source);
}

先来看 IDataSource 声明的接口(声明与实现均位于 libmedia):

class IDataSource : public IInterface {
public:
DECLARE_META_INTERFACE(DataSource); virtual sp<IMemory> getIMemory() = 0;
virtual ssize_t readAt(off64_t offset, size_t size) = 0;
virtual status_t getSize(off64_t* size) = 0;
virtual void close() = 0;
virtual uint32_t getFlags() = 0;
virtual String8 toString() = 0;
};
  1. getIMemory:获取缓冲区地址;
  2. readAt:从指定偏移位置读取数据;
  3. getSize:获取读取文件的大小;
  4. close:减少 DataSource 和 缓冲区对象 的引用;
  5. getFlags:获取 DataSource 的标志位;
  6. toString:获取当前 DataSource 的名称;

这里抛出一个疑问,为什么 IDataSource 要开 getIMemory 接口呢?

我们平时使用 binder 调用时一般只会传递几个参数,并且这些参数需要的内存比较小。如果我们需要通过 binder 传递大量数据,比如这里用 DataSource 读取大量数据传递给 IMediaExtractor 解析,要怎么办呢?

Android 已经为我们提供了共享内存类 IMemory,用这个类就可以让两个进程访问同一块内存,实现不同进程的数据拷贝,这里的 getIMemory 就是让远程代理获取本地对象创建的共享内存对象。

    explicit RemoteDataSource(const sp<DataSource> &source) {
Mutex::Autolock lock(mLock);
mSource = source;
sp<MemoryDealer> memoryDealer = new MemoryDealer(kBufferSize, "RemoteDataSource");
mMemory = memoryDealer->allocate(kBufferSize);
if (mMemory.get() == nullptr) {
ALOGE("Failed to allocate memory!");
}
mName = String8::format("RemoteDataSource(%s)", mSource->toString().string());
}

RemoteDataSource 的构造函数中使用 MemoryDealer 创建了共享内存对象 Allocation,共享内存大小为 64 KB。

RemoteDataSource 是在 GenericSource 所在进程中创建的,所以 GenericSource 所在的 MediaPlayerService 进程为 server,调用者 MediaExtractor 所在的 MediaExtractorService 进程为 client。数据读取的流程如下:

  1. client 进程调用 readAt方法;
  2. server 进程将数据写到共享内存;
  3. client 将数据从共享内存拷贝出来,完成一次 readAt 读取。

继续往下看,MediaExtractorService 收到传递过来的 IDataSource 对象后,会对他进行封装:

::android::binder::Status MediaExtractorService::makeExtractor(
...
sp<DataSource> localSource = CreateDataSourceFromIDataSource(remoteSource);
...
} sp<DataSource> CreateDataSourceFromIDataSource(const sp<IDataSource> &source) {
if (source == nullptr) {
return nullptr;
}
return new TinyCacheSource(new CallbackDataSource(source));
}

从 CreateDataSourceFromIDataSource 可以看到对 IDataSource 进行了两层封装,第一层是 CallbackDataSource,第二层是 TinyCacheSource。

CallbackDataSource 是直接调用 IDataSource 的,由于共享内存大小有限,一次最多读取 64 KB,如果我们要一次读取更多的数据,就要多次调用 IDataSource.readAt 方法,CallbackDataSource 的 readAt 方法帮助我们完成了多次读取调用的封装。

ssize_t CallbackDataSource::readAt(off64_t offset, void* data, size_t size) {
......
size_t totalNumRead = 0;
size_t numLeft = size;
const size_t bufferSize = mMemory->size(); while (numLeft > 0) {
size_t numToRead = std::min(numLeft, bufferSize);
ssize_t numRead =
mIDataSource->readAt(offset + totalNumRead, numToRead);
// A negative return value represents an error. Pass it on.
if (numRead < 0) {
return numRead == ERROR_END_OF_STREAM && totalNumRead > 0 ? totalNumRead : numRead;
}
// A zero return value signals EOS. Return the bytes read so far.
if (numRead == 0) {
return totalNumRead;
}
if ((size_t)numRead > numToRead) {
return ERROR_OUT_OF_RANGE;
}
CHECK(numRead >= 0 && (size_t)numRead <= bufferSize);
memcpy(((uint8_t*)data) + totalNumRead, mMemory->unsecurePointer(),
numRead);
numLeft -= numRead;
totalNumRead += numRead;
}
return totalNumRead;
}

第二层封装 TinyCacheSource 从名字就可以知道是做缓冲区的作用,读取的数据会存到缓冲区中,使用时优先从缓冲区中读取,如果数据不够再真正去读取,这里就不再粘贴代码了。

再回到 MediaExtractorFactory 创建 IMediaExtractor 的地方:

sp<IMediaExtractor> MediaExtractorFactory::CreateFromService(
const sp<DataSource> &source, const char *mime) {
......
CMediaExtractor *ret = ((CreatorFunc)creator)(source->wrap(), meta);
......
}

调用 sniff 返回的函数指针时,向下传的 DataSource 又做了一层C语言风格的封装变成 CDataSource,这一层完全可以不不看,如果想学习C语言风格的面向对象编程可以了解一下。

我们以 MPEG2TSExtractor 为例,看看 sniff 返回的函数指针是什么:

return [](
CDataSource *source,
void *) -> CMediaExtractor* {
return wrap(new MPEG2PSExtractor(new DataSourceHelper(source)));};

调用这个函数指针最终会进入到真正的 extractor 构造函数中,这里又对 CDataSource 进行了封装,成为了 DataSourceHelper。

DataSourceHelper 就不仅仅只封装了,它还扩充了一些便利的方法,例如 getUInt16、getUInt24、getUInt32、getUInt64,用来一次读取 2bytes、3bytes、4bytes、8bytes。

到这里 DataSouce 就阅读完毕了,如果用时序图来表达调用顺序,这么多层的嵌套画出来的时序图估计别人一看就晕了。

我这里依旧用框架图来表示他们的层级结构:


4、IMediaExtractor

IMediaExtractor 的层级结构在上面的图中已经画出来了,它比 IDataSource 要简单很多,CMediaExtractor 和 MediaExtractorCUnwrapper 这两层完全可以忽略,只是接口封装而已,IMediaExtractor 就不再做过多了解。


5、IMediaSource

MediaExtractor 会解析出当前播放码流中的 track, 调用 getTrack 方法会为指定 track 创建 IMediaSource,调用 IMediaSource 读取的数据就是该 track 所包含的数据。

每个 extractor 都有自己的 MediaSource,它们都继承于 MediaTrackHelper,但是内部实现各不相同。我们这里不会关注 MediaSource 具体是如何实现的,重点是了解 IMediaSource 的框架。

以下是我绘制的 IMediaSource 的框架图:

RemoteMeiaSource 封装了 MediaTrack,最内部的实现是 MediaTrackHelper。MediaTrackHelper 中有一个成员 MediaBufferGroupHelper,这个成员需要通过 MediaTrackHelper 的构造函数设定,在它的外层 MediaTrack 的 start 方法中创建并设定下去:

status_t MediaTrackCUnwrapper::start() {
if (bufferGroup == nullptr) {
bufferGroup = new MediaBufferGroup();
}
return reverse_translate_error(wrapper->start(wrapper->data, bufferGroup->wrap()));
} wrapper->start = [](void *data, CMediaBufferGroup *bufferGroup) -> media_status_t {
if (((MediaTrackHelper*)data)->mBufferGroup) {
// this shouldn't happen, but handle it anyway
delete ((MediaTrackHelper*)data)->mBufferGroup;
}
((MediaTrackHelper*)data)->mBufferGroup = new MediaBufferGroupHelper(bufferGroup);
return ((MediaTrackHelper*)data)->start();
};

MediaBufferGroup 其实是一个 BufferPool,里面储存着一个个 MediaBuffer 对象,MediaBuffer 封装有 shared memory(IMemory)或者是普通 memory,这里的设计应该是为了内存复用,并且减少拷贝次数,接下来们来看具体是如何实现的。

在 extractor 中会调用 MediaBufferGroup 的 init 方法,设定内部 MediaBuffer 的数量,以及内部 buffer size 的大小,同时分配这些buffer:

void MediaBufferGroup::init(size_t buffers, size_t buffer_size, size_t growthLimit) {
mInternal->mGrowthLimit = growthLimit; if (mInternal->mGrowthLimit > 0 && buffers > mInternal->mGrowthLimit) {
ALOGW("Preallocated buffers %zu > growthLimit %zu, increasing growthLimit",
buffers, mInternal->mGrowthLimit);
mInternal->mGrowthLimit = buffers;
} #if !defined(NO_IMEMORY) && !defined(__ANDROID_APEX__)
if (buffer_size >= kSharedMemoryThreshold) {
ALOGD("creating MemoryDealer");
// Using a single MemoryDealer is efficient for a group of shared memory objects.
// This loop guarantees that we use shared memory (no fallback to malloc). size_t alignment = MemoryDealer::getAllocationAlignment();
size_t augmented_size = buffer_size + sizeof(MediaBuffer::SharedControl);
size_t total = (augmented_size + alignment - 1) / alignment * alignment * buffers;
sp<MemoryDealer> memoryDealer = new MemoryDealer(total, "MediaBufferGroup"); for (size_t i = 0; i < buffers; ++i) {
sp<IMemory> mem = memoryDealer->allocate(augmented_size);
if (mem.get() == nullptr || mem->unsecurePointer() == nullptr) {
ALOGW("Only allocated %zu shared buffers of size %zu", i, buffer_size);
break;
}
MediaBuffer *buffer = new MediaBuffer(mem);
buffer->getSharedControl()->clear();
add_buffer(buffer);
}
return;
}
#else
(void)kSharedMemoryThreshold;
#endif // Non-shared memory allocation.
for (size_t i = 0; i < buffers; ++i) {
MediaBuffer *buffer = new MediaBuffer(buffer_size);
if (buffer->data() == nullptr) {
delete buffer; // don't call release, it's not properly formed
ALOGW("Only allocated %zu malloc buffers of size %zu", i, buffer_size);
break;
}
add_buffer(buffer);
}
}

如果 buffer size 大于 4KB,那么给 MediaBuffer 分配共享内存,如果 buffer size 小于 4KB,那么就分配普通内存。当调用 IMediaSource 的 read 或者 readMultiple 方法时,会调用 MediaBufferGroup acquire_buffer 获取一个 buffer,并将 demux 后的数据写入到这个 buffer 当中。

为什么 IMediaSource read 不用一块共享内存来传递,而要用多块 MediaBuffer 来传递呢?这是因为 demux 后的数据是以 为单位存储的,我们希望每次拷贝都是一帧,用单独的 MediaBuffer 管理会更方便。

回到 IMediaSource.cpp,这里是 IMediaSource 的 Bn 实现,我们来看 readMultiple 是如何做的:

case READMULTIPLE: {
// 1、读取一帧数据
ret = read((MediaBufferBase **)&buf, useOptions ? &opts : nullptr);
// Even if we're using shared memory, we might not want to use it, since for small
// sizes it's faster to copy data through the Binder transaction
// On the other hand, if the data size is large enough, it's better to use shared
// memory. When data is too large, binder can't handle it.
//
// TODO: reduce MediaBuffer::kSharedMemThreshold
MediaBuffer *transferBuf = nullptr;
const size_t length = buf->range_length();
size_t offset = buf->range_offset();
// 2、如果数据长度大于阈值,并且使用的是共享内存
if (length >= (supportNonblockingRead() && buf->mMemory != nullptr ?
kTransferSharedAsSharedThreshold : kTransferInlineAsSharedThreshold)) {
if (buf->mMemory != nullptr) {
ALOGV("Use shared memory: %zu", length);
transferBuf = buf;
}
}
// 3、将 buffer 返回
if (transferBuf != nullptr) { // Using shared buffers.
uint64_t index = mIndexCache.lookup(transferBuf->mMemory);
if (index == 0) {
index = mIndexCache.insert(transferBuf->mMemory);
reply->writeInt32(SHARED_BUFFER);
reply->writeUint64(index);
reply->writeStrongBinder(IInterface::asBinder(transferBuf->mMemory));
} else {
reply->writeInt32(SHARED_BUFFER_INDEX);
reply->writeUint64(index);
}
reply->writeInt32(offset);
reply->writeInt32(length);
buf->meta_data().writeToParcel(*reply);
transferBuf->addRemoteRefcount(1);
if (transferBuf != buf) {
transferBuf->release(); // release local ref
} else if (!supportNonblockingRead()) {
maxNumBuffers = 0; // stop readMultiple with one shared buffer.
}
} else {
reply->writeInt32(INLINE_BUFFER);
reply->writeByteArray(length, (uint8_t*)buf->data() + offset);
buf->meta_data().writeToParcel(*reply);
inlineTransferSize += length;
if (inlineTransferSize > kInlineMaxTransfer) {
maxNumBuffers = 0; // stop readMultiple if inline transfer is too large.
}
}
// 5、释放本地 buffer
buf->release();
}

这里的代码有点长,但是逻辑并不复杂:

  1. 调用 read 方法将 demux 后的数据读到 MediaBuffer 中;
  2. 如果数据长度短,用内存拷贝会更快,如果数据长则用共享内存会快,另一方面如果数据长度过长,binder 将无法处理;
  3. 返回数据时会判断数据长度,如果用了共享内存,但是数据长度短,接下来将 buffer 返回时还是会以拷贝的方式返回;
  4. 以共享内存的方式返回时,需要增加远程的引用计数,防止 MediaBuffer 被重复使用。
  5. 释放本地 buffer,将本地 buffer 返回到 MediaBufferGroup;

我们要注意的是 MediaBuffer 里面使用了引用计数,但是并没有继承于 RefBase。在这里,调用 MediaBuffer 的 release 方法,让他的引用计数为 0 时,会有 Callback 发到 MediaBufferGroup,通知有空闲的 buffer 可以使用。buffer 可用的条件是 远程和本地 的引用计数之和为 0,所以上层将 buffer 拷贝完成之后需要调用 release,将远程的引用计数减一。

到这儿 IMediaSource 的了解就结束了,它内部维护了一套生产者-消费者机制,这边讲的可能不是那么详细,但是了解了大致框架,相信其他内容就很容易看懂了。

Android 13 - Media框架(8)- MediaExtractor的更多相关文章

  1. 简析Android 兼容性测试框架CTS使用

    一.什么是兼容性测试? 1)为用户提供最好的用户体验,让更多高质量的APP可以顺利的运行在此平台上 2)让程序员能为此平台写更多的高质量的应用程序 3)可以更好的利用Android应用市场 二.CTS ...

  2. 15 个 Android 通用流行框架大全(转)

    1. 缓存 DiskLruCache    Java实现基于LRU的磁盘缓存 2.图片加载 Android Universal Image Loader 一个强大的加载,缓存,展示图片的库 Picas ...

  3. Android 通用流行框架

    原文出处: http://android.jobbole.com/83028/ 1. 缓存 名称 描述 DiskLruCache Java实现基于LRU的磁盘缓存 2.图片加载 名称 描述 Andro ...

  4. 经受时间沉淀的15 个 Android 通用流行框架大全

    1. 缓存 名称描述 DiskLruCache: Java实现基于LRU的磁盘缓存 2.图片加载 名称描述 Android    Universal Image Loader 一个强大的加载,缓存,展 ...

  5. Android通用流行框架大全

    1. 缓存 名称 描述 DiskLruCache Java实现基于LRU的磁盘缓存 2.图片加载 名称 描述 Android Universal Image Loader 一个强大的加载,缓存,展示图 ...

  6. 60.Android通用流行框架大全

    转载:https://segmentfault.com/a/1190000005073746 Android通用流行框架大全 1. 缓存 名称 描述 DiskLruCache Java实现基于LRU的 ...

  7. 15 个 Android 通用流行框架大全

      1. 缓存 名称 描述 DiskLruCache Java实现基于LRU的磁盘缓存 2.图片加载 名称 描述 Android Universal Image Loader 一个强大的加载,缓存,展 ...

  8. Android 通用流行框架大全

    1. 缓存 DiskLruCache    Java实现基于LRU的磁盘缓存 2.图片加载 Android Universal Image Loader 一个强大的加载,缓存,展示图片的库 Picas ...

  9. 玩转Android之数据库框架greenDAO3.0使用指南

    用过ActiveAndroid.玩过ORMLite,穿过千山万水,最终还是发现greenDAO好用,ActiveAndroid我之前有一篇文章介绍过 玩转Android之数据库框架ActiveAndr ...

  10. 15 个 Android 通用流行框架大全(转载)

    1. 缓存 DiskLruCache    Java实现基于LRU的磁盘缓存 2.图片加载 Android Universal Image Loader 一个强大的加载,缓存,展示图片的库 Picas ...

随机推荐

  1. Sample上新,从API 8开始支持!速来拿走

    原文:https://mp.weixin.qq.com/s/TxUOSXySZRwQaECenxt-Og ,点击链接查看更多技术内容. 搭载API 8的新SDK已经发布.围绕着新SDK,官方贴心地输出 ...

  2. 第十五篇:JavaScript 之 Dom操作

    一.后台管理页面布局 主站布局 <div class="pg-header"></div> <div style="width:980px; ...

  3. GAN的一些问题

    GAN为什么难以训练? 大多深度模型的训练都使用优化算法寻找损失函数比较低的值.优化算法通常是个可靠的"下山"过程.生成对抗神经网络要求双方在博弈的过程中达到势均力敌(均衡).每个 ...

  4. Linux 用户名显示为sh-

    前言 本来我们使用bash的时候一直显示是: 后来我操作linux的时候因为有一个新的需求,我使用: useradd -d /home/testuser -m testuser 去创建一个用户名,名字 ...

  5. redis 简单整理——CEO[十五]

    前文 简单介绍一下CEO. 正文 Redis3.2版本提供了GEO(地理信息定位)功能,支持存储地理位置信 息用来实现诸如附近位置.摇一摇这类依赖于地理位置信息的功能,对于需 要实现这些功能的开发者来 ...

  6. js 闭包(新)

    前言 旧的没有搬过来,先写一下新的感悟. 正文 ECMAScript中,闭包指的是: 从理论角度:所有的函数.因为它们都在创建的时候就将上层上下文的数据保存起来了.哪怕是简单的全局变量也是如此,因为函 ...

  7. tensorflow如何切换CPU和GPU

    import os if Bert_Use_GPU: os.environ['CUDA_VISIBLE_DEVICES'] = '0,1' #使用GPU0,1 else: os.environ['CU ...

  8. EasyNLP玩转文本摘要(新闻标题)生成

    简介: 本⽂将提供关于PEGASUS的技术解读,以及如何在EasyNLP框架中使⽤与PEGASUS相关的文本摘要(新闻标题)生成模型. 作者:王明.黄俊 导读 文本生成是自然语言处理领域的一个重要研究 ...

  9. Kruise Rollout:灵活可插拔的渐进式发布框架

    简介: Kruise Rollout 是 OpenKruise 社区开源的渐进式交付框架.Kruise Rollout 支持配合流量和实例灰度的金丝雀发布.蓝绿发布.A/B Testing 发布,以及 ...

  10. [Mobi] frida Hook 略知一二: frida-CLI, frida-server

    Frida 是一款基于 python + javascript 的 hook 框架,主流平台都支持,由于是基于脚本的交互,因此相比 xposed 和 substrace cydia 更加便捷. 使用时 ...