微服务[学成在线] day14:媒资管理

😎 知识点概览

为了方便后续回顾该项目时能够清晰的知道本章节讲了哪些内容,并且能够从该章节的笔记中得到一些帮助,所以在完成本章节的学习后在此对本章节所涉及到的知识点进行总结概述。

本章节为【学成在线】项目的 day14 的内容

  • 视频上传成功后通过 RabbitMQ 进行消息发送,再通过 视频处理服务 对视频进行格式转换,以及 m3u8 视频文件的生成。
  • 实现媒资信息的浏览
  • Vue 跨组件间的通讯实战,实现课程计划与已上传的媒资文件的关联

目录

知识点结合实战应用会更有意义,所以这里不再对单个知识点进行拆分成单个笔记,内容会比较多,可以根据目录进行按需查阅。

一、视频处理

0x01 需求分析

原始视频通常需要经过编码处理,生成 m3u8ts 文件方可基于 HLS 协议播放视频。通常用户上传原始视频,系统自动处理成标准格式,系统对用户上传的视频自动编码、转换,最终生成m3u8 文件和 ts 文件,处理流程如下:

1、用户上传视频成功

2、系统对上传成功的视频自动开始编码处理

3、用户查看视频处理结果,没有处理成功的视频用户可在管理界面再次触发处理

4、视频处理完成将视频地址及处理结果保存到数据库

视频处理流程如下:

视频处理进程的任务是接收视频处理消息进行视频处理,业务流程如下:

1、监听 MQ,接收视频处理消息。

2、进行视频处理。

3、向数据库写入视频处理结果。

视频处理进程属于媒资管理系统的一部分,考虑提高系统的扩展性,将视频处理单独定义视频处理工程。

0x02 视频处理开发

视频处理工程创建

1、导入“资料” 下的视频处理工程:xc-service-manage-media-processor

2、RabbitMQ 配置

使用 rabbitMQrouting 交换机模式,视频处理程序监听视频处理队列,如下图:

RabbitMQ配置如下:

@Configuration
public class RabbitMQConfig {
   
    public static final String EX_MEDIA_PROCESSTASK = "ex_media_processor";
    //视频处理队列
    @Value("${xc‐service‐manage‐media.mq.queue‐media‐video‐processor}")
    public String queue_media_video_processtask;
    //视频处理路由
    @Value("${xc‐service‐manage‐media.mq.routingkey‐media‐video}")
    public String routingkey_media_video;
    /**
* 交换机配置
* @return the exchange
*/
    @Bean(EX_MEDIA_PROCESSTASK)
    public Exchange EX_MEDIA_VIDEOTASK() {
   
        return ExchangeBuilder.directExchange(EX_MEDIA_PROCESSTASK).durable(true).build();
    } 
    //声明队列
    @Bean("queue_media_video_processtask")
    public Queue QUEUE_PROCESSTASK() {
   
        Queue queue = new Queue(queue_media_video_processtask,true,false,true);
        return queue;
    } 
    /**
* 绑定队列到交换机 .
* @param queue the queue
* @param exchange the exchange
* @return the binding
*/
    @Bean
    public Binding binding_queue_media_processtask(@Qualifier("queue_media_video_processtask")Queue queue, @Qualifier(EX_MEDIA_PROCESSTASK) Exchange exchange) {
   
        return BindingBuilder.bind(queue).to(exchange).with(routingkey_media_video).noargs();
    }
}

application.yml 中配置队列名称及 routingkey

xc‐service‐manage‐media:
  mq:
    queue‐media‐video‐processor: queue_media_video_processor
    routingkey‐media‐video: routingkey_media_video

视频处理技术方案

如何通过程序进行视频处理?

ffmpeg 是一个可行的视频处理程序,可以通过 Java 调用 ffmpeg.exe 完成视频处理。

java 中可以使用 Runtime 类和 Process Builder 类两种方式来执行外部程序,工作中至少掌握一种。

本项目使用 Process Builder 的方式来调用 ffmpeg 完成视频处理。

关于 Process Builder 的测试如下:

//测试ping命令
@Test
public void testProcessBuilder() throws IOException {
   
    //创建ProcessBuilder对象
    ProcessBuilder processBuilder = new ProcessBuilder();
    //设置执行的第三方程序(命令)
    List<String> cmds = new ArrayList<>();
    cmds.add("ping");
    cmds.add("127.0.0.1");
    processBuilder.command(cmds);
    //合并标准输入流和错误输出
    processBuilder.redirectErrorStream(true);
    Process start = processBuilder.start();
    //获取输入流
    InputStream inputStream = start.getInputStream();
    //将输入流转换为字符输入流
    InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "gbk");

    //获取流的数据
    int len = -1;
    //数据缓冲区
    char[] cache = new char[1024];
    StringBuffer stringBuffer = new StringBuffer();
    while ((len = inputStreamReader.read(cache)) != -1) {
   
        //获取缓冲区内的数据
        String outStr = new String(cache, 0, len);
        System.out.println(outStr);
        stringBuffer.append(outStr);
    }
    inputStream.close();
}

