C# tts异步连续播放

前言

我司一个C#的软件,遇到播放英文文本的tts消息时,只能一个一个字母的播放的bug。同事让我搞定这个bug.

源码是不可能的,前同事是兼职的,现在给钱也不给弄了。现在只有bin文件。

我担心要向exe或dll中加代码编译不过去。

我运维同事说,没有源码就把你吓到了。
WR, 站着说话真不腰疼。你再找个工程师试试,没源码让他给你添加点功能?

我以前找过一个熟识的C#正向编程工程师帮解决过问题,在dnspy反汇编单步调试的状态下,对程序实现,逻辑实现,C#框架流程,一点也不敏感。就好像突然不会C#编程的小白一样。因为我是C#新手,找过他2次,看了他2次解决问题的表现,决定以后不去麻烦他了。纯C#正向编程的工程师,对逆向后的C#代码,一点也不了解。作为工程师,还是要知识面宽一点,调试程序的底子好些才行。

用dnspy想加点代码进去,发现不行,编译不过。
只要编辑函数,没做任何修改,再编译也是编译不过的。
有的反编译源码,可以用dnspy 改代码(当然要修正编译错误,如果修不动,编译不过,那就不扯了). 如果编译不过,只能参照dnspy的反汇编实现,重写一个同名,同功能的程序。如果目标程序不是一个小的程序(简单插件),要重写,还真有点难度,不只是时间的问题(可能要系统的学一下C#, 啃啃C#的大部头才行)。

通过dnspy逆向单步调试,找到了tts播放语音的实现。
发现前同事用的tts组件是Interop.SpeechLib.dll,应该就是这个组件播放的问题。

还好运气不错,这个播放的功能在我以前逆出来的插件里面。
当时是参照dnspy的反汇编实现,用vs2017C#重写的C#DLL工程。

在网上找了一下tts播放的实现,原来dotnet4.0以上就有内置的tts播放组件System.Speech.SynthesisSpeechSynthesizer.

用msdn的示例代码和网上同学的代码试试,播放倒是可以.
但是同步播放会阻塞UI.

异步播放不会阻塞UI, 播放单条TTS也没有问题。

但是如果要连续添加消息并播放的话,就会引起多条语音的混杂播放。这不符合需求。

我的需求是:异步播放tts, 可以连续,不定时的添加消息。播放完一条,再播放下一条消息。

花了1上午,封装了一个tts播放类,实现了连续异步播放多条消息的实现。
这个实现,到现在为止(2021/5/12), 没看到csdn上有人做。
csdn上现有的博客,都是简单演示了一下MS官方的System.Speech.SynthesisSpeechSynthesizer的基本用法,和官方代码基本相同,没有微创新。

微软官方例子(https://docs.microsoft.com/zh-cn/previous-versions/office/developer/speech-technologies/hh362940(v=office.14)).

试验

using System;
using System.Collections.Generic;
using System.Linq;
using System.Speech.Synthesis;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

// code by lostspeed
// 实现多条tts消息的异步顺序播放

namespace cs_test_tts_A {
	class my_tts {
		// 构造
		public my_tts()
		{
			Console.WriteLine("my_tts");

			if (null == m_resLock) {
				m_resLock = new object();
			}

			if (null == m_tts) {
				m_tts = new SpeechSynthesizer();
				m_tts.SpeakCompleted += new EventHandler<SpeakCompletedEventArgs>(tts_play_completed);
			}

			if (null == m_list_msg) {
				m_list_msg = new List<string>();
			}
		}

		// 析构
		~my_tts()
		{
			Console.WriteLine("~my_tts");

			if (null != m_tts) {
				m_tts.SpeakAsyncCancelAll();
				m_tts = null;
			}

			if (null != m_list_msg) {
				m_list_msg.Clear();
				m_list_msg = null;
			}

			if (null != m_resLock) {
				m_resLock = null;
			}
		}

		// 添加消息
		public void add_msg(string msg)
		{
			Console.WriteLine("add_msg");

			try {
				lock (m_resLock) {
					m_list_msg.Add(msg);
					string str_tip;
					str_tip = string.Format("after add_msg, msg on list counter = {0}", m_list_msg.Count);
					Console.WriteLine(str_tip);
					play(false);
				}
			} catch (Exception ex) {
				Console.WriteLine(ex.ToString());
			}
		}

        public void clear_all_msg()
        {
            Console.WriteLine("clear_all_msg");

            try
            {
                lock (m_resLock)
                {
                    m_list_msg.Clear();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

		// 播放
		// bForce = false, 由添加消息发起的播放, 不强制,有条件判断(播放完时,才播放下一条)
		// bForce = true, 由播放完成事件发起的播放,强制播放
		public void play(bool bForce)
		{
			string msg;
			Console.WriteLine("play");

			// 只有没有播放过,才主动播放;否则只是先队列中添加消息
			// 如果已经播放过,都是由播放完成事件来发起下一条信息的播放
			try {
				lock (m_resLock) {
					if (m_b_first_play || bForce) {
						if (m_list_msg.Count > 0) {
							m_b_first_play = false;
							msg = m_list_msg[0];
							m_list_msg.RemoveAt(0);
							PromptBuilder builder = new PromptBuilder();
							builder.AppendText(msg);
							string str_tip;
							str_tip = string.Format("play msg = [{0}]", msg);
							Console.WriteLine(str_tip);
							m_Prompt_cur = m_tts.SpeakAsync(builder);
						}
					}
				}
			} catch (Exception ex) {
				Console.WriteLine(ex.ToString());
			}
		}

		// 停止
		public void stop()
		{
			Console.WriteLine("stop");

			if (null != m_tts) {
				lock (m_tts) {
					if (null != m_Prompt_cur) {
						m_tts.SpeakAsyncCancel(m_Prompt_cur);
						m_b_first_play = true;
					}
				}
			}
		}

		void tts_play_completed(object sender, SpeakCompletedEventArgs e)
		{
			Console.WriteLine("tts_play_completed");

			try {
				// tts当前播放完成
				lock (m_resLock) {
					// 如果是播放完成且队列为空 m_b_first_play = true
					m_b_first_play = (m_list_msg.Count > 0) ? false : true;

					if (m_list_msg.Count > 0) {
						play(true); // 由播放完成事件发起的下一条信息的播放
						string str_tip;
						str_tip = string.Format("after synth_SpeakCompleted, msg on list counter = {0}", m_list_msg.Count);
						Console.WriteLine(str_tip);
					}
				}
			} catch (Exception ex) {
				Console.WriteLine(ex.ToString());
			}
		}

		private object m_resLock = null; // 一把大锁(资源锁)

		private SpeechSynthesizer m_tts = null;
		Prompt m_Prompt_cur = null;

		private List<String> m_list_msg = null; // new List<string>(sArray)

		// 如果是第一次播放(程序第一次运行时播放) m_b_first_play = true
		// 如果是播放完成且队列为空 m_b_first_play = true
		private bool m_b_first_play = true; // 是否第一次播放tts
	}

	class Program {
		// msdn 异步播放语音的例子
		// https://docs.microsoft.com/zh-cn/previous-versions/office/developer/speech-technologies/hh362940(v=office.14)
		static void Main(string[] args)
		{
			my_tts tts_now = new my_tts();
			tts_now.add_msg("1. hello ms tts bala bala bala bala bala bala bala");
			tts_now.add_msg("2. hello ms tts bala bala bala bala bala bala bala");
			tts_now.add_msg("3. hello ms tts bala bala bala bala bala bala bala");
			tts_now.add_msg("4. hello ms tts bala bala bala bala bala bala bala");
			tts_now.add_msg("5. hello ms tts bala bala bala bala bala bala bala");
			tts_now.add_msg("6. hello ms tts bala bala bala bala bala bala bala");
			tts_now.add_msg("7. hello ms tts bala bala bala bala bala bala bala");
			tts_now.add_msg("8. hello ms tts bala bala bala bala bala bala bala");

            // 模拟停止播放
            Thread.Sleep(3 * 1000);
            tts_now.stop();

            // 模拟恢复播放(播放下一条)
            Thread.Sleep(3 * 1000);
            tts_now.play(false);

            // 模拟再次播放时,只播放当前加入的消息
            Thread.Sleep(3 * 1000);
            tts_now.stop();
            tts_now.clear_all_msg();
            tts_now.add_msg("this is new message, only play this message");
            tts_now.play(false);

            Console.WriteLine("press any key to exit");
			Console.ReadKey();
		}
	}
}

运行效果

my_tts
add_msg
after add_msg, msg on list counter = 1
play
play msg = [1. hello ms tts bala bala bala bala bala bala bala]
add_msg
after add_msg, msg on list counter = 1
play
add_msg
after add_msg, msg on list counter = 2
play
add_msg
after add_msg, msg on list counter = 3
play
add_msg
after add_msg, msg on list counter = 4
play
add_msg
after add_msg, msg on list counter = 5
play
add_msg
after add_msg, msg on list counter = 6
play
add_msg
after add_msg, msg on list counter = 7
play
stop
tts_play_completed
play
play msg = [2. hello ms tts bala bala bala bala bala bala bala]
after synth_SpeakCompleted, msg on list counter = 6
play
tts_play_completed
play
play msg = [3. hello ms tts bala bala bala bala bala bala bala]
after synth_SpeakCompleted, msg on list counter = 5
stop
tts_play_completed
play
clear_all_msg
play msg = [4. hello ms tts bala bala bala bala bala bala bala]
after synth_SpeakCompleted, msg on list counter = 4
add_msg
after add_msg, msg on list counter = 1
play
play
press any key to exit
tts_play_completed
play
play msg = [this is new message, only play this message]
after synth_SpeakCompleted, msg on list counter = 0
tts_play_completed

上一篇:【PC工具】更新文字转语音、文字文本朗读工具,语音朗读软件,TTS语音合成...


下一篇:Android学习之TTS踩坑笔记