一个音视频文件是由音频和视频组成的,我们可以通过MediaExtractor、MediaMuxer把音频或视频给单独抽取出来,抽取出来的音频和视频能单独播放;
MediaExtractor从api16开始添加,可用于分离视频文件的音轨和视频轨道,如果你只想要视频,那么用selectTrack方法选中视频轨道,然后用readSampleData读出数据,这样你就得到了一个没有声音的视频,想得到音频也可以用同样的方法。
MediaExtractor构造函数:
MediaExtractor()
设置数据源:
setDataSource(AssetFileDescriptor afd)
setDataSource(Context context, Uri uri, Map
setDataSource(FileDescriptor fd)
setDataSource(MediaDataSource dataSource)
setDataSource(FileDescriptor fd, long offset, long length)
void setDataSource(String path)//既可以是文件路径,也可以是文件url网址。
void setDataSource(String path, Map
获取源文件轨道数(包括视频和音频)
getTrackCount()
获取某个轨道的MediaFormat:
MediaFormat getTrackFormat(int index),MediaFormat包含该轨道的很多配置信息。
选定轨道:
selectTrack(int index):选定特定的轨道,会影响 readSampleData(ByteBuffer, int), getSampleTrackIndex() and getSampleTime()的输出,这三个函数输出的是选定轨道的信息。
读取信息:
readSampleData(ByteBuffer byteBuf, int offset):读取数据到bytebuffer中,从offset偏移开始。选定轨道之后可以读取该轨道的数据。
获取下一帧数据:
advance()获取下一帧数据
释放资源:
release()释放资源。
MediaFormat封装了Media data的描述信息,通过描述信息可以分辨Meida data 是一个音频还是视频。描述信息是一个键值对,可以通过MediaFormat的getXX函数获取。
MediaFormat 可以通过MediaExtractor 的getTrackFormat(Index) 获取,也可以通过构造函数自己构造。
MediaFormat构造函数:
MediaFormat()
createAudioFormat(String mime, int sampleRate, int channelCount)
创建一个音频的MediaFormat
参数说明:
Mime文件类型:
常见的MIME类型多媒体格式如下(以audio开头的是音频,以video开头的是视频):
MediaFormat中mime有对应的常量,MIMETYPE_AUDIO_AAC对应audio/mp4a-latm,MIMETYPE_VIDEO_AVC对应 "video/avc"等等,其他对应参考MediaFormat内常量。
至于这些类型对应什么类型的文件这里举几个例子:mp3为audio/mpeg;aac为audio/mp4a-latm;mp4为video/mp4v-es 。
createVideoFormat(String mime, int width, int height)
创建一个视频的MediaFormat,mime含义和上面相同,width,height含义为视频宽高。
MediaExtractor的getTrackFormat也可以获取MediaFormat。
通过key值获取信息的getXXX函数:
getFloat(String name)
getInteger(String name)
getLong(String name)
getString(String name)
利用MediaFormat的key常量获取值。
常见的key值包括:
KEY_MIME,KEY_CHANNEL_COUNT,KEY_SAMPLE_RATE,KEY_DURATION,KEY_WIDTH,KEY_HEIGHT等。
MediaExtractor基本用法(来自Developers):
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(...);
int numTracks = extractor.getTrackCount();
for (int i = 0; i < numTracks; ++i) {
MediaFormat format = extractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if (weAreInterestedInThisTrack) {
extractor.selectTrack(i);
}
}
ByteBuffer inputBuffer = ByteBuffer.allocate(...)
while (extractor.readSampleData(inputBuffer, ...) >= 0) {
int trackIndex = extractor.getSampleTrackIndex();
long presentationTimeUs = extractor.getSampleTime();
...
extractor.advance();
}
extractor.release();
extractor = null;
从mp4文件中提取音频和视频轨道的数据,得到aac音频数据和.h264视频数据。
代码示例:为测试代码,只展示使用过程,要使用需要优化
//提取的文件为什么是aac后面会说明
private String pcmPath = Environment.getExternalStorageDirectory()
.getPath() + "/demo/test.aac";
//为什么是.h264 后面会说明
private String mp4Path = Environment.getExternalStorageDirectory()
.getPath() + "/demo/test.h264";
private String dirPath = Environment.getExternalStorageDirectory()
.getPath() + "/demo";
private MediaExtractor mediaExtractor;
private void initMediaDecode() {
String srcPath = Environment.getExternalStorageDirectory()
.getPath() + "/video/video.mp4";
File file = new File(dirPath);
if (!file.exists()){
file.mkdir();
}
File file1 = new File(pcmPath);
File file2 = new File(mp4Path);
try {
if (file1.exists()){
file1.delete();
}
file1.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (file2.exists()){
file2.delete();
}
file2.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
try {
mediaExtractor = new MediaExtractor();//此类可分离视频文件的音轨和视频轨道
mediaExtractor.setDataSource(srcPath);//媒体文件的位置
System.out.println("==========getTrackCount()===================" + mediaExtractor.getTrackCount());
for (int i = 0; i < mediaExtractor.getTrackCount(); i++) {//遍历媒体轨道,包括视频和音频轨道
MediaFormat format = mediaExtractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if (mime.startsWith("audio")) {//获取音频轨道
mediaExtractor.selectTrack(i);//选择此音频轨道
System.out.println("====audio=====KEY_MIME========="+format.getString(MediaFormat.KEY_MIME)); System.out.println("====audio=====KEY_CHANNEL_COUNT======="+format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)+""); System.out.println("====audio=====KEY_SAMPLE_RATE==========="+format.getInteger(MediaFormat.KEY_SAMPLE_RATE)+"");
System.out.println("====audio=====KEY_DURATION==========="+format.getLong(MediaFormat.KEY_DURATION)+"");
System.out.println("====audio=====getSampleFlags==========="+mediaExtractor.getSampleFlags()+"");
System.out.println("====audio=====getSampleTime==========="+mediaExtractor.getSampleTime()+"");
// System.out.println("====audio=====getSampleSize==========="+mediaExtractor.getSampleSize()+"");api28
System.out.println("====audio=====getSampleTrackIndex==========="+ mediaExtractor.getSampleTrackIndex()+"");
try {
ByteBuffer inputBuffer = ByteBuffer.allocate(100 * 1024);
FileOutputStream fe=new FileOutputStream(file1,true);
while ( true) {
int readSampleCount = mediaExtractor.readSampleData(inputBuffer, 0);
if (readSampleCount < 0) {
break;
}
byte[] buffer = new byte[readSampleCount];
inputBuffer.get(buffer);
fe.write(buffer);
inputBuffer.clear();
mediaExtractor.advance();
}
fe.flush();
fe.close();
} catch (IOException e) {
e.printStackTrace();
}finally {
}
}
if (mime.startsWith("video")){
mediaExtractor.selectTrack(i);//选择此视频轨道
System.out.println("====video=====KEY_MIME==========="+format.getString(MediaFormat.KEY_MIME)); System.out.println("====video=====KEY_DURATION==========="+format.getLong(MediaFormat.KEY_DURATION)+"");
System.out.println("====video=====KEY_WIDTH==========="+format.getInteger(MediaFormat.KEY_WIDTH)+""); System.out.println("====video=====KEY_HEIGHT==========="+format.getInteger(MediaFormat.KEY_HEIGHT)+"");
System.out.println("====video=====getSampleFlags==========="+mediaExtractor.getSampleFlags()+"");
System.out.println("====video=====getSampleTime==========="+mediaExtractor.getSampleTime()+"");
// System.out.println("====video=====getSampleSize==========="+mediaExtractor.getSampleSize()+"");api28
System.out.println("====video=====getSampleTrackIndex==========="+ mediaExtractor.getSampleTrackIndex()+"");
try {
ByteBuffer inputBuffer = ByteBuffer.allocate(100 * 1024);
FileOutputStream fe=new FileOutputStream(file2,true);
while ( true) {
int readSampleCount = mediaExtractor.readSampleData(inputBuffer, 0);
if (readSampleCount < 0) {
break;
}
byte[] buffer = new byte[readSampleCount];
inputBuffer.get(buffer);
fe.write(buffer);
inputBuffer.clear();
mediaExtractor.advance();
}
fe.flush();
fe.close();
} catch (IOException e) {
e.printStackTrace();
}finally {
}
}
}
} catch (IOException e) {
e.printStackTrace();
}finally {
mediaExtractor.release();
mediaExtractor = null;
}
}
结果:
获取到的视频轨道信息:
getTrackCount()=2
video=KEY_MIME=video/avc
video=KEY_DURATION=52208333
video=KEY_WIDTH=854
video=KEY_HEIGHT=480
video=getSampleFlags=1
video=getSampleTime=83333
video=getSampleTrackIndex===0
获取到的音频轨道信息:
audio=KEY_MIME=audio/mp4a-latm
audio=KEY_CHANNEL_COUNT=2
audio=KEY_SAMPLE_RATE=48000
audio=KEY_DURATION=51946666
audio=getSampleFlags=1
audio=getSampleTime=0
audio=getSampleTrackIndex===========1
上面虽然获取了视频,音频数据,但是要怎么编码,解码为常用格式呢?
代码中用到的video.mp4文件,在源码中随意找的。https://pan.baidu.com/s/1ANs0DacuTMZgnTfjKoWjtg
AAC(Advanced Audio Coding)高级音频编码,有基于MPEG-2和MPEG-4标准两种音频编码技术。aac音频格式有两种编码方式ADIF和ADTS,两者之间的区别是ADIF在所有aac数据的开始添加一个ADIF头,可以确定音频数据的开始,aac文件有且只有这一个头信息。ADTS则把aac数据分成多个es帧,在每一帧的前面添加一个ADTS头信息,所以adts可以任意帧解码,因为每一帧都有头部信息。
上面通过MediaExtractor分离MP4文件得到的aac音频文件是没有添加adts头的原数据,所以保存的aac文件是没法播放的,只有添加了adts头才能播放。
合成的时候,MP4,flv,rtmp都不需要adts头,hls,rtp,ts是需要adts头,所以这里分离出来的aac是没有adts头信息的。想要能够播放需要手动添加adts头。