如何在IOS上使用ReplayKit与RTC

在日益繁多的直播场景中,如果你也是某位游戏主播的粉丝的话,有一种直播方式是你一定不陌生的,那就是我们今天要聊的屏幕分享。

直播场景下的屏幕分享,不仅要将当前显示器所展示的画面分享给远端,也要将声音传输出去,包括应用的声音,以及主播的声音。鉴于这两点需求,我们可以简单分析出,进行一次屏幕分享的直播所需要的媒体流如下:

  1. 一条显示器画面的视频流
  2. 一条应用声音的音频流
  3. 一条主播声音的音频流

ReplayKit 是苹果提供的用于 iOS 系统进行屏幕录制的框架。

首先我们来看看苹果提供的用于屏幕录制的 ReplayKit 的数据回调接口:

override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
        DispatchQueue.main.async {
            switch sampleBufferType {
            case .video:
                AgoraUploader.sendVideoBuffer(sampleBuffer)
            case .audioApp:
                AgoraUploader.sendAudioAppBuffer(sampleBuffer)
            case .audioMic:
                AgoraUploader.sendAudioMicBuffer(sampleBuffer)
            @unknown default:
                break
            }
        }
    }

从枚举 sampleBufferType 上,我们不难看出,刚好能符合我们上述对媒体流的需求。

视频格式

guard let videoFrame = CMSampleBufferGetImageBuffer(sampleBuffer) else {
    return
}

let type = CVPixelBufferGetPixelFormatType(videoFrame)
type = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange

通过 CVPixelBufferGetPixelFormatType,我们可以获取到每帧的视频格式为 yuv420

帧率

通过打印接口的回调次数,可以知道每秒能够获取的视频帧为30次,也就是帧率为 30。

格式与帧率都能符合 Agora RTC 所能接收的范围,所以通过 Agora RTC 的 pushExternalVideoFrame 就可以将视频分享到远端了。

agoraKit.pushExternalVideoFrame(frame)

插入一个小知识

显示器所显示的帧来自于一个帧缓存区,一般常见的为双缓存或三缓存。当屏幕显示完一帧后,发出一个垂直同步信号(V-Sync),告诉帧缓存区切换到下一帧的缓存上,然后显示器开始读取新的一帧数据做显示。

这个帧缓存区是系统级别的,一般的开发者是无法读取跟写入的。但是如果是苹果自身提供的录制框架 ReplayKit 能够直接读取到已经渲染好且将用于显示器的帧,且这一过程不会影响渲染流程而造成掉帧,那就能减少一次用于提供给 ReplayKit 回调数据的渲染过程。

音频

ReplayKit 能提供的音频有两种,分为麦克风录制进来的音频流,与当前响应的应用播放的音频流。(下文将前者称为 AudioMic,后者为 AudioApp)

可以通过下面的两行代码,来获取音频格式

CMAudioFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);
const AudioStreamBasicDescription *description = CMAudioFormatDescriptionGetStreamBasicDescription(format);

AudioApp

AudioApp 会在不同的机型下有不一样的声道数。例如在 iPad 或 iPhone7 以下机型中,不具备双声道播放的设备,这时候 AudioApp 的数据就是单声道,反之则是双声道。

采样率在部分试过的机型里,都是 44100,但不排除在未测试过的机型会是其他的采样率。

AudioMic

AudioMic 在测试过的机型里,采样率为 32000,声道数为单声道。

音频前处理

如果我们将 AudioApp 与 AudioMic 作为两条音频流去发送,那么流量肯定是大于一条音频流的。我们为了节省一条音频流的流量,就需要将这两条音频流做混音(融合)。

但是通过上述,我们不难看出,两条音频流的格式是不一样的,而且不能保证随着机型的不同,是不是会出现其他的格式。在测试的过程中还发现 OS 版本的不同,每次回调给到的音频数据长度也会出现变化。那么我们在对两条音频流做混音前,就需要进行格式统一,来应对 ReplayKit 给出的各种格式。所以我们采取了以下几个重要的步骤:

if (channels == 1) {
    int16_t* intData = (int16_t*)dataPointer;
    int16_t newBuffer[totalSamples * 2];

    for (int i = 0; i < totalSamples; i++) {
        newBuffer[2 * i] = intData[i];
        newBuffer[2 * i + 1] = intData[i];
    }
    totalSamples *= 2;
    memcpy(dataPointer, newBuffer, sizeof(int16_t) * totalSamples);
    totalBytes *= 2;
    channels = 2;
}

