這篇總結什麼?git
在該系列的上一篇的文章中,咱們總結的大體內容以下:github
一、視頻錄製 AVCaptureSession + AVCaptureMovieFileOutput數組
二、視頻錄製 AVCaptureSession + AVAssetWriter數據結構
三、AVCaptureSession + AVCaptureMovieFileOutput 與 AVCaptureSession + AVAssetWriter 的區別架構
這是這個系列總結文章的第三篇,前面咱們提了音頻以及視頻的基本的播放,錄製等等的知識,這篇文章咱們總結開發祕籍中的第三章的內容 -- 資源和元數據。 異步
說白了就是總結 AVAsset 這個類!ide
AVAsset性能
AVAsset是一個抽象類(抽象類中不必定包含抽象方法,可是包含抽象方法的類必定要被聲明爲抽象類。抽象類自己不具有實際的功能,只能用於派生其子類。抽象類中能夠包含構造方法,可是構造方法不能被聲明爲抽象,簡單點的說你不能實例化一個抽象類。然而,咱們能夠嘗試複製該方案在Objective-C中採用一些技巧,要確保不能實例化你的父類),咱們前面簡單的說明了一下什麼是抽象類,咱們的AVAsset就是一個抽象類,你經過 assetWithURL 實際建立的就是他的子類,名爲 AVURLAsset ,這一段話你們仔細理解一下。優化
一:AVAsset的異步載入 AVAsynchronousKeyValueLoading 協議ui
這個AVAsynchronousKeyValueLoading咱們的AVAsset類是遵照了的,這個協議裏面就兩個必須實現的方法,咱們解釋一下這兩個方法:
/* typedef NS_ENUM(NSInteger, AVKeyValueStatus) { AVKeyValueStatusUnknown, AVKeyValueStatusLoading, AVKeyValueStatusLoaded, AVKeyValueStatusFailed, AVKeyValueStatusCancelled }; // 這個方法能夠用來查詢給定屬性的狀態,若是返回的這個狀態不是AVKeyValueStatusLoaded,那咱們在此刻去請求這個狀態的時候可能會出現卡頓 - (AVKeyValueStatus)statusOfValueForKey:(NSString *)key error:(NSError * _Nullable * _Nullable)outError; // keys參數就是咱們要請求的屬性數組,當完成請求以後就會在handler這個block回調給咱們 - (void)loadValuesAsynchronouslyForKeys:(NSArray<NSString *> *)keys completionHandler:(nullable void (^)(void))handler; */
咱們簡單的應用一下上面的知識,寫個很簡單的Demo,這個Demo仍是會在咱們這一系列文章的git上,咱們請求一些咱們本地數據的一些基本的屬性,代碼以下:
-(void)getAssetMessage{ NSString * path = [[NSBundle mainBundle]pathForResource:@"薛之謙-像風同樣.mp3" ofType:nil]; NSURL * url = [NSURL fileURLWithPath:path]; AVAsset * asset = [AVAsset assetWithURL:url]; NSArray * keys = @[@"duration"]; [asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{ NSError * error; AVKeyValueStatus status = [asset statusOfValueForKey:@"duration" error:&error]; switch (status) { case AVKeyValueStatusLoaded: // 要更新UI的操做須要回到主線程 NSLog(@"屬性載入成功,你能夠訪問了"); NSLog(@"duration = %.2f",CMTimeGetSeconds(asset.duration)); break; case AVKeyValueStatusLoading: NSLog(@"AVKeyValueStatusLoading"); break; case AVKeyValueStatusFailed: NSLog(@"AVKeyValueStatusFailed"); break; case AVKeyValueStatusUnknown: NSLog(@"AVKeyValueStatusUnknown"); break; default: break; } }]; }
上面的輸出的日誌以下:
須要注意的地方在代碼註釋中有些,通過上面的代碼咱們就異步的訪問了它的duration屬性,爲何咱們訪問一個屬性都須要寫這些個代碼呢?咱們說一下緣由爲這個AVAsynchronousKeyValueLoading協議的總結畫一個句號。
說明: 咱們之因此須要異步的訪問一些屬性,是由於屬性的訪問總結同步的發生的,若是正在請求的屬性沒有預先載入,程序就會阻塞,一直到它能夠作出適當的響應,顯然這樣必定會帶來問題,好比咱們上面說的duration屬性可能就是一個潛在的昂貴操做,若是開發者在使用MP3文件時候沒有在頭文件中設置TLEN標籤,這個標籤用於定義duration值,則整個音頻曲目都須要進行解析來準確肯定它的duration值,假設這個請求發生在主線程,那麼等待響應就會阻塞主線程,直到相關的操做完成爲止,在最好的狀況下可能會感受應用變得遲鈍,用戶界面沒有響應。
媒體元數據
元數據的格式:
雖然存在不少種格式的媒體資源,可是咱們在iOS的環境下遇到的媒體的類型主要就是下面的四類,咱們簡單的總結一下下面的四類,就再也不作具體的說明,有興趣的研究這些類型的能夠本身上網查查:
一:QuickTime
QuickTime 是由蘋果開發的一種功能強大、跨平臺的媒體架構。該架構的一部分是 QuickTime File Formant 規範, 定義了 .mov文件的內部結構。 QuickTime 文件由一種稱爲 atoms 的數據結構組成。
二:MPEG-4 音頻和視頻
MPEG-4 Part 14 是定義MP4文件格式的規範,MP4直接派生於 QuickTime 文件格式,這就意味着它與 QuickTime 文件的結構是相似的,就像QuickTime文件同樣,MP4文件也由稱爲 atom 的數據結構組成。 關於文件名再說一點, .mp4 是對MPEG-4媒體的標準擴展。但存在一些變化,如 .m4v、.m4a、.m4p 、 .m4b 等,這些變體都是使用的 MPEG-4 容器格式,但包含了附加的擴展功能。
三:MP3
MP3文件與上面介紹的兩種格式有顯著的區別,MP3文件使用容器格式,而使用編碼音頻數據,包含的可選元數據的結構塊一般位於文件開頭。MP3文件使用一種稱爲ID3v2的格式來保存關於音頻內容的描述信息,包含的數據有歌曲演唱者、所屬唱片和音樂風格等等。
AV Foundation 支持讀取ID3v2標籤的全部版本,但不支持寫入。MP3格式收到專利限制,因此 AVFoundation 沒法支持對MP3後者ID3數據進行編碼。
使用元數據
在大部分狀況下咱們會使用 AVAsset 提供的元數據,不過設計獲取曲目以及原數據等狀況時候也會使用 AVAssetTrack , 讀取具體的資源元數據的接口由 AVMetadataItem 這個類提供,這個類提供了一個面向對象的接口,讓開發這能夠對存儲在 QuickTime、MPeg-4 atom、ID3 幀中的元數據進行訪問。
說一下 AVAsset 的三個屬性/方法:
一、commonMetadata 這個屬性從Common鍵空間獲取元數據,這個屬性會返回以一個包含全部可用元數據的數組
二、availableMetadataFormats 這個屬性會返回一個字符串數組,其中定義了資源中包含的全部的原數據格式
三、metadataForFormat: 這個方法的參數是一個用於定義元數據格式的NSString 對象, 它的返回值是一個包含全部相關元數據信息的NSArray
根據上面這三個方法,咱們看下面的Demo中的一個方法:
-(void)getAVMetadataItemMessage{ NSString * path = [[NSBundle mainBundle]pathForResource:@"薛之謙-像風同樣.mp3" ofType:nil]; NSURL * url = [NSURL fileURLWithPath:path]; AVAsset * asset = [AVAsset assetWithURL:url]; NSArray * keys = @[@"availableMetadataFormats"]; NSMutableArray * metaArray =[NSMutableArray array]; // commonMetadata 從Common鍵空間獲取元數據、這個屬性會返回一個包括全部可用元數據的數組 NSArray * commonMetaArray = [asset commonMetadata]; NSLog(@"commonMetaArray = %@",commonMetaArray); // [asset loadValuesAsynchronouslyForKeys:keys completionHandler:^{ // availableMetadataFormats 這個屬性會返回一個字符串 // 其中定義了資源中包含的全部元數據格式 for (NSString * format in asset.availableMetadataFormats) { // metadataForFormat 方法 這個方法包含一個用於定義元數據格式的NSString對象並返回一個包含全部先關元數據信息的NSArray [metaArray addObjectsFromArray:[asset metadataForFormat:format]]; } // NSLog(@"metaArray = %@",metaArray); // 使用 AVMetadataItem for (AVMetadataItem * item in metaArray) { NSLog(@"%@ : %@",item.key,item.value); } }]; }
上面的這段代碼咱們須要注意的點在代碼的註釋中都已經提到了,下面咱們須要關心的是它的日誌。
分析一下上面代碼的日誌:
commonMetadata 獲取到的全部的可用的元數據的描述信息數組和經過availableMetadataFormats和metadataForFormat這兩個組合方法獲取到的元數據的描述信息是同樣的。
還有一點和我在書中看的描述不一致的地方是 Key 和 Value 這兩個屬性的打印。按照書中的描述這樣的寫法獲取到的 Key 是整型數據,而咱們獲取到的是上面的輸出,其實在最上面的描述信息中能夠看到上面是有Key 這個屬性的,這點暫時我也沒明白,但事實是按照咱們上面的輸出日誌咱們的確是不能理解 TIT2 或者 TALA 甚至是 TPE1 這些Key表明的含義!其實他們都是MP3文件的標籤,我上往搜了一下這些標籤的含義,大體的說一下這些標籤,方便之後使用時候查閱:
/* * TEXT: 歌詞做者 TENC: 編碼 WXXX: URL連接(URL) TCOP: 版權(Copyright) TOPE: 原藝術家 TCOM: 做曲家 TDAT: 日期 TPE3: 指揮者 TPE2: 樂隊 TPE1: 藝術家至關於ID3v1的Artist TPE4: 翻譯(記錄員、修改員) TYER: 即ID3v1的Year USLT: 歌詞 TSIZ: 大小 TALB: 專輯至關於ID3v1的Album TIT1: 內容組描述 TIT2: 標題至關於ID3v1的Title TIT3: 副標題 TCON: 流派(風格)至關於ID3v1的Genre AENC: 音頻加密技術 TSSE: 編碼使用的軟件(硬件設置) TBPM: 每分鐘節拍數 COMM: 註釋至關於ID3v1的Comment TDLY: 播放列表返錄 TRCK: 音軌(曲號)至關於ID3v1的Track TFLT: 文件類型 TIME: 時間 TKEY: 最初關鍵字 TLAN: 語言 TLEN: 長度 TMED: 媒體類型 TOAL: 原唱片集 TOFN: 原文件名 TOLY: 原歌詞做者 TORY: 最初發行年份 TOWM: 文件全部者(許可證者) TPOS: 做品集部分 TPUB: 發行人 TRDA: 錄製日期 TRSN: Intenet電臺名稱 TRSO: Intenet電臺全部者 UFID: 惟一的文件標識符 TSRC: ISRC(國際的標準記錄代碼) */
上面的標籤應該差很少包括了基本的標籤,要是在之後的使用中有其餘遇到的本身沒有見過的再添加進來。
這一章最後說的竟然是 AVAssetExportSession
AVAssetExportSession 這個咱們再前面說過,在前面拍攝完視頻以後咱們就利用這個 AVAssetExportSession 壓縮視頻。AVAssetExportSession 用於將AVAsset 內容根據導出預設條件進行轉碼,並將導出資源寫到磁盤中,AVAssetExportSession 提供了多個功能來實現將一種格式轉換爲另外一個格式、修訂資源的內容、修改資源的音頻和視頻行爲,固然還有咱們最幹星期的功能,即寫入新的元數據。
使用AVAssetExportSession實例大體須要作下面這些:
一、須要一個AVAsset會話
二、根據前面的AVAsset會話實例以及設置的壓縮質量初始化獲得AVAssetExportSession對象
三、其實前面的裏能夠理解成導入設置,接下來就是導出設置,調出的地址outputURL以及outputFileType導出的格式
四、接下來就是利用exportAsynchronouslyWithCompletionHandler方法導出了,導出的數據會在改方法的Block中回調
五、最後就是在回調的block中根據AVAssetExportSession對象的status屬性去判斷壓縮是否成功,進而進行本身想要的操做
上面的步驟大體上就說清楚了AVAssetExportSession,其餘的API有興趣能夠進AVAssetExportSession的.h文件去看看,下面就是咱們前面有用到的一段源碼:
#pragma mark -- #pragma mark -- 視頻壓縮方法 -(void)compressVideoWithFileUrl:(NSURL *)fileUrl{ /* 這裏須要注意的一點就是在重複的路徑上保存文件是不行的,能夠選擇在點擊開始的時候刪除以前的 也能夠這樣按照時間命名不一樣的文件保存 在後面的 AVAssetWriter 也要注意這一點 */ // 壓縮後的視頻的方法命名 NSDateFormatter * formatter = [[NSDateFormatter alloc]init]; [formatter setDateFormat:@"yyyy-MM-dd-HH:mm:ss"]; // 壓縮後的文件路徑 self.videoPath = [NSString stringWithFormat:@"%@/%@.mov",NSTemporaryDirectory(),[formatter stringFromDate:[NSDate date]]]; // 先根據你傳入的文件的路徑穿件一個AVAsset AVAsset * asset = [AVAsset assetWithURL:fileUrl]; /* 根據urlAsset建立AVAssetExportSession壓縮類 第二個參數的意義:經常使用 壓縮中等質量 AVAssetExportPresetMediumQuality AVF_EXPORT NSString *const AVAssetExportPresetLowQuality NS_AVAILABLE_IOS(4_0); AVF_EXPORT NSString *const AVAssetExportPresetMediumQuality NS_AVAILABLE_IOS(4_0); AVF_EXPORT NSString *const AVAssetExportPresetHighestQuality NS_AVAILABLE_IOS(4_0); */ AVAssetExportSession * exportSession = [[AVAssetExportSession alloc]initWithAsset:asset presetName:AVAssetExportPresetMediumQuality]; // 優化壓縮,這個屬性能使壓縮的質量更好 exportSession.shouldOptimizeForNetworkUse = YES; // 處處的文件的路徑 exportSession.outputURL = [NSURL fileURLWithPath:self.videoPath]; // 導出的文件格式 /*! @constant AVFileTypeMPEG4 mp4格式的 AVFileTypeQuickTimeMovie mov格式的 @abstract A UTI for the MPEG-4 file format. @discussion The value of this UTI is @"public.mpeg-4". Files are identified with the .mp4 extension. 能夠看看這個outputFileType格式,好比AVFileTypeMPEG4也能夠寫成public.mpeg-4,其餘相似 */ exportSession.outputFileType = AVFileTypeQuickTimeMovie; NSLog(@"視頻壓縮後的presetName: %@",exportSession.presetName); // 壓縮的方法 [exportSession exportAsynchronouslyWithCompletionHandler:^{ /* exportSession.status 枚舉屬性 typedef NS_ENUM(NSInteger, AVAssetExportSessionStatus) { AVAssetExportSessionStatusUnknown, AVAssetExportSessionStatusWaiting, AVAssetExportSessionStatusExporting, AVAssetExportSessionStatusCompleted, AVAssetExportSessionStatusFailed, AVAssetExportSessionStatusCancelled }; */ int exportStatus = exportSession.status; switch (exportStatus) { case AVAssetExportSessionStatusFailed: NSLog(@"壓縮失敗"); break; case AVAssetExportSessionStatusCompleted: { /* 壓縮後的大小 也能夠利用exportSession的progress屬性,隨時監測壓縮的進度 */ NSData * data = [NSData dataWithContentsOfFile:self.videoPath]; float dataSize = (float)data.length/1024/1024; NSLog(@"視頻壓縮後大小 %f M", dataSize); } break; default: break; } }]; }
上面的內容大體就是書中第三章的內容了,具體的Demo能夠在下面連接中下載!
個人博客即將同步至騰訊雲+社區,邀請你們一同入駐。