Android-音視訊(6):用 MediaExtractor 和 MediaMuxer API 解析和封裝 mp4 檔案
阿新 • • 發佈:2018-12-13
1.MediaExtractor API的作用
作用:
- 可以把音視訊檔案的音訊和視訊分離,並抽取相應的資料通道,然後進行操作。
如何使用:
- 先要知道是針對哪個檔案操作,所以要用 setDataSource(String filePath) 設定目標檔案。
- 然後需要知道這個檔案所有的通道數,用 getTrackCount() 得到,通過遍歷,得到需要的通道 int i。
- 根據得到的通道,用 getTrackFirmat(int i ) 得到這個通道的資料格式(MediaFormat mediaFormat)。
- 然後把MediaExtractor對準這個通道 用 selectTack(int i),準備讀取資料。
- 用 readSampleData(ByteBuffer byteBuffer , int offset) 把指定通道中的資料按偏移量讀取到byteBuffer中,這只是一幀的資料。
- 有了這個byteBuffer資料,之後的事就交給MediaMuxer去操作這一幀的資料,之後呼叫 adVance()調取下一幀,重複5、6。
- 操作完之後,記得釋放 release()。
程式碼示例將在下面和MediaMuxer一起給出。
2.MediaMuxer API的作用
作用:
- 生成一個音訊或視訊檔案;還可以把音訊與視訊混合成一個音視訊檔案
如何使用:
- 因為是生成一個檔案,所以構造一個MediaMuxer的時候需要傳入檔案的路徑,和檔案的格式如:new MediaMuxer(String newfilepath, int format) 格式一般為: MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4
- 然後需要新增通道,記錄一個數據通道的格式(MediaExtracktor第3步得到) addTrack(MediaFormat format)並得到一個trackIndex,之後用這個判斷用哪個通道寫入資料。
- start():開始合成檔案
- 每當MediaExtracktor的第5步後,用 writeSampleData(int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo):把ByteBuffer中的資料寫入之前設定的檔案中。
- 資料寫入完成,stop():停止合成檔案 release():釋放資源
3.結合的示例程式碼如下:
String mp4FilePath = Environment.getExternalStorageDirectory().getAbsolutePath()+ File.separator+"test.mp4";
String newVideoFilePath = Environment.getExternalStorageDirectory().getAbsolutePath()+File.separator+"testVideo.mp4";
private void startGetVideo() { //抽取mp4檔案的視訊資料,並生成新的mp4檔案,但是沒有聲音。
File videofile = new File(newVideoFilePath);
if (videofile.exists()){
videofile.delete();
try {
videofile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
getVideoThread = new Thread(){
@Override
public void run() {
try {
mediaExtractor = new MediaExtractor();
//設定視訊檔案,用於提取視訊軌道資料
mediaExtractor.setDataSource(mp4FilePath);
int videoTrackIndex = -1;
int framerate = 0;
for (int i = 0 ; i < mediaExtractor.getTrackCount() ; i++){
MediaFormat mediaformat = mediaExtractor.getTrackFormat(i);
//跳過不是視訊軌道的軌道
if (!(mediaformat.getString(MediaFormat.KEY_MIME)).startsWith("video/")){
continue;
}
//幾幀/秒
framerate = mediaformat.getInteger(MediaFormat.KEY_FRAME_RATE);
//選擇視訊軌道
mediaExtractor.selectTrack(i);
//設定視訊軌道的輸出位置和格式
mediaMuxer = new MediaMuxer(newVideoFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
//記錄視訊軌道值,之後要用這個值判斷寫入資料
videoTrackIndex = mediaMuxer.addTrack(mediaformat);
mediaMuxer.start();
}
if (mediaMuxer == null){
return;
}
//讀取軌道資訊時,以及向新檔案寫入軌道資訊時。用於儲存每一幀的資訊(如時長,size,flag)
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
//當前演示時長 初始化為0
info.presentationTimeUs = 0;
int sampleSize = 0;
//申請一個空間儲存每一幀視訊資料,然後寫入新檔案
ByteBuffer buffer = ByteBuffer.allocate(500*1024);
while((sampleSize = mediaExtractor.readSampleData(buffer,0)) > 0){
//儲存幀資訊
info.size = sampleSize;
info.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME;
info.presentationTimeUs += 1000*1000 / framerate; // (1000*1000 / framerate) 是一幀的微秒時長
//儲存幀資料(向指定的視訊軌道寫入)
mediaMuxer.writeSampleData(videoTrackIndex,buffer,info);
mediaExtractor.advance(); //下一幀
}
mediaExtractor.release();
mediaMuxer.stop();
mediaMuxer.release();
runOnUiThread(new Runnable() {
@Override
public void run() {
showInfor.append("\n"+"testVideo.mp4 視訊軌道資料提取成功");
}
});
} catch (IOException e) {
e.printStackTrace();
}finally {
getVideoThread = null;
}
}
};
getVideoThread.start();
}