fix:增加对讲数据发送逻辑

This commit is contained in:
liyi 2024-12-13 14:30:33 +08:00
parent aad509e7b9
commit 3200df7793
7 changed files with 287 additions and 146 deletions

View File

@ -236,9 +236,9 @@ class _StarLockLoginPageState extends State<StarLockLoginPage> {
}
: null)),
SubmitBtn(
btnName: '跳转至html',
btnName: '跳转至通话',
onClick: () {
Get.toNamed(Routers.LocalHtmlPage);
Get.toNamed(Routers.lockMonitoringPage);
},
),
SizedBox(height: 50.w),

View File

@ -16,6 +16,7 @@ import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/main/lockDetail/monitoring/monitoring/star_chart_logic.dart';
import 'package:star_lock/talk/call/callTalk.dart';
import 'package:star_lock/talk/startChart/start_chart_talk_status.dart';
import 'package:star_lock/talk/startChart/webView/h264_web_view.dart';
import 'package:star_lock/talk/udp/udp_manage.dart';
import 'package:star_lock/tools/eventBusEventManage.dart';
import 'package:star_lock/tools/showTFView.dart';
@ -56,62 +57,7 @@ class _LockMonitoringPageState extends State<LockMonitoringPage> {
width: 1.sw,
height: 1.sh,
color: Colors.transparent,
child: Stack(
children: <Widget>[
Image.memory(
state.listPhotoData.value,
gaplessPlayback: true,
width: 1.sw,
height: 1.sh,
fit: BoxFit.cover,
filterQuality: FilterQuality.high,
errorBuilder: (BuildContext context, Object error,
StackTrace? stackTrace) {
return Container(color: Colors.transparent);
},
),
Positioned(
top: ScreenUtil().statusBarHeight + 30.h,
width: 1.sw,
child: Obx(() {
final String sec = (state.oneMinuteTime.value % 60)
.toString()
.padLeft(2, '0');
final String min = (state.oneMinuteTime.value ~/ 60)
.toString()
.padLeft(2, '0');
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('$min:$sec',
style:
TextStyle(fontSize: 26.sp, color: Colors.white)),
],
);
}),
),
Positioned(
bottom: 10.w,
child: Container(
width: 1.sw - 30.w * 2,
margin: EdgeInsets.all(30.w),
decoration: BoxDecoration(
color: const Color(0xC83C3F41),
borderRadius: BorderRadius.circular(20.h),
),
child: Column(
children: <Widget>[
SizedBox(height: 20.h),
buildTopButtons(),
SizedBox(height: 20.h),
buildBottomButtons(),
SizedBox(height: 20.h),
],
),
),
),
],
),
child: _buildTalkView(isMpeg4: false),
),
),
);
@ -356,4 +302,109 @@ class _LockMonitoringPageState extends State<LockMonitoringPage> {
logic.stopProcessing();
state.getTVDataRefreshUIEvent!.cancel();
}
Widget _buildTalkView({required bool isMpeg4}) {
return isMpeg4 ? _buildMpeg4TalkView() : _buildH264TalkView();
}
Widget _buildMpeg4TalkView() {
return Stack(
children: <Widget>[
Image.memory(
state.listPhotoData.value,
gaplessPlayback: true,
width: 1.sw,
height: 1.sh,
fit: BoxFit.cover,
filterQuality: FilterQuality.high,
errorBuilder:
(BuildContext context, Object error, StackTrace? stackTrace) {
return Container(color: Colors.transparent);
},
),
Positioned(
top: ScreenUtil().statusBarHeight + 30.h,
width: 1.sw,
child: Obx(() {
final String sec =
(state.oneMinuteTime.value % 60).toString().padLeft(2, '0');
final String min =
(state.oneMinuteTime.value ~/ 60).toString().padLeft(2, '0');
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('$min:$sec',
style: TextStyle(fontSize: 26.sp, color: Colors.white)),
],
);
}),
),
Positioned(
bottom: 10.w,
child: Container(
width: 1.sw - 30.w * 2,
margin: EdgeInsets.all(30.w),
decoration: BoxDecoration(
color: const Color(0xC83C3F41),
borderRadius: BorderRadius.circular(20.h),
),
child: Column(
children: <Widget>[
SizedBox(height: 20.h),
buildTopButtons(),
SizedBox(height: 20.h),
buildBottomButtons(),
SizedBox(height: 20.h),
],
),
),
),
],
);
}
Widget _buildH264TalkView() {
return Stack(
children: <Widget>[
H264WebView(),
Positioned(
top: ScreenUtil().statusBarHeight + 30.h,
width: 1.sw,
child: Obx(() {
final String sec =
(state.oneMinuteTime.value % 60).toString().padLeft(2, '0');
final String min =
(state.oneMinuteTime.value ~/ 60).toString().padLeft(2, '0');
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('$min:$sec',
style: TextStyle(fontSize: 26.sp, color: Colors.white)),
],
);
}),
),
Positioned(
bottom: 10.w,
child: Container(
width: 1.sw - 30.w * 2,
margin: EdgeInsets.all(30.w),
decoration: BoxDecoration(
color: const Color(0xC83C3F41),
borderRadius: BorderRadius.circular(20.h),
),
child: Column(
children: <Widget>[
SizedBox(height: 20.h),
buildTopButtons(),
SizedBox(height: 20.h),
buildBottomButtons(),
SizedBox(height: 20.h),
],
),
),
),
],
);
}
}

