媒体补充增强信息SEI
更新时间: 2024/09/18 16:26:13
通过 NERTC SDK,您可以将包括音量信息在内的自定义信息作为 SEI 的一部分,封装在视频码流中,并将其发送至远端用户解码查看,可用于音画同步等场景。
功能描述
在音视频流媒体应用中,用户的消息分发通道和音视频或直播通道是分开的,通常情况下难以保证消息与视频数据的同步性。NERTC 支持将时间戳等自定义数据作为流媒体补充增强信息 (SEI Supplemental Enhancement Information)的一部分,通过流媒体通道将其与视频内容打包在一起,发送给远端用户,以此实现文本数据与音视频内容的精准同步的目的。
SEI 一般用于以下场景:
- 在线教育场景,知识竞答等教育教学工具中需要老师的声音和需要作答的题目进行同步。
- 电商购物场景,需要将商品信息和主播的声音及画面进行同步。
- 泛娱乐场景的在线 KTV 玩法中,需要在合唱场景中歌词和声音及画面进行同步。
注意事项
- 默认通过主流发送 SEI 信息。您也可以在调用
sendSEIMsg
时指定通过辅流发送 SEI,发送前需确保该通道为开启状态。 - 纯音频场景的 SEI 帧通过 Fake Video 形式发送,不涉及视频流数据计费。
实现方法
音视频直播场景
本端:
- 本端加入房间后,通过
enableLocalVideo
开启视频流。 - 视频流成功开启后,调用
sendSEIMsg
接口发送 SEI 信息。
远端:
- 注册一个
NERtcEngineVideoSEIObserver
观测器用于接收远端流的 SEI 内容回调。 - 远端接收到本端发送的 SEI 信息后,触发
onNERtcEngineRecvSEIMsg
回调。
纯音频通话场景
纯音频通话场景下,如果需要发送 SEI 信息到远端,SDK 会自动采取 Fake Video 方案。实现过程如下:
-
本端调用
sendSEIMsg
发送SEI信息。此时SDK内部会自动生成一个 Fake Video 的视频流,并携带 SEI 信息发送至远端。Fake Video 的分辨率为 16×16,画面为纯黑色。
-
(可选)远端接收到纯音频流下发送的 Fake Video 的信令通知,通过
NERtcVideoProfileType
中的kNERtcVideoProfileFake
字段判断该视频流为 Fake Video。- 若您使用的是 v4.6.0 及之前版本的 SDK,此时需要订阅该视频流,但无需展示该视频流画面。
- 若您使用的是 v4.6.1 及之后版本的 SDK,不再需要关注该信令通知。
-
远端注册一个
NERtcEngineVideoSEIObserver
观测器用于接收远端流的 SEI 内容回调。 -
远端接收到本端发送的SEI信息后,触发
onNERtcEngineRecvSEIMsg
回调。
当您在纯音频通话场景下,通过发送 Fake Video 的方式实现 SEI 帧发送后,如果需要开启视频流正常发送视频数据,可以通过 enableLocalVideo 开启视频流,并调用 sendSEIMsg 继续发送 SEI 信息。此时SDK会自动判断之前是否开启了 Fake Video,如果开启了就会关闭 Fake Video,然后再开启视频。
示例代码
/**
NERtcEngine 扩展回调
*/
@protocol NERtcEngineDelegateEx <NERtcEngineDelegate, NERtcEngineVideoFrameObserver, NERtcEngineAudioSessionObserver,NERtcEngineLiveStreamObserver, NERtcEngineVideoSEIObserver>
@end
//实现SEI的回调注册
- (NERtcEngine *)coreEngine {
if (!_coreEngine) {
_coreEngine = [NERtcEngine sharedEngine];
//video codec
NSMutableDictionary *params = [NSMutableDictionary dictionary];
NSDictionary *keyMap = NTESToNERtcSettingsKeyMap();
fillNERtcParams(params, keyNRTCDemoPreferHWEncode, keyMap);
fillNERtcParams(params, keyNRTCDemoPreferHWDecode, keyMap);
[_coreEngine setParameters:params];
//context
NERtcEngineContext *context = [[NERtcEngineContext alloc] init];
context.engineDelegate = self;
//
NSString *settingsKey = [NTESDemoSettings stringForKey:keyNRTCDemoAppKey];
NSString *appKey = settingsKey.length > 5 ? settingsKey : [NTESDemoConfig sharedConfig].appKey;
context.appKey = appKey;
//
NERtcLogSetting *logSetting = [[NERtcLogSetting alloc] init];
logSetting.logDir = [NTESDemoConfig sharedConfig].rootDir;
context.logSetting = logSetting;
[_coreEngine setupEngineWithContext:context];
}
return _coreEngine;
}
//发送SEI:
- (void)onMenuSendSEI:(id)sender {
static int index = 0;
NSString *msg = [NSString stringWithFormat:@"index:%d,msg:%.f", ++index, [[NSDate date] timeIntervalSince1970]];
NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding];
NERtcStreamChannelType type = kNERtcStreamChannelTypeMainStream;
if ([NTESDemoSettings objectForKey:keyNRTCDemoLocalSEISendPrefer]) {
type = (NERtcStreamChannelType)([NTESDemoSettings integerForKey:keyNRTCDemoLocalSEISendPrefer]);
}
[[[NTESDemoLogic sharedLogic] getCoreEngine] sendSEIMsg:data streamChannelType:type];
}
//接受SEI
- (void)onNERtcEngineRecvSEIMsg:(uint64_t)userID message:(NSData *)message {
NSString *msg = [[NSString alloc] initWithData:message encoding:NSUTF8StringEncoding];
NSString *seiMsg = [NSString stringWithFormat:@"userID:%llu,message:%@",userID, msg];
MakeToast(seiMsg);
}