Android 13 CameraMetadata详解1 (内存分布以及增删改查)_android metadata 内存-程序员宅基地

技术标签: android  智能手机  # Android Camera  Android系统开发  


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

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

简介

初识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还是比较重要的,后面会再开一个章节单独介绍。

本节完,谢谢!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_41678668/article/details/132864332

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf

推荐文章

热门文章

相关标签