Android Camera 開發(fā)你該知道的秘密秘?-新手入門必備

作者:@魷魚先生
本文為原創(chuàng),轉(zhuǎn)載請(qǐng)注明:http://www.itdecent.cn/u/5a6744f8f69e

安卓相機(jī)相關(guān)開發(fā)的文章已經(jīng)數(shù)不勝數(shù),今天提筆想給開發(fā)者說說安卓相機(jī)開發(fā)的一些小秘密,當(dāng)然也會(huì)進(jìn)行一些基礎(chǔ)知識(shí)的普及??。如果還沒有相機(jī)開發(fā)相關(guān)支持的小伙伴,建議打開谷歌的文檔 CameraCamera Guide 進(jìn)行相關(guān)的學(xué)習(xí),然后再結(jié)合本文的內(nèi)容,一定可以達(dá)到事倍功半的效果。

這里提前附上參考代碼的克隆地址: ps: ??貼心的博主特地使用碼云方便國(guó)內(nèi)的小伙伴們高速訪問代碼。

碼云:Camera-Android

本文主要是介紹安卓Camera1相關(guān)的介紹,Camera2的就等待我的更新吧:)??

<span id = "opencamera">1. 啟動(dòng)相機(jī)</span>

從API文檔和很多網(wǎng)絡(luò)的資料一般的啟動(dòng)套路代碼:

/** A safe way to get an instance of the Camera object. */
public static Camera getCameraInstance(){
    Camera c = null;
    try {
        c = Camera.open(); // attempt to get a Camera instance
    }
    catch (Exception e){
        // Camera is not available (in use or does not exist)
    }
    return c; // returns null if camera is unavailable
}

但是調(diào)用該函數(shù)獲取相機(jī)實(shí)例的時(shí)候,一般調(diào)用都是直接在 MainThread 中直接調(diào)用該函數(shù):

 @Override
 protected void onCreate(Bundle savedInstanceState) {
     // ... 
     Camera camera = getCameraInstance();
 }

讓我們來看看安卓源碼的是實(shí)現(xiàn),Camera.java

/**
 * Creates a new Camera object to access the first back-facing camera on the
 * device. If the device does not have a back-facing camera, this returns
 * null.
 * @see #open(int)
 */
public static Camera open() {
    int numberOfCameras = getNumberOfCameras();
    CameraInfo cameraInfo = new CameraInfo();
    for (int i = 0; i < numberOfCameras; i++) {
        getCameraInfo(i, cameraInfo);
        if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
            return new Camera(i);
        }
    }
    return null;
}
    
Camera(int cameraId) {
    mShutterCallback = null;
    mRawImageCallback = null;
    mJpegCallback = null;
    mPreviewCallback = null;
    mPostviewCallback = null;
    mUsingPreviewAllocation = false;
    mZoomListener = null;
    Looper looper;
    if ((looper = Looper.myLooper()) != null) {
        mEventHandler = new EventHandler(this, looper);
    } else if ((looper = Looper.getMainLooper()) != null) {
        mEventHandler = new EventHandler(this, looper);
    } else {
        mEventHandler = null;
    }
    String packageName = ActivityThread.currentPackageName();
    native_setup(new WeakReference<Camera>(this), cameraId, packageName);
}

注意mEventHandler如果當(dāng)前的啟動(dòng)線程不帶 Looper 則默認(rèn)的 mEventHandler 使用UI線程的默認(rèn) Looper。從源碼我們可以看到 EventHandler 負(fù)責(zé)處理底層的消息的回調(diào)。正常情況下,我們期望所有回調(diào)都在UI線程這樣可以方便我們直接操作相關(guān)的頁面邏輯。但是針對(duì)一些特殊場(chǎng)景我們可以做一些特殊的操作,目前可以把這個(gè)知識(shí)點(diǎn)記下,以便后續(xù)他用。

2. 設(shè)置相機(jī)??預(yù)覽模式

2.1 使用 SurfaceHolder 預(yù)覽

根據(jù)官方的 Guide 文章我們直接使用 SurfaceView 作為預(yù)覽的展示對(duì)象。

@Override
protected void onCreate(Bundle savedInstanceState) {
    // ...
    SurfaceView surfaceView = findViewById(R.id.camera_surface_view);
    surfaceView.getHolder().addCallback(this);
}

