代码链接: Github: https://github.com/jessonliu/JFLivePlaye
技术部分------ ⬇️
脑涂: ![ 直播思维导图.png ]

视频直播的大概流程就上脑涂上所画的, 还有一些没列出来, 比如, 聊天, 送礼, 踢出, 禁言, 等等一系列功能, 但本文只是针对视频直播的简单实现!

下边来说一下以下的几个点和使用到的类(后边会附上demo, 里边还有详细的备注)
1. 音视频采集
音视频采集, 网上也有很多大神些的技术博客, demo 等, 我这里边只针对iOS 原声的来介绍以下
利用AVFoundation框架, 进行音视频采集
AVCaptureSession // 音视频录制期间管理者
AVCaptureDevice // 设备管理者, (用来操作所闪光灯, 聚焦, 摄像头切换等)
AVCaptureDeviceInput // 音视频输入数据的管理对象
AVCaptureVideoDataOutput // 视频输出数据的管理者
AVCaptureAudioDataOutput // 音频输出数据的管理者
AVCaptureVideoPreviewLayer // 用来展示视频的图像
注意, 必须要设置音视频输出对象的代理方法, 然后在代理方法中获取sampleBuffer, 然后判断captureOutput是音频还是视频, 来进行音视频数据相应的编码
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
}
也可以利用GPUImageVideoCamera 来进行视频数据的采集获取, 可以利用GPUImage 进行美颜, 添加水印, 人脸识别等
2.流媒体
流媒体是指采用流式传输的方式在网上播放的媒体格式, 是边传边播的媒体,是多媒体的一种!
然后就是大家需要了解的几个关键词
帧:视频是由很多连续图像组成, 每一帧就代表一幅静止的图像
GOP:(Group of Pictures)画面组,一个GOP就是一组连续的画面,每个画面都是一帧,GOP就是很多帧的集合!
帧的分类:I帧、P帧、B帧
为了提高压缩比例,降低视频文件的大小,在针对连续动态图像编码时,一般会将连续若干幅图像编码为P、B、I三种帧类型
I帧:一组连续画面(GOP)的第一个帧, I帧采用帧内压缩法(也成关键帧压缩法), I帧的压缩不依靠与其他帧, 靠尽可能去除图像空间冗余信息来压缩的, 可以单独作为图像!

P帧:预测帧(也叫前向参考帧), P帧的压缩依赖于前一帧, 通过充分降低与图像序列中前面已编码帧的时间冗余信息来压缩传输数据量的编码图像!

B帧:也叫双向预测帧, 当把一帧压缩成B帧时,它根据邻近的前几帧、本帧以及后几帧数据的不同点来压缩本帧,也即仅记录本帧与前后帧的差值。

帧率:就是在1秒钟时间里传输的图片的帧数,也可以理解为图形处理器每秒钟能够刷新几次,通常用FPS表示, 每秒钟帧数 (fps) 愈多,所显示的动作就会愈流畅!

码率: 也成为比特率, 是指每秒传送的比特(bit)数, 比特率越高,传送数据速度越快, 单位为 bps(Bit Per Second)。

3. 音视频的编解码
音视频编解码, 说白了就是对音视频数据进行压缩, 减少数据对空间的占用, 便于网络传输, 存储和使用!
目前直播常用的音视频编解码方式是h.264/AVC, AAC/MP3
硬软编解码的区别:
硬解码:由显卡核心GPU来对高清视频进行解码工作,CPU占用率很低,画质效果比软解码略差一点,需要对播放器进行设置。
  优点:播放流畅、低功耗
  缺点:受视频格式限制、功耗大、画质没有软解码好

软解码:由CPU负责解码进行播放
  优点:不受视频格式限制、画质略好于硬解
  缺点:会占用过高的资源、对于高清视频可能没有硬解码流畅(主要看CPU的能力)

苹果API有提供音视频硬编解码接口, 但只针对iOS8.0以上版本!
利用VideoToolbox 和AudioToolbox 这连个框架进行音视频的硬编码!
这里附上前辈们的关于VideoToolbox使用的简书, http://www.jianshu.com/p/6dfe49b5dab8
和AudioToolbox的技术简书http://www.jianshu.com/p/a671f5b17fc1
感兴趣的话可以研究一下!

4.流媒体数据封装
TS: 是流媒体封装格式的一种,流媒体封装的好处就是不需要加载索引再播放,大大降低了首次载入的延迟,两个TS片段可以无缝拼接,播放器能连续播放!
FLV: 也是一种流媒体的封装格式,但他形成的文件极小、加载速度极快,使得网络观看视频文件成为可能,因此FLV格式成为了当今主流视频格式

