Android车载应用之EvsCameraPreview源码分析(一)

651 阅读7分钟

0 引言

在现代汽车中,车载摄像头已成为提升驾驶安全和用户体验的重要组件,如倒车影像、全景环视、盲区监测等功能。这些功能背后通常需要实时采集和渲染摄像头画面,而 Android Automotive 提供了专门的 Enhanced Vision System (EVS) 框架,简化了车载摄像头数据的获取和管理。

EvsCameraPreview 是 Android Automotive 系统中用于测试 EVS 摄像头画面实现预览的系统应用。作为一款车载应用的重要模块,它通过 CarEvsGLSurfaceView 渲染摄像头的视频流,提供了从服务调用到画面展示的完整实现。

image.png

本系列博客将从源码分析的角度出发,详细解析 EvsCameraPreview 应用的实现细节,帮助开发者深入了解 EVS 框架的使用方法与应用层实现逻辑。通过阅读本文,您将学会如何通过 EVS 服务获取视频流并在 Android 应用中实现实时预览,同时为扩展车载摄像头功能提供技术支持。

1 App项目结构

EvsCameraPreview源码在packages/services/Car/tests/CarEvsCameraPreviewApp目录下,它的目录结构如下:

image.png

可以看到主要就是三个部分res资源文件、src源码文件、Android.bp(Soong 构建系统配置)文件和AndroidManifest.xml应用配置文件。接下来将分别讲解各个部分。

1 Android.bp文件分析

Android.bp文件的源码如下:

package {
    default_applicable_licenses: ["Android-Apache-2.0"],
}

android_app {
    name: "CarEvsCameraPreviewApp",

    owner: "google",

    // 定义模块的源代码文件
    srcs: ["src/**/*.java"],

    // 指定资源文件目录
    resource_dirs: ["res"],

    // 设置为true表示该应用可以使用隐藏的Android API
    platform_apis: true,

    // 签名配置:使用platform证书签名,赋予系统权限。
    certificate: "platform",

    // 设置优化选项:关闭优化
    optimize: {
        enabled: false,
    },

    // Dex相关设置
    enforce_uses_libs:false, // 禁用对uses-library的校验
    dex_preopt: {
        enabled: false,      // 禁用Dex预优化
    },

    // 依赖项
    libs: [
        "android.car", // 提供Android Automotive的核心支持
        "android.car-system-stubs",
    ],

    static_libs: [
        "androidx.annotation_annotation", // AndroidX的注解库
        "car-evs-helper-lib", // 提供与EVS功能相关的辅助支持
    ],

    // JNI支持
    jni_libs: ["libcarevsglrenderer_jni"], // 用于支持OpenGL渲染或其他本地功能

    product_variables: {
        pdk: {
            // 在PDK环境中不编译该模块
            enabled: false,
        },
    },
}

从Android.bp文件中了解到CarEvsCameraPreviewApp是一个系统级应用,接下来将重点分析它的源代码,文件在src文件目录下。

2 CarEvsCameraActivity

CarEvsCameraActivity.java是源码中的第一个文件,它的代码如下:

public class CarEvsCameraActivity extends Activity {
    private static final String TAG = CarEvsCameraActivity.class.getSimpleName();
    private static final int CAR_WAIT_TIMEOUT_MS = 3_000;

    // CarService 状态监听器
    private final CarServiceLifecycleListener mCarServiceLifecycleListener = (car, ready) -> {
        if (!ready) {
            return;
        }

        try {
            // 获取EVS管理器实例,用于与EVS服务交互
            CarEvsManager evsManager = (CarEvsManager) car.getCarManager(
                    Car.CAR_EVS_SERVICE);
            // 启动EVS后视摄像头的Activity
            if (evsManager.startActivity(CarEvsManager.SERVICE_TYPE_REARVIEW) != ERROR_NONE) {
                Log.e(TAG, "Failed to start a camera preview activity");
            }
        } finally {
            mCar = car;
            // 关闭当前Activity,避免界面滞留
            finish();
        }
    };

