Giter Club home page Giter Club logo

Comments (51)

jing332 avatar jing332 commented on May 13, 2024

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

其实加不加锁都一样 在写入音频那里也会阻塞。
我也忘记当初为何要加锁了

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

刚测试了下 确实可以提前获取到后面文本
不过我还得想想怎么实现缓存
总之感谢你的提醒

from tts-server-android.

limne avatar limne commented on May 13, 2024

客户端应用不用考虑并发,搞得效率高了,微软限制就来了

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

from tts-server-android.

limne avatar limne commented on May 13, 2024

微软又收紧限制了,下午听一个小时就不行了

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

我研究了一会 发现缓存实现没那么容易
最主要的的是 start()audioAvailable() 都必须在合成线程上调用,而 audioAvailable() 写入音频这个方法还是阻塞的

官方文档: 告知服务从给定文本合成语音。此方法应阻塞直到 合成完成。在合成线程上调用。

image

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

我也是弄不明白 既然 onSynthesizeText() 必须阻塞直到合成完成,那 TextToSpeech.speak() 的队列参数又有啥意义?

Android: 合成必须是同步的,这意味着引擎不得在方法返回后保留回调或对其调用任何方法

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

这回是没戏了😟

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024

这回是没戏了😟

可以啊,可以把每次请求和callback提交给一个新线程,只是要注意只能在新线程中调用callback

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024

但是新线程不是合成线程,就算调用start和音频写入方法也没反应

参考一下我的写法

        synthesizer.synthesize(speed, volume, text, 1, new SynthesizerCallback() {
            @Override
            public void onBusy() {
                callback.error(TextToSpeech.ERROR_SYNTHESIS);
                Log.d(TAG, "onBusy: engine is busy.");
            }

            @Override
            public void onStart() {
                callback.start(16000, AudioFormat.ENCODING_PCM_16BIT, 1);
                Log.d(TAG, "onStart: everything is ok.");
            }

            @Override
            public void onFinish(String msg) {
                callback.done();
                Log.d(TAG, "onFinish: " + msg);
            }

            @Override
            public void onFailed(String msg) {
                Log.e(TAG, "onFailed: " + msg);
                callback.error(TextToSpeech.ERROR_SERVICE);
            }

            @Override
            public void onSynthesize(byte[] audioData, int index, int size) {
                int offset = 0;
                int length = audioData.length;
                final int maxBufferSize = callback.getMaxBufferSize();
                while (offset < length) {
                    int bytesToWrite = Math.min(maxBufferSize, length - offset);
                    callback.audioAvailable(audioData, offset, bytesToWrite);
                    offset += bytesToWrite;
                }
            }
        });

synthesize方法会启动一个线程,callback 只在新线程中调用,不然播放不了。

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024

但是新线程不是合成线程,就算调用start和音频写入方法也没反应

只要callback的start和audioAvailable、done方法调用的是同一个线程就行,不能分开在不同线程调用

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

但是新线程不是合成线程,就算调用start和音频写入方法也没反应

只要callback的start和audioAvailable、done方法调用的是同一个线程就行

emmm但为什么我已经调用了start() & audioAvailable() 但不播放呢

mScope.launch(Dispatchers.Default) {
            Log.e(TAG, "current thread: ${Thread.currentThread().name}")
            // 接收者
            for (v in audioChannel) {
                val callback = v.first
                callback.start(mAudioFormat.sampleRate, AudioFormat.ENCODING_PCM_16BIT, 1)
                if (v.second == null) {
                    Log.w(TAG, "音频为空")
                    continue
                }
                mAudioDecoder.doDecode(v.second!!, mAudioFormat.sampleRate, {
                    println("writeToCallBack: ${it.size}")
                    writeToCallBack(callback, it)
                }, {})
                callback.done()
            }
        }

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

launch(Dispatchers.Default) 直接用协程 launch 不行么?

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024

doDecode

你的doDecode方法是不是启动别的线程了?

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

没有的,
而且正常来说调用 start() 后阅读APP当前段落就会高亮,然而并无反应

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

