优化苹果手机视频对讲后锁版麦克风声音变小
This commit is contained in:
parent
83b3908826
commit
faa00c6bce
@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
|
||||||
import 'dart:ui' as ui;
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@ -89,44 +88,6 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
|||||||
bool _waitingForIFrame = false;
|
bool _waitingForIFrame = false;
|
||||||
|
|
||||||
int? lastDecodedIFrameSeq;
|
int? lastDecodedIFrameSeq;
|
||||||
|
|
||||||
// 新增:缓冲区满载处理相关变量
|
|
||||||
int _consecutiveFullBufferCount = 0; // 连续缓冲区满载次数
|
|
||||||
int _maxConsecutiveFullBufferCount = 3; // 最大连续满载次数,超过此值将采取措施,降低为3以更快响应
|
|
||||||
bool _isAdjustingForBufferFull = false; // 是否正在为缓冲区满载调整
|
|
||||||
|
|
||||||
// 新增:记录最近一次视频画面开始渲染的时间
|
|
||||||
DateTime? _lastVideoRenderTime;
|
|
||||||
|
|
||||||
// 新增:自适应缓冲区大小相关变量
|
|
||||||
int _currentBufferSize = 3; // 当前缓冲区大小
|
|
||||||
int _lastBufferSizeAdjustmentTime = 0; // 上次缓冲区大小调整时间
|
|
||||||
int _bufferSizeAdjustmentCooldown = 2000; // 缓冲区大小调整冷却时间(毫秒)
|
|
||||||
|
|
||||||
// 新增:网络状况评估相关变量
|
|
||||||
int _lastNetworkQualityCheckTime = 0; // 上次网络质量检查时间
|
|
||||||
int _framesProcessedSinceLastCheck = 0; // 自上次检查以来处理的帧数
|
|
||||||
int _framesDroppedSinceLastCheck = 0; // 自上次检查以来丢弃的帧数
|
|
||||||
double _currentNetworkQualityScore = 1.0; // 当前网络质量评分(0.0-1.0, 1.0为最佳)
|
|
||||||
|
|
||||||
// 网络质量评估相关变量
|
|
||||||
List<int> _frameReceiveTimes = []; // 存储帧接收时间戳
|
|
||||||
List<int> _frameSeqList = []; // 存储帧序列号用于计算丢包
|
|
||||||
int _totalFramesReceived = 0; // 总接收帧数
|
|
||||||
int _lostFrames = 0; // 丢失帧数
|
|
||||||
int _lastFrameSeqNum = -1; // 上一帧序列号
|
|
||||||
DateTime? _testStartTime; // 测试开始时间
|
|
||||||
Timer? _networkQualityTestTimer; // 网络质量评估定时器
|
|
||||||
|
|
||||||
// 包、帧和关键帧统计变量
|
|
||||||
int _totalPacketsReceived = 0; // 总接收包数
|
|
||||||
int _totalFramesReceivedCount = 0; // 总接收帧数
|
|
||||||
int _iFramesReceived = 0; // 接收到的关键帧数(I帧)
|
|
||||||
int _pFramesReceived = 0; // 接收到的预测帧数(P帧)
|
|
||||||
int _processedFrames = 0; // 已处理的帧数
|
|
||||||
int _droppedFrames = 0; // 丢弃的帧数
|
|
||||||
int _framesInBuffer = 0; // 进入缓冲区的帧数
|
|
||||||
int _bufferSize = 0; // 缓冲区大小
|
|
||||||
|
|
||||||
// 初始化视频解码器
|
// 初始化视频解码器
|
||||||
Future<void> _initVideoDecoder() async {
|
Future<void> _initVideoDecoder() async {
|
||||||
@ -167,20 +128,16 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
|||||||
height: height,
|
height: height,
|
||||||
codecType: 'h264',
|
codecType: 'h264',
|
||||||
);
|
);
|
||||||
|
AppLog.log('解码器配置的宽高为:${config.width}x${config.height}');
|
||||||
// 初始化解码器并获取textureId
|
// 初始化解码器并获取textureId
|
||||||
final textureId = await VideoDecodePlugin.initDecoder(config);
|
final textureId = await VideoDecodePlugin.initDecoder(config);
|
||||||
if (textureId != null) {
|
if (textureId != null) {
|
||||||
Future.microtask(() => state.textureId.value = textureId);
|
Future.microtask(() => state.textureId.value = textureId);
|
||||||
|
AppLog.log('视频解码器初始化成功:textureId=$textureId');
|
||||||
VideoDecodePlugin.setOnFrameRenderedListener((textureId) {
|
VideoDecodePlugin.setOnFrameRenderedListener((textureId) {
|
||||||
AppLog.log('已经开始渲染=======');
|
AppLog.log('已经开始渲染=======');
|
||||||
// 记录视频开始渲染时间
|
|
||||||
_lastVideoRenderTime = DateTime.now();
|
|
||||||
// 只有真正渲染出首帧时才关闭loading
|
// 只有真正渲染出首帧时才关闭loading
|
||||||
if (state.isLoading.value) {
|
Future.microtask(() => state.isLoading.value = false);
|
||||||
Future.microtask(() => state.isLoading.value = false);
|
|
||||||
} else {
|
|
||||||
AppLog.log('视频已在渲染状态,保持当前状态');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
AppLog.log('视频解码器初始化失败');
|
AppLog.log('视频解码器初始化失败');
|
||||||
@ -286,110 +243,6 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
|||||||
}
|
}
|
||||||
_lastFrameSeq = frameSeq;
|
_lastFrameSeq = frameSeq;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录帧接收
|
|
||||||
recordFrameReceived(frameSeq, frameType);
|
|
||||||
|
|
||||||
// 优化:对于低延迟场景,仅保留最新的帧以减少延迟
|
|
||||||
bool isVideoRendering = state.textureId.value != null &&
|
|
||||||
(state.isLoading.isFalse || _lastVideoRenderTime != null);
|
|
||||||
|
|
||||||
if (isVideoRendering) {
|
|
||||||
// 如果视频已经开始渲染,优先保留最新的I帧和其关联的P帧
|
|
||||||
if (frameType == TalkDataH264Frame_FrameTypeE.I) {
|
|
||||||
// 对于新的I帧,移除旧的I帧和其关联的P帧
|
|
||||||
_removeOldFramesForIFrame(frameSeq);
|
|
||||||
|
|
||||||
// 当接收到新I帧时,动态调整缓冲区大小以适应当前网络状况
|
|
||||||
_adjustBufferSizeForNetworkCondition();
|
|
||||||
} else {
|
|
||||||
// 对于P帧,确保它关联到有效的I帧
|
|
||||||
_cleanOldPFrameForCurrentIFrame(frameSeq, frameSeqI);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 视频未渲染时,按原有逻辑处理
|
|
||||||
if (state.h264FrameBuffer.length >= state.maxFrameBufferSize) {
|
|
||||||
// 检查是否已经有视频开始渲染
|
|
||||||
bool isVideoRendering = state.textureId.value != null &&
|
|
||||||
(state.isLoading.isFalse || _lastVideoRenderTime != null);
|
|
||||||
|
|
||||||
// 如果视频已经在渲染,我们采用更智能的缓冲区清理策略
|
|
||||||
if (isVideoRendering) {
|
|
||||||
_consecutiveFullBufferCount++;
|
|
||||||
|
|
||||||
// 当连续多次缓冲区满载时,采取特殊处理策略
|
|
||||||
if (_consecutiveFullBufferCount >= _maxConsecutiveFullBufferCount) {
|
|
||||||
|
|
||||||
// 计算当前网络质量并调整缓冲区大小
|
|
||||||
_evaluateCurrentNetworkQuality();
|
|
||||||
|
|
||||||
// 查找所有非关键帧(非I帧)进行清理
|
|
||||||
final framesToRemove = <int>[];
|
|
||||||
for (int i = 0; i < state.h264FrameBuffer.length; i++) {
|
|
||||||
if (state.h264FrameBuffer[i]['frameType'] != TalkDataH264Frame_FrameTypeE.I) {
|
|
||||||
framesToRemove.add(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从后往前删除,避免索引变化问题
|
|
||||||
framesToRemove.reversed.forEach((index) {
|
|
||||||
state.h264FrameBuffer.removeAt(index);
|
|
||||||
recordDroppedFrame();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 如果仍有过多帧,保留最新的I帧及相关的P帧
|
|
||||||
if (state.h264FrameBuffer.length > _currentBufferSize ~/ 2) {
|
|
||||||
// 找到最后一个I帧的位置
|
|
||||||
int lastIFrameIndex = -1;
|
|
||||||
for (int i = state.h264FrameBuffer.length - 1; i >= 0; i--) {
|
|
||||||
if (state.h264FrameBuffer[i]['frameType'] == TalkDataH264Frame_FrameTypeE.I) {
|
|
||||||
lastIFrameIndex = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果找到I帧,只保留I帧及其后续的P帧,删除前面的帧
|
|
||||||
if (lastIFrameIndex > 0) {
|
|
||||||
for (int i = 0; i < lastIFrameIndex; i++) {
|
|
||||||
state.h264FrameBuffer.removeAt(0);
|
|
||||||
recordDroppedFrame();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 如果没有I帧,只保留最新的半数帧
|
|
||||||
while (state.h264FrameBuffer.length > _currentBufferSize ~/ 2) {
|
|
||||||
state.h264FrameBuffer.removeAt(0);
|
|
||||||
recordDroppedFrame();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_consecutiveFullBufferCount = 0; // 重置计数
|
|
||||||
_isAdjustingForBufferFull = false;
|
|
||||||
} else {
|
|
||||||
// 非连续满载情况下,采用保守清理策略
|
|
||||||
int pbIndex = state.h264FrameBuffer.indexWhere((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.P);
|
|
||||||
if (pbIndex != -1) {
|
|
||||||
state.h264FrameBuffer.removeAt(pbIndex);
|
|
||||||
recordDroppedFrame(); // 记录丢弃的帧
|
|
||||||
} else {
|
|
||||||
state.h264FrameBuffer.removeAt(0);
|
|
||||||
recordDroppedFrame(); // 记录丢弃的帧
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 视频尚未渲染时,使用原始策略
|
|
||||||
int pbIndex = state.h264FrameBuffer.indexWhere((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.P);
|
|
||||||
if (pbIndex != -1) {
|
|
||||||
state.h264FrameBuffer.removeAt(pbIndex);
|
|
||||||
recordDroppedFrame(); // 记录丢弃的帧
|
|
||||||
} else {
|
|
||||||
state.h264FrameBuffer.removeAt(0);
|
|
||||||
recordDroppedFrame(); // 记录丢弃的帧
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建包含帧数据和类型的Map
|
// 创建包含帧数据和类型的Map
|
||||||
final Map<String, dynamic> frameMap = {
|
final Map<String, dynamic> frameMap = {
|
||||||
'frameData': frameData,
|
'frameData': frameData,
|
||||||
@ -400,169 +253,18 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
|||||||
'scpMessage': scpMessage,
|
'scpMessage': scpMessage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 如果缓冲区超出最大大小,优先丢弃P/B帧
|
||||||
|
while (state.h264FrameBuffer.length >= state.maxFrameBufferSize) {
|
||||||
|
int pbIndex = state.h264FrameBuffer.indexWhere((f) => f['frameType'] == TalkDataH264Frame_FrameTypeE.P);
|
||||||
|
if (pbIndex != -1) {
|
||||||
|
state.h264FrameBuffer.removeAt(pbIndex);
|
||||||
|
} else {
|
||||||
|
state.h264FrameBuffer.removeAt(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 将帧添加到缓冲区
|
// 将帧添加到缓冲区
|
||||||
state.h264FrameBuffer.add(frameMap);
|
state.h264FrameBuffer.add(frameMap);
|
||||||
recordFrameInBuffer(); // 记录进入缓冲区的帧
|
|
||||||
|
|
||||||
// 重置连续满载计数(只有在缓冲区未满时)
|
|
||||||
if (state.h264FrameBuffer.length < state.maxFrameBufferSize) {
|
|
||||||
_consecutiveFullBufferCount = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助方法:移除旧的I帧和其关联的P帧
|
|
||||||
void _removeOldFramesForIFrame(int newIFrameSeq) {
|
|
||||||
// 保留最新的I帧和相关的P帧,移除旧的I帧和其关联的P帧
|
|
||||||
final List<Map<String, dynamic>> framesToKeep = [];
|
|
||||||
|
|
||||||
// 分别收集I帧和P帧
|
|
||||||
final List<Map<String, dynamic>> iFrames = [];
|
|
||||||
final List<Map<String, dynamic>> pFrames = [];
|
|
||||||
|
|
||||||
for (var frame in state.h264FrameBuffer) {
|
|
||||||
if (frame['frameType'] == TalkDataH264Frame_FrameTypeE.I) {
|
|
||||||
iFrames.add(frame);
|
|
||||||
} else {
|
|
||||||
pFrames.add(frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按frameSeq排序I帧
|
|
||||||
iFrames.sort((a, b) => (a['frameSeq'] as int).compareTo(b['frameSeq'] as int));
|
|
||||||
|
|
||||||
// 保留最新的I帧(即当前正在添加的新I帧)
|
|
||||||
final int maxIFramesToKeep = 2; // 保留最多2个I帧以确保平滑过渡
|
|
||||||
final int startIdx = max(0, iFrames.length - maxIFramesToKeep);
|
|
||||||
|
|
||||||
for (int i = startIdx; i < iFrames.length; i++) {
|
|
||||||
framesToKeep.add(iFrames[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 对于P帧,只保留与保留的I帧关联的P帧
|
|
||||||
for (var pFrame in pFrames) {
|
|
||||||
int refIFrameSeq = pFrame['frameSeqI'];
|
|
||||||
bool shouldKeep = false;
|
|
||||||
|
|
||||||
// 检查该P帧引用的I帧是否被保留
|
|
||||||
for (var keptIFrame in framesToKeep) {
|
|
||||||
if (keptIFrame['frameType'] == TalkDataH264Frame_FrameTypeE.I &&
|
|
||||||
keptIFrame['frameSeq'] == refIFrameSeq) {
|
|
||||||
shouldKeep = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldKeep) {
|
|
||||||
framesToKeep.add(pFrame);
|
|
||||||
} else {
|
|
||||||
recordDroppedFrame(); // 记录丢弃的帧
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 用筛选后的帧替换原缓冲区
|
|
||||||
state.h264FrameBuffer.clear();
|
|
||||||
state.h264FrameBuffer.addAll(framesToKeep);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助方法:清理当前I帧的旧P帧
|
|
||||||
void _cleanOldPFrameForCurrentIFrame(int frameSeq, int frameSeqI) {
|
|
||||||
// 为当前I帧保留最新的P帧,移除过旧的P帧
|
|
||||||
final List<Map<String, dynamic>> framesToKeep = [];
|
|
||||||
final List<Map<String, dynamic>> framesToRemove = [];
|
|
||||||
|
|
||||||
// 首先添加所有非当前I帧引用的帧
|
|
||||||
for (var frame in state.h264FrameBuffer) {
|
|
||||||
if (frame['frameSeqI'] != frameSeqI || frame['frameType'] == TalkDataH264Frame_FrameTypeE.I) {
|
|
||||||
framesToKeep.add(frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 收集当前I帧引用的P帧
|
|
||||||
final List<Map<String, dynamic>> currentIFramePFrames = [];
|
|
||||||
for (var frame in state.h264FrameBuffer) {
|
|
||||||
if (frame['frameSeqI'] == frameSeqI && frame['frameType'] == TalkDataH264Frame_FrameTypeE.P) {
|
|
||||||
currentIFramePFrames.add(frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按frameSeq排序P帧
|
|
||||||
currentIFramePFrames.sort((a, b) => (a['frameSeq'] as int).compareTo(b['frameSeq'] as int));
|
|
||||||
|
|
||||||
// 保留最新的P帧(例如保留最近的5个)
|
|
||||||
final int maxPFramesToKeep = 5;
|
|
||||||
final int startIdx = max(0, currentIFramePFrames.length - maxPFramesToKeep);
|
|
||||||
|
|
||||||
for (int i = startIdx; i < currentIFramePFrames.length; i++) {
|
|
||||||
framesToKeep.add(currentIFramePFrames[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 记录被丢弃的P帧
|
|
||||||
for (int i = 0; i < startIdx; i++) {
|
|
||||||
recordDroppedFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 用筛选后的帧替换原缓冲区
|
|
||||||
state.h264FrameBuffer.clear();
|
|
||||||
state.h264FrameBuffer.addAll(framesToKeep);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 根据网络状况动态调整缓冲区大小
|
|
||||||
void _adjustBufferSizeForNetworkCondition() {
|
|
||||||
// 获取当前时间
|
|
||||||
int currentTime = DateTime.now().millisecondsSinceEpoch;
|
|
||||||
|
|
||||||
// 检查是否在冷却期内
|
|
||||||
if (currentTime - _lastBufferSizeAdjustmentTime < _bufferSizeAdjustmentCooldown) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新最后调整时间
|
|
||||||
_lastBufferSizeAdjustmentTime = currentTime;
|
|
||||||
|
|
||||||
// 根据当前网络质量评分调整缓冲区大小
|
|
||||||
if (_currentNetworkQualityScore > 0.7) {
|
|
||||||
// 网络状况良好,使用较小缓冲区以降低延迟
|
|
||||||
_currentBufferSize = state.adaptiveBufferSizeMin;
|
|
||||||
} else if (_currentNetworkQualityScore > 0.4) {
|
|
||||||
// 网络状况一般,使用中等缓冲区以平衡延迟和稳定性
|
|
||||||
_currentBufferSize = (state.adaptiveBufferSizeMin + state.adaptiveBufferSizeMax) ~/ 2;
|
|
||||||
} else {
|
|
||||||
// 网络状况较差,使用较大缓冲区以减少卡顿
|
|
||||||
_currentBufferSize = state.adaptiveBufferSizeMax;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppLog.log('根据网络状况调整缓冲区大小: ${_currentBufferSize} (当前网络质量评分: ${_currentNetworkQualityScore.toStringAsFixed(2)})');
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 评估当前网络质量
|
|
||||||
void _evaluateCurrentNetworkQuality() {
|
|
||||||
int currentTime = DateTime.now().millisecondsSinceEpoch;
|
|
||||||
|
|
||||||
// 检查是否到了网络质量检查间隔
|
|
||||||
if (currentTime - _lastNetworkQualityCheckTime < state.networkQualityCheckIntervalMs) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_lastNetworkQualityCheckTime = currentTime;
|
|
||||||
|
|
||||||
// 计算丢帧率
|
|
||||||
double dropRate = 0.0;
|
|
||||||
int totalProcessed = _framesProcessedSinceLastCheck + _framesDroppedSinceLastCheck;
|
|
||||||
if (totalProcessed > 0) {
|
|
||||||
dropRate = _framesDroppedSinceLastCheck / totalProcessed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新网络质量评分 (基于丢帧率,评分越高网络越好)
|
|
||||||
_currentNetworkQualityScore = 1.0 - dropRate;
|
|
||||||
if (_currentNetworkQualityScore < 0) {
|
|
||||||
_currentNetworkQualityScore = 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppLog.log('网络质量评估: 丢帧率=${dropRate.toStringAsFixed(2)}, 网络质量评分=${_currentNetworkQualityScore.toStringAsFixed(2)}');
|
|
||||||
|
|
||||||
// 重置计数器
|
|
||||||
_framesProcessedSinceLastCheck = 0;
|
|
||||||
_framesDroppedSinceLastCheck = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 启动帧处理定时器
|
/// 启动帧处理定时器
|
||||||
@ -1282,215 +984,4 @@ class TalkViewNativeDecodeLogic extends BaseGetXController {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 记录帧接收
|
|
||||||
void recordFrameReceived(int frameSeq, TalkDataH264Frame_FrameTypeE frameType) {
|
|
||||||
_totalFramesReceivedCount++;
|
|
||||||
|
|
||||||
// 根据帧类型更新计数
|
|
||||||
if (frameType == TalkDataH264Frame_FrameTypeE.I) {
|
|
||||||
_iFramesReceived++;
|
|
||||||
} else if (frameType == TalkDataH264Frame_FrameTypeE.P) {
|
|
||||||
_pFramesReceived++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算丢包 - 修复:添加合理的帧序号差异限制,避免异常高的丢包计算
|
|
||||||
if (_lastFrameSeqNum != -1) {
|
|
||||||
if (frameSeq > _lastFrameSeqNum + 1) {
|
|
||||||
// 计算中间缺失的帧数,但限制最大丢失数量以防止异常值
|
|
||||||
int gap = frameSeq - _lastFrameSeqNum - 1;
|
|
||||||
// 设置最大允许的帧间隙,防止因为帧序列号回绕或异常导致的错误计算
|
|
||||||
int maxAllowedGap = 50; // 可根据实际情况调整
|
|
||||||
if (gap > maxAllowedGap) {
|
|
||||||
// 如果间隙过大,可能是帧序列号回绕或其他异常,不计入丢失帧
|
|
||||||
AppLog.log('检测到帧序列号异常跳跃: gap=$gap, 当前frameSeq=$frameSeq, 上一个frameSeq=$_lastFrameSeqNum');
|
|
||||||
} else {
|
|
||||||
_lostFrames += gap;
|
|
||||||
}
|
|
||||||
} else if (frameSeq <= _lastFrameSeqNum && frameSeq < 100 && _lastFrameSeqNum > 1000) {
|
|
||||||
// 检测到帧序列号回绕(从大数值回到小数值),重置参考值
|
|
||||||
// 这种情况通常发生在新的视频流开始或流重启
|
|
||||||
AppLog.log('检测到帧序列号回绕: 从 $_lastFrameSeqNum 回到 $frameSeq');
|
|
||||||
_lastFrameSeqNum = frameSeq;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_lastFrameSeqNum = frameSeq;
|
|
||||||
|
|
||||||
// 记录接收时间
|
|
||||||
_frameReceiveTimes.add(DateTime.now().millisecondsSinceEpoch);
|
|
||||||
_frameSeqList.add(frameSeq);
|
|
||||||
|
|
||||||
// 只保留最近的100个帧记录
|
|
||||||
if (_frameReceiveTimes.length > 100) {
|
|
||||||
_frameReceiveTimes.removeAt(0);
|
|
||||||
_frameSeqList.removeAt(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 记录包接收
|
|
||||||
void recordPacketReceived() {
|
|
||||||
_totalPacketsReceived++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 记录已处理的帧
|
|
||||||
void recordProcessedFrame() {
|
|
||||||
_processedFrames++;
|
|
||||||
_framesProcessedSinceLastCheck++; // 用于网络质量评估
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 记录丢弃的帧
|
|
||||||
void recordDroppedFrame() {
|
|
||||||
_droppedFrames++;
|
|
||||||
_framesDroppedSinceLastCheck++; // 用于网络质量评估
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 记录进入缓冲区的帧
|
|
||||||
void recordFrameInBuffer() {
|
|
||||||
_framesInBuffer++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 开始网络质量评估
|
|
||||||
void startNetworkQualityAssessment() {
|
|
||||||
resetNetworkQualityAssessmentVariables();
|
|
||||||
_testStartTime = DateTime.now();
|
|
||||||
|
|
||||||
// 初始化统计计数器
|
|
||||||
_bufferSize = state.maxFrameBufferSize; // 记录缓冲区大小
|
|
||||||
|
|
||||||
// 启动定时器每秒评估一次网络质量
|
|
||||||
_networkQualityTestTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
|
||||||
_evaluateNetworkQuality();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 停止网络质量评估
|
|
||||||
void stopNetworkQualityAssessment() {
|
|
||||||
_networkQualityTestTimer?.cancel();
|
|
||||||
_networkQualityTestTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 重置网络质量评估变量
|
|
||||||
void resetNetworkQualityAssessmentVariables() {
|
|
||||||
_totalFramesReceived = 0;
|
|
||||||
_lostFrames = 0;
|
|
||||||
_lastFrameSeqNum = -1;
|
|
||||||
_frameReceiveTimes.clear();
|
|
||||||
_frameSeqList.clear();
|
|
||||||
_testStartTime = null;
|
|
||||||
|
|
||||||
// 重置统计计数器
|
|
||||||
_totalPacketsReceived = 0;
|
|
||||||
_totalFramesReceivedCount = 0;
|
|
||||||
_iFramesReceived = 0;
|
|
||||||
_pFramesReceived = 0;
|
|
||||||
_processedFrames = 0;
|
|
||||||
_droppedFrames = 0;
|
|
||||||
_framesInBuffer = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 评估网络质量
|
|
||||||
void _evaluateNetworkQuality() {
|
|
||||||
if (_testStartTime == null) return;
|
|
||||||
|
|
||||||
final elapsed = DateTime.now().difference(_testStartTime!).inSeconds;
|
|
||||||
if (elapsed == 0) return;
|
|
||||||
|
|
||||||
// 计算丢包率 - 确保丢包率不会超过100%
|
|
||||||
double lossRate = 0.0;
|
|
||||||
if (_totalFramesReceivedCount > 0) {
|
|
||||||
lossRate = (_lostFrames / _totalFramesReceivedCount) * 100;
|
|
||||||
// 限制丢包率不超过100%
|
|
||||||
if (lossRate > 100.0) {
|
|
||||||
lossRate = 100.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 计算抖动 (基于相邻帧的时间间隔变化)
|
|
||||||
if (_frameReceiveTimes.length >= 2) {
|
|
||||||
List<int> intervals = [];
|
|
||||||
for (int i = 1; i < _frameReceiveTimes.length; i++) {
|
|
||||||
intervals.add(_frameReceiveTimes[i] - _frameReceiveTimes[i-1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据网络质量动态调整缓冲区大小
|
|
||||||
_adjustBufferSizeBasedOnNetworkQuality();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 根据丢包率、帧率和抖动确定网络质量等级
|
|
||||||
String _getNetworkQualityLevel(double lossRate, double frameRate, double jitter) {
|
|
||||||
if (lossRate < 1.0 && frameRate > 15.0 && jitter < 50.0) {
|
|
||||||
return "优秀";
|
|
||||||
} else if (lossRate < 3.0 && frameRate > 10.0 && jitter < 100.0) {
|
|
||||||
return "良好";
|
|
||||||
} else if (lossRate < 5.0 && frameRate > 5.0 && jitter < 200.0) {
|
|
||||||
return "一般";
|
|
||||||
} else {
|
|
||||||
return "较差";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 预测视频卡顿概率
|
|
||||||
String _predictStutterProbability(double lossRate, double frameRate, double jitter) {
|
|
||||||
if (lossRate < 2.0 && frameRate > 10.0 && jitter < 100.0) {
|
|
||||||
return "低风险 - 视频流畅";
|
|
||||||
} else if (lossRate <= 5.0 && frameRate >= 5.0 && jitter <= 200.0) {
|
|
||||||
return "中风险 - 可能轻微卡顿";
|
|
||||||
} else {
|
|
||||||
return "高风险 - 可能严重卡顿";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 根据网络质量动态调整缓冲区大小
|
|
||||||
void _adjustBufferSizeBasedOnNetworkQuality() {
|
|
||||||
if (_testStartTime == null) return;
|
|
||||||
|
|
||||||
final elapsed = DateTime.now().difference(_testStartTime!).inSeconds;
|
|
||||||
if (elapsed == 0) return;
|
|
||||||
|
|
||||||
// 计算丢包率
|
|
||||||
double lossRate = 0.0;
|
|
||||||
if (_totalFramesReceivedCount > 0) {
|
|
||||||
lossRate = (_lostFrames / _totalFramesReceivedCount) * 100;
|
|
||||||
if (lossRate > 100.0) lossRate = 100.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算平均接收帧率
|
|
||||||
final avgFrameRate = _totalFramesReceivedCount / elapsed;
|
|
||||||
|
|
||||||
// 计算抖动
|
|
||||||
double jitter = 0.0;
|
|
||||||
if (_frameReceiveTimes.length >= 2) {
|
|
||||||
List<int> intervals = [];
|
|
||||||
for (int i = 1; i < _frameReceiveTimes.length; i++) {
|
|
||||||
intervals.add(_frameReceiveTimes[i] - _frameReceiveTimes[i-1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (intervals.length > 1) {
|
|
||||||
double mean = intervals.reduce((a, b) => a + b) / intervals.length;
|
|
||||||
double variance = 0.0;
|
|
||||||
for (int interval in intervals) {
|
|
||||||
variance += pow(interval - mean, 2).toDouble();
|
|
||||||
}
|
|
||||||
variance /= intervals.length;
|
|
||||||
jitter = sqrt(variance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据网络质量调整缓冲区大小
|
|
||||||
if (lossRate < 2.0 && avgFrameRate > 15.0 && jitter < 50.0) {
|
|
||||||
// 网络质量优秀 - 使用最小缓冲区以降低延迟
|
|
||||||
_currentBufferSize = state.adaptiveBufferSizeMin;
|
|
||||||
} else if (lossRate < 5.0 && avgFrameRate > 10.0 && jitter < 100.0) {
|
|
||||||
// 网络质量良好 - 使用中等偏小缓冲区
|
|
||||||
_currentBufferSize = (state.adaptiveBufferSizeMin + state.adaptiveBufferSizeMax) ~/ 3;
|
|
||||||
} else if (lossRate < 10.0 && avgFrameRate > 5.0 && jitter < 200.0) {
|
|
||||||
// 网络质量一般 - 使用中等偏大缓冲区
|
|
||||||
_currentBufferSize = ((state.adaptiveBufferSizeMin + state.adaptiveBufferSizeMax) ~/ 2 + state.adaptiveBufferSizeMax) ~/ 2;
|
|
||||||
} else {
|
|
||||||
// 网络质量差 - 使用最大缓冲区以减少卡顿
|
|
||||||
_currentBufferSize = state.adaptiveBufferSizeMax;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -646,7 +646,15 @@ class TalkViewLogic extends BaseGetXController {
|
|||||||
|
|
||||||
// 音频帧处理
|
// 音频帧处理
|
||||||
Future<void> _onFrame(List<int> frame) async {
|
Future<void> _onFrame(List<int> frame) async {
|
||||||
final applyGain = _applyGain(frame, 1.6);
|
// 根据平台调整增益,避免iOS端发送音频过强导致锁端接收音量相对变小
|
||||||
|
double gainFactor = 1.0; // 默认无增益
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
gainFactor = 1.2; // Android端适当增强
|
||||||
|
} else if (Platform.isIOS) {
|
||||||
|
gainFactor = 0.8; // iOS端降低增益,避免锁端接收音量过大而使回传音量显得过小
|
||||||
|
}
|
||||||
|
|
||||||
|
final applyGain = _applyGain(frame, gainFactor);
|
||||||
|
|
||||||
// 编码为G711数据
|
// 编码为G711数据
|
||||||
List<int> encodedData = G711Tool.encode(applyGain, 0); // 0表示A-law
|
List<int> encodedData = G711Tool.encode(applyGain, 0); // 0表示A-law
|
||||||
|
|||||||
@ -445,8 +445,15 @@ class H264WebViewLogic extends BaseGetXController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 首先应用固定增益提升基础音量
|
// 根据平台调整增益,避免iOS端发送音频过强导致锁端接收音量相对变小
|
||||||
List<int> amplifiedFrame = _applyGain(frame, 1.8);
|
double gainFactor = 1.0; // 默认无增益
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
gainFactor = 1.2; // Android端适当增强
|
||||||
|
} else if (Platform.isIOS) {
|
||||||
|
gainFactor = 0.8; // iOS端降低增益,避免锁端接收音量过大而使回传音量显得过小
|
||||||
|
}
|
||||||
|
|
||||||
|
List<int> amplifiedFrame = _applyGain(frame, gainFactor);
|
||||||
// 编码为G711数据
|
// 编码为G711数据
|
||||||
List<int> encodedData = G711Tool.encode(amplifiedFrame, 0); // 0表示A-law
|
List<int> encodedData = G711Tool.encode(amplifiedFrame, 0); // 0表示A-law
|
||||||
_bufferedAudioFrames.addAll(encodedData);
|
_bufferedAudioFrames.addAll(encodedData);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user