《网络摄像机实战②:Flutter集成声网Agora图片抓拍与录像功能》

39 阅读4分钟

声网RTC介绍

声网实时互动(Real-Time Communication,RTC)是提供音视频实时互动服务的产品,可帮助你将实时音视频功能集成到
App 中,实现互动直播、纯语音通话、高清视频通话等功能。在大规模的实时互动场景下,你可以用它实现更好的实时互动效果。 优势介绍
沉浸式音频体验 3A 处理:配备回声消除 (AEC)、降噪 (ANS) 和增益控制 (AGC)
技术,可强力消除百种突发噪声,实现复杂场景非线性回声抑制和近端人声保真,带来纯净沟通体验。
虚拟声卡:声网自研虚拟声卡算法,通过专业算法对用户声音进行优化,让用户的声音美妙动听、更有魅力。
空间音效:用户可以通过音频实时感知到其他用户距离、方位、朝向的变化,增强声音传播的空间感,打造沉浸式音频体验。 至臻画质体验
全平台极致高清:移动端支持 1080P 60 fps,PC端支持 4K 60 fps,配合声网自研 AI 画质算法,实现画质跨级提升。 高效视频编解码:SDK 自适应贴合场景的编解码方式,提供优秀的画质体验和更低的带宽消耗。 自适应高清策略:声网推出自适应高清视频策略,根据设备性能和网络质量自动调整视频属性,实现高清且流畅的视频效果。


《网络摄像机实战》系列文章索引

《网络摄像机实战①:Flutter 集成声网 Agora 打造实时音视频对讲》

《网络摄像机实战②:Flutter集成声网Agora图片抓拍与录像功能》

《网络摄像机实战③:设备端集成声网Agora打造实时音视频对讲功能》

《网络摄像机实战④:设备端集成声网Agora 打造远程控制设备或者异常报警推送功能》

《网络摄像机实战⑤:手机端 flutter集成声网Agora RTM打造远程控制设备功能》

《网络摄像机实战⑥:手机端 flutter集成亚马逊 AWS Kinesis Video Streams 音视频界面和控制》

《网络摄像机实战⑦:嵌入式端AWS KVS IoT SDK 库编译与测试全流程-君正/瑞芯微/全志(超详细指南)》

《网络摄像机实战⑧:嵌入式端接入 AWS KVS Producer 的设计要点与注意事项》


定义和初始化声网核心类rtcEngine

var rtcEngine = Rxn<RtcEngine>();//rtc 引擎-核心
rtcEngine.value = createAgoraRtcEngine();
await rtcEngine.value?.initialize(const RtcEngineContext(appId: appId));

录像功能


定义录像类**_mediaRecorder**

MediaRecorder? _mediaRecorder;

录像由两步:

注意: 录像的中途可能会退出页面,需要退出页面之前调用录像结束接口。 录像结束之后不能马上离开通道, 否则会导致录像不能正常。

  1. 录像开始接口startRecording
Future<void> startRecord() async {
    _mediaRecorder ??= await rtcEngine.value?.createMediaRecorder(RecorderStreamInfo(channelId: channelName.value, uid: remoteUid.value!));

    await _mediaRecorder?.setMediaRecorderObserver(MediaRecorderObserver(
      onRecorderStateChanged: (String channelId, int uid, RecorderState state,
          RecorderReasonCode reason) {
        Get.log(
            'onRecorderStateChanged channelId: $channelId, uid: $uid state: $state, error: $reason');
      },
      onRecorderInfoUpdated: (String channelId, int uid, RecorderInfo info) {
        Get.log(
            'onRecorderInfoUpdated channelId: $channelId, uid: $uid, info: ${info.toJson()}');
      },
    ));

    Directory appDocDir = Platform.isAndroid
        ? (await getExternalStorageDirectory())!
        : await getApplicationDocumentsDirectory();
    AWSDateTime awsDateTime = AWSDateTime(DateTime.now());
    filePath.value = '${appDocDir.path}/${awsDateTime.toString()}.mp4';
    Get.log("mp4 file path:${filePath.value}");
    await _mediaRecorder?.startRecording(MediaRecorderConfiguration(storagePath: filePath.value));
    isStartedMediaRecording.value = true;

    recordingSeconds.value = 0;
    _recordingTimer?.cancel();
    _recordingTimer = Timer.periodic(Duration(seconds: 1), (timer) {
      recordingSeconds.value++;
    });

  }

