工程化与框架系列(27)--前端音视频处理
前端音视频处理 🎥
引言
前端音视频处理是现代Web应用中的重要组成部分,涉及音频播放、视频处理、流媒体传输等多个方面。本文将深入探讨前端音视频处理的关键技术和最佳实践,帮助开发者构建高质量的多媒体应用。
音视频技术概述
前端音视频处理主要包括以下技术方向:
- 音频处理:音频播放、录制、分析
- 视频处理:视频播放、录制、编辑
- 流媒体:实时音视频、直播推流
- WebRTC:点对点通信
- 媒体格式:编解码、转换
音频处理实现
音频播放器
// 音频播放器类
class AudioPlayer {private audio: HTMLAudioElement;private audioContext: AudioContext;private source: MediaElementAudioSourceNode;private analyser: AnalyserNode;private gainNode: GainNode;private equalizer: EqualizerNode;constructor() {this.audio = new Audio();this.audioContext = new AudioContext();// 创建音频源this.source = this.audioContext.createMediaElementSource(this.audio);// 创建分析器this.analyser = this.audioContext.createAnalyser();this.analyser.fftSize = 2048;// 创建音量控制this.gainNode = this.audioContext.createGain();// 创建均衡器this.equalizer = new EqualizerNode(this.audioContext);// 连接音频节点this.source.connect(this.analyser).connect(this.equalizer.input).connect(this.gainNode).connect(this.audioContext.destination);// 初始化事件监听this.initializeEventListeners();}// 加载音频loadAudio(url: string): Promise<void> {return new Promise((resolve, reject) => {this.audio.src = url;this.audio.load();this.audio.oncanplaythrough = () => resolve();this.audio.onerror = () => reject(new Error('Failed to load audio'));});}// 播放play(): Promise<void> {return this.audio.play();}// 暂停pause(): void {this.audio.pause();}// 跳转到指定时间seek(time: number): void {this.audio.currentTime = time;}// 设置音量setVolume(volume: number): void {this.gainNode.gain.value = Math.max(0, Math.min(1, volume));}// 设置均衡器setEqualizerBand(frequency: number, gain: number): void {this.equalizer.setBand(frequency, gain);}// 获取频谱数据getSpectrumData(): Uint8Array {const dataArray = new Uint8Array(this.analyser.frequencyBinCount);this.analyser.getByteFrequencyData(dataArray);return dataArray;}// 获取波形数据getWaveformData(): Uint8Array {const dataArray = new Uint8Array(this.analyser.frequencyBinCount);this.analyser.getByteTimeDomainData(dataArray);return dataArray;}// 初始化事件监听private initializeEventListeners(): void {// 播放状态变化this.audio.addEventListener('play', () => {this.audioContext.resume();});// 音频结束this.audio.addEventListener('ended', () => {// 处理播放结束});// 音频错误this.audio.addEventListener('error', (e) => {console.error('Audio error:', e);});}
}// 均衡器节点类
class EqualizerNode {private context: AudioContext;private bands: BiquadFilterNode[];private _input: GainNode;private _output: GainNode;constructor(context: AudioContext) {this.context = context;this.bands = [];// 创建输入输出节点this._input = context.createGain();this._output = context.createGain();// 创建均衡器频段this.createBands();// 连接频段this.connectBands();}// 获取输入节点get input(): AudioNode {return this._input;}// 获取输出节点get output(): AudioNode {return this._output;}// 设置频段增益setBand(frequency: number, gain: number): void {const band = this.bands.find(b => Math.abs(b.frequency.value - frequency) < 1);if (band) {band.gain.value = gain;}}// 创建均衡器频段private createBands(): void {const frequencies = [60, 170, 310, 600, 1000, 3000, 6000, 12000, 14000, 16000];frequencies.forEach(freq => {const filter = this.context.createBiquadFilter();filter.type = 'peaking';filter.frequency.value = freq;filter.Q.value = 1;filter.gain.value = 0;this.bands.push(filter);});}// 连接频段private connectBands(): void {this.bands.reduce((prev, curr) => {prev.connect(curr);return curr;}, this._input);this.bands[this.bands.length - 1].connect(this._output);}
}// 使用示例
const player = new AudioPlayer();// 加载并播放音频
async function playAudio(url: string) {try {await player.loadAudio(url);await player.play();// 设置音量player.setVolume(0.8);// 设置均衡器player.setEqualizerBand(60, 3); // 增强低频player.setEqualizerBand(12000, 2); // 增强高频// 实时更新频谱显示function updateSpectrum() {const spectrumData = player.getSpectrumData();// 使用频谱数据绘制可视化效果requestAnimationFrame(updateSpectrum);}updateSpectrum();} catch (error) {console.error('Failed to play audio:', error);}
}
音频录制器
// 音频录制器类
class AudioRecorder {private stream: MediaStream | null;private mediaRecorder: MediaRecorder | null;private audioChunks: Blob[];private isRecording: boolean;constructor() {this.stream = null;this.mediaRecorder = null;this.audioChunks = [];this.isRecording = false;}// 请求麦克风权限async requestPermission(): Promise<void> {try {this.stream = await navigator.mediaDevices.getUserMedia({audio: true});} catch (error) {throw new Error('Failed to get microphone permission');}}// 开始录制startRecording(): void {if (!this.stream) {throw new Error('No audio stream available');}this.audioChunks = [];this.mediaRecorder = new MediaRecorder(this.stream);this.mediaRecorder.addEventListener('dataavailable', (event) => {if (event.data.size > 0) {this.audioChunks.push(event.data);}});this.mediaRecorder.start();this.isRecording = true;}// 停止录制stopRecording(): Promise<Blob> {return new Promise((resolve, reject) => {if (!this.mediaRecorder || !this.isRecording) {reject(new Error('Not recording'));return;}this.mediaRecorder.addEventListener('stop', () => {const audioBlob = new Blob(this.audioChunks, {type: 'audio/webm'});resolve(audioBlob);});this.mediaRecorder.stop();this.isRecording = false;});}// 暂停录制pauseRecording(): void {if (this.mediaRecorder && this.isRecording) {this.mediaRecorder.pause();}}// 恢复录制resumeRecording(): void {if (this.mediaRecorder && this.isRecording) {this.mediaRecorder.resume();}}// 释放资源dispose(): void {if (this.stream) {this.stream.getTracks().forEach(track => track.stop());this.stream = null;}this.mediaRecorder = null;this.audioChunks = [];this.isRecording = false;}
}// 使用示例
const recorder = new AudioRecorder();async function startRecording() {try {// 请求麦克风权限await recorder.requestPermission();// 开始录制recorder.startRecording();// 5秒后停止录制setTimeout(async () => {const audioBlob = await recorder.stopRecording();// 创建音频URLconst audioUrl = URL.createObjectURL(audioBlob);// 创建音频元素播放录音const audio = new Audio(audioUrl);audio.play();// 清理资源recorder.dispose();}, 5000);} catch (error) {console.error('Recording failed:', error);}
}
视频处理实现
视频播放器
// 视频播放器类
class VideoPlayer {private video: HTMLVideoElement;private canvas: HTMLCanvasElement;private ctx: CanvasRenderingContext2D;private isPlaying: boolean;constructor(videoElement: HTMLVideoElement,canvas: HTMLCanvasElement) {this.video = videoElement;this.canvas = canvas;this.ctx = canvas.getContext('2d')!;this.isPlaying = false;this.initializePlayer();}// 初始化播放器private initializePlayer(): void {// 设置画布尺寸this.canvas.width = this.video.clientWidth;this.canvas.height = this.video.clientHeight;// 监听视频事件this.video.addEventListener('play', () => {this.isPlaying = true;this.render();});this.video.addEventListener('pause', () => {this.isPlaying = false;});this.video.addEventListener('ended', () => {this.isPlaying = false;});}// 加载视频loadVideo(url: string): Promise<void> {return new Promise((resolve, reject) => {this.video.src = url;this.video.load();this.video.oncanplaythrough = () => resolve();this.video.onerror = () => reject(new Error('Failed to load video'));});}// 播放play(): Promise<void> {return this.video.play();}// 暂停pause(): void {this.video.pause();}// 跳转到指定时间seek(time: number): void {this.video.currentTime = time;}// 设置播放速度setPlaybackRate(rate: number): void {this.video.playbackRate = rate;}// 应用滤镜效果applyFilter(filter: VideoFilter): void {this.ctx.filter = filter.toString();}// 渲染视频帧private render(): void {if (!this.isPlaying) return;// 绘制视频帧this.ctx.drawImage(this.video,0,0,this.canvas.width,this.canvas.height);// 继续渲染下一帧requestAnimationFrame(() => this.render());}// 截取当前帧captureFrame(): string {return this.canvas.toDataURL('image/png');}// 导出视频片段async exportClip(startTime: number,endTime: number): Promise<Blob> {const stream = this.canvas.captureStream();const recorder = new MediaRecorder(stream);const chunks: Blob[] = [];recorder.ondataavailable = (e) => {if (e.data.size > 0) {chunks.push(e.data);}};return new Promise((resolve, reject) => {recorder.onstop = () => {const blob = new Blob(chunks, { type: 'video/webm' });resolve(blob);};// 开始录制this.video.currentTime = startTime;recorder.start();// 到达结束时间后停止const checkTime = () => {if (this.video.currentTime >= endTime) {recorder.stop();this.pause();} else {requestAnimationFrame(checkTime);}};this.play().then(checkTime);});}
}// 视频滤镜类
class VideoFilter {private filters: Map<string, number>;constructor() {this.filters = new Map();}// 设置亮度setBrightness(value: number): void {this.filters.set('brightness', value);}// 设置对比度setContrast(value: number): void {this.filters.set('contrast', value);}// 设置饱和度setSaturation(value: number): void {this.filters.set('saturate', value);}// 设置色相setHue(value: number): void {this.filters.set('hue-rotate', value);}// 设置模糊setBlur(value: number): void {this.filters.set('blur', value);}// 转换为CSS滤镜字符串toString(): string {return Array.from(this.filters.entries()).map(([key, value]) => `${key}(${value}${this.getUnit(key)})`).join(' ');}// 获取滤镜单位private getUnit(filter: string): string {switch (filter) {case 'blur':return 'px';case 'hue-rotate':return 'deg';default:return '%';}}
}// 使用示例
const video = document.getElementById('video') as HTMLVideoElement;
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
const player = new VideoPlayer(video, canvas);// 加载并播放视频
async function playVideo(url: string) {try {await player.loadVideo(url);await player.play();// 应用滤镜效果const filter = new VideoFilter();filter.setBrightness(110);filter.setContrast(120);filter.setSaturation(130);player.applyFilter(filter);// 截取当前帧setTimeout(() => {const frameData = player.captureFrame();const img = new Image();img.src = frameData;document.body.appendChild(img);}, 3000);// 导出视频片段setTimeout(async () => {const clip = await player.exportClip(5, 10);const url = URL.createObjectURL(clip);const a = document.createElement('a');a.href = url;a.download = 'clip.webm';a.click();}, 5000);} catch (error) {console.error('Failed to play video:', error);}
}
流媒体处理
WebRTC实现
// WebRTC连接管理器
class WebRTCManager {private peerConnection: RTCPeerConnection;private localStream: MediaStream | null;private remoteStream: MediaStream | null;private dataChannel: RTCDataChannel | null;constructor() {this.peerConnection = new RTCPeerConnection({iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]});this.localStream = null;this.remoteStream = null;this.dataChannel = null;this.initializeConnection();}// 初始化连接private initializeConnection(): void {// 监听ICE候选this.peerConnection.onicecandidate = (event) => {if (event.candidate) {// 发送ICE候选到远端this.sendSignalingMessage({type: 'candidate',candidate: event.candidate});}};// 监听远端流this.peerConnection.ontrack = (event) => {this.remoteStream = event.streams[0];// 触发远端流更新事件this.onRemoteStreamUpdate(this.remoteStream);};// 创建数据通道this.dataChannel = this.peerConnection.createDataChannel('messageChannel');// 监听数据通道事件this.dataChannel.onmessage = (event) => {this.onDataChannelMessage(event.data);};}// 获取本地媒体流async getLocalStream(constraints: MediaStreamConstraints): Promise<MediaStream> {try {this.localStream = await navigator.mediaDevices.getUserMedia(constraints);// 添加本地流到连接this.localStream.getTracks().forEach(track => {if (this.localStream) {this.peerConnection.addTrack(track, this.localStream);}});return this.localStream;} catch (error) {throw new Error('Failed to get local stream');}}// 创建连接请求async createOffer(): Promise<RTCSessionDescriptionInit> {try {const offer = await this.peerConnection.createOffer();await this.peerConnection.setLocalDescription(offer);return offer;} catch (error) {throw new Error('Failed to create offer');}}// 创建连接应答async createAnswer(): Promise<RTCSessionDescriptionInit> {try {const answer = await this.peerConnection.createAnswer();await this.peerConnection.setLocalDescription(answer);return answer;} catch (error) {throw new Error('Failed to create answer');}}// 处理远端描述async handleRemoteDescription(description: RTCSessionDescriptionInit): Promise<void> {try {await this.peerConnection.setRemoteDescription(new RTCSessionDescription(description));if (description.type === 'offer') {const answer = await this.createAnswer();// 发送应答到远端this.sendSignalingMessage({type: 'answer',answer});}} catch (error) {throw new Error('Failed to handle remote description');}}// 处理ICE候选async handleCandidate(candidate: RTCIceCandidate): Promise<void> {try {await this.peerConnection.addIceCandidate(candidate);} catch (error) {throw new Error('Failed to handle ICE candidate');}}// 发送消息sendMessage(message: string): void {if (this.dataChannel && this.dataChannel.readyState === 'open') {this.dataChannel.send(message);}}// 关闭连接close(): void {if (this.localStream) {this.localStream.getTracks().forEach(track => track.stop());}if (this.dataChannel) {this.dataChannel.close();}this.peerConnection.close();}// 发送信令消息(需要实现)private sendSignalingMessage(message: any): void {// 通过信令服务器发送消息}// 远端流更新回调(需要实现)private onRemoteStreamUpdate(stream: MediaStream): void {// 处理远端流更新}// 数据通道消息回调(需要实现)private onDataChannelMessage(message: string): void {// 处理数据通道消息}
}// 使用示例
const rtcManager = new WebRTCManager();// 开始视频通话
async function startVideoCall() {try {// 获取本地媒体流const localStream = await rtcManager.getLocalStream({video: true,audio: true});// 显示本地视频const localVideo = document.getElementById('localVideo') as HTMLVideoElement;localVideo.srcObject = localStream;// 创建连接请求const offer = await rtcManager.createOffer();// 发送offer到远端(通过信令服务器)// ...} catch (error) {console.error('Video call failed:', error);}
}// 处理远端消息
function handleRemoteMessage(message: any) {switch (message.type) {case 'offer':rtcManager.handleRemoteDescription(message.offer);break;case 'answer':rtcManager.handleRemoteDescription(message.answer);break;case 'candidate':rtcManager.handleCandidate(message.candidate);break;}
}
最佳实践与建议
-
性能优化
- 使用适当的编解码格式
- 实现预加载和缓冲
- 优化渲染性能
- 控制内存使用
-
用户体验
- 流畅的播放体验
- 合适的缓冲策略
- 清晰的错误提示
- 友好的控制界面
-
兼容性处理
- 支持多种格式
- 优雅降级方案
- 跨浏览器兼容
- 移动端适配
-
安全性考虑
- 内容加密
- 权限控制
- 防盗链措施
- 数据保护
总结
前端音视频处理需要考虑以下方面:
- 选择合适的技术方案
- 优化播放性能
- 提供良好体验
- 保证安全可靠
- 处理兼容性
通过合理的技术选型和优化措施,可以构建出高质量的音视频应用。
学习资源
- Web Audio API文档
- Media Source Extensions指南
- WebRTC开发教程
- 音视频编解码知识
- 流媒体协议规范
如果你觉得这篇文章有帮助,欢迎点赞收藏,也期待在评论区看到你的想法和建议!👇
终身学习,共同成长。
咱们下一期见
💻
相关文章:
工程化与框架系列(27)--前端音视频处理
前端音视频处理 🎥 引言 前端音视频处理是现代Web应用中的重要组成部分,涉及音频播放、视频处理、流媒体传输等多个方面。本文将深入探讨前端音视频处理的关键技术和最佳实践,帮助开发者构建高质量的多媒体应用。 音视频技术概述 前端音视…...
芋道打包时报错:缺失@unocss插件
在遇到打包时,报这个错误,提示构建失败是因为 ESLint 在加载 unocss 插件时,找不到 unocss/eslint-plugin 模块 解决办法:安装缺失的依赖:保证unocss/eslint-plugin已经被正确安装, 使用以下命令安装&…...
PY32MD320单片机 QFN32封装,内置多功能三相 NN 型预驱。
PY32MD320单片机是普冉半导体的一款电机专用MCU,芯片采用了高性能的 32 位 ARM Cortex-M0 内核,主要用于电机控制。PY32MD320嵌入高达 64 KB Flash 和 8 KB SRAM 存储器,最高工作频率 48 MHz。PY32MD320单片机的工作温度范围为 -40 ~ 105 ℃&…...
深入解析 configService.addListener 使用中的注意事项
在使用 Nacos 的 configService.addListener 方法进行配置监听时,为了确保程序的稳定性、可靠性以及高效性,有诸多注意事项需要我们关注。下面将对这些关键要点进行详细阐述。 一、连接稳定性 1.1 网络连接问题 Nacos 客户端与服务端通过网络进行通信&…...
Windows控制台函数:控制台读取输入函数ReadConsoleA()
目录 什么是 ReadConsoleA? 它长什么样? 怎么用它? 它跟 std::cin 有什么不一样? 注意事项 什么是 ReadConsoleA? ReadConsoleA 是一个 Windows API 函数,用来从控制台读取用户输入。想象一下&#…...
奇安信 2025 年护网蓝队初选笔试题(附答案解析)
🔥 爆款 CSDN 题库 | 超全护网蓝队笔试真题 | 含详细答案解析 🔥 熬夜为大家整理了 奇安信 2025 年护网蓝队初选笔试题,(关注我我会持续更新)涵盖 SQL 注入、Web 安全、渗透测试、二进制安全 等核心知识点,…...
国产编辑器EverEdit - Web预览设置
1 设置-高级-Web预览 1.1 设置说明 选择主菜单工具 -> 设置 -> 常规,在弹出的选项窗口中选择Web预览分类,如下图所示: 1.1.1 本地浏览HTML文件 如果用户只是在本地浏览HTML文件,即直接用浏览器打开HTML文件,确…...
P8686 [蓝桥杯 2019 省 A] 修改数组--并查集 or Set--lower_bound()的解法!!!
P8686 [蓝桥杯 2019 省 A] 修改数组--并查集 题目 并查集解析代码【并查集解】 Set 解法解析lower_bound代码 题目 并查集解析 首先先让所有的f(i)i,即每个人最开始的祖先都是自己,然后就每一次都让轮到那个数的父亲1(…...
HTML 编辑器推荐与 VS Code 使用教程
在进行 HTML 编程时,选择一款合适的 HTML 编辑器能极大地提高开发效率。以下为大家推荐几款常用且功能强大的 HTML 编辑器,同时详细介绍如何使用 VS Code 创建和预览 HTML 文件。 一、HTML 编辑器推荐 VS Code:由微软开发,是一款…...
MyBatis增删改查:静态与动态SQL语句拼接及SQL注入问题解析
MyBatis 是一个优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的工作。本文将深入探讨 MyBatis 中的增删改查操作,重点讲解静态与动态 SQL 语句的拼接,并分析 S…...
在运维工作中,Lvs、nginx、haproxy工作原理分别是什么?
在运维工作中,LVS、NGINX和HAProxy都是常用的负载均衡和反向代理工具,它们在高可用性和负载均衡场景中发挥重要作用。以下是其原理和应用场景详解: LVS(Linux Virtual Server) 工作原理 LVS是基于Linux内核的负载均…...
linux学习(五)(服务器审查,正常运行时间负载,身份验证日志,正在运行的服务,评估可用内存)
服务器审查 在 Linux 中审查服务器的过程包括评估服务器的性能、安全性和配置,以确定需要改进的领域或任何潜在问题。审查的范围可以包括检查安全增强功能、检查日志文件、审查用户帐户、分析服务器的网络配置以及检查其软件版本。 Linux 以其稳定性和安全性而闻名…...
Java在小米SU7 Ultra汽车中的技术赋能
目录 一、智能驾驶“大脑”与实时数据 场景一:海量数据的分布式计算 场景二:实时决策的毫秒级响应 场景三:弹性扩展与容错机制 技术隐喻: 二、车载信息系统(IVI)的交互 场景一:Android Automo…...
开发环境搭建-02.后端环境搭建-熟悉项目结构
一.后端环境搭建...
js实现pdf文件路径预览和下载
预览 直接浏览器窗口打开默认就是预览 window.open(文件路径)下载 function downloadPDF(url, filename) {fetch(url).then(response > response.blob()).then(blob > {const link document.createElement(a);link.href URL.createObjectURL(blob);link.download fi…...
【RAG】基于向量检索的 RAG (BGE示例)
RAG机器人 结构体 文本向量化: 使用 BGE 模型将文档和查询编码为向量。 (BGE 是专为检索任务优化的开源 Embedding 模型,除了本文API调用,也可以通过Hugging Face 本地部署BGE 开源模型) 向量检索: 从数据库中找到与查询相关的文…...
Vue源码解析之mustache模板引擎
个人简介 👀个人主页: 前端杂货铺 🙋♂️学习方向: 主攻前端方向,正逐渐往全干发展 📃个人状态: 研发工程师,现效力于中国工业软件事业 🚀人生格言: 积跬步…...
python: DDD using postgeSQL and SQL Server
postgreSQL 注意: # psycopg 2 驱动的连接字符串 #engine create_engine(postgresql://post:geovindulocalhost:5433/TechnologyGame) #Session sessionmaker(bindengine)# 使用 psycopg3 驱动的连接字符串 #engine create_engine(postgresqlpsycopg://user:g…...
Python实例:PyMuPDF实现PDF翻译,英文翻译为中文,并按段落创建中文PDF
基于PyMuPDF与百度翻译的PDF翻译处理系统开发:中文乱码解决方案与自动化排版实践 一 、功能预览:将英文翻译为中文后创建的PDF 二、完整代码 from reportlab.lib.pagesizes import letter from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle...
IntelliJ IDEA 2021版创建springboot项目的五种方式
第一种方式,通过https://start.spring.io作为spring Initializr的url来创建项目。 第二种方式,通过https://start.spring.io官网来直接创建springboot项目压缩包,然后导入至我们的idea中。 点击generate后,即可生成压缩包…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...
【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
均衡后的SNRSINR
本文主要摘自参考文献中的前两篇,相关文献中经常会出现MIMO检测后的SINR不过一直没有找到相关数学推到过程,其中文献[1]中给出了相关原理在此仅做记录。 1. 系统模型 复信道模型 n t n_t nt 根发送天线, n r n_r nr 根接收天线的 MIMO 系…...
