音视频开发之IOMX调用端—OMXCodec源码分析

news2024/11/14 8:24:15

概述

OMX Codec是stagefrightplayer中负责解码的模块。由于遵循openmax接口规范,因此结构稍微有点负责,这里就依照awesomeplayer中的调用顺序来介绍。

主要分如下几步:

  • 1 mClient->connect
  • 2 InitAudioDecoder & InitVideoDecoder
  • 3 消息通信机制模型的介绍
  • 4 解码过程介绍

先看下类图

这里OMX Codec是以service的方式提供服务的。Awesomeplayer中通过mOmx(IOMX) 作为客户端通过binder方式与OMX 通信完成解码的工作。

源码分析主要结构

与Android其他API类似,MediaCodec主要分为API、JNI、Native、Server四个部分。

本文主要分析其API、JNI、Native三部分的结构,也就是在客户进程中运行的代码。

MediaCodec源码的主要结构如下:

应用代码编写时使用的是java层MediaCodec的接口。这里主要是通过JNI调用Native代码。

进入JNI代码后,主要与JMediaCodec打交道,JMediaCodec负责调用MediaCodec(c++)的方法。

在MediaCodec(c++)和ACodec中包含了解码器(客户端)的主要逻辑。最后ACodec作为MediaCodec与OMX的桥梁,负责调用OMX服务端的功能。

接下来分别看下JMediaCodec,MediaCodec,ACodec和OMXClient的主要结构。

JMediaCodec

JMediaCodec的定义中主要的两个字段:

struct JMediaCodec : public AHandler {
    //……
private:
    sp<ALooper> mLooper; //mLooper->setName("MediaCodec_looper");
    sp<MediaCodec> mCodec;//MediaCodec::CreateByType/CreateByComponentName
    //……
};

sp是Android源码中的智能指针的概念,通过引用计数自动回收内存

mCodec是MediaCodec(c++)的实例。mLooper用于MediaCodec(c++,下文如无特别说明MediaCodec均指c++中的MediaCodec类)的事件循环。这两个字段在构造函数中初始化.

MediaCodec

MediaCodec定义中,主要看3个字段:

struct MediaCodec : public AHandler {//AHandler::onMessageReceived
    //……
private:
    State mState;
    sp<ALooper> mLooper;
    sp<CodecBase> mCodec;
    //……
}

这里的mLooper就是上面JMediaCodec的mLooper,mCodec是ACodec(ACodec继承CodecBase)

看下初始化过程(有简化):

//CreateByType与CreateByComponentName类似
sp<MediaCodec> MediaCodec::CreateByComponentName(
        const sp<ALooper> &looper, const char *name, status_t *err) {
    sp<MediaCodec> codec = new MediaCodec(looper);
​
    const status_t ret = codec->init(name, false /* nameIsType */, false /* encoder */);
    if (err != NULL) {
        *err = ret;
    }
    return ret == OK ? codec : NULL; // NULL deallocates codec.
}
​
//构造函数,“复制”了mLooper
MediaCodec::MediaCodec(const sp<ALooper> &looper)
    : mState(UNINITIALIZED),
      mLooper(looper),
      mCodec(NULL){
}
​
//init是主要的初始化过程
status_t MediaCodec::init(const AString &name, bool nameIsType, bool encoder) {
    mInitName = name;
    mInitNameIsType = nameIsType;
    mInitIsEncoder = encoder;
​
    mCodec = new ACodec;//创建ACodec
    //registerHandler效果上是增加一个Handler, Looper内部会根据Handler id分发消息
    mLooper->registerHandler(mCodec);
    mLooper->registerHandler(this);
​
    mCodec->setNotificationMessage(new AMessage(kWhatCodecNotify, id()));
​
    sp<AMessage> msg = new AMessage(kWhatInit, id());//第一个消息:kWhatInit
    msg->setString("name", name);
    msg->setInt32("nameIsType", nameIsType);
​
    if (nameIsType) {
        msg->setInt32("encoder", encoder);
    }
​
    sp<AMessage> response;
    return PostAndAwaitResponse(msg, &response);
}

MediaCodec的初始化函数牵涉的几个关键的点:

  • MediaCodec的主要功能由ACodec实现
  • MediaCodec和ACodec内部都是通过事件模型驱动的

ACodec

struct ACodec : public AHierarchicalStateMachine, public CodecBase {
    ACodec();
    //……
};

ACodec的声明(多继承)可以看出,它内部是状态机,并且提供CodecBase功能。

