学成在线-第14天-讲义-媒资管理 一

视频处理 
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配置如下:

@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

1.2.2 视频处理技术方案 
如何通过程序进行视频处理? 
ffffmpeg是一个可行的视频处理程序,可以通过Java调用ffffmpeg.exe完成视频处理。 
java中可以使用Runtime类和Process Builder类两种方式来执行外部程序,工作中至少掌握一种。 
本项目使用Process Builder的方式来调用ffffmpeg完成视频处理。 
关于Process Builder的测试如下:

@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 
分别测试每个工具类的使用方法。

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文件列表,代码如下: 

//处理状态
private String processStatus;
//hls处理
private MediaFileProcess_m3u8 mediaFileProcess_m3u8;
@Data
@ToString
public class MediaFileProcess_m3u8 extends MediaFileProcess {
//ts列表
private List<String> tslist;
}

1.2.3.4 视频处理生成Mp4 
1、创建Dao 
视频处理结果需要保存到媒资数据库,创建dao如下:

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

2、在application.yml中配置ffffmpeg的位置及视频目录的根目录:

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.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的代码。

@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文件内容是否完整。 

发布了835 篇原创文章 · 获赞 152 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/qq_40208605/article/details/104195216
今日推荐