当前位置: 首页 > news >正文

在 Flutter 多人视频通话中实现虚拟背景、美颜与空间音效

前言

在之前的「基于声网 Flutter SDK 实现多人视频通话」里,我们通过 Flutter + 声网 SDK 完美实现了跨平台和多人视频通话的效果,那么本篇我们将在之前例子的基础上进阶介绍一些常用的特效功能,包括虚拟背景、色彩增强、空间音频、基础变声功能。

本篇主要带你了解 SDK 里几个实用的 API 实现,相对简单。

01 虚拟背景

虚拟背景是视频会议里最常见的特效之一,在声网 SDK 里可以通过enableVirtualBackground方法启动虚拟背景支持(点击这里查看虚拟背景接口文档)。

首先,因为我们是在 Flutter 里使用,所以我们可以在 Flutter 里放一张assets/bg.jpg图片作为背景,这里有两个需要注意的点:

  • assets/bg.jpg图片需要在pubspec.yaml文件下的assets添加引用
  assets:- assets/bg.jpg
  • 需要在pubspec.yaml文件下添加path_provider: ^2.0.8path: ^1.8.2依赖,因为我们需要把图片保存在 App 本地路径下

如下代码所示,首先我们通过 Flutter 内的rootBundle读取到bg.jpg,然后将其转化为bytes, 之后调用getApplicationDocumentsDirectory获取路径,保存在的应用的/data"目录下,然后就可以把图片路径配置给enableVirtualBackground方法的source,从而加载虚拟背景。

Future<void> _enableVirtualBackground() async {ByteData data = await rootBundle.load("assets/bg.jpg");List<int> bytes =data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);Directory appDocDir = await getApplicationDocumentsDirectory();String p = path.join(appDocDir.path, 'bg.jpg');final file = File(p);if (!(await file.exists())) {await file.create();await file.writeAsBytes(bytes);}await _engine.enableVirtualBackground(enabled: true,backgroundSource: VirtualBackgroundSource(backgroundSourceType: BackgroundSourceType.backgroundImg,source: p),segproperty:const SegmentationProperty(modelType: SegModelType.segModelAi));setState(() {});
}

如下图所示是都开启虚拟背景图片之后的运行效果,当然,这里还有两个需要注意的参数:

  • BackgroundSourceType :可以配置backgroundColor(虚拟背景颜色)、backgroundImg(虚拟背景图片)、backgroundBlur (虚拟背景模糊) 这三种情况,基本可以覆盖视频会议里的所有场景
  • SegModelType :可以配置为segModelAi(智能算法)或segModelGreen(绿幕算法)两种不同场景下的抠图算法。

这里需要注意的是,在官方的提示里,建议只在搭载如下芯片的设备上使用该功能(应该是对于 GPU 有要求):

  • 骁龙 700 系列 750G 及以上
  • 骁龙 800 系列 835 及以上
  • 天玑 700 系列 720 及以上
  • 麒麟 800 系列 810 及以上
  • 麒麟 900 系列 980 及以上

另外需要注意的是,为了将自定义背景图的分辨率与 SDK 的视频采集分辨率适配,声网 SDK 会在保证自定义背景图不变形的前提下,对自定义背景图进行缩放和裁剪。

02 美颜

美颜作为视频会议里另外一个最常用的功能,声网也提供了setBeautyEffectOptions方法支持一些基础美颜效果调整。(点击查看美颜接口文档)

如下代码所示,setBeautyEffectOptions方法里主要是通过BeautyOptions来调整画面的美颜风格,参数的具体作用如下表格所示。

这里的 .5 只是做了一个 Demo 效果,具体可以根据你的产品需求,配置出几种固定模版让用户选择。

_engine.setBeautyEffectOptions(enabled: true,options: const BeautyOptions(lighteningContrastLevel:LighteningContrastLevel.lighteningContrastHigh,lighteningLevel: .5,smoothnessLevel: .5,rednessLevel: .5,sharpnessLevel: .5,),
);

