Проблемы использования 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, пока он все еще может выводить закодированный элементарный поток, но мультиплексор не может знать точный выходной формат.

Я хочу задать следующие вопросы:

  1. Зависит ли поведение кодировщика (дает MediaCodec.INFO_OUTPUT_FORMAT_CHANGED или нет перед выводом закодированных данных) от уровня Android API или чипов на отдельных устройствах?
  2. Если кодировщик записывает данные до появления MediaCodec.INFO_OUTPUT_FORMAT_CHANGED, есть ли способ получить выходной формат закодированных данных?
  3. Кодер по-прежнему выводит данные конфигурации кодека (с флагом 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.) Нужно ли задавать другие поля, которые могут повлиять на время презентации?


person Mark    schedule 11.03.2014    source источник
comment
Вы отправляете временные метки из экстрактора в декодер?   -  person Marlon    schedule 12.03.2014
comment
да. Я делаю это как образцы в тесте CTS, и я получаю точно правильное время выборки из экстрактора в качестве входных данных декодера, но много нулей на выходе декодера. См. этот вопрос.   -  person Mark    schedule 13.03.2014


Ответы (3)


Поведение кодировщиков MediaCodec было изменено в Android 4.3, чтобы учесть введение класса MediaMuxer. В Android 4.3 вы всегда будете получать INFO_OUTPUT_FORMAT_CHANGED от кодировщика. В предыдущих выпусках не будет. (Я обновил соответствующую запись часто задаваемых вопросов.)

Невозможно запросить кодировщик для MediaFormat.

Я не использовал мультиплексор на основе ffmpeg, поэтому не уверен, какая информация ему нужна. Если он ищет ключи csd-0/csd-1, вы можете извлечь их из пакета CODEC_CONFIG (я думаю, вам нужно разобрать значения SPS/PPS и поместить их в отдельные ключи). Изучение содержимого MediaFormat на устройстве 4.3 покажет вам, каких полей вам не хватает.

person fadden    schedule 11.03.2014
comment
Большое спасибо. Хотя это плохая новость для меня, эта информация очень важна. Я попробую извлечь ключи csd-0/csd-1. (Я видел их на устройстве 4.3, но пока не проверял их содержимое.) - person Mark; 11.03.2014

Чтобы правильно запустить мультиплексор 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;
  } 
}
person Marlon    schedule 12.03.2014

Для синтаксического анализа данных конфигурации кодека неверно предполагать, что последние 8 байтов являются данными PPS. Данные должны быть проанализированы в соответствии с начальным кодом и nal_unit_type.

person Mark    schedule 13.03.2014