視頻
視頻實質(zhì):
純粹的視頻(不包括音頻)實質(zhì)上就是一組幀圖片,經(jīng)過視頻編碼成為視頻(video)文件再把音頻(audio)文件有些還有字幕文件組裝在一起成為我們看到的視頻(movie)文件。1秒內(nèi)出現(xiàn)的圖片數(shù)就是幀率,圖片間隔越小畫面就越流暢,所以幀率越高效果就越好,需要的存儲空間也就越多。
- 協(xié)議層(Protocol Layer):該層處理的數(shù)據(jù)為符合特定流媒體協(xié)議規(guī)范的數(shù)據(jù),例如http,rtmp,file等;
- 封裝層(Format Layer):該層處理的數(shù)據(jù)為符合特定封裝格式規(guī)范的數(shù)據(jù),例如mkv,mp4,flv,mpegts,avi等;
- 編碼層(Codec Layer):該層處理的數(shù)據(jù)為符合特定編碼標(biāo)準(zhǔn)規(guī)范的數(shù)據(jù),例如h264,h265,mpeg2,mpeg4等;
- 像素層(Pixel Layer):該層處理的數(shù)據(jù)為符合特定像素格式規(guī)范的數(shù)據(jù),例如yuv420p,yuv422p,yuv444p,rgb24等;

