前言

  此篇博客讲解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. hdu6331 /// Floyd+分块DP

    题目大意: 给定单向图的n m 为点数和单向边数 接下来m行给定 u v w 为边的起点终点和长度 给定q 为询问个数 接下来q行给定 x y k 求从x到y至少经过k条边的最短路长度 https:/ ...

  2. 欧拉筛 线性筛 素数+莫比乌斯的mu[]

    https://blog.csdn.net/qq_39763472/article/details/82428602 模板来自https://blog.csdn.net/Avalon_cc/artic ...

  3. 案例-html5新标签-input和video

    <form action=""> <fieldset> <!--fieldset标签:可将表单内的元素分组 ,常与legend搭配使用--> & ...

  4. 结对编程项目报告--四则运算CORE

    <!doctype html> sw_lab2.mdhtml {overflow-x: initial !important;}#write, body { height: auto; } ...

  5. ssh隧道实现端口转发

    ssh隧道实现端口转发 本地转发 # 本地转发 ssh -g -f -N -L : root@ # -L 本地端口转发,转发172.16.1.1主机可以访问的资源,这里为转发172.16.1.2的80 ...

  6. shell command to replace UltraEdit

    cat abc.txt |hexdump -C cat abc.txt |hexdump -C

  7. js 404页面跳转

    非原创 <script type="text/javascript"> var num = 5; function redirect() { num--; docume ...

  8. 脚本启动SpringBoot(jar)

    #!/bin/sh RESOURCE_NAME=springbsit-api.jar tpid=`ps -ef|grep $RESOURCE_NAME|grep -v grep|grep -v kil ...

  9. CSP-S2019旅游记

    CSP-S2019 你问我为什么写旅游记? 因为好像除了旅游我今年啥都没干 Day0 校内模拟一直被吊锤,考前几场几乎要爆零 这提莫就不是什么好兆头 在家二刷水淹东京完回学校,带了一大堆家当上车 去广 ...

  10. C# 与 C/C++ 网络传输字符串解决方案

    { 不管你的数据加没加密,只要有中文,请转16进制后再处理,把16进制再转为GB2312的byte再发送, 接收的话同样 c++ 发送时转16进制再发送,c#16进制转字符串后再转GB2312就可以了 ...