最近想要作一個客戶端往服務器推送實時畫面的功能,首先能夠考慮到兩種思路,一種是在客戶端進行視頻流的推送,主要利用RTSP等流媒體協議進行傳輸,而另一種是經過攝像頭獲取當前畫面,將每一幀做爲對象單獨傳輸。
java
項目想要實現的功能最終目的是對實時畫面的每一幀進行處理,能夠考慮客戶端推流到服務器,再在服務器進行幀解析的操做,但因爲目前不少的流媒體推送框架在推流端或者服務端都或多或少存在限制,不多有徹底開源的項目,再加上傳送畫面的同時須要附帶部分的數據,仍然須要另外創建鏈接進行傳輸,因此暫時擱置這一方案。選擇第二種思路,獲取每一幀的畫面,單獨傳輸。android
要想獲取實時畫面,咱們必須經過對安卓設備上的攝像頭進行調用。
api
從API21開始,安卓引入了android.hardware.camera2這個包,來替代原有的camera類,原有的camera類已經再也不建議使用了。camera2中最重要的變化是,攝像頭的調用再也不是簡單地進行實例化,而是用一種相似服務申請的方式來進行調用。經過CameraManager來管理攝像服務,須要經過創建CameraCaptureSession來創建一個調用攝像設備CameraDevices的會話,來實現對攝像頭的調用。而CaptureRequest.Builder類用於創建實際的調用請求,具體的參數設置也能夠經過這個類來實現(而不是對camera設備進行直接設置),這樣作的目的是把對攝像頭的控制與攝像頭自己分離開來,用戶能夠經過不一樣的session根據不一樣的配置來使用攝像頭。服務器
咱們能夠結合具體的代碼來分析新api中攝像頭調用的過程。
session
首先咱們想要對攝像設備進行操做,須要得到CameraManager的實例
框架
CameraManager cameraManager; cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
咱們能夠調用openCamera函數打開攝像頭設備ide
cameraManager.openCamera(cameraId, cameraCallback, mainHandler);
這裏須要傳入三個參數,cameraId是設備編號,cameraCallback控制攝像服務的回調,最後一個參數指定HandlerThread對象 函數
cameraId = Integer.toString(CameraCharacteristics.LENS_FACING_FRONT); private CameraDevice.StateCallback cameraCallback = new CameraDevice.StateCallback() { @Override public void onOpened(CameraDevice camera) { Log.d("CameraCallback", "Camera Opened"); cameraDevice = camera; takePreview(); } @Override public void onDisconnected(CameraDevice cameraDevice) { Log.d("CameraCallback", "Camera Disconnected"); closeCameraDevice(); } @Override public void onError(CameraDevice cameraDevice, int i) { Log.d("CameraCallback", "Camera Error"); Toast.makeText(PusherSurface.this, "攝像頭開啓失敗", Toast.LENGTH_SHORT).show(); } };
回調函數用於指定鏈接攝像頭設備時不一樣狀態的操做。在這裏,咱們在攝像頭成功鏈接的時候調用 takePreview()函數開啓攝像頭畫面的預覽。
佈局
private void takePreview() { try { final CaptureRequest.Builder previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); previewRequestBuilder.addTarget(surfaceHolder.getSurface()); previewRequestBuilder.addTarget(previewReader.getSurface()); cameraDevice.createCaptureSession(Arrays.asList(surfaceHolder.getSurface(), previewReader.getSurface(), p_w_picpathReader.getSurface()), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(CameraCaptureSession cameraCaptureSession) { if (cameraDevice == null) return; mCameraCaptureSession = cameraCaptureSession; try { previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); previewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF); CaptureRequest previewRequest = previewRequestBuilder.build(); mCameraCaptureSession.setRepeatingRequest(previewRequest, null, childHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { Toast.makeText(PusherSurface.this, "配置失敗", Toast.LENGTH_SHORT).show(); } }, childHandler); } catch (CameraAccessException e) { e.printStackTrace(); } }
要從攝像設備中獲取圖像,咱們首先須要創建一個camera capture session。函數ui
createCaptureSession(List, CameraCaptureSession.StateCallback, Handler)的第一個參數傳入了咱們想要繪製的視圖列表,第二個參數傳入的是創建攝像會話的狀態回調函數,第三個參數傳入相應的handler處理器。而後,咱們須要利用capturerequest來定義攝像頭捕獲圖像時候的具體參數,好比是否開啓攝像頭,是否自動對焦等。最後經過CamraCaptureSession.setRepeatingRequest來開啓請求。這樣咱們就能夠從capturesession傳入的list中的surface列表得到連續的圖像。留意到
previewRequestBuilder.addTarget(surfaceHolder.getSurface()); previewRequestBuilder.addTarget(previewReader.getSurface());
這裏除了傳入xml界面佈局中的surfaceHolder的surface外,還傳入了一個previewReader的surface。
previewReader是一個自定義的ImageReader對象。
previewReader = ImageReader.newInstance(1080, 1920, ImageFormat.YUV_420_888, 2); previewReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader p_w_picpathReader) { Image p_w_picpath = null; try { p_w_picpath = p_w_picpathReader.acquireLatestImage(); Log.d("PreviewListener", "GetPreviewImage"); if (p_w_picpath == null) { return; } byte[] bytes = ImageUtil.p_w_picpathToByteArray(p_w_picpath); if (pushFlag == false) uploadImg(bytes); } finally { if (p_w_picpath != null) { p_w_picpath.close(); } } } }, mainHandler);
ImageReader是一個可讓咱們對繪製到surface的圖像進行直接操做的類。在這裏咱們從攝像設備中傳入了連續的預覽圖片,也就是咱們在屏幕上看到的畫面,它們的格式都是未經壓縮的YUV_420_888類型的(一樣的若是要操做拍攝後的圖片,就要設置成jpeg格式)。咱們調用p_w_picpathReader.acquireLatestImage或者acquireNextImage來獲取圖像隊列中的圖片。並進行操做。在這裏我利用一個函數將圖像壓縮後轉化成byte[]格式,並調用uploadImg函數上傳至服務器。這樣,整個攝像頭的調用到預覽圖像的處理也就完成了。想要實現拍照功能也是大同小異,在這裏我就不一一貼出了。
歡迎更多安卓開發者一同交流。