package util;
import org.bytedeco.ffmpeg.avcodec.AVCodecParameters;
import org.bytedeco.ffmpeg.avformat.AVFormatContext;
import org.bytedeco.ffmpeg.avformat.AVStream;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.*;
import java.util.logging.Logger;
public class VideoUtil {
@Value("3")
private Integer retryCount;//重试次数,默认值
@Value("100")
private Integer sleepTime;//休眠时间,默认值
private static final Logger logger = Logger.getLogger(VideoUtil.class.getName());
/**
* 视频转码函数(仅转码)
*
* @param inputfile 原始视频文件完整路径
* @param outputfile 目标视频文件完整保存路径(必须完整文件名,即包含格式后缀,推荐格式后缀为.mp4)
* @throws Exception 异常
*/
public static void videoConvert(String inputfile, String outputfile, Integer retryCount, Integer sleepTime) throws Exception {
if (outputfile.lastIndexOf('.') < 0) {
throw new Exception("Error! Output file format undetected!");
}
String format = outputfile.substring(outputfile.lastIndexOf('.'));
FFmpegLogCallback.set();
Frame frame;
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputfile);
FFmpegFrameRecorder recorder = null;
try {
long start = System.currentTimeMillis();
logger.info("视频存储开始:"+ DateUtil.getCurrentDateTime());
logger.info("开始初始化帧抓取器");
// 初始化帧抓取器,例如数据结构(时间戳、编码器上下文、帧对象等),
// 如果入参等于true,还会调用avformat_find_stream_info方法获取流的信息,放入AVFormatContext类型的成员变量oc中
grabber.start(true);
logger.info("帧抓取器初始化完成");
// grabber.start方法中,初始化的解码器信息存在放在grabber的成员变量oc中
AVFormatContext avformatcontext = grabber.getFormatContext();
// 文件内有几个媒体流(一般是视频流+音频流)
int streamNum = avformatcontext.nb_streams();
// 没有媒体流就不用继续了
if (streamNum < 1)
{
logger.info("文件内不存在媒体流");
throw new Exception("Error! There is no media stream in the file!");
}
// 取得视频的帧率
double framerate = grabber.getVideoFrameRate();
System.out.printf("视频帧率[%f],视频时长[%d]秒,媒体流数量[%d]\r\n", framerate, avformatcontext.duration() / 1000000,
avformatcontext.nb_streams());
// 遍历每一个流,检查其类型
for (int i = 0; i < streamNum; i++) {
AVStream avstream = avformatcontext.streams(i);
AVCodecParameters avcodecparameters = avstream.codecpar();
System.out.printf("流的索引[%d],编码器类型[%d],编码器ID[%d]\r\n", i, avcodecparameters.codec_type(),
avcodecparameters.codec_id());
}
// 视频宽度
int frameWidth = grabber.getImageWidth();
// 视频高度
int frameHeight = grabber.getImageHeight();
// 音频通道数量
int audiochannels = grabber.getAudioChannels();
System.out.printf("视频宽度[%d],视频高度[%d],音频通道数[%d]\r\n", frameWidth, frameHeight, audiochannels);
recorder = new FFmpegFrameRecorder(outputfile, frameWidth, frameHeight, audiochannels);
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setFormat(format);
// 使用原始视频的码率,若需要则自行修改码率
recorder.setVideoBitrate(grabber.getVideoBitrate());
// 一秒内的帧数,帧率
recorder.setFrameRate(framerate);
// 两个关键帧之间的帧数
recorder.setGopSize((int)framerate);
// 设置音频通道数,与视频源的通道数相等
recorder.setAudioChannels(grabber.getAudioChannels());
recorder.start();
int videoframenum = 0;
int audioframenum = 0;
int dataframenum = 0;
logger.info("开始录制\r\n");
// 循环条件
int count = 0;
// 持续从视频源取帧
while (true) {
frame = grabber.grab();
if (frame != null) {
// 有图像,就把视频帧加一
if (null != frame.image) {
videoframenum++;
// 取出的每一帧,都保存到视频
recorder.record(frame);
}
// 有声音,就把音频帧加一
if (null != frame.samples) {
audioframenum++;
// 取出的每一帧,都保存到视频
recorder.record(frame);
}
// 有数据,就把数据帧加一
if (null != frame.data) {
dataframenum++;
}
count = 0;
}else {
if (retryCount == null || retryCount == 0) {
break;
}
if (count > retryCount) {
break;
}
if (sleepTime != null && sleepTime > 0) {
// 休眠一段时间后重试
Thread.sleep(sleepTime);
}
count ++;
}
}
long end = System.currentTimeMillis();
long second = (end - start) / 1000;
logger.info("视频存储结束:"+ DateUtil.getCurrentDateTime());
logger.info("视频存储总耗时(秒):"+ second);
System.out.printf("转码完成,视频帧[%d],音频帧[%d],数据帧[%d]\r\n", videoframenum, audioframenum, dataframenum);
logger.info("转码完成,视频帧"+videoframenum+",音频帧" + audioframenum + ",数据帧" + dataframenum);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (recorder != null) {
try {
recorder.close();
} catch (Exception e) {
e.printStackTrace();
}
}
try {
grabber.close();
} catch (FrameGrabber.Exception e) {
e.printStackTrace();
}
}
}
}
java通过ffmpeg进行视频流的拉取和存储
最新推荐文章于 2024-06-30 03:40:09 发布