• 发送实时音频数据到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 = 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);
    
    		  const ssrc = Math.floor(Math.random() * 0xFFFFFFFF);//同步源标识符, 32位的无符号整数(根据需要修改)
    		  let sequenceNumber = 0;//序列号(根据需要修改)
    		  let timestamp = 0;//开始截取时间戳(根据需要修改)
    		  
    		  // 当音频处理事件发生时
    		  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
    		    
    		    const sampleRate = 44100; // 音频采样率(根据需要修改)
    		    
    		    // 根据需要的时间位置和采样率计算时间戳
    		    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()
    	      }
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    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套接字已绑定');
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    运行app.js: node app.js

    3、udp服务上可以看到发送过来的二进制数据(调试工具在这链接: https://pan.baidu.com/s/1bMLjph5U9ubDmyBiscWISg?pwd=lrw8 提取码: lrw8 复制这段内容后打开百度网盘手机App,操作更方便哦
    在这里插入图片描述
    注:如果提示[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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    然后把上面的采集函数改一下,提示一下,这个可能需要在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));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
  • 相关阅读:
    10、页面结构分析
    【黄啊码】MySQL入门—3、我用select *,老板直接赶我坐火车回家去,买的还是站票
    【152.乘积最大子数组】
    【机器学习】VAE变分自编码器学习笔记
    Redis学习笔记-跳跃表
    vue-router安装报错、版本冲突
    展会邀请丨虹科诚邀您9月15-16日于广州参加2023第14届全国药品质量安全大会
    大数据框架介绍与实操
    MyBatis-Plus学习笔记总结
    金融壹账通拟7月4日香港上市:2年亏近30亿 市值蒸发超90%
  • 原文地址:https://blog.csdn.net/qq_16026045/article/details/133163189