iOS RTMP 推流

推流,就是將采集到的音頻,視頻數(shù)據(jù)通過流媒體協(xié)議發(fā)送到流媒體服務(wù)器。

推流前的工作:采集,處理,編碼壓縮

推流中做的工作: 封裝,上傳


推流前的工作:采集,處理,編碼壓縮

推流中做的工作: 封裝,上傳

推流前的工作

推流——采集到的音頻,視頻數(shù)據(jù)通過流媒體協(xié)議發(fā)送到流媒體服務(wù)器

話說回來,其實(shí)有一個庫LFLiveKit已經(jīng)實(shí)現(xiàn)了 后臺錄制、美顏功能、支持h264、AAC硬編碼,動態(tài)改變速率,RTMP傳輸?shù)?,我們真正開發(fā)的時候直接使用就很方便啦。

另外也有:

LiveVideoCoreSDK: 實(shí)現(xiàn)了美顏直播和濾鏡功能,我們只要填寫RTMP服務(wù)地址,直接就可以進(jìn)行推流啦。

PLCameraStreamingKit: 也是一個不錯的 RTMP 直播推流 SDK。

但還是推薦用LFLiveKit,而為了進(jìn)一步了解推流這個過程,先按自己的步子試著走走,了解下。

一、采集視頻

采集硬件(攝像頭)視頻圖像

#import"MovieViewController.h"#import@interfaceMovieViewController()@property(nonatomic,strong)AVCaptureSession*session;@property(nonatomic,strong)AVCaptureVideoDataOutput*videoOutput;@property(nonatomic,strong)AVCaptureAudioDataOutput*audioOutput;@property(nonatomic,strong)dispatch_queue_tvideoQueue;@property(nonatomic,strong)dispatch_queue_taudioQueue;@property(nonatomic,strong)AVCaptureConnection*videoConnection;@property(nonatomic,strong)AVCaptureConnection*audioConnection;@property(nonatomic,strong)AVCaptureVideoPreviewLayer*previewLayer;@end@implementationMovieViewController- (void)viewDidLoad {? ? [superviewDidLoad];? ? [selfinitSession];? ? [selfshowPlayer];}- (void)viewWillAppear:(BOOL)animated {? ? [superviewWillAppear:animated];? ? [self.session startRunning];}- (void)viewDidDisappear:(BOOL)animated {? ? [self.session stopRunning];}- (void)initSession {// 初始化 session_session = [[AVCaptureSessionalloc] init];// 配置采集輸入源(攝像頭)NSError*error =nil;// 獲得一個采集設(shè)備, 默認(rèn)后置攝像頭AVCaptureDevice*videoDevice = [AVCaptureDevicedefaultDeviceWithMediaType:AVMediaTypeVideo];AVCaptureDevice*audioDevice = [AVCaptureDevicedefaultDeviceWithMediaType:AVMediaTypeAudio];// 用設(shè)備初始化一個采集的輸入對象AVCaptureDeviceInput*videoInput = [AVCaptureDeviceInputdeviceInputWithDevice:videoDevice error:&error];AVCaptureDeviceInput*audioInput = [AVCaptureDeviceInputdeviceInputWithDevice:audioDevice error:&error];if(error) {NSLog(@"Error getting? input device: %@", error.description);return;? ? }if([_session canAddInput:videoInput]) {? ? ? ? [_session addInput:videoInput];// 添加到Session}if([_session canAddInput:audioInput]) {? ? ? ? [_session addInput:audioInput];// 添加到Session}// 配置采集輸出,即我們?nèi)〉靡曨l圖像的接口_videoQueue = dispatch_queue_create("Video Capture Queue", DISPATCH_QUEUE_SERIAL);? ? _audioQueue = dispatch_queue_create("Audio Capture Queue", DISPATCH_QUEUE_SERIAL);? ? _videoOutput = [[AVCaptureVideoDataOutputalloc] init];? ? _audioOutput = [[AVCaptureAudioDataOutputalloc] init];? ? [_videoOutput setSampleBufferDelegate:selfqueue:_videoQueue];? ? [_audioOutput setSampleBufferDelegate:selfqueue:_audioQueue];// 配置輸出視頻圖像格式NSDictionary*captureSettings = @{(NSString*)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA)};? ? _videoOutput.videoSettings = captureSettings;? ? _videoOutput.alwaysDiscardsLateVideoFrames =YES;if([_session canAddOutput:_videoOutput]) {? ? ? [_session addOutput:_videoOutput];// 添加到Session}if([_session canAddOutput:_audioOutput]) {? ? ? ? [_session addOutput:_audioOutput];// 添加到Session}// 保存Connection,用于在SampleBufferDelegate中判斷數(shù)據(jù)來源(Video/Audio)_videoConnection = [_videoOutput connectionWithMediaType:AVMediaTypeVideo];? ? _audioConnection = [_audioOutput connectionWithMediaType:AVMediaTypeAudio];}- (void)showPlayer {? ? _previewLayer = [AVCaptureVideoPreviewLayerlayerWithSession:_session];? ? _previewLayer.videoGravity =AVLayerVideoGravityResizeAspectFill;// 設(shè)置預(yù)覽時的視頻縮放方式[[_previewLayer connection] setVideoOrientation:AVCaptureVideoOrientationPortrait];// 設(shè)置視頻的朝向_previewLayer.frame =self.view.layer.bounds;? ? [self.view.layer addSublayer:_previewLayer];}#pragma mark 獲取 AVCapture Delegate- (void)captureOutput:(AVCaptureOutput*)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection*)connection {// 這里的sampleBuffer就是采集到的數(shù)據(jù)了,根據(jù)connection來判斷,是Video還是Audio的數(shù)據(jù)if(connection ==self.videoConnection) {// VideoNSLog(@"這里獲的 video sampleBuffer,做進(jìn)一步處理(編碼H.264)");? ? }elseif(connection ==self.audioConnection) {// AudioNSLog(@"這里獲得 audio sampleBuffer,做進(jìn)一步處理(編碼AAC)");? ? }}@end

