服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - IOS - 如何在IOS上使用ReplayKit与RTC

如何在IOS上使用ReplayKit与RTC

2021-06-06 17:18声网Agora IOS

这篇文章主要介绍了如何在IOS上使用ReplayKit 与 RTC,对IOS音视频感兴趣的同学,一定要看一下

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

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

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 上,我们不难看出,刚好能符合我们上述对媒体流的需求。

视频格式

 

?
1
2
3
4
5
guard let videoFrame = CMSampleBufferGetImageBuffer(sampleBuffer) else {
    return
}
        
let type = CVPixelBufferGetPixelFormatType(videoFrame)
?
1
type = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange

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

帧率

 

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

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

?
1
agoraKit.pushExternalVideoFrame(frame)

插入一个小知识

 

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

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

音频

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

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

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

AudioApp

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

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

AudioMic

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

音频前处理

 

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
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,只要进来的流为单声道,我们都将它转化为双声道;

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;

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

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

?
1
2
3
4
5
6
7
8
9
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 缓存区);

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

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

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

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

原文链接:https://blog.csdn.net/agora_cloud/article/details/113248712

延伸 · 阅读

精彩推荐
  • IOSiOS 雷达效果实例详解

    iOS 雷达效果实例详解

    这篇文章主要介绍了iOS 雷达效果实例详解的相关资料,需要的朋友可以参考下...

    SimpleWorld11022021-01-28
  • IOSIOS 屏幕适配方案实现缩放window的示例代码

    IOS 屏幕适配方案实现缩放window的示例代码

    这篇文章主要介绍了IOS 屏幕适配方案实现缩放window的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要...

    xiari5772021-06-01
  • IOSiOS通过逆向理解Block的内存模型

    iOS通过逆向理解Block的内存模型

    自从对 iOS 的逆向初窥门径后,我也经常通过它来分析一些比较大的应用,参考一下这些应用中某些功能的实现。这个探索的过程乐趣多多,不仅能满足自...

    Swiftyper12832021-03-03
  • IOS关于iOS自适应cell行高的那些事儿

    关于iOS自适应cell行高的那些事儿

    这篇文章主要给大家介绍了关于iOS自适应cell行高的那些事儿,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的...

    daisy6092021-05-17
  • IOS解析iOS开发中的FirstResponder第一响应对象

    解析iOS开发中的FirstResponder第一响应对象

    这篇文章主要介绍了解析iOS开发中的FirstResponder第一响应对象,包括View的FirstResponder的释放问题,需要的朋友可以参考下...

    一片枫叶4662020-12-25
  • IOSiOS布局渲染之UIView方法的调用时机详解

    iOS布局渲染之UIView方法的调用时机详解

    在你刚开始开发 iOS 应用时,最难避免或者是调试的就是和布局相关的问题,下面这篇文章主要给大家介绍了关于iOS布局渲染之UIView方法调用时机的相关资料...

    windtersharp7642021-05-04
  • IOSiOS中tableview 两级cell的展开与收回的示例代码

    iOS中tableview 两级cell的展开与收回的示例代码

    本篇文章主要介绍了iOS中tableview 两级cell的展开与收回的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    J_Kang3862021-04-22
  • IOSIOS开发之字典转字符串的实例详解

    IOS开发之字典转字符串的实例详解

    这篇文章主要介绍了IOS开发之字典转字符串的实例详解的相关资料,希望通过本文能帮助到大家,让大家掌握这样的方法,需要的朋友可以参考下...

    苦练内功5832021-04-01