Android硬件编解码-MediaCodec(Java-Api)

MediaCodec介绍

MediaCodec是Android平台上用来访问编码器和解码器的组件。

工作流程

mediacodec-processes

MediaCodec中维护了两个BufferQueue,一个用来存放输入数据,一个用来存放输出数据。对于编码来说,InputBufferQueue用来接收视频原始YUV数据,经过Codec编码处理后,将编码后的数据放入到OutputBufferQueue中输出。而对于解码来说,InputBufferQueue用来接收视频编码数据,经过Codec解码处理后,将解码后的视频原始YUV数据放入到OutputBufferQueue中输出。

BufferQueue的数量是有限的,每次通过dequeueInputBuffer从InputBufferQueue中获取一个空的buffer索引,将输入数据填充到该buffer,通过queueInputBuffer通知Codec该索引位置的buffer已经填充了数据,可以开始处理了。然后通过dequeueOutputBuffer从OutputBufferQueue中获取处理完毕的buffer索引,获取到该buffer中的数据进行渲染或封装,使用完后,通过releaseOutputBuffer通知Codec释放该buffer中的数据(也可以让Codec进行数据的渲染)。

MediaCodec的数据分为两种,一种是原始音视频数据,一种是压缩数据。上面讲到这两种数据都是使用bytebuffer来处理的,输入层获取到空的buffer后需要将数据copy到该buffer中,而输出层获取到buffer后也需要将数据copy出来进行消费,大量的数据copy是很影响性能的。

针对原始音视频数据,MediaCodec提供了更加高效的方式来避免了数据的copy,那就是Surface。对于解码器来说,可以通过在configure的时候,设置OutputSurface来接收输出的Buffer,这样就不需要从OutputBufferQueue中copy数据了。而对于编码器来说,可以通过createInputSurface方法创建一个用来输入的Surface,这样就不需要往InputBufferQueue里copy数据了。

mediacodec-surface-processes

数据类型

原始数据