在这里插入图片描述

  1. 录像结束stopRecording
  Future<void> stopMediaRecording() async {

    if( isStartedMediaRecording.value == false)
    {
      Get.log("media recording not start ");
      return;
    }
    if (_mediaRecorder != null) {
      await _mediaRecorder!.stopRecording();
      await rtcEngine.value?.destroyMediaRecorder(_mediaRecorder!);
      _mediaRecorder = null;
    }
    isStartedMediaRecording.value = false;
    _recordingTimer?.cancel();
    _recordingTimer = null;

    final timestamp = DateTime.now();
    final dir = Platform.isAndroid
        ? (await getExternalStorageDirectory())!
        : await getApplicationDocumentsDirectory();
    final mediaPath = '${dir.path}/$userId/${channelName.value}/${timestamp.year}/${timestamp.month}/${timestamp.day}';
    await Directory(mediaPath).create(recursive: true);
    final fileName = '${timestamp.toIso8601String().replaceAll(":", "-")}.mp4';
    final savePath = '$mediaPath/$fileName';
    final savedFile = await File(filePath.value).copy(savePath);


    // 写入缩略图
    final snapfileName = '${timestamp.toIso8601String().replaceAll(":", "-")}.jpg';
    final snapSavePath = '$mediaPath/$snapfileName';
    Future.delayed(Duration(milliseconds: 500),() async {
        await rtcEngine.value?.takeSnapshot(uid: remoteUid.value!, filePath: snapSavePath);
    });
    Get.log("record snapSavedFile path:${snapSavePath}");
    if(Platform.isAndroid)
    {
      // 写入相册
      await Gal.putVideo(savedFile.path, album: GlobalConfig.HIVE_BOX_MEDIA);
        // final snapSavedFile = await File(snapSavePath).copy(snapSavePath);
        // 保存到相册
        await Gal.putImage(snapSavePath, album: GlobalConfig.HIVE_BOX_MEDIA);


    }


    // 保存 index
    final box = Hive.box<MediaEntry>(GlobalConfig.HIVE_BOX_MEDIA);
    final service = MediaService(box);
    await service.addMedia(
      file: savedFile,
      type: 'video',
      userId: GlobalInformation().getCurrentDeviceUser().user.id,
      deviceId: channelName.value,
      eventType: 'manual',
      // savePath: snapSavePath,
      savePath: Platform.isAndroid?savePath:filePath.value,
      thumbPath:snapSavePath,
      // thumbPath: Platform.isAndroid?snapSavePath:GlobalConfig.RECORD_SAMPLE_FILE_PATH,
    );

    final entries = await getAllMediaEntries();
    Get.log("entries.length:${entries.length}");
    for (final entry in entries) {
      Get.log('📁 Media Entry: ${entry.path}-${entry.thumbPath}, type: ${entry.type}');
    }

  }

在这里插入图片描述


抓拍图片接口takeSnapshot

  Future<void> takeSnapshot() async {
    // Directory? appDocDir = await getExternalStorageDirectory();
    Directory? appDocDir = Platform.isAndroid
        ? (await getExternalStorageDirectory())!
        : await getApplicationDocumentsDirectory();
    // String p = path.join(appDocDir.path,
    //     'snapshot_${remoteUid.value}_${DateTime.now().millisecondsSinceEpoch}.jpeg');
    AWSDateTime awsDateTime = AWSDateTime(DateTime.now());
    final timestamp = DateTime.now();
    final filePath = '${appDocDir?.path}/${awsDateTime.toString()}.jpg';
    // final filePath = '${appDocDir.path}/$userId/${channelName.value}/${timestamp.year}/${timestamp.month}/${timestamp.day}';

    await rtcEngine.value?.takeSnapshot(uid: remoteUid.value!, filePath: filePath);
    // await Gal.putImage(filePath, album: "AHS");
    Get.log("savedFile.path:${filePath}");
    GlobalInformation().saveDeviceThumb("deviceThumb${GlobalInformation().getCurrentDeviceUser().device.id}", filePath);

    thumbImageData.value= (await loadDeviceThumb("deviceThumb${GlobalInformation().getCurrentDeviceUser().device.id}"))!;
    Get.log("loadDeviceThumb:${thumbImageData.value}");

    Future.delayed(Duration(milliseconds: 500),() async {
      final timestamp = DateTime.now();
      final dir = await Platform.isAndroid
          ? (await getExternalStorageDirectory())!
          : await getApplicationDocumentsDirectory();
      final mediaPath ='${dir.path}/$userId/${channelName.value}/${timestamp.year}/${timestamp.month}/${timestamp.day}';
      await Directory(mediaPath).create(recursive: true);

      final fileName = '${timestamp.toIso8601String().replaceAll(":", "-")}.jpg';
      final savePath = '$mediaPath/$fileName';

      final savedFile = await File(filePath).copy(savePath);

      if(Platform.isAndroid)
      {
        // 保存到相册
        await Gal.putImage(savedFile.path, album: GlobalConfig.HIVE_BOX_MEDIA);

      }

      // 2. 读取文件为 Uint8List
      final file = File(filePath);
      if (!await file.exists()) {
        Get.log("Snapshot file does not exist: $filePath");
        return;
      }
      else
        Get.log("Snapshot file exist");
      final bytes = await file.readAsBytes();
      // 3. 调用 saveImage 保存
      await GlobalInformation().saveImage(GlobalInformation().getCurrentDeviceUser().device.id, bytes);
    });
  }

录像列表截图

在这里插入图片描述


友情提示🔔

🙏 感谢你的阅读! 如果这篇文章对你有所启发,欢迎关注我 ⭐,欢迎点击 “打赏支持作者” 支持一下我,你的支持是我持续创作的最大动力! 我会持续分享更多关于 智能摄像头 📷、机器人项目、 🤖音视频 RTC 🎧、App 开发 📱、嵌入式开发 🔧 等方向的实战经验,让你更快落地、更少踩坑。 欢迎浏览我其他文章 📚,或许能解决你当前的难题。 如果你正好在做相关项目产品,也欢迎随时私信我,一起技术交流、一起搞事情! 🤝💬📞 联系微信/电话:13826173658