5.RTMP推流
大家先看一张图, 常用的直播协议比较

这里只介绍一下RTMP协议, 如果还想了解更多的可在网上查找一下, 有很多关于流媒体协议的技术博客!
RTMP协议是基于TCP/IP 的协议簇;RTMP(Real Time Messaging Protocol)实时消息传送协议是Adobe Systems公司为Flash播放器和服务器之间音频、视频和数据传输 开发的开放协议
它有多种变种:
a, RTMP工作在TCP之上,默认使用端口1935;
b, RTMPE在RTMP的基础上增加了加密功能;
c, RTMPT封装在HTTP请求之上,可穿透防火墙;
d, RTMPS类似RTMPT,增加了TLS/SSL的安全功能;
它是一个互联网TCP/IP体系结构中应用层的协议。RTMP协议中基本的数据单元称为消息(Message)。当RTMP协议在互联网中传输数据的时候,消息会被拆分成更小的单元,称为消息块(Chunk)。RTMP传输媒体数据的过程中,发送端首先把媒体数据封装成消息,然后把消息分割成消息块,最后将分割后的消息块通过TCP协议发送出去。接收端在通过TCP协议收到数据后,首先把消息块重新组合成消息,然后通过对消息进行解封装处理就可以恢复出媒体数据。
播放一个RTMP协议的流媒体需要经过以下几个步骤:握手,建立连接,建立流,播放。

demo中RTMP协议推流, 用的是librtmp-iOS框架! 参考https://my.oschina.net/jerikc/blog/501948

6. 播放器
IJKPlayer 是一个基于 ffplay 的轻量级 Android/iOS 视频播放器。API 易于集成;编译配置可裁剪,方便控制安装包大小;支持 硬件加速解码,更加省电。而DanmakuFlameMaster(开源弹幕框架) 架构清晰,简单易用,支持多种高效率绘制方式选择,支持多种自定义功能设置!

代码:
#import "JFLiveShowVC.h" 该类负责音视频采集及展示, 用于时间没问题, 没有吧音视频采集单独拿出来封装!

