1

我正在尝试使用 MediaCodec 将帧(通过相机或解码器)编码为视频。通过dequeueOutputBuffer()处理编码器输出时,我期望收到返回index = 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,以及一个value = 1869968451的“what”字段。(我不明白“what”字段。它似乎是一个常数,不是必需的。谁能告诉我它的含义?)

如果我将编解码器配置数据解析为 csd-1 字段(最后 8 个字节)和 csd-0 字段(剩余字节),则复用器似乎可以正常工作并输出可在所有测试设备上播放的视频。(但我想问:这个 8 字节的假设是否正确,还是有更可靠的方法来解析数据?)

但是,我遇到了另一个问题,如果我再次通过 Android MediaCodec 解码视频,则 dequeueOutputBuffer() 获得的 BufferInfo.presentationTimeUs 值对于大多数解码帧都是 0。只有最后几帧有正确的时间。MediaExtractor.getSampleTime() 获得的采样时间是正确的,并且正是我为编码器/复用器设置的值,但解码的帧时间不是。此问题仅发生在 4.2.2 或更低版本的设备上。

奇怪的是帧时间不正确,但视频可以在设备上以正确的速度播放。(我测试的大多数 4.2.2 或更低版本的设备只有 1 个 Video AVC 解码器。)我是否需要设置可能影响演示时间的其他字段?

4

3 回答 3

2

编码器的行为MediaCodec在 Android 4.3 中进行了更改,以适应MediaMuxer类的引入。在 Android 4.3 中,您将始终收到INFO_OUTPUT_FORMAT_CHANGED来自编码器的信息。在以前的版本中,您不会。(我已经更新了相关的常见问题条目。)

无法查询编码器的MediaFormat.

我没有使用基于 ffmpeg 的多路复用器,所以我不确定它需要什么信息。如果它正在寻找 csd-0 / csd-1 键,您可以从CODEC_CONFIG数据包中提取它们(我认为您必须解析 SPS / PPS 值并将它们放在单独的键中)。检查MediaFormat4.3 设备上的内容将显示您缺少哪些字段。

于 2014-03-11T15:09:25.130 回答
0

对于codec config数据的解析,假设最后8个字节是PPS数据是错误的。数据必须根据起始码和nal_unit_type进行解析。

于 2014-03-13T11:06:55.963 回答
0

为了正确初始化视频的ffmpeg muxer,我接下来使用:

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;
  } 
}
于 2014-03-12T07:58:42.777 回答