ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

C# tts异步连续播放

2021-05-12 14:32:06  阅读:304  来源: 互联网

标签:异步 play C# bala tts add msg 播放


前言

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

标签:异步,play,C#,bala,tts,add,msg,播放
来源: https://blog.csdn.net/LostSpeed/article/details/116705206

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有