当前位置: 首页 > news >正文

由播客转向个人定制的音频频道(1)平台搭建

项目的背景

        最近开始听喜马拉雅播客的内容,但是发现许多不方便的地方。

  •         休息的时候收听喜马拉雅,但是还需要不断地选择喜马拉雅的内容,比较麻烦,而且黑灯操作反而伤眼睛。
  •       喜马拉雅为代表的播客平台都是VOD 形式的,需要选择内容收听,有时候想听科技方面的访谈,但是许多访谈节目并不是连续更新播放的。免不了要去主动查找。
  •       休闲或者开车时,希望收听收音机那样轻松一点地享受电台安排的节目,但是目前的电台广告太多,内容匮乏。
  • 家里的老人更不习惯手机操作。老人目前主要是看短视频。

   笔者看来,播客是一个被低估的服务,其实依靠短视频很难接收有效的信息,靠几分钟很难讲清楚一个观点和知识。所以,要完整地了解一些有用的内容,语音比短视频更好。

        那么,能否通过AI 推荐技术,讲播客内容主动生成个人定制的音频频道吗?理论上是可能的,也十分有趣。作为一名创客,我想试试。

        说干就干!本文介绍实验平台的搭建。

基于ardunio ESP32 的选台器  

          电子设备中经常使用旋钮来选择参数,最简单的是旋钮是电位器,它是一个滑动电阻,高端家电,汽车中使用的是编码器Encoder。编码器输出的是脉冲信号。本文介绍如何使用Ardunio ESP32-Nano 来设计一个蓝牙旋转编码器。

外观设计

硬件设计

细节

编码器

下面是日本ALPS 公司的中空旋转编码器EC35A。

 

三个引脚分别是A,C,B。

 

接线图

最好在编码器脉冲计数处理回路中设置下图所示的滤波器。

与Ardunio 的连接图。 

代码

目前的代码使用了蓝牙mouse 仿真,最终的程序也许要改成ble server 。

#include <BleMouse.h>
BleMouse bleMouse;
// Define the pins used for the encoder
const int encoderPinA = 11;
const int encoderPinB = 10;// Variables to keep the current and last state
volatile int encoderPosCount = 0;
int lastEncoded = 0;
int MAX=60;
int channel=-1;
void setup() {Serial.begin(115200);// Set encoder pins as input with pull-up resistorspinMode(encoderPinA, INPUT_PULLUP); pinMode(encoderPinB, INPUT_PULLUP);// Attach interrupts to the encoder pinsattachInterrupt(digitalPinToInterrupt(encoderPinA), updateEncoder, CHANGE);attachInterrupt(digitalPinToInterrupt(encoderPinB), updateEncoder, CHANGE);bleMouse.begin();
}void loop() {static int lastReportedPos = -1; // Store the last reported positionif (encoderPosCount != lastReportedPos) {if((encoderPosCount/2)>channel) {channel++; Serial.print("Encoder Position: ");Serial.println(channel);bleMouse.move(0,0,1);};if((encoderPosCount/2)<channel) {channel--; Serial.print("Encoder Position: ");Serial.println(channel);bleMouse.move(0,0,-1);}lastReportedPos = encoderPosCount;}delay(500) ;}void updateEncoder() {int MSB = digitalRead(encoderPinA); // MSB = most significant bitint LSB = digitalRead(encoderPinB); // LSB = least significant bitint encoded = (MSB << 1) | LSB; // Converting the 2 pin value to single numberint sum  = (lastEncoded << 2) | encoded; // Adding it to the previous encoded valueif(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011){if (encoderPosCount==MAX) encoderPosCount=0;elseencoderPosCount++;} if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000){if (encoderPosCount==0) encoderPosCount=MAX;elseencoderPosCount--;} lastEncoded = encoded; // Store this value for next time
}

 ardunio-ESP32-nano 开发板

外观

引脚图 

