用Java为视频添加图片水印(类似直播)

  • Post author:
  • Post category:java



如何用Java给图片添加文字水印


最近做的项目需要实现为视频添加图片水印,在网上查找和实践后,实现了功能,特此记录一下,方便日后用到。本文用的是从海康摄像头拿到的视频流,添加图片后编译成rtmp视频流,在IE浏览器中可以看到(在谷歌浏览其中打开没画面,不知道为什么),技术使用springboot+vue+ffmpeg+nginx+nginx的rtmp模块



1.首先

下载nginx

在这里插入图片描述

然后

下载nginx rtsp 模块


在这里插入图片描述

下载压缩包解压到nginx的根目录下

在这里插入图片描述

然后打开nginx目录下的conf,如果里面没有nginx.conf就重下一个其他包把下面的配置文件拿过来放在conf下面

在这里插入图片描述

然后双击nginx目录下的nginx.exe就可以启动nginx啦,打开浏览器输入127.0.0.1就可以看到成功页面

在这里插入图片描述

然后再nginx.conf中添加如下配置

rtmp{
	server{
	    listen 1935;
        #转发的地址
		application live{
            live on;
            record off;
		}
		application hls{
            live on;
            hls on;
            hls_path nginx-rtmp-module/hls;
            hls_cleanup off;
		}
	}
}

cmd 切换到nginx目录下面 nginx.exe -s reload

重新加载nginx配置文件 nginx配置结束



2.

下载ffmpeg

链接:https://pan.baidu.com/s/1LUWeVnM8Ig-2b2ubAw3t1A

提取码:1234

解压后在环境变量path中添加目录地址即可

打开cmd,输入ffmpeg -version,可以查看是否安装成功

在这里插入图片描述

使用ffmpeg命令

ffmpeg -i “rtsp流路径” -vcodec copy -acodec copy -f flv “rtmp://127.0.0.1:1935/live/”即可对视频进行操作

具体操作如下



3.用Java操作视频

首先controller层

public class Video {
	/**海康摄像头rtsp格式说明:
	 * ffmpegPath:ffmpeg的安装地址(xx\\xx\\ffmpeg.exe)
	 * rtsp://[username]:[password]@[ip]:[port]/[codec]/[channel]/[subtype]/av_stream
	 * username: 用户名。例如admin。
	 * password: 密码。例如12345。
	 * ip: 为设备IP。例如 192.0.0.64。
	 * port: 端口号默认为554,若为默认可不填写。
	 * codec:有H.264、MPEG-4、mpeg4这几种。
	 * channel: 通道号,起始为1。例如通道1,则为ch1。
	 * subtype: 码流类型,主码流为main,辅码流为sub
	 * logo:添加图片水印的图片(注意windows下面的logo地址前面要写4个反斜杠如:D\\\\:/html/3.png;linux下logo水印路径:/tmp/TVLOG.png)
	 * xaxis:水印的x轴距离(左上角为原点)
	 * yaxis:水印的y轴距离(左上角为原点)
	 */
	@RequestMapping(value = "/waterMark",method = RequestMethod.POST)
	@ResponseBody
	public CommonResult waterMark(@RequestBody Map<String,String> map){
		log.info("进入视频添加水印的方法");
		String ffmpegPath =map.get("ffmpegPath");
		String username = map.get("username");
		String password = map.get("password");
		String ip = map.get("ip");
		String port = map.get("port");
		String codec = map.get("codec");
		String channel = map.get("channel");
		String subtype = map.get("subtype");
		String logo = map.get("logo");
		String xaxis = map.get("xaxis");
		String yaxis = map.get("yaxis");
		if (StrUtil.isEmpty(ffmpegPath)){
			return ResultUtils.error(-1,"ffmpeg.exe地址不能为空");
		}else if (StrUtil.isEmpty(username)){
			return ResultUtils.error(-1,"摄像头用户名不能为空");
		}else if (StrUtil.isEmpty(password)){
			return ResultUtils.error(-1,"摄像头密码不能为空");
		}else if (StrUtil.isEmpty(ip)){
			return ResultUtils.error(-1,"摄像头ip不能为空");
		}else if (StrUtil.isEmpty(port)){
			return ResultUtils.error(-1,"摄像头端口号不能为空");
		}else if (StrUtil.isEmpty(codec)){
			return ResultUtils.error(-1,"摄像头的codec不能为空");
		}else if (StrUtil.isEmpty(channel)){
			return ResultUtils.error(-1,"摄像头通道号不能为空");
		}else if (StrUtil.isEmpty(subtype)){
			return ResultUtils.error(-1,"码流类型不能为空");
		}
		HashMap<String, String>dto=new HashMap<String, String>();
		dto.put("ffmpegPath",ffmpegPath);
		dto.put("username",username);
		dto.put("password",password);
		dto.put("ip",ip);
		dto.put("port",port);
		dto.put("codec",codec);
		dto.put("channel",channel);
		dto.put("subtype",subtype);
		dto.put("logo",logo);
		dto.put("xaxis",xaxis);
		dto.put("yaxis",yaxis);
		String secondsString=  new VideoUtils().videoTransfer(dto);
		log.info("转换共用:"+secondsString+"秒");
		return ResultUtils.success();
	}
}

