自 2015 年的千播大战至今,社交直播已经衍生出很多不同的玩法了。传统的简单“你说我听”,已经再基础不过,又很难给观众带来“刺激”的形式了。你要是看过现在的直播,什么多人连麦、主播 PK、虚拟主播,玩法越来越多。

现如今,如果能了解怎么开发其中一个直播场景,绝对能给自己的简历加分。但问题是,实时音视频技术背后有非常多的坑,很难有一个人能从后端到前端自研出来一套直播系统。而通过调用不同的 API 来搭建是最佳的实践途径。

我们近期在 Github 上开源我们开发的 Agora Live,它实现了单主播直播、多人连麦直播、PK 直播、虚拟主播,四种现在社交直播领域最成熟的场景。大家可以点击下方 Github 链接获取源码。

这个项目原本是声网Agora 为了让更多开发者、用户了解、体验不同互动直播场景而开发的。所以大家也可以在 App Store 搜索到它。(Android 用户就需要访问 Agora.io 的「下载」页面去下载了。)

Agora Live 界面


能实现的场景

简单来讲讲,通过这个开源项目可以快速实现哪些场景。首先,这个开源项目实现的都是社交直播中的热门场景,包括:

  • 单主播直播场景:这是 Agora Live 最初就支持的功能,支持美颜、文字消息、添加背景音乐等功能。
  • 多人连麦直播场景:在直播的基础上,还可邀请另外 6 名观众进行连麦。
  • PK 直播场景:就像大家在陌陌、抖音等应用中看到的 PK 直播一样,主播可以向另一个主播发起 PK 邀请。两个直播间的观众会同时看到两个主播在线互动。
  • 虚拟主播场景:与单主播直播场景类似,只不过App 会为主播生成一个实时的虚拟形象,虚拟形象的表情会与主播同步。在直播过程中,还可以邀请观众上麦。
虚拟主播场景

核心功能的代码实现

为了便于大家快速熟悉源码,我们简单讲解以下核心功能的代码。以下以 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))
        }
    }

大家在声网官网注册后,在声网后台获取 AppID,替换掉源码中的 AppID 即可实现自己的社交直播应用。当然,这个过程是免费的,而且声网会为每个开发者提供每个月 10000 分钟的免费使用额度,完全足够用于个人项目、毕设等项目的使用需求。欢迎大家尝试。

如果你在这份源码基础上,自己实现了更多功能,或作出其它优化、改进,也欢迎提交 PR。我们将会向社区中更多开发者推荐你的项目,给你的 repo 加 Star。