EasyPusher進行Android UVC外接攝像頭直播推送實現方法


最近EasyPusher針對UVC攝像頭做了適配.我們結合了UVCCamera與EasyPusher,支持將UVC攝像頭的視頻推送到RTSP服務器上.在此特別感謝UVCCamera這個牛逼的項目!

來看看是怎么操作UVC攝像頭的吧.我們實現了一個專門檢測UVC攝像頭的服務:UVCCameraService類,主要代碼如下:

監聽

mUSBMonitor = new USBMonitor(this, new USBMonitor.OnDeviceConnectListener() {
            @Override
            public void onAttach(final UsbDevice device) {
                Log.v(TAG, "onAttach:" + device);
                mUSBMonitor.requestPermission(device);
            }

            @Override
            public void onConnect(final UsbDevice device, final USBMonitor.UsbControlBlock ctrlBlock, final boolean createNew) {
                releaseCamera();
                if (BuildConfig.DEBUG) Log.v(TAG, "onConnect:");
                try {
                    final UVCCamera camera = new MyUVCCamera();
                    camera.open(ctrlBlock);
                    camera.setStatusCallback(new IStatusCallback() {
                    // ... uvc 攝像頭鏈接成功

                    Toast.makeText(UVCCameraService.this, "UVCCamera connected!", Toast.LENGTH_SHORT).show();
                    if (device != null)
                        cameras.append(device.getDeviceId(), camera);
                }catch (Exception ex){
                    ex.printStackTrace();
                }
            }

            @Override
            public void onDisconnect(final UsbDevice device, final USBMonitor.UsbControlBlock ctrlBlock) {
              // ... uvc 攝像頭斷開鏈接
                if (device != null) {
                    UVCCamera camera = cameras.get(device.getDeviceId());
                    if (mUVCCamera == camera) {
                        mUVCCamera = null;
                        Toast.makeText(UVCCameraService.this, "UVCCamera disconnected!", Toast.LENGTH_SHORT).show();
                        liveData.postValue(null);
                    }
                    cameras.remove(device.getDeviceId());
                }else {
                    Toast.makeText(UVCCameraService.this, "UVCCamera disconnected!", Toast.LENGTH_SHORT).show();
                    mUVCCamera = null;
                    liveData.postValue(null);
                }
            }

            @Override
            public void onCancel(UsbDevice usbDevice) {
                releaseCamera();
            }

            @Override
            public void onDettach(final UsbDevice device) {
                Log.v(TAG, "onDettach:");
                releaseCamera();
// AppContext.getInstance().bus.post(new UVCCameraDisconnect());
            }
        });

這個類主要實現UVC攝像頭的監聽\鏈接\銷毀\反監聽.當有UVC攝像頭鏈接成功后,會創建一個mUVCCamera對象.

然后在MediaStream里, 我們改造了switchCamera,當參數傳2時,表示要切換到UVCCamera(0,1分別表示切換到后置\前置攝像頭).

創建

在創建攝像頭時,如果是要創建uvc攝像頭,那直接從服務里面獲取之前創建的mUVCCamera實例:

 if (mCameraId == 2) {
   UVCCamera value = UVCCameraService.liveData.getValue();
   if (value != null) {
     // uvc camera.
     uvcCamera = value;
     value.setPreviewSize(width, height,1, 30, UVCCamera.PIXEL_FORMAT_YUV420SP,1.0f);
     return;
     // value.startPreview();
   }else{
     Log.i(TAG, "NO UVCCamera");
     uvcError = new Exception("no uvccamera connected!");
     return;
   }
   // mCameraId = 0;
 }

預覽

在預覽時,如果uvc攝像頭已經創建了,那執行uvc攝像頭的預覽操作:

UVCCamera value = uvcCamera;
if (value != null) {
  SurfaceTexture holder = mSurfaceHolderRef.get();
  if (holder != null) {
    value.setPreviewTexture(holder);
  }
  try {
    value.setFrameCallback(uvcFrameCallback, UVCCamera.PIXEL_FORMAT_YUV420SP/*UVCCamera.PIXEL_FORMAT_NV21*/);
    value.startPreview();
    cameraPreviewResolution.postValue(new int[]{width, height});
  }catch (Throwable e){
    uvcError = e;
  }
}

這里我們選的colorFormat為PIXEL_FORMAT_YUV420SP 相當於標准攝像頭的NV21格式.

關閉預覽

同理,關閉時,調用的是uvc攝像頭的關閉.

        UVCCamera value = uvcCamera;
        if (value != null) {
            value.stopPreview();
        }

銷毀

因為我們這里並沒有實質性的創建,所以銷毀時也僅將實例置為null就可以了.

UVCCamera value = uvcCamera;
if (value != null) {
  // value.destroy();
  uvcCamera = null;
}

有了這些操作,我們看看上層怎么調用,

首先需要在Manifest里面增加若干代碼,具體詳見UVCCamera工程說明.如下:

<activity android:name=".UVCActivity" android:launchMode="singleInstance">

            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
            </intent-filter>

            <meta-data  android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter" />

        </activity>

然后,的代碼在UVCActivity里,這個類可以在library分支的myapplication工程里找到.即這里.

啟動或者停止UVC攝像頭推送:

    public void onPush(View view) {
      // 異步獲取到MediaStream對象.
        getMediaStream().subscribe(new Consumer<MediaStream>() {
            @Override
            public void accept(final MediaStream mediaStream) throws Exception {
              // 判斷當前的推送狀態.
                MediaStream.PushingState state = mediaStream.getPushingState();
                if (state != null && state.state > 0) { // 當前正在推送,那終止推送和預覽
                    mediaStream.stopStream();
                    mediaStream.closeCameraPreview();
                }else{
                    // switch 0表示后置,1表示前置,2表示UVC攝像頭
                    // 異步開啟UVC攝像頭
                    RxHelper.single(mediaStream.switchCamera(2), null).subscribe(new Consumer<Object>() {
                        @Override
                        public void accept(Object o) throws Exception {
                          // 開啟成功,進行推送.
                            // ...
                            mediaStream.startStream("cloud.easydarwin.org", "554", id);
                        }
                    }, new Consumer<Throwable>() {
                        @Override
                        public void accept(final Throwable t) throws Exception {
                          // ooop...開啟失敗,提示下...
                            t.printStackTrace();
                            runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    Toast.makeText(UVCActivity.this, "UVC攝像頭啟動失敗.." + t.getMessage(), Toast.LENGTH_SHORT).show();
                                }
                            });
                        }
                    });
                }
            }
        });
    }

