美文网首页
Android 直播流程3()

Android 直播流程3()

作者: 韩瞅瞅 | 来源:发表于2018-10-19 20:54 被阅读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的服务器-----获取音视频数据解码播放。

    相关文章

      网友评论

          本文标题:Android 直播流程3()

          本文链接:https://www.haomeiwen.com/subject/pliuzftx.html