从Android媒体录制器流式传输后修复3GP文件

13
我正在尝试通过本地的unix套接字从Android相机实时传输视频,并将流写入SD卡文件。一切都很顺利,但是任何播放器都无法播放文件。这是因为Android没有填补文件中的一些空隙,因为套接字不可寻址。据我了解,在视频流结束后,我需要进行一些修改。我阅读了几篇文章(hereherehere),但它们都没有帮助我。我正在使用十六进制编辑器进行探索,以便在之后可以轻松地在Android代码中执行相同的操作。
这是一个保存自流的示例文件:https://dl.dropbox.com/u/17510473/sample_not_playable.3gp
“有人能修复它使其可播放并告诉我他是如何做到的吗?”
“编辑:我删除了3gp文件的标题并写下了以下新标题:”
00 00 00 18 66 74 79 70 33 67 70 34 00 00 03 00 33 67 70 34 33 67 70 36 00 00 00 00

然后我使用以下命令找到mdat和moov原子的起始位置:
grep -aobE "ftyp|mdat|moov" sample_not_playable.3gp

它给了我以下输出:
4:ftyp
28:mdat
1414676:moov

然后计算出 1414676 - 28 = 1,414,648 = 0x1595F8 接着我将0x1595F8写成25-28字节,就在mdat原子之前。因此我的头文件现在看起来像这样:
00 00 00 18 66 74 79 70 33 67 70 34 00 00 03 00 33 67 70 34 33 67 70 36 00 15 95 F8

当我尝试使用mplayer播放它时,会出现一些损坏的视频和音频输出。以下是mplayer输出的一部分:

[amrwb @ 0x7f72ad652380]Frame too small (33 bytes). Truncated file?
[amrwb @ 0x7f72ad652380]Encountered a bad or corrupted frame
[amrwb @ 0x7f72ad652380]Encountered a bad or corrupted frame
[amrwb @ 0x7f72ad652380]Frame too small (33 bytes). Truncated file?
[amrwb @ 0x7f72ad652380]Encountered a bad or corrupted frame
[amrwb @ 0x7f72ad652380]Encountered a bad or corrupted frame
[amrwb @ 0x7f72ad652380]Encountered a bad or corrupted frame
A:  11.0 V:   1.4 A-V:  9.650 ct:  0.023   0/  0 10%  1%  1.6% 0 0                                                        
[mov,mp4,m4a,3gp,3g2,mj2 @ 0x7f72adeafc40]stream 1, offset 0x15e62b: partial file
[h263 @ 0x7f72ad652380]Bad picture start code
[h263 @ 0x7f72ad652380]header damaged
Error while decoding frame!
[h263 @ 0x7f72ad652380]Bad picture start code
[h263 @ 0x7f72ad652380]header damaged
Error while decoding frame!
[h263 @ 0x7f72ad652380]Bad picture start code
[h263 @ 0x7f72ad652380]header damaged
Error while decoding frame!
A:  11.1 V:   1.5 A-V:  9.558 ct:  0.027   0/  0  9%  1%  1.4% 0 0                                                        
[h263 @ 0x7f72ad652380]Bad picture start code
[h263 @ 0x7f72ad652380]header damaged
Error while decoding frame!

我做错了什么?

你想知道如何对Android流媒体进行3gp视频编码吗? - Trung Nguyen
我从Android设备接收到有效的流,记录该流,并且之后我想使记录的文件可以播放。就这样。 - Alex Amiryan
2个回答

14
你需要明白的是,mp4不是一种适合直播的格式,因此没有任何方法可以绕过它进行直播流。文件头[moov原子]写在末尾。Android会在录制结束时创建一个存储帧大小和其他参数的内存表,然后在文件开头写入该表,因此需要访问文件句柄的可寻址性(而套接字则无法实现)。
如果你想将加密内容写入磁盘,以便防止别人播放该文件,那么只需加密文件头部即可,整个文件就无法播放了。
如果你非常需要整个文件进行编码,因为你对上述段落不太信服,那么可以使用像ffmpeg这样的工具进行编码。修改它使其能够直接输出加密结果并保存到硬盘中,再次去除套接字部分。
在Android中,没有办法进行mp4文件的实时流传输。我见过很多人试图这样做,但都失败了。如果你了解视频/格式的话,你就知道这是不可能的。mp4不是为直播流设计的。除非你事先知道帧大小(你不知道)和编码的确切长度(你最有可能不知道),否则你无法预先创建好文件头。
附:mp4和3gp是近亲,因此同样适用。
很多人混淆了直播流和http pd以及伪流传输。直播流意味着我没有整个文件,而是实时创建和流传输的。http pd和伪流传输则适用于完全可用的文件。
编辑:
如果你的目标是在存储到sdcard之前加密记录文件,则需要编码器支持。这意味着需要将自己的编码器引入其中。拿ffmpeg为例,交叉编译它到Android。为应用程序编写一个小的JNI接口。首先完成这些步骤而不进行加密。一旦完成,在ffmpeg中添加加密模块,与流fwrites相同。解码时同理。Y3ng创建。这是最干净的方法。
编辑2:请看spydroid,它有一些你需要的功能。还有其他类似的。