构造函数:

ACodec::ACodec(){
    mUninitializedState = new UninitializedState(this);
    mLoadedState = new LoadedState(this);
    mLoadedToIdleState = new LoadedToIdleState(this);
    mIdleToExecutingState = new IdleToExecutingState(this);
    mExecutingState = new ExecutingState(this);
    changeState(mUninitializedState);
}

构造函数初始化了内部的几个状态类,并将初始状态设置为UninitializedState。

ACodec的消息处理是委托给AHierarchicalStateMachine::handleMessage处理的。AHierarchicalStateMachine会传递给当前状态类去处理。

OMXClient

OMXClient代码比较简单,用于维持binder连接和访问binder方法。

status_t OMXClient::connect() {
    sp<IServiceManager> sm = defaultServiceManager();
    sp<IBinder> binder = sm->getService(String16("media.player"));
    sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder);
​
    CHECK(service.get() != NULL);
​
    mOMX = service->getOMX();
    CHECK(mOMX.get() != NULL);
​
    if (!mOMX->livesLocally(0 /* node */, getpid())) {
        ALOGI("Using client-side OMX mux.");
        mOMX = new MuxOMX(mOMX);
    }
​
    return OK;
}

OMXClient::connect时会从ServiceManager处查询取得media.player服务。

dequeueOutputBuffer

接下来,通过分析dequeueOutputBuffer的调用流程,进一步理解以上结构。

MediaCodec.java会调用native_dequeueOutputBuffer,在android_media_MediaCodec.cpp中映射到android_media_MediaCodec_dequeueOutputBuffer函数.

android_media_MediaCodec_dequeueOutputBuffer调用JMediaCodec.dequeueOutputBuffer.

JMediaCodec.dequeueOutputBuffer`再调用`MediaCodec::dequeueOutputBuffer

这里的JNI逻辑比较简单,不展开分析。

  1. 主要流程分析

1.1 消息队列

接下来看MediaCodec的实现:

status_t MediaCodec::dequeueOutputBuffer(
        size_t *index,
        size_t *offset,
        size_t *size,
        int64_t *presentationTimeUs,
        uint32_t *flags,
        int64_t timeoutUs) {
    sp<AMessage> msg = new AMessage(kWhatDequeueOutputBuffer, id());
    msg->setInt64("timeoutUs", timeoutUs);
​
    sp<AMessage> response;
    status_t err;
    if ((err = PostAndAwaitResponse(msg, &response)) != OK) {
        return err;
    }
​
    CHECK(response->findSize("index", index));
    CHECK(response->findSize("offset", offset));
    CHECK(response->findSize("size", size));
    CHECK(response->findInt64("timeUs", presentationTimeUs));
    CHECK(response->findInt32("flags", (int32_t *)flags));
​
    return OK;
}

dequeueOutputBuffer构造了一个kWhatDequeueOutputBuffer消息,调用PostAndAwaitResponse发送并等待回包。

最后将回包中的字段取出,通过输出参数返回给JMediaCodec去包装。

kWhatDequeueOutputBuffer消息通过ALooper传递给自己处理(MediaCodec继承了AHandler),在onMessageReceived中处理:

void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {//有简化
    switch (msg->what()) {
            case kWhatDequeueOutputBuffer:
            {
                if (handleDequeueOutputBuffer(replyID, true /* new request */)) {
                    break;
                }
            }
    }
}

handleDequeueOutputBuffer取得buffer后,会通知等待中的dequeueOutputBuffer函数(所在的线程)

1.2 handleDequeueOutputBuffer

bool MediaCodec::handleDequeueOutputBuffer(uint32_t replyID, bool newRequest) {
    sp<AMessage> response = new AMessage;
​
        //1. dequeuePortBuffer,从mAvailPortBuffers取index
        ssize_t index = dequeuePortBuffer(kPortIndexOutput);
​
        if (index < 0) {
            CHECK_EQ(index, -EAGAIN);
            return false;
        }
​
        //2. 根据index,从mPortBuffers取ABuffer
        const sp<ABuffer> &buffer =
            mPortBuffers[kPortIndexOutput].itemAt(index).mData;
​
        //3. 取ABuffer中的值,通过response通知给消息等待者
        response->setSize("index", index);
        response->setSize("offset", buffer->offset());
        response->setSize("size", buffer->size());
​
        int64_t timeUs;
        CHECK(buffer->meta()->findInt64("timeUs", &timeUs));
​
        response->setInt64("timeUs", timeUs);
​
        int32_t omxFlags;
        CHECK(buffer->meta()->findInt32("omxFlags", &omxFlags));
​
        uint32_t flags = 0;
        if (omxFlags & OMX_BUFFERFLAG_SYNCFRAME) {
            flags |= BUFFER_FLAG_SYNCFRAME;
        }
        if (omxFlags & OMX_BUFFERFLAG_CODECCONFIG) {
            flags |= BUFFER_FLAG_CODECCONFIG;
        }
        if (omxFlags & OMX_BUFFERFLAG_EOS) {
            flags |= BUFFER_FLAG_EOS;
        }
​
        response->setInt32("flags", flags);
​
    response->postReply(replyID);
​
    return true;
}

