STM32MP157-Linux音频应用编程-简易语音助手

news2024/12/23 0:46:29

文章目录

  • 前言
  • STM32MP157简易语音助手
    • alsa-lib简介:
    • 移植alsa-lib库:
    • libcurl库简介:
    • 移植libcurl库:
    • API调用
      • 修改asrmain.c文件
      • 修改token.c文件
    • 录音
    • 文件IO
      • 打开音频文件
    • 硬件控制
      • sysfs文件系统
      • 数据解析和控制
    • 多线程
    • 主循环
    • 实现效果及注意事项
      • 实现效果
      • 注意事项
  • 源代码(转载请注明出处)


前言

本篇分享:

Linux应用编程之音频编程,使用户用户可以使用语音控制开发板上的LED灯和蜂鸣器模块。

环境介绍:

系统:Linux
硬件:正点原子STM32MP157开发板
声卡:开发板自带


STM32MP157简易语音助手

实现目标 :用户可以使用语音控制开发板上的LED灯和蜂鸣器模块。
知识点 : C语言、文件IO、alsa-lib 库的使用、libcurl库、API调用、字符串解析、多线程。

在上一篇STM32MP157语音识别项目中,由于之前使用的交叉编译器无法正常编译使用了libcurl库的程序,导致不得不使用execl函数调用CURL指令实现(可能是由于交叉编译器的c库版本和libcurl库的c库版本不同导致)。这样的程序不够灵活(依赖操作系统提供的指令和参数格式)且性能低(调用指令时需要花费额外的系统开销和时间,包括切换上下文、启动子进程、进行系统调用等操作,而使用c库函数则避免了这些额外的开销 )。所以,这次继续沿用Linux语音识别项目中用到的libcurl库来实现。

alsa-lib简介:

alsa-lib 是一套 Linux 应用层的 C 语言函数库,为音频应用程序开发提供了一套统一、标准的接口,应用程序只需调用这一套 API 即可完成对底层声卡设备的操控,譬如播放与录音。
用户空间的 alsa-lib 对应用程序提供了统一的API 接口,这样可以隐藏驱动层的实现细节,简化了应用 程序的实现难度、无需应用程序开发人员直接去读写音频设备节点。所以,主要就是学习 alsa-lib 库函数的使用、如何基于 alsa-lib 库函数开发音频应用程序。
alsa-lib官方说明文档:https://www.alsa-project.org/alsa-doc/alsa-lib/

移植alsa-lib库:

正点STM32MP157开发板出厂已移植(非广告!),需要请参考其他教程。

要在嵌入式linux系统上运行使用alsa-lib库的程序,需要移植alsa-lib库,可以参考网上移植alsa-lib库的方法,或自行下载alsa-lib资源包,自行编译移植。

开源ALSA架构的官网地址:https://www.alsa-project.org/wiki/Main_Page

libcurl库简介:

libcurl是一个跨平台的网络协议库,支持http, https, ftp, gopher, telnet, dict, file, 和ldap 协议。libcurl同样支持HTTPS证书授权,HTTP POST, HTTP PUT, FTP 上传, HTTP基本表单上传,代理,cookies,和用户认证。

官网地址:http://curl.haxx.se/

移植libcurl库:

正点STM32MP157开发板出厂已移植(非广告!),需要请参考其他教程。

注意:curl指令和libcurl是两个不同的东西,虽然它们都用于处理HTTP请求,但是有以下区别:

  • curl指令是一个命令行工具,而libcurl是一个C语言的库,可以通过函数调用使用。
  • curl指令可以直接在终端中运行,而libcurl需要在编程时使用。
  • curl指令的功能相对简单,主要用于从终端中发送HTTP请求并获取响应,而libcurl功能更为强大,可以通过编程实现更多复杂的HTTP请求和响应处理操作。
  • curl指令可以在不同的操作系统和终端上运行,而libcurl需要在特定的平台上进行编译和部署。

总之,curl指令是一个简单、方便的工具,可以帮助开发人员快速进行HTTP请求和响应测试。而libcurl是一个功能更为强大的库,适合于在编程时进行HTTP请求和响应处理,但需要进行编译和部署。

API调用

该程序使用的是百度语音识别API
在这里插入图片描述

