編輯:關於Android編程
前沿:
在全新的Camera API2架構下,常常會有人疑問再也看不到熟悉的SetParameter/Paramters等相關的身影,取而代之的是一種全新的CameraMetadata結構的出現,他不僅很早就出現在Camera API1/API2結構下的Camera2Device、Camera3Device中用於和HAL3的數據交互,而現在在API2的驅使下都取代了Parameter,實現了Java到native到hal3的參數傳遞。那麼現在假如需要在APP中設置某一項控制參數,對於Camera API2而言,涉及到對Sensor相關參數的set/control時又需要做哪些工作呢?
1. camera_metadata類整體布局結構
主要涉及到的源文件包括camera_metadata_tags.h,camera_metadata_tag_info.c,CameraMetadata.cpp,camera_metadata.c。對於每個Metadata數據,其通過不同業務控制需求,將整個camera工作需要的參數劃分成多個不同的Section,其中在camera_metadata_tag_info.c表定義了所有Camera需要使用到的Section段的Name:
const char *camera_metadata_section_names[ANDROID_SECTION_COUNT] = { [ANDROID_COLOR_CORRECTION] = android.colorCorrection, [ANDROID_CONTROL] = android.control, [ANDROID_DEMOSAIC] = android.demosaic, [ANDROID_EDGE] = android.edge, [ANDROID_FLASH] = android.flash, [ANDROID_FLASH_INFO] = android.flash.info, [ANDROID_GEOMETRIC] = android.geometric, [ANDROID_HOT_PIXEL] = android.hotPixel, [ANDROID_HOT_PIXEL_INFO] = android.hotPixel.info, [ANDROID_JPEG] = android.jpeg, [ANDROID_LENS] = android.lens, [ANDROID_LENS_INFO] = android.lens.info, [ANDROID_NOISE_REDUCTION] = android.noiseReduction, [ANDROID_QUIRKS] = android.quirks, [ANDROID_REQUEST] = android.request, [ANDROID_SCALER] = android.scaler, [ANDROID_SENSOR] = android.sensor, [ANDROID_SENSOR_INFO] = android.sensor.info, [ANDROID_SHADING] = android.shading, [ANDROID_STATISTICS] = android.statistics, [ANDROID_STATISTICS_INFO] = android.statistics.info, [ANDROID_TONEMAP] = android.tonemap, [ANDROID_LED] = android.led, [ANDROID_INFO] = android.info, [ANDROID_BLACK_LEVEL] = android.blackLevel, };
對於每個Section端而言,其都占據一個索引區域section_bounds,比如ANDROID_CONTROL Section他所代表的control區域是從ANDROID_CONTROL_START到ANDROID_CONTROL_END之間,且每個Section所擁有的Index范圍理論最大可到(1 << 16)大小,完全可以滿足統一Section下不同的控制參數的維護。
以ANDROID_CONTROL為列,他的Section index = 1,即對應的section index區間可到(1<<16,2<<16),但一般以實際section中維護的tag的數量來結束,即ANDROID_CONTROL_END決定最終的section index區間。對於每一個section,其下具備不同數量的tag,這個tag是一個指定section下的index值,通過該值來維護一個tag所在的數據區域,此外每個tag都有相應的string name,在camera_metadata_tag_info.c通過struct tag_info_t來維護一個tag的相關屬性:
typedef struct tag_info { const char *tag_name; uint8_t tag_type; } tag_info_t;其中tag_name為對應section下不同tag的name值 ,tag_type指定了這個tag所維護的數據類型,包括如下:
enum { // Unsigned 8-bit integer (uint8_t) TYPE_BYTE = 0, // Signed 32-bit integer (int32_t) TYPE_INT32 = 1, // 32-bit float (float) TYPE_FLOAT = 2, // Signed 64-bit integer (int64_t) TYPE_INT64 = 3, // 64-bit float (double) TYPE_DOUBLE = 4, // A 64-bit fraction (camera_metadata_rational_t) TYPE_RATIONAL = 5, // Number of type fields NUM_TYPES };對每一個section所擁有的tag_info信息,通過全局結構體tag_info_t *tag_info[ANDROID_SECTION_COUNT] 來定義。
下圖是對整個Camera Metadata對不同section以及相應section下不同tag的布局圖,下圖以最常見的android.control Section為例進行了描述:
2. CameraMetadata通過camera_metadata來維護數據信息
假設現在存在一個CameraMetadata對象,那麼他是如何將一個tag標記的參數維護起來的呢?
CameraMetadata::CameraMetadata(size_t entryCapacity, size_t dataCapacity) : mLocked(false) { mBuffer = allocate_camera_metadata(entryCapacity, dataCapacity); }
camera_metadata_t *allocate_camera_metadata(size_t entry_capacity, size_t data_capacity) { if (entry_capacity == 0) return NULL; size_t memory_needed = calculate_camera_metadata_size(entry_capacity, data_capacity); void *buffer = malloc(memory_needed); return place_camera_metadata(buffer, memory_needed, entry_capacity, data_capacity); }一個CameraMetadata數據內存塊中組成的最小基本單元是struct camera_metadata_buffer_entry,總的entry數目等信息需要struct camera_metadata_t來維護:
struct camera_metadata { size_t size; uint32_t version; uint32_t flags; size_t entry_count;//當前實際的entry數目 size_t entry_capacity;//entry最大可以存儲的數目 uptrdiff_t entries_start; // Offset from camera_metadata size_t data_count;//當前占據的數據空間 size_t data_capacity;//最大可操作的數據容量 uptrdiff_t data_start; // Offset from camera_metadata,大容量數據存儲的起始地址 void *user; // User set pointer, not copied with buffer uint8_t reserved[0]; };
對於每一個entry主要記錄他的所代表的TAG,以及這個TAG的需要存儲的數據類型,此外還需要記錄這個entry是否是需要一個union offset來表示他當前數據量過大時的數據存儲位置,
typedef struct camera_metadata_buffer_entry { uint32_t tag;//表示當時這個entry代表的tag值,即上文提到的section中不同的tag index值 size_t count; union { size_t offset; uint8_t value[4]; } data;//如果存儲的數據量不大於4則直接存儲。否則需要指點一個offset來表示便宜 uint8_t type;//維護的數據類型 uint8_t reserved[3]; } camera_metadata_buffer_entry_t;
3. update更新並建立參數
CameraMetadata支持不同類型的數據更新或者保存到camera_metadata_t中tag所在的entry當中去,以一個更新單字節的數據為例,data_count指定了數據的個數,而tag指定了要更新的entry。
status_t CameraMetadata::update(uint32_t tag, const uint8_t *data, size_t data_count) { status_t res; if (mLocked) { ALOGE(%s: CameraMetadata is locked, __FUNCTION__); return INVALID_OPERATION; } if ( (res = checkType(tag, TYPE_BYTE)) != OK) { return res; } return updateImpl(tag, (const void*)data, data_count); }首先是通過checkType,主要是通過tag找到get_camera_metadata_tag_type其所應當支持的tag_type(因為具體的TAG是已經通過camera_metadata_tag_info.c源文件中的tag_info這個表指定了其應該具備的tag_type),比較兩者是否一致,一致後才允許後續的操作,如這裡需要TYPE_BYTE一致。
updataImpl函數主要是講所有要寫入的數據進行update操作。
status_t CameraMetadata::updateImpl(uint32_t tag, const void *data, size_t data_count) { status_t res; if (mLocked) { ALOGE(%s: CameraMetadata is locked, __FUNCTION__); return INVALID_OPERATION; } int type = get_camera_metadata_tag_type(tag); if (type == -1) { ALOGE(%s: Tag %d not found, __FUNCTION__, tag); return BAD_VALUE; } size_t data_size = calculate_camera_metadata_entry_data_size(type, data_count); res = resizeIfNeeded(1, data_size);//新建camera_metadata_t if (res == OK) { camera_metadata_entry_t entry; res = find_camera_metadata_entry(mBuffer, tag, &entry); if (res == NAME_NOT_FOUND) { res = add_camera_metadata_entry(mBuffer, tag, data, data_count);//將當前新的tag以及數據加入到camera_metadata_t } else if (res == OK) { res = update_camera_metadata_entry(mBuffer, entry.index, data, data_count, NULL); } } if (res != OK) { ALOGE(%s: Unable to update metadata entry %s.%s (%x): %s (%d), __FUNCTION__, get_camera_metadata_section_name(tag), get_camera_metadata_tag_name(tag), tag, strerror(-res), res); } IF_ALOGV() { ALOGE_IF(validate_camera_metadata_structure(mBuffer, /*size*/NULL) != OK, %s: Failed to validate metadata structure after update %p, __FUNCTION__, mBuffer); } return res; }主要分為以下幾個過程:
a.通過tag_type存儲的數據類型,由calculate_camera_metadata_entry_data_size計算要寫入的entry中的數據量。
b. resizeIfNeeded通過已有entry的數量等,增加entry_capacity,或者重建整個camera_metadata_t,為後續增加數據創建內存空間基礎。
c. 通過find_camera_metadata_entry獲取一個entry的入口camera_metadata_entry_t,如果存在這個tag對應的entry,則將camera_metadata_buffer_entry_t的屬性信息轉為camera_metadata_entry_t。
typedef struct camera_metadata_entry { size_t index;//在當前的entry排序中,其所在的index值 uint32_t tag; uint8_t type; size_t count; union { uint8_t *u8; int32_t *i32; float *f; int64_t *i64; double *d; camera_metadata_rational_t *r; } data;//針對不同數據類型,u8表示數據存儲的入口地址,不大於4字節即為value[4]. } camera_metadata_entry_t;d .add_camera_metadata_entry完成全新的entry更新與寫入,即這個TAG目前不存在於這個camera_metadata_t中;update_camera_metadata_entry則是直接完成數據的更新。
3. Java層中CameraMetadata.java和CameraMetadataNative.java
下面以API2中java層中設置AF的工作模式為例,來說明這個參數設置的過程:
mPreviewBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);其中CONTROL_AF_MODE定義在CaptureRequest,java中如下以一個Key的形式存在:
public static final KeyCONTROL_AF_MODE = new Key (android.control.afMode, int.class);
public Key(String name, Class在CameraMetadataNative.java中Key的構造type) { mKey = new CameraMetadataNative.Key (name, type); }
public Key(String name, Classtype) { if (name == null) { throw new NullPointerException(Key needs a valid name); } else if (type == null) { throw new NullPointerException(Type needs to be non-null); } mName = name; mType = type; mTypeReference = TypeReference.createSpecializedTypeReference(type); mHash = mName.hashCode() ^ mTypeReference.hashCode(); }
其中CONTROL_AF_MODE_CONTINUOUS_PICTURE定義在CameraMetadata.java中
public static final int CONTROL_AF_MODE_CONTINUOUS_PICTURE = 4;逐一定位set的入口:
a. mPreviewBuilder是CaptureRequest.java的build類,其會構建一個CaptureRequest
public Builder(CameraMetadataNative template) { mRequest = new CaptureRequest(template); }
private CaptureRequest() { mSettings = new CameraMetadataNative(); mSurfaceSet = new HashSetmSetting建立的是一個CameraMetadataNative對象,主要用於和Native層進行接口交互,構造如下(); }
public CameraMetadataNative() { super(); mMetadataPtr = nativeAllocate(); if (mMetadataPtr == 0) { throw new OutOfMemoryError(Failed to allocate native CameraMetadata); } }
b. CaptureRequest.Build.set()
publicvoid set(Key key, T value) { mRequest.mSettings.set(key, value); }
public考慮到CaptureRequest extend CameraMetadata,則CaptureRequest.java中getNativeKeyvoid set(CaptureRequest.Key key, T value) { set(key.getNativeKey(), value); }
public CameraMetadataNative.KeymKey即為之前構造的CameraMetadataNative.Key.getNativeKey() { return mKey; }
publicvoid set(Key key, T value) { SetCommand s = sSetCommandMap.get(key); if (s != null) { s.setValue(this, value); return; } setBase(key, value); }
privatevoid setBase(Key key, T value) { int tag = key.getTag(); if (value == null) { // Erase the entry writeValues(tag, /*src*/null); return; } // else update the entry to a new value Marshaler marshaler = getMarshalerForKey(key); int size = marshaler.calculateMarshalSize(value); // TODO: Optimization. Cache the byte[] and reuse if the size is big enough. byte[] values = new byte[size]; ByteBuffer buffer = ByteBuffer.wrap(values).order(ByteOrder.nativeOrder()); marshaler.marshal(value, buffer); writeValues(tag, values); }
首先來看key.getTag()函數的實現,他是將這個key交由Native層後轉為一個真正的在Java層中的tag值:
public final int getTag() { if (!mHasTag) { mTag = CameraMetadataNative.getTag(mName); mHasTag = true; } return mTag; }
public static int getTag(String key) { return nativeGetTagFromKey(key); }是將Java層的String交由Native來轉為一個Java層的tag值。
再來看writeValues的實現,同樣調用的是一個native接口,很好的闡明了CameraMetadataNative的意思:
public void writeValues(int tag, byte[] src) { nativeWriteValues(tag, src); }
相關native層的實現在下一小節說明。
4. Native層的CameraMetadata結構完成camera參數的傳遞
在描述萬了CameraMetadata數據的相關操作之後,可明確的一點是SECTION下的TAG是操作他的核心所在。
這裡先說明一個在API1 Camera2Client 參數傳遞的過程,他采用的邏輯是還是在Java層預留了setParameters接口,只是當Parameter在設置時比起CameraClient而言,他是將這個Parameter根據不同的TAG形式直接綁定到CameraMetadata mPreviewRequest/mRecordRequest/mCaptureRequest中,這些數據會由Capture_Request轉為camera3_capture_request中的camera_metadata_t settings完成參數從Java到native到HAL3的傳遞。
但是在Camera API2下,不再需要那麼復雜的轉換過程,在Java層中直接對參數進行設置並將其封裝到Capture_Request即可,即參數控制由Java層來完成。這也體現了API2中Request和Result在APP中就大量存在的原因。對此為了和Framework Native層相關TAG數據的統一,在Java層中大量出現的參數設置是通過Section Tag的name來交由Native完成轉換生成在Java層的TAG。
對於第三小節中提到的native層的實現,其對應的實現函數位於android_hardware_camera2_CameraMetadata.c中,如CameraMetadata_getTagFromKey是實現將一個Java層的string轉為一個tag的值,他的主要原理如下:根據傳入的key string值本質是由一個字符串組成的如上文中提到的android.control.mode。對比最初不同的Section name就可以發現前面兩個x.y的字符串就是代表是Section name.而後面mode即是在該section下的tag數值,所以通過對這個string的分析可知,就可以定位他的section以及tag值。這樣返回到Java層的就是key相應的tag值了。
如果要寫數據,那麼在native同樣需要一個CameraMetadata對象,這裡是在Java構造CameraMetadataNative時實現的,調用的native接口是nativeAllocate():
static jlong CameraMetadata_allocate(JNIEnv *env, jobject thiz) { ALOGV(%s, __FUNCTION__); return reinterpret_cast最終可以明確的是CameraMetadata相關的參數是被Java層來set/get,但本質是在native層進行了實現,後續如果相關控制參數是被打包到CaptureRequest中時傳入到native時即操作的還是native中的CameraMetadata。(new CameraMetadata()); }
一、概述 這一章先來點有意思的百度地圖應用示例,然後再分章詳細介紹用C#開發Android App的各種基本技術。 本章以百度官網2016年1月發布的
最近也想搞下toolbar+drawerlayout的使用。結合網絡上各大神的傑作,我把大部分的內容效果都完成了遍。現在記錄下各個功能效果的實現以及一些細節注意點。 這圖
快捷鍵截圖:
開篇先介紹幾個放在眼前卻經常忽視的快捷鍵如圖:展現出android Studio超強的搜索能力,提高大工程的開發維護效率。雙擊Shift按鍵效果Ctrl+Shift+N