start() & audioAvailable() 返回值也是 = TextToSpeech.SUCCESS

 /* 写入PCM音频到系统组件 */
    private fun writeToCallBack(callback: SynthesisCallback, pcmData: ByteArray) {
        try {
            val maxBufferSize: Int = callback.maxBufferSize
            var offset = 0
            while (offset < pcmData.size) {
                val bytesToWrite = maxBufferSize.coerceAtMost(pcmData.size - offset)
                val ret = callback.audioAvailable(pcmData, offset, bytesToWrite)
                Log.e(TAG, "audioAvailable: $ret")
                offset += bytesToWrite
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

新建了一个线程也不行 mScope.launch(newSingleThreadContext("new-thread-name"))
可能是callback在 onSynthesizeText() return后就不能用了?

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024

新建了一个线程也不行 mScope.launch(newSingleThreadContext("new-thread-name")) 可能是callback在 onSynthesizeText() return后就不能用了?

要不你试试每次callback调用打印线程编号试试?我也看不出什么问题。。。

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024
public abstract int audioAvailable(byte[] buffer, int offset, int length )
This method should only be called on the synthesis thread, while in TextToSpeechService.onSynthesizeText.

看来必须在 synthesis 线程上调用

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024
public abstract int audioAvailable(byte[] buffer, int offset, int length )
This method should only be called on the synthesis thread, while in TextToSpeechService.onSynthesizeText.

看来必须在 synthesis 线程上调用

我之前遇到过无法正常播放的问题,是callback在不同线程调用造成的。有试过直接把原来的synchronized块换成new Thread(lambda).start()吗?

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024
public abstract int audioAvailable(byte[] buffer, int offset, int length )
This method should only be called on the synthesis thread, while in TextToSpeechService.onSynthesizeText.

看来必须在 synthesis 线程上调用

也有可能是其他非同步方法,并发调用时的问题。。。

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

测试是否必须在合成线程调用

 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)

播放正常了 输出:

                                                                                        onSynthesizeText thread: 30951
21:18:56.315 SysTtsService                                                E  callback?.start thread: 30961
21:18:56.320 GoLog                                                        E  time="2022-12-06T13:18:56Z" level=info msg="创建WebSocket连接(Edge)..."
21:18:56.320 GoLog                                                        E  time="2022-12-06T13:18:56Z" level=info msg="连接到IP地址: 202.89.233.103:443"
21:18:56.850 AudioDecode                                                  D  找到音频流的index:0, mime:audio/mpeg

21:18:56.896 SysTtsService                                                E  decode thread: 30961
21:18:56.896 SysTtsService                                                E  audioAvailable: 0
21:18:56.897 SysTtsService                                                E  decode thread: 30961
21:18:56.898 SysTtsService                                                E  audioAvailable: 0

看来不一定非要在合成线程调用

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024

测试是否必须在合成线程调用

 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)

播放正常了 输出:

                                                                                        onSynthesizeText thread: 30951
21:18:56.315 SysTtsService                                                E  callback?.start thread: 30961
21:18:56.320 GoLog                                                        E  time="2022-12-06T13:18:56Z" level=info msg="创建WebSocket连接(Edge)..."
21:18:56.320 GoLog                                                        E  time="2022-12-06T13:18:56Z" level=info msg="连接到IP地址: 202.89.233.103:443"
21:18:56.850 AudioDecode                                                  D  找到音频流的index:0, mime:audio/mpeg

21:18:56.896 SysTtsService                                                E  decode thread: 30961
21:18:56.896 SysTtsService                                                E  audioAvailable: 0
21:18:56.897 SysTtsService                                                E  decode thread: 30961
21:18:56.898 SysTtsService                                                E  audioAvailable: 0

看来不一定非要在合成线程调用

对,看了之前的问题是非同步方法并发调用的问题,可以给一些非同步方法加锁试试

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

不像是这个问题 接收者是阻塞的 audioDecode.doDecode() 也会一直等待到播放完毕

我刚才测试了下callback在return后是否有效,测试下来确实是可以正常播放的

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024

其实单线程也行,每次调用onSynthesizeText时把请求加入处理队列后直接返回;然后用一个新线程,当队列不为空时,从队列中取出请求进行合成

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024

这样就不会有同步的问题了

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

我用的是 Kotlin Channel 生产者消费者模式,其实也不会有线程同步问题,整个接收者都在一个单独线程内

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024

我用的是 Kotlin Channel 生产者消费者模式,其实也不会有线程同步问题,整个接收者都在一个单独线程内

雀食,audioAvailable方法貌似是阻塞的

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

找到问题了哈哈😂 是首次请求由于没过滤空文本导致返回音频为空,然后continue时忘记 callback.done() 了就导致以后的音频callback一直阻塞。

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()
            }
        }

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

