2020-07-10

音視頻系列文章
Android 音視頻開發(fā)(一) -- 使用AudioRecord 錄制PCM(錄音)亚情;AudioTrack播放音頻
工程: 音視頻Demo

Camera1 在 API 21 的時候已經被棄用了,雖然現(xiàn)在google 都推薦 使用 Camerax 來實現(xiàn)相機的一些功能椅您,但這不妨礙我們學習 Camera1 和 Camera2,對此有基礎了解生闲,為后續(xù)學習 Camera2 和 Camerax 做鋪墊

在這篇文章中媳溺,你講學習到:

  1. 實現(xiàn)相機的開啟與預覽
  2. 相機預覽方向的矯正
  3. 實現(xiàn)拍照工鞥,并矯正拍照圖片

效果如下:

GIF.gif

一. 相機的開啟與預覽

首先碍讯,先申請權限:

 <uses-permission android:name="android.permission.CAMERA" />
 <!-- 支持相機才能運行 -->
 <uses-feature
     android:name="android.hardware.camera"
     android:required="true" />

1.1. 獲取相機個數(shù)

一般手機中悬蔽,都有前置攝像頭和后置攝像頭,我們可以根據 Camera 的 getNumberOfCameras() 方法捉兴,來獲取這些信息蝎困。比如:

 //獲取相機個數(shù)
 int numberOfCameras = Camera.getNumberOfCameras();
 for (int i = 0; i < numberOfCameras; i++) {
     Camera.CameraInfo info = new Camera.CameraInfo();
     //獲取相機信息
     Camera.getCameraInfo(i, info);
     //前置攝像頭
     if (Camera.CameraInfo.CAMERA_FACING_FRONT == info.facing) {
         mFrontCameraId = i;
         mFrontCameraInfo = info;
     } else if (Camera.CameraInfo.CAMERA_FACING_BACK == info.facing) {
         mBackCameraId = i;
         mBackCameraInfo = info;
     }
 }

可以看到,通過 Camera.getCameraInfo(i, info) 就可以拿到當前的 CameraInfo 的信息倍啥,里面有個參數(shù)我們需要注意一下禾乘,就是 facing,它表示當前攝像機面對的方向虽缕,理解為前置和后置盖袭,然后我們把這些信息也保存起來。

1.2 打開攝像頭

接著彼宠,我們可以使用 Camera.open(cameraid) 去打開攝像頭

 //根據 cameraId 打開不同攝像頭
 mCamera = Camera.open(cameraId);

打開我們的攝像頭之后鳄虱,可以對它進行一些配置,比如設置預覽方向等凭峡,這個話題我們等到下面出現(xiàn)了再說拙已。

1.3 配置攝像頭屬性

在開啟相機預覽之前,我們需要對相機進行一些參數(shù)配置摧冀,比如聚焦倍踪,預覽尺寸等;這里我使用的是 SurfaceView索昂,所以等SurfaceView 創(chuàng)建好之后建车,可以對它進行一些參數(shù)的設置:

  @Override
  public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
      startPreview(width, height);
  }

