本文介绍的是:针对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;

 

实际项目中这些操作都是在子线程中执行的

 

posted on 2020-09-17 23:15  毕哥  阅读(863)  评论(0编辑  收藏  举报