MediaExtractor、MediaMuxer 分离和合成 mp4
MediaExtractor
視音頻分離器,將一些格式的視頻分離出視頻軌道和音頻軌道。
主要流程,也是主要的 API:
- setDataSource(String path):設置數據源
- getTrackCount():獲取通道數
- getTrackFormat(int index):獲取通道格式
- readSampleData(ByteBuffer byteBuf, int offset):讀取指定通道中的數據
- getSampleTime():獲取當前時間戳
- advance():下一幀
- release():釋放資源
MediaMuxer
視音頻合成器,將視頻和音頻合成相應的格式
- MediaMuxer(String path, int format):初始化輸出文件名稱和輸出文件的格式:mpeg4
- addTrack(MediaFormat format):添加軌道,返回 track Index,會在 writeSampleData 中使用
- start():開始合成文件
- writeSampleData(int, ByteBuffer, MediaCodec.BufferInfo):寫數據
- stop():停止合成文件
- release():釋放資源
大體流程:
以下是分離 mp4 音頻和視頻的源碼及注釋:
private String seperateMedia(String fileName, boolean isAudio) {String type = isAudio ? "audio/" : "video/";MediaExtractor mMediaExtractor = new MediaExtractor();;MediaMuxer mMediaMuxer = null;try {mMediaMuxer = new MediaMuxer(getSDPath() + "/" + fileName, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);// 獲取assert中的資源文件AssetFileDescriptor fileDescriptor = getAssetFileSource();// 設置資源文件mMediaExtractor.setDataSource(fileDescriptor.getFileDescriptor(), fileDescriptor.getStartOffset(), fileDescriptor.getLength());int mMediaIndex = 0;for (int i = 0; i < mMediaExtractor.getTrackCount(); i++) {//獲取碼流的詳細格式/配置信息MediaFormat format = mMediaExtractor.getTrackFormat(i);String mine = format.getString(MediaFormat.KEY_MIME);// 查找音頻:"audio/" 或者視頻:"video/"的軌道if (mine.startsWith(type)) {mMediaIndex = i;break;}}// 選擇感興趣的軌道mMediaExtractor.selectTrack(mMediaIndex);// 獲取通道格式,可以自己新建,但是有坑MediaFormat mediaFormat = mMediaExtractor.getTrackFormat(mMediaIndex);int muxerTrackIndex = mMediaMuxer.addTrack(mediaFormat);// 當采集視頻的使用,需要獲取幀率,音頻軌道沒有這個參數int framerate = isAudio ? 0 : mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE);mMediaMuxer.start();ByteBuffer byteBuffer = ByteBuffer.allocate(500 * 1024);int readSize = 0;// writeSampleData需要BufferInfo參數MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();while((readSize = mMediaExtractor.readSampleData(byteBuffer, 0)) > 0) {bufferInfo.size = readSize;bufferInfo.flags = mMediaExtractor.getSampleFlags(); //設置為關鍵幀等bufferInfo.offset = 0;if (!isAudio) { // 時間戳,音頻和視頻的處理方式不一樣bufferInfo.presentationTimeUs += 1000 * 1000 / framerate;} else {bufferInfo.presentationTimeUs = mMediaExtractor.getSampleTime();}mMediaMuxer.writeSampleData(muxerTrackIndex, byteBuffer, bufferInfo);Log.d("getSampleTime", "seperateMedia: " + mMediaExtractor.getSampleTime() );mMediaExtractor.advance(); //下一幀}return "success";} catch (IOException e) {e.printStackTrace();} finally {// 釋放資源if(mMediaExtractor != null ) {mMediaExtractor.release();mMediaExtractor = null;}if(mMediaMuxer != null) {mMediaMuxer.stop();mMediaMuxer.release();mMediaMuxer = null;}}return null;}坑點:
bufferInfo 信息中需要寫入正確的 presentationTimeUs,而看網上的參考基本都是 MediaExtractor.getSampleTime(),但在實際使用中會異常報錯,也有使用通過計算前兩個關鍵幀的差值,然后逐步增加,因為而且 presentationTimeUs 是需要遞增的,但是實際上最后分離出來的視頻模糊卡頓的現象。
在 Android 中如何提取和生成 mp4 文件 中看到根據幀率來計算 presentationTimeUs,效果比較正常,結合音頻使用 mMediaExtractor.getSampleTime();
視頻: int frameRate=mediaFormat=getInteger(MediaFormat.KEY_FRAME_RATE); //1s 的幀數
bufferInfo.presentationTimeUs = 1000 * 1000/frameRate; // 對于幀率為 x f/s 的視頻而言,時間戳的間隔就是 1000/x ms
音頻: bufferInfo.presentationTimeUs = mMediaExtractor.getSampleTime();
最近發現兩個問題:
1. 為什么 getSampleTime() 不是逐步遞增的?時間戳難道不是應該持續上升的嗎?
2. 在某些機型上出現沒有 MediaFormat.KEY_FRAME_RATE,該怎么計算 presentationTimeUs?
源碼
public class MediaExtractorActivity extends AppCompatActivity implements View.OnClickListener{private static String TAG = MediaExtractorActivity.class.getSimpleName();public static String[] STORAGE = {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE};@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_mediaextractor);findViewById(R.id.seperate_audio_btn).setOnClickListener(this);findViewById(R.id.seperate_media_btn).setOnClickListener(this);findViewById(R.id.muxer_btn).setOnClickListener(this);}@Overridepublic void onClick(View view) {if(ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(this, STORAGE, 2);return;}switch (view.getId()) {case R.id.seperate_audio_btn:new MediaAsyncTask().execute(1);break;case R.id.seperate_media_btn:new MediaAsyncTask().execute(2);break;case R.id.muxer_btn:new MediaAsyncTask().execute(3);break;default:break;}findViewById(R.id.seperate_audio_btn).setEnabled(false);findViewById(R.id.seperate_media_btn).setEnabled(false);findViewById(R.id.muxer_btn).setEnabled(false);}private class MediaAsyncTask extends AsyncTask<Integer, Integer, String> {@Overrideprotected void onPreExecute() {super.onPreExecute();Toast.makeText(MediaExtractorActivity.this, "開始轉化", Toast.LENGTH_SHORT).show();}@Overrideprotected String doInBackground(Integer... param) {if (param.length < 1){return null;}switch (param[0]) {case 1:return seperateMedia("audio.mp4", true);case 2:return seperateMedia("video.mp4", false);case 3:return muxerMediaAndAudio("video.mp4","audio.mp4", "result.mp4");default:return null;}}@Overrideprotected void onPostExecute(String s) {findViewById(R.id.seperate_audio_btn).setEnabled(true);findViewById(R.id.seperate_media_btn).setEnabled(true);findViewById(R.id.muxer_btn).setEnabled(true);if (s != null) {Toast.makeText(MediaExtractorActivity.this, "轉化完成 " + s, Toast.LENGTH_LONG).show();} else {Toast.makeText(MediaExtractorActivity.this, "轉化失敗", Toast.LENGTH_SHORT).show();}}}private String muxerMediaAndAudio(String mediaFileName, String audioFileName, String resultName) {File mediaFile = new File(getSDPath(), mediaFileName);File audioFile = new File(getSDPath(), audioFileName);if (!mediaFile.exists() || !audioFile.exists()) {return "音視頻文件不存在";}MediaMuxer mMediaMuxer = null;MediaExtractor mMediaExtractor = new MediaExtractor();MediaExtractor mAudioExtractor = new MediaExtractor();try {mMediaMuxer = new MediaMuxer(getSDPath() + "/" + resultName, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);mMediaExtractor.setDataSource(mediaFile.getAbsolutePath());mAudioExtractor.setDataSource(audioFile.getAbsolutePath());int mMediaTrack = 0;int mAudioTrack = 0;for (int i = 0; i < mMediaExtractor.getTrackCount(); i++) {String mine = mMediaExtractor.getTrackFormat(i).getString(MediaFormat.KEY_MIME);if (mine.startsWith("video/")) {mMediaTrack = i;break;}}for (int i = 0; i < mAudioExtractor.getTrackCount(); i++) {String mine = mAudioExtractor.getTrackFormat(i).getString(MediaFormat.KEY_MIME);if (mine.startsWith("audio/")) {mAudioTrack = i;break;}}mMediaExtractor.selectTrack(mMediaTrack);mAudioExtractor.selectTrack(mAudioTrack);MediaFormat mediaFormat = mMediaExtractor.getTrackFormat(mMediaTrack);MediaFormat audioFormat = mAudioExtractor.getTrackFormat(mAudioTrack);MediaCodec.BufferInfo mediaBufferInfo = new MediaCodec.BufferInfo();MediaCodec.BufferInfo audioBufferInfo = new MediaCodec.BufferInfo();int mediaMuxerTrack = mMediaMuxer.addTrack(mediaFormat);int audioMuxerTrack = mMediaMuxer.addTrack(audioFormat);mMediaMuxer.start();ByteBuffer byteBuffer = ByteBuffer.allocate(500 * 1024);int readSize = 0;int mMediaFramerate = mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE);while ((readSize = mMediaExtractor.readSampleData(byteBuffer, 0)) > 0) {mediaBufferInfo.presentationTimeUs += 1000 * 1000 / mMediaFramerate;mediaBufferInfo.offset = 0;mediaBufferInfo.flags = mMediaExtractor.getSampleFlags();mediaBufferInfo.size = readSize;mMediaMuxer.writeSampleData(mediaMuxerTrack, byteBuffer, mediaBufferInfo);mMediaExtractor.advance();}while ((readSize = mAudioExtractor.readSampleData(byteBuffer, 0)) > 0) {audioBufferInfo.presentationTimeUs = mAudioExtractor.getSampleTime();audioBufferInfo.offset = 0;audioBufferInfo.flags = mAudioExtractor.getSampleFlags();audioBufferInfo.size = readSize;mMediaMuxer.writeSampleData(audioMuxerTrack, byteBuffer, audioBufferInfo);mAudioExtractor.advance();}mMediaMuxer.stop();return getSDPath() + resultName;} catch (IOException e) {e.printStackTrace();} finally {if(mMediaExtractor != null ) {mMediaExtractor.release();mMediaExtractor = null;}if(mAudioExtractor != null ) {mAudioExtractor.release();mAudioExtractor = null;}if(mMediaMuxer != null) {mMediaMuxer.release();mMediaMuxer = null;}}return null;}private String seperateMedia(String fileName, boolean isAudio) {String type = isAudio ? "audio/" : "video/";MediaExtractor mMediaExtractor = new MediaExtractor();;MediaMuxer mMediaMuxer = null;try {mMediaMuxer = new MediaMuxer(getSDPath() + "/" + fileName, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);// 獲取assert中的資源文件AssetFileDescriptor fileDescriptor = getAssetFileSource();// 設置資源文件mMediaExtractor.setDataSource(fileDescriptor.getFileDescriptor(), fileDescriptor.getStartOffset(), fileDescriptor.getLength());int mMediaIndex = 0;for (int i = 0; i < mMediaExtractor.getTrackCount(); i++) {//獲取碼流的詳細格式/配置信息MediaFormat format = mMediaExtractor.getTrackFormat(i);String mine = format.getString(MediaFormat.KEY_MIME);// 查找音頻:"audio/" 或者視頻:"video/"的軌道if (mine.startsWith(type)) {mMediaIndex = i;break;}}// 選擇感興趣的軌道mMediaExtractor.selectTrack(mMediaIndex);// 獲取通道格式,可以自己新建,但是有坑MediaFormat mediaFormat = mMediaExtractor.getTrackFormat(mMediaIndex);int muxerTrackIndex = mMediaMuxer.addTrack(mediaFormat);// 當采集視頻的使用,需要獲取幀率,音頻軌道沒有這個參數int framerate = 0;if (mediaFormat.containsKey(MediaFormat.KEY_FRAME_RATE)) {framerate = mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE);}mMediaMuxer.start();ByteBuffer byteBuffer = ByteBuffer.allocate(500 * 1024);int readSize = 0;// writeSampleData需要BufferInfo參數MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();while((readSize = mMediaExtractor.readSampleData(byteBuffer, 0)) > 0) {bufferInfo.size = readSize;bufferInfo.flags = mMediaExtractor.getSampleFlags(); //設置為關鍵幀等bufferInfo.offset = 0;if (framerate != 0) { // 時間戳,音頻和視頻的處理方式不一樣bufferInfo.presentationTimeUs += 1000 * 1000 / framerate;} else {bufferInfo.presentationTimeUs = mMediaExtractor.getSampleTime();}mMediaMuxer.writeSampleData(muxerTrackIndex, byteBuffer, bufferInfo);Log.d("getSampleTime", "seperateMedia: " + mMediaExtractor.getSampleTime() );mMediaExtractor.advance(); //下一幀}mMediaMuxer.stop();return "success";} catch (IOException e) {e.printStackTrace();} finally {// 釋放資源if(mMediaExtractor != null ) {mMediaExtractor.release();mMediaExtractor = null;}if(mMediaMuxer != null) {mMediaMuxer.release();mMediaMuxer = null;}}return null;}private AssetFileDescriptor getAssetFileSource() throws IOException {AssetManager manager = getAssets();return manager.openFd("screen_recorder.mp4");}public static String getSDPath() {// 判斷是否掛載if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {return Environment.getExternalStorageDirectory().getAbsolutePath();}return Environment.getRootDirectory().getAbsolutePath();} }參考:
http://blog.51cto.com/ticktick/1710743
總結
以上是生活随笔為你收集整理的MediaExtractor、MediaMuxer 分离和合成 mp4的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android 启动模拟器是出现 Fai
- 下一篇: 如何下载spring sts