Android 13 CameraMetadata详解1 (内存分布以及增删改查)

news2024/11/22 13:47:08

文章目录

  • 简介
  • allocate_camera_metadata
  • add_camera_metadata_entry
  • delete_camera_metadata_entry
  • update_camera_metadata_entry
  • find_camera_metadata_entry

点赞收藏加关注,下次找我不迷路。

也欢迎关注微信公众号 无限无羡
期待与你的相识!

简介

初识camera metadata是不容易理解的,最起码笔者是这样。但是不理解又是不行的,所以只能硬着头皮去看源码,去归纳总结。简单来说,camera metadata就是一块用来存储camera相关参数的内存。比如拍照时的闪光灯,是打开还是关闭还是自动,这个参数就是存储在这块内存当中的。当然,camera的参数有很多,其类型有很多。camera metadata以一定的规则将这些信息全部存储起来,然后再用相同的规则取出。我们先给一张内存分布图,大家在看代码解析时,可以参考这张图来看。
在这里插入图片描述

allocate_camera_metadata

对着代码一行一行解读是费力不讨好的,既然是一块内存,我们不妨从内存的分配谈起。

// system/media/camera/include/system/camera_metadata.h文件中
/**
 * Allocate a new camera_metadata structure, with some initial space for entries
 * and extra data. The entry_capacity is measured in entry counts, and
 * data_capacity in bytes. The resulting structure is all contiguous in memory,
 * and can be freed with free_camera_metadata().
 */
ANDROID_API
camera_metadata_t *allocate_camera_metadata(size_t entry_capacity,
        size_t data_capacity);

通过注释可以知道,这个函数是给entries和extra data分配一块连续的内存空间,而entries是以单位个来衡量的,其容量为entry_capacity个,而extra data是以字节来衡量的,其容量为data_capacity个字节。当我们不再使用这块内存时可以通过free_camera_metadata进行内存的释放。至于这里的enties和extra data具体指什么,我们先不关心。接下来我们去看下这个函数的实现。

// system/media/camera/src/camera_metadata.c
// 这里先关注一下,返回的是一个camera_metadata_t类型的地址
camera_metadata_t *allocate_camera_metadata(size_t entry_capacity,
                                            size_t data_capacity) {

	// 应该是通过这个调用计算出了需要分配内存的大小
    size_t memory_needed = calculate_camera_metadata_size(entry_capacity,
                                                          data_capacity);
    // 调用calloc函数进行内存分配,分配1块大小为memory_needed的连续内存并且初始化为0
    void *buffer = calloc(1, memory_needed);
    // 这里是将分配的内存地址赋值给一个camera_metadata_t的指针,然后对其中的一些参数进行初始化
    // 比如版本号,后面会详解
    camera_metadata_t *metadata = place_camera_metadata(
        buffer, memory_needed, entry_capacity, data_capacity);
    if (!metadata) {
        /* This should not happen when memory_needed is the same
         * calculated in this function and in place_camera_metadata.
         */
        free(buffer);
    }
    // 返回内存地址
    return metadata;
}

请注意,上面分配内存的函数返回的是一个camera_metadata_t类型的指针,再继续分析代码之前,我们有必要先了解下这个类型。

// system/media/camera/include/system/camera_metadata.h
/**
 * A packet of metadata. This is a list of metadata entries, each of which has
 * an integer tag to identify its meaning, 'type' and 'count' field, and the
 * data, which contains a 'count' number of entries of type 'type'. The packet
 * has a fixed capacity for entries and for extra data.  A new entry uses up one
 * entry slot, and possibly some amount of data capacity; the function
 * calculate_camera_metadata_entry_data_size() provides the amount of data
 * capacity that would be used up by an entry.
 *
 * Entries are not sorted by default, and are not forced to be unique - multiple
 * entries with the same tag are allowed. The packet will not dynamically resize
 * when full.
 *
 * The packet is contiguous in memory, with size in bytes given by
 * get_camera_metadata_size(). Therefore, it can be copied safely with memcpy()
 * to a buffer of sufficient size. The copy_camera_metadata() function is
 * intended for eliminating unused capacity in the destination packet.
 */
struct camera_metadata;
typedef struct camera_metadata camera_metadata_t;

这里的注释很多,但是却很重要,我们来翻译一下。
camera_metadata里面存储的是一个metadata entries的列表,每一个entry都包含了一个标识变量tag,不同entry的tag可以是相同的。entry还有其他成员比如type、count等。这块内存是有固定的容量的(分配内存时传入的两个参数决定了它的容量),当内存占满时不会自动扩容。这是一块连续的内存,大小可以通过get_camera_metadata_size来得到(其实就是上面计算出来的大小)。可以通过memcpy进行安全的拷贝操作,可以调用copy_camera_metadata来将内存中没有使用的内存大小消除,得到真正占用的内存的大小的结构体。
(上面的翻译大家姑且先看,或者英文好的理解能力强的应该比我翻译的要更好)

下面我们去看看这个结构体定义的地方。
这里相对就比较形象地展示出了这块内存的内容,我看网上其他网友总结这里的时候,我每次看完都还是没有理解透彻,可能是我的悟性比较差,希望这里我能够给大家讲清楚。

// system/media/camera/src/camera_metadata.c
/**
 * A packet of metadata. This is a list of entries, each of which may point to
 * its values stored at an offset in data.
 *
 * It is assumed by the utility functions that the memory layout of the packet
 * is as follows:
 *
 *   |-----------------------------------------------|
 *   | camera_metadata_t                             |  // 这块内存开头是一个camera_metadata_t类型的结构体
 *   |                                               |
 *   |-----------------------------------------------|
 *   | reserved for future expansion                 | // 接下来是一块预留的内存,用以未来给camera_metadata_t扩展使用
 *   |-----------------------------------------------| // 比如未来要往加一个字段,这里预留的就派上用场了
 *   | camera_metadata_buffer_entry_t #0             | // 接下来就开始保存entry的内容,类型是camera_metadata_buffer_entry_t
 *   |-----------------------------------------------|
 *   | ....                                          | // 都是entry,
 *   |-----------------------------------------------|
 *   | camera_metadata_buffer_entry_t #entry_count-1 | // 最后一个entry
 *   |-----------------------------------------------|
 *   | free space for                                | // 这里也好理解,就是我分配的entry capacity跟实际的entry count的关系,
 *   | (entry_capacity-entry_count) entries          | // count <= capacity,这里就是没用完的地方,单位是entry,比如还剩5个entry
 *   |-----------------------------------------------|
 *   | start of camera_metadata.data                 | // 这里开始存放data,这个data其实是属于entry的数据,当data的数据小于4字
 *   |                                               | // 的时候会存在entry本身的内存里面,否则会根据其offset存在这块区域
 *   |-----------------------------------------------|
 *   | free space for                                | // 这里跟上上面entry一样的,data分配的空间没有使用完的地方
 *   | (data_capacity-data_count) bytes              | // 单位是字节
 *   |-----------------------------------------------|
 *
 * With the total length of the whole packet being camera_metadata.size bytes.
 *
 * In short, the entries and data are contiguous in memory after the metadata
 * header.
 */
