在android8.1系统中调用摄像头是通过 CameraManager::getCameraIdList() 方法获取系统摄像头列表,从列表中选择满足需要摄像头,用以拍照、录像或全景拍照。
上篇中以介绍如何把v4l2loopback移植到android内核,本章介绍如何配置虚拟摄像头参数,以满足android用户程序直接使用。
首先,我们看一下 class CameraBase 这个类的定义,文件路径:
@frameworks/av/camera/include/camera/CameraBase.h
class CameraBase : public IBinder::DeathRecipient
{
public:
static status_t getCameraInfo(int cameraId,
/*out*/
struct hardware::CameraInfo* cameraInfo);
sp<TCamUser> remote();
// Status is set to 'UNKNOWN_ERROR' after successful (re)connection
status_t getStatus();
protected:
Mutex mLock;
// helper function to obtain camera service handle
static const sp<::android::hardware::ICameraService> getCameraService();
sp<TCamUser> mCamera;
status_t mStatus;
sp<TCamListener> mListener;
const int mCameraId;
typedef CameraBase<TCam> CameraBaseT;
}
基类中 getCameraInfo() 是获取摄像头配置信息,其实现方法如下:
@frameworks/av/camera/CameraBase.cpp
// this can be in BaseCamera but it should be an instance method
template <typename TCam, typename TCamTraits>
status_t CameraBase<TCam, TCamTraits>::getCameraInfo(int cameraId,
struct hardware::CameraInfo* cameraInfo) {
const sp<::android::hardware::ICameraService> cs = getCameraService();
if (cs == 0) return UNKNOWN_ERROR;
binder::Status res = cs->getCameraInfo(cameraId, cameraInfo);
return res.isOk() ? OK : res.serviceSpecificErrorCode();
}
template class CameraBase<Camera>;
因为camera在android系统中是采用C/S结构,/system/bin/mediaserverr是服务端守护进程,frameworks/av/camera下实现的是client内容,
此方法是通过 getCameraService() 获取 sp<::android::hardware::ICameraService> cs 智能指针,从而调用服务端的
cs->getCameraInfo(cameraId, cameraInfo) 的方法,我们进一步跟踪。
文件路径
@frameworks/av/services/camera/libcameraservice/CameraService.cpp
Status CameraService::getCameraInfo(int cameraId,
CameraInfo* cameraInfo) {
ATRACE_CALL();
Mutex::Autolock l(mServiceLock);
if (!mInitialized) {
return STATUS_ERROR(ERROR_DISCONNECTED,
"Camera subsystem is not available");
}
if (cameraId < 0 || cameraId >= mNumberOfCameras) {
return STATUS_ERROR(ERROR_ILLEGAL_ARGUMENT,
"CameraId is not valid");
}
Status ret = Status::ok();
status_t err = mCameraProviderManager->getCameraInfo(std::to_string(cameraId), cameraInfo);
if (err != OK) {
ret = STATUS_ERROR_FMT(ERROR_INVALID_OPERATION,
"Error retrieving camera info from device %d: %s (%d)", cameraId,
strerror(-err), err);
}
return ret;
}
通过跟踪源码、发现摄像属性参数是在CameraHAL.cpp文件中,构造摄像头时配置 Facing和Orientation属性值,
源码路径如下:
@vendor/nxp-opensource/imx/libcamera3/CameraHAL.cpp文件中,笔者使用的NXP的android8.1软件系统,
硬件平台IMX8QM。以此类推,摄像头属性是在摄像头HAL层构建设备时通过源码类配置该属性。
因此参数因硬件平台的差异,配置文件会有差异,请读者根据自有硬件平台代码追踪此过程。
@vendor/nxp-opensource/imx/libcamera3/CameraHAL.cpp
CameraHAL::CameraHAL()
: mCameraCount(0),
mCallbacks(NULL)
{
// Allocate camera array and instantiate camera devices
mCameras = new Camera*[MAX_CAMERAS];
memset(mSets, 0, sizeof(mSets));
memset(mCameras, 0, MAX_CAMERAS * sizeof(Camera*));
// enumerate all camera sensors.
enumSensorSet(); //> 配置 camera 的参数子函数
// check if camera exists.
for (int32_t index=0; index<MAX_CAMERAS; index++) {
if (!mSets[index].mExisting) {
continue;
}
mCameras[index] = Camera::createCamera(index, mSets[index].mSensorName,
mSets[index].mFacing, mSets[index].mOrientation,
mSets[index].mDevPath);
if (mCameras[index] == NULL) {
// camera sensor is not supported now.
// So, camera count should not change.
ALOGW("Error: camera:%d, %s create failed", index,
mSets[index].mSensorName);
}
else {
mCameraCount++;
}
}
ALOGI("camera number is %d", mCameraCount);
mHotplugThread = new HotplugThread(this);
}
//> 配置 camera 的参数子函数
void CameraHAL::enumSensorSet()
{
// get property from init.rc mSets.
char orientStr[CAMERA_SENSOR_LENGTH];
ALOGI("%s", __func__);
// get back camera property.
memset(orientStr, 0, sizeof(orientStr));
property_get("back_camera_name", mSets[BACK_CAMERA_ID].mPropertyName, "0");
property_get("back_camera_orient", orientStr, "0");
mSets[BACK_CAMERA_ID].mOrientation = atoi(orientStr);
mSets[BACK_CAMERA_ID].mFacing = CAMERA_FACING_BACK;
mSets[BACK_CAMERA_ID].mExisting = false;
// get front camera property.
memset(orientStr, 0, sizeof(orientStr));
property_get("front_camera_name", mSets[FRONT_CAMERA_ID].mPropertyName, "0");
property_get("front_camera_orient", orientStr, "0");
mSets[FRONT_CAMERA_ID].mOrientation = atoi(orientStr);
mSets[FRONT_CAMERA_ID].mFacing = CAMERA_FACING_FRONT;
mSets[FRONT_CAMERA_ID].mExisting = false;
// make sure of back&front camera parameters.
matchDevNodes();
}
通过源码分析、camera的参数配置是通过property设置来管理,此参数配置是在与硬件平台相关的 init.rc 中
进行配置,IMX8QM的摄像配置参数如下:
@device/fsl/imx8qm/mek_8q/init.rc 文件
#Define the config for dual camera
#For landscape mode, orient is 0
#For portrait mode, orient is 90
#the android before honycomb are all in portrait mode
setprop camera.disable_zsl_mode 1
# 后置摄像头
setprop back_camera_name imx8_ov5640_mipi,max9286_mipi,imx8_ov5640
setprop back_camera_orient 0
# 前置摄像头
setprop front_camera_name imx8_ov5640,imx8_ov5640_mipi,uvc
setprop front_camera_orient 0
我们只需要把此摄像头名称修改为 v4l2loopback_back 和 v4l2loopback_front 名称,在构建 CameraHAL 库
时,采用此名称就可以标注出并区别开来。
int32_t CameraHAL::matchDevNodes()
{
DIR *vidDir = NULL;
struct dirent *dirEntry;
size_t nameLen = CAMERA_SENSOR_LENGTH - 1;
nodeSet *nodes = NULL, *node = NULL, *last = NULL;
ALOGI("%s", __func__);
vidDir = opendir("/sys/class/video4linux");
if (vidDir == NULL) {
return -1;
}
while ((dirEntry = readdir(vidDir)) != NULL) {
if (strncmp(dirEntry->d_name, "video", 5)) {
continue;
}
node = (nodeSet*)malloc(sizeof(nodeSet));
if (node == NULL) {
ALOGE("%s malloc failed", __func__);
break;
}
memset(node, 0, sizeof(nodeSet));
if (nodes == NULL) {
nodes = node;
}
else {
last->next = node;
}
last = node;
sprintf(node->devNode, "/dev/%s", dirEntry->d_name);
getNodeName(node->devNode, node->nodeName, nameLen);
}
closedir(vidDir);
for (int32_t index=0; index<MAX_CAMERAS; index++) {
matchPropertyName(nodes, index);
}
node = nodes;
while (node != NULL) {
last = node->next;
free(node);
node = last;
}
return 0;
}
我们看一下 sys/class/video4linux 路径下都有哪些摄像头设备
1|mek_8q:/ # ls sys/class/video4linux/
video0 video1 video12 video13 video2 video3 video4 video5
int32_t CameraHAL::getNodeName(const char* devNode, char name[], size_t length)
{
int32_t ret = -1;
int32_t fd = -1;
size_t strLen = 0;
struct v4l2_capability vidCap;
struct v4l2_dbg_chip_ident vidChip;
ALOGI("getNodeName: dev path:%s", devNode);
if ((fd = open(devNode, O_RDWR, O_NONBLOCK)) < 0) {
ALOGW("%s open dev path:%s failed:%s", __func__, devNode,
strerror(errno));
return ret;
}
ret = ioctl(fd, VIDIOC_QUERYCAP, &vidCap);
if (ret < 0) {
ALOGW("%s QUERYCAP dev path:%s failed", __func__, devNode);
close(fd);
fd = -1;
return ret;
}
if (!(vidCap.capabilities &
(V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_CAPTURE_MPLANE))) {
ALOGW("%s dev path:%s is not capture", __func__, devNode);
close(fd);
fd = -1;
ret = -1;
return ret;
}
strncat(name, (const char*)vidCap.driver, length);
strLen = strlen((const char*)vidCap.driver);
length -= strLen;
ALOGI("getNodeName: node name:%s", name);
ret = ioctl(fd, VIDIOC_DBG_G_CHIP_IDENT, &vidChip);
if (ret < 0) {
ALOGI("%s CHIP_IDENT dev path:%s failed", __func__, devNode);
strncat(name, ",", length);
strLen = 1;
length -= strLen;
strncat(name, (const char*)vidCap.card, length);
ALOGI("getNodeNames: node name:%s", name);
close(fd);
fd = -1;
return ret;
}
strncat(name, ",", length);
strLen = 1;
length -= strLen;
strncat(name, vidChip.match.name, length);
ALOGI("getNodeNames: node name:%s", name);
close(fd);
return ret;
}
摄像头名称属性是在驱动中标注的,需修改v4l2loopback驱动中节点名称,以此把该摄像头配置为
前置或后置摄像头。
至此位置,我们把 android8.1 中的、摄像头前置或后置的属性配置逻辑给梳理清晰了,如果感觉对你
有所启发或帮助,请点赞或关注,以资鼓励笔者,感谢喽。