iOS

火山美颜

更新时间: 2025/06/30 18:26:46

网易云信 NERTC SDK 支持接入字节跳动火山引擎智能美化特效 SDK(下文简称火山美颜 SDK),实现美颜、美妆、滤镜、贴纸等丰富美颜特效。在视频社交、在线教育、直播互动等场景中,您可以快速构建具备专业美颜能力的应用,提升用户在视频通话或直播过程中的形象表现力。

功能概述

通过集成火山美颜 SDK,您可以在 NERTC 音视频通话中实现以下功能:

  • 美化滤镜:支持美颜、滤镜、美妆、微整形、美体特效等多种技术。
  • 贴纸道具:抖音同款上万款爆款特效贴纸,效果逼真、创新有趣。
  • 特效创作工具:简单易操作,支持 2D、3D 多种特效能力,为企业设计师提供可视化特效制作工具。
  • 端上智能算法:200+ 自研视觉算法,低能耗、高精度、高召回。

工作原理

火山美颜原理
  1. NERTC SDK 通过视频采集回调接口 onNERtcEngineVideoFrameCaptured 将采集到的视频帧数据回调给应用层。
  2. 应用层将视频帧传递给火山美颜 SDK 进行美颜处理。
  3. 火山美颜 SDK 处理完成后,将美颜后的视频帧返回给应用层。
  4. 应用层将美颜后的视频帧返回给 NERTC SDK 进行编码和传输。

注意事项

  • OpenGL 上下文:火山美颜 SDK 全程依赖 OpenGL 环境,必须保证所有 SDK 的函数调用都处于同一个 context 下。在每次调用火山美颜 SDK 接口之前,必须设置正确的 OpenGL 上下文。
  • 像素格式转换:NERTC SDK 采集的像素格式可能与火山美颜 SDK 所需的格式不一致,需要进行格式转换。
  • 图像旋转:火山美颜 SDK 接收的纹理必须是正向的,可能需要在调用 SDK 前调整图像旋转角度。
  • 内存管理:在处理过程中创建的临时 pixelBuffer 需要手动管理内存,防止内存泄漏。

前提条件

在集成火山美颜 SDK 前,请确保您已完成以下准备工作:

第一步:集成 SDK

  1. 集成网易云信 NERTC SDK。如果您已完成本步骤,请跳过此步。

    1. 根据 更新日志 选择 NERTC SDK 版本号。

    2. 使用 CocoaPods 集成 NERTC SDK:

      Rubypod 'NERtcSDK', '~> 5.8.20' # 请使用您项目中的实际版本
      
  2. 集成火山美颜 SDK。

    若您的项目使用 CocoaPods 管理依赖,请按以下步骤操作:

    1. 打开 iossample_static 文件夹,拷贝 BytedEffectSDK.podspeclibeffect-sdk.ainclude 文件夹到您的项目根目录(与 Podfile 同级)。

    2. Podfile 中添加依赖:

      Rubypod 'BytedEffectSDK', :path => './'
      
    3. 执行以下命令安装依赖:

      bashpod install
      
    4. 打开 项目名.xcworkspace,确认 Pods/Development Pods 目录下已包含 BytedEffectSDK

第二步:准备资源

  1. 添加美颜素材资源。

    将火山美颜提供的素材包(通常是 resource 文件夹)添加到您的 Xcode 工程中,详细介绍请参考《火山美颜官网》素材包结构说明(4.2.1 及以上),常见的资源包括:

    • LicenseBag.bundle
    • ModelResource.bundle
    • ComposeMakeup.bundle
    • StickerResource.bundle
    • FilterResource.bundle
  2. 配置 Core 模块。

    1. iossample_static 文件夹中的 Core/Core 目录拷贝至您的工程中并引入项目。

    2. 修改 BELicenseHelper.mm 中的 LICENSE_MODEOFFLINE_LICENSE

    3. 修改 Config.h 中的 LICENSE_NAME 为您获取的绑定了应用包名的证书文件名。

第三步:初始化

  1. 引入必要的头文件。

    Objective-C#import "BEImageUtils.h"
    #import "BEEffectManager.h"
    #import "BEEffectResourceHelper.h"
    #import "BELicenseHelper.h"
    
  2. 创建 OpenGL 上下文。

    Objective-C@property (nonatomic, strong) EAGLContext *glContext;
    // 初始化 OpenGL 上下文
    // 创建 OpenGL ES 3.0 上下文
    self.glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    // 设置上下文
    if ([EAGLContext currentContext] != self.glContext) {
        [EAGLContext setCurrentContext:self.glContext];
    }
    
  3. 初始化火山美颜 SDK。

    Objective-C@property (nonatomic, strong) BEEffectManager *beEffectManager;
    @property (nonatomic, strong) BEImageUtils *imageUtils;
    
    ...
    
    // 初始化图像工具类
    self.imageUtils = [[BEImageUtils alloc] init];
    
    // 初始化特效管理器
    self.beEffectManager = [[BEEffectManager alloc] initWithResourceProvider:[BEEffectResourceHelper new]
                                                            licenseProvider:[BELicenseHelper shareInstance]];
    // 初始化 SDK
    int ret = [self.beEffectManager initTask];
    if (ret == BEF_RESULT_SUC) {
        NSLog(@"火山美颜 SDK 初始化成功");
    } else {
        NSLog(@"火山美颜 SDK 初始化失败,错误码: %d", ret);
    }
    
  4. 加载美颜资源包。

    代码以美颜和微整形为例,其余资源请参考 ComposeMakeup.bundle/ComposeMakeup 目录。

    Objective-C// 加载美颜和微整形资源
    NSArray *nodes = @[@"/beauty_4Items", @"/beauty_IOS_lite", @"/reshape_lite"];
    [self.beEffectManager updateComposerNodes:nodes];
    