視頻編碼:
因為不進行編碼的視頻數(shù)據(jù)量非常大,會造成存儲和傳輸上的困難,所以視頻文件都需要在錄制完成后進行編碼。視頻編碼主要從兩個維度壓縮數(shù)據(jù)。
1、單張圖像某一區(qū)域相鄰像素相似,比如一片紅色只記錄紅色色值和區(qū)域,不用記錄這個區(qū)域的每一個像素點。
2、相鄰圖像之間內(nèi)容相似,因為相鄰兩幀要制造連續(xù)的效果,所以兩幀之間的內(nèi)容一般非常接近。目前主流的視頻編碼技術(shù)都是用圖像編碼方法對第一幀進行編碼,然后用某種方式描述接下來的幀相對于附近的幀有什么區(qū)別。
視頻格式:
MP4、MOV、AVI、RMVB這些播放格式其實都是封裝格式,除了RMVB比較特殊外,其他格式內(nèi)封裝的視頻編碼格式都是H264,H264以高壓縮率聞名于世,壓縮效率比MEPG-2提升一倍多,但是世上沒有兩全其美的事,H264的解碼難度提高了3倍多。
視頻碼率:
視頻文件的大小除以是視頻的時長定義為碼率。
碼率和分辨率跟視頻質(zhì)量的關(guān)系:
碼率可以理解為取樣率,單位時間內(nèi)取樣率越大,精度就越高,同時體積也越大。
當(dāng)視頻沒有經(jīng)過編碼時,如果分辨率越高,那么視頻圖像的細節(jié)越清晰。
但如果視頻經(jīng)過編碼,被限制在一定碼率內(nèi),編碼器就必須舍棄掉一部分細節(jié)。
所以分辨率和碼率都同清晰度有關(guān)。
軟解碼和硬解碼:
對H264的視頻解碼給CPU造成了很大負擔(dān),所以手機工程師把這部分工作交給了更善于進行處理簡單工作但是數(shù)據(jù)量較大的GPU。
GPU解碼就是所謂的硬解碼
CPU解碼就是軟解碼。
iOS提供的播放器類使用的是硬解碼,所以視頻播放對CPU不會有很大的壓力,但是支持的播放格式比較單一,一般就是MP4、MOV、M4V這幾個。
視頻壓縮原理
1、壓縮的方向
數(shù)字化后的視頻信號具有很大的數(shù)據(jù)冗余,壓縮的本質(zhì)就是去掉這些冗余。
- 空間冗余,視頻的背景和整體顏色相近并且平穩(wěn)變化,可以利用幀內(nèi)編碼進行壓縮;(無損)
- 時間冗余,兩個視頻幀之間具有強相關(guān)性,利用運動估計和運動補償進行幀間壓縮;(無損)
- 結(jié)構(gòu)冗余,圖像內(nèi)部存在相似性,通過這種關(guān)系可以進行分形編碼;
- 編碼冗余,出現(xiàn)概率大的顏色編碼長度短,概率小的顏色編碼長度長;(可變長度編碼)
- 視覺冗余,利用人眼對亮度和色度的敏感度不同,在編碼時進行數(shù)據(jù)壓縮;(有損壓縮)
2、變換
空間域描述的圖像相關(guān)性不太明顯,需要變換到頻率域。常用的正交變換有離散傅里葉變換,離散余弦變換等等。數(shù)字視頻壓縮過程中應(yīng)用廣泛的是離散余弦變換。
- 空間域(spatial domain),又稱圖像空間(image space)。由圖像像元組成的空間。在圖像空間中以長度(距離)為自變量直接對像元值進行處理稱為空間域處理。
- 頻率域(spatial frequency domain),以空間頻率為自變量描述圖像的特征,可以將一幅圖像像元值在空間上的變化分解為具有不同振幅、空間頻率和相位的簡振函數(shù)的線性疊加,圖像中各種空間頻率成分的組成和分布稱為空間頻譜。這種對圖像的空間頻率特征進行分解、處理和分析稱為頻率域處理或波數(shù)域處理。
3.H.264格式
I幀是關(guān)鍵幀,解碼時只需要本幀數(shù)據(jù);
P幀是參考幀,表示這一幀與前一個關(guān)鍵幀(或P幀)的差別;
B幀是雙向參考幀,表示本幀與前后幀的差別;(B幀壓縮率高,解碼復(fù)雜,直播中較少用)
IDR幀是第一個I幀,為的是和其他I幀區(qū)別開,方便控制編碼和解碼;
IDR會導(dǎo)致DPB(DecodedPictureBuffer 參考幀列表)清空,而I不會。
GOP(Group Of Picture)是圖像組,是一組連續(xù)的畫面;(直播實現(xiàn)秒開,關(guān)鍵就是CDN節(jié)點緩存GOP,編碼器拿到第一個GOP后馬上解碼播放)
幀內(nèi)壓縮:當(dāng)壓縮一幀圖像時,僅考慮本幀的數(shù)據(jù)而不考慮相鄰幀之間的冗余信息;(幀內(nèi)壓縮一般達不到很高的壓縮,跟編碼jpeg差不多)
幀間壓縮:利用相鄰幀的相關(guān)性提高壓縮量,減少冗余;(運動補償和運動估計是常用的技術(shù))
音頻壓縮原理
數(shù)字音頻壓縮編碼在保證信號在聽覺方面不產(chǎn)生失真的前提下,對音頻數(shù)據(jù)信號進行盡可能大的壓縮。數(shù)字音頻壓縮編碼采取去除聲音信號中冗余成分的方法來實現(xiàn)。所謂冗余成分指的是音頻中不能被人耳感知到的信號,它們對確定聲音的音色,音調(diào)等信息沒有任何的幫助。
冗余信號包含人耳聽覺范圍外的音頻信號以及被掩蔽掉的音頻信號等。
人耳聽覺的掩蔽效應(yīng):當(dāng)一個強音信號與一個弱音信號同時存在時,弱音信號將被強音信號所掩蔽而聽不見,這樣弱音信號就可以視為冗余信號而不用傳送。
頻譜掩蔽效應(yīng)
一個頻率的聲音能量小于某個閾值之后,人耳就會聽不到,這個閾值稱為最小可聞閾。當(dāng)有另外能量較大的聲音出現(xiàn)的時候,該聲音頻率附近的閾值會提高很多,即所謂的掩蔽效應(yīng)。
時域掩蔽效應(yīng)
當(dāng)強音信號和弱音信號同時出現(xiàn)時,還存在時域掩蔽效應(yīng)。即兩者發(fā)生時間很接近的時候,也會發(fā)生掩蔽效應(yīng)。時域掩蔽過程曲線如圖所示,分為前掩蔽、同時掩蔽和后掩蔽三部分。
- PCM是編碼格式,經(jīng)過話筒錄音后直接得到的未經(jīng)壓縮的數(shù)據(jù)流;
數(shù)據(jù)大小=采樣頻率采樣位數(shù)聲道*秒數(shù)/8。
采樣定理表明采樣頻率必須大于被采樣信號帶寬的兩倍,另外一種等同的說法是奈奎斯特頻率必須大于被采樣信號的帶寬。如果信號的帶寬是 100Hz,那么為了避免混疊現(xiàn)象采樣頻率必須大于200Hz。換句話說就是采樣頻率必須至少是信號中最大頻率分量頻率的兩倍,否則就不能從信號采樣中恢復(fù)原始信號。
AAC是編解碼標(biāo)準(zhǔn),基于MPEG-2的音頻編碼技術(shù);
PCM采樣率是44100Hz,那么AAC碼率可設(shè)置64000bps;
如果是16K,可設(shè)置為32000bps;MP3是封裝格式,所存放數(shù)據(jù)使用的編碼方式稱為MPEG1 Layer-3 ;
AMR是封裝格式,專用于有效地壓縮語音頻率;
WAV是封裝格式,里面可以存放多種編碼格式的數(shù)據(jù),一般是PCM數(shù)據(jù);
iOS音頻
HTTP Live Streaming
HLS簡介
HTTP Live Streaming(縮寫是 HLS)是一個由蘋果公司提出的基于HTTP的流媒體網(wǎng)絡(luò)傳輸協(xié)議。它的工作原理是把整個流分成一個個小的基于HTTP的文件來下載,每次只下載一些。
當(dāng)媒體流正在播放時,客戶端可以選擇從許多不同的備用源中以不同的速率下載同樣的資源,允許流媒體會話適應(yīng)不同的數(shù)據(jù)速率。支持的視頻流編碼為H.264。我們在視頻網(wǎng)站上看到的M3U8后綴的播放鏈接就是使用HLS協(xié)議的視頻。
HLS優(yōu)點
1、看完一段緩存一段,防止只看一段視頻但是把整個視頻文件都緩存下來的用戶,減少服務(wù)器壓力和節(jié)省流量。
2、根據(jù)用戶網(wǎng)速切換不同的碼率,兼顧流程性和清晰度。
HLS支持情況
iOS 3.0及之后的版本
Android 3.0及之后的版本
HTML5。
終端播放格式的選取
Android由于3.0之后才支持HLS,所以Android2.3只能用MP4。
Android3.0及之后支持HLS??梢杂胢3u8、mp4格式
iOS支持HLS,但不支持flash??梢杂胢3u8、mp4格式
支持HTML5的瀏覽器 可以用m3u8。
不支持HTML5的瀏覽器只能用flash播放swf。
由于以上原因,目前無法實現(xiàn)一個播放地址在所有的平臺都通用。
iOS視頻播放:
iOS提供MPMoviePlayerController類進行播放,支持流媒體和文件播放。視頻內(nèi)容會渲染到他的View上,可以放在你想放的任何地方,用起來比較方便。這個類設(shè)計上不合理的是視頻播放狀態(tài)和視頻加載狀態(tài)都是通過Notification通知的,而不是通過block或者delegate。
iOS視頻錄制:
同拍照一樣視頻錄制功能有兩種實現(xiàn)方式
1、UIImagePickerViewController
2、AVFoundation。
這里只討論AVFoundation框架,這個框架是蘋果提供的底層多媒體框架,用于音視頻采集、音視頻解碼、視頻編輯等,多媒體基本上都依賴AVFoundation框架。
視頻錄制和拍照需要做的工作差不多,主要有以下5步:
1、創(chuàng)建會話AVCaptureSession,用于控制input到output的流向。
2、獲取設(shè)備AVCaptureDevice,攝像頭用于視頻采集,話筒用于音頻采集。
3、創(chuàng)建輸入設(shè)備AVCaptureDeviceInput,將設(shè)備綁定到input口中,并添加到session上
4、創(chuàng)建輸出AVCaptureOutput,可以輸出到文件和屏幕上。 AVCaptureMovieFileOutput 輸出一個電影文件 AVCaptureVideoDataOutput 輸出處理視頻幀,用于顯示正在錄制的視頻 AVCaptureAudioDataOutput 輸出音頻數(shù)據(jù)
5、音視頻合成到一個文件中
iOS對視頻實時處理:
如果需要對視頻進行實時處理(當(dāng)然需要否則看不到正在錄制的內(nèi)容),則需要直接對相機緩沖區(qū)(camera buffer)中的視頻流進行處理。
1、定義一個視頻數(shù)據(jù)輸出(AVCaptureVideoDataOutput), 并將其添加到session上。
2、設(shè)置接受的controller作為視頻數(shù)據(jù)輸出緩沖區(qū)(sample buffer)的代理。
3、實現(xiàn)代理方法
-(void)captureOutput:(AVCaptureOutput )captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection )connection
當(dāng)數(shù)據(jù)緩沖區(qū)(data buffer)一有數(shù)據(jù)時,AVFoundation就調(diào)用該方法。在該代理方法中,我們可以獲取視頻幀、處理視頻幀、顯示視頻幀。實時濾鏡就是在這里進行處理的。在這個方法中將緩沖區(qū)中的視頻數(shù)據(jù)(就是幀圖片)輸出到要顯示的layer上。
視頻編碼與解碼
移動端編碼無外乎兩種:
軟編碼,利用CPU來對視頻做編碼和解碼的,但效率不高.(大部分移動端通過FFMpeg來實現(xiàn)軟編碼。)
硬編碼,利用GPU或者專用處理器來對視頻做編碼和解碼。iOS 8.0之后開放的Video ToolBox框架就是來實現(xiàn)硬件的編碼和解碼的.
視頻數(shù)據(jù)硬編碼
視頻壓縮編碼通過Video Toolbox框架下的VTCompressionSession完成。Video ToolBox 是一個基于 CoreMedia,CoreVideo,CoreFoundation 框架的 C 語言 API,來處理硬件的編碼和解碼,在iOS 8.0后,蘋果將該框架引入iOS系統(tǒng),蘋果在iOS 8.0系統(tǒng)之前,沒有開放系統(tǒng)的硬件編碼解碼功能。
由于Video ToolBox 提供的是 C 的API,所以編碼時會用到一些swift的指針操作。
定義創(chuàng)建配置CompressionSession:
private var session:VTCompressionSessionRef?
// 將
VTCompressionSessionCreate(
kCFAllocatorDefault,
480, // encode height
640,// encode width
kCMVideoCodecType_H264, //encode type,h.264
nil,
attributes,
nil,
callback,
unsafeBitCast(self, UnsafeMutablePointer<Void>.self),
&session)
// 設(shè)置編碼的屬性
VTSessionSetProperties(session!, properties)
VTCompressionSessionPrepareToEncodeFrames(session!)
VTCompressionSessionCreate 的encode height, encode width設(shè)置編碼輸出的h.264文件的寬高,單位px(像素)。
編碼類型主要用的就是kCMVideoCodecType_H264(h.264),其他的類型還有h.263等,好像最新的 iphone 6s 已經(jīng)支持 h.265的編碼,但還沒有放出接口。
硬解碼和硬編碼使用方法
1.創(chuàng)建AVAssetReader,將H264碼流轉(zhuǎn)換成解碼前的CMSampleBuffer。
(1)提取sps和pps生成format descriptio
(2)提取視頻圖像數(shù)據(jù)生成CMBlockBuffer。
(3)根據(jù)需要,生成CMTime信息。
2.創(chuàng)建AVAssetWriter,設(shè)置輸出及壓縮屬性。
iOS視頻直播~推流、拉流原理
HLS協(xié)議:
簡單講就是把整個流分成一個個小的,基于 HTTP 的文件來下載,每次只下載一些,前面提到了用于 H5 播放直播視頻時引入的一個 .m3u8 的文件,這個文件就是基于 HLS 協(xié)議,存放視頻流元數(shù)據(jù)的文件。
每一個 .m3u8 文件,分別對應(yīng)若干個 ts 文件,這些 ts 文件才是真正存放視頻的數(shù)據(jù),m3u8 文件只是存放了一些 ts 文件的配置信息和相關(guān)路徑,當(dāng)視頻播放時,.m3u8 是動態(tài)改變的,video 標(biāo)簽會解析這個文件,并找到對應(yīng)的 ts 文件來播放,所以一般為了加快速度,.m3u8 放在 web 服務(wù)器上,ts 文件放在 cdn 上。
.m3u8 文件,其實就是以 UTF-8 編碼的 m3u 文件,這個文件本身不能播放,只是存放了播放信息的文本文件:
#EXTM3U m3u文件頭
#EXT-X-MEDIA-SEQUENCE 第一個TS分片的序列號
#EXT-X-TARGETDURATION 每個分片TS的最大的時長
#EXT-X-ALLOW-CACHE 是否允許cache
#EXT-X-ENDLIST m3u8文件結(jié)束符
#EXTINF 指定每個媒體段(ts)的持續(xù)時間(秒),僅對其后面的URI有效mystream-12.ts
HLS 的請求流程是:
1.http 請求 m3u8 的 url。
2.服務(wù)端返回一個 m3u8 的播放列表,這個播放列表是實時更新的,一般一次給出5段數(shù)據(jù)的 url。
3.客戶端解析 m3u8 的播放列表,再按序請求每一段的 url,獲取 ts 數(shù)據(jù)流。
關(guān)于HLS延遲原因:
hls 協(xié)議是將直播流分成一段一段的小段視頻去下載播放的,所以假設(shè)列表里面的包含5個 ts 文件,每個 TS 文件包含5秒的視頻內(nèi)容,那么整體的延遲就是25秒。因為當(dāng)你看到這些視頻時,主播已經(jīng)將視頻錄制好上傳上去了,所以時這樣產(chǎn)生的延遲。當(dāng)然可以縮短列表的長度和單個 ts 文件的大小來降低延遲,極致來說可以縮減列表長度為1,并且 ts 的時長為1s,但是這樣會造成請求次數(shù)增加,增大服務(wù)器壓力,當(dāng)網(wǎng)速慢時回造成更多的緩沖,所以蘋果官方推薦的ts時長時10s,所以這樣就會大改有30s的延遲;
數(shù)據(jù)采集原理:
下面將利用 ios 上的攝像頭,進行音視頻的數(shù)據(jù)采集,主要分為以下幾個步驟:
- 音視頻的采集,ios 中,利用 AVCaptureSession和AVCaptureDevice 可以采集到原始的音視頻數(shù)據(jù)流。
- 對視頻進行 H264 編碼,對音頻進行 AAC 編碼,在 ios 中分別有已經(jīng)封裝好的編碼庫來實現(xiàn)對音視頻的編碼。
- 對編碼后的音、視頻數(shù)據(jù)進行組裝封包;
- 建立 RTMP 連接并上推到服務(wù)端。
ps:由于編碼庫大多使用 c 語言編寫,需要自己使用時編譯,對于 ios,可以使用已經(jīng)編譯好的編碼庫。
RTMP介紹:
Real Time Messaging Protocol(簡稱 RTMP)是 Macromedia 開發(fā)的一套視頻直播協(xié)議。和 HLS 一樣都可以應(yīng)用于視頻直播,區(qū)別是 RTMP 基于 flash 無法在 ios 的瀏覽器里播放,但是實時性比 HLS 要好。所以一般使用這種協(xié)議來上傳視頻流,也就是視頻流推送到服務(wù)器。
對比:
- RTMP 首先就是延遲低,基于TCP的長鏈接,對于數(shù)據(jù)處理及時,收到即刻發(fā)送,推薦使用場景:即時互動。
- HLS 延遲高,短鏈接,原理是集合了一段時間的視頻數(shù)據(jù),切割ts片,逐個下載播放。優(yōu)點是跨平臺。
推流:
推流,就是將我們已經(jīng)編碼好的音視頻數(shù)據(jù)發(fā)往視頻流服務(wù)器中,一般常用的是使用 rtmp 推流,可以使用第三方庫 librtmp-iOS 進行推流,librtmp 封裝了一些核心的 api 供使用者調(diào)用,如果覺得麻煩,可以使用現(xiàn)成的 ios 視頻推流sdk,也是基于 rtmp 的。具體說下:
也就是對編碼好的音視頻數(shù)據(jù)推到服務(wù)器上,這里我們又分為兩類推流模式:手機端推流,服務(wù)器本地推流。就拿我上一家公司的電視直播來說,視頻源是來自電視臺的,需要通過ffmpeg命令來進行個推流,那么推流協(xié)議的話這里又分為了:HLS推流和rtmp推流,這里的取舍主要涉及到了是否需要及其實時的直播問題,也就是延遲20 30s是否接受,當(dāng)然電視直播并不是主播實時互動,所以不需要使用實時流媒體協(xié)議的rtmp,所以通過ffmpeg -loglevel 這么一個命令將電視臺給的視頻進行各像nginx服務(wù)器的一個推流,那么我們就可以通過nginx服務(wù)器給的鏈接,配合我的第三方的直播框架,就可以實現(xiàn)個直播,這個是服務(wù)器本地的HLS協(xié)議的一個推流。當(dāng)然如果我們要做一個沒有延遲的比如實現(xiàn)各主播互動的一個直播,那么就是iOS客戶端用rtmp協(xié)議的一個往nginx服務(wù)器的一個推流了。在iOS設(shè)備上進行各推流的話,是通過AVCaptureSession這么一個捕捉會話,指定兩個AVCaptureDevice 也就是iOS的攝像頭和麥克風(fēng),獲取個原始視頻和音頻,然后需要進行個H.264的視頻編碼和AAC的音頻編碼,再將編碼后的數(shù)據(jù)整合成一個音視頻包,通過rmtp推送到nginx服務(wù)器。這里這些步驟,我們可以通過各第三方集成好的推流工具進行推流,這個工具有l(wèi)ibrtmp,和騰訊的GDLiveStreaming進行個推流。
直播

流媒體(直播需要用到流媒體)
- 流媒體開發(fā):網(wǎng)絡(luò)層(socket或st)負責(zé)傳輸,協(xié)議層(rtmp或hls)負責(zé)網(wǎng)絡(luò)打包,封裝層(flv、ts)負責(zé)編解碼數(shù)據(jù)的封裝,編碼層(h.264和aac)負責(zé)圖像,音頻壓縮。
幀:每幀代表一幅靜止的圖像 - GOP:(Group of Pictures)畫面組,一個GOP就是一組連續(xù)的畫面,每個畫面都是一幀,一個GOP就是很多幀的集合
直播的數(shù)據(jù),其實是一組圖片,包括I幀、P幀、B幀,當(dāng)用戶第一次觀看的時候,會尋找I幀,而播放器會到服務(wù)器尋找到最近的I幀反饋給用戶。因此,GOP Cache增加了端到端延遲,因為它必須要拿到最近的I幀
GOP Cache的長度越長,畫面質(zhì)量越好 - 碼率:圖片進行壓縮后每秒顯示的數(shù)據(jù)量。
- 幀率:每秒顯示的圖片數(shù)。影響畫面流暢度,與畫面流暢度成正比:幀率越大,畫面越流暢;幀率越小,畫面越有跳動感。
由于人類眼睛的特殊生理結(jié)構(gòu),如果所看畫面之幀率高于16的時候,就會認為是連貫的,此現(xiàn)象稱之為視覺暫留。并且當(dāng)幀速達到一定數(shù)值后,再增長的話,人眼也不容易察覺到有明顯的流暢度提升了。 - 分辨率:(矩形)圖片的長度和寬度,即圖片的尺寸
- 壓縮前的每秒數(shù)據(jù)量:幀率X分辨率(單位應(yīng)該是若干個字節(jié))
- 壓縮比:壓縮前的每秒數(shù)據(jù)量/碼率 (對于同一個視頻源并采用同一種視頻編碼算法,則:壓縮比越高,畫面質(zhì)量越差。)
- 視頻文件格式:文件的后綴,比如.wmv,.mov,.mp4,.mp3,.avi,
主要用處,根據(jù)文件格式,系統(tǒng)會自動判斷用什么軟件打開,
注意: 隨意修改文件格式,對文件的本身不會造成太大的影響,比如把avi改成mp4,文件還是avi. - 視頻封裝格式:一種儲存視頻信息的容器,流式封裝可以有TS、FLV等,索引式的封裝有MP4,MOV,AVI等,
主要作用:一個視頻文件往往會包含圖像和音頻,還有一些配置信息(如圖像和音頻的關(guān)聯(lián),如何解碼它們等):這些內(nèi)容需要按照一定的規(guī)則組織、封裝起來.
注意:會發(fā)現(xiàn)封裝格式跟文件格式一樣,因為一般視頻文件格式的后綴名即采用相應(yīng)的視頻封裝格式的名稱,所以視頻文件格式就是視頻封裝格式。 - 視頻封裝格式和視頻壓縮編碼標(biāo)準(zhǔn):就好像項目工程和編程語言,封裝格式就是一個項目的工程,視頻編碼方式就是編程語言,一個項目工程可以用不同語言開發(fā)。
視頻編碼框架
- FFmpeg:是一個跨平臺的開源視頻框架,能實現(xiàn)如視頻編碼,解碼,轉(zhuǎn)碼,串流,播放等豐富的功能。其支持的視頻格式以及播放協(xié)議非常豐富,幾乎包含了所有音視頻編解碼、封裝格式以及播放協(xié)議。
-Libswresample:可以對音頻進行重采樣,rematrixing 以及轉(zhuǎn)換采樣格式等操 作。
-Libavcodec:提供了一個通用的編解碼框架,包含了許多視頻,音頻,字幕流 等編碼/解碼器。
-Libavformat:用于對視頻進行封裝/解封裝。
-Libavutil:包含一些共用的函數(shù),如隨機數(shù)生成,數(shù)據(jù)結(jié)構(gòu),數(shù)學(xué)運算等。
-Libpostproc:用于進行視頻的一些后期處理。
-Libswscale:用于視頻圖像縮放,顏色空間轉(zhuǎn)換等。
-Libavfilter:提供濾鏡功能。 - X264:把視頻原數(shù)據(jù)YUV編碼壓縮成H.264格式
- VideoToolbox:蘋果自帶的視頻硬解碼和硬編碼API,但是在iOS8之后才開放。
- AudioToolbox:蘋果自帶的音頻硬解碼和硬編碼API
流媒體服務(wù)器
- SRS:一款國人開發(fā)的優(yōu)秀開源流媒體服務(wù)器系統(tǒng)
- BMS:也是一款流媒體服務(wù)器系統(tǒng),但不開源,是SRS的商業(yè)版,比SRS功能更多
- nginx:免費開源web服務(wù)器,常用來配置流媒體服務(wù)器。
數(shù)據(jù)分發(fā)
- CDN:(Content Delivery Network),即內(nèi)容分發(fā)網(wǎng)絡(luò),將網(wǎng)站的內(nèi)容發(fā)布到最接近用戶的網(wǎng)絡(luò)”邊緣”,使用戶可以就近取得所需的內(nèi)容,解決 Internet網(wǎng)絡(luò)擁擠的狀況,提高用戶訪問網(wǎng)站的響應(yīng)速度.
- CDN:代理服務(wù)器,相當(dāng)于一個中介。
- CDN工作原理:比如請求流媒體數(shù)據(jù)
1.上傳流媒體數(shù)據(jù)到服務(wù)器(源站)
2.源站存儲流媒體數(shù)據(jù)
3.客戶端播放流媒體,向CDN請求編碼后的流媒體數(shù)據(jù)
4.CDN的服務(wù)器響應(yīng)請求,若節(jié)點上沒有該流媒體數(shù)據(jù)存在,則向源站繼續(xù)請求流媒體數(shù)據(jù);若節(jié)點上已經(jīng)緩存了該視頻文件,則跳到第6步。
5.源站響應(yīng)CDN的請求,將流媒體分發(fā)到相應(yīng)的CDN節(jié)點上
6.CDN將流媒體數(shù)據(jù)發(fā)送到客戶端 - 回源:當(dāng)有用戶訪問某一個URL的時候,如果被解析到的那個CDN節(jié)點沒有緩存響應(yīng)的內(nèi)容,或者是緩存已經(jīng)到期,就會回源站去獲取搜索。如果沒有人訪問,那么CDN節(jié)點不會主動去源站拿.
- 帶寬:在固定的時間可傳輸?shù)臄?shù)據(jù)總量,
比如64位、800MHz的前端總線,它的數(shù)據(jù)傳輸率就等于64bit×800MHz÷8(Byte)=6.4GB/s - 負載均衡: 由多臺服務(wù)器以對稱的方式組成一個服務(wù)器集合,每臺服務(wù)器都具有等價的地位,都可以單獨對外提供服務(wù)而無須其他服務(wù)器的輔助.
通過某種負載分擔(dān)技術(shù),將外部發(fā)送來的請求均勻分配到對稱結(jié)構(gòu)中的某一臺服務(wù)器上,而接收到請求的服務(wù)器獨立地回應(yīng)客戶的請求。
均衡負載能夠平均分配客戶請求到服務(wù)器列陣,籍此提供快速獲取重要數(shù)據(jù),解決大量并發(fā)訪問服務(wù)問題。
這種群集技術(shù)可以用最少的投資獲得接近于大型主機的性能。 - QoS(帶寬管理):限制每一個組群的帶寬,讓有限的帶寬發(fā)揮最大的效用
直播協(xié)議
- HLS:由Apple公司定義的用于實時流傳輸?shù)膮f(xié)議,HLS基于HTTP協(xié)議實現(xiàn),傳輸內(nèi)容包括兩部分,一是M3U8描述文件,二是TS媒體文件??蓪崿F(xiàn)流媒體的直播和點播,主要應(yīng)用在iOS系統(tǒng)
HLS是以點播的技術(shù)方式來實現(xiàn)直播
HLS是自適應(yīng)碼率流播,客戶端會根據(jù)網(wǎng)絡(luò)狀況自動選擇不同碼率的視頻流,條件允許的情況下使用高碼率,網(wǎng)絡(luò)繁忙的時候使用低碼率,并且自動在二者間隨意切
換。這對移動設(shè)備網(wǎng)絡(luò)狀況不穩(wěn)定的情況下保障流暢播放非常有幫助。
實現(xiàn)方法是服務(wù)器端提供多碼率視頻流,并且在列表文件中注明,播放器根據(jù)播放進度和下載速度自動調(diào)整。
HLS與RTMP對比:HLS主要是延時比較大,RTMP主要優(yōu)勢在于延時低
HLS協(xié)議的小切片方式會生成大量的文件,存儲或處理這些文件會造成大量資源浪費
相比使用RTSP協(xié)議的好處在于,一旦切分完成,之后的分發(fā)過程完全不需要額外使用任何專門軟件,普通的網(wǎng)絡(luò)服務(wù)器即可,大大降低了CDN邊緣服務(wù)器的配置要求,可以使用任何現(xiàn)成的CDN,而一般服務(wù)器很少支持RTSP。 - HTTP-FLV:基于HTTP協(xié)議流式的傳輸媒體內(nèi)容。
相對于RTMP,HTTP更簡單和廣為人知,內(nèi)容延遲同樣可以做到1~3秒,打開速度更快,因為HTTP本身沒有復(fù)雜的狀態(tài)交互。所以從延遲角度來看,HTTP-FLV要優(yōu)于RTMP。 - RTSP:實時流傳輸協(xié)議,定義了一對多應(yīng)用程序如何有效地通過IP網(wǎng)絡(luò)傳送多媒體數(shù)據(jù).
- RTP:實時傳輸協(xié)議,RTP是建立在UDP協(xié)議上的,常與RTCP一起使用,其本身并沒有提供按時發(fā)送機制或其它服務(wù)質(zhì)量(QoS)保證,它依賴于低層服務(wù)去實現(xiàn)這一過程。
- RTCP:RTP的配套協(xié)議,主要功能是為RTP所提供的服務(wù)質(zhì)量(QoS)提供反饋,收集相關(guān)媒體連接的統(tǒng)計信息,例如傳輸字節(jié)數(shù),傳輸分組數(shù),丟失分組數(shù),單向和雙向網(wǎng)絡(luò)延遲等等。
iOS視頻邊下邊播--緩存播放數(shù)據(jù)流
使用本地代理服務(wù)器的方式,原理很簡單,但是缺點也很明顯,需要自己寫一個本地代理服務(wù)器或者使用第三方庫httpSever。如果使用httpSever作為本地代理服務(wù)器,如果只緩存一個視頻是沒有問題的,如果緩存多個視頻互相切換,本地代理服務(wù)器提供的數(shù)據(jù)很不穩(wěn)定,crash概率非常大。
這里我采用ios7以后系統(tǒng)自帶的方法實現(xiàn)視頻邊下邊播,這里的邊下邊播不是單獨開一個子線程去下載,而是把視頻播放的數(shù)據(jù)給保存到本地。簡而言之,就是使用一遍的流量,既播放了視頻,也保存了視頻。
用到的框架:<AVFoundation/AVFoundation.h>
用到的播放器:AVplayer
先說一下avplayer自身的播放原理,當(dāng)我們給播放器設(shè)置好url等一些參數(shù)后,播放器就會向url所在的服務(wù)器發(fā)送請求(請求參數(shù)有兩個值,一個是offset偏移量,另一個是length長度,其實就相當(dāng)于NSRange一樣),服務(wù)器就根據(jù)range參數(shù)給播放器返回數(shù)據(jù)。
產(chǎn)品需求:
1.支持正常播放器的一切功能,包括暫停、播放和拖拽
2.如果視頻加載完成且完整,將視頻文件保存到本地cache,下一次播放本地cache中的視頻,不再請求網(wǎng)絡(luò)數(shù)據(jù)
3.如果視頻沒有加載完(半路關(guān)閉或者拖拽)就不用保存到本地cache
實現(xiàn)方案:
1.需要在視頻播放器和服務(wù)器之間添加一層類似代理的機制,視頻播放器不再直接訪問服務(wù)器,而是訪問代理對象,代理對象去訪問服務(wù)器獲得數(shù)據(jù),之后返回給視頻播放器,同時代理對象根據(jù)一定的策略緩存數(shù)據(jù)。
2.AVURLAsset中的resourceLoader可以實現(xiàn)這個機制,resourceLoader的delegate就是上述的代理對象。
3.視頻播放器在開始播放之前首先檢測是本地cache中是否有此視頻,如果沒有才通過代理獲得數(shù)據(jù),如果有,則直接播放本地cache中的視頻即可。
代理對象需要實現(xiàn)的功能
1.接收視頻播放器的請求,并根據(jù)請求的range向服務(wù)器請求本地沒有獲得的數(shù)據(jù)
2.緩存向服務(wù)器請求回的數(shù)據(jù)到本地
3.如果向服務(wù)器的請求出現(xiàn)錯誤,需要通知給視頻播放器,以便視頻播放器對用戶進行提示
流程圖:

視頻播放器處理流程
1.當(dāng)開始播放視頻時,通過視頻url判斷本地cache中是否已經(jīng)緩存當(dāng)前視頻,如果有,則直接播放本地cache中視頻
2.如果本地cache中沒有視頻,則視頻播放器向代理請求數(shù)據(jù)
3.加載視頻時展示正在加載的提示(菊花轉(zhuǎn))
4.如果可以正常播放視頻,則去掉加載提示,播放視頻,如果加載失敗,去掉加載提示并顯示失敗提示
5.在播放過程中如果由于網(wǎng)絡(luò)過慢或拖拽原因?qū)е聸]有播放數(shù)據(jù)時,要展示加載提示,跳轉(zhuǎn)到第4步
代理對象處理流程
1.當(dāng)視頻播放器向代理請求dataRequest時,判斷代理是否已經(jīng)向服務(wù)器發(fā)起了請求,如果沒有,則發(fā)起下載整個視頻文件的請求
2.如果代理已經(jīng)和服務(wù)器建立鏈接,則判斷當(dāng)前的dataRequest請求的offset是否大于當(dāng)前已經(jīng)緩存的文件的offset,如果大于則取消當(dāng)前與服務(wù)器的請求,并從offset開始到文件尾向服務(wù)器發(fā)起請求(此時應(yīng)該是由于播放器向后拖拽,并且超過了已緩存的數(shù)據(jù)時才會出現(xiàn))
3.如果當(dāng)前的dataRequest請求的offset小于已經(jīng)緩存的文件的offset,同時大于代理向服務(wù)器請求的range的offset,說明有一部分已經(jīng)緩存的數(shù)據(jù)可以傳給播放器,則將這部分數(shù)據(jù)返回給播放器(此時應(yīng)該是由于播放器向前拖拽,請求的數(shù)據(jù)已經(jīng)緩存過才會出現(xiàn))
4.如果當(dāng)前的dataRequest請求的offset小于代理向服務(wù)器請求的range的offset,則取消當(dāng)前與服務(wù)器的請求,并從offset開始到文件尾向服務(wù)器發(fā)起請求(此時應(yīng)該是由于播放器向前拖拽,并且超過了已緩存的數(shù)據(jù)時才會出現(xiàn))
5.只要代理重新向服務(wù)器發(fā)起請求,就會導(dǎo)致緩存的數(shù)據(jù)不連續(xù),則加載結(jié)束后不用將緩存的數(shù)據(jù)放入本地cache
6.如果代理和服務(wù)器的鏈接超時,重試一次,如果還是錯誤則通知播放器網(wǎng)絡(luò)錯誤
7.如果服務(wù)器返回其他錯誤,則代理通知播放器網(wǎng)絡(luò)錯誤
resourceLoader的難點處理
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest
{
[self.pendingRequests addObject:loadingRequest];
[self dealWithLoadingRequest:loadingRequest];
return YES;
}
播放器發(fā)出的數(shù)據(jù)請求從這里開始,我們保存從這里發(fā)出的所有請求存放到數(shù)組,自己來處理這些請求,當(dāng)一個請求完成后,對請求發(fā)出finishLoading消息,并從數(shù)組中移除。正常狀態(tài)下,當(dāng)播放器發(fā)出下一個請求的時候,會把上一個請求給finish。
下面這個方法發(fā)出的請求說明播放器自己關(guān)閉了這個請求,我們不需要再對這個請求進行處理,系統(tǒng)每次結(jié)束一個舊的請求,便必然會發(fā)出一個或多個新的請求,除了播放器已經(jīng)獲得整個視頻完整的數(shù)據(jù),這時候就不會再發(fā)起請求。
- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest
{
[self.pendingRequests removeObject:loadingRequest];
}
下面這個方法是對播放器發(fā)出的請求進行填充數(shù)據(jù)
- (BOOL)respondWithDataForRequest:(AVAssetResourceLoadingDataRequest *)dataRequest
{
long long startOffset = dataRequest.requestedOffset;
if (dataRequest.currentOffset != 0) {
startOffset = dataRequest.currentOffset;
}
if ((self.task.offset +self.task.downLoadingOffset) < startOffset)
{
//NSLog(@"NO DATA FOR REQUEST");
return NO;
}
if (startOffset < self.task.offset) {
return NO;
}
NSData *filedata = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:_videoPath] options:NSDataReadingMappedIfSafe error:nil];
// This is the total data we have from startOffset to whatever has been downloaded so far
NSUInteger unreadBytes = self.task.downLoadingOffset - ((NSInteger)startOffset - self.task.offset);
// Respond with whatever is available if we can't satisfy the request fully yet
NSUInteger numberOfBytesToRespondWith = MIN((NSUInteger)dataRequest.requestedLength, unreadBytes);
[dataRequest respondWithData:[filedata subdataWithRange:NSMakeRange((NSUInteger)startOffset- self.task.offset, (NSUInteger)numberOfBytesToRespondWith)]];
long long endOffset = startOffset + dataRequest.requestedLength;
BOOL didRespondFully = (self.task.offset + self.task.downLoadingOffset) >= endOffset;
return didRespondFully;
}
這是對存放所有的請求的數(shù)組進行處理
- (void)processPendingRequests
{
NSMutableArray *requestsCompleted = [NSMutableArray array]; //請求完成的數(shù)組
//每次下載一塊數(shù)據(jù)都是一次請求,把這些請求放到數(shù)組,遍歷數(shù)組
for (AVAssetResourceLoadingRequest *loadingRequest in self.pendingRequests)
{
[self fillInContentInformation:loadingRequest.contentInformationRequest]; //對每次請求加上長度,文件類型等信息
BOOL didRespondCompletely = [self respondWithDataForRequest:loadingRequest.dataRequest]; //判斷此次請求的數(shù)據(jù)是否處理完全
if (didRespondCompletely) {
[requestsCompleted addObject:loadingRequest]; //如果完整,把此次請求放進 請求完成的數(shù)組
[loadingRequest finishLoading];
}
}
[self.pendingRequests removeObjectsInArray:requestsCompleted]; //在所有請求的數(shù)組中移除已經(jīng)完成的
}
resourceLoader的難點基本上就是上面這點了,說到播放器,下面便順便講下AVPlayer的難點。
難點:對播放器狀態(tài)的捕獲
舉個簡單的例子,視頻總長度60分,現(xiàn)在緩沖的數(shù)據(jù)才10分鐘,然后拖動到20分鐘的位置進行播放,在網(wǎng)速較慢的時候,視頻從當(dāng)前位置開始播放,必然會出現(xiàn)一段時間的卡頓,為了有一個更好的用戶體驗,在卡頓的時候,我們需要加一個菊花轉(zhuǎn)的狀態(tài),現(xiàn)在問題就來了。
在拖動到未緩沖區(qū)域內(nèi),是否需要加菊花轉(zhuǎn),如果加,要顯示多久再消失,而且如果在網(wǎng)速很慢的時候,播放器如果等了太久,哪怕最后有數(shù)據(jù)了,播放器也已經(jīng)“死”了,它自己無法恢復(fù)播放,這個時候需要我們?nèi)藶榈娜セ謴?fù)播放,如果恢復(fù)播放不成功,那么過一段時間需要再次恢復(fù)播放,是否恢復(fù)播放成功,這里也需要捕獲其狀態(tài)。所以,如果要有一個好的用戶體驗,我們需要時時知道播放器的狀態(tài)。
有兩個狀態(tài)需要捕獲,一個是正在緩沖,一個是正在播放,監(jiān)聽播放的“playbackBufferEmpty”屬性就可以捕獲正在緩沖狀態(tài),播放器的時間監(jiān)聽器則可以捕獲正在播放狀態(tài),我的demo中一共有4個狀態(tài):
typedef NS_ENUM(NSInteger, TBPlayerState) {
TBPlayerStateBuffering = 1,
TBPlayerStatePlaying = 2,
TBPlayerStateStopped = 3,
TBPlayerStatePause = 4
};
這樣可以對播放器更好的把握和處理了。
然后說一說在緩沖時候的處理,以及緩沖后多久去播放,處理方法:
進入緩沖狀態(tài)后,緩沖2秒后去手動播放,如果播放不成功(緩沖的數(shù)據(jù)太少,還不足以播放),那就再緩沖2秒再次播放,如此循環(huán),看詳細代碼:
- (void)bufferingSomeSecond
{
// playbackBufferEmpty會反復(fù)進入,因此在bufferingOneSecond延時播放執(zhí)行完之前再調(diào)用bufferingSomeSecond都忽略
static BOOL isBuffering = NO;
if (isBuffering) {
return;
}
isBuffering = YES;
// 需要先暫停一小會之后再播放,否則網(wǎng)絡(luò)狀況不好的時候時間在走,聲音播放不出來
[self.player pause];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 如果此時用戶已經(jīng)暫停了,則不再需要開啟播放了
if (self.isPauseByUser) {
isBuffering = NO;
return;
}
[self.player play];
// 如果執(zhí)行了play還是沒有播放則說明還沒有緩存好,則再次緩存一段時間
isBuffering = NO;
if (!self.currentPlayerItem.isPlaybackLikelyToKeepUp) {
[self bufferingSomeSecond];
}
});
}
利用GPUImage處理直播過程中美顏的流程
采集視頻 => 獲取每一幀圖片 => 濾鏡處理 => GPUImageView展示
- GPU:(Graphic Processor Unit圖形處理單元)手機或者電腦用于圖像處理和渲染的硬件
- GPU工作原理:采集數(shù)據(jù)-> 存入主內(nèi)存(RAM) -> CPU(計算處理) -> 存入顯存(VRAM) -> GPU(完成圖像渲染) -> 幀緩沖區(qū) -> 顯示器
*OpenGL ES:(Open Graphics Library For Embedded(嵌入的) Systems 開源嵌入式系統(tǒng)圖形處理框架),一套圖形與硬件接口,用于把處理好的圖片顯示到屏幕上。 - GPUImage:是一個基于OpenGL ES 2.0圖像和視頻處理的開源iOS框架,提供各種各樣的圖像處理濾鏡,并且支持照相機和攝像機的實時濾鏡,內(nèi)置120多種濾鏡效果,并且能夠自定義圖像濾鏡。
- 濾鏡處理的原理:就是把靜態(tài)圖片或者視頻的每一幀進行圖形變換再顯示出來。它的本質(zhì)就是像素點的坐標(biāo)和顏色變化
GPUImage處理畫面原理
GPUImage采用鏈?zhǔn)椒绞絹硖幚懋嬅?通過addTarget:方法為鏈條添加每個環(huán)節(jié)的對象,處理完一個target,就會把上一個環(huán)節(jié)處理好的圖像數(shù)據(jù)傳遞下一個target去處理,稱為GPUImage處理鏈。
零碎的知識
FLV(Flash Video)是Adobe公司設(shè)計開發(fā)的一種流行的流媒體格式,由于其視頻文件體積輕巧、封裝簡單等特點,使其很適合在互聯(lián)網(wǎng)上進行應(yīng)用。
RTSP:實時流傳輸協(xié)議,是TCP/IP協(xié)議體系中的一個應(yīng)用層協(xié)議;
M4A:.m4a是MPEG-4 音頻標(biāo)準(zhǔn)的文件的擴展名,Apple在iTunes以及 iPod中使用“.m4a”以區(qū)別MPEG4的視頻和音頻文件;
音視頻同步:時間戳,時間戳即為一幀的采集時間,音視頻采取同一個參考時間,給每個幀打上時間戳。
rtmp發(fā)送音視頻:xcode中編譯librtmp庫,遵循rtmp協(xié)議,將數(shù)據(jù)發(fā)送到指定服務(wù)器;
AudioToolbox.framework:提供CoreAudio的中高級別的API服務(wù),處理電話和其他高優(yōu)先級語音處理而導(dǎo)致的中斷和恢復(fù)操作等;
AudioUnit.framework:提供DSP數(shù)字信號處理相關(guān)的插件,包括編解碼,混音,音頻均衡等;
AVFoundation.framework:提供一個精簡的音樂播放類,可以播放所有IOS支持的音頻;
OpenAL.framework:提供3D音效播放;
直播平臺的高并發(fā)架構(gòu)設(shè)計
需求:
用戶那邊提的需求就是推流端不能卡,畫質(zhì)要好,不能太燙
低延時:
低延時的操作大部分來自端的配合,服務(wù)端只要是做好緩存,保證這個數(shù)據(jù)是連貫的。如果要丟數(shù)據(jù)的話,把關(guān)鍵幀保留好,丟GOP中間那些PB幀,主要是在端上會收到。
首屏?xí)r間:
就是用戶點開就要看,以前那些開源架構(gòu)就是rtmp server,它是做不到一點開就能看的,現(xiàn)在一些開源的國內(nèi)資源寫得也比較好了,可以看到。我們是自己開發(fā)的,能保存之前的關(guān)鍵幀的信息,用戶一點開就能看,這個就是很細節(jié)的東西了。如果這個做不好的話,會黑屏、綠屏,或者是半天看不著圖像。
不能卡,不能延遲太高:
要滿足這些需求,我們需要做好多分辨率的適配,保證好流暢性,保證好我們追趕的策略不會出現(xiàn)任何異常。所以這三個端很多是相互耦合的,像推流和分發(fā)在一起,要保障好用戶的流暢性和畫質(zhì),分發(fā)和播放器在一起要保證好低延時和播放的流暢。
一般的流媒體的數(shù)據(jù)走向、各種請求。但是其中有一些坑我可以跟大家重點說一下,首先看一下直播發(fā)起流程,這肯定是由應(yīng)用向自己的服務(wù)端去請求一個推流地址,這個推流地址他就用來向我們的流媒體服務(wù)器推,然后我們給它鑒權(quán)。
鑒權(quán)之后,它可以在參數(shù)里選擇是不是要錄像。如果需要錄像截圖,或者需要HLS的分發(fā),我們都可以幫他做,做完之后存到我們的存儲里,這也是后面會提到的,我們各個業(yè)務(wù)之間在做隔離、分不同的優(yōu)先級,這種后端的多媒體的處理盡量都會依賴別的服務(wù),然后就是正常的結(jié)束流程。
一般互聯(lián)網(wǎng)公司做云服務(wù)都怎么做?都是給回調(diào),如果這個推流結(jié)束了,我來回調(diào)業(yè)務(wù)方,讓業(yè)務(wù)方知道我結(jié)束了,你可以做你的邏輯了。
但實際操作中我們遇到了問題,就是業(yè)務(wù)方的服務(wù)器沒那么可靠,我們可能過去時間特別久,有延時,有丟,或者他們的服務(wù)穩(wěn)定性我們也確認不了,這其實就是一個雙方的耦合了。而且它的服務(wù)器,由于是我們來調(diào),它的鑒權(quán)功能沒有辦法做得很復(fù)雜,他自己的服務(wù)器也存在安全漏洞。如果有人來攻擊他的話,他的整個業(yè)務(wù)流程的狀態(tài)全是亂的。
在試了幾家客戶之后,我們就改成另外一種方式,也是大家普遍都接受的,就是由APP和自己的Server發(fā)心跳,如果APP的網(wǎng)絡(luò)不異常的話,它自己結(jié)束它的Server肯定是知道的。如果異常的話心跳斷了,他也會判斷出是結(jié)束了的。而且我們這邊源站服務(wù)也會保證,你5秒鐘沒有數(shù)據(jù)就一定是結(jié)束的了,我們會把你的流給踢掉,這樣就能達到用戶的業(yè)務(wù)狀態(tài)也是穩(wěn)定的,我們的流媒體服務(wù)也是穩(wěn)定的,而且耦合也會比較少。
這是我們實際遇到的一個坑,這個其實不難,只是看現(xiàn)在普遍云服務(wù)提供商還都是在用回掉的方式,所以我特別提一下另外還有一種可選的方式,效果更好。
播放的流程,播放器會先向他自己的服務(wù)請求播放地址,然后來我們這拉流,可以是鑒權(quán)也可以不鑒權(quán),取決于它的業(yè)務(wù)形態(tài)。如果拉流失敗,我們有一些定制化的操作,他用RTMP來拉流的話,我們會告訴他具體是什么錯,包括鑒權(quán)失效,鑒權(quán)參數(shù)錯誤,還是這個流有問題,我們都會在狀態(tài)告訴他的。這是之前用戶提到的需求,說是播放需要知道哪里出了問題,所以我們盡量把狀態(tài)碼都特別詳細的返回給用戶。包括我們原站也有查詢接口,如果他需要那種統(tǒng)一查詢也可以來查。
1、推流端實現(xiàn)方案

