最近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);
}
## 獲取更多信息 ##
EasyDarwin開源流媒體服務器:www.EasyDarwin.org
EasyDSS商用流媒體解決方案:www.EasyDSS.com
EasyNVR無插件直播方案:www.EasyNVR.com
Copyright © EasyDarwin Team 2012-2017