1. 程式人生 > >EasyPusher進行Android UVC外接攝像頭直播推送實現方法

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);
}

## 獲取更多資訊 ##

Copyright © EasyDarwin Team 2012-2017

EasyDarwin