AVAudioFoundation(1):使用 AVAsset


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

本文主要內容來自 AVFoundation Programming Guide

要了解 iOS 上的音視頻相關的內容,首先需要了解的就是 AVFoundation 這個框架。

下圖是 AVFoundation 框架大的層級結構:

image

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

一個 AVAsset 實例表示的是一份或多份音視頻數據(audio and video tracks)的集合,它描述的是這個集合作為一個整體對象的一些屬性,比如:標題、時長、大小等,而不與具體的數據格式綁定。通常,在實際使用時我們可能會基於某個 URL 創建對應的媒體資源對象(AVURLAsset),或者直接創建 compositions(AVComposition),這些類都是 AVAsset 的子類。

一個 AVAsset 中的每一份音頻或視頻數據都稱為一個軌道(track)。在最簡單的情況下,一個媒體文件中可能只有兩個軌道,一個音頻軌道,一個視頻軌道。而復雜的組合中,可能包含多個重疊的音頻軌道和視頻軌道。此外 AVAsset 也可能包含元數據(metadata)

AVFoundation 中另一個非常重要的概念是,初始化一個 AVAsset 或者一個 AVAssetTrack 時並不一定意味着它已經可以立即使用,因為這需要一段時間來做計算,而這個計算可能會阻塞當前線程,所以通常你可以選用異步的方式來初始化,並通過回調來得到異步返回。

我們可以從一個文件或者用戶的相冊中來創建 asset。獲得一個視頻 asset 時,我們可以從中提出靜態圖,對其進行轉碼,裁剪器內容。

創建 Asset

當使用一個 URL 來創建 asset 時,可以用 AVURLAsset

NSURL *url = <#A URL that identifies an audiovisual asset such as a movie file#>;
AVURLAsset *anAsset = [[AVURLAsset alloc] initWithURL:url options:nil];

設置 Asset 選項

可以看到當我們創建一個 AVURLAsset 時,是可以設置一個對象的 options 的,這里可選的設置項包括:

  • AVURLAssetPreferPreciseDurationAndTimingKey,這個選項對應的值是布爾值,默認為 @(NO),當設為 @(YES) 時表示 asset 應該提供精確的時長,並能根據時間准確地隨機訪問,提供這樣的能力是需要開銷更大的計算的。當你只是想播放視頻時,你可以不設置這個選項,但是如果你想把這個 asset 添加到一個 composition(AVMutableComposition)中去做進一步編輯,你通常需要精確的隨機訪問,這時你最好設置這個選項為 YES。
  • AVURLAssetReferenceRestrictionsKey,這個選項對應的值是 AVAssetReferenceRestrictions enum。有一些 asset 可以保護一些指向外部數據的引用,這個選項用來表示對外部數據訪問的限制。具體含義參見 AVAssetReferenceRestrictions
  • AVURLAssetHTTPCookiesKey,這個選項用來設置 asset 通過 HTTP 請求發送的 HTTP cookies,當然 cookies 只能發給同站。具體參見文檔。
  • AVURLAssetAllowsCellularAccessKey,這個選項對應的值是布爾值,默認為 @(YES)。表示 asset 是否能使用移動網絡資源。

不過你要注意這幾個選項適用的 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

獲取用戶相冊的資源時,你需要借用 ALAssetsLibrary 的相關接口:

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 來使用

初始化一個 AVAsset 或者一個 AVAssetTrack 時並不一定意味着它已經可以立即使用,因為這需要一段時間來做計算,而這個計算可能會阻塞當前線程,所以通常你可以選用異步的方式來初始化,並通過回調來得到異步返回。

這時你可以使用 AVAsynchronousKeyValueLoading protocol 來獲取加載 asset 的狀態,並在對應的 completion handler 中做對應的處理。AVAssetAVAssetTrack 都是遵循 AVAsynchronousKeyValueLoading protocol 的。下面是一個示例:

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;
   }
}];

需要注意的是:當你需要加載一個 asset 來點播,你應該加載它的 tracks 屬性。

獲取視頻截圖

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

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

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

獲取一張圖片

我們可以用 copyCGImageAtTime:actualTime:error: 來獲得指定時間的截圖。AVFoundation 也許無法精確地獲得你指定時間的截圖,所以你需要傳入一個 actualTime 參數來獲得截圖所對應的實際時間。

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: 接口來傳入一組時間來獲取相應的一組截圖。同樣的,必須保證在生成圖片時對 AVAssetImageGenerator 實例的強引用。示例代碼如下:

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 接口來中斷截圖。

對視頻進行裁剪和轉碼

我們可以使用一個 AVAssetExportSession 實例來對視頻進行裁剪或格式轉換。流程如下圖所示:

image

AVAssetExportSession 實例用來控制異步的導出 asset。使用 export session 時,首先我們需要傳入要導出的 asset 和對應的 preset 配置,我們可以用 allExportPresets 接口來查看所有可用的 preset 配置。接着,你需要設置導出的 URL 和文件類型。此外,我們還能設置導出視頻文件的 metadata 以及導出的是否應該針對網絡訪問優化。

在下面的示例代碼中,我們用 exportPresetsCompatibleWithAsset: 接口檢查可用的 preset,用 outputURLoutputFileType 接口設置導出 URL 和導出文件類型,通過 timeRange 設置導出時間段。此外,我們還能用 shouldOptimizeForNetworkUse 接口設置是否針對網絡使用優化以方便秒開,用 maxDurationfileLengthLimit 設置導入限制等等。

我們用 exportAsynchronouslyWithCompletionHandler: 接口來開始導出。

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 接口來取消導出。

當我們想要覆蓋已有的文件,或者向應用沙盒外寫文件時,導出會失敗。此外,在導出時突然來了電話、導出時應用在后台狀態並且其他應用開始播放時導出也可能會失敗。在這些情況下,你需要提示用戶導出失敗,並允許用戶重新導出。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM