早先在录制视频的时候一直使用的是 obs-auto-subtitle 作为实时字幕展示功能。不过这个是以 OBS 插件的形式存在,不管是语言和功能上都有一定的限制。故而使用 Blazor server 实现一个。

总体思路

  • 实时字幕自然需要语音转文字的功能。考察了一些服务之后,发现同时具备有一定免费额度和有 C# SDK 两个条件的,就只有 Azure Cognitive Service 了。故而选择了它。

  • 使用 Blazor server 从服务端实时刷新页面到前端是非常简单的事情。因此,渲染一个简单的列表文本,然后通过 OBS 的 browser 组件接入画面即可。

快乐编码

有了基本的思路,我们就可以开始快乐的编码了。

简要设计

一般来说,语音转文字服务是一个与服务端进行持续交互的过程。因此需要一个对象来保持和服务端之间的沟通。我们可以设计一个ILiveCaptioningProvider来表示这种行为:

using System;
using System.Threading.Tasks;namespace Newbe.LiveCaptioning.Services
{public interface ILiveCaptioningProvider : IAsyncDisposable{Task StartAsync();void AddCallBack(Func<CaptionItem, Task> captionCallBack);}
}

为了扩展可能适配不同提供商的可能,我们同样设计一个ILiveCaptioningProviderFactory用于表现创建ILiveCaptioningProvider的行为:

namespace Newbe.LiveCaptioning.Services
{public interface ILiveCaptioningProviderFactory{ILiveCaptioningProvider Create();}
}

有了这样两个接口,在页面上只要通过ILiveCaptioningProviderFactory创建ILiveCaptioningProvider,然后不断的接收回调展示在页面上即可。

将内容展示在页面上

有了基本的项目结构和接口,便可以尝试将内容绑定到页面上。要将实时转换的内容展示到界面上需要进行一定的算法转换。

在此之前,我们需要确定一下页面展示的预期:

  • 在页面上展示至少两行文本

  • 当一句话超过一行文本的宽度时自动进行换行

  • 当一句话结束时,下一句话自动换行

例如,上面这句话进行连续阅读时,可能会出现如下效果:

https://www.newbe.pro/images/20210724-001.gif

live caption display

主要需要注意的是,在判断是要更新当前行还是进行换行,这部分逻辑需要注意进行处理。

填充实现

  • 通过 Azure SDK 提供的SpeechRecognizer对象来进行语音识别

  • 通过 Subject 将事件转换为一个简单的可观测流,简化业务回调的处理

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading.Tasks;
using Microsoft.CognitiveServices.Speech;
using Microsoft.CognitiveServices.Speech.Audio;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;namespace Newbe.LiveCaptioning.Services
{public class AzureLiveCaptioningProvider : ILiveCaptioningProvider{private readonly ILogger<AzureLiveCaptioningProvider> _logger;private readonly IOptions<LiveCaptionOptions> _options;private AudioConfig _audioConfig;private SpeechRecognizer _recognizer;private readonly List<Func<CaptionItem, Task>> _callbacks = new();private Subject<CaptionItem> _sub;public AzureLiveCaptioningProvider(ILogger<AzureLiveCaptioningProvider> logger,IOptions<LiveCaptionOptions> options){_logger = logger;_options = options;}public async Task StartAsync(){var azureProviderOptions = _options.Value.Azure;var speechConfig = SpeechConfig.FromSubscription(azureProviderOptions.Key, azureProviderOptions.Region);speechConfig.SpeechRecognitionLanguage = azureProviderOptions.Language;_audioConfig = AudioConfig.FromDefaultMicrophoneInput();_recognizer = new SpeechRecognizer(speechConfig, _audioConfig);_sub = new Subject<CaptionItem>();_sub.Select(item => Observable.FromAsync(async () =>{try{await Task.WhenAll(_callbacks.Select(f => f.Invoke(item)));}catch (Exception e){_logger.LogError(e, "failed to recognize");}})).Merge().Subscribe();_recognizer.Recognizing += (sender, args) =>{_sub.OnNext(new CaptionItem{Text = args.Result.Text,LineEnd = false});};_recognizer.Recognized += (sender, args) =>{_sub.OnNext(new CaptionItem{Text = args.Result.Text,LineEnd = true});};await _recognizer.StartContinuousRecognitionAsync();}public void AddCallBack(Func<CaptionItem, Task> captionCallBack){_callbacks.Add(captionCallBack);}public ValueTask DisposeAsync(){_recognizer?.Dispose();_audioConfig?.Dispose();_sub?.Dispose();return ValueTask.CompletedTask;}}
}
  • 实现工厂的方式非常多,这里采用 Autofac 来协助完成对象的创建