HLS 网络电台的构建

        HLS 全称是 HTTP Live Streaming, 是一个由 Apple 公司实现的基于 HTTP 的媒体流传输协议。  借助 HLS,视频和音频内容被分解为一系列块,经过压缩以便快速交付,并通过 HTTP 传输到最终用户的设备。

 HLS 由一个m3u8 文件和多个ts 文件构成的。 

节目源

网络上有许多网络广播电台的m3u8 的节目源地址,有的可以播放,有的不行。我们下载之后转换成为CSV 格式,然后通过CSV2JSON.js 软件转化为json 文件.下面是一部分

[{"StationName": "本地音乐台","URL": "media/1.m3u8"},{"StationName": "CGTN Radio","URL": "http://sk.cri.cn/am846.m3u8"},{"StationName": "CRI环球资讯广播","URL": "http://satellitepull.cnr.cn/live/wxhqzx01/playlist.m3u8"},{"StationName": "CRI华语环球广播","URL": "http://sk.cri.cn/hyhq.m3u8"},{"StationName": "CRI南海之声","URL": "http://sk.cri.cn/nhzs.m3u8"},{"StationName": "CRI世界华声","URL": "http://sk.cri.cn/hxfh.m3u8"}]

 音频分发服务器

      构建了一个HLS 音频测试平台,用于测试。

  •           后台nodeJS 编写
  •          前端 使用hlv.js 插件

自制HLS 媒体

除了网络上的节目源之外,我们也制作了一些测试语音媒体。要使用ffmpeg 工具转换。 

使用ffmpeg 将mp3 转换成m3u8 的分段(1.mp3 )

ffmpeg -i 1.mp3 -c:v libx264 -c:a aac -strict -2 -f hls -hls_list_size 2 -hls_time 15 1.m3u8

生成的效果是:

        将 1.mp3 视频文件每 15 秒生成一个 ts 文件,最后生成一个 m3u8 文件(1.m3u8),m3u8 文件是 ts 的索引文件。和两个ts( 1.m3u8 ,10.ts 和11.ts)

将生成的文件放置在nodeJS/public/media 目录中。

我的代码 

NodeJS代码

import express from 'express';
import path from 'path'
import url from 'url'
import   fs   from 'fs';
const router = express.Router();
const app = express();
const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json())router.get('/index', function (req, res) {res.sendFile(path.join(__dirname + '/views/index.html'));
});
router.post('/getStations', async function (req, res) {Request = req.body;//   console.log(Request)const Method = Request.Method;const Stations=JSON.parse(fs.readFileSync("public/doc/StationTable.json",'utf8'))console.log(Stations)res.send(JSON.stringify({Method: "getStations",Result: { Status: "OK", Stations: JSON.stringify(Stations) }}))
});
app.use('/', router);
app.listen(process.env.port || 3000);
console.log('Running at Port 3000');

前端Index.html