# startPreview
private void startPreview(int width, int height) {
    //配置camera參數(shù)
    initPreviewParams(width, height);
    //設置預覽 SurfaceHolder
    Camera camera = mCamera;
    if (camera != null) {
        try {
            camera.setPreviewDisplay(mSurfaceView.getHolder());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //開始顯示
    camera.startPreview();
}

在Camra 中,我們可以通過 camera.getParameters() 拿到相機默認的參數(shù)椒惨,如果要配置自己的參數(shù)缤至,可以使用 camera.setParameters(parameters) 去設置,不過這個比較比較好使康谆,所以相機的配置開啟這些领斥,可以使用 HandlerThread 去開啟,這里就不增加多余代碼了沃暗。
initPreviewParams 的完整代碼如下:

    private void initPreviewParams(int shortSize, int longSize) {
        Camera camera = mCamera;
        if (camera != null) {
            Camera.Parameters parameters = camera.getParameters();
            //獲取手機支持的尺寸
            List<Camera.Size> sizes = parameters.getSupportedPreviewSizes();
            Camera.Size bestSize = getBestSize(shortSize, longSize, sizes);
            //設置預覽大小
            parameters.setPreviewSize(bestSize.width, bestSize.height);
            //設置圖片大小月洛,拍照
            parameters.setPictureSize(bestSize.width, bestSize.height);
            //設置格式,所有的相機都支持 NV21格式
            parameters.setPreviewFormat(ImageFormat.NV21);
            //設置聚焦
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);

            camera.setParameters(parameters);
        }
    }

1.3.1 相機預覽大小

首先,應該根據自己UI的大小去設置相機預覽的大小孽锥,如果你的控件為 200x200嚼黔,但相機的數(shù)據為 1920x1080 细层,這樣填充過去,畫面肯定是會被拉伸的唬涧。
所以今艺,可以通過

List<Camera.Size> sizes = parameters.getSupportedPreviewSizes()

拿到手機相機支持的所有尺寸;所以爵卒,我們需要找到比例相同虚缎,或者近似的大小,跟UI配合钓株,這樣畫面才不會拉伸实牡,注意相機的 width > height,所以獲取一個最佳的預覽尺寸可以這樣寫:

/**
  * 獲取預覽最后尺寸
  */
 private Camera.Size getBestSize(int shortSize, int longSize, List<Camera.Size> sizes) {
     Camera.Size bestSize = null;
     float uiRatio = (float) longSize / shortSize;
     float minRatio = uiRatio;
     for (Camera.Size previewSize : sizes) {
         float cameraRatio = (float) previewSize.width / previewSize.height;

         //如果找不到比例相同的轴合,找一個最近的,防止預覽變形
         float offset = Math.abs(cameraRatio - minRatio);
         if (offset < minRatio) {
             minRatio = offset;
             bestSize = previewSize;
         }
         //比例相同
         if (uiRatio == cameraRatio) {
             bestSize = previewSize;
             break;
         }

     }
     return bestSize;
 }

當 UI 的比例跟相機支持的比例相同创坞,直接返回,否則則找近似的受葛。

接著調用

效果如下:

在這里插入圖片描述

咦题涨,發(fā)現(xiàn)預覽的方向是反的;這個時候就需要使用 setDisplayOrientation() 去設置預覽方向了

二. 調整預覽方向

首先总滩,在調整預覽方向錢纲堵,我們需要先了解一些知識。

  • 屏幕坐標: Android 坐標系中闰渔,在 (0,0) 坐標那席函,向右為 x 軸,向下為 y 軸冈涧。
  • 自然方向: 設置的自然方向茂附,比如手機默認就是豎直是自然方向,平板的話督弓,橫向就是自然方向
  • 圖片傳感器方向: 手機的圖片數(shù)據都來自攝像頭硬件傳感器营曼,這個傳感器有個默認的方向,一般是手機是橫向的愚隧,這就跟手機的自然方向成 90° 關系了蒂阱。

所以,我們要做的就是奸攻,就是把傳感器拿到的圖片蒜危,進行一個角度的變化虱痕,使圖像能跟自然方向一致:
圖片來源

在這里插入圖片描述

所以睹耐,我們的方向調整可以這樣寫:

    private void adjustCameraOrientation(Camera.CameraInfo info) {
        //判斷當前的橫豎屏
        int rotation = getWindowManager().getDefaultDisplay().getRotation();

        int degress = 0;
        //獲取手機的方向
        switch (rotation) {
            case Surface.ROTATION_0:
                degress = 0;
                break;
            case Surface.ROTATION_90:
                degress = 90;
                break;
            case Surface.ROTATION_180:
                degress = 180;
                break;
            case Surface.ROTATION_270:
                degress = 270;
                break;
        }
        int result = 0;
        //后置攝像頭
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
            result = (info.orientation - degress + 360) % 360;
        } else if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            //先鏡像
            result = (info.orientation + degress) % 360;
            result = (360 - result) % 360;
        }
        mCamera.setDisplayOrientation(result);

    }

最后再看一下:


在這里插入圖片描述

三. 切換攝像頭

現(xiàn)在用到的都是后置攝像頭,切換也比較簡單部翘,首先先釋放相機支援硝训,然后再從配置參數(shù),預覽再來一遍即可:

  //關閉攝像頭
  closeCamera();
  
  mCameraID = mCameraID == mFrontCameraId ? mBackCameraId : mFrontCameraId;
  //打開相機
  openCamera(mCameraID);
  //開啟預覽
  startPreview(mSurfaceView.getWidth(), mSurfaceView.getHeight());

#closeCamera
 private void closeCamera() {
     //停止預覽
     mCamera.stopPreview();
     mCamera.release();
     mCamera = null;
 }

四. 拍照及調整圖片方向

Camera 的拍照也比較簡單,使用 takePicture(ShutterCallback shutter, PictureCallback raw窖梁, PictureCallback jpeg) 方法即可赘风,它的三個參數(shù)如下:

  • ShutterCallback :拍照瞬間調用,如果空回調纵刘,則由聲音邀窃,傳 null ,則沒效果
  • PictureCallback :圖片的原始數(shù)據假哎,即沒處理過的
  • PictureCallback : 圖片的 JPEG 數(shù)據

