android之媒体硬解OMX的实现

2023-10-27

转自:http://blog.csdn.net/vincent_blog/article/details/7578112


android的多媒体部分采用的编解码标准是OMX,当然这个标准是用于硬件编解码的,软件编解码在这里我就不说了。
直接从stagefright的awesomeplayer开始说起吧,如果看过我前面博客的人知道stagefright使用的三个步骤:
setdatasoure
prepare
start
至于它们的作用在这里就不多说了。
在prepare里面,当MediaExtractor解析文件后会产生一个音频流和一个视频流(可能还有字幕流)对应到stagefright里面就是一个MediaSource的数据结构。
也就是awesomeplayer里面的mVideoTrack和mAudioTrack两个数据成员。
得到音视频流后就要开始构造解码器了(暂且只说解码,编码类似)。请看initVideoDecoder或initAudioDecoder。
status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {
    mVideoSource = OMXCodec::Create(
            mClient.interface(), mVideoTrack->getFormat(),//mClient.interface() is BnOMX
            false, // createEncoder
            mVideoTrack,
            NULL, flags);
  ...
  ...
  ...
        status_t err = mVideoSource->start();

        if (err != OK) {
            mVideoSource.clear();
            return err;
        }
    }
    return mVideoSource != NULL ? OK : UNKNOWN_ERROR;
}
函数一开始就创建了一个OMXCodec,下面我们看下传进来的几个参数的意思:
===========================================================================
mClient.interface()
在awesomeplayer的构造函数里面有这么一句话    CHECK_EQ(mClient.connect(), OK);可以到OMXClient里面去看实际是通过MediaPlayerService创建了一个BnOMX(bnOMX会在后面讲到)然后作为自己的成员变量保存下来,这里我们可以将OMXClient看作OMX的客户端,BnOMX则是OMX的具体实现。
再回到mClient.interface(),就知道它返回的就是前面创建的BnOMX。
mVideoTrack->getFormat()
这个很显然是格式信息,但是这个里面不仅仅是编码格式,还有宽高,是否旋转等等,通称MetaData
false
指的是创建解码器,true则是编码器
mVideoTrack
视频流(解码前的压缩数据)
NULL
不指定解码器,如果不指定就会到现有的解码器中去找,选择第一个找到的
flags
解码器的类型,你可以在这里将解码器指定为软解码
===========================================================================
进入OMXCodec::Create函数
sp<MediaSource> OMXCodec::Create(
        const sp<IOMX> &omx,//BnOMX
        const sp<MetaData> &meta, bool createEncoder,
        const sp<MediaSource> &source,
        const char *matchComponentName,//NULL
        uint32_t flags) {
    const char *mime;
    bool success = meta->findCString(kKeyMIMEType, &mime);
    CHECK(success);
 
    Vector<String8> matchingCodecs;
    findMatchingCodecs(
            mime, createEncoder, matchComponentName, flags, &matchingCodecs);

    if (matchingCodecs.isEmpty()) {
        return NULL;
    }

    sp<OMXCodecObserver> observer = new OMXCodecObserver;
    IOMX::node_id node = 0;

    const char *componentName;
    for (size_t i = 0; i < matchingCodecs.size(); ++i) {
        componentName = matchingCodecs[i].string();
  LOGV("componentName is %s",componentName);
        sp<MediaSource> softwareCodec = createEncoder?
            InstantiateSoftwareEncoder(componentName, source, meta):
            InstantiateSoftwareCodec(componentName, source);

        if (softwareCodec != NULL) {
            LOGV("Successfully allocated software codec '%s'", componentName);

            return softwareCodec;
        }

        LOGV("Attempting to allocate OMX node '%s'", componentName);

        uint32_t quirks = getComponentQuirks(componentName, createEncoder);

        if (!createEncoder
                && (quirks & kOutputBuffersAreUnreadable)
                && (flags & kClientNeedsFramebuffer)) {
            if (strncmp(componentName, "OMX.SEC.", 8)) {
                // For OMX.SEC.* decoders we can enable a special mode that
                // gives the client access to the framebuffer contents.

                LOGW("Component '%s' does not give the client access to "
                     "the framebuffer contents. Skipping.",
                     componentName);

                continue;
            }
        }

        status_t err = omx->allocateNode(componentName, observer, &node);
        if (err == OK) {
            LOGV("Successfully allocated OMX node '%s'", componentName);

            sp<OMXCodec> codec = new OMXCodec(
                    omx, node, quirks,
                    createEncoder, mime, componentName,
                    source);

            observer->setCodec(codec);

            err = codec->configureCodec(meta, flags);

            if (err == OK) {
                return codec;
            }

            LOGV("Failed to configure codec '%s'", componentName);
        }
    }

    return NULL;
}
这里面有几个关键的函数
findMatchingCodecs
InstantiateSoftwareCodec
omx->allocateNode
下面一一进行说明:
=========================================================================
findMatchingCodecs
这个函数里面实际是会到一个CodecInfo的数组里面去找到符合条件的解码器,这个数组在OMXCodec里面定义的。当然放在前面就被放到数组的前面保存在matchingCodecs里面,
然后通过for循环来遍历这个matchingCodecs数组。

