前段时间,我在android8.1的系统层上,新增了一个usbcamera hal,和一个虚拟摄像头hal。在实际使用的过程中,遇到了一个问题——客户app在用camera api接口调用usbcamera或virtual camera时,希望能够知道当前调用的是系统本身的mipi摄像头,还是usbcamera或virtualcamera。也就是说,客户想知道,我当前调用的摄像头,是个什么东西。

要实现这个功能,有三种方法。第一种,是最下乘的方法,也就是在hal层,open usbcamera或virtual camera的时候,设置一个属性camera.type值为usb或virtual,在close的时候置为空。在app上可以通过这个属性来判断当前打开的是什么摄像头。这个方法,之所以说是最下乘的,那是因为它不是camera标准流程的东西,不能通过camera的标准接口来判断。并且,当我同时打开了usbcamera或者virtual camera时,这就没法搞了。

第二种,是在hal层,构建cameraInfo的时候,将camera_info->facing的值,设为CAMERA_FACING_EXTERNAL。不过在android8.1上,好像这个值很多地方都没有相应的配置。并且有第一种方法同样的问题,比如我同时打开了usbcamera和virtualcamera,就没法判断各种打开的摄像头是什么类型的了。

第三种,是在hal层,新增一个vendor tag ANDROID_CAMERA_TYPE,然后如下方法去设置它的值:

    static const uint8_t cameraType = 1;cm.update(HalModule::ANDROID_CAMERA_TYPE, &cameraType, 1);

再在app层,就可以通过mCharacteristics.get(CameraCharacteristics.JPEG_ORIENTATION);这样类似的方法去获取它的值了。这种方法最灵活,要使用这种方法,新增一个vendor tag,我们必须要先理解camera 的tag是怎么工作。下面我们先来讲一下它们的工作流程。

在android camera hal3上,从app到hal层的参数传递,都是通过metadata来实现的。在app上如果要设置一个tag是通过下面的代码实现的:

CaptureRequest.Builder builder;
builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);

这个CONTROL_MODE定义在CaptureRequest.java

    @PublicKeypublic static final Key<Integer> CONTROL_MODE =new Key<Integer>("android.control.mode", int.class);

这个key也定义在这个文件里:

    public final static class Key<T> {private final CameraMetadataNative.Key<T> mKey;/*** Visible for testing and vendor extensions only.** @hide*/public Key(String name, Class<T> type, long vendorId) {mKey = new CameraMetadataNative.Key<T>(name, type, vendorId);}/*** Visible for testing and vendor extensions only.** @hide*/public Key(String name, Class<T> type) {mKey = new CameraMetadataNative.Key<T>(name, type);}......
}

从这里可以看出,app上调用set时传进来的CaptureRequest.CONTROL_MOD,实际上就是一个CameraMetadataNative。然后这个set一步步的跟进去的话,就可以看到它调用是CameraMetadataNative里的set,它定义在frameworks\base\core\java\android\hardware\camera2\impl\CameraMetadataNative.java里。然后又调到了CameraMetadata_writeValues,它定义在frameworks\base\core\jni\android_hardware_camera2_CameraMetadata.cpp里。然后它又调到了updateAny,这里会去调用metadata->update。这个update定义在frameworks\av\camera\CameraMetadata.cpp里,然后会调到updateImpl。

status_t CameraMetadata::updateImpl(uint32_t tag, const void *data,size_t data_count) {status_t res;if (mLocked) {ALOGE("%s: CameraMetadata is locked", __FUNCTION__);return INVALID_OPERATION;}int type = get_local_camera_metadata_tag_type(tag, mBuffer);if (type == -1) {ALOGE("%s: Tag %d not found", __FUNCTION__, tag);return BAD_VALUE;}// Safety check - ensure that data isn't pointing to this metadata, since// that would get invalidated if a resize is neededsize_t bufferSize = get_camera_metadata_size(mBuffer);uintptr_t bufAddr = reinterpret_cast<uintptr_t>(mBuffer);uintptr_t dataAddr = reinterpret_cast<uintptr_t>(data);if (dataAddr > bufAddr && dataAddr < (bufAddr + bufferSize)) {ALOGE("%s: Update attempted with data from the same metadata buffer!",__FUNCTION__);return INVALID_OPERATION;}size_t data_size = calculate_camera_metadata_entry_data_size(type,data_count);res = resizeIfNeeded(1, data_size);if (res == OK) {camera_metadata_entry_t entry;res = find_camera_metadata_entry(mBuffer, tag, &entry);if (res == NAME_NOT_FOUND) {res = add_camera_metadata_entry(mBuffer,tag, data, data_count);} else if (res == OK) {res = update_camera_metadata_entry(mBuffer,entry.index, data, data_count, NULL);}}if (res != OK) {ALOGE("%s: Unable to update metadata entry %s.%s (%x): %s (%d)",__FUNCTION__, get_local_camera_metadata_section_name(tag, mBuffer),get_local_camera_metadata_tag_name(tag, mBuffer), tag,strerror(-res), res);}IF_ALOGV() {ALOGE_IF(validate_camera_metadata_structure(mBuffer, /*size*/NULL) !=OK,"%s: Failed to validate metadata structure after update %p",__FUNCTION__, mBuffer);}return res;
}

