使用硬件加速 Android MediaCodec 解码器的本机代码中的访问冲突

2024-02-21

我的目标是使用 Android MediaCodec 解码视频流,然后使用输出图像在本机代码中进行进一步的图像处理。

平台:华硕 tf700t android 4.1.1。 测试码流:H.264 全高清 @ 24 frm/s

内置 Tegra-3 SoC,我指望硬件支持视频解码。从功能上讲,我的应用程序的行为符合预期:我确实可以访问解码器图像 并妥善处理它们。然而,我遇到了非常高的解码器 CPU 负载。

在下面的实验中,进程/线程负载是通过 adb shell 中的“top -m 32 -t”来测量的。为了从“top”获得可靠的输出,通过运行几个以最低优先级永远循环的线程来强制所有 4 个 cpu 核心处于活动状态。通过重复执行“cat /sys/devices/system/cpu/cpu[0-3]/online”可以确认这一点。为了简单起见,只有视频解码,没有音频;并且没有时序控制,因此解码器尽可能快地运行。

第一个实验:运行应用程序,调用 JNI 处理函数,但所有进一步的处理调用都被注释掉。结果:

  • 吞吐量:25 frm/s
  • 应用程序线程VideoDecoder的1%负载
  • 进程 /system/bin/mediaserver 的线程 Binder_3 负载为 24%

看来解码速度是受CPU限制的(四核CPU的25%)...... 当启用输出处理时,解码的图像是正确的并且应用程序可以工作。唯一的问题:解码时 CPU 负载过高。

经过大量实验后,我考虑给 MediaCodec 一个表面来绘制其结果。在所有其他方面,代码都是相同的。结果:

  • 吞吐量 55 frm/s(很好!!)
  • 应用程序线程VideoDecoder的2%负载
  • 进程 /system/bin/mediaserver 的线程 mediaserver 负载为 1%

事实上,视频显示在提供的 Surface 上。由于几乎没有任何CPU负载,这必须是硬件加速的......

看来 de MediaCodec 仅在提供 Surface 的情况下才使用硬件加速?

到目前为止,一切都很好。我已经倾向于使用 Surface 作为一种解决方法(不是必需的,但在某些情况下甚至是一个很好的选择)。但是,如果提供了表面,我将无法访问输出图像!结果是本机代码中出现访问冲突。

这实在是让我很困惑!我没有在文档中看到任何访问限制的概念或任何内容http://developer.android.com/reference/android/media/MediaCodec.html http://developer.android.com/reference/android/media/MediaCodec.html。 谷歌 I/O 演示中也没有提到这个方向http://www.youtube.com/watch?v=RQws6vsoav8 http://www.youtube.com/watch?v=RQws6vsoav8.

那么:如何使用硬件加速的Android MediaCodec解码器并以本机代码访问图像?如何避免访问冲突?任何帮助表示赞赏!也是一种解释或提示。

我很确定 MediaExtractor 和 MediaCodec 已正确使用,因为应用程序 功能正常(只要我不提供 Surface)。 它仍然处于实验阶段,良好的 API 设计已在待办事项列表中;-)

请注意,两个实验之间的唯一区别是变量 mSurface: null 或实际的 Surface 在“mDecoder.configure(mediaFormat,mSurface,null,0);”

初始化代码:

mExtractor = new MediaExtractor();
mExtractor.setDataSource(mPath);

// Locate first video stream
for (int i = 0; i < mExtractor.getTrackCount(); i++) {
    mediaFormat = mExtractor.getTrackFormat(i);
    String mime = mediaFormat.getString(MediaFormat.KEY_MIME);
    Log.i(TAG, String.format("Stream %d/%d %s", i, mExtractor.getTrackCount(), mime));
    if (streamId == -1 && mime.startsWith("video/")) {
        streamId = i;
    }
}

if (streamId == -1) {
    Log.e(TAG, "Can't find video info in " + mPath);
    return;
}

mExtractor.selectTrack(streamId);
mediaFormat = mExtractor.getTrackFormat(streamId);

mDecoder = MediaCodec.createDecoderByType(mediaFormat.getString(MediaFormat.KEY_MIME));
mDecoder.configure(mediaFormat, mSurface, null, 0);

width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
Log.i(TAG, String.format("Image size: %dx%d format: %s", width, height, mediaFormat.toString()));
JniGlue.decoutStart(width, height);

解码器循环(在单独的线程中运行):

