音视频之进程间传递 YUV 格式视频流,解决不能同时调用 Camera 问题

执法仪相机视频流传输协议说明

注:java

  • 文章中提到的执法仪设备能够理解为 Android 智能手机
  • 文章中提到的执法软件,能够理解为 Android 手机中第三方软件

看完文章你能够学到git

  • AIDL 技术
  • 进程间 MemoryFile 内存共享技术
  • Camera 视频采集并播放
  • 简要悬浮框口技术

简介说明

因为项目需求,须要在执法仪本地录像的时候,执法软件能正常的使用设备自己的 Camera 资源。因为 Android 系统自身不容许多个软件同时使用 Camera 资源,故开发一套内存共享子码流传输协议,当执法软件须要视频流的时候,向执法仪设备请求往 MemoryFile 中写入 YUV 格式的视频流,执法软件每隔一段时间循环的去指定的内存中 read YUV 视频流。github

完整代码入口服务器

流程图

效果图

  • 本地相机未打开ide

  • 本地相机打开spa

简要流程及示例代码说明

  1. 执法仪服务端收到客户端的开启视频流写入内存的指令3d

    //处理客服端发送过来的须要子码流的数据
        private void onHandleAction(Context context, Intent intent) {
            switch (intent.getAction()) {
                /** * 须要子码流 */
                case Constants.ACTION_CAMERE_CORE_SHOW:
                    //若是正在发送视频流,就不须要执行后面代码了
                    if (!MemoryFileServiceManager.getInsta(context).isSendVideoFrame())
                        MemoryFileServiceManager.getInsta(context).setSendVideoFrame(true, intent);
                    break;
            }
        }
    复制代码
  2. 服务端获取开启视频的条件code

    private void sendVideoFrame(Intent intent) {
            if (intent != null && intent.getExtras() != null) {
                Bundle extras = intent.getExtras();
                //获取须要预览的宽
                Constants.PREVIEWHEIGHT = extras.getInt(Constants.Config.PREVIEW_WIDTH, 1280);
                //获取须要预览的高
                Constants.PREVIEWHEIGHT = extras.getInt(Constants.Config.PREVIEW_HEIGHT, 720);
                //须要绑定对方服务的进程
                Constants.BIND_OTHER_SERVICE_PCK = extras.getString(Constants.Config.BIND_OTHER_SERVICE_PCK, "");
                //须要绑定对方服务的全路径
                Constants.BIND_OTHER_SERVICE_CLASS = extras.getString(Constants.Config.BIND_OTHER_SERVICE_CLASS, "");
                //须要开启 Camera ID 的前置仍是后置 0:后置 1:前置
                Constants.CAMERA_ID = extras.getInt(Constants.Config.CAMERA_ID, 0);
            }
        }
    复制代码
  3. 服务器是否开启相机,若是已经开启则不须要开启component

    //是否摄像头
            if (mCamera == null)
                openCamera();
    复制代码
  4. 服务端初始化一块内存,用于写入 YUV 视频流。cdn

    mMemoryFile = initMemoryFile(Constants.MEMORY_FILE_NAME, Constants.MEMORY_SIZE);
    复制代码
  5. 绑定对方服务,提供文件描述符号

    /** * 绑定对方服务,提供 文件描述符 */
        private void bindOtherService() {
            try {
                if (TextUtils.isEmpty(Constants.BIND_OTHER_SERVICE_PCK) || TextUtils.isEmpty(Constants.BIND_OTHER_SERVICE_CLASS))
                    throw new NullPointerException("PCK or CLSS is null ?");
                Intent intent = new Intent();
                ComponentName cmp = new ComponentName(Constants.BIND_OTHER_SERVICE_PCK, Constants.BIND_OTHER_SERVICE_CLASS);
                intent.setComponent(cmp);
                context.bindService(intent, mCameraServiceConnection, Context.BIND_AUTO_CREATE);
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
            }
        }
    复制代码
  6. 绑定对方服务成功,交于文件描述符 ParcelFileDescriptor

    mCameraService = ICameraCoreService.Stub.asInterface(binder);
                if (mMemoryFile != null) {
                    try {
                        //反射拿到文件描述符号
                        mParcelFileDescriptor = MemoryFileHelper.getParcelFileDescriptor(mMemoryFile);
                        if (mParcelFileDescriptor != null) {
                            mCameraService.addExportMemoryFile(mParcelFileDescriptor, Constants.PREVIEWWIDTH, Constants.PREVIEWHEIGHT, Constants.MEMORY_SIZE);
    
                        }
    复制代码
  7. 发送数据,当标志位为 byte[0] == 0 表明服务端可将 YUV 写入内存, == 1 ,表明客服端能够读取可用的 YUV 数据。

    /** * 读标志位 写入视频流 * * @param memoryFile */
        public void writeBytes(MemoryFile memoryFile) {
            try {
                if (mYUVQueue.size() > 0) {
                    BufferBean mBufferBean = new BufferBean(Constants.BUFFER_SIZE);
                    //读取标志符号
                    memoryFile.readBytes(mBufferBean.isCanRead, 0, 0, 1);
                    //当第一位为 0 的时候,表明客服端已经读取了,能够正常将视频流写入内存中
                    if (mBufferBean.isCanRead[0] == 0) {
                        //拿到视频流
                        byte[] video = mYUVQueue.poll();
                        if (video != null)
                            //将视频流写入内存中
                            memoryFile.writeBytes(video, 0, 0, video.length);
                        //标志位复位,等待客服端读取视频流
                        mBufferBean.isCanRead[0] = 1;
                        memoryFile.writeBytes(mBufferBean.isCanRead, 0, 0, 1);
                    } else {
                        Log.d(TAG, "readShareBufferMsg isCanRead:" + mBufferBean.isCanRead[0] + ";length:"
                                + mBufferBean.mBuffer.length);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
                sendBroadcast(Constants.ACTION_FEEDBACK, e.getMessage());
            }
        }
    复制代码
  8. 开启成功或者失败等其余错误消息反馈给客服端

    //返回给客服端 
    public void sendBroadcast(String action,String content) {
            Intent intent = new Intent();
            intent.setAction(action);
            ComponentName componentName = new ComponentName("com.t01.sharevideostream",
                    "com.t01.sharevideostream.revices.FeedBackReceiver");
            intent.setComponent(componentName);
            Bundle extras = new Bundle();
            extras.putString(Constants.ACTION_FEEDBACK_CONTENT, content);
            intent.putExtras(extras);
            context.sendBroadcast(intent);
        }
    复制代码
相关文章
相关标签/搜索