JavaCV实现byte[]转RTMP流

需求:通过私有的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() {@Overridepublic void run() {List<byte[]> bytesArray = new ArrayList<>();apiService.startRealPlay(new RealPlayCallback<Long, Integer, byte[]>() {@Overridepublic 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() {@Overridepublic 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.hqwc.cn/news/19439.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

96、基于STM32单片机的温湿度DHT11 烟雾火灾报警器蓝牙物联网APP远程控制设计(程序+原理图+任务书+参考论文+开题报告+流程图+元器件清单等)

单片机及温湿度、烟雾传感器是烟雾报警器系统的两大核心。单片机好比一个桥梁&#xff0c;联系着传感器和报警电路设备。近几年来&#xff0c;单片机已逐步深入应用到工农业生产各部门及人们生活的各个方面。各种类型的单片机也根据社会的需求而开发出来。单片机是器件级计算机…

机器人xacro文件转换成urdf文件方法,并在rviz可视化

一、进入工作空间&#xff0c;source一下 cd cat_ws source devel/setup.bash二、进入xacro所在的文件夹&#xff0c;完成xacro文件到urdf文件的转换 cd src/kinova-ros/kinova_description/urdf/然后执行下面命令 rosrun xacro xacro.py two_arm_robot_example_standalone.…

华为路由器如何通过Console口进行基本配置

华为HCIA试听课程&#xff1a;不会传输层协议&#xff0c;HCIA都考不过https://mp.weixin.qq.com/s/oKAL8GvdrcHEb5O_8bEZZQ 思科CCNA试听课程&#xff1a;适合初学者&#xff1a;VLAN原理与配置https://mp.weixin.qq.com/s/toIJg1EVFImalrwzbTONTQ 组网图形 组网需求 通过Cons…

设计模式之责任链模式

文章目录 1、基本介绍2、包含角色3、场景推导4、责任链模式的优缺点5、使用场景 1、基本介绍 王二狗本来是干Android开发的&#xff0c;最近公司想让他把IOS的活也干了&#xff0c;但是Windows笔记本不能开发IOS&#xff0c;所以二狗提出买一台Mac笔记本电脑。这花钱的事需要领…

css animation 鼠标移入暂停会抖动

如图 实现一个赞助商横向滚动列表墙&#xff0c; 上下两排向右滚动&#xff0c;中间向左滚动&#xff0c;鼠标移入暂停当前行。 实现&#xff1a; // 使用animation.moving {animation: move 20s linear infinite; }keyframes move {0% {}100% {transform: translateX(-50%);…

node版本控制工具nvm使用笔记

由于不同的项目所需要的node环境不同&#xff0c;所以在运行支持node 12或者node 16版本的项目时卸载安装不同版本的node非常麻烦&#xff0c;恰巧公司有一个热心的同事告诉我可以使用nvm来进行版本控制&#xff0c;我使用了之后发现确实好用&#xff0c;写一篇笔记记录一下。 …

C#内存不够解决方法

今天在使用C#程序的时候&#xff0c;出现了下图的问题&#xff1a; 注意下图中我用红框标出的位置&#xff0c;实际是一个三维数组。 但是出现这个问题和三维数组没有关系。 他是提示内存不足。 百度了一下&#xff0c;C#在生成的过程中如果是生成对应的32位系统&#xff0c…

【优选算法】—— 滑动窗口类问题

本期&#xff0c;我给大家带来的是关于滑动窗口类算法的介绍&#xff0c;并通过具体的题目帮助大家思考理解。 目录 &#xff08;一&#xff09;基本概念 &#xff08;二&#xff09;题目讲解 1、难度&#xff1a;medium 1️⃣长度最小的子数组 2️⃣找到字符串中所有字⺟…

“坏邻居”导致的kafka生产者TPS下降原因排查

背景&#xff1a; 今天测试了两种不同的场景下kafka producer的tps性能数据&#xff0c;两种场景下都是使用3个线程&#xff0c;每个线程都是对应一个kafka producer&#xff0c;测试发送到kafka集群的消息的量&#xff0c;两个场景的区别是场景A只发送kafka消息&#xff0c;场…

开源代码分享(6)—考虑实时市场联动的电力零售商鲁棒定价策略(附matlab代码)

摘要&#xff1a;电力零售商作为连接电力批发市场与零售市场的桥梁&#xff0c;是电力市场化改革中的重要主体&#xff0c;其经营效率直接决定了市场化改革的成败。然而电力零售商在运营过程中面临着用电量需求和价格双重不确定性的市场风险&#xff0c;亟需通过优化市场行为以…

HCIP第七天

题目 拓扑图 1.所有路由器各自创建一个环回接口&#xff0c;合理规划IP地址 测试 2. R1-R2-R3-R4-R6之间使用OSPF协议&#xff0c;R4-R5-R6之间使用RIP协议 3. R1环回重发布方式引入OSPF网络 4. R4/R6上进行双点双向重发布 将OSPF中的环回接口改成broadcast 因为华为默认环回接…

ue4 MRQ渲染器时,媒体纹理播放速度会被加快

问题描述&#xff1a;当MRQ渲染器开启抗锯齿时&#xff0c;媒体纹理的播放速度会被加快 解决办法&#xff1a;通过执行抗锯齿控制台命令来解决