前言
最近公司項目涉及到視頻壓縮的問題,于是在問題解決之余,總結了一下包括圖片和視頻在內的iOS相關的解決方案。
演示項目地址:https://github.com/Elbertz/ZDXCondenseStudy
一、圖片壓縮
首先,我們要了解,Apple已經在CoreImage庫中為我們提供了2種壓縮的方法,分別是
UIImageJPEGRepresentation(image, 1.0);
UIImagePNGRepresentation(image);
ps:image 是承載原圖片的圖片控件; 1.0 代表相對于原圖片,壓縮后的圖片的百分比。
壓縮方式
鑒于圖片的像素和尺寸這兩個屬性,我們可采取兩種圖片的壓縮方式:
1.壓縮圖片質量(Quality)
2.壓縮圖片尺寸(Size)
通過壓縮圖片質量的方式,首先想到通過循環的方式逐步減小圖片質量,直到圖片稍小于指定大小(maxLength),見接口
- (UIImage *)compressImageQuality:(UIImage *)image toByte:(NSInteger)maxLength;
但是有個明顯的缺點就是,如果maxLength相比于原大小極小,循環次數會變的極大,對于內存和壓縮效率產生比較大的壓力,于是采用二分法進行優化:
- (UIImage *)compressImage2Quality:(UIImage *)image toByte:(NSInteger)maxLength;
CGFloat max = 1;
CGFloat min = 0;
for (int i = 0; i < 6; i++) {
//
compression = (max + min)/2;
tempData = UIImageJPEGRepresentation(image, compression);
if (tempData.length > maxLength) {
max = compression;
}else if (tempData.length < maxLength * 0.9){
min = compression;
} else {
break;
}
}
壓縮策略:當圖片大小小于 maxLength,大于 maxLength * 0.9 時,不再繼續壓縮。最多壓縮 6 次,1/(2^6) = 0.015625 < 0.02,也能達到每次循環 compression 減小 0.02 的效果。這樣的壓縮次數比循環減小 compression 少,耗時短。
優化后的對圖片質量進行壓縮的優缺點為:
優點:盡可能保留圖片清晰度,圖片不會明顯模糊;
缺點:不能保證圖片壓縮后小于指定大小(6次之后圖片大小有可能比maxLength大)。
而對于壓縮圖片尺寸而言,可以有效的使圖片小于指定大小,但會使圖片明顯模糊(比壓縮圖片質量模糊):
-(UIImage *)compressImageSize:(UIImage *)image toByte:(NSUInteger)maxLength {
UIImage *resultImage = image;
NSData *data = UIImageJPEGRepresentation(resultImage, 1);
NSUInteger lastDataLength = 0;
while (data.length > maxLength && data.length != lastDataLength) {
//
lastDataLength = data.length;
CGFloat ratio = (CGFloat) maxLength / data.length;
//每次繪制的尺寸 size,要把寬 width 和 高 height 轉換為整數,防止繪制出的圖片有白邊
CGSize size = CGSizeMake((NSUInteger)(resultImage.size.width * sqrtf(ratio)),
(NSUInteger)(resultImage.size.height * sqrtf(ratio))); // Use NSUInteger to prevent white blank
UIGraphicsBeginImageContext(size);
[resultImage drawInRect:CGRectMake(0, 0, size.width, size.height)];
resultImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
data = UIImageJPEGRepresentation(resultImage, 1);
}
return resultImage;
}
壓縮策略:驗證目標圖片的data大小,如果大于目標大小maxLength,逐次把圖片壓縮為上次圖片的某個百分比來進行圖片大小的遞減,直到圖片大小小于maxLength為止。
在實際應用中,如果要保證圖片清晰度,建議選擇壓縮圖片質量。如果要使圖片一定小于指定大小,壓縮圖片尺寸可以滿足。對于后一種需求,還可以先壓縮圖片質量,如果已經小于指定大小,就可得到清晰的圖片,否則再壓縮圖片尺寸。
使用兩種方式混合壓縮的接口為:
- (UIImage *)compressImage:(UIImage *)image toByte:(NSUInteger)maxLength;
二、視頻壓縮
在系統類中,Apple為我們提供了AVAssetExportSession類來專門處理視頻的壓縮問題。
常用的設置壓縮后的視頻的視頻質量如下,對于一般的壓縮要求我們大多采用MediumQuality,
AVAssetExportPresetLowQuality ? ? ? ?
AVAssetExportPresetMediumQuality ? ?
?AVAssetExportPresetHighestQuality
另外,在AVAssetExportSession類中還為我們提供了更多的壓縮格式,如果你有特殊需求可以選擇適合你的需求的格式,這里就不一一列舉了。
- (void)compressVideoWithURL:(NSURL *)url compressionType:(NSString *)compressionType compressionResultPath:(NSString *)tempPath;
AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:url options:nil];
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:avAsset];
// 所支持的壓縮格式中是否有 所選的壓縮格式
......
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:avAsset presetName:compressionType];
......
exportSession.outputURL = [NSURL fileURLWithPath:resultPath];
exportSession.outputFileType = AVFileTypeMPEG4;
exportSession.shouldOptimizeForNetworkUse = YES;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
//
switch (exportSession.status) {
.......
case AVAssetExportSessionStatusCompleted:
{
NSLog(@"AVAssetExportSessionStatusCompleted");
NSData *resultData = [NSData dataWithContentsOfFile:resultPath];
NSLog(@"after video.data=%lu",resultData.length);
break;
}
case AVAssetExportSessionStatusFailed:
NSLog(@"AVAssetExportSessionStatusFailed");
break;
case AVAssetExportSessionStatusCancelled:
NSLog(@"AVAssetExportSessionStatusCancelled");
break;
default:
break;
}
}];
首先,根據原視頻的url創建AVURLAsset對象;然后根據assert對象和compressionType創建AVAssetExportSession對象;之后再設置壓縮后的輸出視頻的url、視頻類型等,最后通過異步會話exportAsynchronouslyWithCompletionHandler:監測壓縮過程中的各個狀態,并做相應的處理。
詳細代碼請見文首github的demo。
三、壓縮文件
SSZipArchive:https://github.com/ZipArchive/ZipArchive
這是一個已經有2000+star并且在持續更新的壓縮文件的開源代碼。其基于c語言的解決方案分別通過Objective-C和Swift進行封裝,適用于iPhone、iPad、Mac端開發使用。
使用前需要引用libz.tbd
使用很方便,比如:
//解壓zip文件到指定文件地址
- (void)unZipFileswithzipFilepath:(NSString *)filePath toDestination:(NSString *)unzippath{
NSError *error;
BOOL result = [SSZipArchive unzipFileAtPath:filePath toDestination:unzippath];
if (result) {
NSLog(@"unzip success!");
NSLog(@"unzippath=%@",unzippath);
} else {
NSLog(@"unzip error:%@",error);
}}
//對源文件進行壓縮,并存放在指定文件地址的新建的zip文件中
- (void)createZipFileAtPath:(NSString *)path withContentsOfDirectory:(NSString *)directoryPath{
BOOL result = [SSZipArchive createZipFileAtPath:path withContentsOfDirectory:directoryPath];
if (result) {
NSLog(@"zip success!");
NSLog(@"zippath=%@",path);
} else {
NSLog(@"zip error!");
}
}
問題總結:
問題1:Error Domain=SSZipArchiveErrorDomain Code=-1 "failed to open zip file" UserInfo={NSLocalizedDescription=failed to open zip file}
答:這個問題就是說你獲取或者存儲zip文件的文件路徑有誤。這里以iPhone端為例,從Mac地址上獲取zip文件路徑會報錯;如果是模擬器,路徑寫成動態路徑;在真機上調試,沙盒目錄下的路徑都是OK的。