//测试使用工具类将avi转成mp4
@Test
public void testProcessMp4() throws IOException {
   
    ProcessBuilder processBuilder = new ProcessBuilder();
    //定义命令内容
    List<String> command = new ArrayList<>();
    command.add("D:/soft/ffmpeg-20200315-c467328-win64-static/bin/ffmpeg.exe");
    command.add("-i");
    command.add("E:/temp/1.avi");
    command.add("-y"); //覆盖输出文件
    command.add("-c:v");
    command.add("libx264");
    command.add("-s");
    command.add("1280x720");
    command.add("-pix_fmt");
    command.add("yuv420p");
    command.add("-b:a");
    command.add("63k");
    command.add("-b:v");
    command.add("753k");
    command.add("-r");
    command.add("18");
    command.add("E:/temp/1.mp4");
    processBuilder.command(command);
    //将标准输入流和错误输入流合并,通过标准输入流读取信息
    processBuilder.redirectErrorStream(true);
    Process start = processBuilder.start();
    InputStream inputStream = start.getInputStream();
    InputStreamReader streamReader = new InputStreamReader(inputStream, "gbk");

    //获取输入流数据
    int len = -1;
    //数据缓冲区
    char[] cache = new char[1024];
    StringBuffer stringBuffer = new StringBuffer();
    while ((len=streamReader.read(cache)) != -1){
   
        //从缓冲区获取数据
        String out = new String(cache, 0, len);
        System.out.println(out);
        stringBuffer.append(out);
    }
    inputStream.close();
}

上边的代码已经封装成工具类,参见:

上边的工具类中:

Mp4VideoUtil.java 完成 avimp4

HlsVideoUtil.java 完成 mp4hls

分别测试每个工具类的使用方法。

public static void main(String[] args) throws IOException {
   
    String ffmpeg_path = "D:/soft/ffmpeg-20200315-c467328-win64-static/bin/ffmpeg.exe";//ffmpeg的安装位置
    String video_path = "E:\\temp\\1.avi";
    String mp4_name = "2.mp4";
    String mp4_path = "E:\\temp\\";
    Mp4VideoUtil videoUtil = new Mp4VideoUtil(ffmpeg_path,video_path,mp4_name,mp4_path);
    String s = videoUtil.generateMp4();
    System.out.println(s);
}

视频处理实现

1、确定消息格式

MQ 消息统一采用 json 格式,视频处理生产方会向 MQ 发送如下消息,视频处理消费方接收此消息后进行视频处
理:

{
   "mediaId":XXX}
2、处理流程

1)接收视频处理消息

2)判断媒体文件是否需要处理(本视频处理程序目前只接收avi 视频的处理)当前只有 avi 文件需要处理,其它文件需要更新处理状态为 “无需处理”。

3)处理前初始化处理状态为 “未处理

4)处理失败需要在数据库记录处理日志,及处理状态为 “处理失败

5)处理成功记录处理状态为 “处理成功

3、数据模型

MediaFile 类中添加 mediaFileProcess_m3u8 属性记录 ts 文件列表,代码如下 :

//处理状态
private String processStatus;
//hls处理
private MediaFileProcess_m3u8 mediaFileProcess_m3u8;

MediaFileProcess_m3u8 如下

@Data
@ToString
public class MediaFileProcess_m3u8 extends MediaFileProcess {
   
//ts列表
private List<String> tslist;
}
4、视频处理生成 MP4

1)创建 dao

视频处理结果需要保存到媒资数据库,创建 dao 如下:

public interface MediaFileRepository extends MongoRepository<MediaFile,String> {
   
    
}

2)在 application.yml 中配置 ffmpeg 的位置及视频目录的根目录

xc‐service‐manage‐media:
  video‐location: F:/develop/video/
  ffmpeg‐path: D:/Program Files/ffmpeg‐20180227‐fa0c9d6‐win64‐static/bin/ffmpeg.exe

3)处理任务类

mq 包下创建 MediaProcessTask 类,此类负责监听视频处理队列,并进行视频处理。

整个视频处理内容较多,这里分两部分实现:生成 Mp4 和生成 m3u8,下边代码实现了生成 mp4

@Component
public class MediaProcessTask {
   
