feat:调整image格式下对讲的数据逻辑优化

This commit is contained in:
liyi 2025-03-12 17:42:02 +08:00
parent fcdd09fcb2
commit 0cdaa26fe5
3 changed files with 228 additions and 55 deletions

View File

@ -38,17 +38,25 @@ class TalkViewLogic extends BaseGetXController {
final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
Timer? _syncTimer; //
Timer? _audioTimer; //
Timer? _networkQualityTimer; //
int _startTime = 0; //
int bufferSize = 40; //
int audioBufferSize = 500; //
//
final List<double> _lastFewFps = <double>[]; //
int frameIntervalMs = 45; // 4522FPS
int frameIntervalMs = 83; // 8312FPS
int audioFrameIntervalMs = 20; // 4522FPS
int minFrameIntervalMs = 30; // 33 FPS
int maxFrameIntervalMs = 100; // 1 FPS
int minFrameIntervalMs = 83; // 12 FPS
int maxFrameIntervalMs = 166; // 6 FPS
//
final List<int> _bufferedAudioFrames = <int>[];
//
final int maxImageCacheCount = 40; //
final Map<String, ui.Image> _imageCache = {};
///
void _initFlutterPcmSound() {
const int sampleRate = 8000;
@ -150,63 +158,205 @@ class TalkViewLogic extends BaseGetXController {
///
void _playVideoData(TalkData talkData) async {
state.listData.value = Uint8List.fromList(talkData.content);
try {
// key
String cacheKey = talkData.content.hashCode.toString();
//
if (_imageCache.containsKey(cacheKey)) {
// 使
state.currentImage.value = _imageCache[cacheKey];
} else {
// List<int> Uint8List
final Uint8List uint8Data = Uint8List.fromList(talkData.content);
// 线
ui.Image? image = await decodeImageFromList(uint8Data);
//
if (_imageCache.length >= maxImageCacheCount) {
_imageCache.remove(_imageCache.keys.first);
}
//
_imageCache[cacheKey] = image;
state.currentImage.value = image;
}
//
state.listData.value = Uint8List.fromList(talkData.content);
} catch (e) {
print('视频帧解码错误: $e');
}
// state.listData.value = Uint8List.fromList(talkData.content);
}
///
void _startPlayback() {
Future.delayed(Duration(milliseconds: 800), () {
//
_networkQualityTimer ??=
Timer.periodic(const Duration(seconds: 5), _checkNetworkQuality);
_startTime = DateTime.now().millisecondsSinceEpoch;
_syncTimer ??=
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
//
_adjustFrameInterval();
//
_monitorFrameStability();
});
});
}
///
void _adjustFrameInterval() {
int newFrameIntervalMs = frameIntervalMs;
if (state.videoBuffer.length < 10 && frameIntervalMs < maxFrameIntervalMs) {
//
frameIntervalMs += 5;
} else if (state.videoBuffer.length > 20 &&
frameIntervalMs > minFrameIntervalMs) {
//
frameIntervalMs -= 5;
//
int targetInterval = _calculateTargetInterval();
//
if (frameIntervalMs != targetInterval) {
// 2ms使
frameIntervalMs += (targetInterval > frameIntervalMs) ? 2 : -2;
//
frameIntervalMs =
frameIntervalMs.clamp(minFrameIntervalMs, maxFrameIntervalMs);
//
if ((frameIntervalMs - targetInterval).abs() >= 5) {
_rebuildTimers();
}
}
//
if (newFrameIntervalMs != frameIntervalMs) {
frameIntervalMs = newFrameIntervalMs;
//
_syncTimer?.cancel();
_syncTimer =
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
//
_playVideoFrames();
});
// int newFrameIntervalMs = frameIntervalMs;
// if (state.videoBuffer.length < 10 && frameIntervalMs < maxFrameIntervalMs) {
// //
// frameIntervalMs += 5;
// } else if (state.videoBuffer.length > 20 &&
// frameIntervalMs > minFrameIntervalMs) {
// //
// frameIntervalMs -= 5;
// }
// //
// if (newFrameIntervalMs != frameIntervalMs) {
// frameIntervalMs = newFrameIntervalMs;
// //
// _syncTimer?.cancel();
// _syncTimer =
// Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
// //
// _playVideoFrames();
// });
//
// _audioTimer?.cancel();
// _audioTimer =
// Timer.periodic(Duration(milliseconds: audioFrameIntervalMs), (timer) {
// final currentTime = DateTime.now().millisecondsSinceEpoch;
// final elapsedTime = currentTime - _startTime;
//
// //
// if (state.audioBuffer.isNotEmpty &&
// state.audioBuffer.first.durationMs <= elapsedTime) {
// //
// if (state.isOpenVoice.value) {
// _playAudioData(state.audioBuffer.removeAt(0));
// } else {
// //
// //
// //
// state.audioBuffer.removeAt(0);
// }
// }
// });
// }
}
_audioTimer?.cancel();
_audioTimer =
Timer.periodic(Duration(milliseconds: audioFrameIntervalMs), (timer) {
final currentTime = DateTime.now().millisecondsSinceEpoch;
final elapsedTime = currentTime - _startTime;
///
void _monitorFrameStability() {
const stabilityThreshold = 5; //
final currentFps = 1000 / frameIntervalMs;
//
if (state.audioBuffer.isNotEmpty &&
state.audioBuffer.first.durationMs <= elapsedTime) {
//
if (state.isOpenVoice.value) {
_playAudioData(state.audioBuffer.removeAt(0));
} else {
//
//
//
state.audioBuffer.removeAt(0);
}
}
});
if (_lastFewFps.length >= 10) {
_lastFewFps.removeAt(0);
}
_lastFewFps.add(currentFps);
//
if (_lastFewFps.length >= 5) {
double mean = _lastFewFps.reduce((a, b) => a + b) / _lastFewFps.length;
double variance =
_lastFewFps.map((fps) => pow(fps - mean, 2)).reduce((a, b) => a + b) /
_lastFewFps.length;
double stdDev = sqrt(variance);
//
if (stdDev > stabilityThreshold) {
_smoothFrameRate(mean);
}
}
}
///
void _checkNetworkQuality(Timer timer) {
final bufferHealth = state.videoBuffer.length / bufferSize;
if (bufferHealth < 0.3) {
// 30%
//
frameIntervalMs = min(frameIntervalMs + 10, maxFrameIntervalMs);
_rebuildTimers();
} else if (bufferHealth > 0.7) {
// 70%
//
frameIntervalMs = max(frameIntervalMs - 5, minFrameIntervalMs);
_rebuildTimers();
}
}
///
int _calculateTargetInterval() {
const int optimalBufferSize = 15; //
const int bufferTolerance = 5; //
if (state.videoBuffer.length < optimalBufferSize - bufferTolerance) {
//
return (frameIntervalMs * 1.2).round();
} else if (state.videoBuffer.length > optimalBufferSize + bufferTolerance) {
//
return (frameIntervalMs * 0.8).round();
}
return frameIntervalMs;
}
///
void _rebuildTimers() {
//
_syncTimer?.cancel();
_audioTimer?.cancel();
//
_syncTimer =
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
_playVideoFrames();
});
// 使
_audioTimer =
Timer.periodic(Duration(milliseconds: audioFrameIntervalMs), (timer) {
_processAudioFrame();
});
}
///
void _processAudioFrame() {
final currentTime = DateTime.now().millisecondsSinceEpoch;
final elapsedTime = currentTime - _startTime;
while (state.audioBuffer.isNotEmpty &&
state.audioBuffer.first.durationMs <= elapsedTime) {
if (state.isOpenVoice.value) {
_playAudioData(state.audioBuffer.removeAt(0));
} else {
state.audioBuffer.removeAt(0);
}
}
}
@ -404,6 +554,24 @@ class TalkViewLogic extends BaseGetXController {
requestPermissions();
}
///
void _smoothFrameRate(double targetFps) {
//
int targetInterval = (1000 / targetFps).round();
// 使
double weight = 0.3; //
frameIntervalMs =
(frameIntervalMs * (1 - weight) + targetInterval * weight).round();
//
frameIntervalMs =
frameIntervalMs.clamp(minFrameIntervalMs, maxFrameIntervalMs);
//
_rebuildTimers();
}
@override
void onClose() {
_stopPlayG711Data(); //
@ -416,7 +584,13 @@ class TalkViewLogic extends BaseGetXController {
_audioTimer = null; //
state.oneMinuteTimeTimer?.cancel();
state.oneMinuteTimeTimer = null;
//
_networkQualityTimer?.cancel();
_lastFewFps.clear();
stopProcessingAudio();
//
_imageCache.clear();
super.onClose();
}

View File

@ -133,20 +133,16 @@ class _TalkViewPageState extends State<TalkViewPage>
child: SizedBox.expand(
child: RotatedBox(
quarterTurns: -1,
child: Image.memory(
state.listData.value,
width: ScreenUtil().scaleWidth,
height: ScreenUtil().scaleHeight,
gaplessPlayback: true,
fit: BoxFit.cover,
filterQuality: FilterQuality.high,
errorBuilder: (
BuildContext context,
Object error,
StackTrace? stackTrace,
) {
return Container(color: Colors.transparent);
},
child: Obx(
() => state.currentImage.value != null
? RawImage(
image: state.currentImage.value,
width: ScreenUtil().scaleWidth,
height: ScreenUtil().scaleHeight,
fit: BoxFit.cover,
filterQuality: FilterQuality.high,
)
: Container(color: Colors.transparent),
),
),
),

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter_voice_processor/flutter_voice_processor.dart';
@ -89,4 +90,6 @@ class TalkViewState {
RxBool isLongPressing = false.obs; //
RxBool hasAudioData = false.obs; //
RxInt lastAudioTimestamp = 0.obs; //
//
final Rx<ui.Image?> currentImage = Rx<ui.Image?>(null);
}