<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Radio Player</title><link rel="stylesheet" href="css/bootstrap.min.css"><link rel="stylesheet" href="css/font-awesome.min.css"><link rel="stylesheet" href="font/bootstrap-icons.css"><script src="js/jquery-3.4.1.min.js"></script><script src="js/bootstrap.min.js"></script><script src="js/hls.js"></script><script src="js/jquery.mousewheel.min.js"></script><style>select {font-size: 24px;width: 320px;}audio {width: 320px;}</style><script>var hlsvar CurrentChannel=0$(document).ready(function () {$("#Title").click()var audio = document.getElementById('audio');hls = new Hls();$("#Title").mousewheel(Mousewheel)         getStations()});var Stations = nullfunction Mousewheel(event){console.log(event.deltaX, event.deltaY, event.deltaFactor);if (event.deltaY>0) {CurrentChannel++;if (CurrentChannel==64) CurrentChannel==0}else  {if (CurrentChannel==0) CurrentChannel=64elseCurrentChannel--;}$("#Selection").get(0).selectedIndex=CurrentChannelSelected()}function getStations() {var parameter = {Method: "getStations",}$.ajax({url: "/getStations",type: 'post',contentType: "application/json",dataType: "json",data: JSON.stringify(parameter),success: function (response) {Stations = JSON.parse(response.Result.Stations)console.log(Stations)for (let i = 0; i < Stations.length; i++) {$("#Selection").append("<option>" + Stations[i].StationName + "</option>")}$("#Selection").get(0).selectedIndex=CurrentChannelSelected()}})}function Selected() {const StationName = $("#Selection").val()const Index = $("#Selection").get(0).selectedIndexCurrentChannel=Indexconsole.log(StationName)console.log(Index)if (Hls.isSupported()) {var audio = document.getElementById('audio');hls.loadSource(Stations[Index].URL);hls.attachMedia(audio);hls.on(Hls.Events.MANIFEST_PARSED, function () {audio.play();});}}</script></head><body><div class="container" ><h1 class="text-info" id="Title">网络收音机</h1><div class="form-group"><h3 class="text-info">选台</h3><select class="form-select   " aria-label="Default select" id="Selection" onchange="Selected()"></select></div><div><h3 class="text-info">播放器</h3><audio id="audio" controls ></audio></div></div>
</body></html>

界面有点丑

结束语

下一步,开始研究个人定制的音频频道的构建和尝试。感兴趣的可以共同探讨。

相关文章:

由播客转向个人定制的音频频道(1)平台搭建

项目的背景 最近开始听喜马拉雅播客的内容&#xff0c;但是发现许多不方便的地方。 休息的时候收听喜马拉雅&#xff0c;但是还需要不断地选择喜马拉雅的内容&#xff0c;比较麻烦&#xff0c;而且黑灯操作反而伤眼睛。 喜马拉雅为代表的播客平台都是VOD 形式的&#xff0…...

[自然语言处理] [AI]深入理解语言与情感分类:从基础到深度学习的进展

语言是人类智能的核心组成部分,具有极高的复杂性和多样性。理解语言,尤其是语言中的隐含部分,向来是人工智能研究的一个巨大挑战。图灵测试本身便是一场关于语言生成与理解的比赛,旨在检验机器是否能够模拟人类的语言能力。随着深度学习的飞速发展,语音识别、情感分析等自…...

【GPTs】Gif-PT:DALL·E制作创意动图与精灵动画

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | GPTs应用实例 文章目录 &#x1f4af;GPTs指令&#x1f4af;前言&#x1f4af;Gif-PT主要功能适用场景优点缺点 &#x1f4af;小结 &#x1f4af;GPTs指令 中文翻译&#xff1a; 使用Dalle生成用户请求的精灵图动画&#…...

云原生周刊:Istio 1.24.0 正式发布

云原生周刊&#xff1a;Istio 1.24.0 正式发布 开源项目推荐 Kopf Kopf 是一个简洁高效的 Python 框架&#xff0c;只需几行代码即可编写 Kubernetes Operator。Kubernetes&#xff08;K8s&#xff09;作为强大的容器编排系统&#xff0c;虽自带命令行工具&#xff08;kubec…...

Linux设置jar包开机启动

操作系统环境&#xff1a;CentOS 7 【需要 root 权限&#xff0c;使用 root 用户进行操作 或 普通用户使用 sudo 进行操作】 一、系统服务的方式 原理&#xff1a;利用系统服务管理应用程序的生命周期&#xff0c; systemctl 为系统服务管理工具 systemctl start applicati…...

计算机视觉和机器人技术中的下一个标记预测与视频扩散相结合

一种新方法可以训练神经网络对损坏的数据进行分类&#xff0c;同时预测下一步操作。 它可以为机器人制定灵活的计划&#xff0c;生成高质量的视频&#xff0c;并帮助人工智能代理导航数字环境。 Diffusion Forcing 方法可以对嘈杂的数据进行分类&#xff0c;并可靠地预测任务的…...

