一個音視頻文件是由音頻和視頻組成的,Android 提供了 MediaExtractor 和 MediaMuxer 類,用來把音頻或視頻單獨抽取出來,然后合成新的視頻。
我們分別看一下 API 的使用,實現分離 MP4 視頻文件,然后再合成分離的音視頻功能。
MediaExtractor 的使用主要有這么幾步:
- 設置數據源
- 獲取通道數,切換到想要的軌道
- 循環讀取每幀的樣本數據
- 完成后釋放資源
MediaMuxer 的使用和 MediaExtractor 類似:
- 設置目標文件路徑和音視頻格式
- 添加要合成的軌道,包括音軌和視軌
- 開始合成,循環寫入每幀樣本數據
- 完成后釋放資源
MediaFormat 封裝了媒體數據(音頻,視頻,字幕)格式的信息,所有信息都以鍵值對形式表示。MediaFormat 中定義的 key 對于不同媒體數據并不是全部通用的,某些 key 只適用于特定媒體數據。
通用 Keys
KEY_MIME 格式類型
KEY_MAX_INPUT_SIZE 輸出緩沖區的最大字節數
KEY_BIT_RATE 比特率
Video Keys
KEY_WIDTH 視頻寬度
KEY_HEIGHT 視頻高度
KEY_DURATION 內容持續時間(以微妙為單位)
KEY_CORLOR_FORMAT 視頻內容顏色空間
KEY_FRAME_RATE 視頻幀率
KEY_I_FRAME_INTERVAL 關鍵之間的時間間隔
KEY_ROTATION 視頻旋轉順時針角度
KEY_BITRATE 碼率/比特率(畫質和文件體積)
KEY_BITRATE_MODE 比特率模式
Audio Keys
KEY_CHANNEL_COUNT 通道數
KEY_SAMPLE_RATE 采樣率
KEY_DURATION 內容持續時間(以微妙為單位
下面是個提取并合成視頻的示例,MediaExtractor 和 MediaMuxer 一起使用,相當于把原視頻的聲音通道去掉,生成一個無聲的視頻文件。
// 分離視頻的視頻軌,輸入視頻 input.mp4,輸出視頻 output_video.mp4
private void extractVideo() {
logger.info("extractVideo() start");
MediaExtractor mediaExtractor = new MediaExtractor();
MediaMuxer mediaMuxer = null;
try {
File fileDir = FileUtils.getFileDir(this);
// 設置視頻源
mediaExtractor.setDataSource(new File(fileDir, VIDEO_SOURCE).getAbsolutePath());
// 軌道索引 ID
int videoIndex = -1;
// 視頻軌道格式信息
MediaFormat mediaFormat = null;
// 數據源的軌道數(一般有視頻,音頻,字幕等)
int trackCount = mediaExtractor.getTrackCount();
// 循環軌道數,找到我們想要的視頻軌
for (int i = 0; i < trackCount; i++) {
mediaFormat = mediaExtractor.getTrackFormat(i);
String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
// //找到要分離的視頻軌
if (mimeType.startsWith("video/")) {
videoIndex = i;
break;
}
}
if (mediaFormat == null || videoIndex < 0) {
return;
}
// 最大緩沖區字節數
int maxInputSize = mediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
// 格式類型
String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
// 視頻的比特率
int bitRate = mediaFormat.getInteger(MediaFormat.KEY_BIT_RATE);
// 視頻寬度
int width = mediaFormat.getInteger(MediaFormat.KEY_WIDTH);
// 視頻高度
int height = mediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
// 內容持續時間(以微妙為單位)
long duration = mediaFormat.getLong(MediaFormat.KEY_DURATION);
// 視頻的幀率
int frameRate = mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE);
// 視頻內容顏色空間
int colorFormat = -1;
if (mediaFormat.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
mediaFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
}
// 關鍵之間的時間間隔
int iFrameInterval = -1;
if (mediaFormat.containsKey(MediaFormat.KEY_I_FRAME_INTERVAL)) {
iFrameInterval = mediaFormat.getInteger(MediaFormat.KEY_I_FRAME_INTERVAL);
}
// 視頻旋轉順時針角度
int rotation = -1;
if (mediaFormat.containsKey(MediaFormat.KEY_ROTATION)) {
rotation = mediaFormat.getInteger(MediaFormat.KEY_ROTATION);
}
// 比特率模式
int bitRateMode = -1;
if (mediaFormat.containsKey(MediaFormat.KEY_BITRATE_MODE)) {
bitRateMode = mediaFormat.getInteger(MediaFormat.KEY_BITRATE_MODE);
}
logger.info("mimeType:{}, maxInputSize:{}, bitRate:{}, width:{}, height:{}" +
", duration:{}ms, frameRate:{}, colorFormat:{}, iFrameInterval:{}" +
", rotation:{}, bitRateMode:{}", mimeType, maxInputSize, bitRate, width, height
, duration / 1000, frameRate, colorFormat, iFrameInterval, rotation, bitRateMode);
//切換視頻的軌道
mediaExtractor.selectTrack(videoIndex);
String outPath = new File(fileDir, OUTPUT_VIDEO).getAbsolutePath();
mediaMuxer = new MediaMuxer(outPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
//將視頻軌添加到 MediaMuxer,并返回新的軌道
int trackIndex = mediaMuxer.addTrack(mediaFormat);
ByteBuffer byteBuffer = ByteBuffer.allocate(maxInputSize);
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
// 開始合成
mediaMuxer.start();
while (true) {
// 檢索當前編碼的樣本并將其存儲在字節緩沖區中
int readSampleSize = mediaExtractor.readSampleData(byteBuffer, 0);
// 如果沒有可獲取的樣本則退出循環
if (readSampleSize < 0) {
mediaExtractor.unselectTrack(videoIndex);
break;
}
// 設置樣本編碼信息
bufferInfo.size = readSampleSize;
bufferInfo.offset = 0;
bufferInfo.flags = mediaExtractor.getSampleFlags();
bufferInfo.presentationTimeUs = mediaExtractor.getSampleTime();
//寫入樣本數據
mediaMuxer.writeSampleData(trackIndex, byteBuffer, bufferInfo);
//推進到下一個樣本,類似快進
mediaExtractor.advance();
}
logger.info("finish extract video, path:{}", outPath);
Toast.makeText(this, "分離視頻完成", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
logger.error(e);
} finally {
if (mediaMuxer != null) {
mediaMuxer.stop();
mediaMuxer.release();
}
mediaExtractor.release();
}
}
同樣地,分離音頻和合成音視頻的過程和上面差不多,代碼在 GitHub 上,歡迎參閱。
參考文章: