AVFoundation 框架初探究(三)


 

 

這篇總結什么?


 

      在該系列的上一篇的文章中,我們總結的大致內容如下:

      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可以在下面鏈接中下載!

      Demo下載地址

      我的博客即將同步至騰訊雲+社區,邀請大家一同入駐。


免責聲明!

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



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