ByteBuffer[] inputBuffers = mDecoder.getInputBuffers();
ByteBuffer[] outputBuffers = mDecoder.getOutputBuffers();

while (!isEOS && !Thread.interrupted()) {
    int inIndex = mDecoder.dequeueInputBuffer(10000);
    if (inIndex >= 0) {
        // Valid buffer returned
        int sampleSize = mExtractor.readSampleData(inputBuffers[inIndex], 0);
        if (sampleSize < 0) {
            Log.i(TAG, "InputBuffer BUFFER_FLAG_END_OF_STREAM");
            mDecoder.queueInputBuffer(inIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
            isEOS = true;
        } else {
            mDecoder.queueInputBuffer(inIndex, 0, sampleSize, mExtractor.getSampleTime(), 0);
            mExtractor.advance();
        }
    }

    int outIndex = mDecoder.dequeueOutputBuffer(info, 10000);
    if (outIndex >= 0) {
        // Valid buffer returned
        ByteBuffer buffer = outputBuffers[outIndex];
        JniGlue.decoutFrame(buffer, info.offset, info.size);
        mDecoder.releaseOutputBuffer(outIndex, true);
    } else {
        // Some INFO_* value returned
        switch (outIndex) {
        case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
            Log.i(TAG, "RunDecoder: INFO_OUTPUT_BUFFERS_CHANGED");
            outputBuffers = mDecoder.getOutputBuffers();
            break;
        case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
            Log.i(TAG, "RunDecoder: New format " + mDecoder.getOutputFormat());
            break;
        case MediaCodec.INFO_TRY_AGAIN_LATER:
            // Timeout - simply ignore
            break;
        default:
            // Some other value, simply ignore
            break;
        }
    }

    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
        Log.d(TAG, "RunDecoder: OutputBuffer BUFFER_FLAG_END_OF_STREAM");
        isEOS = true;
    }
}

如果配置输出 Surface,则解码数据将写入可用作 OpenGL ES 纹理的图形缓冲区(通过“外部纹理”扩展)。硬件的各个部分可以按照它们喜欢的格式传递数据,并且 CPU 不必复制数据。

如果您不配置 Surface,则输出将进入java.nio.ByteBuffer。至少有一个缓冲区副本可将数据从 MediaCodec 分配的缓冲区获取到您的ByteByffer,并且可能还有另一个副本,用于将数据返回到您的 JNI 代码中。我希望您看到的是开销成本,而不是软件解码成本。

You might能够通过将输出发送到SurfaceTexture,渲染成 FBO 或 pbuffer,然后使用glReadPixels提取数据。如果你读成“直接”ByteBuffer或致电glReadPixels通过本机代码,您可以减少 JNI 开销。这种方法的缺点是您的数据将采用 RGB 而不是 YCbCr。 (OTOH,如果您想要的转换可以在 GLES 2.0 片段着色器中表达,您可以让 GPU 代替 CPU 来完成这项工作。)

正如另一个答案中所述,不同设备上的解码器输出ByteBuffer不同格式的数据,因此如果可移植性对您来说很重要,那么在软件中解释数据可能不可行。

Edit: Grafika http://github.com/google/grafika现在有一个使用GPU做图像处理的例子。您可以观看演示视频here http://www.youtube.com/watch?v=kH9kCP2T5Gg.

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

