写了一个FishSpeech的教程:使用FishSpeech进行语音合成推理 - 天命小猪 - 博客园

研究了一下如何调用服务器API,朗读文本。

经过调研,决定使用NAudio库播放音频。遇到了一些问题,如流媒体如何播放。

流媒体请求时需要设定请求方式,否则需要等到流全部加载完成才能继续。不能用 response = await client.PostAsync(url, content); 而是改用 response = await client.SendAsync(postRequest, HttpCompletionOption.ResponseHeadersRead);

var postRequest = new HttpRequestMessage(HttpMethod.Post, url);
postRequest.Content = content;
//流媒体请求头
response = await client.SendAsync(postRequest, HttpCompletionOption.ResponseHeadersRead);

wav流媒体需要用bufferedWaveProvider缓存字节流,并判断缓冲是否快溢出。

        private BufferedWaveProvider bufferedWaveProvider;
private bool IsBufferNearlyFull
{
get
{
return bufferedWaveProvider != null &&
bufferedWaveProvider.BufferLength - bufferedWaveProvider.BufferedBytes
< bufferedWaveProvider.WaveFormat.AverageBytesPerSecond / 4;
}
}

播放流媒体需要等待播放完成再结束,否则会提前释放对象导致播放不完整。

// 等待播放完成
while (bufferedWaveProvider.BufferedBytes != 0)
{
Debug.WriteLine("等待播放完成");
await Task.Delay(500);
}

完整参考:

using NAudio.Wave;
using Newtonsoft.Json;
using System.Diagnostics;
using System.Net.Http;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading; namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private BufferedWaveProvider bufferedWaveProvider;
private bool IsBufferNearlyFull
{
get
{
return bufferedWaveProvider != null &&
bufferedWaveProvider.BufferLength - bufferedWaveProvider.BufferedBytes
< bufferedWaveProvider.WaveFormat.AverageBytesPerSecond / 4;
}
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
var url = $"{TextApiServer.Text.Trim()}";
var payload = new
{
text = $"{TextSource.Text.Trim()}",
chunk_length = 200,
format = "wav",
references = new object[] { },
reference_id = "毕业女",
use_memory_cache = "on",
normalize = true,
streaming = true as bool?,
max_new_tokens = 1024,
top_p = 0.7,
repetition_penalty = 1.2,
temperature = 0.7
}; var json = JsonConvert.SerializeObject(payload);
var content = new StringContent(json, Encoding.UTF8, "application/json"); try
{
using (var client = new HttpClient())
{
HttpResponseMessage response = null;
if (payload.streaming == true)
{
var postRequest = new HttpRequestMessage(HttpMethod.Post, url);
postRequest.Content = content;
//流媒体请求头
response = await client.SendAsync(postRequest, HttpCompletionOption.ResponseHeadersRead);
}
else
{
response = await client.PostAsync(url, content);
} if (response.IsSuccessStatusCode)
{
Debug.WriteLine("请求成功");
using (var stream = await response.Content.ReadAsStreamAsync())
{ if (payload.format.ToString() == "mp3")
{
using (var reader = new Mp3FileReader(stream))
using (var waveOut = new WaveOutEvent())
{
waveOut.Init(reader);
waveOut.Play(); // 等待播放完成
while (waveOut.PlaybackState == PlaybackState.Playing)
{
await Task.Delay(500);
//Thread.Sleep(100);
}
}
Debug.WriteLine("End");
}
else if (payload.format.ToString() == "wav")
{ #region WAV //// 假设音频格式为 16bit 16kHz 单声道
var waveFormat = new WaveFormat(44100, 16, 1); bufferedWaveProvider = new BufferedWaveProvider(waveFormat)
{
BufferDuration = TimeSpan.FromSeconds(20) // 设置缓冲区大小
}; using (var waveOut = new WaveOutEvent())
{
waveOut.Init(bufferedWaveProvider);
waveOut.Play();
// 5. 持续读取流数据
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
if (IsBufferNearlyFull)
{
Debug.WriteLine("Buffer getting full, taking a break");
//await Task.Delay(500);
Thread.Sleep(500);
}
Debug.WriteLine($"Add bytes,length:{bytesRead}");
bufferedWaveProvider.AddSamples(buffer, 0, bytesRead);
}
// 等待播放完成
while (bufferedWaveProvider.BufferedBytes != 0)
{
Debug.WriteLine("等待播放完成");
await Task.Delay(500);
}
Debug.WriteLine("End");
}
#endregion
}
}
}
else
{
Debug.WriteLine($"Error: {response.StatusCode}");
throw new Exception(response.Content.ToString());
}
} }
catch (Exception ex)
{
throw ex;
}
}
}
}

Git地址:6112562a/WpfAppDemo: WPF demo应用

