B站直播云逗猫趟坑记

思路大概就是做一个机械臂,上面安一根逗猫棒,开直播。然后观众通过弹幕指令控制逗猫棒来逗猫。事实证明,这是一次失败的尝试,因为小猫咪并不会按照人类的想法行事,他们根本不去镜头底下营业!当然折腾的过程还是得记录下来。

设备

首先在淘宝上买了一个机械臂
机械臂
再在淘宝上买了一个舵机控制板
舵机控制板
这块控制板我给了中评,他在功能上没啥问题,但是不给USB口通信协议。他们自家的上位机软件(Windows版)可以通过这个USB口来控制这块板子,但是搞开发还非得走串口,好在我恰好有个树莓派4B,就不用买USB/TTL转接器(其实还是买比较好,也不贵,十几块钱,比我接下来蛋疼的折腾省心多了)。

技术路线

  1. 直播。准备在B站直播,弄两个摄像头,对准猫经常出没的区域。再加上几个文本对玩法做文字说明,这么多内容必然是需要专业的直播软件了,这里使用了常用的OBS。可惜OBS没有ARM版,所以树莓派上是不能运行了,因此需要准备一台专门的,带有独显的电脑推流。
  2. 弹幕抓取。这个没啥好说,基本上复制了萝莉爱萝莉的 极简式 Unity 获取 bilibili 直播弹幕、SC、上舰、礼物等 插件。我用了.NET Core平台开发,恰好Unity也是用C#的,所以他的代码我用了99.9%。B站直播弹幕的WebSocket通信详细解析可以看这个文章获取bilibili直播弹幕的WebSocket协议
  3. 机械臂控制。我采用了比较鸡贼的方式,首先通过上位机软件在板子里设定了一些动作组,然后接收到弹幕之后用树莓派给板子发送运行动作组的指令就可以了。
  4. 控制软件。软件是基于.Net Core平台写的桌面软件,.Net Core是跨平台的,界面也用了跨平台的AvaloniaUI,差不多可以理解成跨平台的WPF吧,因此理论上一次编码,可以运行在Windows,Mac,Linux上,实际下来效果也相当好,在ARM平台的的树莓派上运行也还比较稳定。

坑1:AvaloniaUI汉字显示乱码的问题

这是因为没有设置支持显示汉字的字体,设置一下就可以了,在App.axaml里面加入以下代码,将微软雅黑或者其他支持汉字的字体设置为默认字体。

<Application.Resources>
<FontFamily x:Key="msyh">Microsoft YaHei</FontFamily>
</Application.Resources>
<Application.Styles>
<Style Selector="Window">
<Setter Property="FontFamily" Value="{StaticResource msyh}" />
</Style>
</Application.Styles>

坑2:AvaloniaUI的字体跨平台问题

如果在Windows上用AvaloniaUI开发,然后发布到Linux平台,运行时大概率会遇到报错:System.InvalidOperationException: Default font family name can't be null or empty。至少我在树莓派Raspberry Pi OS和Linux Mint上都遇到了,这是因为在Windows上设置的默认字体Linux系统没有。所以首先我们要给Linux系统添加微软雅黑字体。

创建一个文件夹Microsoft YaHei

mkdir Microsoft YaHei

将微软雅黑的TTF文件复制到这个文件夹里面,然后将文件夹复制进/usr/share/fonts/

cp -R Microsoft YaHei /usr/share/fonts

然后刷新字体缓存

fc-cache

接下来就是得让应用能够识别微软雅黑字体了,AvaloniaUI自带一个字体管理类,里面没有对微软雅黑进行支持,因此我们自建一个CustomFontManagerImpl

namespace CatLiveX
{
public class CustomFontManagerImpl : IFontManagerImpl
{
private readonly Typeface[] _customTypefaces;
private readonly string _defaultFamilyName;

//Load font resources in the project, you can load multiple font resources
private readonly Typeface _defaultTypeface =
new Typeface("resm:CatLiveX.Assets.Fonts.msyh#微软雅黑");

public CustomFontManagerImpl()
{
_customTypefaces = new[] { _defaultTypeface };
_defaultFamilyName = _defaultTypeface.FontFamily.FamilyNames.PrimaryFamilyName;
}

public string GetDefaultFontFamilyName()
{
return _defaultFamilyName;
}

public IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false)
{
return _customTypefaces.Select(x => x.FontFamily.Name);
}

private readonly string[] _bcp47 = { CultureInfo.CurrentCulture.ThreeLetterISOLanguageName, CultureInfo.CurrentCulture.TwoLetterISOLanguageName };

public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily,
CultureInfo culture, out Typeface typeface)
{
foreach (var customTypeface in _customTypefaces)
{
if (customTypeface.GlyphTypeface.GetGlyph((uint)codepoint) == 0)
{
continue;
}

typeface = new Typeface(customTypeface.FontFamily.Name, fontStyle, fontWeight);

return true;
}

var fallback = SKFontManager.Default.MatchCharacter(fontFamily?.Name, (SKFontStyleWeight)fontWeight,
SKFontStyleWidth.Normal, (SKFontStyleSlant)fontStyle, _bcp47, codepoint);

typeface = new Typeface(fallback?.FamilyName ?? _defaultFamilyName, fontStyle, fontWeight);

return true;
}

