//剪輯時(shí)長(zhǎng)
typedef struct TimeRange {
CGFloat location;
CGFloat length;
} TimeRange;
@interface JWVideoEditManage : NSObject
/**
剪輯視頻
@param videoUrl 路徑
@param videoRange 剪輯范圍
@param completionHandle 完成
*/
- (void)captureVideoWithVideoUrl:(NSURL *)videoUrl
timeRange:(TimeRange)videoRange
completion:(void (^)(NSURL * outPutUrl,NSError * error))completionHandle;
/**
去除視頻聲道聲音
@param videoUrl 路徑
@param completionHandle 完成
*/
- (void)deleteVideoAudio:(NSURL )videoUrl
completion:(void (^)(NSURL * outPutUrl,NSError * error))completionHandle;
/*
合成配音視頻
@param videoUrl 無(wú)聲視頻
@param audioUrl 聲音路徑
@param completionHandle nil
*/
- (void)addBackgroundMiusicWithVideoUrlStr:(NSURL )videoUrl
audioUrl:(NSURL )audioUrl
completion:(void(^)(NSURL * outPutUrl,NSError * error))completionHandle;
/
合成配音視頻
@param videoUrl 視頻
@param audioUrl 音樂(lè)
@param musicStart 音樂(lè)開(kāi)始時(shí)間
@param videoRange 視頻截取區(qū)間
@param videoVolume 視頻音量
@param musicVolume 音樂(lè)音量
@param completionHandle
*/
- (void)addBackgroundMiusicWithVideoUrlStr:(NSURL *)videoUrl
audioUrl:(NSURL *)audioUrl
musicStartTime:(CGFloat)musicStart
videoTimeRange:(TimeRange)videoRange
videoVolume:(CGFloat)videoVolume
musicVolume:(CGFloat)musicVolume
completion:(void(^)(NSURL * outPutUrl,NSError * error))completionHandle;
/**
轉(zhuǎn)換視頻格式,導(dǎo)出視頻
@param inputURL 輸入源
@param handler 回調(diào)
*/
- (void)exportSessionVideoWithInputURL:(NSURL*)inputURL
completion:(void(^)(NSURL * outPutUrl,NSError * error))completionHandle;
/**
相冊(cè)讀取之后照片路徑辩诞,內(nèi)部會(huì)將相冊(cè)內(nèi)部的視頻緩存至沙盒路徑
@param info imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
@param result 緩存路徑
*/
- (void)getVideoLocalPath:(NSDictionary<NSString *,id> *)info
result:(void(^)(NSURL * videoURL))result;
/**
獲取視頻縮略圖
@param url url
*/
- (CGImageRef)thumbnailImageRequestWithURL:(NSURL *)url;
/**
清楚剪輯視頻路徑
*/
(void)clearCutVideoCache;
(void)exportSessionVideoWithInputURL:(NSURL)inputURL completion:(void(^)(NSURL * outPutUrl,NSError * error))completionHandle fiter:(CIFilter)filter;
//MARK: 獲取視頻時(shí)長(zhǎng)
/**
獲取視頻市場(chǎng)
@param mediaUrl 路徑
@return value
*/
- (CGFloat)getMediaDurationWithMediaUrl:(NSURL *)mediaUrl;
@end
m
import <AssetsLibrary/AssetsLibrary.h>
import <Photos/Photos.h>
define kCutVideoPath @"cutDoneVideo.mov" //剪切的視頻文件名
define kDubVideoPath @"dubVideoPath.mov" //剪切的視頻文件名
define kMergeVideoPath @"mergeVideoPath.mp4" //剪切的視頻文件名
define kMergeAudioPath @"mergeAudioPath.caf" //合成視頻時(shí)將音頻轉(zhuǎn)換成caf軌道
define kTempAssetVideo @"tempAssetVideo.mov" //相冊(cè)讀取之后沙盒路徑
//視頻配音錄音 洒琢、壓縮裁剪路徑
define kCachesVideoStorageEdit ({\
NSString * path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"VideoStorageEdit"];
if (![[NSFileManager defaultManager]fileExistsAtPath:path]) {
[[NSFileManager defaultManager]createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
}
(path);
})
@implementation VideoManager
//MARK: 檢查保存視頻權(quán)限
(void)checkVideoPermissionCompletionHandler:(void (^)(BOOL granted))handler {
PHAuthorizationStatus permission = [PHPhotoLibrary authorizationStatus];
if (permission == PHAuthorizationStatusAuthorized) {
if (handler) {
handler(YES);
}
}else {
[self resquestUserPermissionCompletionHandler:handler];
}
}
//請(qǐng)求權(quán)限-
(void)resquestUserPermissionCompletionHandler:(void (^)(BOOL granted))handler {
[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
if (status == PHAuthorizationStatusAuthorized) {
// 通過(guò)驗(yàn)證
dispatch_async(dispatch_get_main_queue(), ^{
if (handler) {
handler(YES);
}
});
}else {
// 未通過(guò)驗(yàn)證
handler(NO);
}}];
}
//MARK: 剪輯視頻 -
(void)captureVideoWithVideoUrl:(NSURL )videoUrl timeRange:(TimeRange)videoRange completion:(void (^)(NSURL * outPutUrl,NSError * error))completionHandle{
if (!videoUrl.path || [videoUrl.path isEqualToString:@""] || videoRange.length == NSNotFound) {
return;
}
//AVURLAsset此類(lèi)主要用于獲取媒體信息象迎,包括視頻、聲音等
AVURLAsset videoAsset = [[AVURLAsset alloc] initWithURL:videoUrl options:nil];
NSArray tracks = videoAsset.tracks;
NSLog(@"所有軌道:%@\n",tracks);//打印出所有的資源軌道
//創(chuàng)建AVMutableComposition對(duì)象來(lái)添加視頻音頻資源的AVMutableCompositionTrack
AVMutableComposition mixComposition = [AVMutableComposition composition];
//CMTimeRangeMake(start, duration),start起始時(shí)間汪厨,duration時(shí)長(zhǎng),都是CMTime類(lèi)型
//CMTimeMake(int64_t value, int32_t timescale)衷戈,返回CMTime,value視頻的一個(gè)總幀數(shù)谦趣,timescale是指每秒視頻播放的幀數(shù)蔚润,視頻播放速率,(value / timescale)才是視頻實(shí)際的秒數(shù)時(shí)長(zhǎng)除盏,timescale一般情況下不改變窃祝,截取視頻長(zhǎng)度通過(guò)改變value的值
//CMTimeMakeWithSeconds(Float64 seconds, int32_t preferredTimeScale)粪小,返回CMTime,seconds截取時(shí)長(zhǎng)(單位秒)逞壁,preferredTimeScale每秒幀數(shù)
//開(kāi)始位置startTime
CMTime startTime = CMTimeMakeWithSeconds(videoRange.location, videoAsset.duration.timescale);
//截取長(zhǎng)度videoDuration
CMTime videoDuration = CMTimeMakeWithSeconds(videoRange.length, videoAsset.duration.timescale);CMTimeRange videoTimeRange = CMTimeRangeMake(startTime, videoDuration);
//視頻采集compositionVideoTrack 添加視頻軌道
AVVideoComposition * videoComposition = [self addVideoCompositionTrack:mixComposition timeRange:videoTimeRange assert:videoAsset];//視頻聲音采集(也可不執(zhí)行這段代碼不采集視頻音軌,合并后的視頻文件將沒(méi)有視頻原來(lái)的聲音)
[self addAudioCompositionTrack:mixComposition timeRange:videoTimeRange assert:videoAsset];//AVAssetExportSession用于合并文件姿骏,導(dǎo)出合并后文件,presetName文件的輸出類(lèi)型
NSString *outPutPath = [NSHomeDirectory() stringByAppendingFormat:@"/Documents/cutoutput-%@.mp4", [self getNowTimeTimestamp2]];[self exportVideoSession:mixComposition outPath:outPutPath videoComposition:videoComposition completion:completionHandle];
}
//MARK: 去除視頻聲道 -
(void)deleteVideoAudio:(NSURL )videoUrl completion:(void (^)(NSURL * outPutUrl,NSError * error))completionHandle{
if (!videoUrl.path || [videoUrl.path isEqualToString:@""]) {
return;
}
//AVURLAsset此類(lèi)主要用于獲取媒體信息蟋恬,包括視頻歼争、聲音等
AVURLAsset videoAsset = [[AVURLAsset alloc] initWithURL:videoUrl options:nil];
//創(chuàng)建AVMutableComposition對(duì)象來(lái)添加視頻音頻資源的AVMutableCompositionTrack
AVMutableComposition* mixComposition = [AVMutableComposition composition];//視頻采集compositionVideoTrack
AVVideoComposition * videoComposition = [self addVideoCompositionTrack:mixComposition timeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration) assert:videoAsset];//AVAssetExportSession用于合并文件俩莽,導(dǎo)出合并后文件,presetName文件的輸出類(lèi)型
NSString *outPutPath = [kCachesVideoStorageEdit stringByAppendingPathComponent:kDubVideoPath];
[self exportVideoSession:mixComposition outPath:outPutPath videoComposition:videoComposition completion:completionHandle];
}
//MARK: 合并配音文件 只保留背景音樂(lè) -
(void)addBackgroundMiusicWithVideoUrlStr:(NSURL )videoUrl audioUrl:(NSURL )audioUrl completion:(void(^)(NSURL * outPutUrl,NSError * error))completionHandle {
AVURLAsset audioAsset = [[AVURLAsset alloc] initWithURL:audioUrl options:nil];
AVURLAsset videoAsset = [[AVURLAsset alloc] initWithURL:videoUrl options:nil];
//創(chuàng)建AVMutableComposition對(duì)象來(lái)添加視頻音頻資源的AVMutableCompositionTrack
AVMutableComposition* mixComposition = [AVMutableComposition composition];
//截取長(zhǎng)度videoDuration
CMTimeRange videoTimeRange = CMTimeRangeMake(kCMTimeZero, videoAsset.duration);
//視頻采集compositionVideoTrack
AVVideoComposition * videoComposition = [self addVideoCompositionTrack:mixComposition timeRange:videoTimeRange assert:videoAsset];//外部音頻采集璧疗,最后合成到原視頻崩侠,與原視頻的音頻不沖突
//聲音長(zhǎng)度截取范圍==視頻長(zhǎng)度
CMTimeRange audioTimeRange = videoTimeRange;
if (audioUrl) {
[self addAudioCompositionTrack:mixComposition timeRange:audioTimeRange assert:audioAsset];
}
NSString *outPutPath = [kCachesVideoStorageEdit stringByAppendingPathComponent:kMergeVideoPath];
//導(dǎo)出視頻
[self exportVideoSession:mixComposition outPath:outPutPath videoComposition:videoComposition completion:completionHandle];
}
/**
合成配音視頻 MARK: 剪切視頻 合并配音文件 開(kāi)始時(shí)間
@param videoUrl 視頻
@param audioUrl 音樂(lè)
@param musicStart 音樂(lè)開(kāi)始時(shí)間
@param videoRange 視頻截取區(qū)間
@param videoVolume 視頻音量
@param musicVolume 音樂(lè)音量
@param completionHandle
*/
-
(void)addBackgroundMiusicWithVideoUrlStr:(NSURL *)videoUrl
audioUrl:(NSURL *)audioUrl
musicStartTime:(CGFloat)musicStart
videoTimeRange:(TimeRange)videoRange
videoVolume:(CGFloat)videoVolume
musicVolume:(CGFloat)musicVolume
completion:(void(^)(NSURL * outPutUrl,NSError * error))completionHandle {AVURLAsset* videoAsset = [[AVURLAsset alloc] initWithURL:videoUrl options:nil];
//創(chuàng)建AVMutableComposition對(duì)象來(lái)添加視頻音頻資源的AVMutableCompositionTrack
AVMutableComposition* mixComposition = [AVMutableComposition composition];// 視頻 截取長(zhǎng)度videoDuration
//開(kāi)始位置startTime
CMTime videoStartTime = CMTimeMakeWithSeconds(videoRange.location, videoAsset.duration.timescale);
//截取長(zhǎng)度videoDuration
CMTime videoDuration = CMTimeMakeWithSeconds(videoRange.length, videoAsset.duration.timescale);
//視頻截取區(qū)間
CMTimeRange videoTimeRange = CMTimeRangeMake(videoStartTime, videoDuration);//視頻采集compositionVideoTrack
AVVideoComposition * videoComposition = [self addVideoCompositionTrack:mixComposition timeRange:videoTimeRange assert:videoAsset];//外部音頻采集矢炼,最后合成到原視頻八拱,與原視頻的音頻不沖突
if (audioUrl) {
AVURLAsset* audioAsset = [[AVURLAsset alloc] initWithURL:audioUrl options:nil];CGFloat musicLength = CMTimeGetSeconds(audioAsset.duration); //音樂(lè)長(zhǎng)度不超過(guò)視頻長(zhǎng)度 CGFloat musicCutLength = musicLength - musicStart > videoRange.length ? videoRange.length : musicLength -musicStart; TimeRange musicRange = {musicStart,musicCutLength}; //開(kāi)始位置startTime CMTime musicStartTime = CMTimeMakeWithSeconds(musicRange.location, audioAsset.duration.timescale); //截取長(zhǎng)度videoDuration CMTime musicDuration = CMTimeMakeWithSeconds(musicRange.length, audioAsset.duration.timescale); //音樂(lè)截取區(qū)間 CMTimeRange musicTimeRange = CMTimeRangeMake(musicStartTime, musicDuration); NSArray *videoAudioMixToolsAry = [self mergeVideoAndAudioWithVIdeoAsset:videoAsset musicAssert:audioAsset mixComposition:mixComposition videoVolume:videoVolume musicVolume:musicVolume videoRange:videoTimeRange musicRange:musicTimeRange ]; NSString *outPutPath = [kCachesVideoStorageEdit stringByAppendingPathComponent:kMergeVideoPath]; //導(dǎo)出視頻 [self exportVideoSession:mixComposition outPath:outPutPath videoComposition:videoComposition videoAudioMixToolsAry:videoAudioMixToolsAry completion:completionHandle];
} else {
//視頻聲音采集(也可不執(zhí)行這段代碼不采集視頻音軌,合并后的視頻文件將沒(méi)有視頻原來(lái)的聲音)
[self addAudioCompositionTrack:mixComposition timeRange:videoTimeRange assert:videoAsset];
NSString *outPutPath = [kCachesVideoStorageEdit stringByAppendingPathComponent:kMergeVideoPath];
//導(dǎo)出視頻
[self exportVideoSession:mixComposition outPath:outPutPath videoComposition:videoComposition completion:completionHandle];}
}
//MARK: 添加視頻 軌道
/**
添加視頻 軌道
@param mixComposition 合成器
@param asset 源
@param timeRange 視頻返回
@return 視頻合成方案
*/
-
(AVVideoComposition *)addVideoCompositionTrack:(AVMutableComposition *)mixComposition timeRange:(CMTimeRange)timeRange assert:(AVURLAsset *)asset{
//音頻采集compositionCommentaryTrack
//TimeRange截取的范圍長(zhǎng)度
//ofTrack來(lái)源
//atTime插放在視頻的時(shí)間位置
NSError * error = nil;
//音頻混合軌道
AVMutableCompositionTrack *compositionTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
//插入視頻軌道
// 1.
[compositionTrack insertTimeRange:timeRange ofTrack:([asset tracksWithMediaType:AVMediaTypeVideo].count > 0) ? [asset tracksWithMediaType:AVMediaTypeVideo].firstObject : nil atTime:kCMTimeZero error:&error];
//下面3行代碼用于保證后面輸出的視頻方向跟原視頻方向一致
AVAssetTrack *assetVideoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo]firstObject];
[compositionTrack setPreferredTransform:assetVideoTrack.preferredTransform];
NSLog(@"幀率:%f榛搔,比特率:%f", assetVideoTrack.nominalFrameRate,assetVideoTrack.estimatedDataRate);
// 2.
AVMutableVideoCompositionLayerInstruction *videolayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionTrack];
[videolayerInstruction setOpacity:0.0 atTime:compositionTrack.asset.duration];
// 3.
AVMutableVideoCompositionInstruction *videoCompositionInstrution = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
videoCompositionInstrution.timeRange = CMTimeRangeMake(kCMTimeZero, compositionTrack.asset.duration);
videoCompositionInstrution.layerInstructions = @[videolayerInstruction];
// 4.
AVMutableVideoComposition videoComposition = [AVMutableVideoComposition videoComposition];
videoComposition.renderSize = CGSizeMake(compositionTrack.naturalSize.width, compositionTrack.naturalSize.height);//視頻寬高,必須設(shè)置践惑,否則會(huì)奔潰
/
電影:24
PAL(帕爾制腹泌,電視廣播制式)和SEACM():25
NTSC(美國(guó)電視標(biāo)準(zhǔn)委員會(huì)):29.97
Web/CD-ROM:15
其他視頻類(lèi)型,非丟幀視頻尔觉,E-D動(dòng)畫(huà) 30
*/
// videoComposition.frameDuration = CMTimeMake(1, 43);//必須設(shè)置凉袱,否則會(huì)奔潰侦铜,一般30就夠了
videoComposition.frameDuration = CMTimeMake(1, 30);//必須設(shè)置专甩,否則會(huì)奔潰,一般30就夠了
// videoComposition.renderScale
videoComposition.instructions = [NSArray arrayWithObject:videoCompositionInstrution];
NSLog(@"error---%@",error);return videoComposition;
}
//MARK: 添加聲音 軌道 只保留單個(gè)視頻聲音
/**
添加聲音 軌道钉稍,
@param mixComposition 合成器
@param asset 源
@param timeRange 視頻返回
*/
- (void)addAudioCompositionTrack:(AVMutableComposition *)mixComposition timeRange:(CMTimeRange)timeRange assert:(AVURLAsset *)asset{
//音頻采集compositionCommentaryTrack
//TimeRange截取的范圍長(zhǎng)度
//ofTrack來(lái)源
//atTime插放在視頻的時(shí)間位置
NSError * error = nil;
//這里采用信號(hào)量加鎖涤躲,是聲音轉(zhuǎn)換完成之后繼續(xù)進(jìn)行合成視頻
dispatch_semaphore_t t = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self changeAudioTypeToCAFAsset:asset Complete:^(AVURLAsset *newAudioAsset, NSError error) {
AVAssetTrack audioAssetTrack = [[newAudioAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
AVMutableCompositionTrack * audioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
// 加入合成軌道之中
[audioTrack insertTimeRange:timeRange ofTrack:audioAssetTrack atTime:CMTimeMakeWithSeconds(0, asset.duration.timescale) error:nil];
dispatch_semaphore_signal(t);
}];
});
dispatch_semaphore_wait(t, DISPATCH_TIME_FOREVER);
NSLog(@"error---%@",error);
}
//MARK: 轉(zhuǎn)換聲音頻道
/
聲音軌道轉(zhuǎn)換成caf
這里值得注意的是:因?yàn)閕OS對(duì)MP4視頻要求高,如果需要合成MP4視頻聲音軌道必須使用aac格式的贡未,所以這里視頻采用mov格式种樱,聲音頻道使用caf格式的蒙袍,內(nèi)部包含將視頻源中間的聲道轉(zhuǎn)換成caf格式的
@param asset 源
@param handle (新輸出的聲音路徑,錯(cuò)誤信息)
*/
-
(void)changeAudioTypeToCAFAsset:(AVURLAsset *)asset Complete:(void(^)(AVURLAsset * newAudioAsset,NSError * error))handle {
AVMutableComposition *composition = [AVMutableComposition composition];AVAssetTrack *audioAssetTrack = [[asset tracksWithMediaType:AVMediaTypeAudio] firstObject];
AVMutableCompositionTrack * audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
// 加入合成軌道之中
CMTimeRange audioTimeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);
//插入音頻
[audioTrack insertTimeRange:audioTimeRange ofTrack:audioAssetTrack atTime:CMTimeMakeWithSeconds(0, asset.duration.timescale) error:nil];//合成器
AVAssetExportSession *exportSesstion = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetPassthrough];//輸出路徑
NSString * path = [kCachesVideoStorageEdit stringByAppendingPathComponent:kMergeAudioPath];
if ([[NSFileManager defaultManager]fileExistsAtPath:path]) {
[[NSFileManager defaultManager]removeItemAtPath:path error:nil];
}
exportSesstion.outputURL = [NSURL fileURLWithPath:path];
exportSesstion.outputFileType = AVFileTypeCoreAudioFormat;
exportSesstion.shouldOptimizeForNetworkUse = YES;
[exportSesstion exportAsynchronouslyWithCompletionHandler:^{
AVAssetExportSessionStatus status = exportSesstion.status;
if (status == AVAssetExportSessionStatusCompleted) {
NSLog(@"聲音導(dǎo)出成功");
AVURLAsset * newAudioAsset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:path] options:nil];
if (handle) {
handle(newAudioAsset,nil);
}
}else{
NSLog(@"聲音導(dǎo)出失敗%@",exportSesstion.error);
if (handle) {
handle(nil,exportSesstion.error);
}
}
}];
}
/**
混音
@param videoAsset 第一段
@param musicAssert 第二段
@param mixComposition 原視頻容器
@param videoVolume 第一段音量
@param musicVolume 第二段音量
@return 混合聲音對(duì)象
*/
-
(NSArray *)mergeVideoAndAudioWithVIdeoAsset:(AVURLAsset *)videoAsset musicAssert:(AVURLAsset *)musicAssert mixComposition:(AVMutableComposition *)composition videoVolume:(float)videoVolume musicVolume:(float)musicVolume videoRange:(CMTimeRange)videoRange musicRange:(CMTimeRange)musicRange {
videoVolume = videoVolume >0 ? videoVolume : 1;//聲音默認(rèn)1,
musicVolume = musicVolume >0 ? musicVolume : 1;//聲音默認(rèn)1,//修改背景音樂(lè)的音量start 更改音量大小
NSMutableArray * params = [[NSMutableArray alloc] initWithCapacity:0];
// 第一段聲音處理
if (videoAsset) {
AVAssetTrack *audioAssetTrack = [[videoAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
AVMutableCompositionTrack * audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
// 加入合成軌道之中
// CMTimeRange audioTimeRange = CMTimeRangeMake(kCMTimeZero, videoAsset.duration);
//插入音頻
// atTime:kCMTimeInvalid 混音 音頻1和音頻2 同時(shí)播放
// atTime:firstAudioAsset.duration 拼接 音頻2的播放在音頻1結(jié)束以后開(kāi)始
[audioTrack insertTimeRange:videoRange ofTrack:audioAssetTrack atTime:kCMTimeZero error:nil];
//調(diào)節(jié)音量
//獲取音頻軌道
AVMutableAudioMixInputParameters *firstAudioParam = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:audioAssetTrack];
//設(shè)置音軌音量,可以設(shè)置漸變,設(shè)置為1.0就是全音量 setVolumeRampFromStartVolume:0.1 toEndVolume:1.0
[firstAudioParam setVolumeRampFromStartVolume:videoVolume toEndVolume:videoVolume timeRange:videoRange];
[firstAudioParam setTrackID:audioAssetTrack.trackID];
[params addObject:firstAudioParam];
}
// 第二段聲音處理
if (musicAssert) {
AVAssetTrack *musicAssetTrack = [[musicAssert tracksWithMediaType:AVMediaTypeAudio] firstObject];
AVMutableCompositionTrack * musicTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
// 加入合成軌道之中
// CMTimeRange musicTimeRange = CMTimeRangeMake(kCMTimeZero, musicAssert.duration);
//插入音頻
// atTime:kCMTimeInvalid 混音 音頻1和音頻2 同時(shí)播放
// atTime:firstAudioAsset.duration 拼接 音頻2的播放在音頻1結(jié)束以后開(kāi)始
[musicTrack insertTimeRange:musicRange ofTrack:musicAssetTrack atTime:kCMTimeZero error:nil];
//調(diào)節(jié)音量
//獲取音頻軌道
AVMutableAudioMixInputParameters *secondAudioParam = [AVMutableAudioMixInputParameters audioMixInputParametersWithTrack:musicAssetTrack];
//設(shè)置音軌音量,可以設(shè)置漸變,設(shè)置為1.0就是全音量 setVolumeRampFromStartVolume:0.1 toEndVolume:1.0
[secondAudioParam setVolumeRampFromStartVolume:musicVolume toEndVolume:musicVolume timeRange:musicRange];
[secondAudioParam setTrackID:musicAssetTrack.trackID];
[params addObject:secondAudioParam];
}
return params;
}
//MARK: 導(dǎo)出合成視頻
/**
導(dǎo)出合成視頻
@param mixComposition 合成器
@param outPutPath 輸出路徑
@param videoComposition 設(shè)置導(dǎo)出視頻的處理方案
@param completionHandle 完成
*/
-
(void)exportVideoSession:(AVMutableComposition *)mixComposition outPath:(NSString *)outPutPath videoComposition:(AVVideoComposition *)videoComposition completion:(void(^)(NSURL * outPutUrl,NSError * error))completionHandle {
//AVAssetExportSession用于合并文件缸托,導(dǎo)出合并后文件左敌,presetName文件的輸出類(lèi)型
AVAssetExportSession *assetExportSession = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetPassthrough];//混合后的視頻輸出路徑
NSURL *outPutUrl = [NSURL fileURLWithPath:outPutPath];if ([[NSFileManager defaultManager] fileExistsAtPath:outPutPath]){
[[NSFileManager defaultManager] removeItemAtPath:outPutPath error:nil];
}
//輸出視頻格式 AVFileTypeMPEG4 AVFileTypeQuickTimeMovie...
assetExportSession.outputFileType = AVFileTypeQuickTimeMovie;
// NSArray *fileTypes = assetExportSession.
assetExportSession.outputURL = outPutUrl;
//輸出文件是否網(wǎng)絡(luò)優(yōu)化
assetExportSession.shouldOptimizeForNetworkUse = YES;
//設(shè)置導(dǎo)出視頻的處理方案
if (videoComposition) {
assetExportSession.videoComposition = videoComposition;
}[assetExportSession exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
if (assetExportSession.status == AVAssetExportSessionStatusCompleted) {
NSLog(@"轉(zhuǎn)碼完成");
}else {
NSLog(@"%@",assetExportSession.error);
}
completionHandle(outPutUrl,assetExportSession.error);
});
}];
} -
(void)exportVideoSession:(AVMutableComposition *)mixComposition outPath:(NSString *)outPutPath videoComposition:(AVVideoComposition *)videoComposition videoAudioMixToolsAry:(NSArray *)videoAudioMixToolsAry completion:(void(^)(NSURL * outPutUrl,NSError * error))completionHandle {
//AVAssetExportSession用于合并文件,導(dǎo)出合并后文件俐镐,presetName文件的輸出類(lèi)型
AVAssetExportSession *assetExportSession = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetPassthrough];//混合后的視頻輸出路徑
NSURL *outPutUrl = [NSURL fileURLWithPath:outPutPath];if ([[NSFileManager defaultManager] fileExistsAtPath:outPutPath]){
[[NSFileManager defaultManager] removeItemAtPath:outPutPath error:nil];
}
//輸出視頻格式 AVFileTypeMPEG4 AVFileTypeQuickTimeMovie...
assetExportSession.outputFileType = AVFileTypeQuickTimeMovie;
// NSArray *fileTypes = assetExportSession.
assetExportSession.outputURL = outPutUrl;
//輸出文件是否網(wǎng)絡(luò)優(yōu)化
assetExportSession.shouldOptimizeForNetworkUse = YES;
// 音視頻軌道
if (videoAudioMixToolsAry.count > 0 ) {
AVMutableAudioMix *videoAudioMixTools = [AVMutableAudioMix audioMix];
videoAudioMixTools.inputParameters = [NSArray arrayWithArray:videoAudioMixToolsAry];
assetExportSession.audioMix = videoAudioMixTools;
}
//設(shè)置導(dǎo)出視頻的處理方案
if (videoComposition) {
assetExportSession.videoComposition = videoComposition;
}[assetExportSession exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
if (assetExportSession.status == AVAssetExportSessionStatusCompleted) {
NSLog(@"轉(zhuǎn)碼完成");
}else {
NSLog(@"%@",assetExportSession.error);
}
completionHandle(outPutUrl,assetExportSession.error);
});
}];
}
//MARK: 獲取視頻時(shí)長(zhǎng)
/**
獲取視頻市場(chǎng)
@param mediaUrl 路徑
@return value
*/
-
(CGFloat)getMediaDurationWithMediaUrl:(NSURL *)mediaUrl {
AVURLAsset *mediaAsset = [[AVURLAsset alloc] initWithURL:mediaUrl options:nil];
CMTime duration = mediaAsset.duration;return duration.value / duration.timescale;
} -
(NSUInteger)degressFromVideoFileWithURL:(NSURL *)url {
NSUInteger degress = 0;AVAsset *asset = [AVAsset assetWithURL:url];
NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo];
if([tracks count] > 0) {
AVAssetTrack *videoTrack = [tracks objectAtIndex:0];
CGAffineTransform t = videoTrack.preferredTransform;if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0){ // Portrait degress = 90; }else if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0){ // PortraitUpsideDown degress = 270; }else if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0){ // LandscapeRight degress = 0; }else if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0){ // LandscapeLeft degress = 180; }
}
return degress;
}
//MARK: 獲取視頻縮略圖
/**
獲取視頻縮略圖
@param url 視頻路徑
@return 圖片
*/
-
(CGImageRef)thumbnailImageRequestWithURL:(NSURL *)url
{
//根據(jù)url創(chuàng)建AVURLAsset
AVURLAsset *urlAsset=[AVURLAsset assetWithURL:url];//根據(jù)AVURLAsset創(chuàng)建AVAssetImageGenerator
AVAssetImageGenerator imageGenerator=[AVAssetImageGenerator assetImageGeneratorWithAsset:urlAsset];
imageGenerator.appliesPreferredTrackTransform = YES;
/截圖- requestTime:縮略圖創(chuàng)建時(shí)間
- actualTime:縮略圖實(shí)際生成的時(shí)間
/
NSError error=nil;
CMTime time = CMTimeMake(0, 10);
// CMTime time=CMTimeMakeWithSeconds(0, 10);//CMTime是表示視頻時(shí)間信息的結(jié)構(gòu)體矫限,第一個(gè)參數(shù)表示是視頻第幾秒,第二個(gè)參數(shù)表示每秒幀數(shù).(如果要獲取的某一秒的第幾幀可以使用CMTimeMake方法)
CMTime actualTime;
CGImageRef cgImage= [imageGenerator copyCGImageAtTime:time actualTime:&actualTime error:&error];
if(error){
NSLog(@"截取視頻縮略圖時(shí)發(fā)生錯(cuò)誤佩抹,錯(cuò)誤信息:%@",error.localizedDescription);
}
CMTimeShow(actualTime);
return cgImage;
}
//MARK: 轉(zhuǎn)換視頻格式叼风,導(dǎo)出視頻
/
轉(zhuǎn)換視頻格式,導(dǎo)出視頻
@param inputURL 輸入源
@param outputURL 輸出源
@param handler 回調(diào)
*/
- (void)exportSessionVideoWithInputURL:(NSURL)inputURL completion:(void(^)(NSURL * outPutUrl,NSError * error))completionHandle{
if (!inputURL.path || [inputURL.path isEqualToString:@""] || inputURL.path.length == NSNotFound) {
return;
}
//AVURLAsset此類(lèi)主要用于獲取媒體信息棍苹,包括視頻无宿、聲音等
AVURLAsset videoAsset = [[AVURLAsset alloc] initWithURL:inputURL options:nil];
NSArray tracks = videoAsset.tracks;
NSLog(@"所有軌道:%@\n",tracks);//打印出所有的資源軌道
//創(chuàng)建AVMutableComposition對(duì)象來(lái)添加視頻音頻資源的AVMutableCompositionTrack
AVMutableComposition mixComposition = [AVMutableComposition composition];
//視頻采集compositionVideoTrack 添加視頻軌道
AVVideoComposition * videoComposition = [self addVideoCompositionTrack:mixComposition timeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration) assert:videoAsset];
//視頻聲音采集(也可不執(zhí)行這段代碼不采集視頻音軌,合并后的視頻文件將沒(méi)有視頻原來(lái)的聲音)
[self addAudioCompositionTrack:mixComposition timeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration) assert:videoAsset];
//AVAssetExportSession用于合并文件枢里,導(dǎo)出合并后文件孽鸡,presetName文件的輸出類(lèi)型
NSString *outPutPath = [kCachesVideoStorageEdit stringByAppendingPathComponent:@"convertVideo.mov"];
[self exportVideoSession:mixComposition outPath:outPutPath videoComposition:videoComposition completion:completionHandle];
}
- (void)RreplaceVideoWithTimeLeft:(float)leftTime rightTime:(float)rightTime InputURL:(NSURL*)inputURL completion:(void(^)(NSURL * outPutUrl,NSError * error))completionHandle {
}
//MARK: 轉(zhuǎn)換視頻格式,導(dǎo)出視頻
/**
轉(zhuǎn)換視頻格式栏豺,導(dǎo)出視頻
@param inputURL 輸入源
@param outputURL 輸出源
@param handler 回調(diào)
*/
-
(void)exportSessionVideoWithInputURL:(NSURL)inputURL completion:(void(^)(NSURL * outPutUrl,NSError * error))completionHandle fiter:(CIFilter)filter {
if (!inputURL.path || [inputURL.path isEqualToString:@""] || inputURL.path.length == NSNotFound) {
return;
}
//AVURLAsset此類(lèi)主要用于獲取媒體信息彬碱,包括視頻、聲音等
AVURLAsset* videoAsset = [[AVURLAsset alloc] initWithURL:inputURL options:nil];
NSArray tracks = videoAsset.tracks;
NSLog(@"所有軌道:%@\n",tracks);//打印出所有的資源軌道
//創(chuàng)建AVMutableComposition對(duì)象來(lái)添加視頻音頻資源的AVMutableCompositionTrack
// AVMutableComposition mixComposition = [AVMutableComposition composition];
//視頻采集compositionVideoTrack 添加視頻軌道
// AVMutableComposition * videoComposition = [self filterAVVideoCompositionInputSssset:videoAsset];
//AVAssetExportSession用于合并文件奥洼,導(dǎo)出合并后文件巷疼,presetName文件的輸出類(lèi)型NSString *outPutPath = [kCachesVideoStorageEdit stringByAppendingPathComponent:@"convertVideo.mov"];
// [self exportVideoSession:mixComposition outPath:outPutPath videoComposition:videoComposition completion:completionHandle];
// CIFilter *filter = [CIFilter filterWithName:@"CIColorInvert"];
AVVideoComposition *composition = [AVVideoComposition videoCompositionWithAsset:videoAsset applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest * _Nonnull request) {
CIImage *source = request.sourceImage.imageByClampingToExtent;
int currentTime = request.compositionTime.value / request.compositionTime.timescale;
if (currentTime <1 ) {
[request finishWithImage:source context:nil];
} else {
[filter setValue:source forKey:kCIInputImageKey];
CIImage *output = [filter.outputImage imageByCroppingToRect:request.sourceImage.extent];
[request finishWithImage:output context:nil];
}
}];
//AVAssetExportSession用于合并文件,導(dǎo)出合并后文件灵奖,presetName文件的輸出類(lèi)型
AVAssetExportSession *assetExportSession = [[AVAssetExportSession alloc] initWithAsset:videoAsset presetName:AVAssetExportPresetPassthrough];
//混合后的視頻輸出路徑
NSURL *outPutUrl = [NSURL fileURLWithPath:outPutPath];
if ([[NSFileManager defaultManager] fileExistsAtPath:outPutPath]){
[[NSFileManager defaultManager] removeItemAtPath:outPutPath error:nil];
}
//輸出視頻格式 AVFileTypeMPEG4 AVFileTypeQuickTimeMovie...
assetExportSession.outputFileType = AVFileTypeQuickTimeMovie;
// NSArray *fileTypes = assetExportSession.
assetExportSession.outputURL = outPutUrl;
//輸出文件是否網(wǎng)絡(luò)優(yōu)化
assetExportSession.shouldOptimizeForNetworkUse = YES;
//設(shè)置導(dǎo)出視頻的處理方案
if (composition) {
assetExportSession.videoComposition = composition;
}
[assetExportSession exportAsynchronouslyWithCompletionHandler:^{
// dispatch_async(dispatch_get_main_queue(), ^{
if (assetExportSession.status == AVAssetExportSessionStatusCompleted) {
NSLog(@"轉(zhuǎn)碼完成");
completionHandle(outPutUrl,nil);
}else {
NSLog(@"%@",assetExportSession.error);
}
// });
}];
}
-
(AVMutableComposition )filterAVVideoCompositionInputSssset:(AVURLAsset)videoAsset {
CIFilter *filter = [CIFilter filterWithName:@"CIColorInvert"];
AVVideoComposition *composition = [AVVideoComposition videoCompositionWithAsset:videoAsset applyingCIFiltersWithHandler:^(AVAsynchronousCIImageFilteringRequest * _Nonnull request) {
CIImage *source = request.sourceImage.imageByClampingToExtent;
int currentTime = request.compositionTime.value / request.compositionTime.timescale;
if (currentTime <1 ) {
[request finishWithImage:source context:nil];
} else {
[filter setValue:source forKey:kCIInputImageKey];CIImage *output = [filter.outputImage imageByCroppingToRect:request.sourceImage.extent]; [request finishWithImage:output context:nil]; }
}];
return composition;
}
//MARK: 相冊(cè)讀取之后照片路徑嚼沿,內(nèi)部會(huì)將相冊(cè)內(nèi)部的視頻緩存至沙盒路徑
/**
相冊(cè)讀取之后照片路徑,內(nèi)部會(huì)將相冊(cè)內(nèi)部的視頻緩存至沙盒路徑
@param info imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
@param result 緩存路徑
*/
- (void)getVideoLocalPath:(NSDictionary<NSString *,id> *)info result:(void(^)(NSURL * videoURL))result {
NSURL *videoURL = [info objectForKey:UIImagePickerControllerMediaURL]; // video path
if (videoURL) {
//處于沙盒下的 直接返回就可以了
if (result) {
result(videoURL);
}
} else {
//先判斷權(quán)限
[self checkVideoPermissionCompletionHandler:^(BOOL granted) {
if (granted) {
//相冊(cè)內(nèi)的視頻瓷患,需要先緩存到沙盒下面才能使用
NSURL *imageURL = [info valueForKey:UIImagePickerControllerReferenceURL];
PHFetchResult *fetchResult = [PHAsset fetchAssetsWithALAssetURLs:@[imageURL] options:nil];
PHAsset *asset = fetchResult.firstObject;
NSArray *assetResources = [PHAssetResource assetResourcesForAsset:asset];
PHAssetResource resource;
for (PHAssetResource assetRes in assetResources) {
if (assetRes.type == PHAssetResourceTypePairedVideo ||
assetRes.type == PHAssetResourceTypeVideo) {
resource = assetRes;
}
}
if (asset.mediaType == PHAssetMediaTypeVideo || asset.mediaSubtypes == PHAssetMediaSubtypePhotoLive) {
PHVideoRequestOptions options = [[PHVideoRequestOptions alloc] init];
options.version = PHImageRequestOptionsVersionCurrent;
options.deliveryMode = PHVideoRequestOptionsDeliveryModeAutomatic;
//#warning視頻路徑一定要放置cache路徑下面骡尽,不可以放在Temp路徑下,否則導(dǎo)出的時(shí)候會(huì)有問(wèn)題擅编。****
NSString * PATH_MOVIE_FILE = [kCachesVideoStorageEdit stringByAppendingPathComponent:kTempAssetVideo];
if ([[NSFileManager defaultManager]fileExistsAtPath:PATH_MOVIE_FILE]) {
[[NSFileManager defaultManager]removeItemAtPath:PATH_MOVIE_FILE error:nil];
}
[[PHAssetResourceManager defaultManager] writeDataForAssetResource:resource
toFile:[NSURL fileURLWithPath:PATH_MOVIE_FILE]
options:nil
completionHandler:^(NSError * _Nullable error) {
if (error) {
[JWHudLoadView alertMessage:error.domain];
} else {
NSLog(@"=====filePath:%@",PATH_MOVIE_FILE);
if (result) {
result([NSURL fileURLWithPath:PATH_MOVIE_FILE]);
}
}
}];
} else {
[JWHudLoadView alertMessage:@"暫不支持該視頻爆阶,請(qǐng)選擇其他視頻!"];
}
}else {
[JWHudLoadView alertMessage:@"需要您同意相冊(cè)權(quán)限"];
}
}];
}
}
//MARK: 清楚剪輯視頻緩存
/**
清楚剪輯視頻緩存
*/
-
(void)clearCutVideoCache {
NSLog(@"-------清除上傳視頻資源");
NSString *outPutPath = [kCachesVideoStorageEdit stringByAppendingPathComponent:kCutVideoPath];
if ([[NSFileManager defaultManager] fileExistsAtPath:outPutPath]) {
[[NSFileManager defaultManager] removeItemAtPath:outPutPath error:nil];
}
outPutPath = [kCachesVideoStorageEdit stringByAppendingPathComponent:kDubVideoPath];
if ([[NSFileManager defaultManager] fileExistsAtPath:outPutPath]) {
[[NSFileManager defaultManager] removeItemAtPath:outPutPath error:nil];
}
outPutPath = [kCachesVideoStorageEdit stringByAppendingPathComponent:kMergeVideoPath];
if ([[NSFileManager defaultManager] fileExistsAtPath:outPutPath]) {
[[NSFileManager defaultManager] removeItemAtPath:outPutPath error:nil];
}outPutPath = [kCachesVideoStorageEdit stringByAppendingPathComponent:kMergeAudioPath];
if ([[NSFileManager defaultManager] fileExistsAtPath:outPutPath]) {
[[NSFileManager defaultManager] removeItemAtPath:outPutPath error:nil];
}
}
+(NSString )getNowTimeTimestamp2{
NSDate dat = [NSDate dateWithTimeIntervalSinceNow:0];
NSTimeInterval a=[dat timeIntervalSince1970];
NSString*timeString = [NSString stringWithFormat:@"%0.f", a];//轉(zhuǎn)為字符型
return timeString;
}
@end