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; final LockDetailState lockDetailState = Get.put(LockDetailLogic()).state;
Timer? _syncTimer; // Timer? _syncTimer; //
Timer? _audioTimer; // Timer? _audioTimer; //
Timer? _networkQualityTimer; //
int _startTime = 0; // int _startTime = 0; //
int bufferSize = 40; // int bufferSize = 40; //
int audioBufferSize = 500; // int audioBufferSize = 500; //
//
final List<double> _lastFewFps = <double>[]; //
int frameIntervalMs = 45; // 4522FPS int frameIntervalMs = 83; // 8312FPS
int audioFrameIntervalMs = 20; // 4522FPS int audioFrameIntervalMs = 20; // 4522FPS
int minFrameIntervalMs = 30; // 33 FPS int minFrameIntervalMs = 83; // 12 FPS
int maxFrameIntervalMs = 100; // 1 FPS int maxFrameIntervalMs = 166; // 6 FPS
// //
final List<int> _bufferedAudioFrames = <int>[]; final List<int> _bufferedAudioFrames = <int>[];
//
final int maxImageCacheCount = 40; //
final Map<String, ui.Image> _imageCache = {};
/// ///
void _initFlutterPcmSound() { void _initFlutterPcmSound() {
const int sampleRate = 8000; const int sampleRate = 8000;
@ -150,63 +158,205 @@ class TalkViewLogic extends BaseGetXController {
/// ///
void _playVideoData(TalkData talkData) async { 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() { void _startPlayback() {
Future.delayed(Duration(milliseconds: 800), () { Future.delayed(Duration(milliseconds: 800), () {
//
_networkQualityTimer ??=
Timer.periodic(const Duration(seconds: 5), _checkNetworkQuality);
_startTime = DateTime.now().millisecondsSinceEpoch; _startTime = DateTime.now().millisecondsSinceEpoch;
_syncTimer ??= _syncTimer ??=
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) { Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) {
// //
_adjustFrameInterval(); _adjustFrameInterval();
//
_monitorFrameStability();
}); });
}); });
} }
/// ///
void _adjustFrameInterval() { void _adjustFrameInterval() {
int newFrameIntervalMs = frameIntervalMs; //
if (state.videoBuffer.length < 10 && frameIntervalMs < maxFrameIntervalMs) { int targetInterval = _calculateTargetInterval();
//
frameIntervalMs += 5; //
} else if (state.videoBuffer.length > 20 && if (frameIntervalMs != targetInterval) {
frameIntervalMs > minFrameIntervalMs) { // 2ms使
// frameIntervalMs += (targetInterval > frameIntervalMs) ? 2 : -2;
frameIntervalMs -= 5;
//
frameIntervalMs =
frameIntervalMs.clamp(minFrameIntervalMs, maxFrameIntervalMs);
//
if ((frameIntervalMs - targetInterval).abs() >= 5) {
_rebuildTimers();
}
} }
// // int newFrameIntervalMs = frameIntervalMs;
if (newFrameIntervalMs != frameIntervalMs) { // if (state.videoBuffer.length < 10 && frameIntervalMs < maxFrameIntervalMs) {
frameIntervalMs = newFrameIntervalMs; // //
// // frameIntervalMs += 5;
_syncTimer?.cancel(); // } else if (state.videoBuffer.length > 20 &&
_syncTimer = // frameIntervalMs > minFrameIntervalMs) {
Timer.periodic(Duration(milliseconds: frameIntervalMs), (timer) { // //
// // frameIntervalMs -= 5;
_playVideoFrames(); // }
}); // //
// 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 = void _monitorFrameStability() {
Timer.periodic(Duration(milliseconds: audioFrameIntervalMs), (timer) { const stabilityThreshold = 5; //
final currentTime = DateTime.now().millisecondsSinceEpoch; final currentFps = 1000 / frameIntervalMs;
final elapsedTime = currentTime - _startTime;
// if (_lastFewFps.length >= 10) {
if (state.audioBuffer.isNotEmpty && _lastFewFps.removeAt(0);
state.audioBuffer.first.durationMs <= elapsedTime) { }
// _lastFewFps.add(currentFps);
if (state.isOpenVoice.value) {
_playAudioData(state.audioBuffer.removeAt(0)); //
} else { 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) /
state.audioBuffer.removeAt(0); _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(); 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 @override
void onClose() { void onClose() {
_stopPlayG711Data(); // _stopPlayG711Data(); //
@ -416,7 +584,13 @@ class TalkViewLogic extends BaseGetXController {
_audioTimer = null; // _audioTimer = null; //
state.oneMinuteTimeTimer?.cancel(); state.oneMinuteTimeTimer?.cancel();
state.oneMinuteTimeTimer = null; state.oneMinuteTimeTimer = null;
//
_networkQualityTimer?.cancel();
_lastFewFps.clear();
stopProcessingAudio(); stopProcessingAudio();
//
_imageCache.clear();
super.onClose(); super.onClose();
} }

View File

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

View File

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