• uniapp视频压缩踩坑记录


    最近在聊天APP中实现了一个视频上传发送的功能,在此记录一下所踩的坑。

    视频格式背景

    目前市面上主流的Android及iOS机,其所拍摄的视频大体分为两种格式,分别为H.264H.265格式,这两种格式对于我们开发而言只需要理解:
    H.264为兼容性高的视频格式,可以在绝大部分设备播放
    H.265为高效存储的视频格式,可节约存储空间,缺点是兼容性差,其他设备可能无法播放
    参考链接: H.264_百度百科H.265_百度百科

    相关实现API

    (1)uni.chooseVideo选取视频
    参数名说明
    sourceTypealbum 从相册选视频,camera 使用相机拍摄,默认为:[‘album’, ‘camera’]
    extension根据文件拓展名过滤,每一项都不能是空字符串。默认不过滤。
    compressed是否压缩所选的视频源文件,默认值为 true,需要压缩。
    maxDuration拍摄视频最长拍摄时间,单位秒。最长支持 60 秒。
    camera‘front’、‘back’,默认’back’
    success接口调用成功,返回视频文件的临时文件路径,详见返回参数说明。
    fail接口调用失败的回调函数
    complete接口调用结束的回调函数(调用成功、失败都会执行)
    (2)uni.compressVideo压缩视频
    参数名说明
    src视频文件路径,可以是临时文件路径也可以是永久文件路径
    quality压缩质量
    bitrate码率,单位 kbps
    fps帧率
    resolution相对于原视频的分辨率比例,取值范围(0, 1]
    success接口调用成功的回调函数
    fail接口调用失败的回调函数
    complete接口调用结束的回调函数(调用成功、失败都会执行)
    (3)uni.getVideoInfo获取视频详细信息
    参数名说明
    src视频文件路径,可以是临时文件路径也可以是永久文件路径(不支持网络地址)
    success接口调用成功的回调函数
    fail接口调用失败的回调函数
    complete接口调用结束的回调函数(调用成功、失败都会执行)

    实现细节

    chooseVideoAPI中集成了compressed压缩功能,但在实际操作过程中,还存在有权限及压缩效果不可控的问题。以鸿蒙系统为例,不管你是否开启压缩,其前后选取回调中的视频大小均为原视频大小(即该参数无效)。但实际上,并非该参数没有效果,而是其准备压缩该视频时被拒绝了,原因是无权限,相关代码截图如下:

    // 鸿蒙系统真机调试下
    // demo1 使用uni.chooseVideo并开启compressed
    uni.chooseVideo({
    	compressed: true,
    	success: (res) => {
    		// res.size获取的视频大小为原视频大小,即压缩无效
    	}
    });
    
    // demo2 chooseVideo不开启压缩,改用compressVideo走压缩
    uni.chooseVideo({
    	compressed: false,
    	success: (res) => {
    		uni.compressVideo({
    			src: res.tempFilePath,
    			quality: 'low',
    			success: () => { // 压缩成功 
    			},
    			fail: (err) => { 
    				// 压缩失败
    				console.log('err:', err);
    			}
    		});
    	}
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    运行结果:在鸿蒙系统中demo1不管是否开启了compressed,其获取到的视频大小均为原视频大小;demo2中则在compressVideo中走失败回调,报错如下图:
    压缩时报无权限
    所以基本上可以判定,在鸿蒙系统中是无法直接选取后压缩视频的。

    解决方案

    一番查阅文档后,有了一个大致的方向,既然无法对回调所返回的临时文件进行压缩,那么我把该临时文件拷贝一份放到APP目录下的话,对拷贝文件做压缩就可以了吧?事不宜迟,博主直接上手实践

    封装一个拷贝文件的方法,file-copy.js

    该方法将本地文件拷贝指定的目标文件夹下,并可指定文件名,返回值为retry则需要再调用一次

    export default {
    	/**
    	 * 复制系统文件到APP存储中
    	 * @param {String} localPath 文件本地路径
    	 * @param {String} targetFolder 复制到的目标文件夹
    	 * @param {String} fileName 文件名
    	 * @returns 拷贝成功返回路径
    	 */
    	copySysFileToAPP(localPath, targetFolder, fileName) {
    		return new Promise((resolve) => {
    			// #ifdef APP-PLUS
    			plus.io.resolveLocalFileSystemURL(
    				localPath,
    				(fileEntry) => {
    					console.log('获取文件成功', fileEntry);
    					plus.io.resolveLocalFileSystemURL(
    						`_doc/${targetFolder}`,
    						(directoryEntry) => {
    							console.log('获取目录成功', directoryEntry);
    							fileEntry.copyTo(
    								directoryEntry,
    								fileName,
    								(res) => {
    									console.log('拷贝成功', res);
    									resolve(`_doc/${targetFolder}/${fileName}`); // 拷贝成功返回路径
    								},
    								(err) => {
    									console.log('拷贝失败', err);
    									resolve(false);
    								}
    							);
    						},
    						(directoryErr) => {
    							console.log('获取目录失败', directoryErr);
    							// 获取不到目标目录,需要手动创建该目录后,再重新获取目录拷贝
    							plus.io.resolveLocalFileSystemURL(
    								'_doc',
    								(docEntry) => {
    									console.log('获取_doc成功', docEntry);
    									docEntry.getDirectory(
    										targetFolder,
    										{
    											create: true,
    											exclusive: false
    										},
    										(createSuccessCB) => {
    											console.log('创建目录成功', createSuccessCB);
    											resolve('retry'); // 创建后返回重试
    										},
    										(createFailCB) => {
    											console.log('创建目录失败', createFailCB);
    											resolve(false);
    										}
    									);
    								},
    								(docErr) => {
    									console.log('获取_doc失败', docErr);
    									resolve(false);
    								}
    							);
    						}
    					);
    				},
    				(fileErr) => {
    					console.log('获取文件失败', fileErr);
    					resolve(false);
    				}
    			);
    			// #endif
    		});
    	}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    在业务逻辑页面使用
    <script>
    import fileCopy from './file-copy.js';
    export default {
    	methods: {
    		chooseVideo() {
    			uni.chooseVideo({
    				compressed: false,
    				maxDuration: 60,
    				success: async(res) => {
    					let videoName = 'dafeizhu';
    					let videoPath = await fileCopy.copySysFileToAPP(res.tempFilePath, 'video', videoName);
    					if (videoPath === 'retry') {
    						videoPath = await fileCopy.copySysFileToAPP(res.tempFilePath, 'video', videoName);
    					}
    					// 拷贝完之后,用新的文件路径走压缩
    					uni.compressVideo({
    						src: videoPath,
    						quality: 'low',
    						success: (compressRes) => {
    							console.log('压缩成功', compressRes);
    						},
    						fail: (err) => {
    							console.log('压缩失败', err);
    						}
    					})
    				}
    			});
    		}
    	}
    }
    </script>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    真机测试,压缩成功!

    结束了吗?还没!

    前文提到的视频格式分为两种,这两种格式均可以通过compressVideo压缩,压缩之后的视频格式为H.264,故不需要再考虑视频可播放的问题。
    这个作为一个拓展,讲一下如何在压缩视频的同时,又最大限度的保持视频的质量。
    compressVideo接口中,除了quality之外,还有bitrate(码率),fps(帧率),resolution(相对于原视频的分辨率比例)这三个参数,官方文档上说明了当需要更精细的控制时,可指定 bitrate、fps、和 resolution,当 quality 传入时,这三个参数将被忽略。
    博主在经过实际的上手操作后,发现直接指定quality的话,压缩4K视频会导致失败,故只能使用bitratefpsresolution来控制压缩效果,那么如何获取视频的这三个参数呢?getVideoInfo接口派上用场。
    在实际操作中,博主还发现了Android端获取本机拍摄的视频时,会存在视频的宽高颠倒的情况(图片也有类似的情况),故需要做特定的处理

    let video = await new Promise((resolve) => {
    	uni.getVideoInfo({
    		src: res.tempFilePath,
    		success: (result) => {
    			console.log('视频信息', result, res);
    			if (this.$store.state.platform === 'android' && (result.orientation === 'right' || result.orientation === 'left')) {
    				// Android端在HBuilderX版本为3.6.2以下,会出现获取纵向视频宽高值相反的BUG
    				// HBx3.6.2版本已修复,详见https://ask.dcloud.net.cn/question/151205
    				let width = res.height;
    				res.height = res.width;
    				res.width = width;
    			}
    			res.fps = Math.ceil(result.fps);
    			res.bitrate = result.bitrate;
    			resolve(res);
    		},
    		fail: () => {
    			resolve(false);
    		}
    	});
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    压缩规则制定如下:
    限制视频分辨率上限为1080P(1920×1080),不足1080P则默认为原视频分辨率,超过则换算成比例;码率超过4096的,按4096算;保持原视频帧率

    // 修改一下compressVideo参数
    let ratio = Math.sqrt((1920 * 1080) / (video.width * video.height));
    let resolution = ratio >= 1 ? 1 : ratio;
    let bitrate = video.bitrate > 4 * 1024 ? 4 * 1024 : video.bitrate;
    uni.compressVideo({
    	src: videoPath,
    	bitrate,
    	fps: video.fps,
    	resolution,
    	...
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    大功告成!

    总结

    关于本文中提及的直接用chooseVideo压缩视频失败的问题,在前段时间已反馈给官方,而关于文末的压缩规则制定,是根据博主自身的需求出发考虑的,各位有需要可以参照着修修改改(不过这里面也有!如码率过高也会导致压缩失败)
    每踩一个坑,都是一个成长的脚印,在官方对这些BUG问题无作为无反馈的情况下,只能硬着头皮找出路,明知山有虎,偏向虎山行!
    Keep learning…

  • 相关阅读:
    Linux 进程操作
    二进制加法(位运算)
    TS以及webpack的es module
    通俗理解ABP中的模块Module
    Android 7.1 设置-内存
    Oracle Unifier 22.12 ~ 23.10 功能改进清单表
    多种隐藏滚动条但是依然可以滚动实现方式
    传输层TCP协议
    获取ArcGISPro中conda信息详情
    使用 Pycharm 调试远程代码
  • 原文地址:https://blog.csdn.net/weixin_43905402/article/details/126783174