/* 注意:
新的音乐资源,一定要提前创建加载 , 否则在添加音乐轨迹时, audioAssetTrack.asset 有可能会为nil,会导致在插入音轨时,失败,引发错误:无法完成这项操作
[audioMutableCompositionTrack insertTimeRange:timeRange ofTrack:audioAssetTrack atTime:startTime error:&error];
*/
+ (void)compressVideoQuality:(NSString *)presetName lengthLimit:(NSUInteger)maxLimit
videoUrl:(NSURL *)videoUrl musicAsset:(AVAsset *)musicAsset
completion:(void(^)(NSURL *outputUrl, NSError *error, CGSize videoSize, CGFloat videoDuration))completionBlock progressBlock:(void(^)(float progress))progressBlock
{
AVAsset *asset = [AVAsset assetWithURL:videoUrl];
if (!asset) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey:@"未找到视频资源"}];
completionBlock(nil,error, CGSizeZero, 0.0f);
return;
}
if (!presetName) {
NSError *error = [NSError errorWithDomain:NSArgumentDomain code:-2 userInfo:@{NSLocalizedDescriptionKey:@"请选择压缩视频格式"}];
completionBlock(nil,error, CGSizeZero, 0.0f);
return;
}
//david
CGSize newSize = CGSizeZero;
if (presetName == KSAVAssetExportPreset320x240) {
presetName = AVAssetExportPresetLowQuality;
newSize = CGSizeMake(320, 240);
}else{
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:asset];
if (![compatiblePresets containsObject:presetName]) {
NSError *error = [NSError errorWithDomain:NSArgumentDomain code:-2 userInfo:@{NSLocalizedDescriptionKey:@"不兼容此压缩格式"}];
completionBlock(nil,error, CGSizeZero, 0.0f);
return;
}
}
//源视频时长s
CGFloat originSeconds = round(asset.duration.value / (CGFloat)asset.duration.timescale);
CMTime startTime = CMTimeMakeWithSeconds(0, asset.duration.timescale);
CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);
AVAssetTrack *assetTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVMutableComposition *mixComposition = [AVMutableComposition composition];
// 3 - Video track
AVMutableCompositionTrack *videoCompostionTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
[videoCompostionTrack insertTimeRange:timeRange
ofTrack:assetTrack
atTime:kCMTimeZero error:nil];
// 设置音频
NSArray *audioAssetTracks = nil;
if (musicAsset) {
//新的背景音乐
audioAssetTracks = [musicAsset tracksWithMediaType:AVMediaTypeAudio];
}else{
//视频自带的音乐
audioAssetTracks = [asset tracksWithMediaType:AVMediaTypeAudio];
}
if (audioAssetTracks.count > 0){
NSError *error = nil;
AVAssetTrack *audioAssetTrack = [audioAssetTracks firstObject];
AVMutableCompositionTrack *audioMutableCompositionTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
//asset.duration
[audioMutableCompositionTrack insertTimeRange:timeRange ofTrack:audioAssetTrack atTime:startTime error:&error];
if (error) {
completionBlock(nil,error, CGSizeZero, 0.0f);
}
AVMutableAudioMixInputParameters *mixParameters = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:audioMutableCompositionTrack];
[mixParameters setVolumeRampFromStartVolume:1.f toEndVolume:0.f timeRange:timeRange];
AVMutableAudioMix *mutableAudioMix = [AVMutableAudioMix audioMix];
mutableAudioMix.inputParameters = @[mixParameters];
}
// 3.1 - Create AVMutableVideoCompositionInstruction
AVMutableVideoCompositionInstruction *mainInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
mainInstruction.timeRange = timeRange;
// 3.2 - Create an AVMutableVideoCompositionLayerInstruction for the video track and fix the orientation.
AVMutableVideoCompositionLayerInstruction *videolayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoCompostionTrack];
BOOL isVideoAssetPortrait_ = NO;
CGAffineTransform videoTransform = assetTrack.preferredTransform;
// 左
if (videoTransform.a == 0 && videoTransform.b == 1.0 && videoTransform.c == -1.0 && videoTransform.d == 0) {
isVideoAssetPortrait_ = YES;
}
// 右
if (videoTransform.a == 0 && videoTransform.b == -1.0 && videoTransform.c == 1.0 && videoTransform.d == 0) {
isVideoAssetPortrait_ = YES;
}
CGSize naturalSize;
if(isVideoAssetPortrait_){
naturalSize = CGSizeMake(assetTrack.naturalSize.height, assetTrack.naturalSize.width);
} else {
naturalSize = assetTrack.naturalSize;
}
//david
if (newSize.width > 0 && newSize.height > 0) {
if (newSize.height > newSize.width) {
newSize = CGSizeMake(newSize.height, newSize.width);
}
videoTransform = CGAffineTransformScale(videoTransform, newSize.width/naturalSize.width,
newSize.height/naturalSize.height);
}
[videolayerInstruction setTransform:videoTransform atTime:kCMTimeZero];
[videolayerInstruction setOpacity:0.0 atTime:asset.duration];
// 3.3 - Add instructions
mainInstruction.layerInstructions = @[videolayerInstruction];
AVMutableVideoComposition *mainCompositionInst = [AVMutableVideoComposition videoComposition];
//david
if (newSize.width > 0 && newSize.height > 0){
if(isVideoAssetPortrait_){
naturalSize = CGSizeMake(newSize.height, newSize.width);
} else {
naturalSize = newSize;
}
}
int32_t frameRate = assetTrack.nominalFrameRate <= 0 ? 30 : assetTrack.nominalFrameRate;
float renderWidth, renderHeight;
renderWidth = naturalSize.width;
renderHeight = naturalSize.height;
mainCompositionInst.renderSize = CGSizeMake(renderWidth, renderHeight);
mainCompositionInst.instructions = [NSArray arrayWithObject:mainInstruction];
mainCompositionInst.frameDuration = CMTimeMake(1, frameRate);
// 4 - Get path
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSString *fileDirectory = [NSString getPathFromCache:@"KSVideoCaches"];
if (![fileManager fileExistsAtPath:fileDirectory]) {
[fileManager createDirectoryAtPath:fileDirectory withIntermediateDirectories:YES attributes:nil error:nil];
}
NSString *filePath = [[fileDirectory stringByAppendingPathComponent:@"CompressedVideo"] stringByAppendingPathExtension:@"mp4"];
NSURL *o_url = [NSURL fileURLWithPath:filePath];
if ([fileManager fileExistsAtPath:filePath]) {
[fileManager removeItemAtPath:filePath error:nil];
}
// 5 - Create exporter
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:presetName];
exportSession.outputURL= o_url;
exportSession.outputFileType = AVFileTypeMPEG4;
exportSession.shouldOptimizeForNetworkUse = YES;
exportSession.videoComposition = mainCompositionInst;
exportSession.fileLengthLimit = maxLimit;//长度限制
[exportSession exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
if (exportSession.status == AVAssetExportSessionStatusCompleted) {
completionBlock(exportSession.outputURL, nil, CGSizeMake(renderWidth, renderHeight), originSeconds);
}else if(exportSession.status == AVAssetExportSessionStatusFailed) {
completionBlock(nil, exportSession.error, CGSizeZero, 0.0);
}
});
}];
//获取 压缩进度
if (progressBlock) {
NSTimeInterval period = 1.0; //设置时间间隔
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(_timer, ^{ //在这里执行事件
if (exportSession.progress >= 1.0) {
dispatch_source_cancel(_timer);
_timer = nil;
}else{
dispatch_async(dispatch_get_main_queue(), ^{
progressBlock(exportSession.progress);
});
}
});
dispatch_resume(_timer);
}
}