InstantiateSoftwareCodec
在看这个函数前我声明一下,我分析的代码是2.3.x的在4.0的代码里面这里会有些区别,这个看我的另一篇blog  stagefright之2.3和4.0的区别 就知道了。
在这个函数里面,会将matchingCodecs里面的解码器与一些软解码器进行比较(如果是硬解的话名字当然会不一样)。然而我们一般都是将硬解放在前面的,所以这个函数肯定会返回NULL
所以一般都会走到第三函数

omx->allocateNode
这个函数会调到BnOMX里面来。我们先看下它的三个参数componentName不多说,observer大家得留意了,它可是底层给我们上报消息的东西了,在后面会谈到,node显然是一个输出参数暂时我只能说它就是
一个区分不同解码器的标识。
下面看allocateNode的代码,这个比较重要
status_t OMX::allocateNode(
        const char *name, const sp<IOMXObserver> &observer, node_id *node) {
    Mutex::Autolock autoLock(mLock);

    *node = 0;

    OMXNodeInstance *instance = new OMXNodeInstance(this, observer);

    OMX_COMPONENTTYPE *handle;
    OMX_ERRORTYPE err = mMaster->makeComponentInstance(
            name, &OMXNodeInstance::kCallbacks,
            instance, &handle);

    if (err != OMX_ErrorNone) {
        LOGV("FAILED to allocate omx component '%s'", name);

        instance->onGetHandleFailed();

        return UNKNOWN_ERROR;
    }

    *node = makeNodeID(instance);
    mDispatchers.add(*node, new CallbackDispatcher(instance));//CallbackDispatcher dispatch the callback message from omx hardware

    instance->setHandle(*node, handle);

    mLiveNodes.add(observer->asBinder(), instance);
    observer->asBinder()->linkToDeath(this);

    return OK;
}
这里面先是创建了一个OMXNodeInstance,然后就是mMaster->makeComponentInstance再就是mDispatchers.add(*node, new CallbackDispatcher(instance))
最后instance->setHandle(*node, handle);
先简单介绍几个数据结构
OMXNodeInstance某一种类型的OMX,跟nodeid 一一对应
mMaste是OMXMaster这个说白了就是在本地OMX和硬件厂商的OMX之间做管理和协调工作的
mDispatchers是CallbackDispatcher类型的数组,它负责消息的分发(从硬件厂商获取消息分发到具体的某一种类型的解码器OMXNodeInstance,最后到前面讲的observer)
instance->setHandle(*node, handle)这里的这个handle就很关键了,所有的操作都必须通过它来完成
=========================================================================
细说一下OMXMaster
构造函数里面
OMXMaster::OMXMaster()
    : mVendorLibHandle(NULL) {
    addVendorPlugin();

#ifndef NO_OPENCORE
    addPlugin(new OMXPVCodecsPlugin);
#endif
}
在没有Opencore的情况下我们只看addVendorPlugin(),这从函数名就可以看出来就是将厂家的插件加入进来。
void OMXMaster::addVendorPlugin() {
    mVendorLibHandle = dlopen("libstagefrighthw.so", RTLD_NOW);

    if (mVendorLibHandle == NULL) {
        return;
    }

    typedef OMXPluginBase *(*CreateOMXPluginFunc)();
    CreateOMXPluginFunc createOMXPlugin =
        (CreateOMXPluginFunc)dlsym(
                mVendorLibHandle, "_ZN7android15createOMXPluginEv");

    if (createOMXPlugin) {
        addPlugin((*createOMXPlugin)());
    }
}
看到了吧,这里开始使用动态库来调用了,也就是说厂家自己替换这个库就行了。
至于要实现什么东西是有标准的,这里我就不多说了。
像CreateOMXPluginFunc这个函数是一定得有的,也就是所创建了一个厂家提供的OMX插件。
再看
void OMXMaster::addPlugin(OMXPluginBase *plugin) {
    Mutex::Autolock autoLock(mLock);

    mPlugins.push_back(plugin);

    OMX_U32 index = 0;

    char name[128];
    OMX_ERRORTYPE err;
    while ((err = plugin->enumerateComponents(
                    name, sizeof(name), index++)) == OMX_ErrorNone) {
        String8 name8(name);

        if (mPluginByComponentName.indexOfKey(name8) >= 0) {
            LOGE("A component of name '%s' already exists, ignoring this one.",
                 name8.string());

            continue;
        }

        mPluginByComponentName.add(name8, plugin);
    }
    CHECK_EQ(err, OMX_ErrorNoMore);
}
这里有一个数组保存这些插件,plugin->enumerateComponents这句话的意思是说把厂家提供的这个OMX插件支持的解码器格式一一放入到mPluginByComponentName里面,以后需要的这种格式的解码
器就到这里来找。
回到OMXMaster::makeComponentInstance这个函数,它在OMX::allocateNode中被调用的,
OMX_ERRORTYPE OMXMaster::makeComponentInstance(
        const char *name,//name is codec type 
        const OMX_CALLBACKTYPE *callbacks,
        OMX_PTR appData,
        OMX_COMPONENTTYPE **component) {
    Mutex::Autolock autoLock(mLock);

    *component = NULL;

    ssize_t index = mPluginByComponentName.indexOfKey(String8(name));

    if (index < 0) {
        return OMX_ErrorInvalidComponentName;
    }

    OMXPluginBase *plugin = mPluginByComponentName.valueAt(index);
    OMX_ERRORTYPE err =
        plugin->makeComponentInstance(name, callbacks, appData, component);

    if (err != OMX_ErrorNone) {
        return err;
    }

    mPluginByInstance.add(*component, plugin);

    return err;
}
可以看到这里就通过插件创建了实例,这里的component就是handle,也是底下返回的。callbacks就是OMXNodeInstance里面的几个回调函数OnEvent,OnEmptyBufferDone,OnFillBufferDone

