【Android】Android Camera原始幀格式轉換 —— 獲取Camera圖像(一)


    概述:
  做過Android Camera圖像采集和處理的朋友們應該都知道,Android手機相機采集的原始幀(RawFrame)默認是橫屏格式的,而官方API有沒有提供一個設置Camera采集圖像的方向的方法,導致我們拿到原始幀后還需要再次對其進行轉換為對應需求的數據,例如YUV的格式,圖像的方向等(旋轉多少度合適),下面我就粗略的介紹一下大致的流程,理解淺薄,大神請勿噴。
      注意:當前還都是基於API<21的內容,如果壓根不用android.hardware.Camera的話可能有區別,還沒研究過Camera2是什么原理,這里先不介紹了。
onPreviewFrame的原理
     1. 原理及一般使用流程:
         大致流程:
         Camera — 取數據(onPreviewFrame(Byte[] rawFrameData, Camera camera)) —> 原始幀處理(Rotate/Scale:使用Libyuv/FFmpeg等工具庫)—> 編碼器編碼得到相應的h24數據 —> 發送給流媒體服務器  
 
原始幀的采集
     1. 方向問題:
         這里說明一下采集得到的幀的方向問題:Android默認是左橫屏狀態取景,也就意味着攝像頭采集的數據默認就是橫屏的,如圖1,而且沒有對應的方法來設置傳感器的采集方向,所以想要實現豎直拍攝取景就得經過Rotate(旋轉90°或者270°)得到圖2那樣的效果。
圖 1
 
 
圖 2
 
原始幀的分析:類型、YUV格式:
      之前寫了一篇博客,關於YUV的區別的,感興趣的朋友可以看看: http://www.cnblogs.com/raomengyang/p/4924787.html
 
處理原始幀:
  廢話不多說了,上代碼吧。
  MainActivity.java: 將Camera采集的圖像顯示到屏幕,並且圖像經過90度的旋轉成豎屏。
package com.dreamy.cameraframedemo;

import android.app.Activity;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.os.Bundle;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;

import com.dreamy.cameraframedemo.util.ImageUtils;

import java.io.IOException;

public class MainActivity extends Activity implements SurfaceHolder.Callback,
        Camera.PreviewCallback, View.OnClickListener {
    // raw frame resolution: 1280x720, image format is: YV12
    // you need get all resolution that supported on your devices;
    // my phone is HUAWEI honor 6Plus, most devices can use 1280x720
    private static final int SRC_FRAME_WIDTH = 1280;
    private static final int SRC_FRAME_HEIGHT = 720;
    private static final int IMAGE_FORMAT = ImageFormat.YV12;

    private Camera mCamera;
    private Camera.Parameters mParams;
    private SurfaceView mSurfaceView;
    private SurfaceHolder mSurfaceHolder;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        setListener();
    }

    private void initView() {
        mSurfaceView = (SurfaceView) findViewById(R.id.sv_recording);
        mSurfaceHolder = mSurfaceView.getHolder();
        mSurfaceHolder.setFixedSize(SRC_FRAME_WIDTH, SRC_FRAME_HEIGHT);
        mSurfaceHolder.addCallback(this);
        mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    private void setListener() {
        // set Listener if you want, eg: onClickListener
    }

    @Override
    public void onClick(View v) {
    }

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        ImageUtils.saveImageData(data);
        camera.addCallbackBuffer(data);
    }

    private void openCamera(SurfaceHolder holder) {
        releaseCamera(); // release Camera, if not release camera before call camera, it will be locked
        mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
        mParams = mCamera.getParameters();
        setCameraDisplayOrientation(this, Camera.CameraInfo.CAMERA_FACING_BACK, mCamera);
        mParams.setPreviewSize(SRC_FRAME_WIDTH, SRC_FRAME_HEIGHT);
        mParams.setPreviewFormat(IMAGE_FORMAT); // setting preview format:YV12
        mParams.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
        mCamera.setParameters(mParams); // setting camera parameters
        try {
            mCamera.setPreviewDisplay(holder);
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        mCamera.setPreviewCallback(this);
        mCamera.startPreview();
    }

    private synchronized void releaseCamera() {
        if (mCamera != null) {
            try {
                mCamera.setPreviewCallback(null);
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                mCamera.stopPreview();
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                mCamera.release();
            } catch (Exception e) {
                e.printStackTrace();
            }
            mCamera = null;
        }
    }

    /**
     * Android API: Display Orientation Setting
     * Just change screen display orientation,
     * the rawFrame data never be changed.
     */
    private void setCameraDisplayOrientation(Activity activity, int cameraId, Camera camera) {
        Camera.CameraInfo info = new Camera.CameraInfo();
        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 displayDegree;
        if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            displayDegree = (info.orientation + degrees) % 360;
            displayDegree = (360 - displayDegree) % 360;  // compensate the mirror
        } else {
            displayDegree = (info.orientation - degrees + 360) % 360;
        }
        camera.setDisplayOrientation(displayDegree);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        openCamera(holder); // open camera
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }
@Override
public void surfaceDestroyed(SurfaceHolder holder) { } }
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <SurfaceView
        android:id="@+id/sv_recording"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>

 別忘了在AndroidManifest中打開Camera的權限:

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:name="android.hardware.camera" />
保存原始幀為圖片格式:
package com.dreamy.cameraframedemo.util;

