场景描述
我们需要做个把MP4文件推送到rtmp服务器实现文件点播的场景,MP4可能是个本地文件或者一个url,只解复用不涉及转编码。
使用传统ffmpeg cmd来编写程序
这里我就不一一解释参数含义, 重点 -re 参数控制读取 AVpacket 的速度,按照帧率速度读取文件 AVpacket。如果有多个流,以最慢的帧率为准。
ffmpeg -ss 00:00:15 -re -i test.mp4 -c:v copy -c:a copy -f flv rtmp://127.0.0.1/live/test
使用JavaCV实现
实现已经封装为方法了拿过去直接用
public static void pushRecordStream(String filePath, String pushUrl, Integer second) {
FFmpegLogCallback.setLevel(avutil.AV_LOG_ERROR);
FFmpegFrameGrabber grabber = null;
FFmpegFrameRecorder recorder = null;
try {
grabber = new FFmpegFrameGrabber(filePath);
// 设置读取的最大数据,单位字节
grabber.setOption("probesize", FFmpegConstants.FFMPEG_PROBE_SIZE_PARAM);
// 设置分析的最长时间,单位微秒
grabber.setOption("analyzeduration", FFmpegConstants.FFMPEG_ANALYZE_DURATION_PARAM);
// 定位到特定位置
grabber.start();
grabber.setTimestamp(second * 1000000L);
recorder = new FFmpegFrameRecorder(pushUrl, grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
recorder.setFormat("flv");
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setSampleRate(grabber.getSampleRate());
recorder.setFrameRate(grabber.getFrameRate());
recorder.setVideoBitrate(grabber.getVideoBitrate());
AVFormatContext inputContext = grabber.getFormatContext();
double frameRate=grabber.getFrameRate();
int videoIndex = 0;
AVFormatContext context = grabber.getFormatContext();
for (int i = 0; i < context.nb_streams(); i++) {
if (context.streams(i).codecpar().codec_type() == avutil.AVMEDIA_TYPE_VIDEO) {
videoIndex = i;
}
inputContext.streams(i).codecpar().codec_tag(0);
}
recorder.start(inputContext);
AVPacket avPacket = null;
long waitTime = (long) (1000 / frameRate);
//初次执行时间
long exStartTime = System.currentTimeMillis();
//需要等待的时间
while ((avPacket = grabber.grabPacket()) != null) {
//视频帧推送速度
recorder.recordPacket(avPacket);
if (avPacket.stream_index() == videoIndex) {
long exEndTime = System.currentTimeMillis();
long diffTime = exEndTime - exStartTime;
//计算需要等待的时间
if (diffTime < waitTime) {
Thread.sleep(waitTime - diffTime);
}
exStartTime = System.currentTimeMillis();
}
}
} catch (Exception e) {
throw new FFmpegException("推送回放流失败", e);
} finally {
try {
if (grabber != null) {
grabber.stop();
}
if (recorder != null) {
recorder.stop();
}
} catch (Exception in) {
throw new FFmpegException("关闭取流器失败");
}
}
}
技术细节
- -ss 控制推送进度由 grabber.setTimestamp(second * 1000000L) 来实现
- -re 控制推流速度由帧率计算等待时间来实现