欢迎大家加入[开源鸿蒙跨平台开发者社区](https://openharmonycrossplatform.csdn.net),

一起共建开源鸿蒙跨平台生态。

引言:为什么要做跨设备音视频共享?

在鸿蒙(HarmonyOS)的分布式生态中,“硬件互助、资源共享” 是核心特性之一 —— 比如手机可以调用平板的大屏显示、电脑的键盘输入,而跨设备音视频共享(摄像头 / 麦克风调用)则是更具实用价值的场景:

  • 会议场景:用平板调用远处三脚架上手机的高清摄像头,同时调用桌面麦克风增强收音,避免手持设备的画面抖动和杂音;
  • 智能家居:智慧屏调用门口摄像头的实时画面,无需在智慧屏上额外安装摄像头硬件;
  • 多设备协作:笔记本调用手机的超广角摄像头拍摄文档,同时用平板的麦克风录制讲解音频,实现 “多硬件协同创作”。

而 Flutter 作为跨平台开发框架,在鸿蒙生态中通过 OHOS Flutter 适配层 实现了 “一次开发,多端部署”。本文将从 “概念→环境→实战→优化” 全流程,教你如何基于 Flutter 开发鸿蒙应用,实现跨设备摄像头与麦克风的调用,附带完整代码和官方文档链接,确保你能直接上手实践。

一、核心概念铺垫:鸿蒙分布式能力 + Flutter 适配

在写代码前,必须先理解两个关键技术底座:鸿蒙的分布式硬件架构,以及 Flutter 如何与鸿蒙分布式能力交互。

1.1 鸿蒙分布式硬件的核心原理

鸿蒙通过 分布式软总线(鸿蒙设备间的 “隐形网线”)、能力路由(找到 “最优硬件” 的调度机制)、分布式数据管理(跨设备数据同步)三大组件,实现 “设备即硬件” 的效果。对于摄像头 / 麦克风这类硬件,鸿蒙将其抽象为 “分布式能力”,其他设备可通过 “能力请求→权限验证→资源调用” 的流程,像使用本地硬件一样调用远程设备的音视频资源。

官方文档参考:

1.2 Flutter 与鸿蒙分布式能力的交互方式

Flutter 本身不直接操作鸿蒙底层硬件,而是通过 “Flutter 插件(Plugin)” 桥接鸿蒙的 Native API。对于分布式硬件调用,核心依赖两个层面的插件:

  1. 鸿蒙分布式基础插件:负责设备发现、连接、权限协商(如 ohos_distributed_service);
  2. 音视频硬件插件:负责调用远程设备的摄像头(如 ohos_camera)和麦克风(如 ohos_audio),这些插件需适配鸿蒙的分布式能力。

关键插件与文档:

二、环境准备:从 0 搭建开发环境

在开始编码前,需完成 开发工具、系统环境、依赖库 三大准备步骤,确保跨设备调用功能可正常运行。

2.1 开发工具与系统版本要求

工具 / 环境 版本要求 下载链接
DevEco Studio 4.0 及以上(支持鸿蒙 4.0+) DevEco Studio 官网
Flutter SDK 3.10 及以上 Flutter 官网
鸿蒙设备(真机) 2 台及以上(鸿蒙 4.0+) 需开启 “开发者模式” 和 “分布式能力调试”
鸿蒙模拟器 可选(建议用真机测试) 鸿蒙模拟器下载
关键配置步骤:
  1. 打开 DevEco Studio,在 Settings → Plugins 中安装 Flutter Plugin 和 HarmonyOS Flutter Adapter
  2. 配置 Flutter 环境:在 DevEco Studio 中关联本地 Flutter SDK(需确保 SDK 包含 ohos 平台支持);
  3. 真机调试配置:在鸿蒙设备上开启 “开发者模式”→ 开启 “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

依赖库官方文档:

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 中添加依赖),支持本地文件播放。

四、实战案例:多设备协同音视频会议

将前面的摄像头和麦克风调用结合,实现一个简单的 “多设备协同音视频会议” 场景:

  1. 设备 A(手机):作为 “音视频采集端”,提供摄像头和麦克风;
  2. 设备 B(平板):作为 “显示端”,调用设备 A 的摄像头预览画面;
  3. 设备 C(电脑):作为 “收音端”,调用设备 A 的麦克风录制音频;
  4. 最终在设备 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 权限已申请

六、进阶优化方向

  1. 分布式连接稳定性优化

    • 实现 “心跳机制”:定期向远程设备发送心跳包,检测连接状态,断开时自动重连;
    • 支持 “多设备备份”:当当前连接设备断开时,自动切换到备用设备。
  2. 音视频流压缩与传输

    • 视频压缩:使用 H.264/H.265 编码压缩视频流,降低网络带宽占用;
    • 音频压缩:使用 OPUS 编码(比 AAC 更高效),适合实时传输。
  3. 多设备负载均衡

    • 当调用多个设备的硬件时,根据设备性能分配任务(如高性能设备负责视频采集,低性能设备负责音频录制)。
  4. 异常重试机制

    • 对摄像头初始化、音频录制等关键操作添加 “重试逻辑”(如重试 3 次),提高容错性。

七、总结

本文从 “概念→环境→实战” 全流程,讲解了如何基于 Flutter 开发鸿蒙应用,实现跨设备摄像头与麦克风的调用。核心要点总结如下:

  1. 鸿蒙分布式能力:通过分布式软总线实现设备发现与连接,将远程硬件抽象为 “可调用资源”;
  2. Flutter 适配核心:通过 ohos_distributed_serviceohos_cameraohos_audio 等插件,桥接鸿蒙 Native API,实现跨设备硬件调用;
  3. 关键代码逻辑:所有跨设备调用需传入 deviceId(远程设备唯一标识),确保操作的是远程硬件而非本地;
  4. 权限与兼容性:需在 config.json 中声明分布式、摄像头、麦克风权限,并确保设备版本不低于鸿蒙 4.0。

通过本文的代码,你可以快速搭建跨设备音视频共享的基础框架,并扩展到会议、智能家居、多设备协作等场景。如果在开发中遇到问题,建议优先参考鸿蒙和 Flutter 的官方文档,或在 CSDN、Stack Overflow 等社区提问。

最后,鸿蒙的分布式生态仍在快速发展,后续会有更多硬件和场景支持,建议持续关注鸿蒙开发者官网的更新!

参考资料

  1. 鸿蒙开发者官网 - 分布式能力
  2. 鸿蒙开发者官网 - Flutter 适配
  3. OHOS Flutter 插件仓库
  4. Flutter 官方文档 - 跨平台开发
  5. pub.dev - 鸿蒙相关 Flutter 插件
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