为啥在使用 Android MediaPlayer 读取 H.264 编码的 rtsp 流时出现“不支持的格式”错误?

Posted

技术标签:

【中文标题】为啥在使用 Android MediaPlayer 读取 H.264 编码的 rtsp 流时出现“不支持的格式”错误?【英文标题】:Why am I getting "Unsupported format" errors, reading H.264 encoded rtsp streams with the Android MediaPlayer?为什么在使用 Android MediaPlayer 读取 H.264 编码的 rtsp 流时出现“不支持的格式”错误? 【发布时间】:2014-07-23 05:32:29 【问题描述】:

我正在尝试在 android 设备上显示 H.264 编码的 rtsp 视频。流来自 Raspberry Pi,使用 vlc 对 /dev/video1 进行编码,这是一个“Pi NoIR 相机板”。

vlc-wrapper -vvv v4l2:///dev/video1 --v4l2-width $WIDTH --v4l2-height $HEIGHT --v4l2-fps $FPS.0 --v4l2-chroma h264 --no-audio --no-osd --sout "#rtpsdp=rtsp://:8000/pi.sdp" :demux=h264 > /tmp/vlc-wrapper.log 2>&1

我现在使用的 Android 代码非常少:

final MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setDisplay(holder);
try 
  mediaPlayer.setDataSource(url);
  mediaPlayer.prepare();

并获得“准备失败。:状态=0x1”IOException。当我查看日志时,我看到像

这样的行
06-02 16:28:05.566 W/APacketSource(  316): Format:video 0 RTP/AVP 96  / MIME-Type:H264/90000
06-02 16:28:05.566 W/MyHandler(  316): Unsupported format. Ignoring track #1.
06-02 16:28:05.566 I/MyHandler(  316): SETUP(1) completed with result -1010 (Unknown error 1010)

来自系统进程。搜索这些消息指向libstagefright/rtsp 源,似乎意味着APacketSource::APacketSource 构造函数中的ASessionDescription::getDimensions 调用失败。这似乎不应该发生,因为 VLC 肯定知道要输出什么尺寸:

[0x1c993a8] v4l2 demux debug: trying specified size 800x600
[0x1c993a8] v4l2 demux debug: Driver requires at most 262144 bytes to store a complete image
[0x1c993a8] v4l2 demux debug: Interlacing setting: progressive
[0x1c993a8] v4l2 demux debug: added new video es h264 800x600

似乎正在发生的是ASessionDescription::getDimensions 正在(看似格式正确的)DESCRIBE 结果中寻找framesize 属性

06-02 16:28:05.566 I/MyHandler(  316): DESCRIBE completed with result 0 (Success)
06-02 16:28:05.566 I/ASessionDescription(  316): v=0
06-02 16:28:05.566 I/ASessionDescription(  316): o=- 15508012299902503225 15508012299902503225 IN IP4 pimple
06-02 16:28:05.566 I/ASessionDescription(  316): s=Unnamed
06-02 16:28:05.566 I/ASessionDescription(  316): i=N/A
06-02 16:28:05.566 I/ASessionDescription(  316): c=IN IP4 0.0.0.0
06-02 16:28:05.566 I/ASessionDescription(  316): t=0 0
06-02 16:28:05.566 I/ASessionDescription(  316): a=tool:vlc 2.0.3
06-02 16:28:05.566 I/ASessionDescription(  316): a=recvonly
06-02 16:28:05.566 I/ASessionDescription(  316): a=type:broadcast
06-02 16:28:05.566 I/ASessionDescription(  316): a=charset:UTF-8
06-02 16:28:05.566 I/ASessionDescription(  316): a=control:rtsp://192.168.1.35:8000/pi.sdp
06-02 16:28:05.566 I/ASessionDescription(  316): m=video 0 RTP/AVP 96
06-02 16:28:05.566 I/ASessionDescription(  316): b=RR:0
06-02 16:28:05.566 I/ASessionDescription(  316): a=rtpmap:96 H264/90000

这个看起来它可能是一个 Stagefright 错误:它知道(或应该知道)它有一个 H.264 编码流,但它似乎期待一个 H.263 framesize属性。因此我的问题是:

    我在阅读源代码吗? ASessionDescription::getDimensions 电话有问题吗? (stagefright 实际上只支持 H.263 流媒体吗?) 还是 Pi 端代码在某些方面有问题? 还是我只是在客户端代码中遗漏了一两个关键步骤?

更新,20140606:

MediaPlayer 文档说 -1010 是 MEDIA_ERROR_UNSUPPORTED:“比特流符合相关的编码标准或文件规范,但媒体框架不支持该功能。”这让我想知道问题是否是“标准”渐进式下载问题。也就是说,Supported Media Formats 说

对于通过 HTTP 或 RTSP [in a] MPEG-4 [container] 流式传输的视频内容,moov 原子必须在任何 mdat 原子之前,但必须在 ftyp 原子之后

虽然大多数流将moov 原子放在最后。

不过,我完全不确定如何验证这一点!