@Override
public void surfaceCreated(SurfaceHolder holder) {
    // TODO: Connect Camera.
    if (null != mCamera) {
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
            mHolder = holder;
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

重新運(yùn)行下程序,我相信你已經(jīng)可以看到預(yù)覽的畫面,當(dāng)然它可能有些方向的問題。但是我們至少看到了相機(jī)的畫面。

2.2 使用 SurfaceTexture 預(yù)覽

該方式目前主要是針對(duì)需要利用 OpenGL ES 作為相機(jī) GPU 預(yù)覽的模式。此時(shí)使用的目標(biāo) View 也換成了 GLSurfaceView。在使用的時(shí)候??注意3個(gè)小細(xì)節(jié):

  1. 關(guān)于 GLSurfaceView 的基礎(chǔ)設(shè)置
GLSurfaceView surfaceView = findViewById(R.id.gl_surfaceview);
surfaceView.setEGLContextClientVersion(2); // 開啟 OpenGL ES 2.0 支持
surfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); // 啟用被動(dòng)刷新。
surfaceView.setRenderer(this);

關(guān)于被動(dòng)刷新的開啟,第三點(diǎn)會(huì)詳細(xì)介紹它的意思。

  1. 創(chuàng)建紋理對(duì)應(yīng)的 SurfaceTexture
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    // Init Camera
    int[] textureIds = new int[1];
    GLES20.glGenTextures(1, textureIds, 0);
    GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureIds[0]);
    // 超出紋理坐標(biāo)范圍,采用截?cái)嗟竭吘?    GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
    GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
    //過濾(紋理像素映射到坐標(biāo)點(diǎn))  (縮小、放大:GL_LINEAR線性)
    GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
    GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
   
    mSurfaceTexture = new SurfaceTexture(textureIds[0]);
    mCameraTexture = textureIds[0];
   
    GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
    
    try {
        // 創(chuàng)建的 SurfaceTexture 作為預(yù)覽用的 Texture
        mCamera.setPreviewTexture(mSurfaceTexture); 
        mCamera.startPreview();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

這里創(chuàng)建的紋理是一種特殊的來自 OpenGL ES 的擴(kuò)展,GLES11Ext.GL_TEXTURE_EXTERNAL_OES 有且只有在使用此種類型紋理的時(shí)候,開發(fā)者才能通過自己的 GPU 代碼進(jìn)行攝像頭內(nèi)容的實(shí)時(shí)處理。

  1. 數(shù)據(jù)驅(qū)動(dòng)刷新

將原有的 GLSurfaceView 連續(xù)刷新的模式改成,只有當(dāng)數(shù)據(jù)有變化的時(shí)候才刷新。

GLSurfaceView surfaceView = findViewById(R.id.gl_surfaceview);
surfaceView.setEGLContextClientVersion(2);
surfaceView.setRenderer(this);
// 添加以下設(shè)置,改成被動(dòng)的 GL 渲染。
// Change SurfaceView render mode to RENDERMODE_WHEN_DIRTY. 
surfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);

當(dāng)數(shù)據(jù)變化的時(shí)候我們可以通過以下方式進(jìn)行通知

mSurfaceTexture.setOnFrameAvailableListener(surfaceTexture -> {
    // 有數(shù)據(jù)可以進(jìn)行展示,同時(shí)GL線程工作。
     mSurfaceView.requestRender();
});

其余的部分可以不變,這樣的好處是刷新的幀率可以隨著相機(jī)的幀率變化而變化。不是自己一直自動(dòng)刷新造成不必要的GPU功耗。

2.3 使用YUV-NV21 預(yù)覽

本節(jié)將重點(diǎn)介紹如何使用YUV數(shù)據(jù)進(jìn)行相機(jī)的畫面的預(yù)覽的技術(shù)實(shí)現(xiàn)。這個(gè)技術(shù)方案主要的落地場(chǎng)景是 人臉識(shí)別(Face Detection) 或是其他 CV 領(lǐng)域的實(shí)時(shí)算法數(shù)據(jù)加工。

2.3.1 設(shè)置回調(diào) Camera 預(yù)覽 YUV 數(shù)據(jù)回調(diào) Buffer

本步驟利用舊版本的接口 Camera.setPreviewCallbackWithBuffer , 但是使用此函數(shù)需要做一個(gè)必要操作,就是往相機(jī)里面添加回調(diào)數(shù)據(jù)的 Buffer。