using Autofac;
using Microsoft.Extensions.Options;namespace Newbe.LiveCaptioning.Services
{public class LiveCaptioningProviderFactory : ILiveCaptioningProviderFactory{private readonly ILifetimeScope _lifetimeScope;private readonly IOptions<LiveCaptionOptions> _options;public LiveCaptioningProviderFactory(ILifetimeScope lifetimeScope,IOptions<LiveCaptionOptions> options){_lifetimeScope = lifetimeScope;_options = options;}public ILiveCaptioningProvider Create(){var liveCaptionProviderType = _options.Value.Provider;switch (liveCaptionProviderType){case LiveCaptionProviderType.Azure:var liveCaptioningProvider = _lifetimeScope.Resolve<AzureLiveCaptioningProvider>();return liveCaptioningProvider;default:throw new ProviderNotFoundException();}}}
}
  • 对页面逻辑进行填充,完成效果

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.Logging;
using Newbe.LiveCaptioning.Services;namespace Newbe.LiveCaptioning.Pages
{public partial class Index : IAsyncDisposable{[Inject] public ILiveCaptioningProviderFactory LiveCaptioningProviderFactory { get; set; }[Inject] public ILogger<Index> Logger { get; set; }private ILiveCaptioningProvider _liveCaptioningProvider;private readonly List<CaptionDisplayItem> _captionList = new();protected override async Task OnAfterRenderAsync(bool firstRender){await base.OnAfterRenderAsync(firstRender);if (firstRender){_liveCaptioningProvider = LiveCaptioningProviderFactory.Create();_liveCaptioningProvider.AddCallBack(CaptionCallBack);await _liveCaptioningProvider.StartAsync();}}private int maxCount = 20;private Task CaptionCallBack(CaptionItem arg){return InvokeAsync(() =>{Logger.LogDebug("Received: {Text}", arg.Text);var last = _captionList.FirstOrDefault();var newLine = false;var text = arg.Text;var skipPage = 0;if (arg.Text.Length > maxCount){skipPage = (int) Math.Floor(text.Length * 1.0 / maxCount);text = arg.Text[(skipPage * maxCount)..];}if (last == null || skipPage > last.TagCount){newLine = true;}if (newLine || _captionList.Count == 0){_captionList.Insert(0, new CaptionDisplayItem{Text = text,TagCount = arg.LineEnd ? -1 : skipPage});}else{_captionList[0].Text = text;if (arg.LineEnd){_captionList[0].TagCount = -1;}}if (_captionList.Count > 4){_captionList.RemoveRange(4, _captionList.Count - 4);}StateHasChanged();});}private record CaptionDisplayItem{public string Text { get; set; }public int TagCount { get; set; }}public async ValueTask DisposeAsync(){if (_liveCaptioningProvider != null){await _liveCaptioningProvider.DisposeAsync();}}}
}

通过以上核心的代码,就可以完成从识别到展示相关的内容。

下载与安装

在尝试进行源码了解之前,你可以通过以下步骤来初步体验一下项目的效果。

首先,你可以从 Release 页面下载和你操作系统对应的版本:

https://github.com/newbe36524/Newbe.LiveCaptioning/releases

release

然后,将这个软件包解压到预先创建好的文件夹。

unzip

接着,在 Azure Portal 中创建一个 Cognitive Services。

提示 1:语音转文字每个月有 5 个小时的免费额度,可以参见

https://azure.microsoft.com/pricing/details/cognitive-services/speech-services/?WT.mc_id=DX-MVP-5003606

提示 2:你可以通过这个帮助来创建一个免费的 Azure 账号,新账号包含有 12 个月的免费大礼包,参见

https://docs.microsoft.com/en-us/dynamics-nav/how-to--sign-up-for-a-microsoft-azure-subscription?WT.mc_id=DX-MVP-5003606

create service

region and key

随后,将生成好的 region 和 key 填入到 appsettings.Production.json 中。

记得同时修改 Language 选项,例如美式英语为 en-us,简体中文为 zh-cn。你可以通过以下链接来查看所有支持的语言:

https://docs.microsoft.com/azure/cognitive-services/speech-service/language-support?WT.mc_id=DX-MVP-5003606

update appsettings.Production.json

继而,启动 Newbe.LiveCaptioning.exe,你可以看到如下这样的提示信息,就说明一切已经正常。

region and key

最后,你可以使用浏览器打开http://localhost:5000,并对着你的话筒说话,这样便可以实时产生字幕了。

live caption

在 OBS 中加入字幕

首先,打开你的 OBS,并添加一个 browser 组件。

add browser

在组件的 url 中填入 http://localhost:5000,并设置一个合适的宽度和高度。

add browser

对着你的话筒话说,字幕就出来了。

https://www.newbe.pro/images/20210725-010.gif

test

辅助资料

Azure Speech to Text

可以通过以下链接在初步体验一下识别的效果:

https://azure.microsoft.com/services/cognitive-services/speech-to-text/?WT.mc_id=DX-MVP-5003606#overview

可以通过以下链接找到 C# SDK 的对接方案:

https://docs.microsoft.com/azure/cognitive-services/speech-service/get-started-speech-to-text?WT.mc_id=DX-MVP-5003606

Blazor server

可以通过以下链接来了解,如何通过服务端来推送 UI 变化到前端:

https://swimburger.net/blog/dotnet/pushing-ui-changes-from-blazor-server-to-browser-on-server-raised-events

可以通过以下链接来了解,如何在 UI 线程之外来出发 UI 变化(这不就是 winform 再现):

https://docs.microsoft.com/aspnet/core/blazor/components/rendering?view=aspnetcore-5.0&WT.mc_id=DX-MVP-5003606#receiving-a-call-from-something-external-to-the-blazor-rendering-and-event-handling-system

.Net core publish

通过这里了解如何将 dotnet core 程序发布为一个单文件应用

https://docs.microsoft.com/dotnet/core/deploying/single-file?WT.mc_id=DX-MVP-5003606

了解不同操作系统下发布使用的 RID

https://docs.microsoft.com/dotnet/core/rid-catalog?WT.mc_id=DX-MVP-5003606

Github

了解如何通过 github action 打包发布内容到 release 中:

https://github.com/gittools/gitreleasemanager

小结

这是一个非常简单的项目应用,开发者可以通过该项目初步的了解 Blazor 的使用方法。你可以通过以下地址来获取本项目的源代码:

https://github.com/newbe36524/Newbe.LiveCaptioning

