SurfaceView实现简单的相机

本文介绍了如何利用SurfaceView来实现一个简单的相机应用。SurfaceView作为视频流的绘制载体,独立于UI线程,避免阻塞主线程。文中提供了一个相机Demo,包括相机预览、拍照和照片展示功能。在初始化SurfaceView后,通过SurfaceHolder设置回调监听界面状态。相机在onResume中开启,在onPause中关闭。拍照时,为防止图片过大导致OOM,进行了压缩处理。在AndroidManifest中添加相机权限是必要的。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SurfaceView实现简单的相机

视频流

SurfaceView继承自View,主要用来展示视频流的绘制,典型的应用场景是相机,视频播放器,游戏界面绘制等。它独立于UI线程进行绘制,所以不会阻塞UI线程。本文将结合一个简单的相机demo介绍SurfaceView的使用。

Github Demo地址

我们实现的相机功能很简单,可以进行相机预览,点击拍照按钮拍照,并展示拍摄的照片,点击确定返回相机预览界面。

布局如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    ...
>

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

  <ImageView        
        android:id="@+id/image_view"        
        android:layout_width="match_parent"        
        android:layout_height="match_parent"        
        android:visibility="gone"/>    

  <Button        
        android:id="@+id/take_photo_btn"        
        android:layout_width="wrap_content"        
        android:layout_height="wrap_content"        
        android:layout_alignParentBottom="true"        
        android:layout_centerHorizontal="true"        
        android:text="拍照"/>    

  <Button        
        android:id="@+id/confirm_btn"        
        android:layout_width="wrap_content"        
        android:layout_height="wrap_content"        
        android:layout_alignParentBottom="true"        
        android:layout_centerHorizontal="true"        
        android:text="确定"/>

</RelativeLayout>

SurfaceView的初始化如下,首先根据SurfaceView获取SurfaceHolder,SurfaceHolder是一个接口,提供了控制SurfaceView界面的函数,比如控制界面的尺寸、格式,编辑界面像素,监控界面的变化等。然后给SurfaceHolder添加CallBack回调函数,三个回调函数对应了界面的三个状态,创建,修改和销毁。在这里我们在界面创界后开始进行相机预览。

public class MainActivity extends AppCompatActivity {
    ...
    private Camera camera;
    private SurfaceHolder surfaceHolder;
    private byte[] pictureDataBytes;

    @Override
    protected void onCreate(Bundle savedInstanceState) {    
        super.onCreate(savedInstanceState);
        ...
        surfaceHolder = surfaceView.getHolder();    
        surfaceHolder.addCallback(new SurfaceHolder.Callback() {        
            @Override        
            public void surfaceCreated(SurfaceHolder holder) {            
                startPreview();        
            }        

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

            }        

            @Override        
            public void surfaceDestroyed(SurfaceHolder holder) {        
            
            }    
        });
}

相机的初始化在onResume里进行,销毁在onPause里进行:

@Override
protected void onResume() {    
    super.onResume();  
    initCamera();
}

@Override
protected void onPause() {    
    super.onPause();    
    releaseCamera();
}

private void initCamera() {    
    if (camera == null) {        
        camera = Camera.open();    
    }
}

private void releaseCamera() {    
    if (camera != null) {        
        camera.release();        
        camera = null;    
    }
}

启动相机预览的函数实现如下,通过setPreviewDisplay指定显示预览的view:

private void startPreview() {    
    if (camera != null) {        
        try {            
            camera.setPreviewDisplay(surfaceHolder);            
            if (isCapturing) {                
                camera.startPreview();            
            }        
        } catch (IOException e) {            
            e.printStackTrace();            
            Toast.makeText(this, "Unable to open camera.", Toast.LENGTH_SHORT)                    .show();        
        }    
    }
}

点击拍照按钮,进行拍照并展示。这里为了防止相机拍摄的照片太大导致OOM错误,要对图片进行压缩,压缩包括两个方式,尺寸压缩和像素格式压缩,可以参考博客从Oppo手机拍照无法展示谈图片压缩

@OnClick(R.id.take_photo_btn)
protected void onTakePhotoClicked(View v) {    
    camera.takePicture(null, null, new Camera.PictureCallback() {        
        @Override        
        public void onPictureTaken(byte[] data, Camera camera) {            
            pictureDataBytes = data;            
            showPicture();        
        }    
    });
}

private void showPicture() {    
    Bitmap bitmap = BitmapFactory.decodeByteArray(pictureDataBytes, 0, pictureDataBytes.length);    

    BitmapFactory.Options options = new BitmapFactory.Options();    
    options.inSampleSize = calculateSampleSize(bitmap.getHeight(), bitmap.getWidth());    
    options.inPreferredConfig = Bitmap.Config.RGB_565;

    InputStream inputStream = bitmapToStream(bitmap);    
    imageView.setImageBitmap(BitmapFactory.decodeStream(inputStream, null, options));
    ...
}

计算采样比例采用下面的函数:

// 获取采样比例
public int calculateSampleSize(int outWidth, int outHeight) {    
    int scale = 1;    
    if (outHeight > MAX_HEIGHT || outWidth > MAX_WIDTH) {        
        int maxSize = MAX_WIDTH > MAX_HEIGHT ? MAX_WIDTH : MAX_HEIGHT;        
        scale = (int) Math.pow(2, (int) Math.round(Math.log(maxSize /(double) Math.max(outHeight, outWidth)) / Math.log(0.5)));    
    }    

    return scale;
}

最后记得在Manifest里添加相机权限:

<uses-permission android:name="android.permission.CAMERA"/>

Github Demo地址

参考:
https://developer.android.com/reference/android/view/SurfaceView.html
http://archive.oreilly.com/oreillyschool/courses/android2/CameraAdvanced.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值