1. 程式人生 > >視訊播放surfaceView camera,獲取預覽影象顯示setPreviewCallback()

視訊播放surfaceView camera,獲取預覽影象顯示setPreviewCallback()

同事找我做一個視訊預覽複製,實現螢幕一分為二,同時顯示相同畫面。這裡寫個demo版


本篇的重點是

camera.setPreviewCallback()

在視訊預覽過程中,每一幀的影象資料均會通過這個callback返回,在這裡面我們可以處理返回的位元組陣列,轉換為bitmap,然後顯示出來

其中要注意的是返回的陣列影象格式為YUV,並不支援直接BitmapFactory.decodeByteArray()方法,需要通過格式轉換。但這也導致執行計算所化時間較長,第二個介面卡頓。目前沒有很好的解決方案,想到的兩種解決方式,通過開啟執行緒池,處理資料;或者通過native提高運算效率。

  camera.setPreviewCallback(new Camera.PreviewCallback() {
                @Override
                public void onPreviewFrame(byte[] data, Camera camera) {
                    //處理data
                    previewSize = camera.getParameters().getPreviewSize();//獲取尺寸,格式轉換的時候要用到
                    YuvImage yuvimage = new YuvImage(
                            data,
                            ImageFormat.NV21,
                            previewSize.width,
                            previewSize.height,
                            null);
                    baos = new ByteArrayOutputStream();
                    yuvimage.compressToJpeg(new Rect(0, 0, previewSize.width, previewSize.height), 100, baos);// 80--JPG圖片的質量[0-100],100最高
                    rawImage = baos.toByteArray();
                    //將rawImage轉換成bitmap
                    BitmapFactory.Options options = new BitmapFactory.Options();
                    options.inPreferredConfig = Bitmap.Config.RGB_565;
                    bitmap = BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length, options);
//                    idx++;
//                    text.setText(idx+"");
//                    bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
                    icon.setImageBitmap(bitmap);
                }
            });

介面佈局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.administrator.carmavideoapplication.MainActivity">


    <Button
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:visibility="gone"
        android:layout_height="48dp"
        android:text="button1"/>
    <Button
        android:id="@+id/button2"
        android:visibility="gone"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:text="button2"/>
    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="48dp" />
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <com.example.administrator.carmavideoapplication.SurfaceViewJereh
            android:id="@+id/surfaceview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1">
        </com.example.administrator.carmavideoapplication.SurfaceViewJereh>
        <ImageView
            android:id="@+id/icon"
            android:layout_weight="1"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </LinearLayout>

</LinearLayout>

activity初始化

首先是對ui初始化,然後關聯camera和surfaceview即可

