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

news2024/12/25 13:22:14

由于浏览器不能直接连接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 = true
		
		navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
		  that.rc = stream
		  audio.srcObject = stream
		  
		  that.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:PCMA
		    let 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 = null
	        this.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 = true
	
	navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
	  that.rc = stream
	  audio.srcObject = stream
	  
	  that.audioContext = new AudioContext();
	  const mediaStreamSource = that.audioContext.createMediaStreamSource(stream);
	  
	  that.audioContext.audioWorklet.addModule('audioworklet.js')
          .then(() => {
            // 创建 AudioWorkletNode
            const 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.coloradmin.cn/o/1033313.html

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

相关文章

代码随想录算法训练营 动态规划part06

一、完全背包 卡哥的总结&#xff0c;还挺全代码随想录 (programmercarl.com) 二、零钱兑换 II 518. 零钱兑换 II - 力扣&#xff08;LeetCode&#xff09; 被选物品之间不需要满足特定关系&#xff0c;只需要选择物品&#xff0c;以达到「全局最优」或者「特定状态」即可。 …

uni-app, 实现 scroll-view 自动滚动到底部,并控制触发频率

实现思路 通过 scroll-view 组件的 scroll-top 属性可以设置容器竖向滚动条位置 属性名Valuescroll-y允许纵向滚动scroll-top设置竖向滚动条位置 想要实现 scroll-view 滚动到底部&#xff0c;只需要让 scroll-top scroll-view 内容高度 - scroll-view 容器本身高度&#…

vuejs - - - - - 使用code编辑器codemirror

使用code编辑器codemirror 0. 效果图1. 依赖安装2. 组件封装3. 组件使用 0. 效果图 列表实现参考: 列表实现代码 1. 依赖安装 npm install codemirror codemirror-editor-vue3 jsonlint-mod 2. 组件封装 code-mirror-editor.vue <template><VueCodeMirrorclas…

蓝桥杯 题库 简单 每日十题 day8

01 扫雷 题目描述 在一个n行列的方格图上有一些位置有地雷&#xff0c;另外一些位置为空。 请为每个空位置标一个整数&#xff0c;表示周围八个相邻的方格中有多少个地雷。 输入描述 输入的第一行包含两个整数n&#xff0c;m。 第2行到第n1行每行包含m个整数&#xff0c;相邻整…

Smart UI Web 16.0.1 WebComponents htmlelements Crack

Javascript Web 组件库 Smart UI Web 组件库是您构建令人惊叹的 Web 应用程序所需的唯一套件。它包含 70 多个快速且专业设计的 UI 组件&#xff0c;可在单个包中实现美观且始终现代的 Web 应用程序。 具有高级功能的即用型Javascript 组件。只需几行代码即可使用数据网格、甘特…

Mybatis分页框架-PageHelper

Mybatis分页框架-PageHelper 一、PageHelper基础使用1.引入jar包2.配置conifg3.测试使用 二、PageHelper的多种用法1.使用PageHelper.startPage传入对象2.不使用PageHelper.startPage,而使用PageHelper.offsetPage3.使用Lambda进行分页4.不使用PageHelper直接分页5.想要使用分页…

代码随想录算法训练营 动态规划part17

一、回文子串 647. 回文子串 - 力扣&#xff08;LeetCode&#xff09; class Solution {public int countSubstrings(String s) {boolean[][] dp new boolean[s.length()][s.length()];int ans 0;for (int j 0; j < s.length(); j) {for (int i 0; i < j; i) {if …

5+铁死亡+分型+WGCNA+机器学习分析

今天给同学们分享一篇铁死亡分型WGCNA机器学习的生信文章“Identification of ferroptosis-related molecular clusters and genes for diabetic osteoporosis based on the machine learning”&#xff0c;这篇文章于2023年8月14日发表在Front Endocrinol (Lausanne)期刊上&am…

Nginx location 精准匹配URL = /

Location是什么&#xff1f; Location是Nginx中的块级指令(block directive)&#xff0c;通过配置Location指令块&#xff0c;可以决定客户端发过来的请求URI如何处理&#xff08;是映射到本地文件还是转发出去&#xff09;及被哪个location处理。 匹配模式 分为两种模式&…

Zookeeper-命令操作

命令操作 命令操作1) Zookeeper 数据模型2) Zookeeper 服务端常用命令3) Zookeeper 客户端常用命令 命令操作 1) Zookeeper 数据模型 ZooKeeper 是一个树形目录服务,其数据模型和Unix的文件系统目录树很类似&#xff0c;拥有一个层次化结构。 这里面的每一个节点都被称为&am…

