Android MediaCodec медленнее в асинхронном режиме, чем в синхронном режиме?

#java #android #asynchronous #android-mediacodec

#java #Android #асинхронный #android-mediacodec

Вопрос:

Опять же, у меня есть вопрос относительно класса MediaCodec Android.

Мне успешно удалось декодировать необработанный контент h264 и отобразить результат в двух текстовых представлениях. Поток h264 поступает с сервера, на котором запущена сцена OpenGL.

Сцена имеет камеру и, следовательно, реагирует на ввод пользователя.

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

Вот как я настроил оба варианта: синхронный и асинхронный:

Асинхронный:

 //decoderCodec is "video/avc"
MediaFormat fmt = MediaFormat.createVideoFormat(decoderCodec, 1280,720);
codec.setCallback(new MediaCodec.Callback() {

    @Override
    public void onInputBufferAvailable(MediaCodec codec, int index) {
        byte[] frameData;
        try {
            frameData = frameQueue.take(); //this call is blocking
        } catch (InterruptedException e) {
            return;
        }

        ByteBuffer inputData = codec.getInputBuffer(index);
        inputData.clear();
        inputData.put(frameData);

        codec.queueInputBuffer(index, 0, frameData.length, 0, 0);
    }

    @Override
    public void onOutputBufferAvailable(MediaCodec codec, int index, MediaCodec.BufferInfo info) {
        codec.releaseOutputBuffer(index, true);
    }

     //The two other methods are left blank at the moment.

});


codec.configure(fmt, surface, null, 0);
codec.start();
  

Синхронизация: (настройка похожа на асинхронную, за исключением codec.setCallback(...) части. Класс, в котором находятся оба варианта, является подклассом Runnable .

 public void run() {

    while(!Thread.interrupted())
    {
        if(!IS_ASYNC) {
            byte[] frameData;
            try {
                frameData = frameQueue.take(); //this call is blocking
            } catch (InterruptedException e) {
                break;
            }

            int inIndex = codec.dequeueInputBuffer(BUFFER_TIMEOUT);

            if (inIndex >= 0) {
                ByteBuffer input = codec.getInputBuffer(inIndex);
                input.clear();
                input.put(frameData);
                codec.queueInputBuffer(inIndex, 0, frameData.length, 0, 0);
            }

            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
            int outIndex = codec.dequeueOutputBuffer(bufferInfo, BUFFER_TIMEOUT);

            if(outIndex >= 0)
                codec.releaseOutputBuffer(outIndex, true);
        }
        else sleep(3000); //Just for testing, if we are in Async, this thread has nothing to do actually...
    }
}
  

Оба подхода работают, но я замечаю, что видео, воспроизводимые в синхронном режиме, намного плавнее, а задержка также ниже.

Мне пришла в голову идея использовать асинхронный режим, потому frameQueue что is a LinkedBlockingDeque , и я рассудил, что если синхронный декодер будет слишком долго ждать поступления новых данных кадра, декодированный вывод может быть уже доступен, но не будет отображаться из-за блокирующего характера очереди. С другой стороны, я не хочу делать что-то вроде busy wait и постоянно опрашивать очередь, входные и выходные буферы.

Итак, я попробовал asyncMode, используя обратные вызовы, но результат, который я получаю, еще хуже, чем в синхронном режиме.

Вопрос, который у меня сейчас к вам, ребята, заключается в следующем:

Почему? Я неправильно использовал асинхронный режим? или это что-то еще?

Спасибо за любые отзывы!

Кристоф

Редактировать: ниже приведен обновленный код. Я только перечисляю обновленные части. Итак, как правильно указал @mstorsjo, виновником было то, что я ждал ввода дополнительных данных кадра onInputBufferAvailable() . Обновленная версия передает другой BlockingQueue с доступными индексами буфера. В дополнительном потоке мы ожидаем новых данных кадра И нового индекса буфера, чтобы поставить данные кадра в очередь для декодирования.

 public class DisplayThread implements Runnable {
    private BlockingQueue<Integer> freeInputBuffers;
    //skipped the uninteresting parts.

    private void initCodec(String decoderCodec) {       
        //skipped the uninteresting parts.
        codec.setCallback(new MediaCodec.Callback() {

            @Override
            public void onInputBufferAvailable(MediaCodec codec, int index) {
                freeInputBuffers.add(index);
            }

            //Dont care about the rest of the Callbacks for this demo...
        }
    }   

    @Override
    public void run() {
        while(!Thread.interrupted())
        {

            byte [] frameData;
            int inputIndex;

            try {
                frameData = frameQueue.take();
                //this was, indeed the culprit. We can wait in an additional thread for an buffer index to 
                // become free AND to get new frameData. When waiting in the callback, we will slow down 
                // the decoder.
                inputIndex = freeInputBuffers.take();
            } catch (InterruptedException e) {
                break;
            }

            ByteBuffer inputData = codec.getInputBuffer(inputIndex);
            inputData.clear();
            inputData.put(frameData);
            codec.queueInputBuffer(inputIndex, 0, frameData.length, 0, 0);      
        }

        codec.stop();
        codec.release();
    }
}
  

Комментарии:

1. Спасибо, чувак! Из-за этого у меня заканчивался ANR, и я не смог разобраться в этом … это решило это как по волшебству!

Ответ №1:

Я не удивлюсь, если onInputBufferAvailable причиной является блокирующий вызов. Кажется вероятным, что оба onInputBufferAvailable и onOutputBufferAvailable вызываются в одном потоке, и если вы блокируете в одном, вы останавливаете запуск другого.

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

Комментарии:

1. Вы были правы. Похоже, они вызываются из одного потока. Я обновлю свой вопрос рабочим кодом.

2. Пожалуйста, не обновляйте исходный вопрос новым фиксированным кодом — для любого, кто прочитает вопрос позже (о чем SO!), Это не будет иметь абсолютно никакого смысла, читая вопрос с фиксированным кодом. Если вы хотите поделиться фиксированным кодом, добавьте его, например, в виде отдельного раздела в конце вопроса, четко обозначенного как редактирование, с комментариями, в которых говорится, что это фиксированная версия.

3. @Christoph Работает ли ваш фиксированный код? вы поделились этим в каком-то месте?

4. @Christoph У меня тот же вопрос. Вы где-нибудь делились фиксированным кодом?