为了简化分析,这里跳过错误处理部分,看常规流程:

  1. dequeuePortBuffer,从mAvailPortBuffers取index
  2. 根据index,从mPortBuffers取ABuffer
  3. 取ABuffer中的值,通过response通知给消息等待者

在以上步骤中,用到两个buffer,分别是mPortBuffers和mAvailPortBuffers。

mPortBuffers中保存了解码器的所有buffer(可用,不可用的都在),mAvailPortBuffers类似一个队列,排队可用buffer的index。

下面我们从dequeOutput的角度看下这两个buffer的填充。

  1. mPortBuffers

mPortBuffers声明为:Vector mPortBuffers[2];

最外层是一个长度为2的数组,通过kPortIndexOutput和kPortIndexInput分别访问输出和输入buffer。

BufferInfo主要字段(本文用到的):

struct BufferInfo {
    uint32_t mBufferID;
    sp<ABuffer> mData;
    //……
};
​
struct ABuffer : public RefBase {
    //……
    uint8_t *data();
    sp<AMessage> meta();
private:
    sp<AMessage> mMeta;
    //……
};

mPortBuffers初始化后的内容是空的,需要到服务端解码器创建后才被填充,填充过程如下:

1. ACodec在LoadedState下收到kWhatStart时,会进入mLoadedToIdleState
2. mLoadedToIdleState进入时(stateEntered)会调用allocateBuffers
3. allocateBuffers调用allocateBuffersOnPort(kPortIndexOutput)
4. allocateBuffersOnPort中根据mOMX->getParameter返回的参数,创建portBuffers,并作为参数构建并发送了CodecBase::kWhatBuffersAllocated消息
5. MediaCodec收到kWhatBuffersAllocated消息,从消息中解析得到portBuffers,并保存到mPortBuffers
  1. mAvailPortBuffers

mAvailPortBuffers的声明为:List mAvailPortBuffers[2];

它保存的是可用buffer在mPortBuffers中的index。可以理解为生产消费模式中的物料缓冲区。

对于mAvailPortBuffers主要由3个操作:

  • 消费:dequeuePortBuffer
  • 生产:updateBuffers
  • 清理:returnBuffersToCodecOnPort

3.1 dequeuePortBuffer(消费)

分析MediaCodec的handleDequeueOutputBuffer时,提到从dequeuePortBuffer可以取到当前可用的(最先放入的)buffer的index。dequeuePortBuffer实现如下:

ssize_t MediaCodec::dequeuePortBuffer(int32_t portIndex) {
    CHECK(portIndex == kPortIndexInput || portIndex == kPortIndexOutput);
​
    List<size_t> *availBuffers = &mAvailPortBuffers[portIndex];
​
    if (availBuffers->empty()) {
        return -EAGAIN;
    }
​
    size_t index = *availBuffers->begin();
    availBuffers->erase(availBuffers->begin());
​
    //……
​
    return index;
}

如果没有可用buffer,就返回-EAGAIN要求重试。

如果有可用buffer,就返回队头(也就是最先放入的buffer)。

3.2 updateBuffers(生产)

生产过程如下:

1. ACodec收到omx_message::FILL_BUFFER_DONE消息,调用onOMXFillBufferDone
2. 函数onOMXFillBufferDone中构建CodecBase::kWhatDrainThisBuffer消息,发送给MediaCodec
3. kWhatDrainThisBuffer调用updateBuffers
4. updateBuffers遍历mPortBuffers找到bufferId相同的buffer所在index,放入mAvailPortBuffers队尾

3.3 returnBuffersToCodecOnPort(清理)

清理主要发生在以下时机:

  • flush
  • stop
  • release
  • state进入UNINITIALIZED

