亚马逊AWS官方博客

提高直播用户参与度 – 通过 BytePlus Effects 和 Amazon IVS 实现实时增强现实(AR)效果

本博客由 BytePlus 机器学习解决方案总监陈晓玉、亚马逊云科技客户成功工程师陈昊和亚马逊云科技解决方案架构师史天共同撰写。

根据 Sandvine 发布的“全球互联网现象报告”,在 2022 年,视频内容占全网总流量的 65% 以上,相比 2021 年所占比例提高了 24%。此外,支持创作者通过流媒体触达受众的平台数量也在不断增加,这一比例还将继续上升。

面对如此庞大、多样化且不断增长的市场,要使用户生成内容(UGC)平台在竞争中脱颖而出可能颇具挑战,很多平台通过添加增强现实(AR)等功能取得了成功。

BytePlus Effects 是一种具有丰富 AR 参与工具库的 SDK,为开发人员快速整合多种功能提供了范例。该 SDK 可离线使用,这意味着所有效果都在用户的设备上创建,无需将任何个人数据发送到云端。该方法具有诸多优点,其中性能提升和用户数据隐私保护可帮助平台与用户建立信任,对于打造积极的用户体验至关重要。

此外,BytePlus Effects 还可与实时流媒体 UGC 平台集成。在本博客中,我们将探讨如何使用该库创建一个向 Amazon Interactive Video Service (Amazon IVS) 广播的 iOS 应用。

先决条件

首先,示例要满足如下先决条件:

创建流媒体通道后,请注意 Amazon IVS 提供的以下信息:

  1. 提取服务器 URL
  2. 流密钥
  3. 回放 URL

有关创建 Amazon IVS 通道的完整指南,请参阅 Amazon IVS 入门指南

SDK 安装

为了简化 SDK 的安装和集成,建议通过 CocoaPods 集成 SDK,Amazon IVS 和 BytePlus 均支持 CocoaPods。有关更多信息,请参阅 Amazon IVS 广播 SDK (iOS) 安装指南和 BytePlus Effect SDK (iOS) 安装指南

集成步骤

Amazon IVS iOS 广播 SDK 提供了一个用于控制和拍摄设备摄像头图像以进行流传输的 API。为了与 BytePlus Effect SDK 集成,我们会提供应用逻辑,用于控制设备摄像头,使用 BytePlus Effect SDK 对图像进行处理,然后将处理后的图像通过 CustomImageSource 发送至 IVS 广播 SDK 以进行流传输。在本博客中,我们将使用 Objective-C 代码演示交互步骤,该代码兼容 Amazon IVS iOS 广播 SDK 和 BytePlus Effect SDK。

  1. 设置广播会话
  2. 设置 CustomImageSource 配置
  3. 设置视频拍摄
  4. 在本示例中,BytePlus Effect SDK 通过 BEVideoCapture 访问摄像头。在实际项目中,应用开发人员可以按需使用其选择的其他输入源
  5. 使用 BytePlus Effect SDK 处理拍摄的图像
  6. 将处理后的缓冲图像发送到 CustomImageSource
  7. 开始广播

设置广播会话

有关详细的 SDK 说明,可参阅 Amazon IVS 入门指南和 GitHub 上提供的示例应用程序

设置使用 CustomImageSource 的广播配置

CustomImageSource 是 Amazon IVS 广播 SDK 的一项高级功能,它支持应用开发人员提交来自自定义输入源的缓冲图像以进行流传输。

广播配置

如需使用 CustomImageSource 作为广播会话的输入源,首先需要设置广播配置。在本示例中,我们使用预设配置 standardPortrait

//创建广播配置
IVSBroadcastConfiguration *config = [[IVSPresets configurations] 
standardPortrait];

混合器插槽配置

为了将 CustomImageSource 作为输入源,使用广播 SDK 中的混合器功能来创建自定义混合器配置。混合器是视频处理单元,它接收来自多个输入源(插槽)的输入并生成一个用于提取的单一输出。为便于演示,本示例使用单个插槽。

// 设置混合器插槽配置
IVSMixerSlotConfiguration *customSlot = [IVSMixerSlotConfiguration new];
customSlot.size = config.video.size;
customSlot.position = CGPointMake(0.0, 0.0);
customSlot.preferredAudioInput = IVSDeviceTypeUserAudio;
customSlot.preferredVideoInput = IVSDeviceTypeUserImage;
NSError *customSlotError = nil;
NSString * const customSlotName = @"custom-slot";
[customSlot setName:customSlotName error:customSlotError];