// 設(shè)置目標(biāo)的預(yù)覽分辨率,可以直接使用 1280*720 目前的相機(jī)都會(huì)有該分辨率
parameters.setPreviewSize(previewSize.first, previewSize.second);
// 設(shè)置相機(jī) NV21 數(shù)據(jù)回調(diào)使用用戶設(shè)置的 buffer
mCamera.setPreviewCallbackWithBuffer(this);
mCamera.setParameters(parameters);
// 添加4個(gè)用于相機(jī)進(jìn)行處理的 byte[] buffer 對(duì)象。
mCamera.addCallbackBuffer(createPreviewBuffer(previewSize.first, previewSize.second));
mCamera.addCallbackBuffer(createPreviewBuffer(previewSize.first, previewSize.second));
mCamera.addCallbackBuffer(createPreviewBuffer(previewSize.first, previewSize.second));
mCamera.addCallbackBuffer(createPreviewBuffer(previewSize.first, previewSize.second));

這里需要注意??,如果設(shè)置預(yù)覽回調(diào)使用的是 Camera.setPreviewCallback 那么相機(jī)返回的數(shù)據(jù) onPreviewFrame(byte[] data, Camera camera) 中的 data 是由相機(jī)內(nèi)部創(chuàng)建。

@Override
public void onPreviewFrame(byte[] data, Camera camera) {
    // TODO: 預(yù)處理相機(jī)輸入數(shù)據(jù)
    if (!bytesToByteBuffer.containsKey(data)) {
        Log.d(TAG, "Skipping frame. Could not find ByteBuffer associated with the image "
                        + "data from the camera.");
    } else {
        // 因?yàn)槲覀兪褂玫氖?setPreviewCallbackWithBuffer 所以必須把data還回去
        mCamera.addCallbackBuffer(data);
    }
}

如果不進(jìn)行 mCamera.addCallbackBuffer(byte[]), 當(dāng)回調(diào) 4 次之后,就不會(huì)再觸發(fā) onPreviewFrame ??梢园l(fā)現(xiàn)次數(shù)剛好等于相機(jī)初始化時(shí)候添加的 Buffer 個(gè)數(shù)。

2.3.2 啟動(dòng)相機(jī)預(yù)覽

我們目的是使用 onPreviewFrame 返回?cái)?shù)據(jù)進(jìn)行渲染,所以設(shè)置 mCamera.setPreviewTexture 的邏輯代碼需要去除,因?yàn)槲覀儾幌M鄼C(jī)還繼續(xù)把預(yù)覽的數(shù)據(jù)繼續(xù)發(fā)送給之前設(shè)置的 SurfaceTexture 這個(gè)就系統(tǒng)浪費(fèi)資源了。

??支持注釋相機(jī) mCamera.setPreviewTexture(mSurfaceTexture); 的代碼段:

try {
    // mCamera.setPreviewTexture(mSurfaceTexture);
    mCamera.startPreview();
} catch (Exception e) {
    e.printStackTrace();
}

通過測(cè)試發(fā)現(xiàn) onPreviewFrame 居然不工作了,快速看下文檔,里面提到以下信息:

/**
 * Starts capturing and drawing preview frames to the screen
 * Preview will not actually start until a surface is supplied
 * with {@link #setPreviewDisplay(SurfaceHolder)} or
 * {@link #setPreviewTexture(SurfaceTexture)}.
 *
 * <p>If {@link #setPreviewCallback(Camera.PreviewCallback)},
 * {@link #setOneShotPreviewCallback(Camera.PreviewCallback)}, or
 * {@link #setPreviewCallbackWithBuffer(Camera.PreviewCallback)} were
 * called, {@link Camera.PreviewCallback#onPreviewFrame(byte[], Camera)}
 * will be called when preview data becomes available.
 *
 * @throws RuntimeException if starting preview fails; usually this would be
 *    because of a hardware or other low-level error, or because release()
 *    has been called on this Camera instance.
 */
public native final void startPreview();

相機(jī)的有且僅有被設(shè)置的對(duì)應(yīng)的 Surface 資源之后才能正確的啟動(dòng)預(yù)覽。

下面是見證奇跡的時(shí)刻了:

/**
 * The dummy surface texture must be assigned a chosen name. Since we never use an OpenGL context,
 * we can choose any ID we want here. The dummy surface texture is not a crazy hack - it is
 * actually how the camera team recommends using the camera without a preview.
 */
private static final int DUMMY_TEXTURE_NAME = 100;


@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
    // ... codes
    SurfaceTexture dummySurfaceTexture = new SurfaceTexture(DUMMY_TEXTURE_NAME);
    mCamera.setPreviewTexture(dummySurfaceTexture);
    // ... codes
}

這個(gè)操作之后,相機(jī)的 onPreviewFrame 又開始被觸發(fā)了。這個(gè)虛擬的 SurfaceTexture 它可以讓相機(jī)工作起來,并且通過設(shè)置 :

 dummySurfaceTexture.setOnFrameAvailableListener(surfaceTexture -> {
                Log.d(TAG, "dummySurfaceTexture working.");
            });

我們會(huì)發(fā)現(xiàn)系統(tǒng)是能自己判斷出 SurfaceTexture 是否有效,接著 onFrameAvailable 也毫無反應(yīng)。

2.3.3 渲染 YUV 數(shù)據(jù)繪制到 SurfaceView。

目前安卓默認(rèn)的YUV格式是 NV21. 所以需要使用 Shader 進(jìn)行格式的轉(zhuǎn)換。 在 OpenGL 中只能進(jìn)行 RGB 的顏色進(jìn)行繪制。具體腳本算法可以參考: nv21_to_rgba_fs.glsl

#ifdef GL_ES
precision highp float;
#endif
varying vec2 v_texCoord;
uniform sampler2D y_texture;
uniform sampler2D uv_texture;

void main (void) {
    float r, g, b, y, u, v;
    //We had put the Y values of each pixel to the R,G,B components by
    //GL_LUMINANCE, that's why we're pulling it from the R component,
    //we could also use G or B
    y = texture2D(y_texture, v_texCoord).r;
    //We had put the U and V values of each pixel to the A and R,G,B
    //components of the texture respectively using GL_LUMINANCE_ALPHA.
    //Since U,V bytes are interspread in the texture, this is probably
    //the fastest way to use them in the shader
    u = texture2D(uv_texture, v_texCoord).a - 0.5;
    v = texture2D(uv_texture, v_texCoord).r - 0.5;
    //The numbers are just YUV to RGB conversion constants
    r = y + 1.13983*v;
    g = y - 0.39465*u - 0.58060*v;
    b = y + 2.03211*u;
    //We finally set the RGB color of our pixel
    gl_FragColor = vec4(r, g, b, 1.0);
}

主要思路是將N21的數(shù)據(jù)直接分離成2張紋理數(shù)據(jù),fragment shader 里面進(jìn)行顏色格式的計(jì)算,算回 RGBA。

 mYTexture = new Texture();
created = mYTexture.create(mYuvBufferWidth, mYuvBufferHeight, GLES10.GL_LUMINANCE);
if (!created) {
    throw new RuntimeException("Create Y texture fail.");
}

mUVTexture = new Texture();
created = mUVTexture.create(mYuvBufferWidth/2, mYuvBufferHeight/2, GLES10.GL_LUMINANCE_ALPHA);  // uv 因?yàn)槭莾蓚€(gè)通道所以數(shù)據(jù)的格式上選擇 GL_LUMINANCE_ALPHA
if (!created) {
    throw new RuntimeException("Create UV texture fail.");
}

// ...省略部分邏輯代碼

//Copy the Y channel of the image into its buffer, the first (width*height) bytes are the Y channel
yBuffer.put(data.array(), 0, mPreviewSize.first * mPreviewSize.second);
yBuffer.position(0);

//Copy the UV channels of the image into their buffer, the following (width*height/2) bytes are the UV channel; the U and V bytes are interspread
uvBuffer.put(data.array(), mPreviewSize.first * mPreviewSize.second, (mPreviewSize.first * mPreviewSize.second)/2);
uvBuffer.position(0);

mYTexture.load(yBuffer);
mUVTexture.load(uvBuffer);

2.3.4 性能優(yōu)化

