AVFoundation 資源和元數據

AVAsset 是一個抽象類和不可變類,它定義了媒體資源混合呈現的方式,將媒體資源的靜態屬性模塊化爲一個總體,包括標題、時長和元數據。AVAsset 提供了基本媒體格式的層抽象,隱藏了資源的位置信息。vue

1. 建立資源

經過 URL 建立一個 AVAssetios

NSURL *assetUrl = // url
AVAsset *asset  =[AVAsset assetWithURL:assetUrl];
複製代碼

assetWithURL 方法生成的實際類是 AVAsset 的子類 AVURLAsset,它容許經過 options 字典來調整建立方式。數組

獲取方式

常見的獲取 AVAsset 的途徑有session

  • iOS Asset 庫,即相冊資源 —— Photo Framework
  • iOS iPod 庫 —— MediaPlayer
  • MAC iTunes 庫 —— iTunesLibrary 框架

這裏只舉 Photo Framework 的例子數據結構

[[PHImageManager defaultManager] requestAVAssetForVideo:asset options:options resultHandler:^(AVAsset * _Nullable obj, AVAudioMix * _Nullable audioMix, NSDictionary * _Nullable info) {
                    @strongify(self)
                    AVURLAsset *urlAsset = (AVURLAsset *)obj;
                    NSURL *url = urlAsset.URL;
                    NSData *data = [NSData dataWithContentsOfURL:url];
                }];
複製代碼

2. 異步加載

AVAsset 對資源屬性實現了延遲加載特性,僅在請求時才載入,這樣若是是同步請求屬性,就可能由於屬性沒有預先載入而阻塞主線程,所以應採用異步加載屬性的方法。架構

AVAsset 和 AVAssetTrack 都遵循了 AVAsynchronousKeyValueLoading 協議,此協議有兩個方法app

- (AVKeyValueStatus)statusOfValueForKey:(NSString *)key error:(NSError * _Nullable * _Nullable)outError;
- (void)loadValuesAsynchronouslyForKeys:(NSArray<NSString *> *)keys completionHandler:(nullable void (^)(void))handler;
複製代碼

第一個方法用於肯定當前屬性是預先加載好了、還沒有加載仍是加載出錯了,而第二個方法則是異步加載屬性的方法。框架

[self.targetAVAsset loadValuesAsynchronouslyForKeys:@[@"trackss"] completionHandler:^{
            NSError *error = nil;
            AVKeyValueStatus status = [self.targetAVAsset statusOfValueForKey:@"tracks" error:nil];
            switch (status) {
                case AVKeyValueStatusLoaded: {
                    NSLog(@"AVKeyValueStatusLoaded");
                    break;
                }
                case AVKeyValueStatusFailed: {
                    NSLog(@"AVKeyValueStatusFailed");
                    break;
                }
                case AVKeyValueStatusUnknown: {
                    NSLog(@"AVKeyValueStatusUnknown");
                    break;
                }
                case AVKeyValueStatusCancelled: {
                    NSLog(@"AVKeyValueStatusCancelled");
                    break;
                }
                default:
                    break;
            }
        }];
複製代碼

這裏請求的參數能夠是多個,可是當請求多個參數時,異步請求的回調只會調用一次,此時須要分別對各個屬性調用 statusOfValueForKey 方法來判斷是否加載完成。異步

3. 媒體元數據

3.1 元數據格式

在 Apple 環境下,最多見四種媒體類型,分別是 QuickTime(mov)、MPEG-4 video(mp4 和 m4v)、MPEG-4 audio(m4a)和 MPEG-Layer III Audio(mp3)。ide

QuickTime 是蘋果公司開發的一種跨平臺媒體架構,由一種稱爲 atoms 的數據結構組成,一個 atom 能夠包含元數據,也能夠包含其餘 atom,但不能二者都包含。

MPEG-4 Part 14 定義了 MP4 文件格式的規範,MP4 直接派生於 QuickTime 文件格式,結構相似。要注意,MP4 有多種文件拓展名。.mp4 是標準擴展名,.m4v 是帶有蘋果公司針對 FairPlay 加密及 AC3-audio 擴展的 MPEG-4 視頻格式,而 .m4a 針對音頻,m4p 針對較舊的 itunes 音頻格式,m4b 針對有聲讀物。

