A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

【郑州校区】学成在线-第14天-讲义-媒资管理 一

1 视频处理
1.1需求分析
原始视频通常需要经过编码处理,生成m3u8ts文件方可基于HLS协议播放视频。通常用户上传原始视频,系统自动处理成标准格式,系统对用户上传的视频自动编码、转换,最终生成m3u8文件和ts文件,处理流程如下:
1、用户上传视频成功
2、系统对上传成功的视频自动开始编码处理
3、用户查看视频处理结果,没有处理成功的视频用户可在管理界面再次触发处理
4、视频处理完成将视频地址及处理结果保存到数据库
视频处理流程如下:

视频处理进程的任务是接收视频处理消息进行视频处理,业务流程如下:
1、监听MQ,接收视频处理消息。
2、进行视频处理。
3、向数据库写入视频处理结果。

视频处理进程属于媒资管理系统的一部分,考虑提高系统的扩展性,将视频处理单独定义视频处理工程。
1.2 视频处理开发
1.2.1 视频处理工程创建
1、导入资料下的视频处理工程:xc-service-manage-media-processor


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


RabbitMQ配置如下:
[AppleScript] 纯文本查看 复制代码
 @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
[AppleScript] 纯文本查看 复制代码
 xc‐service‐manage‐media:
mq:
queue‐media‐video‐processor: queue_media_video_processor
routingkey‐media‐video: routingkey_media_video


1.2.2 视频处理技术方案
如何通过程序进行视频处理?
ffffmpeg是一个可行的视频处理程序,可以通过Java调用ffffmpeg.exe完成视频处理。
java中可以使用Runtime类和Process Builder类两种方式来执行外部程序,工作中至少掌握一种。
本项目使用Process Builder的方式来调用ffffmpeg完成视频处理。
关于Process Builder的测试如下:
[AppleScript] 纯文本查看 复制代码
 @Test
public void testProcessBuilder(){
ProcessBuilder processBuilder = new ProcessBuilder();
// processBuilder.command("ping","127.0.0.1");
processBuilder.command("ipconfig");
//将标准输入流和错误输入流合并,通过标准输入流读取信息
processBuilder.redirectErrorStream(true);
try {
//启动进程
Process start = processBuilder.start();
//获取输入流
InputStream inputStream = start.getInputStream();
//转成字符输入流
InputStreamReader inputStreamReader = new InputStreamReader(inputStream,"gbk");
int len = ‐1;
char[] c = new char[1024];
StringBuffer outputString = new StringBuffer();
//读取进程输入流中的内容
while ((len= inputStreamReader.read(c))!=‐1) {
String s = new String(c,0,len);
outputString.append(s);
System.out.print(s);
}
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void testFFmpeg(){
ProcessBuilder processBuilder = new ProcessBuilder();
//定义命令内容
List<String> command = new ArrayList<>();
command.add("D:\\Program Files\\ffmpeg‐20180227‐fa0c9d6‐win64‐static\\bin\\ffmpeg.exe");
command.add("‐i");
command.add("E:\\ffmpeg_test\\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:\\ffmpeg_test\\1.mp4");
processBuilder.command(command);
//将标准输入流和错误输入流合并,通过标准输入流读取信息
processBuilder.redirectErrorStream(true);
try {
//启动进程
Process start = processBuilder.start();
//获取输入流
InputStream inputStream = start.getInputStream();
//转成字符输入流
InputStreamReader inputStreamReader = new InputStreamReader(inputStream,"gbk");
int len = ‐1;
char[] c = new char[1024];
StringBuffer outputString = new StringBuffer();
//读取进程输入流中的内容
while ((len= inputStreamReader.read(c))!=‐1) {
String s = new String(c,0,len);
outputString.append(s);
System.out.print(s);
}
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} 


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


上边的工具类中:
Mp4VideoUtil.java完成avimp4

HlsVideoUtil.java完成mp4hls
分别测试每个工具类的使用方法。
[AppleScript] 纯文本查看 复制代码
 public static void main(String[] args) throws IOException {
//ffmpeg的路径
String ffmpeg_path = "D:\\Program Files\\ffmpeg‐20180227‐fa0c9d6‐win64‐
static\\bin\\ffmpeg.exe";//ffmpeg的安装位置
//源avi视频的路径
String video_path = "E:\\ffmpeg_test\\1.avi";
//转换后mp4文件的名称
String mp4_name = "1.mp4";
//转换后mp4文件的路径
String mp4_path = "E:\\ffmpeg_test\\";
//创建工具类对象
Mp4VideoUtil videoUtil = new Mp4VideoUtil(ffmpeg_path,video_path,mp4_name,mp4_path);
//开始视频转换,成功将返回success
String s = videoUtil.generateMp4();
System.out.println(s);
}


1.2.3 视频处理实现
1.2.3.1确定消息格式
MQ消息统一采用json格式,视频处理生产方会向MQ发送如下消息,视频处理消费方接收此消息后进行视频处理:
“mediaId”:XXX
1.2.3.2处理流程
1)接收视频处理消息
2)判断媒体文件是否需要处理(本视频处理程序目前只接收avi视频的处理)
当前只有avi文件需要处理,其它文件需要更新处理状态为无需处理
3)处理前初始化处理状态为未处理
4)处理失败需要在数据库记录处理日志,及处理状态为处理失败
5)处理成功记录处理状态为处理成功
1.2.3.3数据模型
MediaFile类中添加mediaFileProcess_m3u8属性记录ts文件列表,代码如下:

[AppleScript] 纯文本查看 复制代码
//处理状态
private String processStatus;
//hls处理
private MediaFileProcess_m3u8 mediaFileProcess_m3u8; 

[AppleScript] 纯文本查看 复制代码
@Data
@ToString
public class MediaFileProcess_m3u8 extends MediaFileProcess {
//ts列表
private List<String> tslist;
}

1.2.3.4 视频处理生成Mp4
1、创建Dao
视频处理结果需要保存到媒资数据库,创建dao如下:
[AppleScript] 纯文本查看 复制代码
 public interface MediaFileRepository extends MongoRepository<MediaFile,String> {
}


2、在application.yml中配置ffffmpeg的位置及视频目录的根目录:
[AppleScript] 纯文本查看 复制代码
 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
[AppleScript] 纯文本查看 复制代码
 @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.upload‐location}")
String serverPath;
@Autowired
MediaFileRepository mediaFileRepository;
@RabbitListener(queues = "${xc‐service‐manage‐media.mq.queue‐media‐processtask}")
public void receiveMediaProcessTask(String msg) throws IOException {
Map msgMap = JSON.parseObject(msg, Map.class);
LOGGER.info("receive media process task msg :{} ",msgMap);
//解析消息
//媒资文件id
String mediaId = (String) msgMap.get("mediaId");
//获取媒资文件信息
Optional<MediaFile> optional = mediaFileRepository.findById(fileMd5);
if(!optional.isPresent()){
return ;
}
MediaFile mediaFile = optional.get();
//媒资文件类型
String fileType = mediaFile.getFileType();
if(fileType == null || !fileType.equals("avi")){//目前只处理avi文件
mediaFile.setProcessStatus("303004");//处理状态为无需处理
mediaFileRepository.save(mediaFile);
return ;
}else{
mediaFile.setProcessStatus("303001");//处理状态为未处理
mediaFileRepository.save(mediaFile);
}
//生成mp4
String video_path = serverPath + mediaFile.getFilePath()+mediaFile.getFileName();
String mp4_name = mediaFile.getFileId()+".mp4";
String mp4folder_path = serverPath + mediaFile.getFilePath();
Mp4VideoUtil videoUtil = new
Mp4VideoUtil(ffmpeg_path,video_path,mp4_name,mp4folder_path);
String result = videoUtil.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如何判断转换成功?

根据视频时长来判断,取原视频和转换成功视频的时长(时分秒),如果相等则相同。
1.2.3.5 视频处理生成m3u8
下边是完整的视频处理任务类代码,包括了生成m3u8及生成mp4的代码。
[AppleScript] 纯文本查看 复制代码
 @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.upload‐location}")
String serverPath;
@Autowired
MediaFileRepository mediaFileRepository;
@RabbitListener(queues = "${xc‐service‐manage‐media.mq.queue‐media‐processtask}")
public void receiveMediaProcessTask(String msg) throws IOException {
Map msgMap = JSON.parseObject(msg, Map.class);
LOGGER.info("receive media process task msg :{} ",msgMap);
//解析消息
//媒资文件id
String mediaId = (String) msgMap.get("mediaId");
//获取媒资文件信息
Optional<MediaFile> optional = mediaFileRepository.findById(fileMd5);
if(!optional.isPresent()){
return ;
}
MediaFile mediaFile = optional.get();
//媒资文件类型
String fileType = mediaFile.getFileType();
if(fileType == null || !fileType.equals("avi")){//目前只处理avi文件
mediaFile.setProcessStatus("303004");//处理状态为无需处理
mediaFileRepository.save(mediaFile);
return ;
}else{
mediaFile.setProcessStatus("303001");//处理状态为未处理
mediaFileRepository.save(mediaFile);
}
//生成mp4
String video_path = serverPath + mediaFile.getFilePath()+mediaFile.getFileName();
String mp4_name = mediaFile.getFileId()+".mp4";
String mp4folder_path = serverPath + mediaFile.getFilePath();
Mp4VideoUtil videoUtil = new
Mp4VideoUtil(ffmpeg_path,video_path,mp4_name,mp4folder_path);
String result = videoUtil.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
video_path = serverPath + mediaFile.getFilePath()+mp4_name;//此地址为mp4的地址
String m3u8_name = mediaFile.getFileId()+".m3u8";
String m3u8folder_path = serverPath + mediaFile.getFilePath()+"hls/";
HlsVideoUtil hlsVideoUtil = new
HlsVideoUtil(ffmpeg_path,video_path,m3u8_name,m3u8folder_path);
result = hlsVideoUtil.generateM3u8();
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列表
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/"+m3u8_name);
mediaFileRepository.save(mediaFile);
}
}


说明:
mp4转成m3u8如何判断转换成功?
第一、根据视频时长来判断,同mp4转换成功的判断方法。
第二、最后还要判断m3u8文件内容是否完整。


0 个回复

您需要登录后才可以回帖 登录 | 加入黑马