iOS 百度语音实现播报及iOS12.1后的播报功能问题与实现
最近碰到个接收到推送要实现语音播报的需求,需要后台推送通知,APP客户端收到通知之后语音播放:“您的账户收到一笔巨款”的功能。使用到了Notification Service Extension服务。
在之前的记录使用AVSpeechUtterance 来进行语音播报。
文章地址:http://www.laileshuo.com/?p=1324
经过实验AVSpeechUtterance语音声音很奇怪,于是考虑使用百度语音合成来实现播报。使用到的即是语音合成系统(TTS):语音合成(Text To Speech,TTS):将文本合成为语音,即声音文件。
集成百度语音合成地址:https://ai.baidu.com/docs#/TTS-iOS-SDK/top
一、集成百度TTS
- 加入sdk
下载百度语音合成的SDK,我们需要BDSClientLib中的libBaiduSpeechSDK,Headers中的TTS文件夹下的文件,Resource文件夹的资源。将这些文件放到NotificationService文件夹下
如图所示

- 添加依赖库
使用到的依赖库libsqlite3.0.tbd、libiconv.2.4.0.tbd、libc++.tbd、libz.1.2.5.tbd、GLKit.framework、SystemConfiguration.framework、AudioToolbox.framework、AVFoundation.framework、CFNetwork.framework、CoreLocation.framework、CoreTelephony.framework
二、代码实现
在NotificationService上实现代码
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
// 语音合成,使用AVAudioPlayer播放,成功
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[self configureSDK];
// 这个info 内容就是通知信息携带的数据,后面我们取语音播报的文案,通知栏的title,以及通知内容都是从这个info字段中获取
NSString *alert = self.bestAttemptContent.userInfo[@"alert"];
// 播报语音
//百度语音TTS
NSError *err = nil;
NSInteger sentenceID = [[BDSSpeechSynthesizer sharedInstance] speakSentence: alert withError:&err];
NSLog(@"sentenceID:%ld error:%@",(long)sentenceID,err);
self.contentHandler(self.bestAttemptContent);
}
-(void)configureSDK{
NSLog(@"TTS version info: %@", [BDSSpeechSynthesizer version]);
if (!self.baiduPlayer) {
self.baiduPlayer = [[BDSBuiltInPlayer alloc] init];
self.baiduPlayer.delegate = self;
}
[BDSSpeechSynthesizer setLogLevel:BDS_PUBLIC_LOG_VERBOSE];
[[BDSSpeechSynthesizer sharedInstance] setSynthesizerDelegate:self];
[self configureOnlineTTS];
[self configureOfflineTTS];
}
-(void)configureOnlineTTS {
[[BDSSpeechSynthesizer sharedInstance] setApiKey:NS_Baidu_API_KEY withSecretKey:NS_Baidu_SECRET_KEY];
[[BDSSpeechSynthesizer sharedInstance] setSynthParam:@(BDS_SYNTHESIZER_SPEAKER_FEMALE) forKey:BDS_SYNTHESIZER_PARAM_SPEAKER];
}
-(void)configureOfflineTTS {
NSError *err = nil;
// 在这里选择不同的离线音库(请在XCode中Add相应的资源文件),同一时间只能load一个离线音库。根据网络状况和配置,SDK可能会自动切换到离线合成。
NSString* offlineEngineSpeechData = [[NSBundle mainBundle] pathForResource:@"Chinese_And_English_Speech_Female" ofType:@"dat"];
NSString* offlineChineseAndEnglishTextData = [[NSBundle mainBundle] pathForResource:@"Chinese_And_English_Text" ofType:@"dat"];
err = [[BDSSpeechSynthesizer sharedInstance] loadOfflineEngine:offlineChineseAndEnglishTextData speechDataPath:offlineEngineSpeechData licenseFilePath:self.localPath withAppCode:NS_Baidu_APP_ID];
if(err){
return;
}
}
三、测试
首先选中主app运行到手机上,之后运行NotificationService
最后在极光上进行推送。
最后收到播放声音。
四、特别注意的事情
下面是关于iOS12.1之后:在12.1之后,在这个推送扩展就无法在后台进行播放了.
1、在12.1之后 推送扩展就无法在后台进行播放
下图是官方给出的说明,之前给出这个拓展推送主要是为了丰富推送的UI样式,推送信息加密之类的,结果却被用做推送语音播报,所以就发了这个声明,在12.1之后,在这个推送扩展就无法在后台进行播放了.
所以 iOS12.1使用百度语音无法播报。
2、测试遇到的现象
/ 12.1版本,AVAudioPlayer后台播放会失败
NSString *path = [[NSBundle mainBundle] pathForResource:@"audio" ofType:@"mp3"];
NSURL *url = [NSURL fileURLWithPath:outPutFilePath];
self.myPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
self.myPlayer.delegate = self;
[self.myPlayer play];
// 12.1版本,AudioServicesPlayAlertSoundWithCompletion后台播放会失败
static SystemSoundID soundID = 0;
AudioServicesCreateSystemSoundID((__bridge CFURLRef _Nonnull)(url), &soundID);
AudioServicesPlayAlertSoundWithCompletion(soundID, ^{
NSLog(@"播放完成");
});
既然12.1,NotificationService中无法进行后台播报,所以使用AVAudioPlayer、AudioServicesCreateSystemSoundID都会失败
如果在NotificationService中info.plist文件中加入
plist里面需要加UIBackgroundModes的 audio 就可以播放了
但是:
这个当打包上传到Appstore上就会出现错误了,提示说NotificationService中的UIBackgroundModes的Audio这个字段是非法的,无法添加的。
3、解决方案之修改通知的UNNotificationSound
在收到推送后,我们可以将消息拆成多个本地,每个通知NotificationContent的Sound都对应工程资源的一个音频文件。
代码所示:
- (void)playWithRegisterNotifications:(NSString *)content {
NSArray *array = @[@"shoukuan",@"2",@"bai",@"5",@"shi",@"dian",@"0",@"8",@"yuan"];
for (NSString *string in array) {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[self registerNotificationWithString:string completeHandler:^{
//延迟大于1秒感觉更好一点
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
dispatch_semaphore_signal(semaphore);
});
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
}
- (void)registerNotificationWithString:(NSString *)string completeHandler:(dispatch_block_t)complete {
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc]init];
content.title = @"";
content.subtitle = @"";
content.body = @"";
content.sound = [UNNotificationSound soundNamed:[NSString stringWithFormat:@"%@.mp3",string]];
content.categoryIdentifier = [NSString stringWithFormat:@"categoryIndentifier%@",string];
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:0.01 repeats:NO];
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:[NSString stringWithFormat:@"categoryIndentifier%@",string] content:content trigger:trigger];
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (error == nil) {
if (complete) {
complete();
}
}
}];
}
}];
}
经过测试,可以播放,但是:
dispatch_after在0.25秒后执行一个本地通知,但是这个情况很容易出现第语音播放不完整的情况,如果时间设置过大,语音播放的语速就很慢,体验极差。声音的大小也无法调整。
这种情况下可以采用通用的提示,如:您有一笔收款。这样的语音提示体验会好点,但是不是最优的方式
4、关于支付宝或者微信语音播报
经过查找,大概率支付宝、微信使用的使用voip模式,通过查找微信与支付宝的ipa,ipa中配置的文件都UIBackgroundModes(后台模式)包含voip。
所以大概率支付宝与微信都使用的是Voip PushKit实现的收款的语音播报功能
之后也会调查下Voip PushKit实现的收款的语音播报功能,PushKit和极光推送还不太一样。持续关注中。。。
本文地址:http://www.laileshuo.com/?p=1347
博客地址:www.laileshuo.com