您当前的位置: 首页 > 

暂无认证

  • 0浏览

    0关注

    92582博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

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

发布时间:2018-02-24 15:22:07 ,浏览量:0

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

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

最终效果视频:

下面是实现的一些步骤

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、源码点击【阅读原文】进行下载。

关注
打赏
1653961664
查看更多评论
立即登录/注册

微信扫码登录

0.6419s