Android: как использовать MediaMuxer с видео/mp4v-es вместо видео/avc?

Я хочу иметь возможность использовать mp4v-es вместо avc на некоторых устройствах. Кодировщик нормально работает с avc, но когда я заменяю его на mp4v-es, мультиплексор сообщает:

E/MPEG4Writer(12517): Missing codec specific data

как в Ошибка MediaMuxer Не удалось остановить мультиплексор, и видео не может играть. Разница в том, что я добавляю правильную дорожку/формат в мультиплексор, не получая никакой ошибки:

...else if (encoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
           MediaFormat newFormat = encoder.getOutputFormat();
           mTrackIndex[encID] = mMuxer.addTrack(newFormat);

Есть ли разница в обработке mp4v-es по сравнению с avc? Одно упоминание, я просто пропускаю «bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG», когда это происходит, так как для avc это не нужно. Спасибо.


person user1592546    schedule 04.12.2014    source источник
comment
Есть ли в newFormat csd-0 и csd-1?   -  person Marlon    schedule 05.12.2014
comment
@Marlon: новый формат: {height=720, mime=video/mp4v-es, csd-0=java.nio.ByteArrayBuffer[position=0,limit=30,capacity=30], what=1869968451, width=1280 } и csd-0: 000001B006000001B58913000001000000012000C48881F4528045A1463F. csd-1 нету, но думаю появляется только для H264.   -  person user1592546    schedule 05.12.2014
comment
stackoverflow .com/questions/21341169/ похоже, что может потребоваться csd-1   -  person Marlon    schedule 05.12.2014
comment
Я не думаю, что от encoder до MPEG4Writer обязательно иметь 2 буфера для csd. MPEG4Writer может обрабатывать только один буфер. Ошибка т.е. Missing codec specific data появляется, когда нет CSD. Для элементарного видеопотока video/mp4v-es, т.е. MPEG4, MPEG4Writer ожидает, что данные будут упакованы в формате ESDS по сравнению с AVCC, как здесь: androidxref.com/5.0.0_r2/xref/frameworks/av/media/   -  person Ganesh    schedule 15.12.2014
comment
csd читается при создании нового Track (ссылка: MPEG4Writer.cpp выше, строка № 1370). Для дорожки видеокодер является источником, поэтому ваш кодировщик должен поддерживать getFormat, в котором данные должны быть упакованы в формате ESDS.   -  person Ganesh    schedule 15.12.2014
comment
@Ganesh: Пожалуйста, разработайте это как ответ, сделав акцент на упаковке данных заголовка mpeg4 в формате ESDS, и я приму это.   -  person user1592546    schedule 18.12.2014
comment
@Ganesh: у MediaFormat есть метод setByteBuffer(csd-0,..). Если я правильно понял ваш комментарий, в случае, когда кодировщик возвращает элементарный битовый поток видео mpeg4 для заголовков (как он это делает), этот элементарный битовый поток должен быть упакован как ESDS, записан обратно в csd-0 с помощью метода выше, а затем MediaFormat объект, предоставленный addTrack.   -  person user1592546    schedule 18.12.2014


Ответы (2)


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

На самом деле существует два способа передачи конкретных данных кодека во внутренний класс MPEG4Writer, но ни один из них не работает без изменений.

Как обнаружил Ганеш, в логике переназначения ключей MediaFormat на внутренний формат отсутствует обработка данных, специфичных для кодека, для любого другого видеокодека, кроме H264. Протестированная модификация, устраняющая эту проблему, выглядит следующим образом:

diff --git a/media/libstagefright/Utils.cpp b/media/libstagefright/Utils.cpp
index 25afc5b..304fe59 100644
--- a/media/libstagefright/Utils.cpp
+++ b/media/libstagefright/Utils.cpp
@@ -549,14 +549,14 @@ void convertMessageToMetaData(const sp<AMessage> &msg, sp<MetaData> &meta) {
     // reassemble the csd data into its original form
     sp<ABuffer> csd0;
     if (msg->findBuffer("csd-0", &csd0)) {
-        if (mime.startsWith("video/")) { // do we need to be stricter than this?
+        if (mime == MEDIA_MIMETYPE_VIDEO_AVC) {
             sp<ABuffer> csd1;
             if (msg->findBuffer("csd-1", &csd1)) {
                 char avcc[1024]; // that oughta be enough, right?
                 size_t outsize = reassembleAVCC(csd0, csd1, avcc);
                 meta->setData(kKeyAVCC, kKeyAVCC, avcc, outsize);
             }
-        } else if (mime.startsWith("audio/")) {
+        } else if (mime == MEDIA_MIMETYPE_AUDIO_AAC || mime == MEDIA_MIMETYPE_VIDEO_MPEG4) {
             int csd0size = csd0->size();
             char esds[csd0size + 31];
             reassembleESDS(csd0, esds);

Во-вторых, вместо того, чтобы передавать специфичные для кодека данные как csd-0 в MediaFormat, вы могли бы, в принципе, передать тот же буфер (с установленным флагом MediaCodec.BUFFER_FLAG_CODEC_CONFIG) в MediaMuxer.writeSampleData. Этот подход в настоящее время не работает, так как этот метод вообще не проверяет флаг конфигурации кодека — это может быть исправлено с помощью этой модификации:

diff --git a/media/libstagefright/MediaMuxer.cpp b/media/libstagefright/MediaMuxer.cpp
index c7c6f34..d612e01 100644
--- a/media/libstagefright/MediaMuxer.cpp
+++ b/media/libstagefright/MediaMuxer.cpp
@@ -193,6 +193,9 @@ status_t MediaMuxer::writeSampleData(const sp<ABuffer> &buffer, size_t trackInde
     if (flags & MediaCodec::BUFFER_FLAG_SYNCFRAME) {
         sampleMetaData->setInt32(kKeyIsSyncFrame, true);
     }
+    if (flags & MediaCodec::BUFFER_FLAG_CODECCONFIG) {
+        sampleMetaData->setInt32(kKeyIsCodecConfig, true);
+    }

     sp<MediaAdapter> currentTrack = mTrackList[trackIndex];
     // This pushBuffer will wait until the mediaBuffer is consumed.

Насколько я вижу, прямо сейчас нет возможности мультиплексировать видео MPEG4 с помощью MediaMuxer при использовании общедоступного API без изменения исходного кода платформы. Учитывая проблемы в Utils.cpp выше, вы не можете мультиплексировать любой формат видео, для которого требуются данные, специфичные для кодека, за исключением H264. Если VP8 является вариантом, вы можете мультиплексировать его в файлы webm (вместе с аудио vorbis), но аппаратные кодировщики для VP8, вероятно, гораздо менее распространены, чем аппаратные кодировщики для MPEG4.

person mstorsjo    schedule 27.12.2014
comment
Об этой проблеме сообщалось на странице b.android.com/90138, а исправление было отправлено на android-review.googlesource.com/120945. - person mstorsjo; 27.12.2014
comment
Спасибо, что сообщили об этой проблеме и исправили ее в основном дереве. - person Ganesh; 05.01.2015
comment
Ну, фикс еще не слит. На самом деле я нашел 2 разных изменения, ожидающих рассмотрения, которые делают почти то же самое, которые были отправлены другими людьми (по-видимому, не связанными с этим сообщением здесь), но которые никто еще не объединил или даже не прокомментировал. - person mstorsjo; 05.01.2015
comment
Ну... нам придется связаться с экспертами Google по этому вопросу. Я не уверен, что то же самое уже было интегрировано во внутреннее дерево. - person Ganesh; 05.01.2015
comment
Мое исправление, по крайней мере, полностью сливается с их внутренним деревом (согласно билдботу несколько дней назад), поэтому я не думаю, что у них еще есть какое-либо исправление для него. Но часто может пройти несколько месяцев, прежде чем они отреагируют на исправления, отправленные через систему проверки. - person mstorsjo; 05.01.2015
comment
Что ж.. будем надеяться на лучшее. Я буду продолжать следить за этим решением через вашу ошибку и патч Gerrit. - person Ganesh; 05.01.2015
comment
Мое исправление в android-review.googlesource.com/120945 теперь объединено с мастером AOSP, так что, надеюсь, он станет частью следующего крупного релиза. - person mstorsjo; 20.05.2015
comment
Хорошая работа, Мартин... Очень хорошо, что изменения произошли внутри AOSP... Отличное продолжение... - person Ganesh; 22.05.2015
comment
Ваше изменение помечено для android-m-preview.. проверьте: android.googlesource.com /платформа/фреймворки/av/+журнал/ - person Ganesh; 03.06.2015

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

Предыстория:

Когда encoder завершает кодирование, первый буфер будет содержать информацию csd, которая обычно помечается флагом OMX_BUFFERFLAG_CODECCONFIG. Когда такой буфер возвращается в MediaCodec, он должен хранить то же, что и csd-0 в MediaCodec::amendOutputFormatWithCodecSpecificData.

Теперь, когда этот буфер передается MediaMuxer, он обрабатывается как часть addTrack, в котором convertMessageToMetadata вызывается. Если вы обратитесь к реализации того же самого, мы можем заметить, что только AVC обрабатывается для video и по умолчанию используется audio для создания ESDS.

ИЗМЕНИТЬ:

Здесь я рекомендую изменить эту строку как показано ниже и попробуйте свой эксперимент

} 
if (mime.startsWith("audio/") || (!strcmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4)) {

С этим изменением я чувствую, что оно должно работать и для видеодорожки MPEG4. Изменение заключается в преобразовании else if в if, так как предыдущая проверка для video также попытается обработать данные, но только для AVC.

person Ganesh    schedule 21.12.2014
comment
Не будет кода в строке 552: if (mime.startsWith(video/)) { получить все видеобуферы? В любом случае, мне нужно было бы иметь возможность экспортировать mp4v-es с помощью общедоступного API MediaCodec (чтобы я мог манипулировать содержимым буфера по мере необходимости), но не прибегать к jni и собственному коду. - person user1592546; 22.12.2014
comment
@ user1592546 .. Да, ты прав. Мы можем преодолеть это, удалив else в else if, чтобы было 2 проверки. Чтобы оптимизировать это, для первой проверки video мы могли бы добавить еще одну часть, чтобы проверить, является ли тип MIME AVC или нет. Пожалуйста, смотрите мой отредактированный ответ выше. - person Ganesh; 30.12.2014
comment
Я приму ответ mstorsjo для полноты, дополнительной ссылки и предоставления ссылки на полное решение. В любом случае, этот ответ дал больше понимания проблемы и приветствуется. - person user1592546; 05.01.2015
comment
@ user1592546 .. Все в порядке. Я рад, что ваша проблема решена. - person Ganesh; 05.01.2015
comment
Ну, это все еще не работает так, как я хочу :) т.е. только кодирование на стороне java, но я должен принять это. - person user1592546; 08.01.2015