AVFoundation開發祕籍筆記-03資源和元資料
一、資源AVAsset
AVAsset是一個抽象類和不可變類,定義媒體資源混合呈現的方式,將媒體資源的靜態屬性模組化成為一個整體,比如標題、時長和元資料等。
AVAsset不需要考慮媒體資源所具有的兩個重要範疇:1、提供了對基本媒體格式的層抽象,不需要關注具體格式,只關注資源這個概念。2、隱藏資源的位置資訊。
AVAsset本身不是媒體資源,但他可以作為時基媒體的容器,由一個或多個帶有描述自身元資料的媒體組成。
AVAssetTrack類代表儲存在資源的統一型別媒體,並對每個資源建立相應的模型。
資源的曲目可以通過tracks屬性進行訪問。
二、建立資源
為現有媒體資源建立AVAsset物件時,可以通過URL對齊進行初始化來實現,可以是本地檔案URL,也可以是遠端資源的URL
NSString *path = @"";
NSURL *fileUrl = [NSURL fileURLWithPath:path];
NSDictionary *dict = @{AVURLAssetPreferPreciseDurationAndTimingKey : @YES};
AVAsset *asset = [AVAsset assetWithURL:fileUrl];
AVAsset是一個抽象類,意味著它不能直接被例項化,使用assetWithURL
建立例項時,實際上是建立了他子類AVURLAsset
的一個例項。一般會直接建立這個類,因為可以通過傳遞選項字典來調整資源的建立方式。
NSURL *fileUrl;
NSDictionary *dict = @{AVURLAssetPreferPreciseDurationAndTimingKey : @YES};
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:fileUrl options:dict];
AVURLAssetPreferPreciseDurationAndTimingKey
這個布林值支出這個asset是否應該準備標出一個準確的時間和提供一個以時間為種子的隨機存取。
只是播放asset,options傳遞nil,或者字典裡對應的值是NO(包含在NSValue物件中)
給asset新增一個composition,需要精確的隨機存取,傳遞一個字典,對應值是YES(包含在NSValue物件中)
1、iOS Assets庫
使用者使用系統子弟的Camera程式或者第三方視訊捕捉程式捕捉的侍寢,通常先儲存在使用者照片庫中。iOS提供的Assets庫可以實現從照片庫這種讀寫的功能。 iOS9.0以後,<AssetsLibrary/AssetsLibrary.h>
被取消。
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
[group setAssetsFilter:[ALAssetsFilter allVideos]];
NSLog(@"bbb");
if ([group numberOfAssets] > 0) {
[group enumerateAssetsAtIndexes:[NSIndexSet indexSetWithIndex:0] options:0 usingBlock:^(ALAsset *result, NSUInteger index, BOOL *stop) {
if (result) {
id representation = [result defaultRepresentation];
NSURL *url = [representation url];
AVAsset *asset = [AVAsset assetWithURL:url];
}
}];
}
} failureBlock:^(NSError *error) {
}];
2、iOS iPod庫
iPod Library歌曲(也稱作本地音樂庫歌曲)也就是使用者從iTunes中匯入的歌曲。MediaPlayer
框架提供了API,實現在這個庫中查詢和獲取條目。當需要的條目找到後可以獲取其URL並使用這個URL初始化一個資源:
MediaPlayer
框架提供了一個名為MPMediaPropertyPredicate
的類,它用於構建幫助使用者在iPod苦衷查詢具體內容所用的查詢語句。需要匯入庫檔案<MediaPlayer/MediaPlayer.h>
.
MPMediaPropertyPredicate *artistPredicate = [MPMediaPropertyPredicate predicateWithValue:@"劉德華" forProperty:MPMediaItemPropertyArtist];
MPMediaPropertyPredicate *albumPredicate = [MPMediaPropertyPredicate predicateWithValue:@"真永遠" forProperty:MPMediaItemPropertyArtist];
MPMediaPropertyPredicate *songPredicate = [MPMediaPropertyPredicate predicateWithValue:@"今天" forProperty:MPMediaItemPropertyArtist];
MPMediaQuery *query = [[MPMediaQuery alloc] init];
[query addFilterPredicate:artistPredicate];
[query addFilterPredicate:albumPredicate];
[query addFilterPredicate:songPredicate];
NSArray *results = [query items];
if (results.count > 0) {
MPMediaItem *item = results[0];
NSURL *assetURL = [item valueForProperty:MPMediaItemPropertyAssetURL];
AVAsset *asset = [AVAsset assetWithURL:assetURL];
}
三、非同步載入
AVAsset
使用一種高效的設計方法,即延遲載入資源的睡醒,知道請求時才載入。這樣可以快速建立資源,而不用考慮因為立即載入相關媒體或元資料所帶來的問題。有一點,屬性的訪問總是同步發生的,如果正在請求的屬性沒有預先載入,程式就會阻塞,知道可以做出適當的響應。要解決這個問題,應該使用非同步的方法來查詢資源的屬性。
AVAsset
和AVAssetTrack
都採用了AVAsynchornousKeyValueLoading
協議,該協議通過下面給出的方法實現了一步查詢屬性的功能:
NSURL *assetUrl = [[NSBundle mainBundle] URLForResource:@"崔健-假行僧" withExtension:@"mp3"];
AVAsset *asset = [AVAsset assetWithURL:assetUrl];
NSArray *keys = @[@"tracks"];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{
NSError *error;
AVKeyValueStatus status = [asset statusOfValueForKey:@"tracks" error:&error];
switch (status) {
case AVKeyValueStatusLoaded:
//已經載入,繼續處理
NSLog(@"loaded");
NSLog(@"%@",asset.tracks);
break;
case AVKeyValueStatusFailed:
NSLog(@"failure");
break;
case AVKeyValueStatusCancelled:
NSLog(@"canceld");
break;
case AVKeyValueStatusUnknown:
NSLog(@"unknown");
break;
default:
NSLog(@"default");
break;
}
}];
可使用statusOfValueForKey: error:
方法查詢一個給定屬性的狀態,該方法會返回一個列舉型別的AVKeyValueStatus
值,用於表示當前所請求的屬性的狀態。如果狀態不是AVKeyValueStatusLoaded
,意味著此時請求該屬性可能導致程式卡杜,需要非同步載入一個給定的屬性,可以呼叫loadValuesAsynchronouslyForKeys: completionHandler:
方法,為其提供一個具有一個或多個key的陣列(資源的屬性名)和一個completionHandler
塊,當資源處於迴應請求狀態時,就會回撥這個塊方法。
注意:
completionHandler
可能會在任意一個佇列中被呼叫,在對使用者介面做出相應更新之前,必須先回到主佇列中。在請求多個屬性時,每次呼叫
loadValuesAsynchronouslyForKeys: completionHandler:
方法只會呼叫一次completionHandler
,呼叫該方法的次數並不是根據傳遞給這個方法的鍵的個數決定的。- 需要為每個請求的屬性呼叫
statusOfValueForKey: error:
不能假設所有屬性都返回相同的狀態值。
四、媒體元資料
對於元資料的使用有一定的挑戰,每個媒體型別就具有唯一的格式,並且通常要求開發者對相應格式讀寫操作的底層技術有所瞭解。
AVFoundation提供一套統一的方法,用來處理媒體元資料,可以讓開發者不需要考慮大多數特定格式的細節。
1、元資料格式
Apple環境下遇到的媒體型別主要有4種,分別是:QuickTime(mov)、MPEG-4 video(mp4或m4v)、MPEG-4 Audio(m4a)、MPEG-Layer III audio(mp3)。
QuickTime
MPEG-4音訊和視訊
MP3
五、使用元資料
AVAsset
和AVAssetTrack
都可以實現查詢相關元資料的功能,大部分情況使用AVAsset
提供的元資料,不過涉及獲取曲目一級元資料等情況時會使用AVAssetTrack
。
讀取具體資源元資料的介面由AVMetadataItem
提供。提供一個面向物件的介面,可以對儲存於QuickTime、MPEG-4 atom和ID3幀中的元素進行訪問。
- 鍵空間(key spaces): AV中使用鍵空間作為將相關鍵組合在一起的方法,可以實現對
AVMetadataItem
例項集合的篩選,每個資源至少包含兩個鍵空間,供從中獲取元資料。
common鍵空間用來定義所有支援的媒體型別的鍵,包括諸如曲名、歌手和插圖資訊等常見元素。可以通過查詢資源或曲目的[asset commonMetadata]
屬性從common鍵空間獲取與資料,這個屬性會返回一個包含所有可用元資料的資料.
metadataForFormat:
訪問指定格式的元資料格式,返回一個包含所有相關元資料資訊的NSArray。
[asset availableMetadataFormats]
返回資源中包含的所有元資料格式
1、查詢元資料
2、使用AVMetadataItem
AVMetadataItem
最基本的形式是一個封裝鍵值對的封裝器。而已通過它查詢key或commonKey。value屬性被定義成id<NSObject,NSCopying>
形式,AVMetadataItem
還提供了三個型別強制屬性stringValue
、numberValue
、dataValue
,如果已經提前知道value型別,可以強制轉換。
NSURL *assetUrl = [[NSBundle mainBundle] URLForResource:@"今天" withExtension:@"mp3"];
AVAsset *asset = [AVAsset assetWithURL:assetUrl];
for (AVMetadataFormat item in [asset availableMetadataFormats]) {
NSArray *medata = [asset metadataForFormat:item];
for (AVMetadataItem *mitem in medata) {
NSLog(@"%@:%@",mitem.key,mitem.value);
}
}
// TPE1:劉德華
// TALB:真永遠
// TIT2:今天
// TYER:1995-08-01
六、建立MetaManager應用程式
七、儲存元資料
AVAsset是一個不可變類,如果要儲存元資料的修改,使用AVAssetExportSession
匯出一個新的資源副本以及元資料改動。
AVAssetExportSession
用於將AVAsset
內容根據匯出預設條件進行轉碼,並將匯出資源寫到磁碟。它提供了多個功能來實現將一種格式轉換為另一種格式、修訂資源的內容、修改資源的音訊和視訊行為、寫入新的元資料。
建立一個AVAssetExportSession
例項需要提供資源和匯出預設。匯出預設用於確定匯出內容的質量、大小等屬性。建立匯出會話後,還要指定匯出內容地址outputURL
,並且給出一個outputFileType
表示要匯出的格式。最後呼叫exportAsynchronouslyWithCompletionHandler:
開始匯出。
NSString *presetName = AVAssetExportPresetPassthrough;
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:self.asset presetName:presetName];
NSURL *outputUrl ;
exportSession.outputURL = outputUrl;
exportSession.outputFileType = @"";
// exportSession.metadata = [_asset availableMetadataFormats]
[exportSession exportAsynchronouslyWithCompletionHandler:^{
AVAssetExportSessionStatus status = exportSession.status;
BOOL success = (status == AVAssetExportSessionStatusCompleted);
if (success) {
}
}];