第三次编辑:为了提高答案的质量,我还解释了一个不完美的解决方法。可以通过解析生成的mp4并将元素流分别通过套接字发送来仍然可以流式传输AV。您将面临的唯一问题是,您将无法获得完美的AV同步,因为您不知道确切的AV样本时间戳。只有Android知道这一点,并在结尾处将其写入mp4标题中。对你没有好处。您必须对视频帧的完美采样率做出假设,并且您的音频必须是amr以假设20毫秒的数据包。在其他音频情况下,您将开始在长时间运行中看到漂移[特别是当您开始具有高运动场景时]。这是因为生成的每个音频包都不对应于固定的时间持续时间[除了amr和其他语音编解码器]。


1
感谢您对我的问题进行了详细的解释。问题是我迫切需要加密整个文件。我仅使用套接字是因为我需要实时加密视频(这个要求也很紧急)。那么,您有什么建议吗?我有以下要求:1.在Android设备上录制视频时实时加密视频(不写入任何未加密数据到磁盘),2.能够在设备上播放文件,因此实时解密并将解密后的流提供给视频播放器。3.能够解密文件并拥有完全可播放的视频,可以在PC上使用任何播放器播放。 - Alex Amiryan
1
那么就像我建议的那样,您需要放入自己的编码器来进行编码。使用ffmpeg并将其安装在设备上。一旦您完成了这个步骤,只需稍微更改它以在输出之前加密即可。同时,使用ffmpeg解码流并在解码之前解密输入。对于第三点,您可以自己编写一个非常简单的解密模块,它只需将一个文件输入并输出另一个文件。 - av501
1
谢谢。但我没有使用NDK编码。有Java的视频编码实现吗?您能建议我将视频编码为哪种格式吗?您能提供一些此目的的代码片段吗? - Alex Amiryan
好的。问题是我已经有一个工作的加密模块,它是用Java而不是JNI编写的。我需要使用它。我阅读了整个Spydroid的源代码。事实上,我从中学习了如何通过本地套接字流式传输视频。Spydroid使用它的h263分组器将流转换为RTP。您能建议我需要将我的视频编码成什么格式以满足上述3个标准,并且能否提供一些代码片段或关于您建议的视频格式的文档。谢谢。 - Alex Amiryan
我可以向您保证,如果您不在jni中做一些事情,那么您将陷入死胡同,因为底层系统并不是为了实现您想要的功能而设计的。唯一的出路是,在服务器上[在您的Java代码中]解析数据,理解需要放入标题的内容,并在最后自己编写标题。这意味着您需要清楚地了解mp4格式,并解析传递给您的数据以重新创建标题。可流式传输的格式有传输流[ts]、mjpeg或webm。因此,如果您有能力对其中一种进行编码,那么您现有的方案将毫不费力地运行。不是mp4/3gpp。 - av501
显示剩余8条评论

4
首先,您要做什么并不是非常清楚。
如果您只想通过程序将视频保存为可播放文件,那么您只需要使用文件描述符而不是Unix套接字。MediaRecord的此API是为与文件(而不是Unix套接字)一起使用而设计的,原因在于可以进行随机访问。因此,我的第一个建议是使用文件描述符,最终将得到正确的文件记录结果。
您可以在此处获取示例:如何在Android上捕获视频录制? 在这种情况下,如果您正在编写从设备流式传输视频的应用程序,您将需要实时解析流,按帧划分并单独发送每个帧。最复杂的部分是解析流(某些视频编解码器,例如H263,可以解析,而其他编解码器则无法解析,特别是如果数据与音频交错)。
我相信其中一个项目实现了此功能: http://sipdroid.org/ http://code.google.com/p/imsdroid/

问题是我想要实时加密视频并将其写入磁盘。这就是为什么我需要使用Unix套接字来实现实时视频加密的原因。但问题在于,解密后的加密文件无法播放。因此,我只需要将流式视频写入文件并使其可播放即可。 - Alex Amiryan
顺便说一下,还有一件奇怪的事情。你文件中的mdat似乎在moov之前。我原本以为mdat会被写在文件的最后,因为它包含帧的信息。 - Victor Ronin
加密视频文件实际上是该应用程序的主要目的 :). 因此我们需要这样做。当您告诉Android的MediaRecorder将流写入套接字而不是文件时,它会流式传输mdat原子,并在您按下停止按钮时提供moov原子,最终文件看起来像问题中附带的文件。那么有什么想法可以修复它使其可播放吗? - Alex Amiryan
你是对的。moov是最后编写的(我忘了这一点)。我查看了文件并发现以下内容: - Victor Ronin
现在可以玩吗?你能给我修改后的可玩文件吗,这样我就可以进行十六进制比较了。 - Alex Amiryan
显示剩余12条评论

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