- (void)viewDidLoad {[super viewDidLoad];//Do any additional setup after loading the view.//需要用到的线程videoProcessingQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);audioProcessingQueue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);_jfEncodeQueue_video= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);_jfEncodeQueue_audio= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);//检查权限和设备
[self checkDeviceAuth];//数据保存路径self.documentDictionary = [(NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES)) objectAtIndex:0];//音频编码对象初始化self.audioEncoder =[[AACEncoder alloc] init];self.audioEncoder.delegate = self;    //设置代理self.videoEncoder = [[JFVideoEncoder alloc] init]; //视频编码对象初始化self.videoEncoder.delegate = self;                         //设置代理
_lock= dispatch_semaphore_create(1); //当并行执行的处理更新数据时,会产生数据不一致的情况,使用Serial Dipatch queue 进行同步, 控制并发
}//检查是否授权摄像头的使用权限
- (void)checkDeviceAuth {switch([AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo]) {case AVAuthorizationStatusAuthorized:   //已授权NSLog(@"已授权");[self initAVCaptureSession];break;case AVAuthorizationStatusNotDetermined:    //用户尚未进行允许或者拒绝,
{[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {if(granted) {NSLog(@"已授权");[self initAVCaptureSession];}else{NSLog(@"用户拒绝授权摄像头的使用, 返回上一页, 请打开--> 设置 -- > 隐私 --> 通用等权限设置");}}];}break;default:{NSLog(@"用户尚未授权摄像头的使用权");}break;}
}//初始化 管理者
- (void)initAVCaptureSession {self.session=[[AVCaptureSession alloc] init];//设置录像的分辨率//先判断是被是否支持要设置的分辨率if([self.session canSetSessionPreset:AVCaptureSessionPreset1280x720]) {//如果支持则设置
[self.session canSetSessionPreset:AVCaptureSessionPreset1280x720];}else if([self.session canSetSessionPreset:AVCaptureSessionPresetiFrame960x540]) {[self.session canSetSessionPreset:AVCaptureSessionPresetiFrame960x540];}else if([self.session canSetSessionPreset:AVCaptureSessionPreset640x480]) {[self.session canSetSessionPreset:AVCaptureSessionPreset640x480];}//开始配置
[self.session beginConfiguration];//初始化视频管理self.videoDevice =nil;//创建摄像头类型数组NSArray *devices =[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];//便利管理抓捕道德所有支持制定类型的 设备集合for (AVCaptureDevice *device indevices) {if (device.position ==AVCaptureDevicePositionFront) {self.videoDevice=device;}}//视频
[self videoInputAndOutput];//音频
[self audioInputAndOutput];//录制的同时播放
[self initPreviewLayer];//提交配置
[self.session commitConfiguration];
}//视频输入输出
- (void)videoInputAndOutput {NSError*error;//视频输入//初始化 根据输入设备来初始化输出对象self.videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:self.videoDevice error:&error];if(error) {NSLog(@"-- 摄像头出错 -- %@", error);return;}//将输入对象添加到管理者 -- AVCaptureSession 中//先判断是否能搞添加输入对象if([self.session canAddInput:self.videoInput]) {//管理者能够添加 才可以添加
[self.session addInput:self.videoInput];}//视频输出//初始化 输出对象self.videoOutput =[[AVCaptureVideoDataOutput alloc] init];//是否允许卡顿时丢帧self.videoOutput.alwaysDiscardsLateVideoFrames =NO;if([self supportsFastTextureUpload]){//是否支持全频色彩编码 YUV 一种色彩编码方式, 即YCbCr, 现在视频一般采用该颜色空间, 可以分离亮度跟色彩, 在不影响清晰度的情况下来压缩视频BOOL supportsFullYUVRange =NO;//获取输出对象 支持的像素格式NSArray *supportedPixelFormats =self.videoOutput.availableVideoCVPixelFormatTypes;for (NSNumber *currentPixelFormat insupportedPixelFormats){if ([currentPixelFormat intValue] ==kCVPixelFormatType_420YpCbCr8BiPlanarFullRange){supportsFullYUVRange=YES;}}//根据是否支持 来设置输出对象的视频像素压缩格式,if(supportsFullYUVRange){[self.videoOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarFullRange] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];}else{[self.videoOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];}}else{[self.videoOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];}//设置代理
[self.videoOutput setSampleBufferDelegate:self queue:videoProcessingQueue];//判断管理是否可以添加 输出对象if([self.session canAddOutput:self.videoOutput]) {[self.session addOutput:self.videoOutput];AVCaptureConnection*connection =[self.videoOutput connectionWithMediaType:AVMediaTypeVideo];//设置视频的方向connection.videoOrientation =AVCaptureVideoOrientationPortrait;//视频稳定设置if([connection isVideoStabilizationSupported]) {connection.preferredVideoStabilizationMode=AVCaptureVideoStabilizationModeAuto;}connection.videoScaleAndCropFactor=connection.videoMaxScaleAndCropFactor;}
}//音频输入输出
- (void)audioInputAndOutput {NSError*jfError;//音频输入设备self.audioDevice =[AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeAudio];//音频输入对象self.audioInput = [[AVCaptureDeviceInput alloc] initWithDevice:self.audioDevice error:&jfError];if(jfError) {NSLog(@"-- 录音设备出错 -- %@", jfError);}//将输入对象添加到 管理者中if([self.session canAddInput:self.audioInput]) {[self.session addInput:self.audioInput];}//音频输出对象self.audioOutput =[[AVCaptureAudioDataOutput alloc] init];//将输出对象添加到管理者中if([self.session canAddOutput:self.audioOutput]) {[self.session addOutput:self.audioOutput];}//设置代理
[self.audioOutput setSampleBufferDelegate:self queue:audioProcessingQueue];
}//播放同时进行播放
- (void)initPreviewLayer {[self.view layoutIfNeeded];//初始化对象self.previewLayer =[[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];self.previewLayer.frame=self.view.layer.bounds;self.previewLayer.connection.videoOrientation=[self.videoOutput connectionWithMediaType:AVMediaTypeVideo].videoOrientation;self.previewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;self.previewLayer.position= CGPointMake(self.liveView.frame.size.width*0.5,self.liveView.frame.size.height*0.5);CALayer*layer =self.liveView.layer;layer.masksToBounds= true;[layer addSublayer:self.previewLayer];
}#pragma mark 返回上一级
- (IBAction)backAction:(id)sender {//结束直播
[self.socket stop];[self.session stopRunning];[self.videoEncoder stopEncodeSession];fclose(_h264File);fclose(_aacFile);[self.navigationController popViewControllerAnimated:YES];
}#pragma mark 开始直播
- (IBAction)startLiveAction:(UIButton *)sender {_h264File= fopen([[NSString stringWithFormat:@"%@/jf_encodeVideo.h264", self.documentDictionary] UTF8String], "wb");_aacFile= fopen([[NSString stringWithFormat:@"%@/jf_encodeAudio.aac", self.documentDictionary] UTF8String], "wb");//初始化 直播流信息JFLiveStreamInfo *streamInfo =[[JFLiveStreamInfo alloc] init];streamInfo.url= @"rtmp://192.168.1.110:1935/rtmplive/room";self.socket=[[JFRtmpSocket alloc] initWithStream:streamInfo];self.socket.delegate =self;[self.socket start];//开始直播
[self.session startRunning];sender.hidden=YES;
}#pragma mark --  AVCaptureAudioDataOutputSampleBufferDelegate
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {if (captureOutput ==self.audioOutput) {[self.audioEncoder encodeSampleBuffer:sampleBuffer timeStamp:self.currentTimestamp completionBlock:^(NSData *encodedData, NSError *error) {fwrite(encodedData.bytes,1, encodedData.length, _aacFile);}];}else{[self.videoEncoder encodeWithSampleBuffer:sampleBuffer timeStamp:self.currentTimestamp completionBlock:^(NSData *data, NSInteger length) {fwrite(data.bytes,1, length, _h264File);}];}
}- (void)dealloc {if([self.session isRunning]) {[self.session stopRunning];}[self.videoOutput setSampleBufferDelegate:nil queue:dispatch_get_main_queue()];[self.audioOutput setSampleBufferDelegate:nil queue:dispatch_get_main_queue()];
}//是否支持快速纹理更新
-(BOOL)supportsFastTextureUpload;
{#if TARGET_IPHONE_SIMULATORreturnNO;#else#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wtautological-pointer-compare"return (CVOpenGLESTextureCacheCreate !=NULL);#pragma clang diagnostic pop#endif}//保存h264数据到文件
- (void) writeH264Data:(void*)data length:(size_t)length addStartCode:(BOOL)b
{//添加4字节的 h264 协议 start codeconst Byte bytes[] = "\x00\x00\x00\x01";if(_h264File) {if(b)fwrite(bytes,1, 4, _h264File);fwrite(data,1, length, _h264File);}else{NSLog(@"_h264File null error, check if it open successed");}
}#pragma mark - JFRtmpSocketDelegate
- (void)jf_videoEncoder_call_back_videoFrame:(JFVideoFrame *)frame {if(self.uploading) {[self.socket sendFrame:frame];}
}#pragma mark - AACEncoderDelegate
- (void)jf_AACEncoder_call_back_audioFrame:(JFAudioFrame *)audionFrame {if(self.uploading) {[self.socket sendFrame:audionFrame];}
}#pragma mark -- JFRtmpSocketDelegate
- (void)socketStatus:(nullable JFRtmpSocket *)socket status:(JFLiveState)status {switch(status) {caseJFLiveReady:NSLog(@"准备");break;caseJFLivePending:NSLog(@"链接中");break;caseJFLiveStart:NSLog(@"已连接");if (!self.uploading) {self.timestamp= 0;self.isFirstFrame=YES;self.uploading=YES;}break;caseJFLiveStop:NSLog(@"已断开");break;caseJFLiveError:NSLog(@"链接出错");self.uploading=NO;self.isFirstFrame=NO;self.uploading=NO;break;default:break;}
}//获取当前时间戳
-(uint64_t)currentTimestamp{dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);uint64_t currentts= 0;if(_isFirstFrame == true) {_timestamp=NOW;_isFirstFrame= false;currentts= 0;}else{currentts= NOW -_timestamp;}dispatch_semaphore_signal(_lock);returncurrentts;
}

