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

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,数据格式如下:

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;//nv12mediaFormat.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地址

更多相关文章

  1. Android用户界面 UI组件--TextView及其子类(四) Chronometer计时
  2. Android系统的启动时间
  3. 定时任务Alarm的深入理解
  4. [置顶] android 自己创建一个注释模板
  5. 个人总结--Android(安卓)应用开发中国大学生挑战赛
  6. C# 实现本地推送示例
  7. 使用AudioRecord实现暂停录音功能
  8. Android(安卓)解决部分机型水印变黑问题
  9. DNS负载均衡,导致Android和iOS访问接口,导致导致时间相差很大

随机推荐

  1. 疫情下普通人的生活写照
  2. 原理+代码|深入浅出Python随机森林预测实
  3. 动态图表交互揭秘:制作选择器的奥秘
  4. 探索另类圆环图的做法
  5. 图表操作的几个技巧
  6. 一图看懂《对比Excel,轻松学习Python数据
  7. 计算1-1/2+1/3-1/4.......-1/100的值
  8. 利用Excel学习Python:准备篇
  9. 动态图表揭秘:“动”的关键——取数
  10. 利用Excel学习Python:变量