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 ...
随机推荐
- SQlite 学习资料
很有用的开源跨平台数据库,可以作为客户端的小型内存数据库使用,据说它有N多用户(Nokia's Symbian,Mozilla,Abobe,Google,阿里旺旺,飞信,Chrome,FireFo ...
- ThinkPHP5实用的数据库操作方法
1.update方法总结 /** * 设置记录的某个字段值 * 支持使用数据库字段和方法 * @access public * @param string|array $field 字段名 * @pa ...
- SpringBoot Controller 中使用多个@RequestBody的正确姿势
最近遇到Controller中需要多个@RequestBody的情况,但是发现并不支持这种写法, 这样导致 1.单个字符串等包装类型都要写一个对象才可以用@RequestBody接收: 2.多个对象需 ...
- 内网渗透_linux_socks代理_reGeorg+proxychains
过程演示 测试前提是目标服务器已经getshell. 1.将 reGeorgSocksProxy 中的 tunnel.jsp 文件放置到目标服务器web目录,查看能否正常访问(如图). 2.设置kal ...
- createBottomTabNavigator: 怎么在切换tab的时候让页面重新渲染
1.import withNavigationFocus from react-navigation to your class . 2.hen export your like this : exp ...
- 微信app支付返回-1的问题
我也是被坑就当留个纪念 前两天查了各种关于微信app支付返回-1的都是ERR_COMM 问题然后各种 验证最后还是误解 第三天去验证了一下微信开放平台发现了问题 appid 不在同一个开放平台 项目之 ...
- Servlet(Server Applet) 详解
Java编写的服务器端程序.其主要功能在于交互式地浏览和修改数据,生成动态Web内容. Servlet的工作模式 客户端发送请求至服务器 服务器启动并调用Servlet,Servlet根据客户端请求生 ...
- [原创]Delphi 文件函数:ForceDirectories() 函数和 CreateDir函数
引用单元:SysUtils function ForceDirectories(Dir: string): Boolean; //创建多级目录 父目录不必存在 (Force 有暴力.强制的 ...
- csp-s模拟测试98
csp-s模拟测试98 $T1$??不是我吹我轻松手玩20*20.$T2$装鸭好像挺可做?$T3$性质数据挺多提示很明显? $One$ $Hour$ $Later$ 这$T1$什么傻逼题真$jb$难调 ...
- I/O与NIO(异步I/O)
1.原来的I/O库与NIO最重要的区别是数据打包和传输方式的不同,原来的I/O以流的方式处理数据,而NIO以块的方式处理数据. 面向流的I/O系统一次一个字节地处理数据.一个输入流产生一个字节的数据, ...