推流端設(shè)計的原則總結(jié)下來就是自適應(yīng),推流誰都可以做,開源的也很多。但是為什么有的做得好,有的做得不好呢?就是看自適應(yīng)做的好不好。
總結(jié)下來有三點自適應(yīng):
第一是幀率和碼率自適應(yīng),這是大家都能想到的。我推流,如果網(wǎng)絡(luò)卡了,我就降點幀率或者降一點碼率,把這個事情做好,把流能正常推上去,不要卡頓。也是這張圖里畫到的,在發(fā)送網(wǎng)絡(luò)的時候,我們做了一個QS模塊,我們團隊除了做工程化的人之外,還會有四五個博士專門做算法的。
在這里就有一些體現(xiàn),我們在碼率自適應(yīng)的時候,是直接可以回饋給編碼器的,讓編碼器動態(tài)調(diào)整自己的碼率,盡量保證質(zhì)量無損,傳出來的視頻碼率下降,視頻平滑。幀率的控制就比較簡單了,當(dāng)我們發(fā)現(xiàn)網(wǎng)絡(luò)卡頓了,我們就會反饋給幀率控制模塊。
在采集的時候做一些丟棄的操作,目的就是把我們發(fā)送的帶寬降下來。這個我們是基于TCP做的,肯定沒有UDP的效果好,UDP是我們下一步的嘗試,現(xiàn)在還沒有開始。因為UDP還涉及到源站的一些架構(gòu)重構(gòu),我們還沒有來得及做,現(xiàn)在基于TCP的效果其實已經(jīng)不錯了。后面除了這種簡單的自適應(yīng)之外,我們還加了一個算法類的,那個效果就會更明顯。
第二種自適應(yīng)是軟硬自適應(yīng),這個很好理解,像硬件編碼的優(yōu)點就是手機不燙,缺點一大堆,用MediaRecorder的話,音視頻很難同步,用MediaCodec的話,版本兼容有問題,現(xiàn)在還不太好普及。用軟編的話碼率低,畫質(zhì)好,除了CPU特別燙,別的都是優(yōu)點。
熱門機型有一些低端的,軟編受不了的就改成硬編。因為硬編是體力工作,所以適配的機型肯定是有限的
第三個自適應(yīng),算法自適應(yīng)。我們是真正的第一家能夠把h.265做成商業(yè)化的公司?,F(xiàn)在所有的都在提h.265,不知道大家對h.265了不了解,有沒有人聽說過h.265可以商業(yè)化在Web端無插件播放?我們現(xiàn)在做到了在賽揚機器上可以播30FPS的720P視頻,在瀏覽器上不用裝任何插件,這是我們持續(xù)優(yōu)化的結(jié)果。當(dāng)然這個不適合移動的場景,是我們在接另外一個場景的時候用到的。
在移動端我們做到了IOS手機720P編碼,做到15FPS,然后CPU不會打滿,可能是50%到70%之間。之前數(shù)據(jù)是打滿一個核。這是因為我們之前有很多做算法的團隊,最開始是做技術(shù)授權(quán),后來想在一些產(chǎn)品上落地,移動直播其實是h.265的一個很好的落地的場景,為什么這么說呢?
推流端的任務(wù)是把更好的畫質(zhì)推上來,網(wǎng)絡(luò)有限的情況下,我怎么能推上來更好的畫質(zhì)?h.265相對h.264來說能把帶寬省掉30%。30%的概念是在視頻點播類的應(yīng)用里能省點錢,在初創(chuàng)應(yīng)用來說根本就不在乎,因為主播更貴,誰在乎這樣30%的帶寬。
但是在移動推流就不一樣了,30%是從480P到720P的變化,就是你本來只能推480P上來的畫質(zhì),經(jīng)過h.265這種編碼之后能推上來720P的,主播的需求就是網(wǎng)絡(luò)夠好,CPU夠好,我為什么不推更好的視頻上去呢?這就是h.265的一個場景,我用算法的優(yōu)勢,你的機器只要能夠讓我做到用265來自適應(yīng),我就可以推上去更好的畫質(zhì)。
2、分發(fā)網(wǎng)絡(luò)-多集群源站設(shè)計

