鸿蒙 Flutter 分布式硬件调用:跨设备摄像头 / 麦克风共享
本文介绍了基于Flutter开发鸿蒙应用实现跨设备音视频调用的完整方案。主要内容包括:鸿蒙分布式能力原理(软总线、能力路由)和Flutter适配方式;开发环境搭建(DevEcoStudio、依赖库配置);核心功能实现步骤:设备发现连接、远程摄像头调用(预览/拍照)、远程麦克风调用(录制/播放);多设备协同音视频会议案例;常见问题解决方案及进阶优化方向。通过分布式软总线技术,开发者可以轻松实现鸿蒙设
欢迎大家加入[开源鸿蒙跨平台开发者社区](https://openharmonycrossplatform.csdn.net),
一起共建开源鸿蒙跨平台生态。
引言:为什么要做跨设备音视频共享?
在鸿蒙(HarmonyOS)的分布式生态中,“硬件互助、资源共享” 是核心特性之一 —— 比如手机可以调用平板的大屏显示、电脑的键盘输入,而跨设备音视频共享(摄像头 / 麦克风调用)则是更具实用价值的场景:
- 会议场景:用平板调用远处三脚架上手机的高清摄像头,同时调用桌面麦克风增强收音,避免手持设备的画面抖动和杂音;
- 智能家居:智慧屏调用门口摄像头的实时画面,无需在智慧屏上额外安装摄像头硬件;
- 多设备协作:笔记本调用手机的超广角摄像头拍摄文档,同时用平板的麦克风录制讲解音频,实现 “多硬件协同创作”。
而 Flutter 作为跨平台开发框架,在鸿蒙生态中通过 OHOS Flutter 适配层 实现了 “一次开发,多端部署”。本文将从 “概念→环境→实战→优化” 全流程,教你如何基于 Flutter 开发鸿蒙应用,实现跨设备摄像头与麦克风的调用,附带完整代码和官方文档链接,确保你能直接上手实践。
一、核心概念铺垫:鸿蒙分布式能力 + Flutter 适配
在写代码前,必须先理解两个关键技术底座:鸿蒙的分布式硬件架构,以及 Flutter 如何与鸿蒙分布式能力交互。
1.1 鸿蒙分布式硬件的核心原理
鸿蒙通过 分布式软总线(鸿蒙设备间的 “隐形网线”)、能力路由(找到 “最优硬件” 的调度机制)、分布式数据管理(跨设备数据同步)三大组件,实现 “设备即硬件” 的效果。对于摄像头 / 麦克风这类硬件,鸿蒙将其抽象为 “分布式能力”,其他设备可通过 “能力请求→权限验证→资源调用” 的流程,像使用本地硬件一样调用远程设备的音视频资源。
官方文档参考:
1.2 Flutter 与鸿蒙分布式能力的交互方式
Flutter 本身不直接操作鸿蒙底层硬件,而是通过 “Flutter 插件(Plugin)” 桥接鸿蒙的 Native API。对于分布式硬件调用,核心依赖两个层面的插件:
- 鸿蒙分布式基础插件:负责设备发现、连接、权限协商(如
ohos_distributed_service); - 音视频硬件插件:负责调用远程设备的摄像头(如
ohos_camera)和麦克风(如ohos_audio),这些插件需适配鸿蒙的分布式能力。
关键插件与文档:
- OHOS Flutter 官方插件仓库(包含分布式、音视频相关插件)
- Flutter 鸿蒙适配指南
二、环境准备:从 0 搭建开发环境
在开始编码前,需完成 开发工具、系统环境、依赖库 三大准备步骤,确保跨设备调用功能可正常运行。
2.1 开发工具与系统版本要求
| 工具 / 环境 | 版本要求 | 下载链接 |
|---|---|---|
| DevEco Studio | 4.0 及以上(支持鸿蒙 4.0+) | DevEco Studio 官网 |
| Flutter SDK | 3.10 及以上 | Flutter 官网 |
| 鸿蒙设备(真机) | 2 台及以上(鸿蒙 4.0+) | 需开启 “开发者模式” 和 “分布式能力调试” |
| 鸿蒙模拟器 | 可选(建议用真机测试) | 鸿蒙模拟器下载 |
关键配置步骤:
- 打开 DevEco Studio,在 Settings → Plugins 中安装 Flutter Plugin 和 HarmonyOS Flutter Adapter;
- 配置 Flutter 环境:在 DevEco Studio 中关联本地 Flutter SDK(需确保 SDK 包含
ohos平台支持); - 真机调试配置:在鸿蒙设备上开启 “开发者模式”→ 开启 “USB 调试” 和 “分布式能力调试”→ 通过 USB 连接电脑,确认设备在 DevEco Studio 中可识别。
2.2 依赖库引入(pubspec.yaml)
在 Flutter 项目的 pubspec.yaml 中添加以下核心依赖,用于分布式设备管理、摄像头调用、麦克风录制:
yaml
dependencies:
flutter:
sdk: flutter
# 鸿蒙分布式能力核心库(设备发现、连接)
ohos_distributed_service: ^1.0.2 # 最新版本见 pub.dev
# 鸿蒙摄像头调用库(支持跨设备)
ohos_camera: ^0.5.1
# 鸿蒙麦克风录制库(支持跨设备音频采集)
ohos_audio: ^0.3.0
# 权限管理(需申请摄像头、麦克风、分布式权限)
permission_handler: ^10.2.0
# 状态管理(简化跨设备调用逻辑)
provider: ^6.0.5
依赖库官方文档:
- ohos_distributed_service(鸿蒙分布式设备管理)
- ohos_camera(鸿蒙摄像头调用)
- ohos_audio(鸿蒙音频采集)
- permission_handler(权限申请)
2.3 鸿蒙权限配置(config.json)
鸿蒙应用需在 main_pages/config.json 中声明 分布式能力权限、摄像头权限、麦克风权限,否则调用会失败:
json
{
"module": {
"reqPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC", // 分布式数据同步权限
"reason": "需要跨设备调用硬件资源",
"usedScene": {
"ability": ["com.example.distributed_hardware.MainAbility"],
"when": "always"
}
},
{
"name": "ohos.permission.CAMERA", // 摄像头权限
"reason": "需要调用跨设备摄像头",
"usedScene": {
"ability": ["com.example.distributed_hardware.MainAbility"],
"when": "always"
}
},
{
"name": "ohos.permission.MICROPHONE", // 麦克风权限
"reason": "需要调用跨设备麦克风",
"usedScene": {
"ability": ["com.example.distributed_hardware.MainAbility"],
"when": "always"
}
},
{
"name": "ohos.permission.STORAGE", // 存储权限(用于保存拍摄的图片/音频)
"reason": "需要保存跨设备采集的音视频文件",
"usedScene": {
"ability": ["com.example.distributed_hardware.MainAbility"],
"when": "always"
}
}
]
}
}
权限说明参考:鸿蒙应用权限列表
三、核心实战:跨设备硬件调用全流程
本节分 “分布式设备管理”→“跨设备摄像头调用”→“跨设备麦克风调用” 三个模块,每个模块提供完整代码和关键步骤解析。
3.1 第一步:分布式设备发现与连接
要调用跨设备硬件,首先需通过 分布式软总线 发现周边鸿蒙设备,并建立稳定连接。我们将通过 ohos_distributed_service 实现这一功能。
3.1.1 分布式服务初始化
在应用启动时初始化分布式服务,确保设备可被发现和连接:
dart
import 'package:ohos_distributed_service/ohos_distributed_service.dart';
import 'package:permission_handler/permission_handler.dart';
class DistributedDeviceManager {
// 分布式服务实例
final DistributedService _distributedService = DistributedService.instance;
// 已发现的设备列表(key:设备ID,value:设备名称)
Map<String, String> _discoveredDevices = {};
// 当前连接的设备ID
String? _connectedDeviceId;
// 初始化分布式服务(需先申请分布式权限)
Future<bool> initDistributedService() async {
// 1. 申请分布式权限
PermissionStatus status = await Permission.distributedDataSync.request();
if (status != PermissionStatus.granted) {
print("分布式权限申请失败,无法使用跨设备功能");
return false;
}
// 2. 初始化分布式服务
bool isInitSuccess = await _distributedService.init();
if (!isInitSuccess) {
print("分布式服务初始化失败");
return false;
}
// 3. 监听设备发现事件
_distributedService.onDeviceDiscovered.listen((device) {
// device 包含 deviceId(设备唯一ID)、deviceName(设备名称)、deviceType(设备类型)
_discoveredDevices[device.deviceId] = device.deviceName;
print("发现新设备:${device.deviceName}(ID:${device.deviceId})");
});
// 4. 监听设备连接状态变化
_distributedService.onConnectionStateChanged.listen((state) {
if (state.isConnected) {
_connectedDeviceId = state.deviceId;
print("已连接设备:${_discoveredDevices[state.deviceId]}");
} else {
_connectedDeviceId = null;
print("设备断开连接:${_discoveredDevices[state.deviceId]}");
}
});
print("分布式服务初始化成功");
return true;
}
// 扫描周边分布式设备
Future<void> scanDevices() async {
_discoveredDevices.clear(); // 清空历史设备列表
await _distributedService.startDeviceDiscovery();
print("开始扫描分布式设备...");
}
// 连接指定设备
Future<bool> connectDevice(String deviceId) async {
if (!_discoveredDevices.containsKey(deviceId)) {
print("设备未在已发现列表中,无法连接");
return false;
}
bool isConnected = await _distributedService.connectDevice(deviceId);
return isConnected;
}
// 获取已发现的设备列表
Map<String, String> get discoveredDevices => _discoveredDevices;
// 获取当前连接的设备ID
String? get connectedDeviceId => _connectedDeviceId;
}
3.1.2 设备管理 UI 实现(Flutter Widget)
通过 ListView 展示已发现的设备,并提供 “扫描” 和 “连接” 按钮:
dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'distributed_device_manager.dart';
class DeviceDiscoveryPage extends StatefulWidget {
const DeviceDiscoveryPage({super.key});
@override
State<DeviceDiscoveryPage> createState() => _DeviceDiscoveryPageState();
}
class _DeviceDiscoveryPageState extends State<DeviceDiscoveryPage> {
late DistributedDeviceManager _deviceManager;
@override
void initState() {
super.initState();
// 通过 Provider 获取 DeviceManager 实例(需在顶层注册 Provider)
_deviceManager = Provider.of<DistributedDeviceManager>(context, listen: false);
// 初始化分布式服务
_deviceManager.initDistributedService();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("分布式设备管理")),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// 扫描设备按钮
ElevatedButton(
onPressed: () => _deviceManager.scanDevices(),
child: const Text("扫描周边鸿蒙设备"),
),
const SizedBox(height: 20),
// 当前连接状态
Consumer<DistributedDeviceManager>(
builder: (context, manager, child) {
return Text(
manager.connectedDeviceId != null
? "当前连接:${manager.discoveredDevices[manager.connectedDeviceId]}"
: "未连接任何设备",
style: const TextStyle(fontSize: 16),
);
},
),
const SizedBox(height: 20),
// 已发现设备列表
Expanded(
child: Consumer<DistributedDeviceManager>(
builder: (context, manager, child) {
if (manager.discoveredDevices.isEmpty) {
return const Center(child: Text("未发现设备,请点击扫描按钮"));
}
return ListView.builder(
itemCount: manager.discoveredDevices.length,
itemBuilder: (context, index) {
String deviceId = manager.discoveredDevices.keys.elementAt(index);
String deviceName = manager.discoveredDevices[deviceId]!;
bool isConnected = manager.connectedDeviceId == deviceId;
return ListTile(
title: Text(deviceName),
subtitle: Text("设备ID:${deviceId.substring(0, 8)}..."), // 隐藏部分ID,避免过长
trailing: isConnected
? const Icon(Icons.check_circle, color: Colors.green)
: ElevatedButton(
onPressed: () => manager.connectDevice(deviceId),
child: const Text("连接"),
),
);
},
);
},
),
),
],
),
),
);
}
}
关键说明:
- 需在
main.dart中通过ChangeNotifierProvider注册DistributedDeviceManager(确保状态可全局共享);- 设备 ID 是鸿蒙设备的唯一标识,连接时需传入完整 ID,但 UI 展示可简化(如只显示前 8 位)。
3.2 第二步:跨设备摄像头调用
当设备连接成功后,即可通过 ohos_camera 插件调用远程设备的摄像头,实现 “预览→拍照→录像” 功能。
3.2.1 跨设备摄像头管理类
封装摄像头初始化、预览、拍照逻辑,核心是通过 deviceId 指定 “远程设备”:
dart
import 'dart:io';
import 'package:ohos_camera/ohos_camera.dart';
import 'package:permission_handler/permission_handler.dart';
import 'distributed_device_manager.dart';
class RemoteCameraManager {
final DistributedDeviceManager _deviceManager;
// 摄像头控制器(用于预览和操作)
OhosCameraController? _cameraController;
// 远程设备的摄像头列表
List<CameraDescription> _remoteCameras = [];
// 当前选中的摄像头(前置/后置)
CameraDescription? _selectedCamera;
// 拍摄的图片文件
File? _capturedImage;
RemoteCameraManager(this._deviceManager);
// 初始化远程摄像头(关键:传入 remoteDeviceId)
Future<bool> initRemoteCamera() async {
// 1. 检查是否已连接远程设备
String? remoteDeviceId = _deviceManager.connectedDeviceId;
if (remoteDeviceId == null) {
print("未连接远程设备,无法初始化摄像头");
return false;
}
// 2. 申请摄像头权限
PermissionStatus status = await Permission.camera.request();
if (status != PermissionStatus.granted) {
print("摄像头权限申请失败");
return false;
}
// 3. 获取远程设备的摄像头列表(关键:指定 deviceId 为远程设备ID)
_remoteCameras = await OhosCamera.getCameras(deviceId: remoteDeviceId);
if (_remoteCameras.isEmpty) {
print("远程设备无可用摄像头");
return false;
}
// 4. 默认选择后置摄像头(可根据需求切换为前置)
_selectedCamera = _remoteCameras.firstWhere(
(camera) => camera.lensDirection == CameraLensDirection.back,
orElse: () => _remoteCameras.first,
);
// 5. 初始化摄像头控制器(配置预览分辨率)
_cameraController = OhosCameraController(
cameraDescription: _selectedCamera!,
resolutionPreset: ResolutionPreset.medium, // 中等分辨率(平衡画质和性能)
deviceId: remoteDeviceId, // 绑定远程设备ID
);
// 6. 初始化摄像头(开启预览)
try {
await _cameraController!.initialize();
print("远程摄像头初始化成功(设备:${remoteDeviceId.substring(0,8)}...,摄像头:${_selectedCamera!.name})");
return true;
} catch (e) {
print("远程摄像头初始化失败:$e");
_cameraController = null;
return false;
}
}
// 切换摄像头(前置/后置)
Future<void> switchCamera() async {
if (_remoteCameras.length < 2) {
print("远程设备只有一个摄像头,无法切换");
return;
}
// 切换为另一个方向的摄像头
CameraDescription newCamera = _remoteCameras.firstWhere(
(camera) => camera.lensDirection != _selectedCamera!.lensDirection,
);
// 重新初始化控制器
_selectedCamera = newCamera;
String? remoteDeviceId = _deviceManager.connectedDeviceId;
_cameraController = OhosCameraController(
cameraDescription: newCamera,
resolutionPreset: ResolutionPreset.medium,
deviceId: remoteDeviceId,
);
await _cameraController!.initialize();
}
// 拍摄照片
Future<File?> captureImage() async {
if (_cameraController == null || !_cameraController!.value.isInitialized) {
print("摄像头未初始化,无法拍照");
return null;
}
try {
// 调用远程摄像头拍摄照片(返回临时文件路径)
XFile xFile = await _cameraController!.takePicture();
_capturedImage = File(xFile.path);
print("照片拍摄成功,保存路径:${_capturedImage!.path}");
return _capturedImage;
} catch (e) {
print("拍照失败:$e");
return null;
}
}
// 释放摄像头资源
Future<void> dispose() async {
await _cameraController?.dispose();
_cameraController = null;
}
// Getter 方法
OhosCameraController? get cameraController => _cameraController;
List<CameraDescription> get remoteCameras => _remoteCameras;
File? get capturedImage => _capturedImage;
}
3.2.2 跨设备摄像头预览 UI
实现 “摄像头预览 + 拍照 + 切换摄像头 + 查看照片” 的完整 UI:
dart
import 'package:flutter/material.dart';
import 'package:ohos_camera/ohos_camera.dart';
import 'package:provider/provider.dart';
import 'distributed_device_manager.dart';
import 'remote_camera_manager.dart';
class RemoteCameraPage extends StatefulWidget {
const RemoteCameraPage({super.key});
@override
State<RemoteCameraPage> createState() => _RemoteCameraPageState();
}
class _RemoteCameraPageState extends State<RemoteCameraPage> {
late RemoteCameraManager _cameraManager;
bool _isCameraInitialized = false;
@override
void initState() {
super.initState();
// 获取 DeviceManager 实例,初始化 CameraManager
DistributedDeviceManager deviceManager = Provider.of<DistributedDeviceManager>(context, listen: false);
_cameraManager = RemoteCameraManager(deviceManager);
// 初始化远程摄像头
_initCamera();
}
Future<void> _initCamera() async {
bool isSuccess = await _cameraManager.initRemoteCamera();
setState(() {
_isCameraInitialized = isSuccess;
});
}
@override
void dispose() {
// 释放摄像头资源
_cameraManager.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("跨设备摄像头调用")),
body: _isCameraInitialized
? Column(
children: [
// 摄像头预览(占满屏幕上半部分)
Expanded(
child: OhosCameraPreview(
_cameraManager.cameraController!,
child: Container(
alignment: Alignment.topRight,
padding: const EdgeInsets.all(16),
// 切换摄像头按钮
child: IconButton(
icon: const Icon(Icons.switch_camera, color: Colors.white, size: 32),
onPressed: () async {
await _cameraManager.switchCamera();
setState(() {}); // 刷新预览
},
),
),
),
),
// 拍照按钮和照片预览
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
// 拍照按钮
ElevatedButton(
onPressed: () async {
File? image = await _cameraManager.captureImage();
if (image != null) {
// 拍照成功后,跳转查看照片
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ImagePreviewPage(image: image),
),
);
}
},
style: ElevatedButton.styleFrom(
fixedSize: const Size(80, 80),
shape: const CircleBorder(),
),
child: const Icon(Icons.camera_alt, size: 32),
),
const SizedBox(height: 16),
// 当前摄像头信息
Text(
"当前摄像头:${_cameraManager._selectedCamera?.lensDirection == CameraLensDirection.back ? "后置" : "前置"}",
style: const TextStyle(fontSize: 14),
),
],
),
),
],
)
: const Center(child: Text("摄像头初始化中...")),
);
}
}
// 照片预览页面
class ImagePreviewPage extends StatelessWidget {
final File image;
const ImagePreviewPage({super.key, required this.image});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("照片预览")),
body: Center(
child: Image.file(image),
),
);
}
}
关键技术点:
OhosCamera.getCameras(deviceId: remoteDeviceId):通过deviceId指定获取 “远程设备” 的摄像头列表,而非本地;OhosCameraPreview:Flutter 端的预览组件,需传入绑定了远程设备的OhosCameraController;- 拍照后返回的
XFile是临时文件,如需长期保存,需将其复制到应用的私有目录或公共存储目录。
3.3 第三步:跨设备麦克风调用
跨设备麦克风调用的逻辑与摄像头类似,核心是通过 ohos_audio 插件指定远程设备 ID,实现 “音频录制→保存→播放” 功能。
3.3.1 跨设备麦克风管理类
封装音频录制逻辑,支持指定远程设备的麦克风:
dart
import 'dart:io';
import 'package:ohos_audio/ohos_audio.dart';
import 'package:permission_handler/permission_handler.dart';
import 'distributed_device_manager.dart';
class RemoteAudioManager {
final DistributedDeviceManager _deviceManager;
// 音频录制控制器
OhosAudioRecorder? _audioRecorder;
// 是否正在录制
bool _isRecording = false;
// 录制的音频文件
File? _recordedAudio;
RemoteAudioManager(this._deviceManager);
// 初始化远程麦克风(指定远程设备ID)
Future<bool> initRemoteMicrophone() async {
// 1. 检查是否已连接远程设备
String? remoteDeviceId = _deviceManager.connectedDeviceId;
if (remoteDeviceId == null) {
print("未连接远程设备,无法初始化麦克风");
return false;
}
// 2. 申请麦克风和存储权限
Map<Permission, PermissionStatus> statuses = await [
Permission.microphone,
Permission.storage,
].request();
if (statuses[Permission.microphone] != PermissionStatus.granted ||
statuses[Permission.storage] != PermissionStatus.granted) {
print("麦克风或存储权限申请失败");
return false;
}
// 3. 初始化音频录制器(关键:指定 remoteDeviceId)
_audioRecorder = OhosAudioRecorder(
deviceId: remoteDeviceId, // 绑定远程设备
audioFormat: AudioFormat.aac, // 音频格式:AAC(兼容性好)
sampleRate: 44100, // 采样率:44.1kHz(标准音质)
bitRate: 128000, // 比特率:128kbps(平衡音质和文件大小)
);
print("远程麦克风初始化成功(设备:${remoteDeviceId.substring(0,8)}...)");
return true;
}
// 开始录制音频
Future<bool> startRecording() async {
if (_isRecording || _audioRecorder == null) {
print("已在录制中或麦克风未初始化");
return false;
}
try {
// 生成音频保存路径(应用私有目录)
String savePath = "${(await getApplicationDocumentsDirectory()).path}/remote_audio_${DateTime.now().millisecondsSinceEpoch}.aac";
_recordedAudio = File(savePath);
// 开始录制(传入保存路径)
await _audioRecorder!.startRecording(savePath);
setState(() => _isRecording = true);
print("开始录制音频,保存路径:$savePath");
return true;
} catch (e) {
print("录制启动失败:$e");
return false;
}
}
// 停止录制音频
Future<bool> stopRecording() async {
if (!_isRecording || _audioRecorder == null) {
print("未在录制中或麦克风未初始化");
return false;
}
try {
await _audioRecorder!.stopRecording();
setState(() => _isRecording = false);
print("音频录制停止,文件大小:${_recordedAudio?.lengthSync() ?? 0} bytes");
return true;
} catch (e) {
print("录制停止失败:$e");
return false;
}
}
// 释放麦克风资源
Future<void> dispose() async {
if (_isRecording) {
await stopRecording();
}
await _audioRecorder?.dispose();
_audioRecorder = null;
}
// Getter 方法
bool get isRecording => _isRecording;
File? get recordedAudio => _recordedAudio;
}
// 辅助方法:获取应用私有目录(用于保存音频文件)
Future<Directory> getApplicationDocumentsDirectory() async {
return Directory("/data/data/com.example.distributed_hardware/files"); // 替换为你的应用包名
}
3.3.2 跨设备麦克风录制 UI
实现 “开始录制→停止录制→播放音频” 的 UI:
dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'distributed_device_manager.dart';
import 'remote_audio_manager.dart';
import 'package:audioplayers/audioplayers.dart';
class RemoteAudioPage extends StatefulWidget {
const RemoteAudioPage({super.key});
@override
State<RemoteAudioPage> createState() => _RemoteAudioPageState();
}
class _RemoteAudioPageState extends State<RemoteAudioPage> {
late RemoteAudioManager _audioManager;
bool _isMicrophoneInitialized = false;
// 音频播放器(用于播放录制的音频)
final AudioPlayer _audioPlayer = AudioPlayer();
// 是否正在播放音频
bool _isPlaying = false;
@override
void initState() {
super.initState();
// 初始化远程麦克风
DistributedDeviceManager deviceManager = Provider.of<DistributedDeviceManager>(context, listen: false);
_audioManager = RemoteAudioManager(deviceManager);
_initMicrophone();
}
Future<void> _initMicrophone() async {
bool isSuccess = await _audioManager.initRemoteMicrophone();
setState(() {
_isMicrophoneInitialized = isSuccess;
});
}
@override
void dispose() {
// 释放麦克风和播放器资源
_audioManager.dispose();
_audioPlayer.dispose();
super.dispose();
}
// 播放录制的音频
Future<void> _playRecordedAudio() async {
if (_audioManager.recordedAudio == null) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("暂无录制的音频")));
return;
}
if (_isPlaying) {
await _audioPlayer.stop();
setState(() => _isPlaying = false);
return;
}
// 播放音频文件
Source audioSource = UrlSource(_audioManager.recordedAudio!.path);
await _audioPlayer.play(audioSource);
setState(() => _isPlaying = true);
// 监听播放完成事件
_audioPlayer.onPlayerComplete.listen((_) {
setState(() => _isPlaying = false);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("跨设备麦克风调用")),
body: _isMicrophoneInitialized
? Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 录制状态提示
Text(
_audioManager.isRecording
? "正在录制音频...(点击按钮停止)"
: "未在录制(点击按钮开始)",
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 40),
// 录制按钮
ElevatedButton(
onPressed: () async {
if (_audioManager.isRecording) {
await _audioManager.stopRecording();
} else {
await _audioManager.startRecording();
}
setState(() {});
},
style: ElevatedButton.styleFrom(
fixedSize: const Size(120, 60),
backgroundColor: _audioManager.isRecording ? Colors.red : Colors.blue,
),
child: Text(
_audioManager.isRecording ? "停止录制" : "开始录制",
style: const TextStyle(fontSize: 16, color: Colors.white),
),
),
const SizedBox(height: 40),
// 播放按钮(仅当有录制文件时可用)
ElevatedButton(
onPressed: _audioManager.recordedAudio != null ? _playRecordedAudio : null,
style: ElevatedButton.styleFrom(fixedSize: const Size(120, 60)),
child: Text(
_isPlaying ? "停止播放" : "播放音频",
style: const TextStyle(fontSize: 16),
),
),
const SizedBox(height: 20),
// 录制文件信息
if (_audioManager.recordedAudio != null)
Text(
"录制文件:${_audioManager.recordedAudio!.path.split('/').last}\n"
"文件大小:${(_audioManager.recordedAudio!.lengthSync() / 1024).toStringAsFixed(2)} KB",
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 14, color: Colors.grey),
),
],
),
)
: const Center(child: Text("麦克风初始化中...")),
);
}
}
关键说明:
OhosAudioRecorder的deviceId参数需传入远程设备 ID,确保录制的是远程麦克风的音频;- 音频文件默认保存到应用的私有目录(
/data/data/[包名]/files),需替换为你的应用包名;- 播放音频使用
audioplayers插件(需在pubspec.yaml中添加依赖),支持本地文件播放。
四、实战案例:多设备协同音视频会议
将前面的摄像头和麦克风调用结合,实现一个简单的 “多设备协同音视频会议” 场景:
- 设备 A(手机):作为 “音视频采集端”,提供摄像头和麦克风;
- 设备 B(平板):作为 “显示端”,调用设备 A 的摄像头预览画面;
- 设备 C(电脑):作为 “收音端”,调用设备 A 的麦克风录制音频;
- 最终在设备 B 上展示 “设备 A 的摄像头画面 + 设备 C 录制的音频”。
核心代码逻辑(简化):
dart
// 多设备协同管理类
class MultiDeviceCollabManager {
final DistributedDeviceManager _deviceManager;
late RemoteCameraManager _cameraManager;
late RemoteAudioManager _audioManager;
MultiDeviceCollabManager(this._deviceManager) {
_cameraManager = RemoteCameraManager(_deviceManager);
_audioManager = RemoteAudioManager(_deviceManager);
}
// 初始化多设备协同(连接设备A,同时初始化摄像头和麦克风)
Future<bool> initCollab(String deviceAId) async {
// 1. 连接设备A
bool isConnected = await _deviceManager.connectDevice(deviceAId);
if (!isConnected) return false;
// 2. 同时初始化设备A的摄像头和麦克风
bool isCameraInit = await _cameraManager.initRemoteCamera();
bool isAudioInit = await _audioManager.initRemoteMicrophone();
return isCameraInit && isAudioInit;
}
// 开始协同:预览摄像头 + 录制音频
Future<void> startCollab() async {
await _cameraManager.cameraController?.initialize(); // 预览摄像头
await _audioManager.startRecording(); // 录制音频
}
// 停止协同:停止预览 + 停止录制
Future<void> stopCollab() async {
await _cameraManager.dispose();
await _audioManager.stopRecording();
}
// Getter 方法
RemoteCameraManager get cameraManager => _cameraManager;
RemoteAudioManager get audioManager => _audioManager;
}
通过这个案例,你可以扩展到更复杂的场景,比如多设备音视频流的拼接、实时传输等。
五、常见问题与解决方案
在开发过程中,你可能会遇到以下问题,这里提供对应的解决方案:
| 常见问题 | 原因分析 | 解决方案 |
|---|---|---|
| 分布式设备搜索不到 | 1. 设备不在同一局域网;2. 未开启分布式权限;3. 设备版本低于鸿蒙 4.0 | 1. 确保所有设备连接同一 WiFi;2. 检查 config.json 权限配置;3. 升级设备系统版本 |
| 跨设备调用时权限被拒 | 1. 未在 config.json 中声明权限;2. 动态权限申请失败 |
1. 核对 reqPermissions 配置;2. 在代码中检查权限状态,引导用户授权 |
| 远程摄像头预览卡顿 | 1. 网络带宽不足;2. 预览分辨率过高;3. 设备性能不足 | 1. 切换到更稳定的网络;2. 降低 ResolutionPreset(如改为 low);3. 关闭设备后台其他应用 |
| 麦克风录制无声音 | 1. 远程设备麦克风被占用;2. 音频格式不支持;3. 音量过低 | 1. 确保远程设备未在使用麦克风;2. 改用 AudioFormat.aac 格式;3. 调高远程设备音量 |
| 应用崩溃(日志提示 “分布式服务未初始化”) | 1. 分布式服务初始化顺序错误;2. 未申请分布式权限 | 1. 先初始化分布式服务,再初始化摄像头 / 麦克风;2. 确保 DISTRIBUTED_DATASYNC 权限已申请 |
六、进阶优化方向
-
分布式连接稳定性优化:
- 实现 “心跳机制”:定期向远程设备发送心跳包,检测连接状态,断开时自动重连;
- 支持 “多设备备份”:当当前连接设备断开时,自动切换到备用设备。
-
音视频流压缩与传输:
- 视频压缩:使用 H.264/H.265 编码压缩视频流,降低网络带宽占用;
- 音频压缩:使用 OPUS 编码(比 AAC 更高效),适合实时传输。
-
多设备负载均衡:
- 当调用多个设备的硬件时,根据设备性能分配任务(如高性能设备负责视频采集,低性能设备负责音频录制)。
-
异常重试机制:
- 对摄像头初始化、音频录制等关键操作添加 “重试逻辑”(如重试 3 次),提高容错性。
七、总结
本文从 “概念→环境→实战” 全流程,讲解了如何基于 Flutter 开发鸿蒙应用,实现跨设备摄像头与麦克风的调用。核心要点总结如下:
- 鸿蒙分布式能力:通过分布式软总线实现设备发现与连接,将远程硬件抽象为 “可调用资源”;
- Flutter 适配核心:通过
ohos_distributed_service、ohos_camera、ohos_audio等插件,桥接鸿蒙 Native API,实现跨设备硬件调用; - 关键代码逻辑:所有跨设备调用需传入
deviceId(远程设备唯一标识),确保操作的是远程硬件而非本地; - 权限与兼容性:需在
config.json中声明分布式、摄像头、麦克风权限,并确保设备版本不低于鸿蒙 4.0。
通过本文的代码,你可以快速搭建跨设备音视频共享的基础框架,并扩展到会议、智能家居、多设备协作等场景。如果在开发中遇到问题,建议优先参考鸿蒙和 Flutter 的官方文档,或在 CSDN、Stack Overflow 等社区提问。
最后,鸿蒙的分布式生态仍在快速发展,后续会有更多硬件和场景支持,建议持续关注鸿蒙开发者官网的更新!
参考资料
更多推荐






所有评论(0)