RTC Engine
  • 产品概述
  • Web
    • 跑通 Demo
    • SDK 快速启动
    • 基础功能
      • 屏幕分享
      • 直播
      • 媒体设备
      • 音量
      • 设置编码配置文件
      • 检测网络质量
      • 检测能力
    • 高级功能
      • 启用 AI 降噪
      • 启用混音
      • 启用水印
      • Enable Virtual Background
      • 启用美颜和特效
      • 数据消息
      • 自定义采集和渲染
    • 接口列表
    • 发布说明
    • 支持平台
    • Web 常见问题
      • 优化多人视频通话
      • 处理自动播放受限
      • 应对防火墙限制
      • 其他
  • Android
    • Run Sample Code
    • Integration
    • 集成
      • 7. 检测网络质量
      • 8. 开启屏幕共享
      • 9. 设置视频质量
      • 10. 旋转视频
    • Advanced Features
      • 测试网络质量
      • 自定义采集和渲染
      • 自定义音频采集和播放
    • 客户端API
      • 产品概述
      • TRTCCloud
      • TRTCStatistics
      • TRTCCloudListener
      • TXAudioEffectManager
      • TXBeautyManager
      • TXDeviceManager
      • 类型定义
      • 废弃接口
      • 错误码
    • 解决方案
      • 实时合唱 (TUIKaraoke)
        • 快速集成
        • 实施步骤
        • 歌曲同步
        • 歌词同步
        • 语音同步
        • 混流解决方案
        • TRTCKaraoke 相关接口
        • 常见问题
    • 发布说明
  • iOS
    • Run Sample Code
    • Integration
    • 集成
      • 7. 检测网络质量
      • 8. 开启屏幕共享
      • 9. 设置视频质量
      • 10. 旋转视频
    • Advanced Features
      • 测试网络质量
      • 自定义采集和渲染
      • 自定义音频采集和播放
    • 客户端API
      • 产品概述
      • TRTCCloud
      • TRTCCloudDelegate
      • TRTCStatistics
      • TXAudioEffectManager
      • TXBeautyManager
      • TXDeviceManager
      • 类型定义
      • 废弃接口
      • ErrorCode
    • 解决方案
      • 快速集成
      • 实施步骤
      • 歌曲同步
      • 歌词同步
      • 语音同步
      • 混流解决方案
      • TRTCKaraoke 相关接口
      • 常见问题
    • 发布说明
  • macOS
    • Run Sample Code
    • Integration
    • 集成
      • 7. 检测网络质量
      • 8. 开启屏幕共享
      • 9. 共享电脑声音
      • 10. 设置视频质量
      • 11. 旋转视频
    • Advanced Features
      • 测试硬件设备
      • 测试网络质量
      • 自定义采集和渲染
      • 自定义音频采集和播放
    • 客户端API
      • 产品概述
      • TRTCCloud
      • TRTCCloudDelegate
      • TRTCStatistics
      • TXAudioEffectManager
      • TXBeautyManager
      • TXDeviceManager
      • 类型定义
      • 废弃接口
      • ErrorCode
      • 发布说明
    • 发布说明
  • Windows
    • Run Sample Code
    • Integration
    • 集成
      • 7. 检测网络质量
      • 8. 开启屏幕共享
      • 9. 设置视频质量
      • 10. 旋转视频
    • Advanced Features
      • 测试硬件设备
      • 测试网络质量
      • 自定义采集和渲染
      • 自定义音频采集和播放
    • 客户端API
      • 产品概述
      • ITRTCCloud
      • ITRTCStatistics
      • TRTCCloudCallback
      • ITXAudioEffectManager
      • ITXDeviceManager
      • 类型定义
      • 废弃接口
      • 错误码
    • 发布说明
  • Electron
    • 集成
      • 1. 接口示例
      • 2. 导入 SDK
      • 3. 进入房间
      • 4. 订阅音视频流
      • 5. 发布音视频流
      • 6. 退出房间
      • 7. 检测网络质量
      • 8. 开启屏幕共享
      • 9. 共享电脑声音
      • 10. 设置视频质量
      • 11. 旋转视频
    • 客户端API
      • 产品概述
      • 错误码
  • Flutter
    • 集成
      • 1. 接口示例
      • 2. 导入 SDK
      • 3. 进入房间
      • 4. 订阅音视频流
      • 5. 发布音视频流
      • 6. 退出房间
      • 7. 检测网络质量
      • 8. 开启屏幕共享
      • 9. 共享电脑声音
      • 10. 设置视频质量
      • 11. 旋转视频
    • 客户端API
      • 产品概述
      • 错误码
  • Unity
    • 集成
      • 1. 接口示例
      • 2. 导入 SDK
    • 客户端API
      • 产品概述
      • 错误码
  • QT
    • 集成
      • 1. 导入 SDK
  • 产品概述
    • 产品概述
  • 概念
  • 产品特性
  • 性能统计
  • 价格中心
    • 免费时长
    • RTC-Engine 套餐
    • 订阅套餐时长计费说明
    • 现收现付
      • 音视频时长计费说明
      • 云端录制计费说明
      • 混流转码与旁路转推计费说明
  • 常见问题
    • 入门常见问题
    • 迁移指南
      • Twilio Video 到 TRTC 的迁移
      • 计费
      • 产品特性
      • UserSig
      • 防火墙限制
      • 如何缩减安装包
      • TRTCCalling Web 相关
      • 音视频质量
      • 其他
RTC Engine

歌曲同步