#define METADATA_ALIGNMENT ((size_t) 4)
struct camera_metadata {
    metadata_size_t          size; // 分配的内存的大小
    uint32_t                 version; // 版本号
    uint32_t                 flags; // 标记当前是否有对entry进行排序,0:不排序, 1: 排序
    metadata_size_t          entry_count; // 这个count初始为0,每增加一个entry就加1
    metadata_size_t          entry_capacity; // 分配的固定的也是最大的entry的个数
    metadata_uptrdiff_t      entries_start; // 开始存放entry结构体的地方,这里表示的是相对于整块内存首地址的偏移量
    metadata_size_t          data_count; // 实际内存中的count,字节为单位,每有一个大于4字节的data,这里就递增
    metadata_size_t          data_capacity; // 分配的固定的也是最大的data的字节数
    metadata_uptrdiff_t      data_start; // 开始存储data的地址,这里表示的是相对于整块内存首地址的偏移量
    uint32_t                 padding;    // padding to 8 bytes boundary, 字节对齐,默认是8字节对齐
    metadata_vendor_id_t     vendor_id;  // 厂商自己定义的tag开始的字段
};

通过上面可以看出,通过camera_metadata这个结构体是可以找到这块内存中的任意一个entry的信息的。后面的增删改查操作也是基于这个结构体来实现的。其实说了这么多,里面存储的是entry数据,那么这个entry的庐山真面目如何呢,我们来看下。

// system/media/camera/src/camera_metadata.c
// 需要注意,这个结构体定义是在.c文件中,说明对外是不可见的
/**
 * A single metadata entry, storing an array of values of a given type. If the
 * array is no larger than 4 bytes in size, it is stored in the data.value[]
 * array; otherwise, it can found in the parent's data array at index
 * data.offset.
 */
#define ENTRY_ALIGNMENT ((size_t) 4)
typedef struct camera_metadata_buffer_entry {
    uint32_t tag; // 每个entry都有一个tag,可以理解为key
    // 这个count到这里的话是不太好理解的,我举一个例子大家就很容易理解了。
    // 假设我这个entry存储的数据是TYPE_INT32类型(占用4个字节),那么这个count就表示这个数据占用多少个TYPE_INT32(4字节)
    // 也就是说这个entry的data占用的内存大小为count * sizeof(TYPE_INT32) = 32字节
    uint32_t count; // 
    union {// 存放tag对应的value信息的,如果data小于等于4字节,则存放在data.value里,
           // 否则存放在偏移地址为data_start+为offset的内存中
           // 这里请大家暂停思考一下,为什么小于等于4字节存储在value里面,大于4字节存放在另外的地方呢?
           // 因为这个data是一个共用体,对于共用体来说offset和value共用内存,而这里offset是uint32_t占用4字节,
           // uint8_t  value[4]也是占用4字节。所以小于等于4字节存储在value里,offset此时是不使用的。当大于4字节
           // 时,value不使用,offset存储地址。这样做的目的是当数据小于等于4字节可以不用使用offset处的data区域的地址,
           // 达到节省内存的目的。(希望我讲清楚了,不清楚的话大家多看几遍)
        uint32_t offset;
        uint8_t  value[4];
    } data; 
    uint8_t  type; // data value的类型:TYPE_BYTE、TYPE_INT32、TYPE_FLOAT、TYPE_INT64
    uint8_t  reserved[3]; // 预留的内存供未来使用
} camera_metadata_buffer_entry_t;

entry的结构体介绍完了,其实还有一个共用体我们需要介绍下,它就是camera_metadata_data,当entry的value大于4字节时,其value就存储在这个共用体里面。这个比较简单,大家看一下其定义。

// system/media/camera/src/camera_metadata.c
/**
 * A datum of metadata. This corresponds to camera_metadata_entry_t::data
 * with the difference that each element is not a pointer. We need to have a
 * non-pointer type description in order to figure out the largest alignment
 * requirement for data (DATA_ALIGNMENT).
 */
#define DATA_ALIGNMENT ((size_t) 8)
typedef union camera_metadata_data {
    uint8_t u8;
    int32_t i32;
    float   f;
    int64_t i64;
    double  d;
    camera_metadata_rational_t r;
} camera_metadata_data_t;

好,到现在为止,几个重要的数据结构已经介绍完了,下面我们接着看下分配内存的实现,继续看是如何计算出所需内存的大小的

// system/media/camera/src/camera_metadata.c
size_t calculate_camera_metadata_size(size_t entry_count,
                                      size_t data_count) {
    // 先拿到camera_metadata_t的大小
    // 我们前面说到,camera_metadata_t的后面有一块预留内存供未来拓展字段使用,那么
    // 这里怎么没有看到预留内存是多大呢?其实大家想想就能明天,我如果在camera_metadata_t
    // 中增加了几个字段的时候,这里的sizeof是不是也会跟着增加呢?
    size_t memory_needed = sizeof(camera_metadata_t);
    // Start entry list at aligned boundary
    // 作4字节对齐
    memory_needed = ALIGN_TO(memory_needed, ENTRY_ALIGNMENT);
    // 再加上entry * entry_count的大小,注意这里entry_count实际是entry_capacity
    memory_needed += sizeof(camera_metadata_buffer_entry_t[entry_count]);
    // Start buffer list at aligned boundary
    // 作8字节对齐
    memory_needed = ALIGN_TO(memory_needed, DATA_ALIGNMENT);
    // 加上data_count, 因为data就是以字节为单位的,所以直接加data_count
    // 同样,这里data_count是data_capacity
    memory_needed += sizeof(uint8_t[data_count]);
    // Make sure camera metadata can be stacked in continuous memory
    // 8字节对齐
    memory_needed = ALIGN_TO(memory_needed, METADATA_PACKET_ALIGNMENT);
    return memory_needed;
}

在allocate_camera_metadata函数中,还有一个place_camera_metadata函数,我们看下其实现