WPF调用FishSpeech的Demo的更多相关文章

  1. WPF调用Matlab函数方法

    有的时候用C#写图像处理方法,比较费事,不如Matlab简单,但是Matlab又做不出WPF那样的好看界面,怎么办呢. 今天正好我要实现这个功能,就顺便写个小例子,给需要的人做个借鉴. 想要用WPF调 ...

  2. WPF [调用线程无法访问此对象,因为另一个线程拥有该对象。] 解决方案以及如何实现字体颜色的渐变

    本文说明WPF [调用线程无法访问此对象,因为另一个线程拥有该对象.] 解决方案以及如何实现字体颜色的渐变 先来看看C#中Timer的简单说明,你想必猜到实现需要用到Timer的相关知识了吧. C# ...

  3. WPF调用图片路径,或资源图片

    一.加载本项目的图片WPF引入了统一资源标识Uri(Unified Resource Identifier)来标识和访问资源.其中较为常见的情况是用Uri加载图像.Uri表达式的一般形式为:协议+授权 ...

  4. 使用Prism提供的类实现WPF MVVM点餐Demo

    使用Prism提供的类实现WPF MVVM点餐Demo 由于公司开发的技术需求,近期在学习MVVM模式开发WPF应用程序.进过一段时间的学习,感受到:学习MVVM模式,最好的方法就是用MVVM做几个D ...

  5. C# WPF 调用FFMPEG实现“SORRY 为所欲为/王境泽”表情包GIF生成软件

    C# WPF 调用FFMPEG实现“SORRY 为所欲为/王境泽”表情包GIF生成 1,调用ffmpeg将外挂字幕“嵌入”视频中,保存副本: 2,调用ffmpeg将副本视频导出为gif图片. 参考资料 ...

  6. WPF 调用资源图片

    原文:WPF 调用资源图片 最近做的wpf项目中,在开发的时候,把图片放到了bin下面,采用了imagePath =System.IO.Directory.GetCurrentDirectory()+ ...

  7. WPF 调用API修改窗体风格实现真正的无边框窗体

    原文:WPF 调用API修改窗体风格实现真正的无边框窗体 WPF中设置无边框窗体似乎是要将WindowStyle设置为None,AllowTransparency=true,这样才能达到WinForm ...

  8. WPF调用C++生成的dll文件(示例)

    注:笔者使用的VS版本为2019.1.打开VS2019,选择文件 -> 新建 -> 项目 2.选择项目 新建项目时选择C++“控制台应用”语言:C++平台:Windows项目类型:空项目 ...

  9. WPF调用office2010的ppt出错

      各位热爱WPF编程小伙伴不可避免的会遇到将ppt嵌入到自己编写的软件,可是有时候会遇到错误,此错误值出现在卸载office2013并安装其他版本office时候会出现.这是由于某些机器上offic ...

  10. WPF调用Win Form

    WPF是win form的下一代版本,现在越来越多的公司使用WPF.如何兼容已有的使用win form开发的应用程序呢?下面有三种方式来在WPF中调用win form. 使用WPF中的WindowsF ...

随机推荐

  1. WPS Pro 最新专业版,一站式办公

    聊一聊 随着科技的进步,办公软件已经成为现代人工作和学习中不可或缺的重要工具.无论是在企业.学校还是个人使用中,办公软件都能够帮助我们提高工作效率.组织信息和进行沟通.在众多的办公套件中,微软的Off ...

  2. Flutter ListView报错RenderBox was not laid out: RenderViewport#680c1 NEEDS-LAYOUT NEEDS-PAINT

    RenderBox was not laid out: RenderViewport#680c1 NEEDS-LAYOUT NEEDS-PAINT 使用ListView.builder()布局时,如果 ...

  3. 逍遥模拟器+Fiddler抓包 (附带软件)

    获取软件地址: 链接:https://pan.baidu.com/s/1zE9AECWOZlw_VDVMAnkBhQ?pwd=c0kq 提取码:c0kq 一.逍遥模拟器安装 1. 安装逍遥模拟器: 2 ...

  4. [sa-token]StpUtil.getLoginId

    闲聊 一般情况下,我们想用uid,可能需要前端将uid传过来,或者将token传来,然后我们进行识别. 用了sa-token之后,可以使用StpUtil.getLoginId()方法获取当前会话的用户 ...

  5. 2024年1月Java项目开发指南4:IDEA里配置MYSQL

    提前声明:文章首发博客园(cnblogs.com/mllt) 自动"搬家"(同步)到CSDN,如果博客园中文章发生修改是不会同步过去的,所以建议大家到我的博客园中查看 前提条件: ...

  6. JSchException: Algorithm negotiation fail问题解决之路

    最近一个需求用到了SFTP上传功能,同事之前已经封装好了SFTP工具类,用的是JSch,本着不要重复造轮子的想法,就直接拿来用了.交代下环境,JDK为1.7,JSch版本为0.1.51.自测通过.测试 ...

  7. Qt编写的项目作品15-皮肤生成器+UIDemo

    一.功能特点 自带17套精美皮肤样式,其中包括黑色.灰色.扁平等. 皮肤生成器只需要简单几步就可以生成一套自定义的皮肤. 自带了26种uidemo,非常漂亮美观,涵盖了主界面布局.菜单切换等各种效果, ...

  8. [转]C#中委托类型的BeginInvoke和EndEndInvoke方法的理解和应用

    参考链接: 1.[C#基础]c#中的BeginInvoke和EndEndInvoke 2.C#当中的BeginInvoke和EndInvoke

  9. 痞子衡嵌入式:Farewell, 我的写博故事2024

    -- 题图:苏州周庄古镇双桥 2024 年的最后一天,照旧写个年终总结.今年工作上稳步发挥,但是在生活上收获了一个新的爱好,大家可能知道,痞子衡比较爱运动,一直有在打篮球羽毛球桌球.有感于公司乒乓球文 ...

  10. ElasticSearch接口

    DSL语法 DSL为ES过滤数据时的语法,可用于查询.删除等操作 基本构成 默认分页查询,size默认为10.ES查询默认最大文档数量限制为10000,可通过 index.max_result_win ...