這樣,整個推送就完成了.如果一切順利,應當能在VLC播放出來UVC攝像頭的視頻了~~

我們再看看如何錄像.也非常簡單…

    public void onRecord(View view) {       // 開始或結束錄像.
        final TextView txt = (TextView) view;
        getMediaStream().subscribe(new Consumer<MediaStream>() {
            @Override
            public void accept(MediaStream mediaStream) throws Exception {
                if (mediaStream.isRecording()){ // 如果正在錄像,那停止.
                    mediaStream.stopRecord();
                    txt.setText("錄像");
                }else { // 沒在錄像,開始錄像...
                    // 表示最大錄像時長為30秒,30秒后如果沒有停止,會生成一個新文件.依次類推...
                    // 文件格式為test_uvc_0.mp4,test_uvc_1.mp4,test_uvc_2.mp4,test_uvc_3.mp4
                    String path = getExternalFilesDir(Environment.DIRECTORY_MOVIES) + "/test_uvc.mp4";
                    mediaStream.startRecord(path, 30000);

                    final TextView pushingStateText = findViewById(R.id.pushing_state);
                    pushingStateText.append("\n錄像地址:" + path);
                    txt.setText("停止");
                }
            }
        });
    }

UVC攝像頭還支持后台推送,即不預覽的情況下進行推送,同時再切換到前台繼續預覽.只需要調用一個接口即可實現,如下:

@Override
public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
  ms.setSurfaceTexture(surfaceTexture); // 設置預覽的surfaceTexture
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
  ms.setSurfaceTexture(null);           // 設置預覽窗口為null,表示關閉預覽功能
  return true;
}

如果要徹底退出uvc攝像頭的預覽\推送,那只需要同時退出服務即可.

public void onQuit(View view) {     // 退出
  finish();

  // 終止服務...
  Intent intent = new Intent(this, MediaStream.class);
  stopService(intent);
}

## 獲取更多信息 ##

郵件:support@easydarwin.org

EasyDarwin開源流媒體服務器:www.EasyDarwin.org

EasyDSS商用流媒體解決方案:www.EasyDSS.com

EasyNVR無插件直播方案:www.EasyNVR.com

Copyright © EasyDarwin Team 2012-2017

EasyDarwin


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM