JavaCV实现byte[]转RTMP流

news2024/12/26 12:57:42

需求:通过私有的api我可以不断收到byte[]形式的视频数据,现在我需要处理这些数据,最终推送出RTMP流。
实现:通过管道流将不断收到的byte[]视频数据转化为输入流然后提供给JavaCV的FFmpegFrameGrabber使用,然后通过FFmpegFrameRecorder将视频数据推送至指定RTMP服务器(这个通过mediamtx实现)。

效果图
VLC播放
在这里插入图片描述

关键依赖

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

完整的Demo代码


import lombok.extern.slf4j.Slf4j;
import org.bytedeco.ffmpeg.avcodec.AVCodecParameters;
import org.bytedeco.ffmpeg.avformat.AVFormatContext;
import org.bytedeco.ffmpeg.avformat.AVStream;
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.Frame;
import org.jfjy.ch2ji.ecctv.dh.api.ApiService;
import org.jfjy.ch2ji.ecctv.dh.callback.RealPlayCallback;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import static org.bytedeco.ffmpeg.global.avutil.AV_LOG_DEBUG;
import static org.bytedeco.ffmpeg.global.avutil.AV_LOG_INFO;

@Slf4j
public class GetBytes2PipedStreamAndPushRTMP {

    private static final String SRS_PUSH_ADDRESS = "rtmp://127.0.0.1:1935/live/livestream";

    static int BUFFER_CAPACITY = 1024 * 1024;

