Android 直播流程3()

2018-10-19  本文已影响0人  韩瞅瞅

3、使用

MediaCodec创建之后,需要通过start()方法进行开启。MediaCodec有输入缓冲区队列和输出缓冲区队列,不断通过往输入缓冲区队列传递数据,经过MediaCodec处理后就可以得到响应的输出数据。当在编码的时候,需要向输入缓冲区传入采集到的原始的视音频数据,然后获取输出缓冲区的数据,输出出来的数据也就是编码处理后的数据。当在解码的时候,往输入缓冲区输入需要解码的数据,然后获取输出缓冲区的数据,输出出来的数据也就是解码后得到的原始的视音频数据。当需要清空输入和输出缓冲区的时候,可以调用MediaCodec的flush()方法。当编码或者解码结束时,通过往输入缓冲区输入带结束标记的数据,然后从输出缓冲区可以得到这个结束标记,从而完成整个编解码过程。下面一张图片很好地展示了MediaCodec的状态变化。

                                                                MediaCodec状态

对于MediaCodec通过处理输入的数据,从而得到输出数据。MediaCodec通过一系列的输入和输出缓冲区来处理数据。如下图所示,输入客户端通过查询得到空的输入缓冲区,然后往里面填充数据,然后将输入缓冲区传递给MediaCodec;输出客户端通过查询得到塞满的输出缓冲区,然后得到里面的数据,然后通知MediaCodec释放这个输出缓冲区。

                                                                MediaCodec过程

在API 21及以后可以通过下面这种异步的方式来使用MediaCodec。

MediaCodec codec = MediaCodec.createByCodecName(name);

MediaFormat mOutputFormat; // member variable

codec.setCallback(new MediaCodec.Callback() {

  @Override

  void onInputBufferAvailable(MediaCodec mc, int inputBufferId) {

    ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferId);

    // fill inputBuffer with valid data

    …

    codec.queueInputBuffer(inputBufferId, …);

  }

  @Override

  void onOutputBufferAvailable(MediaCodec mc, int outputBufferId, …) {

    ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);

    MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A

    // bufferFormat is equivalent to mOutputFormat

    // outputBuffer is ready to be processed or rendered.

    …

    codec.releaseOutputBuffer(outputBufferId, …);

  }

  @Override

  void onOutputFormatChanged(MediaCodec mc, MediaFormat format) {

    // Subsequent data will conform to new format.

    // Can ignore if using getOutputFormat(outputBufferId)

    mOutputFormat = format; // option B

  }

  @Override

  void onError(…) {

    …

  }

});

codec.configure(format, …);

mOutputFormat = codec.getOutputFormat(); // option B

codec.start();

// wait for processing to complete

codec.stop();

codec.release();

从API 21开始,可以使用下面这种同步的方式来使用MediaCodec。

MediaCodec codec = MediaCodec.createByCodecName(name);

codec.configure(format, …);

MediaFormat outputFormat = codec.getOutputFormat(); // option B

codec.start();

for (;;) {

  int inputBufferId = codec.dequeueInputBuffer(timeoutUs);

  if (inputBufferId >= 0) {

    ByteBuffer inputBuffer = codec.getInputBuffer(…);

    // fill inputBuffer with valid data

    …

    codec.queueInputBuffer(inputBufferId, …);

  }

  int outputBufferId = codec.dequeueOutputBuffer(…);

  if (outputBufferId >= 0) {

    ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferId);

    MediaFormat bufferFormat = codec.getOutputFormat(outputBufferId); // option A

    // bufferFormat is identical to outputFormat

    // outputBuffer is ready to be processed or rendered.

    …

    codec.releaseOutputBuffer(outputBufferId, …);

  } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {

    // Subsequent data will conform to new format.

    // Can ignore if using getOutputFormat(outputBufferId)

    outputFormat = codec.getOutputFormat(); // option B

  }

}

codec.stop();

codec.release();

