自己实现MediaExtractor(一)

2023-11-10

1、背景

Android很坑,编解码大坑。最近遇到MediaExtractor的坑了:
坑1: 读PCM音频巨慢,因为Android的实现是一个一个sample读的
坑2: 某些手机只能读取到一路视频或音频track,如oppo Find X
个人觉得这不是个什么很难的事情,决定自己实现一下。

2、知识储备

Android上视频主要是MP4,所以目标是自己实现一个MediaExtractor完成Mp4音视频的数据提取。要完成这件事需要了解:
1、Android MediaExtractor的接口和使用方法,这个可以通过看源码和官方文档学习;
2、熟悉Mp4的封装;
下面重点介绍下2。

3、MP4封装简介

mp4是一种视频封装容器,他的所有内容都是放在box中,box又是一种嵌套结构。各种box的描述可以在《ISO/IEC 14496-12》标准文件中找到。

3.1、基本概念:

track 表示一些sample的集合,对于媒体数据来说,track表示一个视频或音频序列。
hint track 这个特殊的track并不包含媒体数据,而是包含了一些将其他数据track打包成流媒体的指示信息
sample 对于非hint track来说,video sample即为一帧视频,或一组连续视频帧,audio sample即为一段连续的压缩音频,它们统称sample。对于hint track,sample定义一个或多个流媒体包的格式。
sample table 指明sampe时序和物理布局的表。
chunk 一个track的几个sample组成的单元。

3.2、常见的MP4封装结构。

图3-1 MP4 box 树
标红注释的box(除ftyp)是需要解析获取信息的内容。
ftyp(file type box)该box应该被放在文件的最开始,指示该MP4文件应用的相关信息
mdat(media data Box)所有的实际媒体数据
moov(movie box Box)包含了文件媒体的metadata信息
tkhd (track header Box)描述track头信息

stbl(Sample Table Box)“stbl”包含了关于track中sample所有时间和位置的信息,以及sample的编解码等信息。
stsd(Sample Description Box)这里面是解码器的配置信息,包含了vps sps pps。
stts(Time To Sample Box)“stts”存储了sample的duration,描述了sample时序的映射方法,我们通过它可以找到任何时间的sample。
stss(Sync Sample Box) “stss”确定media中的关键帧。
stsc(Sample To Chunk Box)描述了sample与chunk的映射关系,查看这张表就可以找到包含指定sample的thunk,从而找到这个sample。
stsz(Sample Size Box) “stsz” 定义了每个sample的大小,包含了媒体中全部sample的数目和一张给出每个sample大小的表。
stco(Chunk Offset Box)“stco”定义了每个thunk在媒体流中的位置。

4、具体实现

了解了MP4的结构,发现我们所需要的数据确实都可以找到,现在要做的就是怎么转化塞到MediaExtractor的接口里。要完成这个目标需要完成3个任务:
1、解析上面的box数据,这个工作量有点大,可以用第3方库解析’com.googlecode.mp4parser:isoparser:1.1.21’

2、获取视频的MediaFormat信息,这个主要难点在解析hvcc box获取vps sps pps

3、获取每一帧数据和帧时间戳,是否关键帧等信息。
这一块文字描述起来非常繁琐,下面是获取帧信息关键部分流程图,如果对代码感兴趣可以前往https://github.com/liyang-hello/Mp4Extractor查看。

图4-1获取帧信息流程图
代码实现在TrackParser中

public void prepareFramesInfo() {
        if(mSTBLBoxParser != null) {
            frames.clear();
            for (int i = 1; i<mSTBLBoxParser.getChunkCount()+1; i++) {
                //create a frame
                MP4Frame frame = new MP4Frame();
                frame.setKeyFrame(mSTBLBoxParser.isKeyFrame(i));
                frame.setOffset(mSTBLBoxParser.getChunkOffset(i));
                frame.setSize((int) mSTBLBoxParser.getChunkSize(i));
                frame.setTime(getTimestamp(i-1));
                if(getFormat().getString(MediaFormat.KEY_MIME).startsWith("video")) {
                    frame.setType(IoConstants.TYPE_VIDEO);
                } else {
                    frame.setType(IoConstants.TYPE_AUDIO);
                }
                frames.add(frame);
//                LogU.d("prepareFramesInfo i="+i+" frame  "+ frame);
            }
        }
    }

5、运行测试

下面是测试代码,用Mp4Extractor提取音视频保存成一个文件,然后在电脑上播放。