基于 Blazor 打造一款实时字幕相关推荐

  1. 基于java的一款实时聊天系统,包含服务端 + 客户端 + web端

    最终效果 为什么先看最终效果?因为此刻代码已经撸完了.更重要的是我们带着感官的目标去进行后续的分析,可以更好地理解.标题中提到了,整个工程包含三个部分: 1.聊天服务器 聊天服务器的职责一句话解释:负 ...

  2. 腾讯如何打造一款实时对战手游

    2015年以来,手机游戏的市场偏好,逐渐从早期的休闲类.跑酷类.卡牌类游戏,转向重度.操作性更强的ARPG .FPS..MOBA类游戏.因此实时对战这一游戏玩法,也逐渐成为了手机游戏的一个核心玩法.纵 ...

  3. 解密:腾讯如何打造一款实时对战手游

    2015年以来,手机游戏的市场偏好,逐渐从早期的休闲类.跑酷类.卡牌类游戏,转向重度.操作性更强的ARPG .FPS..MOBA类游戏.因此实时对战这一游戏玩法,也逐渐成为了手机游戏的一个核心玩法.纵 ...

  4. web player html5源码,基于Flowplayer打造一款免费的WEB视频播放器附源码

    Flowplayer 是一个开源(GPL 3的)WEB视频播放器.您可以将该播放器嵌入您的网页中,如果您是开发人员,您还可以自由定制和配置播放器相关参数以达到您要的播放效果.本文主要介绍Flowpla ...

  5. php网页播放器源码免费,基于Flowplayer打造一款免费的WEB视频播放器附源码

    Flowplayer 是一个开源(GPL 3的)WEB视频播放器.您可以将该播放器嵌入您的网页中,如果您是开发人员,您还可以自由订制跟配置播放器相关参数以达到您要的播放疗效.本文主要介绍Flowpla ...

  6. Oceanus:基于Apache Flink的一站式实时计算平台

    Flink Forward是由Apache官方授权,用于介绍Flink社区的最新动态.发展计划以及Flink相关的生产实践经验的会议.2018年12月20日,Flink Forward首次来到中国举办 ...

  7. 【FastDFS】如何打造一款高可用的分布式文件系统?这次我明白了!!

    写在前面 前面我们学习了如何基于两台服务器搭建FastDFS环境,而往往在生产环境中,需要FastDFS做到高可用,那如何基于FastDFS打造一款高可用的分布式文件系统呢?别急,今天,我们就一起来基 ...

  8. 基于android的pc系统,Phoneix OS 系统一款基于安卓打造的个人电脑系统

    原标题:Phoneix OS 系统一款基于安卓打造的个人电脑系统 凤凰系统x86版是运行在英特尔(Intel) x86 CPU设备上的个人电脑系统,支持上百万主流安卓应用.相比于传统的Android, ...

  9. linux手机+华为,基于Linux打造,华为重磅宣布,开始在6款手机测试新系统

    原标题:基于Linux打造,华为重磅宣布,开始在6款手机测试新系统 全球智能手机的发展已经来到了十字路口,技术瓶颈越来越明显.有数据显示,苹果iphone手机的销量依然在下滑,没有很大的起色,这表明消 ...

最新文章

  1. SAP EWM 代码实现Transportation Unit(TU)的创建
  2. 排班系统c语言设计说明,帮我设计一个关于员工排班的C语言程序
  3. Android热修复升级探索——SO库修复方案 1
  4. 对C#面向对象三大特性的一点总结
  5. linux select读取节点数据失败_MySQL中覆盖索引查询和select*查询执行结果案例分析...
  6. python导入sas数据集_运用import过程进行SAS数据导入完全实用教程
  7. Java笔记-模拟QQ三方登录(单点登录2.0)
  8. git团队如何提交_如何使您的提交消息很棒并保持团队快乐
  9. gitlab客户端下载配置
  10. CMU计算机学院院长Andrew Moore离职,下一任院长人选未定
  11. 南沙发布全国首个智慧城市物联网大数据管理平台
  12. VC++编程之字符串解惑--Unicode MBCS
  13. 【燃烧吧 切割机】 自制微型激光切割
  14. Excel插件POI-ET扩展(NiceXSSFWorkbook)说明
  15. 想要买房的人究竟有多可悲?! --水木周平
  16. matlab fft 采样点数,MATLAB中的FFT的采样频率和采样点怎样确定
  17. SVAC的重要Feature
  18. 专题三 Problem X
  19. OpenCV中八种不同的目标追踪算法
  20. flutter1.9升级flutter2.0错误整理

热门文章

  1. 前台jsp页面向后台传汉字出现乱码问题解决办法
  2. shell对于字符串的操作
  3. 一步一步SharePoint 2007之四十三:实现自定义Workflow(2)——设置配置文件
  4. C++ : 内联函数和引用变量
  5. sublime text3搭建react native
  6. Oracle session连接数和inactive的问题记录【转】
  7. U9在SQL Server上的性能优化经验(转述) — 之 行版本快照
  8. tabnavigator_使用TabNavigator在Firefox中享受桌面Alt-Tab样式导航
  9. 计算机程序设计vb课后题,《VB程序设计》课后题答案
  10. php优化-》常用到的部分优化