Agora 开源 | 一个 Demo,帮你快速实现社交直播四大场景

为了让大家体验基于 Agora SDK 实现的互动直播场景,我们三年前开发了应用 Agora Live。近期,我们对 Agora Live 进行了一次“史诗级”更新。我们不仅重新设计了 UI,并且还增加了多人连麦直播、PK 直播、虚拟主播功能。更重要的是,我们决定把这份代码也开源给所有开发者。希望大家能更快速地实现这些社交娱乐中最热门的实时互动场景,并创新出更多别具一格的玩法!

image
新版本 Agora Live 已经上线应用商店,仅面向 Android 和 iOS 用户。 iOS 用户 可以在 App Store 搜索「Agora Live」直接下载或更新。 Android 用户 可以扫描下方二维码,或访问声网官网下载中心下载。

image

一个 App,多种热门场景

新版 Agora Live 目前已经支持四种时下最热门的实时互动场景,包括:

  • 单主播直播场景:这是 Agora Live 最初就支持的功能,支持美颜、文字消息、添加背景音乐等功能。
    image

  • 多人连麦直播场景:在直播的基础上,还可邀请另外 6 名观众进行连麦。

  • PK 直播场景:就像大家在陌陌、抖音等应用中看到的 PK 直播一样,主播可以向另一个主播发起 PK 邀请。两个直播间的观众会同时看到两个主播在线互动。

  • 虚拟主播场景:与单主播直播场景类似,只不过App 会为主播生成一个实时的虚拟形象,虚拟形象的表情会与主播同步。在直播过程中,还可以邀请观众上麦。
    image
    App 中所有音视频实时互动与文字消息、控制指令(如邀请上麦),都是基于声网 Agora Native SDK 、声网 Agora 实时消息 RTM SDK 实现的。美颜、虚拟形象则是基于声网Agora 的生态合作伙伴相芯科技 FaceUnity SDK 实现的。

核心功能的实现

为了便于大家快速熟悉源码,我们简单讲解以下核心功能的代码。以下以 Swift 代码为例。

这个示例中, 直播间、房主与观众连麦 ,都是基于 Agora RTC SDK 实现的。我们通过以下代码可以让用户加入RTC频道,实现音视频的互通。


  func join(channel: String, token: String? = nil, streamId: Int, success: Completion = nil) {
        agoraKit.join(channel: channel, token: token, streamId: streamId) { [unowned self] in
            self.channelStatus = .ing
            if let success = success {
                success()
            }
        }
  }

在直播间中的 文字消息、控制指令 (比如邀请观众上麦)等,都是基于 Agora 实时消息 RTM SDK 实现的。在这里我们集成 RTM SDK 后,通过以下代码让用户加入 RTM 频道。


    func joinChannel(_ id: String, delegate: AgoraRtmChannelDelegate, success: Completion, fail: ErrorCompletion) {
        do {
            let channel = try createChannel(id: id, delegate: delegate)
            channel.join { (errorCode) in
                switch errorCode {
                case .channelErrorOk:
                    self.log(info: "rtm join channel success", extra: "channel id: \(id)")
                    if let success = success {
                        success()
                    }
                default:
                    let error = AGEError.rtm("join channel fail",
                                             code: errorCode.rawValue,
                                             extra: "channel: \(id)")
                    
                    self.log(error: error)
                    if let fail = fail {
                        fail(error)
                    }
                }
            }
        } catch {
            log(error: error, extra: "create channel fail")
            if let fail = fail {
                fail(error)
            }
        }
    }

美颜与虚拟形象 是通过接入 FaceUnity 的服务来实现的。可以结合 FUClient 这个类的实现与 FaceUnity 的文档来集成美颜模块。


typedef void (^FUCompletion)(void);
typedef void (^FUErrorCompletion)(NSError *error);

typedef NS_ENUM(NSUInteger, FUFilterItemType) {
    FUFilterItemTypeSmooth      = 1,
    FUFilterItemTypeBrighten    = 2,
    FUFilterItemTypeThinning    = 3,
    FUFilterItemTypeEye         = 4
};

@interface FUFilterItem : NSObject
@property (nonatomic, assign) FUFilterItemType type;
@property (nonatomic, assign) float defaultValue;
@property (nonatomic, assign) float minValue;
@property (nonatomic, assign) float maxValue;
@property (nonatomic, assign) float value;
@property (nonatomic, copy) NSString *funcName;
@end

@interface FUClient : NSObject
- (void)loadFilterWithSuccess:(FUCompletion)success fail:(FUErrorCompletion)fail;
- (void)setFilterValue:(float)value withType:(FUFilterItemType)type;
- (FUFilterItem *)getFilterItemWithType:(FUFilterItemType)type;

- (void)loadBackgroudWithSuccess:(FUCompletion)success fail:(FUErrorCompletion)fail;
- (void)loadAnimoji:(NSString *)name success:(FUCompletion)success fail:(FUErrorCompletion)fail;
- (void)renderItemsToPixelBuffer:(CVPixelBufferRef)pixelBuffer;
- (void)destoryAllItems;
@end

视频流从 AVCaptureSession 流出,流入 FaceUnity 进行前处理,然后进入 Agora RTC SDK 发送到远端。

    func camera(_ camera: AGESingleCamera, position: AGECamera.Position, didOutput sampleBuffer: CMSampleBuffer) {
        cameraStreamQueue.async { [unowned self] in
            guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
                return
            }
            
            CVPixelBufferLockBaseAddress(pixelBuffer, .init(rawValue: 0))
            
            let timeStamp = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
            
            if self.enhancement.beauty == .on || self.enhancement.appearance != .none {
                self.enhancement.renderItems(to: pixelBuffer)
            }
            
            self.consumer?.consumePixelBuffer(pixelBuffer,
                                              withTimestamp: timeStamp,
                                              rotation: .rotationNone)
            
            CVPixelBufferUnlockBaseAddress(pixelBuffer, .init(rawValue: 0))
        }
    }

项目开源与未来计划

我们已经在官方 Github「AgoraIO Usecase」中开源了 Agora Live 的代码。大家可以直接 fork,替换 AppID 即可使用。方便大家快速实现多人连麦直播、单主播直播、PK 直播、虚拟主播 4 种实时互动场景。

Github URL:https://github.com/AgoraIO-usecase/agoralive

我们还将在接下来几个月对 repo 中的代码进一步梳理、优化,提升源码的易用性、可读性。而且,还会在本季度增加新的场景。当然,如果你在这份源码基础上,自己实现了更多功能,也欢迎提交 PR。我们也会向社区中更多开发者推荐你的项目。
image