iOS 直播相關技術

直播流程圖

直播推流拉流流程.webp

1、數據採集

經過麥克風攝像頭採集音視頻數據
複製代碼

視頻採集

  • AVCaptureDevice 先後攝像頭做爲視頻源生成輸入
  • AVCaptureDeviceInput 視頻輸入 加入到 👇 session
  • AVCaptureSession 視頻對話
  • AVCaptureVideoDataOutput 會話session 導出視頻輸出
幀率
  • 幀率表示圖形處理器處理場時每秒鐘可以更新的次數,即:每秒視頻播放的圖片數
  • 人眼溫馨放鬆時可視幀數是每秒24幀,集中精神時不超過30幀。眨眼時睜開眼瞬間能夠捕捉到的幀數是30幀以上。
  • 幀率太小會形成視頻卡頓,幀率過大會形成視頻過大。
  • iOS默認輸出的視頻幀率爲30幀/秒,普通用途的話設置成24~30就夠用了。

音頻採集

  • AVAudioSession
  • AudioComponentDescription
  • AudioComponentFindNext
AVAudioSession *session = [AVAudioSession sharedInstance];
        
        // 監聽聲音路線改變
        [[NSNotificationCenter defaultCenter] addObserver: self
                                                 selector: @selector(handleRouteChange:)
                                                     name: AVAudioSessionRouteChangeNotification
                                                   object: session];
        // 監聽聲音被打斷
        [[NSNotificationCenter defaultCenter] addObserver: self
                                                 selector: @selector(handleInterruption:)
                                                     name: AVAudioSessionInterruptionNotification
                                                   object: session];
        /// 建立AudioComponent
        AudioComponentDescription acd;
        acd.componentType = kAudioUnitType_Output;
        //acd.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
        acd.componentSubType = kAudioUnitSubType_RemoteIO;
        acd.componentManufacturer = kAudioUnitManufacturer_Apple; // 廠商 直接寫kAudioUnitManufacturer_Apple
        acd.componentFlags = 0; // 沒有明確值時必須設爲0
        acd.componentFlagsMask = 0; // 沒有明確值時必須設爲0
        self.component = AudioComponentFindNext(NULL, &acd);
        
        OSStatus status = noErr;
        status = AudioComponentInstanceNew(self.component, &_componetInstance);
        
        if (noErr != status) {
            [self handleAudioComponentCreationFailure];
        }
        
        /// 鏈接麥克風
        UInt32 flagOne = 1;
        AudioUnitSetProperty(self.componetInstance, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &flagOne, sizeof(flagOne));
        
        AudioStreamBasicDescription desc = {0};
        desc.mSampleRate = _configuration.audioSampleRate; // 採樣率
        desc.mFormatID = kAudioFormatLinearPCM;
        desc.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
        desc.mChannelsPerFrame = (UInt32)_configuration.numberOfChannels;
        desc.mFramesPerPacket = 1;
        desc.mBitsPerChannel = 16; // 表示每一個聲道的音頻數據要多少位,一個字節是8位,因此用8 * 每一個採樣的字節數
        desc.mBytesPerFrame = desc.mBitsPerChannel / 8 * desc.mChannelsPerFrame;
        desc.mBytesPerPacket = desc.mBytesPerFrame * desc.mFramesPerPacket; // 根據mFormatFlags指定的Float類型非交錯存儲,就設置爲bytesPerSample表示每一個採樣的字節數。但若是是Interleaved交錯存儲的,就應該設置爲bytesPerSample * mChannelsPerFrame 由於左右聲道數據是交錯存在一塊兒的。
        

        // 鏈接揚聲器
        AudioUnitSetProperty(self.componetInstance, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &desc, sizeof(desc));
        
        // 設置聲音回調
        AURenderCallbackStruct cb;
        cb.inputProcRefCon = (__bridge void *)(self);
        cb.inputProc = handleInputBuffer;
        AudioUnitSetProperty(self.componetInstance, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 1, &cb, sizeof(cb));
        
        // 初始化AudioComponentInstance
        status = AudioUnitInitialize(self.componetInstance);
        
        if (noErr != status) {
            [self handleAudioComponentCreationFailure];
        }
        
        [session setPreferredSampleRate:_configuration.audioSampleRate error:nil];
        /// AVAudioSessionCategoryPlayAndRecord 支持音頻播放和錄音、打斷其餘不支持混音APP、不會被靜音鍵或鎖屏鍵靜音
        /// AVAudioSessionCategoryOptionDefaultToSpeaker 系統會自動選擇最佳的內置麥克風組合支持視頻聊天。
        /// AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers 支持和其餘APP音頻混合
        [session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers error:nil];
        [session setActive:YES withOptions:kAudioSessionSetActiveFlag_NotifyOthersOnDeactivation error:nil];
        
        [session setActive:YES error:nil];