运行后效果如下图所示,开了 0.5 参数后的美颜整体画面更加白皙,同时唇色也更加明显。

没开美颜开了美颜

03 色彩增强

接下来要介绍的一个 API 是色彩增强:setColorEnhanceOptions,如果是美颜还无法满足你的需求,那么色彩增强 API 可以提供更多参数来调整你的需要的画面风格。(点击查看色彩增强接口文档)

如下代码所示,色彩增强 API 很简单,主要是调整ColorEnhanceOptionsstrengthLevel和skinProtectLevel参数,也就是调整色彩强度和肤色保护的效果。

  _engine.setColorEnhanceOptions(enabled: true,options: const ColorEnhanceOptions(strengthLevel: 6.0, skinProtectLevel: 0.7));

如下图所示,因为摄像头采集到的视频画面可能存在色彩失真的情况,而色彩增强功能可以通过智能调节饱和度和对比度等视频特性,提升视频色彩丰富度和色彩还原度,最终使视频画面更生动。

开启增强之后画面更抢眼了。

没开增强开了美颜+增强

04 空间音效

其实声音调教才是重头戏,声网既然叫声网,在音频处理上肯定不能落后,在声网 SDK 里就可以通过enableSpatialAudio打开空间音效的效果。(点击查看空间音效接口文档)

_engine.enableSpatialAudio(true);

什么是空间音效?简单说就是特殊的 3D 音效,它可以将音源虚拟成从三维空间特定位置发出,包括听者水平面的前后左右,以及垂直方向的上方或下方。

本质上空间音效就是通过一些声学相关算法计算,模拟实现类似空间 3D 效果的音效实现。

同时你还可以通过setRemoteUserSpatialAudioParams来配置空间音效的相关参数,如下表格所示,可以看到声网提供了非常丰富的参数来让我们可以自主调整空间音效,例如这里面的enable_blurenable_air_absorb效果就很有意思,十分推荐大家去试试。

音频类的效果这里就无法展示了,强烈推荐大家自己动手去试试。

05 人声音效

另外一个推荐的 API 就是人声音效:setAudioEffectPreset, 调用该方法可以通过 SDK 预设的人声音效(点击查看人声音效接口文档),在不会改变原声的性别特征的前提下,修改用户的人声效果,例如:

_engine.setAudioEffectPreset(AudioEffectPreset.roomAcousticsKtv);

声网 SDK 里预设了非常丰富的AudioEffectPreset,如下表格所示,从场景效果如 KTV、录音棚,到男女变声,再到恶搞的音效猪八戒等,可以说是相当惊艳。

PS:为获取更好的人声效果,需要在调用该方法前将setAudioProfile的 scenario 设为audioScenarioGameStreaming(3):

_engine.setAudioProfile(profile: AudioProfileType.audioProfileDefault,scenario: AudioScenarioType.audioScenarioGameStreaming);

当然,这里需要注意的是,这个方法只推荐用在对人声的处理上,不建议用于处理含音乐的音频数据。

最后,完整代码如下所示:

class VideoChatPage extends StatefulWidget {const VideoChatPage({Key? key}) : super(key: key);@overrideState<VideoChatPage> createState() => _VideoChatPageState();
}class _VideoChatPageState extends State<VideoChatPage> {late final RtcEngine _engine;///初始化状态late final Future<bool?> initStatus;///当前 controllerlate VideoViewController currentController;///是否加入聊天bool isJoined = false;/// 记录加入的用户idMap<int, VideoViewController> remoteControllers = {};@overridevoid initState() {super.initState();initStatus = _requestPermissionIfNeed().then((value) async {await _initEngine();///构建当前用户 currentControllercurrentController = VideoViewController(rtcEngine: _engine,canvas: const VideoCanvas(uid: 0),);return true;}).whenComplete(() => setState(() {}));}Future<void> _requestPermissionIfNeed() async {if (Platform.isMacOS) {return;}await [Permission.microphone, Permission.camera].request();}Future<void> _initEngine() async {//创建 RtcEngine_engine = createAgoraRtcEngine();// 初始化 RtcEngineawait _engine.initialize(const RtcEngineContext(appId: appId,));_engine.registerEventHandler(RtcEngineEventHandler(// 遇到错误onError: (ErrorCodeType err, String msg) {if (kDebugMode) {print('[onError] err: $err, msg: $msg');}},onJoinChannelSuccess: (RtcConnection connection, int elapsed) {// 加入频道成功setState(() {isJoined = true;});},onUserJoined: (RtcConnection connection, int rUid, int elapsed) {// 有用户加入setState(() {remoteControllers[rUid] = VideoViewController.remote(rtcEngine: _engine,canvas: VideoCanvas(uid: rUid),connection: const RtcConnection(channelId: cid),);});},onUserOffline:(RtcConnection connection, int rUid, UserOfflineReasonType reason) {// 有用户离线setState(() {remoteControllers.remove(rUid);});},onLeaveChannel: (RtcConnection connection, RtcStats stats) {// 离开频道setState(() {isJoined = false;remoteControllers.clear();});},));// 打开视频模块支持await _engine.enableVideo();// 配置视频编码器,编码视频的尺寸(像素),帧率await _engine.setVideoEncoderConfiguration(const VideoEncoderConfiguration(dimensions: VideoDimensions(width: 640, height: 360),frameRate: 15,),);await _engine.startPreview();}@overridevoid dispose() {_engine.leaveChannel();super.dispose();}@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(),body: Stack(children: [FutureBuilder<bool?>(future: initStatus,builder: (context, snap) {if (snap.data != true) {return const Center(child: Text("初始化ing",style: TextStyle(fontSize: 30),),);}return AgoraVideoView(controller: currentController,);}),Align(alignment: Alignment.topLeft,child: SingleChildScrollView(scrollDirection: Axis.horizontal,child: Row(///增加点击切换children: List.of(remoteControllers.entries.map((e) => InkWell(onTap: () {setState(() {remoteControllers[e.key] = currentController;currentController = e.value;});},child: SizedBox(width: 120,height: 120,child: AgoraVideoView(controller: e.value,),),),)),),),)],),floatingActionButton: FloatingActionButton(onPressed: () async {// 加入频道_engine.joinChannel(token: token,channelId: cid,uid: 0,options: const ChannelMediaOptions(channelProfile:ChannelProfileType.channelProfileLiveBroadcasting,clientRoleType: ClientRoleType.clientRoleBroadcaster,),);},),persistentFooterButtons: [ElevatedButton.icon(onPressed: () {_enableVirtualBackground();},icon: const Icon(Icons.accessibility_rounded),label: const Text("虚拟背景")),ElevatedButton.icon(onPressed: () {_engine.setBeautyEffectOptions(enabled: true,options: const BeautyOptions(lighteningContrastLevel:LighteningContrastLevel.lighteningContrastHigh,lighteningLevel: .5,smoothnessLevel: .5,rednessLevel: .5,sharpnessLevel: .5,),);//_engine.setRemoteUserSpatialAudioParams();},icon: const Icon(Icons.face),label: const Text("美颜")),ElevatedButton.icon(onPressed: () {_engine.setColorEnhanceOptions(enabled: true,options: const ColorEnhanceOptions(strengthLevel: 6.0, skinProtectLevel: 0.7));},icon: const Icon(Icons.color_lens),label: const Text("增强色彩")),ElevatedButton.icon(onPressed: () {_engine.enableSpatialAudio(true);},icon: const Icon(Icons.surround_sound),label: const Text("空间音效")),ElevatedButton.icon(onPressed: () {                _engine.setAudioProfile(profile: AudioProfileType.audioProfileDefault,scenario: AudioScenarioType.audioScenarioGameStreaming);_engine.setAudioEffectPreset(AudioEffectPreset.roomAcousticsKtv);},icon: const Icon(Icons.surround_sound),label: const Text("人声音效")),]);}Future<void> _enableVirtualBackground() async {ByteData data = await rootBundle.load("assets/bg.jpg");List<int> bytes =data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);Directory appDocDir = await getApplicationDocumentsDirectory();String p = path.join(appDocDir.path, 'bg.jpg');final file = File(p);if (!(await file.exists())) {await file.create();await file.writeAsBytes(bytes);}await _engine.enableVirtualBackground(enabled: true,backgroundSource: VirtualBackgroundSource(backgroundSourceType: BackgroundSourceType.backgroundImg,source: p),segproperty:const SegmentationProperty(modelType: SegModelType.segModelAi));setState(() {});}
}

