最小依赖
1.5.5
windows-x86_64
org.bytedeco
javacv
${javacv.version}
org.bytedeco
javacpp-platform
${javacv.version}
org.bytedeco
ffmpeg
4.3.2-${javacv.version}
${system.windowsx64}
org.bytedeco
javacv
${javacv.version}
org.bytedeco
javacpp-platform
${javacv.version}
org.bytedeco
ffmpeg
4.3.2-${javacv.version}
${system.windowsx64}
org.bytedeco
javacv
${javacv.version}
org.bytedeco
javacpp-platform
${javacv.version}
org.bytedeco
opencv
4.5.1-${javacv.version}
${system.windowsx64}
org.bytedeco
openblas
0.3.13-${javacv.version}
${system.windowsx64}
org.bytedeco
flycapture
2.13.3.31-${javacv.version}
${system.windowsx64}
视频采集源可以使用摄像头或者其他,我这里用了桌面录像
也排除了声音的采集
package com.test.utiles;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.InputStream;
import java.util.concurrent.ExecutorService;
/**
* @author yudy
* Created by 2022/9/10
*/
@Slf4j
public class MyLiveTest {
private static int frameRate = 16;// 录制的帧率
static Frame imgFrame = null;
static String url = "";
static boolean isStop = false;
public static void start() throws Exception {
// 帧记录
// window 建议使用 FFmpegFrameGrabber("desktop") 进行屏幕捕捉
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber("desktop");
grabber.setFormat("gdigrab");
grabber.setFrameRate(frameRate);// 帧获取间隔
grabber.setOption("framerate", "25");
grabber.setOption("draw_mouse", "0");
// 捕获指定区域,不设置则为全屏
// grabber.setImageHeight(600);
// grabber.setImageWidth(800);
// grabber.setOption("offset_x", "200");
// grabber.setOption("offset_y", "200");//必须设置了大小才能指定区域起点,参数可参考 FFmpeg 入参
grabber.start();
//初始化默认帧,用于锁屏状态下无法或者图形的问题导致异常
// if (imgFrame == null){
// InputStream in = this.getClass().getClassLoader().getResourceAsStream("img.jpeg");
// BufferedImage image = ImageIO.read(in);
// Java2DFrameConverter java2dConverter = new Java2DFrameConverter();
// imgFrame = java2dConverter.convert(image);
// }
if (StringUtils.isEmpty(url)) {
url = "rtmp://192.168.0.25:1935/live/livestream/aa";
}
// 直播推流
//推送固定大小分辨率的图像
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(
url,
1024, 768, 0);
//推送实际小打分辨率的图像
// FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(
// url,
// grabber.getImageWidth(), grabber.getImageHeight(), 0);
recorder.setMaxDelay(5000);
// 用于存储视频 , 调用stop后,需要释放,就会在指定位置输出文件,,这里我保存到D盘
//FFmpegFrameRecorder recorder = FFmpegFrameRecorder.createDefault(file, grabber.getImageWidth(), grabber.getImageHeight());
recorder.setInterleaved(true);
// https://trac.ffmpeg.org/wiki/StreamingGuide
recorder.setVideoOption("tune", "zerolatency");// 加速 零延迟
//recorder.setVideoOption("flvflags", "no_duration_filesize");// 加速
// https://trac.ffmpeg.org/wiki/Encode/H.264
recorder.setVideoOption("preset", "ultrafast");
recorder.setFrameRate(frameRate);// 设置帧率,重要!
// Key frame interval, in our case every 2 seconds -> 30 (fps) * 2 = 60
recorder.setGopSize(frameRate * 2);
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);// 编码,使用编码能让视频占用内存更小,根据实际自行选择
// recorder.profile(AVCodecContext.FF_PROFILE_H264_CONSTRAINED_BASELINE);
// https://trac.ffmpeg.org/wiki/Encode/H.264
recorder.setVideoOption("crf", "28");
// 2000 kb/s 720P
recorder.setVideoBitrate(1000000);
recorder.setFormat("flv");
recorder.start();
new Thread(new Runnable() {
@Override
public void run() {
try {
// 获取屏幕捕捉的一帧
// 屏幕录制,由于已经对音频进行了记录,需要对记录时间进行调整即可
// 即上面调用了 recorder.recordSamples 需要重新分配时间,否则视频输出时长等于实际 的2倍
// while ((frame = grabber.grab()) != null) {
boolean isGdi = false;
int screenW = grabber.getImageWidth();
int screenH = grabber.getImageHeight();
while (true) {
if (isStop) {
try {
// 停止
recorder.stop();
grabber.stop();
// 释放内存,我们都知道c/c++需要手动释放资源
recorder.release();
grabber.release();
log.info("停止直播完成");
} catch (Exception e) {
e.printStackTrace();
}
break;
}
Frame frame = grabber.grabFrame();
if (frame == null) {
isGdi = true;
frame = imgFrame;
} else if (isGdi) {
//先判断分辨率
int screenWt = (int) Toolkit.getDefaultToolkit().getScreenSize().getWidth();
int screenHt = (int) Toolkit.getDefaultToolkit().getScreenSize().getHeight();
if (screenWt == screenW && screenHt == screenH) {
log.info("分辨率没有变化,无需重置");
isGdi = false;
} else {
log.info("分辨率有变化,需要重置,马上停止");
isStop = true;
}
}
recorder.record(frame);
}
} catch (Exception e) {
isStop = true;
e.printStackTrace();
}
}
}).start();
}
public static void main(String[] args) throws Exception {
start();
}
}
可以用docker起一个srs进行推流播放。
# 先启动
docker run -p 1935:1935 -p 1985:1985 -p 8080:8080 \
ccr.ccs.tencentyun.com/ossrs/srs:4
再启动推流
本示例使用的是SRS,也可以更新为其他的平台服务!启动后SRS控制台上就可以看见当前的直播流了