-首先理解为什么使用分片处理,以下摘自ChatGPT。
提高传输效率:视频文件通常很大,如果直接将整个视频文件传输到客户端或其他服务器,会占用大量的带宽和资源。通过将视频分成小片段,可以将视频按需传输,减少传输延迟,并提高传输效率。
支持流媒体播放:视频分片处理是实现流媒体播放的基础。将视频分为若干片段后,客户端可以逐个片段地请求和播放,而不需要等待整个视频下载完成。这样用户就能够快速开始观看视频,同时也能够根据网络状况调整视频的清晰度和码率。
提供多码率适配:视频分片处理可以为不同的设备和网络环境提供多个不同码率的片段。这样,根据用户的设备和网络带宽情况,选择适合的码率片段进行播放,以确保流畅的观看体验。
实现并行处理:对于大型视频文件,分片处理可以将视频处理任务分解为多个并行处理的子任务。这样可以利用多台服务器或多核处理器的并行计算能力,加快视频处理的速度。
容错和恢复:如果视频文件出现损坏或传输中断的情况,只需要重新请求或传输对应的片段,而不需要重新下载整个视频文件。这提高了容错性,并减少了对网络带宽和服务器存储空间的要求。
综上所述,视频分片处理可以提高传输效率、支持流媒体播放、提供多码率适配、实现并行处理以及提高容错性和恢复能力。这使得视频分片成为一种常用的视频处理和传输方式。
分片往往是由前端处理分片,之后由后端进行合并、存储(因为分片更多是考虑到网络波动导致下载失败又要重新下载的问题,而如果后端都完整的获得了所有的文件资源那也没什么必要分片了)。但这里为了演示,我用后端代码实现了分片和合并,原理实际上都一样,不同的是代码一个是js一个是java而已。
演示demo: 六花i/Breakpoint-continuation-demo (gitee.com)
一、文件分块流程
- 获取源文件长度
- 根据设定的分块文件大小,计算出块数(向上取整,例如33.4M的文件,块大小为1M,则需要34块)
- 从源文件读取数据,并依次向每一个块文件写数据
分块代码:
/**
* 分块
* @throws IOException
*/
@Test
public void testChunk() throws IOException {
//准备一个视频文件,一定要大,内容无所谓!!
File sourcePath = new File("D:\\XL\\明裏紬\\IPZZ-090-UC\\IPZZ-090-UC.mp4");
// 快文件存储路径
String chunkPath = "D:\\uploadPath\\video\\";
File chunkFolder = new File(chunkPath);
boolean mkdirs = chunkFolder.mkdirs();
//分块大小,5M(MinIO合并必须5M)
long chunkSize = 1024 * 1024 * 5;
//计算块数,向上取整
long chunkNum = (long)Math.ceil(sourcePath.length() * 1.0 / chunkSize);
//缓冲区,缓冲 1k
byte[] bytes = new byte[1024];
// 使用RandomAccessFile访问文件
RandomAccessFile redAccess = new RandomAccessFile(sourcePath, "r");
for (int i = 0; i < chunkNum; i++) {
// 创建分块文件,默认文件名 path + i,例如chunk\1 chunk\2
File file = new File(chunkPath + i);
if (file.exists()) {
file.delete();
}
boolean newFile = file.createNewFile();
if (newFile) {
int len = -1;
RandomAccessFile wrtAccess = new RandomAccessFile(file, "rw");
//如果每次读都有内容则循环
while ((len = redAccess.read(bytes)) != -1) {
wrtAccess.write(bytes,0,len);
if (file.length() >= chunkSize) {
break;
}
}
wrtAccess.close();
}
}
redAccess.close();
System.out.println("分块完成");
}
结果:
二、文件合并流程
- 找到要合并的文件并按文件分块的先后顺序排序
- 创建合并文件
- 依次从合并的文件中读取数据冰箱合并文件写入数据
代码:
/**
* 合并
* @throws IOException
*/
@Test
public void testMerge() throws IOException {
// 块文件目录
File chunkFolder = new File("D:\\uploadPath\\video\\");
// 源文件
File sourceFile = new File("D:\\XL\\明裏紬\\IPZZ-090-UC\\IPZZ-090-UC.mp4");
// 合并文件
File mergeFile = new File("D:\\uploadPath\\video\\明裏紬老师.mp4");
mergeFile.createNewFile();
// 用于写文件
RandomAccessFile raf_write = new RandomAccessFile(mergeFile, "rw");
// 缓冲区
byte[] buffer = new byte[1024];
// 文件名升序排序
File[] files = chunkFolder.listFiles();
List<File> fileList = Arrays.asList(files);
fileList.sort((f1, f2) -> {
try {
Integer num1 = Integer.parseInt(f1.getName());
Integer num2 = Integer.parseInt(f2.getName());
return num1.compareTo(num2);
} catch (NumberFormatException e) {
// 处理异常,例如返回默认值或者放到列表末尾
return 0; // 返回默认值,不改变顺序
}
});
// 合并文件
for (File chunkFile : fileList) {
RandomAccessFile raf_read = new RandomAccessFile(chunkFile, "r");
int len;
while ((len = raf_read.read(buffer)) != -1) {
raf_write.write(buffer, 0, len);
}
raf_read.close();
}
raf_write.close();
// 判断合并后的文件是否与源文件相同
FileInputStream fileInputStream = new FileInputStream(sourceFile);
FileInputStream mergeFileStream = new FileInputStream(mergeFile);
//取出原始文件的md5
String originalMd5 = DigestUtils.md5DigestAsHex(fileInputStream);
//取出合并文件的md5进行比较
String mergeFileMd5 = DigestUtils.md5DigestAsHex(mergeFileStream);
if (originalMd5.equals(mergeFileMd5)) {
System.out.println("合并文件成功");
} else {
System.out.println("合并文件失败");
}
}
结果:(视频能播放,内容就不给你们看了)
总结
分块处理是实现断点续传的一种方法,但要实现断点续传还要编写方法校验分块内容是否已经上传。
我这里建议合并之后,把分片文件删了。
在while方法里调用chunkFile.delete();