在API版本21之前,获取缓冲区的方式有所不同,不能直接得到相应的缓冲区,需要根据索引序号从缓冲区列表中得到相应的缓冲区,具体的代码如下所示:

MediaCodec codec = MediaCodec.createByCodecName(name);

codec.configure(format, …);

codec.start();

ByteBuffer[] inputBuffers = codec.getInputBuffers();

ByteBuffer[] outputBuffers = codec.getOutputBuffers();

for (;;) {

  int inputBufferId = codec.dequeueInputBuffer(…);

  if (inputBufferId >= 0) {

    // fill inputBuffers[inputBufferId] with valid data

    …

    codec.queueInputBuffer(inputBufferId, …);

  }

  int outputBufferId = codec.dequeueOutputBuffer(…);

  if (outputBufferId >= 0) {

    // outputBuffers[outputBufferId] is ready to be processed or rendered.

    …

    codec.releaseOutputBuffer(outputBufferId, …);

  } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {

    outputBuffers = codec.getOutputBuffers();

  } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {

    // Subsequent data will conform to new format.

    MediaFormat format = codec.getOutputFormat();

  }

}

codec.stop();

codec.release();

当数据输入结束的时候,通过queueInputBuffer的输入标记设置为BUFFER_FLAG_END_OF_STREAM来通知MediaCodec结束编码。当MediaCodec作为编码器的时候, dequeueOutputBuffer方法能够得到当前编码输出缓冲区数据的相关信息,这些信息存储在bufferInfo里面,通过bufferInfo信息能够得到数据的真实长度,当前数据为关键帧或者非关键帧等等信息。

当使用Output Surface作为解码的输出的时候,可以根据以下情况来设置是否将视频渲染到Surface上。

releaseOutputBuffer(bufferId, false)  //不渲染buffer里面的数据

releaseOutputBuffer(bufferId, true)  //渲染buffer里面的数据

releaseOutputBuffer(bufferId, timestamp)  //在特定时间渲染buffer里面的数据

当使用Input Surface作为编码器输入的时候,不允许使用dequeueInputBuffer。当输入结束的时候,使用signalEndOfInputStream()来使得编码器停止。

MediaMuxer

前面讲述了MediaExtractor(视音频分离器),现在讲述MediaMuxer(视音频合成器)。MediaMuxer是Android提供的视音频合成器,目前只支持mp4和webm两种格式的视音频合成。一般来时视音频媒体都有视频轨道和音频轨道,有些时候也还有字母轨道,MediaMuxer将这些轨道糅合在一起存储在一个文件中。

MediaMuxer在Android中一个最常使用的场景是录制mp4文件。一般来说当存储为mp4文件时,视频轨道一般是经过编码处理后的h264视频,音频轨道一般是经过编码后处理的aac音频。前面已经讲述了如何对采集的视频和音频进行硬编,那么这时候如果对硬编后的视频和音频使用MediaMuxer进行合成,那么就可以合成为mp4文件。

下面是MediaMuxer一般的使用方法。

MediaMuxer muxer = new MediaMuxer("temp.mp4", OutputFormat.MUXER_OUTPUT_MPEG_4);

// More often, the MediaFormat will be retrieved from MediaCodec.getOutputFormat()

// or MediaExtractor.getTrackFormat().

MediaFormat audioFormat = new MediaFormat(...);

MediaFormat videoFormat = new MediaFormat(...);

int audioTrackIndex = muxer.addTrack(audioFormat);

int videoTrackIndex = muxer.addTrack(videoFormat);

ByteBuffer inputBuffer = ByteBuffer.allocate(bufferSize);

boolean finished = false;

BufferInfo bufferInfo = new BufferInfo();

muxer.start();

