声网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;
录像由两步:
注意: 录像的中途可能会退出页面,需要退出页面之前调用录像结束接口。 录像结束之后不能马上离开通道, 否则会导致录像不能正常。
- 录像开始接口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++;
});
}
- 录像结束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