项目背景
- 为了在工厂测试中快速启动芯片外设测试项目,而不启动Android系统,使用fastmmi(FFBM: Fast Factory Boot Mode)测试框架。
- 本人负责测试芯片中的camera外设,保证camera preview function,设置最原始的camera xml参数,preview图像清晰。
- 使用Vendor NDK实现Android Camera preview.博客中使用vendor NDK的方式实现camera preview function。但是在项目中并没有能够使用,因为使用FFBM测试的时候发现camera service根本无法正常启动,所以只能使用HIDL interface来实现。
HIDL interface介绍
- 可以从Google的官方网站上查看,写得非常的详细:HIDL Interface.
- HIDL 的目标是可以在无需重新构建 HAL 的情况下上层系统替换框架。HAL 将由供应商或 SOC 制造商构建,并放置在设备的 /vendor
分区中,这样一来,就可以在框架自己的分区中通过 OTA 替换框架,而无需重新编译 HAL。这是一种解耦的思想,降低vendor和system 之间耦合度,通过把之间的接口媒介规定好,system和vendor各自升级替换都非常的方便。 - 因为无法使用camera service作为中间媒介调用camera HAL,通过NDK interface就无法实现camera preview function。只能直接调用camera HAL层,虽然Qualcomm HAL是有interface提供使用的,但是太过于复杂而且并没有非常好的文档和sample支持。考虑使用Android camera HIDL interface,本身有Google强制规定接口形式和功能定义,并且有VTS testcase可以参考流程(虽然VTS testcase在configure stream中只是request一个buffer,result回调回来也没有进行处理,而是直接抛弃掉,但是configure stream的过程也是极具参考性)
源码逻辑分析
代码逻辑分析主要突出主干,送request到camera HAL进行处理,然后接收function callback result的一个循环。
代码中只给出大概的接口流程,突出整个camera设置的过程,并且讲解设置接口的意义,完整项目大家可以参考camera VTS code。
//获取HAL实现的命名
android::hardware::getAllHalInstanceNames(ICameraProvider::descriptor)[0];
//获取有几个camera device
ICameraProvider::getService(service_name);
getCameraDeviceNames(mProvider);
//HIDL interface获取到device实例,本示例code中使用的是HAL所以是V3
ret = getCameraDeviceInterface_V3_x(
name,
[&](auto status, const auto& device) {
ALOGI("getCameraDeviceInterface_V1_x returns status:%d",
(int)status);
if(status != Status::OK)
MMI_ALOGI("status != Status::OK");
if(device == nullptr)
MMI_ALOGI("device == nullptr");
mdevice3 = device;
});
//如果使用HIDL interface一定要判断return值,如果不判断,在运行过程中会crash,
//Google使用crash的机制强制使你判断
if(!ret.isOk())
MMI_ALOGE("return error");
//获取能够使用的camera metadata,设置camera tuning参数
ret = getCameraCharacteristics([&] (Status s,
::android::hardware::camera::device::V3_2::CameraMetadata metadata) {
staticMeta = clone_camera_metadata(
reinterpret_cast<const camera_metadata_t*>(metadata.data()));
});
//通过metadata获取到当前这颗camera是front还是rear,对前后摄设置不同的参数
camera_metadata_ro_entry facing;
auto status = find_camera_metadata_ro_entry(staticMeta,
ANDROID_LENS_FACING, &facing);
//new device callback函数,并且在open device的过程中去注册,capture result就通过callback回调回来
mDeviceCb = new DeviceCb(this, deviceVersion, staticMeta);
ret = mdevice3->open(mDeviceCb, [&session](auto status, const auto& newSession) {
MMI_ALOGI("device::open returns status:%d", (int)status);
if(status != Status::OK)
MMI_ALOGI("returnStatus != Status::OK");
if(newSession == nullptr)
MMI_ALOGI("newSession == nullptr");
*session = newSession;
});
//configure stream 因为HAL是3.5version,使用_3_5
ret = configureStreams_3_5(config3_5,
[&] (Status s, device::V3_4::HalStreamConfiguration halConfig) {
if(s != Status::OK)
MMI_ALOGI("s != Status::OK");
if(1u != halConfig.streams.size())
MMI_ALOGI("1u != halConfig.streams.size()");
halStreamConfig->streams.resize(1);
halStreamConfig->streams[0] = halConfig.streams[0].v3_3.v3_2;
if (*useHalBufManager) {
hidl_vec<V3_4::Stream> streams(1);
hidl_vec<V3_2::HalStream> halStreams(1);
streams[0] = config3_4.streams[0];
halStreams[0] = halConfig.streams[0].v3_3.v3_2;
mDeviceCb->setCurrentStreamConfig(streams, halStreams);
}
});
//通过camera session construct request setting
ret = constructDefaultRequestSettings(reqTemplate,
[&](auto status, const auto& req) {
if(status != Status::OK)
MMI_ALOGE("return error");
msettings = req;
});
//这一段代码表示了使用gralloc的接口通过buffer handle创建graphic buffer,创建六个buffer进行轮转,
//使用之前gralloc循环申请对应的buffer地址,
//这个地址作为存放preview image的地址的handle
void allocateGraphicBuffer(uint32_t width, uint32_t height, uint64_t usage,
::android::hardware::graphics::common::V1_0::PixelFormat format, hidl_handle *buffer_handle /*out*/) {
buffer_handle_t buffer;
buffer = nullptr;
uint32_t stride;
android::status_t err = android::GraphicBufferAllocator::get().allocate(
width, height, static_cast<int32_t>(format), 1u /*layerCount*/, usage, &buffer, &stride,
"VtsHalCameraProviderV2_4");
MMI_ALOGI("allocateGraphicBuffer_buffer %p", buffer);
if(err != android::NO_ERROR){
MMI_ALOGI("err != android::NO_ERROR err %d", err);
}
buffer_handle->setTo(const_cast<native_handle_t*>(buffer), false /*shouldOwn*/);
}
hidl_handle buffer_handle[6];
memset(buffer_handle, 0, sizeof(buffer_handle));
for(int i=0; i<6; i++){
if (museHalBufManager) {
bufferId = 0;
} else {
allocateGraphicBuffer(mpreviewStream.width, mpreviewStream.height,
android_convertGralloc1To0Usage(halStreamConfig.streams[0].producerUsage,
halStreamConfig.streams[0].consumerUsage),
halStreamConfig.streams[0].overrideFormat, &buffer_handle[i]);
}
std::shared_ptr <StreamBuffer> outputBuffer = std::make_shared<StreamBuffer>();
outputBuffer->streamId = halStreamConfig.streams[0].id;
outputBuffer->bufferId = bufferId;
outputBuffer->buffer = buffer_handle[i].getNativeHandle();
outputBuffer->status = BufferStatus::OK;
outputBuffer->acquireFence = nullptr;
outputBuffer->releaseFence = nullptr;
MMI_ALOGI("module run allocateGraphicBuffer buffer_handle :%p bufferId %d",
buffer_handle[i].getNativeHandle(), bufferId);
buffer_queue.push(outputBuffer);
bufferId++;
}
//为了让camera循环不断地下发request,在NDK interface中很简单,直接调用repeating request就可以,但是在HIDL interface中是没有的,
//所以只能够创建出一个连续request方法,使用Android自带的threadloop不断下发request,调用run方法激活thread loop
camerahidltest->run("camera threads", ANDROID_PRIORITY_FOREGROUND);
//treadloop函数不断下发request流程
bool CameraHidlTest::threadLoop(){
std::shared_ptr <StreamBuffer> outputBuffer;
Return<void> ret;
pthread_mutex_lock(&g_mutex_lock);
if(buffer_queue.size() > 0){
outputBuffer = buffer_queue.front();
buffer_queue.pop();
pthread_mutex_unlock(&g_mutex_lock);
}else{
pthread_mutex_unlock(&g_mutex_lock);
std::unique_lock<std::mutex> con_lock(mtx);
condition.wait(con_lock);
if(camerahidltest->mthreadloop_t == true){
return true;
}else{
return false;
}
}
mframenumberbuffer[mframeNumber] = outputBuffer->buffer.getNativeHandle();
::android::hardware::hidl_vec<StreamBuffer> outputBuffers = {*outputBuffer};
const StreamBuffer emptyInputBuffer = {-1, 0, nullptr,
BufferStatus::ERROR, nullptr, nullptr};
//构建request,mframeNumber必须唯一不能重复
CaptureRequest request = {mframeNumber, 0 /* fmqSettingsSize */, msettings,
emptyInputBuffer, outputBuffers};
{
std::unique_lock<std::mutex> l(mLock);
mInflightMap.add(mframeNumber, &minflightReq);
}
Status status = Status::INTERNAL_ERROR;
uint32_t numRequestProcessed = 0;
hidl_vec<BufferCache> cachesToRemove;
//process_capture_request: Send a new capture request to the HAL.
//The HAL should not return from this call until it is ready to accept the next request to process.
//下发request到HAL interface
ret = msession->processCaptureRequest(
{request}, cachesToRemove, [&status, &numRequestProcessed](auto s,
uint32_t n) {
status = s;
numRequestProcessed = n;
});
if(!ret.isOk())
MMI_ALOGE("return error");
mframeNumber++;
}
bool DeviceCb::processCaptureResultLocked(const CaptureResult& results,
hidl_vec<PhysicalCameraMetadata> physicalCameraMetadata) {
uint32_t frameNumber = results.frameNumber;
ssize_t idx = mParent->mInflightMap.indexOfKey(frameNumber);
//其中有一些解析metadata的数据代码,省略,具体参考VTS testcase code
//通过result上来的frame number查找到对应的buffer handle
//std::map<uint32_t, buffer_handle_t> mframenumberbuffer
auto buffer_iter = mframenumberbuffer.find(frameNumber);
}
hidl_handle buffer_handle;
buffer_handle.setTo(const_cast<native_handle_t*>(buffer_iter->second), false);
std::vector<ui::PlaneLayout> planeLayouts;
//gralloc interface通过buffer handle获取到plane payout
status_t err = GraphicBufferMapper::get().getPlaneLayouts(buffer_handle, &planeLayouts);
if (err == NO_ERROR && !planeLayouts.empty()){
MMI_ALOGE("err == NO_ERROR && !planeLayouts.empty()");
}
int32_t Y_Stride = planeLayouts[0].strideInBytes;
int32_t UV_Stride = planeLayouts[1].strideInBytes;
//gralloc interface lock到真实的image buffer地址
Rect bounds(0, 0, preview_width, preview_height);
uint8_t *buffer_pointer = nullptr;
err = GraphicBufferMapper::get().lock(buffer_handle,
GRALLOC_USAGE_SW_READ_OFTEN,
bounds,
(void **)&buffer_pointer(image buffer真实地址));
//处理image buffer过程,code省略
//使用完之后unlock buffer地址,gralloc才能够再次填充数据
err = GraphicBufferMapper::get().unlock(buffer_handle);
//这一部分主要是重新把stream buffer 保存在queue中然后在thread loop中再次使用
for(auto iter=results.outputBuffers.begin(); iter!=results.outputBuffers.end(); iter++){
std::shared_ptr <StreamBuffer> finalSb = std::make_shared<StreamBuffer>();
finalSb->streamId = iter->streamId;
finalSb->bufferId = iter->bufferId;
finalSb->buffer = buffer_iter->second;
finalSb->status = BufferStatus::OK;
finalSb->acquireFence = nullptr;
finalSb->releaseFence = nullptr;
if(finalSb->buffer.getNativeHandle() != nullptr){
mParent->buffer_queue.push(finalSb);
std::unique_lock<std::mutex> con_lock(mParent->mtx);
mParent->condition.notify_all();
}else{
MMI_ALOGI("results outputBuffers address is nullptr");
}
}
//这段代码表示在进程结束当前实例后做的清理功能,需要把gralloc中注册的buffer handle进行free,避免占用资源
while (!camerahidltest->buffer_queue.empty())
camerahidltest->buffer_queue.pop();
buffer_handle_t handle[7];
for(int i=1; i<7; i++){
handle[i] = camerahidltest->mframenumberbuffer[i];
}
camerahidltest->mframenumberbuffer.clear();
for(int i=1; i<7; i++ ){
android::GraphicBufferAllocator::get().free(handle[i]);
}
- 创建map表来同时对应frame number和buffer handle,因为在result返回中只有frame number非常的明确,通过和buffer handle之间的对应来获取真实的image buffer address。补充一点,capture result回调是有buffer address地址的,但是为nullptr,可以在HIDL interface的文档中发现这个buffer address设计就是为nullptr,没有指向真实的buffer地址,无法使用,我当时做项目的时候还以为哪里弄错了怎么address 指针为nullptr,所以只能在上层建立map来对应链接buffer handle。在capture result的回调函数中其实有很多非data buffer的回调,比如说metadata的回调函数,还有很多具体什么我忘记了,可以首先判空过滤一下
- 创建了buffer_queue来存放stream buffer,在thread loop的过程中取出并通过request下发到HAL,然后在capture result中重新存入到buffer queue中。
- 为什么不直接建立buffer handle queue,因为在thread loop中需要stream buffer来组成request construction下发到camera HAL,使用buffer handle循环就没办法组装stream buffer。
acknowledge
- 这个项目吸取教训,什么事情都得自己去验证才能保证准确性,最开始和fastmmi负责人沟通的时候他确定的说camera service和camera provider进程都是可以启动的,没有问题。之后芯片下来,我离线调试了一个周的时间,VNDK的方法基本上功能完整才到chip上面去测试,发现居然不行。当时人都蒙了,然后发现camera service居然无法正常去启动,不断地重启。和负责人沟通之后反馈没有办法启动camera service,因为会连带启动zygote UI进程,严重影响启动时间导致FFBM测试没有价值。当时听到这个消息人都蒙了,只能使用HIDL interface来构建,但是只有一周时间chip就要evb贴片量产,根本来不及,最后还是组长和PM决定在evb工厂测试中只是测试open camera,后续preview function后面再开发,觉得挺难受的,幸好头和组长也没有多说什么,希望今年年中奖不受影响。
- 如果博客有什么不清楚或者错误或者疑惑的地方欢迎大家留言讨论,虚心向大家求教!新年快乐!