MP3 不是容器格式,使用編碼音頻數據,使用 ID3v2 格式保存元數據。因爲專利限制,AVFoundation 只支持讀取 MP3,不支持編碼 MP3。

4. 使用元數據

AVFoundation 使用鍵空間做爲將相關鍵組合在一塊兒的方法,能夠實現對 AVMetaDataItem 實例集合的篩選。Common 鍵空間用於定義全部支持的媒體類型的鍵,包括曲名、歌手、插圖信息等。開發者能夠經過查詢資源或曲目的 commonMetadata 屬性從 common 鍵空間獲取元數據,這個屬性會返回一個包含全部可用元數據的數組。

訪問指定格式的元數據,須要對 AVAsset 對象調用 metadataForFormat 方法,此方法包含一個用於定義元數據格式的 NSString 對象並返回一個包含全部相關元數據信息的 NSArray。一個 AVAsset 所支持的資源格式能夠經過 availableMetadataFormats 屬性來獲取。

[self.targetAVAsset loadValuesAsynchronouslyForKeys:@[@"availableMetadataFormats"] completionHandler:^{
            NSError *error = nil;
            AVKeyValueStatus status = [self.targetAVAsset statusOfValueForKey:@"availableMetadataFormats" error:nil];
            switch (status) {
                case AVKeyValueStatusLoaded: {
                    for (NSString *format in self.targetAVAsset.availableMetadataFormats) {
                        NSLog(@"%@", format);
                        NSLog(@"%@", [self.targetAVAsset metadataForFormat:format]);
                    }
                    break;
                }
                case AVKeyValueStatusFailed: {
                    NSLog(@"AVKeyValueStatusFailed");
                    break;
                }
                case AVKeyValueStatusUnknown: {
                    NSLog(@"AVKeyValueStatusUnknown");
                    break;
                }
                case AVKeyValueStatusCancelled: {
                    NSLog(@"AVKeyValueStatusCancelled");
                    break;
                }
                default:
                    break;
            }
        }];
複製代碼

示例輸出以下

com.apple.quicktime.mdta
(
    "<AVMetadataItem: 0x1c801f230, identifier=mdta/com.apple.quicktime.software, keySpace=mdta, key class = __NSCFString, key=com.apple.quicktime.software, commonKey=software, extendedLanguageTag=(null), dataType=com.apple.metadata.datatype.UTF-8, time={INVALID}, duration={INVALID}, startDate=(null), extras={\n dataType = 1;\n dataTypeNamespace = \"com.apple.quicktime.mdta\";\n}, value class=__NSCFString, value=video.vue.ios.280>",
    "<AVMetadataItem: 0x1c42004d0, identifier=mdta/com.apple.quicktime.description, keySpace=mdta, key class = __NSCFString, key=com.apple.quicktime.description, commonKey=description, extendedLanguageTag=(null), dataType=com.apple.metadata.datatype.UTF-8, time={INVALID}, duration={INVALID}, startDate=(null), extras={\n dataType = 1;\n dataTypeNamespace = \"com.apple.quicktime.mdta\";\n}, value class=__NSCFString, value=ewogICJzc2Z3IiA6IFsKICAgICIiCiAgXSwKICAic2RldiIgOiBbCiAgICAiIgogIF0sCiAgInNjIiA6IDEsCiAgInNsb2MiIDogWwogICAgIiIKICBdLAogICJzdWIiIDogWwogICAgIiIKICBdLAogICJ0cnMiIDogWwogICAgMCwKICAgIDAKICBdLAogICJzZHVyIiA6IFsKICAgIDEwCiAgXSwKICAiY2FwIiA6ICIgIiwKICAiZiIgOiBbCiAgICAiMSIKICBdCn0=>"
)
複製代碼

5. 實踐

5.1 對 AVAsset 進行封裝

經過 url 咱們能夠實例化一個 AVAsset,也能夠獲取到其中一些有效的元數據信息

_url = url;
        _asset = [AVAsset assetWithURL:url];
        _filename = [url lastPathComponent];
        _filetype = [self fileTypeForURL:url];                              
        _editable = ![_filetype isEqualToString:AVFileTypeMPEGLayer3];
複製代碼