音频原始数据类型是一个PCM音频数据帧,视频原始数据类型由color_format决定,常用的有以下两种:

  • Surface Format :这种类型的format数据,表明使用的GraphicBuffer,这是一个内存共享的缓冲区。(CodecCapabilities#COLOR_FormatSurface )。
  • YUV Format :YUV颜色格式,支持多种YUV格式(CodecCapabilities#COLOR_FormatYUV420Flexible )。

在configure的时候,对于编码器来说可以指定编码器输出的数据的format,对于解码器来说可以指定解码器输入的format。

对于编码器,如果输入的时候使用的是Surface,则format需要设置为COLOR_FormatSurface。而对于解码器来说输入的format一般从媒体文件中读取。

压缩数据

对于视频来说,buffer中是一帧的压缩数据,对于音频来说,buffer中是一个单元的压缩数据,buffer中包含的都是完整的一帧或一个单元的数据。

组件状态

Stopped:创建了一个MediaCodec对象后,默认为Uninitialized状态,调用configure方法后,状态变为Configured,通过reset可以重新配置。

Executing:调用start方法后,状态就变为了Flushed,第一次调用dequeueInputBuffer时,状态变为Running,当通过queueInputBuffer写入了一个EOS标记后,则状态变为End of Stream,可以通过flush方法重新回到Flushed状态。

Released:调用release进入到Released状态。

使用指南

创建编解码器

编码器

通过制定的mimeType创建一个编码器,比如 “video/avc” ,如果当前设备不支持该type则会抛IllegalArgumentException异常。

1
mVideoEncoder = MediaCodec.createEncoderByType("mimeType");

通过MediaCodecList查询当前设备是否支持该mimeType的编码。

1
2
3
4
5
6
7
8
9
10
11
12
13
final int numCodecs = MediaCodecList.getCodecCount();
for (int i = 0; i < numCodecs; i++) {
final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
if (!codecInfo.isEncoder()) {
continue;
}
// select first codec that match a specific MIME type and color format
final String[] types = codecInfo.getSupportedTypes();
for (int j = 0; j < types.length; j++) {
if (types[j].equalsIgnoreCase(mimeType)) {
...
}
}

解码器

通过mimeType就可以快速的创建一个解码器,关于媒体文件的mimeType获取可以使用MediaExtractor.getTrackFormat,如果当前设备不支持该type则会抛IllegalArgumentException异常。

1
mVideoDecoder = MediaCodec.createDecoderByType("mimeType")

通过MediaCodecList查询当前设备是否支持该mimeType的解码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
final int numCodecs = MediaCodecList.getCodecCount();
for (int i = 0; i < numCodecs; i++) {
final MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
if (codecInfo.isEncoder()) {
continue;
}
// select first codec that match a specific MIME type and color format
final String[] types = codecInfo.getSupportedTypes();
for (int j = 0; j < types.length; j++) {
if (types[j].equalsIgnoreCase(mimeType)) {
...
}
}

初始化

编码器

在创建完编码器之后,需要对编码器进行confiigure。

1
2
3
4
5
6
 public void configure(
@Nullable MediaFormat format,
@Nullable Surface surface, @Nullable MediaCrypto crypto,
@ConfigureFlag int flags) {

}

对于编码器来说

第一个参数MediaFormat是指定编码器输出的数据格式。这个格式需要我们自己去计算制定,

1
2
3
4
5
6
7
8
9
10
// 配置编码器输出格式
mVideoEncoderFormat = MediaFormat.createVideoFormat(MIME_TYPE, mNewWidth, mNewHeight);
// 设置color_format,如果使用的是InputSurface,则设置为FormatSurface
mVideoEncoderFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
// 码率
mVideoEncoderFormat.setInteger(MediaFormat.KEY_BIT_RATE, mNewBitRate);
// 帧率
mVideoEncoderFormat.setInteger(MediaFormat.KEY_FRAME_RATE, OUTPUT_FRAME_RATE);
// gop
mVideoEncoderFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, OUTPUT_IFRAME_INTERVAL);

第二个参数是OutputSurface,对于编码器来说忽略。

第三个参数是加密相关,可忽略。

第四个参数是编码标记,编码器需要加上 MediaCodec.CONFIGURE_FLAG_ENCODE 标记。

解码器

在创建完解码器之后,需要对解码器进行configure。

1
2
3
4
5
6
 public void configure(
@Nullable MediaFormat format,
@Nullable Surface surface, @Nullable MediaCrypto crypto,
@ConfigureFlag int flags) {

}

对于解码器来说

第一个参数MediaFormat是指定解码器输入的数据格式。一般来说是通过MediaExtractor.getTrackFormat从媒体文件中获取,但是其中有几点需要注意。

  1. 最好先将KEY_LEVEL的值设置为null(MediaFormat.setString(KEY_LEVEL, null)),因为这个值经常不准。
  2. 在5.0的设备上,MediaCodecList.findDecoder/EncoderForFormat不得包含frame rate。使用MediaFormat.setString(MediaFormat.KEY_FRAME_RATE, null))清除格式中的任何现有帧率设置。

第二个参数是OutputSurface,解码完成后,通过releaseOutputBuffer可将解码后数据直接输出到该Surface中。

第三个参数是加密相关,可忽略。

第四个参数是解码器也可忽略。

Codec-specific Data

对于某些格式,特别是AAC音频和H.264和H.265视频格式要求实际数据前需要包含设置数据或编解码器特定数据的多个缓冲区。 处理这种压缩格式时,必须在start()之后和任何帧数据之前将此数据提交给编解码器。 此类数据必须在输入时queueInputBuffer使用标BUFFER_FLAG_CODEC_CONFIG进行标记。

关于CSD,其实在封装篇讲过,在muxer写入第一帧之前,需要写入CSD数据。

mediacodec-csd

对于解码器来说,MediaExtractor.getTrackFormat可以直接获取到媒体文件的CSD信息。

对于编码器来说,在MediaCodec.INFO_OUTPUT_FORMAT_CHANGED回调时,使用mVideoEncoder.getOutputFormat() 可以获取到附带CSD信息的Format。

处理

根据API历史,MediaCodec提供了三套处理API。

Processing Mode API version <= 20 API version >= 21
Synchronous API using buffer arrays Supported 已过时
Synchronous API using buffers Not Available Supported
Asynchronous API using buffers Not Available Supported

同步API

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
// 创建
MediaCodec codec = MediaCodec.createByCodecName(name);
// 初始化
codec.configure(format, …);
MediaFormat outputFormat = codec.getOutputFormat();
// 启动
codec.start();
for (;;) {
// 从InputBufferQueue中获取空闲索引
int inputBufferId = codec.dequeueInputBuffer(timeoutUs);
if (inputBufferId >= 0) {
// 获取空闲索引对应的buffer
ByteBuffer inputBuffer = codec.getInputBuffer(…);
// 向buffer中写入数据

if (inputDown) {
// 输入结束的时候,向InputBufferQueue中写入一个EOS的标记,通知编码器。
codec.queueInputBuffer(inputBufferId, 0, 0, 0L,MediaCodec.BUFFER_FLAG_END_OF_STREAM);
} else {
// 通知Codec数据写入
codec.queueInputBuffer(inputBufferId, …);
}
}
// 从OutputBufferQueue中获取处理完成的索引
int outputBufferId = codec.dequeueOutputBuffer(…);
if (outputBufferId >= 0) {
// 获取到处理完成的数据和格式
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId);
// 封装或渲染

// 释放该buffer
codec.releaseOutputBuffer(outputBufferId, …);
} else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
// 数据格式发生变化
outputFormat = codec.getOutputFormat();
}
}
codec.stop();
codec.release();