注册后领取免费额度及创建中文普通话应用(创建前先领取免费额度(180 天免费额度,可调用约 5 万次左右) )

在这里插入图片描述

创建好应用后,可以得到API key和Secret Key(填写到程序中的相应位置)

在这里插入图片描述

调用API相关说明,Demo代码中有多种语言的调用示例可以参考,使用c语言的话也可以直接在本程序上面再次更改:

在这里插入图片描述

API相关c文件中 需要修改的有asrmain.c、token.c和相应的头文件

修改asrmain.c文件

asrmain.c的fill_config函数中(该函数我已修改,原本无file参数,根据实际情况使用),需要修改的有:音频文件格式,API Key以及Secret Key

RETURN_CODE fill_config(struct asr_config *config,char *file) {
    // 填写网页上申请的appkey 如 g_api_key="g8eBUMSokVB1BHGmgxxxxxx"
    char api_key[] = "填写网页上申请的API key";
    // 填写网页上申请的APP SECRET 如 $secretKey="94dc99566550d87f8fa8ece112xxxxx"
    char secret_key[] = "填写网页上申请的Secret Key";
    // 需要识别的文件
    char *filename = NULL;
    filename = file;

    // 文件后缀仅支持 pcm/wav/amr 格式,极速版额外支持m4a 格式
    char format[] = "pcm";

    char *url = "http://vop.baidu.com/server_api";  // 可改为https

    //  1537 表示识别普通话,使用输入法模型。其它语种参见文档
    int dev_pid = 1537;

    char *scope = "audio_voice_assistant_get"; // # 有此scope表示有asr能力,没有请在网页里勾选,非常旧的应用可能没有
    …………

结合音频录制的程序使用的话,还需要删除示例中的main函数,run函数中的相关初始化以及API调用函数需要根据实际情况重新调整调用位置。本项目总体按照:获取token(在程序开始时获取一次即可,根据官网可知获取的token有效期为30天,重新获取token则之前获取的token失效)->调用API->得到返回结果->解析结果->对硬件控制

asrmain.c的run_asr函数中(该函数我已修改,原本无result_voice参数,根据实际情况使用),需要修改的有:禁用SSL证书验证(在文件中查到下面这个函数名即可找到需要修改的位置)和 延长连接超时时间(源代码中设定时间为5s,在开发板上连接时间更长,需要改为10s,可能是由于硬件性能较弱或者网络环境不稳定等原因导致 )。

curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10);

curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0)是一个CURL库的选项设置,用于控制CURL库对SSL证书的验证行为。默认情况下,CURL库会验证SSL证书的有效性,以确保请求的安全性。如果SSL证书验证失败,CURL库将阻止请求的进一步处理并返回一个错误。

CURLOPT_SSL_VERIFYPEER选项设置为0将禁用SSL证书验证,从而允许不受信任的证书通过。这个设置通常用于调试和测试目的,不建议在生产环境中使用,因为它会降低请求的安全性。如果需要在生产环境中使用不受信任的证书,建议使用自签名证书或受信任的CA签名证书,并通过其他手段验证证书的有效性,而不是禁用SSL证书验证。

修改token.c文件

和上面部分相同,禁用SSL证书验证。

录音

查看Linux语音识别中的录音内容

文件IO

我们需要将录制的音频文件保存到本地,就需要用到文件IO相关知识,打开音频文件以及向音频文件写数据。

打开音频文件

函数:

函数原型:
FILE *fopen(const char *filename, const char *mode)

参数:
filename -- 字符串,表示要打开的文件名称。
mode -- 字符串,表示文件的访问模式。

作用:
以指定的方式打开文件。

代码:

/*创建一个保存PCM数据的文件*/
if ((pcm_data_file = fopen(argv[1], "wb")) == NULL)
{
    printf("无法创建%s音频文件.\n", argv[1]);
    exit(1);
}
printf("用于录制的音频文件已打开.\n");

参数:
argv[1]:程序执行时传递的参数,./voice record.cpm,则该参数为"record.cpm"
"wb":只写打开或新建一个二进制文件,只允许写数据。

硬件控制

sysfs文件系统

