为啥在使用 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 源中看不到moov
或 ftyp
原子。 (我被告知 vlc 只是流式传输,在这里;实际的 H264 内容来自摄像头驱动程序。)
我在https://github.com/raspberrypi linux 或用户区分支中看不到moov
或ftyp
原子。 (不过,也许我只是在寻找错误的东西。)
当我让 vlc 保存流时,我会在 mdat
之前得到一个带有 moov
的 mp4 文件,但当然 vlc 可以在这里进行一些转码。
更新,20140610:
GPAC "Osmo4" player 可以在 Android 4.3 平板电脑上显示流。很糟糕(比笔记本电脑上的 VLC 更滞后,并且容易锁定),但它可以显示它。
更新,20140616:
当我再次尝试对 VLC 源进行 grepping 时(这次不区分大小写且不区分单词),我确实在 modules/mux/mp4.c
中找到了定义 moov
和 ftyp
原子的 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 播放音频文件