本文介绍的是:针对Android 视频的合成以及 视频、音频的分离
1、MediaMuxer+MediaCodc合成视频
2、MediaExtractor分离视频流以及音频流
首先针对视频:一个视频是可以有多个视轨、多个音轨 这些轨道都是可以放入音频数据、视频数据的
经测试一个轨道也是可以放来源不同的视频流或者音频流
1、MediaMuxer+MediaCodc合成视频:
以上是经典的音视频流合成视频的图
使用MediaCodec的基本顺序:
1、int addTrack(@NonNull MediaFormat format)
2、start() 一旦开始将不能再addTrack
3、void writeSampleData(int trackIndex, @NonNull ByteBuffer byteBuf,@NonNull BufferInfo bufferInfo)
4、void stop()
5、void release()
以上顺序不可颠倒 否则会报IllegalStateException
通常视频的提供者可以是Camera的预览数据,也可以是录屏的视频流或者是网络实时的视频流;音频数据是麦克风采集的声音或者其他的音频流,这些数据都是字节数组
当我们拿到这些音视频数据 第一步要将该数据写到指定的轨道上,此时要先去执行MediaMuxer的addTrack()方法添加对应轨道
例如音频数据
MediaFormat newFormat = mAudioEncoder.getOutputFormat(); mAudioTrackIndex = mMuxer.addTrack(newFormat);
当音频以及视频的轨道全部添加完成之后再去调用start()方法开启混流器此时可以往混流器里写入数据,此步骤涉及音频线程以及视频线程和混流器线程的控制,可自由发挥
while(true) {
int encoderStatus = mAudioEncoder.dequeueOutputBuffer(mBufferInfo, TIMEOUT_USEC);//获取输出缓冲区的index
if (encoderStatus == MediaCodec.INFO_TRY_AGAIN_LATER) {
...
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
encoderOutputBuffers = mAudioEncoder.getOutputBuffers();
} else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
MediaFormat newFormat = mAudioEncoder.getOutputFormat();
mAudioTrackIndex = mMuxer.addTrack(newFormat);//添加轨道
} else if (encoderStatus < 0) {
...
} else {
ByteBuffer encodedData = encoderOutputBuffers[encoderStatus];//真实的音频或视频数据
...//组织mBufferInfo的数据 包括有size offset PTS时间戳
if (mBufferInfo.size != 0) {
mMuxer.writeSampleData(mAudioTrackIndex, encodedData, mBufferInfo);//写入轨道
}
mAudioEncoder.releaseOutputBuffer(encoderStatus, false);//释放该输出缓冲区
if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
break;
}
}
写完数据之后 调用stop release 释放资源
2、视频 音频数据、视频数据的的分离提取 首先获取所有轨道数量 然后遍历这些轨道 找出我们感兴趣的轨道 直接获取轨道里的音视频数据
MediaExtractor extractor = new MediaExtractor();
extractor.setDataSource(...);
int numTracks = extractor.getTrackCount();
for (int i = 0; i < numTracks; ++i) {
MediaFormat format = extractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if (weAreInterestedInThisTrack) {
extractor.selectTrack(i);
}
}
ByteBuffer inputBuffer = ByteBuffer.allocate(...)
while (extractor.readSampleData(inputBuffer, ...) >= 0) {
int trackIndex = extractor.getSampleTrackIndex();
long presentationTimeUs = extractor.getSampleTime();
...
extractor.advance();
}
extractor.release();
extractor = null;
实际项目中这些操作都是在子线程中执行的