public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface)
{
SKTypeface skTypeface;

switch (typeface.FontFamily.Name)
{
case FontFamily.DefaultFontFamilyName:
case "微软雅黑": //font family name
case "Microsoft YaHei":
skTypeface = SKTypeface.FromFamilyName(_defaultTypeface.FontFamily.Name);
break;
default:
skTypeface = SKTypeface.FromFamilyName(typeface.FontFamily.Name,
(SKFontStyleWeight)typeface.Weight, SKFontStyleWidth.Normal, (SKFontStyleSlant)typeface.Style);
break;
}

return new GlyphTypefaceImpl(skTypeface);
}
}
}

然后在App.axaml.cs里加入一下代码,将自定义的字体管理器注册一下,问题就解决了。

/// <summary>
/// override RegisterServices register custom service
/// </summary>
public override void RegisterServices()
{
AvaloniaLocator.CurrentMutable.Bind<IFontManagerImpl>().ToConstant(new CustomFontManagerImpl());
base.RegisterServices();
}

对于不需要支持中文的或者不特意指定字体的同志这个问题就好解决的多,只需要给Linux安装微软常用字体就完事了,一条命令的事

sudo apt-get install ttf-mscorefonts-installer

坑3:树莓派串口不稳定

树莓派开放串口只需要在rasp-config里开启就行,开放之后执行命令ls -l /dev/serial*可以查看串口,这时候有两个,一个是ttyS0、ttyAMA0。ttyAMA0是蓝牙用的串口,ttyS0是mini串口,该串口性能低,功能也简单,并且没有波特率专用的时钟源,而是由CPU内核时钟提供。因此mini串口有个致命的弱点是:波特率受到内核时钟的影响。也就是说,和CPU共用同一时钟,当CPU处理较多任务时,或者低功耗时,你的串口波特率不是稳定的,数据误传、传丢就会成为常态。所以需要将我们的通信串口改成ttyAMA0。对于树莓派4B来说,只需要在/boot/config.txt最后加一行dtoverlay=disable-bt把蓝牙禁用掉,然后重启树莓派,就会发现ttyS0、ttyAMA0的顺序变了。

另外,树莓派还会默认开启串口登录功能,在我的折腾中,这玩意会导致串口莫名其妙连接失败,返回数据也会经常错误,因此也需要禁用掉,将文件/boot/cmdline.txt里面的console=ttyAMA0,115200删掉,然后重启树莓派即可。

坑4:推流机器与树莓派联动

这个我主要是想实现当有人发送弹幕指令或者送礼物时时,直播界面可以展示相关信息。好在OBS是可以读取文本而且可以时时变更的,也就是文本变的时候推流画面中的文字会跟着变。但是因为我的控制程序是在树莓派运行的,OBS是在另一台Windows电脑运行的,因此我想到将树莓派中的文件夹共享,然后挂载成Windows电脑的网络磁盘,让OBS去读。

这里使用samba来实现树莓派网络共享功能。首先安装samba

sudo apt install samba samba-common-bin

我的程序直接放在了树莓派的桌面,也就是在Home的子文件夹下,而且只需要读,所以我都不用配置,直接设置密码就行

sudo smbpasswd -a pi

然后用在Windows文件浏览器里输入\\192.168.0.102\pi就可以访问,随后挂载即可。

坑5:小猫咪不营业

机械臂控制的逗猫棒根本吸引不到小猫咪,小猫咪也不经常去镜头底下,怎么办怎么办怎么办,求解决方法。
小猫咪

总结

所以说呀,这么多坑都是不必要趟的,一个十几块几十块的USB/TTL转接器解决上面大部分问题,一台Windows电脑,不需要使用跨平台的AvaloniaUI,不需要使用树莓派。只需要解决最后一个也是最难的小猫咪不营业的问题。

加载评论框需要翻墙