returnBuffersToCodecOnPort主要是clear了mAvailPortBuffers。

总结

梳理整个分析过程,可以总结MediaCodec的源码如下:

  • 客户端进程内的代码主要是封装了与服务端omx通信的内容,包括远程buffer到本地的映射(mPortBuffers)
  • MediaCodec代码组织分层清晰,分析其他接口一样可以按JMediaCodec -> MediaCodec -> ACodec -> omx的顺序去看
  • MediaCodec(c++)和ACodec内部都是基于状态机和消息队列实现的。消息队列可有效解决多线程问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/399854.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++回顾(十三)—— 运算符重载提高

13.1 为什么不要重载 && 和 || 运算符 1&#xff09;&&和||是C中非常特殊的操作符2&#xff09;&&和||内置实现了短路规则3&#xff09;操作符重载是靠函数重载来完成的4&#xff09;操作数作为函数参数传递5&#xff09;C的函数参数都会被求值&#…

xxl-job分布式任务调度平台

分布式任务调度平台XXL-JOB (xuxueli.com) 1 xxl-job概述 XXL-JOB是一个分布式任务调度平台&#xff0c;其核心设计目标是开发迅速、学习简单、轻量级、易扩展。 1.1 xxl-job架构 我把上面的图精简了一下&#xff0c;xxl-job 的调度器和业务执行是独立的。调度器决定任务的调…

【数据分析师求职面试指南】必备基础知识整理

数据分析师基础知识统计 数据分析知识基础概念随机变量常用特征正态分布与大数定律、中心极限定律假设检验模型、数据挖掘知识常用概念数据集划分欠拟合过拟合模型分类方法常见模型介绍线性回归模型&#xff1a;逻辑回归模型决策树模型随机森林模型Boosting模型XGBoost模型模型…

前后端分离项目学习-vue+springboot 博客

前后端分离项目 文章总体分为2大部分&#xff0c;Java后端接口和vue前端页面 项目演示&#xff1a;www.markerhub.com:8084/blogs Java后端接口开发 1、前言 从零开始搭建一个项目骨架&#xff0c;最好选择合适&#xff0c;熟悉的技术&#xff0c;并且在未来易拓展&#xf…

精简:设计模式

1.设计模式概述 1.什么是设计模式 设计模式(Design Pattern)是前辈们对代码开发经验的总结&#xff0c;是解决特定问题的一系列套路。 它不是语法规定&#xff0c;而是一套用来提高代码可复用性、可维护性、可读性、稳健性以及安全性的解决方案。 1995年&#xff0c;GoF (Ga…

flutter工程创建过程中遇到一些问题。

安装环境版本&#xff1a;JDK7.-JDK 8 Andriod SDK 10 flutter 版本 3.0 1.当创建完后flutter工程后会遇到 run gradle task assemlble Debug 的问题&#xff0c;需要设置远程仓库&#xff0c;共需要修改三个地方build.gradle两处以及flutter 下面的D:\FVM\versions\3.0.0\pac…

Excel常用可视化图表

目录柱状图与条形图折线图饼图漏斗图雷达图瀑布图及甘特图旭日图组合图excel图表&#xff1a;柱状数据条、excel热力图、mini图可视化工具的表现形式&#xff1a;看板、可视化大屏、驾驶舱 柱状图与条形图 条形图是柱状图的转置 类别&#xff1a; 单一柱状图&#xff1a;反映…

Linux内核移植

内核移植半导体厂商会从linux内核官网下载某个版本&#xff0c;将其移植到自己的CPU上&#xff0c;测试成功后就会将其开放给该半导体的厂商的CPU开发者&#xff0c;开发者下载其提供的linux内核&#xff0c;然后将其移植到自己的 产品上。1、NXP官方开发板Linux内核编译测试编…

VR会议不断升级,为商务会谈打造云端洽谈服务!

VR会议不断升级&#xff0c;为商务会谈打造云端洽谈服务。在商务合作中&#xff0c;对客户需求的理解以及与客户讲解方案都需要建立在一个有效的沟通上&#xff0c;因此VR会议的用武之地就有了&#xff0c;以VR全景技术为核心&#xff0c;通过同屏互动和全景通信技术&#xff0…

wiki(维基)是什么?企业为什么需要搭建wiki?

维基百科是wiki的一个著名例子。维基百科上的内容可以由任何人创建和编辑&#xff0c;只要他们能够访问网络浏览器&#xff0c;并且可以使用简化的加价语言进行写作。对于 wiki&#xff0c;没有集中的作者或团队负责内容生成。从某种意义上说&#xff0c;维基是非常民主的。维基…

