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

问题描述 投票:13回答:1

我最初尝试过How to play raw NAL units in Andoid exoplayer?,但我注意到我将不得不做低级别的事情。

我发现了这个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,我传递给MediaPlayer,我使用(如何)在一个活动中显示视频。输出缓冲区根本不会出现在onOutputBufferAvailable上(因为surface首先消耗它们),而不是onInputBufferAvailable

现在问题是:我究竟如何构建包含视频缓冲区的Surface,以及如何在活动中显示MediaPlayer

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

android video h.264 codec
1个回答
0
投票

你首先需要删除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(第一帧字节)包含敏感数据并且必须首先馈送到解码器

© www.soinside.com 2019 - 2024. All rights reserved.