Проблемы использования 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 или нет до вывода закодированных данных) от уровня API Android или микросхем на отдельных устройствах?
  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 декодер.) Нужно ли устанавливать другие поля, которые могут повлиять на время презентации?

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;
  } 
}
Другие вопросы по тегам