前言

  此篇博客讲解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详解_将一个视频文件分离视频与音频的更多相关文章

  1. Android开发 多媒体提取器MediaExtractor详解_入门篇

    前言 MediaExtractor字面意思是多媒体提取器,它在Android的音视频开发里主要负责提取视频或者音频中的信息和数据流(例如将视频文件,剥离出音频与视频).本章博客将讲解一些入门简单的东西 ...

  2. Android开发:文本控件详解——TextView(一)基本属性

    一.简单实例: 新建的Android项目初始自带的Hello World!其实就是一个TextView. 在activity_main.xml中可以新建TextView,从左侧组件里拖拽到右侧预览界面 ...

  3. Android开发:文本控件详解——TextView(二)文字跑马灯效果实现

    一.需要使用的属性: 1.android:ellipsize 作用:若文字过长,控制该控件如何显示. 对于同样的文字“Android开发:文本控件详解——TextView(二)文字跑马灯效果实现”,不 ...

  4. 『动善时』JMeter基础 — 35、JMeter接口关联【JSON提取器】详解

    目录 1.JSON提取器介绍 2.JSON提取器界面详解 3.JSON提取器的使用 (1)测试计划内包含的元件 (2)HTTP Cookie管理器内容 (3)用户登陆请求界面内容 (4)JSON提取器 ...

  5. Android开发数据存储之ContentProvider详解

    转载:十二.ContentProvider和Uri详解 一.使用ContentProvider(内容提供者)共享数据 ContentProvider在android中的作用是对外共享数据,也就是说你可 ...

  6. 转: Android开发中的MVP架构详解(附加链接比较不错)

    转: http://www.codeceo.com/article/android-mvp-artch.html 最近越来越多的人开始谈论架构.我周围的同事和工程师也是如此.尽管我还不是特别深入理解M ...

  7. Android开发5大布局方式详解

    Android中常用的5大布局方式有以下几种: 线性布局(LinearLayout):按照垂直或者水平方向布局的组件. 帧布局(FrameLayout):组件从屏幕左上方布局组件. 表格布局(Tabl ...

  8. Jmeter中正则表达式提取器使用详解

    在使用Jmeter过程中,会经常使用到正则表达式提取器提取器,虽然并不直接涉及到请求的测试,但是对于数据的传递起着很大的作用,本篇博文就是主要讲解关于正则表达式及其在Jmeter的Sampler中的调 ...

  9. Android中measure过程、WRAP_CONTENT详解以及 xml布局文件解析流程浅析

    转自:http://www.uml.org.cn/mobiledev/201211221.asp 今天,我着重讲解下如下三个内容: measure过程 WRAP_CONTENT.MATCH_PAREN ...

随机推荐

  1. vue中this.$refs可以拿到,但是里面的属性undefind的问题

    1.和vue的生命周期有关,必须要在从mounted开始拿,才能拿得到里面的Dom元素 2.想在element ui 对话框打开后取dom时,应该使用$nextTick,而不是直接使用this.$re ...

  2. 最基础知识 sql之left join、right join、inner join的区别

    sql之left join.right join.inner join的区别 left join(左联接) 返回包括左表中的所有记录和右表中联结字段相等的记录 right join(右联接) 返回包括 ...

  3. vue+element 构建的后台管理系统项目(1)新建项目

    1.运行 vue init webpack demo   这里的demo是你项目的名字 2.npm run dev 查看项目启动效果 3.安装Element cd 项目 cmd  运行 npm i e ...

  4. python编程学习day04

    1.函数名是变量名 “=”是内存指向,等号赋值操作,内存指向操作 变量——可赋值,可作为列表元素 函数名可以作为返回值返回 函数名可作为参数传递 2.闭包 内层函数使用了外层函数的变量 作用:可以让一 ...

  5. Java——package和import关键字

    1.8 package和import关键字 1.8.1 package 包其实就是目录,特别是项目比较大,java 文件特别多的情况下,我们应该分目录管理,在java 中称为分包管理,包名称通常采用小 ...

  6. CSS案例2(一个简单的新闻网页)

     知识点: 1.一般网页不用纯黑,用淡灰色  3c3c3c 2.text-align: center;  /*  文字水平居中 */ 3.font-weight: normal;  /* 清除加粗效果 ...

  7. Dart编程运算符

    表达式是一种特殊类型的语句,它计算为一个值.每个表达都由 操作数 - 表示数据 运算符 - 定义如何处理操作数以生成值. 考虑以下表达式 2 + 3.在该表达式中,2和3是操作数,符号+(加号)是 运 ...

  8. 阿里云成为Hyperledger超级账本全球会员,发力区块链生态建设

    摘要: 阿里云将会与Hyperledger社区共同推进全球区块链技术和商业生态在多行业领域的建设. 2018年12月12日在瑞士巴塞尔的2018Hyperledger全球峰会上,阿里云正式加入Hype ...

  9. BZOJ 4517: [Sdoi2016]排列计数(组合数学)

    题面 Description 求有多少种长度为 n 的序列 A,满足以下条件: 1 ~ n 这 n 个数在序列中各出现了一次 若第 i 个数 A[i] 的值为 i,则称 i 是稳定的.序列恰好有 m ...

  10. NX二次开发-UFUN工程图插入PNG图片UF_DRF_create_image_from_file

    #include <uf.h> #include <uf_drf.h> UF_initialize(); //插入PNG char* file_name = "D:\ ...