Android 分别使用 SurfaceView 和 TextureView 来预览 Camera,获取NV21数据

本文介绍如何使用CameraAPI进行视频采集,对比SurfaceView与TextureView预览Camera数据的优缺点,详细讲解了在Android中实现视频预览的具体步骤,包括权限设置、UI准备、SurfaceView与TextureView的使用及注意事项。
部署运行你感兴趣的模型镜像

本文目的

使用 Camera API 进行视频的采集,分别使用 SurfaceView、TextureView 来预览 Camera 数据,取到 NV21 的数据回调

SurfaceView优缺点

优点:

可以在一个独立的线程中进行绘制,不会影响主线程
使用双缓冲机制,播放视频时画面更流畅

缺点:

Surface不在View hierachy中,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换,也不能放在其它ViewGroup中。SurfaceView
不能嵌套使用。

TextureView作用

TextureView可用于显示内容流,内容流可以是视频或者OpenGL的场景。内容流可来自应用进程或是远程其它进程。

Textureview必须在硬件加速开启的窗口中使用。

注意:若是软解,TextureView不会显示东西。

TextureView优点及缺点

优点:

支持移动、旋转、缩放等动画,支持截图

缺点:

必须在硬件加速的窗口中使用,占用内存比SurfaceView高,在5.0以前在主线程渲染,5.0以后有单独的渲染线程。

准备工作

添加相机权限
<uses-permission android:name="android.permission.CAMERA" />

注意:camera预览回调中默认使用NV21格式

UI 准备
<!-- 全屏显示 -->
<style name="FullScreenTheme" parent="AppTheme">
    <item name="windowNoTitle">true</item>
    <item name="android:windowFullscreen">true</item>
</style>

承载预览图像

<FrameLayout
    android:id="@+id/camera_preview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

使用 SurfaceView 预览 Camera,取到NV21数据

  • 自定义CameraPreview继承SurfaceView,实现SurfaceHolder.Callback接口
  • 获取NV21数据,Camera.setPreviewCallback() 要放在Camera.startPreview() 之前。使用Camera.PreviewCallback获取预览数据回调。默认是NV21格式。
  • surfaceChanged中,camera启动预览前可以进行设置,例如设置尺寸,调整方向
/**
 * camera预览视图
 * Created by on 2020/2/26.
 */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
    private static final String TAG = "CameraPreview";
    private SurfaceHolder mHolder;
    private Camera mCamera;
    private int mFrameCount = 0;

    public CameraPreview(Context context) {
        super(context);
    }

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mCamera = camera;
        mHolder = getHolder();
        mHolder.addCallback(this);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    public void setCamera(Camera c) {
        this.mCamera = c;
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        // 开启预览
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "设置相机预览失败: " + e.getMessage());
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 可在此释放camera
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // 若需要旋转、更改大小或重新设置,请确保证已停止预览
        if (mHolder.getSurface() == null) {
            return;
        }
        try {
            mCamera.stopPreview();
        } catch (Exception e) {
            // 在此停止不存在的预览
        }
        Camera.Parameters parameters = mCamera.getParameters();
        // ImageFormat.NV21 == 17
        if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
            mCamera.setDisplayOrientation(90);
        } else {
            mCamera.setDisplayOrientation(0);
        }
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.setPreviewCallback(mCameraPreviewCallback); // 回调要放在 startPreview() 之前
            mCamera.startPreview();
        } catch (Exception e) {
            Log.d(TAG, "开启相机预览失败: " + e.getMessage());
        }
    }

    private Camera.PreviewCallback mCameraPreviewCallback = new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            mFrameCount++;
            Log.d(TAG, "onPreviewFrame: data.length=" + data.length + ", frameCount=" + mFrameCount);
        }
    };
}

为了防止阻塞UI线程,在子线程中打开camera。camera常放在try catch中使用。

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private Camera mCamera;
    private CameraPreview mPreview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new InitCameraThread().start();
    }

    @Override
    protected void onResume() {
        if (null == mCamera) {
            if (safeCameraOpen()) {
                mPreview.setCamera(mCamera); // 重新获取camera操作权
            } else {
                Log.e(TAG, "无法操作camera");
            }
        }
        super.onResume();
    }

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

    private boolean safeCameraOpen() {
        boolean qOpened = false;
        try {
            releaseCamera();
            mCamera = Camera.open();
            qOpened = (mCamera != null);
        } catch (Exception e) {
            Log.e(TAG, "打开相机失败");
            e.printStackTrace();
        }
        return qOpened;
    }

    private void releaseCamera() {
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.release();        // release the camera for other applications
            mCamera = null;
        }
    }

    private class InitCameraThread extends Thread {
        @Override
        public void run() {
            super.run();
            if (safeCameraOpen()) {
                Log.d(TAG, "开启摄像头");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mPreview = new CameraPreview(MainActivity.this, mCamera);
                        FrameLayout preview = findViewById(R.id.camera_preview);
                        preview.addView(mPreview);
                    }
                });
            }
        }
    }
}