複製代碼
音頻採樣率
  • 音頻採樣率是指錄音設備在一秒鐘內對聲音信號的採樣次數,採樣頻率越高聲音的還原就越真實越天然。
  • 在當今的主流採集卡上,採樣頻率通常共分爲11025Hz、22050Hz、24000Hz、44100Hz、48000Hz五個等級,11025Hz能達到AM調幅廣播的聲音品質,而22050Hz和24000HZ能達到FM調頻廣播的聲音品質,44100Hz則是理論上的CD音質界限,48000Hz則更加精確一些。
  • 通常音頻質量採用44100Hz, 高等音頻質量採用48000Hz

配置採樣參數

音頻配置:碼率(128)和採樣率(44100HZ)
視頻配置:視頻分辨率(720P )、碼率(2000KB/S)和幀率(30FPS)
複製代碼

音頻錄製實時轉爲 aac

蘋果默認是 PCMweb

aac 是爲了取代 MP3,壓縮了原始文件,可是取決於比特率,合適的比特率人耳分辨不出算法

2、圖像處理

將數據採集的輸入流進行實時濾鏡, 美顏
複製代碼

特效濾鏡處理緩存

GPUImage (開源) 只有iOS 小型的能夠作 封裝和思惟基於 OpenGL ES服務器

Metal 蘋果 新開發的markdown

OpenGL PCsession

OpenGL ES 線路 手機框架

3、音視頻編碼(壓縮)

截屏2021-05-16 上午11.11.23.png

1、音頻編碼

AudioToolBox FFmpeg AACide

軟解碼:使用 fdk_aac 將 PCM 轉爲 AAC
複製代碼
經常使用音頻壓縮編碼格式
有損壓縮和無損壓縮

人耳掩蓋效應:去除冗餘信息ui

目前使用有損壓縮比較多編碼

  • 有損壓縮:解壓後的數據徹底能夠復原

  • 有損壓縮:解壓後的數據不能徹底復原,會丟失一部分信息,壓縮比越大,丟失的信息越多,信號還原的失真就好越大

WAV
  • 未進行壓縮:
  • 在 PCM 裸數據前面加了44個字節,包含了採樣率,聲道數,數據格式等信息
  • 音質很是好
MP3
  • 不錯的壓縮比,接近於 WAV
  • 高比特率下,128kbs表現很好,應用很廣
AAC
  • 目前很熱門的有損壓縮格式
  • 多適用於小比特率下,而且多用於視頻中的音頻編碼
OGG
  • 潛力 用於語音聊天場景
  • 可是軟硬件支持問題

2、視頻編碼

VideoToolBox FFmpeg H264

軟編碼: FFmpeg X264
	用到CPU
	
硬編碼 VideoToolBox AudioToolBox
商業項目 通用 硬編碼   
	GPU (運算大於CPU)
	硬件加速器
	
	視頻編碼   VideoToolBox  FFmpeg   H264
	
	音頻編碼   AudioToolBox  FFmpeg   AAC
	
複製代碼
編碼到底作了什麼
YUV  而不是用 rgba

1s 內 60張照片

10張爲一組 進行壓縮 

​	取第一幀爲I幀,後面稱之爲 P幀,只保留和前一幀的不一樣點

​	B 幀 是保留和後一幀的差別。

爲了追求高壓縮(如小視頻),可以使用 I  P  B 

可是直播是爲了追求高實時性,所以使用I P,而不使用B幀
複製代碼

使用 VideoToolBox 框架的流程

一、建立編碼會話 session
VTCompressionSessionCreate
複製代碼
二、設置編碼相關參數
  • 設置是否實時編碼
  • 是否使用B幀
  • GOP 關鍵幀的間隔
  • 設置幀率
  • 設置碼率
三、開始編碼
四、循環獲取採集數據
五、獲取編碼後的數據

經過 AVFoundation 獲取捕獲結果回調

截屏2021-05-17 下午2.19.25.png

  • 原始數據封裝爲 CVPixelBuffers,

    CVPixelBuffers 爲主內存存儲的全部像素點數據的對象
    複製代碼
  • encode 爲 CMSampleBuffers

截屏2021-05-17 下午2.26.19.png

六、將數據寫入 H264 文件 獲取到 SPS/PPS, 第一幀寫入 SPS/PPS VideoToolBox 在沒一個關鍵幀前面都會輸出 SPS/PPS信息,若是本幀爲關鍵幀,則能夠取出對應的 PPS / SPS 信息。

4、推流