工具类

public class VideoUtils {
	public static String exec(List<String> cmd) {
		String converted_time = null;
		Process proc =null;
		BufferedReader stdout = null;
		try {
			ProcessBuilder builder = new ProcessBuilder();
			builder.command(cmd);
			builder.redirectErrorStream(true);
			proc = builder.start();
			stdout = new BufferedReader(new InputStreamReader(proc.getInputStream()));
			String line;
			int lineNumber=1;
			List<String> returnStringList = new LinkedList<String>();
			while ((line = stdout.readLine()) != null) {
				System.out.println("第"+lineNumber+"行:"+line);
				lineNumber=lineNumber+1;
					returnStringList.add(dealString(line));
			}
			String info = "";
			for (int i = returnStringList.size() - 1; i >= 0; i--) {
				if (null != returnStringList.get(i)	&& returnStringList.get(i).startsWith("frame=")) {
					info = returnStringList.get(i);
					break;
				}
			}
			if (null != info) {
				converted_time = info.split("time=")[1].split("bitrate=")[0].trim();
			}
		} catch (IndexOutOfBoundsException ex) {
			converted_time = null;
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			try {
				proc.waitFor();
				stdout.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return converted_time;
	}

	/**
	 * 判断返回内容通过正则表达式判断
	 * @param str
	 * @return
	 */
	public static String dealString( String str ){
		Matcher m=java.util.regex.Pattern.compile("^frame=.*" ).matcher(str);
		String msg="";
		while (m.find()){
			msg = m.group();
		}
		return msg;
	}

	/**
	 *  如果返回不是null的值就是成功(值为转换用时单位:秒)
	 * @param instr
	 * @return
	 */
	public static String returnSecond(String instr){
		String returnValue=null;
		if (null!=instr) {
			String[] a=instr.split("\\.");
			String[] b=a[0].split(":");
			int returnNumber=0;
			if (null!=instr&&b[0].length()!=0) {
				returnNumber=Integer.valueOf(b[0])*60*60 + Integer.valueOf(b[1])*60 + Integer.valueOf(b[2]);
				returnValue=String.valueOf(returnNumber);
			}else{
				returnValue=null;
			}
		}
		return returnValue;
	}

	/**
	 * @ HashMap<String,String> dto 参数传递对象<br>
	 * dto中包含的参数<br>
	 * (必填)1.ffmpeg_path:ffmpeg执行文件地址,如 d:\\ffmpeg\\ffmpeg.exe Linux下直接调用ffmpeg命令(当然你事先已经有这个程序了)<br>
	 * (必填)2.input_path:原视频路径<br>
	 * (必填)3.video_converted_path:转换后视频输出路径<br>
	 * (可选)4.screen_size:视频尺寸 长度乘宽度 乘号用英文小写"x"如 512x480<br>
	 * (可选)5.logo:水印地址(其实在ffmpeg中有一个专门的watermark参数,logo跟它有何不同,我还没看,不过对我来说效果一样 貌似需要png图片才行)<br>
	 * (可选,如果填写必须有logo才行,默认为0)6.xaxis:水印logo的横坐标(只有logo参数为一个正确路径才行) 比如0<br>
	 * (可选,如果填写必须有logo才行,默认为0)6.yaxis:水印logo的纵坐标(只有logo参数为一个正确路径才行) 比如0<br>
	 * (可选)vb:视频比特率,传入一个数值,单位在程序里面拼接了k
	 * (可选)ab:音频比特率,传入一个数值,单位在程序里面拼接了k
	 */
	public String videoTransfer(HashMap<String, String> dto){
		List<String> cmd = new ArrayList<String>();
		cmd.add(dto.get("ffmpegPath"));
		//-y: 覆盖已经存在的输出文件
		cmd.add("-y");
		//-i: 指定输入文件
		cmd.add("-i");
		String str ="\"rtsp://"+dto.get("username")+":"+dto.get("password")+"@"+dto.get("ip")+":"
				+dto.get("port")+"/"+dto.get("codec")+"/"+dto.get("channel")+"/"+dto.get("subtype")+"/av_stream\"";
		cmd.add(str);
		//图片水印
		if (null!=dto.get("logo")) {
			String logo=dto.get("logo");
			cmd.add("-vf");
			String xaxis=dto.get("xaxis");
			String yaxis=dto.get("yaxis");
			xaxis=xaxis!=null&&!xaxis.equals("")?xaxis:"0";
			yaxis=yaxis!=null&&!yaxis.equals("")?yaxis:"0";
			String logoString="movie="+logo+"[logo],[in][logo]overlay=x="+xaxis+":y="+yaxis+"[out]";
			cmd.add(logoString);
			cmd.add("\"-\"vcodec copy -vcodec libx264 -f flv \"rtmp://127.0.0.1:1935/live/\"");
		}
		log.info("list中的内容 -> {}"+cmd);
		String converted_time= VideoUtils.exec(cmd);
		//获取转换时间(如果是一段视频的话,则会有时间,如果是从摄像头拿到的视频的话,则会一直编辑下去,没有返回时间)
		return VideoUtils.returnSecond(converted_time);
	}
}



4. vue中编写

首先需要下载插件

npm install --save vue-video-player
npm install --save videojs-flash


下载video-js.swf


这是个文件,直接放在static中即可

在这里插入图片描述

index.vue界面

<template>
  <video-player  class="video-player vjs-custom-skin"
                 ref="videoPlayer"
                 :playsinline="true"
                 :options="playerOptions"
  ></video-player>
</template>
<script>
export default {
  name: 'waterMark',
  data() {
    return {
      // 视频播放
      playerOptions: {
        playbackRates: [0.7, 1.0, 1.5, 2.0], //播放速度
        autoplay: true, //如果true,浏览器准备好时开始回放。
        muted: false, // 默认情况下将会消除任何音频。
        loop: false, // 导致视频一结束就重新开始。
        preload: 'auto', // 建议浏览器在<video>加载元素后是否应该开始下载视频数据。auto浏览器选择最佳行为,立即开始加载视频(如果浏览器支持)
        language: 'zh-CN',
        aspectRatio: '16:7', // 将播放器置于流畅模式,并在计算播放器的动态大小时使用该值。值应该代表一个比例 - 用冒号分隔的两个数字(例如"16:9"或"4:3")
        techOrder: ['flash', 'html5'],      // 兼容顺序
        flash: {
          hls: {withCredentials: false},
          swf: 'static/video-js.swf' // 引入静态文件swf
        },
        html5: {hls: {withCredentials: false}},
        sources: [{ // 流配置,数组形式,会根据兼容顺序自动切换
          type: 'rtmp/flv',
          src: 'rtmp://127.0.0.1:1935/live/'
        }],
        poster: "", //你的封面地址
        // width: document.documentElement.clientWidth,
        notSupportedMessage: '此视频暂无法播放,请稍后再试', // 允许覆盖Video.js无法播放媒体源时显示的默认信息。
        controlBar: {
          timeDivider: true,
          durationDisplay: true,
          remainingTimeDisplay: false,
          fullscreenToggle: true  //全屏按钮
        }
      }
    }
  }
}
</script>

注意src中的rtmp地址后要加/

在这里插入图片描述



5.测试

一切准备就绪开始测试,拿到摄像头的rtsp地址用vlc软件测试,正常可以打开,

在这里插入图片描述

效果视频

在这里插入图片描述

到此结束。



版权声明:本文为qq_43441670原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。