精华内容
下载资源
问答
  • Android RTMP推流实现

    千次下载 热门讨论 2016-12-02 21:37:27
    详见http://blog.csdn.net/huaxun66/article/details/53427771
  • 基于JavaCV技术实现RTMP推流和拉流功能

    万次阅读 热门讨论 2020-03-12 16:06:37
          ...技术不算困难,开发思路也比较清晰,主要是通Nginx实现流媒体服务支撑,JavaCV实现客户端视频数据流通过RTMP协议向服务端推流操作。 主要技术 RTMP &nb...

            最近项目上要求增加视频直播功能,用户又不想多花钱购买专业的视频直播系统组件,客户是上帝没办法只能自己用Java实现一套推拉流中间件使用了。技术不算困难,开发思路也比较清晰,主要是通Nginx实现流媒体服务支撑,JavaCV实现客户端视频数据流通过RTMP协议向服务端推流操作。

    主要技术

    RTMP

            RTMP是Real Time Messaging Protocol(实时消息传输协议)的首字母缩写。该协议基于TCP,是一个协议族,包括RTMP基本协议及RTMPT/RTMPS/RTMPE等多种变种。RTMP是一种设计用来进行实时数据通信的网络协议,主要用来在Flash/AIR平台和支持RTMP协议的流媒体/交互服务器之间进行音视频和数据通信。

    Nginx

             Nginx是一款自由的、开源的、高性能的HTTP服务器和反向代理服务器;同时也是一个IMAP、POP3、SMTP代理服务器;Nginx可以作为一个HTTP服务器进行网站的发布处理,另外Nginx可以作为反向代理进行负载均衡的实现。

    JavaCV

            JavaCV 是一款开源的视觉处理库,基于Apache License Version 2.0协议和GPLv2两种协议 ,对各种常用计算机视觉库封装后的一组jar包,封装了OpenCV、libdc1394、OpenKinect、videoInput和ARToolKitPlus等计算机视觉编程人员常用库的接口。

    HLS

    HTTP Live Streaming,也就是我们常说的HLS。HLS是苹果公司提出的基于HTTP的流媒体网络传输协议。类似于MPEG-DASH,但是HLS更加简洁,它的基本原理也是服务端把文件或媒体流按照不同的码率切分成一个个小片段进行传输,客户端在播放码流时,可以根据自身的带宽及性能限制,在同一视频内容的不同码率的备用源中,选择合适码率的码流进行下载播放。在传输会话开始时,客户端首先需要下载描述不同码流元数据的M3U8索引文件(类似于DASH中的MPD文件)。

    服务端环境搭建

    1. 安装nginx服务(下载地址https://github.com/arut/nginx-rtmp-module)

    在这里插入图片描述

    2. 配置nginx服务
    worker_processes  1;
    
    error_log  logs/error.log debug;
    
    events {
        worker_connections  1024;
    }
    
    http {
        include       mime.types;
        default_type  application/octet-stream;
        sendfile        on;
        keepalive_timeout  65;
        server {
            listen      6064;
    		
            location / {
                root html;
            }
    		
            location /stat {
                rtmp_stat all;
                rtmp_stat_stylesheet stat.xsl;
            }
    
            location /stat.xsl {
                root html;
            }
    		#HLS配置开始,这个配置为了`客户端`能够以http协议获取HLS的拉流
            location /hls {  
                #server hls fragments  
                types{  
                    application/vnd.apple.mpegurl m3u8;  
                    video/mp2t ts;  
                }  
                alias temp/hls;  
                expires -1;  
            }  
        }
    }
    
    rtmp {
        server {
            listen 1935;
    
            application live {
                live on;
    			record off;
    			publish_notify on;
    			#on_publish http://localhost:8080/newsweb/api/v1/rtmp/on_publish;
    			#on_publish_done http://localhost:8080/newsweb/api/v1/rtmp/on_publish_done;
    			#on_play http://localhost:8080/newsweb/api/v1/rtmp/on_play;
    			#on_play_done http://localhost:8080/newsweb/api/v1/rtmp/on_play_done;
            }
    		
            application hls {
                live on;
                hls on;  				 #是否开启hls
                hls_path temp/hls; 		 #本地切片路径
                hls_fragment 8s;  		 #本地切片长度
    			publish_notify on;
    			#on_publish http://localhost:8080/newsweb/api/v1/rtmp/on_publish;
    			#on_publish_done http://localhost:8080/newsweb/api/v1/rtmp/on_publish_done;
    			#on_play http://localhost:8080/newsweb/api/v1/rtmp/on_play;
    			#on_play_done http://localhost:8080/newsweb/api/v1/rtmp/on_play_done;
            }
        }
    }
    
    
    3. 启动nginx服务程序

    在这里插入图片描述
    Nginx常用命令
    1、启动nginx
    nginx.exe 或者 start nginx
    2、停止nginx
    nginx -s stop或者nginx -s quit
    3、重载nginx命令
    nginx -s reload
    用浏览器访问地址:http://xxx.xxx.xxx.xxx:6064/ 可以看到如下页面说明ngxin启动成功。
    在这里插入图片描述

    客户端代码实现

    创建BStreamer基础视频流类主要作为通用视频流的基类使用,在他的基础上可以扩展实现RTMP和RTSP的推流操作类。

    /**
     * 基础视频流
     */
    public class BStreamer {
        private int width = 640;
        private int height = 480;
        private String url;
    
        public BStreamer(String url) {
            this.url = url;
        }
    
        public BStreamer(String url, int w, int h) {
            this.url = url;
            if (w > 0 && h > 0) {
                this.width = w;
                this.height = h;
            }
        }
        public int getWidth() {
            return width;
        }
    
        public void setWidth(int width) {
            this.width = width;
        }
    
        public int getHeight() {
            return height;
        }
    
        public void setHeight(int height) {
            this.height = height;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    }
    
    

    Pusher 类通过继承上面的BStreamer 类实现基于RTMP协议的视频流推送操作。

    /**
     * RTMP视频推流器
     */
    public class Pusher extends BStreamer {
        private Thread thread;
        /**
         * 视频采集器(摄像头)
         */
        OpenCVFrameGrabber grabberCV;
        ///推流
        FrameRecorder recorder;
        private boolean exit = true;
    
        public Pusher(String url) {
            super(url);
        }
    
        public Pusher(String url, int w, int h) {
            super(url, w, h);
        }
        public void close() throws Exception {
            exit = false;
            if(grabberCV!=null){
                grabberCV.close();
            }
            if(recorder.isInterleaved()){
                recorder.close();
                recorder.setInterleaved(false);
            }
            this.thread.interrupt();
        }
        public void start() throws Exception {
            exit = true;
            if(grabberCV!=null){
                grabberCV.start();
            }
            if(recorder.isInterleaved()){
                recorder.start();
                recorder.setInterleaved(true);
            }
            this.thread.start();
        }
        public void push(Consumer<Frame> consumer) throws Exception {
            ///采集摄像头
            grabberCV = new OpenCVFrameGrabber(0);
            grabberCV.setImageWidth(getWidth());
            grabberCV.setImageHeight(getHeight());
            ///推流
           // recorder = FrameRecorder.createDefault(getUrl(), getWidth(), getHeight());
            recorder=new FFmpegFrameRecorder(getUrl(), getWidth(), getHeight());
    
            recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); // 28
            recorder.setFormat("flv"); // rtmp的类型
            recorder.setFrameRate(30);
            // recorder.setVideoBitrate(10 * 1024 * 1024);
    
            this.thread = new Thread(() -> {
                try {
                    while (exit) {
                        Frame f = grabberCV.grab();
                        if (f != null) {
                            if (recorder.isInterleaved()) {
                                ConsoleOut.println("push stream...");
                                recorder.record(f);
                                consumer.accept(f);
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    recorder.setInterleaved(false);
                }
            });
    
        }
    }
    
    

    TestRTMPPusher单元测试类,通过单元测试类完成代码效果验证。

    
    public class TestRTMPPusher {
        public static void main(String[] args) throws Exception {
            //String url="rtsp://192.168.56.1:554/Sword";
            String url="rtmp://127.0.0.1/live/stream";
            Pusher pusher = new Pusher(url);
            CanvasFrame cf = Windows.build("测试RTMP推流", w -> {
                ConsoleOut.println("窗口已关闭!");
                try {
                    pusher.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            pusher.push(f -> {
                cf.showImage(f);
            });
            pusher.start();
        }
    }
    
    

    运行测试程序结果如下。

    在这里插入图片描述
    打开上面提到的nginx服务端程序界面,即可看到推流成功。
    在这里插入图片描述
    至此基于JavaCV实现的IPC推流功能就实现了,相关代码我已经上传到csdn资源,如有需要请自行下载学习。

    展开全文
  • java直播推流

    2018-12-17 16:29:27
    java rtsp推流到Nginx服务器,Nginx生成rtmp和hls,在html上可以进行播放操作
  • java调用海康威视的摄像头,进行rtsp流转rtmp流,里面提供了demo,可以在项目中正式使用,也可以调整里面相关的参数。
  • Window环境下 海康视频RTMP推流方法,使用到的工具。希望能够帮到大家。
  • RTSP2RTMP 推流

    2020-03-11 14:26:02
    需要的人,自然懂的
  • Android摄像头RTMP推流

    热门讨论 2016-03-01 12:05:40
    使用FFMPEG的RTMP推流修改而来,可以读取摄像头和音频推流至流媒体服务器,适合做视频直播的新手参考.
  • JavaCV 通过rtsp拉流并推流rtmp目录结构pom.xmlVideoRealPlayApplicationIndexControllerVideoDataCacheThreadPoolUtilVideoTimerVideoStreamServiceVideoDTO nginx-rtmp服务搭建 目录结构 pom.xml <...

    nginx-rtmp服务搭建

    目录结构

    在这里插入图片描述

    pom.xml

    <dependency>
        <groupId>org.bytedeco</groupId>
        <artifactId>javacv</artifactId>
        <version>1.5.1</version>
    </dependency>
    
    <dependency>
        <groupId>org.bytedeco</groupId>
        <artifactId>ffmpeg-platform</artifactId>
        <version>4.1.3-1.5.1</version>
    </dependency>
    

    VideoRealPlayApplication

    @SpringBootApplication
    @EnableScheduling
    public class VideoRealPlayApplication {
    
        public static void main(String[] args) {
            // 服务启动执行FFmpegFrameGrabber和FFmpegFrameRecorder的tryLoad(),以免导致第一次推流时耗时。
            try {
                FFmpegFrameGrabber.tryLoad();
                FFmpegFrameRecorder.tryLoad();
            } catch (org.bytedeco.javacv.FrameRecorder.Exception e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            SpringApplication.run(VideoRealPlayApplication.class, args);
        }
    
        @PreDestroy
        public void destroy() {
            ThreadPoolUtil.shutdownAndAwaitTermination();
        }
    
    }
    

    IndexController

    @Slf4j
    @RestController
    @RequestMapping("/video")
    public class IndexController {
    
        @Reference
        private IRoadsideDeviceService iRoadsideDeviceService;
        @Reference
        private IDeviceInfoService iDeviceInfoService;
    
        /**
         * 获取设备信息,并执行拉流、推流任务,并返回rtmp地址
         * @return
         */
        @GetMapping("/rtmp")
        public BaseRespEntity rtmp(@RequestParam String deviceIp, @RequestParam String factory) {
            /*if (StrUtil.isBlank(deviceno)) {
                return BaseRespEntity.error("设备序列号不能为空!");
            }
    
            DeviceInfoBO device = iDeviceInfoService.getOne(DeviceInfoBO.builder().deviceno(deviceno).build());
            if (ObjectUtil.isNull(device)) {
                return BaseRespEntity.error("设备信息异常!");
            }*/
    
            // 如果设备已经存在拉流,直接返回rtmp
            VideoDTO video = VideoDataCache.VIDEO_MAP.get(deviceIp);
            if (ObjectUtil.isNotNull(video) && StrUtil.isNotBlank(video.getRtmp())) {
                return BaseRespEntity.ok(video.getRtmp());
            }
            String rtsp;
            if (factory.equals("DH")) {
                rtsp = StrUtil.format(VideoConsts.DAHUA_RTSP_URL, VideoConsts.DAHUA_USERNAME,
                        VideoConsts.DAHUA_PASSWORD, deviceIp);
            } else {
                rtsp = StrUtil.format(VideoConsts.YUSHI_RTSP_URL, VideoConsts.YUSHI_USERNAME,
                        VideoConsts.YUSHI_PASSWORD, deviceIp);
            }
            String rtmp = StrUtil.format(VideoConsts.RTMP_URL, VideoConsts.RTMP_PUSH_IP,
                    VideoConsts.RTMP_PORT, deviceIp.hashCode());
    
            video = new VideoDTO()
                    .setDeviceIp(deviceIp)
                    .setRtsp(rtsp)
                    .setRtmp(rtmp)
                    .setOpentime(LocalDateTime.now());
            VideoStreamService videoStreamService = new VideoStreamService(video);
            Future<?> task = ThreadPoolUtil.POOL.submit(() -> {
                try {
                    videoStreamService.from().to().go(Thread.currentThread());
                } catch (BaseException e) {
                    log.error("BaseException error {}", e.getMsg());
                    videoStreamService.close();
                    e.printStackTrace();
                } catch (FrameGrabber.Exception e) {
                    log.error("FrameGrabber error {}", e.getMessage());
                    videoStreamService.close();
                    e.printStackTrace();
                } catch (FrameRecorder.Exception e) {
                    log.error("FrameRecorder error {}", e.getMessage());
                    videoStreamService.close();
                    e.printStackTrace();
                }
            });
            // 缓存信息
            video.setRtmp(StrUtil.format(VideoConsts.RTMP_URL, VideoConsts.RTMP_ACCESS_IP,
                    VideoConsts.RTMP_PORT, deviceIp.hashCode()));
            String key = String.valueOf(video.getDeviceIp().hashCode());
            VideoDataCache.VIDEO_MAP.put(key, video);
            VideoDataCache.RTMP_MAP.put(key, videoStreamService);
            VideoDataCache.TASK_MAP.put(key, task);
            return BaseRespEntity.ok(video.getRtmp());
        }
    }
    

    VideoDataCache

    public class VideoDataCache {
        /**
         * 保存已经开始推的流
         */
        public static final ConcurrentHashMap<String, VideoStreamService> RTMP_MAP = new ConcurrentHashMap();
    
        /**
         * 保存正在推送的设备信息
         */
        public static final ConcurrentHashMap<String, VideoDTO> VIDEO_MAP = new ConcurrentHashMap();
    
        /**
         * 保存正在推送的任务
         */
        public static final ConcurrentHashMap<String, Future<?>> TASK_MAP = new ConcurrentHashMap<>();
    
    
        public static void remove(String key) {
            // 终止线程
            ThreadPoolUtil.cancelTask(VideoDataCache.TASK_MAP.get(key));
    
            // 清除缓存
            VideoDataCache.TASK_MAP.remove(key);
            VideoDataCache.VIDEO_MAP.remove(key);
            VideoDataCache.RTMP_MAP.remove(key);
        }
    }
    

    ThreadPoolUtil

    @Slf4j
    public class ThreadPoolUtil {
    
        private static final int CORE_POOL_SIZE = (int) (Runtime.getRuntime().availableProcessors() / (1 - 0.5f));
        private static final int MAX_POOL_SIZE = 35;
        private static final long KEEP_LIVE_TIME = 60L;
        private static final BlockingQueue<Runnable> BLOCKING_QUEUE = new LinkedBlockingQueue<>();
        public static final ThreadPoolExecutor POOL;
    
        static{
            POOL = new ThreadPoolExecutor(CORE_POOL_SIZE,
                    MAX_POOL_SIZE,
                    KEEP_LIVE_TIME,
                    TimeUnit.MILLISECONDS,
                    BLOCKING_QUEUE,
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.DiscardOldestPolicy());
        }
        
        /**
         * 取消当前正在执行的任务
         */
        public static void cancelTask(Future<?> future) {
            // 终止正在执行的任务
            if (ObjectUtil.isNotNull(future) && !future.isDone() && !future.isCancelled()) {
                future.cancel(true);
            }
        }
    
        /**
         * 释放线程池
         */
        public static void shutdownAndAwaitTermination() {
            if (POOL != null && !POOL.isShutdown()) {
                POOL.shutdown();
                try {
                    if (!POOL.awaitTermination(120, TimeUnit.SECONDS)) {
                        POOL.shutdownNow();
                        if (!POOL.awaitTermination(120, TimeUnit.SECONDS)) {
                            log.info("pool did not termination");
                        }
                    }
                } catch (InterruptedException e) {
                    POOL.shutdownNow();
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
    

    VideoTimer

    /**
     * 请求rtmp服务状态地址,获取nclients在线客户端数量,等于1时表示没有被预览
     *
     */
    @Slf4j
    @Component
    public class VideoTimer {
    
    	/** */
        private static final String RTMP_STAT_URL = "http://***.***.***.***/stat";
        private static final int TIME_OUT = 3000;
    
        @Scheduled(cron = "0/5 * * * * ?")
        public void configureTasks() {
            List<String> rtmpStatList = getRtmpStat();
            if (CollUtil.isNotEmpty(rtmpStatList)) {
                for (String key : rtmpStatList) {
                    VideoDTO video = VideoDataCache.VIDEO_MAP.get(key);
                    if (ObjectUtil.isNotNull(video) && video.getOpentime().plusMinutes(1).isBefore(LocalDateTime.now())) {
                        log.info("Video Streaming Stop ={}", video);
                        VideoDataCache.remove(key);
                    }
                }
            }
        }
    
        private static List<String> getRtmpStat() {
            try {
                String body = HttpRequest.get(RTMP_STAT_URL)
                        .timeout(TIME_OUT)
                        .execute()
                        .body();
                return xmlToObj(body);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        private static List<String> xmlToObj(String xmlStr) {
            List<String> resList = new ArrayList<>();
            if (StrUtil.isBlank(xmlStr) && !xmlStr.contains("<live>")) {
                return resList;
            }
    
            String live = StrUtil.subBetween(xmlStr, "<live>", "</live>");
            if (!live.contains("<stream>")) {
                return resList;
            }
            String[] split = StrUtil.split(live, "</stream>");
            for (int i = 0; i < split.length; i++) {
                String s = split[i];
                if (s.contains("<name>") && s.contains("<nclients>")) {
                    Integer nclients = Integer.valueOf(StrUtil.subBetween(s, "<nclients>", "</nclients>"));
                    if (nclients == 1) {
                        String name = StrUtil.subBetween(s, "<name>", "</name>");
                        resList.add(name);
                    }
                }
            }
            return resList;
        }
    }
    

    VideoStreamService

    @Slf4j
    public class VideoStreamService {
    
        private VideoDTO videoDTO;
        /**
         * 解码器
         */
        private FFmpegFrameGrabber grabber = null;
    
        /**
         * 编码器
         */
        private FFmpegFrameRecorder recorder = null;
    
        /**
         * 帧率
         */
        private double FRAMERATE;
    
        /**
         * 比特率
         */
        private int BITRATE;
    
        /**
         * 视频像素宽
         */
        public int WIDTH;
        /**
         * 视频像素高
         */
        public int HEIGHT;
    
        public VideoStreamService(VideoDTO videoDTO) {
            this.videoDTO = videoDTO;
        }
    
        /**
         * 视频源
         */
        public VideoStreamService from() throws FrameGrabber.Exception {
            grabber = new FFmpegFrameGrabber(videoDTO.getRtsp());
            // tcp用于解决丢包问题
            grabber.setOption("rtsp_transport", "tcp");
    
            // 设置采集器构造超时时间
            grabber.setOption("stimeout", "3000");
            // 开始之后ffmpeg会采集视频信息,之后就可以获取音视频信息
            grabber.start();
    
            WIDTH = grabber.getImageWidth();
            HEIGHT = grabber.getImageHeight();
            FRAMERATE = grabber.getVideoFrameRate();
            BITRATE = grabber.getVideoBitrate();
            // 若视频像素值为0,说明采集器构造超时,程序结束
            if (WIDTH == 0 && HEIGHT == 0) {
                log.error("Streaming Exception ...");
                return null;
            }
            return this;
        }
    
        /**
         * 输出
         *
         */
        public VideoStreamService to() throws FrameRecorder.Exception {
            recorder = new FFmpegFrameRecorder(videoDTO.getRtmp(), WIDTH, HEIGHT);
            // 画面质量参数,0~51;18~28是一个合理范围
            recorder.setVideoOption("crf", "28");
            // 该参数用于降低延迟
            recorder.setVideoOption("tune", "zerolatency");
            /**
             ** 权衡quality(视频质量)和encode speed(编码速度) values(值): *
             * ultrafast(终极快),superfast(超级快), veryfast(非常快), faster(很快), fast(快), *
             * medium(中等), slow(慢), slower(很慢), veryslow(非常慢) *
             * ultrafast(终极快)提供最少的压缩(低编码器CPU)和最大的视频流大小;而veryslow(非常慢)提供最佳的压缩(高编码器CPU)的同时降低视频流的大小
             */
            recorder.setVideoOption("preset", "ultrafast");
            recorder.setGopSize(2);
            recorder.setFrameRate(FRAMERATE);
            recorder.setVideoBitrate(BITRATE);
            AVFormatContext fc = null;
            if (videoDTO.getRtmp().indexOf("rtmp") >= 0 || videoDTO.getRtmp().indexOf("flv") > 0) {
                // 封装格式flv
                recorder.setFormat("flv");
                recorder.setAudioCodecName("aac");
                fc = grabber.getFormatContext();
            }
            recorder.start(fc);
            log.info("Push Stream Device Info:\ndeviceIp:{}  \nrtsp:{} \nrtmp:{}",
                    videoDTO.getDeviceIp(), videoDTO.getRtsp(), videoDTO.getRtmp());
            return this;
        }
    
        public VideoStreamService go(Thread nowThread) throws FrameGrabber.Exception, FrameRecorder.Exception {
            // 采集或推流导致的错误次数
            long errIndex = 0;
            // 连续五次没有采集到帧则认为视频采集结束,程序错误次数超过5次即中断程序
            
            // 将探测时留下的数据帧释放掉,以免因为dts,pts的问题对推流造成影响
            grabber.flush();
    
            for (int noFrameIndex = 0; noFrameIndex < 5 || errIndex < 5; ) {
                try {
                    // 用于中断线程时,结束该循环
                    nowThread.sleep(1);
                    // 获取没有解码的音视频帧
                    AVPacket pkt = grabber.grabPacket();
                    if (pkt == null || pkt.size() <= 0 || pkt.data() == null) {
                        // 空包记录次数跳过
                        noFrameIndex++;
                        errIndex++;
                        continue;
                    }
                    // 不需要编码直接把音视频帧推出去
                    errIndex += (recorder.recordPacket(pkt) ? 0 : 1);
                    avcodec.av_packet_unref(pkt);
                } catch (InterruptedException e) {
                    // 当需要结束推流时,调用线程中断方法,中断推流的线程。当前线程for循环执行到
                    // nowThread.sleep(1);这行代码时,因为线程已经不存在了,所以会捕获异常,结束for循环
                    log.info("Device interrupt push stream succeeded...");
                    break;
                } catch (FrameGrabber.Exception e) {
                    errIndex++;
                } catch (FrameRecorder.Exception e) {
                    errIndex++;
                }
            }
            // 程序正常结束释放资源
            this.close();
            log.info("The device streaming is stop...");
            return this;
        }
    
        public void close() {
            try {
                if (recorder != null) {
                    recorder.close();
                    log.info("Recorder close success!");
                }
            } catch (FrameRecorder.Exception e) {
                e.printStackTrace();
            }
            try {
                if (grabber != null) {
                    grabber.close();
                    log.info("Grabber close success!");
                }
            } catch (FrameGrabber.Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    VideoDTO

    @Data
    @Accessors(chain = true)
    public class VideoDTO {
    
        private String rtsp;
    
        private String rtmp;
    
        private String deviceIp;
    
        /**
         * 打开时间
         */
        private LocalDateTime opentime;
    }
    
    展开全文
  • JAVA实现内网rtmp公网rtmp

    千次阅读 2021-01-07 14:58:27
    项目中有个业务场景要实现在客户端监看内网rtmp流,方案是搭建媒体,然后通过java起进程,将内网流转到公网,特此写笔记,记录。 1.Nginx+rtmp流媒体搭建 方式一: 参考此文章一步一步进行安装,链接如下: ...

    JAVA实现内网trmp转推公网rtmp

    项目中有个业务场景要实现在客户端监看内网rtmp流,方案是搭建流媒体,然后通过java起进程,将内网流转推到公网流,特此写笔记,记录。

    1.Nginx+rtmp流媒体搭建

    方式一:
    参考此文章一步一步进行安装,链接如下:
    nginx + rtmp 搭建流媒体服务器

    方式二:
    (1)首先先装一些基础的支持:

    yum install -y psmisc telnet lrzsz tcpdump ftp vim ntp gcc wget gcc-c++ pam-devel expat-devel python python-paramiko lsb slf4j make zip unzip nfs-utils net-tools 
    

    (2)从网盘下载nginx集成nginx-rtmp-module模块的压缩包至 /usr/local/src目录下
    网盘地址:链接:https://pan.baidu.com/s/1dsoTw3CedWBwFSpkl1VFIw
    提取码:wzky
    (3)解压压缩包,命令:tar -zxvf centos_nginstall_lua.tar.gz
    解压文件如下
    (4)执行nginx_log.sh(注:脚本文件路径都是基于/usr/local/src,若有其他路径,请自行校验)
    (5)安装nginx,若已经有nginx,不需要再装,没有的话上述解压的文件中有nginx-1.18.0,自行进行安装,至此,nginx已经包含rtmp模块。
    (6)在nginx配置文件中加入此配置

      rtmp {
        server {
        listen 1935; #监听的端口
        chunk_size 4000;
        application tuiliu{#rtmp推流请求路径 (切记路径错了会推不上流)
            live on; #开启实时
          }
        }
      }
    

    (7) 修改nginx配置文件

    1. 验证nginx文件是否正确:/usr/local/nginx/sbin/nginx -t
    2. 重启加载nginx配置文件 /usr/local/nginx/sbin/nginx -s reload

    2.Java程序执行脚本拉起ffmpeg进程

    linux命令:
    ffmpeg -i rtmp://ip:1935/cctvf/tuiliu -vcodec copy -acodec copy -f flv -y rtmp://ip:1935/cctvf/my
    rtmp://ip:1935/cctvf/tuiliu是内网rtmp流转推到外网rtmp://ip:1935/cctvf/my

    1. shell脚本:
    #!/bin/bash
    #根据内网rtmp地址转推公网rtmp
    echo "内网rtmp地址------>>>$1"
    echo "公网rtmp地址------>>>$2"
    ffmpeg -i $1 -vcodec copy -acodec copy -f flv -y $2
    echo "ffmpeg命令执行成功......"
    

    注:若在windows环境编写的脚本,脚本执行可能会报格式错误,此时执行:
    yum install -y dos2unix
    dos2unix start.sh
    若脚本无权限,执行:chmod u+x start.sh 进行授权

    1. java执行启动进程执行脚本代码:
        /**
         * ffmpeg 启动推拉流命令
         *
         * @param shellPath  脚本路径
         * @param inRtmpUrl  内网地址
         * @param outRtmpUrl 外网地址
         * @return 返回状态
         */
        public static ResultBean<Object> startFfmpeg(String shellPath, String inRtmpUrl, String outRtmpUrl) {
            try {
                ProcessBuilder pb = new ProcessBuilder(shellPath + "start.sh", inRtmpUrl, outRtmpUrl);
                log.info("shell脚本执行的命令行:{}", String.format("%s  %s %s", shellPath, inRtmpUrl, outRtmpUrl));
                //要运行的脚本所在的目录
                //pb.directory(new File(shellPath));
                pb.redirectErrorStream(true);
                Process p = pb.start();
                //ProcessBuilder的redirectErrorStream()方法合并输出流和错误流,新起一个线程读取缓冲区,防止程序block
                ThreadPool.execute(new InOutThread(p));
                //此处不进行p.waitFor(),否则一直等待
    /*          try {
                    p.waitFor();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
                return ResultBean.ok();
            } catch (IOException e) {
                log.info("FFMPEG命令运行异常:{} ", e.getMessage(), e);
                return ResultBean.error();
            }
        }
    

    线程池:

    public class ThreadPool {
    	private static int corePoolSize = 50;
    	private static ExecutorService workers;
    	private ThreadPool(){
    	}
    	private static synchronized void init(){
    		if(workers != null){
    			return;
    		}
    		workers = Executors.newFixedThreadPool(corePoolSize);
    	}
    	public static synchronized void execute(Runnable command){
    		if(workers == null){
    			init();
    		}
    		workers.execute(command);
    	}
    }
    

    ProcessBuilder的redirectErrorStream()方法合并输出流和错误流线程读取:

    import lombok.extern.slf4j.Slf4j;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    
    @Slf4j
    public class InOutThread implements Runnable {
        private Process process;
        public InOutThread(Process process) {
            this.process = process;
        }
        @Override
        public void run() {
            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
            try {
                while (( in.readLine()) != null){}
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    1. java执行kill ffmpeg进程脚本代码:
      shell:
    #!/bin/bash
    ps -ef|grep 'ffmpeg'|grep $1|grep -v grep|awk '{print $2}'|xargs kill -9
    

    java代码:

        /**
         * kill ffmpeg 进程
         *
         * @param shellPath 脚本路径
         * @param inRtmpUrl 内网rtmp地址
         * @return 响应值
         */
        public static ResultBean<Object> killByRtmpUrl(String shellPath, String inRtmpUrl) {
            try {
                ProcessBuilder pb = new ProcessBuilder(shellPath + "kill.sh", inRtmpUrl);
                log.info("shell脚本执行的命令行:{}", String.format("%s  %s", shellPath, inRtmpUrl));
                //pb.directory(new File(SHELL_FILE_DIR));
                Process p = pb.start();
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(p.getInputStream()));
                while ((bufferedReader.readLine()) != null)
                    bufferedReader.close();
                try {
                    p.waitFor();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return ResultBean.ok();
            } catch (IOException e) {
                log.info("查询PID命令运行异常:{} ", e.getMessage(), e);
                return ResultBean.error();
            }
        }
    

    以上是流媒体搭建,用java程序执行shell脚本启动及kill相应进程,若有错误,烦请指出,谢谢。
    参考:

    1. https://blog.csdn.net/v6543210/article/details/108146140
    2. https://www.cnblogs.com/monjeo/p/8492357.html
    展开全文
  • UDP发送TS流打包成RTMP推流到服务器

    热门讨论 2015-04-10 16:40:37
    主要做对UDP发送的H264 AAC的音视频TS流做解包并重新打包成RTMP需要的格式,并推送至...由于在前端是以H264 AAC编码,所以在后期实时封装RTMP时不需要重新编码,缩短了封装推流的时间。输入源为H264 AAC编码的视频流。
  • javaCV将socket获取的视频流推媒体(RTMP)服务器所需要的jar
  • rtmp推流和拉流

    千次阅读 2021-06-22 18:32:31
    提前说明,文章所用主要摘自网络,本文只是整合,...nginx-rtmp模块 组件下载地址 https://github.91chifun.workers.dev/https://github.com//arut/nginx-rtmp-module/archive/refs/tags/v1.2.1.tar.gz 注:模块版本号

    提前说明,文章所用主要摘自网络,本文只是整合。鉴于来源太多,不进行引用说明

    NGINX

    安装nginx及安装nginx模块

    nginx下载地址

    http://nginx.org/download/nginx-1.20.1.tar.gz

    nginx-rtmp模块

    组件下载地址

    https://github.91chifun.workers.dev/https://github.com//arut/nginx-rtmp-module/archive/refs/tags/v1.2.1.tar.gz

    注:模块版本号必须等于nginx版本号

    解压和安装

    tar -zxvf ${nginx}/nginx.tar.gz
    tar -zxvf ${nginx-module}/nginx-rtmp.tar.gz
    cd ${nginx}/nginx
    ./configure --add-module=${nginx-module}/nginx-remp.tar.gz
    make && make install
    cd /usr/lcoal/nginx/conf
    vim nginx.conf
    # 修改nginx.conf,具体修改信息下文叙述
    cd ..
    ./sbin/nginx -c conf/nginx.conf
    

    nginx配置

    #user  nobody;
    worker_processes  1;
    
    error_log  logs/error.log;
    error_log  logs/error.log  notice;
    error_log  logs/error.log  info;
    
    pid        logs/nginx.pid;
    
    
    events {
        worker_connections  1024;
    }
    
    rtmp {
        server {
    
            listen 1935;
    
            chunk_size 4096;
    
            application play {
                play /usr/local/nginx/html/play;
            }
        
            application /hls {
                live on;
                hls on;
                hls_path /usr/local/nginx/html/hls;
                hls_fragment 1s;
                hls_playlist_length 4s;
            }
        
            application /live {
                    live on;
            }
    
            application rtmp {
                live on;
            }
        }
    }
    
    http {
        include       mime.types;
        default_type  application/octet-stream;
    
        #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
        #                  '$status $body_bytes_sent "$http_referer" '
        #                  '"$http_user_agent" "$http_x_forwarded_for"';
    
        #access_log  logs/access.log  main;
    
        sendfile        on;
        #tcp_nopush     on;
    
        #keepalive_timeout  0;
        keepalive_timeout  65;
    
        #gzip  on;
    
        server {
            listen       80;
            server_name  localhost;
    
            #charset koi8-r;
    
            #access_log  logs/host.access.log  main;
    
    	location /stat {
                rtmp_stat all;
                rtmp_stat_stylesheet stat.xsl;
            }
    
            location /stat.xsl {
                # XML stylesheet to view RTMP stats.
                # Copy stat.xsl wherever you want
                # and put the full directory path here
                root /usr/local/nginx-rtmp-module;
            }
                
            location /hls {
            # Serve HLS fragments
                types {
                    application/vnd.apple.mpegurl m3u8;
                    video/mp2t ts;
                      }
                root /usr/local/nginx/html;
                add_header Cache-Control no-cache;
            }
    
    
            location / {
                root   html;
                index  index.html index.htm;
            }
    
            #error_page  404              /404.html;
    
            # redirect server error pages to the static page /50x.html
            #
            error_page   500 502 503 504  /50x.html;
            location = /50x.html {
                root   html;
            }
    
            # proxy the PHP scripts to Apache listening on 127.0.0.1:80
            #
            #location ~ \.php$ {
            #    proxy_pass   http://127.0.0.1;
            #}
    
            # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
            #
            #location ~ \.php$ {
            #    root           html;
            #    fastcgi_pass   127.0.0.1:9000;
            #    fastcgi_index  index.php;
            #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
            #    include        fastcgi_params;
            #}
    
            # deny access to .htaccess files, if Apache's document root
            # concurs with nginx's one
            #
            #location ~ /\.ht {
            #    deny  all;
            #}
        }
    
    
        # another virtual host using mix of IP-, name-, and port-based configuration
        #
        #server {
        #    listen       8000;
        #    listen       somename:8080;
        #    server_name  somename  alias  another.alias;
    
        #    location / {
        #        root   html;
        #        index  index.html index.htm;
        #    }
        #}
    
    
        # HTTPS server
        #
        #server {
        #    listen       443 ssl;
        #    server_name  localhost;
    
        #    ssl_certificate      cert.pem;
        #    ssl_certificate_key  cert.key;
    
        #    ssl_session_cache    shared:SSL:1m;
        #    ssl_session_timeout  5m;
    
        #    ssl_ciphers  HIGH:!aNULL:!MD5;
        #    ssl_prefer_server_ciphers  on;
    
        #    location / {
        #        root   html;
        #        index  index.html index.htm;
        #    }
        #}
    
    }
    

    解释

    nginx.conf->rtmp模块配置详解

    Application 创建一个RTMP应用,这里有点区别于http的location
    Timeout 60s
    stocket超时,可以配合keepalive和ping值来实现不让服务器端长期处于监听连接客户端状态,实现快速关掉socket
    Ping 3m
    ping_timeout 30s
    RTMP ping用于检查活动连接的协议。发送一个特殊的包远程连接,在ping_timeout指定时间内期待一个回复,如果没有收到回复,连接断开
    max_streams 32
    设置RTMP流的最大数目,单一流数据最大限制,一般默认的32就可以了
    ack_window 5000000
    设置RTMP窗口的大小
    chunk_size 4096
    数据块大小 设置值越大CPU负载就越小
    max_queue
    最大队列数,一般默认即可
    max_message 1M
    输入数据消息的最大大小。所有输入数据消息都会保存在内存中,等待完成流媒体转发。在理论上传入的消息可以是非常大,对服务器稳定性影响较大,所以一般默认即可。
    out_queue
    out_cork
    Buflen 5s
    设置默认缓冲区长度。通常客户端发送播放前RTMP set_buflen命令并重置该设置
    
    访问控制
    Access
    Allow/deny
    允许来自指定地址或者所有地址发布/播放
    Allow public 127.0.0.1
    Deny publish all;
    Allow play 192.168.0.0/24
    Deny play all;
    
    Exec命令
    Exce
    exec_options on;
    启动一些exec指令选项,通过一些exec事件来干预整个RTMP流
    可以仔细一些外部编解码功能
    Exec ffmpeg -i rtmp://localhost?src/$name -vcodec libx264 -vprofile baseline -g 10 -s 300x200 -acodec libfaac -ar 44100 -ac 1 -f flv rtmp://localhost/hls/$name 2>> /var/log/ffmpeg-$name.log;
    Exce_statc
    类似exce,属于静态命令,不支持传递上下文参数
    Exec_kill_signal term;
    Exec_kill_signal user1;
    Exec_kill_signal 3;
    Exec_pull
    Exec_push
    Exec_publish
    指定与参数外部命令要在发布事件执行。
    Exec_play
    指定与要在打开事件执行外部命令
    Exec_play_done
    指定要在打开完成事件执行外部命令
    Exec_publish_done
    Exec_record_done
    例子
    exec_play bash -c “echo $addr $pageurl >> /tmp/clients”
    Exec_publish base -c “echo $addr $flashver >> /tmp/publishers”
    转录
    Exec_record_done ffmpeg -y -i $path -acodec libmp31ame -ar 44100 -ac 1 -vcodec libx264 $dirname/$basename.mp4
    
    Live 模式
    Live on
    切换直播模式,即一对多广播
    Meta on/copy/off
    奇幻发送元数据到客户端 默认on
    Interleave on/off
    切换交叉模式。在该模式下,音视频会在同一个RTMPchunk流中传输。默认为off
    wait_key on|off
    使视频流从一个关键帧开始,默认为off
    wait_video on|off
    在一个视频帧发送前禁用音频。默认off
    通过wait_key/wait_video进行组合以使客户端可以收到具有所有其他数据的视频关键帧。但这样会增加连接延迟。不过可以通过编解码器中调整关键帧间隔来减少延迟。
    Publish_notify on
    发送NetStream.Publish.Start和NetStream.Publish.Stop给用户,默认off
    Drop_idle_publisher 10s
    终止指定时间内闲置(没有音频、视频)的发布连接,默认为off。注意这个仅仅对于发布模式的连接起作用(发送publish命令之后)
    Sync 10ms
    同步音视频流。如果用户带宽不足以接收发布率,服务器会丢弃一些帧。这将导致同步问题。当时间戳差超过sync指定值,将会发送一个绝对帧来解决这个问题,默认为300ms
    Play_restart off
    使nginx-rtmp能够在发布启动或者停止时发送NetStream.Play.Start 和 NetStrem.Play.Stop到每个用户。如果关闭的话,那么每个用户就只能在回放的开始结束时收到该通知了。默认为on
    
    Record 模式
    Record off|all|audio|video|keyframes|manual
    切换录制模式,流可以被记录到flv文件
    Off 不录制
    All 录制音频和视频
    Audio
    Video
    Keyframes 只录制关键视频帧
    Manual 不自动启动录制,使用控制接口来进行启动和停止
    Record_path /tmp/rec
    指定录制的flv文件存放目录
    Record_suffix -%d-%b-%y-%T.flv
    录制后缀strftime格式
    Record_unique on|off
    是否添加时间戳到录制文件,防止文件被覆盖,默认off
    record_append on|off
    切换文件附加模式。开启后,录制时将新数据附加到旧文件后面。默认off
    record_lock on|off
    锁定文件,调用系统的fcntl
    record_max_size 128K
    设置录制文件的最大值
    Record_max_frames 2
    设置每个录制文件的视频帧最大数量
    Record_interval 1s/15m
    在这个指令指定的时间之后重启录制。默认off设置为0表示录制中无延迟。如果record_unique为off时所有的记录都会被写到同一个文件中。否则就会以时间戳区分在不同文件
    Record_notify on|off
    奇幻当定义录制启动或者停止文件时发送NetStream.Record.Start和NetStream.Record.Stop状态信息onStatus到发布者。
    
    应用
    Application rtmp{
    Live on;
    Record all;
    Record_path /var/rec;
    
    Recorder audio{
    Record audio;
    Record_suffix .audio.flv;
    }
    Recorder chunked{
    Record all;
    Record_interval 15s;
    Record_path /var/rec/chunked;
    }
    }
    创建录制块。可以在单个application中创建多个记录 。
    
    VOD媒体
    Play dir|http://loc
    播放指定目录或者HTTP地址的flv或者mp4文件。注意HTTP播放是要在整个文件下载完后才开始播放。同一个play可以播放多个视频地址(用于负载)。MP4格式要在编解码都被RTMP支持才可以播放。一般常见的就是H264/AAC
    Application vod{
    Play /var/flvs;
    }
    Application vod_http{
    Play http://localhost/vod;
    }
    Play_temp_path /www
    设置远程VOD文件完全下载之后复制于play_temp_path之后的路径。空值的话禁用此功能。
    Play_local_path dir
    在播放前设置远程存储VOD文件路径,默认/tmp
    Play_local_path /tmp/videos;
    Paly /tmp/videos http://localhost/videos
    表示播放视频,先播放本地缓存,如果没有的话,从localhost/videos下载到本地/tmp/videos后,在进行播放
    
    
    Relay模式
    Pull url [key=value]
    创建pull中继。主要是从远程服务器拉取流媒体。并进行重新发布。
    Url语法 [rtmp://]host[:port][/app[/playpath]] 如果application找不到那么将会使用本地application名,如果找不到playpath那么久用当前流名称。
    参数如下(使用Key=Value方式)
    app 明确application名
    Name 捆绑到relay的bending流名称。如果为空,那么会使用application中所有本地流
    tcUrl
    pageUrl
    swfUrl
    flashVer
    playPath
    Live
    Start
    Stop
    Static
    Pull rtmp://cdn.example.com/main/ch?id=1234 name=channel;
    Push url [key=value]
    与pull类似,只是push推送发布流到远程服务器。
    Push_reconnect 1s
    在断开连接后,在push重新连接钱等待的时间,默认3秒
    Session_relay on;
    切换会话relay模式。在这种情况下关闭时relay销毁。
    
    Notify 模式
    这个功能主要是提供HTTP回调。当发送一些连接操作是,一个HTTP请求异步发送。命令处理会被暂停挂起,知道它返回结果代码。当HTTP返回2xx成功状态码时,RTMP会话继续。3xx状态码会使RTMP重定向到另一个从HTTP返回头获取到的application,否则连接丢失。其他状态码,连接断开。目前用来做简单的鉴权。
    On_connect url
    设置HTTP连接回调。当客户分发连接命令时。
    例子:
    On_connect http://localhost/my_auth;
    Location /on_connect{
    If($arg_flashver != “my_secret_flashver”){
    Rewrite ^.*$ fallback?permanent;
    }
    }
    On_play url
    设置HTTP播放回调。分发客户分发播放命令时。
    http {
    Location /redirect {
    Rewrite ^.*$ newname?permanent;
    }
    }
    Rtmp{
    Application myqpp{
    Live on;
    On_play http://localhost/redirect;
    }
    }
    On_publish
    On_doone
    On_play_done
    On_publish_done
    On_record_done
    On_update
    Notify_update_timeout
    设置on_update回调时间
    Notify_update_strict on|off
    Notify_relay_redirect on
    Notify_method get
    设置HTTP方法通知,默认是application/x-www-form-urlencodeed 的POST内容类型。有时候可能会需要GET方法,在nginx的http{}部分处理调用。在这种情况下可以使用arg_*变量去访问参数。
    例如如果是method为get时
    Location /on_play{
    If($arg_pageUrl ~* localhost){
    Return 200;
    }
    Return 500;
    }
    
    HLS 模式
    Hls on|off
    使application 切换HLS协议直播
    Hls_path /tmp/hls;
    设置HLS播放列表和分段目录。这一目录必须在nginx启动前就已经存在。
    Hls_fragment 15s;
    设置HLS分段长度,默认5秒,这个跟直播延迟有比较大的影响
    Hls_playlist_length 20m;
    设置HLS播放列表长度,默认30秒。这个跟直播缓存有关。
    Hls_sync time
    设置HLS时间戳同步阈值。默认2ms。这个功能防止由低分辨率RTMP(1KHz)转换到高分辨率MPEG-TS(90KHz)之后出现的噪音。
    Hls_continuous on|off
    切换HLS连续模式,默认off。
    Hls_nested on|off
    切换HLS嵌套模式。默认off。
    Hls_cleanup on|off;
    切换HLS清理。默认on
    
    AccessLog日志
    Access_log off|path [format_name]
    Log_format new_format ‘$remote_addr’;
    Access_log logs/rtmp_access.log new_format;
    Log_format 指定日志格式
    创建指定的日志格式。日志格式看起来很像 nginx HTTP 日志格式。日志格式里支持的几个变量有:
    * connection - 连接数。
    * remote_addr - 客户端地址。
    * app - application 名。
    * name - 上一个流名。
    * args - 上一个流播放/发布参数。
    * flashver - 客户端 flash 版本。
    * swfurl - 客户端 swf url。
    * tcurl - 客户端 tcUrl。
    * pageurl - 客户端页面 url。
    * command - 客户端发送的播放/发布命令:NONE、PLAY、PUBLISH、PLAY+PUBLISH。
    * bytes_sent - 发送到客户端的字节数。
    * bytes_received - 从客户端接收到的字节数。
    * time_local - 客户端连接结束的本地时间。
    * session_time - 持续连接的秒数。
    * session_readable_time - 在可读格式下的持续时间。
    默认的日志格式叫做 combined。这里是这一格式的定义:
    $remote_addr [$time_local] $command "$app" "$name" "$args" -
    $bytes_received $bytes_sent "$pageurl" "$flashver" ($session_readable_time)
    
    Limits限制
    max_connections number;
    设置rtmp引擎最大连接数,默认off
    
    Application hls{
    Live on;
    Hls on;
    Hls_path /tmp/hls;
    Hls_fragment 15s;
    }
    

    推流工具

    OBS

    下载地址

    https://cdn-fastly.obsproject.com/downloads/OBS-Studio-27.0.1-Full-Installer-x64.exe

    安装过程略

    推流设置

    在这里插入图片描述
    串流密钥即为该流的名

    JAVACV

    pom

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.example</groupId>
        <artifactId>RTMP-practive</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.bytedeco</groupId>
                <artifactId>javacv-platform</artifactId>
                <version>1.5.2</version>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <executions>
                        <execution>
                            <phase>package</phase>
                            <goals>
                                <goal>repackage</goal>
                            </goals>
                        </execution>
                    </executions>
                    <configuration>
                        <includeSystemScope>true</includeSystemScope>
                        <mainClass>com.me.rtmp.util.ConvertVideoPacket</mainClass>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>
    

    运行demo

    package com.me.rtmp.util;
    
    import org.bytedeco.ffmpeg.avcodec.AVPacket;
    import org.bytedeco.ffmpeg.avformat.AVFormatContext;
    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.FFmpegLogCallback;
    import org.bytedeco.javacv.FrameRecorder;
    
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * 转换流
     *
     * @author admin
     */
    public class ConvertVideoPacket {
    
        FFmpegFrameGrabber grabber = null;
        FFmpegFrameRecorder record = null;
        int width = -1, height = -1;
    
        // 视频参数
        protected int audiocodecid;
        protected int codecid;
        protected double framerate;// 帧率
        protected int bitrate;// 比特率
    
        /**
         * 选择视频源.
         */
        public ConvertVideoPacket from(String src) throws Exception {
            // 采集/抓取器
            grabber = new FFmpegFrameGrabber(src);
            if(src.indexOf("rtsp")>=0) {
                grabber.setOption("rtsp_transport","tcp");
            }
            grabber.start();// 开始之后ffmpeg会采集视频信息,之后就可以获取音视频信息
            if (width < 0 || height < 0) {
                width = grabber.getImageWidth();
                height = grabber.getImageHeight();
            }
            // 视频参数
            audiocodecid = grabber.getAudioCodec();
            return this;
        }
    
        /**
         * 选择输出.
         */
        public ConvertVideoPacket to(String out) throws IOException {
            // 录制/推流器
            record = new FFmpegFrameRecorder(out, width, height);
            record.setGopSize(2);
            record.setFrameRate(framerate);
            record.setVideoBitrate(bitrate);
            AVFormatContext fc = null;
            if (out.indexOf("rtmp") >= 0 || out.indexOf("flv") > 0) {
                // 封装格式flv
                record.setFormat("flv");
                record.setAudioCodecName("aac");
                fc = grabber.getFormatContext();
            }
            record.start(fc);
            return this;
        }
    
        /**
         * 转封装.
         */
        public ConvertVideoPacket go() throws IOException {
            long err_index = 0;//采集或推流导致的错误次数test
            //连续五次没有采集到帧则认为视频采集结束,程序错误次数超过1次即中断程序
            for(int no_frame_index=0;no_frame_index<5||err_index>1;) {
                AVPacket pkt=null;
                try {
                    //没有解码的音视频帧
                    pkt=grabber.grabPacket();
                    if(pkt==null||pkt.size()<=0||pkt.data()==null) {
                        //空包记录次数跳过
                        no_frame_index++;
                        continue;
                    }
                    //不需要编码直接把音视频帧推出去
                    //如果失败err_index自增1
                    err_index+=(record.recordPacket(pkt)?0:1);
                    avcodec.av_packet_unref(pkt);
                }catch (Exception e) {//推流失败
                    err_index++;
                }
            }
            return this;
        }
    
        public static void main(String[] args) throws Exception, IOException {
            //运行,设置视频源和推流地址
            new ConvertVideoPacket().from(args[0])
                    .to(args[1])
                    .go();
        }
    }
    

    注意事项

    • 不能修改文件后缀
    • 编码格式必须为H264
    • 视频格式FL测试成功的
    • to()的地址为nginx本机测试成功

    拉流工具

    VLC

    下载地址

    https://plug-mirror.rcac.purdue.edu/vlc/vlc/3.0.14/win32/vlc-3.0.14-win32.exe

    安装过程略

    打开网络串流,填写流的路径/${流名}

    在这里插入图片描述

    展开全文
  • RTSPtoHTTP-FLV 使用JavaCV开发的rtsp流转http-flv流(rtmp已不推荐)并进行推流的流媒体服务 求star!!! 提问求助等优先提交issues,让其他遇到同样问题的朋友可以很方便找到解决方式,尽量避免直接加微信qq咨询...
  • 线上抓娃娃最近三个月火爆了。自己也参与其中的开发,在网上各种找资料,整理了这份demo,解压密码123#。
  • 手机端调用后台生成推流地址,如:rtmp://10.9.160.147:1935/live/1234567890,其中1234567890可以用uuid生成,这样每次推流地址都不一样,有效防止推流地址固定,摄像头就推流到这个地址,通过下面java验证是最好的...
  • RTMP协议分析及推流过程

    千次阅读 2017-07-06 17:33:24
    1.RTMP(实时消息传输协议)是Adobe 公司开发的一个基于TCP的应用层协议。 2.RTMP协议中基本的数据单元称为消息(Message)。 3.当RTMP协议在互联网中传输数据的时候,消息会被拆分成更小的单元,称为消息块...
  • rtmpdump实现flv视频推流demo程序
  • RTSP, RTMP协议推流, 以及保存到本地MP4,MKV录像文件。 如果要成功编译, 需要下载和编译ffmpeg库,libfdk-aac库,x264库。 编译这些库,非常耗时。如果懒得去编译, 可以直接使用已经编译好的stream_push.dll...
  • 安卓直播开源: RTMP 推流SDK 前些日子在github上提交了基于GPUImage的IOS直播推流SDK(https://github.com/runner365/GPUImageRtmpPush) 最近整理了android直播推流SDK,在github上开源出来。 1,支持市面上绝大...
  • 解析1078部标终端推流,并转发至rtmp流服务器.代码有点乱但是本人保证可用. 1078分了2014和2016.该版本是否都支持我已近刚忘记了.但是大同小异对不对~~就是解析的时候注意下就行了
  • 1.安装ffmpeg yum安装即可,安装后检测版本是否安装成功,不...nginx本身不详述,这里已安装nginx的情况下增加编译rtmp模块,git上可下载rtmp模块nginx-rtmp-module存放至nginx安装目录下 ./configure --pref...
  • EasyRTMP介绍EasyRTMP是结合了多种音视频缓存及网络技术的一个rtmp直播推流端,包括:圆形缓冲区(circular buffer)、智能丢帧、自动重连、rtmp协议等等多种技术,能够非常有效地适应各种平台(Windows、Linux、ARM、...
  • h264裸流,可用于rtmp和rtsp推流测试,分辨率800*,608,绝对可用
  • 因为公司任务需要让做一个直播的系统,经过一段时间的研究,和方便以后捡起来所以把这个写了下来 下载windows版本的nginx ... 解压到c盘,最好把目录名改成nginx方便后面...下载nginx-rtmp-modle https://github...
  • RTMP推流摄像头接入腾讯云直播

    千次阅读 2018-07-05 17:01:10
    前端使用叁陆伍视讯公司的RTMP推流摄像头。此摄像头支持嵌入式RTMP协议,也就是无需外加软件控制编码器,摄像机自己就可以推流到腾讯云直播服务器。注册腾讯云直播服务器,提交资料后腾讯云会审核,审核通过后,进入...
  • 集成RTMP推流源码

    2020-06-05 22:44:27
    一、RTMPDump源码地址和Git地址(RTMPDump版本是2.4) RTMPDump源码地址:http://rtmpdump.mplayerhq.hu/ Git地址git clone git://git.ffmpeg.org/rtmpdump
  • 中我们已经实现了推流,但是只是大体的讲,没有具体直播rtmp推流案例,因为我们最常见的直播平台推流方式都是rtmp推流,所以本章算是第二章的补充。 本篇文章会简单阐述flv和live_flv格式说明,以及flv文件读取或...
  • RTMP服务器搭建参考:https://blog.csdn.net/wangchao1412/article/details/103641770 h264格式,aac格式,rtmppacket格式参考:... 环境: RtmpDump c文件少,可以直接引入源文件,配置针对rt...
  • rtmp 推流 修改anyrtc的开源项目,适配最新的webrtc实现推流 DlgRtmpPush 的 大体流程: 继承RTMPHosterEvent ,创建Hoster hoster事件类型: stream 建立连接 stream 重连 stream 状态 推流失败 流...
  • rtmp推流服务器降低延时

    万次阅读 2018-09-05 10:20:16
    在搭建好的nginx-rtmp服务器上做推流延时会很高我最开始时在6s左右,这时候需要修改接收端和发送端的缓存就可以。 以下是发送端的例子(视频缓存太小后会不能播放,这个只针对音频) 录屏加摄像头和麦克风这个延时...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,065
精华内容 1,226
关键字:

javartmp推流

java 订阅