日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

MediaExtractor、MediaMuxer 分离和合成 mp4

發布時間:2023/12/31 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。