iOS RTMP 推流

推流,就是将采集到的音频,视频数据通过流媒体协议发送到流媒体服务器。

推流前的工作:采集,处理,编码压缩

推流中做的工作: 封装,上传


推流前的工作:采集,处理,编码压缩

推流中做的工作: 封装,上传

推流前的工作

推流——采集到的音频,视频数据通过流媒体协议发送到流媒体服务器

话说回来,其实有一个库LFLiveKit已经实现了 后台录制、美颜功能、支持h264、AAC硬编码,动态改变速率,RTMP传输等,我们真正开发的时候直接使用就很方便啦。

另外也有:

LiveVideoCoreSDK: 实现了美颜直播和滤镜功能,我们只要填写RTMP服务地址,直接就可以进行推流啦。

PLCameraStreamingKit: 也是一个不错的 RTMP 直播推流 SDK。

但还是推荐用LFLiveKit,而为了进一步了解推流这个过程,先按自己的步子试着走走,了解下。

一、采集视频

采集硬件(摄像头)视频图像

#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;// 获得一个采集设备, 默认后置摄像头AVCaptureDevice*videoDevice = [AVCaptureDevicedefaultDeviceWithMediaType:AVMediaTypeVideo];AVCaptureDevice*audioDevice = [AVCaptureDevicedefaultDeviceWithMediaType:AVMediaTypeAudio];// 用设备初始化一个采集的输入对象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}// 配置采集输出,即我们取得视频图像的接口_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中判断数据来源(Video/Audio)_videoConnection = [_videoOutput connectionWithMediaType:AVMediaTypeVideo];    _audioConnection = [_audioOutput connectionWithMediaType:AVMediaTypeAudio];}- (void)showPlayer {    _previewLayer = [AVCaptureVideoPreviewLayerlayerWithSession:_session];    _previewLayer.videoGravity =AVLayerVideoGravityResizeAspectFill;// 设置预览时的视频缩放方式[[_previewLayer connection] setVideoOrientation:AVCaptureVideoOrientationPortrait];// 设置视频的朝向_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就是采集到的数据了,根据connection来判断,是Video还是Audio的数据if(connection ==self.videoConnection) {// VideoNSLog(@"这里获的 video sampleBuffer,做进一步处理(编码H.264)");    }elseif(connection ==self.audioConnection) {// AudioNSLog(@"这里获得 audio sampleBuffer,做进一步处理(编码AAC)");    }}@end

上述是大致实现获取最基本数据的情况,一些细节(尺寸、方向)暂时没有深入,真正做直播的时候,一般是视频和音频是分开处理的,只有重点注意那个代理方法。

二、GPUImage 处理