// system/media/camera/src/camera_metadata.c
// dst: 使用calloc分配的内存地址
// dst_size: 上面计算出的memory_needed
camera_metadata_t *place_camera_metadata(void *dst,
                                         size_t dst_size,
                                         size_t entry_capacity,
                                         size_t data_capacity) {
    if (dst == NULL) return NULL;

	// 又计算了一次内存大小,原因是拷贝函数copy_camera_metadata中也调用了place_camera_metadata
	// 拷贝时,src size是不能大于dst size的,所以下面我们会看到有个比较
    size_t memory_needed = calculate_camera_metadata_size(entry_capacity,
                                                          data_capacity);
	// 这里的memory_needed详单与src size
    if (memory_needed > dst_size) {
      ALOGE("%s: Memory needed to place camera metadata (%zu) > dst size (%zu)", __FUNCTION__,
              memory_needed, dst_size);
      return NULL;
    }

	// 对camera_metadata_t进行初始化,将dst地址赋值给metadata地址,
	// 这样metadata就是整个内存的首地址了,也就将camera_metadata_t
	// 放到了内存的开始位置
    camera_metadata_t *metadata = (camera_metadata_t*)dst;
    metadata->version = CURRENT_METADATA_VERSION; // 版本号为1
    metadata->flags = 0; // 0表示不对entry进行排序
    metadata->entry_count = 0; // 初始大小都为0,data也一样,后面有插入时递增
    metadata->entry_capacity = entry_capacity; // 最大容量entry
    metadata->entries_start = // entry的起始地址,在camera_metadata_t后面做字节对齐后就是entry_start
            ALIGN_TO(sizeof(camera_metadata_t), ENTRY_ALIGNMENT);
    metadata->data_count = 0; // 初始value为0
    metadata->data_capacity = data_capacity; // 最大value字节数
    metadata->size = memory_needed; // 申请的内存大小
    // entry_start + entry_capacity的大小
    size_t data_unaligned = (uint8_t*)(get_entries(metadata) +
            metadata->entry_capacity) - (uint8_t*)metadata;
    // entry_start + entry_capacity作8字节对齐后,就是data_start在metadata中的相对地址
    metadata->data_start = ALIGN_TO(data_unaligned, DATA_ALIGNMENT);
    // vendor_id有api可以设置,我们放到下个章节详细讲解vendor metadata
    metadata->vendor_id = CAMERA_METADATA_INVALID_VENDOR_ID;

    assert(validate_camera_metadata_structure(metadata, NULL) == OK);
    return metadata;

	// 这个函数其实就是初始化了分配的内存中的头部部分,也就是camera_metadata_t所占用的内存,
	// 剩余未分配的内存里面现在全是0
}

add_camera_metadata_entry

我们常说增删改查,下面我们就先看下如何“增”。

// system/media/camera/include/system/camera_metadata.h
/**
 * Add a metadata entry to a metadata structure. Returns 0 if the addition
 * succeeded. Returns a non-zero value if there is insufficient reserved space
 * left to add the entry, or if the tag is unknown.  data_count is the number of
 * entries in the data array of the tag's type, not a count of
 * bytes. Vendor-defined tags can not be added using this method, unless
 * set_vendor_tag_query_ops() has been called first. Entries are always added to
 * the end of the structure (highest index), so after addition, a
 * previously-sorted array will be marked as unsorted.
 *
 * Returns 0 on success. A non-0 value is returned on error.
 */
// 返回0表示成功
// dst: 就是我们前面分配的metadata指针
// tag: 要插入的entry的tag
// data: 要插入的数据buffer
// data_count: 指的是需要的类型数据在上面的data中占用的个数
// 比如,entry的type是TYPE_INT32,那么要写入的data大小为
// data_count*sizeof(int32_t)个字节。我们看下面的函数的具体
// 实现的时候可以更好的理解。
ANDROID_API
int add_camera_metadata_entry(camera_metadata_t *dst,
        uint32_t tag,
        const void *data,
        size_t data_count);

下面我们看下增加一个entry的具体实现

// system/media/camera/src/camera_metadata.c
int add_camera_metadata_entry(camera_metadata_t *dst,
        uint32_t tag,
        const void *data,
        size_t data_count) {
	// 很简单,根据tag拿到其存储的value的type,也就是这个type其实是一开始就定义好的
    int type = get_local_camera_metadata_tag_type(tag, dst);
    if (type == -1) {
        ALOGE("%s: Unknown tag %04x.", __FUNCTION__, tag);
        return ERROR;
    }

	// 拿到type之后再继续调用这个函数,其中多了一个type参数
    return add_camera_metadata_entry_raw(dst,
            tag,
            type,
            data,
            data_count);
}

能够根据tag拿到type,说明一开始有些东西是初始化好的,或者说有地方去定义好的,到这里我们有必要再多了解一下这里的tag了。
tag分为系统原生的tag和厂商自己定义的tag(vendor tag),tag是分组的,每一组tag表示同一功能的不同属性,每一个分组我们又叫一个section。

// system/media/camera/include/system/camera_metadata_tags.h
typedef enum camera_metadata_section {
    ANDROID_COLOR_CORRECTION,
    ANDROID_CONTROL,
    ANDROID_DEMOSAIC,
    ANDROID_EDGE,
    ANDROID_FLASH,
    ANDROID_FLASH_INFO,
    ANDROID_HOT_PIXEL,
    ANDROID_JPEG,
    ANDROID_LENS,
    ANDROID_LENS_INFO,
    ANDROID_NOISE_REDUCTION,
    ANDROID_QUIRKS,
    ANDROID_REQUEST,
    ANDROID_SCALER,
    ANDROID_SENSOR,
    ANDROID_SENSOR_INFO,
    ANDROID_SHADING,
    ANDROID_STATISTICS,
    ANDROID_STATISTICS_INFO,
    ANDROID_TONEMAP,
    ANDROID_LED,
    ANDROID_INFO,
    ANDROID_BLACK_LEVEL,
    ANDROID_SYNC,
    ANDROID_REPROCESS,
    ANDROID_DEPTH,
    ANDROID_LOGICAL_MULTI_CAMERA,
    ANDROID_DISTORTION_CORRECTION,
    ANDROID_HEIC,
    ANDROID_HEIC_INFO,
    ANDROID_AUTOMOTIVE,
    ANDROID_AUTOMOTIVE_LENS,
    ANDROID_SECTION_COUNT,
	// 厂商自定义section从这里开始
    VENDOR_SECTION = 0x8000
} camera_metadata_section_t;

为了保证每一个section能容纳足够的tag,系统给每个section预留了64K的空间,从前面我们可以知道tag的类型是uint32_t,也就是32位4字节。

// system/media/camera/include/system/camera_metadata_tags.h
/**
 * Hierarchy positions in enum space. All vendor extension tags must be
 * defined with tag >= VENDOR_SECTION_START
 */
