目的
准备一个封装格式的文件(mp4.mp3等),从中读取音频轨道的数据,使用 dsp 解码成 pcm 文件后,截取其中一段音频,保存成一个新的文件
这个练习是为后面的视频混音做准备,熟悉相关 api
步骤
- 通过 MediaExtractor 读取 mp3 文件的轨道信息
- 读取音频轨道的 pcm 裸流数据,并且保存成 pcm 文件
- 操作 pcm 文件,截取片段
相关知识
- MediaExtractor : 可以把音视频文件的音频和视频分离,并抽取相应的数据通道,然后进行操作。
重要方法:
- setDataSource(String path) ,设置数据源
- getTrackCount(),获取轨道总数,音视频文件的轨道有视频轨道、音频轨道、字母轨道等,该方法用于遍历轨道信息
- MediaFormat getTrackFormat(int index),返回一个 MediaFormat 对象,其实内部是一个 Map
- seekTo(long timeUs, @SeekMode int mode),seek 到某个时间,对应音视频 app 上的进度条
- advance(), 前进到下一个样本,跳过当前
- getSampleTime(),获取当前样本的微秒时间
- readSampleData(@NonNull ByteBuffer byteBuf, int offset) 读取数据
- MediaMuxer : 生成一个音频或视频文件;还可以把音频与视频混合成一个音视频文件
重要方法:
相关链接
用 MediaExtractor 和 MediaMuxer API 解析和封装 mp4 文件
实践代码
- 从视频文件中分离出音频轨道,并保存为 WAV
/**
* @author : kai.mao
* @date : 2021/1/23
*/
public class AudioClipHelper {
private static AudioClipHelper INSTANCE;
private MediaExtractor mMediaExtractor;
private AudioClipHelper() {
}
public static AudioClipHelper getInstance() {
if (INSTANCE == null) {
INSTANCE = new AudioClipHelper();
}
return INSTANCE;
}
public void setDataSource(String filePath) {
if (mMediaExtractor == null) {
mMediaExtractor = new MediaExtractor();
}
try {
mMediaExtractor.setDataSource(filePath);
} catch (IOException e) {
e.printStackTrace();
}
}
public void readAuido(int startTime,int endTime) throws IOException {
Log.e("David", "开始转换");
int maxBufferSize;
//1. 查找文件的音频轨道
int audioIndex = selectAudioTrack(mMediaExtractor);
//2. 指定音频轨道
mMediaExtractor.selectTrack(audioIndex);
mMediaExtractor.seekTo(startTime, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
//3. 解码音频数据,由封转格式 -> PCM 裸数据
MediaFormat originAudioFormat = mMediaExtractor.getTrackFormat(audioIndex);
if (originAudioFormat.containsKey(MediaFormat.KEY_MAX_INPUT_SIZE)){
maxBufferSize = originAudioFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
}else {
maxBufferSize = 100 * 1000;
}
ByteBuffer buffer = ByteBuffer.allocateDirect(maxBufferSize);
// 使用 MediaCodec 解码器解码音频数据
MediaCodec mediaCodec = MediaCodec.createDecoderByType(originAudioFormat.getString(MediaFormat.KEY_MIME));
mediaCodec.configure(originAudioFormat,null,null,0);
File pcmFile = new File(Environment.getExternalStorageDirectory(), "out.pcm");
FileChannel writeChannel = new FileOutputStream(pcmFile).getChannel();
mediaCodec.start();
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
int outputBufferIndex = -1;
// 读取解码后的数据
while (true){
// 获取空闲的写入缓冲区
int decodeInputIndex = mediaCodec.dequeueInputBuffer(100000);
// 已经拿到可以使用的写入缓冲区
if (decodeInputIndex >= 0) {
// 将预定时间间隔内的音频数据塞入缓冲区
long sampleTimeUs = mMediaExtractor.getSampleTime();
if (sampleTimeUs == -1){
break;
}else if (sampleTimeUs < startTime){
mMediaExtractor.advance();
continue;
}else if (sampleTimeUs > endTime){
break;
}
// 获取数据
info.size = mMediaExtractor.readSampleData(buffer, 0);
info.presentationTimeUs = sampleTimeUs;
info.flags = mMediaExtractor.getSampleFlags();
// 通过 remaining 方法高效读取数据
byte[] content = new byte[buffer.remaining()];
buffer.get(content);
FileUtils.writeContent(content);
ByteBuffer inputByteBuffer = mediaCodec.getInputBuffer(decodeInputIndex);
inputByteBuffer.put(content);
mediaCodec.queueInputBuffer(decodeInputIndex,0,info.size,info.presentationTimeUs,info.flags);
Log.e("David", "presentationTimeUs:"+sampleTimeUs);
mMediaExtractor.advance();
}
// 取出解码后的 pcm 裸数据
outputBufferIndex = mediaCodec.dequeueOutputBuffer(info,100_000);
while (outputBufferIndex >= 0){
ByteBuffer decodeOutputBuffer = mediaCodec.getOutputBuffer(outputBufferIndex);
writeChannel.write(decodeOutputBuffer);//MP3 1 pcm2
mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mediaCodec.dequeueOutputBuffer(info, 100_000);
}
}
writeChannel.close();
mMediaExtractor.release();
mediaCodec.stop();
mediaCodec.release();
File wavFile = new File(Environment.getExternalStorageDirectory(),"output.mp3" );
new PcmToWavUtil(44100, AudioFormat.CHANNEL_IN_STEREO,
2, AudioFormat.ENCODING_PCM_16BIT).pcmToWav(pcmFile.getAbsolutePath()
, wavFile.getAbsolutePath());
Log.i("David", "mixAudioTrack: 转换完毕");
}
private int selectAudioTrack(MediaExtractor mediaExtractor) {
int trackTotal = mediaExtractor.getTrackCount();
for (int index = 0; index < trackTotal; index++) {
MediaFormat mediaFormat = mediaExtractor.getTrackFormat(index);
// 获取格式类型为 audio 的轨道
if (mediaFormat.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
return index;
}
}
return -1;
}
}
网友评论