在进行编码 H.264 之前,一般来说肯定会做一些美颜处理的,否则那播出的感觉太真实,就有点丑啦,在此以磨皮和美白为例简单了解。(具体参考的是:琨君基于 GPUImage 的实时美颜滤镜

直接用BeautifyFaceDemo中的类GPUImageBeautifyFilter, 可以对的图片直接进行处理:

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

备注下 CMSampleBufferRef 与 UIImage 的转换

- (UIImage *)sampleBufferToImage:(CMSampleBufferRef)sampleBuffer {//制作 CVImageBufferRefCVImageBufferRefbuffer;buffer= CMSampleBufferGetImageBuffer(sampleBuffer);    CVPixelBufferLockBaseAddress(buffer,0);//从 CVImageBufferRef 取得影像的细部信息uint8_t *base;    size_twidth,height, bytesPerRow;    base = CVPixelBufferGetBaseAddress(buffer);width= CVPixelBufferGetWidth(buffer);height= CVPixelBufferGetHeight(buffer);    bytesPerRow = CVPixelBufferGetBytesPerRow(buffer);//利用取得影像细部信息格式化 CGContextRefCGColorSpaceRef colorSpace;    CGContextRef cgContext;    colorSpace = CGColorSpaceCreateDeviceRGB();    cgContext = CGBitmapContextCreate(base,width,height,8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);    CGColorSpaceRelease(colorSpace);//透过 CGImageRef 将 CGContextRef 转换成 UIImageCGImageRef cgImage;    UIImage *image;    cgImage = CGBitmapContextCreateImage(cgContext);image= [UIImage imageWithCGImage:cgImage];    CGImageRelease(cgImage);    CGContextRelease(cgContext);    CVPixelBufferUnlockBaseAddress(buffer,0);returnimage;}

但是视频中是怎样进行美容处理呢?怎样将其转换的呢?平常我们这样直接使用:

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

此处用到了GPUImageVideoCamera,可以大致了解下GPUImage详细解析(三)- 实时美颜滤镜

GPUImageVideoCamera: GPUImageOutput的子类,提供来自摄像头的图像数据作为源数据,一般是响应链的源头。

GPUImageView:响应链的终点,一般用于显示GPUImage的图像。

GPUImageFilter:用来接收源图像,通过自定义的顶点、片元着色器来渲染新的图像,并在绘制完成后通知响应链的下一个对象。

GPUImageFilterGroup:多个GPUImageFilter的集合。

GPUImageBeautifyFilter

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

简单理解这个美颜的流程

不得不说GPUImage 是相当强大的,此处的功能也只是显现了一小部分,其中 filter 那块的处理个人目前还有好多不理解,需要去深入了解啃源码,暂时不过多引入。通过这个过程将 sampleBuffer 美容处理后,自然是进行编码啦。

三、视频、音频压缩编码

而编码是用硬编码呢 还是软编码呢? 相同码率,软编图像质量更清晰,但是耗电更高,而且会导致CPU过热烫到摄像头。不过硬编码会涉及到其他平台的解码,有很多坑。综合来说,iOS 端硬件兼容性较好,iOS 8.0占有率也已经很高了,可以直接采用硬编。

硬编码:下面几个DEMO 可以对比下,当然看LFLiveKit更直接。

VideoToolboxPlus

iOSHardwareDecoder

-VideoToolboxDemo

iOS-h264Hw-Toolbox

软编码:利用FFmpeg+x264将iOS摄像头实时视频流编码为h264文件,备忘下:FFmpeg-X264-Encode-for-iOS

我直接使用了LFLiveKit,里面已经封装的很好啦,此处对  Audiotoolbox  && VideoToolbox 简单了解下:

AudioToolbox

iOS使用AudioToolbox中的AudioConverter API 把源格式转换成目标格式, 详细可以看使用iOS自带AAC编码器

//1、根据输入样本初始化一个编码转换器AudioStreamBasicDescription 根据指定的源格式和目标格式创建 audio converter//2、初始化一个输出缓冲列表outBufferList //3、获取 AudioCallBackOSStatus inputDataProc(AudioConverterRefinConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData,AudioStreamPacketDescription **outDataPacketDescription, void *inUserData) //4、音频格式完成转换AudioConverterFillComplexBuffer 实现inBufferList 和outBufferList、inputDataProc音频格式之间的转换。

VideoToolbox

iOS8之后的硬解码、硬编码API,此处只做编码用。

// 1、初始化 VTCompressionSessionRef- (void)initCompressionSession;// 2、传入  解码一个frameVTCompressionSessionEncodeFrame(compressionSession, pixelBuffer, presentationTimeStamp, duration, (__bridgeCFDictionaryRef)properties, (__bridge_retainedvoid*)timeNumber, &flags);// 3、回调,处理 取得PPS和SPSstaticvoidVideoCompressonOutputCallback(void*VTref,void*VTFrameRef, OSStatus status, VTEncodeInfoFlags infoFlags,CMSampleBufferRefsampleBuffer)// 4、完成编码,然后销毁sessionVTCompressionSessionCompleteFrames(compressionSession, kCMTimeInvalid);VTCompressionSessionInvalidate(compressionSession);CFRelease(compressionSession);compressionSession =NULL;

四、推流

封装数据成 FLV,通过 RTMP 协议打包上传,从主播端到服务端即基本完成推流。

4-1、封装数据通常是封装成 FLV

FLV流媒体格式是一种新的视频格式,全称为FlashVideo。由于它形成的文件极小、加载速度极快,使得网络观看视频文件成为可能,它的出现有效地解决了视频文件导入Flash后,使导出的SWF文件体积庞大,不能在网络上很好的使用等缺点。(What)

格式: 源自(封包 FLV

一般FLV文件结构里是这样存放的:[[Flv Header][Metainfo Tag][Video Tag][Audio Tag][Video Tag][Audio Tag][Other Tag]…]其中AudioTag和VideoTag出现的顺序随机的,没有严格的定义。FlvHeader是文件的头部,用FLV字符串标明了文件的类型,以及是否有音频、视频等信息。之后会有几个字节告诉接下来的包字节数。Metainfo中用来描述Flv中的各种参数信息,例如视频的编码格式、分辨率、采样率等等。如果是本地文件(非实时直播流),还会有偏移时间戳之类的信息用于支持快进等操作。VideoTag存放视频数据。对于H.264来说,第一帧发送的NALU应为SPS和PPS,这个相当于H.264的文件头部,播放器解码流必须先要找到这个才能进行播放。之后的数据为I帧或P帧。AudioTag存放音频数据。对于AAC来说,我们只需要在每次硬编码完成后给数据加上adts头部信息即可。

iOS 中的使用:详细看看 LFLiveKit 中的 LFStreamRTMPSocket 类。

4-2、RTMP

从推流端到服务端,数据经过处理后,最常用的协议是RTMP(Real Time Messaging Protocol,实时消息传送协议)。

RTMP的传输延迟通常在1-3秒,符合手机直播对性能的要求,因此RTMP是手机直播中最常见的传输协议。但是网络延迟和阻塞等问题的一直存在的,所以通过Quality of Servic一种网络机制将流数据推送到网络端,通过CDN分发是必要的。另外,服务端还需要对数据流一定的处理,转码,使得数据流支持HLS,HTTP-FLV,RTMP等格式的拉流,支持一转多,适配不同网络、分辨率的终端。(当然这就是服务端要做的事情啦)

可以用LFLiveKit直接尝试,或者也可以看看LMLiveStreaming,当然此处先用一个本地视频推送尝试一下。

4-3、本地模拟推流

此处是跟着快速集成iOS基于RTMP的视频推流来实现的,否则就连基本的展示都不能啦啊。此处也可以配合着Mac搭建nginx+rtmp服务器来安装,安装好 nginx 之后,安装ffmpeg、下载VLC就可以直接开始啦

起初在用 ffmpeg 的时候,遇到下面那个错:

一个输入的错

后来发现是自己输入错了,还是要仔细:

视频文件地址:/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、手机直播 - VLC上 显示

为了更好的感受下,我们可以直接 用LMLiveStreaming,然后打开 VLC 中 的 file -- Open Network, 直接输入代码中的 url:

代码中的这个地址

然后我们电脑端就可以显示啦

Live.gif

而目前有延迟2秒的情况,话说这是正常的。但如何优化呢?不知道,如有朋友有好建议欢迎告之。备注下:直播中累积延时的优化

总结

PS:上面传输只是推流端到服务端的模拟过程,然而传输一般是包括系统的多个部分,连接推流端,服务端,播放端等多个部分。而 iOS 这块播放端直接用ijkplayer, 像上一个笔记——直播初探, 就很快实现了拉流的过程,当然也是ijkplayer过于强大的原因咯。

下面宏观上了解下整个传输过程:

整体传输流程

PS: 另外其实好多第三方的集成也很好用,可参考

七牛云

腾讯的直播 LVB

网易云信 SDK

趣拍云

总的说来,这又是一个粗略的过程,站在好多个巨人的肩膀上,但是还是基本了解了一个推流的流程,没有正式项目的经验,肯定有太很多细节点忽略了和好多坑需要填,还是那个目的,暂时先作为自己的预备知识点吧,不过此处可以扩展和深入的知识点真的太多啦,如LFLiveKitGPUImage仅仅展露的是冰山一角。

备注参考:

LiveVideoCoreSDK

LFLiveKit

GPUImage

LMLiveStreaming

PLCameraStreamingKit

iOS手机直播Demo技术简介

iOS视频开发经验

iOS 上的相机捕捉

CMSampleBufferRef 与 UIImage 的转换

GPUImage详细解析(三)- 实时美颜滤镜

iOS8系统H264视频硬件编解码说明

利用FFmpeg+x264将iOS摄像头实时视频流编码为h264文件

使用VideoToolbox硬编码H.264

使用iOS自带AAC编码器

如何搭建一个完整的视频直播系统?

直播中累积延时的优化

使用VLC做流媒体服务器(直播形式)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容