package com.example.administrator.carmavideoapplication;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.media.MediaRecorder;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    SurfaceViewJereh surfaceView;
    ImageView icon;
    private Button button, button2;
    private TextView text;
    private MediaRecorder mediaRecorder;
    private String path;
    private Camera camera;
    private SurfaceHolder.Callback callback;
    private int idx;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
        initSurfaceView();
    }
    private void initSurfaceView() {

        surfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        surfaceView.setKeepScreenOn(true);
        callback = new SurfaceHolder.Callback() {

            //在控制元件建立的時候,進行相應的初始化工作
            public void surfaceCreated(SurfaceHolder holder) {
                //開啟相機,同時進行各種控制元件的初始化mediaRecord等
                camera = Camera.open();
                mediaRecorder = new MediaRecorder();
            }

            //當控制元件發生變化的時候呼叫,進行surfaceView和camera進行繫結,可以進行畫面的顯示
            public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                doChange(holder);
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder holder) {
                camera.setPreviewCallback(null);
                camera.stopPreview();

            }

        };
        //為SurfaceView設定回撥函式
        surfaceView.getHolder().addCallback(callback);
    }
    ByteArrayOutputStream baos;
    byte[] rawImage;
    Bitmap bitmap;
    Camera.Size previewSize;
    BitmapFactory.Options newOpts = new BitmapFactory.Options();
    //當我們的程式開始執行,即使我們沒有開始錄製視訊,我們的surFaceView中也要顯示當前攝像頭顯示的內容
    private void doChange(SurfaceHolder holder) {
        try {
            camera.setPreviewDisplay(holder);
            //設定surfaceView旋轉的角度,系統預設的錄製是橫向的畫面,把這句話註釋掉執行你就會發現這行程式碼的作用
//            camera.setDisplayOrientation(getDegree());

            if (camera != null )
            {
                try
                {
                    Camera.Parameters parameters = camera.getParameters(); //獲取攝像頭引數
//                    parameters.setZoom();  //鏡頭縮放
                    // 設定預覽照片的大小
//                    parameters.setPreviewSize(200, 200);
                    // 設定預覽照片時每秒顯示多少幀的最小值和最大值
//                    parameters.setPreviewFpsRange(4, 10);
                    // 設定圖片格式
//                    parameters.setPictureFormat(ImageFormat.JPEG);
                    // 設定JPG照片的質量
//                    parameters.set("jpeg-quality", 85);
                    // 設定照片的大小
//                    parameters.setPictureSize(200, 200);
                    camera.setParameters(parameters);
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            }
            newOpts.inJustDecodeBounds = true;

            camera.setPreviewCallback(new Camera.PreviewCallback() {
                @Override
                public void onPreviewFrame(byte[] data, Camera camera) {
                    //處理data
//                    previewSize = camera.getParameters().getPreviewSize();//獲取尺寸,格式轉換的時候要用到
//                    YuvImage yuvimage = new YuvImage(
//                            data,
//                            ImageFormat.NV21,
//                            previewSize.width,
//                            previewSize.height,
//                            null);
//                    baos = new ByteArrayOutputStream();
//                    yuvimage.compressToJpeg(new Rect(0, 0, previewSize.width, previewSize.height), 100, baos);// 80--JPG圖片的質量[0-100],100最高
//                    rawImage = baos.toByteArray();
//                    //將rawImage轉換成bitmap
//                    BitmapFactory.Options options = new BitmapFactory.Options();
//                    options.inPreferredConfig = Bitmap.Config.RGB_565;
//                    bitmap = BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length, options);
////                    idx++;
////                    text.setText(idx+"");
////                    bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
//                    icon.setImageBitmap(bitmap);
                }
            });
            camera.startPreview();//開始預覽

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public int getDegree() {
        //獲取當前螢幕旋轉的角度
        int rotating = this.getWindowManager().getDefaultDisplay().getRotation();
        int degree = 0;
        //根據手機旋轉的角度,來設定surfaceView的顯示的角度
        switch (rotating) {
            case Surface.ROTATION_0:
                degree = 90;
                break;
            case Surface.ROTATION_90:
                degree = 0;
                break;
            case Surface.ROTATION_180:
                degree = 270;
                break;
            case Surface.ROTATION_270:
                degree = 180;
                break;
        }
        return degree;
    }

    private void init() {
        button = (Button) findViewById(R.id.button1);
        button2 = (Button) findViewById(R.id.button2);
        surfaceView = (SurfaceViewJereh)findViewById(R.id.surfaceview);
        icon = (ImageView)findViewById(R.id.icon);
        text = (TextView) findViewById(R.id.text);
        button.setOnClickListener(this);
        button2.setOnClickListener(this);

        path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/hello.3gp";
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button1:
                startRecord();
                break;
            case R.id.button2:
                stopRecord();
                break;
        }
    }

    private void stopRecord() {
        //當結束錄製之後,就將當前的資源都釋放
        mediaRecorder.release();
        camera.release();
        mediaRecorder = null;
        //然後再重新初始化所有的必須的控制元件和物件
        camera = Camera.open();
        mediaRecorder = new MediaRecorder();
        doChange(surfaceView.getHolder());
    }

    private void startRecord() {
        //先釋放被佔用的camera,在將其設定為mediaRecorder所用的camera
        camera.unlock();
        mediaRecorder.setCamera(camera);

        mediaRecorder.setPreviewDisplay(surfaceView.getHolder().getSurface());
        //設定音訊的來源  麥克風
        mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        //設定視訊的來源
        mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
        //設定視訊的輸出格式  3gp
        mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        //設定視訊中的聲音和視訊的編碼格式
        mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
        //設定儲存的路徑
        mediaRecorder.setOutputFile(path);
        //開始錄製
        try {
            mediaRecorder.prepare();
            mediaRecorder.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(camera!=null)
        camera.release();//釋放相機資源
    }
}