纯JAVA实现MP4文件边下载边播放
背景
客户提出有部分Mp4视频需要完全加载完才能播放,体验很差,尤其是视频相对较大网速不太好的时候,毕竟现在都是边下载边播放模式.导致这种情况出现的原因是有些Mp4文件中的moov信息在文件末尾.只要将moov信息移动到视频信息前面,前端就可以实现边下边放.
https://gitee.com/chenjin0827/mp4Demo.git
写了个demo给各位同学参考
https://gitee.com/chenjin0827/mp4Demo.git
大致介绍下Mp4的格式
MP4文件主要由ftyp,mdat,moov三部分组成。三部分的主要职责:
- ftyp ,记录了mp4格式,编码格式之类的一些基本信息;
- mdat,记录了视频媒体信息(mdat的体积往往非常的大,几乎等于MP4总大小);
- moov,如同检索表一样的存在,里面记录了每一帧对应的数据在哪里等等;
前期弯路
介绍下前期考虑的方案踩过的坑供大家参考,想直接看最终纯Java解决方案的可以跳过
- 使用ffmpeg处理
ffmpeg -i G:\Download\bad2.mp4 -c copy -movflags faststart -map_metadata -1 G:\Download\good2.mp4
命令解释:
-i
指定输入文件: G:\Download\bad2.mp4
-c copy
处理方式: 复制所有流而不重新编码 ,很重要,不加会被重新编码,输出文件可能会变大。
-movflags faststart
优化选项: 将 moov atom 移动到文件开头。
-map_metadata -1
元数据处理: 移除所有元数据,也可以轻微减小生成文件大小 。
输出文件: G:\Download\good2.mp4
说明:该命令可以实现Mp4文件的转换,实现边下边播.缺点就是需要在服务端安装ffmpeg工具,然后使用java命令调用它,不够轻量 - 使用python处理(未进行尝试,各位同学自行体会)
- 前期使用java版本踩坑
<dependency>
<groupId>com.googlecode.mp4parser</groupId>
<artifactId>isoparser</artifactId>
<version>1.1.22</version>
</dependency>
上面是旧版本,推荐使用迁移后的新版本,见下方
使用mp4parser完成Mp4的转换
原理就是读入Mp4,重新生成个,新生成的默认moov在前面
- 导入包
<dependency>
<groupId>org.mp4parser</groupId>
<artifactId>muxer</artifactId>
<version>1.9.27</version>
</dependency>
- 这里传入的MultipartFile 是来自SpringMVC传入来的值,返回的byte可以生成文件,各位同学自行处理
private byte[] generateNewMp4(MultipartFile file) {
File outPutFile = null;
// 创建临时文件
File convFile = null;
List<Track> tracks = new ArrayList<>();
try {
outPutFile = File.createTempFile("tempOut", file.getOriginalFilename());
convFile = File.createTempFile("tempIn", file.getOriginalFilename());
file.transferTo(convFile);
// 创建Movie对象 CustomMovieCreator是自定义,后面讲为什么
Movie movie = CustomMovieCreator.build(convFile.getPath());
// 获取所有轨道
tracks = movie.getTracks();
// 创建一个新的Movie对象
Movie newMovie = new Movie();
for (Track track : tracks) {
newMovie.addTrack(track);
}
// 构建 MP4 文件
try (FileOutputStream fos = new FileOutputStream(outPutFile);
FileChannel fc2 = fos.getChannel()) {
//这里有个坑,要使用FragmentedMp4Builder,不能使用默认DefaultMp4Builder,DefaultMp4Builder的注释说Creates a plain MP4 file from a video. Plain as plain can be.大概意思是创建个简化的Mp4,这个会导致你在本地浏览器能播放,但是浏览器会因为缺失了头文件等信息无法播放
Container mp4file = new FragmentedMp4Builder().build(newMovie);
mp4file.writeContainer(fc2);
}