import android.os.Environment;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by raomengyang on 4/25/16.
 */
public class ImageUtils {
    public static final int MEDIA_TYPE_IMAGE = 1;
    public static final int MEDIA_TYPE_VIDEO = 2;

    // save image to sdcard path: Pictures/MyTestImage/
    public static void saveImageData(byte[] imageData) {
        File imageFile = getOutputMediaFile(MEDIA_TYPE_IMAGE);
        if (imageFile == null) return;
        try {
            FileOutputStream fos = new FileOutputStream(imageFile);
            fos.write(imageData);
            fos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static File getOutputMediaFile(int type) {
        File imageFileDir =
                new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "MyTestImage");
        if (!imageFileDir.exists()) {
            if (!imageFileDir.mkdirs()) {
                return null;
            }
        }
        // Create a media file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        File imageFile;
        if (type == MEDIA_TYPE_IMAGE) {
            imageFile = new File(imageFileDir.getPath() + File.separator +
                    "IMG_" + timeStamp + ".jpg");
        } else if (type == MEDIA_TYPE_VIDEO) {
            imageFile = new File(imageFileDir.getPath() + File.separator +
                    "VID_" + timeStamp + ".mp4");
        } else return null;
        return imageFile;
    }
}   

通過javah -classpath ./ com.dreamy.jni.LibyuvUtils 生成jni的h頭文件LibyuvUtils.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_dreamy_jni_LibyuvUtils */

#ifndef _Included_com_dreamy_jni_LibyuvUtils
#define _Included_com_dreamy_jni_LibyuvUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_dreamy_jni_LibyuvUtils
 * Method:    initRotateNV21
 * Signature: (II)V
 */
JNIEXPORT void JNICALL Java_com_dreamy_jni_LibyuvUtils_initRotateNV21
  (JNIEnv *, jclass, jint, jint);

/*
 * Class:     com_dreamy_jni_LibyuvUtils
 * Method:    ScaleYV12ToI420
 * Signature: ([B[BIIIII)V
 */
JNIEXPORT void JNICALL Java_com_dreamy_jni_LibyuvUtils_ScaleYV12ToI420
  (JNIEnv *, jclass, jbyteArray, jbyteArray, jint, jint, jint, jint, jint);

/*
 * Class:     com_dreamy_jni_LibyuvUtils
 * Method:    ReleaseRotateNV21
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_dreamy_jni_LibyuvUtils_ReleaseRotateNV21
  (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif 

資源下載:

  YUV格式查看工具:RawViewerhttp://download.csdn.net/detail/zxccxzzxz/9508288

     項目地址:https://github.com/eterrao/BlogExamples.git

    名稱:CameraFrameDemo
 
  接下來會寫Libyuv對原始幀進行Rotate和Scale的使用方法。

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM