iOS 直播 —— 推流
来源:互联网 发布:淘宝话费流量券大礼包 编辑:程序博客网 时间:2024/06/05 18:14
推流,就是将采集到的音频,视频数据通过流媒体协议发送到流媒体服务器。
- 推流前的工作:采集,处理,编码压缩
- 推流中做的工作: 封装,上传
话说回来, 其实有一个库 LFLiveKit 已经实现了 后台录制、美颜功能、支持h264、AAC硬编码,动态改变速率,RTMP传输等,我们真正开发的时候直接使用就很方便啦。
另外也有:
- LiveVideoCoreSDK : 实现了美颜直播和滤镜功能,我们只要填写RTMP服务地址,直接就可以进行推流啦。
- PLCameraStreamingKit: 也是一个不错的 RTMP 直播推流 SDK。
但还是推荐用 LFLiveKit,而为了进一步了解推流这个过程,先按自己的步子试着走走,了解下。
一、采集视频
采集硬件(摄像头)视频图像
#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; // 获得一个采集设备, 默认后置摄像头 AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo]; AVCaptureDevice *audioDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio]; // 用设备初始化一个采集的输入对象 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 } // 配置采集输出,即我们取得视频图像的接口 _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中判断数据来源(Video/Audio) _videoConnection = [_videoOutput connectionWithMediaType:AVMediaTypeVideo]; _audioConnection = [_audioOutput connectionWithMediaType:AVMediaTypeAudio];}- (void)showPlayer { _previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_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) { // Video NSLog(@"这里获的 video sampleBuffer,做进一步处理(编码H.264)"); } else if (connection == self.audioConnection) { // Audio NSLog(@"这里获得 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 { //制作 CVImageBufferRef CVImageBufferRef buffer; buffer = CMSampleBufferGetImageBuffer(sampleBuffer); CVPixelBufferLockBaseAddress(buffer, 0); //从 CVImageBufferRef 取得影像的细部信息 uint8_t *base; size_t width, height, bytesPerRow; base = CVPixelBufferGetBaseAddress(buffer); width = CVPixelBufferGetWidth(buffer); height = CVPixelBufferGetHeight(buffer); bytesPerRow = CVPixelBufferGetBytesPerRow(buffer); //利用取得影像细部信息格式化 CGContextRef CGColorSpaceRef colorSpace; CGContextRef cgContext; colorSpace = CGColorSpaceCreateDeviceRGB(); cgContext = CGBitmapContextCreate(base, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); CGColorSpaceRelease(colorSpace); //透过 CGImageRef 将 CGContextRef 转换成 UIImage CGImageRef cgImage; UIImage *image; cgImage = CGBitmapContextCreateImage(cgContext); image = [UIImage imageWithCGImage:cgImage]; CGImageRelease(cgImage); CGContextRelease(cgContext); CVPixelBufferUnlockBaseAddress(buffer, 0); return image;}
但是视频中是怎样进行美容处理呢?怎样将其转换的呢?平常我们这样直接使用:
GPUImageBeautifyFilter *beautifyFilter = [[GPUImageBeautifyFilter alloc] init];[self.videoCamera addTarget:beautifyFilter];[beautifyFilter addTarget: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(AudioConverterRef inConverter, 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, (__bridge CFDictionaryRef)properties, (__bridge_retained void *)timeNumber, &flags);// 3、回调,处理 取得PPS和SPSstatic void VideoCompressonOutputCallback(void *VTref, void *VTFrameRef, OSStatus status, VTEncodeInfoFlags infoFlags, CMSampleBufferRef sampleBuffer)// 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 出现的顺序随机的,没有严格的定义。Flv Header 是文件的头部,用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常用基本命令。
4-4、手机直播 - VLC上 显示
为了更好的感受下,我们可以直接 用 LMLiveStreaming,然后打开 VLC 中 的 file -- Open Network, 直接输入代码中的 url:
然后我们电脑端就可以显示啦
而目前有延迟2秒的情况,话说这是正常的。但如何优化呢?不知道,如有朋友有好建议欢迎告之。备注下:直播中累积延时的优化。
总结
PS:上面传输只是推流端到服务端的模拟过程,然而传输一般是包括系统的多个部分,连接推流端,服务端,播放端等多个部分。而 iOS 这块播放端直接用 ijkplayer, 像上一个笔记——直播初探 , 就很快实现了拉流的过程,当然也是ijkplayer 过于强大的原因咯。
下面宏观上了解下整个传输过程:
PS: 另外其实好多第三方的集成也很好用,可参考
- 七牛云
- 腾讯的直播 LVB
- 网易云信 SDK
- 趣拍云
总的说来,这又是一个粗略的过程,站在好多个巨人的肩膀上,但是还是基本了解了一个推流的流程,没有正式项目的经验,肯定有太很多细节点忽略了和好多坑需要填,还是那个目的,暂时先作为自己的预备知识点吧,不过此处可以扩展和深入的知识点真的太多啦,如LFLiveKit 和 GPUImage 仅仅展露的是冰山一角。
备注参考:
- LiveVideoCoreSDK
- LFLiveKit
- GPUImage
- LMLiveStreaming
- PLCameraStreamingKit
- iOS手机直播Demo技术简介
- iOS视频开发经验
- iOS 上的相机捕捉
- CMSampleBufferRef 与 UIImage 的转换
- GPUImage详细解析(三)- 实时美颜滤镜
- iOS8系统H264视频硬件编解码说明
- 利用FFmpeg+x264将iOS摄像头实时视频流编码为h264文件
- 使用VideoToolbox硬编码H.264
- 使用iOS自带AAC编码器
- 如何搭建一个完整的视频直播系统?
- 直播中累积延时的优化
- 使用VLC做流媒体服务器(直播形式)
iOS 直播 —— 推流
转 映客 LFLiveKit 推流
【如何快速的开发一个完整的iOS直播app】(原理篇)
最简单的基于FFmpeg的推流器(以推送RTMP为例)
基于GPUImage的实时美颜滤镜
- iOS 直播 —— 推流
- FFmpeg+RTMP 直播 iOS推流
- iOS开发直播app推流
- 腾讯直播——推流SDK(Android)
- iOS 视频直播类,推流,转码,编码
- iOS直播-实现后台录音并推流
- iOS简单直播实现(二:推流)
- iOS RTMP直播推流学习笔记 & VideoCore源码梳理
- iOS RTMP上推直播视频
- 推流资源(视频直播)
- Android直播推流学习
- rtmp推流直播流程
- 推流直播工具推荐
- 直播推流基础知识笔记
- yasea 直播推流工具
- iOS直播相关(快速集成基于RTMP的视频推流与拉流)
- iOS开发之利用IJKPlayer+nginx+rtmp搭建直播的推流和拉流
- 1小时学会:最简单的iOS直播推流(一)介绍
- VMware Ubuntu安装详细过程
- 第二十三篇:JAVA注解
- K:顺序表和链表的比较
- 欢迎使用CSDN-markdown编辑器
- HTML初学笔记5
- iOS 直播 —— 推流
- hdu 1160 dp (二维最长上升子序列 记录路径
- python爬虫备忘(2)
- SQL Tuning 基础概述03
- QML与C++交换数据
- FastDFS文件集群服务器搭建
- python 在Windows 下切换工作目录
- LeetCode Algorithms #1 Two Sum
- 正则表达式:检测密码由6-21字母和数字组成