总结一下操作的流程:

OMXCodec  --->   BnOMX   ---->   OMXNodeInstance  ----->  handle(看作是厂家OMX的句柄)

消息回调的流程就是

handle  ---->OnEvent(OMXNodeInstance) ------->OnEvent(BnOMX)-------->post(CallbackDispatcher)-------->onMessage(OMXNodeInstance)
----->onMessage(OMXCodecObserver)------->on_message(OMXCodec)

操作的流程大家都清楚了,下面正式进入我们最关心的,OMX是怎么来实现解码,又是怎么把解码后的数据交给我们的:
上面的操作流程里面我们知道了解码器的创建,开始解码我们必须调用OMXCodec的start函数
status_t OMXCodec::start(MetaData *meta) {
    ...
 ...
 ...
    status_t err = mSource->start(params.get());

    if (err != OK) {
        return err;
    }
 ...
 ...
 ...
 
    return init();
}
我选择了两个比较重要的地方贴了出来。
mSource->start(params.get());
这句话实际是开启了音视频流Track,也就是说准备好压缩数据给解码器去取。
init()里面会调用一个非常重要的函数allocateBuffers();从字面上看是分配内存,没错它就是分配内存的。
status_t OMXCodec::allocateBuffers() {
    status_t err = allocateBuffersOnPort(kPortIndexInput);

    if (err != OK) {
        return err;
    }

    return allocateBuffersOnPort(kPortIndexOutput);
}
它分配了两块内存,一块用于输入,一块用于输出。
至于这里面的实现不同的厂家又有所不同了,我之前做过的一个项目是从/dev/pmem_adsp这个设备中映射出来的一块内存。当然,内存的大小跟厂家提供的解码器的能力是相关的可以通过
status_t err = mOMX->getParameter(
            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
来获取(前面我们讲到了调用的流程,这里就不再多说,总之看到调OMX的最终都会到厂家自定义里面去)。申请的内存在代码中被分为一块一块的(了解camera底层实现的应该知道,这个跟camera的风格很类似)
这些内存块会放到一个叫mPortBuffers[inPort/outPort]的容器中

在awesomeplayer里面开始进行播放后不管是视频还是音频都会调OMXCodec里面的read函数。
status_t OMXCodec::read(
        MediaBuffer **buffer, const ReadOptions *options) {
    *buffer = NULL;

    ...
 ... 
 ...

    if (mInitialBufferSubmit) {
        mInitialBufferSubmit = false;

 ...
 ...
 ...
        drainInputBuffers();//key word

        if (mState == EXECUTING) {
            // Otherwise mState == RECONFIGURING and this code will trigger
            // after the output port is reenabled.
            fillOutputBuffers();//key word
        }
    }
 ...
 ...
 ...

    while (mState != ERROR && !mNoMoreOutputData && mFilledBuffers.empty()) {
  LOGV("NO MORE OUTPUT DATA=============");
        mBufferFilled.wait(mLock);//key word
    }

    if (mState == ERROR) {
        return UNKNOWN_ERROR;
    }

    if (mFilledBuffers.empty()) {
        return mSignalledEOS ? mFinalStatus : ERROR_END_OF_STREAM;
    }

    if (mOutputPortSettingsHaveChanged) {
        mOutputPortSettingsHaveChanged = false;

        return INFO_FORMAT_CHANGED;
    }

    size_t index = *mFilledBuffers.begin();
    mFilledBuffers.erase(mFilledBuffers.begin());

    BufferInfo *info = &mPortBuffers[kPortIndexOutput].editItemAt(index);//key word ,we got original data
    info->mMediaBuffer->add_ref();
    *buffer = info->mMediaBuffer;

    return OK;
}
read要做的事就是从Track里面读取数据给解码器解码后返回给awesomeplayer。而其中最关键的就是
mOMX->emptyBuffer(
            mNode, info->mBuffer, 0, offset,
            flags, timestampUs);//在drainInputBuffers中被调用
和mOMX->fillBuffer(mNode, info->mBuffer);//在fillOutputBuffers中被调用
前面一个函数是将从Track读取来的数据交给OMX解码器解码,而后一个函数就是向OMX解码器请求获取解码后的数据。
这两个操作完成后都是有回调的,至于回调的地方大家自己找吧!上面流程里面已经提到过了。

由于时间的原因后面说的比较简洁,但是大家认真看代码再结合我说的看起来应该没问题的。
先就说这么多吧!希望如果有搞驱动或硬件的能贴出来一些厂家OMX具体实现的代码,并且做下讲解,大家一起学习,感激不尽!


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

android之媒体硬解OMX的实现 的相关文章

随机推荐

  • 找不到viewModels,viewModels()

    谷歌官方文档上学习mvvm 当我写到private val model UserViewModel by viewModels 时 viewModels 爆红 查了资料 找到gradle 添加依赖 dependencies implemen
  • (二)Java常量与变量

    Java常量与变量 主要内容 标识符 关键字 变量 数据类型 类型转换 常量 一 标识符与关键字 1 标识符 类的名字就是标识符 标识符的命名规则 标识符只能由字母数字下划线和美元符 组成 不能以数字开头 标识符不能有空格 标识符严格区分大
  • 龙蜥(Anolis) 编译PHP-8.1.22提示No supported shared memory caching support was found when configuring opcac

    龙蜥 Anolis 编译PHP 8 1 22提示No supported shared memory caching support was found when configuring opcac 使用龙蜥 Anolis 编译安装php8
  • SpringBoot入门最详细教程

    网上有很多springboot的入门教程 自己也因为项目要使用springboot 所以利用业余时间自学了下springboot和springcloud 使用下来发现springboot还是挺简单的 体现了极简的编程风格 大部分通用都是通过
  • 如何用ChatGPT使开发效率提高50%以上?

    简介 ChatGPT是一个大型语言模型 由OpenAI开发 它被训练用于进行对话式交互 能够理解和生成自然语言文本 ChatGPT可以用于多种任务和场景 包括但不限于 智能助手 创意生成 语言学习 编程辅助等 ChatGPT的优势在于它的广
  • Eclipse汉化教程

    在看了很多博主的安装教程后 我不是很懂为什么要查找版本号 此处提供另一种方法 1 查看eclipse安装时间 若刚安装的 请直接看第二条 打开eclipse 在help里找到about 具体请看下图的图标和位置 打开后 找到buildid
  • Java多线程基础之创建和运行

    前言 在现代软件开发中 多线程编程已经成为一项重要的技能 通过合理地利用多线程 我们可以实现并发处理 提高程序性能和改善用户体验 本文将介绍Java中多线程编程的基础知识 包括多线程的概念 线程的创建和运行 以及线程的生命周期和状态转换 一
  • Linux(ubuntu)上安装vmware workstation虚拟机

    Linux ubuntu 上安装vmware workstation虚拟机 首先下载vmware workstation 官网下载地址 https www vmware com products workstation pro workst
  • Qt中的HTTP

    Qt中的HTTP HTTP概述 两种架构 基于请求响应的模式 无状态 请求报文 响应报文 请求方式 Qt实现HTTP请求 HTTP概述 HTTP是一种超文本传输协议 是一种用于分布式 协作式和超媒体信息系统的应用层协议 是浏览器端Web通信
  • GDB常用指令

    摘抄整合 勿喷 GDB r run 执行程序 n next 下一步 不进入函数 s step 下一步 会进入函数 b breakponit 设置断点 l list 查看源码 c continue 继续执行到下一断点 bt backtrace
  • Android AIDL使用详解

    1 什么是aidl aidl是 Android Interface definition language的缩写 一看就明白 它是一种android内部进程通信接口的描述语言 通过它我们可以定义进程间的通信接口 icp interproce
  • C++ Primer 第Ⅱ部分笔记

    1 向算法传递函数 1 1向排序算法传递二元谓词 定义isSHorter方法 bool isShorter const int i const int j return i
  • C++中deque的用法(超详细,入门必看)

    博主简介 Hello大家好呀 我是陈童学 一个与你一样正在慢慢前行的人 博主主页 陈童学哦 所属专栏 C STL 如果本文对你有所帮助的话 希望可以点赞 收藏 支持一下哦 期待你的关注 一起成长哟 前言 Hello各位小伙伴们好 欢迎来到本
  • Spring事务源码详解-spring原码(二)

    上篇文章介绍了事务开启 前面介绍了解析adviors spring事务源码详解 spring原码 一 https blog csdn net ke1ying article details 131360060 事务源码 先从缓存里获取 主要
  • django view.py error 'function' object has no attribute 'objects' 解决办法

    view py中函数和app的名字重复了 请def一个和app名字不一样的函数名 问题就解决了 参考 http stackoverflow com questions 28471404 attributeerror function obj
  • Qt 编译一直死循环问题

    Qt 编译一直死循环问题 有时候Qt编译项目时 一直编不过 查看一下编译窗口 发现一直在循环 输出如下 C soft Qt5 11 5 11 1 mingw53 32 bin qmake exe o Makefile PalmQtLib P
  • 数据分析利器Python——爬虫(含爬取过程、Scrapy框架介绍)

    文章目录 一 基础知识 1 定义 2 基本架构 二 URL管理模块 三 网页下载模块 Python中的requests模块 四 网页解析模块 1 结构化网页解析 2 BeautifulSoup使用步骤 2 1 创建BeautifulSoup
  • Kubernetes服务访问-Nodeport、Loadbalancer和Ingress

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 本文翻译自 https medium com google cloud kubernetes nodeport vs loadbalancer vs ingress whe
  • stm32低功耗休眠模式下使用看门狗wdt

    最近一个项目在用stm32f103 发现stm32一旦开启看门狗 就关不了 进入低功耗模式后 看门狗也重启 不使用看门狗非常危险 网上看了好多资料也查不到解决方案 后来自己想到一个可以进入低功耗又可以使用看门狗的方案 在这里给大家分享 我自
  • android之媒体硬解OMX的实现

    转自 http blog csdn net vincent blog article details 7578112 android的多媒体部分采用的编解码标准是OMX 当然这个标准是用于硬件编解码的 软件编解码在这里我就不说了 直接从st