typedef enum camera_metadata_section_start {
    ANDROID_COLOR_CORRECTION_START = ANDROID_COLOR_CORRECTION  << 16,
    ANDROID_CONTROL_START          = ANDROID_CONTROL           << 16,
    ANDROID_DEMOSAIC_START         = ANDROID_DEMOSAIC          << 16,
    ANDROID_EDGE_START             = ANDROID_EDGE              << 16,
    ANDROID_FLASH_START            = ANDROID_FLASH             << 16,
    ANDROID_FLASH_INFO_START       = ANDROID_FLASH_INFO        << 16,
    ANDROID_HOT_PIXEL_START        = ANDROID_HOT_PIXEL         << 16,
    ANDROID_JPEG_START             = ANDROID_JPEG              << 16,
    ANDROID_LENS_START             = ANDROID_LENS              << 16,
    ANDROID_LENS_INFO_START        = ANDROID_LENS_INFO         << 16,
    ANDROID_NOISE_REDUCTION_START  = ANDROID_NOISE_REDUCTION   << 16,
    ANDROID_QUIRKS_START           = ANDROID_QUIRKS            << 16,
    ANDROID_REQUEST_START          = ANDROID_REQUEST           << 16,
    ANDROID_SCALER_START           = ANDROID_SCALER            << 16,
    ANDROID_SENSOR_START           = ANDROID_SENSOR            << 16,
    ANDROID_SENSOR_INFO_START      = ANDROID_SENSOR_INFO       << 16,
    ANDROID_SHADING_START          = ANDROID_SHADING           << 16,
    ANDROID_STATISTICS_START       = ANDROID_STATISTICS        << 16,
    ANDROID_STATISTICS_INFO_START  = ANDROID_STATISTICS_INFO   << 16,
    ANDROID_TONEMAP_START          = ANDROID_TONEMAP           << 16,
    ANDROID_LED_START              = ANDROID_LED               << 16,
    ANDROID_INFO_START             = ANDROID_INFO              << 16,
    ANDROID_BLACK_LEVEL_START      = ANDROID_BLACK_LEVEL       << 16,
    ANDROID_SYNC_START             = ANDROID_SYNC              << 16,
    ANDROID_REPROCESS_START        = ANDROID_REPROCESS         << 16,
    ANDROID_DEPTH_START            = ANDROID_DEPTH             << 16,
    ANDROID_LOGICAL_MULTI_CAMERA_START
                                   = ANDROID_LOGICAL_MULTI_CAMERA
                                                                << 16,
    ANDROID_DISTORTION_CORRECTION_START
                                   = ANDROID_DISTORTION_CORRECTION
                                                                << 16,
    ANDROID_HEIC_START             = ANDROID_HEIC              << 16,
    ANDROID_HEIC_INFO_START        = ANDROID_HEIC_INFO         << 16,
    ANDROID_AUTOMOTIVE_START       = ANDROID_AUTOMOTIVE        << 16,
    ANDROID_AUTOMOTIVE_LENS_START  = ANDROID_AUTOMOTIVE_LENS   << 16,
    VENDOR_SECTION_START           = VENDOR_SECTION            << 16
} camera_metadata_section_start_t;

如上,已知每个tag的类型是4字节32位,那么左移16位后说明其高16位已经是确定的了,那么其一共能容纳的tag的数量就是低16位来决定了,一共就是2的16次方64K,也就是每个section的tag数最大为64K,本世纪应该够用了。当然,每个section不可能64k全定义完,下面我们看一下系统定义好的section。我们看到,每个section都有start和end,当我们要给一个section新加一个tag的时候必须添加在其end的前面。

typedef enum camera_metadata_tag {
    ANDROID_COLOR_CORRECTION_MODE =                   // enum         | public       | HIDL v3.2
            ANDROID_COLOR_CORRECTION_START,
    ANDROID_COLOR_CORRECTION_TRANSFORM,               // rational[]   | public       | HIDL v3.2
    ANDROID_COLOR_CORRECTION_GAINS,                   // float[]      | public       | HIDL v3.2
    ANDROID_COLOR_CORRECTION_ABERRATION_MODE,         // enum         | public       | HIDL v3.2
    ANDROID_COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES,
                                                      // byte[]       | public       | HIDL v3.2
    ANDROID_COLOR_CORRECTION_END,

    ANDROID_CONTROL_AE_ANTIBANDING_MODE =             // enum         | public       | HIDL v3.2
            ANDROID_CONTROL_START,
    ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION,         // int32        | public       | HIDL v3.2
    ANDROID_CONTROL_AE_LOCK,                          // enum         | public       | HIDL v3.2
    ANDROID_CONTROL_AE_MODE,                          // enum         | public       | HIDL v3.2
    ANDROID_CONTROL_AE_REGIONS,                       // int32[]      | public       | HIDL v3.2
    ANDROID_CONTROL_AE_TARGET_FPS_RANGE,              // int32[]      | public       | HIDL v3.2
    ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER,            // enum         | public       | HIDL v3.2
    ANDROID_CONTROL_AF_MODE,                          // enum         | public       | HIDL v3.2
    ANDROID_CONTROL_AF_REGIONS,                       // int32[]      | public       | HIDL v3.2
    ANDROID_CONTROL_AF_TRIGGER,                       // enum         | public       | HIDL v3.2
    ANDROID_CONTROL_AWB_LOCK,                         // enum         | public       | HIDL v3.2
    ANDROID_CONTROL_AWB_MODE,                         // enum         | public       | HIDL v3.2
    ANDROID_CONTROL_AWB_REGIONS,                      // int32[]      | public       | HIDL v3.2
    ANDROID_CONTROL_CAPTURE_INTENT,                   // enum         | public       | HIDL v3.2
    ANDROID_CONTROL_EFFECT_MODE,                      // enum         | public       | HIDL v3.2
    ANDROID_CONTROL_MODE,                             // enum         | public       | HIDL v3.2
    ANDROID_CONTROL_SCENE_MODE,                       // enum         | public       | HIDL v3.2
    ANDROID_CONTROL_VIDEO_STABILIZATION_MODE,         // enum         | public       | HIDL v3.2
    ANDROID_CONTROL_AE_AVAILABLE_ANTIBANDING_MODES,   // byte[]       | public       | HIDL v3.2
    ANDROID_CONTROL_AE_AVAILABLE_MODES,               // byte[]       | public       | HIDL v3.2
    ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES,   // int32[]      | public       | HIDL v3.2
    ANDROID_CONTROL_AE_COMPENSATION_RANGE,            // int32[]      | public       | HIDL v3.2
    ANDROID_CONTROL_AE_COMPENSATION_STEP,             // rational     | public       | HIDL v3.2
    ANDROID_CONTROL_AF_AVAILABLE_MODES,               // byte[]       | public       | HIDL v3.2
    ANDROID_CONTROL_AVAILABLE_EFFECTS,                // byte[]       | public       | HIDL v3.2
    ANDROID_CONTROL_AVAILABLE_SCENE_MODES,            // byte[]       | public       | HIDL v3.2
    ANDROID_CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES,
                                                      // byte[]       | public       | HIDL v3.2
    ANDROID_CONTROL_AWB_AVAILABLE_MODES,              // byte[]       | public       | HIDL v3.2
    ANDROID_CONTROL_MAX_REGIONS,                      // int32[]      | ndk_public   | HIDL v3.2
    ANDROID_CONTROL_SCENE_MODE_OVERRIDES,             // byte[]       | system       | HIDL v3.2
    ANDROID_CONTROL_AE_PRECAPTURE_ID,                 // int32        | system       | HIDL v3.2
    ANDROID_CONTROL_AE_STATE,                         // enum         | public       | HIDL v3.2
    ANDROID_CONTROL_AF_STATE,                         // enum         | public       | HIDL v3.2
    ANDROID_CONTROL_AF_TRIGGER_ID,                    // int32        | system       | HIDL v3.2
    ANDROID_CONTROL_AWB_STATE,                        // enum         | public       | HIDL v3.2
    ANDROID_CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS,
                                                      // int32[]      | hidden       | HIDL v3.2
    ANDROID_CONTROL_AE_LOCK_AVAILABLE,                // enum         | public       | HIDL v3.2
    ANDROID_CONTROL_AWB_LOCK_AVAILABLE,               // enum         | public       | HIDL v3.2
    ANDROID_CONTROL_AVAILABLE_MODES,                  // byte[]       | public       | HIDL v3.2
    ANDROID_CONTROL_POST_RAW_SENSITIVITY_BOOST_RANGE, // int32[]      | public       | HIDL v3.2
    ANDROID_CONTROL_POST_RAW_SENSITIVITY_BOOST,       // int32        | public       | HIDL v3.2
    ANDROID_CONTROL_ENABLE_ZSL,                       // enum         | public       | HIDL v3.2
    ANDROID_CONTROL_AF_SCENE_CHANGE,                  // enum         | public       | HIDL v3.3
    ANDROID_CONTROL_AVAILABLE_EXTENDED_SCENE_MODE_MAX_SIZES,
                                                      // int32[]      | ndk_public   | HIDL v3.5
    ANDROID_CONTROL_AVAILABLE_EXTENDED_SCENE_MODE_ZOOM_RATIO_RANGES,
                                                      // float[]      | ndk_public   | HIDL v3.5
    ANDROID_CONTROL_EXTENDED_SCENE_MODE,              // enum         | public       | HIDL v3.5
    ANDROID_CONTROL_ZOOM_RATIO_RANGE,                 // float[]      | public       | HIDL v3.5
    ANDROID_CONTROL_ZOOM_RATIO,                       // float        | public       | HIDL v3.5
    ANDROID_CONTROL_AVAILABLE_HIGH_SPEED_VIDEO_CONFIGURATIONS_MAXIMUM_RESOLUTION,
                                                      // int32[]      | hidden       | HIDL v3.6
    ANDROID_CONTROL_AF_REGIONS_SET,                   // enum         | fwk_only
    ANDROID_CONTROL_AE_REGIONS_SET,                   // enum         | fwk_only
    ANDROID_CONTROL_AWB_REGIONS_SET,                  // enum         | fwk_only
    ANDROID_CONTROL_END,
    // 此处省略很多tag
    ......
} camera_metadata_tag_t;

