掘金 后端 ( ) • 2024-04-06 12:03

javacv + ffmpeg + nginx将原始rtsp流推送到流媒体服务器,提供hls或flv流供人查看

2024年4月5日

Maven依赖

        <dependency>
            <groupId>org.bytedeco</groupId>
            <artifactId>javacv-platform</artifactId>
            <version>1.5.9</version>
        </dependency>

外部依赖

需要下载ffmpeg并配置环境变量,不然程序运行报错

下载nginx搭建rtmp服务器,这里是我用的版本

自己去下也行,要下载带有rtmp模块的nginx(Gryphon)这里

nginx配置

修改上面的nginx下载好解压后的配置文件,即conf/nginx-win.conf文件

无需删减,加两段配置就行。后面有完整版配置

rtmp {
    server {
        listen 1935;
        # 查看flv流时打开rtmp://localhost:1935/flv-live/test就可以了
        application flv-live {
            live on;
            record off;
            allow play all;
        }
        application hls {
            live on;
            hls on;
            # 改成自己准备存放m3u8文件的目录
            hls_path C:/Users/hachimi/Videos/test/hls;
            hls_fragment 5s;
            hls_playlist_length 30s;
        }
    }
}

server块里加一个location

        # 第二处修改,之后用vlc打开网络串流输入http://localhost/hls/test.m3u8就可以了
        location /hls {
            types {
                application/vnd.apple.mpegurl m3u8;
                video/mp2t ts;
            }
            # 修改成自己的存放m3u8文件的目录
            alias C:/Users/hachimi/Videos/test/hls;
            add_header Cache-Control no-cache;
        }

完整版修改后的配置文件

#user  nobody;
# multiple workers works !
worker_processes  2;

events {
    worker_connections  8192;
}
# 第一处修改
rtmp {
    server {
        listen 1935;
        # 查看flv流时打开rtmp://localhost:1935/flv-live/test就可以了
        application flv-live {
            live on;
            record off;
            allow play all;
        }
        application hls {
            live on;
            hls on;
            # 改成自己准备存放m3u8文件的目录
            hls_path C:/Users/hachimi/Videos/test/hls;
            hls_fragment 5s;
            hls_playlist_length 30s;
        }
    }
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    sendfile        off;

    server_names_hash_bucket_size 128;

    client_body_timeout   10;
    client_header_timeout 10;
    keepalive_timeout     30;
    send_timeout          10;
    keepalive_requests    10;

    server {
        listen       80;
        server_name  localhost;

        location / {
            root   html;
            index  index.html index.htm;
        }

        # 第二处修改,之后用vlc打开网络串流输入http://localhost/hls/test.m3u8就可以了
        location /hls {
            types {
                application/vnd.apple.mpegurl m3u8;
                video/mp2t ts;
            }
            # 修改成自己的存放m3u8文件的目录
            alias C:/Users/hachimi/Videos/test/hls;
            add_header Cache-Control no-cache;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

代码

package rtsp;

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 java.util.concurrent.TimeUnit;

public class RtspToStreamTest {

    static {
        // 设置FFmpeg日志级别
        avutil.av_log_set_level(avutil.AV_LOG_ERROR);
    }
    private static final String RTSP_URL = "rtsp://admin:[email protected]:554/video1";
    private static final String HLS_URL = "C:/Users/hachimi/Videos/test/hls/test.m3u8";
    private static final String FLV_URL = "rtmp://localhost:1935/flv-live/test";
    private static final String FLV_OUTPUT_PATH = "C:/Users/hachimi/Videos/test/output.flv";
    private static long startTime = System.currentTimeMillis(); // 定义startTime变量

    public static void main(String[] args) throws Exception {
        System.out.println("hello world");
        pushRtspToHLS();
        //pushRtspToFLV();
//        saveRtspToFlfFile();
        System.out.println("good bye");
    }

    public static void pushRtspToHLS() throws Exception {
        processAndPushStream(RTSP_URL, HLS_URL, "hls");
    }

    public static void pushRtspToFLV() throws Exception {
        processAndPushStream(RTSP_URL, FLV_URL, "flv");
    }

    public static void saveRtspToFlfFile() throws Exception {
        recordRtspToFile(RTSP_URL, FLV_OUTPUT_PATH, "flv");
    }

    private static void processAndPushStream(String inputUrl, String outputUrl, String format) throws Exception {
        FFmpegFrameGrabber grabber = createGrabber(inputUrl);
        FFmpegFrameRecorder recorder = createRecorder(outputUrl, grabber, format);
        startRecording(grabber, recorder);
    }

    private static void recordRtspToFile(String inputUrl, String outputPath, String format) throws Exception {
        FFmpegFrameGrabber grabber = createGrabber(inputUrl);
        FFmpegFrameRecorder recorder = createRecorder(outputPath, grabber, format);
        startRecording(grabber, recorder, 10, TimeUnit.SECONDS);
    }

    private static FFmpegFrameGrabber createGrabber(String inputUrl) throws Exception {
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputUrl);
        grabber.start();
        return grabber;
    }

    private static FFmpegFrameRecorder createRecorder(String outputUrl, FFmpegFrameGrabber grabber, String format) throws Exception {
        FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(outputUrl, grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
        recorder.setFormat(format);
        configureRecorder(recorder, grabber);
        return recorder;
    }

    private static void configureRecorder(FFmpegFrameRecorder recorder, FFmpegFrameGrabber grabber) throws Exception {
        recorder.setVideoBitrate(grabber.getVideoBitrate());
        recorder.setFrameRate(grabber.getFrameRate());
        recorder.setGopSize((int) grabber.getVideoFrameRate());

        if ("hls".equals(recorder.getFormat())) {
            configureHlsRecorder(recorder);
        }

        recorder.setPixelFormat(avutil.AV_PIX_FMT_YUV420P);
        recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);

        if (grabber.getAudioChannels() > 0) {
            recorder.setAudioChannels(grabber.getAudioChannels());
            recorder.setAudioBitrate(grabber.getAudioBitrate());
            recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
        }
    }

    private static void configureHlsRecorder(FFmpegFrameRecorder recorder) {
        recorder.setOption("hls_time", "15");
        recorder.setOption("hls_list_size", "20");
        recorder.setOption("hls_wrap", "20");
        recorder.setOption("hls_flags", "delete_segments");
    }

    private static void startRecording(FFmpegFrameGrabber grabber, FFmpegFrameRecorder recorder) throws Exception {
        startRecording(grabber, recorder, Long.MAX_VALUE, null);
    }

    private static void startRecording(FFmpegFrameGrabber grabber, FFmpegFrameRecorder recorder, long timeout, TimeUnit unit) throws Exception {
        Frame frame;
        recorder.start();
        while ((frame = grabber.grab()) != null && (!shouldStop(timeout, unit))) {
            recorder.record(frame);
        }
        recorder.close();
        grabber.close();
    }

    private static boolean shouldStop(long timeout, TimeUnit unit) {
        if (unit == null || timeout == Long.MAX_VALUE) {
            return false;
        }
        return System.currentTimeMillis() - startTime >= unit.toMillis(timeout);
    }
}

其他

其实推流不用Java代码就行,用安装好的ffmpeg命令就可以

rtsp推流成hls

ffmpeg -i rtsp://admin:[email protected]:554/video1 -c:v copy -c:a aac -hls_time 10 -hls_list_size 6 C:/Users/hachimi/Videos/test/hls/test.m3u8

rtsp推流成flv(这里有问题,用代码推出来的可以直接看,用下面的命令行就不行,想用flv的话就用上面的代码或者自己研究一下其他的命令)

ffmpeg -rtsp_transport tcp -i rtsp://admin:[email protected]:554/video1 -c:v copy -c:a aac -strict experimental -f flv rtmp://localhost:1935/flv-live/test

ffmpeg -i rtsp://admin:[email protected]:554/video1 -c:v copy -f flv rtmp://localhost:1935/flv-live/test

运行

运行代码后就可以在vlc中查看了

hls用http://localhost/hls/test.m3u8

flv用rtmp://localhost:1935/flv-live/test