在 Linux 系统下,一切皆文件!应用层如何操控底层硬件,同样也是通过文件 I/O 的方式来实现。本项目的硬件控制都是通过sysfs文件系统实现对LED和蜂鸣器的控制。

在嵌入式Linux开发中,sysfs文件系统通常被用来访问硬件资源,例如GPIO、I2C、SPI等外设,可以通过sysfs文件系统的接口来控制和读取硬件设备的状态信息。因此,sysfs文件系统对于嵌入式Linux开发非常重要,几乎所有的嵌入式Linux系统都会支持sysfs文件系统。

sysfs 文件系统挂载在/sys 目录下,启动开发板后可以到/sys目录查看。/sys下不同的子目录。

在这里插入图片描述

/sys目录下包含了很多子目录,每个子目录都代表一个系统设备或内核模块,其中常见的子目录包括:

  • block:块设备相关的信息,例如硬盘、光驱等。
  • bus:系统总线相关的信息,例如USB、PCI、I2C等。
  • class:设备类型相关的信息,例如输入设备、网络设备等。
  • dev:与设备文件相关的信息,例如设备号、设备名称等。
  • firmware:硬件固件相关的信息,例如BIOS、驱动程序等。
  • fs:文件系统相关的信息,例如文件系统挂载状态等。
  • kernel:内核相关的信息,例如内核版本、内核命令行参数等。
  • module:内核模块相关的信息,例如已加载的内核模块等。
  • power:电源管理相关的信息,例如电量、电源状态等。
  • sys:系统信息相关的信息,例如CPU信息、内存信息等。

这些子目录下包含了许多虚拟文件和目录,通过这些文件和目录可以方便地访问内核数据结构和设备信息,从而实现对设备的控制和监控。

在sysfs文件系统中,一个硬件设备为一个目录,设备的属性则为文件
在正点原子STM32MP157中,LED和蜂鸣器对应的设备目录均为/sys/class/leds/。
LED的触发方式控制文件为/sys/class/leds/user-led/trigger,LED亮度控制文件为/sys/class/leds/user-led/brightness。
蜂鸣器的触发方式控制未见为/sys/class/leds/beep/trigger,蜂鸣器开关控制文件为/sys/class/leds/beep/brightness。

数据解析和控制

本项目使用strstr函数对识别的结果进行判断,判断识别结果中是否包含"灯"、“蜂鸣器"字符串,有的话再判断结果中包含"开"还是"关”。
如识别的结果为"灯打开"或"开灯",程序将修改向LED灯的触发方式文件写入none(无触发),向LED亮度控制文件写入"1",这样就实现了LED灯的点亮

函数:

函数原型:
char *strstr(const char *haystack, const char *needle) 

haystack -- 要被检索的 C 字符串.
needle -- 在 haystack 字符串内要搜索的子字符串。

作用:
查看原字符串中是否存在子字符串,不存在返回NULL

部分代码:

void Voice_Controll(char result[])
{
    /*检测语音和灯的控制有关*/
    if(strstr(result,"灯")!=NULL)
    {
        if(strstr(result,"开")!=NULL && strstr(result,"关")!=NULL)
            return;
        else if(strstr(result,"开")!=NULL)  
            Led_Controll(1);  
        else if(strstr(result,"关")!=NULL)  
            Led_Controll(0);  
    }
    
    /*检测语音和蜂鸣器的控制有关*/
    ......
}

void Led_Controll(int ONOFF)
{
    int fd1,fd2;

    /*打开LED触发文件*/
    fd1 = open(LED_TRIGGER, O_WRONLY);
    if (0 > fd1) {
        perror("open error");
        exit(-1);
    }

    /*打开LED开关文件*/
    fd2 = open(LED_BRIGHTNESS, O_WRONLY);
    if (0 > fd2) {
        perror("open error");
        exit(-1);
    }

    /*根据传递参数控制LED*/
    if(ONOFF == 1)
    {
        write(fd1, "none", 4);  //先将触发模式设置为 none
        write(fd2, "1", 1);     //点亮 LED
        printf("LED已打开!\n");
    }
    else
    {
        write(fd1, "none", 4);  //先将触发模式设置为 none
        write(fd2, "0", 1);     //熄灭 LED
        printf("LED已关闭!\n");
    }

    /*关闭文件*/
    close(fd1);
    close(fd2);
}

