iOS 直播相关技术

直播流程图

直播推流拉流流程.webp

1、数据采集

经过麦克风摄像头采集音视频数据
复制代码

视频采集

  • AVCaptureDevice 先后摄像头做为视频源生成输入
  • AVCaptureDeviceInput 视频输入 加入到 👇 session
  • AVCaptureSession 视频对话
  • AVCaptureVideoDataOutput 会话session 导出视频输出
帧率
  • 帧率表示图形处理器处理场时每秒钟可以更新的次数,即:每秒视频播放的图片数
  • 人眼温馨放松时可视帧数是每秒24帧,集中精神时不超过30帧。眨眼时睁开眼瞬间能够捕捉到的帧数是30帧以上。
  • 帧率太小会形成视频卡顿,帧率过大会形成视频过大。
  • iOS默认输出的视频帧率为30帧/秒,普通用途的话设置成24~30就够用了。

音频采集

  • AVAudioSession
  • AudioComponentDescription
  • AudioComponentFindNext
AVAudioSession *session = [AVAudioSession sharedInstance];
        
        // 监听声音路线改变
        [[NSNotificationCenter defaultCenter] addObserver: self
                                                 selector: @selector(handleRouteChange:)
                                                     name: AVAudioSessionRouteChangeNotification
                                                   object: session];
        // 监听声音被打断
        [[NSNotificationCenter defaultCenter] addObserver: self
                                                 selector: @selector(handleInterruption:)
                                                     name: AVAudioSessionInterruptionNotification
                                                   object: session];
        /// 建立AudioComponent
        AudioComponentDescription acd;
        acd.componentType = kAudioUnitType_Output;
        //acd.componentSubType = kAudioUnitSubType_VoiceProcessingIO;
        acd.componentSubType = kAudioUnitSubType_RemoteIO;
        acd.componentManufacturer = kAudioUnitManufacturer_Apple; // 厂商 直接写kAudioUnitManufacturer_Apple
        acd.componentFlags = 0; // 没有明确值时必须设为0
        acd.componentFlagsMask = 0; // 没有明确值时必须设为0
        self.component = AudioComponentFindNext(NULL, &acd);
        
        OSStatus status = noErr;
        status = AudioComponentInstanceNew(self.component, &_componetInstance);
        
        if (noErr != status) {
            [self handleAudioComponentCreationFailure];
        }
        
        /// 链接麦克风
        UInt32 flagOne = 1;
        AudioUnitSetProperty(self.componetInstance, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &flagOne, sizeof(flagOne));
        
        AudioStreamBasicDescription desc = {0};
        desc.mSampleRate = _configuration.audioSampleRate; // 采样率
        desc.mFormatID = kAudioFormatLinearPCM;
        desc.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
        desc.mChannelsPerFrame = (UInt32)_configuration.numberOfChannels;
        desc.mFramesPerPacket = 1;
        desc.mBitsPerChannel = 16; // 表示每一个声道的音频数据要多少位,一个字节是8位,因此用8 * 每一个采样的字节数
        desc.mBytesPerFrame = desc.mBitsPerChannel / 8 * desc.mChannelsPerFrame;
        desc.mBytesPerPacket = desc.mBytesPerFrame * desc.mFramesPerPacket; // 根据mFormatFlags指定的Float类型非交错存储,就设置为bytesPerSample表示每一个采样的字节数。但若是是Interleaved交错存储的,就应该设置为bytesPerSample * mChannelsPerFrame 由于左右声道数据是交错存在一块儿的。
        

        // 链接扬声器
        AudioUnitSetProperty(self.componetInstance, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &desc, sizeof(desc));
        
        // 设置声音回调
        AURenderCallbackStruct cb;
        cb.inputProcRefCon = (__bridge void *)(self);
        cb.inputProc = handleInputBuffer;
        AudioUnitSetProperty(self.componetInstance, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 1, &cb, sizeof(cb));
        
        // 初始化AudioComponentInstance
        status = AudioUnitInitialize(self.componetInstance);
        
        if (noErr != status) {
            [self handleAudioComponentCreationFailure];
        }
        
        [session setPreferredSampleRate:_configuration.audioSampleRate error:nil];
        /// AVAudioSessionCategoryPlayAndRecord 支持音频播放和录音、打断其余不支持混音APP、不会被静音键或锁屏键静音
        /// AVAudioSessionCategoryOptionDefaultToSpeaker 系统会自动选择最佳的内置麦克风组合支持视频聊天。
        /// AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers 支持和其余APP音频混合
        [session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker | AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers error:nil];
        [session setActive:YES withOptions:kAudioSessionSetActiveFlag_NotifyOthersOnDeactivation error:nil];
        
        [session setActive:YES error:nil];
