如何在 Android MediaCodec 中播放原始 NAL 单元

How to play raw NAL units in Android MediaCodec

我最初尝试 但我注意到我将不得不在低级别上做事。

我找到了这个 simple MediaCodec example。如您所见,它是一个在传递给它的表面上播放文件的线程。

注意线条

mExtractor = new MediaExtractor();
mExtractor.setDataSource(filePath);

看来我应该创建自己的 MediaExtractor,它不会从文件中提取视频单元,而是使用我提供的缓冲区中的 h264 NAL 单元。

然后我可以调用 mExtractor.setDataSource(MediaDataSource dataSource),请参阅 MediaDataSource

它有 readAt(long position, byte[] buffer, int offset, int size)

这是它读取 NAL 单元的地方。但是,我应该如何通过它们?我没有关于需要读取的缓冲区结构的信息。

我应该传递一个包含 NAL 单元的 byte[] buffer 吗?如果是,采用哪种格式?什么是偏移量?如果它是一个缓冲区,我不应该只擦除已读取的行,因此没有偏移量或大小吗?

顺便说一句,h264 NAL 单元是流媒体单元,它们来自 RTP 数据包,而不是文件。我将通过 C++ 获取它们并将它们存储在缓冲区中并尝试传递给 mediaExtractor。

更新:

我已经阅读了很多有关 MediaCodec 的资料,我想我对它的理解更好了。根据 https://developer.android.com/reference/android/media/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();

如您所见,我可以传递输入缓冲区并获取解码后的输出缓冲区。确切的字节格式仍然是个谜,但我认为这就是它的工作原理。同样根据同一篇文章,ByteBuffers 的使用速度很慢,Surfaces 是首选。它们自动消耗输出缓冲区。虽然没有关于如何操作的教程,但文章中有一节说它几乎相同,所以我想我只需要添加额外的行

 codec.setInputSurface(Surface inputSurface) 
 codec.setOutputSurface(Surface outputSurface) 

其中 inputSurfaceoutputSurfaceSurfaces which I pass to a MediaPlayer,我使用(如何)在 activity 中显示视频。并且输出缓冲区根本不会出现 onOutputBufferAvailable(因为 surface 首先消耗它们),也不会 onInputBufferAvailable.

所以现在的问题是:我究竟如何构建一个包含视频缓冲区的 Surface,以及如何将 MediaPlayer 显示到 activity

对于输出,我可以简单地创建一个 Surface 并传递给 MediaPlayerMediaCodec,但是输入呢?无论如何我都需要 ByteBuffer 作为输入,而 Surface 只是为了使用其他输出作为输入吗?

您首先需要删除 NAL 单元,并将原始 H264 字节输入到此方法中,无论您是从文件中读取什么,所以不需要删除任何东西,因为您不使用数据包,只需输入此方法的数据字节:

 rivate void initDecoder(){
    try {
        writeHeader = true;
        if(mDecodeMediaCodec != null){
            try{
                mDecodeMediaCodec.stop();
            }catch (Exception e){}
            try{
                mDecodeMediaCodec.release();
            }catch (Exception e){}
        }
        mDecodeMediaCodec = MediaCodec.createDecoderByType(MIME_TYPE);
       //MIME_TYPE = video/avc    in your case
        mDecodeMediaCodec.configure(format,mSurfaceView.getHolder().getSurface(),
                null,
                0);
        mDecodeMediaCodec.start();
        mDecodeInputBuffers = mDecodeMediaCodec.getInputBuffers();
    } catch (IOException e) {
        e.printStackTrace();
        mLatch.trigger();
    }
}


   private void decode(byte[] data){


            try {

                MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
                int inputBufferIndex = mDecodeMediaCodec.dequeueInputBuffer(1000);//
                if (inputBufferIndex >= 0) {
                    ByteBuffer buffer = mDecodeInputBuffers[inputBufferIndex];
                    buffer.clear();
                    buffer.put(data);
                    mDecodeMediaCodec.queueInputBuffer(inputBufferIndex,
                            0,
                            data.length,
                            packet.sequence / 1000,
                            0);

                    data = null;
                    //decodeDataBuffer.clear();
                    //decodeDataBuffer = null;
                }

                int outputBufferIndex = mDecodeMediaCodec.dequeueOutputBuffer(info,
                        1000);
                do {
                    if (outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
                        //no output available yet
                    } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                        //encodeOutputBuffers = mDecodeMediaCodec.getOutputBuffers();
                    } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                        format = mDecodeMediaCodec.getOutputFormat();
                        //mediaformat changed
                    } else if (outputBufferIndex < 0) {
                        //unexpected result from encoder.dequeueOutputBuffer
                    } else {

                        mDecodeMediaCodec.releaseOutputBuffer(outputBufferIndex,
                                true);

                        outputBufferIndex = mDecodeMediaCodec.dequeueOutputBuffer(info,
                                0);

                    }
                } while (outputBufferIndex > 0);

    }

请不要忘记 iFrame(第一帧字节)包含敏感数据,必须首先提供给解码器