前言
我司一个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的基本用法,和官方代码基本相同,没有微创新。
试验
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