这里通过update_camera_metadata_entry,将app上传下来的值,设置到hal里去。然后在hal层,就可以通过CameraMetadata::find(uint32_t tag)来读取设置的值就可以了。

上面说的是app上设置参数,到hal层读取。当然我们也可以在hal层去设置,然后在app层读取。比如

CameraMetadata cm;
static const int32_t jpegOrientation = 0;
cm.update(ANDROID_JPEG_ORIENTATION, &jpegOrientation, 1);

这样就设置好了ANDROID_JPEG_ORIENTATION这个key的值,它定义在system\media\camera\src\camera_metadata_tag_info.c里:

static tag_info_t android_jpeg[ANDROID_JPEG_END -ANDROID_JPEG_START] = {......[ ANDROID_JPEG_ORIENTATION - ANDROID_JPEG_START ] ={ "orientation",                   TYPE_INT32  },......
};

这里的ANDROID_JPEG_START,它的值定义在system\media\camera\include\system\camera_metadata_tags.h里

typedef enum camera_metadata_section_start {......ANDROID_JPEG_START             = ANDROID_JPEG              << 16,......
} camera_metadata_section_start_t;

ANDROID_JPEG定义在同一个文件里:

typedef enum camera_metadata_section {......ANDROID_JPEG,......ANDROID_SECTION_COUNT,VENDOR_SECTION = 0x8000
} camera_metadata_section_t;

然后在system\media\camera\src\camera_metadata_tag_info.c里,声明并定义了一个数组变量:

const char *camera_metadata_section_names[ANDROID_SECTION_COUNT] = {......[ANDROID_JPEG]                 = "android.jpeg",......
};

这里的camera_metadata_section_names,就是指jpeg相关的tag的一个集合的名字前缀。比如jpeg相关的tag有很多。有android.jpeg.quality, 对应在CaptureRequest.java里的key为 “public static final Key<Integer> JPEG_ORIENTATION = new Key<Integer>("android.jpeg.orientation", int.class);”对应hal层的名字为ANDROID_JPEG_QUALITY。还比如android.jpeg.gpsTimestamp,它对应在CaptureRequest.java里的key为“public static final Key<Long> JPEG_GPS_TIMESTAMP =new Key<Long>("android.jpeg.gpsTimestamp", long.class);”,对应在hal层的名字为ANDROID_JPEG_GPS_TIMESTAMP。 等等等之类的,只要是跟jpeg相关的tag,都以camera_metadata_section_names这个数组里定义的ANDROID_JPEG为前缀。

再联系到刚刚说的在hal层设置的ANDROID_JPEG_ORIENTATION值,它的全名就是ANDROID_JPEG这个做前缀,加上它自己在static tag_info_t android_jpeg[ANDROID_JPEG_END - ANDROID_JPEG_START]这个数组里的对应位置的值"orientation",加起来就是"android.jpeg.orientation"。

在hal层通过cm.update(ANDROID_JPEG_ORIENTATION, &jpegOrientation, 1);它来设置这个值后,在app层,可以通过如下的代码取得hal层设置的值:

private Integer mJpegOrientation;
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
mCharacteristics = manager.getCameraCharacteristics(cameraId);
mJpegOrientation = mCharacteristics.get(CameraCharacteristics.JPEG_ORIENTATION);