第四步:视频处理流程

  1. 开启 NERTC SDK 的视频采集回调。

    在加入房间之前,需要开启 NERTC SDK 的视频采集回调功能:

    Objective-CNSDictionary *params = @{kNERtcKeyVideoCaptureObserverEnabled : @YES};
    [[NERtcEngine sharedEngine] setParameters:params];
    
  2. 实现 NERTC 视频帧捕获回调。

    onNERtcEngineVideoFrameCaptured 协议的回调方法中处理视频帧:

    Objective-C// 注意:
    // * OpenGLES 上下文
    // * rotateCVPixelBuffer 方法输出的 pixelBuffer 需要手动释放
    // * processTexture 方法得到的 outTexture.pixelBuffer 等同于 transforCVPixelBufferToTexture 输入的 pixelBuffer
    - (void)onNERtcEngineVideoFrameCaptured:(CVPixelBufferRef)pixelBuffer
                                  rotation:(NERtcVideoRotationType)rotation
                              contentHint:(NERtcVideoContentHint)contentHint {
        // 确保正确的 OpenGL 上下文
        if ([EAGLContext currentContext] != self.glContext) {
            [EAGLContext setCurrentContext:self.glContext];
        }
    
        // 参数准备
        double timeStamp = [[NSDate date] timeIntervalSince1970];
        CVPixelBufferRef srcPixelBuffer = pixelBuffer;
        CVPixelBufferRef dstPixelBuffer = pixelBuffer;
        CVPixelBufferRef rotatedPixelBuffer = NULL;
    
        // 格式转换:NV12 -> BGRA
        BEPixelBufferInfo *pixelBufferInfo = [self.imageUtils getCVPixelBufferInfo:srcPixelBuffer];
        if (pixelBufferInfo.format != BE_BGRA) {
            dstPixelBuffer = [self.imageUtils transforCVPixelBufferToCVPixelBuffer:srcPixelBuffer outputFormat:BE_BGRA];
        }
    
        // 旋转图像至 0 度
        if (rotation != kNERtcVideoRotation_0) {
            rotatedPixelBuffer = [self.imageUtils rotateCVPixelBuffer:dstPixelBuffer rotation:rotation];
            dstPixelBuffer = rotatedPixelBuffer;
        }
    
        // 准备纹理
        id<BEGLTexture> texture = [self.imageUtils transforCVPixelBufferToTexture:dstPixelBuffer];
        BEPixelBufferGLTexture *outTexture = [self.imageUtils getOutputPixelBufferGLTextureWithWidth:texture.width
                                                                                          height:texture.height
                                                                                          format:BE_BGRA];
    
        // 处理纹理(应用美颜)
        int ret = [self.beEffectManager processTexture:texture.texture
                                       outputTexture:outTexture.texture
                                              width:texture.width
                                             height:texture.height
                                             rotate:[self getDeviceOrientation]
                                          timeStamp:timeStamp];
    
        if (ret != BEF_RESULT_SUC) {
            outTexture = texture;
        }
    
        // 旋转图像回到原始角度
        if (rotation != kNERtcVideoRotation_0) {
            CVPixelBufferRef newPixelBuffer = [self.imageUtils rotateCVPixelBuffer:outTexture.pixelBuffer
                                                                     rotation:(360 - (int)rotation)];
            CFRelease(rotatedPixelBuffer);
            rotatedPixelBuffer = newPixelBuffer;
            dstPixelBuffer = rotatedPixelBuffer;
        } else {
            dstPixelBuffer = outTexture.pixelBuffer;
        }
    
        // 格式转换:BGRA -> NV12
        CVPixelBufferRef finalPixelBuffer = [self.imageUtils transforCVPixelBufferToCVPixelBuffer:dstPixelBuffer
                                                                              outputFormat:BE_YUV420F];
    
        // 将处理后的数据复制回原始 PixelBuffer
        [self copyPixelBuffer:finalPixelBuffer toPixelBuffer:srcPixelBuffer];
    
        // 释放临时创建的 PixelBuffer
        if (rotatedPixelBuffer) {
            CFRelease(rotatedPixelBuffer);
        }
    }
    
  3. 设备方向获取辅助方法。

    Objective-C- (bef_ai_rotate_type)getDeviceOrientation {
        UIDeviceOrientation orientation = [[UIDevice currentDevice] orientation];
        switch (orientation) {
            case UIDeviceOrientationPortrait:
                return BEF_AI_CLOCKWISE_ROTATE_0;
            case UIDeviceOrientationPortraitUpsideDown:
                return BEF_AI_CLOCKWISE_ROTATE_180;
            case UIDeviceOrientationLandscapeLeft:
                return BEF_AI_CLOCKWISE_ROTATE_270;
            case UIDeviceOrientationLandscapeRight:
                return BEF_AI_CLOCKWISE_ROTATE_90;
            default:
                return BEF_AI_CLOCKWISE_ROTATE_0;
        }
    }
    
  4. PixelBuffer 复制辅助方法。

    Objective-C- (void)copyPixelBuffer:(CVPixelBufferRef)srcPixelBuffer toPixelBuffer:(CVPixelBufferRef)outPixelBuffer {
        if (!srcPixelBuffer || !outPixelBuffer) {
            NSLog(@"%s, buffer is invalid", __func__);
            return;
        }
    
        OSType srcType = CVPixelBufferGetPixelFormatType(srcPixelBuffer);
        OSType dstType = CVPixelBufferGetPixelFormatType(outPixelBuffer);
        if ((srcType != kCVPixelFormatType_420YpCbCr8BiPlanarFullRange &&
            srcType != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) ||
            (dstType != kCVPixelFormatType_420YpCbCr8BiPlanarFullRange &&
             dstType != kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)) {
            NSLog(@"%s, format is not supported", __func__);
            return;
        }
    
        CVPixelBufferLockBaseAddress(srcPixelBuffer, 0);
        CVPixelBufferLockBaseAddress(outPixelBuffer, 0);
    
        size_t height = CVPixelBufferGetHeight(srcPixelBuffer);
    
        // Y 分量
        void *yBaseAddress = CVPixelBufferGetBaseAddressOfPlane(srcPixelBuffer, 0);
        size_t yBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(srcPixelBuffer, 0);
        size_t yLength = yBytesPerRow * height;
    
        // UV 分量
        void *uvBaseAddress = CVPixelBufferGetBaseAddressOfPlane(srcPixelBuffer, 1);
        size_t uvBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(srcPixelBuffer, 1);
        size_t uvLength = uvBytesPerRow * height / 2;
    
        void *yBaseAddressOut = CVPixelBufferGetBaseAddressOfPlane(outPixelBuffer, 0);
        memcpy(yBaseAddressOut, yBaseAddress, yLength);
        void *uvBaseAddressOut = CVPixelBufferGetBaseAddressOfPlane(outPixelBuffer, 1);
        memcpy(uvBaseAddressOut, uvBaseAddress, uvLength);
    
        CVPixelBufferUnlockBaseAddress(outPixelBuffer, 0);
        CVPixelBufferUnlockBaseAddress(srcPixelBuffer, 0);
    }
    

第五步:设置美颜特效

火山美颜 SDK 提供丰富的美颜特效功能,您可以根据需求设置不同的美颜效果:

  1. 设置美颜参数。参数分类和 key 名称请参考《火山美颜官网》素材 key 对应说明(4.2.1 及以上)

    Objective-C// 确保正确的 OpenGL 上下文
    if ([EAGLContext currentContext] != self.glContext) {
        [EAGLContext setCurrentContext:self.glContext];
    }
    
    float value = 0.8;
    
    // 美白
    [self.beEffectManager updateComposerNodeIntensity:@"/beauty_IOS_lite" key:@"whiten" intensity:value];
    
    // 亮眼
    [self.beEffectManager updateComposerNodeIntensity:@"/beauty_4Items" key:@"BEF_BEAUTY_BRIGHTEN_EYE" intensity:value];
    
    // 瘦脸
    [self.beEffectManager updateComposerNodeIntensity:@"/reshape_lite" key:@"Internal_Deform_Overall" intensity:value];
    // 更多美颜参数设置...
    
  2. 设置滤镜,滤镜的名称请参考 FilterResource.bundle/Filter 目录下的内容。

    Objective-C// 确保正确的 OpenGL 上下文
    if ([EAGLContext currentContext] != self.glContext) {
        [EAGLContext setCurrentContext:self.glContext];
    }
    
    // 设置滤镜,path 为相对 FilterResource.bundle/Filter 的路径
    [self.beEffectManager setFilterPath:@"/Filter_27_Po5"];
    [self.beEffectManager setFilterIntensity:0.8];
    
    // 关闭滤镜
    [self.beEffectManager setFilterPath:@""];
    [self.beEffectManager setFilterIntensity:0];
    

第六步:资源释放

释放相关资源:

Objective-C- (void)cleanUp {
  
    // 释放火山美颜资源
    [self.beEffectManager destroyTask];
    self.beEffectManager = nil;
}

参考文档

以下为本文涉及的火山美颜 SDK 官网文档:

此文档是否对你有帮助?
有帮助
去反馈
  • 功能概述
  • 工作原理
  • 注意事项
  • 前提条件
  • 第一步:集成 SDK
  • 第二步:准备资源
  • 第三步:初始化
  • 第四步:视频处理流程
  • 第五步:设置美颜特效
  • 第六步:资源释放
  • 参考文档