AVFoundation開發(fā)秘籍筆記-09媒體的組合和編輯

一、組合媒體

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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末闪湾,一起剝皮案震驚了整個濱河市冲甘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌途样,老刑警劉巖江醇,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異何暇,居然都是意外死亡陶夜,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進(jìn)店門裆站,熙熙樓的掌柜王于貴愁眉苦臉地迎上來条辟,“玉大人,你說我怎么就攤上這事宏胯∮鸬眨” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵肩袍,是天一觀的道長杭棵。 經(jīng)常有香客問我,道長氛赐,這世上最難降的妖魔是什么魂爪? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮艰管,結(jié)果婚禮上滓侍,老公的妹妹穿的比我還像新娘。我一直安慰自己牲芋,他們只是感情好撩笆,可當(dāng)我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著缸浦,像睡著了一般夕冲。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上餐济,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天耘擂,我揣著相機與錄音胆剧,去河邊找鬼絮姆。 笑死醉冤,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的篙悯。 我是一名探鬼主播蚁阳,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鸽照!你這毒婦竟也來了螺捐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤矮燎,失蹤者是張志新(化名)和其女友劉穎定血,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诞外,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡澜沟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了峡谊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茫虽。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖既们,靈堂內(nèi)的尸體忽然破棺而出濒析,到底是詐尸還是另有隱情,我是刑警寧澤啥纸,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布号杏,位于F島的核電站,受9級特大地震影響脾拆,放射性物質(zhì)發(fā)生泄漏馒索。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一名船、第九天 我趴在偏房一處隱蔽的房頂上張望绰上。 院中可真熱鬧,春花似錦渠驼、人聲如沸蜈块。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽百揭。三九已至,卻和暖如春蜓席,著一層夾襖步出監(jiān)牢的瞬間器一,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工厨内, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留祈秕,地道東北人渺贤。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像请毛,于是被迫代替她去往敵國和親志鞍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容