AVAudioFoundation(1):使用 AVAsset

本文轉(zhuǎn)自:AVAudioFoundation(1):使用 AVAsset | www.samirchen.com

本文主要內(nèi)容來自 AVFoundation Programming Guide

要了解 iOS 上的音視頻相關(guān)的內(nèi)容,首先需要了解的就是 AVFoundation 這個(gè)框架。

下圖是 AVFoundation 框架大的層級(jí)結(jié)構(gòu):

image

AVFoundation 框架中,最主要的表示媒體的類就是 AVAsset,甚至可以認(rèn)為 AVFoundation 框架的大部分能力都是圍繞著 AVAsset 展開的。

一個(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),這些類都是 AVAsset 的子類。

一個(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í)間來做計(jì)算,而這個(gè)計(jì)算可能會(huì)阻塞當(dāng)前線程,所以通常你可以選用異步的方式來初始化,并通過回調(diào)來得到異步返回。

我們可以從一個(gè)文件或者用戶的相冊(cè)中來創(chuàng)建 asset。獲得一個(gè)視頻 asset 時(shí),我們可以從中提出靜態(tài)圖,對(duì)其進(jìn)行轉(zhuǎn)碼,裁剪器內(nèi)容。

創(chuàng)建 Asset

當(dāng)使用一個(gè) URL 來創(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ī)訪問,提供這樣的能力是需要開銷更大的計(jì)算的。當(dāng)你只是想播放視頻時(shí),你可以不設(shè)置這個(gè)選項(xiàng),但是如果你想把這個(gè) asset 添加到一個(gè) composition(AVMutableComposition)中去做進(jìn)一步編輯,你通常需要精確的隨機(jī)訪問,這時(shí)你最好設(shè)置這個(gè)選項(xiàng)為 YES。
  • AVURLAssetReferenceRestrictionsKey,這個(gè)選項(xiàng)對(duì)應(yīng)的值是 AVAssetReferenceRestrictions enum。有一些 asset 可以保護(hù)一些指向外部數(shù)據(jù)的引用,這個(gè)選項(xiàng)用來表示對(duì)外部數(shù)據(jù)訪問的限制。具體含義參見 AVAssetReferenceRestrictions
  • AVURLAssetHTTPCookiesKey,這個(gè)選項(xiàng)用來設(shè)置 asset 通過 HTTP 請(qǐng)求發(fā)送的 HTTP cookies,當(dāng)然 cookies 只能發(fā)給同站。具體參見文檔。
  • AVURLAssetAllowsCellularAccessKey,這個(gè)選項(xiàng)對(duì)應(yīng)的值是布爾值,默認(rèn)為 @(YES)。表示 asset 是否能使用移動(dòng)網(wǎng)絡(luò)資源。

不過你要注意這幾個(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];

訪問用戶的 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 來使用

初始化一個(gè) AVAsset 或者一個(gè) AVAssetTrack 時(shí)并不一定意味著它已經(jīng)可以立即使用,因?yàn)檫@需要一段時(shí)間來做計(jì)算,而這個(gè)計(jì)算可能會(huì)阻塞當(dāng)前線程,所以通常你可以選用異步的方式來初始化,并通過回調(diào)來得到異步返回。

這時(shí)你可以使用 AVAsynchronousKeyValueLoading protocol 來獲取加載 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 來點(diǎn)播,你應(yīng)該加載它的 tracks 屬性。

獲取視頻截圖

我們可以用一個(gè) AVAssetImageGenerator 實(shí)例來獲取視頻中的截圖。即使初始化時(shí)在 asset 中沒有檢查到視覺 track,AVAssetImageGenerator 的初始化也可能會(huì)成功,所以必要的情況下,你可以用 tracksWithMediaCharacteristic: 方法去檢查一下 asset 是否有可用的視覺 track。

AVAsset anAsset = <#Get an asset#>;
if ([[anAsset tracksWithMediaType:AVMediaTypeVideo] count] > 0) {
    AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:anAsset];
    // Implementation continues...
}

我們可以配置一下 AVAssetImageGenerator,比如用 maximumSizeapertureMode 來指定生成圖像的最大尺寸和光圈模式。接下來,可以生成指定時(shí)間的一張截圖或者一系列圖集。必須保證在生成圖片時(shí)對(duì) AVAssetImageGenerator 實(shí)例的強(qiáng)引用。

獲取一張圖片

我們可以用 copyCGImageAtTime:actualTime:error: 來獲得指定時(shí)間的截圖。AVFoundation 也許無法精確地獲得你指定時(shí)間的截圖,所以你需要傳入一個(gè) actualTime 參數(shù)來獲得截圖所對(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: 接口來傳入一組時(shí)間來獲取相應(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 接口來中斷截圖。

對(duì)視頻進(jìn)行裁剪和轉(zhuǎn)碼

我們可以使用一個(gè) AVAssetExportSession 實(shí)例來對(duì)視頻進(jìn)行裁剪或格式轉(zhuǎn)換。流程如下圖所示:

image

AVAssetExportSession 實(shí)例用來控制異步的導(dǎo)出 asset。使用 export session 時(shí),首先我們需要傳入要導(dǎo)出的 asset 和對(duì)應(yīng)的 preset 配置,我們可以用 allExportPresets 接口來查看所有可用的 preset 配置。接著,你需要設(shè)置導(dǎo)出的 URL 和文件類型。此外,我們還能設(shè)置導(dǎo)出視頻文件的 metadata 以及導(dǎo)出的是否應(yīng)該針對(duì)網(wǎng)絡(luò)訪問優(yōu)化。

在下面的示例代碼中,我們用 exportPresetsCompatibleWithAsset: 接口檢查可用的 preset,用 outputURLoutputFileType 接口設(shè)置導(dǎo)出 URL 和導(dǎo)出文件類型,通過 timeRange 設(shè)置導(dǎo)出時(shí)間段。此外,我們還能用 shouldOptimizeForNetworkUse 接口設(shè)置是否針對(duì)網(wǎng)絡(luò)使用優(yōu)化以方便秒開,用 maxDurationfileLengthLimit 設(shè)置導(dǎo)入限制等等。

我們用 exportAsynchronouslyWithCompletionHandler: 接口來開始導(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 接口來取消導(dǎo)出。

當(dāng)我們想要覆蓋已有的文件,或者向應(yīng)用沙盒外寫文件時(shí),導(dǎo)出會(huì)失敗。此外,在導(dǎo)出時(shí)突然來了電話、導(dǎo)出時(shí)應(yīng)用在后臺(tái)狀態(tài)并且其他應(yīng)用開始播放時(shí)導(dǎo)出也可能會(huì)失敗。在這些情況下,你需要提示用戶導(dǎo)出失敗,并允許用戶重新導(dǎo)出。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,595評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,814評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,224評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,444評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,988評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,804評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,998評(píng)論 1 370
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評(píng)論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,237評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評(píng)論 1 287
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,706評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,993評(píng)論 2 374

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