Проблемы использования MediaCodec.getOutputFormat() для кодировщика на устройствах Android 4.1/4.2
Я пытаюсь использовать MediaCodec для кодирования кадров (с помощью камеры или декодера) в видео. При обработке вывода кодера с помощью dequeueOutputBuffer(), я ожидаю получить возвращаемый индекс = MediaCodec.INFO_OUTPUT_FORMAT_CHANGED, поэтому я могу вызвать getOutputFormat(), чтобы получить выходной формат кодера в качестве входных данных используемого в настоящее время мультиплексора ffmpeg.
Я протестировал некоторые планшетные / телефонные устройства с версией Android 4.1~4.3. Все они имеют как минимум один аппаратный видеокодер AVC и используются в тесте. На устройствах с версией 4.3 кодер выдает MediaCodec.INFO_OUTPUT_FORMAT_CHANGED перед записью закодированных данных, как и ожидалось, и формат вывода, возвращаемый из getOutputFormat(), может правильно использоваться мультиплексором. На устройствах с 4.2.2 или ниже кодер никогда не выдает MediaCodec.INFO_OUTPUT_FORMAT_CHANGED, хотя он все еще может выводить закодированный элементарный поток, но мультиплексор не может знать точный формат вывода.
Я хочу задать следующие вопросы:
- Зависит ли поведение кодировщика (дает MediaCodec.INFO_OUTPUT_FORMAT_CHANGED или нет до вывода закодированных данных) от уровня API Android или микросхем на отдельных устройствах?
- Если кодировщик записывает данные до появления MediaCodec.INFO_OUTPUT_FORMAT_CHANGED, есть ли способ получить выходной формат закодированных данных?
- Кодер по-прежнему выводит данные конфигурации кодека (с флагом MediaCodec.BUFFER_FLAG_CODEC_CONFIG) на устройства перед закодированными данными. Он в основном используется для настройки декодера, но можно ли получить выходной формат по данным конфигурации кодека?
Я пробовал эти решения, чтобы получить выходной формат, но не смог:
- Часто вызывайте getOutputFormat() в течение всего процесса кодирования. Однако все они генерируют исключение IllegalStateException без появления MediaCodec.INFO_OUTPUT_FORMAT_CHANGED.
Используйте начальное использование MediaFormat для настройки кодера в начале, как в примере:
m_init_encode_format = MediaFormat.createVideoFormat(m_encode_video_mime, m_frame_width, m_frame_height); int encode_bit_rate = 3000000; int encode_frame_rate = 15; int encode_iframe_interval = 2; m_init_encode_format.setInteger(MediaFormat.KEY_COLOR_FORMAT, m_encode_color_format); m_init_encode_format.setInteger(MediaFormat.KEY_BIT_RATE, encode_bit_rate); m_init_encode_format.setInteger(MediaFormat.KEY_FRAME_RATE, encode_frame_rate); m_init_encode_format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, encode_iframe_interval); m_encoder = MediaCodec.createByCodecName(m_video_encoder_codec_info.getName()); m_encoder.configure(m_init_encode_format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); // Assume m_init_encode_format is the output format of the encoder
Однако это не удается, поскольку выходной формат кодера все еще "изменен" по сравнению с исходным.
Пожалуйста, помогите мне понять поведение кодировщика, и если есть какое-либо решение для запроса выходного формата, если требуемый MediaCodec.INFO_OUTPUT_FORMAT_CHANGED отсутствует.
Сравнивая формат вывода и данные конфигурации кодека, пропускаются поля csd-0, csd-1 и поле "что" со значением = 1869968451. (Я не понимаю поле "что". постоянный и не обязательный. Может кто-нибудь рассказать мне о его значении?)
Если я проанализирую данные конфигурации кодека как поле csd-1 (последние 8 байтов) и поле csd-0 (оставшиеся байты), кажется, что мультиплексор может работать правильно и выводить видео, воспроизводимое на всех тестирующих устройствах. (Но я хочу спросить: верно ли это 8-байтовое предположение или есть более надежный способ анализа данных?)
Однако у меня возникла другая проблема: если я снова декодирую видео с помощью Android MediaCodec, значение BufferInfo.presentationTimeUs, полученное с помощью dequeueOutputBuffer(), будет равно 0 для большинства декодированных кадров. Только последние несколько кадров имеют правильное время. Время выборки, полученное с помощью MediaExtractor.getSampleTime(), является правильным и точно равно значению, которое я установил для кодера / мультиплексора, но время декодированного кадра - нет. Эта проблема возникает только на 4.2.2 или ниже.
Странно, что время кадра неправильное, но видео можно воспроизводить на устройстве с правильной скоростью. (Большинство протестированных мной устройств с 4.2.2 или ниже имеет только 1 Video AVC декодер.) Нужно ли устанавливать другие поля, которые могут повлиять на время презентации?
3 ответа
Поведение MediaCodec
Кодеры были изменены в Android 4.3, чтобы приспособить введение MediaMuxer
учебный класс. В Android 4.3 вы всегда будете получать INFO_OUTPUT_FORMAT_CHANGED
от кодировщика. В предыдущих выпусках вы не будете. (Я обновил соответствующую запись FAQ.)
Нет способа запросить кодировщик для MediaFormat
,
Я не использовал муксер на основе ffmpeg, поэтому я не уверен, какая информация ему нужна. Если он ищет ключи csd-0 / csd-1, вы можете извлечь их из CODEC_CONFIG
пакет (я думаю, что вы должны разобрать значения SPS / PPS и поместить их в отдельные ключи). Изучение содержимого MediaFormat
на устройстве 4.3 покажет вам, какие поля вам не хватает.
Для анализа данных конфигурации кодека неправильно, если предположить, что последние 8 байтов являются данными PPS. Данные должны быть проанализированы согласно стартовому коду и nal_unit_type.
Чтобы правильно инициализировать мультиплексор ffmpeg для видео, я использую следующее:
int outputBufferIndex = videoCodec.dequeueOutputBuffer(bufferInfo, -1);
if (MediaCodec.BUFFER_FLAG_CODEC_CONFIG == bufferInfo.flags) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
headerData = new byte[bufferInfo.size];
outputBuffer.get(headerData);
// jni call
WriteVideoHeader(headerData, headerData.length);
videoCodec.releaseOutputBuffer(outputBufferIndex, false);
}
В JNI я использую что-то вроде этого:
jint Java_com_an_FileWriterEx_WriteVideoHeader(JNIEnv * env, jobject this, jbyteArray data, jint datasize)
{
jboolean isCopy;
jbyte* rawjBytes = (*env)->GetByteArrayElements(env, data, &isCopy);
WriteVideoHeaderInternal(env, m_pFormatCtx, m_pVideoStream, rawjBytes, datasize);
(*env)->ReleaseByteArrayElements(env, data, rawjBytes, 0);
return 0;
}
jint WriteVideoHeaderInternal(JNIEnv * env, AVFormatContext* pFormatCtx, AVStream* pVideoStream, jbyte* data, jint datasize)
{
jboolean bNoError = JNI_TRUE;
jbyte* pExtDataBuffer = av_malloc(datasize);
if(!pExtDataBuffer)
{
LOGI("av alloc error\n");
bNoError = JNI_FALSE;
}
if (bNoError)
{
memcpy(pExtDataBuffer, data, datasize * sizeof(jbyte));
pVideoStream->codec->extradata = pExtDataBuffer;
pVideoStream->codec->extradata_size = datasize;
}
}