相機(jī)的回調(diào) YUV 的速度和 OpenGL ES 渲染相機(jī)預(yù)覽畫面的速度不一定是匹配的,所以我們可以進(jìn)行優(yōu)化。既然是相機(jī)的預(yù)覽我們必須保證當(dāng)前渲染的畫面一定是最新的。我們可以利用 pendingFrameData 一個(gè)公用資源進(jìn)行渲染線程和相機(jī)數(shù)據(jù)回調(diào)線程的同步,保證畫面的時(shí)效性。

synchronized (lock) {
    if (pendingFrameData != null) { // frame data tha has not been processed. Just return back to Camera.
        camera.addCallbackBuffer(pendingFrameData.array());
        pendingFrameData = null;
    }

    pendingFrameData = bytesToByteBuffer.get(data);
    // Notify the processor thread if it is waiting on the next frame (see below).
    // Demo 中是通知 GLThread 中渲染線程如果處理等待狀態(tài)就是直接喚醒。
    lock.notifyAll();
}

// 通知 GLSurfaceView 可以刷新了
mSurfaceView.requestRender();

最后還有一個(gè)優(yōu)化的小技巧秘?,需要結(jié)合在 啟動(dòng)相機(jī) 中提到的關(guān)于 Handler 的事情。如果我們是在安卓的主線程或是不帶有 Looper 的子線程中調(diào)用相機(jī) Camera.open() 最終的結(jié)局都是所有相機(jī)的回調(diào)信息都會(huì)從主線程的 Looper.getMainLooper()Looper 進(jìn)行信息處理。我們可以想象如果目前 UI 的線程正在進(jìn)行重的操作,勢(shì)必將影響到相機(jī)預(yù)覽的幀率問題,所以最好的方法就是開辟子線程進(jìn)行相機(jī)的開啟操作。

final ConditionVariable startDone = new ConditionVariable();

new Thread() {
    @Override
    public void run() {
        Log.v(TAG, "start loopRun");
        // Set up a looper to be used by camera.
        Looper.prepare();
        // Save the looper so that we can terminate this thread
        // after we are done with it.
        mLooper = Looper.myLooper();
        mCamera = Camera.open(cameraId);
        Log.v(TAG, "camera is opened");
        startDone.open();
        Looper.loop(); // Blocks forever until Looper.quit() is called.
        if (LOGV) Log.v(TAG, "initializeMessageLooper: quit.");
        }
}.start();

Log.v(TAG, "start waiting for looper");

if (!startDone.block(WAIT_FOR_COMMAND_TO_COMPLETE)) {
    Log.v(TAG, "initializeMessageLooper: start timeout");
    fail("initializeMessageLooper: start timeout");
}

3. 攝像頭角度問題

攝像頭的數(shù)據(jù)預(yù)覽是跟攝像頭傳感器的安裝位置有關(guān)系的,相關(guān)的內(nèi)容可以單獨(dú)再寫一篇文章進(jìn)行討論,我這邊就直接上代碼。

private void setRotation(Camera camera, Camera.Parameters parameters, int cameraId) {
    WindowManager windowManager = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
    int degrees = 0;
    int rotation = windowManager.getDefaultDisplay().getRotation();
    switch (rotation) {
        case Surface.ROTATION_0:
            degrees = 0;
            break;
        case Surface.ROTATION_90:
            degrees = 90;
            break;
        case Surface.ROTATION_180:
            degrees = 180;
            break;
        case Surface.ROTATION_270:
            degrees = 270;
            break;
        default:
            Log.e(TAG, "Bad rotation value: " + rotation);
    }

    Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
    Camera.getCameraInfo(cameraId, cameraInfo);

    int angle;
    int displayAngle;
    if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
        angle = (cameraInfo.orientation + degrees) % 360;
        displayAngle = (360 - angle) % 360; // compensate for it being mirrored
    } else { // back-facing
        angle = (cameraInfo.orientation - degrees + 360) % 360;
        displayAngle = angle;
    }

    // This corresponds to the rotation constants.
    mRotation = angle;

    camera.setDisplayOrientation(displayAngle);
    parameters.setRotation(angle);
}

