一种视频录制时,类似监控视频中加入动态时间标记的装置

我们经常在使用视频录制时,动态添加像监控画面一样的精确到秒的时间信息,需要记录当前时间到视频中去,这样的需求很常见。今天使用Java代码来实现,通常来说这种用C/C++更高效。如使用FFmpeg的filter功能可以很快实现。下图是网上找的一张监控视频画面。那么我们在录制视频时实现类似功能:

一种视频录制时,类似监控视频中加入动态时间标记的装置_第1张图片

在使用Java代码实现时,需要使用视频录制(MediaRecorder)类,状态周期图如下:

一种视频录制时,类似监控视频中加入动态时间标记的装置_第2张图片

最终效果视频:

class="video_iframe" data-vidtype="2" allowfullscreen="" frameborder="0" data-ratio="0.5666666666666667" data-w="272" data-src="http://v.qq.com/iframe/player.html?vid=z1334nqtq77&width=638&height=478.5&auto=0" style="display: none; width: 638px !important; height: 478.5px !important;" width="638" height="478.5" data-vh="478.5" data-vw="638"/>gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==


下面是实现的一些步骤

1、使用MediaRecord录制一段视频。

 
   

   private void startRecorder() {
       if (mState == State.RECORDE) {
           return;
       }
       if (mState == State.COMPLETE) {
           mCamera.startPreview();//重拍启动预览,这里主要启动对焦程序,如果不启动,则manager不知道已经启动,在stop的时候不会关闭预览
       }
       // 关闭预览并释放资源
       Camera c = mCamera;
       c.unlock();
       mRecorder = new MediaRecorder();
       mRecorder.reset();
       mRecorder.setCamera(c);
       mRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
       mRecorder.setProfile(CamcorderProfile.get(mQuality));
       //设置选择角度,顺时针方向,因为默认是逆向度的,这样图像就是正常显示了,这里设置的是观看保存后的视频的角度
       mRecorder.setOrientationHint(90);
       videoCreateTime = System.currentTimeMillis();
       Log.d(TAG, "video cache path:" + fileCachePath);
       try {
           File file = new File(fileCachePath);
           if (!file.getParentFile().exists()) file.getParentFile().mkdirs();
           if (file.exists()) file.delete();
           file.createNewFile();
           mRecorder.setOutputFile(file.getAbsolutePath());
       } catch (IOException e) {
           e.printStackTrace();
       }
       try {
           mRecorder.prepare();
           mRecorder.start();
       } catch (Exception e) {
           e.printStackTrace();
       }
       mState = State.RECORDE;
       unRecordButton.setVisibility(View.VISIBLE);
       recordButton.setVisibility(View.GONE);
       previewButton.setVisibility(View.GONE);
       unRecordButton.setText("停止");
   }