// 将此插槽设置为之前创建的广播配置
config.mixer.slots = @[customSlot];

广播会话

最后,我们将使用之前的配置启动广播会话。

NSError *broadcastSessionError = nil;
// 将摄像头描述符参数设为 nil,让应用逻辑控制摄像头设备
IVSBroadcastSession *broadcastSession = [[IVSBroadcastSession alloc] initWithConfiguration:config descriptors:nil delegate:nil error:&broadcastSessionError];

// 附加自定义音频输入源
id customAudioSource = [broadcastSession createAudioSourceWithName:@"custom-audio"];
[broadcastSession attachDevice:customAudioSource toSlotWithName:customSlotName onComplete:nil];// <连接到 onComplete 回调函数>
self.customAudioSource = customAudioSource;

// 附加自定义图像输入源
id customImageSource = [broadcastSession createImageSourceWithName:@"custom-image"];
[broadcastSession attachDevice:customImageSource toSlotWithName:customSlotName onComplete:nil];// <连接到 onComplete 回调函数>
self.customImageSource = customImageSource;

self.broadcastSession = broadcastSession;

设置视频拍摄

为便于演示,我们使用 BytePlus Effect SDK 的 API BEVideoCapture 来设置视频拍摄。

BytePlus Effect SDK 初始化

self.imageUtils = [[BEImageUtils alloc] init];
self.manager = [[BEEffectManager alloc] initWithResourceProvider:[BEEffectResourceHelper new] licenseProvider:[BELicenseHelper shareInstance]];
// SDK 初始化
int ret = [self.manager initTask];

通过设备摄像头拍摄视频图像

BEVideoCapture *capture = [[BEVideoCapture alloc] init];
capture.outputFormat = kCVPixelFormatType_32BGRA;
// 为简洁起见,删除了设置 BEVideoCapture 初始配置的代码
[capture startRunning];

使用 BytePlus Effect SDK 处理拍摄的图像

BEVideoCapture 提供的以下 videoCapture 代理方法将传输通过设备摄像头拍摄的 CMSampleBufferRef 进行处理。

// BEVideoCapture 委托回调
- (void)videoCapture:(id<BEVideoSourceProtocol>)source didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer withRotation:(int)rotation {

// 为简洁起见,删除了实现 GLContext 检查的代码

    CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    CMTime sampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
    double timeStamp = (double)sampleTime.value/sampleTime.timescale;
    
// 为简洁起见,删除了为确保线程安全而锁定 BytePlus Effect SDK 的代码,例如:NSRecursiveLock
    [self processWithCVPixelBuffer:pixelBuffer rotation:rotation timeStamp:timeStamp];
}

使用 BytePlus Effect SDK 处理摄像头缓冲图像

实际的图像处理将通过下文的 processWithCVPixelBuffer 函数完成。

设置缓冲的输入和输出图像

