PlayServiceImpl.java 69 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287
  1. package com.genersoft.iot.vmp.service.impl;
  2. import com.alibaba.fastjson2.JSON;
  3. import com.alibaba.fastjson2.JSONArray;
  4. import com.alibaba.fastjson2.JSONObject;
  5. import com.genersoft.iot.vmp.common.StreamInfo;
  6. import com.genersoft.iot.vmp.conf.DynamicTask;
  7. import com.genersoft.iot.vmp.conf.SipConfig;
  8. import com.genersoft.iot.vmp.conf.UserSetting;
  9. import com.genersoft.iot.vmp.conf.exception.ControllerException;
  10. import com.genersoft.iot.vmp.conf.exception.ServiceException;
  11. import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
  12. import com.genersoft.iot.vmp.gb28181.bean.*;
  13. import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
  14. import com.genersoft.iot.vmp.gb28181.session.AudioBroadcastManager;
  15. import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
  16. import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
  17. import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
  18. import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
  19. import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
  20. import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
  21. import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils;
  22. import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
  23. import com.genersoft.iot.vmp.media.zlm.ZLMRTPServerFactory;
  24. import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
  25. import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
  26. import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForRtpServerTimeout;
  27. import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
  28. import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  29. import com.genersoft.iot.vmp.service.IDeviceService;
  30. import com.genersoft.iot.vmp.service.IMediaServerService;
  31. import com.genersoft.iot.vmp.service.IMediaService;
  32. import com.genersoft.iot.vmp.service.IPlayService;
  33. import com.genersoft.iot.vmp.service.bean.*;
  34. import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
  35. import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
  36. import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
  37. import com.genersoft.iot.vmp.utils.DateUtil;
  38. import com.genersoft.iot.vmp.utils.redis.RedisUtil;
  39. import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
  40. import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
  41. import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
  42. import com.genersoft.iot.vmp.vmanager.gb28181.play.bean.AudioBroadcastEvent;
  43. import gov.nist.javax.sip.message.SIPResponse;
  44. import org.slf4j.Logger;
  45. import org.slf4j.LoggerFactory;
  46. import org.springframework.beans.factory.annotation.Autowired;
  47. import org.springframework.beans.factory.annotation.Qualifier;
  48. import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
  49. import org.springframework.stereotype.Service;
  50. import org.springframework.util.ObjectUtils;
  51. import org.springframework.web.context.request.async.DeferredResult;
  52. import javax.sip.InvalidArgumentException;
  53. import javax.sip.ResponseEvent;
  54. import javax.sip.SipException;
  55. import javax.sip.header.CallIdHeader;
  56. import java.math.BigDecimal;
  57. import java.math.RoundingMode;
  58. import java.text.ParseException;
  59. import java.util.HashMap;
  60. import java.util.List;
  61. import java.util.Map;
  62. import java.util.UUID;
  63. @SuppressWarnings(value = {"rawtypes", "unchecked"})
  64. @Service
  65. public class PlayServiceImpl implements IPlayService {
  66. private final static Logger logger = LoggerFactory.getLogger(PlayServiceImpl.class);
  67. @Autowired
  68. private IVideoManagerStorage storager;
  69. @Autowired
  70. private SIPCommander cmder;
  71. @Autowired
  72. private AudioBroadcastManager audioBroadcastManager;
  73. @Autowired
  74. private IDeviceService deviceService;
  75. @Autowired
  76. private ISIPCommanderForPlatform sipCommanderFroPlatform;
  77. @Autowired
  78. private IRedisCatchStorage redisCatchStorage;
  79. @Autowired
  80. private ZLMRTPServerFactory zlmrtpServerFactory;
  81. @Autowired
  82. private DeferredResultHolder resultHolder;
  83. @Autowired
  84. private ZLMRESTfulUtils zlmresTfulUtils;
  85. @Autowired
  86. private AssistRESTfulUtils assistRESTfulUtils;
  87. @Autowired
  88. private IMediaService mediaService;
  89. @Autowired
  90. private IMediaServerService mediaServerService;
  91. @Autowired
  92. private VideoStreamSessionManager streamSession;
  93. @Autowired
  94. private UserSetting userSetting;
  95. @Autowired
  96. private SipConfig sipConfig;
  97. @Autowired
  98. private DynamicTask dynamicTask;
  99. @Autowired
  100. private ZlmHttpHookSubscribe subscribe;
  101. @Autowired
  102. private ISIPCommanderForPlatform commanderForPlatform;
  103. @Qualifier("taskExecutor")
  104. @Autowired
  105. private ThreadPoolTaskExecutor taskExecutor;
  106. @Autowired
  107. private RedisGbPlayMsgListener redisGbPlayMsgListener;
  108. @Autowired
  109. private ZlmHttpHookSubscribe hookSubscribe;
  110. @Override
  111. public void play(MediaServerItem mediaServerItem, String deviceId, String channelId,
  112. ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
  113. Runnable timeoutCallback) {
  114. if (mediaServerItem == null) {
  115. throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm");
  116. }
  117. String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId;
  118. RequestMessage msg = new RequestMessage();
  119. msg.setKey(key);
  120. Device device = redisCatchStorage.getDevice(deviceId);
  121. StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
  122. if (streamInfo != null) {
  123. String streamId = streamInfo.getStream();
  124. if (streamId == null) {
  125. WVPResult wvpResult = new WVPResult();
  126. wvpResult.setCode(ErrorCode.ERROR100.getCode());
  127. wvpResult.setMsg("点播失败, redis缓存streamId等于null");
  128. msg.setData(wvpResult);
  129. resultHolder.invokeAllResult(msg);
  130. return;
  131. }
  132. String mediaServerId = streamInfo.getMediaServerId();
  133. MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
  134. JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId);
  135. if (rtpInfo.getInteger("code") == 0) {
  136. if (rtpInfo.getBoolean("exist")) {
  137. int localPort = rtpInfo.getInteger("local_port");
  138. if (localPort == 0) {
  139. logger.warn("[点播],点播时发现rtpServerC存在,但是尚未开始推流");
  140. // 此时说明rtpServer已经创建但是流还没有推上来
  141. WVPResult wvpResult = new WVPResult();
  142. wvpResult.setCode(ErrorCode.ERROR100.getCode());
  143. wvpResult.setMsg("点播已经在进行中,请稍候重试");
  144. msg.setData(wvpResult);
  145. resultHolder.invokeAllResult(msg);
  146. return;
  147. } else {
  148. WVPResult wvpResult = new WVPResult();
  149. wvpResult.setCode(ErrorCode.SUCCESS.getCode());
  150. wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
  151. wvpResult.setData(streamInfo);
  152. msg.setData(wvpResult);
  153. resultHolder.invokeAllResult(msg);
  154. if (hookEvent != null) {
  155. hookEvent.response(mediaServerItem, JSON.parseObject(JSON.toJSONString(streamInfo)));
  156. }
  157. }
  158. } else {
  159. redisCatchStorage.stopPlay(streamInfo);
  160. storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
  161. streamInfo = null;
  162. }
  163. } else {
  164. //zlm连接失败
  165. redisCatchStorage.stopPlay(streamInfo);
  166. storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
  167. streamInfo = null;
  168. }
  169. }
  170. if (streamInfo == null) {
  171. String streamId = null;
  172. if (mediaServerItem.isRtpEnable()) {
  173. streamId = String.format("%s_%s", device.getDeviceId(), channelId);
  174. }
  175. SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false);
  176. if (ssrcInfo == null) {
  177. WVPResult wvpResult = new WVPResult();
  178. wvpResult.setCode(ErrorCode.ERROR100.getCode());
  179. wvpResult.setMsg("开启收流失败");
  180. msg.setData(wvpResult);
  181. resultHolder.invokeAllResult(msg);
  182. return;
  183. }
  184. play(mediaServerItem, ssrcInfo, device, channelId, (mediaServerItemInUse, response) -> {
  185. if (hookEvent != null) {
  186. hookEvent.response(mediaServerItem, response);
  187. }
  188. }, event -> {
  189. // sip error错误
  190. WVPResult wvpResult = new WVPResult();
  191. wvpResult.setCode(ErrorCode.ERROR100.getCode());
  192. wvpResult.setMsg(String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg));
  193. msg.setData(wvpResult);
  194. resultHolder.invokeAllResult(msg);
  195. if (errorEvent != null) {
  196. errorEvent.response(event);
  197. }
  198. }, (code, msgStr) -> {
  199. // invite点播超时
  200. WVPResult wvpResult = new WVPResult();
  201. wvpResult.setCode(ErrorCode.ERROR100.getCode());
  202. if (code == 0) {
  203. wvpResult.setMsg("点播超时,请稍候重试");
  204. } else if (code == 1) {
  205. wvpResult.setMsg("收流超时,请稍候重试");
  206. }
  207. msg.setData(wvpResult);
  208. // 回复之前所有的点播请求
  209. resultHolder.invokeAllResult(msg);
  210. });
  211. }
  212. }
  213. @Override
  214. public void talk(MediaServerItem mediaServerItem, Device device, String channelId,
  215. ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
  216. Runnable timeoutCallback) {
  217. String streamId = null;
  218. if (mediaServerItem.isRtpEnable()) {
  219. streamId = String.format("%s_%s", device.getDeviceId(), channelId);
  220. }
  221. SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false);
  222. logger.info("[对讲开始] deviceId: {}, channelId: {},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, ssrcInfo.getPort(), device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
  223. // 超时处理
  224. String timeOutTaskKey = UUID.randomUUID().toString();
  225. SSRCInfo finalSsrcInfo = ssrcInfo;
  226. System.out.println("设置超时任务: " + timeOutTaskKey);
  227. dynamicTask.startDelay(timeOutTaskKey, () -> {
  228. logger.info("[对讲超时] 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", device.getDeviceId(), channelId, finalSsrcInfo.getPort(), finalSsrcInfo.getSsrc());
  229. timeoutCallback.run();
  230. // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
  231. try {
  232. cmder.streamByeCmd(device, channelId, finalSsrcInfo.getStream(), null);
  233. } catch (InvalidArgumentException | ParseException | SipException e) {
  234. logger.error("[对讲超时], 发送BYE失败 {}", e.getMessage());
  235. } catch (SsrcTransactionNotFoundException e) {
  236. timeoutCallback.run();
  237. mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc());
  238. mediaServerService.closeRTPServer(mediaServerItem, finalSsrcInfo.getStream());
  239. streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
  240. }
  241. }, userSetting.getPlayTimeout());
  242. final String ssrc = ssrcInfo.getSsrc();
  243. final String stream = ssrcInfo.getStream();
  244. //端口获取失败的ssrcInfo 没有必要发送点播指令
  245. if (ssrcInfo.getPort() <= 0) {
  246. logger.info("[对讲] 端口分配异常,deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo);
  247. return;
  248. }
  249. String callId = SipUtils.getNewCallId();
  250. boolean pushing = false;
  251. // 查看设备是否已经在推流
  252. // MediaItem mediaItem = zlmrtpServerFactory.getMediaInfo(mediaServerItem, "rtp",ssrcInfo.getStream());
  253. // if (mediaItem != null) {
  254. // SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem,
  255. // mediaItem.getOriginSock().getPeer_ip(), mediaItem.getOriginSock().getPeer_port(), ssrcInfo.getSsrc(), device.getDeviceId(),
  256. // device.getDeviceId(), channelId,
  257. // false);
  258. //
  259. // sendRtpItem.setTcpActive(false);
  260. // sendRtpItem.setCallId(callId);
  261. // sendRtpItem.setPlayType(InviteStreamType.TALK);
  262. // sendRtpItem.setStatus(1);
  263. // sendRtpItem.setIp(mediaItem.getOriginSock().getPeer_ip());
  264. // sendRtpItem.setPort(mediaItem.getOriginSock().getPeer_port());
  265. // sendRtpItem.setTcpActive(false);
  266. // sendRtpItem.setStreamId(ssrcInfo.getStream());
  267. // sendRtpItem.setApp("1000");
  268. // sendRtpItem.setStreamId("1000");
  269. // sendRtpItem.setSsrc(ssrc);
  270. // sendRtpItem.setOnlyAudio(true);
  271. // redisCatchStorage.updateSendRTPSever(sendRtpItem);
  272. //
  273. // Map<String, Object> param = new HashMap<>(12);
  274. // param.put("vhost","__defaultVhost__");
  275. // param.put("app",sendRtpItem.getApp());
  276. // param.put("stream",sendRtpItem.getStreamId());
  277. // param.put("ssrc", sendRtpItem.getSsrc());
  278. // param.put("dst_url", sendRtpItem.getIp());
  279. // param.put("dst_port", sendRtpItem.getPort());
  280. // param.put("src_port", sendRtpItem.getLocalPort());
  281. // param.put("pt", sendRtpItem.getPt());
  282. // param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
  283. // param.put("is_udp", sendRtpItem.isTcp() ? "0" : "1");
  284. // param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
  285. // JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaServerItem, param);
  286. // System.out.println(2222);
  287. // System.out.println(jsonObject);
  288. // }else {
  289. try {
  290. cmder.talkStreamCmd(mediaServerItem, ssrcInfo, device, channelId, callId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
  291. logger.info("[对讲] 流已生成, 开始推流: " + response.toJSONString());
  292. dynamicTask.stop(timeOutTaskKey);
  293. // TODO 暂不做处理
  294. }, (MediaServerItem mediaServerItemInuse, JSONObject json) -> {
  295. logger.info("[对讲] 设备开始推流: " + json.toJSONString());
  296. dynamicTask.stop(timeOutTaskKey);
  297. // 获取远程IP端口 作为回复语音流的地址
  298. String ip = json.getString("ip");
  299. Integer port = json.getInteger("port");
  300. logger.info("[设备开始推流]{}/{}, 来自ip:{}, 端口:{}", device.getDeviceId(), channelId, ip, port);
  301. // 查看平台推流是否就绪
  302. // Boolean ready = zlmrtpServerFactory.isStreamReady(mediaServerItemInuse, "talk", stream);
  303. // if (!ready) {
  304. // try {
  305. // cmder.streamByeCmd(device, channelId, finalSsrcInfo.getStream(), null);
  306. // } catch (InvalidArgumentException | ParseException | SipException e) {
  307. // logger.error("[对讲超时], 发送BYE失败 {}", e.getMessage());
  308. // } catch (SsrcTransactionNotFoundException e) {
  309. // timeoutCallback.run();
  310. // mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc());
  311. // mediaServerService.closeRTPServer(mediaServerItem, finalSsrcInfo.getStream());
  312. // streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
  313. // }
  314. // }else {
  315. // try {
  316. // Thread.sleep(1000);
  317. // } catch (InterruptedException e) {
  318. // throw new RuntimeException(e);
  319. // }
  320. SendRtpItem sendRtpItem = zlmrtpServerFactory.createSendRtpItem(mediaServerItem, ip, port, ssrcInfo.getSsrc(), device.getDeviceId(),
  321. device.getDeviceId(), channelId,
  322. false, false);
  323. // if (sendRtpItem.getLocalPort() == 0) {
  324. // logger.warn("服务器端口资源不足");
  325. // try {
  326. // cmder.streamByeCmd(device, channelId, finalSsrcInfo.getStream(), null);
  327. // } catch (InvalidArgumentException | ParseException | SipException e) {
  328. // logger.error("[对讲超时], 发送BYE失败 {}", e.getMessage());
  329. // } catch (SsrcTransactionNotFoundException e) {
  330. // timeoutCallback.run();
  331. // mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc());
  332. // mediaServerService.closeRTPServer(mediaServerItem, finalSsrcInfo.getStream());
  333. // streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
  334. // }
  335. // return;
  336. // }
  337. sendRtpItem.setTcpActive(false);
  338. sendRtpItem.setCallId(callId);
  339. sendRtpItem.setPlayType(InviteStreamType.TALK);
  340. sendRtpItem.setStatus(1);
  341. sendRtpItem.setIp(ip);
  342. sendRtpItem.setPort(port);
  343. sendRtpItem.setTcpActive(false);
  344. sendRtpItem.setApp("1000");
  345. sendRtpItem.setStreamId("1000");
  346. sendRtpItem.setSsrc(ssrc);
  347. sendRtpItem.setOnlyAudio(true);
  348. sendRtpItem.setRtcp(false);
  349. redisCatchStorage.updateSendRTPSever(sendRtpItem);
  350. Map<String, Object> param = new HashMap<>(12);
  351. param.put("vhost","__defaultVhost__");
  352. param.put("app",sendRtpItem.getApp());
  353. param.put("stream",sendRtpItem.getStreamId());
  354. param.put("ssrc", sendRtpItem.getSsrc());
  355. param.put("dst_url", sendRtpItem.getIp());
  356. param.put("dst_port", sendRtpItem.getPort());
  357. param.put("src_port", sendRtpItem.getLocalPort());
  358. param.put("pt", sendRtpItem.getPt());
  359. param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
  360. param.put("is_udp", sendRtpItem.isTcp() ? "0" : "1");
  361. param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
  362. JSONObject jsonObject = zlmrtpServerFactory.startSendRtpStream(mediaServerItemInuse, param);
  363. System.out.println(11111);
  364. System.out.println(sendRtpItem.getIp() + ":" + sendRtpItem.getPort());
  365. // System.out.println(jsonObject);
  366. // }
  367. }, (event) -> {
  368. }, (event) -> {
  369. dynamicTask.stop(timeOutTaskKey);
  370. mediaServerService.closeRTPServer(mediaServerItem, finalSsrcInfo.getStream());
  371. // 释放ssrc
  372. mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc());
  373. streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
  374. errorEvent.response(event);
  375. });
  376. } catch (InvalidArgumentException | SipException | ParseException e) {
  377. logger.error("[命令发送失败] 对讲消息: {}", e.getMessage());
  378. dynamicTask.stop(timeOutTaskKey);
  379. mediaServerService.closeRTPServer(mediaServerItem, finalSsrcInfo.getStream());
  380. // 释放ssrc
  381. mediaServerService.releaseSsrc(mediaServerItem.getId(), finalSsrcInfo.getSsrc());
  382. streamSession.remove(device.getDeviceId(), channelId, finalSsrcInfo.getStream());
  383. SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null));
  384. eventResult.msg = "命令发送失败";
  385. errorEvent.response(eventResult);
  386. }
  387. // }
  388. }
  389. @Override
  390. public void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId,
  391. ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
  392. InviteTimeOutCallback timeoutCallback) {
  393. logger.info("[点播开始] deviceId: {}, channelId: {},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, ssrcInfo.getPort(), device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
  394. // 超时处理
  395. String timeOutTaskKey = UUID.randomUUID().toString();
  396. dynamicTask.startDelay(timeOutTaskKey, () -> {
  397. // 执行超时任务时查询是否已经成功,成功了则不执行超时任务,防止超时任务取消失败的情况
  398. if (redisCatchStorage.queryPlayByDevice(device.getDeviceId(), channelId) == null) {
  399. logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", device.getDeviceId(), channelId, ssrcInfo.getPort(), ssrcInfo.getSsrc());
  400. // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
  401. try {
  402. cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null);
  403. } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
  404. logger.error("[点播超时], 发送BYE失败 {}", e.getMessage());
  405. } finally {
  406. timeoutCallback.run(1, "收流超时");
  407. mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  408. mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
  409. streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  410. mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
  411. }
  412. }
  413. }, userSetting.getPlayTimeout());
  414. //端口获取失败的ssrcInfo 没有必要发送点播指令
  415. if (ssrcInfo.getPort() <= 0) {
  416. logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo);
  417. dynamicTask.stop(timeOutTaskKey);
  418. // 释放ssrc
  419. mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  420. streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  421. RequestMessage msg = new RequestMessage();
  422. msg.setKey(DeferredResultHolder.CALLBACK_CMD_PLAY + device.getDeviceId() + channelId);
  423. msg.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), "点播端口分配异常"));
  424. resultHolder.invokeAllResult(msg);
  425. return;
  426. }
  427. try {
  428. cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
  429. logger.info("收到订阅消息: " + response.toJSONString());
  430. dynamicTask.stop(timeOutTaskKey);
  431. // hook响应
  432. onPublishHandlerForPlay(mediaServerItemInuse, response, device.getDeviceId(), channelId);
  433. hookEvent.response(mediaServerItemInuse, response);
  434. logger.info("[点播成功] deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
  435. String streamUrl = String.format("rtsp://127.0.0.1:%s/%s/%s", mediaServerItemInuse.getRtspPort(), "rtp", ssrcInfo.getStream());
  436. String path = "snap";
  437. String fileName = device.getDeviceId() + "_" + channelId + ".jpg";
  438. // 请求截图
  439. logger.info("[请求截图]: " + fileName);
  440. zlmresTfulUtils.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName);
  441. }, (event) -> {
  442. ResponseEvent responseEvent = (ResponseEvent) event.event;
  443. String contentString = new String(responseEvent.getResponse().getRawContent());
  444. // 获取ssrc
  445. int ssrcIndex = contentString.indexOf("y=");
  446. // 检查是否有y字段
  447. if (ssrcIndex >= 0) {
  448. //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
  449. String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
  450. // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
  451. if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
  452. return;
  453. }
  454. logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
  455. if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
  456. logger.info("[点播消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
  457. if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
  458. // ssrc 不可用
  459. // 释放ssrc
  460. mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  461. streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  462. event.msg = "下级自定义了ssrc,但是此ssrc不可用";
  463. event.statusCode = 400;
  464. errorEvent.response(event);
  465. return;
  466. }
  467. // 单端口模式streamId也有变化,需要重新设置监听
  468. if (!mediaServerItem.isRtpEnable()) {
  469. // 添加订阅
  470. HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
  471. subscribe.removeSubscribe(hookSubscribe);
  472. hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
  473. subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
  474. logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
  475. dynamicTask.stop(timeOutTaskKey);
  476. // hook响应
  477. onPublishHandlerForPlay(mediaServerItemInUse, response, device.getDeviceId(), channelId);
  478. hookEvent.response(mediaServerItemInUse, response);
  479. });
  480. }
  481. // 关闭rtp server
  482. mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
  483. // 重新开启ssrc server
  484. mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), false, ssrcInfo.getPort());
  485. }
  486. }
  487. }, (event) -> {
  488. dynamicTask.stop(timeOutTaskKey);
  489. mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
  490. // 释放ssrc
  491. mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  492. streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  493. errorEvent.response(event);
  494. });
  495. } catch (InvalidArgumentException | SipException | ParseException e) {
  496. logger.error("[命令发送失败] 点播消息: {}", e.getMessage());
  497. dynamicTask.stop(timeOutTaskKey);
  498. mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
  499. // 释放ssrc
  500. mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  501. streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  502. SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null));
  503. eventResult.msg = "命令发送失败";
  504. errorEvent.response(eventResult);
  505. }
  506. }
  507. @Override
  508. public void onPublishHandlerForPlay(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId) {
  509. StreamInfo streamInfo = onPublishHandler(mediaServerItem, response, deviceId, channelId);
  510. RequestMessage msg = new RequestMessage();
  511. msg.setKey(DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId);
  512. if (streamInfo != null) {
  513. DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
  514. if (deviceChannel != null) {
  515. deviceChannel.setStreamId(streamInfo.getStream());
  516. storager.startPlay(deviceId, channelId, streamInfo.getStream());
  517. }
  518. redisCatchStorage.startPlay(streamInfo);
  519. WVPResult wvpResult = new WVPResult();
  520. wvpResult.setCode(ErrorCode.SUCCESS.getCode());
  521. wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
  522. wvpResult.setData(streamInfo);
  523. msg.setData(wvpResult);
  524. resultHolder.invokeAllResult(msg);
  525. } else {
  526. logger.warn("设备预览API调用失败!");
  527. msg.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), "设备预览API调用失败!"));
  528. resultHolder.invokeAllResult(msg);
  529. }
  530. }
  531. private void onPublishHandlerForPlayback(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId, String uuid) {
  532. RequestMessage msg = new RequestMessage();
  533. msg.setKey(DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId);
  534. if (!ObjectUtils.isEmpty(uuid)) {
  535. msg.setId(uuid);
  536. }
  537. StreamInfo streamInfo = onPublishHandler(mediaServerItem, response, deviceId, channelId);
  538. if (streamInfo != null) {
  539. DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
  540. if (deviceChannel != null) {
  541. deviceChannel.setStreamId(streamInfo.getStream());
  542. storager.startPlay(deviceId, channelId, streamInfo.getStream());
  543. }
  544. redisCatchStorage.startPlay(streamInfo);
  545. WVPResult wvpResult = new WVPResult();
  546. wvpResult.setCode(ErrorCode.SUCCESS.getCode());
  547. wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
  548. wvpResult.setData(streamInfo);
  549. msg.setData(wvpResult);
  550. resultHolder.invokeAllResult(msg);
  551. } else {
  552. logger.warn("录像回放调用失败!");
  553. msg.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), "录像回放调用失败!"));
  554. resultHolder.invokeAllResult(msg);
  555. }
  556. }
  557. @Override
  558. public MediaServerItem getNewMediaServerItem(Device device) {
  559. if (device == null) {
  560. return null;
  561. }
  562. MediaServerItem mediaServerItem;
  563. if (ObjectUtils.isEmpty(device.getMediaServerId()) || "auto".equals(device.getMediaServerId())) {
  564. mediaServerItem = mediaServerService.getMediaServerForMinimumLoad();
  565. } else {
  566. mediaServerItem = mediaServerService.getOne(device.getMediaServerId());
  567. }
  568. if (mediaServerItem == null) {
  569. logger.warn("点播时未找到可使用的ZLM...");
  570. }
  571. return mediaServerItem;
  572. }
  573. @Override
  574. public DeferredResult<WVPResult<StreamInfo>> playBack(String deviceId, String channelId, String startTime,
  575. String endTime, InviteStreamCallback inviteStreamCallback,
  576. PlayBackCallback callback) {
  577. Device device = storager.queryVideoDevice(deviceId);
  578. if (device == null) {
  579. return null;
  580. }
  581. MediaServerItem newMediaServerItem = getNewMediaServerItem(device);
  582. SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, device.isSsrcCheck(), true);
  583. return playBack(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, inviteStreamCallback, callback);
  584. }
  585. @Override
  586. public DeferredResult<WVPResult<StreamInfo>> playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,
  587. String deviceId, String channelId, String startTime,
  588. String endTime, InviteStreamCallback infoCallBack,
  589. PlayBackCallback playBackCallback) {
  590. if (mediaServerItem == null || ssrcInfo == null) {
  591. return null;
  592. }
  593. String uuid = UUID.randomUUID().toString();
  594. String key = DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId;
  595. Device device = storager.queryVideoDevice(deviceId);
  596. if (device == null) {
  597. throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备: " + deviceId + "不存在");
  598. }
  599. DeferredResult<WVPResult<StreamInfo>> result = new DeferredResult<>(30000L);
  600. resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId, uuid, result);
  601. RequestMessage requestMessage = new RequestMessage();
  602. requestMessage.setId(uuid);
  603. requestMessage.setKey(key);
  604. PlayBackResult<RequestMessage> playBackResult = new PlayBackResult<>();
  605. String playBackTimeOutTaskKey = UUID.randomUUID().toString();
  606. dynamicTask.startDelay(playBackTimeOutTaskKey, () -> {
  607. logger.warn(String.format("设备回放超时,deviceId:%s ,channelId:%s", deviceId, channelId));
  608. playBackResult.setCode(ErrorCode.ERROR100.getCode());
  609. playBackResult.setMsg("回放超时");
  610. playBackResult.setData(requestMessage);
  611. try {
  612. cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null);
  613. } catch (InvalidArgumentException | ParseException | SipException e) {
  614. logger.error("[录像流]回放超时 发送BYE失败 {}", e.getMessage());
  615. } catch (SsrcTransactionNotFoundException e) {
  616. // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
  617. mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  618. mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
  619. streamSession.remove(deviceId, channelId, ssrcInfo.getStream());
  620. }
  621. // 回复之前所有的点播请求
  622. playBackCallback.call(playBackResult);
  623. result.setResult(WVPResult.fail(ErrorCode.ERROR100.getCode(), "回放超时"));
  624. resultHolder.exist(DeferredResultHolder.CALLBACK_CMD_PLAYBACK + deviceId + channelId, uuid);
  625. }, userSetting.getPlayTimeout());
  626. SipSubscribe.Event errorEvent = event -> {
  627. dynamicTask.stop(playBackTimeOutTaskKey);
  628. requestMessage.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg)));
  629. playBackResult.setCode(ErrorCode.ERROR100.getCode());
  630. playBackResult.setMsg(String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg));
  631. playBackResult.setData(requestMessage);
  632. playBackResult.setEvent(event);
  633. playBackCallback.call(playBackResult);
  634. streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  635. };
  636. InviteStreamCallback hookEvent = (InviteStreamInfo inviteStreamInfo) -> {
  637. logger.info("收到回放订阅消息: " + inviteStreamInfo.getResponse().toJSONString());
  638. dynamicTask.stop(playBackTimeOutTaskKey);
  639. StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
  640. if (streamInfo == null) {
  641. logger.warn("设备回放API调用失败!");
  642. playBackResult.setCode(ErrorCode.ERROR100.getCode());
  643. playBackResult.setMsg("设备回放API调用失败!");
  644. playBackCallback.call(playBackResult);
  645. return;
  646. }
  647. redisCatchStorage.startPlayback(streamInfo, inviteStreamInfo.getCallId());
  648. WVPResult<StreamInfo> success = WVPResult.success(streamInfo);
  649. requestMessage.setData(success);
  650. playBackResult.setCode(ErrorCode.SUCCESS.getCode());
  651. playBackResult.setMsg(ErrorCode.SUCCESS.getMsg());
  652. playBackResult.setData(requestMessage);
  653. playBackResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem());
  654. playBackResult.setResponse(inviteStreamInfo.getResponse());
  655. playBackCallback.call(playBackResult);
  656. };
  657. try {
  658. cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, infoCallBack,
  659. hookEvent, eventResult -> {
  660. if (eventResult.type == SipSubscribe.EventResultType.response) {
  661. ResponseEvent responseEvent = (ResponseEvent) eventResult.event;
  662. String contentString = new String(responseEvent.getResponse().getRawContent());
  663. // 获取ssrc
  664. int ssrcIndex = contentString.indexOf("y=");
  665. // 检查是否有y字段
  666. if (ssrcIndex >= 0) {
  667. //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
  668. String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
  669. // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
  670. if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
  671. return;
  672. }
  673. logger.info("[回放消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
  674. if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
  675. logger.info("[回放消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
  676. if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
  677. // ssrc 不可用
  678. // 释放ssrc
  679. mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  680. streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  681. eventResult.msg = "下级自定义了ssrc,但是此ssrc不可用";
  682. eventResult.statusCode = 400;
  683. errorEvent.response(eventResult);
  684. return;
  685. }
  686. // 单端口模式streamId也有变化,需要重新设置监听
  687. if (!mediaServerItem.isRtpEnable()) {
  688. // 添加订阅
  689. HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
  690. subscribe.removeSubscribe(hookSubscribe);
  691. hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
  692. subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
  693. logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
  694. dynamicTask.stop(playBackTimeOutTaskKey);
  695. // hook响应
  696. onPublishHandlerForPlayback(mediaServerItemInUse, response, device.getDeviceId(), channelId, uuid);
  697. hookEvent.call(new InviteStreamInfo(mediaServerItem, null, eventResult.callId, "rtp", ssrcInfo.getStream()));
  698. });
  699. }
  700. // 关闭rtp server
  701. mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
  702. // 重新开启ssrc server
  703. mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), true, ssrcInfo.getPort());
  704. }
  705. }
  706. }
  707. }, errorEvent);
  708. } catch (InvalidArgumentException | SipException | ParseException e) {
  709. logger.error("[命令发送失败] 回放: {}", e.getMessage());
  710. SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null));
  711. eventResult.msg = "命令发送失败";
  712. errorEvent.response(eventResult);
  713. }
  714. return result;
  715. }
  716. @Override
  717. public DeferredResult<WVPResult<StreamInfo>> download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack) {
  718. Device device = storager.queryVideoDevice(deviceId);
  719. if (device == null) {
  720. return null;
  721. }
  722. MediaServerItem newMediaServerItem = getNewMediaServerItem(device);
  723. SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, device.isSsrcCheck(), true);
  724. return download(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, downloadSpeed, infoCallBack, hookCallBack);
  725. }
  726. @Override
  727. public DeferredResult<WVPResult<StreamInfo>> download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack) {
  728. if (mediaServerItem == null || ssrcInfo == null) {
  729. return null;
  730. }
  731. String uuid = UUID.randomUUID().toString();
  732. String key = DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId;
  733. DeferredResult<WVPResult<StreamInfo>> result = new DeferredResult<>(30000L);
  734. Device device = storager.queryVideoDevice(deviceId);
  735. if (device == null) {
  736. throw new ControllerException(ErrorCode.ERROR400.getCode(), "设备:" + deviceId + "不存在");
  737. }
  738. resultHolder.put(key, uuid, result);
  739. RequestMessage requestMessage = new RequestMessage();
  740. requestMessage.setId(uuid);
  741. requestMessage.setKey(key);
  742. WVPResult<StreamInfo> wvpResult = new WVPResult<>();
  743. requestMessage.setData(wvpResult);
  744. PlayBackResult<RequestMessage> downloadResult = new PlayBackResult<>();
  745. downloadResult.setData(requestMessage);
  746. String downLoadTimeOutTaskKey = UUID.randomUUID().toString();
  747. dynamicTask.startDelay(downLoadTimeOutTaskKey, () -> {
  748. logger.warn(String.format("录像下载请求超时,deviceId:%s ,channelId:%s", deviceId, channelId));
  749. wvpResult.setCode(ErrorCode.ERROR100.getCode());
  750. wvpResult.setMsg("录像下载请求超时");
  751. downloadResult.setCode(ErrorCode.ERROR100.getCode());
  752. downloadResult.setMsg("录像下载请求超时");
  753. hookCallBack.call(downloadResult);
  754. // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
  755. try {
  756. cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null);
  757. } catch (InvalidArgumentException | ParseException | SipException e) {
  758. logger.error("[录像流]录像下载请求超时, 发送BYE失败 {}", e.getMessage());
  759. } catch (SsrcTransactionNotFoundException e) {
  760. mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  761. mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
  762. streamSession.remove(deviceId, channelId, ssrcInfo.getStream());
  763. }
  764. // 回复之前所有的点播请求
  765. hookCallBack.call(downloadResult);
  766. }, userSetting.getPlayTimeout());
  767. SipSubscribe.Event errorEvent = event -> {
  768. dynamicTask.stop(downLoadTimeOutTaskKey);
  769. downloadResult.setCode(ErrorCode.ERROR100.getCode());
  770. downloadResult.setMsg(String.format("录像下载失败, 错误码: %s, %s", event.statusCode, event.msg));
  771. wvpResult.setCode(ErrorCode.ERROR100.getCode());
  772. wvpResult.setMsg(String.format("录像下载失败, 错误码: %s, %s", event.statusCode, event.msg));
  773. downloadResult.setEvent(event);
  774. hookCallBack.call(downloadResult);
  775. streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  776. };
  777. try {
  778. cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, infoCallBack,
  779. inviteStreamInfo -> {
  780. logger.info("收到订阅消息: " + inviteStreamInfo.getResponse().toJSONString());
  781. dynamicTask.stop(downLoadTimeOutTaskKey);
  782. StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
  783. streamInfo.setStartTime(startTime);
  784. streamInfo.setEndTime(endTime);
  785. redisCatchStorage.startDownload(streamInfo, inviteStreamInfo.getCallId());
  786. wvpResult.setCode(ErrorCode.SUCCESS.getCode());
  787. wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
  788. wvpResult.setData(streamInfo);
  789. downloadResult.setCode(ErrorCode.SUCCESS.getCode());
  790. downloadResult.setMsg(ErrorCode.SUCCESS.getMsg());
  791. downloadResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem());
  792. downloadResult.setResponse(inviteStreamInfo.getResponse());
  793. hookCallBack.call(downloadResult);
  794. }, errorEvent);
  795. } catch (InvalidArgumentException | SipException | ParseException e) {
  796. logger.error("[命令发送失败] 录像下载: {}", e.getMessage());
  797. SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null));
  798. eventResult.msg = "命令发送失败";
  799. errorEvent.response(eventResult);
  800. }
  801. return result;
  802. }
  803. @Override
  804. public StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream) {
  805. StreamInfo streamInfo = redisCatchStorage.queryDownload(deviceId, channelId, stream, null);
  806. if (streamInfo != null) {
  807. if (streamInfo.getProgress() == 1) {
  808. return streamInfo;
  809. }
  810. // 获取当前已下载时长
  811. String mediaServerId = streamInfo.getMediaServerId();
  812. MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
  813. if (mediaServerItem == null) {
  814. logger.warn("查询录像信息时发现节点已离线");
  815. return null;
  816. }
  817. if (mediaServerItem.getRecordAssistPort() > 0) {
  818. JSONObject jsonObject = assistRESTfulUtils.fileDuration(mediaServerItem, streamInfo.getApp(), streamInfo.getStream(), null);
  819. if (jsonObject != null && jsonObject.getInteger("code") == 0) {
  820. long duration = jsonObject.getLong("data");
  821. if (duration == 0) {
  822. streamInfo.setProgress(0);
  823. } else {
  824. String startTime = streamInfo.getStartTime();
  825. String endTime = streamInfo.getEndTime();
  826. long start = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime);
  827. long end = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime);
  828. BigDecimal currentCount = new BigDecimal(duration / 1000);
  829. BigDecimal totalCount = new BigDecimal(end - start);
  830. BigDecimal divide = currentCount.divide(totalCount, 2, RoundingMode.HALF_UP);
  831. double process = divide.doubleValue();
  832. streamInfo.setProgress(process);
  833. }
  834. }
  835. }
  836. }
  837. return streamInfo;
  838. }
  839. @Override
  840. public void onPublishHandlerForDownload(InviteStreamInfo inviteStreamInfo, String deviceId, String channelId, String uuid) {
  841. RequestMessage msg = new RequestMessage();
  842. msg.setKey(DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId);
  843. msg.setId(uuid);
  844. StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
  845. if (streamInfo != null) {
  846. redisCatchStorage.startDownload(streamInfo, inviteStreamInfo.getCallId());
  847. msg.setData(JSON.toJSONString(streamInfo));
  848. resultHolder.invokeResult(msg);
  849. } else {
  850. logger.warn("设备预览API调用失败!");
  851. msg.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), "设备预览API调用失败!"));
  852. resultHolder.invokeResult(msg);
  853. }
  854. }
  855. public StreamInfo onPublishHandler(MediaServerItem mediaServerItem, JSONObject resonse, String deviceId, String channelId) {
  856. String streamId = resonse.getString("stream");
  857. JSONArray tracks = resonse.getJSONArray("tracks");
  858. StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(mediaServerItem, "rtp", streamId, tracks, null);
  859. streamInfo.setDeviceID(deviceId);
  860. streamInfo.setChannelId(channelId);
  861. return streamInfo;
  862. }
  863. @Override
  864. public void zlmServerOffline(String mediaServerId) {
  865. // 处理正在向上推流的上级平台
  866. List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServer(null);
  867. if (sendRtpItems.size() > 0) {
  868. for (SendRtpItem sendRtpItem : sendRtpItems) {
  869. if (sendRtpItem.getMediaServerId().equals(mediaServerId)) {
  870. ParentPlatform platform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
  871. try {
  872. sipCommanderFroPlatform.streamByeCmd(platform, sendRtpItem.getCallId());
  873. } catch (SipException | InvalidArgumentException | ParseException e) {
  874. logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
  875. }
  876. }
  877. }
  878. }
  879. // 处理正在观看的国标设备
  880. List<SsrcTransaction> allSsrc = streamSession.getAllSsrc();
  881. if (allSsrc.size() > 0) {
  882. for (SsrcTransaction ssrcTransaction : allSsrc) {
  883. if (ssrcTransaction.getMediaServerId().equals(mediaServerId)) {
  884. Device device = deviceService.getDevice(ssrcTransaction.getDeviceId());
  885. if (device == null) {
  886. continue;
  887. }
  888. try {
  889. cmder.streamByeCmd(device, ssrcTransaction.getChannelId(),
  890. ssrcTransaction.getStream(), null);
  891. } catch (InvalidArgumentException | ParseException | SipException |
  892. SsrcTransactionNotFoundException e) {
  893. logger.error("[zlm离线]为正在使用此zlm的设备, 发送BYE失败 {}", e.getMessage());
  894. }
  895. }
  896. }
  897. }
  898. }
  899. @Override
  900. public AudioBroadcastResult audioBroadcast(Device device, String channelId) {
  901. if (device == null || channelId == null) {
  902. return null;
  903. }
  904. logger.info("[语音喊话] device: {}, channel: {}", device.getDeviceId(), channelId);
  905. DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId);
  906. if (deviceChannel == null) {
  907. logger.warn("开启语音广播的时候未找到通道: {}", channelId);
  908. return null;
  909. }
  910. MediaServerItem mediaServerItem = mediaServerService.getMediaServerForMinimumLoad();
  911. String app = "broadcast";
  912. // TODO 从sip user agent中判断是什么品牌设备,大华默认使用talk模式,其他使用broadcast模式
  913. // String app = "talk";
  914. String stream = device.getDeviceId() + "_" + channelId;
  915. StreamInfo broadcast = mediaService.getStreamInfoByAppAndStream(mediaServerItem, "broadcast", stream, null, null, null, false);
  916. AudioBroadcastResult audioBroadcastResult = new AudioBroadcastResult();
  917. audioBroadcastResult.setApp(app);
  918. audioBroadcastResult.setStream(stream);
  919. audioBroadcastResult.setStreamInfo(mediaService.getStreamInfoByAppAndStream(mediaServerItem, app, stream, null, null, null,false));
  920. audioBroadcastResult.setCodec("G.711");
  921. return audioBroadcastResult;
  922. }
  923. @Override
  924. public void audioBroadcastCmd(Device device, String channelId, int timeout, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException {
  925. if (device == null || channelId == null) {
  926. return;
  927. }
  928. logger.info("[语音喊话] device: {}, channel: {}", device.getDeviceId(), channelId);
  929. DeviceChannel deviceChannel = storager.queryChannel(device.getDeviceId(), channelId);
  930. if (deviceChannel == null) {
  931. logger.warn("开启语音广播的时候未找到通道: {}", channelId);
  932. event.call("开启语音广播的时候未找到通道");
  933. return;
  934. }
  935. // 查询通道使用状态
  936. if (audioBroadcastManager.exit(device.getDeviceId(), channelId)) {
  937. SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
  938. if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) {
  939. // 查询流是否存在,不存在则认为是异常状态
  940. MediaServerItem mediaServerItem = mediaServerService.getOne(sendRtpItem.getMediaServerId());
  941. Boolean streamReady = zlmrtpServerFactory.isStreamReady(mediaServerItem, sendRtpItem.getApp(), sendRtpItem.getStreamId());
  942. if (streamReady) {
  943. logger.warn("语音广播已经开启: {}", channelId);
  944. event.call("语音广播已经开启");
  945. return;
  946. } else {
  947. stopAudioBroadcast(device.getDeviceId(), channelId);
  948. }
  949. }
  950. }
  951. // 发送通知
  952. cmder.audioBroadcastCmd(device, channelId, eventResultForOk -> {
  953. // 发送成功
  954. AudioBroadcastCatch audioBroadcastCatch = new AudioBroadcastCatch(device.getDeviceId(), channelId, AudioBroadcastCatchStatus.Ready);
  955. audioBroadcastManager.update(audioBroadcastCatch);
  956. }, eventResultForError -> {
  957. // 发送失败
  958. logger.error("语音广播发送失败: {}:{}", channelId, eventResultForError.msg);
  959. event.call("语音广播发送失败");
  960. stopAudioBroadcast(device.getDeviceId(), channelId);
  961. });
  962. }
  963. @Override
  964. public void stopAudioBroadcast(String deviceId, String channelId) {
  965. AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(deviceId, channelId);
  966. if (audioBroadcastCatch != null) {
  967. Device device = deviceService.getDevice(deviceId);
  968. if (device == null) {
  969. return;
  970. }
  971. SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(deviceId, audioBroadcastCatch.getChannelId(), null, null);
  972. if (sendRtpItem != null) {
  973. redisCatchStorage.deleteSendRTPServer(deviceId, sendRtpItem.getChannelId(), null, null);
  974. MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
  975. Map<String, Object> param = new HashMap<>();
  976. param.put("vhost", "__defaultVhost__");
  977. param.put("app", sendRtpItem.getApp());
  978. param.put("stream", sendRtpItem.getStreamId());
  979. zlmresTfulUtils.stopSendRtp(mediaInfo, param);
  980. }
  981. audioBroadcastManager.del(deviceId, channelId);
  982. }
  983. }
  984. @Override
  985. public void zlmServerOnline(String mediaServerId) {
  986. // TODO 查找之前的点播,流如果不存在则给下级发送bye
  987. // MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
  988. // zlmresTfulUtils.getMediaList(mediaServerItem, (mediaList ->{
  989. // Integer code = mediaList.getInteger("code");
  990. // if (code == 0) {
  991. // JSONArray data = mediaList.getJSONArray("data");
  992. // if (data == null || data.size() == 0) {
  993. // zlmServerOffline(mediaServerId);
  994. // }else {
  995. // Map<String, JSONObject> mediaListMap = new HashMap<>();
  996. // for (int i = 0; i < data.size(); i++) {
  997. // JSONObject json = data.getJSONObject(i);
  998. // String app = json.getString("app");
  999. // if ("rtp".equals(app)) {
  1000. // String stream = json.getString("stream");
  1001. // if (mediaListMap.get(stream) != null) {
  1002. // continue;
  1003. // }
  1004. // mediaListMap.put(stream, json);
  1005. // // 处理正在观看的国标设备
  1006. // List<SsrcTransaction> ssrcTransactions = streamSession.getSsrcTransactionForAll(null, null, null, stream);
  1007. // if (ssrcTransactions.size() > 0) {
  1008. // for (SsrcTransaction ssrcTransaction : ssrcTransactions) {
  1009. // if(ssrcTransaction.getMediaServerId().equals(mediaServerId)) {
  1010. // cmder.streamByeCmd(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(),
  1011. // ssrcTransaction.getStream(), null);
  1012. // }
  1013. // }
  1014. // }
  1015. // }
  1016. // }
  1017. // if (mediaListMap.size() > 0 ) {
  1018. // // 处理正在向上推流的上级平台
  1019. // List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServer(null);
  1020. // if (sendRtpItems.size() > 0) {
  1021. // for (SendRtpItem sendRtpItem : sendRtpItems) {
  1022. // if (sendRtpItem.getMediaServerId().equals(mediaServerId)) {
  1023. // if (mediaListMap.get(sendRtpItem.getStreamId()) == null) {
  1024. // ParentPlatform platform = storager.queryPlatformByServerGBId(sendRtpItem.getPlatformId());
  1025. // sipCommanderFroPlatform.streamByeCmd(platform, sendRtpItem.getCallId());
  1026. // }
  1027. // }
  1028. // }
  1029. // }
  1030. // }
  1031. // }
  1032. // }
  1033. // }));
  1034. }
  1035. @Override
  1036. public void pauseRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException {
  1037. String key = redisCatchStorage.queryPlaybackForKey(null, null, streamId, null);
  1038. StreamInfo streamInfo = redisCatchStorage.queryPlayback(null, null, streamId, null);
  1039. if (null == streamInfo) {
  1040. logger.warn("streamId不存在!");
  1041. throw new ServiceException("streamId不存在");
  1042. }
  1043. streamInfo.setPause(true);
  1044. RedisUtil.set(key, streamInfo);
  1045. MediaServerItem mediaServerItem = mediaServerService.getOne(streamInfo.getMediaServerId());
  1046. if (null == mediaServerItem) {
  1047. logger.warn("mediaServer 不存在!");
  1048. throw new ServiceException("mediaServer不存在");
  1049. }
  1050. // zlm 暂停RTP超时检查
  1051. JSONObject jsonObject = zlmresTfulUtils.pauseRtpCheck(mediaServerItem, streamId);
  1052. if (jsonObject == null || jsonObject.getInteger("code") != 0) {
  1053. throw new ServiceException("暂停RTP接收失败");
  1054. }
  1055. Device device = storager.queryVideoDevice(streamInfo.getDeviceID());
  1056. cmder.playPauseCmd(device, streamInfo);
  1057. }
  1058. @Override
  1059. public void resumeRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException {
  1060. String key = redisCatchStorage.queryPlaybackForKey(null, null, streamId, null);
  1061. StreamInfo streamInfo = redisCatchStorage.queryPlayback(null, null, streamId, null);
  1062. if (null == streamInfo) {
  1063. logger.warn("streamId不存在!");
  1064. throw new ServiceException("streamId不存在");
  1065. }
  1066. streamInfo.setPause(false);
  1067. RedisUtil.set(key, streamInfo);
  1068. MediaServerItem mediaServerItem = mediaServerService.getOne(streamInfo.getMediaServerId());
  1069. if (null == mediaServerItem) {
  1070. logger.warn("mediaServer 不存在!");
  1071. throw new ServiceException("mediaServer不存在");
  1072. }
  1073. // zlm 暂停RTP超时检查
  1074. JSONObject jsonObject = zlmresTfulUtils.resumeRtpCheck(mediaServerItem, streamId);
  1075. if (jsonObject == null || jsonObject.getInteger("code") != 0) {
  1076. throw new ServiceException("继续RTP接收失败");
  1077. }
  1078. Device device = storager.queryVideoDevice(streamInfo.getDeviceID());
  1079. cmder.playResumeCmd(device, streamInfo);
  1080. }
  1081. @Override
  1082. public void startPushStream(SendRtpItem sendRtpItem, SIPResponse sipResponse, ParentPlatform platform, CallIdHeader callIdHeader) {
  1083. // 开始发流
  1084. // 取消设置的超时任务
  1085. // String channelId = request.getCallIdHeader().getCallId();
  1086. String is_Udp = sendRtpItem.isTcp() ? "0" : "1";
  1087. MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
  1088. logger.info("收到ACK,rtp/{}开始向上级推流, 目标={}:{},SSRC={}, RTCP={}", sendRtpItem.getStreamId(),
  1089. sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isRtcp());
  1090. Map<String, Object> param = new HashMap<>(12);
  1091. param.put("vhost","__defaultVhost__");
  1092. param.put("app",sendRtpItem.getApp());
  1093. param.put("stream",sendRtpItem.getStreamId());
  1094. param.put("ssrc", sendRtpItem.getSsrc());
  1095. param.put("src_port", sendRtpItem.getLocalPort());
  1096. param.put("pt", sendRtpItem.getPt());
  1097. param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
  1098. param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
  1099. param.put("is_udp", is_Udp);
  1100. if (!sendRtpItem.isTcp()) {
  1101. // udp模式下开启rtcp保活
  1102. param.put("udp_rtcp_timeout", sendRtpItem.isRtcp()? "1":"0");
  1103. }
  1104. if (mediaInfo == null) {
  1105. RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(
  1106. sendRtpItem.getMediaServerId(), sendRtpItem.getApp(), sendRtpItem.getStreamId(),
  1107. sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(),
  1108. sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio());
  1109. redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, json -> {
  1110. startSendRtpStreamHand(sendRtpItem, platform, json, param, callIdHeader);
  1111. });
  1112. } else {
  1113. // 如果是非严格模式,需要关闭端口占用
  1114. JSONObject startSendRtpStreamResult = null;
  1115. if (sendRtpItem.getLocalPort() != 0) {
  1116. HookSubscribeForRtpServerTimeout hookSubscribeForRtpServerTimeout = HookSubscribeFactory.on_rtp_server_timeout(sendRtpItem.getSsrc(), null, mediaInfo.getId());
  1117. hookSubscribe.removeSubscribe(hookSubscribeForRtpServerTimeout);
  1118. if (zlmrtpServerFactory.releasePort(mediaInfo, sendRtpItem.getSsrc())) {
  1119. if (sendRtpItem.isTcpActive()) {
  1120. startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, param);
  1121. }else {
  1122. param.put("dst_url", sendRtpItem.getIp());
  1123. param.put("dst_port", sendRtpItem.getPort());
  1124. startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
  1125. }
  1126. }
  1127. }else {
  1128. if (sendRtpItem.isTcpActive()) {
  1129. startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpPassive(mediaInfo, param);
  1130. }else {
  1131. param.put("dst_url", sendRtpItem.getIp());
  1132. param.put("dst_port", sendRtpItem.getPort());
  1133. startSendRtpStreamResult = zlmrtpServerFactory.startSendRtpStream(mediaInfo, param);
  1134. }
  1135. }
  1136. if (startSendRtpStreamResult != null) {
  1137. startSendRtpStreamHand(sendRtpItem, platform, startSendRtpStreamResult, param, callIdHeader);
  1138. }
  1139. }
  1140. }
  1141. @Override
  1142. public void startSendRtpStreamHand(SendRtpItem sendRtpItem, ParentPlatform parentPlatform,
  1143. JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader) {
  1144. if (jsonObject == null) {
  1145. logger.error("RTP推流失败: 请检查ZLM服务");
  1146. } else if (jsonObject.getInteger("code") == 0) {
  1147. logger.info("调用ZLM推流接口, 结果: {}", jsonObject);
  1148. logger.info("RTP推流成功[ {}/{} ],{}->{}:{}, " ,param.get("app"), param.get("stream"), jsonObject.getString("local_port"), param.get("dst_url"), param.get("dst_port"));
  1149. } else {
  1150. logger.error("RTP推流失败: {}, 参数:{}",jsonObject.getString("msg"), JSON.toJSONString(param));
  1151. if (sendRtpItem.isOnlyAudio()) {
  1152. Device device = deviceService.getDevice(sendRtpItem.getDeviceId());
  1153. AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
  1154. if (audioBroadcastCatch != null) {
  1155. try {
  1156. cmder.streamByeCmd(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null);
  1157. } catch (SipException | ParseException | InvalidArgumentException |
  1158. SsrcTransactionNotFoundException e) {
  1159. logger.error("[命令发送失败] 停止语音对讲: {}", e.getMessage());
  1160. }
  1161. }
  1162. }else {
  1163. // 向上级平台
  1164. try {
  1165. commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId());
  1166. } catch (SipException | InvalidArgumentException | ParseException e) {
  1167. logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
  1168. }
  1169. }
  1170. }
  1171. }
  1172. }