Web对讲/广播功能

Web对讲/广播功能

介绍

轻量级的 Web 对讲/广播网关系统 。利用 Netty 的高性能网络处理能力,实现了从 Web 前端采集音频,经过服务端转码与协议封装,最终以标准 GB28181/RTP 协议推送到前端设备(如摄像机、NVR 或国标平台)的核心功能。

1. 核心功能模块

1.1. Web 接入与信令层 (Web Access Layer)
  • 核心类 : NettyServer.java , WebSocketHandler.java
  • 功能 :
    • WebSocket 服务 :监听 WebSocket 连接(路径 /ws_pcm ),维持与 Web 前端的长连接。
    • 信令交互 :解析连接 URL 中的 deviceId ,并在建立连接时调用外部接口( wvp-gb28181 )通知设备准备接收语音流。
    • 音频接收 :接收前端发送的 Base64 编码的 PCM 音频数据。
1.1. 音频编解码层 (Audio Codec Layer)
  • 核心类 : G711Codec.java , AudioCodec.java
  • 功能 :
    • 格式转换 :实现了 PCM (16-bit 8000Hz) 与 G.711 A-law (8-bit 8000Hz) 之间的双向转换算法。这是安防领域最通用的音频编码标准。
    • 海思头处理 :具备识别和剔除海思私有音频头(Hisilicon Header)的能力,增强了对特定硬件的兼容性。
1.3. 媒体流传输层 (Media Transport Layer)
  • 核心类 : BroadcastServer.java , RtpPack.java
  • 功能 :
    • RTP 封装 :将 G.711A 音频数据封装为 RTP (Real-time Transport Protocol) 包。 优化后的逻辑 现在能正确处理 RTP 头、序列号(Sequence Number)和时间戳(Timestamp),符合标准 RFC 3550。
    • TCP 流式发送 :支持 RTP over TCP 模式(RFC 4571),每个广播会话启动一个独立的 TCP Server 进行推流。
    • 动态端口 :为每个会话动态分配监听端口,支持多路并发广播。
1.4. 数据缓冲机制 (Buffering Strategy)
  • 实现方式 :文件系统缓冲
  • 流程 : WebSocketHandler 将转码后的音频写入磁盘文件 -> BroadcastServer 轮询读取文件发送。
  • 特点 :利用磁盘文件作为简易的“消息队列”,解耦了 Web 接收线程和 TCP 发送线程。

2. 能力与数据流向

2.1. 核心能力
  1. Web 实时对讲 :用户无需安装插件,通过浏览器即可向监控设备喊话。
  2. 标准协议对接 :输出标准的 RTP 流,可对接海康、大华等主流安防设备及 GB28181 国标平台。
  3. 高并发基础 :基于 Netty NIO 框架,具备处理高并发连接的潜力(注:目前的磁盘 IO 缓冲方式可能是瓶颈,建议未来优化为内存队列)
2. 数据处理全流程

采集 PCM (Blob)

BinaryWebSocketFrame

转码 (PCM -> G.711A)

轮询读取

RTP 封装 (RtpPack)

Web 用户

Netty Server

WebSocketHandler

磁盘文件缓冲

BroadcastServer

TCP Stream

摄像机/国标平台

3. 前端实现 (web/):采集与二进制发送

前端核心逻辑位于 teach.realtime.encode_transfer_frame_pcm.js 中,主要负责音频的采集、切片和 直接二进制传输 。

  • 音频采集与切片 :
    • 使用 Recorder.js 采集 8000Hz 16bit PCM 音频。
    • RealTimeSendTry 函数将音频流切分为固定大小的帧( SendFrameSize ,例如 3200 字节),确保发送频率稳定(约 100ms/帧)。
  • 二进制发送 (优化点) :
    • 传输层 : TransferUpload 函数中移除了 FileReader 转 Base64 的逻辑。
    • WebSocket :直接调用 ws.send(blob) 发送 Blob 对象。浏览器底层会自动将其作为二进制帧(Binary Frame)处理, 带宽节省约 33% 。
    • 配置 :显式设置 ws.binaryType = ‘arraybuffer’ 以便正确接收服务端回显(如有)。
以下是前端核心实现(音频采集、切片与二进制发送)的关键代码片段,位于 teach.realtime.encode_transfer_frame_pcm.js 中:
音频参数配置与切片