- (void)processWithCVPixelBuffer:(CVPixelBufferRef)pixelBuffer rotation:(int)rotation timeStamp:(double)timeStamp {

    BEPixelBufferInfo *pixelBufferInfo = [self.imageUtils getCVPixelBufferInfo:pixelBuffer];

    // BytePlus Effect SDK 需要采用 BGRA 格式
    if (pixelBufferInfo.format != BE_BGRA) {
        pixelBuffer = [self.imageUtils transforCVPixelBufferToCVPixelBuffer:pixelBuffer outputFormat:BE_BGRA];
    }
    if (rotation != 0) {
        // BytePlus Effect SDK 所接收的纹理贴图必须是正的,在调用 SDK 之前,需要先旋转一下
        pixelBuffer = [self.imageUtils rotateCVPixelBuffer:pixelBuffer rotation:rotation];
    }
    
    // 设置缓冲的输入图像
    id<BEGLTexture> texture = [self.imageUtils transforCVPixelBufferToTexture:pixelBuffer];
    // 设置缓冲的输出图像
    id<BEGLTexture> outTexture = nil;
    outTexture = [self.imageUtils getOutputPixelBufferGLTextureWithWidth:texture.width height:texture.height format:BE_BGRA];

处理图像

我们使用的是 self.manager,它是我们之前在 BytePlus Effect SDK 初始化期间设置的 BEEffectManager 对象。

// self.manager 为 BytePlus Effect SDK 初始化过程中的 BEEffectManager
    // 为便于演示,我们将修正图像的旋转。
    int ret = [self.manager processTexture:texture.texture outputTexture:outTexture.texture width:texture.width height:texture.height rotate:BEF_AI_CLOCKWISE_ROTATE_0
timeStamp:timeStamp];
    if (ret != BEF_RESULT_SUC) {
        // 记录图像处理成功完成
    } else {
        // 记录图像处理未成功完成
    }
    outTexture = texture;

将处理后的缓冲图像发送到 CustomImageSource

处理后的缓冲图像准备就绪后,将其发送到前面步骤中设置的广播会话中的 CustomImageSource

if (self.broadcastSession != nil) {
        // 获取 CVPixelBufferRef
        CVPixelBufferRef outputPixelBuffer = [(BEPixelBufferGLTexture *)outTexture pixelBuffer];

        CMVideoFormatDescriptionRef formatDescription;
        CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, outputPixelBuffer, &formatDescription);

        
        CMSampleTimingInfo timing = { kCMTimeInvalid, kCMTimeInvalid, kCMTimeInvalid };
        CMSampleBufferRef sampleBuffer;
    
        // 转换为 CMSampleBufferRef
CMSampleBufferCreateReadyWithImageBuffer(kCFAllocatorDefault,
                                              outputPixelBuffer,
                                              formatDescription,
                                              &timing,
                                              &sampleBuffer);


        // 将 CMSampleBufferRef 提交至 customImageSource
        [self.customImageSource onSampleBuffer:sampleBuffer];
    }    
// 为简洁起见,删除了在 UI 中设置预览窗口的代码
}

开启视频广播

if (self.broadcastSession != nil) {
    
        NSURL *myURL = [NSURL URLWithString:@"<INGEST_URL>"];
        NSString *myKey = @"<STREAM_KEY>";
        
        [self.broadcastSession startWithURL:myURL streamKey:myKey error:nil];
    }

集成结果

正面修容效果演示。左图:iPhone 应用拍摄的源图像;
右图:Amazon IVS 通道的 HLS 输出

图像滤镜效果演示。左图:iPhone 应用拍摄的源图像;
右图:Amazon IVS 通道的 HLS 输出

这种集成可以进一步自定义,BytePlus Effect SDK 提供多种集成选项,用于处理各种图像源的图像,例如 BEImageCapture 或 BELocalVideoCapture

应用架构

最后,我们将 Amazon IVS 广播 SDK 和 BytePlus Effect SDK 集成到一个 iOS 移动应用中。我们使用 BytePlus Effect SDK 处理了通过设备摄像头拍摄的视频图像,并将它们提交给 IVS 广播 SDK 进行流传输。

关于 BytePlus

BytePlus 是字节跳动的一个部门,旨在为客户提供各种智能技术解决方案,帮助客户最大程度地实现业务增长。该部门构成了一支专门的专家团队,与客户携手合作,帮助客户打造更优质的产品,提供更优质的体验,最终实现业务增长。

本篇作者

陈晓玉

BytePlus AI 解决方案总监,负责 AI toB 产品海外商业化和产品化,包括 BytePlus Effects、AR、Video Editor、Audio、Media Processing Services 等产品线。本科毕业于南洋理工大学,研究生毕业于新加坡国立大学。拥有丰富的国内外 AI/ML 产品和项目经验。曾主导多个面向东南亚、日韩、出海等市场的互娱、文旅、银行、保险公司的 AI/ML 项目。

陈昊

Amazon IVS 客户成功工程师,其职责是帮助亚马逊云科技客户使用 Amazon IVS 构建创新的实时流解决方案。

史天

亚马逊云科技高级解决方案架构师。拥有丰富的云计算、数据分析和机器学习经验,目前致力于数据科学、机器学习、无服务器等领域的研究和实践。译有《机器学习即服务》《基于 Kubernetes 的 DevOps 实践》《Kubernetes 微服务实战》《Prometheus 监控实战》《云原生时代的 CoreDNS 学习指南》等。