使用硬件加速 Android MediaCodec 解码器的本机代码中的访问冲突 的相关文章

  • Android 覆盖在软件按钮之上

    我正在尝试编写一个绘制自定义鼠标指针的应用程序 我目前有一个服务 它创建一个扩展 ViewGroup 的类 并使用 WindowManager 系统服务将其显示为带有 FLAG LAYOUT IN SCREEN 设置的 TYPE SYSTE
  • EditText 中的验证允许 IP 或 Web Url 主机

    我需要对我的 EditText 进行验证 以便它允许我输入有效的 IP 地址格式 即示例 132 0 25 225 or 网址格式 www 例如 www example com 逻辑是 如果用户首先输入任何数值 则验证 IP 将执行操作 否
  • 关闭 Android 中的飞行模式

    如果 num gt 50 我想关闭飞行模式 我实现了这段代码 来自在 Android 中切换飞行模式 https stackoverflow com questions 5533881 toggle airplane mode in and
  • 定期运行任务(每天一次/每周一次)

    我想定期 每周 每天一次 运行一些任务 即获取我的网站新闻页面 即使我的应用程序已关闭 是否可以 是的 您需要查看报警管理器 http developer android com reference android app AlarmMan
  • 按回键隐藏软键盘

    我有一个EditText in an Activity我希望当我打开它时它处于活动状态并且软键盘处于打开状态Activity 这是我的xml for EditText
  • Android Studio - 错误:未捕获翻译错误:com.android.dx.cf.code.SimException:本地 0001:无效

    我刚刚使用 Android Studio 设置了一台新计算机 并从 bitbucket 导入了我的项目 问题是我现在在尝试构建项目时遇到此错误 信息 Gradle 任务 app clean app generateDebugSources
  • Android - 当不在栏顶部时推送通知空白

    我在使用 Android 推送通知时遇到一个小问题 如果有 3 个通知 并且只有其中一个显示标题和消息 位于酒吧顶部的那个 如果有人知道可能是什么问题 请告诉我 请参阅此链接上的图像 这就是我接收通知的方式http postimg org
  • Android-工具栏中的SearchView

    我只想在我的应用程序中添加 searchview 但我不想搜索任何东西 只是我想要用户输入的查询 到目前为止 我尝试了这段代码 但是当我运行我的应用程序时它崩溃了 Update 我尝试了这个 但即使我的应用程序崩溃了 main menu x
  • Android Studio - 如何关闭“单词‘word’中的拼写错误?”

    当命名变量或给出字符串参数时 Android Studio 似乎对我如何标记事物有问题 有办法把它关掉吗 是的 打开Preferences gt Editor gt Inspections gt Spelling gt 关闭Typo并按OK
  • 如何从android获取应用程序安装时间

    我尝试了一些方法 但没有成功 请帮助我 PackageManager pm context getPackageManager ApplicationInfo appInfo pm getApplicationInfo app packag
  • Android模拟器分配内存失败8

    当我尝试从 Eclipse 运行 WXGA800 模拟器时 出现如下错误 Failed to allocate memory 8 This application has requested the Runtime to terminate
  • 如何在android中的操作栏中创建Edittext?

    我们可以在操作栏中使用编辑文本吗 在阅读了 Google 中的大量资源后 我找不到如何在操作栏中创建编辑文本 谁能告诉我该怎么做 您可以设置自定义View为了ActionBar像这样 getActionBar setCustomView R
  • android 中camera.setParameters 失败

    我已将相机功能包含在我的应用程序中 我还在市场上推出了该应用程序 我从一位用户那里收到一条错误消息 称他在打开相机时遇到错误 我已经在 2 1 的设备上测试了该应用程序 我从用户那里得到的错误是使用 Nexus One 它主要运行 2 2
  • 删除Android所有语言中的字符串

    我有一个包含多个翻译的应用程序 我想删除一些字符串 我怎样才能重构并删除它们一次 例如在默认情况下strings xml文件并自动将删除传播到其他翻译的其他 strings xml 文件 您可以通过 Android Studio 中的 翻译
  • 如何获取android手机型号、版本、sdk详细信息?

    如何获取android手机型号 版本 sdk详细信息 首先 看看 android sdk 页面上的这些 Build 类 http developer android com reference android os Build html h
  • Android:无法发送http post

    我一直在绞尽脑汁试图弄清楚如何在 Android 中发送 post 方法 这就是我的代码的样子 public class HomeActivity extends Activity implements OnClickListener pr
  • Android 中的 Google Places API - 适用于个人用户的 API_KEY

    我已经浏览了与在 Android 应用程序中使用 Places API 相关的 Android 文档和其他博客 到处都建议使用 API KEY 来调用 REST 服务 API KEY 在整个项目 应用程序中都是相同的 每天的请求数限制为 1
  • 在状态栏下方显示DialogFragment内容

    我试图显示高度和宽度均具有 match parent 的 DialogFragment 但碰巧在顶部 DialogFragment 显示在 StatusBar 下方 DialogFragment 正在应用一些默认值来填充底部 右侧 左侧和顶
  • putFragment() - 片段 x 当前不在 FragmentManager 中

    上面的标题被问了很多次 但答案似乎与FragmentStatePagerAdapter这与我的问题无关 我正在使用该方法putFragment Bundle String Fragment 直接地 The 安卓文档 http develop
  • 异步更新后更新Android Listview

    我正在将 HTTP 调用从同步调用转换为异步调用 由于连接在后台运行 因此当我最初设置列表适配器时 数据不存在 如何在 HTTP 调用后更新列表适配器 我尝试了一些方法 例如在数据发送回之前不设置适配器并再次设置适配器 但没有任何效果 这是

随机推荐