View File

@ -10,7 +10,6 @@ import 'package:star_lock/talk/startChart/proto/generic.pb.dart';
import 'package:star_lock/talk/startChart/proto/talk_data.pb.dart';
import 'package:star_lock/talk/startChart/proto/talk_data.pbserver.dart';
class UdpTalkDataHandler extends ScpMessageBaseHandle
implements ScpMessageHandler {
@override
@ -24,11 +23,10 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
@override
void handleRealTimeData(ScpMessage scpMessage) {
// print('收到音视频数据:$scpMessage');
if (scpMessage.Payload != null) {
final TalkData talkData = scpMessage.Payload;
//
// _handleTalkData(talkData: talkData);
_handleTalkData(talkData: talkData);
//
talkDataOverTimeTimerManager.receiveMessage();
}
@ -55,86 +53,10 @@ class UdpTalkDataHandler extends ScpMessageBaseHandle
void _handleVideoH264(TalkData talkData) {
final List<int> content = talkData.content;
// H.264 NALU
_parseH264Nalus(content);
talkDataRepository.addTalkData(content);
}
void _handleVideoImage(TalkData talkData) {}
void _handleVideoG711(TalkData talkData) {}
// H.264 NALU
void _parseH264Nalus(List<int> h264Stream) {
print('开始解析 H.264 NALU...');
int index = 0;
while (index < h264Stream.length) {
// NALU
int nextStartCodeIndex = findNextStartCode(h264Stream, index);
if (nextStartCodeIndex == -1) {
//
break;
}
// NALU
Uint8List naluData =
Uint8List.fromList(h264Stream.sublist(index, nextStartCodeIndex));
// NALU
int naluType = naluData[0] & 0x1F; // NALU 5
print('找到 NALU类型: $naluType,长度: ${naluData.length}');
// NALU
handleNalu(naluType, naluData);
// NALU
index = nextStartCodeIndex;
}
}
// NALU
int findNextStartCode(List<int> data, int startIndex) {
for (int i = startIndex; i < data.length - 3; i++) {
// 3
if (data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x01) {
return i;
}
// 4
if (i < data.length - 4 &&
data[i] == 0x00 &&
data[i + 1] == 0x00 &&
data[i + 2] == 0x00 &&
data[i + 3] == 0x01) {
return i;
}
}
// -1
return -1;
}
// NALU
void handleNalu(int naluType, Uint8List naluData) {
switch (naluType) {
case 5:
print('IDR 帧 (关键帧)');
break;
case 1:
print('非 IDR 帧 (P 帧)');
break;
case 7:
print('SPS (序列参数集)');
break;
case 8:
print('PPS (图像参数集)');
break;
default:
print('其他 NALU 类型: $naluType');
}
// NALU
// SPS PPS
}
}

View File

@ -1,3 +1,6 @@
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:get/get.dart';
import 'package:star_lock/talk/startChart/entity/scp_message.dart';
@ -5,6 +8,7 @@ import 'package:star_lock/talk/startChart/handle/scp_message_base_handle.dart';
import 'package:star_lock/talk/startChart/handle/scp_message_handle.dart';
import 'package:star_lock/talk/startChart/proto/gateway_reset.pb.dart';
import 'package:star_lock/talk/startChart/proto/generic.pb.dart';
import 'package:star_lock/talk/startChart/proto/talk_data.pb.dart';
import 'package:star_lock/talk/startChart/proto/talk_expect.pb.dart';
import '../../start_chart_manage.dart';
@ -16,6 +20,12 @@ class UdpTalkExpectHandler extends ScpMessageBaseHandle
//
final TalkExpect talkExpect = scpMessage.Payload;
print('收到预期音视频数据请求:$talkExpect');
//
replySuccessMessage(scpMessage);
//
// startChartManage.startTalkDataTimer();
}
@override
@ -28,12 +38,8 @@ class UdpTalkExpectHandler extends ScpMessageBaseHandle
}
@override
void handleInvalidReq(ScpMessage scpMessage) {
}
void handleInvalidReq(ScpMessage scpMessage) {}
@override
void handleRealTimeData(ScpMessage scpMessage) {
}
void handleRealTimeData(ScpMessage scpMessage) {}
}