细心的读者可能发现了,CameraCharacteristics.JPEG_ORIENTATION这个值在CameraCharacteristics.java里并没有定义。没错,是没有定义,但是我们可以在这里自己增加一条就可以了。

    @PublicKeypublic static final Key<Integer> JPEG_ORIENTATION =new Key<Integer>("android.jpeg.orientation", int.class);

这么一来,我们从hal层cm.update(ANDROID_JPEG_ORIENTATION, &jpegOrientation, 1);这样设置,然后从app层通过mCharacteristics.get(CameraCharacteristics.JPEG_ORIENTATION);去获取这条线就走通了。另外在app层通过builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO);去设置到hal层,然后在hal层通过cm.find(ANDROID_CONTROL_MODE)获取设置的值这条线也通过了。(CaptureRequest.CONTROL_MODE的值为"android.control.mode",在camera_metadata_tag_info.c里很容易就找到了它对应的section name ——[ANDROID_CONTROL]              = "android.control",,然后找到对应的tag ANDROID_CONTROL_MODE的值mode,就和CaptureRequest里的"android.control.mode"对应起来了.)

看到这里,大家应该都明白了在camera hal3上, 参数tag是如何声明定义的,以及从app到hal层间,tag是如何传递的.现在我们来深入一点—— 新增自己的tag。

通过上面的讲解,我们知道了,要新增一个tag,首先需要在camera_metadata_tags.h里新增自己的vendor tag的section值:

typedef enum camera_metadata_section_start {......ANDROID_JPEG_START             = ANDROID_JPEG              << 16,......VENDOR_SECTION_START           = VENDOR_SECTION            << 16
} camera_metadata_section_start_t;

然后定义对应的每一个tag项的值:

typedef enum camera_metadata_tag {........ANDROID_JPEG_START,ANDROID_JPEG_GPS_PROCESSING_METHOD,               // byte         | ndk_publicANDROID_JPEG_GPS_TIMESTAMP,                       // int64        | ndk_publicANDROID_JPEG_ORIENTATION,                         // int32        | publicANDROID_JPEG_QUALITY,                             // byte         | publicANDROID_JPEG_THUMBNAIL_QUALITY,                   // byte         | publicANDROID_JPEG_THUMBNAIL_SIZE,                      // int32[]      | publicANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES,           // int32[]      | publicANDROID_JPEG_MAX_SIZE,                            // int32        | systemANDROID_JPEG_SIZE,                                // int32        | systemANDROID_JPEG_END,........
}

它以ANDROID_JPEG_START开始,以ANDROID_JPEG_END结束。 然后在camera_metadata_tag_info.c里定义对应的section的名字:

const char *camera_metadata_section_names[ANDROID_SECTION_COUNT] = {......[ANDROID_JPEG]                 = "android.jpeg",......
};

再定义需要增加的tag section一共有多少个子项:

unsigned int camera_metadata_section_bounds[ANDROID_SECTION_COUNT][2] = {......[ANDROID_JPEG]                 = { ANDROID_JPEG_START,ANDROID_JPEG_END },......
}

然后将需要添加的tag的每一项名字名出来

static tag_info_t android_jpeg[ANDROID_JPEG_END -ANDROID_JPEG_START] = {[ ANDROID_JPEG_GPS_COORDINATES - ANDROID_JPEG_START ] ={ "gpsCoordinates",                TYPE_DOUBLE },[ ANDROID_JPEG_GPS_PROCESSING_METHOD - ANDROID_JPEG_START ] ={ "gpsProcessingMethod",           TYPE_BYTE   },[ ANDROID_JPEG_GPS_TIMESTAMP - ANDROID_JPEG_START ] ={ "gpsTimestamp",                  TYPE_INT64  },[ ANDROID_JPEG_ORIENTATION - ANDROID_JPEG_START ] ={ "orientation",                   TYPE_INT32  },[ ANDROID_JPEG_QUALITY - ANDROID_JPEG_START ] ={ "quality",                       TYPE_BYTE   },[ ANDROID_JPEG_THUMBNAIL_QUALITY - ANDROID_JPEG_START ] ={ "thumbnailQuality",              TYPE_BYTE   },[ ANDROID_JPEG_THUMBNAIL_SIZE - ANDROID_JPEG_START ] ={ "thumbnailSize",                 TYPE_INT32  },[ ANDROID_JPEG_AVAILABLE_THUMBNAIL_SIZES - ANDROID_JPEG_START ] ={ "availableThumbnailSizes",       TYPE_INT32  },[ ANDROID_JPEG_MAX_SIZE - ANDROID_JPEG_START ] ={ "maxSize",                       TYPE_INT32  },[ ANDROID_JPEG_SIZE - ANDROID_JPEG_START ] ={ "size",                          TYPE_INT32  },
};

