-
Notifications
You must be signed in to change notification settings - Fork 245
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
为什么要在onSynthesizeText方法整个代码块上加同步锁? #20
Comments
emmm原来系统TTS是支持并发的么?
主要是因为获取音频和解码音频都不支持多线程,而我又以为系统TTS是同步的,所以为了方便直接加了锁。
|
其实加不加锁都一样 在写入音频那里也会阻塞。 |
刚测试了下 确实可以提前获取到后面文本 |
客户端应用不用考虑并发,搞得效率高了,微软限制就来了 |
不是网络并发,是提前获取到后面的段落来预缓存音频
|
微软又收紧限制了,下午听一个小时就不行了 |
混用接口可以减轻限制
|
我也是弄不明白 既然
|
这回是没戏了😟 |
可以啊,可以把每次请求和callback提交给一个新线程,只是要注意只能在新线程中调用callback |
但是新线程不是合成线程,就算调用start和音频写入方法也没反应
|
参考一下我的写法
synthesize方法会启动一个线程,callback 只在新线程中调用,不然播放不了。 |
只要callback的start和audioAvailable、done方法调用的是同一个线程就行,不能分开在不同线程调用 |
emmm但为什么我已经调用了start() & audioAvailable() 但不播放呢
|
|
你的doDecode方法是不是启动别的线程了? |
没有的, |
start() & audioAvailable() 返回值也是 = TextToSpeech.SUCCESS
|
新建了一个线程也不行 |
要不你试试每次callback调用打印线程编号试试?我也看不出什么问题。。。 |
看来必须在 synthesis 线程上调用 |
我之前遇到过无法正常播放的问题,是callback在不同线程调用造成的。有试过直接把原来的synchronized块换成new Thread(lambda).start()吗? |
也有可能是其他非同步方法,并发调用时的问题。。。 |
测试是否必须在合成线程调用 override fun onSynthesizeText(request: SynthesisRequest?, callback: SynthesisCallback?) {
Log.e(TAG, request?.charSequenceText.toString())
Log.e(TAG, "onSynthesizeText thread: ${Thread.currentThread().id}")
GlobalScope.launch(newSingleThreadContext("thread")) {
Log.e(TAG, "callback?.start thread: ${Thread.currentThread().id}")
callback?.start(16000, AudioFormat.ENCODING_PCM_16BIT, 1)
val audio = MsTTS().getAudio("测试文本")
AudioDecoder().doDecode(audio!!, 24000, {
val maxBufferSize: Int = callback!!.maxBufferSize
var offset = 0
while (offset < it.size) {
val bytesToWrite = maxBufferSize.coerceAtMost(it.size - offset)
val ret = callback.audioAvailable(it, offset, bytesToWrite)
Log.e(TAG, "audioAvailable: $ret")
offset += bytesToWrite
}
}, {})
}
Thread.sleep(1000) 播放正常了 输出:
看来不一定非要在合成线程调用 |
对,看了之前的问题是非同步方法并发调用的问题,可以给一些非同步方法加锁试试 |
不像是这个问题 接收者是阻塞的 我刚才测试了下callback在return后是否有效,测试下来确实是可以正常播放的 |
其实单线程也行,每次调用onSynthesizeText时把请求加入处理队列后直接返回;然后用一个新线程,当队列不为空时,从队列中取出请求进行合成 |
这样就不会有同步的问题了 |
我用的是 Kotlin Channel 生产者消费者模式,其实也不会有线程同步问题,整个接收者都在一个单独线程内 |
雀食,audioAvailable方法貌似是阻塞的 |
找到问题了哈哈😂 是首次请求由于没过滤空文本导致返回音频为空,然后continue时忘记 GlobalScope.launch(newSingleThreadContext("new-thread")) {
for (v in audioChannel) {
val callback = v.first
val audio = v.second
val ret = callback.start(mAudioFormat.sampleRate, AudioFormat.ENCODING_PCM_16BIT, 1)
if (audio == null) {
Log.w(TAG, "音频为空")
callback.done()
continue
}
Log.e(TAG, "音频大小: ${audio.size}")
mAudioDecoder.doDecode(audio, mAudioFormat.sampleRate, {
println("writeToCallBack: ${it.size}")
writeToCallBack(callback, it)
}, {})
callback.done()
}
} |
又遇到问题了 =-= |
啊,是这样吗?那么正常暂停朗读系统是怎么处理的呢,是使正在调用的audioAvailable无效并返回异常吗? |
正常是 |
但如果 |
我还尝试通过判断返回值确定是否暂停,但就算暂停了也始终=SUCCESS |
那就在同一个线程播放,在另一个线程合成? |
生产者消费者本就是两个线程,问题在于 需要通过
|
虽说缓存实现了,但这不能暂停太影响体验了,实在不行只能放弃了 😶 |
行吧,系统不让这么做放弃也行 |
你没明白我的意思,我想说的是自己写一个生产者消费者模型,一旦某一个callback失效,就终止所有合成和后续播放 |
问题是callback我没办法知道它是否失效
|
除了阅读主动支持外,我还想到可以写个Server APP,把系统TTS转发到阅读支持的网络朗读引擎,这样也可以变相实现预缓存。
|
如果朗读停止后,依旧通过callback.audioAvailable写入音频数据,音频不会被播放且返回码为-1,正常为0 |
好吧,不阻塞时不行 |
就算暂停后 |
有个不成熟的想法,如果你还想试试的话可以看看。在onSynthesizeText方法内,在方法的最后让最后一个请求线程阻塞,之前的线程返回。实现类似这样:
实在不想折腾就算了,可以理解 |
对,看来这个方法必须阻塞了 |
onSynthesizeText在return后系统才会调用后续的onSynthesizeText |
算了 不折腾了 |
如题,为什么要在onSynthesizeText方法整个代码块上加同步锁?这样不是将本来可以并发合成的方法,变成了一个同步的方法,一次只处理一个请求?阅读实际上一次发送了多个合成请求,如果并行处理的话,在朗读第二段以及之后的段之前,实际音频已经合成了,这样朗读段之间就不会有网络请求造成的延迟。
The text was updated successfully, but these errors were encountered: