Camera Metadata原理

概述

在Camera2 + HAL3的架構中,App --> Framework --> HAL通過metadata的方式來傳遞參數。metadata實際上就是一個參數對(key, value),比如設置AE mode爲auto,傳下來的是類似(10,1)這樣的參數對(AE mode的key爲10,參數auto的value爲1)。然後在HAL中通過10來獲取設置值1。

本文通過剖析metadata的數據結構以及一些關鍵函數調用來探索metadata的原理。

相關文件路徑

/system/media/camera/include/system/camera_metadata_tags.h
/system/media/camera/src/camera_metadata.c
/system/media/camera/src/camera_metadata_tag_info.c

數據結構

在頭文件 system/media/camera/include/system/camera_metadata.h 有如下定義:

struct camera_metadata;
typedef struct camera_metadata camera_metadata_t;

camera_metadata_t 就是外部訪問使用metadata的結構體。

這裏有一個很有意思的用法:camera_metadata_t 的類型實際上是 camera_metadata,而 camera_metadata 是在 camera_metadata.c 中實現的。
外部文件只能inlcude頭文件camera_metadata.h,這意味着如果外部只能看到camera_metadata_t,不能看到camera_metadata。所以無法直接訪問camera_metadata的成員。
操控camera_metadata的唯一方式就是通過調用camera_metadata.h提供的函數接口。通過這種方式,實現了C語言的封裝功能。

描述camera metadata的數據結構如下:
(1) struct camera_metadata;

typedef uint32_t metadata_uptrdiff_t;
typedef uint32_t metadata_size_t;
typedef uint64_t metadata_vendor_id_t;

struct camera_metadata {
    metadata_size_t          size;				// metadata的總大小
    uint32_t                 version;			// CURRENT_METADATA_VERSION,一般是1
    uint32_t                 flags;             // FLAG_SORTED,標記當前是否有對entry進行排序(根據entry的tag從小到大)。好處:排序後可以使用二分查找,可以提升性能。
    metadata_size_t          entry_count;       // 當前entry的數量,初始化爲0
    metadata_size_t          entry_capacity;    // entry的最大數量
    metadata_uptrdiff_t      entries_start; 	// entry的起始地址(類型爲:camera_metadata_buffer_entry)
    metadata_size_t          data_count;		// 當前data的數量(類型爲uint8),初始化爲0
    metadata_size_t          data_capacity;		// data的最大數量
    metadata_uptrdiff_t      data_start; 		// data的起始地址
    uint32_t                 padding;    		// 8字節對齊,不夠就填充到這
    metadata_vendor_id_t     vendor_id;			// 標記平臺的id,default值爲CAMERA_METADATA_INVALID_ID
};

(2) struct camera_metadata_buffer_entry;

typedef struct camera_metadata_buffer_entry {
    uint32_t tag;								// tag的key值
    uint32_t count;								// tag的value對應的data的數量。比如data的類型爲uint8_t ,count爲100。則總共100個字節。
    union {
        uint32_t offset;						// offset標記當前的key值對應的value在data_start中的位置
        uint8_t  value[4];						// 當value佔用的字節數<=4時,直接存儲到這裏(省空間)
    } data;										
    uint8_t  type;								// TYPE_BYTE、TYPE_INT32、TYPE_FLOAT、TYPE_INT64、TYPE_DOUBLE、TYPE_RATIONAL
    uint8_t  reserved[3];
} camera_metadata_buffer_entry_t;

(3) struct camera_metadata_entry ;

typedef struct camera_metadata_entry {
    size_t   index;								// 該entry在當前metadta裏面的index(0 ~ entry_count-1)
    uint32_t tag;								// tag的key值
    uint8_t  type;								// TYPE_BYTE、TYPE_INT32、TYPE_FLOAT、TYPE_INT64、TYPE_DOUBLE、TYPE_RATIONAL
    size_t   count;								// tag的value對應的data的數量。比如data的類型爲uint8_t ,count爲100。則總共100個字節。
    union {
        uint8_t *u8;
        int32_t *i32;
        float   *f;
        int64_t *i64;
        double  *d;
        camera_metadata_rational_t *r;
    } data;										// tag的value對應的data值
} camera_metadata_entry_t;