【SCL】移位和循环指令的应用(音乐喷泉改进)

移位指令&#xff1a;右移&#xff08;SHR&#xff09;左移&#xff08;SHL&#xff09;和循环左移/右移&#xff08;ROR/ROL&#xff09;指令的应用 文章目录 目录 一、移位和循环移位指令 1.左移右移 2.使用左移和脉冲实现音乐喷泉 3.循环移位指令 二、优化的其它方法 1.使用…

计算机SCI期刊的分值是什么意思? - 易智编译EaseEditing

影响因子&#xff08;Impact Factor,IF)是美国ISI&#xff08;科学信息研究所)的JCR(期刊引证报告)中的一项数据。 即某期刊前两年发表的论文在统计当年的被引用总次数除以该期刊在前两年内发表的论文总数。这是一个国际上通行的期刊评价指标。 例如&#xff0c;某期刊2005年影…

2023年主流的固定资产管理方式

2023年主流的固定资产管理方式可能有以下三种&#xff1a; 基于PaaS平台的固定资产管理系统&#xff0c;支持低代码平台&#xff0c;可以通过拖拉拽的方式进行表单搭建、流程搭建、自定义仪表盘等&#xff0c;满足不同行业和企业的个性化需求。基于RFID和二维码相结合的固定资…

卷麻了,00后Jmeter用的比我还熟练,简直没脸见人......

经常看到无论是刚入职场的新人&#xff0c;还是工作了一段时间的老人&#xff0c;都会对测试工具的使用感到困扰&#xff1f;前言性能测试是一个全栈工程师/架构师必会的技能之一&#xff0c;只有学会性能测试&#xff0c;才能根据得到的测试报告进行分析&#xff0c;找到系统性…

Allegro如何快速查看差分对是否等长的方法

在用Allegro进行PCB设计时&#xff0c;用快速查看差分对是否等长的方法&#xff0c;可以提高效率。那如何操作呢&#xff1f;具体操作方法如下&#xff1a;&#xff08;1&#xff09;选择菜单栏Route选择Timing Vision&#xff08;时序视图&#xff09; 然后在Options选项卡Tim…

陀螺和加计有关参数部分说明

部分参数计算一、零偏二、随机游走三、Allan方差分析使用要点一、零偏 如果只用一个指标来代表一款IMU的精度的话&#xff0c;那毫无疑问是陀螺零偏。这是因为&#xff1a;1) 惯导系统的精度主要取决于IMU中的陀螺器件精度&#xff0c;而不是加速度计精度&#xff1b;2) 陀螺的…

黑客入门教程【非常详细】从零基础入门到精通,看这一篇就够了!

首先要明白啊&#xff0c;我们现在说的黑客不是那种窃取别人信息、攻击别人系统的黑客&#xff0c;说的是调试和分析计算机安全系统的网络安全工程师。 黑客技术的核心就是渗透攻防技术&#xff0c;是为了证明网络防御按照预期计划正常运行而提供的一种机制。就是通过模拟恶意…

C#:Krypton控件使用方法详解(第十三讲) ——kryptonDomainUpDown

今天介绍的Krypton控件中的kryptonDomainUpDown。下面介绍控件的外观属性和Item属性&#xff1a;Cursor属性&#xff1a;表示鼠标移动过该控件的时候&#xff0c;鼠标显示的形状。属性值如下图所示&#xff1a;Text属性&#xff1a;表示控件的显示文本内容&#xff0c;属性值为…

Apache HTTP Server <2.4.56 mod_proxy_uwsgi 模块存在请求走私漏洞(CVE-2023-27522)

漏洞描述 Apache HTTP Server 是一个Web服务器软件。 该项目受影响版本存在请求走私漏洞。由于mod_proxy_uwsgi.c 中uwsgi_response方法对于源响应头缺少检查&#xff0c;当apache启用mod_proxy_uwsgi后&#xff0c;攻击者可利用过长的源响应头等迫使应转发到客户端的响应被截…

单例模式(设计模式详解)

单例模式 描述 单例模式是一种创建型模式&#xff0c;它的目的是确保一个类只有一个实例&#xff0c;并提供全局访问点。这个实例可以被多个客户端共享&#xff0c;从而避免创建多个实例所带来的资源浪费和不必要的复杂性。 实现 懒汉模式 public class LasySingleton {priv…