最后再在对应的hal层里实现自己的get_vendor_tag_ops函数就可以了。下面我来举个完整的添加自己的tag的例子。

1.)在我的usbcamera或virtualcamera 的hal3目录里,新增一个HalModule.h,内容如下:

namespace android {
namespace HalModule {//vendor tag对应的每一项的值,这里只有一个。google规定,第三方的tag,都要在 //VENDOR_SECTION_START后面添加,所以ANDROID_CAMERA_TYPE的值就设为了 //VENDOR_SECTION_STARTtypedef enum camera_ext_tags{/*sprd add flag start*/ANDROID_CAMERA_TYPE =VENDOR_SECTION_START,VENDOR_SECTION_END,/*not parameter but only flag between framework and hal*//*sprd add flag end*/}camera_metadata_tag_t;//vendor tag对应的section索引值typedef enum cam_hal_metadata_section {ANDROID_ADD_PARAMETERS,ANDROID_VENDOR_SECTION_COUNT} cam_hal_metadata_section_t;typedef struct tags_info {const char *tag_name;uint8_t     tag_type;} tags_info_t;};
};

2.)在HalModule.cpp里添加如下代码:

//这里定义vendor tag的section的名字为com.addParameters
const char *cam_hal_metadata_section_names[ANDROID_VENDOR_SECTION_COUNT] = {"com.addParameters",
};//这里定义vendor tag的子项的名字为camera-type
static tags_info_t android_add_parameters[android::HalModule::VENDOR_SECTION_END - VENDOR_SECTION_START] = {{ "camera-type",                          TYPE_INT32   },
};tags_info_t *cam_tag_info[ANDROID_VENDOR_SECTION_COUNT] = {android_add_parameters,
};//定义新增的vendor tag 的section的范围
int cam_hal_metadata_section_bounds[ANDROID_VENDOR_SECTION_COUNT][2] = {{ android::HalModule::ANDROID_CAMERA_TYPE, android::HalModule::VENDOR_SECTION_END },
};  //返回vendor tag的个数,有多少个返回多少个
static int get_tag_count(const vendor_tag_ops_t* ops)
{//UNUSED(ops);return (android::HalModule::VENDOR_SECTION_END - VENDOR_SECTION_START);
}把所有vendor tag挨个放在service传下来的uint32_t * tag_array里面,这样上层就知道每一个tag对应的序号值了
static void get_all_tags(const vendor_tag_ops_t* ops, uint32_t* tag_array)
{uint32_t *tag_array_tmp = tag_array;//UNUSED(ops);for(int i = android::HalModule::ANDROID_CAMERA_TYPE; i<android::HalModule::VENDOR_SECTION_END; i++) {*tag_array_tmp = i;tag_array_tmp++;}
}
//获取vendor tag的section对应的section名称,我这里会返回"com.addParameters"
static const char* get_section_name(const vendor_tag_ops_t* ops, uint32_t tag)
{uint32_t tag_section = (tag >> 16) - VENDOR_SECTION;//UNUSED(ops);if (tag_section >= ANDROID_VENDOR_SECTION_COUNT) {return NULL;}return cam_hal_metadata_section_names[tag_section];
}//用于获取每一个tag的名称,比如我这个地方返回“camera-type”就可以了
static const char* get_tag_name(const vendor_tag_ops_t* ops, uint32_t tag)
{uint32_t tag_section = (tag >> 16) - VENDOR_SECTION;uint32_t tag_index = tag & 0xFFFF;//UNUSED(ops);if (tag_section >= ANDROID_VENDOR_SECTION_COUNT|| tag >= (uint32_t)(cam_hal_metadata_section_bounds[tag_section][1])) {return NULL;}return cam_tag_info[tag_section][tag_index].tag_name;
}//返回tag对应的设置数据的类型,可以用TYPE_INT32, TYPE_FLOAT等多种数据格式,我这里是TYPE_INT32。
static int get_tag_type(const vendor_tag_ops_t* ops, uint32_t tag)
{uint32_t tag_section = (tag >> 16) - VENDOR_SECTION;uint32_t tag_index = tag & 0xFFFF;//UNUSED(ops);if (tag_section >= ANDROID_VENDOR_SECTION_COUNT|| tag >= (uint32_t)(cam_hal_metadata_section_bounds[tag_section][1])) {ALOGE("####get_tag_type: hal %d, tag=0x%x, tag_section=%x, bounds=%x\n", __LINE__, tag, tag_section, cam_hal_metadata_section_bounds[tag_section][1]);return -1;}return cam_tag_info[tag_section][tag_index].tag_type;
}//下面这个函数,会赋值给get_vendor_tag_ops,这函数里引用的get_tag_count、get_all_tags等等,都是hal3的标准接口。
static void getVendorTagOps(vendor_tag_ops_t* ops)
{ALOGE("getVendorTagOps start");ops->get_tag_count      = get_tag_count;ops->get_all_tags       = get_all_tags;ops->get_section_name   = get_section_name;ops->get_tag_name       = get_tag_name;ops->get_tag_type       = get_tag_type;
}camera_module_t HAL_MODULE_INFO_SYM = {.common = {.tag                = HARDWARE_MODULE_TAG,.module_api_version = CAMERA_MODULE_API_VERSION_2_3,.hal_api_version    = HARDWARE_HAL_API_VERSION,//.id                 = CAMERA_HARDWARE_MODULE_ID, .id                 = "virtual_camera",.name               = "virtual_camera", .author             = "Antmicro Ltd.",.methods            = &android::HalModule::moduleMethods,.dso                = NULL,.reserved           = {0}},.get_number_of_cameras  = android::HalModule::getNumberOfCameras,.get_camera_info        = android::HalModule::getCameraInfo,.set_callbacks          = android::HalModule::setCallbacks,//将上面自己定义的get_tag_type等接口传给hal3标准接口.get_vendor_tag_ops     = android::HalModule::getVendorTagOps,
};

3.)在自定义的virtual camera的hal3目录的camera.cpp里的staticCharacteristics() 函数中,调用如下代码:

    static const int32_t cameraType = 1;cm.update(HalModule::ANDROID_CAMERA_TYPE, &cameraType, 1);