下圖比較直觀總結出三個結構體之間的關係:
在這裏插入圖片描述
metadata的基本操作就是增(增加tag)、刪(刪除tag)、查(根據tag查找對應的value)、改(修改tag對應的value)。到這裏metadata的原理基本上可以推導出來了,以“查”爲例:
(1) 當用戶拿到 camera_metadata 以及對應的tag後,需要從該meta中,找到對應的value。
(2) 從metadata的 entries_start 成員中可以拿到entry的首地址,再根據 entry_count 可以遍歷所有的entry。
(3) 根據tag來逐一比較camera_metadata_buffer_entry中的 tag 。就可以找到該tag對應的entry。
(4) 根據 counttype 可以計算出value的字節數。當字節數<=4的時候,直接取 data.value;否則就根據 offset 從metadata的 data_start 找到對應的value。
(5) 將其轉換爲結構體 camera_metadata_entry_t,返回給用戶。用戶通過count和type就可以找到該tag對應的value啦。

PS:這裏有一個非常重要也非常容易出bug的地方,就是返回的entry,裏面的data指向的是實際數據的地址。
所以如果直接改寫data裏面的內容,會覆蓋之前的數據。 一定要記得做memcpy。

metadata關鍵函數接口

如前文所提到,用戶只能通過調用函數接口,來訪問camera_metadata_t裏面的內容。函數接口實現的源碼位於:system/media/camera/src/camera_metadata.c。這個文件有上千行,這裏僅提到幾個關鍵的函數。

allocate_camera_metadata (分配metadata)

// 傳入max entry和max data,給metadata分配地址空間
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); 	// 1. 計算size
    void *buffer = calloc(1, memory_needed);											  	// 2. 分配memory
    camera_metadata_t *metadata = place_camera_metadata(									// 3. 生成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;
}
size_t calculate_camera_metadata_size(size_t entry_count,
                                      size_t data_count) {
    size_t memory_needed = sizeof(camera_metadata_t);
    // Start entry list at aligned boundary
    memory_needed = ALIGN_TO(memory_needed, ENTRY_ALIGNMENT);
    memory_needed += sizeof(camera_metadata_buffer_entry_t[entry_count]);
    // Start buffer list at aligned boundary
    memory_needed = ALIGN_TO(memory_needed, DATA_ALIGNMENT);
    memory_needed += sizeof(uint8_t[data_count]);
    // Make sure camera metadata can be stacked in continuous memory
    memory_needed = ALIGN_TO(memory_needed, METADATA_PACKET_ALIGNMENT);
    return 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;

    size_t memory_needed = calculate_camera_metadata_size(entry_capacity,
                                                          data_capacity);
    if (memory_needed > dst_size) return NULL;

    camera_metadata_t *metadata = (camera_metadata_t*)dst;
    metadata->version = CURRENT_METADATA_VERSION;
    metadata->flags = 0;
    metadata->entry_count = 0;
    metadata->entry_capacity = entry_capacity;
    metadata->entries_start =
            ALIGN_TO(sizeof(camera_metadata_t), ENTRY_ALIGNMENT);
    metadata->data_count = 0;
    metadata->data_capacity = data_capacity;
    metadata->size = memory_needed;
    size_t data_unaligned = (uint8_t*)(get_entries(metadata) +
            metadata->entry_capacity) - (uint8_t*)metadata;
    metadata->data_start = ALIGN_TO(data_unaligned, DATA_ALIGNMENT);
    metadata->vendor_id = CAMERA_METADATA_INVALID_VENDOR_ID;

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

find_camera_metadata_entry(從metadata中根據tag查找value)

這個函數的基本邏輯可以參考上文描述從metadata中“查”tag的邏輯。

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;
    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;
    }

    return get_camera_metadata_entry(src, index,
            entry);                                                         
}
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;

    camera_metadata_buffer_entry_t *buffer_entry = get_entries(src) + index;

    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;
}

