本文轉(zhuǎn)自:AVAudioFoundation(1):使用 AVAsset | www.samirchen.com
本文主要內(nèi)容來(lái)自 AVFoundation Programming Guide丸卷。
要了解 iOS 上的音視頻相關(guān)的內(nèi)容缘圈,首先需要了解的就是 AVFoundation
這個(gè)框架唆香。
下圖是 AVFoundation
框架大的層級(jí)結(jié)構(gòu):
在 AVFoundation
框架中问拘,最主要的表示媒體的類(lèi)就是 AVAsset
,甚至可以認(rèn)為 AVFoundation
框架的大部分能力都是圍繞著 AVAsset
展開(kāi)的。
一個(gè) AVAsset
實(shí)例表示的是一份或多份音視頻數(shù)據(jù)(audio and video tracks)的集合羔挡,它描述的是這個(gè)集合作為一個(gè)整體對(duì)象的一些屬性敬鬓,比如:標(biāo)題、時(shí)長(zhǎng)偏塞、大小等唱蒸,而不與具體的數(shù)據(jù)格式綁定。通常灸叼,在實(shí)際使用時(shí)我們可能會(huì)基于某個(gè) URL 創(chuàng)建對(duì)應(yīng)的媒體資源對(duì)象(AVURLAsset)神汹,或者直接創(chuàng)建 compositions(AVComposition),這些類(lèi)都是 AVAsset
的子類(lèi)古今。
一個(gè) AVAsset
中的每一份音頻或視頻數(shù)據(jù)都稱為一個(gè)軌道(track)屁魏。在最簡(jiǎn)單的情況下,一個(gè)媒體文件中可能只有兩個(gè)軌道捉腥,一個(gè)音頻軌道氓拼,一個(gè)視頻軌道。而復(fù)雜的組合中抵碟,可能包含多個(gè)重疊的音頻軌道和視頻軌道披诗。此外 AVAsset
也可能包含元數(shù)據(jù)(metadata)。
在 AVFoundation
中另一個(gè)非常重要的概念是立磁,初始化一個(gè) AVAsset
或者一個(gè) AVAssetTrack
時(shí)并不一定意味著它已經(jīng)可以立即使用呈队,因?yàn)檫@需要一段時(shí)間來(lái)做計(jì)算,而這個(gè)計(jì)算可能會(huì)阻塞當(dāng)前線程唱歧,所以通常你可以選用異步的方式來(lái)初始化宪摧,并通過(guò)回調(diào)來(lái)得到異步返回。
我們可以從一個(gè)文件或者用戶的相冊(cè)中來(lái)創(chuàng)建 asset颅崩。獲得一個(gè)視頻 asset 時(shí)几于,我們可以從中提出靜態(tài)圖,對(duì)其進(jìn)行轉(zhuǎn)碼沿后,裁剪器內(nèi)容沿彭。
創(chuàng)建 Asset
當(dāng)使用一個(gè) URL 來(lái)創(chuàng)建 asset 時(shí),可以用 AVURLAsset
:
NSURL *url = <#A URL that identifies an audiovisual asset such as a movie file#>;
AVURLAsset *anAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
設(shè)置 Asset 選項(xiàng)
可以看到當(dāng)我們創(chuàng)建一個(gè) AVURLAsset
時(shí)尖滚,是可以設(shè)置一個(gè)對(duì)象的 options 的喉刘,這里可選的設(shè)置項(xiàng)包括:
-
AVURLAssetPreferPreciseDurationAndTimingKey
,這個(gè)選項(xiàng)對(duì)應(yīng)的值是布爾值漆弄,默認(rèn)為@(NO)
睦裳,當(dāng)設(shè)為@(YES)
時(shí)表示 asset 應(yīng)該提供精確的時(shí)長(zhǎng),并能根據(jù)時(shí)間準(zhǔn)確地隨機(jī)訪問(wèn)撼唾,提供這樣的能力是需要開(kāi)銷(xiāo)更大的計(jì)算的廉邑。當(dāng)你只是想播放視頻時(shí),你可以不設(shè)置這個(gè)選項(xiàng),但是如果你想把這個(gè) asset 添加到一個(gè) composition(AVMutableComposition)中去做進(jìn)一步編輯蛛蒙,你通常需要精確的隨機(jī)訪問(wèn)糙箍,這時(shí)你最好設(shè)置這個(gè)選項(xiàng)為 YES。 -
AVURLAssetReferenceRestrictionsKey
牵祟,這個(gè)選項(xiàng)對(duì)應(yīng)的值是AVAssetReferenceRestrictions
enum倍靡。有一些 asset 可以保護(hù)一些指向外部數(shù)據(jù)的引用,這個(gè)選項(xiàng)用來(lái)表示對(duì)外部數(shù)據(jù)訪問(wèn)的限制课舍。具體含義參見(jiàn)AVAssetReferenceRestrictions
塌西。 -
AVURLAssetHTTPCookiesKey
,這個(gè)選項(xiàng)用來(lái)設(shè)置 asset 通過(guò) HTTP 請(qǐng)求發(fā)送的 HTTP cookies筝尾,當(dāng)然 cookies 只能發(fā)給同站捡需。具體參見(jiàn)文檔。 -
AVURLAssetAllowsCellularAccessKey
筹淫,這個(gè)選項(xiàng)對(duì)應(yīng)的值是布爾值站辉,默認(rèn)為@(YES)
饰剥。表示 asset 是否能使用移動(dòng)網(wǎng)絡(luò)資源棒卷。
不過(guò)你要注意這幾個(gè)選項(xiàng)適用的 iOS 版本。
NSURL *url = <#A URL that identifies an audiovisual asset such as a movie file#>;
NSDictionary *options = @{ AVURLAssetPreferPreciseDurationAndTimingKey : @YES };
AVURLAsset *anAssetToUseInAComposition = [[AVURLAsset alloc] initWithURL:url options:options];
訪問(wèn)用戶的 Asset
獲取用戶相冊(cè)的資源時(shí),你需要借用 ALAssetsLibrary
的相關(guān)接口:
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
// Enumerate just the photos and videos group by using ALAssetsGroupSavedPhotos.
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
// Within the group enumeration block, filter to enumerate just videos.
[group setAssetsFilter:[ALAssetsFilter allVideos]];
// For this example, we're only interested in the first item.
[group enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:0]
options:0
usingBlock:^(ALAsset *alAsset, NSUInteger index, BOOL *innerStop) {
// The end of the enumeration is signaled by asset == nil.
if (alAsset) {
ALAssetRepresentation *representation = [alAsset defaultRepresentation];
NSURL *url = [representation url];
AVAsset *avAsset = [AVURLAsset URLAssetWithURL:url options:nil];
// Do something interesting with the AV asset.
}
}];
}
failureBlock: ^(NSError *error) {
// Typically you should handle an error more gracefully than this.
NSLog(@"No groups");
}];
加載 Asset 來(lái)使用
初始化一個(gè) AVAsset
或者一個(gè) AVAssetTrack
時(shí)并不一定意味著它已經(jīng)可以立即使用肃晚,因?yàn)檫@需要一段時(shí)間來(lái)做計(jì)算监徘,而這個(gè)計(jì)算可能會(huì)阻塞當(dāng)前線程,所以通常你可以選用異步的方式來(lái)初始化,并通過(guò)回調(diào)來(lái)得到異步返回。
這時(shí)你可以使用 AVAsynchronousKeyValueLoading
protocol 來(lái)獲取加載 asset 的狀態(tài)皮假,并在對(duì)應(yīng)的 completion handler 中做對(duì)應(yīng)的處理贺纲。AVAsset
和 AVAssetTrack
都是遵循 AVAsynchronousKeyValueLoading
protocol 的稠肘。下面是一個(gè)示例:
NSURL *url = <#A URL that identifies an audiovisual asset such as a movie file#>;
AVURLAsset *anAsset = [[AVURLAsset alloc] initWithURL:url options:nil];
NSArray *keys = @[@"duration"];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^() {
NSError *error = nil;
AVKeyValueStatus tracksStatus = [asset statusOfValueForKey:@"duration" error:&error];
switch (tracksStatus) {
case AVKeyValueStatusLoaded:
[self updateUserInterfaceForDuration];
break;
case AVKeyValueStatusFailed:
[self reportError:error forAsset:asset];
break;
case AVKeyValueStatusCancelled:
// Do whatever is appropriate for cancelation.
break;
}
}];
需要注意的是:當(dāng)你需要加載一個(gè) asset 來(lái)點(diǎn)播环揽,你應(yīng)該加載它的 tracks
屬性巴粪。
獲取視頻截圖
我們可以用一個(gè) AVAssetImageGenerator
實(shí)例來(lái)獲取視頻中的截圖。即使初始化時(shí)在 asset 中沒(méi)有檢查到視覺(jué) track储矩,AVAssetImageGenerator
的初始化也可能會(huì)成功即硼,所以必要的情況下,你可以用 tracksWithMediaCharacteristic:
方法去檢查一下 asset 是否有可用的視覺(jué) track舆蝴。
AVAsset anAsset = <#Get an asset#>;
if ([[anAsset tracksWithMediaType:AVMediaTypeVideo] count] > 0) {
AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:anAsset];
// Implementation continues...
}
我們可以配置一下 AVAssetImageGenerator
谦絮,比如用 maximumSize
和 apertureMode
來(lái)指定生成圖像的最大尺寸和光圈模式。接下來(lái)洁仗,可以生成指定時(shí)間的一張截圖或者一系列圖集层皱。必須保證在生成圖片時(shí)對(duì) AVAssetImageGenerator
實(shí)例的強(qiáng)引用。
獲取一張圖片
我們可以用 copyCGImageAtTime:actualTime:error:
來(lái)獲得指定時(shí)間的截圖赠潦。AVFoundation
也許無(wú)法精確地獲得你指定時(shí)間的截圖叫胖,所以你需要傳入一個(gè) actualTime 參數(shù)來(lái)獲得截圖所對(duì)應(yīng)的實(shí)際時(shí)間。
AVAsset *myAsset = <#An asset#>];
AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:myAsset];
Float64 durationSeconds = CMTimeGetSeconds([myAsset duration]);
CMTime midpoint = CMTimeMakeWithSeconds(durationSeconds/2.0, 600);
NSError *error;
CMTime actualTime;
CGImageRef halfWayImage = [imageGenerator copyCGImageAtTime:midpoint actualTime:&actualTime error:&error];
if (halfWayImage != NULL) {
NSString *actualTimeString = (NSString *)CMTimeCopyDescription(NULL, actualTime);
NSString *requestedTimeString = (NSString *)CMTimeCopyDescription(NULL, midpoint);
NSLog(@"Got halfWayImage: Asked for %@, got %@", requestedTimeString, actualTimeString);
// Do something interesting with the image.
CGImageRelease(halfWayImage);
}
獲取一組截圖
我們可以用 generateCGImagesAsynchronouslyForTimes:completionHandler:
接口來(lái)傳入一組時(shí)間來(lái)獲取相應(yīng)的一組截圖她奥。同樣的瓮增,必須保證在生成圖片時(shí)對(duì) AVAssetImageGenerator
實(shí)例的強(qiáng)引用。示例代碼如下:
AVAsset *myAsset = <#An asset#>];
// Assume: @property (strong) AVAssetImageGenerator *imageGenerator;
self.imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:myAsset];
Float64 durationSeconds = CMTimeGetSeconds([myAsset duration]);
CMTime firstThird = CMTimeMakeWithSeconds(durationSeconds/3.0, 600);
CMTime secondThird = CMTimeMakeWithSeconds(durationSeconds*2.0/3.0, 600);
CMTime end = CMTimeMakeWithSeconds(durationSeconds, 600);
NSArray *times = @[NSValue valueWithCMTime:kCMTimeZero],
[NSValue valueWithCMTime:firstThird], [NSValue valueWithCMTime:secondThird],
[NSValue valueWithCMTime:end]];
[imageGenerator generateCGImagesAsynchronouslyForTimes:times
completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error) {
NSString *requestedTimeString = (NSString *)
CFBridgingRelease(CMTimeCopyDescription(NULL, requestedTime));
NSString *actualTimeString = (NSString *)
CFBridgingRelease(CMTimeCopyDescription(NULL, actualTime));
NSLog(@"Requested: %@; actual %@", requestedTimeString, actualTimeString);
if (result == AVAssetImageGeneratorSucceeded) {
// Do something interesting with the image.
}
if (result == AVAssetImageGeneratorFailed) {
NSLog(@"Failed with error: %@", [error localizedDescription]);
}
if (result == AVAssetImageGeneratorCancelled) {
NSLog(@"Canceled");
}
}];
我們還能使用 cancelAllCGImageGeneration
接口來(lái)中斷截圖哩俭。
對(duì)視頻進(jìn)行裁剪和轉(zhuǎn)碼
我們可以使用一個(gè) AVAssetExportSession
實(shí)例來(lái)對(duì)視頻進(jìn)行裁剪或格式轉(zhuǎn)換绷跑。流程如下圖所示:
AVAssetExportSession
實(shí)例用來(lái)控制異步的導(dǎo)出 asset。使用 export session 時(shí)凡资,首先我們需要傳入要導(dǎo)出的 asset 和對(duì)應(yīng)的 preset 配置砸捏,我們可以用 allExportPresets
接口來(lái)查看所有可用的 preset 配置。接著隙赁,你需要設(shè)置導(dǎo)出的 URL 和文件類(lèi)型垦藏。此外,我們還能設(shè)置導(dǎo)出視頻文件的 metadata 以及導(dǎo)出的是否應(yīng)該針對(duì)網(wǎng)絡(luò)訪問(wèn)優(yōu)化伞访。
在下面的示例代碼中掂骏,我們用 exportPresetsCompatibleWithAsset:
接口檢查可用的 preset,用 outputURL
和 outputFileType
接口設(shè)置導(dǎo)出 URL 和導(dǎo)出文件類(lèi)型厚掷,通過(guò) timeRange
設(shè)置導(dǎo)出時(shí)間段弟灼。此外级解,我們還能用 shouldOptimizeForNetworkUse
接口設(shè)置是否針對(duì)網(wǎng)絡(luò)使用優(yōu)化以方便秒開(kāi),用 maxDuration
袜爪、fileLengthLimit
設(shè)置導(dǎo)入限制等等蠕趁。
我們用 exportAsynchronouslyWithCompletionHandler:
接口來(lái)開(kāi)始導(dǎo)出薛闪。
AVAsset *anAsset = <#Get an asset#>;
NSArray *compatiblePresets = [AVAssetExportSession exportPresetsCompatibleWithAsset:anAsset];
if ([compatiblePresets containsObject:AVAssetExportPresetLowQuality]) {
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:anAsset presetName:AVAssetExportPresetLowQuality];
exportSession.outputURL = <#A file URL#>;
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
CMTime start = CMTimeMakeWithSeconds(1.0, 600);
CMTime duration = CMTimeMakeWithSeconds(3.0, 600);
CMTimeRange range = CMTimeRangeMake(start, duration);
exportSession.timeRange = range;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
switch ([exportSession status]) {
case AVAssetExportSessionStatusFailed:
NSLog(@"Export failed: %@", [[exportSession error] localizedDescription]);
break;
case AVAssetExportSessionStatusCancelled:
NSLog(@"Export canceled");
break;
default:
break;
}
}];
}
此外辛馆,我們還可以用 cancelExport
接口來(lái)取消導(dǎo)出。
當(dāng)我們想要覆蓋已有的文件豁延,或者向應(yīng)用沙盒外寫(xiě)文件時(shí)昙篙,導(dǎo)出會(huì)失敗。此外诱咏,在導(dǎo)出時(shí)突然來(lái)了電話苔可、導(dǎo)出時(shí)應(yīng)用在后臺(tái)狀態(tài)并且其他應(yīng)用開(kāi)始播放時(shí)導(dǎo)出也可能會(huì)失敗。在這些情況下袋狞,你需要提示用戶導(dǎo)出失敗焚辅,并允許用戶重新導(dǎo)出。