From 0e586a75bbd0e3a337b73c2767285c5e56c2a127 Mon Sep 17 00:00:00 2001 From: shikong <919411476@qq.com> Date: Thu, 21 Sep 2023 14:27:06 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=20video=20sip=E4=BF=A1?= =?UTF-8?q?=E4=BB=A4=E4=B8=8B=E8=BD=BD=E9=87=8D=E6=9E=84,=20=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=96=87=E4=BB=B6=20=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wvp/api/download/DownloadController.java | 8 -- .../wvp/api/video/VideoController.java | 12 ++- .../service/ffmpeg/FfmpegSupportService.java | 1 + .../gb28181/Gb28181DownloadService.java | 73 +++++++++++++++++-- .../wvp/service/video/VideoService.java | 10 +-- .../src/main/resources/application-local.yml | 14 ++-- .../src/main/resources/application.yml | 7 +- 7 files changed, 95 insertions(+), 30 deletions(-) diff --git a/gb28181-wvp-proxy-api/src/main/java/cn/skcks/docking/gb28181/wvp/api/download/DownloadController.java b/gb28181-wvp-proxy-api/src/main/java/cn/skcks/docking/gb28181/wvp/api/download/DownloadController.java index 8829f99..f6517c3 100644 --- a/gb28181-wvp-proxy-api/src/main/java/cn/skcks/docking/gb28181/wvp/api/download/DownloadController.java +++ b/gb28181-wvp-proxy-api/src/main/java/cn/skcks/docking/gb28181/wvp/api/download/DownloadController.java @@ -1,7 +1,5 @@ package cn.skcks.docking.gb28181.wvp.api.download; -import cn.skcks.docking.gb28181.annotation.web.methods.GetJson; -import cn.skcks.docking.gb28181.wvp.api.video.dto.VideoReq; import cn.skcks.docking.gb28181.wvp.config.SwaggerConfig; import cn.skcks.docking.gb28181.wvp.service.download.DownloadService; import cn.skcks.docking.gb28181.wvp.service.gb28181.Gb28181DownloadService; @@ -10,7 +8,6 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; -import org.springdoc.core.annotations.ParameterObject; import org.springdoc.core.models.GroupedOpenApi; import org.springframework.context.annotation.Bean; import org.springframework.web.bind.annotation.*; @@ -28,11 +25,6 @@ public class DownloadController { return SwaggerConfig.api("DownloadApi", "/download"); } - @GetJson - public void download(@ParameterObject VideoReq req){ - gb28181DownloadService.download(req.getDeviceCode(),req.getStartTime(),req.getEndTime()); - } - @Operation(summary = "下载代理") @RequestMapping(method = RequestMethod.HEAD, value = "/proxy") public void downloadProxyHeader(HttpServletRequest request, HttpServletResponse response, @RequestParam String url) { diff --git a/gb28181-wvp-proxy-api/src/main/java/cn/skcks/docking/gb28181/wvp/api/video/VideoController.java b/gb28181-wvp-proxy-api/src/main/java/cn/skcks/docking/gb28181/wvp/api/video/VideoController.java index cb1d19e..d3e0584 100644 --- a/gb28181-wvp-proxy-api/src/main/java/cn/skcks/docking/gb28181/wvp/api/video/VideoController.java +++ b/gb28181-wvp-proxy-api/src/main/java/cn/skcks/docking/gb28181/wvp/api/video/VideoController.java @@ -3,6 +3,8 @@ package cn.skcks.docking.gb28181.wvp.api.video; import cn.skcks.docking.gb28181.media.config.ZlmMediaConfig; import cn.skcks.docking.gb28181.wvp.api.video.dto.VideoReq; import cn.skcks.docking.gb28181.wvp.config.SwaggerConfig; +import cn.skcks.docking.gb28181.wvp.config.WvpProxyConfig; +import cn.skcks.docking.gb28181.wvp.service.gb28181.Gb28181DownloadService; import cn.skcks.docking.gb28181.wvp.service.video.VideoService; import cn.skcks.docking.gb28181.wvp.service.wvp.WvpService; import io.swagger.v3.oas.annotations.Operation; @@ -30,6 +32,10 @@ public class VideoController { private final VideoService videoService; private final WvpService wvpService; + private final WvpProxyConfig proxyConfig; + + private final Gb28181DownloadService gb28181DownloadService; + @Bean public GroupedOpenApi videoApi() { return SwaggerConfig.api("VideoApi", "/video"); @@ -39,6 +45,10 @@ public class VideoController { @GetMapping(produces = MediaType.APPLICATION_OCTET_STREAM_VALUE) @ResponseBody public void video(HttpServletRequest request, HttpServletResponse response, @ParameterObject VideoReq req) { - wvpService.video(request,response,req.getDeviceCode(), req.getStartTime(), req.getEndTime()); + if(proxyConfig.getEnable()){ + wvpService.video(request,response,req.getDeviceCode(), req.getStartTime(), req.getEndTime()); + } else { + gb28181DownloadService.video(request,response,req.getDeviceCode(), req.getStartTime(), req.getEndTime()); + } } } diff --git a/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/ffmpeg/FfmpegSupportService.java b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/ffmpeg/FfmpegSupportService.java index bcb7cf8..9a4227b 100644 --- a/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/ffmpeg/FfmpegSupportService.java +++ b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/ffmpeg/FfmpegSupportService.java @@ -36,6 +36,7 @@ public class FfmpegSupportService { String logLevelParam = StringUtils.joinWith(" ","-loglevel", rtp.getLogLevel()); String command = StringUtils.joinWith(" ", ffmpegConfig.getFfmpeg(), inputParam, outputParam, logLevelParam); CommandLine commandLine = CommandLine.parse(command); + log.info("{}", commandLine); Executor executor = new DefaultExecutor(); ExecuteWatchdog watchdog = new ExecuteWatchdog(unit.toMillis(time)); executor.setStreamHandler(streamHandler); diff --git a/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/gb28181/Gb28181DownloadService.java b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/gb28181/Gb28181DownloadService.java index 0cc0df2..afffc15 100644 --- a/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/gb28181/Gb28181DownloadService.java +++ b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/gb28181/Gb28181DownloadService.java @@ -1,7 +1,11 @@ package cn.skcks.docking.gb28181.wvp.service.gb28181; +import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUnit; import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.IoUtil; +import cn.skcks.docking.gb28181.common.json.JsonException; +import cn.skcks.docking.gb28181.common.json.JsonResponse; import cn.skcks.docking.gb28181.core.sip.gb28181.sdp.GB28181Description; import cn.skcks.docking.gb28181.core.sip.gb28181.sdp.MediaSdpHelper; import cn.skcks.docking.gb28181.core.sip.gb28181.sdp.StreamMode; @@ -16,10 +20,12 @@ import cn.skcks.docking.gb28181.media.dto.status.ResponseStatus; import cn.skcks.docking.gb28181.media.proxy.ZlmMediaService; import cn.skcks.docking.gb28181.service.ssrc.SsrcService; import cn.skcks.docking.gb28181.wvp.config.ProxySipConfig; +import cn.skcks.docking.gb28181.wvp.config.WvpProxyConfig; import cn.skcks.docking.gb28181.wvp.orm.mybatis.dynamic.model.WvpProxyDevice; import cn.skcks.docking.gb28181.wvp.orm.mybatis.dynamic.model.WvpProxyDocking; import cn.skcks.docking.gb28181.wvp.service.device.DeviceService; import cn.skcks.docking.gb28181.wvp.service.docking.DockingService; +import cn.skcks.docking.gb28181.wvp.service.video.VideoService; import cn.skcks.docking.gb28181.wvp.sip.request.SipRequestBuilder; import cn.skcks.docking.gb28181.wvp.sip.sender.SipSender; import cn.skcks.docking.gb28181.wvp.sip.subscribe.SipSubscribe; @@ -27,10 +33,14 @@ import gov.nist.javax.sdp.MediaDescriptionImpl; import gov.nist.javax.sdp.fields.TimeField; import gov.nist.javax.sdp.fields.URIField; import gov.nist.javax.sip.message.SIPResponse; +import jakarta.servlet.AsyncContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import javax.sdp.Connection; @@ -40,6 +50,8 @@ import javax.sip.ListeningPoint; import javax.sip.header.CallIdHeader; import javax.sip.message.Request; import javax.sip.message.Response; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; import java.util.Date; import java.util.List; import java.util.Optional; @@ -58,12 +70,31 @@ public class Gb28181DownloadService { private final ProxySipConfig proxySipConfig; private final SipSender sender; private final SipSubscribe subscribe; + private final VideoService videoService; + + private final WvpProxyConfig wvpProxyConfig; private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); + public void header(HttpServletResponse response, String fileName) { + response.setContentType("video/mp4"); + response.setHeader("Accept-Ranges", "none"); + response.setHeader("Connection", "close"); + response.setHeader("Content-Disposition", + MessageFormat.format("attachment; filename=\"{0}.mp4\"",fileName)); + } + private String videoUrl(String streamId) { return StringUtils.joinWith("/", zlmMediaConfig.getUrl(), "rtp", streamId + ".live.flv"); } + @SneakyThrows + private void writeErrorToResponse(HttpServletResponse response, JsonResponse json) { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + IoUtil.writeUtf8(response.getOutputStream(), false, json); + } + private int openRtpServer(String streamId, int streamMode) { GetRtpInfoResp rtpInfo = zlmMediaService.getRtpInfo(streamId); if (rtpInfo.getExist()) { @@ -84,13 +115,45 @@ public class Gb28181DownloadService { return openRtpServerResp.getPort(); } - public void download(String deviceCode, Date startTime, Date endTime){ + @SneakyThrows + public void video(HttpServletRequest request, HttpServletResponse response, String deviceCode, Date startTime, Date endTime) { + AsyncContext asyncContext = request.startAsync(); + asyncContext.start(()->{ + HttpServletResponse asyncResponse = (HttpServletResponse)asyncContext.getResponse(); + try{ + header(response, StringUtils.joinWith("_", + deviceCode, + DateUtil.format(startTime, DatePattern.PURE_DATETIME_FORMAT), + DateUtil.format(endTime, DatePattern.PURE_DATETIME_FORMAT))); + + download(deviceCode, startTime,endTime).whenComplete((url, e)->{ + if(e != null){ + writeErrorToResponse(asyncResponse, JsonResponse.error(e.getMessage())); + } + if(wvpProxyConfig.getUseFfmpeg()){ + videoService.ffmpegRecord(asyncResponse, url, DateUtil.between(startTime,endTime,DateUnit.SECOND) + 60); + } else { + videoService.javaCVrecord(asyncResponse, url, DateUtil.between(startTime,endTime,DateUnit.SECOND) + 60); + } + asyncContext.complete(); + }); + } catch(Exception e) { + writeErrorToResponse(asyncResponse, JsonResponse.error(e.getMessage())); + asyncContext.complete(); + } + }); + } + + @SneakyThrows + public CompletableFuture download(String deviceCode, Date startTime, Date endTime) { List deviceByDeviceCode = deviceService.getDeviceByDeviceCode(deviceCode); if (deviceByDeviceCode.isEmpty()) { - log.info("未能找到 设备编码 为 {} 的设备",deviceCode); + String reason = MessageFormat.format("未能找到 设备编码 为 {0} 的设备", deviceCode); + log.error("{}",reason); + throw new JsonException(reason); } else { WvpProxyDevice device = deviceByDeviceCode.get(0); - download(device.getGbDeviceId(), device.getGbDeviceChannelId(), startTime, endTime); + return download(device.getGbDeviceId(), device.getGbDeviceChannelId(), startTime, endTime); } } @@ -146,14 +209,14 @@ public class Gb28181DownloadService { CallIdHeader callId = provider.getNewCallId(); String subscribeKey = GenericSubscribe.Helper.getKey(Request.INVITE, callId.getCallId()); subscribe.getInviteSubscribe().addPublisher(subscribeKey); - Flow.Subscriber subscriber = inviteSubscriber(docking,device,subscribeKey, ssrc, streamId, result, time); + Flow.Subscriber subscriber = inviteSubscriber(docking,device,subscribeKey, ssrc, streamId, result); subscribe.getInviteSubscribe().addSubscribe(subscribeKey, subscriber); scheduledExecutorService.schedule(subscriber::onComplete, time + 60, TimeUnit.SECONDS); return SipRequestBuilder.createInviteRequest(ip, port, docking, device.getGbDeviceChannelId(), description.toString(), SipUtil.generateViaTag(), SipUtil.generateFromTag(), null, ssrc, callId); }; } - public Flow.Subscriber inviteSubscriber(WvpProxyDocking docking, WvpProxyDevice device, String subscribeKey,String ssrc,String streamId,CompletableFuture result, long time){ + public Flow.Subscriber inviteSubscriber(WvpProxyDocking docking, WvpProxyDevice device, String subscribeKey,String ssrc,String streamId,CompletableFuture result){ return new Flow.Subscriber<>() { private Flow.Subscription subscription; diff --git a/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/video/VideoService.java b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/video/VideoService.java index 5b444aa..2bb1b4e 100644 --- a/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/video/VideoService.java +++ b/gb28181-wvp-proxy-service/src/main/java/cn/skcks/docking/gb28181/wvp/service/video/VideoService.java @@ -161,20 +161,16 @@ public class VideoService { PumpStreamHandler streamHandler = new PumpStreamHandler(outputStream, errorStream); DefaultExecuteResultHandler defaultExecuteResultHandler = new DefaultExecuteResultHandler(); Executor executor = ffmpegSupportService.downloadToStream(url, time, TimeUnit.SECONDS,streamHandler,defaultExecuteResultHandler); - // executor.setStreamHandler(streamHandler); log.info("开始录制 {}", url); ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); - AtomicBoolean record = new AtomicBoolean(true); scheduledExecutorService.schedule(() -> { log.info("到达结束时间, 结束录制 {}", url); executor.getWatchdog().destroyProcess(); log.info("结束录制 {}", url); - // try { - // outputStream.close(); - // } catch (IOException e) { - // throw new RuntimeException(e); - // } }, time, TimeUnit.SECONDS); defaultExecuteResultHandler.waitFor(); + if(errorStream.size() > 0){ + log.error("{}", errorStream); + } } } diff --git a/gb28181-wvp-proxy-starter/src/main/resources/application-local.yml b/gb28181-wvp-proxy-starter/src/main/resources/application-local.yml index c8ec726..b5c9926 100644 --- a/gb28181-wvp-proxy-starter/src/main/resources/application-local.yml +++ b/gb28181-wvp-proxy-starter/src/main/resources/application-local.yml @@ -45,9 +45,9 @@ proxy: wvp: url: http://127.0.0.1:18978 user: admin - passwd: admi - use-ffmpeg: false - enable: true + passwd: admin + use-ffmpeg: true + enable: false gb28181: sip: id: 44050100002000000003 @@ -60,15 +60,15 @@ proxy: ffmpeg-support: task: max: 4 - ffmpeg: D:\Soft\Captura\ffmpeg\ffmpeg.exe + ffmpeg: C:\ffmpeg\bin\ffmpeg.exe ffprobe: D:\Soft\Captura\ffmpeg\ffprobe.exe rtp: - input: -r -i http://10.10.10.200:5080/live/test.live.flv - # input: -re -i +# input: -i http://10.10.10.200:5080/live/test.live.flv + input: -re -i output: -vcodec copy -acodec copy -movflags empty_moov+frag_keyframe+default_base_moof -f mp4 # -rtsp_transport tcp debug: download: false - input: true + input: false output: false # [可选] 日志配置, 一般不需要改 diff --git a/gb28181-wvp-proxy-starter/src/main/resources/application.yml b/gb28181-wvp-proxy-starter/src/main/resources/application.yml index e45e7b2..532955f 100644 --- a/gb28181-wvp-proxy-starter/src/main/resources/application.yml +++ b/gb28181-wvp-proxy-starter/src/main/resources/application.yml @@ -50,7 +50,9 @@ proxy: user: admin passwd: admin use-wvp-assist: false - enable: true + # 是否使用 wvp 的 api(wvp 的 并发有问题,仅保留用于兼容), 否则使用sip 信令直接操作设备 + enable: false + # 是否使用 ffmpeg 编/解码, 否则使用内置 javacv use-ffmpeg: false gb28181: sip: @@ -66,7 +68,8 @@ proxy: ffmpeg-support: task: max: 4 - ffmpeg: /usr/bin/ffmpeg/ffmpeg +# ffmpeg: /usr/bin/ffmpeg/ffmpeg + ffmpeg: C:\ffmpeg\bin\ffmpeg.exe ffprobe: /usr/bin/ffmpeg/ffprobe rtp: input: -i