音視頻之進程間傳遞 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);
        }
    複製代碼
相關文章
相關標籤/搜索