public void testMp4Extractor(final String path) {
        MediaExtractor mediaExtractor = new MediaExtractor();
        try {
            mediaExtractor.setDataSource(path);
            for(int i=0; i<mediaExtractor.getTrackCount(); i++) {
                mediaExtractor.selectTrack(i);
                MediaFormat mediaFormat = mediaExtractor.getTrackFormat(i);
                LogU.d("mediaFormat "+ mediaFormat);
                ByteBuffer csd_0 = mediaFormat.getByteBuffer("csd-0");
                if(csd_0 != null) {
                    String buf = ConvertUtil.bytesToHex(csd_0.array(), csd_0.limit());
                  
                }

            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                IExtractor extractor = new Mp4Extractor();
                try {
                    extractor.setDataSource(path);

                    for (int i=0; i<extractor.getTrackCount(); i++) {
                        MediaFormat format = extractor.getTrackFormat(i);
                   
                        ByteBuffer csd_0 = format.getByteBuffer("csd-0");
                        if(csd_0 != null) {
                            String buf = ConvertUtil.bytesToHex(csd_0.array(), csd_0.limit());
                         
                        }

                        extractor.selectTrack(i);
                        extractor.seekTo(0,0);
                        ByteBuffer byteBuffer = ByteBuffer.allocate(4*1024*1024);
                        byteBuffer.position(0);
                        int readSize = 1;
                        String path = "/sdcard/Test/track_"+i;

                        out = null;
                        if(csd_0 != null) {
                            path = path+ ".265";
                            saveFile(csd_0.array(), csd_0.limit(), false, path);
                        } else {
                            path = path+".wav";
                        }

                        while (true) {
                            byteBuffer.position(0);
                            readSize = extractor.readSampleData(byteBuffer,0);
                            //LogU.d("readSize "+ readSize);
                            if(readSize > 0) {
                                saveFile(byteBuffer.array(), readSize, false, path);
                            } else {
                                break;
                            }
                            extractor.advance();
                        }

                        saveFile(byteBuffer.array(), 0, true, path);
                        //LogU.d("save "+path + " successfully");
                    }


                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            private OutputStream out = null;
            public void saveFile(byte[] byteBuffer, int size, boolean bEnd, String path){
                if(byteBuffer!=null){
                    if(out == null){
                        try {
                            out = new FileOutputStream(path);
                        } catch (FileNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                    try {
                        out.write(byteBuffer,0 ,size);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    if(bEnd){
                        try {
                            out.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }

            }
        }).start();
    }

对比发现mediaformat信息差不多;
用ffplay播放保存的视频文件可以播放。

6、总结

虽然功能实现,但是获取每一帧的速度较慢,还需要优化。

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

自己实现MediaExtractor(一) 的相关文章

  • 如何为ScrollView放置固定图像背景?

    我应该想要滚动视图滚动 而不是背景中的图像 将图像添加到滚动视图框架之前的视图层次结构的较高位置
  • 使用workmanager时Firestore脱机持久性错误

    我正在使用一个WorkManger定期从我的中检索信息Firestore当应用程序处于后台和前台时的数据库 此信息用于根据状态更新 UI 因此不同的状态会添加或删除 UI 的不同部分 第一次运行时效果很好 但是 一旦应用程序处于后台并且Wo
  • 如何重试已消耗的 Observable?

    我正在尝试重新执行失败的已定义可观察对象 一起使用 Retrofit2 和 RxJava2 我想在单击按钮时重试特定请求及其订阅和行为 那可能吗 service excecuteLoginService url tokenModel Ret
  • 在画布上绘图

    我正在编写一个 Android 应用程序 它可以在视图的 onDraw 事件上直接绘制到画布上 我正在绘制一些涉及单独绘制每个像素的东西 为此我使用类似的东西 for int x 0 x lt xMax x for int y 0 y lt
  • CollapsingToolBarLayout - 状态栏稀松布颜色不改变

    几天前我更新了我的 android studio 并开始使用 CoordinatorLayout 和 CollapsingToolbarLayout 只是尝试一些东西 工具栏稀松布颜色似乎覆盖了状态栏初始颜色和状态栏稀松布颜色 从 xml
  • 在 java 类和 android 活动之间传输时音频不清晰

    我有一个android活动 它连接到一个java类并以套接字的形式向它发送数据包 该类接收声音数据包并将它们扔到 PC 扬声器 该代码运行良好 但在 PC 扬声器中播放声音时会出现持续的抖动 中断 安卓活动 public class Sen
  • 带有 EditText 和 Spinner 的对话框

    我有一个按钮 单击后会弹出一个对话框 我希望对话框有一个EditText and a Spinner对话框内 我不知道如何设置它的视图 我有一个代码AlertDialog它有效 只是EditText and Spinner我需要将其放入其中
  • 如何使用 Cordova 获取当前安装的应用程序的版本?

    我已经找到了应用程序可用性插件 https github com ohh2ahh AppAvailability它主要检查用户是否在其设备上安装了某个应用程序 是否有可能获得应用程序的当前版本 开发者名称 重要 以及所有可能的信息 一般来说
  • 是否有 ADB 命令来检查媒体是否正在播放

    我想使用 ADB 命令检查根植于终端的外部设备中是否正在播放音频 视频 我无法找到任何 ADB 命令 如果有 我尝试过 adb shell dumpsys media player 我想要一个命令来指定视频是否正在运行 您可以使用以下命令查
  • 获取当前 android.intent.category.LAUNCHER 活动的实例

    我创建了一个库项目 并在多个应用程序之间共享 我实现了一个简单的会话过期功能 该功能将在一段时间后将用户踢回到登录屏幕 登录屏幕活动是我的主要活动 因此在清单中它看起来像这样
  • 如何在PreferenceActivity中添加工具栏

    我已经使用首选项创建了应用程序设置 但我注意到 我的 PreferenceActivity 中没有工具栏 如何将工具栏添加到我的 PreferenceActivity 中 My code 我的 pref xml
  • Ubuntu 16.04 - Genymotion:找不到 /dev/hw_random

    I install Genymotion on the Ubuntu 16 04 64Bit I created a virtual emulator for Android 6 0 then I run this emulator but
  • 如何使用InputConnectionWrapper?

    我有一个EditText 现在我想获取用户对此所做的所有更改EditText并在手动将它们插入之前使用它们EditText 我不希望用户直接更改中的文本EditText 这只能由我的代码完成 例如通过使用replace or setText
  • Android Studio - Windows 7 上的 Android SDK 问题

    我对 Google i o 2013 上发布的最新开发工具 Android Studio 有疑问 我已经成功安装了该程序并且能够正常启动 我可以导入现有项目并对其进行编辑 但是 当我尝试单击 SDK 管理器图标或 AVD 管理器图标时 或者
  • Android向menuItem添加子菜单,addSubMenu()在哪里?

    我想根据我的参数以编程方式将 OptionsMenu 内的子菜单添加到 menuItem 中 我检查了android sdk中的 MenuItem 没有addSubMenu 方法 尽管你可以找到 hasSubMenu 和 getSubMen
  • 如何根据 gradle 风格设置变量

    我想传递一个变量test我为每种风格设置了不同的值作为 NDK 的定义 但出于某种原因 他总是忽略了最后味道的价值 这是 build gradle apply plugin com android library def test andr
  • Android 套接字和 asynctask

    我即将开始制作一个应该充当 tcp 聊天客户端的应用程序 我一直在阅读和阅读 我得出的结论是最好 如果不需要 将我的套接字和异步任务中的阅读器 问题是我不确定从哪里开始 因为我是 Android 新手 这至少对我来说是一项艰巨的任务 但据我
  • 将 Intent 包装在 LabeledIntent 中以用于显示目的

    要求 我的应用程序中有一个 共享 按钮 我需要通过 Facebook 分享 我需要选择是否安装原生 Facebook 应用程序 我们的决定是 如果未安装该应用程序 则将用户发送到 facebook com 进行分享 当前状态 我可以检测何时
  • android sdk 的位置尚未在 Windows 操作系统的首选项中设置

    在 Eclipse 上 我转到 windows gt Android SDK 和 AVD Manager 然后弹出此消息 Android sdk 的位置尚未在首选项中设置 进入首选项 在侧边栏找到 Android 然后会出现一个 SDK 位
  • 按日期对 RecyclerView 进行排序

    我正在尝试按日期对 RecyclerView 进行排序 但我尝试了太多的事情 我不知道现在该尝试什么 问题就出在这条线上适配器 notifyDataSetChanged 因为如果我不放 不会显示错误 但也不会更新 recyclerview

随机推荐

  • 刷脸支付完全融入了我们的日常生活

    现金支付的假币 丢失等问题层出不穷 随着现金交易出现的不便 银行卡的出现成为人们支付方式的一大转变 智能手机的发展和网络科技的进步催生了网络支付方式 AI智能技术的不断发展又让人们迎来了一场刷脸支付的新革命 刷脸支付是指用户在购物后的支付认
  • rabbitmq 连接报错 An unexpected connection driver error occured(亲测)

    在服务器上安装了一个RabbitMq 并新创建了一个用户授予了管理员角色 登录控制台查看一切正常 兴高采烈启动项目进行连接 结果一盆冷水下来 报如下错误 o s a r l SimpleMessageListenerContainer Fa
  • git中format-patch和chery-pick的区别和联系

    chery pick 把其他分支的一次或多次commit 在当前分支上重演 典型的使用场景 其他分支有很多提交 但是你只对其中的一部分感兴趣 这时候可以使用chery pick 只挑选其他分支感兴趣的commit 合并到自己的分支中 for
  • mybatis if-else(写法)

  • Debian GNU/Linux 中以源码方式安装Odoo 14(社区版)

    Odoo是一种流行的开源商务应用程序套件 可帮助公司管理和运营其业务 也可用于在线教学 它包括广泛的应用程序 Debian GNU Linux 是社区版服务器的代表 本文将介绍如何在Debian GNU Linux中以源码方式安装和部署Od
  • 垃圾回收之CMS GC

    一 六个阶段 阶段 1 Initial Mark 初始标记 这个阶段伴随着 STW 暂停 初始标记的目标是标记所有的 根对象 包括根对象直接引用的对象 以及被年轻代中所 有存活对象所引用的对象 老年代单独回收 阶段 2 Concurrent
  • 剑指 Offer 57. 和为s的两个数字(java+python)

    输入一个递增排序的数组和一个数字s 在数组中查找两个数 使得它们的和正好是s 如果有多对数字的和等于s 则输出任意一对即可 示例 1 输入 nums 2 7 11 15 target 9 输出 2 7 或者 7 2 示例 2 输入 nums
  • MyBatis快速入门

    Mybatis概述 Mybatis概念 MyBatis 是一款优秀的持久层框架 用于简化 JDBC 开发 MyBatis 本是 Apache 的一个开源项目iBatis 2010年这个项目由apache software foundatio
  • show process cpu

    Router show proc cpu CPU utilization for five seconds 63 50 one minute 58 five minutes 58 PID Runtime ms Invoked uSecs 5
  • NVM在windows下切换node版本

    如果您很忙或者很急 请直接阅读 三 步骤 一 问题背景 生活里偶尔穿梭在大街小巷中 工作中时常并行于多项目任务里 当多个项目并行时 由于创建的时间或人为选择等因素 各个项目里有着差异的node版本 这样我们在不同的项目里需要切换不同版本的N
  • Linux软链接与硬链接区别

    一 背景 链接 是一种在共享文件和访问它的用户的若干目录项之间建立联系的一种方法 Linux中包括两种链接 硬链接 Hard Link 和软链接 Soft Link 软链接又称为符号链接 Symbolic link 要了解链接 我们首先得了
  • vscode 问题解决:“检测到 #include 错误,请更新 includePath”

    当我们在使用vscode进行编辑代码时 往往会遇到以下警告 这表明 在我们的代码中 无法找到对应的头文件 但问题在于 阅读和编辑代码 需要保证代码的统一性 所以我们需要知道这个头文件到底在哪 我们也需要让vscode通过点击该头文件名就可以
  • EI、Scopus双检索

    会议简介 Brief Introduction 2023年第四届自动化 机械与设计工程国际会议 SAMDE 2023 会议时间 2023年12月8 10日 召开地点 中国 南京 大会官网 www samde org 机械设计制造及其自动化学
  • python第三方库集锦

    环境管理管理 Python 版本和环境的工具 p 非常简单的交互式 python 版本管理工具 pyenv 简单的 Python 版本管理工具 Vex 可以在虚拟环境中执行命令 virtualenv 创建独立 Python 环境的工具 vi
  • Qt : d指针和q指针?

    目录 一 什么是d指针和q指针 1 d指针 2 q 指针 二 d指针和q指针的作用 三 d指针和q指针的使用 demo 一 什么是d指针和q指针 在Qt的源码中 我们看到大量的Q D 和Q P 宏的调用 查看代码时在一定程度上增加了复杂度
  • 阿里云部署 ChatGLM2-6B 与 langchain+ChatGLM

    1 ChatGLM2 6B 部署 更新系统 apt get update 安装git apt get install git lfs git init git lfs install 克隆 ChatGLM2 6B 源码 git clone
  • vite中无法使用require和@无法使用的问题

    1 安装插件 yarn 安装 yarn add D vite plugin require transform npm 安装 npm i vite plugin require transform S 2 在vite中配置一下即可 impo
  • 解决python发送https请求,出现证书错误,及报错提示

    详细报错信息如下 Error Traceback most recent call last File E WebWafUi venv lib site packages urllib3 connectionpool py line 603
  • 2.4G天线匹配设计,不谈原理和规范,直接怼PCB设计

    关注嘉友创科技公众号 各位大虾 说个天线设计的过程 此过程实测性能是官方的参数的80 不需要任何仪器测试的情况下 适合穷逼 买不起仪器 1 确定厂家 确定板厚 2 依据板厚 找生成PCB厂家确定板层分布 厂家不同 很多参数不同 3 确定阻抗
  • 自己实现MediaExtractor(一)

    1 背景 Android很坑 编解码大坑 最近遇到MediaExtractor的坑了 坑1 读PCM音频巨慢 因为Android的实现是一个一个sample读的 坑2 某些手机只能读取到一路视频或音频track 如oppo Find X 个