2、使用MediaExtractor分离出音视频数据,使用MediaMuxer进行合成。

 
   

  private void init(String srcPath, String dstPath) {
       MediaMetadataRetriever mmr = new MediaMetadataRetriever();
       mmr.setDataSource(srcPath);
       try {
           mSrcWidth = Integer.parseInt(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
           mSrcHeight = Integer.parseInt(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
       } catch (IllegalArgumentException e) {
           e.printStackTrace();
       } catch (IllegalStateException e) {
           e.printStackTrace();
       }
       try {
           mExtractor = new MediaExtractor();
           mExtractor.setDataSource(srcPath);
           String mime = null;
           for (int i = 0; i < mExtractor.getTrackCount(); i++) {
               //获取码流的详细格式/配置信息
               MediaFormat format = mExtractor.getTrackFormat(i);
               mime = format.getString(MediaFormat.KEY_MIME);
               if (mime.startsWith("video/")) {
                   mVideoTrackIndex = i;
                   mMediaFormat = format;
               } else if (mime.startsWith("audio/")) {
                   continue;
               } else {
                   continue;
               }
           }
           mExtractor.selectTrack(mVideoTrackIndex); //选择读取视频数据
           //创建合成器
           mSrcWidth = mMediaFormat.getInteger(MediaFormat.KEY_WIDTH);
           mSrcHeight = mMediaFormat.getInteger(MediaFormat.KEY_HEIGHT);
           mVideoMaxInputSize = mMediaFormat.getInteger(MediaFormat.KEY_MAX_INPUT_SIZE);
           mVideoDuration = mMediaFormat.getLong(MediaFormat.KEY_DURATION);
           mVideoRotation = 90;//低版本不支持获取旋转,手动写入了
           if (mVideoRotation == 90) {
               mDstWidth = mSrcHeight;
               mDstHeight = mSrcWidth;
           } else if (mVideoRotation == 0) {
               mDstWidth = mSrcWidth;
               mDstHeight = mSrcHeight;
           }
           mMax = (int) (mVideoDuration / 1000);
           Log.d(TAG, "videoWidth=" + mSrcWidth + ",videoHeight=" + mSrcHeight + ",mVideoMaxInputSize=" + mVideoMaxInputSize + ",mVideoDuration=" + mVideoDuration + ",mVideoRotation=" + mVideoRotation);
           //写入文件的合成器
           mMediaMuxer = new MediaMuxer(dstPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
           MediaCodec.BufferInfo videoInfo = new MediaCodec.BufferInfo();
           videoInfo.presentationTimeUs = 0;
           initMediaDecode(mime);
       } catch (IOException e) {
           e.printStackTrace();
       }
   }

3、借用MediaCodec解码出每一帧,进行处理。实际就是Codec出的outputBuffer数据。再用Canvas把时间画上去。时间获取是视频录制时创建文件的时间,也就是我们在录制那个时间。这样就能匹配上。

 
   

   private void decode(MediaCodec.BufferInfo videoInfo, int inputIndex) {
       mMediaDecode.queueInputBuffer(inputIndex, 0, videoInfo.size, videoInfo.presentationTimeUs, videoInfo.flags);//通知MediaDecode解码刚刚传入的数据
       //获取解码得到的byte[]数据 参数BufferInfo上面已介绍 10000同样为等待时间 同上-1代表一直等待,0代表不等待。此处单位为微秒
       //此处建议不要填-1 有些时候并没有数据输出,那么他就会一直卡在这 等待
       MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
       int outputIndex = mMediaDecode.dequeueOutputBuffer(bufferInfo, 50000);
       switch (outputIndex) {
           case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
               Log.d(TAG, "INFO_OUTPUT_BUFFERS_CHANGED");
               mDecodeOutputBuffers = mMediaDecode.getOutputBuffers();
               break;
           case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
               MediaFormat format = mMediaDecode.getOutputFormat();
               Log.d(TAG, "New mMediaFormat " + format);
               if (format != null && format.containsKey(MediaFormat.KEY_COLOR_FORMAT)) {
                   mVideoDecodeColor = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
                   Log.d(TAG, "decode extract get mVideoDecodeColor =" + mVideoDecodeColor);//解码得到视频颜色格式
               }
               initMediaEncode();//根据颜色格式初始化编码器
               break;
           case MediaCodec.INFO_TRY_AGAIN_LATER:
               Log.d(TAG, "dequeueOutputBuffer timed out!");
               break;
           default:
               ByteBuffer outputBuffer;
               byte[] frame;
               while (outputIndex >= 0) {//每次解码完成的数据不一定能一次吐出 所以用while循环,保证解码器吐出所有数据
                   outputBuffer = mDecodeOutputBuffers[outputIndex];//拿到用于存放PCM数据的Buffer
                   frame = new byte[bufferInfo.size];//BufferInfo内定义了此数据块的大小
                   outputBuffer.get(frame);//将Buffer内的数据取出到字节数组中
                   outputBuffer.clear();//数据取出后一定记得清空此Buffer MediaCodec是循环使用这些Buffer的,不清空下次会得到同样的数据
                   handleFrameData(frame, videoInfo);//自己定义的方法,供编码器所在的线程获取数据,下面会贴出代码
                   mMediaDecode.releaseOutputBuffer(outputIndex, false);//此操作一定要做,不然MediaCodec用完所有的Buffer后 将不能向外输出数据
                   outputIndex = mMediaDecode.dequeueOutputBuffer(mDecodeBufferInfo, 50000);//再次获取数据,如果没有数据输出则outputIndex=-1 循环结束
               }
               break;
       }
   }

4、关键在于取到的数据帧,是YUV格式的,根据拍摄时选取的不同还不一样,Camera获取数据是NV21格式,也就是YUV420sp,拿到NV21格式的帧以后,转成RGB渲染,然后又转回NV21交给编码器。

 
   

   private void handleFrameData(byte[] data, MediaCodec.BufferInfo info) {
       //转换Yuv数据成RGB格式的bitmap
       Bitmap image = changeYUV2Bitmap(data);
       //旋转图像
       Bitmap bitmap = rotaingImage(mVideoRotation, image);
       image.recycle();
       //渲染文字及背景 0-1ms
       Canvas canvas = new Canvas(bitmap);
       canvas.drawText(mVideoTimeFormat.format(mVideo.videoCreateTime + info.presentationTimeUs / 1000), 20, 60, mTextPaint);
       //通知进度 0-5ms
       mProgress = (int) (info.presentationTimeUs / 1000);
       mProgressHandler.obtainMessage(PROGRESS, mProgress, mMax, mVideo).sendToTarget();
       synchronized (MediaCodec.class) {//加锁
           mTimeDataContainer.add(new Frame(info, bitmap));
       }
   }


5、源码点击【阅读原文】进行下载。

一种视频录制时,类似监控视频中加入动态时间标记的装置_第3张图片

你可能感兴趣的:(一种视频录制时,类似监控视频中加入动态时间标记的装置)