结合SK和ChatGLM3B+whisper+Avalonia实现语音切换城市

先创建一个Avalonia的MVVM项目模板,项目名称GisApp

项目创建完成以后添加以下nuget依赖

  1. <PackageReference Include="Mapsui.Avalonia" Version="4.1.1" />
  2. <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
  3. <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
  4. <PackageReference Include="Microsoft.SemanticKernel" Version="1.0.0-beta8" />
  5. <PackageReference Include="NAudio" Version="2.2.1" />
  6. <PackageReference Include="Whisper.net" Version="1.5.0" />
  7. <PackageReference Include="Whisper.net.Runtime" Version="1.5.0" />
  • Mapsui.Avalonia是Avalonia的一个Gis地图组件
  • Microsoft.Extensions.DependencyInjection用于构建一个DI容器
  • Microsoft.Extensions.Http用于注册一个HttpClient工厂
  • Microsoft.SemanticKernel则是SK用于构建AI插件
  • NAudio是一个用于录制语音的工具包
  • Whisper.net是一个.NET的Whisper封装Whisper用的是OpenAI开源的语音识别模型
  • Whisper.net.Runtime属于Whisper

修改App.cs

打开App.cs,修改成以下代码

  1. public partial class App : Application
  2. {
  3. public override void Initialize()
  4. {
  5. AvaloniaXamlLoader.Load(this);
  6. }
  7. public override void OnFrameworkInitializationCompleted()
  8. {
  9. if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
  10. {
  11. var services = new ServiceCollection();
  12. services.AddSingleton<MainWindow>((services) => new MainWindow(services.GetRequiredService<IKernel>(), services.GetRequiredService<WhisperProcessor>())
  13. {
  14. DataContext = new MainWindowViewModel(),
  15. });
  16. services.AddHttpClient();
  17. var openAIHttpClientHandler = new OpenAIHttpClientHandler();
  18. var httpClient = new HttpClient(openAIHttpClientHandler);
  19. services.AddTransient<IKernel>((serviceProvider) =>
  20. {
  21. return new KernelBuilder()
  22. .WithOpenAIChatCompletionService("gpt-3.5-turbo-16k", "fastgpt-zE0ub2ZxvPMwtd6XYgDX8jyn5ubiC",
  23. httpClient: httpClient)
  24. .Build();
  25. });
  26. services.AddSingleton(() =>
  27. {
  28. var ggmlType = GgmlType.Base;
  29. // 定义使用模型
  30. var modelFileName = "ggml-base.bin";
  31. return WhisperFactory.FromPath(modelFileName).CreateBuilder()
  32. .WithLanguage("auto") // auto则是自动识别语言
  33. .Build();
  34. });
  35. var serviceProvider = services.BuildServiceProvider();
  36. desktop.MainWindow = serviceProvider.GetRequiredService<MainWindow>();
  37. }
  38. base.OnFrameworkInitializationCompleted();
  39. }
  40. }

OpenAIHttpClientHandler.cs,这个文件是用于修改SK的访问地址,默认的SK只支持OpenAI官方的地址并且不能进行修改!

  1. public class OpenAIHttpClientHandler : HttpClientHandler
  2. {
  3. protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
  4. {
  5. if (request.RequestUri.LocalPath == "/v1/chat/completions")
  6. {
  7. var uriBuilder = new UriBuilder("http://您的ChatGLM3B地址/api/v1/chat/completions");
  8. request.RequestUri = uriBuilder.Uri;
  9. }
  10. return base.SendAsync(request, cancellationToken);
  11. }
  12. }

修改ViewModels/MainWindowViewModel.cs

  1. public class MainWindowViewModel : ViewModelBase
  2. {
  3. private string subtitle = string.Empty;
  4. public string Subtitle
  5. {
  6. get => subtitle;
  7. set => this.RaiseAndSetIfChanged(ref subtitle, value);
  8. }
  9. private Bitmap butBackground;
  10. public Bitmap ButBackground
  11. {
  12. get => butBackground;
  13. set => this.RaiseAndSetIfChanged(ref butBackground, value);
  14. }
  15. }
  • ButBackground是显示麦克风图标的写到模型是为了切换图标
  • Subtitle用于显示识别的文字

添加SK插件

创建文件/plugins/MapPlugin/AcquireLatitudeLongitude/config.json:这个是插件的相关配置信息

  1. {
  2. "schema": 1,
  3. "type": "completion",
  4. "description": "获取坐标",
  5. "completion": {
  6. "max_tokens": 1000,
  7. "temperature": 0.3,
  8. "top_p": 0.0,
  9. "presence_penalty": 0.0,
  10. "frequency_penalty": 0.0
  11. },
  12. "input": {
  13. "parameters": [
  14. {
  15. "name": "input",
  16. "description": "获取坐标",
  17. "defaultValue": ""
  18. }
  19. ]
  20. }
  21. }

创建文件/plugins/MapPlugin/AcquireLatitudeLongitude/skprompt.txt:下面是插件的prompt,通过以下内容可以提取用户城市然后得到城市的经纬度

  1. 请返回{{$input}}的经纬度然后返回以下格式,不要回复只需要下面这个格式:
  2. {
  3. "latitude":"",
  4. "longitude":""
  5. }

修改Views/MainWindow.axaml代码,将[素材](# 素材)添加到Assets中,

  1. <Window xmlns="https://github.com/avaloniaui"
  2. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  3. xmlns:vm="using:GisApp.ViewModels"
  4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
  7. x:Class="GisApp.Views.MainWindow"
  8. x:DataType="vm:MainWindowViewModel"
  9. Icon="/Assets/avalonia-logo.ico"
  10. Width="800"
  11. Height="800"
  12. Title="GisApp">
  13. <Design.DataContext>
  14. <vm:MainWindowViewModel />
  15. </Design.DataContext>
  16. <Grid>
  17. <Grid Name="MapStackPanel">
  18. </Grid>
  19. <StackPanel HorizontalAlignment="Right" VerticalAlignment="Bottom" Background="Transparent" Margin="25">
  20. <TextBlock Foreground="Black" Text="{Binding Subtitle}" Width="80" TextWrapping="WrapWithOverflow" Padding="8">
  21. </TextBlock>
  22. <Button Width="60" Click="Button_OnClick" Background="Transparent" VerticalAlignment="Center" HorizontalAlignment="Center">
  23. <Image Name="ButBackground" Source="{Binding ButBackground}" Height="40" Width="40"></Image>
  24. </Button>
  25. </StackPanel>
  26. </Grid>
  27. </Window>

修改Views/MainWindow.axaml.cs代码

  1. public partial class MainWindow : Window
  2. {
  3. private bool openVoice = false;
  4. private WaveInEvent waveIn;
  5. private readonly IKernel _kernel;
  6. private readonly WhisperProcessor _processor;
  7. private readonly Channel<string> _channel = Channel.CreateUnbounded<string>();
  8. private MapControl mapControl;
  9. public MainWindow(IKernel kernel, WhisperProcessor processor)
  10. {
  11. _kernel = kernel;
  12. _processor = processor;
  13. InitializeComponent();
  14. mapControl = new MapControl();
  15. // 默认定位到深圳
  16. mapControl.Map = new Map()
  17. {
  18. CRS = "EPSG:3857",
  19. Home = n =>
  20. {
  21. var centerOfLondonOntario = new MPoint(114.06667, 22.61667);
  22. var sphericalMercatorCoordinate = SphericalMercator
  23. .FromLonLat(centerOfLondonOntario.X, centerOfLondonOntario.Y).ToMPoint();
  24. n.ZoomToLevel(15);
  25. n.CenterOnAndZoomTo(sphericalMercatorCoordinate, n.Resolutions[15]);
  26. }
  27. };
  28. mapControl.Map?.Layers.Add(Mapsui.Tiling.OpenStreetMap.CreateTileLayer());
  29. MapStackPanel.Children.Add(mapControl);
  30. DataContextChanged += (sender, args) =>
  31. {
  32. using var voice = AssetLoader.Open(new Uri("avares://GisApp/Assets/voice.png"));
  33. ViewModel.ButBackground = new Avalonia.Media.Imaging.Bitmap(voice);
  34. };
  35. Task.Factory.StartNew(ReadMessage);
  36. }
  37. private MainWindowViewModel ViewModel => (MainWindowViewModel)DataContext;
  38. private void Button_OnClick(object? sender, RoutedEventArgs e)
  39. {
  40. if (openVoice)
  41. {
  42. using var voice = AssetLoader.Open(new Uri("avares://GisApp/Assets/voice.png"));
  43. ViewModel.ButBackground = new Avalonia.Media.Imaging.Bitmap(voice);
  44. waveIn.StopRecording();
  45. }
  46. else
  47. {
  48. using var voice = AssetLoader.Open(new Uri("avares://GisApp/Assets/open-voice.png"));
  49. ViewModel.ButBackground = new Avalonia.Media.Imaging.Bitmap(voice);
  50. // 获取当前麦克风设备
  51. waveIn = new WaveInEvent();
  52. waveIn.DeviceNumber = 0; // 选择麦克风设备,0通常是默认设备
  53. WaveFileWriter writer = new WaveFileWriter("recorded.wav", waveIn.WaveFormat);
  54. // 设置数据接收事件
  55. waveIn.DataAvailable += (sender, a) =>
  56. {
  57. Console.WriteLine($"接收到音频数据: {a.BytesRecorded} 字节");
  58. writer.Write(a.Buffer, 0, a.BytesRecorded);
  59. if (writer.Position > waveIn.WaveFormat.AverageBytesPerSecond * 30)
  60. {
  61. waveIn.StopRecording();
  62. }
  63. };
  64. // 录音结束事件
  65. waveIn.RecordingStopped += async (sender, e) =>
  66. {
  67. writer?.Dispose();
  68. writer = null;
  69. waveIn.Dispose();
  70. await using var fileStream = File.OpenRead("recorded.wav");
  71. using var wavStream = new MemoryStream();
  72. await using var reader = new WaveFileReader(fileStream);
  73. var resampler = new WdlResamplingSampleProvider(reader.ToSampleProvider(), 16000);
  74. WaveFileWriter.WriteWavFileToStream(wavStream, resampler.ToWaveProvider16());
  75. wavStream.Seek(0, SeekOrigin.Begin);
  76. await Dispatcher.UIThread.InvokeAsync(() => { ViewModel.Subtitle = string.Empty; });
  77. string text = string.Empty;
  78. await foreach (var result in _processor.ProcessAsync(wavStream))
  79. {
  80. await Dispatcher.UIThread.InvokeAsync(() => { ViewModel.Subtitle += text += result.Text; });
  81. }
  82. _channel.Writer.TryWrite(text);
  83. };
  84. Console.WriteLine("开始录音...");
  85. waveIn.StartRecording();
  86. }
  87. openVoice = !openVoice;
  88. }
  89. private async Task ReadMessage()
  90. {
  91. try
  92. {
  93. var pluginsDirectory = Path.Combine(Directory.GetCurrentDirectory(), "plugins");
  94. var chatPlugin = _kernel
  95. .ImportSemanticFunctionsFromDirectory(pluginsDirectory, "MapPlugin");
  96. // 循环读取管道中的数据
  97. while (await _channel.Reader.WaitToReadAsync())
  98. {
  99. // 读取管道中的数据
  100. while (_channel.Reader.TryRead(out var message))
  101. {
  102. // 使用AcquireLatitudeLongitude插件,解析用户输入的地点,然后得到地点的经纬度
  103. var value = await _kernel.RunAsync(new ContextVariables
  104. {
  105. ["input"] = message
  106. }, chatPlugin["AcquireLatitudeLongitude"]);
  107. // 解析字符串成模型
  108. var acquireLatitudeLongitude =
  109. JsonSerializer.Deserialize<AcquireLatitudeLongitude>(value.ToString());
  110. // 使用MapPlugin插件,定位到用户输入的地点
  111. var centerOfLondonOntario = new MPoint(acquireLatitudeLongitude.longitude, acquireLatitudeLongitude.latitude);
  112. var sphericalMercatorCoordinate = SphericalMercator
  113. .FromLonLat(centerOfLondonOntario.X, centerOfLondonOntario.Y).ToMPoint();
  114. // 默认使用15级缩放
  115. mapControl.Map.Navigator.ZoomToLevel(15);
  116. mapControl.Map.Navigator.CenterOnAndZoomTo(sphericalMercatorCoordinate, mapControl.Map.Navigator.Resolutions[15]);
  117. }
  118. }
  119. }
  120. catch (Exception e)
  121. {
  122. Console.WriteLine(e);
  123. }
  124. }
  125. public class AcquireLatitudeLongitude
  126. {
  127. public double latitude { get; set; }
  128. public double longitude { get; set; }
  129. }
  130. }

流程讲解:

  1. 用户点击了录制按钮触发了Button_OnClick事件,然后在Button_OnClick事件中会打开用户的麦克风,打开麦克风进行录制,在录制结束事件中使用录制完成产生的wav文件,然后拿到Whisper进行识别,识别完成以后会将识别结果写入到_channel
  2. ReadMessage则是一直监听_channel的数据,当有数据写入,这里则会读取到,然后就将数据使用下面的sk执行AcquireLatitudeLongitude函数。
  1. var value = await _kernel.RunAsync(new ContextVariables
  2. {
  3. ["input"] = message
  4. }, chatPlugin["AcquireLatitudeLongitude"]);
  1. 在解析value得到用户的城市经纬度
  2. 通过mapControl.Map.Navigator修改到指定经纬度。

完整的操作流程就完成了,当然实际业务会比这个更复杂。

素材



分享总结

讨论总结:

在本次会议中,讨论了如何结合SK、ChatGLM3B、Whisper和Avalonia来实现语音切换城市的功能。具体讨论了创建Avalonia的MVVM项目模板,添加了相关的NuGet依赖,修改了App.cs、ViewModels/MainWindowViewModel.cs以及添加了SK插件的相关配置和文件。

行动项目:

  1. 创建Avalonia的MVVM项目模板,项目名称为GisApp
  2. 添加所需的NuGet依赖,包括Mapsui.Avalonia, Microsoft.Extensions.DependencyInjection, Microsoft.Extensions.Http, Microsoft.SemanticKernel, NAudio, Whisper.netWhisper.net.Runtime
  3. 修改App.csOpenAIHttpClientHandler.csViewModels/MainWindowViewModel.cs以及相关的视图文件。
  4. 添加SK插件,包括创建相关的配置信息和prompt文件。
  5. 实现录制语音、语音识别和切换城市的功能流程。

技术交流群:737776595

结合SK和ChatGLM3B+whisper+Avalonia实现语音切换城市的更多相关文章

  1. Linux ALSA音频库(二) 环境测试+音频合成+语音切换 项目代码分享

    1. 环境测试 alsa_test.c #include <alsa/asoundlib.h> #include <stdio.h> // 官方测试代码, 运行后只要有一堆信息 ...

  2. iOS OC环信实时语音切换听筒免提听不到声音报错:AVAudioSessionErrorCodeBadParam

    出现这个报错:AVAudioSessionErrorCodeBadParam 先看看你的问题是不是在切换听筒免提的时候 听不到声音了, 不是的可以继续搜索去了   问题在这里 把圈住的那个货换成这个就 ...

  3. extjs实现多国语音切换

    http://kuyur.info/blog/archives/2490 http://blog.chinaunix.net/uid-28661623-id-3779637.html http://b ...

  4. 闻其声而知雅意,基于Pytorch(mps/cpu/cuda)的人工智能AI本地语音识别库Whisper(Python3.10)

    前文回溯,之前一篇:含辞未吐,声若幽兰,史上最强免费人工智能AI语音合成TTS服务微软Azure(Python3.10接入),利用AI技术将文本合成语音,现在反过来,利用开源库Whisper再将语音转 ...

  5. 基于Matlab的MMSE的语音增强算法的研究

    本课题隶属于学校的创新性课题研究项目.2012年就已经做完了,今天一并拿来发表.   目录: --基于谱减法的语音信号增强算法..................................... ...

  6. Chapter 3:Speech Production and Perception

    作者:桂. 时间:2017-05-24  09:09:36 主要是<Speech enhancement: theory and practice>的读书笔记,全部内容可以点击这里. 一. ...

  7. 开源不到 48 小时获 35k star 的推荐算法「GitHub 热点速览」

    本周的热点除了 GPT 各类衍生品之外,还多了一个被马斯克预告过.在愚人节开源出来的推特推荐算法,开源不到 2 天就有了 35k+ 的 star,有意思的是,除了推荐算法本身之外,阅读源码的工程师们甚 ...

  8. 天气预报API(三):免费接口测试(“旧编码”)

    说明 我以参考文章为引子,自己测试并扩展,努力寻找更多的气象API... 本文所有测试均以青岛为例. 本文所列接口城市代码(cityid)参数都使用的 "旧编码": 全国城市代码列 ...

  9. WP8.1开发者预览版本号已知 Bug

    偶的 Lumia 920 已经升级到最新的 8.1 开发者预览版本号,使用中没有发现什么问题. 可能是由于偶玩手机的情况比較少吧!忽然看到 MS 停止此版本号的更新,并说明有非常多的 BUG,偶就郁闷 ...

  10. Python 简单的天气预报

    轻巧的树莓派一直是大家的热爱,在上面开发一些小东西让我们很有成就感,而在linux下,python能使麻烦的操作变得简单,而树莓派功耗还很低,相结合,完美! 1,直接进入正题,一般在linux或树莓派 ...

随机推荐

  1. 使用axios发送请求的几种方式

    1.是什么? axios 它的底层是用了 XMLHttpRequest(xhr)方式发送请求和接收响应,xhr 相对于之前讲过的 fetch api 来说,功能更强大,但由于是比较老的 api,不支持 ...

  2. Git使用(GitEE)

    Git分布式版本控制工具 1. Git概述 1.1 Git历史 Git 诞生于一个极富纷争大举创新的年代.Linux 内核开源项目有着为数众多的参与者. 绝大多数的 Linux 内核维护工作都花在了提 ...

  3. Scrapy爬虫文件代码基本认识和细节解释

    import scrapy from scrapy.http.request import Request from scrapy.http.response.html import HtmlResp ...

  4. NLP复习之向量语义

    向量语义 词汇语义 语义概念(sense or concept)是单词含义(word sense)的组成部分,词原型可以是多义的. 同义词是指:在某些或者全部的上下文中,单词之间有相同或近似含义 可能 ...

  5. Pulsar3.0新功能介绍

    在上一篇文章 Pulsar3.0 升级指北讲了关于升级 Pulsar 集群的关键步骤与灾难恢复,本次主要分享一些 Pulsar3.0 的新功能与可能带来的一些问题. 升级后所遇到的问题 先来个欲扬先抑 ...

  6. 【OpenCV】在MacOS上使用OpenCvSharp

    前言   OpenCV是一个基于Apache2.0许可(开源)发行的跨平台计算机视觉和机器学习软件库,它具有C++,Python,Java和MATLAB接口,并支持Windows,Linux,Andr ...

  7. 在ubuntu下将virtualbox虚拟机的磁盘重设大小的方法

    1.VBoxManage modifyhd /home/beyond/xxx.vdi --resize 20480 {20480(单位:M)是你要扩容之后的总大小,/home/beyond 是你存放 ...

  8. rime中州韵 输入效果一览 100+增强功能效果

    rime是一个定制化程度很高的输入法框架, 我们可以在该框架上搭建适合自己的输入法程序.我们将在专栏 小狼毫 Rime 保姆教程 中完成以下近百种定制化效果的配置与演示.欢迎订阅. 以下为个性化定制的 ...

  9. (转)Harbor 启用镜像扫描功能方法

    A demo environment with the latest Harbor stable build installed. For additional information please ...

  10. 高性能网络设计秘笈:深入剖析Linux网络IO与epoll

    本文分享自华为云社区<高性能网络设计秘笈:深入剖析Linux网络IO与epoll>,作者: Lion Long . 一.epoll简介 epoll是Linux内核中一种可扩展的IO事件处理 ...