package com.ruoyi.hcnetsdk.utils;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.FrameGrabber;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
@Slf4j
public class VideoTransformer {
public PipedOutputStream writer;
public PipedInputStream reader;
public FFmpegFrameGrabber grabber;
public FFmpegFrameRecorder recorder;
public volatile boolean running = false;
public VideoTransformer() {
writer = new PipedOutputStream();
reader = new PipedInputStream();
try {
writer.connect(reader);
} catch (IOException e) {
log.error("writer connect pipe error", e);
}
grabber = new FFmpegFrameGrabber(reader);
}
public void start(int hash) {
try {
grabber.start();
recorder = new FFmpegFrameRecorder("rtmp://localhost:1935/hash/" + hash, grabber.getImageWidth(),
grabber.getImageHeight(), grabber.getAudioChannels());
recorder.setFormat("flv");
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
recorder.start();
running = true;
// 开启线程处理回放回调函数的视频流
new Thread(this::processStream).start();
} catch (FFmpegFrameGrabber.Exception e) {
log.error("start grabber error", e);
} catch (FFmpegFrameRecorder.Exception e) {
log.error("start recorder error", e);
}
}
private void processStream() {
try {
Frame frame;
while (running && ((frame = grabber.grab()) != null)) {
recorder.record(frame);
Thread.sleep(40);
}
} catch (IOException e) {
log.error("record frame error", e);
} catch (InterruptedException e) {
log.error("Thread sleep error", e);
Thread.currentThread().interrupt();
} finally {
this.cleanup();
}
}
/**
* stop the transformer
*/
public void stop() {
running = false;
try {
if (writer != null) writer.close();
} catch (IOException e) {
log.error("writer close error", e);
}
}
/**
* write bytes to the writer
*
* @param bytes the bytes to write
*/
public void write(byte[] bytes) {
try {
writer.write(bytes);
} catch (IOException e) {
log.error("writer write error", e);
}
}
/**
* release all resources, except for the writer
*/
private void cleanup() {
try {
if (recorder != null) {
recorder.stop();
recorder.release();
}
} catch (FFmpegFrameRecorder.Exception e) {
log.error("recorder release error", e);
}
try {
if (grabber != null) {
grabber.stop();
grabber.close();
grabber.release();
}
} catch (FFmpegFrameGrabber.Exception e) {
log.error("grabber release error", e);
} catch (FrameGrabber.Exception e) {
log.error(grabber.getClass().getSimpleName() + " release error", e);
}
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
log.error("reader close error", e);
}
}
}
异常信息:
剩下的都一样了
我想知道这个问题怎么解决,只要输入流Reset,就剩下全是异常了。
我想通过把bytes数据写入队列,然后再写入OutputStream,来平衡InputStream和OutputStream速度,但是grabber.start()会从InputStream取流失败。
我从ai搜到的用javacv 获取视频流怎么只有短短一段函数啊
public static void main(String[] args) throws Exception {
// 视频流地址
String streamURL = "your_video_stream_url_here";
// 创建 FrameGrabber 对象
FrameGrabber grabber = FrameGrabber.createDefault(streamURL);
grabber.start();
// 创建一个窗口来显示视频帧
CanvasFrame canvasFrame = new CanvasFrame("Video Stream");
// 读取和显示视频帧
Frame frame;
while ((frame = grabber.grab()) != null) {
// 在窗口中显示当前帧
canvasFrame.showImage(frame);
// 等待 30 毫秒,如果用户关闭窗口则退出循环
if (canvasFrame.isResizable()) {
canvasFrame.waitKey(30);
} else {
break;
}
}
// 释放 FrameGrabber 和关闭窗口
canvasFrame.dispose();
grabber.stop();
}
这个没有推流,
grabber.getImageHeight(), grabber.getAudioChannels());```是推流成rtmp浏览器播放的
@echo_lovely: 推流的搜索变成这样了
public static void main(String[] args) throws FrameGrabber.Exception, Exception {
// 捕获摄像头视频
OpenCVFrameGrabber grabber = new OpenCVFrameGrabber(0);
grabber.start();
// 推流到 RTMP 服务器
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder("rtmp://your_rtmp_server_url/live/stream_name", 640, 480);
recorder.setFormat("flv");
recorder.start();
// 循环捕获视频帧并推流
Frame frame;
while ((frame = grabber.grab()) != null) {
recorder.record(frame);
}
// 停止捕获和推流
recorder.stop();
grabber.stop();
}
@九亿BUG的噩梦: 不好使
@echo_lovely: 解码器设置H264 ,不能拿过来直接用, 调整调整
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); // 设置视频编解码器为 H264
@九亿BUG的噩梦: 写了,是264的
@echo_lovely: 那就爱莫能助了,搜索、复制、粘贴三板斧用完咧!!!
@九亿BUG的噩梦: 锤你!其实我感觉拉流和推流没有问题,问题在于数据从回调到拉流这里或者是回调数据够不够,我大概计算了下,回调数据基本上是够的,十几秒钟和真实的是差不多的,所以问题就是回调给的可能太快,需要控制下流速到拉流这里
@echo_lovely: 我理解的是在循环里一直读取流里有没有内容,如果过了很久还读不到内容就关闭流,应该和流的速度关系不大,没实操过
@九亿BUG的噩梦: 问题是,回调数据如果一边写,一边读只有5s就中断了,但是搞个队列,一边写入回调到队列,另一头写入输出流,他能写到回调数据结束。,但是grabber不会读
@echo_lovely: 可能流不等于null就退出读取流那里得再加个逻辑, while循环里加一个等待时间,如果流为空,开始进入等待,等待时间内再各一定时间尝试读取流,超过等待时间,结束读取
@九亿BUG的噩梦: 呜呜呜,似乎可以了,但是不知道为啥可以了,emmm,也不确定是否是真的可以了
@echo_lovely: 什么都没改就可以了嘛 0.0
@九亿BUG的噩梦: 现在整理后的代码
package com.ruoyi.hcnetsdk.utils;
import lombok.extern.slf4j.Slf4j;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.ffmpeg.global.avutil;
import org.bytedeco.javacv.*;
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
@Slf4j
public class VideoTransformer {
public PipedOutputStream writer;
public PipedInputStream reader;
public FFmpegFrameGrabber grabber;
public FFmpegFrameRecorder recorder;
public volatile boolean running = false;
public VideoTransformer() {
try {
writer = new PipedOutputStream();
reader = new PipedInputStream(writer);
grabber = new FFmpegFrameGrabber(reader);
grabber.setOption("analyzeduration", "15000000"); // 15s
grabber.setOption("probesize", "15000000"); // 150 MB
} catch (IOException e) {
log.error("VideoTransformer init error:\n" + e);
}
FFmpegLogCallback.set();
}
public void start(int hash) {
try {
running = true;
grabber.start();
recorder = new FFmpegFrameRecorder("rtmp://localhost:1935/hash/" + hash, grabber.getImageWidth(),
grabber.getImageHeight(), grabber.getAudioChannels());
recorder.setFormat("flv");
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
recorder.start();
// 开启线程处理回放回调函数的视频流
new Thread(this::processStream).start();
} catch (FFmpegFrameGrabber.Exception e) {
log.error("start grabber error:\t" + e.getMessage());
} catch (FFmpegFrameRecorder.Exception e) {
log.error("start recorder error:\t" + e.getMessage());
}
}
private void processStream() {
while (running) {
try {
Frame frame;
if ((frame = grabber.grab()) != null) {
recorder.record(frame);
} else {
Thread.sleep(40);
}
} catch (InterruptedException e) {
log.error("processStream -> Thread.sleep error:\t" + e.getMessage());
} catch (FFmpegFrameGrabber.Exception e) {
log.error("Grabber error:\t" + e.getMessage());
} catch (FFmpegFrameRecorder.Exception e) {
log.error("Recorder error:\t" + e.getMessage());
}
}
this.cleanup();
log.info("VideoTransformer stopped");
}
/**
* stop the transformer
*/
public void stop() {
running = false;
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
log.error("停止播放回放视频:\t" + e.getMessage());
}
}
/**
* write bytes to the writer
*
* @param bytes the bytes to write
*/
public void write(byte[] bytes) {
try {
writer.write(bytes);
} catch (IOException e) {
log.error("writer write error", e);
}
log.info("write bytes to writer:\t" + bytes.length + " 字节");
}
/**
* release all resources, except for the writer
*/
private void cleanup() {
try {
if (recorder != null) {
recorder.stop();
recorder.close();
recorder.release();
}
} catch (FFmpegFrameRecorder.Exception e) {
log.error("recorder release error" + e.getMessage());
} catch (FrameRecorder.Exception e) {
log.error("recorder close error" + e.getMessage());
}
try {
if (grabber != null) {
grabber.stop();
grabber.close();
grabber.release();
}
} catch (FFmpegFrameGrabber.Exception e) {
log.error("grabber release error", e);
} catch (FrameGrabber.Exception e) {
log.error(grabber.getClass().getSimpleName() + " release error", e);
}
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
log.error("reader close error", e);
}
}
}
@九亿BUG的噩梦: 你可以对比下,修改了哪些
@echo_lovely: while循环那里更符合逻辑了,我们俩真强!
@echo_lovely: 别忘了改下源码命名啥的,或者把这个提问删了,一般是不准粘公司源码到网上的