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