多线程

函数:

函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg)

thread -- 传出参数,保存系统为我们分配好的线程 ID
attr -- 通常传 NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
start_routine -- 函数指针,指向线程主函数,该函数运行结束,则线程结束。
arg -- 线程主函数执行期间所使用的参数。

作用:
创建一个新线程。

函数原型:
int int truncate(const char *path,off_t length);

参数:
path -- 文件路径名。
length --  截断长度,若文件大小>length大小,额外的数据丢失。若文件大小<length大小,那么,这个文件将会被扩展,扩展的部分将补以null,也就是‘\0’。

作用:
截断或扩展文件。

代码:

/*创建子线程检测按键是否按下*/
pthread_t tid;
ret = pthread_create(&tid, NULL, button_tfn, NULL);
if (ret != 0) perror("pthread_create failed");

void *button_tfn(void *arg)
{
	struct input_event in_ev = {0};
	int fd;
	int value = -1;

	/*打开按键事件对应的文件*/
	if (0 > (fd = open("/dev/input/event1", O_RDONLY)))
	{
		perror("open error");
		exit(-1);
	}

	while(1)
	{
		/*循环读取数据*/
		if (sizeof(struct input_event) != read(fd, &in_ev, sizeof(struct input_event)))
		{
			perror("read error");
			exit(-1);
		}
		if (EV_KEY == in_ev.type && in_ev.code == 114)//114为KEY0 
		{ 
			/*按键事件*/
			switch (in_ev.value)
			{
				/*KEY0松开*/
				case 0:
					/**
					 * 1.更新按键状态为松开
					 * 2.延时等待主循环判断,否则可能出现主循环先判断标志位为1而出现PCM设备停止还在继续读数据
					 * 3.停止PCM设备
					*/
					key_flag_now = 0;
					sleep(1);
					snd_pcm_drop(capture_handle);
					break;
				/*KEY0按下*/
				case 1:
					/**
					 * 1.清空文件,使文件从头开始写,等于重新录制音频
					 * 2.同样注意顺序,先使设备恢复进入准备状态,避免出现主循环先检测到标志位为1而读取声卡设备
					 * 3.更新按键状态为按下
					*/
					truncate(pcm_file_name,1);
					snd_pcm_prepare(capture_handle);
					key_flag_now = 1;
					break;
			}
		}
		else if(EV_KEY == in_ev.type && in_ev.code == 115)//115为KEY1
		{
			/*按键事件*/
			switch (in_ev.value)
			{
				/*KEY1按下*/
				case 1:
					/*退出程序*/
					exit_program();
					break;
			}
				
		}
	}
}

主循环

主循环内判断声卡设备状态是否改变(按键状态决定),若当前声卡为运行状态则进行音频采集,若当前声卡为停止状态则调用API进行识别。

while (1)
{
    /*判断按键状态是否更新*/
    if(key_flag_now != key_flag_old)
    {
        /*视当前状态为旧状态*/
        key_flag_old = key_flag_now;

        /*若按键按下*/
        if(key_flag_now == 1)
            printf("开始采集音频数据...\n");
        /*若按键松开*/
        else
        {
            printf("采集结束!\n");

            /*调用API进行识别*/
            run_asr(&config, token,result);
            /*对识别的结果进行处理*/
            Voice_Controll(result);

            printf("请长按KEY0按键开始采集音频数据!单击KEY1退出程序!\n");
        }
    }

    /*若按键按下*/
    if(key_flag_now == 1)
    {
        /*从声卡设备读取一帧音频数据:2048字节*/
        ret = snd_pcm_readi(capture_handle, buffer, buffer_frames);
        if(0 > ret)
        {
            printf("从音频接口读取失败(%s)\n", snd_strerror(ret));
            exit(1);
        }

        /*写数据到文件: 音频的每帧数据样本大小是16位=2个字节*/
        fwrite(buffer, (ret * AUDIO_CHANNEL_SET), frame_byte, pcm_data_file);
    }
}

实现效果及注意事项

实现效果

在这里插入图片描述