C语言之简单的获取命令行参数和环境变量

C语言之简单的获取命令行参数和环境变量 本人的开发环境为WIN10操作系统用VMWARE虚拟的UBUNTU LINUX 18.04LTS&#xff01;&#xff01;&#xff01; 所有代码的编辑、编译、运行都在虚拟机上操作&#xff0c;初学的朋友要注意这一点&#xff01;&#xff01;&#xff01; 详细…...

STL之vecor的使用(超详解)

目录 1. C/C中的数组 1.1. C语言中的数组 1.2. C中的数组 2. vector的接口 2.1. vector的迭代器 2.2. vector的初始化与销毁 2.3. vector的容量操作 2.4. vector的访问操作 2.5. vector的修改操作 &#x1f493; 博客主页&#xff1a;C-SDN花园GGbond ⏩ 文章专栏…...

SystemVerilog学习笔记(一):数据类型

在systemverilog中&#xff0c;主要包含以下数据类型&#xff1a; 4值类型2值类型数组字符串结构体和联合体枚举自定义类型 无符号数&#xff1a;无符号数的符号不使用任何标志&#xff0c;即无符号数只能存储正数。无符号二进制数的范围从 0 到 ((2^n) - 1)&#xff0c;n 表…...

Linux软件包管理与Vim编辑器使用指南

目录 一、Linux软件包管理器yum 1.什么是软件包&#xff1f; 2.什么是软件包管理器&#xff1f; 3.查看软件包 4.安装软件 ​编辑 5.卸载软件 Linux开发工具&#xff1a; 二、Linux编辑器---vim 1.vim的基本概念 (1) 正常/普通模式&#xff08;Normal mode&#xff0…...

每日一练 | 包过滤防火墙的工作原理

01 真题题目 包过滤防火墙对哪一层的数据报文进行检查&#xff1f; A. 应用层 B. 物理层 C. 网络层 D. 链路层 02 真题答案 C 03 答案解析 包过滤防火墙是一种基本的安全设备&#xff0c;它通过检查进出网络的数据包来决定是否允许该数据包通过。 这种类型的防火墙主要关注…...

AR眼镜方案_AR智能眼镜阵列/衍射光波导显示方案

在当今AR智能眼镜的发展中&#xff0c;显示和光学组件成为了技术攻坚的主要领域。由于这些组件的高制造难度和成本&#xff0c;其光学显示模块在整个设备的成本中约占40%。 采用光波导技术的AR眼镜显示方案&#xff0c;核心结构通常由光机、波导和耦合器组成。光机内的微型显示…...

SpringBoot(十九)创建多模块Springboot项目(完整版)

之前我有记录过一次SpringBoot多模块项目的搭建,但是那一次只是做了一个小小的测试。只是把各模块联通之后就结束了。 最近要增加业务开发,要将目前的单模块项目改成多模块项目,我就参照了一下我上次搭建的流程,发现总是有报错。上次搭建的比较顺利,很多细枝末节也没有仔细…...

Navicat 17 功能简介 | 单元格编辑器

Navicat 17 功能简介 | 单元格编辑器 本期&#xff0c;我们一起了解 Navicat 17 出色的数据操作功能的单元格编辑器。单元格编辑器支持文本、十六进制、图像和网页四种格式的数据编辑&#xff0c;位于底部的编辑器窗格&#xff0c;为你编辑更大容量的数据信息提供足够的显示和操…...

MySQL【四】

插入数据 向数据表中插入一行数据 INSERT|REPLACE INTO 表名[(字段列表)] VALUES(值列表); ########## 在s表中插入一条记录&#xff1a;学号为s011,姓名为李思&#xff0c;性别为默认值&#xff0c;计算机专业 ########## insert into s(sno,sname,dept)values(s011,李思,计…...

简单叙述 Spring Boot 启动过程

