diff --git a/.gitignore b/.gitignore index f7d69d8..0496bfa 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ hs_err_pid* replay_pid* logs/ +.idea/ diff --git a/src/main/kotlin/com/hisense/dahua_video/util/ScreenshotUtil.kt b/src/main/kotlin/com/hisense/dahua_video/util/ScreenshotUtil.kt index fa7653a..2a07dc8 100644 --- a/src/main/kotlin/com/hisense/dahua_video/util/ScreenshotUtil.kt +++ b/src/main/kotlin/com/hisense/dahua_video/util/ScreenshotUtil.kt @@ -12,7 +12,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import org.bytedeco.ffmpeg.avcodec.AVPacket import org.bytedeco.javacv.FFmpegFrameGrabber +import org.bytedeco.javacv.FFmpegFrameRecorder import org.bytedeco.javacv.Java2DFrameConverter import java.io.File import java.time.LocalDateTime @@ -218,6 +220,7 @@ class ScreenshotUtil(vertx: Vertx) { fun recordAction(screenshot: JsonObject): JsonObject { taskCount.getAndAdd(1) val url = screenshot.getString("url") + val duration = screenshot.getLong("duration", 120) // 时长秒 logger.info("抓取url:$url") val result = JsonObject() result.put("startTime", LocalDateTime.now().toString()) @@ -231,39 +234,44 @@ class ScreenshotUtil(vertx: Vertx) { grabber.setOption("stimeout", "2000000") grabber.start() logger.info("grabber启动成功") - while (true) { - when (val frame = grabber.grabImage()) { - null -> { - logger.info("截图取帧失败") + val videoWidth = grabber.imageWidth + val videoHeight = grabber.imageHeight + val audioChannels = grabber.audioChannels + val fileName = + record + File.separator + screenshot.getString("channelId") + + File.separator + screenshot.getString("channelId") + "_" + DateTimeFormatter.ofPattern( + "yyyyMMddHHmmss-HHmmss" + ) + .format(LocalDateTime.now()) + ".mp4" + val recorder = FFmpegFrameRecorder(fileName, videoWidth, videoHeight, audioChannels) + recorder.videoBitrate = 4096 + recorder.gopSize = 2 + recorder.frameRate = 25.0 + recorder.videoCodecName = "copy" + recorder.format = "mp4" + try { + recorder.start(grabber.formatContext) + val startTime = System.currentTimeMillis() // 开始录制时间 + while (true) { // 不断循环录制 + val packet = grabber.grabPacket() + if (packet != null) { + recorder.recordPacket(packet) } - - else -> { - val bufferedImage = converter.getBufferedImage(frame) - val filePath = record + File.separator + screenshot.getString("channelId") - if (!File(filePath).exists()) { - File(filePath).mkdir() - } - val fileName = - record + File.separator + screenshot.getString("channelId") + - File.separator + screenshot.getString("channelId") + "_" + DateTimeFormatter.ofPattern( - "yyyyMMddHHmmss-HHmmss" - ) - .format(LocalDateTime.now()) + ".jpg" - logger.info("保存地址:$fileName") - ImageIO.write(bufferedImage, "jpg", File(fileName)) - logger.info("图片保存成功:$fileName") - event!!.publish( - EventBusAddress.SYS_DEVICE_CHANNEL_SCREENSHOT_PICTURE_SAVE.address, - JsonObject().put("fileSize", File(fileName).length()) - .put("channelNo", screenshot.getString("channelId")) - .put("filePath", fileName) - ) + var durationMS = System.currentTimeMillis() - startTime + if (durationMS > (duration * 1000)) { // 超过预设录制时长 break } } + } catch (e: FFmpegFrameRecorder.Exception) { + logger.info("recorder启动失败", e) + } finally { + logger.info("视频保存成功:$fileName") + recorder.stop() + recorder.close() + recorder.release() } } catch (e: Exception) { - logger.error("抓取截图·失败·", e) + logger.error("抓取录制短视频·失败·", e) } finally { converter.close() grabber.stop() @@ -275,5 +283,50 @@ class ScreenshotUtil(vertx: Vertx) { return result } + event!!.consumer(EventBusAddress.SYS_DEVICE_CHANNEL_SCREENSHOT_VIDEO_SAVE.address)?.handler { replay -> + val message = replay.body() + val port = message.getString("monitorDomain").substringAfterLast(":").toInt() + var domain = message.getString("monitorDomain").substringBeforeLast(":").substringAfterLast("/") + val tokenString = message.getString("token") + + val channelId = message.getString("channelId") + val subType = message.getString("subType") + val scheme = message.getString("scheme") + + val duration = message.getLong("duration", 120) // 时长秒 + + val realmonitorUrl = "/videoService/realmonitor/uri?channelId=$channelId&subType=$subType&scheme=$scheme" + logger.info("获取设备通道预览url:$realmonitorUrl") + client!!.get(port, domain, realmonitorUrl) + .putHeader("X-Subject-Token", tokenString) + .send() + .onSuccess { res -> + val result = res.bodyAsJsonObject() + result.put("scheme", scheme) + logger.info("获取设备通道预览 statusCode:" + res.statusCode()) + if (res.statusCode() == 200) { + result.put("channelId", channelId) // 通道id + result.put("duration", duration) + GlobalScope.launch(Dispatchers.IO) { + while (true) { + if (taskCount.get() < cpuNum * 2 - 1) { + replay.reply(recordAction(result)) + break + } else { + logger.info("截图队列已满,尝试重试中:${result.encode()}") + val random = (3000..10000).random().toLong() + delay(random) + } + } + } + } else { + replay.reply(JsonObject().put("msg", "获取预览地址失败")) + } + } + .onFailure { + replay.reply(JsonObject().put("msg", "获取预览地址失败")) + } + } + } }