复制代码
音频采样率
  • 音频采样率是指录音设备在一秒钟内对声音信号的采样次数,采样频率越高声音的还原就越真实越天然。
  • 在当今的主流采集卡上,采样频率通常共分为11025Hz、22050Hz、24000Hz、44100Hz、48000Hz五个等级,11025Hz能达到AM调幅广播的声音品质,而22050Hz和24000HZ能达到FM调频广播的声音品质,44100Hz则是理论上的CD音质界限,48000Hz则更加精确一些。
  • 通常音频质量采用44100Hz, 高等音频质量采用48000Hz

配置采样参数

音频配置:码率(128)和采样率(44100HZ)
视频配置:视频分辨率(720P )、码率(2000KB/S)和帧率(30FPS)
复制代码

音频录制实时转为 aac

苹果默认是 PCMweb

aac 是为了取代 MP3,压缩了原始文件,可是取决于比特率,合适的比特率人耳分辨不出算法

2、图像处理

将数据采集的输入流进行实时滤镜, 美颜
复制代码

特效滤镜处理缓存

GPUImage (开源) 只有iOS 小型的能够作 封装和思惟基于 OpenGL ES服务器

Metal 苹果 新开发的markdown

OpenGL PCsession

OpenGL ES 线路 手机框架

3、音视频编码(压缩)

截屏2021-05-16 上午11.11.23.png

1、音频编码

AudioToolBox FFmpeg AACide

软解码:使用 fdk_aac 将 PCM 转为 AAC
复制代码
经常使用音频压缩编码格式
有损压缩和无损压缩

人耳掩盖效应:去除冗余信息ui

目前使用有损压缩比较多编码

  • 有损压缩:解压后的数据彻底能够复原

  • 有损压缩:解压后的数据不能彻底复原,会丢失一部分信息,压缩比越大,丢失的信息越多,信号还原的失真就好越大

WAV
  • 未进行压缩:
  • 在 PCM 裸数据前面加了44个字节,包含了采样率,声道数,数据格式等信息
  • 音质很是好
MP3
  • 不错的压缩比,接近于 WAV
  • 高比特率下,128kbs表现很好,应用很广
AAC
  • 目前很热门的有损压缩格式
  • 多适用于小比特率下,而且多用于视频中的音频编码
OGG
  • 潜力 用于语音聊天场景
  • 可是软硬件支持问题

2、视频编码

VideoToolBox FFmpeg H264

软编码: FFmpeg X264
	用到CPU
	
硬编码 VideoToolBox AudioToolBox
商业项目 通用 硬编码   
	GPU (运算大于CPU)
	硬件加速器
	
	视频编码   VideoToolBox  FFmpeg   H264
	
	音频编码   AudioToolBox  FFmpeg   AAC
	
复制代码
编码到底作了什么
YUV  而不是用 rgba

1s 内 60张照片

10张为一组 进行压缩 

​	取第一帧为I帧,后面称之为 P帧,只保留和前一帧的不一样点

​	B 帧 是保留和后一帧的差别。

为了追求高压缩(如小视频),可以使用 I  P  B 

可是直播是为了追求高实时性,所以使用I P,而不使用B帧
复制代码

使用 VideoToolBox 框架的流程

一、建立编码会话 session
VTCompressionSessionCreate
复制代码
二、设置编码相关参数
  • 设置是否实时编码
  • 是否使用B帧
  • GOP 关键帧的间隔
  • 设置帧率
  • 设置码率
三、开始编码
四、循环获取采集数据
五、获取编码后的数据

经过 AVFoundation 获取捕获结果回调

截屏2021-05-17 下午2.19.25.png

  • 原始数据封装为 CVPixelBuffers,

    CVPixelBuffers 为主内存存储的全部像素点数据的对象
    复制代码
  • encode 为 CMSampleBuffers

截屏2021-05-17 下午2.26.19.png

六、将数据写入 H264 文件 获取到 SPS/PPS, 第一帧写入 SPS/PPS VideoToolBox 在没一个关键帧前面都会输出 SPS/PPS信息,若是本帧为关键帧,则能够取出对应的 PPS / SPS 信息。

