0 引言
在现代汽车中,车载摄像头已成为提升驾驶安全和用户体验的重要组件,如倒车影像、全景环视、盲区监测等功能。这些功能背后通常需要实时采集和渲染摄像头画面,而 Android Automotive 提供了专门的 Enhanced Vision System (EVS) 框架,简化了车载摄像头数据的获取和管理。
EvsCameraPreview 是 Android Automotive 系统中用于测试 EVS 摄像头画面实现预览的系统应用。作为一款车载应用的重要模块,它通过 CarEvsGLSurfaceView 渲染摄像头的视频流,提供了从服务调用到画面展示的完整实现。
本系列博客将从源码分析的角度出发,详细解析 EvsCameraPreview 应用的实现细节,帮助开发者深入了解 EVS 框架的使用方法与应用层实现逻辑。通过阅读本文,您将学会如何通过 EVS 服务获取视频流并在 Android 应用中实现实时预览,同时为扩展车载摄像头功能提供技术支持。
1 App项目结构
EvsCameraPreview源码在packages/services/Car/tests/CarEvsCameraPreviewApp目录下,它的目录结构如下:
可以看到主要就是三个部分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.");
}
}
}
根据上面的代码可以总结出下面的这张图:
接下来该系列博客将重点分析CarEvsManager和CarEvsGLSurfaceView这两个关键类。