Как играть необработанные NAL-единицы в Android MediaCodec - PullRequest
0 голосов
/ 08 июня 2018

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

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

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

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

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

Затем я могу позвонить 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();

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

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

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

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

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

1 Ответ

0 голосов
/ 21 июля 2018

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

...