推流,就是將采集到的音頻洛口,視頻數(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 更直接琼了。
軟編碼: 利用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/qiu/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í)好多第三方的集成也很好用份汗,可參考
總的說(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ù)器(直播形式)