这段代码定义了采样率(与后端对齐为 8000Hz)、位深(16bit)和发送帧大小(3200字节,约 200ms),是保证音频流畅播放的基础。

var testSampleRate =8000;// 采样率必须与后端 G711Codec 一致var testBitRate =16;// 位深 16位// 每次发送指定二进制数据长度的数据帧,单位字节。// 16位 8000hz 的 pcm 1秒有:8000hz * 16位 / 8比特 = 16000 字节的数据// 配置 3200 字节则每秒发送大约 5 次 (200ms/帧)var SendFrameSize =3200;
实时切片与转码 (RealTimeSendTry)

RealTimeSendTry 函数负责将采集到的 PCM 数据流缓存起来,凑够一帧( SendFrameSize )后才进行处理。虽然源数据本身就是 PCM,但为了统一流程,这里使用了 Recorder.mock 将数据封装为 Blob 对象。

// ... 从缓冲中切出一帧数据 ...// 满一帧了,清除已消费掉的缓冲if(pcmLen==chunkSize){ pcmOK=true;// ...}// ...// 16位pcm格式可以不经过mock转码,直接发送new Blob([pcm.buffer],{type:"audio/pcm"}) // 但为了通用性,这里使用 Recorder mock 接口统一封装为 Blobvar recMock=Recorder({type:"pcm",sampleRate:testSampleRate,// 8000bitRate:testBitRate // 16}); recMock.mock(pcm,pcmSampleRate); recMock.stop(function(blob,duration){// 转码/封装好就推入传输函数TransferUpload(number,blob,duration,recMock,false);// 递归调用,继续处理剩余数据RealTimeSendTry([],0, isClose);});
二进制发送逻辑 (TransferUpload)
var ws;//=====数据传输函数==========varTransferUpload=function(number, blobOrNull, duration, blobRec, isClose){if(blobOrNull){var blob = blobOrNull;//*********发送方式二:Blob二进制发送 (推荐) ***************if(!ws){// 连接 WebSocket,带上设备ID ws =newWebSocket('ws://127.0.0.1:7211/ws_pcm?deviceId=填入设备id'); ws.binaryType ='arraybuffer';// 显式设置接收类型为二进制 ws.onopen=evt=>{ console.log("ws talk open (Binary Mode)");};// ... 错误处理与关闭逻辑 ...}if(ws && ws.readyState === WebSocket.OPEN){// 直接发送 Blob 对象,浏览器会自动处理为二进制帧 (Binary Frame)// 相比 Base64 节省约 33% 带宽 ws.send(blob); console.log("Sent binary blob, size: "+ blob.size);}}// ...};

1. 音频参数配置与切片

这段代码定义了采样率(与后端对齐为 8000Hz)、位深(16bit)和发送帧大小(3200字节,约 200ms),是保证音频流畅播放的基础。

4. 后端接入 (src/):二进制帧处理与转码

后端核心逻辑位于 WebSocketHandler.java ,负责接收二进制流并进行转码存储。

  • Netty 处理器升级 :
    • 类定义改为 SimpleChannelInboundHandler ,同时支持 FullHttpRequest (握手)和 WebSocketFrame (数据)。
    • handleWebSocketFrame 方法增加了对 BinaryWebSocketFrame 的支持。
    • 零拷贝读取 :直接从 Netty 的 ByteBuf 中读取 PCM 字节流,避免了 Base64 解码的 CPU 消耗和内存分配。
  • 音频转码 :
    • 调用优化后的 G711Codec.java 的 encodeToG711A 方法。
    • 将 16bit PCM (128kbps) 压缩为 8bit G.711A (64kbps),符合安防标准。
  • 文件缓冲 :
    • 将转码后的 G.711 数据写入本地磁盘({项目根目录}/recorders/{日期}/{端口}/),作为发送缓冲。
二进制帧处理 (Netty Inbound)

兼容 Text 帧(Base64)和 Binary 帧(Raw PCM),并实现 零拷贝 读取二进制数据。

// 支持处理多种类型的 WebSocket 帧publicclassWebSocketHandlerextendsSimpleChannelInboundHandler<Object>{privatevoidhandleWebSocketFrame(ChannelHandlerContext ctx,WebSocketFrame frame)throwsIOException{byte[] pcmData =null;if(frame instanceofTextWebSocketFrame){// 兼容:Base64 文本模式String text =((TextWebSocketFrame) frame).text(); pcmData =Base64Decoder.decode(text);}elseif(frame instanceofBinaryWebSocketFrame){ :二进制模式 (性能更优)// 直接从 Netty ByteBuf 读取数据,避免 Base64 解码开销ByteBuf content = frame.content(); pcmData =newbyte[content.readableBytes()]; content.readBytes(pcmData);}if(pcmData !=null&& pcmData.length >0){processPcmData(ctx, pcmData);}}// ...}
音频转码与缓冲 (Transcode & Buffer)

收到 PCM 数据后,立即进行 G.711A 转码,并将结果写入磁盘文件作为缓冲。这是连接 Web 前端与 RTP 发送端的关键桥梁。

privatevoidprocessPcmData(ChannelHandlerContext ctx,byte[] pcmData)throwsIOException{// 转码 PCM -> G.711Abyte[] g711a =G711Codec.encodeToG711A(pcmData);String dirStr =getRecorderDir(server.getPort());FileUtil.mkdir(dirStr);// 使用时间戳作为文件名,注意:高并发下 System.currentTimeMillis() 可能重复,建议加随机数或原子计数器// 但这里是单连接单线程处理(Netty EventLoop),只要处理速度够快通常没问题,或者用 System.nanoTime()String fileName =System.currentTimeMillis()+".pcm";try(FileOutputStream out =newFileOutputStream(dirStr + fileName,false)){ out.write(g711a);}// 回显逻辑 (可选)/* String deviceId = ctx.channel().attr(key).get(); if (deviceId != null) { List<Channel> channelList = getChannelByName(deviceId); // 注意:回显通常不需要发回给自己,或者是为了调试 // 如果要发回二进制,需要用 BinaryWebSocketFrame // channelList.forEach(channel -> channel.writeAndFlush(new BinaryWebSocketFrame(Unpooled.copiedBuffer(pcmData)))); } */}
连接握手与信令触发 (Handshake & Signaling)

在建立 WebSocket 连接时,解析 URL 参数并触发外部 SIP 信令(通知摄像机/平台准备接收)。

privatevoidhandleHttpRequest(ChannelHandlerContext ctx,FullHttpRequest request){// 启动对应的 RTP 推流服务 server =newBroadcastServer(); server.start();// 解析 deviceIdMap<String,String> paramMap =getUrlParams(request.uri());String deviceId = paramMap.get("deviceId");if(deviceId !=null){online(deviceId, ctx.channel());// 调用外部 WVP 接口,通过 SIP 信令通知设备连接 BroadcastServer 的端口try{HttpRequest.get("https://127.0.0.1:8843/api/play/broadcast/"+ deviceId).execute(true);}catch(Exception e){System.err.println("调用 WVP 接口失败: "+ e.getMessage());}}}

4. 推流服务 (src/):RTP 封装与 TCP 推送

这部分由 BroadcastServer.java 和 RtpPack.java 负责,确保输出符合 GB28181 标准的流。

  • RTP 封装 (已修复) :
    • RtpPack 类负责将 G.711 音频流封装为 RTP 包。
    • 头部修正 :现在能正确生成 12字节 RTP 头 + 2字节 TCP 长度头 。
    • 时间戳同步 :根据数据长度动态计算 RTP 时间戳,确保播放连续、不卡顿、不变调。
  • TCP 推流 :
    • BroadcastServer 启动一个独立的 TCP Server(动态端口)。
    • 智能轮询 (优化点) :循环读取缓冲目录下的新文件。如果暂无新文件,线程会短暂休眠( Thread.sleep ),防止 CPU 空转。
    • 读取到的数据经过 RTP 封装后,直接通过 Socket 发送给连接的摄像机或平台。

5. 总结

该工程是一个 轻量级的 Web 对讲/广播网关系统。它利用 Netty 的高性能网络处理能力,实现了从 Web 前端采集音频,经过服务端转码与协议封装,最终以标准 GB28181/RTP 协议推送到前端设备(如摄像机、NVR 或国标平台)的核心功能。

  • 前端 : PCM -> Blob -> WebSocket (Binary)
  • 后端 : BinaryFrame -> PCM -> G.711A -> File -> RTP -> TCP

代码示例 - github地址

Read more

【MySQL】三大范式

【MySQL】三大范式

下面我们来聊聊表的设计,如何设计一张比较合理,冗余性低且IO次数比较少,效率高的表。 我们需要先认识一下范式 什么是范式? 范式是⼀组规则。在设计关系数据库时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式。 范式有哪些? 关系数据库有六种范式:第⼀范式(1NF)、第⼆范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,⼜称完美范式),越高的范式数据库冗余越小。然而,普遍认为范式越高虽然对数据关系有更好的约束性,但也可能导致数据库IO更繁忙,因此在实际应用中,数据库设计通常只需满足第三范式即可,如果在想提高效率,再去增加某个字段的冗余性 为啥越高的范式数据库冗余越小,IO效率越忙呢?继续看 第一范式 第一范式即:数据库表的每⼀列都是不可分割的原子数据项,而不能是集合,数组,对象等非原子数据 在关系型数据库的设计中,满足第⼀范式是对关系模式的基本要求。

By Ne0inhk
Flutter 组件 conventional 适配鸿蒙 HarmonyOS 实战:约定式提交标准,构建自动化版本治理与 CI/CD 质量治理架构

Flutter 组件 conventional 适配鸿蒙 HarmonyOS 实战:约定式提交标准,构建自动化版本治理与 CI/CD 质量治理架构

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 conventional 适配鸿蒙 HarmonyOS 实战:约定式提交标准,构建自动化版本治理与 CI/CD 质量治理架构 前言 在鸿蒙(OpenHarmony)生态迈向大规模研发协同、涉及数十个跨职能团队共同维护大型 HAP/HSP 项目的背景下,如何确保每一行代码的变更都“有迹可循”、在端侧实现自动化的版本语义化(Semantic Versioning)管理,已成为衡量工程化成熟度的“地基”。在鸿蒙设备这类强调分布式协同与持续集成(CI)交付的环境下,如果代码提交记录(Commit Messages)依然采用随意的口语化描述,由于由于缺乏机器可读性,极易由于由于无法自动生成变更日志(Changelog)导致跨版本维护时的回溯成本激增。 我们需要一种能够强制执行规范检查、支持 RFC 标准且具备解析语义结构的提交治理框架。 conventional 为 Flutter

By Ne0inhk
Flutter for OpenHarmony: Flutter 三方库 redux_thunk 解决鸿蒙应用状态管理中的复杂异步副作用(异步架构神器)

Flutter for OpenHarmony: Flutter 三方库 redux_thunk 解决鸿蒙应用状态管理中的复杂异步副作用(异步架构神器)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在 OpenHarmony 应用架构设计中,状态管理(State Management)是业务的核心。如果你选择了经典的 Redux 模式,你会发现它天生是“同步”的:Action 发出,Reducer 改变 State。但在真实项目中,我们需要处理网络请求、数据库读写、文件 IO 等延时操作。如何在纯净的 Redux 链条中插入这些破坏性的“副作用”? redux_thunk 提供了一个简单而精妙的方案。它通过扩展 Redux 的中间件机制,允许你 Dispatch(派发)一个 函数 而不仅仅是对象。这为鸿蒙应用处理复杂的业务流提供了极大灵活性。 一、异步 Action

By Ne0inhk
Flutter 组件 riverpod_signals 的适配 鸿蒙Harmony 实战 - 驾驭双剑合璧状态架构、实现鸿蒙端强依赖注入与细粒度刷新深度融合方案

Flutter 组件 riverpod_signals 的适配 鸿蒙Harmony 实战 - 驾驭双剑合璧状态架构、实现鸿蒙端强依赖注入与细粒度刷新深度融合方案

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 组件 riverpod_signals 的适配 鸿蒙Harmony 实战 - 驾驭双剑合璧状态架构、实现鸿蒙端强依赖注入与细粒度刷新深度融合方案 前言 在鸿蒙(OpenHarmony)生态的极繁数字化政务底座、大型分布式供应链管理系统以及对架构严密性与交互流畅度有“双重严苛审计要求”的各类企业级应用开发中,“架构的解耦深度与 UI 的响应广度”是衡量软件成熟度的两把关键标尺。面对包含上百个全局服务(Service)与数千个高频局部刷新节点(Widget)的复杂资产体系。如果全量使用 Riverpod 的 Consumer 监听,可能会在大型列表中产生不必要的树扫描开销;而如果仅使用 Signals,又会因为缺乏完善的依赖注入(DI)机制。导致业务逻辑流的组织变得松散且难以维护。 我们需要一种“顶级架构对齐、局部响应闭环”的融合艺术。 riverpod_signals 是一套专注于将

By Ne0inhk