
這篇總結什么?
在該系列的上一篇的文章中,我們總結的大致內容如下:
1、視頻錄制 AVCaptureSession + AVCaptureMovieFileOutput
2、視頻錄制 AVCaptureSession + AVAssetWriter
3、AVCaptureSession + AVCaptureMovieFileOutput 與 AVCaptureSession + AVAssetWriter 的區別
這是這個系列總結文章的第三篇,前面我們提了音頻以及視頻的基本的播放,錄制等等的知識,這篇文章我們總結開發秘籍中的第三章的內容 -- 資源和元數據。
說白了就是總結 AVAsset 這個類!
AVAsset
AVAsset是一個抽象類(抽象類中不一定包含抽象方法,但是包含抽象方法的類一定要被聲明為抽象類。抽象類本身不具備實際的功能,只能用於派生其子類。抽象類中可以包含構造方法,但是構造方法不能被聲明為抽象,簡單點的說你不能實例化一個抽象類。然而,我們可以嘗試復制該方案在Objective-C中采用一些技巧,要確保不能實例化你的父類),我們前面簡單的說明了一下什么是抽象類,我們的AVAsset就是一個抽象類,你通過 assetWithURL 實際創建的就是他的子類,名為 AVURLAsset ,這一段話大家仔細理解一下。
一:AVAsset的異步載入 AVAsynchronousKeyValueLoading 協議
這個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 的三個屬性/方法:
1、commonMetadata 這個屬性從Common鍵空間獲取元數據,這個屬性會返回以一個包含所有可用元數據的數組
2、availableMetadataFormats 這個屬性會返回一個字符串數組,其中定義了資源中包含的所有的原數據格式
3、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實例大致需要做下面這些:
1、需要一個AVAsset會話
2、根據前面的AVAsset會話實例以及設置的壓縮質量初始化得到AVAssetExportSession對象
3、其實前面的里可以理解成導入設置,接下來就是導出設置,調出的地址outputURL以及outputFileType導出的格式
4、接下來就是利用exportAsynchronouslyWithCompletionHandler方法導出了,導出的數據會在改方法的Block中回調
5、最后就是在回調的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可以在下面鏈接中下載!
我的博客即將同步至騰訊雲+社區,邀請大家一同入駐。