    public static void main(String[] args) throws Exception {
        FFmpegLogCallback.set();
        FFmpegLogCallback.setLevel(AV_LOG_DEBUG);
        ApiService apiService = new ApiService();
        Long login = apiService.login("10.3.0.54", 8801, "admin", "xxxx");
        PipedInputStream inputStream = new PipedInputStream(BUFFER_CAPACITY);
        PipedOutputStream outputStream = new PipedOutputStream(inputStream);
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                List<byte[]> bytesArray = new ArrayList<>();
                apiService.startRealPlay(new RealPlayCallback<Long, Integer, byte[]>() {
                    @Override
                    public void apply(Long aLong, Integer integer, byte[] bytes) {
                        log.info("收到视频数据,类型:{},字节:{}", integer, bytes.length);


                        //之所以没在这里写入管道流,是因为每次回调都会创建新的线程,而管道流要求只能在一个线程中写入,否则会出错。
                        //所以这里把数据丢给了集合对象
                        synchronized (bytesArray) {
                            bytesArray.add(bytes);
                        }
                    }
                }, 0, 0);

                try {
                    while (true) {
                        synchronized (bytesArray) {
                            //将视频数据写入管道流
                            for (byte[] bytes : bytesArray) {
                                outputStream.write(bytes);
                            }
                            outputStream.flush();
                            bytesArray.clear();
                        }
                        Thread.sleep(100);
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        });

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                boolean isStartPush = false;
                log.info("推送数据线程启动");
                while (true) {
                    try {
                        //当管道输入流有数据后则开始推送,只需要调用一次
                        if (!isStartPush && inputStream.available() > 0) {
                            log.info("推送任务开始执行| available size : {}",inputStream.available());
                            grabAndPush(inputStream, SRS_PUSH_ADDRESS);
                        }
                        Thread.sleep(500);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        while (true) {

        }
    }


    private static synchronized void grabAndPush(InputStream inputStream, String pushAddress) throws Exception {
        avutil.av_log_set_level(AV_LOG_INFO);
        FFmpegLogCallback.set();

        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(inputStream,0);

        grabber.setFormat("dhav");
        grabber.startUnsafe();

        AVFormatContext avFormatContext = grabber.getFormatContext();

        int streamNum = avFormatContext.nb_streams();

        if (streamNum < 1) {
            log.error("no media!");
            return;
        }

        int frameRate = (int) grabber.getVideoFrameRate();
        if (0 == frameRate) {
            frameRate = 15;
        }
        log.info("frameRate[{}],duration[{}]秒,nb_streams[{}]",
                frameRate,
                avFormatContext.duration() / 1000000,
                avFormatContext.nb_streams());

        for (int i = 0; i < streamNum; i++) {
            AVStream avStream = avFormatContext.streams(i);
            AVCodecParameters avCodecParameters = avStream.codecpar();
            log.info("stream index[{}],codec type[{}],codec ID[{}]", i, avCodecParameters.codec_type(), avCodecParameters.codec_id());
        }

        int frameWidth = grabber.getImageWidth();
        int frameHeight = grabber.getImageHeight();
        int audioChannels = grabber.getAudioChannels();

        log.info("frameWidth[{}],frameHeight[{}],audioChannels[{}]",
                frameWidth,
                frameHeight,
                audioChannels);

        FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(pushAddress,
                frameWidth,
                frameHeight,
                audioChannels);

        recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
        recorder.setInterleaved(true);

        recorder.setFormat("flv");

        recorder.setFrameRate(frameRate);

        recorder.setGopSize(frameRate);

        recorder.setAudioChannels(grabber.getAudioChannels());


        recorder.start();


        Frame frame;


        log.info("start push");

        int videoFrameNum = 0;
        int audioFrameNum = 0;
        int dataFrameNum = 0;

        int interVal = 1000 / frameRate;
        interVal /= 8;

        while (null != (frame = grabber.grab())) {

            if (null != frame.image) {
                videoFrameNum++;
            }

            if (null != frame.samples) {
                audioFrameNum++;
            }

            if (null != frame.data) {
                dataFrameNum++;
            }

            recorder.record(frame);

            Thread.sleep(interVal);
        }

        log.info("push complete,videoFrameNum[{}],audioFrameNum[{}],dataFrameNum[{}]",
                videoFrameNum,
                audioFrameNum,
                dataFrameNum);

        recorder.close();
        grabber.close();
    }


}



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/745192.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

两种传输层协议TCP和UDP【图解TCP/IP(笔记十二)】

文章目录 两种传输层协议TCP和UDPTCP与UDP区分UDP的特点及其目的TCP的特点及其目的 两种传输层协议TCP和UDP 在TCP/IP中能够实现传输层功能的、具有代表性的协议是TCP和UDP。 ■ TCP TCP是面向连接的、可靠的流协议。流就是指不间断的数据结构&#xff0c;你可以把它想象成排…

【C++】C++11 (2): 右值引用、移动构造、移动赋值和模板的可变参数

一、右值引用和移动语义 C11更新后&#xff0c;容器中增加的新方法有插入接口函数的右值引用版本 这些接口的意义在哪&#xff1f;网上都说它们能提高效率&#xff0c;它们是如何提高效率的&#xff1f; 请看下面的右值引用和移动语义的介绍。另外emplace还涉及模板的可变参…

开发跨平台APP,是用Flutter还是React Native开发框架?

随着移动互联网的飞速发展&#xff0c;对于开发人员而言&#xff0c;如何快速地开发出兼容不同平台&#xff08;iOS、Android&#xff09;的应用&#xff0c;成为了一个重要的问题。 跨平台应用程序开发框架的好处&#xff1a; 1. 一个App适用于多个设备&#xff1b; 2. 一个…

问一下路过的大神keil5与keil5mdk 的区别是什么?

从Keil C51都Keil5 MDK&#xff0c;不知不觉已经用了Keil十几年。 虽然现在新增了一些开发环境&#xff0c;不过keil对于老工程师来说&#xff0c;应该是最亲切的了… Keil出过很多个版本&#xff0c;很多人最熟悉的是Keil C51和Keil5 MDK。 我们在做STM32程序开发编译的时候…

Centos7安装SDWebui

Centos7安装SDWebui 1.nvidia显卡驱动安装 #查看显卡编号 lspci | grep -i vga#查询显卡型号 http://pci-ids.ucw.cz/mods/PC/10de?actionhelp?helppci#安装依赖包 yum install kernel-devel gcc -y #查看nouveau是否已禁用&#xff0c;如果有内容说明没有禁用 lsmod | gre…

记录一下2023.2kali的默认密码和修改root用户密码的方法

要水一篇博客了…… 默认登录用户名/密码&#xff1a; kali/kali 切换root用户&#xff1a; sudo su 这时输入的密码是kali 然后就切换到了root用户 输入passwd root 提示修改新密码 根据提示输入两遍新密码就修改了root用户的密码啦 &#xff08;感觉改不改的其实也……无所…

大华监控前端实时预览(踩坑)

难点在后端&#xff0c;前端主要是文档太少了&#xff0c;前端难点主要是接入摄像头&#xff0c;摄像头接入了&#xff0c;剩下什么对讲、调整方向、变焦之类的就简单了。 大华官网&#xff1a;https://open-icc.dahuatech.com/#/home 1.到官网下载插件或者demo&#xff0c;我是…

Xamarin.Android实现界面自动添加控件

目录 1、背景说明2、效果3、代码3.1、UI代码3.2、实现代码 4、代码下载5、相关知识点5.1、原理说明5.2、其他说明 6、参考资料 1、背景说明 有时需要在APP中动态的添加控件&#xff0c;因此记录下在Xamarin中的实现步骤。 VS2022社区版 2、效果 3、代码 3.1、UI代码 UI的代…

文件块读写

写文件&#xff1a; size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); 功能&#xff1a;以数据块的方式给文件写入内容 参数&#xff1a; &#xff1a;准备写入文件数据的地址ptr &#xff1a; 为 类型&#xff0c;此参数指定写入文件内容的块数据大…

面试官:说说Redis的持久化以及主从同步呗

目录 1、秃顶面试官&#xff1a;今天我们聊了聊redis的主从模式啊~ 2、秃顶面试官&#xff1a;Redis有哪几种方式进行数据的持久化&#xff1f; 3、秃顶面试官&#xff1a;RDB持久化是什么呢&#xff1f;触发机制又是什么呢&#xff1f; 4、秃顶面试官&#xff1a;嗯&#…

海外问卷调查怎么做?要准备什么?

科思创业汇 大家好&#xff0c;这里是科思创业汇&#xff0c;一个轻资产创业孵化平台。赚钱的方式有很多种&#xff0c;我希望在科思创业汇能够给你带来最快乐的那一种&#xff01; 海外问卷调查业务一直存在。与国内不同&#xff0c;国外有大量的支付问卷资源&#xff0c;所…

一招永久解决github上不去问题,秒开

步骤 进入如下路径&#xff0c;把hosts复制到桌面 在桌面将hosts以记事本方式打开&#xff0c;复制下面内容&#xff0c;退出保存 20.205.243.166 github.com # GitHub Start 140.82.114.4 github.com 199.232.69.194 github.global.ssl.fastly.net # GitHub End3. 将修改好…

S7-200 SMART PLC PID向导详细介绍(如何实现P、PD、PID控制器)

这篇博客主要介绍SMART PLC PID向导的使用,PID控制相关的其它内容请查看专栏系列文章,常用链接如下: SMART PLC PID负压控制(过程量为负数)_负压控制pid控制程序_RXXW_Dor的博客-CSDN博客1、如何实现PID反作用调节? 在有些控制中需要PID反作用调节。例如:在夏天控制空调…

doubletrouble1靶场详解

doubletrouble1靶场复盘 首先扫描到ip后对ip单独一个全面扫描。 nmap -sP 192.168.102.0/24同时扫描一下目录&#xff0c;扫到一个secret&#xff0c;打开看一下。 dirsearch -u http://192.168.102.165发现里面是一个图片&#xff0c;下载到我们kali 中去&#xff0c;因为都…

关于E-PGM+ 烧录器烧录失败的原因分析

最近在调试A96L416方案的一款产品&#xff0c;发现有时候无法烧录成功&#xff0c;当然多数时候你可以通过&#xff1a; 1.多试几次 2.替换烧录线 3. 替换烧录器 予以解决。 但我试了上述方法发现问题依旧&#xff0c;该设备A之前还烧录过&#xff0c;所以我并没有怀疑设…

C#基础学习_集合中对象的排序

C#基础学习_集合中对象的排序 基本数据类型的排序: 集合名.Sort(); //通过Sort方法进行排序,默认按照英文的字母先后顺序集合名.Reverse(); //通过Reverse方法进行排序,按照英文字母倒序进行排列对象类型的元素排序: 因为对象有若干个属性,所以执行排序时应该指定按照哪一…

海康摄像头开发笔记(一):连接防爆摄像头、配置摄像头网段、设置rtsp码流、播放rtsp流、获取rtsp流、调优rtsp流播放延迟以及录像存储

文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/131679108 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结…

c++方向服务器开发和数据库哪个更好?

选择C方向的服务器开发还是数据库开发&#xff0c;取决于你的兴趣、职业目标以及行业需求。以下是一些考虑因素&#xff1a; 我这里刚好有嵌入式、单片机、plc的资料需要可以私我或在评论区扣个6 服务器开发&#xff1a; 兴趣和技能&#xff1a;如果你对网络编程、分布式系统…

【万字解析】JS逆向由浅到深,3个案例由简到难,由练手到项目解析(代码都附详细注释)

目录 介绍简单案例简单案例二项目实战案例-某查查 介绍 大家好&#xff0c;我是辣条哥&#xff01; 今天给大家上点难度&#xff0c;不然总觉得辣条哥太菜了&#xff01;我们今天聊聊JS逆向&#xff0c;首先JS逆向是指对使用JavaScript编写的代码进行逆向工程&#xff0c;以获…

JAVA数据结构—飞机售票系统

飞机售票系统 1. 题目要求&#xff1a; 1.1 通过该系统可以实现如下功能。 录入功能&#xff1a;可以录入航班情况。 查询功能&#xff1a;可以查询某个航线的情况&#xff0c;如输入航班号&#xff0c;可以查询起降时间、起飞抵达城市、航班票价、票价折扣、确定航班是否满…