4.)在system\media\camera\src\camera_metadata_tag_info.c里的camera_metadata_enum_snprint函数里,新增如下代码:

int camera_metadata_enum_snprint(uint32_t tag,uint32_t value,char *dst,size_t size) {const char *msg = "error: not an enum";int ret = -1;switch(tag) {......case ANDROID_CAMERA_TYPE: {switch (value) {case 0:msg = "mipi";ret = 0;break;case 1:msg = "virtual";ret = 0;break;case 2:msg = "usb";ret = 0;break;                    default:msg = "error: enum value out of range";}break;}  ......}
}

5.)在frameworks\base\core\java\android\hardware\camera2\CameraCharacteristics.java里新增:

    @PublicKeypublic static final Key<Integer> CAMERA_TYPE =new Key<Integer>("com.addParameters.camera-type", int.class);

6.)在frameworks\base\core\java\android\hardware\camera2\CaptureRequest.java里新增:

@PublicKeypublic static final Key<Integer> CAMERA_TYPE =new Key<Integer>("com.addParameters.camera-type", int.class);

7.)添加完上面这些后,就可以在app上通过下面代码来获取和设置这个tag了:

//设置
CaptureRequest.Builder builder;
builder.set(CaptureRequest.CAMERA_TYPE, CameraMetadata.1);//获取
CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
mCharacteristics = manager.getCameraCharacteristics(cameraId);
int type = mCharacteristics.get(CameraCharacteristics.CAMERA_TYPE);

按上面配置后,我们可能在adb shell里通过"dumpsys media.camera"来查看一下:

看到没,这里就可以看到我们新增的tag的值了。

好了,到这里为止,就全部讲完了。

本人建了个android camera系统 微信群,不过现在群成员超200了,不能通过二维码扫码加入,有兴趣的,可以加我微信号:xuhui_7810,到时拉你们入群。