View File

@ -0,0 +1,30 @@
import 'dart:async';
import 'package:star_lock/talk/startChart/proto/talk_data.pb.dart';
class TalkDataRepository {
//
TalkDataRepository._();
// 使 _instance
static final TalkDataRepository _instance = TalkDataRepository._();
//
static TalkDataRepository get instance => _instance;
// StreamController
final StreamController<List<int>> _talkDataStreamController = StreamController<List<int>>.broadcast();
// Stream
Stream<List<int>> get talkDataStream => _talkDataStreamController.stream;
// TalkData Stream
void addTalkData(List<int> talkData) {
_talkDataStreamController.add(talkData);
}
// StreamController
void dispose() {
_talkDataStreamController.close();
}
}

View File

@ -4,7 +4,9 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/talk/other/audio_player_manager.dart';
import 'package:star_lock/talk/startChart/constant/udp_constant.dart';
import 'package:star_lock/talk/startChart/entity/scp_message.dart';
import 'package:star_lock/talk/startChart/handle/other/overtime_timer_manger.dart';
import 'package:star_lock/talk/startChart/handle/other/talk_data_repository.dart';
import 'package:star_lock/talk/startChart/proto/generic.pb.dart';
import 'package:star_lock/talk/startChart/start_chart_manage.dart';
@ -13,6 +15,9 @@ import 'package:star_lock/talk/startChart/start_chart_talk_status.dart';
class ScpMessageBaseHandle {
final startChartManage = StartChartManage();
//
final TalkDataRepository talkDataRepository = TalkDataRepository.instance;
final audioManager = AudioPlayerManager();
//
@ -25,12 +30,18 @@ class ScpMessageBaseHandle {
timeoutInSeconds: 3,
);
//
void replySuccessMessage(ScpMessage scpMessage){
startChartManage.sendGenericRespSuccessMessage(
ToPeerId: scpMessage.FromPeerId!,
FromPeerId: scpMessage.ToPeerId!,
PayloadType: scpMessage.PayloadType!,
);
}
// StartChartTalkStatus
StartChartTalkStatus talkStatus = StartChartTalkStatus.instance;
bool checkGenericRespSuccess(GenericResp genericResp) {
if (genericResp == null) return false;
final code = genericResp.code;

View File

@ -4,6 +4,7 @@ import 'dart:typed_data';
import 'package:convert/convert.dart';
import 'package:fast_rsa/fast_rsa.dart' as fastRsa;
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:pointycastle/export.dart' as pc;
import 'package:star_lock/app_settings/app_settings.dart';
@ -22,6 +23,7 @@ import 'package:star_lock/talk/startChart/entity/scp_message.dart';
import 'package:star_lock/talk/startChart/entity/star_chart_register_node_entity.dart';
import 'package:star_lock/talk/startChart/handle/scp_message_handle.dart';
import 'package:star_lock/talk/startChart/handle/scp_message_handler_factory.dart';
import 'package:star_lock/talk/startChart/proto/talk_data.pb.dart';
import 'package:star_lock/talk/startChart/proto/talk_expect.pb.dart';
import 'package:star_lock/talk/startChart/start_chart_talk_status.dart';
import 'package:star_lock/tools/baseGetXController.dart';
@ -70,6 +72,8 @@ class StartChartManage {
Timer? talkPingTimer; //
int talkExpectIntervalTime = 1; // (s)
Timer? talkExpectTimer; //
int talkDataIntervalTime = 10; // (ms)
Timer? talkDataTimer; //
//
TalkExpect defaultTalkExpect = TalkExpect(
@ -77,6 +81,9 @@ class StartChartManage {
audioType: [TalkExpect_AudioTypeE.G711],
);
//
TalkData defaultTalkData = TalkData();
String relayPeerId = ''; // peerId
// StartChartTalkStatus
@ -228,6 +235,17 @@ class StartChartManage {
await _sendMessage(message: message);
}
//
Future<void> sendTalkDataMessage({required TalkData talkData}) async {
// 线
final message = MessageCommand.talkDataMessage(
FromPeerId: FromPeerId,
ToPeerId: ToPeerId,
talkData: talkData,
);
await _sendMessage(message: message);
}
//
void _sendHeartbeatMessage() async {
if (_heartBeatTimerRunning) {
@ -764,6 +782,108 @@ class StartChartManage {
);
}
///
void startTalkDataTimer() async {
//
if (talkDataTimer != null) return;
// assets
final ByteData data = await rootBundle.load('assets/talk.h264');
final List<int> byteData = data.buffer.asUint8List();
int current = 0;
int start = 0;
int end = 0;
final List<int> chunks = extractChunks(byteData);
talkDataTimer ??= Timer.periodic(
Duration(
milliseconds: talkDataIntervalTime,
),
(Timer timer) {
if (current >= chunks.length) {
print('数据已经发完');
start = 0;
end = 0;
current = 0;
timer.cancel();
return;
}
// NALU chunks
end = chunks[current];
current++;
List<int> frameData = byteData.sublist(start, end);
if (frameData.length == 0) timer.cancel();
defaultTalkData = TalkData(
content: frameData,
contentType: TalkData_ContentTypeE.H264,
);
start = end;
//
sendTalkDataMessage(talkData: defaultTalkData);
},
);
}
List<int> extractChunks(List<int> byteData) {
int i = 0;
int length = byteData.length;
int naluCount = 0;
int value;
int state = 0;
int lastIndex = 0;
List<int> result = [];
const minNaluPerChunk = 22; // NALU数量
while (i < length) {
value = byteData[i++];
// finding 3 or 4-byte start codes (00 00 01 OR 00 00 00 01)
switch (state) {
case 0:
if (value == 0) {
state = 1;
}
break;
case 1:
if (value == 0) {
state = 2;
} else {
state = 0;
}
break;
case 2:
case 3:
if (value == 0) {
state = 3;
} else if (value == 1 && i < length) {
if (lastIndex > 0) {
naluCount++;
}
if (naluCount >= minNaluPerChunk) {
result.add(lastIndex - state - 1);
naluCount = 0;
}
state = 0;
lastIndex = i;
} else {
state = 0;
}
break;
default:
break;
}
}
if (naluCount > 0) {
result.add(lastIndex);
}
return result;
}
//
void stopTalkDataTimer() {
talkDataTimer?.cancel();
talkDataTimer = null; //
}
//
void stopTalkExpectMessageTimer() {
talkExpectTimer?.cancel();
@ -781,6 +901,7 @@ class StartChartManage {
stopTalkPingMessageTimer();
stopHeartbeat();
stopReStartOnlineStartChartServer();
stopTalkDataTimer();
await Storage.removerRelayInfo();
await Storage.removerStarChartRegisterNodeInfo();
}