每个section中有很多tag,而每个tag所存储的数据类型却不一定是相同的,所以我们必须指定,同时,每个tag都有自己的name,这些信息存储在一个tag_info_t的结构体中。

// system/media/camera/src/camera_metadata.c
typedef struct tag_info {
    const char *tag_name;
    uint8_t     tag_type;
} tag_info_t;

其name和type的初始化如下(我们以ANDROID_COLOR_CORRECTION为例)

// system/media/camera/src/camera_metadata_tag_info.c
// 结构体的大小为end-start,每个tag的下标就是tag-start
static tag_info_t android_color_correction[ANDROID_COLOR_CORRECTION_END -
        ANDROID_COLOR_CORRECTION_START] = {
    // ANDROID_COLOR_CORRECTION_MODE的name是“mode”, 其类型是TYPE_BYTE
    // 下标为0的元素
    [ ANDROID_COLOR_CORRECTION_MODE - ANDROID_COLOR_CORRECTION_START ] =
    { "mode",                          TYPE_BYTE   },
    // ANDROID_COLOR_CORRECTION_TRANSFORM的name是“transform”,类型是TYPE_RATIONAL
    // 下标为1的元素
    [ ANDROID_COLOR_CORRECTION_TRANSFORM - ANDROID_COLOR_CORRECTION_START ] =
    { "transform",                     TYPE_RATIONAL },
    // 下标为2的元素
    [ ANDROID_COLOR_CORRECTION_GAINS - ANDROID_COLOR_CORRECTION_START ] =
    { "gains",                         TYPE_FLOAT  },
    // 下标为3的元素
    [ ANDROID_COLOR_CORRECTION_ABERRATION_MODE - ANDROID_COLOR_CORRECTION_START ] =
    { "aberrationMode",                TYPE_BYTE   },
    // 下标为4的元素
    [ ANDROID_COLOR_CORRECTION_AVAILABLE_ABERRATION_MODES - ANDROID_COLOR_CORRECTION_START ] =
    { "availableAberrationModes",      TYPE_BYTE   },
};

// android_color_correction会被放到一个tag_info_t的二维数组中,如下:
// 这里的顺序跟section定义的顺序必须一致,这样就可以从tag_info中以section为下标
// 找到其信息
tag_info_t *tag_info[ANDROID_SECTION_COUNT] = {
    android_color_correction,
    ......
};
// 这样就可以在tag_info中根据根据

到这里,我们把tag和section相关的介绍完了,那么我们继续分析(为了使大家不用在去前面找,这里再贴一下前面分析到的地方)

// system/media/camera/src/camera_metadata.c
int add_camera_metadata_entry(camera_metadata_t *dst,
        uint32_t tag,
        const void *data,
        size_t data_count) {
    int type = get_local_camera_metadata_tag_type(tag, dst);
    if (type == -1) {
        ALOGE("%s: Unknown tag %04x.", __FUNCTION__, tag);
        return ERROR;
    }

	// 拿到type之后再继续调用这个函数,其中多了一个type参数
    return add_camera_metadata_entry_raw(dst,
            tag,
            type,
            data,
            data_count);
}

// 看下get_local_camera_metadata_tag_type的实现
// system/media/camera/src/camera_metadata.c
int get_local_camera_metadata_tag_type(uint32_t tag,
        const camera_metadata_t *meta) {
    // meta不为NULL,所以id为meta->vendor_id的值
    metadata_vendor_id_t id = (NULL == meta) ? CAMERA_METADATA_INVALID_VENDOR_ID :
            meta->vendor_id;

    return get_local_camera_metadata_tag_type_vendor_id(tag, id);
}

// system/media/camera/src/camera_metadata.c
// 这里假设我们添加的是系统section,非vendor_section
int get_local_camera_metadata_tag_type_vendor_id(uint32_t tag,
        metadata_vendor_id_t id) {
    // tag右移16位得到的是tag所在的section
    uint32_t tag_section = tag >> 16;
    // 下面列出了两种VENDOR_SECTION的场景,这里需要对vendor_cache_ops或者
    // vendor_tag_ops进行初始化,这两个是全局变量,我们下章节详解VENDOR_SECTION。
    //VENDOR_SECTION且设置了vendor_id的话从vendor_cache_ops查找type
    if (tag_section >= VENDOR_SECTION && vendor_cache_ops != NULL &&
                id != CAMERA_METADATA_INVALID_VENDOR_ID) {
            return vendor_cache_ops->get_tag_type(tag, id);
    // 如果是VENDOR_SECTION且vendor_tag_ops不为空,就从vendor_tag_ops里面查找type
    } else if (tag_section >= VENDOR_SECTION && vendor_tag_ops != NULL) {
        return vendor_tag_ops->get_tag_type(
            vendor_tag_ops,
            tag);
    }
    // 超出了section范围
    if (tag_section >= ANDROID_SECTION_COUNT ||
            tag >= camera_metadata_section_bounds[tag_section][1] ) {
        return -1;
    }
    // 这里注意,0xFFFF的高16位全为0,低16位全为1,进行按位与运算后,
    // 就得到了低16位的值,也就是每个tag相对于其section_start的偏移
    uint32_t tag_index = tag & 0xFFFF;
    // tag_section的第tag_index个tag
    return tag_info[tag_section][tag_index].tag_type;
}

根据tag拿到其对应的存储数据的类型的方法就分析完了,我们继续往下分析增加entry的实现