异步API

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
// 创建
MediaCodec codec = MediaCodec.createByCodecName(name);
MediaFormat mOutputFormat;
// 异步回调
codec.setCallback(new MediaCodec.Callback() {
@Override
void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {
// InputBufferQueue有空闲buffer回调
ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);
// 写入数据

// 通知Codec
codec.queueInputBuffer(inputBufferId, …);
}

@Override
void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {
// OutputBufferQueue有处理完成的buffer回调
ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);
MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId);
// 处理数据

// 释放buffer
codec.releaseOutputBuffer(outputBufferId, …);
}

@Override
void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {
// Codec数据格式发生变化
mOutputFormat = format;
}

@Override
void onError(…) {

}
});
// 初始化
codec.configure(format, …);
mOutputFormat = codec.getOutputFormat();
// 启动
codec.start();
// 等待处理

codec.stop();
codec.release();
  1. 在输入结束后,需要写入EOS标记,此时buffer可为空,需要注意的是一旦写入EOS标记后,就不可以再向InputBufferQueue中写入数据了。

  2. 一旦设置了OutputSurface,则不可以访问OutputBufferQueue,对应API会返回null。

  3. 一旦设置了InputSurface,则不可以访问InputBufferQueue,对应API会抛异常或返回null。

  4. M版本之前,软件解码器在渲染到Surface上时可能未应用旋转。

  5. Codec在处理的第一帧的数据一定要是关键帧。

源码分析

创建

MediaCodec 创建

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
// static
sp<MediaCodec> MediaCodec::CreateByType(
const sp<ALooper> &looper, const AString &mime, bool encoder, status_t *err, pid_t pid,
uid_t uid) {
Vector<AString> matchingCodecs;
// 查找该mimeType对应的编解码器
MediaCodecList::findMatchingCodecs(
mime.c_str(),
encoder,
0,
&matchingCodecs);

if (err != NULL) {
*err = NAME_NOT_FOUND;
}
for (size_t i = 0; i < matchingCodecs.size(); ++i) {
// 创建MediaCodec
sp<MediaCodec> codec = new MediaCodec(looper, pid, uid);
AString componentName = matchingCodecs[i];
// 初始化编解码器
status_t ret = codec->init(componentName);
if (err != NULL) {
*err = ret;
}
if (ret == OK) {
return codec;
}
}
return NULL;
}

ACode 创建

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
status_t MediaCodec::init(const AString &name) {
mResourceManagerService->init();
// 编解码器名称
mInitName = name;
AString tmp = name;

const sp<IMediaCodecList> mcl = MediaCodecList::getInstance();
...
// 查找该编解码器
for (const AString &codecName : { name, tmp }) {
ssize_t codecIdx = mcl->findCodecByName(codecName.c_str());
if (codecIdx < 0) {
continue;
}
// 获取该编解码器信息
mCodecInfo = mcl->getCodecInfo(codecIdx);
...
break;
}
...
// 创建ACodec
mCodec = GetCodecBase(name, mCodecInfo->getOwnerName());
...
mLooper->registerHandler(this);

mCodec->setCallback(
std::unique_ptr<CodecBase::CodecCallback>(
new CodecCallback(new AMessage(kWhatCodecNotify, this))));
mBufferChannel = mCodec->getBufferChannel();
mBufferChannel->setCallback(
std::unique_ptr<CodecBase::BufferCallback>(
new BufferCallback(new AMessage(kWhatCodecNotify, this))));
// 创建Init消息
sp<AMessage> msg = new AMessage(kWhatInit, this);
msg->setObject("codecInfo", mCodecInfo);
msg->setString("name", name);
...
status_t err;
Vector<MediaResource> resources;
MediaResource::Type type =
secureCodec ? MediaResource::kSecureCodec : MediaResource::kNonSecureCodec;
MediaResource::SubType subtype =
mIsVideo ? MediaResource::kVideoCodec : MediaResource::kAudioCodec;
resources.push_back(MediaResource(type, subtype, 1));
for (int i = 0; i <= kMaxRetry; ++i) {
if (i > 0) {
// Don't try to reclaim resource for the first time.
if (!mResourceManagerService->reclaimResource(resources)) {
break;
}
}
sp<AMessage> response;
// 发送Init消息,连接OMX服务,创建解码器实例
err = PostAndAwaitResponse(msg, &response);
if (!isResourceError(err)) {
break;
}
}
return err;
}

OMX Codec 创建

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
bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) {
ALOGV("onAllocateComponent");

CHECK(mCodec->mOMXNode == NULL);

sp<AMessage> notify = new AMessage(kWhatOMXMessageList, mCodec);
notify->setInt32("generation", mCodec->mNodeGeneration + 1);

sp<RefBase> obj;
CHECK(msg->findObject("codecInfo", &obj));
sp<MediaCodecInfo> info = (MediaCodecInfo *)obj.get();
if (info == nullptr) {
ALOGE("Unexpected nullptr for codec information");
mCodec->signalError(OMX_ErrorUndefined, UNKNOWN_ERROR);
return false;
}
AString owner = (info->getOwnerName() == nullptr) ? "default" : info->getOwnerName();

AString componentName;
CHECK(msg->findString("componentName", &componentName));

sp<CodecObserver> observer = new CodecObserver(notify);
sp<IOMX> omx;
sp<IOMXNode> omxNode;

status_t err = NAME_NOT_FOUND;
OMXClient client;
// 连接OMX服务
if (client.connect(owner.c_str()) != OK) {
mCodec->signalError(OMX_ErrorUndefined, NO_INIT);
return false;
}
omx = client.interface();

pid_t tid = gettid();
int prevPriority = androidGetThreadPriority(tid);
androidSetThreadPriority(tid, ANDROID_PRIORITY_FOREGROUND);
// 创建编解码器实例
err = omx->allocateNode(componentName.c_str(), observer, &omxNode);
androidSetThreadPriority(tid, prevPriority);

if (err != OK) {
ALOGE("Unable to instantiate codec '%s' with err %#x.", componentName.c_str(), err);

mCodec->signalError((OMX_ERRORTYPE)err, makeNoSideEffectStatus(err));
return false;
}

mDeathNotifier = new DeathNotifier(new AMessage(kWhatOMXDied, mCodec));
auto tOmxNode = omxNode->getHalInterface<IOmxNode>();
if (tOmxNode && !tOmxNode->linkToDeath(mDeathNotifier, 0)) {
mDeathNotifier.clear();
}

++mCodec->mNodeGeneration;

mCodec->mComponentName = componentName;
mCodec->mRenderTracker.setComponentName(componentName);
mCodec->mFlags = 0;

if (componentName.endsWith(".secure")) {
mCodec->mFlags |= kFlagIsSecure;
mCodec->mFlags |= kFlagIsGrallocUsageProtected;
mCodec->mFlags |= kFlagPushBlankBuffersToNativeWindowOnShutdown;
}

mCodec->mOMX = omx;
mCodec->mOMXNode = omxNode;
mCodec->mCallback->onComponentAllocated(mCodec->mComponentName.c_str());
mCodec->changeState(mCodec->mLoadedState);

return true;
}

Omx 创建