    // 保存Car服务实例,用于管理与车载服务的连接
    private Car mCar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // 初始化CarService,并设置连接超时时间和状态监听器
        Car.createCar(getApplicationContext(), /* handler = */ null, CAR_WAIT_TIMEOUT_MS,
                mCarServiceLifecycleListener);
    }

    @Override
    protected void onDestroy() {
        Log.d(TAG, "onDestroy");
        if (mCar != null) {
            // 断开与CarService的连接
            mCar.disconnect();
        }

        super.onDestroy();
    }
}

从上面的代码中可以看到,CarEvsCameraActivity主要是创建与CarService的连接。当连接好后会马上打开一个新的Activity,它应该就是我们接下来要分析的CarEvsCameraPreviewActivity。

3 CarEvsCameraPreviewActivity

public class CarEvsCameraPreviewActivity extends Activity
        implements CarEvsGLSurfaceView.BufferCallback {

    private static final String TAG = CarEvsCameraPreviewActivity.class.getSimpleName();
    /**
     * ActivityManagerService encodes the reason for a request to close system dialogs with this
     * key.
     */
    private final static String EXTRA_DIALOG_CLOSE_REASON = "reason";
    /** This string literal is from com.android.systemui.car.systembar.CarSystemBarButton class. */
    private final static String DIALOG_CLOSE_REASON_CAR_SYSTEMBAR_BUTTON = "carsystembarbutton";
    /** This string literal is from com.android.server.policy.PhoneWindowManager class. */
    private final static String DIALOG_CLOSE_REASON_HOME_KEY = "homekey";

    /**
     * Defines internal states.
     */
    private final static int STREAM_STATE_STOPPED = 0;
    private final static int STREAM_STATE_VISIBLE = 1;
    private final static int STREAM_STATE_INVISIBLE = 2;
    private final static int STREAM_STATE_LOST = 3;

    private static String streamStateToString(int state) {
        switch (state) {
            case STREAM_STATE_STOPPED:
                return "STOPPED";

            case STREAM_STATE_VISIBLE:
                return "VISIBLE";

            case STREAM_STATE_INVISIBLE:
                return "INVISIBLE";

            case STREAM_STATE_LOST:
                return "LOST";

            default:
                return "UNKNOWN: " + state;
        }
    }

    // BufferQueue用来存储相关帧的引用
    @GuardedBy("mLock")
    private final ArrayList<CarEvsBufferDescriptor> mBufferQueue = new ArrayList<>();

    // 同步锁对象
    private final Object mLock = new Object();

    // 建立一个大小为1的线程池,专门用于执行回调
    private final ExecutorService mCallbackExecutor = Executors.newFixedThreadPool(1);

    // 用于显示mEvsManager的视频流
    private CarEvsGLSurfaceView mEvsView;
    
    // 根视图容器
    private ViewGroup mRootView;
    
    // 线性布局器
    private LinearLayout mPreviewContainer;

    // DisplayManager对象
    private DisplayManager mDisplayManager;

    // 当前显示状态
    private int mDisplayState = Display.STATE_OFF;

    // 当前视频流状态
    @GuardedBy("mLock")
    private int mStreamState = STREAM_STATE_STOPPED;

    // 用于获取车载服务
    @GuardedBy("mLock")
    private Car mCar;

    // 车载Evs管理器
    @GuardedBy("mLock")
    private CarEvsManager mEvsManager;

    @GuardedBy("mLock")
    private IBinder mSessionToken;

    private boolean mUseSystemWindow;

    // 实现CarEvsStreamCallback接口
    private final CarEvsManager.CarEvsStreamCallback mStreamHandler =
            new CarEvsManager.CarEvsStreamCallback() {

        // 处理Evs流事件
        @Override
        public void onStreamEvent(int event) {
            // 如果视频流停止或超时则关闭当前Activity
            Log.i(TAG, "Received: " + event);
            if (event == CarEvsManager.STREAM_EVENT_STREAM_STOPPED ||
                event == CarEvsManager.STREAM_EVENT_TIMEOUT) {
                finish();
            }
        }

        // 接收来自EVS流的新帧数据
        @Override
        public void onNewFrame(CarEvsBufferDescriptor buffer) {
            synchronized (mLock) {
                if (mStreamState == STREAM_STATE_INVISIBLE) {
                    // 当Activity不可见时,不停止流,但立即归还接收到的帧数据
                    doneWithBufferLocked(buffer);
                } else {
                    // 将新帧数据加入mBufferQueue
                    mBufferQueue.add(buffer);
                }
            }
        }
    };

    // 监听显示屏状态变化
    private final DisplayListener mDisplayListener = new DisplayListener() {
        @Override
        public void onDisplayAdded(int displayId) {}

        @Override
        public void onDisplayRemoved(int displayId) {}

        // 当显示屏状态变化时
        @Override
        public void onDisplayChanged(int displayId) {
            if (displayId != Display.DEFAULT_DISPLAY) {
                return;
            }
            int state = decideViewVisibility();
            synchronized (mLock) {
                mDisplayState = state;
                // 传入新状态并处理
                handleVideoStreamLocked(state == Display.STATE_ON ?
                        STREAM_STATE_VISIBLE : STREAM_STATE_INVISIBLE);
            }
        }
    };

    // 监听 CarService 状态
    private final CarServiceLifecycleListener mCarServiceLifecycleListener = (car, ready) -> {
        try {
            synchronized (mLock) {
                mCar = ready ? car : null;
                mEvsManager = ready ? (CarEvsManager) car.getCarManager(Car.CAR_EVS_SERVICE) : null;
                if (!ready) {
                    if (!mUseSystemWindow) {
                        // 处理视频流丢失.
                        handleVideoStreamLocked(STREAM_STATE_LOST);
                    } else {
                        // 处理视频流停止
                        handleVideoStreamLocked(STREAM_STATE_STOPPED);
                        finish();
                    }
                } else {
                    // 如果连接到CarService,则开始启动视频流
                    handleVideoStreamLocked(STREAM_STATE_VISIBLE);
                }
            }
        } catch (CarNotConnectedException err) {
            Log.e(TAG, "Failed to connect to the Car Service");
        }
    };

    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
                Bundle extras = intent.getExtras();
                if (extras != null) {
                    // 获取EXTRA_DIALOG_CLOSE_REASON
                    String reason = extras.getString(EXTRA_DIALOG_CLOSE_REASON);
                    // 如果既不是carsystembarbutton也不是homekey,则打印日志并忽略请求
                    if (!DIALOG_CLOSE_REASON_CAR_SYSTEMBAR_BUTTON.equals(reason) &&
                        !DIALOG_CLOSE_REASON_HOME_KEY.equals(reason)) {
                        Log.i(TAG, "Ignore a request to close the system dialog with a reason = " +
                                   reason);
                        return;
                    }
                    Log.d(TAG, "Requested to close the dialog, reason = " + reason);
                }
                finish();
            } else {
                Log.e(TAG, "Unexpected intent " + intent);
            }
        }
    };

    private void registerBroadcastReceiver() {
        IntentFilter filter = new IntentFilter();
        // 监听ACTION_CLOSE_SYSTEM_DIALOGS广播事件
        filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
    
        // 确保广播可以接收所有用户的事件
        registerReceiverForAllUsers(mBroadcastReceiver, filter, /* broadcastPermission= */ null,
                /* scheduler= */ null, Context.RECEIVER_EXPORTED);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.d(TAG, "onCreate");
        super.onCreate(savedInstanceState);

        // 注册广播监听
        registerBroadcastReceiver();
        // 解析启动Activity时传入的Intent参数
        parseExtra(getIntent());

        // 允许Activity在锁屏时显示
        setShowWhenLocked(true);
        // 获取DisplayManager系统服务,并注册显示监听器mDisplayListener
        mDisplayManager = getSystemService(DisplayManager.class);
        mDisplayManager.registerDisplayListener(mDisplayListener, null);
        
        // 计算显示状态并初始化
        int state = decideViewVisibility();
        synchronized (mLock) {
            mDisplayState = state;
        }

        // 调用Car.createCar初始化Car服务
        Car.createCar(getApplicationContext(), /* handler = */ null,
                Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, mCarServiceLifecycleListener);
        // 创建EVS预览界面CarEvsGLSurfaceView
        mEvsView = CarEvsGLSurfaceView.create(getApplication(), this, getApplicationContext()
                .getResources().getInteger(R.integer.config_evsRearviewCameraInPlaneRotationAngle));
                
        // 将mEvsView添加到布局中
        mRootView = (ViewGroup) LayoutInflater.from(this).inflate(
                R.layout.evs_preview_activity, /* root= */ null);
        mPreviewContainer = mRootView.findViewById(R.id.evs_preview_container);
        LinearLayout.LayoutParams viewParam = new LinearLayout.LayoutParams(
                LinearLayout.LayoutParams.MATCH_PARENT,
                LinearLayout.LayoutParams.MATCH_PARENT,
                1.0f
        );
        mEvsView.setLayoutParams(viewParam);
        mPreviewContainer.addView(mEvsView, 0);
        
        // 如果close_button按钮被点击则关闭activity
        View closeButton = mRootView.findViewById(R.id.close_button);
        if (closeButton != null) {
            closeButton.setOnClickListener(v -> finish());
        }

        // 如果mUseSystemWindow为true,使用固定的预览宽高,否则默认全屏
        int width = WindowManager.LayoutParams.MATCH_PARENT;
        int height = WindowManager.LayoutParams.MATCH_PARENT;
        if (mUseSystemWindow) {
            width = getResources().getDimensionPixelOffset(R.dimen.camera_preview_width);
            height = getResources().getDimensionPixelOffset(R.dimen.camera_preview_height);
        }
        
        // 设置窗口属性
        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                width, height,
                2020 /* WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY */,
                WindowManager.LayoutParams.FLAG_DIM_BEHIND
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
                PixelFormat.TRANSLUCENT);
        params.gravity = Gravity.CENTER;
        params.dimAmount = getResources().getFloat(R.dimen.config_cameraBackgroundScrim);

        // 添加视图到窗口
        // 如果mUseSystemWindow为true,通过WindowManager添加mRootView,否则直接调用setContentView
        if (mUseSystemWindow) {
            WindowManager wm = getSystemService(WindowManager.class);
            wm.addView(mRootView, params);
        } else {
            setContentView(mRootView, params);
        }
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        parseExtra(intent);
    }

    private void parseExtra(Intent intent) {
        Bundle extras = intent.getExtras();
        if (extras == null) {
            mSessionToken = null;
            return;
        }
        mSessionToken = extras.getBinder(CarEvsManager.EXTRA_SESSION_TOKEN);
        mUseSystemWindow = mSessionToken != null;
    }

    @Override
    protected void onRestart() {
        Log.d(TAG, "onRestart");
        super.onRestart();
        synchronized (mLock) {
            // When we come back to the top task, we start rendering the view.
            handleVideoStreamLocked(STREAM_STATE_VISIBLE);
        }
    }

    @Override
    protected void onStop() {
        Log.d(TAG, "onStop");
        try {
            if (mUseSystemWindow) {
                // When a new activity is launched, this activity will become the background
                // activity and, however, likely still visible to the users if it is using the
                // system window.  Therefore, we should not transition to the INVISIBLE state.
                //
                // Similarly, this activity continues previewing the camera when the user triggers
                // the home button.  If the users want to manually close the preview window, they
                // can trigger the close button at the bottom of the window.
                return;
            }

            synchronized (mLock) {
                handleVideoStreamLocked(STREAM_STATE_INVISIBLE);
            }
        } finally {
            super.onStop();
        }
    }

    @Override
    protected void onDestroy() {
        Log.d(TAG, "onDestroy");
        try {
            // Request to stop current service and unregister a status listener
            synchronized (mLock) {
                if (mEvsManager != null) {
                    handleVideoStreamLocked(STREAM_STATE_STOPPED);
                    mEvsManager.clearStatusListener();
                }
                if (mCar != null) {
                    mCar.disconnect();
                }
            }

            mDisplayManager.unregisterDisplayListener(mDisplayListener);
            if (mUseSystemWindow) {
                WindowManager wm = getSystemService(WindowManager.class);
                wm.removeViewImmediate(mRootView);
            }

            unregisterReceiver(mBroadcastReceiver);
        } finally {
            super.onDestroy();
        }
    }

    @GuardedBy("mLock")
    private void handleVideoStreamLocked(int newState) {
        Log.d(TAG, "Requested: " + streamStateToString(mStreamState) + " -> " +
                streamStateToString(newState));
        if (newState == mStreamState) {
            // Safely ignore a request of transitioning to the current state.
            return;
        }

        boolean needToUpdateState = false;
        switch (newState) {
            case STREAM_STATE_STOPPED:
                if (mEvsManager != null) {
                    // 停止视频流
                    mEvsManager.stopVideoStream();
                    // 清空mBufferQueue
                    mBufferQueue.clear();
                    needToUpdateState = true;
                } else {
                    Log.w(TAG, "EvsManager is not available");
                }
                break;

            case STREAM_STATE_VISIBLE:
                if (mEvsManager != null) {
                    // 开始后视镜视频流
                    int result = mEvsManager.startVideoStream(CarEvsManager.SERVICE_TYPE_REARVIEW,
                            mSessionToken, mCallbackExecutor, mStreamHandler);
                    if (result != ERROR_NONE) {
                        Log.e(TAG, "Failed to start a video stream, error = " + result);
                    } else {
                        needToUpdateState = true;
                    }
                } else {
                    Log.w(TAG, "EvsManager is not available");
                }
                break;

            case STREAM_STATE_INVISIBLE:
                needToUpdateState = true;
                break;

            case STREAM_STATE_LOST:
                needToUpdateState = true;
                break;

            default:
                throw new IllegalArgumentException();
        }

        if (needToUpdateState) {
            mStreamState = newState;
            Log.d(TAG, "Completed: " + streamStateToString(mStreamState));
        }
    }

    // 判断View的状态:VISIBLE or INVISIBLE
    private int decideViewVisibility() {
        Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
        int state = defaultDisplay.getState();
        Log.d(TAG, "decideShowWhenLocked: displayState=" + state);
        if (state == Display.STATE_ON) {
            getWindow().getDecorView().setVisibility(View.VISIBLE);
        } else {
            getWindow().getDecorView().setVisibility(View.INVISIBLE);
        }

        return state;
    }

    // 请求新buffer
    @Override
    public CarEvsBufferDescriptor onBufferRequested() {
        synchronized (mLock) {
            if (mBufferQueue.isEmpty()) {
                return null;
            }

            // 从mBufferQueue中取出下一帧
            CarEvsBufferDescriptor newFrame = mBufferQueue.get(0);
            mBufferQueue.remove(0);

            return newFrame;
        }
    }

    // 处理已使用buffer
    @Override
    public void onBufferProcessed(CarEvsBufferDescriptor buffer) {
        synchronized (mLock) {
            doneWithBufferLocked(buffer);
        }
    }

    @GuardedBy("mLock")
    private void doneWithBufferLocked(CarEvsBufferDescriptor buffer) {
        try {
            mEvsManager.returnFrameBuffer(buffer);
        } catch (Exception e) {
            Log.w(TAG, "CarEvsService is not available.");
        }
    }
}

根据上面的代码可以总结出下面的这张图:

image.png

接下来该系列博客将重点分析CarEvsManagerCarEvsGLSurfaceView这两个关键类。