万江专业网站快速排名个人免费网站注册

news/2025/9/24 21:10:40/文章来源:
万江专业网站快速排名,个人免费网站注册,wordpress导入媒体失败,服务器网站配置本研究的主要目的是基于Python aiortc api实现抓取本地设备媒体流#xff08;摄像机、麦克风#xff09;并与Web端实现P2P通话。本文章仅仅描述实现思路#xff0c;索要源码请私信我。 1 demo-server解耦 1.1 原始代码解析 1.1.1 http服务器端 import argparse import …本研究的主要目的是基于Python aiortc api实现抓取本地设备媒体流摄像机、麦克风并与Web端实现P2P通话。本文章仅仅描述实现思路索要源码请私信我。 1 demo-server解耦 1.1 原始代码解析 1.1.1 http服务器端 import argparse import asyncio import json import logging import os import ssl import uuidimport cv2 from aiohttp import web from aiortc import MediaStreamTrack, RTCPeerConnection, RTCSessionDescription from aiortc.contrib.media import MediaBlackhole, MediaPlayer, MediaRecorder, MediaRelay from av import VideoFrame # 这行代码设置了 ROOT 变量它代表了当前执行文件脚本所在的目录。 # __file__ 是 Python 中的一个特殊变量它包含了当前文件的路径。 # os.path.dirname 函数返回路径中的目录名称。 # 这个变量通常用于构建其他文件路径确保它们相对于当前脚本的位置。 ROOT os.path.dirname(__file__) # 这行代码创建了一个日志记录器logger它的名称是 pc。在 Python 的 logging 模块中 # 每个 logger 都有一个唯一的名称你可以使用这个名称来获取对应的 logger 实例。 # 这个 logger 将用于记录程序中与 WebRTC 相关的事件和信息。 logger logging.getLogger(pc) # 这行代码初始化了一个空的集合set名为 pcs。 # 这个集合可能用于存储 RTCPeerConnection 对象的引用。 # 使用集合可以方便地进行添加、检查和删除操作并且集合中的元素是唯一的。 pcs set() # 这行代码创建了一个 MediaRelay 对象名为 relay。MediaRelay 可能是一个用于 # 中继媒体流的自定义类它允许将从一个 RTCPeerConnection # 接收到的媒体流转发到另一个 RTCPeerConnection。 # 这种类型的中继通常用于 MCUMultipoint Control Unit场景或简单的媒体路由。 relay MediaRelay()# 这段代码定义了一个名为 VideoTransformTrack 的类它是 MediaStreamTrack 的子类。 # VideoTransformTrack 类的目的是从一个已有的视频轨道track接收帧 # 并对这些帧应用特定的转换transform # 然后返回转换后的帧。以下是对类中各个部分的详细解释 class VideoTransformTrack(MediaStreamTrack): # 继承A video stream track that transforms frames from an another track. # kind video: 这行代码设置了轨道的类型为视频。kind video # 构造函数 __init__(self, track, transform) # track 参数是另一个 MediaStreamTrack 实例VideoTransformTrack 将从这个轨道接收帧。 # transform 参数是一个字符串指定要应用的转换类型可以是 cartoon、edges 或 rotate。def __init__(self, track, transform):super().__init__() # dont forget this!self.track trackself.transform transform# 这个方法展示了如何使用OpenCV对视频帧进行实时处理包括卡通效果、边缘检测和旋转 # 并将处理后的帧返回给WebRTC轨道。 # 这段代码是VideoTransformTrack类中的recv方法它是一个异步方法# 用于接收视频帧并根据指定的转换类型对帧进行处理。# 下面是对这个方法的详细解释async def recv(self):# 这行代码异步地从self.track即类的track属性一个视频轨道接收一个视频帧。frame await self.track.recv() # 如果转换类型是“cartoon”则将接收到的视频帧转换为BGR颜色空间的NumPy数组。if self.transform cartoon:img frame.to_ndarray(formatbgr24) # 这部分代码首先对图像进行两次降采样pyrDown然后应用六次双边滤波bilateralFilter# 最后进行两次升采样pyrUp。# 这个过程有助于减少图像噪声并保持边缘清晰。# prepare colorimg_color cv2.pyrDown(cv2.pyrDown(img))for _ in range(6):img_color cv2.bilateralFilter(img_color, 9, 9, 7)img_color cv2.pyrUp(cv2.pyrUp(img_color)) # 这部分代码将图像转换为灰度图然后应用中值滤波medianBlur# 自适应阈值adaptiveThreshold来提取边缘 # 最后将灰度图转换回RGB颜色空间。# prepare edgesimg_edges cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)img_edges cv2.adaptiveThreshold(cv2.medianBlur(img_edges, 7),255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_BINARY,9,2,)# 将处理过的颜色和边缘图像进行按位与操作以结合两者。img_edges cv2.cvtColor(img_edges, cv2.COLOR_GRAY2RGB)# combine color and edgesimg cv2.bitwise_and(img_color, img_edges) # 最后将处理后的NumPy数组转换回VideoFrame对象 # 并保留原始帧的时间戳pts和时间基time_base然后返回这个新帧。# rebuild a VideoFrame, preserving timing informationnew_frame VideoFrame.from_ndarray(img, formatbgr24)new_frame.pts frame.ptsnew_frame.time_base frame.time_basereturn new_frame# 如果转换类型是“edges”则对帧进行Canny边缘检测并将结果转换回BGR颜色空间。elif self.transform edges:# perform edge detectionimg frame.to_ndarray(formatbgr24)img cv2.cvtColor(cv2.Canny(img, 100, 200), cv2.COLOR_GRAY2BGR) # 然后将处理后的边缘检测图像转换回VideoFrame对象并返回。# rebuild a VideoFrame, preserving timing informationnew_frame VideoFrame.from_ndarray(img, formatbgr24)new_frame.pts frame.ptsnew_frame.time_base frame.time_basereturn new_frame# 如果转换类型是“rotate”则将帧转换为NumPy数组并计算旋转矩阵# 然后应用仿射变换warpAffine来旋转图像。elif self.transform rotate:# rotate imageimg frame.to_ndarray(formatbgr24)rows, cols, _ img.shapeM cv2.getRotationMatrix2D((cols / 2, rows / 2), frame.time * 45, 1)img cv2.warpAffine(img, M, (cols, rows)) # 将旋转后的图像转换回VideoFrame对象并返回。# rebuild a VideoFrame, preserving timing informationnew_frame VideoFrame.from_ndarray(img, formatbgr24)new_frame.pts frame.ptsnew_frame.time_base frame.time_basereturn new_frameelse:# 如果转换类型既不是“cartoon”、“edges”也不是“rotate”则直接返回原始帧。return frame# 这个函数处理对服务器根URL通常是/的GET请求。 # 它的作用是返回服务器根目录下index.html文件的内容作为HTTP响应。 # request这是aiohttp传入的请求对象包含了请求的详细信息。 # os.path.join(ROOT, index.html)使用os.path.join函数构建index.html文件的完整路径。 # ROOT是之前定义的服务器根目录变量。 # open(...).read()以只读模式打开index.html文件并读取其内容。 # web.Response(content_typetext/html, textcontent)创建一个aiohttp响应对象 # 设置内容类型为text/html并将读取的HTML内容作为响应正文返回。 async def index(request):content open(os.path.join(ROOT, index.html), r).read()return web.Response(content_typetext/html, textcontent)# 这个函数处理对/client.js路径的GET请求。它的作用是返回服务器根目录下 # client.js文件的内容作为HTTP响应。 # request这是aiohttp传入的请求对象。 # os.path.join(ROOT, client.js)构建client.js文件的完整路径。 # open(...).read()以只读模式打开client.js文件并读取其内容。 # web.Response(content_typeapplication/javascript, textcontent) # 创建一个aiohttp响应对象设置内容类型为application/javascript # 并将读取的JavaScript内容作为响应正文返回。 async def javascript(request):content open(os.path.join(ROOT, client.js), r).read()return web.Response(content_typeapplication/javascript, textcontent)# 这个offer函数是一个异步的Web服务器路由处理函数用于处理WebRTC连接的建立过程。 # 它接收客户端发送的offer # 创建或处理一个RTCPeerConnection对象并返回一个answer给客户端。 async def offer(request):# 这行代码异步地解析客户端请求的JSON数据通常包含SDP会话描述协议信息和其他参数。params await request.json()# 使用客户端发送的SDP信息和类型创建一个RTCSessionDescription对象这个对象表示客户端的offer。offer RTCSessionDescription(sdpparams[sdp], typeparams[type])# 创建一个RTCPeerConnection对象这是WebRTC中用于管理WebRTC连接的对象。pc RTCPeerConnection()# 为这个连接创建一个唯一的ID。pc_id PeerConnection(%s) % uuid.uuid4()# 将这个连接对象添加到全局的连接集合中以便后续可以对其进行管理。pcs.add(pc)# 定义一个日志记录函数用于记录与特定连接相关的信息。def log_info(msg, *args):logger.info(pc_id msg, *args)# 记录创建连接的远程地址信息。log_info(Created for %s, request.remote)# 准备本地媒体# prepare local media# 创建一个MediaPlayer对象用于播放本地音频文件。player MediaPlayer(os.path.join(ROOT, demo-instruct.wav))# 根据命令行参数决定是否创建一个MediaRecorder对象来录制接收到的媒体流# 或者使用一个MediaBlackhole对象来忽略接收到的媒体流。if args.record_to:recorder MediaRecorder(args.record_to)else:recorder MediaBlackhole()# 监听数据通道事件。pc.on(datachannel)def on_datachannel(channel):# 在数据通道上监听消息事件。channel.on(message)def on_message(message):# 如果接收到的消息是字符串并且以ping开头则回复一个以pong开头的消息。if isinstance(message, str) and message.startswith(ping):channel.send(pong message[4:])# 监听连接状态变化事件。pc.on(connectionstatechange)async def on_connectionstatechange():log_info(Connection state is %s, pc.connectionState)# 记录连接状态。# 如果连接状态为失败则关闭连接并从集合中移除。if pc.connectionState failed:await pc.close()pcs.discard(pc)pc.on(track)def on_track(track):log_info(Track %s received, track.kind)# 监听轨道事件。# 如果接收到的是音频轨道则将播放器的音频轨道添加到连接中并录制接收到的音频轨道。if track.kind audio:pc.addTrack(player.audio)recorder.addTrack(track)# 如果接收到的是视频轨道则创建一个VideoTransformTrack对象来处理视频并将其添加到连接中。elif track.kind video:pc.addTrack(VideoTransformTrack(relay.subscribe(track), transformparams[video_transform]))# 如果需要录制则将接收到的视频轨道添加到录制器中。if args.record_to:recorder.addTrack(relay.subscribe(track))# 监听轨道结束事件。track.on(ended)async def on_ended():log_info(Track %s ended, track.kind)# 当轨道结束时停止录制。await recorder.stop()# 处理offer和发送answer# handle offer将客户端的offer设置为远程描述。await pc.setRemoteDescription(offer)# 开始录制。await recorder.start()# 创建answer。# send answeranswer await pc.createAnswer()# 将创建的answer设置为本地描述。await pc.setLocalDescription(answer)# 将本地描述的SDP信息和类型以JSON格式返回给客户端。# 这个函数展示了如何使用aiortc库来处理WebRTC的offer/answer模型# 包括创建连接、处理媒体流、设置事件监听器以及发送answer。return web.Response(content_typeapplication/json,textjson.dumps({sdp: pc.localDescription.sdp, type: pc.localDescription.type}),)# 这个on_shutdown函数是一个异步函数它被设计为在Web服务器关闭时执行。 # 它的主要任务是优雅地关闭所有的RTCPeerConnection对象并清理相关的资源。 # 这个函数接受一个参数app它代表aiohttp的应用程序实例。这个参数在这个函数中并没有被直接使用 # 但它是aiohttp应用程序关闭事件的一部分。 async def on_shutdown(app):# close peer connections# 这行代码创建了一个列表coros其中包含了所有pcs集合中的RTCPeerConnection对象的关闭操作。# pc.close()是一个异步方法用于关闭一个RTCPeerConnection对象。coros [pc.close() for pc in pcs]# 这行代码使用asyncio.gather来并发执行所有的关闭操作。asyncio.gather是一个异步函数# 它接受一个可迭代的异步任务列表并返回一个代表所有任务完成的异步任务。# 这确保了所有的RTCPeerConnection对象可以同时关闭而不是一个接一个地关闭# 从而提高了关闭过程的效率。await asyncio.gather(*coros)# 在所有的RTCPeerConnection对象都被关闭之后这行代码清空了pcs集合移除了所有的连接对象引用。# 这是一个清理步骤确保了在服务器关闭时不会有任何遗留的资源占用。pcs.clear()# 这段代码是Python脚本的入口点它负责解析命令行参数、设置日志记录、配置SSL上下文、 # 初始化aiohttp应用并启动Web服务器。 if __name__ __main__:# 创建一个ArgumentParser对象用于解析命令行参数。描述信息说明了这个脚本# 是一个WebRTC的音频/视频/数据通道演示。parser argparse.ArgumentParser(descriptionWebRTC audio / video / data-channels demo)# 添加两个命令行参数分别用于指定SSL证书文件和密钥文件的路径。# 这些参数是可选的用于配置HTTPS服务。parser.add_argument(--cert-file, helpSSL certificate file (for HTTPS))parser.add_argument(--key-file, helpSSL key file (for HTTPS))# 添加两个命令行参数用于指定HTTP服务器的主机地址和端口号。# 默认值分别是0.0.0.0所有可用网络接口和8080。parser.add_argument(--host, default0.0.0.0, helpHost for HTTP server (default: 0.0.0.0))parser.add_argument(--port, typeint, default8080, helpPort for HTTP server (default: 8080))# 添加一个命令行参数用于指定将接收到的媒体流录制到文件的路径。parser.add_argument(--record-to, helpWrite received media to a file.)# 添加一个命令行参数用于控制日志记录的详细程度。-v或--verbose可以被指定多次以增加日志的详细程度。parser.add_argument(--verbose, -v, actioncount)# 解析命令行参数并将解析结果存储在args对象中。args parser.parse_args()# 根据args.verbose的值设置日志记录的级别。# 如果args.verbose为真则设置日志级别为DEBUG否则为INFO。if args.verbose:logging.basicConfig(levellogging.DEBUG)else:logging.basicConfig(levellogging.INFO)# SSL上下文配置# 如果用户提供了证书文件和密钥文件则创建一个SSL上下文对象并加载证书和密钥。# 否则ssl_context被设置为None表示不使用SSL。if args.cert_file:ssl_context ssl.SSLContext()ssl_context.load_cert_chain(args.cert_file, args.key_file)else:ssl_context None# 初始化aiohttp应用# 创建一个aiohttp应用实例。app web.Application()# 将on_shutdown函数添加到应用的关闭事件中以便在应用关闭时执行资源清理。app.on_shutdown.append(on_shutdown)# 为应用添加路由分别处理根URL的GET请求返回首页、/client.js的# GET请求返回客户端JavaScript代码和/offer的POST请求处理WebRTC offerapp.router.add_get(/, index)app.router.add_get(/client.js, javascript)app.router.add_post(/offer, offer)# 启动Web服务器# 启动aiohttp应用设置访问日志为None不记录访问日志主机地址和端口号根据命令行参数设置# 如果配置了SSL则使用相应的SSL上下文。web.run_app(app, access_logNone, hostargs.host, portargs.port, ssl_contextssl_context)1.1.2 web端 1.1.2.1 client.js // 获取DOM元素 var dataChannelLog document.getElementById(data-channel), // 获取数据通道日志元素iceConnectionLog document.getElementById(ice-connection-state), // 获取ICE连接状态元素iceGatheringLog document.getElementById(ice-gathering-state), // 获取ICE收集状态元素signalingLog document.getElementById(signaling-state); // 获取信令状态元素// 对等连接对象 var pc null; // 初始化对等连接对象为null// 数据通道对象 var dc null, dcInterval null; // 初始化数据通道对象和定时器// 创建对等连接 function createPeerConnection() {var config {sdpSemantics: unified-plan // 设置SDP语义};// 如果选中了使用STUN服务器if (document.getElementById(use-stun).checked) {config.iceServers [{ urls: [stun:stun.l.google.com:19302] }]; // 配置STUN服务器}// 创建新的对等连接实例pc new RTCPeerConnection(config);// 注册一些监听器以帮助调试pc.addEventListener(icegatheringstatechange, () { // 当ICE收集状态改变时iceGatheringLog.textContent - pc.iceGatheringState; // 更新ICE收集状态日志}, false);iceGatheringLog.textContent pc.iceGatheringState; // 初始化ICE收集状态日志pc.addEventListener(iceconnectionstatechange, () { // 当ICE连接状态改变时iceConnectionLog.textContent - pc.iceConnectionState; // 更新ICE连接状态日志}, false);iceConnectionLog.textContent pc.iceConnectionState; // 初始化ICE连接状态日志pc.addEventListener(signalingstatechange, () { // 当信令状态改变时signalingLog.textContent - pc.signalingState; // 更新信令状态日志}, false);signalingLog.textContent pc.signalingState; // 初始化信令状态日志// 连接音频/视频pc.addEventListener(track, (evt) { // 当接收到轨道时if (evt.track.kind video) // 如果是视频轨道document.getElementById(video).srcObject evt.streams[0]; // 设置视频源else // 如果是音频轨道document.getElementById(audio).srcObject evt.streams[0]; // 设置音频源});return pc; // 返回对等连接实例 }// 枚举输入设备 function enumerateInputDevices() {const populateSelect (select, devices) { // 填充选择器的函数let counter 1;devices.forEach((device) { // 遍历设备const option document.createElement(option); // 创建新的选项option.value device.deviceId; // 设置选项的值option.text device.label || (Device # counter); // 设置选项的文本select.appendChild(option); // 将选项添加到选择器counter 1;});};navigator.mediaDevices.enumerateDevices().then((devices) { // 枚举设备populateSelect( // 填充音频输入选择器document.getElementById(audio-input),devices.filter((device) device.kind audioinput) // 过滤音频输入设备);populateSelect( // 填充视频输入选择器document.getElementById(video-input),devices.filter((device) device.kind videoinput) // 过滤视频输入设备);}).catch((e) { // 如果发生错误alert(e); // 显示错误消息}); }// 协商过程 function negotiate() {return pc.createOffer().then((offer) { // 创建offer// [add]local offer中不存在candidate信息也就是创建pc.setLocalDescription(offer)会自动进行candidate收集。// [add]这里异步方法等待candidate收集完毕后在发送offer。console.log(local offer: , offer);return pc.setLocalDescription(offer); // 设置本地描述}).then(() {// 等待ICE收集完成return new Promise((resolve) {if (pc.iceGatheringState complete) {resolve(); // 如果ICE收集已完成解析Promise} else {function checkState() { // 检查ICE收集状态的函数if (pc.iceGatheringState complete) {pc.removeEventListener(icegatheringstatechange, checkState); // 移除事件监听器resolve(); // 解析Promise}}pc.addEventListener(icegatheringstatechange, checkState); // 添加事件监听器}});}).then(() {var offer pc.localDescription; // 获取本地描述// [add]添加offer日志,这里的offer包含candidate信息而在我的webrtc1v1demo代码中offer中没有candidate信息。// [add]webrtc1v1demo中candidate信息是offer后进行交换的。console.log(send local offer: , offer);var codec;codec document.getElementById(audio-codec).value; // 获取音频编解码器if (codec ! default) { // 如果不是默认编解码器offer.sdp sdpFilterCodec(audio, codec, offer.sdp); // 过滤SDP中的音频编解码器}codec document.getElementById(video-codec).value; // 获取视频编解码器if (codec ! default) { // 如果不是默认编解码器offer.sdp sdpFilterCodec(video, codec, offer.sdp); // 过滤SDP中的视频编解码器}document.getElementById(offer-sdp).textContent offer.sdp; // 显示offer的SDPreturn fetch(/offer, { // 发送请求到服务器body: JSON.stringify({sdp: offer.sdp,type: offer.type,video_transform: document.getElementById(video-transform).value}),headers: {Content-Type: application/json},method: POST});}).then((response) {return response.json(); // 解析响应为JSON}).then((answer) {document.getElementById(answer-sdp).textContent answer.sdp; // 显示answer的SDPreturn pc.setRemoteDescription(answer); // 设置远程描述}).catch((e) { // 如果发生错误alert(e); // 显示错误消息}); }// 开始过程 function start() {document.getElementById(start).style.display none; // 隐藏开始按钮pc createPeerConnection(); // 创建对等连接var time_start null; // 初始化时间戳const current_stamp () { // 获取当前时间戳的函数if (time_start null) { // 如果还没有开始计时time_start new Date().getTime(); // 开始计时return 0; // 返回0} else {return new Date().getTime() - time_start; // 返回当前时间戳}};if (document.getElementById(use-datachannel).checked) { // 如果选中了使用数据通道var parameters JSON.parse(document.getElementById(datachannel-parameters).value); // 获取数据通道参数dc pc.createDataChannel(chat, parameters); // 创建数据通道dc.addEventListener(close, () { // 当数据通道关闭时clearInterval(dcInterval); // 清除定时器dataChannelLog.textContent - close\n; // 更新数据通道日志});dc.addEventListener(open, () { // 当数据通道打开时dataChannelLog.textContent - open\n; // 更新数据通道日志dcInterval setInterval(() { // 设置定时器var message ping current_stamp(); // 创建ping消息dataChannelLog.textContent message \n; // 更新数据通道日志dc.send(message); // 发送消息}, 1000); // 每秒发送一次});dc.addEventListener(message, (evt) { // 当接收到消息时dataChannelLog.textContent evt.data \n; // 更新数据通道日志if (evt.data.substring(0, 4) pong) { // 如果是pong消息var elapsed_ms current_stamp() - parseInt(evt.data.substring(5), 10); // 计算往返时间dataChannelLog.textContent RTT elapsed_ms ms\n; // 更新数据通道日志}});}// 构建媒体约束const constraints {audio: false,video: false};if (document.getElementById(use-audio).checked) { // 如果选中了使用音频const audioConstraints {};const device document.getElementById(audio-input).value; // 获取音频输入设备if (device) { // 如果选择了设备audioConstraints.deviceId { exact: device }; // 设置设备ID}constraints.audio Object.keys(audioConstraints).length ? audioConstraints : true; // 设置音频约束}if (document.getElementById(use-video).checked) { // 如果选中了使用视频const videoConstraints {};const device document.getElementById(video-input).value; // 获取视频输入设备if (device) { // 如果选择了设备videoConstraints.deviceId { exact: device }; // 设置设备ID}const resolution document.getElementById(video-resolution).value; // 获取视频分辨率if (resolution) { // 如果设置了分辨率const dimensions resolution.split(x); // 分割分辨率videoConstraints.width parseInt(dimensions[0], 0); // 设置宽度videoConstraints.height parseInt(dimensions[1], 0); // 设置高度}constraints.video Object.keys(videoConstraints).length ? videoConstraints : true; // 设置视频约束}// 获取媒体并开始协商if (constraints.audio || constraints.video) { // 如果需要获取媒体if (constraints.video) { // 如果需要视频document.getElementById(media).style.display block; // 显示媒体元素}navigator.mediaDevices.getUserMedia(constraints).then((stream) { // 获取媒体stream.getTracks().forEach((track) { // 遍历轨道pc.addTrack(track, stream); // 添加轨道到对等连接});return negotiate(); // 开始协商}, (err) { // 如果发生错误alert(Could not acquire media: err); // 显示错误消息});} else { // 如果不需要获取媒体negotiate(); // 开始协商}document.getElementById(stop).style.display inline-block; // 显示停止按钮 }// 停止过程 function stop() {document.getElementById(stop).style.display none; // 隐藏停止按钮// 关闭数据通道if (dc) { // 如果存在数据通道dc.close(); // 关闭数据通道}// 关闭传输器if (pc.getTransceivers) { // 如果对等连接支持获取传输器pc.getTransceivers().forEach((transceiver) { // 遍历传输器if (transceiver.stop) { // 如果传输器可以停止transceiver.stop(); // 停止传输器}});}// 关闭本地音频/视频pc.getSenders().forEach((sender) { // 遍历发送器sender.track.stop(); // 停止轨道});// 关闭对等连接setTimeout(() { // 设置延迟pc.close(); // 关闭对等连接}, 500); // 500毫秒后关闭 }// 过滤SDP中的编解码器 function sdpFilterCodec(kind, codec, realSdp) {var allowed []var rtxRegex new RegExp(afmtp:(\\d) apt(\\d)\r$);var codecRegex new RegExp(artpmap:([0-9]) escapeRegExp(codec))var videoRegex new RegExp((m kind .*?)( ([0-9]))*\\s*$)var lines realSdp.split(\n);var isKind false;for (var i 0; i lines.length; i) {if (lines[i].startsWith(m kind )) {isKind true;} else if (lines[i].startsWith(m)) {isKind false;}if (isKind) {var match lines[i].match(codecRegex);if (match) {allowed.push(parseInt(match[1]));}match lines[i].match(rtxRegex);if (match allowed.includes(parseInt(match[2]))) {allowed.push(parseInt(match[1]));}}}var skipRegex a(fmtp|rtcp-fb|rtpmap):([0-9]);var sdp ;isKind false;for (var i 0; i lines.length; i) {if (lines[i].startsWith(m kind )) {isKind true;} else if (lines[i].startsWith(m)) {isKind false;}if (isKind) {var skipMatch lines[i].match(skipRegex);if (skipMatch !allowed.includes(parseInt(skipMatch[2]))) {continue;} else if (lines[i].match(videoRegex)) {sdp lines[i].replace(videoRegex, $1 allowed.join( )) \n;} else {sdp lines[i] \n;}} else {sdp lines[i] \n;}}return sdp; }// 转义正则表达式字符串 function escapeRegExp(string) {return string.replace(/[.*?^${}()|[\]\\]/g, \\$); // $ means the whole matched string }enumerateInputDevices(); // 枚举输入设备1.1.3 解耦 1.1.3.1 设备端部署音频服务 修改说明 取消Web服务 删除了 index 和 javascript 函数。删除了与Web服务相关的路由设置。 修改 **offer** 方法 将 offer 方法改为 connect_to_websocket用于连接到WebSocket服务并接收消息。 解耦Web服务到WebSocket服务 将Web服务的功能解耦到WebSocket服务中WebRTC的Web端由WebSocket服务提供Java实现。 WebSocket客户端 使用 websockets 库连接到WebSocket服务。接收WebSocket服务发送的 offer 消息并处理 offer 消息。创建 RTCPeerConnection 对象并设置远程描述。创建本地描述并将其发送回WebSocket服务。 1.1.3.2 Web服务增加信令服务Java实现 1.1.3.3 client.js修改为ws调用 修改说明 添加 WebSocket 连接 在 connectToWebSocket() 函数中创建 WebSocket 连接并处理 onopen、onmessage 和 onclose 事件。 修改 **start()** 函数 在 start() 函数中通过 WebSocket 连接发送消息而不是使用 fetch 发送 HTTP 请求。 处理 WebSocket 消息 在 connectToWebSocket() 函数中处理从 WebSocket 服务接收到的消息并调用 handleOffer() 和 handleAnswer() 函数。 页面加载时连接 WebSocket 在 window.onload 事件中调用 connectToWebSocket() 函数确保页面加载时自动连接 WebSocket 服务。 2 抓取本地麦克风流 3 音频效果

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

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

