From 359cabbd2cb9a8e3643e339193e77fd56b218661 Mon Sep 17 00:00:00 2001 From: DragonWenLong <552045633@qq.com> Date: Thu, 20 Nov 2025 09:06:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(video):=20=E6=94=AF=E6=8C=81BGR565?= =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E8=A7=86=E9=A2=91=E5=A4=84=E7=90=86-=20?= =?UTF-8?q?=E6=96=B0=E5=A2=9Ecode=E5=8F=82=E6=95=B0=E7=94=A8=E4=BA=8E?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E8=A7=86=E9=A2=91=E8=BD=AC=E7=A0=81=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=20-=20=E5=AE=9E=E7=8E=B0BGR565=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E7=9A=84=E5=B8=A7=E6=95=B0=E6=8D=AE=E8=BD=AC=E6=8D=A2=E9=80=BB?= =?UTF-8?q?=E8=BE=91=20-=20=E6=B7=BB=E5=8A=A0convertFramesToBGR565?= =?UTF-8?q?=E5=92=8CconvertToBGR565=E6=96=B9=E6=B3=95=20-=20=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=B0=86BGR565=E6=95=B0=E6=8D=AE=E9=80=9A=E8=BF=87FFm?= =?UTF-8?q?peg=E7=94=9F=E6=88=90MP4=E6=96=87=E4=BB=B6-=20=E6=9B=B4?= =?UTF-8?q?=E6=96=B0VideoProcessUtil=E5=B7=A5=E5=85=B7=E7=B1=BB=E4=BB=A5?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E4=B8=A4=E7=A7=8D=E9=A2=9C=E8=89=B2=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=20-=20=E5=9C=A8=E8=A7=86=E9=A2=91=E5=A4=84=E7=90=86?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E4=B8=AD=E8=AE=B0=E5=BD=95=E5=A4=84=E7=90=86?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E5=8F=8Ahex=E5=88=97=E8=A1=A8=E4=BF=A1?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/controller/AppVideoController.java | 7 +- .../app/service/VideoProcessService.java | 7 +- .../fuyuanshen/web/util/VideoProcessUtil.java | 144 +++++++++++++++++- 3 files changed, 148 insertions(+), 10 deletions(-) diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java index f48a1a0..87c0840 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/controller/AppVideoController.java @@ -29,10 +29,13 @@ public class AppVideoController extends BaseController { private final VideoProcessService videoProcessService; private final AudioProcessService audioProcessService; + /** + * 上传视频转码code默认1:RGB565 2:BGR565 + */ @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS,message = "请勿重复提交!") - public R> uploadVideo(@RequestParam("file") MultipartFile file) { - return R.ok(videoProcessService.processVideo(file)); + public R> uploadVideo(@RequestParam("file") MultipartFile file, @RequestParam(defaultValue = "1") int code) { + return R.ok(videoProcessService.processVideo(file, code)); } /** diff --git a/fys-admin/src/main/java/com/fuyuanshen/app/service/VideoProcessService.java b/fys-admin/src/main/java/com/fuyuanshen/app/service/VideoProcessService.java index a141f52..1fde958 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/app/service/VideoProcessService.java +++ b/fys-admin/src/main/java/com/fuyuanshen/app/service/VideoProcessService.java @@ -28,7 +28,7 @@ public class VideoProcessService { private final VideoProcessUtil videoProcessUtil; - public List processVideo(MultipartFile file) { + public List processVideo(MultipartFile file, int code) { // 1. 参数校验 validateVideoFile(file); @@ -39,9 +39,10 @@ public class VideoProcessService { // 3. 处理视频并提取帧数据 List hexList = videoProcessUtil.processVideoToHex( - tempFile, FRAME_RATE, DURATION, WIDTH, HEIGHT + tempFile, FRAME_RATE, DURATION, WIDTH, HEIGHT, code ); - + log.info("code: {} hexList(前100个): {}", code, + hexList.subList(0, Math.min(100, hexList.size()))); log.info("视频处理成功,生成Hex数据长度: {}", hexList.size()); return hexList; diff --git a/fys-admin/src/main/java/com/fuyuanshen/web/util/VideoProcessUtil.java b/fys-admin/src/main/java/com/fuyuanshen/web/util/VideoProcessUtil.java index 06f96d5..2381390 100644 --- a/fys-admin/src/main/java/com/fuyuanshen/web/util/VideoProcessUtil.java +++ b/fys-admin/src/main/java/com/fuyuanshen/web/util/VideoProcessUtil.java @@ -11,6 +11,8 @@ import javax.imageio.ImageIO; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; +import java.io.OutputStream; import java.nio.file.Files; import java.util.ArrayList; import java.util.List; @@ -37,15 +39,26 @@ public class VideoProcessUtil { /** * 处理视频并转换为Hex字符串列表 */ - public List processVideoToHex(File videoFile, int frameRate, int duration, int width, int height) throws Exception { + public List processVideoToHex(File videoFile, int frameRate, int duration, int width, int height, int code) throws Exception { // 1. 提取视频帧 List frames = extractFramesFromVideo(videoFile, frameRate, duration, width, height); - // 2. 转换为RGB565格式 - byte[] binaryData = convertFramesToRGB565(frames); + if (code == 1) { + // 1. 转换为RGB565格式 + byte[] binaryData = convertFramesToRGB565(frames); - // 3. 转换为Hex字符串列表 - return bytesToHexList(binaryData); + // 2. 转换为Hex字符串列表 + return bytesToHexList(binaryData); + } else { + // 1. 转换为BGR565格式 + byte[] binaryData = convertFramesToBGR565(frames); + + // 新增:直接生成 mp4 + //bgr565ToMp4(binaryData, width, height, frameRate, "output.mp4"); + + // 2. 转换为Hex字符串列表 + return bytesToHexList(binaryData); + } } /** @@ -110,6 +123,55 @@ public class VideoProcessUtil { return result; } + /** + * 将所有帧转换为 BGR565 格式字节数组 + */ + private byte[] convertFramesToBGR565(List frames) throws Exception { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + + for (BufferedImage image : frames) { + byte[] bgr565Bytes = convertToBGR565(image); + byteArrayOutputStream.write(bgr565Bytes); + } + + byte[] result = byteArrayOutputStream.toByteArray(); + log.debug("转换BGR565数据完成,总字节数: {}", result.length); + return result; + } + + /** + * 将BufferedImage转换为真正的BGR565格式字节数组 + */ + private byte[] convertToBGR565(BufferedImage image) { + int width = image.getWidth(); + int height = image.getHeight(); + byte[] bgr565Data = new byte[width * height * 2]; + + int index = 0; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int rgb = image.getRGB(x, y); + + // 提取RGB分量 + int red = (rgb >> 16) & 0xFF; + int green = (rgb >> 8) & 0xFF; + int blue = rgb & 0xFF; + + int b = (blue >> 3) & 0x1F; // 5位蓝色 + int g = (green >> 2) & 0x3F; // 6位绿色 + int r = (red >> 3) & 0x1F; // 5位红色 + + // 正确的BGR565组合:红色在高位,蓝色在低位 + int bgr565 = (b << 11) | (g << 5) | r; + + bgr565Data[index++] = (byte) ((bgr565 >> 8) & 0xFF); + // 小端序存储 + bgr565Data[index++] = (byte) (bgr565 & 0xFF); + } + } + return bgr565Data; + } + /** * 将字节数组转换为Hex字符串列表 */ @@ -191,4 +253,76 @@ public class VideoProcessUtil { } } } + + /** + * 把 BGR565 字节流直接写成 MP4(H.264) + * @param bgr565 完整的 BGR565 裸帧流(每像素 2 字节) + * @param width 帧宽 + * @param height 帧高 + * @param fps 帧率 + * @param outMp4 输出 mp4 文件绝对路径 + * @throws IOException 进程启动 / IO 失败 + */ + public static void bgr565ToMp4(byte[] bgr565, + int width, + int height, + int fps, + String outMp4) throws IOException { + + int framePixels = width * height; + int frameBytes = framePixels * 2; + if (bgr565.length % frameBytes != 0) { + throw new IllegalArgumentException("字节数组长度不是整帧"); + } + + /* 1. 构造 FFmpeg 命令 */ + String[] cmd = { + "ffmpeg", + "-y", // 覆盖输出 + "-f", "rawvideo", + "-pixel_format", "bgr24", + "-video_size", width + "x" + height, + "-framerate", String.valueOf(fps), + "-i", "-", // 从 stdin 读 + "-c:v", "libx264", + "-pix_fmt", "yuv420p", + "-crf", "23", // 画质可自己调 + outMp4 + }; + + /* 2. 启动进程 */ + ProcessBuilder pb = new ProcessBuilder(cmd); + pb.redirectError(ProcessBuilder.Redirect.INHERIT); // 把 FFmpeg 日志打到控制台 + Process p = pb.start(); + try (OutputStream ffmpegIn = p.getOutputStream()) { + + /* 3. 逐帧转换并写入管道 */ + byte[] bgr24 = new byte[framePixels * 3]; + for (int off = 0; off < bgr565.length; off += frameBytes) { + for (int i = 0, j = 0; i < frameBytes; i += 2, j += 3) { + int u = ((bgr565[off + i + 1] & 0xFF) << 8) + | (bgr565[off + i] & 0xFF); + int b = (u & 0x1F) << 3; + int g = ((u >> 5) & 0x3F) << 2; + int r = ((u >> 11) & 0x1F) << 3; + bgr24[j] = (byte) b; + bgr24[j + 1] = (byte) g; + bgr24[j + 2] = (byte) r; + } + ffmpegIn.write(bgr24); + } + ffmpegIn.flush(); + } + + /* 4. 等待编码结束 */ + try { + int exit = p.waitFor(); + if (exit != 0) { + throw new IOException("FFmpeg 异常退出,code=" + exit); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IOException("等待 FFmpeg 被中断", e); + } + } } \ No newline at end of file