分發(fā)網(wǎng)絡(luò)是躲在很遠的一個地方了,我們當(dāng)時設(shè)計的三個原則就是高并發(fā)、高可用、系統(tǒng)解耦,前兩個很虛了,只要是做系統(tǒng)都會想怎么高并發(fā),怎么高可用,怎么橫向擴展最容易。
我們做了一個多源站,相對于很多公司在做單源站的方式,我們就是為了讓用戶能更好的觸達我們的網(wǎng)絡(luò)。在各個集群、各個城市做了多源站,這樣怎么能做到橫向的擴容和數(shù)據(jù)與業(yè)務(wù)中心的隔離,這種方案并不是很難,用一些存儲做好同步其實也做到了。
一些開源服務(wù),也做多分辨率適配,但是它所有的轉(zhuǎn)碼調(diào)度都是由它的流媒體服務(wù)來調(diào)起的。包括轉(zhuǎn)碼的生命周期也是流媒體服務(wù)來控制的,他們都在同級部署。其實這是有很大問題的,多分辨率適配和原畫的推送和分發(fā)完全不是一個優(yōu)先級的服務(wù)。做系統(tǒng)定級的時候就應(yīng)該把它們分離開,應(yīng)該分離在不同的系統(tǒng)來做。
在線轉(zhuǎn)碼是一個非常耗CPU的業(yè)務(wù)。一臺現(xiàn)在很高端配置的24核機器,如果我想轉(zhuǎn)一些畫質(zhì)比較好的視頻,每個視頻轉(zhuǎn)三個分辨率,這樣我轉(zhuǎn)八路就把它打滿了,這是很耗CPU的。如果我轉(zhuǎn)了沒人看,這個CPU就在那耗著,而且這個是不適合和源站混部的一個服務(wù)。
轉(zhuǎn)碼要和數(shù)據(jù)離的近,在那個源站集群的同一機房,我們會申請一些轉(zhuǎn)碼的資源,然后由核心機房來統(tǒng)一調(diào)度。我們把調(diào)度和具體的功能分離開,根據(jù)你這個流推到哪,我們就就近在哪里轉(zhuǎn)碼。轉(zhuǎn)碼也加了一些實時轉(zhuǎn)碼的策略。
為什么要做在線轉(zhuǎn)碼?因為推流端已經(jīng)是盡最大努力把最好的畫質(zhì)、最高的帶寬傳上來。但是播放端不一定看得了,這樣我們就需要把它轉(zhuǎn)出來,而且h.265雖然好,但是有個最大的問題就是在移動端的瀏覽器上沒有辦法播。分享出來的必須是h.264,要不然去微信或者是QQ瀏覽器,你是看不了的。
我們做了兩種策略,一種是有限的機器合理調(diào)度。我們的轉(zhuǎn)碼系統(tǒng)是個分布式,流水線式的,類似Storm那種系統(tǒng),但是我們自己做得更適合轉(zhuǎn)碼。任務(wù)進來之后,我們第一個流程不是轉(zhuǎn),而是分析,看看你是要轉(zhuǎn)成什么樣,你是什么畫質(zhì),大概會用什么CPU。
如果你的CPU占用很多,我會認為這是一個很難再次被調(diào)度的服務(wù),比如你一下進來一個占四個核的轉(zhuǎn)碼服務(wù),后來再來一堆占一個核的,肯定是一個核的比較好調(diào)度,這個機器資源緊張了,我可以給你調(diào)度另外一臺機器,或者另外一臺機器本來就有些空余,現(xiàn)在剩三個核,我接不了四個核的,我只能先接一個核的,所以我們會按優(yōu)先級,優(yōu)先分配高CPU占用的任務(wù),然后才是低CPU占用的任務(wù),在流式系統(tǒng)里,會在預(yù)分析之后把不同的任務(wù)扔進不同的優(yōu)先級隊列,這個優(yōu)先級隊列就承擔(dān)著去轉(zhuǎn)不同分辨率視頻的職能。
而且在后頭如果需要降級容災(zāi)的話,也是靠這個優(yōu)先級隊列來解決的,每個用戶會有配額。我剛才說24和準(zhǔn)24路,其實對于一個云服務(wù)公司來說這個量太小了。像我之前在百度做媒體云的時候,每天轉(zhuǎn)碼量是有30萬,我覺得一個業(yè)務(wù)做大了,一天30萬的轉(zhuǎn)碼量是很正常的。
這是考驗并發(fā)的一個項目,怎么能做到盡量的把CPU打平,因為波峰波谷很明顯。像h.265這個場景,我們是做了一套實時轉(zhuǎn)碼,有人分享就立刻給你轉(zhuǎn),讓用戶一旦開始分享的時候,能達到秒開的作用。但是你不看的時候,我們會有策略盡快幫你停下來。因為這種分享出去的視頻并不是一個高并發(fā)的業(yè)務(wù),有人看我們才給他轉(zhuǎn)是個比較合理的場景。
對于那些低分辨率的現(xiàn)在也在逐步上灰度,不是說所有的你分發(fā)了,你發(fā)起了,我都給你轉(zhuǎn),我們會逐漸判斷,有人看我們才轉(zhuǎn),盡量節(jié)省系統(tǒng)資源。后面也會考慮存儲資源,因為每個機房都會有存儲,存儲是完全不用CPU的,它保證的是磁盤和IO,和我們完全是資源不復(fù)用的,是可以混部的,后面我們會考慮一步一步的混部。
CDN的分發(fā)環(huán)節(jié),分發(fā)環(huán)節(jié)其實有很多東西是需要播放來配合的,比如說現(xiàn)在推流為了保證畫質(zhì)好,我會增加B幀,加大GOP,這樣編碼出來的視頻質(zhì)量會變好,代價就是我增加了GOP,那我的延遲就會大,用戶一定是從上一個關(guān)鍵幀開始看,這樣他看到的可能就是5秒甚至是10秒之前的視頻,這個對社交類的移動直播是不可忍受的。既然有這種需求,源站就需要把之前的都保存好。但是怎么能讓延時被消化掉,就要靠播放端。
3、播放器端實現(xiàn)方案

