歌曲评分
更新时间: 2024/11/26 15:44:05
网易云信正版曲库支持歌曲评分功能。您可以通过集成 NEPitchKit 组件快捷实现含 UI 的歌曲评分功能,您也可以通过 API 接口实现歌曲评分能力,并自行实现相关 UI 界面。
功能介绍
歌曲评分包括如下功能:
- 歌曲播放时,同步回调对应的音高数据。
- 单句歌词播放结束,返回实时得分数据。
- 单首歌曲播放结束,返回整首歌曲的得分数据。
前提条件
请确认您已完成以下操作:
实现歌曲评分(含UI)
功能原理
歌曲评分UI组件的架构示意图如下图所示。
注意事项
不需要执行 destory
操作,页面销毁的时候,NEPitchKit 组件内部会进行销毁操作。
集成 NEPitchUIKit 组件
-
导入 NEPitchUIKit 组件。
pod 'NEPitchUIKit'
-
初始化
NEPitchRecordComponentView
页面 , 添加到当前视图上 ,并设置相关代理。
///页面初始化
self.compoentView = [[NEPitchRecordComponentView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 300)];;
///添加到当前视图
[self.view addSubview:self.compoentView];
///设置代理
self.compoentView.delegate = self;
- 初始化相关数据。
///获取字歌词资源内容:
NSString *lyricContent = [NSString
stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"xxx.txt" ofType:nil]
encoding:NSUTF8StringEncoding
error:nil];
///构造歌词数据:
NELyric *model = [[NELyric alloc] initWithContent:lyricContent andType:NELyricTypeYrc];
///获取打分资源内容:
NSString *midiContentString = [NSString
stringWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"xxx.txt" ofType:nil]
encoding:NSUTF8StringEncoding
error:nil];
- 加载相关配置内容。
///separator 为打分内容的分割符 ;例如 打分内容为 xxx,xxx,xxx ... 则分割符为 " , "。
[self.compoentView
loadRecordDataWithPitchContent:midiContentString
separator:@","
startTime:nil
endTime:nil
LocalLyric:lyricContent
andType:NELyricTypeYrc builder:nil];
- 调用
timeForCompoentView
方法实现代理。
///staticTime 为音乐的播放时间戳
- (NSInteger)timeForCompoentView:(NEPitchRecordComponentView *)compoentView {
return staticTime;
}
///如果使用NERtc进行播放,则按照如下方法获取到时间戳
///1.创建 RTC 房间,并加入
///2.播放音乐
///3.通过调用 RTC `getAudioMixingCurrentPosition` 的方法获取当前播放时间戳 ,具体RTC接入方式请参考官网
///第3步 获取方法如下 :
uint64_t currentTime;
int value = [[NERtcEngine sharedEngine] getAudioMixingCurrentPosition:¤tTime];
-
推送打分数据。
-
采集音频裸数据。
如果您使用的是网易云信的 NERTC,请参考以下示例代码。如果您使用的是其他RTC产品,请自行实现相关逻辑。
///1.实现RTC 代理方法 `onNERtcEngineAudioFrameDidRecord`,数据类型为 `NERtcAudioFrame`。 ///2.将数据类型`NERtcAudioFrame`转化为 `NEPitchAudioData`,具体代码如下所示: ///3.`stop` 为是否为最后一个数据传入的标记位,如果是最后一个数据,需要用户设置为YES,最终打分需要依赖最后一个数据属性已经传入。 NEPitchAudioData *audioData = [[NEPitchAudioData alloc] init]; if (self.stop) { audioData.done = YES; self.sendStop = YES; NSLog(@"最后一个数据输入"); [[RTCManager getInstance] stopAudioMixing]; } else { audioData.done = NO; } void *data = frame.data; audioData.samples = &data; audioData.validSampleCount = frame.format.samplesPerChannel; audioData.timeStamp = (int32_t)[[RTCManager getInstance] getAudioMixingCurrentPosition] - 10; audioData.sampleRate = frame.format.sampleRate; audioData.channelCount = frame.format.channels; // NSLog(@"数据推送 ---- %d",audioData.done); [self.compoentView pushAudioData:audioData];
-
调用
pushAudioData
接口推送打分数据。
void *data = 采集的音频裸数据; audioData.samples = &data; audioData.validSampleCount = frame.format.samplesPerChannel; audioData.timeStamp = (int32_t)[[RTCManager getInstance] getAudioMixingCurrentPosition] - 10; audioData.sampleRate = frame.format.sampleRate; audioData.channelCount = frame.format.channels; // NSLog(@"数据推送 ---- %d",audioData.done); [self.compoentView pushAudioData:audioData];
-
定制 UI 界面
UI Kit 支持自定义界面的元素,包括颜色、线形状、分数蹦出效果、分数格式等。
在步骤 4 加载相关配置内容时,可以通过设置 builder
相关属性,实现自定义 UI 界面,示例代码如下:
[self.compoentView
loadRecordDataWithPitchContent:midiContentString
separator:@","
startTime:nil
endTime:nil
LocalLyric:content
andType:NELyricTypeYrc
builder:^(NEPitchLayoutBuilder *_Nonnull builder) {
NSString *path = [[NSBundle mainBundle] pathForResource:@""
ofType:nil];
builder.pitchImagePath = path;
builder.emitterPathArray = @[
[[NSBundle mainBundle] pathForResource:@"pop_1.png" ofType:nil],
[[NSBundle mainBundle] pathForResource:@"pop_2.png" ofType:nil],
[[NSBundle mainBundle] pathForResource:@"pop_3.png" ofType:nil]
];
builder.finalScorePathArray = @[
[[NSBundle mainBundle] pathForResource:@"score-s.webp" ofType:nil],
[[NSBundle mainBundle] pathForResource:@"score-sss.webp"
ofType:nil],
[[NSBundle mainBundle] pathForResource:@"score-ss.webp" ofType:nil]
];
builder.onceTimeScorePathArray = @[
[[NSBundle mainBundle] pathForResource:@"score-nice.json"
ofType:nil],
[[NSBundle mainBundle] pathForResource:@"score-perfect.json"
ofType:nil],
[[NSBundle mainBundle] pathForResource:@"score-cool.json"
ofType:nil]
];
NSString *resource =
[[NSBundle mainBundle] pathForResource:@"sing_score_pic_x.png"
ofType:nil];
builder.perfectScorePathArray = @[ resource ];
}];
相关属性及规则说明如下表所示。
属性名 | 含义 | 规范 |
---|---|---|
lineHeight | 音阶线高度 默认4 | 无 |
eachTimeLength | 音阶线缩放长度 默认0.08 | 无 |
UIColor * (^drawColorBlock)(void) | 音调重合渲染颜色 | 无 |
UIColor * (^bottomColorBlock)(void) | 基础底色渲染 | 无 |
UIColor * (^LinearGradientStartColorBlock)(void) | 遮罩层渐变颜色的开始颜色 | 无 |
UIColor * (^LinearGradientEndColorBlock)(void) | 遮罩层渐变颜色的结束颜色 | 无 |
pitchImagePath | 自定义箭头的文件路径 完整路径 | 无 |
emitterPathArray | 自定义气泡样式的文件路径 | 建议3个文件路径 |
finalScorePathArray | 最终打分格式的文件路径 | 文件命名规范 xxx-sss.webp;xxx-ss.webp;xxx-s.webp 尺寸设置为 628 x 540 |
onceTimeScorePathArray | 单次评分格式的文件路径 | 文件命名规范 xxx-perfect.json;xxx-nice.json;xxx-cool.json |
perfectScorePathArray | 演唱perfect时 追加的 x1 到 x9 到 xn图片 | 文件命名规范 xxxx_score_pic_x ;xxxx_score_pic_0;xxxx_score_pic_1;... 尺寸设置为 20 x 20 |
实现歌曲评分(不含UI)
功能原理
实现方法
-
初始化歌曲评分组件。
-
调用
NECopyrightedMedia getInstance
接口创建版权音乐对象。NECopyrightedMedia * copyRight = [NECopyrightedMedia getInstance];
-
调用
initialize
接口初始化歌曲评分组件。//初始化版权SDK [[NECopyrightedMedia getInstance] initialize:copyrightedAppKey token:copyrightedToken userUuid:account extras:nil];
-
-
初始化打分引擎。
- 调用
NEPitchSongScore getInstance
接口创建打分引擎对象。NEPitchSongScore * songScore = [NEPitchSongScore getInstance];
- 调用
createInfoWithPitchContent
接口生成NEPitchRecordSingInfo
对象。
NEPitchRecordSingInfo *info = [NEPitchRecordSingInfo createInfoWithPitchContent:打分内容文本 separator:打分内容文本分割符 startTime:开始时间 (完整歌曲 填入nil 或 0) endTime:结束时间 (完整歌曲填入nil或歌曲时长 ,单位是毫秒) LocalLyric:歌词内容文本 andType:歌词类型];
- 调用
initialize
接口初始化引擎对象。
[[NEPitchSongScore getInstance] initialize:info];
- 调用
-
调用
start
接口启动打分功能。[[NEPitchSongScore getInstance] start];
-
持续调用
pushAudioData
接口进行数据推送,驱动打分功能运行。//NEKaraokeRtcAudioFrame * frame 为 RTC回调数据 // BOOL isEnd 为是否最后一个数据 ,业务流程处理,唱完歌后,最后一次数据传递设置为YES // _recorStart 为了保证最后一次数据传入后后续数据不再push //收到采集的数据后调用如下方法,最后这一次数据需要设置 .done = YES - (void)pushAudioFrameWithFrame:(NEKaraokeRtcAudioFrame *)frame isEnd:(BOOL)isEnd{ if (_recorStart) { if (isEnd) { _recorStart = NO; NSLog(@"发送最后一次数据"); } NEPitchAudioData *audioData = [[NEPitchAudioData alloc] init]; audioData.done = isEnd; void * data = frame.data; audioData.samples = &data; audioData.validSampleCount = frame.format.samplesPerChannel; audioData.timeStamp = (int32_t)self.timeForCurrent() - 10; audioData.sampleRate = frame.format.sampleRate; audioData.channelCount = frame.format.channels; [[NEPitchSongScore getInstance] pushAudioData:audioData]; } }
-
通过
markerReceiveNoteBlock
回调,获取实时音高数据。[NEPitchSongScore getInstance].onNote = ^(NEPitchRecordItemModel * _Nonnull item) { };
-
通过
markerReceiveGradeBlock
回调,获取实时打分数据。[NEPitchSongScore getInstance].onGrade = ^(NEPitchRecordSingMarkModel * _Nonnull markModel) { };
-
调用
getFinalScoreComplete
接口计算最终分数。[[NEPitchSongScore getInstance] getFinalScoreComplete:^(NSError *_Nullable error, NEPitchRecordSingInfo *_Nullable pitchRecordSingInfo) { __block long markValue = pitchRecordSingInfo.chorusFinalMark.totalValue / pitchRecordSingInfo.availableLyricCount; __block NEOpusLevel markLevel = NEOpusLevelC; dispatch_async(dispatch_get_main_queue(), ^{ if (markValue > 90) { markLevel = NEOpusLevelSSS; } else if (markValue > 80) { markLevel = NEOpusLevelSS; } else { markLevel = NEOpusLevelS; } //结束 开始打分 [NEKaraokeSongLog successLog:karaokeSongLog desc:@"展示打分"]; @strongify(self)[self.lyricActionView lyricActionViewLevel:markLevel resultModel:playResultModel]; }); }]
-
调用
destroy
接口销毁打分器。[[NEPitchSongScore getInstance]] destroy];
其他接口
//暂停打分
[[NEPitchSongScore getInstance] pause];
//录唱过程中seek,就是跳过一些句子或者重新从某个句子开始唱,需要刷新一下打分库。
[[NEPitchSongScore getInstance] seekTime:startTime];
//注:seek之后需要100ms之后调用resetMidi方法,打分库响应时间需要
[[NEPitchSongScore getInstance] resetMidi];