视频转流-java-视频推流-内网推公网

523 阅读4分钟

之前弄过一次转流,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…