Android 录像添加时间戳水印

最近项目中需要后台录像并添加时间戳,就类似监控视频,直接放效果图了,

demo界面功能如图:跑的时候注意自己到设置加相机权限

           

这个demo主要做到了两点,一、添加时间戳水印。二、暂停,继续录像。github地址。视频录制目录: /sdcard/yuvVideo/  ,请手动在设置加相机和存储权限。首先这个demo是没有录制声音的,如果需要录制声音,参考其它文章。录制用的是android自带的 MediaCodec +camera。这里假设你已经知道camera 和MediaCodec 编解码器的常规用法。

然后你要去了解一下yuv,参考:https://blog.csdn.net/swartz_lubel/article/details/75758806。

YUV定义:分为三个分量,“Y”表示明亮度,也就是灰度值;而“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。

主要了解yuv420sp,数据格式如下:

Android 录像添加时间戳水印_第1张图片

yuv420sp有两种,就是nv12和nv21,区别在uv的顺序反过来。

NV12: YYYYYYYY UVUV     =>YUV420SP
NV21: YYYYYYYY VUVU     =>YUV420SP

首先Camera 相机流数据配置为nv21。

this.mCameraParamters = this.mCamera.getParameters();
this.mCameraParamters.setPreviewFormat(ImageFormat.NV21);

因为android camera相机流只支持nv21和其它几种,但视频编码器 MediaCodec 保存视频流需要nv12,所以保存的时候需要转换一下。

mColorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar;//nv12
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, mColorFormat);

mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE,videoW, videoH);
....
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, mColorFormat);

mMediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
mMediaCodec.configure(mediaFormat, null, null,MediaCodec.CONFIGURE_FLAG_ENCODE);
....

1、添加时间戳

说一下大致原理,添加时间其实就和添加水印是一样的,把一张图片转换为nv12数据覆盖在原帧数据上,只不过这个水印图片是动态生成的。

你可以用 Canvas 每帧去画,后转成nv12数据,然后合并,例如这样:

 String date = mFormat.format(new Date());
 Bitmap bitmap=Bitmap.createBitmap(100,20, Bitmap.Config.ARGB_8888);
 Canvas canvas=new Canvas(bitmap);
 Paint p=new Paint(Paint.ANTI_ALIAS_FLAG);
 canvas.drawText(date,0,0,p);
 byte[] nv12=YuvOsdUtils.bitmapToNV12(bitmap,100,20);
 //合成...

但是这样每次都画和生成,速度太慢了,别一种思路是事先生成所需要的字符nv12数据, 因为时间所需字符集不大,只有0~9,和"-" 下划线,“ :”冒号和空格,常规如:"YYYY-MM-dd HH:mm:ss" 形式。所以可以先把字符yuv数据加载到内存,生成方式和上面类似,因为时间水印只需要黑白两色,所以只需要YUV中的Y数据,不需要UV,如果你需要彩色的话,需要保留uv

byte[] nv12 = YuvOsdUtils.bitmapToGrayNV(srcBit, w, h);//生成黑白字

而数字图片,可以用任意方式得到,这里也是用canvas+bitmap 画出来的,你也可以ps画好,把图片转成yuv数据,然后把黑色16转为0,其它转为1,(这一步是为了后面方便处理)最终把这些写死在代码中,像这样

char NUM_0[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0,
                0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
                0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
                0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0,
                0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,
                0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,
                0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
                0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
                0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
                0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
                0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
                0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
                0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
                0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
                0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
                0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
                0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0,
                0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,
                0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 
                0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 
                0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0,
                0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0};

还是容易看出这是数字0的,用的时候把数字替换到帧数据指定位置就可以了。详写YuvOsdUtils.c,

(如果你不想直接写在代码里,也可以把数据保存到本地,初始化的时候从本地取出来)调用如下:

初始化时间戳水印,主要是把数字yuv数据都加载到内存中。

YuvOsdUtils.initOsd(off_x, off_y, pattern.length(), dstWidth, dstHeight, rotation);

off_x,off_y为水印是相对于左上角的偏移像素,pattern.length() 是时间格式长度,rotation则为要相机的角度。(0,90,180,270)dstWidth,dstHeight 则是相机分辨率,一般为相机预览和保存视频的分辨率是一样的。

this.mCamera.setDisplayOrientation(90);
this.mCameraParamters.setPreviewSize(width, height);

在每一帧视频数据中添加时间水印

 if (outData == null) {
     outData = new byte[data.length];
 }
 YuvOsdUtils.addOsd(data, outData, “2018-11-21 19:40:01”);

data 为相机帧原数据,outData为转换后和加时间后的数据,保存视频时保存outData,(注意这里不要直接修改原帧中的数据,因为在预览的时候会重用缓存后回收,否则会有花屏现象)

最后不用时,释放内存

YuvOsdUtils.releaseOsd();

2、暂停录制,继续录制

这个原理也很简单,只需要控制queueInputBuffer 的时间轴就可以了

 //mTime是暂停时长,暂停时长可以通过暂停时记录当前时间,继续时的时间减少上次暂停时间则为暂停时长
long currentTimeUs = (System.nanoTime()-mTime) / 1000;//通过控制时间轴,达到暂停录制,继续录制的效果
codec.queueInputBuffer(index, 0, outData.length, currentTimeUs, 0);

mTime是暂停时长,暂停时长可以通过暂停时记录当前时间,继续时的当前时间减少上次暂停时间则为暂停时长。

正常currentTimeUs 时间是随着当前时间而移动的,而如果要暂停后继续的话,只要减去暂停时长,就保证currentTimeUs是暂停时开始的时间,从而达到效果。

好了,本次分享就到这里,其实加时间戳这功能一般app不会使用,但在android物联网方面可能会有监控的需求。而且之前我在找的时候只找到别人封装好的.so包,并没有开放C的源码,后来去了解了YUV数据格式,然后自己一步一步瞎摸索的,而且加水印的方式其实java层也可以做,不过在c层的效率会快点。其实最关键还是要搞懂YUV格式,nv12,nv21的区别,不明白多画画,搞懂后不管是视频旋转,镜像什么的都可以自己定制了。

Demo 放git了。github地址

你可能感兴趣的:(android)