while(!finished) {

// getInputBuffer() will fill the inputBuffer with one frame of encoded

// sample from either MediaCodec or MediaExtractor, set isAudioSample to

// true when the sample is audio data, set up all the fields of bufferInfo,

// and return true if there are no more samples.

    finished = getInputBuffer(inputBuffer, isAudioSample, bufferInfo);

    if (!finished) {

        int currentTrackIndex = isAudioSample ? audioTrackIndex : videoTrackIndex;

        muxer.writeSampleData(currentTrackIndex, inputBuffer, bufferInfo);

    }

};

muxer.stop();

muxer.release();

其实上面的注释很好说明了MediaMuxer的使用场景。视音频轨道的初始化需要传入MediaFormat,而MediaFormat可以通过MediaCodec.getOutputFormat()获取(采集后进行硬编得到MediaFormat),也可以通过MediaExtractor.getTrackFormat()获取(分离器分离出视音频得到MediaFormat)。上面包含了两个应用场景,一个是采集,一个是转码。

结合

MediaExtractor和MediaCodec结合使用可以实现视频的播放功能,MediaCodec和MediaMuxer结合使用可以实现视频的录制功能,MediaExtractor、MediaCodec和MediaMuxer三者一起使用可以实现视频的转码功能。下面讲述一下这几个功能的实现。

1、视音频录制

之前讲述了视频的采集和音频的采集,将采集到的视音频通过MediaCodec进行编码处理,之后将编码数据传递到MediaMuxer进行合成,也就完成了视音频录制的功能。

                                                                        视频录制

根据视音频采集的相关参数创建MediaCodec,当MediaCodec的outputBufferId为INFO_OUTPUT_FORMAT_CHANGED时,可以通过codec.getOutputFormat()得到相应的MediaFormat,之后便可以用这个MediaFormat为MediaMuxer添加相应的视音频轨道。通过codec.dequeueOutputBuffer(…)可以得到编码后的数据的bufferInfo信息和相应的数据,之后将这个数据和bufferInfo通过muxer.writeSampleData(currentTrackIndex, inputBuffer, bufferInfo)传递给Muxer,也就将整个视音频数据合成到了mp4中。

2、视音频播放

                                                                                视音频播放

利用Android提供的Media API来实现一个播放器也是可以的,实际上Google著名的开源项目ExoPlayer就是这么做的。

上面的示意图简要描述了一个简单的本地播放器的结构。利用MediaExtractor分离视音频文件,得到相应的音频轨道和视频轨道。之后通过MediaExtractor从相应的轨道中获取数据,并且将这些数据传递给MediaCodec的输入缓冲区,经过MediaCodec的解码便可以得到相应的原始数据。音频解码后可以得到PCM数据,从而可以传递给AudioTrack进行播放。视频解码后可以渲染到相应的Surface,这个Surface可以是通过SurfaceTexture创建,而SurfaceTexture是可以通过纹理创建的,从而将解码后的视频数据传递到纹理上了。

MediaExtractor解析视音频文件,可以得到相应数据的pts,之后pts可以传输到MediaCodec,之后在MediaCodec的输出里面可以得到相应的pts,之后在根据视音频的pts来控制视音频的渲染,从而实现视音频的同步。

3、视音频转码

视音频的转码,其实就是通过MediaExtractor解析相应的文件,之后得到相应的视频轨道和音频轨道,之后将轨道里的数据传输到MediaCodec进行解码,然后将解码后的数据进行相应的处理(例如音频变声、视频裁剪、视频滤镜),之后将处理后的数据传递给MediaCodec进行编码,最后利用MediaMuxer将视频轨道和音频轨道进行合成,从而完成了整个转码过程。

                                                                           视音频转码

讲述了,如何使用Media API进行相应的录制、播放、转码,讲述了如何将视音频编解码和纹理相结合

以上就是直播功能的基本流程:

采集视频、音频数据 ---- 将视频数据通过h264/aac进行编码 ---- 将编码好的音频视频数据混合封装成flv的格式 ---- 把flv数据推送到支持rtmp的服务器-----获取音视频数据解码播放。

上一篇下一篇

猜你喜欢

热点阅读