发送实时音频数据到udp服务

由于浏览器不能直接连接udp服务,所以需要搭建一个websocket服务做中转,让websocket服务连接udp服务
1、vue开发获取实时音频数据并按4096分包后添加rtp协议头发送到websocket服务(连接websocket自行编写连接到127.0.0.1:8889)

data(){return {audioContext:null,rc:null,}
},
methods:{startRecorder(){let that = this;//可以用下面的代码来边讲话边听const audio = new Audio()audio.autoplay = truenavigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {that.rc = streamaudio.srcObject = streamthat.audioContext = new AudioContext();const mediaStreamSource = that.audioContext.createMediaStreamSource(stream);const bufferSize = 4096; // 根据需要选择缓冲区大小const scriptNode = that.audioContext.createScriptProcessor(bufferSize, 1, 1);// 当音频处理事件发生时scriptNode.onaudioprocess = (event) => {const inputBuffer = event.inputBuffer;const inputData = inputBuffer.getChannelData(0);// 将音频数据编码为 RTP 协议头的 16 位二进制数据const rtpData = new Int16Array(inputData.length);for (let i = 0; i < inputData.length; i++) {rtpData[i] = inputData[i] * 32767; // 缩放到 16 位范围}//原数据的二进制数据const rtpBinary = rtpData.buffer;//发送原数据到websocket服务// websocketsend(rtpBinary);//下面是创建rtp协议头的,可选// 创建 RTP 协议头const version = 2; // RTP 版本const padding = 0; // 填充位const extension = 0; // 扩展位const csrcCount = 0; // CSRC 计数const marker = 0; // 标记位const payloadType = 8; // 负载类型 8:PCMAlet sequenceNumber = 0;//序列号(根据需要修改)let timestamp = 0;//开始截取时间戳(根据需要修改)const sampleRate = 44100; // 音频采样率(根据需要修改)const ssrc = Math.floor(Math.random() * 0xFFFFFFFF);//同步源标识符, 32位的无符号整数(根据需要修改)// 根据需要的时间位置和采样率计算时间戳timestamp += (bufferSize / sampleRate) * 90000; // bufferSize 是 RTP 包的大小const rtpHeader = new Uint8Array(12); // RTP 协议头为12字节const view = new DataView(rtpHeader.buffer);view.setUint8(0, (version << 6) | (padding << 5) | (extension << 4) | csrcCount);//<<左移操作符view.setUint8(1, (marker << 7) | payloadType & 0x7F);//view.setUint8(1, (marker << 7) | payloadType);view.setUint16(2, sequenceNumber);view.setUint32(4, timestamp);view.setUint32(8, ssrc);// 将 RTP 协议头和 rtpBinary 合并为一个数据包const rtpPacket = new Uint8Array(rtpHeader.length + rtpBinary.byteLength);rtpPacket.set(rtpHeader);rtpPacket.set(new Uint8Array(rtpBinary), rtpHeader.length);sequenceNumber = (sequenceNumber + 1) & 0xFFFF;// 发送 RTP 数据到 WebSocket 服务器websocketsend(rtpPacket);};mediaStreamSource.connect(scriptNode);scriptNode.connect(that.audioContext.destination);}).catch(error => alert(error));},//停止采集音频stopRecorder(){const audioTrack = this.rc?.getAudioTracks()[0];if(audioTrack){audioTrack.stop();}if(this.rc){this.rc = nullthis.audioContext.close()this.audioContext = null//停止websocket连接websocketclose()}}
}

2、使用nodejs搭建websocket服务
创建一个app.js,并安装dgram依赖 npm install dgram --save

const WebSocket = require('ws');
const dgram = require('dgram');// 创建WebSocket服务器
const wss = new WebSocket.Server({ port: 8889 });// 创建UDP套接字
const udpSocket = dgram.createSocket('udp4');// 监听WebSocket连接
wss.on('connection', (ws) => {console.log('客户端已连接');// 监听WebSocket消息ws.on('message', (message) => {console.log('收到消息:', message);// 发送消息到UDP服务器udpSocket.send(message, 0, message.length, 8888, '127.0.0.1', (err) => {if (err) {console.error('发送UDP消息失败:', err);}});});// 监听UDP消息udpSocket.on('message', (message, rinfo) => {console.log('收到UDP消息:', message.toString());// 将UDP消息发送给WebSocket客户端ws.send(message.toString());});// 监听WebSocket关闭ws.on('close', () => {console.log('客户端已断开连接');});
});// 开始监听UDP端口
udpSocket.bind(8888, '127.0.0.1', () => {console.log('UDP套接字已绑定');
});

运行app.js: node app.js

3、udp服务上可以看到发送过来的二进制数据(调试工具在这百度网盘地址)
在这里插入图片描述
注:如果提示[Deprecation] The ScriptProcessorNode is deprecated. Use AudioWorkletNode instead. (https://bit.ly/audio-worklet)是因为ScriptProcessorNode已经弃用了,可以不用更改,多了个警告而已,不影响。如需要更换为AudioWorkletNode,以下就是更换后的代码
1、新建一个audioworklet.js

class MyAudioWorkletProcessor extends AudioWorkletProcessor {constructor() {super();// 在这里初始化任何需要的变量,如序列号、时间戳、SSRC 等this.sequenceNumber = 0;this.timestamp = 0;this.ssrc = Math.floor(Math.random() * 0xFFFFFFFF);}process(inputs, outputs, parameters) {const input = inputs[0];const inputData = input[0]; // 假设只有一个输入通道// 创建 RTP 协议头const rtpHeader = new Uint8Array(12); // RTP 协议头为12字节const view = new DataView(rtpHeader.buffer);const version = 2; // RTP 版本const padding = 0; // 填充位const extension = 0; // 扩展位const csrcCount = 0; // CSRC 计数const marker = 0; // 标记位const payloadType = 8; // 负载类型,根据需要更改view.setUint8(0, (version << 6) | (padding << 5) | (extension << 4) | csrcCount);view.setUint8(1, (marker << 7) | payloadType);view.setUint16(2, this.sequenceNumber);view.setUint32(4, this.timestamp);view.setUint32(8, this.ssrc);// 将 RTP 协议头和音频数据合并为一个数据包const rtpPacket = new Uint8Array(rtpHeader.length + inputData.length);rtpPacket.set(rtpHeader);rtpPacket.set(new Uint8Array(inputData.buffer), rtpHeader.length);console.log(rtpPacket);// 发送 RTP 数据到 WebSocket 服务器// 你需要将这个数据包发送到 WebSocket 服务器,可能需要一个 WebSocket 连接来发送数据// WebSocket 发送代码应该放在这里// 更新序列号和时间戳this.sequenceNumber = (this.sequenceNumber + 1) & 0xFFFF;this.timestamp += inputData.length;return true;}}registerProcessor('my-audio-worklet-processor', MyAudioWorkletProcessor);

然后把上面的采集函数改一下,提示一下,这个可能需要在https的网站使用

startRecorder(){let that = this;//可以用下面的代码来边讲话边听const audio = new Audio()audio.autoplay = truenavigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {that.rc = streamaudio.srcObject = streamthat.audioContext = new AudioContext();const mediaStreamSource = that.audioContext.createMediaStreamSource(stream);that.audioContext.audioWorklet.addModule('audioworklet.js').then(() => {// 创建 AudioWorkletNodeconst workletNode = new AudioWorkletNode(that.audioContext, 'my-audio-worklet-processor');// 连接音频输入和输出mediaStreamSource.connect(workletNode);workletNode.connect(that.audioContext.destination);// 音频处理逻辑已在 audioworklet.js 中定义}).catch((error) => {console.error('加载音频工作线程失败:', error);});mediaStreamSource.connect(scriptNode);scriptNode.connect(that.audioContext.destination);}).catch(error => alert(error));
}

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

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

相关文章

购物H5商城架构运维之路

一、引言 公司属于旅游行业&#xff0c;需要将旅游&#xff0c;酒店&#xff0c;购物&#xff0c;聚合到线上商城。通过对会员数据进行聚合&#xff0c;形成大会员系统&#xff0c;从而提供统一的对客窗口。 二、业务场景 围绕更加有效地获取用户&#xff0c;提升用户的LTV&a…

Python线程和进程

1、深度解析Python线程和进程 一篇文章带你深度解析Python线程和进程 - 知乎使用Python中的线程模块&#xff0c;能够同时运行程序的不同部分&#xff0c;并简化设计。如果你已经入门Python&#xff0c;并且想用线程来提升程序运行速度的话&#xff0c;希望这篇教程会对你有所帮…

stm32之看门狗

STM32 有两个看门狗&#xff0c;独立看门狗和窗口看门狗&#xff0c;独立看门狗又称宠物狗&#xff0c;窗 口看门狗又称警犬。可用来检测和解决由软件错误引起的故障。两个看门狗的原理都是当计数器达到给定的超时值时&#xff0c;产生系统复位&#xff0c;对于窗口型看门狗同…

FL Studio21.2中文版数字音乐制作软件

现在的FL也可以像splice一样啦&#xff0c;需要什么样的声音只需在fl里搜索&#xff0c;就会自动展示给你! FL Studio 简称FL&#xff0c;全称&#xff1a;Fruity Loops Studio&#xff0c;国人习惯叫它"水果"。软件现有版本是 FL Studio 21&#xff0c;已全面升级支…

如何利用播放器节省20%点播成本

点播成本节省的点其实涉及诸多部分&#xff0c;例如&#xff1a;CDN、转码、存储等&#xff0c;而利用播放器降本却是很多客户比较陌生的部分。火山引擎基于内部支撑抖音集团相关业务的实践&#xff0c;播放器恰恰是成本优化中最重要和最为依赖的部分。 火山引擎的视频团队做了…

“智慧”北京,人工智能引领“新风尚”

原创 | 文 BFT机器人 北京时间&#xff0c;9月15日&#xff0c;北京人工智能产业峰会暨中关村科学城科创大赛颁奖典礼在北京中关村举行&#xff0c;同时惠阳还举行了“中关村人工智能大模型产业集聚区”启动建设的揭牌仪式。 此次大会围绕北京AI产业的建设与发展&#xff0c;各…

大模型时代,探人工智能发展的新动向

导语 | 今年以来大模型的热度居高不下&#xff0c;人工智能成为国内外各大厂商争相布局的新赛道。那么近期 AI 领域有哪些值得关注的新趋势&#xff0c;它又将为软件开发带来哪些影响呢&#xff1f;今天&#xff0c;我们特邀了微智云科技 CEO、腾讯云 TVP 张虎老师&#xff0c;…

不用addEventListener(‘resize‘, this.resize),用新的Web API ResizeObserver监听DIV元素尺寸的变化

响应式设计指的是根据屏幕视口尺寸的不同&#xff0c;对 Web 页面的布局、外观进行调整&#xff0c;以便更加有效地进行信息的展示。我们日常生活中接触的很多应用都遵循响应式的设计。 响应式设计如今也成为 web 应用的基本需求&#xff0c;而现在很多 web 应用都已经组件化&a…

分布式运用之Filebeat+Kafka+ELK 的服务部署

一、Kafka 架构深入了解 1.1 Kafka 工作流程及文件存储机制 Kafka 中消息是以 topic 进行分类的&#xff0c;生产者生产消息&#xff0c;消费者消费消息&#xff0c;都是面向 topic 的。 topic 是逻辑上的概念&#xff0c;而 partition 是物理上的概念&#xff0c;每个 par…

LinkedList相较于Arravlist的特点/优化

Arravlist底层是内存空间连续的数组&#xff0c;可以根据下标进行随机访问&#xff0c;效率比较高&#xff0c;因为在根据下标访问某一个元素时&#xff0c;并不是一个一个去查&#xff0c;而是算出来这个下标的地址&#xff0c;直接根据这个地址的指向去获取的&#xff0c;因为…

Linux 创建 终止线程(thread)

进程线程区别 创建线程 #include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); -功能&#xff1a;创建一个子线程&#xff0c;一般情况下main函数所在的线程称为主线程&#xff0c;…

Unity的AB包相关

1、打包 在这个界面左边右键&#xff0c;CreateNewBundle 将要打包的模型制作成预设体 在下面勾选 选好平台路径&#xff0c;点击Build 2、加载AB包 public class ABTest : MonoBehaviour {// Start is called before the first frame updatevoid Start(){//加载AB包AssetB…

mysql集群使用nginx配置负载均衡

参考链接&#xff1a;https://mu-sl.com//archives/mysql%E9%9B%86%E7%BE%A4%E4%BD%BF%E7%94%A8nginx%E9%85%8D%E7%BD%AE%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1 配置文件nginx_tcp.conf 示例 load_module modules/ngx_stream_module.so;stream{upstream tcpssh{hash $remote_…

【c#-Nuget 包“在此源中不可用”】 Nuget package “Not available in this source“

标题c#-Nuget 包“在此源中不可用”…但 VS 仍然知道它吗&#xff1f; (c# - Nuget package “Not available in this source”… but VS still knows about it?) 背景&#xff1a; 今日从公司svn 上拉取很久很久以前的代码&#xff0c;拉取下来200报错&#xff0c;进一步发…

如何设置代理ip服务器地址

在今天的互联网环境中&#xff0c;代理服务器在保护个人隐私和规避网络限制方面扮演着重要的角色。设置代理服务器地址的方式主要取决于你使用的具体软件或编程语言。在本文中&#xff0c;我们将分别介绍如何在Python和Java中使用HTTP代理服务器、SOCKS代理服务器以及代理池。 …

C/C++开发,opencv阀值操作

目录 一、OpenCV-阀值操作 1.1阀值操作函数threshold 1.2threshold的操作类型 1.3Otsu算法 二、样例开发 2.1 Makefile 2.2 main.cpp 2.3 运行效果 三、OpenCV-自适应阀值操作 3.1 自适应阀值操作函数-adaptiveThreshold 3.2 样例开发 一、OpenCV-阀值操作 1.1阀值操…

Python与数据分析--Matplotlib-1

目录 1.Matplotlib库函数导入 2.简单尝试绘图 3.绘制多条折线图 4.绘制多种颜色风格曲线 5.图片内容文本操作实例 6.图例设置实例 7.坐标轴设置实例 1.Matplotlib库函数导入 #导入matplotlib库 import matplotlib as mpl import matplotlib.pyplot as plt #平常一般用第…

Java实现Modbus Tcp协议读写模拟工具数据

标题 前言一、读写模拟工具中数据(1) 定义Controller层(2) 定义Service层实现 二、调试(1) 读数据(2) 向寄存器写单个数据(3) 向寄存器写多个数据 前言 参考文章&#xff1a;https://www.cnblogs.com/ioufev/p/10831289.html 该文中谈及常见的几种读取设备数据实现&#xff0…

【数据结构】二叉树之堆的实现

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;数据结构 &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、二叉树的顺序结构 &#x1f4d2;1.1顺序存储 &#x1f4d2;1.2堆的性质…

Linux下的基本指令

目录 01. ls 指令 02. pwd命令 03. cd 指令 04. touch指令 05.mkdir指令&#xff08;重要&#xff09;&#xff1a; 06.rmdir指令 && rm 指令&#xff08;重要&#xff09;&#xff1a; 07.man指令&#xff08;重要&#xff09;&#xff1a; 08mv指令&#xff…