// system/media/camera/src/camera_metadata.c
static int add_camera_metadata_entry_raw(camera_metadata_t *dst,
        uint32_t tag,
        uint8_t  type,
        const void *data,
        size_t data_count) {

    if (dst == NULL) return ERROR;
    // 如果目前已经达到最大容量则返回,因为不会自动扩容
    if (dst->entry_count == dst->entry_capacity) return ERROR;
    // data为空的话也返回,没必要往下加了
    if (data_count && data == NULL) return ERROR;

	// 拿到需要存储的value所占用的字节数
    size_t data_bytes =
            calculate_camera_metadata_entry_data_size(type, data_count);
    // calculate_camera_metadata_entry_data_size start
    size_t calculate_camera_metadata_entry_data_size(uint8_t type,
        	size_t data_count) {
        // 不再定义的类型范围内,也就是不支持此类型
    	if (type >= NUM_TYPES) return 0;
		// data_count乘以type占用的字节数就是整个value占用的字节数
    	size_t data_bytes = data_count *
            	camera_metadata_type_size[type];
		// value所需字节数小于4返回0,到时候存储在data.value里面,否则按8字节对齐返回,
		// 保存在偏移量为offset的data区,
    	return data_bytes <= 4 ? 0 : ALIGN_TO(data_bytes, DATA_ALIGNMENT);
	}
    // calculate_camera_metadata_entry_data_size end
    // data占用的空间超出了分配的最大空间,返回错误
    if (data_bytes + dst->data_count > dst->data_capacity) return ERROR;

	// value真正占用的字节数
    size_t data_payload_bytes =
            data_count * camera_metadata_type_size[type];
    // 新的entry的地址
    camera_metadata_buffer_entry_t *entry = get_entries(dst) + dst->entry_count;
    // 初始化为0
    memset(entry, 0, sizeof(camera_metadata_buffer_entry_t));
    // 赋值
    entry->tag = tag;
    entry->type = type;
    entry->count = data_count;

    if (data_bytes == 0) {
    	// value小于等于4字节,存储到data.value
        memcpy(entry->data.value, data,
                data_payload_bytes);
    } else {
    	// value大于4字节,在data区末尾添加value
        entry->data.offset = dst->data_count;
        memcpy(get_data(dst) + entry->data.offset, data,
                data_payload_bytes);
        // 更新data_count,这里的data_bytes大于等于data_payload_bytes,
        // 因为做了8字节对齐
        dst->data_count += data_bytes;
    }
    // entry_count递增
    dst->entry_count++;
    // 不排序
    dst->flags &= ~FLAG_SORTED;
    // 对齐检查,整个内存必须保证METADATA_ALIGNMENT,ENTRY_ALIGNMENT,
    // DATA_ALIGNMENT都能对齐,否则抛出异常。
    assert(validate_camera_metadata_structure(dst, NULL) == OK);
    return OK;
}

delete_camera_metadata_entry

接着我们来谈谈增删改查中的“删”。

// system/media/camera/include/system/camera_metadata.h
/**
 * Delete an entry at given index. This is an expensive operation, since it
 * requires repacking entries and possibly entry data. This also invalidates any
 * existing camera_metadata_entry.data pointers to this buffer. Sorting is
 * maintained.
 */
ANDROID_API
int delete_camera_metadata_entry(camera_metadata_t *dst,
        size_t index);

从注释中我们可以看出,整个操作是消耗资源比较高的,跟我们正常的数据删除原理类似,你要删除最后一个还好说,如果说要删除中间的某个元素,那么整个内存的后半部分都得前移,所以比较消耗资源。

我们看下其实现

// system/media/camera/src/camera_metadata.c
// 参数index表示要查询的entry是第几个,也就是从entry_start开始
// 的第几个entry
int delete_camera_metadata_entry(camera_metadata_t *dst,
        size_t index) {
    // 参数检查
    if (dst == NULL) return ERROR;
    if (index >= dst->entry_count) return ERROR;

	// 得到要删除的entry地址
    camera_metadata_buffer_entry_t *entry = get_entries(dst) + index;
    // 拿到该entry中的value占用的字节数,这个函数我们前面已经分析过
    size_t data_bytes = calculate_camera_metadata_entry_data_size(entry->type,
            entry->count);
	// 大于0,说明字节数是大于4的,存储在data区段
    if (data_bytes > 0) {
        // Shift data buffer to overwrite deleted data
        // 拿到data的地址
        uint8_t *start = get_data(dst) + entry->data.offset;
        // data结束的地址
        uint8_t *end = start + data_bytes;
        // length为data区段中去除要删除的entry data后剩余的data value占用的字节数
        size_t length = dst->data_count - entry->data.offset - data_bytes;
        // 剩余的data区段向上移动到要删除的entry的offset处,也就是将要删除的entry data
        // 进行了覆盖
        memmove(start, end, length);

        // Update all entry indices to account for shift
        // 很显然的是,data进行了移动,那么对应的entry中的offset字段也得更新。
        // 将每一个在要删除的entry后面的entry的data.offset前移data_bytes
        // 个字节(其data.value大于4字节的,小于4字节的不用处理的,因为不在
        // data区段存储value)
        camera_metadata_buffer_entry_t *e = get_entries(dst);
        size_t i;
        for (i = 0; i < dst->entry_count; i++) {
            if (calculate_camera_metadata_entry_data_size(
                    e->type, e->count) > 0 &&
                    e->data.offset > entry->data.offset) {
                e->data.offset -= data_bytes;
            }
            ++e;
        }
        // 整个的data_count减去data_bytes
        dst->data_count -= data_bytes;
    }
    // Shift entry array
    // 将存储entry的区域,把要删除的entry的后面的entry前移。
    // 如果存储的数据小于等于4字节的话直接执行这里一段就OK了,
    // 那就是只移动entry,因为data区没有存储数据
    memmove(entry, entry + 1,
            sizeof(camera_metadata_buffer_entry_t) *
            (dst->entry_count - index - 1) );
    // entry_cunt减1
    dst->entry_count -= 1;

    assert(validate_camera_metadata_structure(dst, NULL) == OK);
    return OK;
}

删除的代码理解起来比较简单,就是其中的内存数据的移动比较消耗性能。

update_camera_metadata_entry

接着我们来谈谈增删改查中的“改”。

// system/media/camera/include/system/camera_metadata.h
// 如果data的大小不变,其算法复杂度为O(1),否则为O(N)
/**
 * Updates a metadata entry with new data. If the data size is changing, may
 * need to adjust the data array, making this an O(N) operation. If the data
 * size is the same or still fits in the entry space, this is O(1). Maintains
 * sorting, but invalidates camera_metadata_entry instances that point to the
 * updated entry. If a non-NULL value is passed in to entry, the entry structure
 * is updated to match the new buffer state.  Returns a non-zero value if there
 * is no room for the new data in the buffer.
 */
ANDROID_API
int update_camera_metadata_entry(camera_metadata_t *dst,
        size_t index,
        const void *data,
        size_t data_count,
        camera_metadata_entry_t *updated_entry);

看下其实现

