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

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

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

<PackageReference Include="Mapsui.Avalonia" Version="4.1.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.SemanticKernel" Version="1.0.0-beta8" />
<PackageReference Include="NAudio" Version="2.2.1" />
<PackageReference Include="Whisper.net" Version="1.5.0" />
<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,修改成以下代码

public partial class App : Application
{
public override void Initialize()
{
AvaloniaXamlLoader.Load(this);
} public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
var services = new ServiceCollection();
services.AddSingleton<MainWindow>((services) => new MainWindow(services.GetRequiredService<IKernel>(), services.GetRequiredService<WhisperProcessor>())
{
DataContext = new MainWindowViewModel(),
});
services.AddHttpClient(); var openAIHttpClientHandler = new OpenAIHttpClientHandler();
var httpClient = new HttpClient(openAIHttpClientHandler);
services.AddTransient<IKernel>((serviceProvider) =>
{
return new KernelBuilder()
.WithOpenAIChatCompletionService("gpt-3.5-turbo-16k", "fastgpt-zE0ub2ZxvPMwtd6XYgDX8jyn5ubiC",
httpClient: httpClient)
.Build();
}); services.AddSingleton(() =>
{
var ggmlType = GgmlType.Base;
// 定义使用模型
var modelFileName = "ggml-base.bin"; return WhisperFactory.FromPath(modelFileName).CreateBuilder()
.WithLanguage("auto") // auto则是自动识别语言
.Build();
}); var serviceProvider = services.BuildServiceProvider(); desktop.MainWindow = serviceProvider.GetRequiredService<MainWindow>();
} base.OnFrameworkInitializationCompleted();
}
}

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

public class OpenAIHttpClientHandler : HttpClientHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.RequestUri.LocalPath == "/v1/chat/completions")
{
var uriBuilder = new UriBuilder("http://您的ChatGLM3B地址/api/v1/chat/completions");
request.RequestUri = uriBuilder.Uri;
} return base.SendAsync(request, cancellationToken);
}
}

修改ViewModels/MainWindowViewModel.cs

public class MainWindowViewModel : ViewModelBase
{
private string subtitle = string.Empty; public string Subtitle
{
get => subtitle;
set => this.RaiseAndSetIfChanged(ref subtitle, value);
} private Bitmap butBackground; public Bitmap ButBackground
{
get => butBackground;
set => this.RaiseAndSetIfChanged(ref butBackground, value);
}
}
  • ButBackground是显示麦克风图标的写到模型是为了切换图标
  • Subtitle用于显示识别的文字

添加SK插件

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

{
"schema": 1,
"type": "completion",
"description": "获取坐标",
"completion": {
"max_tokens": 1000,
"temperature": 0.3,
"top_p": 0.0,
"presence_penalty": 0.0,
"frequency_penalty": 0.0
},
"input": {
"parameters": [
{
"name": "input",
"description": "获取坐标",
"defaultValue": ""
}
]
}
}

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

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

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

<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:GisApp.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="GisApp.Views.MainWindow"
x:DataType="vm:MainWindowViewModel"
Icon="/Assets/avalonia-logo.ico"
Width="800"
Height="800"
Title="GisApp">
<Design.DataContext>
<vm:MainWindowViewModel />
</Design.DataContext>
<Grid>
<Grid Name="MapStackPanel">
</Grid>
<StackPanel HorizontalAlignment="Right" VerticalAlignment="Bottom" Background="Transparent" Margin="25">
<TextBlock Foreground="Black" Text="{Binding Subtitle}" Width="80" TextWrapping="WrapWithOverflow" Padding="8">
</TextBlock>
<Button Width="60" Click="Button_OnClick" Background="Transparent" VerticalAlignment="Center" HorizontalAlignment="Center">
<Image Name="ButBackground" Source="{Binding ButBackground}" Height="40" Width="40"></Image>
</Button>
</StackPanel>
</Grid>
</Window>

修改Views/MainWindow.axaml.cs代码

public partial class MainWindow : Window
{
private bool openVoice = false; private WaveInEvent waveIn; private readonly IKernel _kernel;
private readonly WhisperProcessor _processor; private readonly Channel<string> _channel = Channel.CreateUnbounded<string>();
private MapControl mapControl; public MainWindow(IKernel kernel, WhisperProcessor processor)
{
_kernel = kernel;
_processor = processor;
InitializeComponent();
mapControl = new MapControl();
// 默认定位到深圳
mapControl.Map = new Map()
{
CRS = "EPSG:3857",
Home = n =>
{
var centerOfLondonOntario = new MPoint(114.06667, 22.61667);
var sphericalMercatorCoordinate = SphericalMercator
.FromLonLat(centerOfLondonOntario.X, centerOfLondonOntario.Y).ToMPoint();
n.ZoomToLevel(15);
n.CenterOnAndZoomTo(sphericalMercatorCoordinate, n.Resolutions[15]);
}
};
mapControl.Map?.Layers.Add(Mapsui.Tiling.OpenStreetMap.CreateTileLayer());
MapStackPanel.Children.Add(mapControl); DataContextChanged += (sender, args) =>
{
using var voice = AssetLoader.Open(new Uri("avares://GisApp/Assets/voice.png")); ViewModel.ButBackground = new Avalonia.Media.Imaging.Bitmap(voice);
}; Task.Factory.StartNew(ReadMessage);
} private MainWindowViewModel ViewModel => (MainWindowViewModel)DataContext; private void Button_OnClick(object? sender, RoutedEventArgs e)
{
if (openVoice)
{
using var voice = AssetLoader.Open(new Uri("avares://GisApp/Assets/voice.png")); ViewModel.ButBackground = new Avalonia.Media.Imaging.Bitmap(voice); waveIn.StopRecording();
}
else
{
using var voice = AssetLoader.Open(new Uri("avares://GisApp/Assets/open-voice.png")); ViewModel.ButBackground = new Avalonia.Media.Imaging.Bitmap(voice); // 获取当前麦克风设备
waveIn = new WaveInEvent();
waveIn.DeviceNumber = 0; // 选择麦克风设备,0通常是默认设备 WaveFileWriter writer = new WaveFileWriter("recorded.wav", waveIn.WaveFormat); // 设置数据接收事件
waveIn.DataAvailable += (sender, a) =>
{
Console.WriteLine($"接收到音频数据: {a.BytesRecorded} 字节");
writer.Write(a.Buffer, 0, a.BytesRecorded);
if (writer.Position > waveIn.WaveFormat.AverageBytesPerSecond * 30)
{
waveIn.StopRecording();
}
}; // 录音结束事件
waveIn.RecordingStopped += async (sender, e) =>
{
writer?.Dispose();
writer = null; waveIn.Dispose(); await using var fileStream = File.OpenRead("recorded.wav");
using var wavStream = new MemoryStream(); await using var reader = new WaveFileReader(fileStream);
var resampler = new WdlResamplingSampleProvider(reader.ToSampleProvider(), 16000);
WaveFileWriter.WriteWavFileToStream(wavStream, resampler.ToWaveProvider16()); wavStream.Seek(0, SeekOrigin.Begin); await Dispatcher.UIThread.InvokeAsync(() => { ViewModel.Subtitle = string.Empty; }); string text = string.Empty;
await foreach (var result in _processor.ProcessAsync(wavStream))
{
await Dispatcher.UIThread.InvokeAsync(() => { ViewModel.Subtitle += text += result.Text; });
} _channel.Writer.TryWrite(text);
}; Console.WriteLine("开始录音...");
waveIn.StartRecording();
} openVoice = !openVoice;
} private async Task ReadMessage()
{
try
{
var pluginsDirectory = Path.Combine(Directory.GetCurrentDirectory(), "plugins"); var chatPlugin = _kernel
.ImportSemanticFunctionsFromDirectory(pluginsDirectory, "MapPlugin"); // 循环读取管道中的数据
while (await _channel.Reader.WaitToReadAsync())
{
// 读取管道中的数据
while (_channel.Reader.TryRead(out var message))
{
// 使用AcquireLatitudeLongitude插件,解析用户输入的地点,然后得到地点的经纬度
var value = await _kernel.RunAsync(new ContextVariables
{
["input"] = message
}, chatPlugin["AcquireLatitudeLongitude"]); // 解析字符串成模型
var acquireLatitudeLongitude =
JsonSerializer.Deserialize<AcquireLatitudeLongitude>(value.ToString()); // 使用MapPlugin插件,定位到用户输入的地点
var centerOfLondonOntario = new MPoint(acquireLatitudeLongitude.longitude, acquireLatitudeLongitude.latitude);
var sphericalMercatorCoordinate = SphericalMercator
.FromLonLat(centerOfLondonOntario.X, centerOfLondonOntario.Y).ToMPoint(); // 默认使用15级缩放
mapControl.Map.Navigator.ZoomToLevel(15);
mapControl.Map.Navigator.CenterOnAndZoomTo(sphericalMercatorCoordinate, mapControl.Map.Navigator.Resolutions[15]); }
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
} public class AcquireLatitudeLongitude
{
public double latitude { get; set; }
public double longitude { get; set; }
}
}

流程讲解:

  1. 用户点击了录制按钮触发了Button_OnClick事件,然后在Button_OnClick事件中会打开用户的麦克风,打开麦克风进行录制,在录制结束事件中使用录制完成产生的wav文件,然后拿到Whisper进行识别,识别完成以后会将识别结果写入到_channel
  2. ReadMessage则是一直监听_channel的数据,当有数据写入,这里则会读取到,然后就将数据使用下面的sk执行AcquireLatitudeLongitude函数。
 var value = await _kernel.RunAsync(new ContextVariables
{
["input"] = message
}, 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. Docker的安装、镜像加速配置

    wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce. ...

  2. flask中使用pyjwt

    **pyjwt使用教程: ** https://pyjwt.readthedocs.io/en/stable/ 使用案例 import datetime from flask import Flask ...

  3. 数字孪生和GIS系统融合能为水利领域带来什么改变?

    随着科技的不断进步,数字孪生和GIS系统的融合应用逐渐成为了水利领域的新趋势.数字孪生是指通过数字化技术模拟物理实体和过程,将现实世界与虚拟世界相结合的技术,而GIS系统则是地理信息系统,用于收集.存 ...

  4. ElasticSearch之cat nodes API

    命令样例如下: curl -X GET "https://localhost:9200/_cat/nodes?v=true&pretty" --cacert $ES_HOM ...

  5. 【GIT-精讲】从零玩转Git-基础理论

    关于版本控制 一.什么是版本控制 版本控制(Version Control Systems)版本控制(Revision control)是一种软件工程技巧 在开发的过程中,确保由不同人所编辑的同一档案 ...

  6. 文心一言 VS 讯飞星火 VS chatgpt (168)-- 算法导论13.2 1题

    一.用go语言,写出 RIGHT-ROTATE 的伪代码. 文心一言: 在计算机科学中,右旋转(RIGHT-ROTATE)是一种常见的操作,通常用于数组或链表.以下是一个使用Go语言实现右旋转的伪代码 ...

  7. CSS3学习笔记-选择器

    在CSS中,选择器是一种指定一个或多个元素的方法.可以根据元素的类型.类.ID.属性等特征来选择元素.CSS3引入了很多新的选择 器,让我们可以更加灵活和精准地选择元素. 下面介绍一些常用的CSS3选 ...

  8. Numpy计算近邻表时间对比

    技术背景 所谓的近邻表求解,就是给定N个原子的体系,找出满足cutoff要求的每一对原子.在前面的几篇博客中,我们分别介绍过CUDA近邻表计算与JAX-MD关于格点法求解近邻表的实现.虽然我们从理论上 ...

  9. DevOps常用工具全家桶,实现高效运维和交付

    DevOps常用工具全家桶,实现高效运维和交付 1.DevOps发展 DevOps发展背景: 随着互联网技术的快速发展,软件开发和运维的挑战也日益增加.传统的软件开发和运维模式往往存在分离.效率低下. ...

  10. 斯坦福 UE4 C++ ActionRoguelike游戏实例教程 10.控制台变量的用法 & 静态函数库 & 使用对象通道对碰撞进行控制

    斯坦福课程 UE4 C++ ActionRoguelike游戏实例教程 0.绪论 概述 本文对应Lecture 15, 61 - Console Variables for debugging and ...