javacv实现直播流

javacv从入门到入土系列,音视频入门有一点门槛的延迟大概是2~4秒之间,

依赖

        <!-- 需要注意,javacv主要是一组API为主,还需要加入对应的实现 -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.6</version>
</dependency> <!-- 用到了 ffmpeg 需要把 ffmpeg 的平台实现依赖引入 -->
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg-platform</artifactId>
<version>4.4-1.5.6</version>
</dependency> <!--所有平台实现,依赖非常大,几百MB吧-->
<!--<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.6</version>
</dependency>-->

视频采集可以使用摄像头或者什么的,我这里用了桌面录像

package top.lingkang.test.gui;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.stage.WindowEvent;
import org.bytedeco.ffmpeg.global.avcodec;
import org.bytedeco.javacv.*; import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.TargetDataLine;
import java.io.File;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;
import java.util.Timer;
import java.util.TimerTask;
/**
* @author lingkang
* Created by 2022/5/10
*/
public class MyLive extends Application {
private static final int frameRate = 24;// 录制的帧率
private static boolean isStop = false; private static TargetDataLine line; @Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("lingkang-桌面录屏大师");
ImageView imageVideo = new ImageView();// 用于软件录制显示
imageVideo.setFitWidth(800);
imageVideo.setFitHeight(600);
Button button = new Button("停止录制");
button.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
isStop = true;
if (line != null) {// 马上停止声音录入
try {
line.close();
} catch (Exception e) {
}
}
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("info");
alert.setHeaderText("已经停止录制");
alert.setOnCloseRequest(event1 -> alert.hide());
alert.showAndWait();
}
}); VBox box = new VBox();
box.getChildren().addAll(button, imageVideo);
primaryStage.setScene(new Scene(box));
primaryStage.setHeight(600);
primaryStage.setWidth(800);
primaryStage.show();
primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() {
@Override
public void handle(WindowEvent event) {// 退出时停止
isStop = true;
System.exit(0);
}
}); // 帧记录
// window 建议使用 FFmpegFrameGrabber("desktop") 进行屏幕捕捉
FrameGrabber grabber = new FFmpegFrameGrabber("desktop");
grabber.setFormat("gdigrab");
grabber.setFrameRate(frameRate);// 帧获取间隔
// 捕获指定区域,不设置则为全屏
grabber.setImageHeight(600);
grabber.setImageWidth(800);
// grabber.setOption("offset_x", "200");
// grabber.setOption("offset_y", "200");//必须设置了大小才能指定区域起点,参数可参考 FFmpeg 入参
grabber.start(); File file = new File("D://output.avi");
if (file.exists())
file.delete(); // 直播推流
final FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(
"rtmp://10.8.4.191/live/livestream",
grabber.getImageWidth(), grabber.getImageHeight(), 2); // 用于存储视频 , 调用stop后,需要释放,就会在指定位置输出文件,,这里我保存到D盘
//FFmpegFrameRecorder recorder = FFmpegFrameRecorder.createDefault(file, grabber.getImageWidth(), grabber.getImageHeight());
recorder.setInterleaved(true);
// https://trac.ffmpeg.org/wiki/StreamingGuide
recorder.setVideoOption("tune", "zerolatency");// 加速
// https://trac.ffmpeg.org/wiki/Encode/H.264
recorder.setVideoOption("preset", "ultrafast");
recorder.setFrameRate(frameRate);// 设置帧率,重要!
// Key frame interval, in our case every 2 seconds -> 30 (fps) * 2 = 60
recorder.setGopSize(frameRate * 2);
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);// 编码,使用编码能让视频占用内存更小,根据实际自行选择
// https://trac.ffmpeg.org/wiki/Encode/H.264
recorder.setVideoOption("crf", "28");
// 2000 kb/s 720P
recorder.setVideoBitrate(2000000);
recorder.setFormat("flv"); // 添加音频录制
// 不可变音频
recorder.setAudioOption("crf", "0");
// 最高音质
recorder.setAudioQuality(0);
// 192 Kbps
recorder.setAudioBitrate(192000);
recorder.setSampleRate(44100);
recorder.setAudioChannels(2);
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC); recorder.start(); // 44100 16声道
AudioFormat audioFormat = new AudioFormat(44100.0F, 16, 2, true, false);
DataLine.Info dataLineInfo = new DataLine.Info(TargetDataLine.class, audioFormat);
// 可以捕捉不同声道
line = (TargetDataLine) AudioSystem.getLine(dataLineInfo);
// 录制声音
new Thread(new Runnable() {
@Override
public void run() {
try {
line.open(audioFormat);
line.start(); final int sampleRate = (int) audioFormat.getSampleRate();
final int numChannels = audioFormat.getChannels(); // 缓冲区
final int audioBufferSize = sampleRate * numChannels;
final byte[] audioBytes = new byte[audioBufferSize];
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
try {
if (isStop) {// 停止录音
line.stop();
line.close();
System.out.println("已经停止!");
timer.cancel();
} // 读取音频
// read会阻塞
int readLenth = 0;
while (readLenth == 0)
readLenth = line.read(audioBytes, 0, line.available()); // audioFormat 定义了音频输入为16进制,需要将字节[]转为短字节[]
// FFmpegFrameRecorder.recordSamples 源码中的 AV_SAMPLE_FMT_S16
int rl = readLenth / 2;
short[] samples = new short[rl]; // short[] 转换为 ShortBuffer
ByteBuffer.wrap(audioBytes).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(samples);
ShortBuffer sBuff = ShortBuffer.wrap(samples, 0, rl); // 记录
recorder.recordSamples(sampleRate, numChannels, sBuff);
} catch (Exception e) {
e.printStackTrace();
}
}
}, 1000, 1000 / frameRate);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start(); new Thread(new Runnable() {
@Override
public void run() {
try {
// 获取屏幕捕捉的一帧
Frame frame = null;
// 屏幕录制,由于已经对音频进行了记录,需要对记录时间进行调整即可
// 即上面调用了 recorder.recordSamples 需要重新分配时间,否则视频输出时长等于实际 的2倍
while ((frame = grabber.grab()) != null) {
if (isStop) {
try {
// 停止
recorder.stop();
grabber.stop();
// 释放内存,我们都知道c/c++需要手动释放资源
recorder.release();
grabber.release();
} catch (Exception e) {
e.printStackTrace();
}
break;
} // 将这帧放到录制
recorder.record(frame);
Image convert = new JavaFXFrameConverter().convert(frame);
imageVideo.setImage(convert);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
} public static void main(String[] args) {
launch(args);
}
}

可以用docker起一个srs进行推流播放。

# 先启动
docker run -p 1935:1935 -p 1985:1985 -p 8080:8080 \
ccr.ccs.tencentyun.com/ossrs/srs:4


再启动推流:
http://10.8.4.191:8080/players/srs_player.html?schema=http



javacv实现直播流的更多相关文章

  1. 利用docker搭建RTMP直播流服务器实现直播

    一.rtmp服务器搭建 环境: centos 7.* 1.先安装docker(省略) 2.下载docker容器 docker pull alfg/nginx-rtmp 3.运行容器(记得打开防火墙端口 ...

  2. nginx + nginx-rtmp-module搭建直播流服务器实现推流实时直播功能

    业务需求 最近公司在做养老相关的业务,现在需要子女从小程序端对家里的老人通过家庭终端交互屏进行实时看护. 解决方案 第三方的一些现成的服务:腾讯音视频通话.直播功能; 阿里的音视频通信;两者都挺好的, ...

  3. Java 监控直播流rtsp协议转rtmp、hls、httpflv协议返回浏览器

    Java 监控直播流rtsp协议转rtmp.hls.httpflv协议返回浏览器 目录 需求背景: 一:了解音视频流协议: 二:方案一 rtsp 转rtmp 1.下载nginx + nginx-rtm ...

  4. 抛开flash,自己开发实现C++ RTMP直播流播放器

    抛开flash,自己开发实现C++ RTMP直播流播放器 众所周知,RTMP是以flash为客户端播放器的直播协议,主要应用在B/S形式的场景中.本人研究并用C++开发实现了RTMP直播流协议的播放器 ...

  5. 实现输出h264直播流的rtmp服务器

    RTMP(Real Time Messaging Protocol)是常见的流媒体协议,用来传输音视频数据,结合flash,广泛用于直播.点播.聊天等应用,以及pc.移动.嵌入式等平台,是做流媒体开发 ...

  6. 实现输出h264直播流的rtmp服务器 flash直播服务器【转】

    实现输出h264直播流的rtmp服务器 RTMP(Real Time Messaging Protocol)是常见的流媒体协议,用来传输音视频数据,结合flash,广泛用于直播.点播.聊天等应用,以及 ...

  7. 实现输出h264直播流的rtmp服务器 flash直播服务器

    http://www.cnblogs.com/haibindev/archive/2012/04/16/2450989.html 实现输出h264直播流的rtmp服务器 RTMP(Real Time ...

  8. 调用Live555接收RTSP直播流,转换为Http Live Streaming(iOS直播)协议

    Live555接收RTSP直播流,转换Http Live Streaming(iOS直播)协议 RTSP协议也是广泛使用的直播/点播流媒体协议,之前实现过一个通过live555接收RTSP协议,然后转 ...

  9. 搭建rtmp直播流服务之4:videojs和ckPlayer开源播放器二次开发(播放rtmp、hls直播流及普通视频)

    前面几章讲解了使用 nginx-rtmp搭建直播流媒体服务器; ffmpeg推流到nginx-rtmp服务器; java通过命令行调用ffmpeg实现推流服务; 从数据源获取,到使用ffmpeg推流, ...

  10. 实时监控、直播流、流媒体、视频网站开发方案流媒体服务器搭建及配置详解:使用nginx搭建rtmp直播、rtmp点播、,hls直播服务配置详解

    注意:这里不会讲到nginx流媒体模块如何安装的问题,只研究rtmp,hls直播和录制相关的nginx服务器配置文件的详细用法和说明.可以对照这些命令详解配置nginx -rtmp服务 一.nginx ...

随机推荐

  1. Python爬虫——爬虫时如何知道是否代理ip伪装成功?

    在进行爬虫时,我们可能需要使用代理IP来伪装自己的身份,以避免被网站封禁.如何判断代理IP是否伪装成功呢?本篇文章将围绕这个问题展开讲解,同时提供Python代码示例. 1. 确认代理IP地址 首先, ...

  2. 解密网络通信的关键技术(上):DNS、ARP、DHCP和NAT,你了解多少?

    IP 协议相关技术 在与 IP 协议相关的技术中,有一些重要且常见的技术,其中包括 DNS 域名解析.ARP 协议.DHCP 动态获取 IP 地址以及NAT 网络地址转换.这些技术在网络通信中起着关键 ...

  3. Go 函数的健壮性、panic异常处理、defer 机制

    Go 函数的健壮性.panic异常处理.defer 机制 目录 Go 函数的健壮性.panic异常处理.defer 机制 一.函数健壮性的"三不要"原则 1.1 原则一:不要相信任 ...

  4. 基于LangChain的LLM应用开发1——介绍

    这是基于LangChain的大语言模型应用开发系列的第一篇. 文章内容会参考deeplearning.ai的短课程(https://learn.deeplearning.ai/langchain/), ...

  5. Chromium 消息循环和线程池详解

    Chromium 中的多线程机制由 base 库提供,要理解 Chromium 中的多线程机制,首先要理解的概念就是 base::MessageLoop 和 base::TaskScheduler , ...

  6. 钉钉OA自定义审批流的创建和使用

    前言 大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教. 钉钉作为一款办公软件,审批功能是它的核心功能 ...

  7. 2023江苏省领航杯(部分CRYPTO题目复现)

    决赛 回文 1.题目信息 =QfzEDO4YDNlBzN4gzN0YGM1QzYyUGZ3QDZzgDM7V2Sn52bI52Q= 2.解题方法 base64解码,两种思路: 要么是去掉前面=号解码 ...

  8. Nacos注册中心有几种调用方式?

    Spring Cloud Alibaba Nacos 作为近几年最热门的注册中心和配置中心,也被国内无数公司所使用,今天我们就来看下 Nacos 作为注册中心时,调用它的接口有几种方式? 1.什么是注 ...

  9. go语言reflection反射

    一.反射 1.1简介 Reflection(反射)在计算机中就是表示程序在运行期间能够探知自身结构的能力类型(类型信息.内存结构.更新变量.以及调用方法) 1.2使用场景 函数的参数类型是interf ...

  10. 看完包你搞懂Redis缓存穿透、击穿和雪崩!!!说到做到

    缓存穿透 缓存穿透是指当用户对Redis发出无效或者不存在的数据信息操作时,这条数据在Redis中不存在,Redis就会在MySQL数据库中查询,可时无效的信息在mysql数据库中也不存在,就会造成R ...