MediaCodec是Android平台上用来访问编码器和解码器的组件。
工作流程
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数据了。
数据类型 原始数据 音频原始数据类型是一个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 ; } 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 ; } 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); 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); 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 从媒体文件中获取,但是其中有几点需要注意。
最好先将KEY_LEVEL的值设置为null(MediaFormat.setString(KEY_LEVEL, null)
),因为这个值经常不准。
在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数据。
对于解码器来说,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 (;;) { int inputBufferId = codec.dequeueInputBuffer(timeoutUs); if (inputBufferId >= 0 ) { ByteBuffer inputBuffer = codec.getInputBuffer(…); … if (inputDown) { codec.queueInputBuffer(inputBufferId, 0 , 0 , 0L ,MediaCodec.BUFFER_FLAG_END_OF_STREAM); } else { codec.queueInputBuffer(inputBufferId, …); } } int outputBufferId = codec.dequeueOutputBuffer(…); if (outputBufferId >= 0 ) { ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId); MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); … 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) { ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId); … codec.queueInputBuffer(inputBufferId, …); } @Override void onOutputBufferAvailable (MediaCodec mc, int outputBufferId, …) { ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId); MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); … codec.releaseOutputBuffer(outputBufferId, …); } @Override void onOutputFormatChanged (MediaCodec mc, MediaFormat format) { mOutputFormat = format; } @Override void onError (…) { … } }); codec.configure(format, …); mOutputFormat = codec.getOutputFormat(); codec.start(); … codec.stop(); codec.release();
在输入结束后,需要写入EOS标记,此时buffer可为空,需要注意的是一旦写入EOS标记后,就不可以再向InputBufferQueue中写入数据了。
一旦设置了OutputSurface,则不可以访问OutputBufferQueue,对应API会返回null。
一旦设置了InputSurface,则不可以访问InputBufferQueue,对应API会抛异常或返回null。
在M
版本之前,软件解码器在渲染到Surface上时可能未应用旋转。
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 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; MediaCodecList::findMatchingCodecs( mime.c_str(), encoder, 0 , &matchingCodecs); if (err != NULL ) { *err = NAME_NOT_FOUND; } for (size_t i = 0 ; i < matchingCodecs.size(); ++i) { 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 ; } ... 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)))); 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 ) { if (!mResourceManagerService->reclaimResource(resources)) { break ; } } sp<AMessage> response; 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; 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 { 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); 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(); }