*推流束世,就是將采集到的音頻,視頻數(shù)據(jù)通過(guò)流媒體協(xié)議發(fā)送到流媒體服務(wù)器床玻。
*推流前的工作:采集毁涉,處理,編碼壓縮
*推流中做的工作: 封裝锈死,上傳
話說(shuō)回來(lái), 其實(shí)有一個(gè)庫(kù) LFLiveKit 已經(jīng)實(shí)現(xiàn)了 后臺(tái)錄制待牵、美顏功能严嗜、支持h264、AAC硬編碼洲敢,動(dòng)態(tài)改變速率,RTMP傳輸?shù)惹羊牵覀冋嬲_(kāi)發(fā)的時(shí)候直接使用就很方便啦压彭。
另外也有:
LiveVideoCoreSDK : 實(shí)現(xiàn)了美顏直播和濾鏡功能,我們只要填寫(xiě)RTMP服務(wù)地址渗常,直接就可以進(jìn)行推流啦壮不。
PLCameraStreamingKit: 也是一個(gè)不錯(cuò)的 RTMP 直播推流 SDK。
但還是推薦用 LFLiveKit皱碘,而為了進(jìn)一步了解推流這個(gè)過(guò)程询一,先按自己的步子試著走走,了解下癌椿。
一健蕊、采集視頻
采集硬件(攝像頭)視頻圖像
#import "MovieViewController.h"
#import <AVFoundation/AVFoundation.h>
@interface MovieViewController ()<AVCaptureVideoDataOutputSampleBufferDelegate,AVCaptureAudioDataOutputSampleBufferDelegate>
@property (nonatomic, strong) AVCaptureSession *session;
@property (nonatomic, strong) AVCaptureVideoDataOutput *videoOutput;
@property (nonatomic, strong) AVCaptureAudioDataOutput *audioOutput;
@property (nonatomic, strong) dispatch_queue_t videoQueue;
@property (nonatomic, strong) dispatch_queue_t audioQueue;
@property (nonatomic, strong) AVCaptureConnection *videoConnection;
@property (nonatomic, strong) AVCaptureConnection *audioConnection;
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer;
@end
@implementation MovieViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self initSession];
[self showPlayer];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.session startRunning];
}
- (void)viewDidDisappear:(BOOL)animated {
[self.session stopRunning];
}
- (void)initSession {
// 初始化 session
_session = [[AVCaptureSession alloc] init];
// 配置采集輸入源(攝像頭)
NSError *error = nil;
// 獲得一個(gè)采集設(shè)備, 默認(rèn)后置攝像頭
AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];
// 用設(shè)備初始化一個(gè)采集的輸入對(duì)象
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
AVCaptureDeviceInput *audioInput = [AVCaptureDeviceInput deviceInputWithDevice: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 = [[AVCaptureVideoDataOutput alloc] init];
_audioOutput = [[AVCaptureAudioDataOutput alloc] init];
[_videoOutput setSampleBufferDelegate:self queue:_videoQueue];
[_audioOutput setSampleBufferDelegate:self queue:_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ù)來(lái)源(Video/Audio)
_videoConnection = [_videoOutput connectionWithMediaType:AVMediaTypeVideo];
_audioConnection = [_audioOutput connectionWithMediaType:AVMediaTypeAudio];
}
- (void)showPlayer {
_previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session];
_previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; // 設(shè)置預(yù)覽時(shí)的視頻縮放方式
[[_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來(lái)判斷缩功,是Video還是Audio的數(shù)據(jù)
if (connection == self.videoConnection) { // Video
NSLog(@"這里獲的 video sampleBuffer,做進(jìn)一步處理(編碼H.264)");
} else if (connection == self.audioConnection) { // Audio
NSLog(@"這里獲得 audio sampleBuffer都办,做進(jìn)一步處理(編碼AAC)");
}
}
@end
上述是大致實(shí)現(xiàn)獲取最基本數(shù)據(jù)的情況嫡锌,一些細(xì)節(jié)(尺寸、方向)暫時(shí)沒(méi)有深入琳钉,真正做直播的時(shí)候势木,一般是視頻和音頻是分開(kāi)處理的,只有重點(diǎn)注意那個(gè)代理方法歌懒。
二啦桌、GPUImage 處理
在進(jìn)行編碼 H.264 之前,一般來(lái)說(shuō)肯定會(huì)做一些美顏處理的歼培,否則那播出的感覺(jué)太真實(shí)震蒋,就有點(diǎn)丑啦茸塞,在此以磨皮和美白為例簡(jiǎn)單了解。(具體參考的是:琨君 基于 GPUImage 的實(shí)時(shí)美顏濾鏡)
直接用 BeautifyFaceDemo 中的類GPUImageBeautifyFilter
, 可以對(duì)的圖片直接進(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 {
//制作 CVImageBufferRef
CVImageBufferRef buffer;
buffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(buffer, 0);
//從 CVImageBufferRef 取得影像的細(xì)部信息
uint8_t *base;
size_t width, height, bytesPerRow;
base = CVPixelBufferGetBaseAddress(buffer);
width = CVPixelBufferGetWidth(buffer);
height = CVPixelBufferGetHeight(buffer);
bytesPerRow = CVPixelBufferGetBytesPerRow(buffer);
//利用取得影像細(xì)部信息格式化 CGContextRef
CGColorSpaceRef colorSpace;
CGContextRef cgContext;
colorSpace = CGColorSpaceCreateDeviceRGB();
cgContext = CGBitmapContextCreate(base, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);
//透過(guò) CGImageRef 將 CGContextRef 轉(zhuǎn)換成 UIImage
CGImageRef cgImage;
UIImage *image;
cgImage = CGBitmapContextCreateImage(cgContext);
image = [UIImage imageWithCGImage:cgImage];
CGImageRelease(cgImage);
CGContextRelease(cgContext);
CVPixelBufferUnlockBaseAddress(buffer, 0);
return image;
}
但是視頻中是怎樣進(jìn)行美容處理呢查剖?怎樣將其轉(zhuǎn)換的呢钾虐?平常我們這樣直接使用:
GPUImageBeautifyFilter *beautifyFilter = [[GPUImageBeautifyFilter alloc] init];
[self.videoCamera addTarget:beautifyFilter];
[beautifyFilter addTarget:self.gpuImageView];
此處用到了 GPUImageVideoCamera,可以大致了解下 GPUImage詳細(xì)解析(三)- 實(shí)時(shí)美顏濾鏡:
GPUImageVideoCamera: GPUImageOutput的子類笋庄,提供來(lái)自攝像頭的圖像數(shù)據(jù)作為源數(shù)據(jù)效扫,一般是響應(yīng)鏈的源頭。
GPUImageView:響應(yīng)鏈的終點(diǎn)直砂,一般用于顯示GPUImage的圖像菌仁。
GPUImageFilter:用來(lái)接收源圖像,通過(guò)自定義的頂點(diǎn)静暂、片元著色器來(lái)渲染新的圖像济丘,并在繪制完成后通知響應(yīng)鏈的下一個(gè)對(duì)象。
GPUImageFilterGroup:多個(gè)GPUImageFilter的集合洽蛀。
** GPUImageBeautifyFilter**:
@interface GPUImageBeautifyFilter : GPUImageFilterGroup {
GPUImageBilateralFilter *bilateralFilter;
GPUImageCannyEdgeDetectionFilter *cannyEdgeFilter;
GPUImageCombinationFilter *combinationFilter;
GPUImageHSBFilter *hsbFilter;
}
不得不說(shuō)GPUImage 是相當(dāng)強(qiáng)大的摹迷,此處的功能也只是顯現(xiàn)了一小部分,其中 filter 那塊的處理個(gè)人目前還有好多不理解郊供,需要去深入了解啃源碼峡碉,暫時(shí)不過(guò)多引入。通過(guò)這個(gè)過(guò)程將 sampleBuffer 美容處理后驮审,自然是進(jìn)行編碼啦鲫寄。
三、視頻疯淫、音頻壓縮編碼
而編碼是用 硬編碼呢 還是軟編碼呢地来? 相同碼率,軟編圖像質(zhì)量更清晰熙掺,但是耗電更高靠抑,而且會(huì)導(dǎo)致CPU過(guò)熱燙到攝像頭。不過(guò)硬編碼會(huì)涉及到其他平臺(tái)的解碼适掰,有很多坑颂碧。綜合來(lái)說(shuō),iOS 端硬件兼容性較好类浪,iOS 8.0占有率也已經(jīng)很高了载城,可以直接采用硬編。
硬編碼:下面幾個(gè)DEMO 可以對(duì)比下费就,當(dāng)然看 LFLiveKit 更直接诉瓦。
VideoToolboxPlus
iOSHardwareDecoder
-VideoToolboxDemo
iOS-h264Hw-Toolbox
軟編碼: 利用FFmpeg+x264將iOS攝像頭實(shí)時(shí)視頻流編碼為h264文件 ,備忘下: FFmpeg-X264-Encode-for-iOS
我直接使用了 LFLiveKit ,里面已經(jīng)封裝的很好啦睬澡,此處對(duì) Audiotoolbox && VideoToolbox 簡(jiǎn)單了解下:
AudioToolbox
iOS使用AudioToolbox中的AudioConverter API 把源格式轉(zhuǎn)換成目標(biāo)格式, 詳細(xì)可以看 使用iOS自帶AAC編碼器固额。
// 1、根據(jù)輸入樣本初始化一個(gè)編碼轉(zhuǎn)換器
AudioStreamBasicDescription 根據(jù)指定的源格式和目標(biāo)格式創(chuàng)建 audio converter
// 2煞聪、初始化一個(gè)輸出緩沖列表 outBufferList
// 3斗躏、獲取 AudioCallBack
OSStatus inputDataProc(AudioConverterRef inConverter, 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静陈、傳入 解碼一個(gè)frame
VTCompressionSessionEncodeFrame(compressionSession, pixelBuffer, presentationTimeStamp, duration, (__bridge CFDictionaryRef)properties, (__bridge_retained void *)timeNumber, &flags);
// 3燕雁、回調(diào),處理 取得PPS和SPS
static void VideoCompressonOutputCallback(void *VTref, void *VTFrameRef, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer)
// 4鲸拥、完成編碼贵白,然后銷毀session
VTCompressionSessionCompleteFrames(compressionSession, kCMTimeInvalid);
VTCompressionSessionInvalidate(compressionSession);
CFRelease(compressionSession);
compressionSession = NULL;
四、推流
封裝數(shù)據(jù)成 FLV崩泡,通過(guò) 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ī)的尖淘,沒(méi)有嚴(yán)格的定義。
Flv Header 是文件的頭部著觉,用FLV字符串標(biāo)明了文件的類型村生,以及是否有音頻、視頻等信息饼丘。之后會(huì)有幾個(gè)字節(jié)告訴接下來(lái)的包字節(jié)數(shù)趁桃。
Metainfo 中用來(lái)描述Flv中的各種參數(shù)信息,例如視頻的編碼格式、分辨率卫病、采樣率等等油啤。如果是本地文件(非實(shí)時(shí)直播流),還會(huì)有偏移時(shí)間戳之類的信息用于支持快進(jìn)等操作蟀苛。
VideoTag 存放視頻數(shù)據(jù)益咬。對(duì)于H.264來(lái)說(shuō),第一幀發(fā)送的NALU應(yīng)為 SPS和PPS屹逛,這個(gè)相當(dāng)于H.264的文件頭部础废,播放器解碼流必須先要找到這個(gè)才能進(jìn)行播放。之后的數(shù)據(jù)為I幀或P幀罕模。
AudioTag 存放音頻數(shù)據(jù)评腺。對(duì)于AAC來(lái)說(shuō),我們只需要在每次硬編碼完成后給數(shù)據(jù)加上adts頭部信息即可淑掌。
iOS 中的使用:詳細(xì)看看 LFLiveKit 中的 LFStreamRTMPSocket 類蒿讥。
4-2、RTMP
從推流端到服務(wù)端抛腕,數(shù)據(jù)經(jīng)過(guò)處理后芋绸,最常用的協(xié)議是RTMP(Real Time Messaging Protocol,實(shí)時(shí)消息傳送協(xié)議)担敌。
RTMP的傳輸延遲通常在1-3秒摔敛,符合手機(jī)直播對(duì)性能的要求,因此RTMP是手機(jī)直播中最常見(jiàn)的傳輸協(xié)議全封。
但是網(wǎng)絡(luò)延遲和阻塞等問(wèn)題的一直存在的马昙,所以通過(guò)Quality of Servic一種網(wǎng)絡(luò)機(jī)制將流數(shù)據(jù)推送到網(wǎng)絡(luò)端,通過(guò)CDN分發(fā)是必要的刹悴。
另外行楞,服務(wù)端還需要對(duì)數(shù)據(jù)流一定的處理,轉(zhuǎn)碼土匀,使得數(shù)據(jù)流支持HLS子房,HTTP-FLV,RTMP等格式的拉流就轧,支持一轉(zhuǎn)多证杭,適配不同網(wǎng)絡(luò)、分辨率的終端妒御。(當(dāng)然這就是服務(wù)端要做的事情啦)
可以用 LFLiveKit 直接嘗試躯砰,或者也可以看看 LMLiveStreaming,當(dāng)然此處先用一個(gè)本地視頻推送嘗試一下携丁。
4-3琢歇、本地模擬推流
此處是跟著 快速集成iOS基于RTMP的視頻推流 來(lái)實(shí)現(xiàn)的兰怠,否則就連基本的展示都不能啦啊。此處也可以配合著Mac搭建nginx+rtmp服務(wù)器 來(lái)安裝李茫,安裝好 nginx 之后揭保,安裝ffmpeg、下載 VLC 就可以直接開(kāi)始啦
起初在用 ffmpeg 的時(shí)候魄宏,遇到下面那個(gè)錯(cuò):
后來(lái)發(fā)現(xiàn)是自己輸入錯(cuò)了秸侣,還是要仔細(xì):
視頻文件地址:/Users/pipixia/Desktop/kobe.mp4(自己的一個(gè)測(cè)試視頻)
推流拉流地址: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
那個(gè)-vcodec libx264 -acodec aac -strict -2 -f flv命令也不要寫(xiě)錯(cuò)了,ffmpeg 命令可參考 FFmpeg常用基本命令宠互。
4-4味榛、手機(jī)直播 - VLC上 顯示
為了更好的感受下,我們可以直接 用 LMLiveStreaming予跌,然后打開(kāi) VLC 中 的 file -- Open Network, 直接輸入代碼中的 url:
然后我們電腦端就可以顯示啦
而目前有延遲2秒的情況搏色,話說(shuō)這是正常的。但如何優(yōu)化呢券册?不知道频轿,如有朋友有好建議歡迎告之。備注下:直播中累積延時(shí)的優(yōu)化烁焙。
總結(jié)
PS:上面?zhèn)鬏斨皇峭屏鞫说椒?wù)端的模擬過(guò)程航邢,然而傳輸一般是包括系統(tǒng)的多個(gè)部分,連接推流端骄蝇,服務(wù)端膳殷,播放端等多個(gè)部分。而 iOS 這塊播放端直接用 ijkplayer, 像上一個(gè)筆記——直播初探 , 就很快實(shí)現(xiàn)了拉流的過(guò)程九火,當(dāng)然也是 ijkplayer 過(guò)于強(qiáng)大的原因咯赚窃。
下面宏觀上了解下整個(gè)傳輸過(guò)程:
PS: 另外其實(shí)好多第三方的集成也很好用,可參考
七牛云
騰訊的直播 LVB
網(wǎng)易云信 SDK
趣拍云
總的說(shuō)來(lái)吃既,這又是一個(gè)粗略的過(guò)程,站在好多個(gè)巨人的肩膀上跨细,但是還是基本了解了一個(gè)推流的流程鹦倚,沒(méi)有正式項(xiàng)目的經(jīng)驗(yàn),肯定有太很多細(xì)節(jié)點(diǎn)忽略了和好多坑需要填冀惭,還是那個(gè)目的震叙,暫時(shí)先作為自己的預(yù)備知識(shí)點(diǎn)吧,不過(guò)此處可以擴(kuò)展和深入的知識(shí)點(diǎn)真的太多啦散休,如 LFLiveKit 和 GPUImage 僅僅展露的是冰山一角媒楼。
備注參考:
LiveVideoCoreSDK
LFLiveKit
GPUImage
LMLiveStreaming
PLCameraStreamingKit
iOS手機(jī)直播Demo技術(shù)簡(jiǎn)介
iOS視頻開(kāi)發(fā)經(jīng)驗(yàn)
iOS 上的相機(jī)捕捉
CMSampleBufferRef 與 UIImage 的轉(zhuǎn)換
GPUImage詳細(xì)解析(三)- 實(shí)時(shí)美顏濾鏡
iOS8系統(tǒng)H264視頻硬件編解碼說(shuō)明
利用FFmpeg+x264將iOS攝像頭實(shí)時(shí)視頻流編碼為h264文件
使用VideoToolbox硬編碼H.264
使用iOS自帶AAC編碼器
如何搭建一個(gè)完整的視頻直播系統(tǒng)?
直播中累積延時(shí)的優(yōu)化
使用VLC做流媒體服務(wù)器(直播形式)