无论是 AudioMic 还是 AudioApp,只要进来的流为单声道,我们都将它转化为双声道;

if (sampleRate != resampleRate) {
    int inDataSamplesPer10ms = sampleRate / 100;
    int outDataSamplesPer10ms = (int)resampleRate / 100;

    int16_t* intData = (int16_t*)dataPointer;

    switch (type) {
        case AudioTypeApp:
            totalSamples = resampleApp(intData, dataPointerSize, totalSamples,
                                       inDataSamplesPer10ms, outDataSamplesPer10ms, channels, sampleRate, (int)resampleRate);
            break;
        case AudioTypeMic:
            totalSamples = resampleMic(intData, dataPointerSize, totalSamples,
                                       inDataSamplesPer10ms, outDataSamplesPer10ms, channels, sampleRate, (int)resampleRate);
            break;
    }

    totalBytes = totalSamples * sizeof(int16_t);
}

无论是 AudioMic 还是 AudioApp,只要进来的流采样率不为 48000,我们将它们重采样为 48000;

memcpy(appAudio + appAudioIndex, dataPointer, totalBytes);
appAudioIndex += totalSamples;
memcpy(micAudio + micAudioIndex, dataPointer, totalBytes);
micAudioIndex += totalSamples;

通过第一步与第二步,我们保证了两条音频流都为同样的音频格式。但是由于 ReplayKit 是一次回调给到一种数据的,所以在混音前我们还得用两个缓存区来存储这两条流数据;

int64_t mixIndex = appAudioIndex > micAudioIndex ? micAudioIndex : appAudioIndex;

int16_t pushBuffer[appAudioIndex];

memcpy(pushBuffer, appAudio, appAudioIndex * sizeof(int16_t));

for (int i = 0; i < mixIndex; i ++) {
   pushBuffer[i] = (appAudio[i] + micAudio[i]) / 2;
}

ReplayKit 有选项是否开启麦克风录制,所以在关闭麦克风录制的时候,我们就只有一条 AudioApp 音频流。所以我们以这条流为主,去读取 AudioMic 缓存区的数据长度,然后对比两个缓存区的数据长度,以最小的数据长度为我们的混音长度。将混音长度的两个缓存区里的数据做融合,得到混音后的数据,写入一个新的混音缓存区(或者直接写入 AudioApp 缓存区);

[AgoraAudioProcessing pushAudioFrame:(*unsigned* *char* *)pushBuffer
                                   withFrameSize:appAudioIndex * *sizeof*(int16_t)];

最后我们再将这段混音后的数据拷贝进 Agora RTC 的 C++ 录制回调接口里,这时候就可以把麦克风录制的声音与应用播放的声音传输到远端了。

通过对音视频流的处理,结合 Agora RTC SDK,我们就完成了一个屏幕分享直播场景的实现了。

以上就是如何在IOS上使用ReplayKit与RTC的详细内容,更多关于IOS上使用ReplayKit与RTC的资料请关注我们其它相关文章!

(0)