android camera hal3 新增vendor tag相关推荐

  1. Android Camera HAL3 - Multi Camera(1)

    本文介绍下 Google Android 在其文档中对于 Multi-Camera 的描述,以及 Android R 中对 Camera HAL3 的一些新增内容,Multi-Camera 从 And ...

  2. 基于Mtk平台的android camera hal3学习

     框架 Android Camera硬件抽象层(HAL,Hardware Abstraction Layer)主要用于把底层camera driver的实现接口进行封装,再经过算法处理,提供接口给f ...

  3. Android Camera HAL3 -SessionParameter

    本文参考: https://source.android.google.cn/devices/camera/session-parameters https://source.android.goog ...

  4. Android Camera HAL3 -架构设计

    其实从 APP 到 Google HAL 再到 Vendor HAL 的通用 interface,这些地方的架构都是包含在 Android 包里面的,基本上是有迹可循的,在开发的时候即使是什么都不没有 ...

  5. Android Camera HAL3 - 框架流程预览

    前面说了 HAL3 是一个总线型的设计结构,本文就先对 HAL3 的控制流进行一个提纲挈领式的概述,主要理解整个 HAL3 的主干框架,以便对后续深入各个细节. 主干流程 以下全部都是摘抄 Googl ...

  6. android camera hal3 分析,HAL3 enabler下载-HAL3 enabler(开启camera2 api)下载v5.0 安卓版-西西软件下载...

    HAL3 enabler(开启camera2 api)让你无需Magisk模块就能够轻松的开启手机当中的camera2 api功能.对于想要将手机当中的相机功能玩出更多花样的人来说,这绝对是一款极为便 ...

  7. Android Camera HAL3 hdr

    HAL3的主要流程 获取现在设备上可用的相机设备,包括相机每一个相机的属性功能,get_number_of_camera. 获取想要打开的相机的Information,在调用中被定为 get_came ...

  8. 我心依旧之Android Camera模块FW/HAL3探学序

    前沿: 目前对于Android Camera软硬件技术发展的主流方向是高像素.高帧率.多摄像头.超强的ISP以及各种视频图形处理算法等等.当前主流的Android系统中较为常见的Camera模块还均是 ...

  9. Android [Camera 源码] 相机 HAL3(Camera3) Google官方文档(二)

    Google源码网地址链接:https://source.android.com/devices/camera 该Google Camera的文档为系列文章,文章列表: overview Camera ...

  10. Camera HAL3学习: Android Camera System

    Android Camera硬件抽象层(HAL,Hardware Abstraction Layer)主要用于把底层camera drive与硬件和位于android.hardware中的framew ...

最新文章

  1. print(__doc__)
  2. 数字转换成中文大小写
  3. 分区供水条件口诀_口诀+总结!耐火等级要求及调整原则及记忆方式
  4. [复习]莫比乌斯反演,杜教筛,min_25筛
  5. 如何下载多段ts视频 m3u8 ffmpeg
  6. 最长递增子序列(力扣)图解
  7. Django 06模板语言的复用
  8. python加入小学课本_Python走进小学教材
  9. 关于手眼标定的误差计算
  10. 绕过tp路由器管理密码_TP-Link无线路由器管理员密码是什么?
  11. 用Excel制作贪吃蛇
  12. 新浪微博--分享到微博的简单使用
  13. android 老人机模式,如何将智能手机切换成老人机模式
  14. 时序数据到底是什么,为什么我们需要时序数据库?
  15. iOS https证书双向认证的实现机制
  16. java正则表达式 位置_正则表达式(一)—位置锚定
  17. EXCEL中的数据分析—抽样分析
  18. POJO与Entity/PO、JavaBean、DTO、BO、VO的区别与联系
  19. 干货!3步,新手快速扒网站图片,仿站必备
  20. 汉朔电子价签墨水屏网络时钟,基于Esp32的Arduino的测试代码

热门文章

  1. [ble_mesh]3.9 Mesh beacon
  2. 美国MAK Technoligies介绍
  3. 10以太坊Token详解
  4. 前端 JS 根据日期查询周几 星期几
  5. oracle固定资产部门分摊,用友软件操作手册--固定资产--多部门使用、分摊处理...
  6. 如何解决hangfire使用redis存储时,如果采用了prefix报“Key has MOVED from Endpoint”的错...
  7. python之pexpect模块
  8. 由12306.cn谈谈网站性能技术
  9. 【我的架构师之路】- golang源码分析之channel的底层实现
  10. [读书]如果人类只有一种性别的话