Android开发 多媒体提取器MediaExtractor详解_将一个视频文件分离视频与音频
前言
此篇博客讲解MediaExtractor将一个视频文件分离视频与音频,如果你对MediaExtractor还没有一个笼统的概念建议先了解我的另一篇入门博客:https://www.cnblogs.com/guanxinjing/p/11378133.html
直接上代码
已经大量注释了就不另外切分讲解了... 另外注意,实际项目里请将这些放到线程中操作.
private void separate() {
mFile = new File(getExternalCacheDir(), "demo.mp4");
if (!mFile.exists()) {
Log.e(TAG, "mp4文件不存在");
return;
}
MediaExtractor extractor = new MediaExtractor();//实例一个MediaExtractor
try {
extractor.setDataSource(mFile.getAbsolutePath());//设置添加MP4文件路径
} catch (IOException e) {
e.printStackTrace();
}
int trackCount = extractor.getTrackCount();//获得通道数量
int videoTrackIndex = 0;//视频轨道索引
MediaFormat videoMediaFormat = null;//视频格式
int audioTrackIndex = 0;//音频轨道索引
MediaFormat audioMediaFormat = null;
/**
* 查找需要的视频轨道与音频轨道index
*/
for (int i = 0; i < trackCount; i++) { //遍历所以轨道
MediaFormat itemMediaFormat = extractor.getTrackFormat(i);
String itemMime = itemMediaFormat.getString(MediaFormat.KEY_MIME);
if (itemMime.startsWith("video")) { //获取视频轨道位置
videoTrackIndex = i;
videoMediaFormat = itemMediaFormat;
continue;
}
if (itemMime.startsWith("audio")) { //获取音频轨道位置
audioTrackIndex = i;
audioMediaFormat = itemMediaFormat;
continue;
}
}
File videoFile = new File(getExternalCacheDir(), "video.h264");
File audioFile = new File(getExternalCacheDir(), "audio.acc");
if (videoFile.exists()) {
videoFile.delete();
}
if (audioFile.exists()) {
audioFile.delete();
}
try {
FileOutputStream videoOutputStream = new FileOutputStream(videoFile);
FileOutputStream audioOutputStream = new FileOutputStream(audioFile);
/**
* 分离视频
*/
int maxVideoBufferCount = videoMediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);//获取视频的输出缓存的最大大小
ByteBuffer videoByteBuffer = ByteBuffer.allocate(maxVideoBufferCount);
extractor.selectTrack(videoTrackIndex);//选择到视频轨道
int len = 0;
while ((len = extractor.readSampleData(videoByteBuffer, 0)) != -1) {
byte[] bytes = new byte[len];
videoByteBuffer.get(bytes);//获取字节
videoOutputStream.write(bytes);//写入字节
videoByteBuffer.clear();
extractor.advance();//预先加载后面的数据
}
videoOutputStream.flush();
videoOutputStream.close();
extractor.unselectTrack(videoTrackIndex);//取消选择视频轨道
/**
* 分离音频
*/
int maxAudioBufferCount = audioMediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);//获取音频的输出缓存的最大大小
ByteBuffer audioByteBuffer = ByteBuffer.allocate(maxAudioBufferCount);
extractor.selectTrack(audioTrackIndex);//选择音频轨道
len = 0;
while ((len = extractor.readSampleData(audioByteBuffer, 0)) != -1) {
byte[] bytes = new byte[len];
audioByteBuffer.get(bytes);
/**
* 添加adts头
*/
byte[] adtsData = new byte[len + 7];
addADTStoPacket(adtsData, len+7);
System.arraycopy(bytes, 0, adtsData, 7, len);
audioOutputStream.write(bytes);
audioByteBuffer.clear();
extractor.advance();
}
audioOutputStream.flush();
audioOutputStream.close();
} catch (FileNotFoundException e) {
Log.e(TAG, "separate: 错误原因=" + e);
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
extractor.release();//释放资源
}
private static void addADTStoPacket(byte[] packet, int packetLen) {
/*
标识使用AAC级别 当前选择的是LC
一共有1: AAC Main 2:AAC LC (Low Complexity) 3:AAC SSR (Scalable Sample Rate) 4:AAC LTP (Long Term Prediction)
*/
int profile = 2;
int frequencyIndex = 0x04; //设置采样率
int channelConfiguration = 2; //设置频道,其实就是声道
// fill in ADTS data
packet[0] = (byte) 0xFF;
packet[1] = (byte) 0xF9;
packet[2] = (byte) (((profile - 1) << 6) + (frequencyIndex << 2) + (channelConfiguration >> 2));
packet[3] = (byte) (((channelConfiguration & 3) << 6) + (packetLen >> 11));
packet[4] = (byte) ((packetLen & 0x7FF) >> 3);
packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);
packet[6] = (byte) 0xFC;
}
注意! 在分离音频后并没有adts头. 所以这需要我们手动导入. 如果不太了解什么是adts可以参考https://www.cnblogs.com/guanxinjing/p/11438181.html
结果:

一些你可能会碰到的坑
坑1.
在上面的代码中,有下面2行代码会坑...ByteBuffer.allocate();的值,不是跟创建byte[] 一样随便输一个固定值.... 因为视频或者音频流的一帧字节大小是强制输出固定大小的.. 如果你ByteBuffer.allocate(1*1024);写成这样肯定会报错,因为极有可能在执行extractor.readSampleData()时放不下输出的一帧流字节而报错... 所以最正确的方式是使用videoMediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);获取当前输出一帧的流的字节大小.. 这样既不会因为申请过量内存而浪费也不会因为申请内存太小而报错
int maxVideoBufferCount = videoMediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);//获取视频的输出缓存的最大大小
ByteBuffer videoByteBuffer = ByteBuffer.allocate(maxVideoBufferCount);
坑2.
请无视extractor.readSampleData(videoByteBuffer, 0)这个方法里的第二个参数,因为只需要输入0,根本不需要设置.注释里也没有这个参数的说明,真实情况是使用extractor.advance();方法来跳到下一帧的数据.. 简直莫名其妙....
end
Android开发 多媒体提取器MediaExtractor详解_将一个视频文件分离视频与音频的更多相关文章
- Android开发 多媒体提取器MediaExtractor详解_入门篇
前言 MediaExtractor字面意思是多媒体提取器,它在Android的音视频开发里主要负责提取视频或者音频中的信息和数据流(例如将视频文件,剥离出音频与视频).本章博客将讲解一些入门简单的东西 ...
- Android开发:文本控件详解——TextView(一)基本属性
一.简单实例: 新建的Android项目初始自带的Hello World!其实就是一个TextView. 在activity_main.xml中可以新建TextView,从左侧组件里拖拽到右侧预览界面 ...
- Android开发:文本控件详解——TextView(二)文字跑马灯效果实现
一.需要使用的属性: 1.android:ellipsize 作用:若文字过长,控制该控件如何显示. 对于同样的文字“Android开发:文本控件详解——TextView(二)文字跑马灯效果实现”,不 ...
- 『动善时』JMeter基础 — 35、JMeter接口关联【JSON提取器】详解
目录 1.JSON提取器介绍 2.JSON提取器界面详解 3.JSON提取器的使用 (1)测试计划内包含的元件 (2)HTTP Cookie管理器内容 (3)用户登陆请求界面内容 (4)JSON提取器 ...
- Android开发数据存储之ContentProvider详解
转载:十二.ContentProvider和Uri详解 一.使用ContentProvider(内容提供者)共享数据 ContentProvider在android中的作用是对外共享数据,也就是说你可 ...
- 转: Android开发中的MVP架构详解(附加链接比较不错)
转: http://www.codeceo.com/article/android-mvp-artch.html 最近越来越多的人开始谈论架构.我周围的同事和工程师也是如此.尽管我还不是特别深入理解M ...
- Android开发5大布局方式详解
Android中常用的5大布局方式有以下几种: 线性布局(LinearLayout):按照垂直或者水平方向布局的组件. 帧布局(FrameLayout):组件从屏幕左上方布局组件. 表格布局(Tabl ...
- Jmeter中正则表达式提取器使用详解
在使用Jmeter过程中,会经常使用到正则表达式提取器提取器,虽然并不直接涉及到请求的测试,但是对于数据的传递起着很大的作用,本篇博文就是主要讲解关于正则表达式及其在Jmeter的Sampler中的调 ...
- Android中measure过程、WRAP_CONTENT详解以及 xml布局文件解析流程浅析
转自:http://www.uml.org.cn/mobiledev/201211221.asp 今天,我着重讲解下如下三个内容: measure过程 WRAP_CONTENT.MATCH_PAREN ...
随机推荐
- cdh5.47 上配置flume
flume 配置文件 # Define a memory channel called ch1 on agent1agent1.channels.ch1.type = memoryagent1.cha ...
- bigger is greater
题目: Lexicographical order is often known as alphabetical order when dealing with strings. A string i ...
- 2-vim-打开和新建文件-01-打开/新建文件/打开定位到文件指定行
1.新建或打开文件 命令: vim 文件名 在终端中输入vi在后面跟上文件名即可. 如果文件已经存在,会直接打开文件. 如果文件不存在,会新建一个文件. 2.打开文件并定位到文件指定行. 命令: vi ...
- TRUNCATE - 清空一个表
SYNOPSIS TRUNCATE [ TABLE ] name DESCRIPTION 描述 TRUNCATE 快速地从一个表中删除所有行.它和无条件的 DELETE 有同样的效果,不过因为它不做表 ...
- 继续搞我的linux
小程序研发已经告一段落,还是继续我的Linux研究.上次因为捣鼓那个fastab,结果吧虚拟机搞崩溃了.好吧,这次老子来装正式机,从机房拉来了一台破烂货,联想的老式服务器,开工吧. 用UltraISO ...
- java heap space以及jvisualvm.exe 工具
最近遇到了java heap space错误. 这个问题的原因,其实还是堆溢出了. 解决这个问题 1 首先我们考虑,在代码中哪里使用了较多的对象,但是又没有及时回收. 2 我们可以通过 jvisua ...
- linux redis的启动---后台启动
1.启动redis服务: redis-server 如果想要开启后台进程: 1.找到redis.conf里边的 把no 改为yes. 2.redis-server redis.conf(这个是针对两个 ...
- 阿里巴巴持续投入,etcd 正式加入 CNCF
摘要: 2018 年 12 月 11 日,在 KubeCon + CloudNativeCon 北美峰会上,etcd 项目正式加入 CNCF. 2018 年 12 月 11 日,在 KubeCon + ...
- JavaWeb学习篇之----自定义标签&&JSTL标签库详解
今天来看一下自定义标签的内容,自定义标签是JavaWeb的一部分非常重要的核心功能,我们之前就说过,JSP规范说的很清楚,就是Jsp页面中禁止编写一行Java代码,就是最好不要有Java脚本片段,下面 ...
- 26 函数形参值回传问题——C++解决多个return的一般方法
0 引言 在使用数组和vector作为函数的参数进行参数传递并希望得到值的回传时,由于不知道怎么写数组函数形参的引用形式,一直采用vector的引用形式.但是,刚刚测试了一下,发现数组作为参数本身就是 ...