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

news2024/11/14 10:31:40

项目的背景

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

  •         休息的时候收听喜马拉雅,但是还需要不断地选择喜马拉雅的内容,比较麻烦,而且黑灯操作反而伤眼睛。
  •       喜马拉雅为代表的播客平台都是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 resistors
  pinMode(encoderPinA, INPUT_PULLUP); 
  pinMode(encoderPinB, INPUT_PULLUP);

  // Attach interrupts to the encoder pins
 attachInterrupt(digitalPinToInterrupt(encoderPinA), updateEncoder, CHANGE);
  attachInterrupt(digitalPinToInterrupt(encoderPinB), updateEncoder, CHANGE);
   bleMouse.begin();
}

void loop() {
   
  static int lastReportedPos = -1; // Store the last reported position
  if (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 bit
  int LSB = digitalRead(encoderPinB); // LSB = least significant bit
  int encoded = (MSB << 1) | LSB; // Converting the 2 pin value to single number
  int sum  = (lastEncoded << 2) | encoded; // Adding it to the previous encoded value

  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011){
    
    if (encoderPosCount==MAX) encoderPosCount=0;
    else
      encoderPosCount++;
    } 
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000){
   if (encoderPosCount==0) encoderPosCount=MAX;
   else
    encoderPosCount--;
   
    } 

  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 hls
        var CurrentChannel=0
        $(document).ready(function () {
            $("#Title").click()
            var audio = document.getElementById('audio');
            hls = new Hls();
            $("#Title").mousewheel(Mousewheel)         
            getStations()

        });
        var Stations = null
       
        function 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=64
                else
                CurrentChannel--;
            }
            $("#Selection").get(0).selectedIndex=CurrentChannel
            Selected()
        }
        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=CurrentChannel
                    Selected()
                }
            })
        }
        function Selected() {
            const StationName = $("#Selection").val()
            const Index = $("#Selection").get(0).selectedIndex
            CurrentChannel=Index
            console.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>

界面有点丑

结束语

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

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

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

相关文章

7+纯生信,单细胞识别细胞marker+100种机器学习组合建模,机器学习组合建模取代单独lasso回归势在必行!

影响因子&#xff1a;7.3 研究概述&#xff1a; 皮肤黑色素瘤&#xff08;SKCM&#xff09;是所有皮肤恶性肿瘤中最具侵袭性的类型。本研究从GEO数据库下载单细胞RNA测序&#xff08;scRNA-seq&#xff09;数据集&#xff0c;根据原始研究中定义的细胞标记重新注释各种免疫细胞…

uniapp解析蓝牙设备响应数据bug

本文章为了解决《uniapp 与蓝牙设备收发指令详细步骤(完整项目版)》中第十步的Array 解析成 number函数bug 1、原代码说明 function array16_to_number(arrayValue) {const newArray arrayValue.filter(item > String(item) ! 00 || String(item) ! 0)const _number16 ne…

【递归回溯与搜索算法篇】算法的镜花水月:在无尽的自我倒影中,递归步步生花

文章目录 递归回溯搜索专题&#xff08;一&#xff09;&#xff1a;递归前言第一章&#xff1a;递归基础及应用1.1 汉诺塔问题&#xff08;easy&#xff09;解法&#xff08;递归&#xff09;C 代码实现时间复杂度和空间复杂度易错点提示 1.2 合并两个有序链表&#xff08;easy…

大数据开发面试宝典

312个问题&#xff0c;问题涵盖广、从自我介绍到大厂实战、19大主题&#xff0c;一网打尽、真正提高面试成功率 一、Linux 1. 说⼀下linux的常⽤命令&#xff1f; 说一些高级命令即可 systemctl 设置系统参数 如&#xff1a;systemctl stop firewalld关闭防火墙 tail / hea…

链表归并与并集相关算法题|两递增归并为递减到原位|b表归并到a表|两递减归并到新链表(C)

两递增归并为递减到原位 假设有两个按元素递增次序排列的线性表&#xff0c;均以单链表形式存储。将这两个单链表归并为一个按元素递减次序排列的单链表&#xff0c;并要求利用原来两个单链表的节点存放归并后的单链表 算法思想 因为两链表已按元素值递增次序排列&#xff0…

【RabbitMQ】06-消费者的可靠性

1. 消费者确认机制 没有ack&#xff0c;mq就会一直保留消息。 spring:rabbitmq:listener:simple:acknowledge-mode: auto # 自动ack2. 失败重试机制 当消费者出现异常后&#xff0c;消息会不断requeue&#xff08;重入队&#xff09;到队列&#xff0c;再重新发送给消费者。…

【陕西】《陕西省省级政务信息化项目投资编制指南(建设类)(试行)》-省市费用标准解读系列07

《陕西省省级政务信息化项目投资编制指南&#xff08;建设类&#xff09;&#xff08;试行&#xff09;》规定了建设类项目的费用投资测算方法与计价标准&#xff0c;明确指出建设类项目费用包括项目建设费和项目建设其他费&#xff08;了解更多可直接关注咨询我们&#xff09;…

VB6.0桌面小程序(桌面音乐播放器)

干货源码 Imports System.IO Imports System.Security.Cryptography Public Class Form1 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load Button1.Text “上一曲” Button4.Text “播放” Button3.Text “下一曲” Button2.Text “顺序播…

docker安装jdk8

1、拉取镜像 docker pull openjdk:82、运行镜像 docker run -d --restartalways --network portainer_network -it --name jdk8 openjdk:8命令 作用 docker run 创建并启动一个容器 –name jdk8 将容器取名为jdk8 -d 设置后台运行 –restartalways 随容器启动 –network port…

【人工智能】Transformers之Pipeline(二十三):文档视觉问答(document-question-answering)

​​​​​​​ 目录 一、引言 二、文档问答&#xff08;document-question-answering&#xff09; 2.1 概述 2.2 impira/layoutlm-document-qa 2.2.1 LayoutLM v1 2.2.2 LayoutLM v2 2.2.3 LayoutXLM 2.2.4 LayoutLM v3 2.3 pipeline参数 2.3.1 pipeline对象实例化…

微服务day06

MQ入门 同步处理业务&#xff1a; 异步处理&#xff1a; 将任务处理后交给MQ来进行分发处理。 MQ的相关知识 同步调用 同步调用的小结 异步调用 MQ技术选型 RabbitMQ 安装部署 其中包含几个概念&#xff1a; publisher&#xff1a;生产者&#xff0c;也就是发送消息的一方 …

[CKS] K8S RuntimeClass SetUp

最近准备花一周的时间准备CKS考试&#xff0c;在准备考试中发现有一个题目关于RuntimeClass创建和挂载的题目。 ​ 专栏其他文章: [CKS] Create/Read/Mount a Secret in K8S-CSDN博客[CKS] Audit Log Policy-CSDN博客 -[CKS] 利用falco进行容器日志捕捉和安全监控-CSDN博客[CKS…

Halcon基于laws纹理特征的SVM分类

与基于区域特征的 SVM 分类不同&#xff0c;针对图像特征的 SVM 分类的算子不需要直接提取 特征&#xff0c;下面介绍基于 Laws 纹理特征的 SVM 分类。 纹理在计算机视觉领域的图像分割、模式识别等方面都有着重要的意义和广泛的应 用。纹理是指由于物体表面的物理属性不同所…

初始Python篇(6)—— 字符串

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a; Python 目录 字符串的常见操作 格式化字符串 占位符 f-string 字符串的 format 方法 字符串的编码与解码 与数据验证相关的方法 …

基于Spring Boot+Vue的养老院管理系统【原创】

一.系统开发工具与环境搭建 1.系统设计开发工具 后端使用Java编程语言的Spring boot框架 项目架构&#xff1a;B/S架构 运行环境&#xff1a;win10/win11、jdk17 前端&#xff1a; 技术&#xff1a;框架Vue.js&#xff1b;UI库&#xff1a;ElementUI&#xff1b; 开发工具&…

Maven 中央仓库地址 mvnrepository.com

下载一些 jar 包驱动&#xff0c;不需用去官网下了&#xff0c;直接去 Maven 中央仓库&#xff0c;高效、简单 Maven 中央仓库地址 https://mvnrepository.com/open-source 我们下期见&#xff0c;拜拜&#xff01;

2024 年将 Postman 文档导出为 HTML 或 Markdown

2024 年将 Postman 文档导出为 HTML 或 Markdown

Anaconda安装库

相信有些人可能遇到pip直接安装失败&#xff0c;conda直接安装失败&#xff0c;pip镜像安装仍然失败的可能性&#xff0c;下面我记录一下我的一种解决方法。 我使用的是上面的miniconda3配置。 1.创建虚拟环境 首先&#xff0c;先新建一个虚拟环境 conda create -n py39 pyt…

linux命令详解,openssl+历史命令详解

openssl openssl是一个开源的加密工具包&#xff0c;提供了各种加密、解密、签名、验证等功能 openssl passwd -1 123password表示这个命令用于处理密码相关的操作&#xff0c;-1参数指定使用MD5加密算法对密码“123”进行加密处理。MD5是一种常用的哈希算法&#xff0c;它将…

Flink运行时架构以及核心概念

1.运行构架 1.提交作业后启动一个客户端进程&#xff0c;客户端解析参数&#xff08;-d -t 等等&#xff09;&#xff0c;后进行封装由Actor通信系统提交&#xff0c;取消&#xff0c;更新任务给JobManager。 2.JobManager&#xff08;进程&#xff09;通信系统一个组件叫分发…