# rtsp2httpflv
**Repository Path**: chengxuworld/rtsp2httpflv
## Basic Information
- **Project Name**: rtsp2httpflv
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2025-02-07
- **Last Updated**: 2025-02-11
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
https://blog.csdn.net/weixin_47914635/article/details/129691433
#
一、下载nginx
##
1、更新相关依赖
```shell
yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel
```
##
2、下载nginx
```shell
cd /usr/local
mkdir nginx
cd nginx
wget http://nginx.org/download/nginx-1.19.3.tar.gz
tar -zxvf nginx-1.19.3.tar.gz
```
#
二、下载nginx-http-flv-module
下载地址:https://gitcode.com/gh_mirrors/ng/nginx-http-flv-module?utm_source=csdn_github_accelerator&isLogin=1
直接下载最新的代码上传到服务器/usr/local/下
#
三、配置环境支持http2.0
注:配置http2.0是为了解决同源策略的限制,同一源下只能同时创建6个请求,播放6路视频。若无此需求,可跳第一步,直接使用http即可。
##
1、更新openssl版本
openssl version
openssl 版本在1.0.2以上则不需要更新,否则需要升级版本,如下是升级为1.1.1n
```shell
cd /usr/local/
mkdir openssl
cd openssl
wget https://www.openssl.org/source/openssl-1.1.1n.tar.gz
tar -zxvf openssl-1.1.1n.tar.gz
cd openssl-1.1.1n
./config
make && make install
```
备份原有的openssl
```shell
mv /usr/bin/openssl /usr/bin/openssl.bak
mv /usr/include/openssl /usr/include/openssl.bak
```
添加新的软链接
```shell
ln -s /usr/local/bin/openssl /usr/bin/openssl
ln -s /usr/local/include/openssl/ /usr/include/openssl
echo "/usr/local/lib64" >> /etc/ld.so.conf
ldconfig -v
```
##
2、编译nginx
```shell
cd /usr/local/nginx-1.19.3
./configure --with-http_ssl_module --with-openssl=/usr/local/openssl/openssl-1.1.1n --with-http_v2_module --add-module=/usr/local/nginx-http-flv-module
./configure --with-http_ssl_module --with-openssl=/root/openssl/openssl-1.1.1w --with-http_v2_module --add-module=/usr/local/nginx-http-flv-module
其中--with-openssl 是openssl的路径,--add-module 是nginx-http-flv-module的路径
make -j4
make install
```
##
3、生成https测试证书
```shell
cd /usr/local/nginx
mkdir certs
cd /certs
openssl genrsa -des3 -out server.key 2048
openssl rsa -in server.key -out server.key
openssl req -new -key server.key -out server.csr
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt
```
##
4、配置nginx.conf
```
worker_processes 1;
error_log logs/error.log error;
# load_module modules/ngx_http_flv_live_module.so;
events {
worker_connections 4096;
}
http {
include mime.types;
default_type application/octet-stream;
keepalive_timeout 65;
server {
listen 18071;
location /live {
flv_live on;
chunked_transfer_encoding on;
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Headers' 'X-Requested-With';
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS';
add_header 'Cache-Control' 'no-cache';
}
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /tmp; # hls方式m3u8访问的文件路径
}
location /stat {
rtmp_stat all;
rtmp_stat_format json;
}
}
server {
listen 18181 ssl http2;
server_name 192.168.3.122;
ssl_certificate "/usr/local/nginx/certs/server.crt"; # server公钥证书的路径
ssl_certificate_key "/usr/local/nginx/certs/server.key"; # server私钥的路径
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location /{
proxy_pass http://192.168.3.122:18071/;
}
}
}
rtmp_auto_push on;
rtmp_auto_push_reconnect 1s;
rtmp_socket_dir /tmp;
rtmp {
out_queue 4096;
out_cork 8;
max_streams 128;
timeout 15s;
drop_idle_publisher 15s;
log_interval 5s; #log 模块在 access.log 中记录日志的间隔时间,对调试非常有用
log_size 1m; #log 模块用来记录日志的缓冲区大小
server {
listen 18072;
server_name 192.168.3.122;
application transition {
live on;
gop_cache off;
# on_play http://192.168.3.13:18061/flv/on_play;
}
}
}
```
指定配置文件启动
```shell
../sbin/nginx -s stop
../sbin/nginx -c /usr/local/nginx/conf/nginx.conf
```
#
四、安装ffmpeg
```shell
cd /usr/local
mkdir ffmpeg
cd ffmpeg
```
##
1、升级yasm(yasm和nasm选一即可)
```shell
cd /usr/local/ffmpeg
mkdir yasm
cd yasm/
wget http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz
tar -zxvf yasm-1.3.0.tar.gz
cd yasm-1.3.0/
./configure
make && make install
yasm --version
ln -s /usr/local/ffmpeg/yasm/yasm-1.3.0/yasm /usr/bin/yasm
```
##
2、升级nasm
```shell
cd /usr/local/ffmpeg
mkdir nasm
cd nasm/
wget https://www.nasm.us/pub/nasm/releasebuilds/2.14.02/nasm-2.14.02.tar.bz2
tar -jxvf nasm-2.14.02.tar.bz2
cd nasm-2.14.02
./configure
make && make install
nasm --version
make && make install
ln -s /usr/local/ffmpeg/nasm/nasm-2.14.02/nasm /usr/bin/nasm
```
##
3、安装x264视频编码库
```shell
cd /usr/local/ffmpeg
mkdir x264
cd x264/
wget https://code.videolan.org/videolan/x264/-/archive/master/x264-master.tar.gz
tar -zxvf x264-master.tar.gz
cd x264-master
./configure
make && make install
```
##
4、安装ffmpeg
```shell
cd /usr/local/ffmpeg
wget http://www.ffmpeg.org/releases/ffmpeg-6.0.tar.gz
tar -zxvf ffmpeg-6.0.tar.gz
cd ffmpeg-6.0
./configure --prefix=/usr/local/ffmpeg
make && make install
```
配置环境变量
```shell
vim /etc/profile
```
在最后添加
```shell
export PATH=$PATH:/usr/local/ffmpeg/bin # 指向自己安装的ffmpeg下的bin目录
source /etc/profile #设置生效
ffmpeg -version # 查看版本
```
#
五、转码测试
##
1、转码为http-flv直接访问
```shell
# 执行指令,转换rtsp转为rtmp
ffmpeg -rtsp_transport tcp -i "rtsp视频流地址" -vcodec libx264 -vprofile baseline -an -tune zerolatency -preset ultrafast -g 25 -f flv "rtmp://192.168.3.122:18072/transition/home"
# 其中 18072为nginx下配置的rtmp端口,transition为nginx配置的location,home是自定义房间号
```
出现如下表示成功
vlc下载地址:http://www.videolan.org/vlc/
使用vlc直接访问,注意其中的18181是https配置的端口,port对应上方的18072,app对应上方的location,stream对应上方的home
点击播放查看,楼主测试的延迟大概在1s内,符合预期,直接采用该方案
##
2、转码为hls通过m3u8文件访问
hls保存了最新的视频文件,可自定义保存时限,可通过保存的文件实现回放功能,测试的延迟在2-3秒
```shell
# 执行指令,转为m3u8文件
ffmpeg -rtsp_transport tcp -i "rtsp视频流地址" -vcodec libx264 -vprofile baseline -an -f hls -hls_time 5 -hls_list_size 2 -hls_flags 2 "/tmp/hls/22222.m3u8"
/tmp/hls/测试.m3u8 # 注意这里配置的nginx中fls下root 配置的 /tmp,根据自己的配置修改
-hls_time n: # 设置每片的长度,默认值为2。单位为秒
-hls_list_size n: # 设置播放列表保存的最多条目,设置为0会保存有所片信息,默认值为5
-hls_flags n:# 设置多少片之后开始覆盖,如果设置为0则不会覆盖,默认值为0.这个选项能够避免在磁盘上存储过多的片,而且能够限制写入磁盘的最多的片的数量
-hls_start_number n:# 设置播放列表中sequence number的值为number,默认值为0
```
查看文件 cd /tmp/hls
使用vlc直接访问
http://192.168.3.122.1807/hls/测试.m3u8
#
六、通过Java下发指令
##
1、配置文件yml
```yaml
#flv视频地址
camera:
#转码服务器账号
username: root
#转码服务器密码
password: 123456
#转码服务器ip
host: 192.168.3.122
#播放视频流服务器地址
flv-url: https://192.168.3.122:18181/live?port=18072&app=transition&stream=%s
stat-url: http://192.168.3.122:18071/stat
#接收流服务器地址
rtmp-url: rtmp://192.168.3.122:18072/transition/
#关闭指定进程
close-command: ps -ef | grep 'ffmpeg.*%s' | grep -v grep | awk '{print $2}' | sed -e "s/^/kill -9 /g" | sh - &
#开始转码推流指令
open-command: nohup /usr/local/ffmpeg/bin/ffmpeg -rtsp_transport tcp -i "%s" -codec:v libx264 -vprofile baseline -codec:a aac -tune zerolatency -preset ultrafast -ar 24000 -ac 1 -g 25 -s 1600*900 -f flv "${camera.rtmp-url}%s" 1>/dev/null 2>&1 &
```
##
2、远程执行linux指令工具类
```java
package com.gongl.common.utils;
import com.jcraft.jsch.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.io.*;
@Service
public class ExecuteShellUtil {
private static final Logger log = LoggerFactory.getLogger(ExecuteShellUtil.class);
@Value("${camera.username}")
private String username;
@Value("${camera.password}")
private String password;
@Value("${camera.host}")
private String host;
private Session session;
public ExecuteShellUtil() {
}
@PostConstruct
private void connect() throws JSchException {
JSch jsch = new JSch();
session = jsch.getSession(username, host);
session.setPassword(password);
java.util.Properties config = new java.util.Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
session.connect();
log.info("连接到SFTP成功。host: " + host);
}
/**
*
* @param command
* @return 0--成功,1--失败
* @throws Exception
*/
public int execCmd(String command) throws Exception {
int returnCode = -1;
Channel channel = session.openChannel("exec");
((ChannelExec) channel).setCommand(command);
channel.setInputStream(null);
((ChannelExec) channel).setErrStream(System.err);
BufferedReader reader = new BufferedReader(new InputStreamReader(channel.getInputStream()));
channel.connect();
StringBuffer buf = new StringBuffer();
int c = -1;
while ((c = reader.read()) != -1) {
buf.append((char) c);
}
reader.close();
if (channel.isClosed()) {
returnCode = channel.getExitStatus();
}
log.info("---给: " + host + " 发送指令:" + command + " 。result:" + returnCode);
channel.disconnect();
return returnCode;
}
}
```
##
3、RestTemplate工具类,用于维护不需要转码的rtsp
###
3.1 添加依赖
支持http携带body请求,不需要可以不添加
```xml
org.apache.httpcomponents
httpclient
4.5.6
```
3.2 添加RestTemplateConfig配置类
https://blog.csdn.net/weixin_47914635/article/details/123206595
```java
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import javax.net.ssl.*;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
/**
* RestTemplate 配置
*
* @author gongl
*/
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
//修改restTemplate的RequestFactory使其支持Get携带body参数
restTemplate.setRequestFactory(new HttpComponentsClientRestfulHttpRequestFactory());
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
return restTemplate;
}
//http支持body-----------------start
public static final class HttpComponentsClientRestfulHttpRequestFactory extends HttpComponentsClientHttpRequestFactory {
@Override
protected HttpUriRequest createHttpUriRequest(HttpMethod httpMethod, URI uri) {
if (httpMethod == HttpMethod.GET) {
return new HttpGetRequestWithEntity(uri);
}
return super.createHttpUriRequest(httpMethod, uri);
}
}
private static final class HttpGetRequestWithEntity extends HttpEntityEnclosingRequestBase {
public HttpGetRequestWithEntity(final URI uri) {
super.setURI(uri);
}
@Override
public String getMethod() {
return HttpMethod.GET.name();
}
}
//http支持body-----------------end
//支持https
public static class HttpsClientRequestFactory extends SimpleClientHttpRequestFactory {
@Override
protected void prepareConnection(HttpURLConnection connection, String httpMethod) {
try {
if (!(connection instanceof HttpsURLConnection)) {
throw new RuntimeException("An instance of HttpsURLConnection is expected");
}
HttpsURLConnection httpsConnection = (HttpsURLConnection) connection;
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
@Override
public void checkClientTrusted(X509Certificate[] certs, String authType) {
}
@Override
public void checkServerTrusted(X509Certificate[] certs, String authType) {
}
}
};
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustAllCerts, new java.security.SecureRandom());
httpsConnection.setSSLSocketFactory(new MyCustomSSLSocketFactory(sslContext.getSocketFactory()));
httpsConnection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String s, SSLSession sslSession) {
return true;
}
});
super.prepareConnection(httpsConnection, httpMethod);
} catch (Exception e) {
e.printStackTrace();
}
}
// SSLSocketFactory用于创建 SSLSockets
private static class MyCustomSSLSocketFactory extends SSLSocketFactory {
private final SSLSocketFactory delegate;
public MyCustomSSLSocketFactory(SSLSocketFactory delegate) {
this.delegate = delegate;
}
// 返回默认启用的密码套件。除非一个列表启用,对SSL连接的握手会使用这些密码套件。
// 这些默认的服务的最低质量要求保密保护和服务器身份验证
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
// 返回的密码套件可用于SSL连接启用的名字
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
@Override
public Socket createSocket(final Socket socket, final String host, final int port,
final boolean autoClose) throws IOException {
final Socket underlyingSocket = delegate.createSocket(socket, host, port, autoClose);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final String host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final String host, final int port, final InetAddress localAddress,
final int localPort) throws
IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final InetAddress host, final int port) throws IOException {
final Socket underlyingSocket = delegate.createSocket(host, port);
return overrideProtocol(underlyingSocket);
}
@Override
public Socket createSocket(final InetAddress host, final int port, final InetAddress localAddress,
final int localPort) throws
IOException {
final Socket underlyingSocket = delegate.createSocket(host, port, localAddress, localPort);
return overrideProtocol(underlyingSocket);
}
private Socket overrideProtocol(final Socket socket) {
if (!(socket instanceof SSLSocket)) {
throw new RuntimeException("An instance of SSLSocket is expected");
}
((SSLSocket) socket).setEnabledProtocols(new String[]{"TLSv1.2"});
return socket;
}
}
}
}
```
###
3.3 添加公共类RestTemplateUtils
```java
package com.haxk.common.utils.http;
import com.alibaba.fastjson.JSON;
import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
/**
* RestTemplate 远程调用工具类
*
* @author gongl
*/
public class RestTemplateUtils {
private static final RestTemplate restTemplate = new RestTemplate();
private static final RestTemplate sslRestTemplate = new RestTemplate(new RestTemplateConfig.HttpsClientRequestFactory());
/**
* GET请求调用方式
*
* @param url 请求URL
* @param responseType 返回对象类型
* @return ResponseEntity 响应对象封装类
*/
public static ResponseEntity get(String url, Class responseType) {
return restTemplate.getForEntity(url, responseType);
}
/**
* GET请求调用方式
*
* @param url 请求URL
* @param responseType 返回对象类型
* @param uriVariables URL中的变量,按顺序依次对应
* @return ResponseEntity 响应对象封装类
*/
public static ResponseEntity get(String url, Class responseType, Object... uriVariables) {
return restTemplate.getForEntity(url, responseType, uriVariables);
}
/**
* GET请求调用方式
*
* @param url 请求URL
* @param responseType 返回对象类型
* @param uriVariables URL中的变量,与Map中的key对应
* @return ResponseEntity 响应对象封装类
*/
public static ResponseEntity get(String url, Class responseType, Map uriVariables) {
return restTemplate.getForEntity(url, responseType, uriVariables);
}
/**
* 带请求头的GET请求调用方式
*
* @param url 请求URL
* @param headers 请求头参数
* @param responseType 返回对象类型
* @param uriVariables URL中的变量,按顺序依次对应
* @return ResponseEntity 响应对象封装类
*/
public static ResponseEntity get(String url, Map headers, Class responseType, Object... uriVariables) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setAll(headers);
return get(url, httpHeaders, responseType, uriVariables);
}
/**
* 带请求头的GET请求调用方式
*
* @param url 请求URL
* @param headers 请求头参数
* @param responseType 返回对象类型
* @param uriVariables URL中的变量,按顺序依次对应
* @return ResponseEntity 响应对象封装类
*/
public static ResponseEntity get(String url, HttpHeaders headers, Class responseType, Object... uriVariables) {
HttpEntity> requestEntity = new HttpEntity<>(headers);
return exchange(url, HttpMethod.GET, requestEntity, responseType, uriVariables);
}
/**
* 带请求头的GET请求调用方式
*
* @param url 请求URL
* @param headers 请求头参数
* @param responseType 返回对象类型
* @param uriVariables URL中的变量,与Map中的key对应
* @return ResponseEntity 响应对象封装类
*/
public static ResponseEntity get(String url, Map headers, Class responseType, Map uriVariables) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setAll(headers);
return get(url, httpHeaders, responseType, uriVariables);
}
/**
* 带请求头的GET请求调用方式
*
* @param url 请求URL
* @param headers 请求头参数
* @param responseType 返回对象类型
* @param uriVariables URL中的变量,与Map中的key对应
* @return ResponseEntity 响应对象封装类
*/
public static ResponseEntity get(String url, HttpHeaders headers, Class responseType, Map uriVariables) {
HttpEntity> requestEntity = new HttpEntity<>(headers);
return exchange(url, HttpMethod.GET, requestEntity, responseType, uriVariables);
}
/**
* body携带参数的GET请求调用方式
*
* @param url 请求URL
* @param params 请求参数
* @return ResponseEntity 响应对象封装类
*/
public static ResponseEntity get(String url, Map params, Class responseType) {
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/json");
HttpEntity httpEntity = new HttpEntity<>(JSON.toJSONString(params), headers);
return restTemplate.exchange(url, HttpMethod.GET, httpEntity, responseType);
}
/**
* POST请求调用方式
*
* @param url 请求URL
* @param responseType 返回对象类型
* @return ResponseEntity 响应对象封装类
*/
public static ResponseEntity post(String url, Class responseType) {
return restTemplate.postForEntity(url, HttpEntity.EMPTY, responseType);
}
/**
* POST请求调用方式
*
* @param url 请求URL
* @param requestBody 请求参数体
* @param responseType 返回对象类型
* @return ResponseEntity 响应对象封装类
*/
public static ResponseEntity post(String url, Object requestBody, Class responseType) {
return restTemplate.postForEntity(url, requestBody, responseType);
}
/**
* POST请求调用方式
*
* @param url 请求URL
* @param requestBody 请求参数体
* @param responseType 返回对象类型
* @param uriVariables URL中的变量,按顺序依次对应
* @return ResponseEntity 响应对象封装类
*/
public static ResponseEntity post(String url, Object requestBody, Class responseType, Object... uriVariables) {
return restTemplate.postForEntity(url, requestBody, responseType, uriVariables);
}
/**
* POST请求调用方式
*
* @param url 请求URL
* @param requestBody 请求参数体
* @param responseType 返回对象类型
* @param uriVariables URL中的变量,与Map中的key对应
* @return ResponseEntity 响应对象封装类
*/
public static ResponseEntity post(String url, Object requestBody, Class responseType, Map uriVariables) {
return restTemplate.postForEntity(url, requestBody, responseType, uriVariables);
}
/**
* 带请求头的POST请求调用方式
*
* @param url 请求URL
* @param headers 请求头参数
* @param requestBody 请求参数体
* @param responseType 返回对象类型
* @param uriVariables URL中的变量,按顺序依次对应
* @return ResponseEntity 响应对象封装类
*/
public static ResponseEntity post(String url, Map headers, Object requestBody, Class responseType, Object... uriVariables) {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setAll(headers);
return post(url, httpHeaders, requestBody, responseType, uriVariables);
}
/**
* 带请求头的POST请求调用方式
*
* @param url 请求URL
* @param headers 请求头参数
* @param requestBody 请求参数体
* @param responseType 返回对象类型
* @param uriVariables URL中的变量,按顺序依次对应
* @return ResponseEntity 响应对象封装类
*/
public static ResponseEntity post(String url, HttpHeaders headers, Object requestBody, Class responseType, Object... uriVariables) {
HttpEntity