代码路径:frameworks\av\media\libstagefright\foundation\MetaData.cpp
这个是在学习NuPlayer的时候看到的,觉得有必要记录一下,积累C++数据存储的技巧;在NuPlayer的getFrameRate()函数中,用到了MetaData,所以从这里开始记录:
float NuPlayer::getFrameRate() {
sp<MetaData> meta = mSource->getFormatMeta(false /* audio */);
if (meta == NULL) {
return 0;
}
int32_t rate;
if (!meta->findInt32(kKeyFrameRate, &rate)) {
// fall back to try file meta
sp<MetaData> fileMeta = getFileMeta();
if (fileMeta == NULL) {
ALOGW("source has video meta but not file meta");
return -1;
}
int32_t fileMetaRate;
if (!fileMeta->findInt32(kKeyFrameRate, &fileMetaRate)) { //通过findInt32函数,查找键kKeyFrameRate对应的值
return -1;
}
return fileMetaRate;
}
return rate;
}
下面来看,MetaData是如何存储/获取值的:
先来看findInt32对应的存储方法MetaData::setInt32
bool MetaData::setInt32(uint32_t key, int32_t value) {
return setData(key, TYPE_INT32, &value, sizeof(value));
}
bool MetaData::setData(
uint32_t key, uint32_t type, const void *data, size_t size) {
bool overwrote_existing = true;
ssize_t i = mItems.indexOfKey(key); //检查key是否已存在,不存在的话,创建一个添加进入
if (i < 0) {
typed_data item;
i = mItems.add(key, item); //添加一个新的item
overwrote_existing = false;
}
typed_data &item = mItems.editValueAt(i); //获取刚才添加好的item的引用,并进行编辑复制操作
item.setData(type, data, size);
return overwrote_existing;
}
以上代码有两个知识点:
1. 从上面申明item可以知道item是typed_data类型,而typed_data是类MetaData中的一个结构体,这个结构体专门用来保存数据
2. setData()的最后一个参数是size_t,传入的是value值的字节大小;可以得知C++存储数据时,需要考虑存储数据的字节数
下面来看item.setData(type, data, size)这个方法的实现,代码是在结构体typed_data中:
void MetaData::typed_data::setData(uint32_t type, const void *data, size_t size) {
clear(); //clear()的作用稍后再说明
mType = type; //type保存在成员变量中
void *dst = allocateStorage(size); //申请size大小的内存,这是明白了size的作用
if (dst) {
memcpy(dst, data, size); //将data地址的数据copy size字节到dts地址中去
}
}
以上有两个函数clear()/allocateStorage(),需要继续跟踪以明白它是如何实现的
void *MetaData::typed_data::allocateStorage(size_t size) {
mSize = size; //将size存储在成员变量中
if (usesReservoir()) { //判断是否使用已有存储池来存储数据
return &u.reservoir; //可以使用的话,返回u.reservoir地址
}
u.ext_data = malloc(mSize); //如果不可以使用,则重新申请一块mSize大小的内存
if (u.ext_data == NULL) {
ALOGE("Couldn't allocate %zu bytes for item", size);
mSize = 0;
}
return u.ext_data;
}
//下面来看usesReservoir():
bool usesReservoir() const {
return mSize <= sizeof(u.reservoir); //哦,明白了,是判断要存储的数据是否大于存储池的空间,如果小于的话,就将data存在u.reservoir中
}
看了刚才的allocateStorage()函数的实现后,不禁会好奇u.ext_data和u.reservoir两个元素,它是结构体typed_data中定义的用一个联合体:
union { //是定义在共用体中,共用体的特点是,只是使用其中一个成员,如果使用两个的话,前一个的值将会丢弃
void *ext_data; //void* 表示不确定类型的指针,可以接受任意类型的赋值
float reservoir;
} u;
我们可以存上面的allocateStorage()函数看到,如果存储的数据小于float字节数的话,就存储在u.reservoir地址中;如果存储的数据大于float字节数,则使用void*来存储;
----------------------------------------存储的过程已解释,下面来看取数据的过程--------------------------------------------
来看frameworks\av\media\libstagefright\foundation\MetaData.cpp中findInt32()函数的实现:
bool MetaData::findInt32(uint32_t key, int32_t *value) { //通过key获取对应value的值
uint32_t type = 0;
const void *data; //要获取的是data的地址
size_t size;
if (!findData(key, &type, &data, &size) || type != TYPE_INT32) { //先获取key对应的type/data/size的值;然后比较type是否是TYPE_INT32类型,如果不是说明key不是通过setInt32()来存储的,直接返回false
return false;
}
CHECK_EQ(size, sizeof(*value)); //比较*value和我们获取的data大小是否一致,如果一致就将data数据赋值给value
*value = *(int32_t *)data;
return true;
}
bool MetaData::findData(uint32_t key, uint32_t *type,
const void **data, size_t *size) const {
ssize_t i = mItems.indexOfKey(key); //获取key在mItems中的id
if (i < 0) {
return false;
}
const typed_data &item = mItems.valueAt(i); //通过id获取存储的item的值
item.getData(type, data, size); //获取值
return true;
}
从mItems.indexOfKey(key)/mItems.valueAt(i)可以明白,我们存储的数据其实都是存在了mItems中去了
void MetaData::typed_data::getData(
uint32_t *type, const void **data, size_t *size) const {
*type = mType; //直接使用之前存在成员变量中的数据给它们赋值
*size = mSize;
*data = storage(); //给一级指针赋值,这样*data的值就是storage返回的地址了
}
void *storage() {
//从size判断是当时数据存储成了哪个类型
return usesReservoir() ? &u.reservoir : u.ext_data;
}
这样取数据的过程就完成了
关于data的二级指针,不太明白,日后再论