    //日志对象
    private static final Logger LOGGER = LoggerFactory.getLogger(MediaProcessTask.class);

    //ffmpeg绝对路径
    @Value("${xc-service-manage-media.ffmpeg-path}")
    String ffmpeg_path;

    //上传文件根目录
    @Value("${xc-service-manage-media.video-location}")
    String serverPath;

    @Autowired
    MediaFileRepository mediaFileRepository;

    @RabbitListener(queues = "${xc-service-manage-media.mq.queue-media-video-processor}")
    public void receiveMediaProcessTask(String msg){
   
        //将接收到的消息转换为json数据
        Map msgMap = JSON.parseObject(msg);
        LOGGER.info("receive media process task msg :{} ",msgMap);
        //解析消息
        //媒资文件id
        String mediaId = (String) msgMap.get("mediaId");
        //获取媒资文件信息
        Optional<MediaFile> byId = mediaFileRepository.findById(mediaId);
        if(!byId.isPresent()){
   
            return;
        }
        MediaFile mediaFile = byId.get();
        //媒资文件类型
        String fileType = mediaFile.getFileType();
        //目前只处理avi文件
        if(fileType == null || !fileType.equals("avi")){
   
            mediaFile.setProcessStatus("303004"); // 处理状态为无需处理
            mediaFileRepository.save(mediaFile);
        }else{
   
            mediaFile.setProcessStatus("303001"); //处理状态为未处理
        }
        //生成MP4
        String videoPath = serverPath + mediaFile.getFilePath() + mediaFile.getFileName();
        String mp4Name = mediaFile.getFileId() + ".mp4";
        String mp4FloderPath = serverPath  + mediaFile.getFilePath();
        Mp4VideoUtil mp4VideoUtil = new Mp4VideoUtil(ffmpeg_path, videoPath, mp4Name, mp4FloderPath);
        String result = mp4VideoUtil.generateMp4();
        if(result == null || !result.equals("success")){
   
            //操作失败写入处理日志
            mediaFile.setProcessStatus("303003");//处理状态为处理失败
            MediaFileProcess_m3u8 mediaFileProcess_m3u8 = new MediaFileProcess_m3u8();
            mediaFileProcess_m3u8.setErrormsg(result);
            mediaFile.setMediaFileProcess_m3u8(mediaFileProcess_m3u8);
            mediaFileRepository.save(mediaFile);
            return;
        }

        //生成m3u8...
    }
}

说明:

1、原始视频转成 mp4 如何判断转换成功?

根据视频时长来判断,取原视频和转换成功视频的时长(时分秒),如果相等则相同。

5、视频处理生成 m3u8

下边是完整的视频处理任务类代码,包括了生成 m3u8 及生成 mp4 的代码

package com.xuecheng.manage_media_process.mq;

import com.alibaba.fastjson.JSON;
import com.xuecheng.framework.domain.media.MediaFile;
import com.xuecheng.framework.domain.media.MediaFileProcess_m3u8;
import com.xuecheng.framework.utils.HlsVideoUtil;
import com.xuecheng.framework.utils.Mp4VideoUtil;
import com.xuecheng.manage_media_process.dao.MediaFileRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.Optional;

@Component
public class MediaProcessTask {
   
    //日志对象
    private static final Logger LOGGER = LoggerFactory.getLogger(MediaProcessTask.class);

    //ffmpeg绝对路径
    @Value("${xc-service-manage-media.ffmpeg-path}")
    String ffmpeg_path;

    //上传文件根目录
    @Value("${xc-service-manage-media.video-location}")
    String serverPath;

    @Autowired
    MediaFileRepository mediaFileRepository;

