Java编码
好文章,来自【福优学苑@音视频+流媒体】
创建数据库:db_webavtc
创建数据表:avcategory(素材类别)
id int primary key,
pid int ,
cname varchar(255),
cmemo varchar(1000)
CREATE TABLE `avcategory` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`pid` INT(11) NOT NULL,
`cname` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`cmemo` VARCHAR(1000) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
PRIMARY KEY (`id`) USING BTREE
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;
创建数据表:avtemplate(转码模板)
Id int primary key auto_increment,
tcTemplateId varchar(64),
tcTemplateName varchar(64),
tcFormatType int,//0:m3u8, 1:mp4, 2:flv
threadsNum int,//转码线程数
hlsSegmentTime int, //m3u8的切片长度(秒)
isMultiOutput int,
hasWaterMark int,
Priority int,
removeAV int,//去除视频(0),去除音频(1)
waterMark varchar(2048),//水印json串
tcTemplateDetails varchar(4096)//转码模板的json串
CREATE TABLE `avtemplate` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`tcTemplateId` VARCHAR(64) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`tcTemplateName` VARCHAR(64) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`tcTemplateEnglish` VARCHAR(64) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`tcFormatType` VARCHAR(64) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`isDeleted` INT(11) NULL DEFAULT '0',
`state` VARCHAR(64) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`threadsNum` INT(11) NULL DEFAULT '0',
`hlsSegmentTime` INT(11) NULL DEFAULT '10',
`tcFastMode` INT(11) NULL DEFAULT '0',
`isMultiOutput` INT(11) NULL DEFAULT '0',
`tcOutputFileCount` INT(11) NULL DEFAULT '0',
`hasWaterMark` INT(11) NULL DEFAULT '0',
`priority` INT(11) NULL DEFAULT '0',
`waterMark` VARCHAR(2048) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`tcTemplateDetails` VARCHAR(4096) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`createTime` DATETIME NULL DEFAULT NULL,
`removeAV` INT(11) NULL DEFAULT '0',
`isSystemReserved` INT(11) NULL DEFAULT '0',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_by_name` (`tcTemplateName`) USING BTREE,
INDEX `idx_by_tcid` (`tcTemplateId`) USING BTREE
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
ROW_FORMAT=COMPACT
AUTO_INCREMENT=43
;
创建数据表:avassets(素材)
Id int primary key auto_increment,
avAssetsGuid varchar(255),
avSrcName varchar(500),
avTitle varchar(255),
avMakeFullName varchar(500),
avType int,
avCategoryId int,
avState int,
avTag varchar(255),
avPoster varchar(255),
tcTemplateId varchar(64)
创建数据表:avtranscodetask(转码任务)
CREATE TABLE `avtranscodetask` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`tcTaskGuid` VARCHAR(128) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`avAssetsGuid` VARCHAR(128) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`tcTaskOutDir` VARCHAR(500) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`tcTaskOutName` VARCHAR(500) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`tcTaskState` INT(11) NULL DEFAULT NULL,
`tcTaskPercent` VARCHAR(128) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`tcConsumeTime` INT(11) NULL DEFAULT NULL,
`tcTranscoderIP` VARCHAR(128) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
PRIMARY KEY (`id`) USING BTREE
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
;
bean
创建AVCategory
创建AVTemplate
创建AVAssets
创建AVTranscodeTask
dao
AVCategoryDaoImpl
AVTemplateDaoImpl
AVAssetsDaoImpl
AVTrancodeTaskDaoImpl
service
AVCategoryService
AVCategoryServiceImpl
AVTemplateService
AVTemplateServiceImpl
AVAssetsService
AVAssetsServiceImpl
AVTrancodeTaskService
AVTrancodeTaskServiceImpl
action
AVCategoryAction
AVTemplateAction
稍微复杂,一点一点改造
AVAssetsAction
AVTrancodeTaskAction
UI
avcategoryList.jsp
avtemplateList.jsp
avassetsMother.jsp
文件上传的组件:webuploader.js
avassetsEdit.jsp:添加素材
avassetsEdit2.jsp: 编辑素材
avassetsAddNew.action
avassetsList.jsp:素材列表
avtranscodetaskList.jsp:转码列表
配置文件
Struts.xml: action
applicationContext.xml: *.hbm.xml, beans
创建数据表: ctype
CREATE TABLE `ctype` (
`ctid` INT(11) NOT NULL AUTO_INCREMENT,
`ctname` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`ctcode` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`ctno` INT(11) NULL DEFAULT NULL,
`ctdesc` VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8_general_ci',
`csCount` INT(11) NULL DEFAULT NULL,
`pid` INT(11) NULL DEFAULT NULL,
PRIMARY KEY (`ctid`) USING BTREE
)
COLLATE='utf8_general_ci'
ENGINE=InnoDB
AUTO_INCREMENT=14
;
db_webavtc
左侧导航树
ztree
Java调用ffmpeg进行转码
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
public class JavaFFmpeg extends Thread{
private boolean stoped = false;
public boolean isStoped() {
return stoped;
}
public void setStoped(boolean stoped) {
this.stoped = stoped;
}
public void run() {
// 在run方法中编写需要执行的操作
stoped = false;
openFFmpegExe();
stoped = true;
}
public static String readToString(String fileName) {
String encoding = "UTF-8";
File file = new File(fileName);
Long filelength = file.length();
byte[] filecontent = new byte[filelength.intValue()];
try {
FileInputStream in = new FileInputStream(file);
in.read(filecontent);
in.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
return new String(filecontent, encoding);
} catch (UnsupportedEncodingException e) {
System.err.println("The OS does not support " + encoding);
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
System.out.println("hello,ffmpeg......");
// 在main方法(线程)中,创建线程对象,并启动线程.
JavaFFmpeg objTC = new JavaFFmpeg();
objTC.start();
String strFileContent;
while( !objTC.isStoped() ){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
strFileContent = readToString("d:/_movies/__test/ande_302.mp4.txt");
// frame=。
int nIdxFrameStart = strFileContent.lastIndexOf("frame=");
if(nIdxFrameStart >= 0){
int nIdxFrameEnd = strFileContent.indexOf((char)0x0A, nIdxFrameStart);
if(nIdxFrameEnd > 0){
String strFrames = strFileContent.substring(nIdxFrameStart, nIdxFrameEnd);
System.out.println(strFrames);// 打印输出信息
/// out_time=00:00:02.066576
int nIdxOutTimeStart = strFileContent.indexOf("out_time=", nIdxFrameEnd);
if(nIdxOutTimeStart > 0){
int nIdxOutTimeEnd = strFileContent.indexOf((char)0x0A, nIdxOutTimeStart);
if(nIdxOutTimeEnd > 0){
String strOutTime = strFileContent.substring(nIdxOutTimeStart, nIdxOutTimeEnd);
System.out.println(strOutTime);// 打印输出信息
}
}
}
}
}
System.out.println("bye,ffmpeg......");
}
//调用其他的可执行文件,例如:自己制作的exe,或是 下载 安装的软件.
private void openFFmpegExe() {
Runtime rn = Runtime.getRuntime();
Process p = null;
try {
///ffmpeg.exe -re -i d:/_movies/ande10.mp4 -vcodec libx264 -acodec aac -f flv rtmp://192.168.0.104/hls1/test
p = rn.exec("ffmpeg431.exe -progress d:/_movies/__test/ande_302.mp4.txt -i d:/_movies/__test/ande_302.mp4 -threads 1 -vcodec libx264 -acodec aac -y d:/_movies/__test/ande_302_test2.mp4" );
BufferedInputStream in = new BufferedInputStream(p.getErrorStream());
BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
String lineStr;
System.out.println("开始转码");
while((lineStr = inBr.readLine())!=null){
//获得命令执行后在控制台的输出信息
//System.err.println("获得命令执行后在控制台的输出信息");
/////System.out.println(lineStr);// 打印输出信息
}
// 检查命令是否执行失败。
if(p.waitFor()!=0){
if(p.exitValue()==1)//p.exitValue()==0表示正常结束,1:非正常结束
System.err.println("命令执行失败!");
}
inBr.close();in.close();
} catch (Exception e) {
System.out.println("Error exec:" + e.getMessage());
}
}
}
spring定时器
spring定时器一般有两种:
TimerTask、Quartz。
1.定时执行任务的类继承TimerTask:
Java代码
public class EmailReportTask extends TimerTask{
@Override
public void run() {
System.out.println(" EmailReportTask Run... ");
}
}
2.spring的配置文件:
Xml代码
<!-- Bean -->
<bean id="emailReportTask" class="com.fyl.spring.timertask.EmailReportTask" />
<!-- ScheduledTimerTask设置定时器属性 : period=定时器周期;delay=延迟多久启动
86400000代表24个小时;timerTask=执行定时任务的类对象 -->
<bean id="emailReportScheduleReportTask"
class="org.springframework.scheduling.timer.ScheduledTimerTask">
<property name="timerTask" ref="emailReportTask" />
<property name="period" value="2000" />
<property name="delay" value="1000" />
</bean>
<!-- Spring的TimerFactoryBean负责启动定时任务;
scheduledTimerTasks = 需要启动的定时器任务的列表-->
<bean class="org.springframework.scheduling.timer.TimerFactoryBean">
<property name="scheduledTimerTasks">
<list>
<ref bean="emailReportScheduleReportTask"/>
</list>
</property>
</bean>
<!-- ........................................ -->
<!-- ........................................ -->
<!-- ...................spring.timer..................... -->
<bean id="tcTimerTaskScheduler001" class="com.fyl.ssh.timer.TCTimerTaskScheduler">
</bean>
<bean id="scheduled_tcTimerTaskScheduler001" class="org.springframework.scheduling.timer.ScheduledTimerTask">
<property name="delay" value="1000" />
<property name="period" value="5000" />
<property name="timerTask" ref="tcTimerTaskScheduler001" />
</bean>
<bean class="org.springframework.scheduling.timer.TimerFactoryBean">
<property name="scheduledTimerTasks">
<list>
<ref bean="scheduled_tcTimerTaskScheduler001" />
</list>
</property>
</bean>
转码任务调度
查询“素材表”:等待状态
1.avassets:
avType: 0视频,1音频, 2图片
avState: <=100等待,101:转码中, 200:转码完成, 500:转码失败
2.avassets:素材绑定模板
3.avassets:查询对应模板的详细内容,根据tcTemplateGuid
AVAssetsAction
需要添加一个属性:AVTemplateService,需要在spring配置文件中配置
4.解析模板信息
(1)String------>Class
com.alibaba.fastjson.JSON
strAVTemplateString
Class obj = JSON.parseObject(strAVTemplateString, AVTemplateDetail.class);
(2)String--->JsonArray
JSONArray templateDetailJsonArray = new JSONArray();
templateDetailJsonArray =
JSONArray.parseArray(avtTmp.getTcTemplateDetails());
for(int i = 0; i < templateDetailJsonArray.size(); i++){
AVTemplateDetail avtDetail =
JSON.parseObject(templateDetailJsonArray.getString(i) , AVTemplateDetail.class);
System.out.println(":::"+ avtDetail.getvCodec() );
}
5.准备转码
(1)先在数据表中插入一条记录
定时器,每5秒钟,执行一次
avAssets: 检测 avState==101,转码中。
如果有,跳过本地timer;否则,继续开启新转码
(2)构造转码命令,开始转码,并更新转码进度
final public static String THE_TC_FFMPEG_CMDLINE_SINGLE =
"ffmpeg431 -i %s -vcodec %s -s %dx%d -acodec %s -f %s -y %s";
6.获取音视频素材的媒体基本信息
基本信息、视频信息、音频信息 ...
ffprobe431 -v quiet -print_format json -show_format
-show_streams D:\_movies\__test\ande_302_test2.mp4
ffprobe431 -i D:\_movies\__test\ande_302_test2.mp4
7.更新转码进度
(1)获取totalDuration
(2)获取实时转码进度,并转成百分比,更新数据库
(3)刷新转码列表页面
8.多码流输出
性能很高:只需要一次解码,同时进行多路编码
9.m3u8转码
切片命令行:
ffmpeg431.exe -i ande_302.mp4
-vbsf h264_mp4toannexb -vcodec libx264 -acodec aac
-strict -2 -s 640x480 -r 30 -g 150 -start_number 0 -hls_time 5 -hls_list_size
0 -threads 1 -f hls -y aaa.m3u8
-vbsf h264_mp4toannexb -vcodec libx264 -acodec aac
-strict -2 -s 960x540 -r 30 -g 150 -start_number 0 -hls_time 5 -hls_list_size
0 -threads 1 -f hls -y aaa.m3u8
-vbsf h264_mp4toannexb -vcodec libx264 -acodec aac
-strict -2 -s 1280x720 -r 30 -g 150 -start_number 0 -hls_time 5 -hls_list_size
0 -threads 1 -f hls -y aaa.m3u8
10.添加水印(图片、文字)
ffmpeg431.exe -i ande_302.mp4 -vf "drawtext=fontfile=simhei.ttf:text='hello, 您 好ffmpeg':x=(mod(2*n\,w+tw)-tw):y=10:fontcolor=#FF6600:fontsize=60" -f mp4 -y
ande_test_text3.mp4
11.水印:中文乱码
加上盘符后的表达式为
这个郁闷头疼:
原始语法:-vf drawtext="fontfile='d\:/_movies/__test/simhei.ttf':text=’xxx’"
Java语法:
String strXXX = “-vf drawtext=\"fontfile='d\\://_movies/__test/simhei.ttf’:text=’xxx’\"”;
Fontconfig error: Cannot load default config file
[Parsed_drawtext_0 @ 0102d840] Using "C:/WINDOWS/fonts/mingliub.ttc"
ffmpeg431 -i f:/fzavms1/fxhttp_out//__avsrc//2020/10/16/2020101614355828787ef9e22-0131-4777-b7a8-d063bc8d0b4b.mp4 -vf "drawtext=fontfile='d\:/_movies/__test/simhei.ttf':text='284378sxxxdqqq啊啊啊':x=(mod(2*n\,w+tw)-tw):y=10:fontcolor=#FF6600:fontsize=60" -vcodec libx264 -s 640x360 -acodec aac -threads 1 -f mp4 -y f:/fzavms1/fxhttp_out//__tasks//2020/10/16/2020101614355828787ef9e22-0131-4777-b7a8-d063bc8d0b4b.mp4/first.mp4
ffmpeg431 -i f:/fzavms1/fxhttp_out//__avsrc//2020/10/16/2020101614355828787ef9e22-0131-4777-b7a8-d063bc8d0b4b.mp4 -vf "drawtext=fontfile='d\:/_movies/__test/simhei.ttf':text='284378sxxxdqqq啊啊啊':x=(mod(2*n\,w+tw)-tw):y=10:fontcolor=#FF6600:fontsize=60" -vcodec libx264 -s 640x360 -acodec aac -threads 1 -f mp4 -y f:/fzavms1/fxhttp_out//__tasks//2020/10/16/2020101614355828787ef9e22-0131-4777-b7a8-d063bc8d0b4b.mp4/first.mp4
fastjson
String strWMJsonStr = JSON.toJSONString(objWaterMark, true);
WaterMark avtWaterMarkDetail = JSON.parseObject(strxxx , WaterMark.class);
好文章,来自【福优学苑@音视频+流媒体】
***【在线视频教程】***