上述是大致實(shí)現(xiàn)獲取最基本數(shù)據(jù)的情況,一些細(xì)節(jié)(尺寸、方向)暫時沒有深入,真正做直播的時候,一般是視頻和音頻是分開處理的,只有重點(diǎn)注意那個代理方法。

二、GPUImage 處理

在進(jìn)行編碼 H.264 之前,一般來說肯定會做一些美顏處理的,否則那播出的感覺太真實(shí),就有點(diǎn)丑啦,在此以磨皮和美白為例簡單了解。(具體參考的是:琨君基于 GPUImage 的實(shí)時美顏濾鏡

直接用BeautifyFaceDemo中的類GPUImageBeautifyFilter, 可以對的圖片直接進(jìn)行處理:

GPUImageBeautifyFilter *filter = [[GPUImageBeautifyFilter alloc] init];UIImage *image = [UIImage imageNamed:@"testMan"];UIImage *resultImage = [filter imageByFilteringImage:image];self.backgroundView.image= resultImage;

備注下 CMSampleBufferRef 與 UIImage 的轉(zhuǎn)換

- (UIImage *)sampleBufferToImage:(CMSampleBufferRef)sampleBuffer {//制作 CVImageBufferRefCVImageBufferRefbuffer;buffer= CMSampleBufferGetImageBuffer(sampleBuffer);? ? CVPixelBufferLockBaseAddress(buffer,0);//從 CVImageBufferRef 取得影像的細(xì)部信息uint8_t *base;? ? size_twidth,height, bytesPerRow;? ? base = CVPixelBufferGetBaseAddress(buffer);width= CVPixelBufferGetWidth(buffer);height= CVPixelBufferGetHeight(buffer);? ? bytesPerRow = CVPixelBufferGetBytesPerRow(buffer);//利用取得影像細(xì)部信息格式化 CGContextRefCGColorSpaceRef colorSpace;? ? CGContextRef cgContext;? ? colorSpace = CGColorSpaceCreateDeviceRGB();? ? cgContext = CGBitmapContextCreate(base,width,height,8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);? ? CGColorSpaceRelease(colorSpace);//透過 CGImageRef 將 CGContextRef 轉(zhuǎn)換成 UIImageCGImageRef cgImage;? ? UIImage *image;? ? cgImage = CGBitmapContextCreateImage(cgContext);image= [UIImage imageWithCGImage:cgImage];? ? CGImageRelease(cgImage);? ? CGContextRelease(cgContext);? ? CVPixelBufferUnlockBaseAddress(buffer,0);returnimage;}

但是視頻中是怎樣進(jìn)行美容處理呢?怎樣將其轉(zhuǎn)換的呢?平常我們這樣直接使用:

GPUImageBeautifyFilter*beautifyFilter= [[GPUImageBeautifyFilter alloc] init];[self.videoCameraaddTarget:beautifyFilter];[beautifyFilteraddTarget:self.gpuImageView];

此處用到了GPUImageVideoCamera,可以大致了解下GPUImage詳細(xì)解析(三)- 實(shí)時美顏濾鏡

GPUImageVideoCamera: GPUImageOutput的子類,提供來自攝像頭的圖像數(shù)據(jù)作為源數(shù)據(jù),一般是響應(yīng)鏈的源頭。

GPUImageView:響應(yīng)鏈的終點(diǎn),一般用于顯示GPUImage的圖像。

GPUImageFilter:用來接收源圖像,通過自定義的頂點(diǎn)、片元著色器來渲染新的圖像,并在繪制完成后通知響應(yīng)鏈的下一個對象。

GPUImageFilterGroup:多個GPUImageFilter的集合。

GPUImageBeautifyFilter

@interface GPUImageBeautifyFilter : GPUImageFilterGroup {? GPUImageBilateralFilter *bilateralFilter;GPUImageCannyEdgeDetectionFilter *cannyEdgeFilter;GPUImageCombinationFilter *combinationFilter;GPUImageHSBFilter *hsbFilter;}

簡單理解這個美顏的流程

不得不說GPUImage 是相當(dāng)強(qiáng)大的,此處的功能也只是顯現(xiàn)了一小部分,其中 filter 那塊的處理個人目前還有好多不理解,需要去深入了解啃源碼,暫時不過多引入。通過這個過程將 sampleBuffer 美容處理后,自然是進(jìn)行編碼啦。

三、視頻、音頻壓縮編碼

而編碼是用硬編碼呢 還是軟編碼呢? 相同碼率,軟編圖像質(zhì)量更清晰,但是耗電更高,而且會導(dǎo)致CPU過熱燙到攝像頭。不過硬編碼會涉及到其他平臺的解碼,有很多坑。綜合來說,iOS 端硬件兼容性較好,iOS 8.0占有率也已經(jīng)很高了,可以直接采用硬編。

硬編碼:下面幾個DEMO 可以對比下,當(dāng)然看LFLiveKit更直接。

VideoToolboxPlus

iOSHardwareDecoder

-VideoToolboxDemo

iOS-h264Hw-Toolbox

軟編碼:利用FFmpeg+x264將iOS攝像頭實(shí)時視頻流編碼為h264文件,備忘下:FFmpeg-X264-Encode-for-iOS

我直接使用了LFLiveKit,里面已經(jīng)封裝的很好啦,此處對? Audiotoolbox? && VideoToolbox 簡單了解下:

AudioToolbox

iOS使用AudioToolbox中的AudioConverter API 把源格式轉(zhuǎn)換成目標(biāo)格式, 詳細(xì)可以看使用iOS自帶AAC編碼器。

//1、根據(jù)輸入樣本初始化一個編碼轉(zhuǎn)換器AudioStreamBasicDescription 根據(jù)指定的源格式和目標(biāo)格式創(chuàng)建 audio converter//2、初始化一個輸出緩沖列表outBufferList //3、獲取 AudioCallBackOSStatus inputDataProc(AudioConverterRefinConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData,AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) //4、音頻格式完成轉(zhuǎn)換AudioConverterFillComplexBuffer 實(shí)現(xiàn)inBufferList 和outBufferList、inputDataProc音頻格式之間的轉(zhuǎn)換。

VideoToolbox

iOS8之后的硬解碼、硬編碼API,此處只做編碼用。

// 1、初始化 VTCompressionSessionRef- (void)initCompressionSession;// 2、傳入? 解碼一個frameVTCompressionSessionEncodeFrame(compressionSession, pixelBuffer, presentationTimeStamp, duration, (__bridgeCFDictionaryRef)properties, (__bridge_retainedvoid*)timeNumber, &flags);// 3、回調(diào),處理 取得PPS和SPSstaticvoidVideoCompressonOutputCallback(void*VTref,void*VTFrameRef, OSStatus status, VTEncodeInfoFlags infoFlags,CMSampleBufferRefsampleBuffer)// 4、完成編碼,然后銷毀sessionVTCompressionSessionCompleteFrames(compressionSession, kCMTimeInvalid);VTCompressionSessionInvalidate(compressionSession);CFRelease(compressionSession);compressionSession =NULL;

四、推流

封裝數(shù)據(jù)成 FLV,通過 RTMP 協(xié)議打包上傳,從主播端到服務(wù)端即基本完成推流。

4-1、封裝數(shù)據(jù)通常是封裝成 FLV

FLV流媒體格式是一種新的視頻格式,全稱為FlashVideo。由于它形成的文件極小、加載速度極快,使得網(wǎng)絡(luò)觀看視頻文件成為可能,它的出現(xiàn)有效地解決了視頻文件導(dǎo)入Flash后,使導(dǎo)出的SWF文件體積龐大,不能在網(wǎng)絡(luò)上很好的使用等缺點(diǎn)。(What)

格式: 源自(封包 FLV

一般FLV文件結(jié)構(gòu)里是這樣存放的:[[Flv Header][Metainfo Tag][Video Tag][Audio Tag][Video Tag][Audio Tag][Other Tag]…]其中AudioTag和VideoTag出現(xiàn)的順序隨機(jī)的,沒有嚴(yán)格的定義。FlvHeader是文件的頭部,用FLV字符串標(biāo)明了文件的類型,以及是否有音頻、視頻等信息。之后會有幾個字節(jié)告訴接下來的包字節(jié)數(shù)。Metainfo中用來描述Flv中的各種參數(shù)信息,例如視頻的編碼格式、分辨率、采樣率等等。如果是本地文件(非實(shí)時直播流),還會有偏移時間戳之類的信息用于支持快進(jìn)等操作。VideoTag存放視頻數(shù)據(jù)。對于H.264來說,第一幀發(fā)送的NALU應(yīng)為SPS和PPS,這個相當(dāng)于H.264的文件頭部,播放器解碼流必須先要找到這個才能進(jìn)行播放。之后的數(shù)據(jù)為I幀或P幀。AudioTag存放音頻數(shù)據(jù)。對于AAC來說,我們只需要在每次硬編碼完成后給數(shù)據(jù)加上adts頭部信息即可。

iOS 中的使用:詳細(xì)看看 LFLiveKit 中的 LFStreamRTMPSocket 類。

4-2、RTMP

從推流端到服務(wù)端,數(shù)據(jù)經(jīng)過處理后,最常用的協(xié)議是RTMP(Real Time Messaging Protocol,實(shí)時消息傳送協(xié)議)。

RTMP的傳輸延遲通常在1-3秒,符合手機(jī)直播對性能的要求,因此RTMP是手機(jī)直播中最常見的傳輸協(xié)議。但是網(wǎng)絡(luò)延遲和阻塞等問題的一直存在的,所以通過Quality of Servic一種網(wǎng)絡(luò)機(jī)制將流數(shù)據(jù)推送到網(wǎng)絡(luò)端,通過CDN分發(fā)是必要的。另外,服務(wù)端還需要對數(shù)據(jù)流一定的處理,轉(zhuǎn)碼,使得數(shù)據(jù)流支持HLS,HTTP-FLV,RTMP等格式的拉流,支持一轉(zhuǎn)多,適配不同網(wǎng)絡(luò)、分辨率的終端。(當(dāng)然這就是服務(wù)端要做的事情啦)

可以用LFLiveKit直接嘗試,或者也可以看看LMLiveStreaming,當(dāng)然此處先用一個本地視頻推送嘗試一下。

4-3、本地模擬推流

此處是跟著快速集成iOS基于RTMP的視頻推流來實(shí)現(xiàn)的,否則就連基本的展示都不能啦啊。此處也可以配合著Mac搭建nginx+rtmp服務(wù)器來安裝,安裝好 nginx 之后,安裝ffmpeg、下載VLC就可以直接開始啦

起初在用 ffmpeg 的時候,遇到下面那個錯:

一個輸入的錯

后來發(fā)現(xiàn)是自己輸入錯了,還是要仔細(xì):

視頻文件地址:/Users/qiu/Desktop/kobe.mp4(自己的一個測試視頻)

推流拉流地址:rtmp://localhost:1935/rtmplive/room

~ ffmpeg -re -i/Users/qiu/Desktop/kobe -vcodec libx264 -acodec aac -strict -2-f flv rtmp://localhost:1935/rtmplive/room

那個-vcodec libx264 -acodec aac -strict -2 -f flv命令也不要寫錯了,ffmpeg 命令可參考FFmpeg常用基本命令

kobeAndOneal.gif

4-4、手機(jī)直播 - VLC上 顯示

為了更好的感受下,我們可以直接 用LMLiveStreaming,然后打開 VLC 中 的 file -- Open Network, 直接輸入代碼中的 url:

代碼中的這個地址

然后我們電腦端就可以顯示啦

Live.gif

而目前有延遲2秒的情況,話說這是正常的。但如何優(yōu)化呢?不知道,如有朋友有好建議歡迎告之。備注下:直播中累積延時的優(yōu)化。

總結(jié)

PS:上面?zhèn)鬏斨皇峭屏鞫说椒?wù)端的模擬過程,然而傳輸一般是包括系統(tǒng)的多個部分,連接推流端,服務(wù)端,播放端等多個部分。而 iOS 這塊播放端直接用ijkplayer, 像上一個筆記——直播初探, 就很快實(shí)現(xiàn)了拉流的過程,當(dāng)然也是ijkplayer過于強(qiáng)大的原因咯。

下面宏觀上了解下整個傳輸過程:

整體傳輸流程

PS: 另外其實(shí)好多第三方的集成也很好用,可參考

七牛云

騰訊的直播 LVB

網(wǎng)易云信 SDK

趣拍云

總的說來,這又是一個粗略的過程,站在好多個巨人的肩膀上,但是還是基本了解了一個推流的流程,沒有正式項(xiàng)目的經(jīng)驗(yàn),肯定有太很多細(xì)節(jié)點(diǎn)忽略了和好多坑需要填,還是那個目的,暫時先作為自己的預(yù)備知識點(diǎn)吧,不過此處可以擴(kuò)展和深入的知識點(diǎn)真的太多啦,如LFLiveKitGPUImage僅僅展露的是冰山一角。

備注參考:

LiveVideoCoreSDK

LFLiveKit

GPUImage

LMLiveStreaming

PLCameraStreamingKit

iOS手機(jī)直播Demo技術(shù)簡介

iOS視頻開發(fā)經(jīng)驗(yàn)

iOS 上的相機(jī)捕捉

CMSampleBufferRef 與 UIImage 的轉(zhuǎn)換

GPUImage詳細(xì)解析(三)- 實(shí)時美顏濾鏡

iOS8系統(tǒng)H264視頻硬件編解碼說明

利用FFmpeg+x264將iOS攝像頭實(shí)時視頻流編碼為h264文件

使用VideoToolbox硬編碼H.264

使用iOS自帶AAC編碼器

如何搭建一個完整的視頻直播系統(tǒng)?

直播中累積延時的優(yōu)化

使用VLC做流媒體服務(wù)器(直播形式)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容