相关文章

glTF/glb:您需要知道的一切,怎么免费获取下载

有一种新的丰富 3D 模型格式,称为 glTF,并且一直在崛起。本文将告诉您有关 glTF 的所有信息,包括它是什么、为什么开发它以及谁在使用它。glb下载官网免费获取模型什么是glTF? GL 传输格式(简称 glTF)是一种开源…

成品网站短视频源码搭建网站建设培训 苏州

首次连接 打开装有 AirPods 的充电盒,并将它放在 iPhone 旁边。此时你的 iPhone 上将出现设置动画。轻点「连接」,然后轻点「完成」。 就这么简单,而且会自动设置,实现与已使用同一 Apple ID 登录 iCloud 的任一支持设备搭配使用…

3.HTTP/HTTPS:报文格式、技巧、状态码、缓存、SSLTLS握手

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

keepalived服务器

keepalived服务器keepalived高可用原理:搭建主、备服务器一样配置,在keepalived中配置相同的vip;主服务器发送“心跳消息”给备服务器,主服务器宕机,“心跳消息”停止发送,备服务器会让vip生效,产生“IP漂移”,…

外部 Tomcat 部署详细 - 实践

外部 Tomcat 部署详细 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco"…

20231326《密码系统设计》第三周预习报告

20231326《密码系统设计》第三周预习报告20231326《密码系统设计》第三周预习报告 目录20231326《密码系统设计》第三周预习报告学习内容《Head First C 嗨翻 C 语言》第4章《Windows C/C++加密解密实战》第4章AI 对学…

