AVAudioFoundation(1):使用 AVAsset

本文轉(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):

image

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)的處理贺纲。AVAssetAVAssetTrack 都是遵循 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谦絮,比如用 maximumSizeapertureMode 來(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)換绷跑。流程如下圖所示:

image

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,用 outputURLoutputFileType 接口設(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)出。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末苟鸯,一起剝皮案震驚了整個(gè)濱河市同蜻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌早处,老刑警劉巖湾蔓,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異砌梆,居然都是意外死亡默责,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)咸包,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)桃序,“玉大人,你說(shuō)我怎么就攤上這事烂瘫∶叫埽” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵忱反,是天一觀的道長(zhǎng)泛释。 經(jīng)常有香客問(wèn)我,道長(zhǎng)温算,這世上最難降的妖魔是什么怜校? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮注竿,結(jié)果婚禮上茄茁,老公的妹妹穿的比我還像新娘魂贬。我一直安慰自己,他們只是感情好裙顽,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布付燥。 她就那樣靜靜地躺著,像睡著了一般愈犹。 火紅的嫁衣襯著肌膚如雪键科。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天漩怎,我揣著相機(jī)與錄音勋颖,去河邊找鬼。 笑死勋锤,一個(gè)胖子當(dāng)著我的面吹牛饭玲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叁执,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼茄厘,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了谈宛?” 一聲冷哼從身側(cè)響起次哈,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎入挣,沒(méi)想到半個(gè)月后亿乳,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡径筏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年葛假,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片滋恬。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡聊训,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恢氯,到底是詐尸還是另有隱情带斑,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布勋拟,位于F島的核電站勋磕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏敢靡。R本人自食惡果不足惜挂滓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望啸胧。 院中可真熱鬧赶站,春花似錦幔虏、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至烙博,卻和暖如春瑟蜈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背习勤。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工踪栋, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留焙格,地道東北人图毕。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像眷唉,于是被迫代替她去往敵國(guó)和親予颤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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