一、組合媒體
AVFoundation有關(guān)資源的組合功能源于AVAsset的子類AVComposition狸臣。
一個組合就是將其他幾種媒體資源組合成一個自定義的臨時排列票腰,再將這個臨時排列視為一個可以呈現(xiàn)或處理的獨立媒體項目。比如AVAsset對象纯陨,組合相當(dāng)于包含了一個或多個給定類型的媒體軌道的容器政溃。
AVCamposition中的軌道都是AVAssetTrack的子類AVCompositionTrack趾访。
一個組合軌道本身由一個或多個媒體片段組成,有AVCompositionTrackSegment定義董虱,代表組合中的實際媒體區(qū)域腹缩。
AVAsset到特殊媒體文件具有直接一對一的映射,組合的概念更像一組說明,描述多個資源間如何正確地呈現(xiàn)或處理藏鹊。
AVComposition及其相關(guān)類沒有遵循NSCoding協(xié)議润讥,所以不能簡單地將一個組合在內(nèi)存的狀態(tài)歸檔到磁盤上。如果需要創(chuàng)建具有保存項目文件能力的音頻或視頻編輯程序盘寡,需要自定義的數(shù)據(jù)模型類來保存這個狀態(tài)楚殿。
AVComposition和AVCompositionTrack都是不可變對象,提供對資源的只讀操作竿痰。
創(chuàng)建自己的組合脆粥,就需要使用AVMutableComposition和AVMutableCompositionTrack所提供的可變子類。
要創(chuàng)建自定義組合影涉,需要指定在將要添加到組合的源媒體的時間范圍变隔,還要指定添加片段的每個軌道的位置。
二蟹倾、組合的基礎(chǔ)方法
(一)匣缘、創(chuàng)建資源
創(chuàng)建一個組合資源需要擁有一個或多個等待處理的AVAsset對象。創(chuàng)建資源用于組合時鲜棠,需要創(chuàng)建AVAsset的子類AVURLAsset的實例肌厨。
NSURL *url = [[NSBundle mainBundle] URLForResource:@"video" withExtension:@"mp4"];
//AVURLAssetPreferPreciseDurationAndTimingKey值為YES確保當(dāng)資源的屬性使用
//AVAsynchronousKeyValueLoading協(xié)議載入時可以計算出準(zhǔn)確的時長和時間信息。
//會對載入過程增加一些額外開銷豁陆,但可以保證資源正處于合適的編輯狀態(tài)柑爸。
NSDictionary *options = @{AVURLAssetPreferPreciseDurationAndTimingKey:@YES};
AVAsset *asset = [AVURLAsset URLAssetWithURL:url options:options];
NSArray *keys = @[@"tracks",@"duration",@"commonMetadata",];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{
}];
(二)、創(chuàng)建組合資源
案例將從兩個視頻片段中第一個5秒視頻拿出來盒音,并按照組合視頻軌道的順序進(jìn)行排列表鳍。還會從一個MP3文件將音頻軌道整合到視頻中。
- 1祥诽、創(chuàng)建資源
NSURL *url = [[NSBundle mainBundle] URLForResource:@"video" withExtension:@"mp4"];
NSURL *url1 = [[NSBundle mainBundle] URLForResource:@"video" withExtension:@"mp4"];
NSURL *url2 = [[NSBundle mainBundle] URLForResource:@"audio" withExtension:@"mp3"];
//AVURLAssetPreferPreciseDurationAndTimingKey值為YES確保當(dāng)資源的屬性使用
//AVAsynchronousKeyValueLoading協(xié)議載入時可以計算出準(zhǔn)確的時長和時間信息譬圣。
//會對載入過程增加一些額外開銷,但可以保證資源正處于合適的編輯狀態(tài)原押。
NSDictionary *options = @{AVURLAssetPreferPreciseDurationAndTimingKey:@YES};
AVAsset *videoAsset1 = [AVURLAsset URLAssetWithURL:url options:options];
AVAsset *videoAsset2 = [AVURLAsset URLAssetWithURL:url1 options:options];
AVAsset *audioAsset = [AVURLAsset URLAssetWithURL:url2 options:options];
- 2、創(chuàng)建
AVMutableComposition
并添加兩個軌道對象
//創(chuàng)建AVMutableComposition偎血,并添加兩個軌道對象诸衔。
AVMutableComposition *composition = [AVMutableComposition composition];
//創(chuàng)建組合軌道
AVMutableCompositionTrack *videoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
//創(chuàng)建組合軌道時,必須指明它所能支持的媒體類型颇玷。并給出一個軌道標(biāo)示符笨农。preferredTrackID:,是一個32位整數(shù)值,這個標(biāo)示符在之后返回軌道時會用到帖渠。一般來說都是賦給它一個常量谒亦。
kCMPersistentTrackID_Invalid表示需要創(chuàng)建一個合適軌道ID的任務(wù)委托給框架,標(biāo)識會以1..n排列
創(chuàng)建兩個軌道,包括video軌道和Audio軌道份招,后面可以分別向兩個軌道組合添加內(nèi)容
- 3切揭、將獨立媒體片段插入到組合軌道
#pragma mark -- 將獨立媒體片段插入到組合的軌道內(nèi)。
//定義CMTime表示插入光標(biāo)點锁摔,表示插入媒體片段的位置
CMTime cursorTime = kCMTimeZero;
//目標(biāo)是捕捉每個視頻前5秒的內(nèi)容廓旬,創(chuàng)建一個CMTimeRange,從kCMTimeZero開始谐腰,持續(xù)時間5秒
CMTime videoDuration = CMTimeMake(5, 1);
CMTimeRange videoTimeRange = CMTimeRangeMake(kCMTimeZero, videoDuration);
//從第一個AVAsset中提取視頻軌道孕豹,返回一個匹配給定媒體類型的軌道數(shù)組
//不過由于這個媒體只包含一個單獨的視頻軌道,只取第一個對象
AVAssetTrack *assetTrack;
assetTrack = [[videoAsset1 tracksWithMediaType:AVMediaTypeVideo] firstObject];
//在視頻軌道上將視頻片段插入到軌道中十气。
[videoTrack insertTimeRange:videoTimeRange ofTrack:assetTrack atTime:cursorTime error:nil];
//移動光標(biāo)插入時間励背,讓下一段內(nèi)容在另一段內(nèi)容最后插入。
cursorTime = CMTimeAdd(cursorTime, videoDuration);
//去資源視頻軌道并將它插入組合資源的視頻軌道上砸西。
assetTrack = [[videoAsset2 tracksWithMediaType:AVMediaTypeVideo] firstObject];
[videoTrack insertTimeRange:videoTimeRange ofTrack:assetTrack atTime:cursorTime error:nil];
//希望音頻軌道覆蓋整個視頻剪輯叶眉,充值cursorTime到kCMTimeZero
cursorTime = kCMTimeZero;
//獲取組合資源的總duration值,并創(chuàng)建一個CMTimeRange籍胯,擴(kuò)展為整個組合時長竟闪。
CMTime audioDuration = composition.duration;
CMTimeRange audioTimeRange = CMTimeRangeMake(kCMTimeZero, audioDuration);
//從音頻資源中提取音頻軌道并將它插入組合的音頻軌道
assetTrack = [[audioAsset tracksWithMediaType:AVMediaTypeAudio] firstObject];
[audioTrack insertTimeRange:audioTimeRange ofTrack:assetTrack atTime:cursorTime error:nil];
視頻軌道與音頻軌道時分開的,所以時間上要單獨處理杖狼。
組合的資源與其他Asset一樣炼蛤,可以播放、導(dǎo)出蝶涩、處理理朋。
三、導(dǎo)出組合
導(dǎo)出使用的是AVAssetExportSession
绿聘,通過AVComposition
及一個預(yù)設(shè)屬性presetName
初始化嗽上。
設(shè)置導(dǎo)出地址outputURL
,以及導(dǎo)出文件類型outputFileType
熄攘。
NSString *preset = AVAssetExportPresetHighestQuality;
self.exportSession = [AVAssetExportSession exportSessionWithAsset:[self.composition copy] presetName:preset]
// 為會話動態(tài)生成一個唯一的輸出URL兽愤,設(shè)置輸出文件類型
self.exportSession.outputURL = [self exportURL];
self.exportSession.outputFileType = AVFileTypeMPEG4;
接下來便是導(dǎo)出過程
// 開始導(dǎo)出過程
[self.exportSession exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
// 導(dǎo)出完成回調(diào)
});
}];
@interface THCompositionExporter ()
@property (strong, nonatomic) id <THComposition> composition;
@property (strong, nonatomic) AVAssetExportSession *exportSession;
@end
@implementation THCompositionExporter
- (instancetype)initWithComposition:(id <THComposition>)composition {
self = [super init];
if (self) {
_composition = composition;
}
return self;
}
- (void)beginExport {
// Listing 9.9
// 創(chuàng)建一個組合的可導(dǎo)出版本,返回一個AVAssetExportSession
/**
- (AVAssetExportSession *)makeExportable {
// 創(chuàng)建新的AVAssetExportSession實例挪圾,得到一個AVMutableComposition副本浅萧。
NSString *preset = AVAssetExportPresetHighestQuality;
return [AVAssetExportSession exportSessionWithAsset:[self.composition copy] presetName:preset];
}
**/
self.exportSession = [self.composition makeExportable];
// 為會話動態(tài)生成一個唯一的輸出URL,設(shè)置輸出文件類型
self.exportSession.outputURL = [self exportURL];
self.exportSession.outputFileType = AVFileTypeMPEG4;
// 開始導(dǎo)出過程
[self.exportSession exportAsynchronouslyWithCompletionHandler:^{
dispatch_async(dispatch_get_main_queue(), ^{
AVAssetExportSessionStatus status = self.exportSession.status;
// 檢查導(dǎo)出狀態(tài) 哲思,成功保存到相冊
if (status == AVAssetExportSessionStatusCompleted) {
[self writeExportedVideoToAssetsLibrary];
} else {
NSLog(@"Export Failed" message:@"Export failed");
}
});
}];
// 設(shè)置exporting為YES洼畅,通過監(jiān)聽該屬性變化,呈現(xiàn)用戶界面進(jìn)展
self.exporting = YES;
// 輪詢導(dǎo)出會話狀態(tài)棚赔,確定當(dāng)前進(jìn)度
[self monitorExportProgress];
}
// 監(jiān)視導(dǎo)出過程
- (void)monitorExportProgress {
// Listing 9.10
double delayInsSeconds = 0.1;
int64_t delta = (int64_t)delayInsSeconds * NSEC_PER_SEC;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delta);
// 短暫的延遲之后將一段執(zhí)行代碼放入隊列帝簇,檢查導(dǎo)出過程的狀態(tài)
dispatch_after(popTime, dispatch_get_main_queue(), ^{
AVAssetExportSessionStatus status = self.exportSession.status;
// 檢查當(dāng)初會話的status屬性確定當(dāng)前狀態(tài)徘郭。
// AVAssetExportSessionStatusExporting 用當(dāng)前會話進(jìn)度更新progress屬性,并遞歸調(diào)用monitorExportProgress方法
// 如果status屬性返回其他值丧肴,這是exporting屬性為NO残揉,用戶界面做出相應(yīng)更新。
if (status == AVAssetExportSessionStatusExporting) {
self.progress = self.exportSession.progress;
[self monitorExportProgress];
} else {
self.exporting = NO;
}
});
}
- (void)writeExportedVideoToAssetsLibrary {
// Listing 9.11
NSURL *exportUrl = self.exportSession.outputURL;
// 將導(dǎo)出視頻保存到相冊
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:exportUrl]) {
[library writeVideoAtPathToSavedPhotosAlbum:exportUrl completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
NSLog(@"Write Failed" message:message);
}
[[NSFileManager defaultManager] removeItemAtURL:exportUrl error:nil];
}];
} else {
NSLog(@"Video could not b exported to the assets library.");
}
}
- (NSURL *)exportURL {
NSString *filePath = nil;
NSUInteger count = 0;
do {
filePath = NSTemporaryDirectory();
NSString *numberString = count > 0 ?
[NSString stringWithFormat:@"-%li", (unsigned long) count] : @"";
NSString *fileNameString =
[NSString stringWithFormat:@"Masterpiece-%@.m4v", numberString];
filePath = [filePath stringByAppendingPathComponent:fileNameString];
count++;
} while ([[NSFileManager defaultManager] fileExistsAtPath:filePath]);
return [NSURL fileURLWithPath:filePath];
}
@end