吉林网站开发公司网站首页设计html代码

作者| Rohan Wadiwala、Mangesh More翻译 | 天道酬勤,编辑 | Carol出品| CSDN云计算(ID:CSDNcloud)在分析的世界中,网站的每次点击都是数据分析的候选对象,显然,这会涉及大量的数据生成。对于海…

做普工招聘网站上海汽车设计公司名单

静态长效代理IP和动态短效代理IP是两种常见的代理IP类型,它们在用途和适用场景上存在一定的差异。了解它们的特性以及使用场景有助于我们更好地利用代理IP,提高网络访问的效率和安全性。 一、静态长效代理IP 1. 用途 静态长效代理IP是指长期保持稳定的代…

深圳做网站专业的公司如何做英文网站推广

来源:数码之家文 | 禅哥这台机器在本人的eBay收藏夹里呆了很久,某日无意间扫了一眼收藏夹,突然发现卖家大降价,只要15刀,还有best offer选项。15刀你买不了吃亏,15刀你买不了上当。事不宜迟果断下手。根据非…

天津星创网站建设有限公司微信小商店分销系统

977. 有序数组的平方y 思路,原数组是有序的,但是因为负数平方后可能变无序了,因此利用双指针遍历原数组,比较 nums[left]*nums[left]和nums[right]*nums[right]谁更大,然后对新数组赋值 class Solution {public int…

FortiGate连接中国联通SDWAN

最近上线SAP,需要使用公司飞塔防火墙连接中国联通SDWAN,记录下过程吧。 飞塔防火墙型号F200E,有2条互联网带宽,需要分别与联通建立IPSEC+BGP连接。 联通会提供2个IPSEC配置信息+2个BGP配置信息 1,在飞塔完成2条IP…

第五章 运算符、表达式和语句

本章将介绍以下内容: 1、关键字——while、typedef; 2、运算符——=、-、*、/、%、++、--; 3、C语言的各种运算符,包括用于普通数学运算的运算符; 4、运算符优先级以及语句、表达式的含义; 5、while循环; 6、复…

wordpress 淘宝客网站模板平面设计与网页设计

Hi1102A和Hi1105V500都是属于海思旗下的两款WIFIBTGNSSFM四功能一体(江湖俗称四合一)高性能方案,应该可以推出,这个原本是在手机方案集成使用的,本身海思有视频安防主控HI315X系列平台,如果搭配上自己的无线phy芯片,一…

广州网站建设 易企建站公司网站开发和运营合同分开签么

以前的大部分程序都是操作Chrome,很少有操作Edge,现在以Edge为例。 Selenium本身是无法直接控制浏览器的,不同的浏览器需要不同的驱动程序,Google Chrome需要安装ChromeDriver、Edge需要安装Microsoft Edge WebDriver&#xff0c…

【Golang】素材设计模式

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

学习问题日记-2

在开发过程中遇到了一个问题,报错描述如下:java: 无法将类 com.chools.demo.entity.Address中的构造器 Address应用到给定类型; 需要: 没有参数 找到: java.lang.String,java.lang.String,java.lang.String …

樟树网站制作wordpress在线音乐

电脑的设备驱动程序:驱动程序一般指的是设备驱动程序(DeviceDriver),是一种可以使计算机和设备通信的特殊程序。相当于硬件的接口,操作系统只有通过这个接口,才能控制硬件设备的工作,如某设备的驱动程序未能正确安装&a…

封神台复现

EzPyeditor这个界面没啥东西,直接下载源码看一下进去看一下app.py熟悉的界面,又是他 这里先去追踪一下这个函数parse这里这个函数大体上来说是存在一个文件读取的漏洞的 这个漏洞会接受filename这个参数 这里我们就可…

北京商城网站建设费用企业网页如何制作

在window下,我们一般用Source Insight来查看代码而在linux下,使用vim来查看代码,vim是一个简单的文本浏览/编辑器,它可以通过插件的形式,搭建一个完全的类Source Insight环境,通过快捷键的形式,快速查看、定位变量/函数,本文就是基于vim,通过ctags+cscope+taglist+Ner…

网站建设与维护试卷做网站 十万

Mybatis:一对一查询映射处理 前言一、概述二、创建数据模型三、 问题四、解决方案1、方案一:级联方式处理映射关系2、方案二:使用association处理映射关系3、方案三:分步查询 前言 本博主将用CSDN记录软件开发求学之路上亲身所得…