// system/media/camera/src/camera_metadata.c
int update_camera_metadata_entry(camera_metadata_t *dst,
        size_t index,
        const void *data,
        size_t data_count,
        camera_metadata_entry_t *updated_entry) {
    if (dst == NULL) return ERROR;
    if (index >= dst->entry_count) return ERROR;

	// 拿到要更新的entry的地址
    camera_metadata_buffer_entry_t *entry = get_entries(dst) + index;

	// 计算出要更新的data的大小,小于等于4返回0,否则内存对齐后返回
    size_t data_bytes =
            calculate_camera_metadata_entry_data_size(entry->type,
                    data_count);
    // 要更新的data的大小的实际大小
    size_t data_payload_bytes =
            data_count * camera_metadata_type_size[entry->type];

	// 目前entry的data占用的大小
    size_t entry_bytes =
            calculate_camera_metadata_entry_data_size(entry->type,
                    entry->count);
    // 更新前后大小不一致
    if (data_bytes != entry_bytes) {
        // May need to shift/add to data array
        // 大了,超出了capacity,返回错误
        if (dst->data_capacity < dst->data_count + data_bytes - entry_bytes) {
            // No room
            return ERROR;
        }
        // 大于4字节的情况
        if (entry_bytes != 0) {
            // Remove old data
            // 这个代码熟悉吗,跟删除操作了删除data的地方一样的
            uint8_t *start = get_data(dst) + entry->data.offset;
            uint8_t *end = start + entry_bytes;
            size_t length = dst->data_count - entry->data.offset - entry_bytes;
            memmove(start, end, length);
            dst->data_count -= entry_bytes;

            // Update all entry indices to account for shift
            // offset更新
            camera_metadata_buffer_entry_t *e = get_entries(dst);
            size_t i;
            for (i = 0; i < dst->entry_count; i++) {
                if (calculate_camera_metadata_entry_data_size(
                        e->type, e->count) > 0 &&
                        e->data.offset > entry->data.offset) {
                    e->data.offset -= entry_bytes;
                }
                ++e;
            }
        }
		
        if (data_bytes != 0) {
            // Append new data
            // 更add entry里的代码又一样了
            // 所以就是进行了一次先删除,后插入的操作
            entry->data.offset = dst->data_count;

            memcpy(get_data(dst) + entry->data.offset, data, data_payload_bytes);
            dst->data_count += data_bytes;
        }
    } else if (data_bytes != 0) {
        // data size unchanged, reuse same data location
        // 如果data大小不变,只更新内存的数据既可以了
        memcpy(get_data(dst) + entry->data.offset, data, data_payload_bytes);
    }

    if (data_bytes == 0) {
        // Data fits into entry
        // 如果小于等于4字节,则更新data.value字段即可
        memcpy(entry->data.value, data,
                data_payload_bytes);
    }

	// 更新count
    entry->count = data_count;

	// 这里的updated_entry是一个出参
	// 不为空的话返回更新后的entry信息
    if (updated_entry != NULL) {
        get_camera_metadata_entry(dst,
                index,
                updated_entry);
    }

    assert(validate_camera_metadata_structure(dst, NULL) == OK);
    return OK;
}

find_camera_metadata_entry

接着我们来谈谈增删改查中的“查”。

// system/media/camera/include/system/camera_metadata.h
// 1. 建议先排序,后查询
// 2. 如果有多个相同的tag存在,则返回哪一个是不确定的
// 3. 返回类型,也就是出参camera_metadata_entry_t跟
// camera_metadata_buffer_entry_t是有区别的
/**
 * Find an entry with given tag value. If not found, returns -ENOENT. Otherwise,
 * returns entry contents like get_camera_metadata_entry.
 *
 * If multiple entries with the same tag exist, does not have any guarantees on
 * which is returned. To speed up searching for tags, sort the metadata
 * structure first by calling sort_camera_metadata().
 */
ANDROID_API
int find_camera_metadata_entry(camera_metadata_t *src,
        uint32_t tag,
        camera_metadata_entry_t *entry);

看下其实现

// system/media/camera/src/camera_metadata.c
int find_camera_metadata_entry(camera_metadata_t *src,
        uint32_t tag,
        camera_metadata_entry_t *entry) {
    if (src == NULL) return ERROR;

    uint32_t index;
    // 如果已经排序了,则做二分查找,
    // 有多个相同tag存在的话,返回哪一个就不确定了
    if (src->flags & FLAG_SORTED) {
        // Sorted entries, do a binary search
        camera_metadata_buffer_entry_t *search_entry = NULL;
        camera_metadata_buffer_entry_t key;
        key.tag = tag;
        search_entry = bsearch(&key,
                get_entries(src),
                src->entry_count,
                sizeof(camera_metadata_buffer_entry_t),
                compare_entry_tags);
        if (search_entry == NULL) return NOT_FOUND;
        index = search_entry - get_entries(src);
    } else {
        // Not sorted, linear search
        // 没有排序,线性查找,这样返回的就是找到的第一个
        camera_metadata_buffer_entry_t *search_entry = get_entries(src);
        for (index = 0; index < src->entry_count; index++, search_entry++) {
            if (search_entry->tag == tag) {
                break;
            }
        }
        if (index == src->entry_count) return NOT_FOUND;
    }
	// 返回,这个entry是出参,这里看下这个实现吧,比较简单
	// get_camera_metadata_entry start
	int get_camera_metadata_entry(camera_metadata_t *src,
	       size_t index,
	        camera_metadata_entry_t *entry) {
	    if (src == NULL || entry == NULL) return ERROR;
	    if (index >= src->entry_count) return ERROR;
	
		// 拿到index对应的entry
	    camera_metadata_buffer_entry_t *buffer_entry = get_entries(src) + index;
	
		// 给camera_metadata_entry_t类型的结构体赋值
	    entry->index = index;
	    entry->tag = buffer_entry->tag;
	    entry->type = buffer_entry->type;
	    entry->count = buffer_entry->count;
	    if (buffer_entry->count *
	            camera_metadata_type_size[buffer_entry->type] > 4) {
	        //此时存储的是地址
	        entry->data.u8 = get_data(src) + buffer_entry->data.offset;
	    } else {
	    	// 此时存储的是数据
	        entry->data.u8 = buffer_entry->data.value;
	    }
	    return OK;
	}
	// get_camera_metadata_entry end
    return get_camera_metadata_entry(src, index,
            entry);
}

以上,增删改查的基本操作就介绍完毕。其实还有很多的函数我们没有讲解,但是如果掌握了上面4个基本操作的话,其他的实现也就很容易理解了。本文中没有介绍vendor_section的地方,是为了先掌握camera metadata的基本原理,后面理解vendor section也就容易了,关于vendor section还是比较重要的,后面会再开一个章节单独介绍。

本节完,谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1022248.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【面试必刷TOP101】删除链表的倒数第n个节点 两个链表的第一个公共结点

目录 题目&#xff1a;删除链表的倒数第n个节点_牛客题霸_牛客网 (nowcoder.com) 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 题目&#xff1a;两个链表的第一个公共结点_牛客题霸_牛客网 (nowcoder.com) …

雷达仿真:FMCW DDMA-MIMO 3D点云获取方法

