在做图片相关开发,发现从相机抓到的jpg图片不能打开,通过hexdump命令打开发现图片二进制最前面多了四个0,后面才是jpg的图片头(ffd8),于是就开始追查下图片保存的代码。很快找到了,图片在保存前是ByteBuffer数据,转换成ByteArray后才写入的文件,转换的代码如下:
val pos = byteBuffer.position()
val limit = byteBuffer.limit()
val byteArray = byteBuffer.array().sliceArray(pos until limit)
看起来貌似没有问题,但是看了下camera2的官方样例ByteBuffer转ByteArray的代码如下:
val size = currentBuffer.remaining()
val byteArray = ByteArray(size)
currentBuffer.get(byteArray)
这个时候数据position为0,两种实现看起来没有区别,但是当我调试时发现ByteBuffer中存储数据的数组中(byteBuffer.array())的数据前四个字节为0,这也解释通了为什么保存的图片前面数据为0了,但是第二种方法为什么取到的值从第五位开始了呢?调试发现它取值时索引值加了offset,而此对象offset值正好为4,也就是说很明确的标明了前四个字节不是有效数据,所以得出结论,从ByteBuffer中取数据时不要直接从array中取,要通过get函数获取。
接下来我们继续看这个偏移是怎么来的,我这里的ByteBuffer对象是自己创建出来的空对象,用来拷贝相机回调的数据,我惊奇的发现创建出来的一个空对象offset里面的值竟然就是4。
//此对象offset值为4
val byteBuffer = ByteBuffer.allocateDirect(128)
//这种方法创建出来的offset为0
val byteBuffer = ByteBuffer.allocate(128)
下面是创建对象的相关源码:
/******* ByteBuffer.java ********/
public static ByteBuffer allocateDirect(int capacity) {
// Android-changed: Android's DirectByteBuffers carry a MemoryRef.
// return new DirectByteBuffer(capacity);
DirectByteBuffer.MemoryRef memoryRef = new DirectByteBuffer.MemoryRef(capacity);
return new DirectByteBuffer(capacity, memoryRef);
}
/****** DirectByteBuffer.java *********/
DirectByteBuffer(int capacity, MemoryRef memoryRef) {
super(-1, 0, capacity, capacity, memoryRef.buffer, memoryRef.offset);
// Only have references to java objects, no need for a cleaner since the GC will do all
// the work.
this.memoryRef = memoryRef;
this.address = memoryRef.allocatedAddress + memoryRef.offset;
cleaner = null;
this.isReadOnly = false;
}
MemoryRef(int capacity) {
VMRuntime runtime = VMRuntime.getRuntime();
buffer = (byte[]) runtime.newNonMovableArray(byte.class, capacity + 7);
allocatedAddress = runtime.addressOf(buffer);
// Offset is set to handle the alignment: http://b/16449607
offset = (int) (((allocatedAddress + 7) & ~(long) 7) - allocatedAddress);
isAccessible = true;
isFreed = false;
originalBufferObject = null;
}
从这段代码可以知道offset的值来源于 这行代码:
offset = (int) (((allocatedAddress + 7) & ~(long) 7) - allocatedAddress);
查看了一些资料得知这行代码目的是对起始地址做8个字节的对齐,也可以自行调试验证。至此困惑解决,搜索上面代码时看到这篇文章,讲的更详细(之前一直搜offset为4相关关键词搜不到相关文章),供参考:
ByteBuffer 中的字节对齐 | 假装在香港