Кодировщик MediaCodec H264 не работает на устройствах Snapdragon 800

Я написал потоковый кодировщик H264, используя API MediaCodec для Android. Я протестировал его примерно на десяти разных устройствах с разными процессорами, и он работал на всех из них, кроме устройств с процессором Snapdragon 800 (Google Nexus 5 и Sony Xperia Z1). На этих устройствах я получаю SPS и PPS и первый ключевой кадр, но после этого mEncoder.dequeueOutputBuffer(mBufferInfo, 0) возвращает только MediaCodec.INFO_TRY_AGAIN_LATER. Я уже экспериментировал с разными таймаутами, битрейтами, разрешениями и другими параметрами конфигурации, но безрезультатно. Результат всегда один и тот же.

Я использую следующий код для инициализации кодировщика:

        mBufferInfo = new MediaCodec.BufferInfo();
        encoder = MediaCodec.createEncoderByType("video/avc");
        MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", 640, 480);
        mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, 768000);
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
        mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, mEncoderColorFormat);
        mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);
        encoder.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);

где выбранный цветовой формат:

MediaCodecInfo.CodecCapabilities capabilities = mCodecInfo.getCapabilitiesForType(MIME_TYPE);
            for (int i = 0; i < capabilities.colorFormats.length && selectedColorFormat == 0; i++)
            {
                int format = capabilities.colorFormats[i];
                switch (format) {
                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
                    case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
                    case MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar:
                        selectedColorFormat = format;
                        break;
                    default:
                        LogHandler.e(LOG_TAG, "Unsupported color format " + format);
                        break;
                }
            }

И я получаю данные, выполняя

            ByteBuffer[] inputBuffers = mEncoder.getInputBuffers();
        ByteBuffer[] outputBuffers = mEncoder.getOutputBuffers();

        int inputBufferIndex = mEncoder.dequeueInputBuffer(-1);
        if (inputBufferIndex >= 0)
        {
            // fill inputBuffers[inputBufferIndex] with valid data
            ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
            inputBuffer.clear();
            inputBuffer.put(rawFrame);
            mEncoder.queueInputBuffer(inputBufferIndex, 0, rawFrame.length, 0, 0);
            LogHandler.e(LOG_TAG, "Queue Buffer in " + inputBufferIndex);
        }

        while(true)
        {
            int outputBufferIndex = mEncoder.dequeueOutputBuffer(mBufferInfo, 0);
            if (outputBufferIndex >= 0)
            {
                Log.d(LOG_TAG, "Queue Buffer out " + outputBufferIndex);
                ByteBuffer buffer = outputBuffers[outputBufferIndex];
                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0)
                {
                    // Config Bytes means SPS and PPS
                    Log.d(LOG_TAG, "Got config bytes");
                }

                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0)
                {
                    // Marks a Keyframe
                    Log.d(LOG_TAG, "Got Sync Frame");
                }

                if (mBufferInfo.size != 0)
                {
                    // adjust the ByteBuffer values to match BufferInfo (not needed?)
                    buffer.position(mBufferInfo.offset);
                    buffer.limit(mBufferInfo.offset + mBufferInfo.size);

                    int nalUnitLength = 0;
                    while((nalUnitLength = parseNextNalUnit(buffer)) != 0)
                    {
                        switch(mVideoData[0] & 0x0f)
                        {
                            // SPS
                            case 0x07:
                            {
                                Log.d(LOG_TAG, "Got SPS");
                                break;
                            }

                            // PPS
                            case 0x08:
                            {
                                Log.d(LOG_TAG, "Got PPS");
                                break;
                            }

                            // Key Frame
                            case 0x05:
                            {
                                Log.d(LOG_TAG, "Got Keyframe");
                            }

                            //$FALL-THROUGH$
                            default:
                            {
                                // Process Data
                                break;
                            }
                        }
                    }
                }

                mEncoder.releaseOutputBuffer(outputBufferIndex, false);

                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0)
                {
                    // Stream is marked as done,
                    // break out of while
                    Log.d(LOG_TAG, "Marked EOS");
                    break;
                }
            }
            else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED)
            {
                outputBuffers = mEncoder.getOutputBuffers();
                Log.d(LOG_TAG, "Output Buffer changed " + outputBuffers);
            }
            else if(outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED)
            {
                MediaFormat newFormat = mEncoder.getOutputFormat();
                Log.d(LOG_TAG, "Media Format Changed " + newFormat);
            }
            else if(outputBufferIndex == MediaCodec.INFO_TRY_AGAIN_LATER)
            {
                // No Data, break out
                break;
            }
            else
            {
                // Unexpected State, ignore it
                Log.d(LOG_TAG, "Unexpected State " + outputBufferIndex);
            }
        }

Спасибо за вашу помощь!


person lowtraxx    schedule 09.12.2013    source источник
comment
Сколько входных кадров стоит в очереди в момент остановки вывода? (Я хочу убедиться, что он не просто жаждет ввода.) Есть ли что-то подозрительное в logcat? (Кодеки, как правило, распыляют Log.e, что затрудняет определение.) Какой цветовой формат выбран? (Это формат QCOM?) Размер необработанного кадра точно такой же, как емкость входного буфера? (Если нет... почему бы и нет?)   -  person fadden    schedule 09.12.2013
comment
@fadden Неважно, как долго я позволяю ему работать, но кажется, что во входных буферах всегда есть 5 кадров. Его вывод после создания: I/OMXClient(11245): Using client-side OMX mux. I/ACodec(11245): setupVideoEncoder succeeded Выбранный цветовой формат в обоих случаях MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar (Если я запрашиваю все форматы, у него есть только два, вышеупомянутый и тот, который имеет константу 2130708361, которая вылетает, если выбрана.) Необработанный кадр и входной буфер не то же самое (размер необработанного кадра всегда меньше, а емкость входного буфера всегда 282624)   -  person lowtraxx    schedule 10.12.2013
comment
Типично пять кадров - похоже, что он не обрабатывает ввод, следовательно, нет вывода. Я полагаю, вы звоните encoder.start()? YUV420SemiPlanar хорош; 2130708361 используется только для ввода с поверхности. Размер буфера YUV420 должен быть width * height * 1.5 или 460800 байт, поэтому я немного запутался в размере вашего буфера. Видите ли вы сообщение Media Format Changed в файле журнала, и если да, то что оно говорит?   -  person fadden    schedule 10.12.2013
comment
@fadden да, я звоню encoder.start(). Разрешение было другим для следов в моем ответе. Размер rawFrame равен width * height * 1.5, но размер буфера всегда немного больше. Я не получил сообщение об изменении формата.   -  person lowtraxx    schedule 11.12.2013
comment
Я не уверен, что еще попробовать - код, вставленный выше, выглядит нормально. Наилучший подход отсюда может заключаться в том, чтобы взять что-то похожее, о котором известно, что оно работает (скажем, код EncodeDecodeTest буфера в буфер), заставить его работать в вашем приложении, а затем постепенно изменить его, чтобы оно выглядело как ваша реализация.   -  person fadden    schedule 11.12.2013
comment
@fadden Хорошо, я попробую и вернусь к вам с моими результатами. В очередной раз благодарим за помощь.   -  person lowtraxx    schedule 12.12.2013


Ответы (2)


Вам нужно установить параметр PresentationTimeUs в вызове queueInputBuffer. Большинство кодировщиков игнорируют это, и вы можете кодировать для потоковой передачи без проблем. Кодировщик, используемый для устройств Snapdragon 800, этого не делает.

Этот параметр представляет собой время записи вашего кадра, поэтому его необходимо увеличить на количество нас между кадром, который вы хотите закодировать, и предыдущим кадром.

Если набор параметров имеет то же значение, что и в предыдущем кадре, кодер отбрасывает его. Если параметр установлен на слишком маленькое значение (например, 100000 при записи 30 кадров в секунду), качество закодированных кадров падает.

person Markus S    schedule 12.12.2013
comment
Хм. Отметка времени представления не является частью элементарного потока H.264, поэтому я ожидал, что значение будет просто передано. Я добавил новую запись часто задаваемых вопросов (bigflake.com/mediacodec/#q8). - person fadden; 18.12.2013
comment
Не могли бы вы привести пример того, как установить время презентации? - person Stefan Alexandru; 12.08.2014
comment
Параметр TimeUs в вашем вызове для значения queueInputBuffer для устройств Snapdragon 800 - person DreamCoder; 19.12.2014

encodeCodec.queueInputBuffer(inputBufferIndex, 0, input.length, (System.currentTimeMillis() - startMs) * 1000, 0);

person Mike Ke    schedule 15.01.2014
comment
Использование текущего времени допустимо, если вы получаете входные данные в реальном времени (например, с камеры), но будет плохо работать, если вы работаете с другими источниками (например, перекодируете видео быстрее, чем в реальном времени). Я также не рекомендую System.currentTimeMillis(), так как он подвержен внезапным скачкам (вперед и назад). Монотонный System.nanoTime() - лучший источник. - person fadden; 14.04.2014
comment
Даже в случае перекодирования временные метки исходного контента будут применимы (на основе частоты кадров исходного контента). Кодер должен знать метки времени, чтобы иметь возможность управлять контролем скорости. Итак, если вы настроили частоту кадров с помощью mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FPS), рекомендуется генерировать метки времени (N * 1000 * 1000/FPS) для кодирования не в реальном времени. - person peasea; 16.04.2014
comment
@fadden, нужно ли мне устанавливать время презентации для экземпляра асинхронного кодировщика MediaCodec? ffmpeg не может видеть время презентации для видеокадров, когда я декодирую вывод h264. Пожалуйста, посоветуй мне - person gbenroscience; 14.04.2020