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

13

我最初尝试了如何在Andoid exoplayer中播放原始的NAL单元?,但我注意到我必须以低级别进行操作。

我找到了这个简单的MediaCodec示例。正如您所看到的,它是一个线程,可以在传递给它的表面上播放文件。

请注意这些行。

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();

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

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

在此情况下,我将inputSurfaceoutputSurface作为Surface传递给MediaPlayer,然后使用它来在活动中显示视频。但是输出缓冲区不会在onOutputBufferAvailable(因为surface首先使用它们),也不会在onInputBufferAvailable中出现。

现在的问题是:我应该如何构造包含视频缓冲区的Surface,并将MediaPlayer显示到活动中

对于输出,可以简单地创建一个Surface并将其传递给MediaPlayerMediaCodec,但输入呢?我是否仍然需要ByteBuffer输入,并且Surface只是用作其他输出的输入?


您不需要使用MediaExtractor。 您所需要做的就是去除RTP头和起始码,然后将数据复制到MediaCodec的输入缓冲区中。 - Tomer Nuni
@TomerNuni 我应该使用哪种方法?我在MediaDataSource中只找到了readAt(long position, byte[] buffer, int offset, int size) - PPP
你需要将原始数据复制到 mediaCodec 的输入缓冲区中。通过将输出表面设置为输出表面,您可以直接渲染到表面,无需使用 MediaPlayer。 - Tomer Nuni
@TomerNuni 我需要使用哪个活动视图对象?它有办法将一个表面输入进去吗? - PPP
你能使用 https://developer.android.com/reference/android/view/TextureView 吗? - Tomer Nuni
1个回答

1

首先需要移除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(第一帧字节)包含敏感数据,必须首先输入解码器。

实际上,我需要从 RTP 数据包中提取 NAL 单元。整个 RTP 负载是一个 NAL 单元。我只需将其输入缓冲区吗? - PPP
此外,这是某个类的子类吗?我看到decode是私有的,但没有看到任何人调用它。 - PPP

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接