相关推荐

  • iOS开发中音频视频播放的简单实现方法

    前言 我们在平时的iOS开发中,音视频的播放有很多种,目前系统的自带的都属于 AVFoundation 框架,更加接近于底层,所以灵活性很强,更加方便自定义 还有就是第三方音视频视频播放,特点是功能强大,实现简单,支持流媒体,下面来逐一介绍,给大家参考学习,下面来一起看看详细的介绍吧. 播放系统音效或者短音效 注意: 这里的资源长度最多30秒 资源必须在 Target --> Build Phases --> Copy Bundle Resources 引入资源文件,否则获取不到文件 if l

  • 代码详解iOS视频直播弹幕功能

    本篇内容通过步骤详细给大家讲解了iOS视频直播弹幕的原理以及实现代码分析,以下就是全部内容: 1.弹幕的实现性分析 首先,从视觉上明确当前弹幕所具有的功能 从屏幕右侧滑入左侧,直至完全消失 不管是长的弹幕,还是短的弹幕,速度一致(可能有的需求是依据弹幕长度,调整速度) 有弹幕轨道,不是随机产生的弹幕 弹幕不会进行重叠 接下来从功能角度思考需要做什么 重用机制,类似tableView有一个重用池,每个弹幕就是一个cell,当有弹幕发送的时候,如果当前的重用池没有控件,则创建一个新的控件,如果重用池

  • 浅析iOS中视频播放的几种方案

    1.AVPlayer (1) 优缺点 优点:可以自定义 UI, 进行控制 缺点:单纯的播放,没有控制 UI(进度,暂停,播放等按钮),而且如果要显示播放界面, 需要借助AVPlayerLayer, 添加图层到需要展示的图层上 (2)实现远程视频播放 实现播放功能(只有声音) 1.导入框架 #import <AVFoundation/AVFoundation.h> 2.通过远程 URL 创建 AVPlayer 对象 NSURL *remoteURL = [NSURL URLWithString:

  • IOS 开发之ios视频截屏的实现代码

    IOS 开发之ios视频截屏的实现代码 现在好多视频截屏软件,这里提供一个IOS 视频截屏的方法,大家可以参考下, 实现代码: //截屏 static int i=0; -(IBAction)screenShot:(id)sender{ UIGraphicsBeginImageContextWithOptions(CGSizeMake(640, 960), YES, 0); [[self.window layer] renderInContext:UIGraphicsGetCurrentCont

  • 详解iOS视频播放方式

    多媒体这整个系列的文章自己也准备好开始整理了,先从视频音频最简单也是最常用的播放出发慢慢的往下深究,探索到底层的编码解码等等,这篇文章就从视频的播放这个最简单的说起. iOS的视频播放方式有几种?其实要是只是简单的想播放一段视频并且对UI没什么要求的话的确比较简单,很容易搞定,但我相信这种情况除了你的Demo一般是不会出现的,对播放UI的定义以及可能有各种不同的需求对应着你是不能随便写个播放器就没事了的. 最原始的播放 要不是刚接触iOS开发的同学应该是知道MediaPlayer这个框架的,要是

  • 详解iOS应用中播放本地视频以及选取本地音频的组件用法

    MPMoviePlayerControlle播放本地视频 MPMoviePlayerControlle与AVAudioPlayer有点类似,前者播放视频,后者播放音频,不过也有很大不同,MPMoviePlayerController 可以直接通过远程URL初始化,而AVAudioPlayer则不可以.不过大体上用起来感觉差不多.废话少说进入体验. 格式支持:MOV.MP4.M4V.与3GP等格式,还支持多种音频格式. 首先你得引入 MediaPlayer.framework.然后在使用到MPMo

  • 如何在IOS上使用ReplayKit与RTC

    在日益繁多的直播场景中,如果你也是某位游戏主播的粉丝的话,有一种直播方式是你一定不陌生的,那就是我们今天要聊的屏幕分享. 直播场景下的屏幕分享,不仅要将当前显示器所展示的画面分享给远端,也要将声音传输出去,包括应用的声音,以及主播的声音.鉴于这两点需求,我们可以简单分析出,进行一次屏幕分享的直播所需要的媒体流如下: 一条显示器画面的视频流 一条应用声音的音频流 一条主播声音的音频流 ReplayKit 是苹果提供的用于 iOS 系统进行屏幕录制的框架. 首先我们来看看苹果提供的用于屏幕录制的 R

  • 如何在iOS上使用MVVM进行路由详解

    前言 我已经在几个项目中使用MVVM了一段时间,我真的很喜欢它的简单性.特别是,如果你像许多人一样从MVC迁移,你只需要在你的架构中增加一层ViewModel.如果您发现太多层级造成的复杂,这确实使事情变得更容易. 这是一个良好的开端,但这种简单并不总是好的.在MVVM中,您将业务逻辑移出视图控制器(VC),然后意识到它仍然很胖.视图模型(VM)现在具有业务逻辑,但是展示数据(格式化)或路由如何?他们仍然被困在VC中,我们需要将它们移出. #示例流程 假设我们正在实现登陆页面,如下所示. ##路

  • 如何在 iOS 应用中添加位置信息

    最近要在 iOS 应用中添加位置信息, 需要满足的需求如下: 应用在前台时能够获取位置信息: 通过切换. Home 按键将应用切换到后台时,停止获取位置信息: 应用程序在前台运行, 直接锁定屏幕时,能够继续获取位置信息: 接下来逐步实现这三个需求. 获取设备位置信息 在 iOS 上获取位置信息是很容易的, 网上的资料也很多, 我的代码如下: // make sure location service is enabled. if (!CLLocationManager.LocationServi

  • 如何在IOS中使用IBeacon

    什么是iBeacon? iBeacon 是苹果公司2013年9月发布的移动设备用OS(iOS7)上配备的新功能.其工作方式是,配备有低功耗蓝牙(BLE)通信功能的设备使用BLE技术向周围发送自己特有的 ID,接收到该 ID 的应用软件会根据该 ID 采取一些行动. 从个人的角度看: iBeacon向四面八方不停地广播信号,就像是往平静的水面上扔了一块石子,泛起层层涟漪(俗称水波),波峰相当于 iBeacon 的RSSI(接受信号强度指示),越靠近中心点的地方波峰越高(RSSI 越大),这个波峰的

  • 如何在iOS中高效的加载图片详解

    目录 前言 图片的渲染流程 DataBuffer SD源码分析 ImageBuffer 占用内存大小 Xcode测试 如何减少图像占用内存 向下采样 SD源码分析解码过程 选择正确的图片渲染格式 渲染格式 如何正确的选择渲染格式 减少后备存储器的使用 减少或者不使用 draw(rect:) 方法 如何在列表中加载图片 线程爆炸 总结 前言 在iOS开发中,图片(UIImage)是我们在开发中,占用手机内存比较大的对象,如果在运行过程中,内存占用过大,对电池寿命会造成影响,如果超过了内存占用的最大

  • Android仿IOS上拉下拉弹性效果的实例代码

    用过iphone的朋友相信都体验过页面上拉下拉有一个弹性的效果,使用起来用户体验很好:Android并没有给我们封装这样一个效果,我们来看下在Android里如何实现这个效果.先看效果,感觉有些时候还是蛮实用的. 思路:其实原理很简单,实现一个自定义的Scrollview方法(来自网上大神),然后在布局文件中使用自定义方法Scrollview就可以了. 代码: 自定义View,继承自Scrollview.MyReboundScrollView类 package com.wj.myrebounds

  • 如何在IDEA上安装scala插件并创建工程(图文教程)

      大家好,我是不温卜火,是一名计算机学院大数据专业大二的学生,昵称来源于成语-不温不火,本意是希望自己性情温和.作为一名互联网行业的小白,博主写博客一方面是为了记录自己的学习过程,另一方面是总结自己所犯的错误希望能够帮助到很多和自己一样处于起步阶段的萌新.但由于水平有限,博客中难免会有一些错误出现,有纰漏之处恳请各位大佬不吝赐教!暂时只有csdn这一个平台,博客主页:https://buwenbuhuo.blog.csdn.net/   关于scala的安装并配置环境变量的问题,小伙伴们感兴趣

  • 如何在Android上使用opencv

    1.下载OpenCV的Android包并解压缩(https://opencv.org/releases/) 2.创建Android应用或者在现有应用中,导入OpenCV模块 导入目录时选择Opencv Android中的sdk / java目录 3.修改导入的Opencv模块的build.gradle,使compileSdkVersion.buildToolsVersion.minSdkVersion.targetSdkVersion与app的build.gradle中的一致. 4.修改导入Op

  • 如何在mac上用docker对Oracle进行部署使用

    如何在mac上用docker对Oracle进行部署使用 首先安装docker 安装docker可以直接去官网进行下载,但是貌似官网的速度有点慢,这边有条件的推荐复制下载链接在迅雷进行下载(有会员的话会更快哦-) docker官网 dockerMac版下载链接 其他系统需要安装docker请自行去官网选择系统安装 下载完成后的安装需要把Docker的图标拖进application中,安装成功之后会在mac启动台中出现Docker的Logo. 同时安装成功之后在Mac顶端会出现Docker的图标,如

  • 如何在Mac上通过docker配置PHP开发环境

    使用docker-compose配置开发环境 一般一个基本的PHP开发环境包括PHP.PHP-FPM.WEB服务器.MySQL数据库,另外还会有Redis或memcache等相关NoSQL服务.我主要是通过docker-compose来配置服务. 什么是docker-compose docker-compose是一个通过YAML文件来定义项目,项目中包含单个或多个容器服务.一般配置文件名为:docker-compose.yml. 目录结构 你可以按自己的喜好组织项目,下面是我用的方法,app 目

随机推荐