使用 TextureView 预览 Camera,取到NV21数据

  • 使用TextureView很简单:获取到它的SurfaceTexture,使用SurfaceTexture呈现内容。

  • CameraPreview继承了TextureView,外部需要传入camera实例。在onSurfaceTextureAvailable中,配置camera,比如设置图像方向。

  • 通过设置Camera.PreviewCallback来取得预览数据。

import java.io.IOException;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.SurfaceTexture;
import android.hardware.Camera;
import android.util.Log;
import android.view.TextureView;
/**
 * camera预览视图
 * Created by on 2020/2/26.
 */
public class CameraPreview extends TextureView implements TextureView.SurfaceTextureListener {
    private static final String TAG = "CameraPreview";
    private Camera mCamera;

    public CameraPreview(Context context) {
        super(context);
    }

    public CameraPreview(Context context, Camera camera) {
        super(context);
        mCamera = camera;
    }

    public void setCamera(Camera camera) {
        this.mCamera = camera;
    }

    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
        Log.d(TAG, "TextureView onSurfaceTextureAvailable");
        if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
            mCamera.setDisplayOrientation(90);
        } else {
            mCamera.setDisplayOrientation(0);
        }
        try {
            mCamera.setPreviewCallback(mCameraPreviewCallback);
            mCamera.setPreviewTexture(surface); // 使用SurfaceTexture
            mCamera.startPreview();
        } catch (IOException ioe) {
            // Something bad happened
        }
    }

    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
        Log.d(TAG, "TextureView onSurfaceTextureSizeChanged"); // Ignored, Camera does all the work for us
    }

    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        Log.d(TAG, "TextureView onSurfaceTextureDestroyed");
        mCamera.stopPreview();
        mCamera.release();
        return true;
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
        
    }

    private Camera.PreviewCallback mCameraPreviewCallback = new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            Log.d(TAG, "onPreviewFrame: data.length=" + data.length);
        }
    };
}

操作界面TextureAct。获取camera操作权,初始化CameraPreview并添加到布局中。第一次获取camera时在子线程中操作。

在onPause中释放camera,onResume中尝试取回camera控制权。这样应用暂时退回后台时,其他应用可以操作摄像头

public class TextureAct extends AppCompatActivity {
    private static final String TAG = "TextureAct";
    private Camera mCamera;
    private CameraPreview mPreview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_texture);
        new InitCameraThread().start();
    }

    @Override
    protected void onResume() {
        if (null == mCamera) {
            if (safeCameraOpen()) {
                mPreview.setCamera(mCamera); // 重新获取camera操作权
            } else {
                Log.e(TAG, "无法操作camera");
            }
        }
        super.onResume();
    }

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

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

    private boolean safeCameraOpen() {
        boolean qOpened = false;
        try {
            releaseCamera();
            mCamera = Camera.open();
            qOpened = (mCamera != null);
        } catch (Exception e) {
            Log.e(TAG, "打开相机失败");
            e.printStackTrace();
        }
        return qOpened;
    }

    private void releaseCamera() {
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.release();        // release the camera for other applications
            mCamera = null;
        }
    }

    private class InitCameraThread extends Thread {
        @Override
        public void run() {
            super.run();
            if (safeCameraOpen()) {
                Log.d(TAG, "TextureAct 开启摄像头");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mPreview = new CameraPreview(TextureAct.this, mCamera);
                        mPreview.setSurfaceTextureListener(mPreview);
                        FrameLayout preview = findViewById(R.id.camera_preview);
                        preview.addView(mPreview);
                    }
                });
            }
        }
    }
}
  • Textureview必须在硬件加速开启的窗口中使用。android:hardwareAccelerated=“true” 默认的这个属性就是true,无需再设置。

  • 每接到一帧数据,就会调用一次onSurfaceTextureUpdated()。通过这个接口。能够将上来的SurfaceTexture送给OpenGL再去处理。

您可能感兴趣的与本文相关的镜像

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

<think>好的,用户的问题是关于在Android开发中使用Camera2接口在SurfaceView渲染预览时,如何获取SurfaceView数据并转换为YUV420格式。我需要分步骤来思考这个问题。 首先,用户可能已经了解Camera2的基本用法,但可能在数据获取处理方面遇到了困难。SurfaceView通常用于显示相机预览,但直接从中获取数据并不容易,因为它是一个UI组件,负责渲染而不是数据输出。所以,我需要想到Camera2 API中是否有其他方式获取原始数据。 接下来,记得Camera2可以通过ImageReader来捕获图像数据。YUV420是相机常用的输出格式,所以用户可能需要配置ImageReader使用ImageFormat.YUV_420_888。这样,当相机捕获帧时,ImageReader会提供YUV数据。 然后,用户需要在Camera2的配置过程中,将ImageReader的Surface添加到相机的输出目标中。这意味着相机同时输出到SurfaceViewImageReader,这样既保持预览又能获取数据。需要注意的是,同时添加多个输出可能会影响性能,所以需要检查设备是否支持。 接下来是获取YUV数据的步骤。当ImageReader有新图像可用时,通过OnImageAvailableListener回调获取Image对象。然后,需要将Image对象中的Y、U、V三个平面数据提取出来,并转换成字节数组。这一步要注意不同设备的paddingstride问题,可能需要处理数据对齐,确保正确提取有效数据。 用户可能需要将YUV数据保存或处理,比如进行编码或图像处理。因此,示例代码需要展示如何将各个平面的数据复制到字节数组中,并考虑可能的偏移量行距。此外,还要提醒用户在使用完毕后关闭Image对象,避免资源泄漏。 最后,要提到性能方面的考虑,比如在高分辨率下处理YUV数据可能比较耗时,建议在后台线程处理,以及不同设备对YUV格式的支持可能不同,需要做好兼容性检查。总结整个过程,确保用户能够理解每个步骤,并提供示例代码作为参考。</think>在Android Camera2 API获取SurfaceView预览数据并转换为YUV420格式,可以通过以下步骤实现: 1. **配置ImageReader获取YUV数据** ```java // 创建ImageReader(YUV420格式) ImageReader imageReader = ImageReader.newInstance( previewWidth, previewHeight, ImageFormat.YUV_420_888, // 标准YUV格式 2 // 最大缓冲帧数 ); // 设置监听器 imageReader.setOnImageAvailableListener(new OnImageAvailableListener() { @Override public void onImageAvailable(ImageReader reader) { Image image = reader.acquireLatestImage(); if (image != null) { // 在此处理YUV数据 processYUV420(image); image.close(); } } }, backgroundHandler); ``` 2. **修改Camera2配置** ```java // 创建相机请求时添加两个输出目标 session.createCaptureSession(Arrays.asList( surfaceView.getHolder().getSurface(), // SurfaceView预览输出 imageReader.getSurface() // ImageReader数据输出 ), ...); ``` 3. **YUV420数据提取方法** ```java private void processYUV420(Image image) { // 获取YUV三个平面 Image.Plane[] planes = image.getPlanes(); // Y分量(亮) ByteBuffer yBuffer = planes[0].getBuffer(); int ySize = yBuffer.remaining(); // U分量(色) ByteBuffer uBuffer = planes[1].getBuffer(); int uSize = uBuffer.remaining(); // V分量(色) ByteBuffer vBuffer = planes[2].getBuffer(); int vSize = vBuffer.remaining(); // 创建字节数组存储YUV数据 byte[] yuvBytes = new byte[ySize + uSize + vSize]; // 按YUV顺序复制数据 yBuffer.get(yuvBytes, 0, ySize); uBuffer.get(yuvBytes, ySize, uSize); vBuffer.get(yuvBytes, ySize + uSize, vSize); // 注意:实际需要考虑stridepadding的影响 // 可使用以下方法获取像素步长 int yStride = planes[0].getRowStride(); int uvStride = planes[1].getRowStride(); } ``` **关键注意事项:** 1. **格式差异处理**:不同设备的YUV_420_888实现可能不同,需通过`planes[1].getPixelStride()`检查UV分量的像素步长 2. **性能优化**:建议使用后台线程处理数据,避免阻塞主线程 3. **内存管理**:及时关闭Image对象(`image.close()`),防止内存泄漏 4. **格式转换**:如需标准YUV420SP(NV21)格式,需重新排列UV平面数据 **补充方案**(当设备不支持双输出时): 可通过`setRepeatingRequest`动态切换两种输出目标,但会导致帧率下降 **数据验证建议**: 可添加代码将YUV数据保存为文件,使用YUV Viewer工具验证数据有效性 实际开发中建议结合`TextureView`代替`SurfaceView`以获得更灵活的控制,但需要处理额外的矩阵变换。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值