我在 vlc 源中看不到 moovftyp 原子。 (我被告知 vlc 只是流式传输,在这里;实际的 H264 内容来自摄像头驱动程序。) 我在https://github.com/raspberrypi linux 或用户区分支中看不到moovftyp 原子。 (不过,也许我只是在寻找错误的东西。) 当我让 vlc 保存流时,我会在 mdat 之前得到一个带有 moov 的 mp4 文件,但当然 vlc 可以在这里进行一些转码。

更新,20140610:

GPAC "Osmo4" player 可以在 Android 4.3 平板电脑上显示流。很糟糕(比笔记本电脑上的 VLC 更滞后,并且容易锁定),但它可以显示它。

更新,20140616:

当我再次尝试对 VLC 源进行 grepping 时(这次不区分大小写且不区分单词),我确实在 modules/mux/mp4.c 中找到了定义 moovftyp 原子的 FOURCC 宏,这很快导致--sout-mp4-faststart(和--no-sout-mp4-faststart)开关......没有任何区别。

所以,看起来它实际上可能不是原子排序问题。很高兴知道,如果它关闭了整个类别的死胡同,但它确实让我毫无头绪地把头撞到墙上(这似乎总是对我的头部造成更多的伤害而不是对墙壁造成的伤害)。

更新,20140702:

我为Android编译了VLC,它可以在pi上显示VLC生成的流。它将图像放在屏幕的左上角;我尝试为他们的 .so 编写自己的皮肤,但找不到任何可以让我放大到表面或其他任何东西的“旋钮”。 (加上 .apk 达到了大约 12M!)

所以,我找到了相关的 RFC 并编写了自己的 RTSP 客户端。或者尝试:我可以解析SDP 并生成足够有效的RTSP 来获取RTP 和RTCP 数据报,并且我可以解析RTP 和RTCP 标头。但即使 SDP 声称提供 m=video 0 RTP/AVP 96 和 a=rtpmap:96 H264/90000,MediaCodec 也不会在我的表面上显示视频,无论三个 H264 编解码器中的哪个我将平板电脑传递给 MediaCodec.createByCodecName(),当我查看 RTP 有效负载时,我并不感到惊讶:我在任何数据包中都没有看到 NAL sync pattern。

相反,它们全部21 9A __ 22 FF(通常)或偶尔3C 81 9A __ 22 FF开头,其中__似乎总是一个偶数,每个数据包增加2。我不认识这种模式 - 你呢?

更新,20140711:

事实证明,H264 数据包不必以 NAL 同步模式开头 - 只有在 NAL 单元可能嵌入到更大数据流中时才需要这样做。我的 RTP 数据包是 RFC 6184 格式。

【问题讨论】:

你能用另一个应用程序读取相同的流吗,例如VLC? 嗯 - 我很惊讶我没有提到这一点。是的,VLC 可以读取流......但是,当然,VLC 愿意打开第二个流来查找 moov 原子,而 Android 不是。 (对不起,在工作的机器上引用了它。) bytechunk.net/mobile_progressive_playback/index.php 其实我还以为你在安卓上试过VLC播放…… 我已经成功地使用 imagemagic(我用它来拼接图像)和 avconv (manpages.ubuntu.com/manpages/precise/man1/avconv.1.html) 的组合对移动播放的视频进行编码,两者都在 linux 上运行良好,因为 RPi 是 linux基于这两个都不应该是你开始工作的问题。只是一个建议。值得研究,因为我使用任何其他工具都没有成功 【参考方案1】:

经过大量死胡同后,我可以在 Android SurfaceView 上显示 H264 RTSP 流。这个答案只是某种的答案,因为我仍然无法解决我原来的三个问题,但即使充满了错误和快捷方式,我的 75K apk 也比 Vlc for Android 或osmo4 播放器:它具有亚秒级延迟(至少当发送方和接收方在同一个 wifi 路由器上时!)并填充SurfaceView

一些要点,以帮助任何尝试做类似事情的人:

您传递给 MediaCodec.queueInputBuffer() 的所有输入缓冲区必须以 00 00 01 sync pattern 开头。 您可以立即使用configure()start() 编解码器 - 但在看到 SPS(NALU 代码 7)和 PPS(NALU 代码 8)数据包之前,不要将任何“正常”输入缓冲区排队。 (这些可能不是 0x67 和 0x68 - “nal_ref_idc”位应该是非零但不一定是 11。Fwiw,vlc 似乎总是给我 01。) 几乎正常传递 SPS/PPS 数据包 - 将 BUFFER_FLAG_CODEC_CONFIG 标志传递给 queueInputBuffer()。特别是,不要尝试将它们放在附加到 MediaFormat! 的“csd-0”缓冲区中! 当您看到 (a) 丢失帧时(您看到 RTP 序列号的跳跃)不要致电codec.flush()!只需跳过部分帧,在下一个完整帧之前不要将缓冲区排队。

【讨论】:

以上是关于为啥在使用 Android MediaPlayer 读取 H.264 编码的 rtsp 流时出现“不支持的格式”错误?的主要内容,如果未能解决你的问题,请参考以下文章

在 Android 中使用 MediaPlayer 播放音频文件

Android 音频开发之 MediaPlayer

Android -- MediaPlayer内部实现简析

为啥 MediaPlayer.start 不从零开始播放?

在 Android 中是不是可以录制 MediaPlayer 播放的内容?

android:在模拟器上使用 MediaPlayer 录制音频