add_camera_metadata_entry(增加tag和value到metadata)

int add_camera_metadata_entry(camera_metadata_t *dst, uint32_t tag, const void *data, size_t data_count) {
    // 1.根據tag,找到該tag對應的value的type。這個函數的具體實現不再粘貼出來,裏面涉及到tag section相關結構體,後文描述
    int type = get_local_camera_metadata_tag_type(tag, dst);		
    if (type == -1) {
        ALOGE("%s: Unknown tag %04x.", __FUNCTION__, tag);
        return ERROR;
    }
	// 2.將tag和data添加到metadata中。
    return add_camera_metadata_entry_raw(dst, tag, type, data, data_count);	
}
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;
    if (data_count && data == NULL) return ERROR;

	// 1. 計算size,並進行4字節判斷。如果小於4字節,將返回0。
    size_t data_bytes = calculate_camera_metadata_entry_data_size(type, data_count);
    if (data_bytes + dst->data_count > dst->data_capacity) return ERROR;

	// 2. 計算數據的size
    size_t data_payload_bytes = data_count * camera_metadata_type_size[type];

	// 3. 生成camera_metadata_buffer_entry_t
    camera_metadata_buffer_entry_t *entry = get_entries(dst) + dst->entry_count;
    memset(entry, 0, sizeof(camera_metadata_buffer_entry_t));
    entry->tag = tag;
    entry->type = type;
    entry->count = data_count;

	// 4. copy數據到entry中
    if (data_bytes == 0) {
        memcpy(entry->data.value, data, data_payload_bytes);
    } else {
        entry->data.offset = dst->data_count;
        memcpy(get_data(dst) + entry->data.offset, data,
                data_payload_bytes);
        dst->data_count += data_bytes;
    }

	// 5. 增加一個entry
    dst->entry_count++;

	
    dst->flags &= ~FLAG_SORTED;	// add後,是沒有經過排序的
    assert(validate_camera_metadata_structure(dst, NULL) == OK);
    return OK;
}

size_t calculate_camera_metadata_entry_data_size(uint8_t type,
        size_t data_count) {
    if (type >= NUM_TYPES) return 0;

    size_t data_bytes = data_count *
            camera_metadata_type_size[type];

    return data_bytes <= 4 ? 0 : ALIGN_TO(data_bytes, DATA_ALIGNMENT);
}

delete_camera_metadata_entry(刪除tag)

刪除的邏輯相對有點複雜,因爲tag對應的value可能在data數組的中間,需要後面的內容,覆蓋要刪除的內容。

int delete_camera_metadata_entry(camera_metadata_t *dst, size_t index) {
    if (dst == NULL) return ERROR;
    if (index >= dst->entry_count) return ERROR;

	// 1. 根據index,找到對應的entry
    camera_metadata_buffer_entry_t *entry = get_entries(dst) + index;

	// 2. 獲取value的size
    size_t data_bytes = calculate_camera_metadata_entry_data_size(entry->type,
            entry->count);

    if (data_bytes > 0) {
		// 3. data_bypes > 0,value的size>4字節,所以存儲的data數組中
		// 這裏開始對data數組的內容進行memmove

        // Shift data buffer to overwrite deleted data
        uint8_t *start = get_data(dst) + entry->data.offset;
        uint8_t *end = start + data_bytes;
        size_t length = dst->data_count - entry->data.offset - data_bytes;  // data_count是數組總長度,offset是value的起始位置,data_types是value的長度。相減就是value後面的數據的長度
        memmove(start, end, length); 	// value後面的數據向前移動到start位置,從end開始計算length個字節

		// 4. 更新當前tag之後的entry的offset
        // Update all entry indices to account for shift
        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;
        }
        dst->data_count -= data_bytes;
    }

	// 5. 移動entry
    // Shift entry array
    memmove(entry, entry + 1, sizeof(camera_metadata_buffer_entry_t) * (dst->entry_count - index - 1) );
    dst->entry_count -= 1;

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