如图所示长按KEY0按键开始音频录制,松开即音频录制结束,再调用百度语言API进行识别,并向用户展示识别的结果以及实现对硬件的控制。之后用户可自行选择继续识别或退出程序。

注意事项

该程序在声卡不进行录音时是将声卡设备给停止工作了的,在停止声卡设备前需要加入一小段的延时等待,若不添加延时等待,可能会出现子线程使声卡设备停止的同时主线程在读取声卡设备,从而导致下图中出现的错误:
在这里插入图片描述


源代码(转载请注明出处)

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/389654.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hive与HBase的区别及应用场景

当数据量达到一定量级的时候&#xff0c;存储和统计计算查询都会遇到问题&#xff0c;今天了解一下Hive和Hbase的区别和应用场景。 一、定义 Hive是基于Hadoop的一个数据仓库工具&#xff0c;可以将结构化的数据文件映射为一张数据库表&#xff0c;并提供简单的sql查询功能&am…

Umi使用百度地图服务

需求描述 需要在前端页面中使用地图定位功能&#xff0c;所以在前端umi项目中使用百度地图服务&#xff0c;由于umi项目默认没有入口的html文件&#xff0c;所以无法通过常规的在head中加入外链js的方式使用 百度ak zyqeLCzvQPCCNImRu9yRGOqWlEUicxxGreact使用百度api 链接:…

【Mybatis系列】Mybatis常见的分页方法以及源码理解

Mybatis-Plus的selectPage 引入依赖 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version></dependency>添加分页插件 Configuration public class My…

ifm3dlib+Python实现摄像头点云数据保存

0. 起因&需求 现有一款摄像头 O3D303&#xff0c;通过网线将其连接到局域网后&#xff0c;同一局域网的电脑可以通过可视化软件查看到各项参数以及对应的点云图。 但是如果想定制化具体的需求&#xff0c;用官方的可视化软件无疑是不可取的。这时候就需要用到SDK&#xf…

【Java】JVM

一、介绍 1.什么是JVM? JVM是一种用于计算设备的规范&#xff0c;它是一个虚构出来的机器&#xff0c;是通过在实际的计算机上仿真模拟各种功能实现的。JVM包含一套字节码指令集&#xff0c;一组寄存器&#xff0c;一个栈&#xff0c;一个垃圾回收堆和一个存储方法域。JVM屏…

面向对象设计模式:创建型模式之抽象工厂模式

一、抽象工厂模式&#xff0c;Abstract Factory Pattern 1.1 Definition 定义 抽象工厂模式是围绕一个抽象工厂&#xff08;其他工厂的工厂&#xff09;创建其他工厂的创建型模式。 1.2 Intent 意图 Provide an interface for creating families of related or dependent o…

【AutoSAR】【MCAL】Dio

一、结构 二、功能介绍 DIO&#xff08;数字输入输出&#xff09;驱动模块主要是对端口&#xff08;Port&#xff09;&#xff0c;通道&#xff08;Channel&#xff09;和通道组&#xff08;ChannelGroup&#xff09;进行读写操作。 通道&#xff08;Channel&#xff09;&…

Tomcat服务器配置以及问题解决方案

文章目录01 Tomcat简介02 Tomcat的安装03 Tomcat的使用启动Tomcat服务器 &#xff08;解决一闪而过&#xff09;测试 Tomcat 是否启动Tomcat 服务器的关闭04 Tomcat的配置配置端口控制台配置&#xff08;乱码解决&#xff09;部署工程到Tomcat中01 Tomcat简介 Tomcat是一款开源…

Android Compose——一个简单的Bilibili APP

Bilibili移动端APP简介依赖效果登录效果WebView自定义TobRow的Indicator大小首页推荐LazyGridView使用Paging3热门排行榜搜索模糊搜索富文本搜索结果视频详情合集信息Coroutines进行网络请求管理&#xff0c;避免回调地狱添加suspendwithContextGit项目链接末简介 此Demo采用A…

Motor-DK (MM32SPIN05PF, MM32SPIN06PF, MM32SPIN07PF)