文章目录 1. 准备阶段&#xff1a;应用启动的入口2. 创建 SpringApplication 对象&#xff1a;开始启动工作3. 配置环境&#xff08;Environment&#xff09;&#xff1a;识别开发环境与生产环境4. 启动监听器和初始化器&#xff1a;感知启动的关键事件5. 创建 ApplicationCont…...

微信小程序自定义tabbar;禁用某个tab;修改某个tab的样式

微信小程序自定义tabbar&#xff1b;禁用某个tab&#xff1b;修改某个tab的样式 原本使用本身的tabBar就已经很舒服了&#xff0c;很合适了的&#xff0c;但是总有一些脑洞大开的产品和客户&#xff0c;给你搞点多样式&#xff0c;没办法牛马就得去做咯&#xff0c;现在就给大…...

力扣113:路径总和II

给你二叉树的根节点 root 和一个整数目标和 targetSum &#xff0c;找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。 叶子节点 是指没有子节点的节点。 示例 1&#xff1a; 输入&#xff1a;root [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum 22 输出&a…...

JavaScript字符串常用方法

在JavaScript中&#xff0c;字符串是用来表示文本数据的基本数据类型。字符串可以用单引号()、双引号(")、或反引号()包裹。JavaScript中的字符串是不可变的&#xff0c;也就是说&#xff0c;字符串的值一旦创建就无法更改&#xff0c;但可以创建新字符串来替换原有字符串…...

xtu oj 加一

样例输入# 2 4 1 2 3 4 4 3 2 4 1样例输出# 3 5 解题思路&#xff1a;最小操作次数一定是把所有数变成数组中最大值max。 1、找最大值&#xff0c;一开始我把max初始值设为0&#xff0c;如果a[i]>max,maxa[i],WA了。又看了一遍题目&#xff0c;发现所有整数的绝对值小于…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试

作者&#xff1a;Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位&#xff1a;中南大学地球科学与信息物理学院论文标题&#xff1a;BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接&#xff1a;https://arxiv.…...

【Java学习笔记】Arrays类

Arrays 类 1. 导入包&#xff1a;import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序&#xff08;自然排序和定制排序&#xff09;Arrays.binarySearch()通过二分搜索法进行查找&#xff08;前提&#xff1a;数组是…...

1.3 VSCode安装与环境配置

进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件&#xff0c;然后打开终端&#xff0c;进入下载文件夹&#xff0c;键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

2021-03-15 iview一些问题

1.iview 在使用tree组件时&#xff0c;发现没有set类的方法&#xff0c;只有get&#xff0c;那么要改变tree值&#xff0c;只能遍历treeData&#xff0c;递归修改treeData的checked&#xff0c;发现无法更改&#xff0c;原因在于check模式下&#xff0c;子元素的勾选状态跟父节…...

2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面

代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口&#xff08;适配服务端返回 Token&#xff09; export const login async (code, avatar) > {const res await http…...

【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验

系列回顾&#xff1a; 在上一篇中&#xff0c;我们成功地为应用集成了数据库&#xff0c;并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了&#xff01;但是&#xff0c;如果你仔细审视那些 API&#xff0c;会发现它们还很“粗糙”&#xff1a;有…...

Robots.txt 文件

什么是robots.txt&#xff1f; robots.txt 是一个位于网站根目录下的文本文件&#xff08;如&#xff1a;https://example.com/robots.txt&#xff09;&#xff0c;它用于指导网络爬虫&#xff08;如搜索引擎的蜘蛛程序&#xff09;如何抓取该网站的内容。这个文件遵循 Robots…...

DBAPI如何优雅的获取单条数据

API如何优雅的获取单条数据 案例一 对于查询类API&#xff0c;查询的是单条数据&#xff0c;比如根据主键ID查询用户信息&#xff0c;sql如下&#xff1a; select id, name, age from user where id #{id}API默认返回的数据格式是多条的&#xff0c;如下&#xff1a; {&qu…...