將採集的音視頻信息經過流媒體協議發送到流媒體服務器
複製代碼

推流技術

封包
  • 視頻封裝格式: FLV / TS
  • 音視頻封裝格式: MP3 / AAC
上傳
  • 流媒體協議: RTMP / HLS / RTSP / FLV

5、流媒體服務器處理

  • 數據分發 CDN

  • 截屏

  • 錄製

  • 實時轉碼

6、播放器

負責拉流、解碼和播放
複製代碼

拉流:

  • 流媒體協議: RTMP / HLS / RTSP / FLV

音視頻解碼

  • 硬解碼 : videoToolbox audioToolBox
  • 軟解碼 : 視頻 FFmpeg x264算法視頻解碼, 使用 fdk_aac 音頻解碼

7、流媒體協議的區別

RTMP

相對於 HLS 來講,採用 RTMP 協議時,從採集推流端到流媒體服務器再到播放端是一條數據流,所以在服務器不會有落地文件。這樣 RTMP 相對來講就有這些優勢:

延時較小,一般爲 1-3s。
基於 TCP 長鏈接,不須要屢次建連。
所以業界大部分直播業務都會選擇用 RTMP 做爲流媒體協議。一般會將數據流封裝成 FLV 經過 HTTP 提供出去。可是這樣也有一些問題須要解決:

iOS 平臺沒有提供原生支持 RTMP 或 HTTP-FLV 的播放器,這就須要開發支持相關協議的播放器。
複製代碼

HLS

HLS 的基本原理就是當採集推流端將視頻流推送到流媒體服務器時,服務器將收到的流信息每緩存一段時間就封包成一個新的 ts 文件,同時服務器會創建一個 m3u8 的索引文件來維護最新幾個 ts 片斷的索引。當播放端獲取直播時,它是從 m3u8 索引文件獲取最新的 ts 視頻文件片斷來播放,從而保證用戶在任什麼時候候鏈接進來時都會看到較新的內容,實現近似直播的體驗。相對於常見的流媒體直播協議,例如 RTMP 協議、RTSP 協議等,HLS 最大的不一樣在於直播客戶端獲取到的並非一個完整的數據流,而是連續的、短時長的媒體文件,客戶端不斷的下載並播放這些小文件。這種方式的理論最小延時爲一個 ts 文件的時長,通常狀況爲 2-3 個 ts 文件的時長。HLS 的分段策略,基本上推薦是 10 秒一個分片,這就看出了 HLS 的缺點:

一般 HLS 直播延時會達到 20-30s,而高延時對於須要實時互動體驗的直播來講是不可接受的。
HLS 基於短鏈接 HTTP,HTTP 是基於 TCP 的,這就意味着 HLS 須要不斷地與服務器創建鏈接,TCP 每次創建鏈接時的三次握手、慢啓動過程、斷開鏈接時的四次揮手都會產生消耗。
不過 HLS 也有它的優勢:

數據經過 HTTP 協議傳輸,因此採用 HLS 時不用考慮防火牆或者代理的問題。
使用短時長的分片文件來播放,客戶端能夠平滑的切換碼率,以適應不一樣帶寬條件下的播放。
HLS 是蘋果推出的流媒體協議,在 iOS 平臺上能夠得到自然的支持,採用系統提供的 AVPlayer 就能直接播放,不用本身開發播放器。
image
複製代碼

HTTP FLV

先經過服務器將FLV下載到本地緩存,而後再經過NetConnection的本地鏈接來播放這個FLV,這種方法是播放本地的視頻,並非播放服務器的視頻。所以在本地緩存裏能夠找到這個FLV。其優勢就是服務器下載完這個FLV,服務器就沒有消耗了,節省服務器消耗。其缺點就是FLV會緩存在客戶端,對FLV的保密性很差。
 是一種將直播流模擬成FLV文件,經過HTTP協議進行下載的模式來實現流媒體傳輸的協議,端口號80
 通常建議使用HTTP FLV,實時性和RTMP相等。
 優勢:HTTP相比於RTMP省去了一些協議交互時間,首屏時間更短。HTTP可拓展的功能更多。
複製代碼

協議差異

  • HLS:HTTP Live Streaming;基於短鏈接 HTTP;集合一段時間的數據生成 ts 切片文件,更新 m3u8 文件;延時 25s+。
  • RTMP:Real Time Messaging Protocal;基於長鏈接TCP;每一個時刻收到的數據當即轉發;延時 1~3s。
  • HTTP-FLV: RTMP over HTTP;基於長鏈接 HTTP;每一個時刻收到的數據當即轉發,使用 HTTP 協議;延時 1~3s。
相關文章
相關標籤/搜索