// 注: 必须控制好线程, 不然很容易出现卡死或闪退的情况!
关于音视频编解码的代码, 就不在这里展示了, 放在demo 中, 有需要的话话可以下载!
Github: https://github.com/jessonliu/JFLivePlaye
本地流媒体服务器的搭建这个给大家一个连接: http://www.jianshu.com/p/8ea016b2720e

iOS-直播开发(开发从底层做起)相关推荐

  1. IOS直播平台开发简单的队列效果实现

    说到IOS直播平台开发队列的话就想到了多线程,NSOperation ,我们可以重写它,然后在 start 方法中添加动画,但是注意我们只是需要让这些消息排队,更新 UI 还是要在主线程操作:我们还要 ...

  2. iOS动手做一个直播app开发(代码篇)

    iOS动手做一个直播app开发(代码篇) ###开篇 好久没写简书,因为好奇的我跑去学习直播了,今天就分享一下我的感慨. 目前为止直播还是比较热点的技术的,简书,git上有几篇阅读量和含金量都不错的文 ...

  3. iOS 直播类APP开发流程

    (一) iOS 直播类APP开发流程分解: 1 . 音视频处理的一般流程: 数据采集→数据编码→数据传输(流媒体服务器) →解码数据→播放显示 1.数据采集: 摄像机及拾音器收集视频及音频数据,此时得 ...

  4. 直播平台开发中解决iOS 14 兼容问题和静默推送

    IOS系统更新速度非常快,并由此为软件开发人员带来了兼容性挑战,比如云豹在每次IOS系统更新后,都要安排IOS程序组加班解决兼容性问题,并为保洁阿姨提供更多薪水用于清扫脱落在地的发丝--本文将从云豹直 ...

  5. 开发一个完整的iOS直播app必须技能

    今年,直播行业火了,当然也诞生了一大批网红,甚至明星也开始直播了,因此现在都要搞直播了!由于第一次接触,花了很多时间了解直播,目前整理了直播的原理(因为项目汇报的需要就整理了一下),现在只是展示一下从 ...

  6. (转)【如何快速的开发一个完整的iOS直播app】(原理篇)

    原文链接:https://www.jianshu.com/p/bd42bacbe4cc [如何快速的开发一个完整的iOS直播app](原理篇) [如何快速的开发一个完整的iOS直播app](原理篇) ...

  7. 如何快速的开发一个完整的iOS直播app(原理篇)

    本文转自袁峥Seemygo的博客分享.觉得很不错.特意粘来给大家分享. 1.一个完整直播app功能(来自落影loyinglin分享) 1.聊天 私聊.聊天室.点亮.推送.黑名单等; 2.礼物 普通礼物 ...

  8. iOS 直播类APP开发流程解析

    1 . 音视频处理的一般流程: 数据采集→数据编码→数据传输(流媒体服务器) →解码数据→播放显示 1.数据采集:摄像机及拾音器收集视频及音频数据,此时得到的为原始数据 涉及技术或协议: 摄像机:CC ...

  9. iOS 直播类APP开发流程分解:

    1 . 音视频处理的一般流程: 数据采集→数据编码→数据传输(流媒体服务器) →解码数据→播放显示 1.数据采集:摄像机及拾音器收集视频及音频数据,此时得到的为原始数据 涉及技术或协议: 摄像机:CC ...

  10. (转载)iOS直播类APP开发流程

    转载自博主:iOS_developer_zhong,博客地址: http://blog.csdn.net/zhonggaorong/article/details/51483282 本文为大家分享了i ...