又遇到问题了 =-=
onStop() 在用户暂停时不调用,导致一直播放 只能关闭朗读
经过测试发现 必须要让 onSynthesizeText() 阻塞,否则系统不调用 onStop()

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024

又遇到问题了 =-= onStop() 在用户暂停时不调用,导致一直播放 只能关闭朗读 经过测试发现 必须要让 onSynthesizeText() 阻塞,否则系统不调用 onStop()

啊,是这样吗?那么正常暂停朗读系统是怎么处理的呢,是使正在调用的audioAvailable无效并返回异常吗?

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

正常是 onSynthesizeText() 一直阻塞等待播放完毕,如果这时用户暂停,系统就会调用 onStop(),然后就进行一些取消操作。

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

但如果 onSynthesizeText() return后,拿着它的 callbackrequest 进行合成和写入音频,这时候用户暂停, 当前callback就会失效立刻停止播放,但后面预先缓存的callback还能正常写入播放

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

我还尝试通过判断返回值确定是否暂停,但就算暂停了也始终=SUCCESS

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024

但如果 onSynthesizeText() return后,拿着它的 callbackrequest 进行合成和写入音频,这时候用户暂停, 当前callback就会失效立刻停止播放,但后面预先缓存的callback还能正常写入播放

那就在同一个线程播放,在另一个线程合成?

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

生产者消费者本就是两个线程,问题在于 需要通过 onSynthesizeText() 获取后续所有的段落文本,而我又不能去阻塞它。
安卓文档也说:

Synthesis must be synchronous which means the engine must NOT hold on to the callback or call any methods on it after the method returns.
合成必须是同步的,这意味着引擎不能保留回调,也不能在方法返回后对其调用任何方法。

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

虽说缓存实现了,但这不能暂停太影响体验了,实在不行只能放弃了 😶

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024

虽说缓存实现了,但这不能暂停太影响体验了,实在不行只能放弃了 😶

行吧,系统不让这么做放弃也行

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024

生产者消费者本就是两个线程,问题在于 需要通过 onSynthesizeText() 获取后续所有的段落文本,而我又不能去阻塞它。 安卓文档也说:

Synthesis must be synchronous which means the engine must NOT hold on to the callback or call any methods on it after the method returns.
合成必须是同步的,这意味着引擎不能保留回调,也不能在方法返回后对其调用任何方法。

你没明白我的意思,我想说的是自己写一个生产者消费者模型,一旦某一个callback失效,就终止所有合成和后续播放

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024

问题是callback我没办法知道它是否失效

如果朗读停止后,依旧通过callback.audioAvailable写入音频数据,音频不会被播放且返回码为-1,正常为0

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024

问题是callback我没办法知道它是否失效

如果朗读停止后,依旧通过callback.audioAvailable写入音频数据,音频不会被播放且返回码为-1,正常为0

好吧,不阻塞时不行

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

就算暂停后callback.audioAvailable()也是返回 0 SUCCESS,所以说没法知道是否暂停

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024

有个不成熟的想法,如果你还想试试的话可以看看。在onSynthesizeText方法内,在方法的最后让最后一个请求线程阻塞,之前的线程返回。实现类似这样:

	private volatile boolean isLast = true;
	private volatile boolean isDone = false;
	private final Object mLock = new Object();
	
	protected void onSynthesizeText(SynthesisRequest request, SynthesisCallback callback) {
		...
		// 让之前请求的线程释放锁
		isLast = false;
        	// 当前(最后一个)请求线程获取锁
		synchronized (mLock) {
  			isLast = true;
			// 接收到后来线程请求、或所有合成完成时释放锁
			while (isLast && !isDone) {
				Thread.sleep(100);
			}
		}
	}

实在不想折腾就算了,可以理解

from tts-server-android.

GeminiT369 avatar GeminiT369 commented on May 13, 2024

就算暂停后callback.audioAvailable()也是返回 0 SUCCESS,所以说没法知道是否暂停

对,看来这个方法必须阻塞了

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

onSynthesizeText在return后系统才会调用后续的onSynthesizeText
这行不通吧

from tts-server-android.

jing332 avatar jing332 commented on May 13, 2024

算了 不折腾了

from tts-server-android.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.