06 最后

本篇的内容作为「基于声网 Flutter SDK 实现多人视频通话」的补充,相对来说内容还是比较简单,不过可以看到不管是在画面处理还是在声音处理上,声网 SDK 都提供了非常便捷的 API 实现,特别在声音处理上,因为文章限制这里只展示了简单的 API 介绍,所以强烈建议大家自己尝试下这些音频 API ,真的非常有趣。除此之外,还有许多场景与玩法,可以点击此处访问官网了解。

欢迎开发者们也尝试体验声网 SDK,实现实时音视频互动场景。现注册声网账号下载 SDK,可获得每月免费 10000 分钟使用额度。如在开发过程中遇到疑问,可在声网开发者社区与官方工程师交流。

相关文章:

在 Flutter 多人视频通话中实现虚拟背景、美颜与空间音效

前言 在之前的「基于声网 Flutter SDK 实现多人视频通话」里&#xff0c;我们通过 Flutter 声网 SDK 完美实现了跨平台和多人视频通话的效果&#xff0c;那么本篇我们将在之前例子的基础上进阶介绍一些常用的特效功能&#xff0c;包括虚拟背景、色彩增强、空间音频、基础变声…...

Ambari-web 架构

Ambari-web 使用的前端 Embar.js MVC 框架实现&#xff0c;Embar.js 是一个 TodoMVC 框架&#xff0c;涵盖了单页面应用&#xff08;single page application&#xff09;几乎所有的行为 Nodejs 是一个基于 Chrome JavaScript 运行时建立的一个平台&#xff0c;用来方便的搭建…...

对接百思买Best Buy EDI 的注意事项

在此前的文章&#xff1a;《Best Buy Drop Ship(Commerce hub) EDI业务测试常见报错及解决》中&#xff0c;我们介绍了在业务测试过程中遇到的常见报错及解决方案&#xff0c;以下在此基础上进行补充。 数据未能成功发送给Best Buy可能遇到的情况 Best Buy EDI项目传输业务报…...

2023年郑州重点建设项目名单公布,中创“算力数据中心”项目入选!

4月7日&#xff0c;郑州市人民政府网站公布2023年郑州市重点建设项目名单&#xff0c;名单共列项目680个&#xff0c;总投资1.08万亿元&#xff0c;年度计划投资2691亿元。 在创新驱动能力提升项目名单里&#xff0c;中创算力与人民网人民数据&#xff08;国家大数据灾备中心&a…...

Pytorch 容器 - 1. Module类介绍

目录 1. 基于Module构建自己的网络 2. Module的初始化变量 3. Modules中需要子类 forward() 4. Modules中其他内置函数 1. 基于Module构建自己的网络 torch.nn.Module是所有神经网络模块的基类&#xff0c;如何定义自已的网络&#xff1a; 由于 Module 是神经网络模块的基…...

百度墨卡托坐标转化笔记

一、墨卡托坐标转化 调研了python和java多种实现方式的转换&#xff0c;发现有的不符合需求&#xff0c;原因还没找到。 我是用百度地图返回的poi边界&#xff08;返回的是墨卡托坐标&#xff09; 转换的原理没有深入研究&#xff0c;直接拿来用的&#xff0c;测试可行&…...

每日学术速递4.12

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.HC 随着新的“生成代理”论文的发布&#xff0c;LLM刚刚达到了一个重要的里程碑——通过使用 LLM&#xff0c;生成代理能够在受《模拟人生》启发的交互式沙箱中模拟类人行为。代理架构扩展…...

HarmonyOS/OpenHarmony公司级技术开发团队硬件基本配置清单

有朋友公司咨询进入HarmonyOS/OpenHarmony领域&#xff0c;组建技术团队&#xff0c;硬件设备的基本配置应该是怎么样的比较合适&#xff1f;这个是进入鸿蒙开发领域相关配置的第一步&#xff0c;我们以一个基本的团队配置为例说明&#xff0c;供想进入的团队参考。 HarmonyOS/…...

新一代信息技术赋能,安科瑞搭建智慧水务体系的新思路

随着新时期治水方针的逐步落实&#xff0c;水利现代化、智能化建设已开启&#xff0c;物联网、图像识别、数字孪生等新技术的成熟&#xff0c;也为智慧水务体系的搭建提供了技术保障&#xff0c;新时代治水新思路正逐步得到落实。本文对智慧水务的总体架构与包含的建设内容进行…...

37岁测试工程师被裁,120天没找到工作,无奈...

从短期来看&#xff0c;程序员的确算是个不错的工作&#xff0c;薪水也比一般岗位高很多&#xff0c;但是从长远来看&#xff0c;程序员的中年危机会比其他岗位来的更早&#xff0c;很多程序员只有到了35岁左右&#xff0c;才能真正认清楚互联网行业&#xff0c;尤其是被裁之后…...

Java容器使用注意点

前置&#xff1a;问题 判空集合转map集合遍历集合去重集合转数组数组转集合 一&#xff1a;集合判空 《阿里巴巴 Java 开发手册》的描述如下&#xff1a; 判断所有集合内部的元素是否为空&#xff0c;使用 isEmpty() 方法&#xff0c;而不是 size()0 的方式。 我们在开发中也…...

密文题解(图论+字典树)

题目大意 有一段长度为nnn的密文&#xff0c;密文的每一位都可以用一个非负整数来描述&#xff0c;并且每一位都有一个权值aia_iai​。你可以操作任意多次&#xff0c;每次操作可以选择任意一段密文&#xff0c;花费选择的所有位上权值的异或和的代价获得这段密文每一位的异或…...

Baumer工业相机堡盟工业相机如何通过BGAPISDK里的工具函数来计算工业相机的实时帧率(C#)

Baumer工业相机堡盟工业相机如何通过BGAPISDK里函数来计算相机的实时帧率&#xff08;C#&#xff09;Baumer工业相机Baumer工业相机的帧率的技术背景Baumer工业相机的帧率计算方式在BufferEvent声明显示FrameID设计显示帧率的函数Baumer工业相机通过BGAPI SDK计算帧率的优势​B…...

数据结构与常量(Java)

目录 1.字面常量 2. 数据类型 3. 变量 3.1 变量概念 3.2 语法格式 补充&#xff1a;变量 int long short double和float char boolean byte 4.类型转换 类型提升小结 5. 字符串类型 1. int 转成 String 2. String 转成 int 1.字面常量 类似System.Out.p…...

【LeetCode】剑指 Offer 54. 二叉搜索树的第k大节点 p269 -- Java Version

题目链接&#xff1a;https://leetcode.cn/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/ 1. 题目介绍&#xff08; 54. 二叉搜索树的第k大节点&#xff09; 给定一棵二叉搜索树&#xff0c;请找出其中第 k 大的节点的值。 【测试用例】&#xff1a; 示例 1: 示例2&…...

[工具类] post请求 获取request对象, 获取request的请求体(body)参数

目录 引言: 1. 获取request对象的几种常用方式 -> 1.1 获取请求对象 通过请求上下文对象 获取信息[推荐] -> 1.2 在controller层直接获取[不推荐 侵害性太强] -> 1.3 interceptor中获取[部分业务中使用] -> 1.4 request常用api简介 2. 获取request的body的工具…...

Golang 多版本安装小工具G

​ voidint制作的Golang版本安装管理&#xff0c;非常好用。想装就装&#xff0c;想换版本就换版本 除了一些使用go install的场景可能有不兼容&#xff0c;主要是安装了工具有时候不能直接用。 GitHub - voidint/g: Golang Version Manager​​​​​​​ 使用方式很简单&a…...

day29—选择题

文章目录1.HashSet子类依靠什么方法区分重复元素&#xff08;C&#xff09;2.以下代码在编译和运行过程中会出现什么情况&#xff08;A&#xff09;3.有这么一段程序&#xff0c;执行的结果是&#xff08;C&#xff09;1.HashSet子类依靠什么方法区分重复元素&#xff08;C&…...

day8 互斥锁/读写锁的概念及使用、死锁的避免

目录 互斥锁的概念和使用 线程通信 - 互斥 互斥锁的创建和销毁 互斥锁的创建 互斥锁的销毁 互斥锁的使用 申请锁 释放锁 互斥锁的概念和使用 线程通信 - 互斥 临界资源&#xff1a; 一次只允许一个任务&#xff08;进程、线程&#xff09;访问的共享资源&#xff1b…...

2023-04-13 monetdb-str类型变长存储-分析

摘要: monetdb的列的基本抽象是BAT,但是对于列数据的存储方式, 对于固定长度和不固定长度,使用了不同的存储方式。 固定长度的数据比如int,int64之类的, 直接存储在了数据tail文件。 但是对于不固定长度比如string, 则使用另外一个独立的theap文件存储, tail文件仅保留对于…...

KubeSphere 容器平台高可用:环境搭建与可视化操作指南

Linux_k8s篇 欢迎来到Linux的世界&#xff0c;看笔记好好学多敲多打&#xff0c;每个人都是大神&#xff01; 题目&#xff1a;KubeSphere 容器平台高可用&#xff1a;环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查

在对接支付宝API的时候&#xff0c;遇到了一些问题&#xff0c;记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

汇编常见指令

汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX&#xff08;不访问内存&#xff09;XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用

1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?

Redis 的发布订阅&#xff08;Pub/Sub&#xff09;模式与专业的 MQ&#xff08;Message Queue&#xff09;如 Kafka、RabbitMQ 进行比较&#xff0c;核心的权衡点在于&#xff1a;简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

服务器--宝塔命令

一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行&#xff01; sudo su - 1. CentOS 系统&#xff1a; yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...

保姆级教程:在无网络无显卡的Windows电脑的vscode本地部署deepseek

文章目录 1 前言2 部署流程2.1 准备工作2.2 Ollama2.2.1 使用有网络的电脑下载Ollama2.2.2 安装Ollama&#xff08;有网络的电脑&#xff09;2.2.3 安装Ollama&#xff08;无网络的电脑&#xff09;2.2.4 安装验证2.2.5 修改大模型安装位置2.2.6 下载Deepseek模型 2.3 将deepse…...

Java毕业设计:WML信息查询与后端信息发布系统开发

JAVAWML信息查询与后端信息发布系统实现 一、系统概述 本系统基于Java和WML(无线标记语言)技术开发&#xff0c;实现了移动设备上的信息查询与后端信息发布功能。系统采用B/S架构&#xff0c;服务器端使用Java Servlet处理请求&#xff0c;数据库采用MySQL存储信息&#xff0…...