這是播放端的實現(xiàn)框圖,中間少畫了一個地方。這就是個傳統(tǒng)的播放器框圖,沒有體現(xiàn)出我們的核心的技術(shù)點,數(shù)據(jù)從網(wǎng)絡(luò)接收進來之后,經(jīng)過RTMP的Demux之后,我們是有一個模塊的,這個模塊會去判斷當(dāng)前視頻是否需要被丟棄,這個原則也和我們接收緩存有關(guān)系,我們緩存配的是兩秒,如果超過兩秒,或者超過某一個其他的閾值的話,我們會開啟丟棄的模式。
這個丟棄有多種策略,有的是直接丟掉幀,有的是快進。如果做過播放器就會知道,傳統(tǒng)的視頻追趕一般都是在視頻解碼之后來做追趕。解碼就意味著會耗CPU,尤其是現(xiàn)在如果我想播720的視頻,光是解碼就基本上勉強實時的話,根本就沒有什么追趕的余地了。
所以我們在算法上做了一些優(yōu)化,我們拿到這個視頻的時候會先去判斷它是不是一個可以丟的,如果它是可以丟的,在解碼之前我們就丟,但是這樣丟會出問題,因為解碼器會內(nèi)部不連續(xù),一旦解碼器內(nèi)部不連續(xù)了,它可能會產(chǎn)生黑屏,所以我們即使要丟,也是在解碼器里邊做了一些定制化的開發(fā),還是把要丟的視頻傳進去,讓它自己來丟,它不去解,這樣就能達到更快速的把這個視頻丟掉,趕上現(xiàn)在的實際主播的進度。
這樣的話,如果我們網(wǎng)絡(luò)狀況很好,不擔(dān)心以后抖動的話,我們能做到從推流到觀看是2秒的延遲,但是一般我們都控制在4秒,就是為了防止抖動產(chǎn)生。
剛才說的是丟的這種邏輯,如果想快進,類似斗魚那種,在一點進去之后,開始畫面是很快過去的,但是沒有音頻,我們現(xiàn)在在做有音頻的方式,視頻在快進,音頻也在快進,這樣的話聲音會變調(diào),因為采樣率變了。以前在做端的經(jīng)驗的時候,也做過這種變速不變調(diào)的算法,很多開源的,改改其實效果都能不錯,這種算法只要做好逆向優(yōu)化,放進來之后,音頻也能保證不變調(diào)。