Android:如何使用MediaMuxer与video/mp4v-es而不是video/avc?

3

我希望能在某些设备上使用 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来说不需要。谢谢。


新格式中是否有csd-0和csd-1? - Marlon
1
@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 中。 - user1592546
似乎需要csd-1。如果不存在,则MediaMuxer无法停止。 - Marlon
1
编码器MPEG4Writer,我认为没有必要为csd准备2个缓冲区。 MPEG4Writer只能处理一个缓冲区。当没有CSD时,会出现错误,即“缺少编解码器特定数据”。对于video/mp4v-esMPEG4视频基本流,MPEG4Writer期望将数据打包为ESDS格式,而不是像这里一样使用AVCC:http://androidxref.com/5.0.0_r2/xref/frameworks/av/media/libstagefright/MPEG4Writer.cpp#1466 - Ganesh
1
当创建新的“Track”时,会读取“csd”(参考上文的MPEG4Writer.cpp,第1370行)。对于该轨道,视频编码器是其来源,因此您的编码器应支持getFormat,其中数据应以ESDS格式打包。 - Ganesh
显示剩余2条评论
2个回答

2

正如Ganesh所指出的那样,很遗憾现在似乎没有办法在不修改平台源代码的情况下实现此功能。

实际上,有两种方法可以将编解码器特定数据传递给内部的MPEG4Writer类,但是如果不进行修改,这两种方法都无法正常工作。

正如Ganesh所发现的那样,用于将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.

据我所知,目前在使用公共API时,没有办法使用MediaMuxer混合MPEG4视频,而不修改平台源代码。鉴于上面Utils.cpp中的问题,您无法混合任何需要编解码器特定数据的视频格式,除了H264。如果VP8是一个选项,您可以将其混合到webm文件中(与vorbis音频一起),但是VP8的硬件编码器可能比MPEG4的硬件编码器少得多。

此问题已在 http://b.android.com/90138 上报告,并已在 https://android-review.googlesource.com/120945 提交修复。 - mstorsjo
感谢您报告此问题并将其修复到主流代码库中。 - Ganesh
1
我的修复至少可以干净地合并到他们的内部代码库中(根据几天前的构建机器人),所以我认为他们还没有对此进行任何修复。但是,通常需要几个月时间才能对通过审查系统发送的补丁做出反应。 - mstorsjo
1
我的修复已经在 https://android-review.googlesource.com/120945 中合并到了 AOSP 主分支,因此它有望成为下一个主要版本的一部分。 - mstorsjo
您的更改已被标记为 android-m-preview。请检查:https://android.googlesource.com/platform/frameworks/av/+log/1d82e69dffe9214fd35d0ec75ce9b957da7fc50b - Ganesh
显示剩余4条评论

2

我假设您有修改Stagefright源代码的能力,因此,我为您提出了一个解决方案,但需要进行定制。

背景:

当编码器完成编码时,第一个缓冲区将具有带有OMX_BUFFERFLAG_CODECCONFIG标志的csd信息。当这样的缓冲区返回到MediaCodec时,它将把其存储为MediaCodec::amendOutputFormatWithCodecSpecificData中的csd-0

现在,当将此缓冲区提供给MediaMuxer时,它将作为addTrack的一部分进行处理,在其中调用convertMessageToMetadata。如果您参考该方法的实现,我们可以观察到只处理videoAVC并默认为audio以创建ESDS

编辑:

在这里,我建议您将此行修改为下面的内容并尝试您的实验。

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

通过这个改变,我认为它也应该适用于 MPEG4 视频轨道。 改变是将 else if 转换为 if,因为以前对于 video 的检查也会尝试处理数据,但仅适用于 AVC

不会在第552行代码处编写:"if (mime.startsWith("video/")) {"获取所有视频缓冲区吗?无论如何,我需要能够使用MediaCodec公共API导出mp4v-es(以便根据需要操作缓冲区内容),但不要求使用jni和本地代码。 - user1592546
@user1592546..是的,你说得对。我们可以通过在else if中删除else来克服这个问题,以便进行两次检查。为了优化此过程,在第一个video检查中,我们可以添加另一个部分来验证MIME类型是否为AVC。请参见我上面编辑过的答案。 - Ganesh
1
我会接受mstorsjo的答案,以便完整性、进一步参考和提供完整解决方案的链接。无论如何,这个答案为问题提供了更多的见解,值得赞赏。 - user1592546
@user1592546,很好。我很高兴你的问题得到了解决。 - Ganesh
好吧,它仍然不能按照我想要的方式工作 :) 即只有Java端编码,但我必须接受这一点。 - user1592546

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接