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

news/2024/5/18 15:29:40 标签: 音视频, udp, 网络协议, javascript, vue

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

javascript">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

javascript">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

javascript">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的网站使用

javascript">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.niftyadmin.cn/n/5043219.html

相关文章

npm install报错 code:128

报的错误: npm ERR! code 128 npm ERR! An unknown git error occurred npm ERR! command git --no-replace-objects ls-remote ssh://gitgithub.com/nhn/raphael.git npm ERR! gitgithub.com: Permission denied (publickey). npm ERR! fatal: Could not read from remote re…

计算机毕业设计 基于SpringBoot的“漫画之家”系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

有哪些好用的上网行为管理软件?(上网行为管理软件功能好的软件推荐)

随着互联网的快速发展&#xff0c;企业的信息化管理和员工的上网行为已经成为企业信息化建设的重要组成部分。上网行为管理软件作为一种新型的管理工具&#xff0c;可以帮助企业实现对员工上网行为的管控和优化&#xff0c;进而提高企业的工作效率和网络安全。本文将对多款市场…

Spring Cloud Gateway快速入门(三)——过滤器

文章目录 前言Gateway内置网关过滤器什么是网关过滤器Gateway内置网关过滤器GlobalFilterPreFilterPostFilter 使用示例 Gateway全局网关过滤器什么是全局网关过滤器使用全局网关过滤器注册全局网关过滤器使用全局网关过滤器 全局网关过滤器和Gateway内置网关过滤器的区别1. 注…

thinkphp8路由

thinkphp8已出来有好一段时间了。这些天闲来无事&#xff0c;研究了下tp8的路由。默认情况下&#xff0c;tp8的路由是在route\app.php的文件里。但在实际工作中&#xff0c;我们并不会这样子去写路由。因为这样不好管理。更多的&#xff0c;是通过应用级别去管理路由。假如项目…

VuePress网站如何使用axios请求第三方接口

前言 VuePress是一个纯静态网站生成器,也就是它是无后端,纯前端的,那想要在VuePress中,发送ajax请求,请求一些第三方接口,有时想要达到自己一些目的 在VuePress中&#xff0c;使用axios请求第三方接口&#xff0c;需要先安装axios&#xff0c;然后引入&#xff0c;最后使用 本文…

ruoyi框架修改左侧菜单样式

菜单效果 ruoyi前端框架左侧的菜单很丑&#xff0c;我们需要修改一下样式&#xff0c;下面直接看效果。 修改代码 1、sidebar.scss .el-menu-item, .el-submenu__title {overflow: hidden !important;text-overflow: ellipsis !important;white-space: nowrap !important;//…

SQLite 3.43 发布,性能大提升!

前言 SQLite是一种被广泛运用的嵌入式关系型数据库管理系统&#xff0c;最新发布的SQLite 3.43版本带来了一个重要的改进&#xff0c;大幅提升了对JSON数据的处理性能&#xff0c;达到了之前的两倍。 主要更新 添加对 Contentless-Delete FTS5 索引的支持。这是 FTS5 全文搜索…