ASP.NET MVC中的超快文本到语音(WAV – > MP3)

这个问题主要是关于Microsoft的Speech API(SAPI)对服务器工作负载的适用性,以及它是否可以在w3wp中可靠地用于语音合成。 我们有一个异步控制器使用.NET 4中的本机System.Speech程序集(不是作为Microsoft Speech Platform – Runtime Version 11的一部分提供的Microsoft.Speech)和lame.exe来生成mp3,如下所示:

  [CacheFilter] public void ListenAsync(string url) { string fileName = string.Format(@"C:\test\{0}.wav", Guid.NewGuid()); try { var t = new System.Threading.Thread(() => { using (SpeechSynthesizer ss = new SpeechSynthesizer()) { ss.SetOutputToWaveFile(fileName, new SpeechAudioFormatInfo(22050, AudioBitsPerSample.Eight, AudioChannel.Mono)); ss.Speak("Here is a test sentence..."); ss.SetOutputToNull(); ss.Dispose(); } var process = new Process() { EnableRaisingEvents = true }; process.StartInfo.FileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"bin\lame.exe"); process.StartInfo.Arguments = string.Format("-V2 {0} {1}", fileName, fileName.Replace(".wav", ".mp3")); process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardOutput = false; process.StartInfo.RedirectStandardError = false; process.Exited += (sender, e) => { System.IO.File.Delete(fileName); AsyncManager.OutstandingOperations.Decrement(); }; AsyncManager.OutstandingOperations.Increment(); process.Start(); }); t.Start(); t.Join(); } catch { } AsyncManager.Parameters["fileName"] = fileName; } public FileResult ListenCompleted(string fileName) { return base.File(fileName.Replace(".wav", ".mp3"), "audio/mp3"); } 

问题是为什么SpeechSynthesizer需要在一个单独的线程上运行才能返回(这是在SO和其他地方报告的)以及为此请求实现STAThreadRouteHandler是否比上述方法更有效/可扩展?

其次,在ASP.NET(MVC或WebForms)上下文中运行SpeakAsync有哪些选择? 我尝试过的选项似乎都没有用(参见下面的更新)。

关于如何改进这种模式的任何其他建议(即必须相互串行执行但每个都具有异步支持的两个依赖项)是受欢迎的。 我不认为这种方案在负载下是可持续的,特别是考虑到SpeechSynthesizer 已知的内存泄漏 。 考虑在不同的堆栈上一起运行此服务。

更新: SpeakSpeakAsnc选项似乎都不在STAThreadRouteHandler下工作。 前者产生:

System.InvalidOperationException:在此上下文中不允许异步操作。 启动异步操作的页面必须将Async属性设置为true,并且只能在PreRenderComplete事件之前的页面上启动异步操作。 位于System.Speech.Syntech.SpeechSynthesizer.get_VoiceSynthesizer的System.Speech.Internal.Synthesis.VoiceSynthesis..ctor(WeakReference speechSynthesizer)的System.ComponentModel.AsyncOperationManager.CreateOperation(Object userSuppliedState)中的System.Web.LegacyAspNetSynchronizationContext.OperationStarted()处于)在System.Speech.Synthesis.SpeechSynthesizer.SetOutputToWaveFile(String path,SpeechAudioFormatInfo formatInfo)

后者导致:

System.InvalidOperationException:异步操作方法“Listen”无法同步执行。 在System.Web.Mvc.Async.AsyncActionDescriptor.Execute(ControllerContext controllerContext,IDictionary`2参数)

看起来像一个自定义STA线程池(使用COM对象的ThreadStatic实例)是一种更好的方法: http : //marcinbudny.blogspot.ca/2012/04/dealing-with-sta-coms-in-web.html

更新#2 :它似乎不像System.Speech.SpeechSynthesizer需要STA处理,似乎在MTA线程上运行正常,只要您遵循该Start/Join模式。 这是一个能够正确使用SpeakAsync的新版本(问题是过早处理它!)并将WAV生成和MP3生成分解为两个单独的请求:

 [CacheFilter] [ActionName("listen-to-text")] public void ListenToTextAsync(string text) { AsyncManager.OutstandingOperations.Increment(); var t = new Thread(() => { SpeechSynthesizer ss = new SpeechSynthesizer(); string fileName = string.Format(@"C:\test\{0}.wav", Guid.NewGuid()); ss.SetOutputToWaveFile(fileName, new SpeechAudioFormatInfo(22050, AudioBitsPerSample.Eight, AudioChannel.Mono)); ss.SpeakCompleted += (sender, e) => { ss.SetOutputToNull(); ss.Dispose(); AsyncManager.Parameters["fileName"] = fileName; AsyncManager.OutstandingOperations.Decrement(); }; CustomPromptBuilder pb = new CustomPromptBuilder(settings.DefaultVoiceName); pb.AppendParagraphText(text); ss.SpeakAsync(pb); }); t.Start(); t.Join(); } [CacheFilter] public ActionResult ListenToTextCompleted(string fileName) { return RedirectToAction("mp3", new { fileName = fileName }); } [CacheFilter] [ActionName("mp3")] public void Mp3Async(string fileName) { var process = new Process() { EnableRaisingEvents = true, StartInfo = new ProcessStartInfo() { FileName = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"bin\lame.exe"), Arguments = string.Format("-V2 {0} {1}", fileName, fileName.Replace(".wav", ".mp3")), UseShellExecute = false, RedirectStandardOutput = false, RedirectStandardError = false } }; process.Exited += (sender, e) => { System.IO.File.Delete(fileName); AsyncManager.Parameters["fileName"] = fileName; AsyncManager.OutstandingOperations.Decrement(); }; AsyncManager.OutstandingOperations.Increment(); process.Start(); } [CacheFilter] public ActionResult Mp3Completed(string fileName) { return base.File(fileName.Replace(".wav", ".mp3"), "audio/mp3"); } 

I / O在服务器上非常昂贵。 你认为可以在服务器硬盘上使用多少个wav写入流? 为什么不在内存中完成所有操作并且只在完全处理后才编写mp3? mp3的数量要小得多,I / O也会占用很少的时间。 您甚至可以更改代码以将流直接返回给用户,而不是保存到mp3(如果需要)。

我如何使用LAME将wav编码为mp3 c#

这个问题现在有点老了,但这就是我正在做的事情,到目前为止它一直很好用:

  public Task Speak(string text) { return Task.Factory.StartNew(() => { using (var synthesizer = new SpeechSynthesizer()) { var ms = new MemoryStream(); synthesizer.SetOutputToWaveStream(ms); synthesizer.Speak(text); ms.Position = 0; return new FileStreamResult(ms, "audio/wav"); } }); } 

可能会帮助别人……