1
2
3
4
5
6
Omx::Omx() :
mMaster(new OMXMaster()),
mParser() {
(void)mParser.parseXmlFilesInSearchDirs();
(void)mParser.parseXmlPath(mParser.defaultProfilingResultsXmlPath);
}

OMXMaster 创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
	OMXMaster::OMXMaster() {

pid_t pid = getpid();
char filename[20];
snprintf(filename, sizeof(filename), "/proc/%d/comm", pid);
int fd = open(filename, O_RDONLY);
if (fd < 0) {
ALOGW("couldn't determine process name");
strlcpy(mProcessName, "<unknown>", sizeof(mProcessName));
} else {
ssize_t len = read(fd, mProcessName, sizeof(mProcessName));
if (len < 2) {
ALOGW("couldn't determine process name");
strlcpy(mProcessName, "<unknown>", sizeof(mProcessName));
} else {
// the name is newline terminated, so erase the newline
mProcessName[len - 1] = 0;
}
close(fd);
}

addVendorPlugin();
addPlatformPlugin();
}
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
void OMXMaster::addVendorPlugin() {
addPlugin("libstagefrighthw.so");
}

void OMXMaster::addPlatformPlugin() {
addPlugin("libstagefright_softomx_plugin.so");
}

void OMXMaster::addPlugin(const char *libname) {
void *libHandle = android_load_sphal_library(libname, RTLD_NOW);

if (libHandle == NULL) {
return;
}

typedef OMXPluginBase *(*CreateOMXPluginFunc)();
CreateOMXPluginFunc createOMXPlugin =
(CreateOMXPluginFunc)dlsym(
libHandle, "createOMXPlugin");
if (!createOMXPlugin)
createOMXPlugin = (CreateOMXPluginFunc)dlsym(
libHandle, "_ZN7android15createOMXPluginEv");

OMXPluginBase *plugin = nullptr;
if (createOMXPlugin) {
plugin = (*createOMXPlugin)();
}

if (plugin) {
mPlugins.push_back({ plugin, libHandle });
addPlugin(plugin);
} else {
android_unload_sphal_library(libHandle);
}
}

创建 编解码器实例

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
Return<void> Omx::allocateNode(
const hidl_string& name,
const sp<IOmxObserver>& observer,
allocateNode_cb _hidl_cb) {

using ::android::IOMXNode;
using ::android::IOMXObserver;

sp<OMXNodeInstance> instance;
{
Mutex::Autolock autoLock(mLock);
if (mLiveNodes.size() == kMaxNodeInstances) {
_hidl_cb(toStatus(NO_MEMORY), nullptr);
return Void();
}
// 创建编解码器实例
instance = new OMXNodeInstance(
this, new LWOmxObserver(observer), name.c_str());

OMX_COMPONENTTYPE *handle;
// 生成实例
OMX_ERRORTYPE err = mMaster->makeComponentInstance(
name.c_str(), &OMXNodeInstance::kCallbacks,
instance.get(), &handle);

if (err != OMX_ErrorNone) {
LOG(ERROR) << "Failed to allocate omx component "
"'" << name.c_str() << "' "
" err=" << asString(err) <<
"(0x" << std::hex << unsigned(err) << ")";
_hidl_cb(toStatus(StatusFromOMXError(err)), nullptr);
return Void();
}
instance->setHandle(handle);

// Find quirks from mParser
const auto& codec = mParser.getCodecMap().find(name.c_str());
if (codec == mParser.getCodecMap().cend()) {
LOG(WARNING) << "Failed to obtain quirks for omx component "
"'" << name.c_str() << "' "
"from XML files";
} else {
uint32_t quirks = 0;
for (const auto& quirk : codec->second.quirkSet) {
if (quirk == "quirk::requires-allocate-on-input-ports") {
quirks |= OMXNodeInstance::
kRequiresAllocateBufferOnInputPorts;
}
if (quirk == "quirk::requires-allocate-on-output-ports") {
quirks |= OMXNodeInstance::
kRequiresAllocateBufferOnOutputPorts;
}
}
instance->setQuirks(quirks);
}

mLiveNodes.add(observer.get(), instance);
mNode2Observer.add(instance.get(), observer.get());
}
observer->linkToDeath(this, 0);

_hidl_cb(toStatus(OK), new TWOmxNode(instance));
return Void();
}