Как играть в сырые блоки NAL в Android MediaCodec

Я изначально пробовал Как играть в сырые NAL-подразделения в Andoid exoplayer? но я заметил, что мне нужно делать что-то на низком уровне.

Я нашел этот простой пример MediaCodec. Как вы можете видеть, это поток, который воспроизводит файл на поверхности, переданной ему.

Обратите внимание на строки

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

Похоже, что я должен создать свой собственный MediaExtractor который вместо извлечения видеоустройств из файла будет использовать единицы NAL h264 из буфера, который я предоставил.

Затем я могу вызвать mExtractor.setDataSource(MediaDataSource dataSource), см. MediaDataSource

Он имеет readAt(long position, byte[] buffer, int offset, int size)

Здесь читаются единицы NAL. Однако, как я должен их передавать? У меня нет информации о структуре буфера, который нужно прочитать.

Должен ли я передавать byte[] buffer с единицами NAL в нем, и если да, то в каком формате? Для чего это смещение? Если это буфер, не следует ли просто стирать строки, которые были прочитаны и, следовательно, не имеют смещения или размера?

Кстати, блоки 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();

Как вы можете видеть, я могу передавать входные буферы и получать декодированные выходные буферы. Точные форматы байтов все еще являются mistery, но я думаю, что это работает. Также, согласно той же статье, использование ByteBuffer происходит медленно, а Surface - предпочтительным. Они автоматически потребляют выходные буферы. Хотя нет учебника о том, как это сделать, есть раздел в статье, который говорит, что он почти идентичен, поэтому я думаю, мне просто нужно добавить дополнительные строки

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

Где inputSurface и outputSurface - это Surface, которые я outputSurface MediaPlayer, который я использую (как) для отображения видео в активности. И выходные буферы просто не будут onOutputBufferAvailable (потому что surface их потребляет в первую очередь), ни onInputBufferAvailable.

Итак, теперь вопросы: как именно я создаю Surface который содержит видео-буфер, и как отображать MediaPlayer в активности

Для вывода я могу просто создать Surface и перейти к MediaPlayer и MediaCodec, но как насчет ввода? Нужно ли мне ByteBuffer для ввода в любом случае, и Surface будет просто использовать другие выходы в качестве входных данных?

Ответ 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 (первые байты кадра) содержит конфиденциальные данные и ДОЛЖЕН быть сначала подан в декодер