在android中解码原始H264流?
Posted
技术标签:
【中文标题】在android中解码原始H264流?【英文标题】:Decoding Raw H264 stream in android? 【发布时间】:2012-10-29 17:09:06 【问题描述】:我有一个项目,要求我在 android 中显示视频流,该流是原始 H.264,我正在连接到服务器并将从服务器接收字节流。
基本上我想知道有没有办法将原始字节发送到 android 中的解码器并将其显示在表面上?
我已经成功地使用 android 4.1 中的新 MediaCodec 和 MediaExtractor API 解码包装在 mp4 容器中的 H264,不幸的是,我还没有找到使用这些 API 解码原始 H264 文件或流的方法。
我知道一种方法是编译和使用 FFmpeg,但我宁愿使用可以使用硬件加速的内置方法。我也了解 android 支持 RTSP 流,但这不是一个选项。 Android 版本不是问题。
【问题讨论】:
为什么不使用 BitmapFactory decodeByteArray 或 decodeFile。根据developer.android.com/guide/appendix/media-formats.html#core,它应该支持 h.264 这是 1 年前提出的,已经有一个使用 Android MediaCodec API 的解决方案,该 API 是为解码视频而设计的。我真的怀疑 BitmapFactory 可以解码 h264 视频。 我也不这么认为。事实上,我认为任何内置类都没有帮助。我现在正在研究 ffmpeg 嗯,我的意思是我能够使用我在回答中描述的方法播放原始 h264,只要你先给它正确的视频数据来配置解码器,它就应该可以工作。 【参考方案1】:很遗憾,我无法为此提供任何代码,但我会尽力根据我的工作原理来解释它。
以下是我如何使用 MediaCodec 类使原始 H.264 编码视频工作的概述。
使用上面的链接有一个获取解码器设置的示例以及如何使用它,您需要设置它以解码 H264 AVC。
H.264 的格式是它由 NAL 单元组成,每个单元都以三个字节的起始前缀开头,值为 0x00、0x00、0x01,每个单元根据第 4 字节的值具有不同的类型在这 3 个起始字节之后。一个 NAL 单元不是视频中的一帧,每一帧由多个 NAL 单元组成。
基本上,我编写了一个方法来查找每个单独的单元并将其传递给解码器(一个 NAL 单元作为起始前缀,之后的任何字节直到下一个起始前缀)。
现在,如果您设置了用于解码 H.264 AVC 的解码器,并且有来自解码器的 InputBuffer,那么您就可以开始了。您需要使用 NAL 单元填充此 InputBuffer 并将其传递回解码器并继续为流的长度执行此操作。 但是,为了完成这项工作,我必须首先向解码器传递一个 SPS(序列参数集)NAL 单元。这个单元在起始前缀(第 4 个字节)之后的字节值为 0x67,在某些设备上,除非它首先收到这个单元,否则解码器会崩溃。 基本上在你找到这个单元之前,忽略所有其他 NAL 单元并继续解析流直到你得到这个单元,然后你可以将所有其他单元传递给解码器。
有些设备首先不需要 SPS,有些则需要,但最好先传递它。
现在,如果您有一个在配置时传递给解码器的表面,那么一旦它为一帧获得足够的 NAL 单元,它应该在表面上显示它。
【讨论】:
使用你上面的笔记,我得到了一些东西,虽然我看到了一些东西:有时我在 0x01 之前得到 3 个零,似乎可以全部发送;某些设备(Nexus 7/2012)无法使用某些编码器设置(例如 x264 扼流圈的低延迟调整);有些设备似乎根本无法工作(例如,最新的 Kindle Fire 和 ASUS Transformer)。这符合你的经验吗?还是我可能还有其他一些我没有找到的问题?00 00 01
是一种同步模式——它标识了块的开始,并且不允许出现在块本身内部(如果出现,它会被转义)。理论上,出现在模式之前的任何内容都将被忽略。 Android 4.3 具有确保合理行为的 CTS 测试,但许多测试将编码器的输出反馈到解码器,因此可能无法捕获不受支持的功能。
我真的只需要测试一些设备(Nexus 4、Galaxy S3、Nexus 7),但正如我在帖子中所说,我在使用 S3 时遇到了问题。我真的没有任何建议让它在其他设备上运行,最后我决定当时只支持 Nexus 4 和 S3。同步模式正如@fadden 所说,代表一个块的开始。
@will,+1 以获得很好的解释。我想通过 WI-FI 解码来自视频处理器的 H.264 高配置文件,我可以采用您的程序来满足我的要求吗?您正在解码的配置文件类型是什么?如果您与我们分享代码将不胜感激,谢谢
"一个 NAL 单元不是视频中的一帧,每一帧由多个 NAL 单元组成。"
【参考方案2】:
您可以从服务器下载原始 H.264,然后通过手机上运行的本地 HTTP 服务器提供它,然后让 VLC for Android 从该 HTTP 服务器进行播放。您应该使用 VLC 的 http/h264:// 方案强制解复用器为原始 H.264(如果您不强制解复用器 VLC 可能无法识别流,即使 HTTP 服务器返回的 MIME 类型是设置正确)。见
https://github.com/rauljim/tgs-android/blob/integrate_record/src/com/tudelft/triblerdroid/first/VideoPlayerActivity.java#L211
有关如何创建将启动 VLC 的 Intent 的示例。
注意:原始 H.264 显然没有时间信息,因此 VLC 将尽可能快地播放。 首先将其嵌入MPEGTS会更好。还没有找到可以做到这一点的 Android 库。
【讨论】:
感谢您的回复,我曾想过这个选项,但最后我通过编写自己的字节解析器将正确的 NAL 单元发送到解码器,让新的 MediaCodec API 工作。解码器可以传递一个 Surface 来渲染,所以一切都很好,但是是的,H.264 没有时间数据,所以它基本上可以尽可能快地渲染。 没有公开代码,但我会尽快发布一个简短的概述。 我对您的解决方案非常感兴趣。所以,如果你能概述一下你是如何做到的,那就太好了,我会很高兴【参考方案3】:以下是我在类似项目中发现的有用资源:
-
This video 在理解 MediaCodec 如何处理高级别的原始 h.264 流方面非常有见地。
This thread 更详细地介绍了具体处理 SPS/PPS NALU。如上所述,您需要使用起始前缀分隔各个 NAL 单元,然后将剩余数据交给 MediaCodec。
This repo(libstreaming)是在 Android 中使用 RTSP/RTP 解码 H264 流进行传输的一个很好的例子。
【讨论】:
以上是关于在android中解码原始H264流?的主要内容,如果未能解决你的问题,请参考以下文章
有没有办法在 Android API 中播放原始音频/视频流?