我司一个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