方式一(Java集成ffmpeg)
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv-platform</artifactId>
<version>1.5.7</version>
</dependency>
核心代码
import lombok.SneakyThrows;
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.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import static org.bytedeco.ffmpeg.global.avcodec.av_packet_unref;
/**
* rtsp转rtmp(转封装方式).
*/
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 += (record.recordPacket(pkt) ? 0 : 1);//如果失败err_index自增1
av_packet_unref(pkt);
} catch (Exception e) {//推流失败
err_index++;
}
}
return this;
}
@SneakyThrows
public static void main(String[] args) throws Exception, IOException {
String rtsp = "rtsp://admin:Ctkj@123456+@222.88.170.174:55103/LiveMedia/ch1/Media1";
//String output = "C:\\Users\\zz\\Desktop\\a\\m3u8one.m3u8";
// push(rtsp, output);
//运行,设置视频源和推流地址
new ConvertVideoPacket()
.from(rtsp)
.to("rtmp://127.0.0.1:1935/live/test")
.go();
}
/**
* 转m3u8
* input rtsp的账号密码端口流字符串
* output 要保存的文件目录生成m3u8文件 一直生成ts文件
*/
@SneakyThrows
public static void push(String input, String output) {
FFmpegFrameGrabber grabber = null;// 采集器
FFmpegFrameRecorder recorder = null;// 解码器
int bitrate = 2500000;// 比特率
double framerate;// 帧率
int err_index = 0;// 推流过程中出现错误的次数
int timebase;// 时钟基
long dts = 0, pts = 0;// pkt的dts、pts时间戳
try {
// 开启ffmpeg日志级别;QUIET是屏蔽所有,可选INFO、DEBUG、ERROR等
avutil.av_log_set_level(avutil.AV_LOG_INFO);
FFmpegLogCallback.set();
grabber = new FFmpegFrameGrabber(input);
grabber.setOption("rtsp_transport", "tcp");
grabber.start();
// 异常的framerate,强制使用25帧
if (grabber.getFrameRate() > 0 && grabber.getFrameRate() < 100) {
framerate = grabber.getFrameRate();
} else {
framerate = 25.0;
}
bitrate = grabber.getVideoBitrate();// 获取到的比特率 0
recorder = new FFmpegFrameRecorder(output, grabber.getImageWidth(), grabber.getImageHeight(), 0);
// 设置比特率
recorder.setVideoBitrate(bitrate);
// h264编/解码器
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
// 设置音频编码
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
// 视频帧率(保证视频质量的情况下最低25,低于25会出现闪屏)
recorder.setFrameRate(framerate);
// 关键帧间隔,一般与帧率相同或者是视频帧率的两倍
recorder.setGopSize((int) framerate);
// 解码器格式
recorder.setFormat("hls");
// 单个切片时长,单位是s,默认为2s
recorder.setOption("hls_time", "5");
// HLS播放的列表长度,0标识不做限制
recorder.setOption("hls_list_size", "20");
// 设置切片的ts文件序号起始值,默认从0开始,可以通过此项更改
recorder.setOption("start_number", "1");
// recorder.setOption("hls_segment_type","mpegts");
// 自动删除切片,如果切片数量大于hls_list_size的数量,则会开始自动删除之前的ts切片,只保 留hls_list_size个数量的切片
recorder.setOption("hls_flags", "delete_segments");
// ts切片自动删除阈值,默认值为1,表示早于hls_list_size+1的切片将被删除
//recorder.setOption("hls_delete_threshold","1");
/*
* hls的切片类型: 'mpegts':以MPEG-2传输流格式输出ts切片文件,可以与所有HLS版本兼容。 'fmp4':以Fragmented
* MP4(简称:fmp4)格式输出切片文件,类似于MPEG-DASH,fmp4文件可用于HLS version 7和更高版本。
*/
// recorder.setOption("hls_segment_type","mpegts");
AVFormatContext fc = null;
fc = grabber.getFormatContext();
recorder.start(fc);
// 清空探测时留下的缓存
// grabber.flush();
AVPacket pkt = null;
for (int no_pkt_index = 0; no_pkt_index < 5000000 && err_index < 5000000; ) {
pkt = grabber.grabPacket();
if (pkt == null || pkt.size() <= 0 || pkt.data() == null) {
Thread.sleep(1);
no_pkt_index++;
continue;
}
// 获取到的pkt的dts,pts异常,将此包丢弃掉。
if (pkt.dts() == avutil.AV_NOPTS_VALUE && pkt.pts() == avutil.AV_NOPTS_VALUE || pkt.pts() < dts) {
err_index++;
av_packet_unref(pkt);
continue;
}
// 矫正dts,pts
pkt.pts(pts);
pkt.dts(dts);
err_index += (recorder.recordPacket(pkt) ? 0 : 1);
// pts,dts累加
timebase = grabber.getFormatContext().streams(pkt.stream_index()).time_base().den();
pts += (timebase / (int) framerate);
dts += (timebase / (int) framerate);
}
} catch (Exception e) {
grabber.stop();
grabber.close();
if (recorder != null) {
recorder.stop();
recorder.release();
}
} finally {
grabber.stop();
grabber.close();
if (recorder != null) {
recorder.stop();
recorder.release();
}
}
}
/**
* 创建BufferedImage对象
*/
public static BufferedImage FrameToBufferedImage(Frame frame) {
Java2DFrameConverter converter = new Java2DFrameConverter();
BufferedImage bufferedImage = converter.getBufferedImage(frame);
//旋转90度
bufferedImage = rotateClockwise90(bufferedImage);
return bufferedImage;
}
/**
* 处理图片,将图片旋转90度
*/
public static BufferedImage rotateClockwise90(BufferedImage bi) {
int width = bi.getWidth();
int height = bi.getHeight();
BufferedImage bufferedImage = new BufferedImage(height, width, bi.getType());
for (int i = 0; i < width; i++)
for (int j = 0; j < height; j++)
bufferedImage.setRGB(j, i, bi.getRGB(i, j));
return bufferedImage;
}
/**
* 摄像头截图
*/
public static void screenshot(String rtspURL, String fileName) throws IOException {
FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(rtspURL);
grabber.setOption("rtsp_transport", "tcp");
grabber.setImageWidth(960);
grabber.setImageHeight(540);
grabber.start();
File outPut = new File(fileName);
while (true) {
Frame frame = grabber.grabImage();
if (frame != null) {
ImageIO.write(FrameToBufferedImage(frame), "jpg", outPut);
grabber.stop();
grabber.release();
break;
}
}
}
}
接口
@Slf4j
@RestController
public class RtspToHttpController {
@Autowired
private StreamConfig streamConfig;
public volatile ExecutorService executor;
/**
* 播放rtsp流
* @param stream
* @return
*/
@PostMapping("/rtsp/play")
public JSONObject rtspPlay(@RequestBody RtspStream stream) {
log.info("进入播发视频监控流参数:{}", stream);
//随机数
String random = RandomUtil.randomString(Integer.parseInt(streamConfig.getRandom()));
String rtmpStream = streamConfig.getRtmp() + random;
try {
executor = Executors.newSingleThreadExecutor();
executor.execute(new packetRun(rtmpStream, stream.getRtspStream()));
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
JSONObject json = new JSONObject();
json.put("http", streamConfig.getHttp() + random);
json.put("rtmp", rtmpStream);
log.info("播发视频监控流返回参数:{}", json);
return json;
}
/**
* 关闭流
* @return
*/
@GetMapping("/rtsp/stop")
public JSONObject rtspStop() {
log.info("关闭监控视频流");
JSONObject resJson = new JSONObject();
resJson.put("code", HttpStatus.HTTP_OK);
resJson.put("msg", "正常关闭");
executor.shutdownNow();
if (executor.isShutdown()) {
resJson.put("msg", "未正常关闭");
}
return resJson;
}
class packetRun implements Runnable {
private String rtmp;
private String rtsp;
public packetRun(String rtmp, String rtsp) {
this.rtmp = rtmp;
this.rtsp = rtsp;
}
@Override
public void run() {
try {
new ConvertVideoPacket().from(rtsp)
.to(rtmp)
.go();
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
}
请求参数
@Data
public class RtspStream {
private String rtspStream;
}
配置文件
stream:
http: http://127.0.0.1:8001/live?port=1935&app=live&stream=
rtmp: rtmp://127.0.0.1:1935/live/
random: 3
实体类
@Data
@Component
public class StreamConfig {
@Value("${stream.http}")
private String http;
@Value("${stream.rtmp}")
private String rtmp;
@Value("${stream.random}")
private String random;
}
NGINX配置
\#user nobody;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8001;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location /live {
flv_live on; #打开HTTP播放FLV直播流功能
chunked_transfer_encoding on; #支持'Transfer-Encoding: chunked'方式回复
add_header 'Access-Control-Allow-Origin' '*'; #添加额外的HTTP头
add_header 'Access-Control-Allow-Credentials' 'true'; #添加额外的HTTP头
}
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root temp;
add_header 'Cache-Control' 'no-cache';
}
location /dash {
root temp;
add_header 'Cache-Control' 'no-cache';
}
location /stat {
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
}
location /stat.xsl {
root www/rtmp; #指定stat.xsl的位置
}
location /control {
rtmp_control all; #rtmp控制模块的配置
}
}
}
rtmp {
server {
listen 1935;
application live {
live on;
gop_cache on;
}
}
}
NGINX添加插件命令
./configure --add-module=./nginx-http-flv-module
make & make install
方式二(独立部署ffmpeg)
FFMPEG
下载ffmpeg插件, 下面有提供插件包,也可自行百度下载, 安装好后配置环境变量即可
执行命令
ffmpeg -re -rtsp_transport tcp -i “rtsp://admin:wzn123456@127.0.0.1/cam/realmonitor?channel=1&subtype=0” -f flv -vcodec libx264 -vprofile baseline -acodec aac -ar 44100 -strict -2 -ac 1 -f flv -s 1280x720 -q 10 “rtmp://127.0.0.1:1935/live/test”
HTTP访问格式
http://127.0.0.1:8001/live?port=1935&app=live&stream=test
相关插件
nginx-flv-file.zip :已经集成http-flv-module的nginx
ffmpeg: 流转换工具, 使用方式二必须安装
nginx-http-flv-module: rtsp转http的nginx插件
链接:https://pan.baidu.com/s/1LEcUC3N0a6rezTi8Pv2sNg
提取码:20hw```java
方式一(Java集成ffmpeg)
核心代码
import lombok.SneakyThrows;
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.*;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import static org.bytedeco.ffmpeg.global.avcodec.av_packet_unref;
/**
* rtsp转rtmp(转封装方式).
*/
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 += (record.recordPacket(pkt) ? 0 : 1);//如果失败err_index自增1
av_packet_unref(pkt);
} catch (Exception e) {//推流失败
err_index++;
}
}
return this;
}
@SneakyThrows
public static void main(String[] args) throws Exception, IOException {
String rtsp = "rtsp://admin:Ctkj@123456+@222.88.170.174:55103/LiveMedia/ch1/Media1";
//String output = "C:\\Users\\zz\\Desktop\\a\\m3u8one.m3u8";
// push(rtsp, output);
//运行,设置视频源和推流地址
new ConvertVideoPacket()
.from(rtsp)
.to("rtmp://127.0.0.1:1935/live/test")
.go();
}
/**
* 转m3u8
* input rtsp的账号密码端口流字符串
* output 要保存的文件目录生成m3u8文件 一直生成ts文件
*/
@SneakyThrows
public static void push(String input, String output) {
FFmpegFrameGrabber grabber = null;// 采集器
FFmpegFrameRecorder recorder = null;// 解码器
int bitrate = 2500000;// 比特率
double framerate;// 帧率
int err_index = 0;// 推流过程中出现错误的次数
int timebase;// 时钟基
long dts = 0, pts = 0;// pkt的dts、pts时间戳
try {
// 开启ffmpeg日志级别;QUIET是屏蔽所有,可选INFO、DEBUG、ERROR等
avutil.av_log_set_level(avutil.AV_LOG_INFO);
FFmpegLogCallback.set();
grabber = new FFmpegFrameGrabber(input);
grabber.setOption("rtsp_transport", "tcp");
grabber.start();
// 异常的framerate,强制使用25帧
if (grabber.getFrameRate() > 0 && grabber.getFrameRate() < 100) {
framerate = grabber.getFrameRate();
} else {
framerate = 25.0;
}
bitrate = grabber.getVideoBitrate();// 获取到的比特率 0
recorder = new FFmpegFrameRecorder(output, grabber.getImageWidth(), grabber.getImageHeight(), 0);
// 设置比特率
recorder.setVideoBitrate(bitrate);
// h264编/解码器
recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264);
// 设置音频编码
recorder.setAudioCodec(avcodec.AV_CODEC_ID_AAC);
// 视频帧率(保证视频质量的情况下最低25,低于25会出现闪屏)
recorder.setFrameRate(framerate);
// 关键帧间隔,一般与帧率相同或者是视频帧率的两倍
recorder.setGopSize((int) framerate);
// 解码器格式
recorder.setFormat("hls");
// 单个切片时长,单位是s,默认为2s
recorder.setOption("hls_time", "5");
// HLS播放的列表长度,0标识不做限制
recorder.setOption("hls_list_size", "20");
// 设置切片的ts文件序号起始值,默认从0开始,可以通过此项更改
recorder.setOption("start_number", "1");
// recorder.setOption("hls_segment_type","mpegts");
// 自动删除切片,如果切片数量大于hls_list_size的数量,则会开始自动删除之前的ts切片,只保 留hls_list_size个数量的切片
recorder.setOption("hls_flags", "delete_segments");
// ts切片自动删除阈值,默认值为1,表示早于hls_list_size+1的切片将被删除
//recorder.setOption("hls_delete_threshold","1");
/*
* hls的切片类型: 'mpegts':以MPEG-2传输流格式输出ts切片文件,可以与所有HLS版本兼容。 'fmp4':以Fragmented
* MP4(简称:fmp4)格式输出切片文件,类似于MPEG-DASH,fmp4文件可用于HLS version 7和更高版本。
*/
// recorder.setOption("hls_segment_type","mpegts");
AVFormatContext fc = null;
fc = grabber.getFormatContext();
recorder.start(fc);
// 清空探测时留下的缓存
// grabber.flush();
AVPacket pkt = null;
for (int no_pkt_index = 0; no_pkt_index < 5000000 && err_index < 5000000; ) {
pkt = grabber.grabPacket();
if (pkt == null || pkt.size() <= 0 || pkt.data() == null) {
Thread.sleep(1);
no_pkt_index++;
continue;
}
// 获取到的pkt的dts,pts异常,将此包丢弃掉。
if (pkt.dts() == avutil.AV_NOPTS_VALUE && pkt.pts() == avutil.AV_NOPTS_VALUE || pkt.pts() < dts) {
err_index++;
av_packet_unref(pkt);
continue;
}
// 矫正dts,pts
pkt.pts(pts);
pkt.dts(dts);
err_index += (recorder.recordPacket(pkt) ? 0 : 1);
// pts,dts累加
timebase = grabber.getFormatContext().streams(pkt.stream_index()).time_base().den();
pts += (timebase / (int) framerate);
dts += (timebase / (int) framerate);
}
} catch (Exception e) {
grabber.stop();
grabber.close();
if (recorder != null) {
recorder.stop();
recorder.release();
}
} finally {
grabber.stop();
grabber.close();
if (recorder != null) {
recorder.stop();
recorder.release();
}
}
}
/**
* 创建BufferedImage对象
*/
public static BufferedImage FrameToBufferedImage(Frame frame) {
Java2DFrameConverter converter = new Java2DFrameConverter();
BufferedImage bufferedImage = converter.getBufferedImage(frame);
//旋转90度
bufferedImage = rotateClockwise90(bufferedImage);
return bufferedImage;
}
/**
* 处理图片,将图片旋转90度
*/
public static BufferedImage rotateClockwise90(BufferedImage bi) {
int width = bi.getWidth();
int height = bi.getHeight();
BufferedImage bufferedImage = new BufferedImage(height, width, bi.getType());
for (int i = 0; i < width; i++)
for (int j = 0; j < height; j++)
bufferedImage.setRGB(j, i, bi.getRGB(i, j));
return bufferedImage;
}
/**
* 摄像头截图
*/
public static void screenshot(String rtspURL, String fileName) throws IOException {
FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(rtspURL);
grabber.setOption("rtsp_transport", "tcp");
grabber.setImageWidth(960);
grabber.setImageHeight(540);
grabber.start();
File outPut = new File(fileName);
while (true) {
Frame frame = grabber.grabImage();
if (frame != null) {
ImageIO.write(FrameToBufferedImage(frame), "jpg", outPut);
grabber.stop();
grabber.release();
break;
}
}
}
}
接口
@Slf4j
@RestController
public class RtspToHttpController {
@Autowired
private StreamConfig streamConfig;
public volatile ExecutorService executor;
/**
* 播放rtsp流
* @param stream
* @return
*/
@PostMapping("/rtsp/play")
public JSONObject rtspPlay(@RequestBody RtspStream stream) {
log.info("进入播发视频监控流参数:{}", stream);
//随机数
String random = RandomUtil.randomString(Integer.parseInt(streamConfig.getRandom()));
String rtmpStream = streamConfig.getRtmp() + random;
try {
executor = Executors.newSingleThreadExecutor();
executor.execute(new packetRun(rtmpStream, stream.getRtspStream()));
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
JSONObject json = new JSONObject();
json.put("http", streamConfig.getHttp() + random);
json.put("rtmp", rtmpStream);
log.info("播发视频监控流返回参数:{}", json);
return json;
}
/**
* 关闭流
* @return
*/
@GetMapping("/rtsp/stop")
public JSONObject rtspStop() {
log.info("关闭监控视频流");
JSONObject resJson = new JSONObject();
resJson.put("code", HttpStatus.HTTP_OK);
resJson.put("msg", "正常关闭");
executor.shutdownNow();
if (executor.isShutdown()) {
resJson.put("msg", "未正常关闭");
}
return resJson;
}
class packetRun implements Runnable {
private String rtmp;
private String rtsp;
public packetRun(String rtmp, String rtsp) {
this.rtmp = rtmp;
this.rtsp = rtsp;
}
@Override
public void run() {
try {
new ConvertVideoPacket().from(rtsp)
.to(rtmp)
.go();
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
}
}
请求参数
@Data
public class RtspStream {
private String rtspStream;
}
配置文件
stream:
http: http://127.0.0.1:8001/live?port=1935&app=live&stream=
rtmp: rtmp://127.0.0.1:1935/live/
random: 3
实体类
@Data
@Component
public class StreamConfig {
@Value("${stream.http}")
private String http;
@Value("${stream.rtmp}")
private String rtmp;
@Value("${stream.random}")
private String random;
}
NGINX配置
\#user nobody;
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 8001;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location /live {
flv_live on; #打开HTTP播放FLV直播流功能
chunked_transfer_encoding on; #支持'Transfer-Encoding: chunked'方式回复
add_header 'Access-Control-Allow-Origin' '*'; #添加额外的HTTP头
add_header 'Access-Control-Allow-Credentials' 'true'; #添加额外的HTTP头
}
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root temp;
add_header 'Cache-Control' 'no-cache';
}
location /dash {
root temp;
add_header 'Cache-Control' 'no-cache';
}
location /stat {
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
}
location /stat.xsl {
root www/rtmp; #指定stat.xsl的位置
}
location /control {
rtmp_control all; #rtmp控制模块的配置
}
}
}
rtmp {
server {
listen 1935;
application live {
live on;
gop_cache on;
}
}
}
NGINX添加插件命令
./configure --add-module=./nginx-http-flv-module
make & make install
方式二(独立部署ffmpeg)
代码Demo
public static void main(String[] args) {
try {
String cmd = "ffmpeg -re -rtsp_transport tcp -i \"" + rtsp + "\" -f flv -vcodec libx264 -vprofile baseline -acodec aac -ar 44100 -strict -2 -ac 1 -f flv -s 1280x720 -q 10 \"" + rtmp + "\"";
// 调用CMD命令
ProcessBuilder processBuilder = new ProcessBuilder("cmd.exe", "/c", cmd); // /c参数表示执行后关闭CMD窗口
processBuilder.redirectErrorStream(true); // 将错误输出流与标准输出流合并
Process process = processBuilder.start();
// 获取命令输出结果
InputStream inputStream = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "GBK")); // 设置编码为GBK
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// 等待命令执行完成
process.waitFor();
} catch (Exception e) {
throw new RuntimeException(e.getMessage());
}
}
FFMPEG
下载ffmpeg插件, 下面有提供插件包,也可自行百度下载, 安装好后配置环境变量即可
执行命令
ffmpeg -re -rtsp_transport tcp -i “rtsp://admin:wzn123456@127.0.0.1/cam/realmonitor?channel=1&subtype=0” -f flv -vcodec libx264 -vprofile baseline -acodec aac -ar 44100 -strict -2 -ac 1 -f flv -s 1280x720 -q 10 “rtmp://127.0.0.1:1935/live/test”
HTTP访问格式
http://127.0.0.1:8001/live?port=1935&app=live&stream=test
相关插件
nginx-flv-file.zip :已经集成http-flv-module的nginx
ffmpeg: 流转换工具, 使用方式二必须安装
nginx-http-flv-module: rtsp转http的nginx插件
链接:https://pan.baidu.com/s/1LEcUC3N0a6rezTi8Pv2sNg
提取码:20hw