目的
準(zhǔn)備一個(gè)封裝格式的文件(mp4.mp3等),從中讀取音頻軌道的數(shù)據(jù),使用 dsp 解碼成 pcm 文件后,截取其中一段音頻,保存成一個(gè)新的文件
這個(gè)練習(xí)是為后面的視頻混音做準(zhǔn)備,熟悉相關(guān) api
步驟
- 通過 MediaExtractor 讀取 mp3 文件的軌道信息
- 讀取音頻軌道的 pcm 裸流數(shù)據(jù),并且保存成 pcm 文件
- 操作 pcm 文件,截取片段
相關(guān)知識(shí)
- MediaExtractor : 可以把音視頻文件的音頻和視頻分離,并抽取相應(yīng)的數(shù)據(jù)通道,然后進(jìn)行操作。
重要方法:
- setDataSource(String path) ,設(shè)置數(shù)據(jù)源
- getTrackCount(),獲取軌道總數(shù),音視頻文件的軌道有視頻軌道、音頻軌道、字母軌道等,該方法用于遍歷軌道信息
- MediaFormat getTrackFormat(int index),返回一個(gè) MediaFormat 對(duì)象,其實(shí)內(nèi)部是一個(gè) Map
- seekTo(long timeUs, @SeekMode int mode),seek 到某個(gè)時(shí)間,對(duì)應(yīng)音視頻 app 上的進(jìn)度條
- advance(), 前進(jìn)到下一個(gè)樣本,跳過當(dāng)前
- getSampleTime(),獲取當(dāng)前樣本的微秒時(shí)間
- readSampleData(@NonNull ByteBuffer byteBuf, int offset) 讀取數(shù)據(jù)
- MediaMuxer : 生成一個(gè)音頻或視頻文件;還可以把音頻與視頻混合成一個(gè)音視頻文件
重要方法:
相關(guān)鏈接
用 MediaExtractor 和 MediaMuxer API 解析和封裝 mp4 文件
實(shí)踐代碼
- 從視頻文件中分離出音頻軌道,并保存為 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", "開始轉(zhuǎn)換");
int maxBufferSize;
//1. 查找文件的音頻軌道
int audioIndex = selectAudioTrack(mMediaExtractor);
//2. 指定音頻軌道
mMediaExtractor.selectTrack(audioIndex);
mMediaExtractor.seekTo(startTime, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
//3. 解碼音頻數(shù)據(jù),由封轉(zhuǎn)格式 -> PCM 裸數(shù)據(jù)
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 解碼器解碼音頻數(shù)據(jù)
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;
// 讀取解碼后的數(shù)據(jù)
while (true){
// 獲取空閑的寫入緩沖區(qū)
int decodeInputIndex = mediaCodec.dequeueInputBuffer(100000);
// 已經(jīng)拿到可以使用的寫入緩沖區(qū)
if (decodeInputIndex >= 0) {
// 將預(yù)定時(shí)間間隔內(nèi)的音頻數(shù)據(jù)塞入緩沖區(qū)
long sampleTimeUs = mMediaExtractor.getSampleTime();
if (sampleTimeUs == -1){
break;
}else if (sampleTimeUs < startTime){
mMediaExtractor.advance();
continue;
}else if (sampleTimeUs > endTime){
break;
}
// 獲取數(shù)據(jù)
info.size = mMediaExtractor.readSampleData(buffer, 0);
info.presentationTimeUs = sampleTimeUs;
info.flags = mMediaExtractor.getSampleFlags();
// 通過 remaining 方法高效讀取數(shù)據(jù)
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 裸數(shù)據(jù)
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: 轉(zhuǎn)換完畢");
}
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;
}
}