输入电压范围&#xff1a;12V - 30V 使用 60V / 40A N-MOS 管 使用内建&#xff08;MM32SPIN2x&#xff09;/外挂&#xff08;MM32SPIN05 / MM32SPIN06 / MM32SPIN07&#xff09;GBW 6MHz 高速运放 x 4 MCU 使用 5V 供电 支持 48 / 64 Pin MM32SPIN 系列 MCU 支持无霍尔&#x…

LearnDash测验报告如何帮助改进您的课程

某一个场景。Pennywell 大学有一门课程“Introduction to Linear Algebra”。上学期进行了两次测验。20% 的学生在第一次测验中不及格&#xff0c;而 80% 在第二次测验中不及格。在进一步评估中&#xff0c;观察到第一次测验不及格的学生在第二次测验中也不及格。在第二次测验中…

基于Linux系统-搭建Java Web开发环境

目录 1. 安装JDK 2.安装MySQL数据库 3.安装Tomcat 4.访问Tomcat 1. 安装JDK 1.执行以下命令&#xff0c;查看yum源中JDK版本。 yum list java* 2.执行以下命令&#xff0c;使用yum安装JDK1.8。 yum -y install java-1.8.0-openjdk* 3.执行以下命令&#xff0c;查看是否安…

【软件使用】MarkText下载安装与汉化设置 (markdown快捷键收藏)

一、安装与汉化 对版本没要求的可以直接选择 3、免安装的汉化包 1、下载安装MarkText MaxText win64 https://github.com/marktext/marktext/releases/download/v0.17.1/marktext-setup.exe 使用迅雷可以快速下载 2. 配置中文语言包 中文包下载地址&#xff1a;GitHub - chi…

TPU编程竞赛系列|算能赛道冠军SO-FAST团队获第十届CCF BDCI总决赛特等奖!

近日&#xff0c;第十届中国计算机学会&#xff08;CCF&#xff09;大数据与计算智能大赛总决赛暨颁奖典礼在苏州顺利落幕&#xff0c;算能赛道的冠军队伍SO-FAST从2万余支队伍中脱颖而出&#xff0c;获得了所有赛道综合评比特等奖&#xff01; 本届CCF大赛吸引了来自全国的2万…

【MySQL】查询访问方法

查询语句经过查询优化器生成 SQL 执行计划&#xff0c;在引入索引的情况下&#xff0c;MySQL 不可能让我们什么查询都是走全表扫描&#xff0c;那样效率太低了&#xff0c;所有需要有各种各样的执行计划 &#xff0c; MySQL 会根据经验为我们的查询语句生成它认为最优的执行计划…

mac安装nvm

1、nvm介绍 &#xff08;1&#xff09;什么是nvm&#xff1f;简单来说&#xff0c;nvm是一款可以用命令行快速切换node版本的工具&#xff01; &#xff08;2&#xff09;为什么要切换node版本&#xff1f;打个比方&#xff0c;你目前正在用node 14版本&#xff0c;现在出了nod…

Greenplum-主备同步机制

我们在学习Greenplum的架构时知道&#xff0c;Greenplum中主要有Master管理层和Segment计算层。在高可用方面&#xff0c;Master通过配置一个Standby来实现主备&#xff0c;Segment则通过对实例设置镜像的方式也实现主备高可用&#xff08;其中主实例称为Primary&#xff0c;备…

网络基础(二)

目录 应用层 再谈 "协议" 协议是一种 "约定". socket api的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们要传输一些"结构化的数据" 怎么办呢? 为什么要转换呢&#xff1f; 如果我们将struct message里面…

传输线的物理基础(二):信号在传输线中的速度

铜中电子的速度信号在传输线上传输的速度有多快&#xff1f;如果人们经常错误地认为信号在传输线上的速度取决于导线中电子的速度。凭着这种错误的直觉&#xff0c;我们可能会想象降低互连的电阻会提高信号的速度。事实上&#xff0c;典型铜线中电子的速度实际上比信号速度慢约…

OpenWrt 软路由 IPV6 配置 DDNS

一、申请 dynv6 账号 1、去官网注册一个账号&#xff0c;不过人机验证那块需要 "梯子" 才能注册成功 Free dynamic DNS for IPv6 2、注册成功后&#xff0c;创建一个 Domain 3、这是我创建好的 4、获取 密码&#xff0c;后面需要用到 二、配置 DDNS 1、点击服务菜…