之前弄过一次转流,rtsp转rtmp。之前的方法download.csdn.net/download/qq…
本次采用另一种方式。这里仅做简单记录,方便后期再次使用时查看。
本实例只是推流,修改一下工具类参数也可用于转流。
先说一下思路,将转流的链接存redis,定期查redis是否还有该链接,有就继续转,没有就暂停转流(业务需求这样设计,实现定时关闭不必要的转流)
pom文件
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>javacv</artifactId>
<version>1.5.4</version>
</dependency>
<dependency>
<groupId>org.bytedeco</groupId>
<artifactId>ffmpeg-platform</artifactId>
<version>4.3.1-1.5.4</version>
</dependency>
VideoUtils(转流工具类也有参考网上的项目)
//import org.bytedeco.javacpp.avcodec;
import com.ruoyi.common.redis.service.RedisService;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.FFmpegFrameRecorder;
import org.bytedeco.javacv.Frame;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class VideoUtils {
@Autowired
private RedisService redisService;
/* public static void main(String[] args) throws Exception {
System.out.println("start...");
String rtmpPath = "rtmp://localhost:1935/live/YFZX2";
String rtspPath ="rtmp://58.200.131.2:1935/livetv/gxtv";// "rtmp://58.200.131.2:1935/livetv/hunantv";//"rtsp://admin:admin88888@192.168.100.200:554/h264/ch34/sub/av_stream";
int audioRecord =1; // 0 = 不录制,1=录制
boolean saveVideo = false;
push(rtmpPath,rtspPath,audioRecord,saveVideo);
System.out.println("end...");
}*/
/**
* @param newurl
* @param oldurl
* @param audioRecord
* @param saveVideo
* @throws Exception
*/
public void push(String newurl,String oldurl,String uuid,int audioRecord,boolean saveVideo ) throws Exception {
// 使用rtsp的时候需要使用 FFmpegFrameGrabber,不能再用 FrameGrabber
int width = 1024,height = 576;
FFmpegFrameGrabber grabber = FFmpegFrameGrabber.createDefault(oldurl);
//grabber.setOption("rtsp_transport", "tcp"); // 使用tcp的方式,不然会丢包很严重
grabber.setOption("rtmp_transport", "tcp");
grabber.setImageWidth(width);
grabber.setImageHeight(height);
System.out.println("grabber start");
grabber.start();
// 流媒体输出地址,分辨率(长,高),是否录制音频(0:不录制/1:录制)
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder(newurl,width,height, audioRecord);
recorder.setInterleaved(true);
recorder.setVideoOption("crf","28");
//recorder.setVideoCodec(avcodec.AV_CODEC_ID_H264); // 28
recorder.setFormat("flv"); // rtmp的类型
recorder.setFrameRate(25);
recorder.setImageWidth(width);recorder.setImageHeight(height);
//recorder.setPixelFormat(0); // yuv420p
recorder.setAudioCodec(grabber.getAudioCodec());
System.out.println("recorder start");
recorder.start();
System.out.println("all start!!");
int count = 0;
//boolean exit = false;
while(true){
count++;
// Frame frame = grabber.grabImage();
Frame frame;
if(audioRecord==1){
frame=grabber.grab();
}else{
frame=grabber.grabImage();
}
/// Frame frame = grabber.grabImage();
if(frame == null){
continue;
}
if(count % 100 == 0){
System.out.println(newurl+"count="+count);
if(count % 1000 == 0){
if(redisService.getCacheObject("video:"+uuid)==null){
System.out.println(newurl+"已停止");
break;
}
}
}
recorder.record(frame);
//Thread.sleep(1000/50);
}
grabber.stop();
grabber.release();
recorder.stop();
recorder.release();
}
}
VideoController(传入的url是需要转流的地址经过url编码,比如rtmp%3a%2f%2f58.200.131.2%3a1935%2flivetv%2fhunantv)
另外就是线程池,这里自己其实也不知道应不应该使用。
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.video.utils.VideoUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.*;
import java.util.UUID;
import java.util.concurrent.*;
@RefreshScope
@RestController
@RequestMapping("video")
public class VideoController {
@Value("${domain.name.rtmp:localhost:1935}")
private String rtmpaddress;
@Autowired
private RedisService redisService;
@Autowired
private VideoUtils videoUtils;
//url : rtmp%3a%2f%2f58.200.131.2%3a1935%2flivetv%2fgxtv
//rtmp%3a%2f%2f58.200.131.2%3a1935%2flivetv%2fhunantv
@PostMapping("getPublicUrl")
public AjaxResult getPublicUrl(@RequestParam("url")String url){
if(url==null||url.equals("")){
return AjaxResult.error("链接不能为空");
}
String result= UUID.randomUUID().toString().replace("-", "").toUpperCase();
String newurl="rtmp://"+rtmpaddress+"/live/"+result;
int audioRecord =1; // 0 = 不录制,1=录制声音
boolean saveVideo = false;
redisService.setCacheObject("video:"+result,url,12L, TimeUnit.HOURS);
//线程池
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
singleThreadPool.execute(()-> {
try {
videoUtils.push(newurl,url,result,audioRecord,saveVideo);
} catch (Exception e) {
e.printStackTrace();
}
});
singleThreadPool.shutdown();
return AjaxResult.success("成功",newurl);
}
@RequestMapping("stopPublicUrl")
public AjaxResult stopPublicUrl(String newurl){
String url=newurl.replaceAll("rtmp://(.*)/live/","");
url="video:"+url;
redisService.deleteObject(url);
return AjaxResult.success("成功");
}
}
项目是基于ruoyi-cloud写的,所以reids操作类如果没有的话可以复制下面的。
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
/**
* spring redis 工具类
*
* @author ruoyi
**/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisService {
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit)
{
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout)
{
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit)
{
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key)
{
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection)
{
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList)
{
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key)
{
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> long setCacheSet(final String key, final Set<T> dataSet)
{
Long count = redisTemplate.opsForSet().add(key, dataSet);
return count == null ? 0 : count;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key)
{
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
{
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key)
{
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value)
{
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey)
{
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
{
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern)
{
return redisTemplate.keys(pattern);
}
}
项目仅供参考,另外nginx大家可以直接网上下载,配置rtmp模块,或者直接点击下载。download.csdn.net/download/qq…