拿到 byte 數(shù)據后瞬捕,轉換成bitmap即可,如下:

Camera camera = mCamera;
camera.takePicture(new Camera.ShutterCallback() {
    @Override
    public void onShutter() {
        
    }
}, null, new Camera.PictureCallback() {
    @Override
    public void onPictureTaken(byte[] data, Camera camera) {
        new SavePicAsyncTask(data).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
    }
});

這里的圖片保存舵抹,用一個 AsyncTask 來保存:

    /**
     * 保存圖片
     */
    class SavePicAsyncTask extends AsyncTask<Void, Void, File> {

        byte[] data;
        File file;

        public SavePicAsyncTask(byte[] data) {
            this.data = data;
            File dir = new File(Constants.PATH);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            String name = "test.jpg";
            file = new File(dir, name);
        }

        @Override
        protected File doInBackground(Void... voids) {
            Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
            if (bitmap == null) {
                return null;
            }
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(file);
                //保存之前先調整方向
                Camera.CameraInfo info = mCameraID == mFrontCameraId ? mFrontCameraInfo : mBackCameraInfo;
                if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                    bitmap = BitmapUtils.rotate(bitmap, 90);
                } else {
                    bitmap = BitmapUtils.rotate(bitmap, 270);
                }
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);


            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } finally {
                CloseUtils.close(fos);
            }
            return file;
        }

        @Override
        protected void onPostExecute(File file) {
            super.onPostExecute(file);
            if (file != null) {
                Toast.makeText(Camera1Activity.this, "圖片保存成功", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(Camera1Activity.this, "圖片保存失敗", Toast.LENGTH_SHORT).show();
            }
        }
    }
#BitmapUtils#rotate

    public static Bitmap rotate(Bitmap bitmap,float degress){
        Matrix matrix = new Matrix();
        matrix.postRotate(degress);
        return Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
    }

當拿到 byte[] 數(shù)據時肪虎,使用 BitmapFactory.decodeByteArray 解析 bitmap ,但此時的圖片也是不對的惧蛹,需要對它進行一個旋轉扇救,如上所示,這樣香嗓,我們的拍照就也完成了迅腔。

參考:
https://developer.android.google.cn/reference/android/hardware/Camera?hl=en#takePicture(android.hardware.Camera.ShutterCallback,%20android.hardware.Camera.PictureCallback,%20android.hardware.Camera.PictureCallback,%20android.hardware.Camera.PictureCallback)

http://www.reibang.com/p/f8d0d1467584

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市靠娱,隨后出現(xiàn)的幾起案子钾挟,更是在濱河造成了極大的恐慌,老刑警劉巖饱岸,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掺出,死亡現(xiàn)場離奇詭異,居然都是意外死亡苫费,警方通過查閱死者的電腦和手機汤锨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來百框,“玉大人闲礼,你說我怎么就攤上這事☆砦” “怎么了柬泽?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嫁蛇。 經常有香客問我锨并,道長,這世上最難降的妖魔是什么睬棚? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任第煮,我火速辦了婚禮解幼,結果婚禮上,老公的妹妹穿的比我還像新娘包警。我一直安慰自己撵摆,他們只是感情好,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布害晦。 她就那樣靜靜地躺著特铝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪壹瘟。 梳的紋絲不亂的頭發(fā)上苟呐,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音俐筋,去河邊找鬼牵素。 笑死,一個胖子當著我的面吹牛澄者,可吹牛的內容都是我干的笆呆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼粱挡,長吁一口氣:“原來是場噩夢啊……” “哼赠幕!你這毒婦竟也來了?” 一聲冷哼從身側響起询筏,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤榕堰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后嫌套,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逆屡,經...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年踱讨,在試婚紗的時候發(fā)現(xiàn)自己被綠了魏蔗。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡痹筛,死狀恐怖莺治,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情帚稠,我是刑警寧澤谣旁,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站滋早,受9級特大地震影響榄审,放射性物質發(fā)生泄漏。R本人自食惡果不足惜馆衔,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一瘟判、第九天 我趴在偏房一處隱蔽的房頂上張望怨绣。 院中可真熱鬧角溃,春花似錦拷获、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至未蝌,卻和暖如春驮吱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背萧吠。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工左冬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纸型。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓拇砰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親狰腌。 傳聞我的和親對象是個殘疾皇子除破,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359