OpenCV实现图像 开闭运算

开运算概念 闭运算概念 API cv.morphologyEx(img , op, kernal) 参数&#xff1a; img ;要处理的图像op : 处理方式 &#xff1a; 若进行开运算&#xff0c;则设为cv.MORPH_OPEN &#xff0c;若进行闭运算&#xff0c;则设为cv.MORPH_CLOSEKernel &#xff1a;核结构 代码实现…

面试题:RocketMQ 如何保证消息不丢失,如何保证消息不被重复消费?

文章目录 1、消息整体处理过程Producer发送消息阶段手段一&#xff1a;提供SYNC的发送消息方式&#xff0c;等待broker处理结果。手段二&#xff1a;发送消息如果失败或者超时&#xff0c;则重新发送。手段三&#xff1a;broker提供多master模式&#xff0c;即使某台broker宕机…

【STM32单片机】小恐龙游戏设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用STM32F103C8T6单片机控制器&#xff0c;使用按键、IIC OLED模块等。 主要功能&#xff1a; 系统运行后&#xff0c;OLED液晶显示游戏初始界面&#xff0c;按下K4键开始&#xff0c;K1键跳跃&am…

Twitter优化秘籍:置顶、列表、受众增长

在 Twitter 上&#xff0c;将你的一条推送文置顶到个人数据顶部是提高可见性和吸引关注者的绝佳方式。无论你是个人用户还是企业&#xff0c;此功能都可以让你的重要信息常驻在众人眼前&#xff0c;即使你发布了新的推文。接下来&#xff0c;我们将分享一些优化建议&#xff0c…

机器视觉工程师们,常回家看看

我们在这个社会上扮演着多重角色&#xff0c;有时候我们很难平衡好这些角色之间的关系。 人们常言&#xff0c;积善成德&#xff0c;改变命运。善修者&#xff0c;懂得积累&#xff0c;懂得改变命运的重要性。 我曾年少不知父母之不易。一路依靠&#xff0c;一路成长。 所谓…

【数据增强】

【数据增强】 1 数据增强的情形2 数据增强的方法 1 数据增强的情形 当数据比较小&#xff0c;难以获取新的训练数据时&#xff0c;可以考虑数据增强&#xff0c;如随机裁剪部分&#xff0c;随机左右上下翻转、随机旋转一个角度、随机亮度变化等微小变化&#xff0c;数据的多样…

conan入门(二十七):因profile [env]字段废弃导致的boost/1.81.0 在aarch64-linux-gnu下交叉编译失败

今天在尝试用conan 1.60.0使用aarch64-linux-gnu编译器交叉编译boost/1.81.0时报错了&#xff1a; conan install boost/1.81.0 -pr:h aarch64-linux-gnu.jinja -pr:b default --build boost输出如下&#xff1a; Configuration (profile_host): [settings] archarmv8 arch_b…

iOS——present相关属性以及dismiss多级的方法

push和present 两者的区别 push: push由视图栈控制&#xff0c;每一个视图都入栈&#xff0c;调用之前的视图则需要出栈&#xff0c;可返回任意一层&#xff0c;一般用于同一业务不同界面之间的切换。 push是由UINavigationController管理的视图控制器堆栈&#xff0c;在wind…

CSS3实现上下拉长加载动画效果

上下拉长加载动画效果 <!DOCTYPE html> <html><head><style>.container {display: flex;justify-content: center;align-items: center;width: 150px;height: 150px;margin: 50px auto;}.rectangle {width: 20px;height: 50px;background-color: #02A…

黑马JVM总结(十九)

&#xff08;1&#xff09;GC调优1 通过官网查看查看JVM的参数&#xff1a; 可以使用java命令查看当前环境下的虚拟机参数&#xff1a; 学会使用一些工具如前面学的jmap &#xff0c;jconsole等等工具 &#xff08;2&#xff09;GC调优2 垃圾回收调优只是众多调优中的一个方…