lastPathComponent 從 URL (例如 "file:///Users/yasic/Library/Application%20Support/MetaManager/01%20Demo%20AAC.m4a")的最後一個路徑取出文件名,fileTypeForURL 具體實現以下

- (NSString *)fileTypeForURL:(NSURL *)url {
    NSString *ext = [[self.url lastPathComponent] pathExtension];
    NSString *type = nil;
    if ([ext isEqualToString:@"m4a"]) {
        type = AVFileTypeAppleM4A;
    } else if ([ext isEqualToString:@"m4v"]) {
        type = AVFileTypeAppleM4V;
    } else if ([ext isEqualToString:@"mov"]) {
        type = AVFileTypeQuickTimeMovie;
    } else if ([ext isEqualToString:@"mp4"]) {
        type = AVFileTypeMPEG4;
    } else {
        type = AVFileTypeMPEGLayer3;
    }
    return type;
}
複製代碼

5.2 獲取元數據

對於通用鍵空間,能夠經過 commonMetadata 屬性獲取到元數據對應的 AVMetadataItem 對象。

NSArray *keys = @[COMMON_META_KEY];
    [self.asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{
        AVKeyValueStatus commonStatus =
            [self.asset statusOfValueForKey:COMMON_META_KEY error:nil];
        self.prepared = (commonStatus == AVKeyValueStatusLoaded);

        if (self.prepared) {
            for (AVMetadataItem *item in self.asset.commonMetadata) {
                //NSLog(@"%@: %@", item.keyString, item.value);
                [self.metadata addMetadataItem:item withKey:item.commonKey];
            }
        }
    }];
複製代碼

對於其餘格式的元數據,先獲取到可用格式的數組,而後分別進行元數據獲取

NSArray *keys = @[AVAILABLE_META_KEY];
    [self.asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{
        AVKeyValueStatus formatsStatus = [self.asset statusOfValueForKey:AVAILABLE_META_KEY error:nil];
        self.prepared = (formatsStatus == AVKeyValueStatusLoaded);

        if (self.prepared) {
            for (id format in self.asset.availableMetadataFormats) {        // 5
                if ([self.acceptedFormats containsObject:format]) {
                    NSArray *items = [self.asset metadataForFormat:format];
                    for (AVMetadataItem *item in items) {
                        //NSLog(@"%@: %@", item.keyString, item.value);
                        [self.metadata addMetadataItem:item
                                               withKey:item.keyString];
                    }
                }
            }
        }
    }];
複製代碼

獲取到的元數據有些是能夠直接閱讀的,有些則不夠語義化,須要進行不一樣方式的轉化,包括 Artwork、註釋、音軌數據、唱片數據、風格數據等類型的數據,這裏再也不贅述。

5.3 保存元數據

對於元數據的修改不能直接操做原 AVAsset,須要使用 AVAssetExportSession 導出一個新的資源副原本覆蓋本來的資源對象。

NSString *presetName = AVAssetExportPresetPassthrough;
    AVAssetExportSession *session =
        [[AVAssetExportSession alloc] initWithAsset:self.asset
                                         presetName:presetName];
複製代碼

AVAssetExportSession 的初始化須要一個待修改的 AVAsset 對象,以及一個預設值,AVAssetExportPresetPassthrough 預設值可以在不從新編碼媒體的前提下實現寫入元數據功能,但不能添加新的元數據。

NSURL *outputURL = [self tempURL];
    session.outputURL = outputURL;
    session.outputFileType = self.filetype;
    session.metadata = [self.metadata metadataItems];
複製代碼

指定導出副本的存儲位置,配置到 session 上。

[session exportAsynchronouslyWithCompletionHandler:^{
        AVAssetExportSessionStatus status = session.status;
        BOOL success = (status == AVAssetExportSessionStatusCompleted);
        if (success) {
            NSURL *sourceURL = self.url;
            NSFileManager *manager = [NSFileManager defaultManager];
            [manager removeItemAtURL:sourceURL error:nil];
            [manager moveItemAtURL:outputURL toURL:sourceURL error:nil];
        }
    }];
複製代碼

導出須要用到 exportAsynchronouslyWithCompletionHandler 方法,完成後對狀態進行判斷,獲知導出是否成功,而後經過 NSFileManager 的 moveItemAtURL 方法來覆蓋原始的媒體資源。

相關文章
相關標籤/搜索