4、推流

将采集的音视频信息经过流媒体协议发送到流媒体服务器
复制代码

推流技术

封包
  • 视频封装格式: FLV / TS
  • 音视频封装格式: MP3 / AAC
上传
  • 流媒体协议: RTMP / HLS / RTSP / FLV

5、流媒体服务器处理

  • 数据分发 CDN

  • 截屏

  • 录制

  • 实时转码

6、播放器

负责拉流、解码和播放
复制代码

拉流:

  • 流媒体协议: RTMP / HLS / RTSP / FLV

音视频解码

  • 硬解码 : videoToolbox audioToolBox
  • 软解码 : 视频 FFmpeg x264算法视频解码, 使用 fdk_aac 音频解码

7、流媒体协议的区别

RTMP

相对于 HLS 来讲,采用 RTMP 协议时,从采集推流端到流媒体服务器再到播放端是一条数据流,所以在服务器不会有落地文件。这样 RTMP 相对来讲就有这些优势:

延时较小,一般为 1-3s。
基于 TCP 长链接,不须要屡次建连。
所以业界大部分直播业务都会选择用 RTMP 做为流媒体协议。一般会将数据流封装成 FLV 经过 HTTP 提供出去。可是这样也有一些问题须要解决:

iOS 平台没有提供原生支持 RTMP 或 HTTP-FLV 的播放器,这就须要开发支持相关协议的播放器。
复制代码

HLS

HLS 的基本原理就是当采集推流端将视频流推送到流媒体服务器时,服务器将收到的流信息每缓存一段时间就封包成一个新的 ts 文件,同时服务器会创建一个 m3u8 的索引文件来维护最新几个 ts 片断的索引。当播放端获取直播时,它是从 m3u8 索引文件获取最新的 ts 视频文件片断来播放,从而保证用户在任什么时候候链接进来时都会看到较新的内容,实现近似直播的体验。相对于常见的流媒体直播协议,例如 RTMP 协议、RTSP 协议等,HLS 最大的不一样在于直播客户端获取到的并非一个完整的数据流,而是连续的、短时长的媒体文件,客户端不断的下载并播放这些小文件。这种方式的理论最小延时为一个 ts 文件的时长,通常状况为 2-3 个 ts 文件的时长。HLS 的分段策略,基本上推荐是 10 秒一个分片,这就看出了 HLS 的缺点:

一般 HLS 直播延时会达到 20-30s,而高延时对于须要实时互动体验的直播来讲是不可接受的。
HLS 基于短链接 HTTP,HTTP 是基于 TCP 的,这就意味着 HLS 须要不断地与服务器创建链接,TCP 每次创建链接时的三次握手、慢启动过程、断开链接时的四次挥手都会产生消耗。
不过 HLS 也有它的优势:

数据经过 HTTP 协议传输,因此采用 HLS 时不用考虑防火墙或者代理的问题。
使用短时长的分片文件来播放,客户端能够平滑的切换码率,以适应不一样带宽条件下的播放。
HLS 是苹果推出的流媒体协议,在 iOS 平台上能够得到自然的支持,采用系统提供的 AVPlayer 就能直接播放,不用本身开发播放器。
image
复制代码

HTTP FLV

先经过服务器将FLV下载到本地缓存,而后再经过NetConnection的本地链接来播放这个FLV,这种方法是播放本地的视频,并非播放服务器的视频。所以在本地缓存里能够找到这个FLV。其优势就是服务器下载完这个FLV,服务器就没有消耗了,节省服务器消耗。其缺点就是FLV会缓存在客户端,对FLV的保密性很差。
 是一种将直播流模拟成FLV文件,经过HTTP协议进行下载的模式来实现流媒体传输的协议,端口号80
 通常建议使用HTTP FLV,实时性和RTMP相等。
 优势:HTTP相比于RTMP省去了一些协议交互时间,首屏时间更短。HTTP可拓展的功能更多。
复制代码

协议差异

  • HLS:HTTP Live Streaming;基于短链接 HTTP;集合一段时间的数据生成 ts 切片文件,更新 m3u8 文件;延时 25s+。
  • RTMP:Real Time Messaging Protocal;基于长链接TCP;每一个时刻收到的数据当即转发;延时 1~3s。
  • HTTP-FLV: RTMP over HTTP;基于长链接 HTTP;每一个时刻收到的数据当即转发,使用 HTTP 协议;延时 1~3s。
相关文章
相关标签/搜索