#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 У меня тот же вопрос. Вы где-нибудь делились фиксированным кодом?