实时方案中需要在开唱后实时同步歌曲进度,避免因歌曲误差而增加端到端延迟。同步歌曲需要基于 NTP 时间,不同设备的本地时钟并不一致,存在一定误差,因此需要引入腾讯云自研 NTP 服务。同时,中途加入合唱的用户也需要同步歌曲进度,只有同步进度后,才能参与合唱。

实现流程

歌曲同步的做法为:主唱用户约定在未来某个时间点(如延迟 N 秒后)开始播放歌曲,其他用户参与合唱。各端的时间都以 NTP 时间为准,NTP 时间会在 TRTC SDK 初始化后开始同步。



具体流程如下:
1. 各端进行 NTP 校时,向 TRTC 云端更新并获取最新的 NTP 时间 T。
2. 主唱端发送合唱信令(自定义消息),约定合唱开始时间 T2。
3. 本地根据 T2 预加载歌曲,定时播放。
4. 其他合唱用户接收到合唱信令后执行步骤 3。
5. 过程中对本地歌曲播放进度进行校验,当 TE 与 TC 差值超过 50ms 即 seek 校准。
说明:
这里的 50ms 误差是个典型值,可以根据业务容忍度适当调整,建议在 50ms 上下浮动。

时序图




歌曲同步时序主要可以分为三个部分:NTP 校时、发送及接收合唱信令、歌曲播放进度修正。下面将针对这四部分给出具体的代码实现。

关键代码实现

1. NTP 校时服务

//进房时调用NTP校时接口
[TXLiveBase updateNetworkTime];

// TXLiveBaseDelegate 回调内判断是否校时成功
- (void)onUpdateNetworkTime:(int)errCode message:(NSString *)errMsg {
// errCode 0: 校时成功且偏差在30ms以内;1: 校时成功但偏差可能在30ms以上;-1: 校时失败
if (errCode == 0) {
// 调用TXLivebase的getNetworkTimestamp即可获取NTP时间戳
NSInteger ntpTime = [TXLiveBase getNetworkTimestamp];
} else {
// 重新调用updateNetworkTime启动一次校时
[TXLiveBase updateNetworkTime];
}
}

2. 主唱端发送合唱信令

NSDictionary *json = @{
@"cmd": @"startChorus",
// 约定时间合唱
@"startPlayMusicTS": @(startTs),
@"musicId": @"musicId",
@"musicDuration": @(musicDuration),
};
NSString *jsonString = [self jsonStringFrom:json];
[trtcCloud sendCustomMessage:jsonString reliable:NO];
说明:
建议主唱以固定时间频率循环向房间内发送合唱信令消息,以便合唱者可以中途加入合唱;
不使用 SEI 消息发送合唱信令的原因: SEI 信息会插入到视频帧中,导致观众侧拉取的视频流中携带很多无效信息。

3. 合唱端接收合唱信令

- (void)onRecvCustomCmdMsgUserId:(NSString *)userId cmdID:(NSInteger)cmdId seq:(UInt32)seq message:(NSData *)message {

NSString *msg = [[NSString alloc] initWithData:message encoding:NSUTF8StringEncoding];
NSError *error;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:[msg dataUsingEncoding:NSUTF8StringEncoding]
options:NSJSONReadingMutableContainers
error:&error];

NSObject *cmdObj = [json objectForKey:@"cmd"];

NSInteger musicDuration = [[json objectForKey:@"musicDuration"] integerValue];

NSString *cmd = (NSString *)cmdObj;

// 合唱命令
if ([cmd isEqualToString:@"startChorus"]) {
// 合唱开始时间
NSObject *startPlayMusicTsObj = [json objectForKey:@"startPlayMusicTS"];
NSString *musicId = [json objectForKey:@"musicId"];

NSInteger startPlayMusicTs = ((NSNumber *)startPlayMusicTsObj).longLongValue;
// 合唱约定时间和当前时间差值
NSInteger startDelayMS = labs(startPlayMusicTs - [TXLiveBase getNetworkTimestamp]);
// 开启预加载,根据合唱约定时间和当前ntp差值,跳跃歌曲进度
NSDictionary *jsonDict = @{
@"api": @"preloadMusic",
@"params": @{
@"musicId": @(musicId),
@"path": path,
@"startTimeMS": @(startDelayMS),
}
};
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonDict options:0 error:NULL];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[subCloud callExperimentalAPI:jsonString];

// 播放歌曲
TXAudioMusicParam *param = [[TXAudioMusicParam alloc] init];
param.ID = musicId;
param.path = url;
param.loopCount = 0;
param.publish = NO;

[[subCloud getAudioEffectManager] startPlayMusic:param onStart:^(NSInteger errCode) {
// 开始播放回调
} onProgress:^(NSInteger progressMs, NSInteger durationMs) {
// 歌曲进度回调
} onComplete:^(NSInteger errCode) {
// 播放完成回调
}];

}
}
说明:
合唱端接收到第一个 startChorus 信令后,状态应从“未合唱”转为“合唱中”,此后不再响应 startChorus 信令,避免重复启动 BGM 播放。

4. 歌曲播放进度修正

self.startPlayChorusMusicTs; //最初约定的合唱时间
// 当前播放进度
NSInteger currentProgress = [[self audioEffecManager] getMusicCurrentPosInMS:self.currentPlayMusicID];
// 当前歌曲的理想播放时间进度
NSInteger estimatedProgress = [TXLiveBase getNetworkTimestamp] - self.startPlayChorusMusicTs;
if (estimatedProgress >= 0 && labs(currentProgress - estimatedProgress) > 50) {
// 当播放进度超过50ms,进行修正
[[subCloud getAudioEffectManager] seekMusicToPosInMS:self.currentPlayMusicID pts:estimatedProgress];
}