前言

  此篇博客讲解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. ImsConference.java中会议成员更新处理详解

    public class ConferenceParticipant implements Parcelable { //自定义数据结构 private static final String ANO ...

  2. 2-vim-打开和新建文件-01-打开/新建文件/打开定位到文件指定行

    1.新建或打开文件 命令: vim 文件名 在终端中输入vi在后面跟上文件名即可. 如果文件已经存在,会直接打开文件. 如果文件不存在,会新建一个文件. 2.打开文件并定位到文件指定行. 命令: vi ...

  3. 无法CREATE UNIQUE INDEX;找到重复的关键字

  4. linux指令【参考鸟哥的Linux私房菜】

    date指令  显示日期 cal指令 显示日历 bc  计算器  scale+number  显示几位小数  quit退出bc tab键  命令提示  入输入ca,按下tab键,会将所有ca的指令全部 ...

  5. 普通浏览器实现点击打开微信app

    给予点击事件,然后调用以下方法即可(我这用的是jq的点击): $(function() { Cz.Alert().success({text: '请返回公众号查看充值结果'}); $(".a ...

  6. leetcood学习笔记-113-路径总和 II

    题目描述: 参考后的提交: class Solution(object): def pathSum(self, root, sum): """ :type root: T ...

  7. Linkedlist 详解

    基本介绍 Linkedlist基于链表的动态数组(双向链表): 可以被当作堆栈(后进先出).队列(先进先出)或双端队列进行操作. 数据添加删除效率高,只需要改变指针指向即可,但是访问数据的平均效率低, ...

  8. JS分支结构与循环结构

    1.分支结构 ①if语句 语法结构 if (/* 条件表达式 */) { // 执行语句 } ​ if (/* 条件表达式 */){ // 成立执行语句 } else { // 否则执行语句 } ​ ...

  9. NX二次开发-UFUN获取圆柱的参数UF_MODL_ask_cylinder_parms

    NX11+VS2013 #include <uf.h> #include <uf_modl.h> #include <uf_ui.h> UF_initialize( ...

  10. NX二次开发-UF_MODL_ask_distance_tolerance获取建模的长度公差

    NX9+VS2012 #include <uf.h> #include <uf_modl.h> #include <uf_ui.h> UF_initialize() ...