Android App 通过 MediaCodec Java API 获得的编解码器,实际上是由 StageFright 媒体框架提供。android.media.MediaCodec 调用 libmedia_jni.so 中 JNI native 函数,这些 JNI 函数再去调用 libstagefright.so 库获得 StageFright 框架中的编解码器。
## MediaCodec Java 使用 libmedia_jni.so JNI native 函数
XBMC 通过 MediaCodec API 创建解码器时,使用了 android.media.MediaCodec 包中 createByCodecName(), configure() 和 start() 方法。
其中 createByCodecName() 为 MediaCodec 类静态函数,用于创建 MediaCodec 对象:
frameworks/base/media/java/android/media/MediaCodec.java
final public class MediaCodec {
public static MediaCodec createByCodecName(String name) {
return new MediaCodec(
name, false , false );
}
private MediaCodec(
String name, boolean nameIsType, boolean encoder) {
native_setup(name, nameIsType, encoder);
}
}
在 MediaCodec 构造函数中,调用 JNI 函数 native_setup():
frameworks/base/media/jni/android_media_MediaCodec.cpp
static void android_media_MediaCodec_native_setup(
JNIEnv *env, jobject thiz,
jstring name, jboolean nameIsType, jboolean encoder) {
const char *tmp = env->GetStringUTFChars(name, NULL);
sp codec = new JMediaCodec(env, thiz, tmp, nameIsType, encoder);
env->ReleaseStringUTFChars(name, tmp);
setMediaCodec(env,thiz, codec);
}
JNI 中 JMediaCodec 类封装了 StageFright 框架编解码器 API。
struct JMediaCodec : public RefBase {
JMediaCodec(
JNIEnv *env, jobject thiz,
const char *name, bool nameIsType, bool encoder);
status_t initCheck() const;
status_t configure(
const sp &format,
const sp &bufferProducer,
const sp &crypto,
int flags);
status_t start();
status_t queueInputBuffer(
size_t index,
size_t offset, size_t size, int64_t timeUs, uint32_t flags,
AString *errorDetailMsg);
...
}
JMediaCodec 对象创建时通过调用 StageFright 框架中的 MediaCodec 函数获得解码器对象。
JMediaCodec::JMediaCodec(
JNIEnv *env, jobject thiz,
const char *name, bool nameIsType, bool encoder)
: mClass(NULL),
mObject(NULL) {
mLooper = new ALooper;
if (nameIsType) {
mCodec = MediaCodec::CreateByType(mLooper, name, encoder);
} else {
mCodec = MediaCodec::CreateByComponentName(mLooper, name);
}
}
MediaCodec Java API 中 configure() 和 start() 等方法也使用类似的方式,通过 JNI 访问 StageFright 框架函数:
frameworks/base/media/java/android/media/MediaCodec.java
public void configure(
MediaFormat format,
Surface surface, MediaCrypto crypto, int flags) {
Map formatMap = format.getMap();
String[] keys = null;
Object[] values = null;
if (format != null) {
keys = new String[formatMap.size()];
values = new Object[formatMap.size()];
int i = 0;
for (Map.Entry entry: formatMap.entrySet()) {
keys[i] = entry.getKey();
values[i] = entry.getValue();
++i;
}
}
native_configure(keys, values, surface, crypto, flags);
}
public native final void start();
}
native_configure() 和 start() 使用的 JMediaCodec 对象 codec 在 native_setup() 中创建。
frameworks/base/media/jni/android_media_MediaCodec.cpp
static void android_media_MediaCodec_native_configure(
JNIEnv *env,
jobject thiz,
jobjectArray keys, jobjectArray values,
jobject jsurface,
jobject jcrypto,
jint flags) {
sp codec = getMediaCodec(env, thiz);
sp format;
status_t err = ConvertKeyValueArraysToM essage(env, keys, values, &format);
sp bufferProducer;
if (jsurface != NULL) {
sp surface(android_view_Surface_getSurface(env, jsurface));
if (surface != NULL) {
bufferProducer = surface->getIGraphicBufferProduce r();
}
}
err = codec->configure(format, bufferProducer, crypto, flags);
}
status_t JMediaCodec::configure(
const sp &format,
const sp &bufferProducer,
const sp &crypto,
int flags) {
sp client;
if (bufferProducer != NULL) {
mSurfaceTextureClient = new Surface(bufferProducer, true );
} else {
mSurfaceTextureClient.clear();
}
return mCodec->configure(format, mSurfaceTextureClient, crypto, flags);
}
static void android_media_MediaCodec_start(JNIEnv *env, jobject thiz) {
sp codec = getMediaCodec(env, thiz);
status_t err = codec->start();
}
status_t JMediaCodec::start() {
return mCodec->start();
}
## StageFright MediaCodec
StageFright 框架支持 OpenMAX IL 接口的编解码器,硬件厂商只需提供 OMX 接口的组件就可以为 Android 平台上提供通用的硬件编解码支持。StageFright 支持软件和硬件实现的编解码器, 如果一种编码有多种解码器组件,用户可以配置 /etc/media_codecs.xml 进行选择。
使用 OMX 组件有两种模式:一是阻塞式的,StageFright 用 OMXCodec 类封装相应函数;另一种是非阻塞式的,用 ACodec 类封装。Android 提供的 MediaPlayer 采用的阻塞式方法,MediaCodec 则使用非阻塞式的方法。
非阻塞式方法通过异步函数调用实现,调用程序使用解码组件时,不等待处理结束直接返回,然后借助消息处理机制,解码组件处理完数据后会发送消息给调用程序,这时调用程序再处理解码后的数据。
MediaCodec 结构继承自消息处理类 AHandler:
frameworks/av/include/media/stagefright/MediaCodec.h
struct MediaCodec : public AHandler {
static sp CreateByComponentName(
const sp &looper, const char *name);
status_t configure(
const sp &format,
const sp &nativeWindow,
const sp &crypto,
uint32_t flags);
status_t start();
}
通过 MediaCodec 静态函数 CreateByComponentName() 创建 MediaCodec 对象:
frameworks/av/media/libstagefright/MediaCodec.cpp
// static
sp MediaCodec::CreateByComponentName(
const sp &looper, const char *name) {
sp codec = new MediaCodec(looper);
if (codec->init(name, false , false ) != OK) {
return NULL;
}
return codec;
}
MediaCodec 构造函数中会创建 ACodec 对象:
MediaCodec::MediaCodec(const sp &looper)
: mState(UNINITIALIZED),
mLooper(looper),
mCodec(new ACodec),
mReplyID(0),
mFlags(0),
mSoftRenderer(NULL),
mDequeueInputTimeoutGene ration(0),
mDequeueInputReplyID(0),
mDequeueOutputTimeoutGen eration(0),
mDequeueOutputReplyID(0),
mHaveInputSurface(false) {
}
MediaCodec 对象初始化时先使用 MediaCodecList 获取编解码器组件列表,从中查询对应的解码器组件,然后告诉 ACodec 回调的通知消息 kWhatCodecNotify,发送 kWhatInit 消息到消息队列后返回:
status_t MediaCodec::init(const char *name, bool nameIsType, bool encoder) {
bool needDedicatedLooper = false;
if (nameIsType && !strncasecmp(name, "video/", 6)) {
needDedicatedLooper = true;
} else {
AString tmp = name;
const MediaCodecList *mcl = MediaCodecList::getInstance();
ssize_t codecIdx = mcl->findCodecByName(tmp.c_str());
}
mCodec->setNotificationMessage(new AMessage(kWhatCodecNotify, id()));
sp msg = new AMessage(kWhatInit, id());
msg->setString("name", name);
msg->setInt32("nameIsType", nameIsType);
sp response;
return PostAndAwaitResponse(msg, &response);
}
在 MediaCodec 的消息处理 onMessageRececived() 函数中对 KWhatInit 消息进行处理,包括设置状态,调用 ACodec 对象 mCodec->initiateAllocateComponen t() 函数。
void MediaCodec::onMessageReceived(const sp &msg) {
switch (msg->what()) {
case kWhatInit:
{
mReplyID = replyID;
setState(INITIALIZING);
AString name;
CHECK(msg->findString("name", &name));
int32_t nameIsType;
int32_t encoder = false;
CHECK(msg->findInt32("nameIsType", &nameIsType));
if (nameIsType) {
CHECK(msg->findInt32("encoder", &encoder));
}
sp format = new AMessage;
if (nameIsType) {
format->setString("mime", name.c_str());
format->setInt32("encoder", encoder);
} else {
format->setString("componentName", name.c_str());
}
mCodec->initiateAllocateComponen t(format);
break;
}
...
}
}
ACodec 初始化并为组件分配内存动作完成后,发送 ACodec::kWhatComponentAllocated 消息通知MediaCodec,MediaCodec 的 onMessageReceived() 中再对此消息进行响应:
case kWhatCodecNotify:
{
int32_t what;
CHECK(msg->findInt32("what", &what));
switch (what) {
case ACodec::kWhatComponentAllocated:
{
CHECK_EQ(mState, INITIALIZING);
setState(INITIALIZED);
CHECK(msg->findString("componentName", &mComponentName));
if (mComponentName.startsWith("OMX.google.") ||
mComponentName.startsWith("OMX.ffmpeg.")) {
mFlags |= kFlagIsSoftwareCodec;
} else {
mFlags &= ~kFlagIsSoftwareCodec;
}
if (mComponentName.endsWith(".secure")) {
mFlags |= kFlagIsSecure;
} else {
mFlags &= ~kFlagIsSecure;
}
(new AMessage)->postReply(mReplyID);
break;
}
...
}
}
configure() 和 start() 函数也使用同样方式分别发送 kWhatConfigure 和 kWhatStart 消息到消息队列
status_t MediaCodec::configure(
const sp &format,
const sp &nativeWindow,
const sp &crypto,
uint32_t flags) {
sp msg = new AMessage(kWhatConfigure, id());
msg->setMessage("format", format);
msg->setInt32("flags", flags);
if (nativeWindow != NULL) {
msg->setObject(
"native-window",
new NativeWindowWrapper(nativeWindow));
}
sp response;
return PostAndAwaitResponse(msg, &response);
}
status_t MediaCodec::start() {
sp msg = new AMessage(kWhatStart, id());
sp response;
return PostAndAwaitResponse(msg, &response);
}
消息处理函数中调用 mCodec->initiateConfigureCompone nt(format) 和 mCodec->initiateStart():
case kWhatConfigure:
{
uint32_t replyID;
CHECK(msg->senderAwaitsResponse(&replyID));
mCodec->initiateConfigureCompone nt(format);
break;
}
case kWhatStart:
{
uint32_t replyID;
CHECK(msg->senderAwaitsResponse(&replyID));
mReplyID = replyID;
setState(STARTING);
mCodec->initiateStart();
break;
}
然后再响应 ACodec 返回的消息,其中 initiateStart() 完成后没有消息通知 MediaCodec。
case ACodec::kWhatComponentConfigured :
{
CHECK_EQ(mState, CONFIGURING);
setState(CONFIGURED);
// reset input surface flag
mHaveInputSurface = false;
(new AMessage)->postReply(mReplyID);
break;
}