    @RabbitListener(queues = "${xc-service-manage-media.mq.queue-media-video-processor}")
    public void receiveMediaProcessTask(String msg){
   
        //将接收到的消息转换为json数据
        Map msgMap = JSON.parseObject(msg);
        LOGGER.info("receive media process task msg :{} ",msgMap);
        //解析消息
        //媒资文件id
        String mediaId = (String) msgMap.get("mediaId");
        //获取媒资文件信息
        Optional<MediaFile> byId = mediaFileRepository.findById(mediaId);
        if(!byId.isPresent()){
   
            return;
        }
        MediaFile mediaFile = byId.get();
        //媒资文件类型
        String fileType = mediaFile.getFileType();
        //目前只处理avi文件
        if(fileType == null || !fileType.equals("avi")){
   
            mediaFile.setProcessStatus("303004"); // 处理状态为无需处理
            mediaFileRepository.save(mediaFile);
        }else{
   
            mediaFile.setProcessStatus("303001"); //处理状态为未处理
        }
        //生成MP4
        String videoPath = serverPath + mediaFile.getFilePath() + mediaFile.getFileName();
        String mp4Name = mediaFile.getFileId() + ".mp4";
        String mp4FloderPath = serverPath  + mediaFile.getFilePath();
        Mp4VideoUtil mp4VideoUtil = new Mp4VideoUtil(ffmpeg_path, videoPath, mp4Name, mp4FloderPath);
        String result = mp4VideoUtil.generateMp4();
        if(result == null || !result.equals("success")){
   
            //操作失败写入处理日志
            mediaFile.setProcessStatus("303003");//处理状态为处理失败
            MediaFileProcess_m3u8 mediaFileProcess_m3u8 = new MediaFileProcess_m3u8();
            mediaFileProcess_m3u8.setErrormsg(result);
            mediaFile.setMediaFileProcess_m3u8(mediaFileProcess_m3u8);
            mediaFileRepository.save(mediaFile);
            return;
        }

        //生成m3u8列表
        //生成m3u8
        String mp4VideoPath = serverPath + mediaFile.getFilePath()+ mp4Name;//此地址为mp4的地址
        String m3u8Name = mediaFile.getFileId()+".m3u8";
        String m3u8FolderPath = serverPath + mediaFile.getFilePath()+"hls/";
        //调用工具类进行生成m3u8
        HlsVideoUtil hlsVideoUtil = new HlsVideoUtil(ffmpeg_path, mp4VideoPath, m3u8Name, m3u8FolderPath);
        String m3u8Result = hlsVideoUtil.generateM3u8();

        if(m3u8Result==null || !m3u8Result.equals("success")){
   
            //操作失败写入处理日志
            mediaFile.setProcessStatus("303003");//处理状态为处理失败
            MediaFileProcess_m3u8 mediaFileProcess_m3u8 = new MediaFileProcess_m3u8();
            mediaFileProcess_m3u8.setErrormsg(m3u8Result);
            mediaFile.setMediaFileProcess_m3u8(mediaFileProcess_m3u8);
            mediaFileRepository.save(mediaFile);
            return ;
        }

        //获取m3u8列表
        List<String> ts_list = hlsVideoUtil.get_ts_list();
        //更新处理状态为成功
        mediaFile.setProcessStatus("303002");//处理状态为处理成功
        MediaFileProcess_m3u8 mediaFileProcess_m3u8 = new MediaFileProcess_m3u8();
        mediaFileProcess_m3u8.setTslist(ts_list);
        mediaFile.setMediaFileProcess_m3u8(mediaFileProcess_m3u8);
        //m3u8文件url
        mediaFile.setFileUrl(mediaFile.getFilePath()+"hls/"+m3u8Name);
        mediaFileRepository.save(mediaFile);
    }
}

0x03 发送视频处理消息

当视频上传成功后向 MQ 发送视频 处理消息。

修改媒资管理服务的文件上传代码,当文件上传成功向 MQ 发送视频处理消息。

配置RabbitMQ

1、将media-processor 工程下的 RabbitmqConfig 配置类拷贝到 media 工程下。

2、在 media 工程下配置 mq 队列等信息

修改 application.yml

xc-service-manage-media:
  mq:
    queue-media-video-processor: queue_media_video_processor
    routingkey-media-video: routingkey_media_video

配置Service

在文件合并方法中添加向 mq 发送视频处理消息的代码:

//视频处理路由
@Value("${xc-service-manage-media.mq.routingkey-media-video}")
public String routingkey_media_video;

@Autowired
RabbitTemplate rabbitTemplate;

//向MQ发送视频处理消息
private ResponseResult sendProcessVideoMsg(String mediaId){
   
    Optional<MediaFile> optional = mediaFileRepository.findById(mediaId);
    if(!optional.isPresent()){
   
        return new ResponseResult(CommonCode.FAIL);
    }
    MediaFile mediaFile = optional.get();
    //发送视频处理消息
    Map<String,String> msgMap = new HashMap<>();
    msgMap.put("mediaId",mediaId);
    //发送的消息
    String msg = JSON.toJSONString(msgMap);
    try {
   
        this.rabbitTemplate.convertAndSend(RabbitMQConfig.EX_MEDIA_PROCESSTASK,routingkey_media_video,msg);
        LOGGER.info("send media process task msg:{}",msg);
    }catch (Exception e){
   
        e.printStackTrace();
        LOGGER.info("send media process task error,msg is:{},error:{}",msg,e.getMessage());
        return new ResponseResult(CommonCode.FAIL);
    }
    return new ResponseResult(CommonCode.SUCCESS)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值