最新文章

  1. mongoDB设置用户名密码的一个要点
  2. Transformer-LS霸榜ImageNet,输入长度提升三倍!极度压缩参数
  3. node.js搭建简单服务器,用于前端测试websocket链接方法和性能测试
  4. Qt网络程序:基于TCP的服务器、客户端实例
  5. [jQuery] 根据表单的不同参数跳转不同的链接
  6. python如何读取数据时出现错误_连接数据库时出现的错误,怎样解决??
  7. Redis集群一致性Hash效果的代码演示
  8. 一步一步教你使用AgileEAS.NET基础类库进行应用开发-系列目录
  9. 3-unit8 Mariadb数据库
  10. [ 1003 ] 判断小偷那些事
  11. FOUND MODULE 所在的表及刪除不啟作用的INCLUDE
  12. 淺談auto_ptr
  13. 将运算放大器(运放)用作比较器
  14. 2022蓝帽杯半决赛电子取证
  15. Maven Setting.xml配置文件下载 阿里云镜像 下载可用
  16. python实现集合并交差运算_集合的并交差运算
  17. Redis持久化——AOF机制详解
  18. 在Dynamics 365 CRM 中使用Xrm.WebApi实现增,删,改,查(需V9.0或以上)
  19. 酷派大观4 8970 刷android 4.4,极速达百兆! 移动4G版酷派大观4网络体验
  20. MTK android 9.0半屏显示 单手模式

热门文章

  1. 浅谈几种区块链网络攻击以及防御方案之日蚀攻击
  2. 【python】使用python脚本将CelebA中图片按照 list_attr_celeba.txt 中属性处理(删除、复制、移动)
  3. 【opencv】ubuntu14.04上编译opencv-4.0.1 + opencv_contrib-4.0.1
  4. 给动态生成的按钮添加ajax,Ajax/Javascript动态创建按钮的问题
  5. 高斯拟合原理_看得见的高斯过程:这是一份直观的入门解读
  6. html css js实现快递单打印_html+css+js实现计算器
  7. mysql如何避免特殊字符查询_如何避免MySQL中的特殊字符?
  8. Linux的常用命令!
  9. 数据结构学习系列文章合集
  10. uniapph5配置index.html模板路径不生效解决办法