接上篇内容,这里主要以oculars在openHMD中的提交,来分析oculars的DK1/DK2在openHMD中是如何运行的。
simple代码的主流程看上篇文章”openHMD-simple代码分析(1/2)”。
//打开设备列表中第一个设备
ohmd_device* hmd = ohmd_list_open_device(ctx, 0);
if(!hmd){
printf("failed to open device: %s\n", ohmd_ctx_get_error(ctx));
return -1;
}
ohmd_list_open_device函数:
ohmd_device* OHMD_APIENTRY ohmd_list_open_device(ohmd_context* ctx, int index)
{
ohmd_device_settings settings;
//设置设备数据的更新方式:
//false:需要手动调用ohmd_ctx_update来更新数据
//true:创建线程,调用设备的update方法来更新数据(如rift.c里的update_devices)
settings.automatic_update = true;
return ohmd_list_open_device_s(ctx, index, &settings);
}
ohmd_list_open_device_s函数:
ohmd_device* OHMD_APIENTRY ohmd_list_open_device_s(ohmd_context* ctx, int index, ohmd_device_settings* settings)
{
ohmd_lock_mutex(ctx->update_mutex);
//轮询所有设备
if(index >= 0 && index < ctx->list.num_devices){
ohmd_device_desc* desc = &ctx->list.devices[index];
ohmd_driver* driver = (ohmd_driver*)desc->driver_ptr;
ohmd_device* device = driver->open_device(driver, desc);
if (device == NULL)
return NULL;
//填充device参数值
device->rotation_correction.w = 1;
device->settings = *settings;
device->ctx = ctx;
device->active_device_idx = ctx->num_active_devices;
ctx->active_devices[ctx->num_active_devices++] = device;
ohmd_unlock_mutex(ctx->update_mutex);
if(device->settings.automatic_update)
//创建线程,用来更新上报的数据。
ohmd_set_up_update_thread(ctx);
return device;
}
ohmd_unlock_mutex(ctx->update_mutex);
//如果没有设备则返回错误
ohmd_set_error(ctx, "no device with index: %d", index);
return NULL;
}
其中ohmd_set_up_update_thread会创建更新数据的线程ohmd_update_thread,这个函数主要用来读取sensor数据,调用fusion算法,将raw数据处理为四元素。
static void ohmd_set_up_update_thread(ohmd_context* ctx)
{
if(!ctx->update_thread){
ctx->update_mutex = ohmd_create_mutex(ctx);
ctx->update_thread = ohmd_create_thread(ctx, ohmd_update_thread, ctx);
}
}
static unsigned int ohmd_update_thread(void* arg)
{
ohmd_context* ctx = (ohmd_context*)arg;
//ctx->update_request_quit 控制循环是否结束,一般在ohmd_ctx_destroy里设置为true
while(!ctx->update_request_quit)
{
ohmd_lock_mutex(ctx->update_mutex);
//调用所有处于激活状态的设备的update方法来循环更新数据(如rift.c里的update_device)
for(int i = 0; i < ctx->num_active_devices; i++){
if(ctx->active_devices[i]->settings.automatic_update && ctx->active_devices[i]->update)
ctx->active_devices[i]->update(ctx->active_devices[i]);
}
ohmd_unlock_mutex(ctx->update_mutex);
//休眠一段时间,释放CPU
ohmd_sleep(AUTOMATIC_UPDATE_SLEEP);
}
return 0;
}
ctx->active_devices[i]->update(ctx->active_devices[i])实际上是调用到具体设备中的数据更新的接口中,这里以oculars的DK1/DK2为例进行说明数据的更新
static void update_device(ohmd_device* device)
{
rift_priv* priv = rift_priv_get(device);
unsigned char buffer[FEATURE_BUFFER_SIZE];
//处理心跳包信息:按照keep_alive_interval参数值(默认1秒)为间隔,发送心跳包给HMD
double t = ohmd_get_tick();
if(t - priv->last_keep_alive >= (double)priv->sensor_config.keep_alive_interval / 1000.0 - .2){
// send keep alive message
pkt_keep_alive keep_alive = { 0, priv->sensor_config.keep_alive_interval };
int ka_size = encode_keep_alive(buffer, &keep_alive);
send_feature_report(priv, buffer, ka_size);
// Update the time of the last keep alive we have sent.
priv->last_keep_alive = t;
}
//处理HMD上报的所有数据
while(true){
//通过HID接口读取HMD的数据
int size = hid_read(priv->handle, buffer, FEATURE_BUFFER_SIZE);
if(size < 0){
LOGE("error reading from device");
return;
} else if(size == 0) {//数据读完退出
return;
}
//根据oculars协议,对上报数据进行处理
if(buffer[0] == RIFT_IRQ_SENSORS){
handle_tracker_sensor_msg(priv, buffer, size);
}else{
LOGE("unknown message type: %u", buffer[0]);
}
}
}
其中handle_tracker_sensor_msg函数如下:
static void handle_tracker_sensor_msg(rift_priv* priv, unsigned char* buffer, int size)
{
//按照oculars的数据协议解析buffer中的数据填充到priv->sensor中
if(!decode_tracker_sensor_msg(&priv->sensor, buffer, size)){
LOGE("couldn't decode tracker sensor message");
}
pkt_tracker_sensor* s = &priv->sensor;
//打印调试信息
dump_packet_tracker_sensor(s);
//根据采样数设置dt值(一般采样数都在3以内)
float dt = s->num_samples > 3 ? (s->num_samples - 2) * TICK_LEN : TICK_LEN;
int32_t mag32[] = { s->mag[0], s->mag[1], s->mag[2] };
//将上报数据转为浮点数(HMD上报时不能直接上报浮点数,所以先*10000转化为整数上报)
//这里*0.0001相当于做了还原).
vec3f_from_rift_vec(mag32, &priv->raw_mag);
for(int i = 0; i < OHMD_MIN(s->num_samples, 3); i++){
//将上报数据转为浮点数
vec3f_from_rift_vec(s->samples[i].accel, &priv->raw_accel);
//将上报数据转为浮点数
vec3f_from_rift_vec(s->samples[i].gyro, &priv->raw_gyro);
//融合算法
ofusion_update(&priv->sensor_fusion, dt, &priv->raw_gyro, &priv->raw_accel, &priv->raw_mag);
// reset dt to tick_len for the last samples if there were more than one sample
dt = TICK_LEN;
}
}
主要是解析HMD上报的数据,关于ofusion_update融合算法,中间的四元素操作比较复杂,可以看这篇老外的文章IMU指南,需要花点时间琢磨。
void OHMD_APIENTRY ohmd_ctx_update(ohmd_context* ctx)
{
for(int i = 0; i < ctx->num_active_devices; i++){
ohmd_device* dev = ctx->active_devices[i];
//如果设备对应驱动中没有创建线程实时刷新数据则,通过这里刷新数据。
//(Oculars DK1是通过线程刷新数据的.
if(!dev->settings.automatic_update && dev->update)
dev->update(dev);
ohmd_lock_mutex(ctx->update_mutex);
//获取位置信息填充到dev->position中
dev->getf(dev, OHMD_POSITION_VECTOR, (float*)&dev->position);
//获取经过融合算法的orient数据填充到dev->rotation中。
dev->getf(dev, OHMD_ROTATION_QUAT, (float*)&dev->rotation);
ohmd_unlock_mutex(ctx->update_mutex);
}
}
float zero[] = {.0, .1, .2, 1};
//计算QUAT和VECTOR的校准因子correction
ohmd_device_setf(hmd, OHMD_ROTATION_QUAT, zero);
ohmd_device_setf(hmd, OHMD_POSITION_VECTOR, zero);
//利用上面计算的校准因子,来得出最后的四元素并打印出来。
print_infof(hmd, "rotation quat:", 4, OHMD_ROTATION_QUAT);