size_t calculate_camera_metadata_entry_data_size(uint8_t type,
        size_t data_count) {
    if (type >= NUM_TYPES) return 0;

    size_t data_bytes = data_count *
            camera_metadata_type_size[type];

    return data_bytes <= 4 ? 0 : ALIGN_TO(data_bytes, DATA_ALIGNMENT);
}

update_camera_metadata_entry(更新tag的value值)

在調用 update_camera_metadata_entry() 更新tag前,一定要通過 find_camera_metadata_entry() 找到對應的entry,通過該entry獲取其index(即entry在metadata的index)。
PS:參數updated_entry,是用於獲取update之後的tag。

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;

	// 1. 根據index找到對應的entry
    camera_metadata_buffer_entry_t *entry = get_entries(dst) + index;

	// 2. data_bytes是新的value的size,如果小於4,就是0; 
	//    data_payload_bytes是新的value真正的size;
	//    entry_bytes是就的value的size
    size_t data_bytes = calculate_camera_metadata_entry_data_size(entry->type, data_count);
    size_t data_payload_bytes = data_count * camera_metadata_type_size[entry->type];
    size_t entry_bytes = calculate_camera_metadata_entry_data_size(entry->type, entry->count);
    
    if (data_bytes != entry_bytes) {
		// 新的value和舊的value的size不同時,需要進行下述操作

		// 3. 確定data的容量是否可以滿足新的value
        // May need to shift/add to data array
        if (dst->data_capacity < dst->data_count + data_bytes - entry_bytes) {
            // No room
            return ERROR;
        }

		// 4. 刪除舊的tag對應的value,實現類似delete函數
        if (entry_bytes != 0) {
            // Remove old 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
            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;
            }
        }

		// 5. 將新的tag對應的value插入到最後方
        if (data_bytes != 0) {
            // Append new data
            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) {
		// 6. data的size相等時直接override

        // data size unchanged, reuse same data location
        memcpy(get_data(dst) + entry->data.offset, data, data_payload_bytes);
    }

    if (data_bytes == 0) {
        // Data fits into entry
        memcpy(entry->data.value, data,
                data_payload_bytes);
    }

    entry->count = data_count;

    if (updated_entry != NULL) {
        get_camera_metadata_entry(dst,
                index,
                updated_entry);
    }

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

tag分類

tag從歸屬方可以被分類兩類:(1) android平臺原生tag。如ANDROID_CONTROL_AE_MODE用於控制AE曝光方式(auto、manual等)。 (2) vendor tag(platfrom如Qcom/SumSung/MTK新增tag)。

android tag

tag是通過section的方式來進行分類的,如下:

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_SECTION_COUNT,

    VENDOR_SECTION = 0x8000
} camera_metadata_section_t;

上面都是android原生tag的section,每一個section支持的tag總數最大是65536(1<<16)。

vendor tag

vendor tag必須從0x8000000開始使用

tag 命名

從上文可以瞭解到,tag實際上是一個UINT32的key值。如果在coding的時候,總是使用一個UINT32類型的數字是描述tag,是一件非常難以容忍的事情。

實際上tag是有char型的名字的,平時使用的時候,對於android原生tag,既可以使用char型的名字來獲取tag,也可以直接使用宏。但是對於平臺定義的vendor tag,基本山都是使用tag name的方式來讀寫tag。

system/media/camera/src/camera_metadata_tag_info.c 定義了android原生tag name和type。

hardware/libhardware/modules/camera/CameraHAL.cpp 有一個deamon,描述如何在HAL實現vendor tag。這裏不再贅述。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章