CarRecorder源码分析(一)随拍随录

来源:互联网 发布:db2查看数据库列表 编辑:程序博客网 时间:2024/06/06 01:57

主体结构:行车记录仪的代码逻辑全部放在PreviewService中,UI显示也在其中,UI的显示只是在Service中,通过WindowManager实现的,MainActivvity没有什么作用。


MainActivity中的业务逻辑:

  • ### 1
//在onCreate中启动服务,在服务里面根据action来显示UI    Intent intent = new Intent(MainActivity.this,                PreviewService.class);        intent.putExtra("setup_ui", true);        startService(intent);
  • ### 2
//收到该广播后,将activity销毁,该广播在PreviewService中发出,隐藏UI返回到主界面@Override        public void onReceive(Context context, Intent intent) {            String msg = intent.getAction();            if (msg.equals(ACTION_FINISH_SELF)) {                if (context instanceof Activity) {                    ((Activity) context).finish();                }            }        }

一:随拍的流程:

入口是在onStartCommand中,启动随拍的代码如下:

else if (ACTION_GET_PICTURE_SHARE.equals(intent.getAction())) {            if (checkState_window() > 0) {                return super.onStartCommand(intent, flags, startId);            }            state_window = 2;            try {                if (mIsSharing)                    return Service.START_STICKY_COMPATIBILITY;;                mIsSharing = true;                //更新UI,当随拍的时候可以看见类似一个对话框弹出来,实际是用了WindowManager的updateViewLayout方法                updateViewPosition(SW_SHOW_SHARE_PICTURE);                recorder.startShotPicture(mShareCallBack);            } catch (Exception e) {                e.printStackTrace();            }        } 
  • 可以看到,更新完UI之后调用了VideoRecorder的startShotPicture(mShareCallBack)方法,并传入EventCallBack接口的引用;EventCallBack接口的定义如下:
interface IShareCallBack {    void onVideoComplete(String strFile);    void onPictureShareComplete(String strFile);}public class EventCallBack {    private IShareCallBack mCallBack;     public void onPictureShareComplete(String strFile) {        mCallBack.onPictureShareComplete(strFile);    }    public void onVideoComplete(String strFile) {        mCallBack.onVideoComplete(strFile);    }    public void setCallBack(IShareCallBack callback) {          this.mCallBack = callback;      }  }
  • 由上可见在EventCallBack类有三个方法,分别是当随拍完成时的回调,当随录完成时的回调,这两个方法中具体业务的实现,是在IShareCallBack接口中定义的,第三个方法是setCallBack,并传入了一个IShareCallBack对象,传入这个对象的同时,要实现 onVideoComplete(String strFile);onPictureShareComplete(String strFile);两个方法,这两个方法即是最终实现完成随拍随录的业务逻辑的。

  • 继续来看VideoRecorder的startShotPicture(mShareCallBack)做了什么事情

public void startShotPicture(EventCallBack shareCallBack) {        File fileDir = new File(CAMERA_SHARING_PATH);        if (!fileDir.exists()) {            fileDir.mkdirs();        }        mIsSharePicture = true;        persistUtils.setPreviewPushEnable(true);        mShareCallBack = shareCallBack;    }
  • 此方法可看出三个有用的信息,①: persistUtils.setPreviewPushEnable(true);即是让onPreviewCallBack的onPreviewFrame开始出数据,出来的数据用于随拍随录等
    ②mShareCallBack = shareCallBack;在VideoRecorder;即将EventCallBack传过来。
    ③mIsSharePicture为true,即当前的状态为随拍的状态,数据从onPreviewFrame出来,再来看onPreviewFrame中做的事情。
//将摄像头的一帧数据传入getSharePictureFilegetSharePictureFile(data);//getSharePictureFile的实现如下:private void getSharePictureFile(byte[] data) {        Camera.Parameters parameters = mCamera.getParameters();    //定义文件名        SimpleDateFormat storeDate = new SimpleDateFormat("yyyyMMddHHmmss");        String times = storeDate.format(new Date(System.currentTimeMillis()));        mShareFile = CAMERA_SHARING_PATH + times + ".jpg";        File imageFile = new File(mShareFile);//将一个byte[]数组变成一张图片,并存储在磁盘中        VideoEncoder.getFrameJpegFileWithTime(mShareFile, data,                parameters.getPreviewFormat(), PREVIEW_WIDTH, PREVIEW_HEIGHT, 1f, isRecording());        mIsSharePicture = false;        persistUtils.setPreviewPushEnable(false);        //随拍完成        mShareCallBack.onPictureShareComplete(mShareFile);    }

接着我们来看VideoEncoder.getFrameJpegFileWithTime方法具体的实现逻辑

    public static void getFrameJpegFileWithTime(String jpgFile, byte[] srcData,            int format, int width, int height, float zoom, boolean isrec) {        File imageFile = new File(jpgFile);        BufferedOutputStream bos = null;        try {            YuvImage yuv = new YuvImage(srcData, format, width, height, null);            ByteArrayOutputStream stream = new ByteArrayOutputStream();            yuv.compressToJpeg(new Rect(0, 0, width, height), 100, stream);            Bitmap bitmap = BitmapFactory.decodeByteArray(stream.toByteArray(),                    0, stream.size());            stream.close();            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");            bitmap = drawStringToBitmap(bitmap,                    sdf.format(System.currentTimeMillis()), 40);            Matrix matrix = new Matrix();            matrix.postScale(zoom, zoom);            Bitmap newBmp = Bitmap.createBitmap(bitmap, 0, 0,                    bitmap.getWidth(), bitmap.getHeight(), matrix, true);                    //将处理后的bitmap转化为jpg图片保存在磁盘中            newBmp.compress(Bitmap.CompressFormat.JPEG, 80, bos);            srcData = null;            bitmap.recycle();            bitmap = null;            newBmp.recycle();            newBmp = null;            bos.flush();            bos.close();        } catch (IOException e) {            e.printStackTrace();        }    }
  • 可以看到该方法将传过来的byte数组压缩成一个jpg文件并保存在磁盘中,并在图片的右上角将当前的时间写了进去,处理完成之后调用随拍完成的方法onPictureShareComplete(file),这时界面就将处理后的照片显示出来,1S之后退回到主界面,相关代码如下:
 @Override            public void onPictureShareComplete(final String strFile) {                x.task().post(new Runnable() {                    @Override                    public void run() {                        if (pool != null && sourceid != -1)                            pool.play(sourceid, 1, 1, 0, 0, 1);                        FileInputStream fs = null;                        try {                            fs = new FileInputStream(strFile);                        } catch (FileNotFoundException e) {                            e.printStackTrace();                        }                        if (fs != null) {                            Bitmap bitmap = BitmapFactory.decodeStream(fs);                            mSharePictureView.setImageBitmap(bitmap);                            mSharePictureView.setVisibility(View.VISIBLE);                        }                        mShareFile = strFile;                        Message msg = new Message();                        msg.what = 2;                        handlerShare.sendMessage(msg);                    }                });            }
  • 当随拍的图片处理完毕之后,显示已经处理的图片显示在界面上,显示完成之后通过handlerShare发送消息,我们来看看handler是怎么处理的
else if (msg.what == 2) {                try {                    Thread.sleep(1000);                    updateViewPosition(SW_HIDE_SHARE_PICTURE);                    if (mIsSharing)                        sendShareBroadcast("com.cxb.sharepicture", mShareFile);                } catch (InterruptedException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }                closeActivity();            }
  • 先是将显示的随拍照片隐藏,然后发送一个携带随拍文件路径的广播出去,最后关闭activity,发送出去之后,在Launcher中接收,并通过Mqtt的前台代理将图片发送至服务器。至此,随拍的流程分析完毕

二:随录的流程分析

随录的入口和随拍一样,应该可以确切的说,行车记录仪的绝大多数的业务逻辑都在ostartCommand中,循环录像除外,入口代码如下:

else if (ACTION_GET_VIDEO_SHARE.equals(intent.getAction())) {            if (checkState_window() > 0) {                return super.onStartCommand(intent, flags, startId);            }            state_window = 3;            try {                if (mIsSharing)                    return Service.START_STICKY_COMPATIBILITY;;                mIsSharing = true;                updateViewPosition(SW_SHOW_SHARE_VIDEO);            } catch (Exception e) {                e.printStackTrace();            }        }

在此方法中调用了updateViewPosition,直接更新UI

 //随录时,显示6S的倒计时                shareTimeLayout.setVisibility(View.VISIBLE);                //显示底部的取消按钮                shareButtonLayout.setVisibility(View.VISIBLE);                   // 在第4秒发送一个检查信号,如果encode在4秒前已结束,则可能由异常引起。如果4前时没结束,但是录制时间没有变化,也可能是encode出现问题                handlerShare.postDelayed(new Runnable() {                    @Override                    public void run() {                        Message msg = new Message();                        msg.what = 201601181;                        handlerShare.sendMessage(msg);                    }                }, 4000);     //该方法启动随录,处理具体的业务逻辑                             if (recorder != null)                    recorder.startEncodeVideo(mShareCallBack);                    //该方法更新UI每300毫秒更新一下时间                new Thread(new ThreadShareTime()).start();


  • ### 问题:VideoEncoder的getEncodeTime方法,时间具体是如何计算的,逻辑是怎样的?

recorder.startEncodeVideo(mShareCallBack)方法具体做了哪些事?

public void startEncodeVideo(EventCallBack shareCallBack) {        //以当前的时间戳为随录文件的名称        File fileDir = new File(CAMERA_SHARING_PATH);        if (!fileDir.exists())            fileDir.mkdirs();        SimpleDateFormat storeDate = new SimpleDateFormat("yyyyMMddHHmmss");        String times = storeDate.format(new Date(System.currentTimeMillis()));        mShareCallBack = shareCallBack;        //构造方法的作用是初始化VideoReccorder中的参数        mVideoEncoder = new VideoEncoder(PREVIEW_WIDTH, PREVIEW_HEIGHT, 10, 250000);        //为视频文件命名        mShareFile = CAMERA_SHARING_PATH + times + ".mp4";        //准备编码        mVideoEncoder.startEncode(mShareFile);        //允许onPreviewFrame出数据        persistUtils.setPreviewPushEnable(true);        if (state == STATE_IDLE)            new Thread(new ThreadShare()).start();    }

我们来看看这个方法具体执行了哪些逻辑:①首先是为随录的文件命名;②然后初始化编码VideoRecorder中的参数,即是VideoEnconder的实例化;③startEncode,这个方法是准备开始编码,将参数都设置好,算是初始化的第二步,④ persistUtils.setPreviewPushEnable(true),此方法允许onPreviewFrame开始出数据,有了数据才可以进行编码;⑤new Thread(new ThreadShare()).start(),将摄像头出来的数据丢给VideoEncoder进行编码,编完6S的视频之后交由Mqtt发送至服务器,随录的主要流程至此完结,之后会对循环录像进行一个剖析

阅读代码涉及的知识点:

WindowManager的使用,SurfaceView的生命周期,AnimationDrawable的使用,Bitmap的处理等

0 0
原创粉丝点击