1.DDMA-MIMO原理 由于TDMA-MIMO采用不同单天线交替发射信号&#xff0c;没有更好的利用发射天线同时工作的发射资源&#xff0c;导致发射功率低以及损耗大&#xff0c;从而使得TDMA波形只能应用在近距离探测的低功率雷达场景。而DDMA波形则能很好的弥补TDMA上述缺点&#xff0c…

为什么用IP访问网站也要使用SSL证书

IP地址SSL证书是一种专门用于公网IP地址验证的数字证书。它可以为公网IP地址提供安全的数据传输保障&#xff0c;解决了IP地址明文传输的安全隐患&#xff0c;保护了IP地址的数据传输安全。 与普通的SSL证书不同&#xff0c;IP地址SSL证书是基于IP地址进行验证的。在申请IP地址…

ssh登录时间久或登陆后报错

情况1 问题描述&#xff1a; ssh登录时间很久&#xff0c;登录后出现abrt-cli status timed out 的报错 问题原因&#xff1a; .lock文件被锁导致 执行systemctl status abrtd.service可以看到被锁的.lock 处理方式&#xff1a; ps -ef | grep pid 找到被锁的进程kill掉…

Java基于SpringBoot的在线考试系统的研究与实现(附源码,教程)

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W,Csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 第一章第二章.主要技术第三章第四章 系统设计4.1功能结构4.2 数据库设计4.2.1 数据库E/R图4.2.2 数…

Windows安装cuda和cudnn教程最新版(2023年9月)

文章目录 cudacudnn cuda 查看电脑的cuda最高驱动版本&#xff08;适用于N卡电脑-Nvidia&#xff09; winR打开命令行&#xff0c;输入nvidia-smi 右上角cuda -version就是目前支持的最高cuda版本 nvidia官网下载cuda 下载地址&#xff1a;https://developer.nvidia.com/cuda…

pip常用指令

文章目录 简介pip的基本用法常用指令查看pip版本安装Python软件包指定版本安装Python软件包pip安装多个Python软件包通过requirements.txt文件批量安装Python软件包pip安装本地whl安装包 下载安装包到本地将已安装的Python软件包信息导出到指定文件中卸载Python软件包查看当前环…

[S2] Challenge 25 心脏病预测

问题 您是一家医疗保健公司的数据科学家&#xff0c;试图创建患者是否患有心脏病的预测因子。目前&#xff0c;您正在试验 11 种不同的特征&#xff08;潜在心脏病指标&#xff09;和 XGBoost 分类模型&#xff0c;您注意到它的性能可能会根据其调整方式而发生很大变化。在此挑…

Git学习笔记3

Git分支管理&#xff1a; 先来考虑一个问题: 开发者A开发软件的某一个功能模块, 还没有开发完成&#xff0c;但害怕进度丢失就提交。假设另一位开发者B并不知道A没有完成, 而直接使用了A开发的文件,这造成了问题。 解决方法: 开发者A创建一个属于自己的分支&#xff0c;这个分…

vite+react 使用 react-activation 实现缓存页面

对应的版本 "react": "^18.2.0", "react-activation": "^0.12.4", "react-dom": "^18.2.0", "react-router-dom": "^6.15.0",react-activation 这是一个npm包&#xff0c;在react keep alive…

Linux服务器查看CPU相关信息

文章目录 一、基本概念cpu个数cpu核数超线程技术 二、查看命令查看CPU型号查看物理CPU个数&#xff08;物理层面&#xff09;查看每个物理CPU中core个数(核数)查看逻辑CPU个数&#xff08;逻辑层面&#xff09; 三、以上概念的关系参考资料 一、基本概念 cpu个数 物理cpu个数…

图神经网络系列之序章

文章目录 一、为什么需要图神经网络&#xff1f;二、图的定义1.图的定义和种类2.一些关于图的重要概念2.1 子图2.2 连通图2.3 顶点的度、入度和出度2.4 边的权和网2.5 稠密图、稀疏图 3.图的存储结构3.1 邻接矩阵3.2 邻接表3.3 边集数组3.4 邻接多重表3.5 十字链表3.6 链式前向…

华为云HECS云服务器docker环境下安装mysql

华为云HECS云服务器&#xff0c;已经安装了docker环境&#xff0c;准备下docker环境下安装mysql。 一、HECS云服务器安装docker 登录华为HECS云服务器&#xff0c;安装docker环境。 安装docker参考如下文章&#xff1a; 华为云HECS安装docker并安装mysql-CSDN博客 二、拉取…

Linux下的Docker安装,以Ubuntu为例

Docker是一种流行的容器化平台&#xff0c;它能够简化应用程序的部署和管理。 Docker安装 1、检查卸载老版本Docker&#xff08;为保证安装正确&#xff0c;尽量在安装前先进行一次卸载&#xff09; apt-get remove docker docker-engine docker.io containerd runc 2、Dock…

【栈与队列面试题】用队列实现栈(动图演示)

两个队列实现一个栈 前言&#xff1a; &#x1f4a5;&#x1f388;个人主页:​​​​​​Dream_Chaser&#xff5e; &#x1f388;&#x1f4a5; ✨✨刷题专栏:http://t.csdn.cn/UlvTc ⛳⛳本篇内容:力扣上栈与队列的面试OJ题目 目录 两个队列实现一个栈 队列的实现&#xf…

Java毕业设计-基于SpingBoot的网上图书商城

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝30W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 文章目录 1. 简介2 技术栈3.1系统功能 4系统设计4.1数据库设计 5系统详细设计5.1系统功能模块5.1系统功能…

Python 图像处理库PIL ImageOps笔记

# 返回一个指定大小的裁剪过的图像。该图像被裁剪到指定的宽高比和尺寸。 # 变量size是要求的输出尺寸&#xff0c;以像素为单位&#xff0c;是一个&#xff08;宽&#xff0c;高&#xff09;元组 # bleed&#xff1a;允许用户去掉图像的边界&#xff08;图像四个边界&#xff…

APP产品经理的职责(合集)

APP产品经理的职责1 职责&#xff1a; 1、根据部门发展规划、主动发掘业务需求&#xff0c;独立负责线上用户产品线的完整业务规划、产品设计、产品管理等工作; 2、负责协调BD、运营、研发、市场等各部门&#xff0c;共同推进新产品开发&#xff0c;确保产品能够保质按时上线…

C语言指针,深度长文全面讲解

指针对于C来说太重要。然而&#xff0c;想要全面理解指针&#xff0c;除了要对C语言有熟练的掌握外&#xff0c;还要有计算机硬件以及操作系统等方方面面的基本知识。所以本文尽可能的通过一篇文章完全讲解指针。 为什么需要指针&#xff1f; 指针解决了一些编程中基本的问题。…

linus调试器---gdb的操作介绍

目录 一.背景 二.gdb的常用的操作介绍 小技巧&#xff1a;gdb会记住上一次的命令&#xff0c;按回车即可打出上次的命令。 1.看代码 2.打断点 3.删断点 4.禁用与开启断点 5.查看断点信息 6.调试 7.调试 8.查看变量 9.运行至某行 10.打印变量值 11.从一断点直接运行…