但是測(cè)試中你會(huì)發(fā)現(xiàn)在使用YUV數(shù)據(jù)預(yù)覽模式的時(shí)候是不起作用的,這個(gè)是因?yàn)樵O(shè)置的角度參數(shù)不會(huì)直接影響 PreviewCallback#onPreviewFrame 返回的結(jié)果。我們通過查看源碼的注釋后更加確信這點(diǎn)。

 /**
  * Set the clockwise rotation of preview display in degrees. This affects
  * the preview frames and the picture displayed after snapshot. This method
  * is useful for portrait mode applications. Note that preview display of
  * front-facing cameras is flipped horizontally before the rotation, that
  * is, the image is reflected along the central vertical axis of the camera
  * sensor. So the users can see themselves as looking into a mirror.
  *
  * <p>This does not affect the order of byte array passed in {@link
  * PreviewCallback#onPreviewFrame}, JPEG pictures, or recorded videos. This
  * method is not allowed to be called during preview.
  *
  * <p>If you want to make the camera image show in the same orientation as
  * the display, you can use the following code.
  * <pre>
  * public static void setCameraDisplayOrientation(Activity activity,
  *         int cameraId, android.hardware.Camera camera) {
  *     android.hardware.Camera.CameraInfo info =
  *             new android.hardware.Camera.CameraInfo();
  *     android.hardware.Camera.getCameraInfo(cameraId, info);
  *     int rotation = activity.getWindowManager().getDefaultDisplay()
  *             .getRotation();
  *     int degrees = 0;
  *     switch (rotation) {
  *         case Surface.ROTATION_0: degrees = 0; break;
  *         case Surface.ROTATION_90: degrees = 90; break;
  *         case Surface.ROTATION_180: degrees = 180; break;
  *         case Surface.ROTATION_270: degrees = 270; break;
  *     }
  *
  *     int result;
  *     if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
  *         result = (info.orientation + degrees) % 360;
  *         result = (360 - result) % 360;  // compensate the mirror
  *     } else {  // back-facing
  *         result = (info.orientation - degrees + 360) % 360;
  *     }
  *     camera.setDisplayOrientation(result);
  * }
  * </pre>
  *
  * <p>Starting from API level 14, this method can be called when preview is
  * active.
  *
  * <p><b>Note: </b>Before API level 24, the default value for orientation is 0. Starting in
  * API level 24, the default orientation will be such that applications in forced-landscape mode
  * will have correct preview orientation, which may be either a default of 0 or
  * 180. Applications that operate in portrait mode or allow for changing orientation must still
  * call this method after each orientation change to ensure correct preview display in all
  * cases.</p>
  *
  * @param degrees the angle that the picture will be rotated clockwise.
  *                Valid values are 0, 90, 180, and 270.
  * @throws RuntimeException if setting orientation fails; usually this would
  *    be because of a hardware or other low-level error, or because
  *    release() has been called on this Camera instance.
  * @see #setPreviewDisplay(SurfaceHolder)
  */
  public native final void setDisplayOrientation(int degrees);

為了得到正確的方向角度。我們需要進(jìn)行YUV渲染的是改變下坐標(biāo)點(diǎn)。
這里我用了一個(gè)很暴力的手段,直接去調(diào)整下紋理的坐標(biāo)

    private static final float FULL_RECTANGLE_COORDS[] = {
            -1.0f, -1.0f,   // 0 bottom left
            1.0f, -1.0f,   // 1 bottom right
            -1.0f,  1.0f,   // 2 top left
            1.0f,  1.0f,   // 3 top right
    };
    // FIXME: 為了繪制正確的角度,將紋理坐標(biāo)按90度進(jìn)行計(jì)算,中間還包含了一次紋理數(shù)據(jù)的鏡像處理
    private static final float FULL_RECTANGLE_TEX_COORDS[] = {
            1.0f, 1.0f,     // 0 bottom left
            1.0f, 0.0f,     // 1 bottom right
            0.0f, 1.0f,     // 2 top left
            0.0f, 0.0f      // 3 top right
    };

重啟程序 Perfect 搞定。

總結(jié)

關(guān)于安卓相機(jī)的開發(fā),總結(jié)就是在踩坑中度過。建議正在學(xué)習(xí)的同學(xué),最好能結(jié)合我參考資料里面附加的內(nèi)容以及相機(jī)源碼進(jìn)行學(xué)習(xí)。你將會(huì)得到很大的收獲。
同時(shí)我也希望自己寫的經(jīng)驗(yàn)文章可以幫到正在學(xué)習(xí)的你。??????

參考資料

  1. Grafika
  2. Firbase Quick Start Samples
  3. Android Camera CTS
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容