android->media->Image.java
Image
类,它代表了一个完整的图像缓冲区,可以与诸如 MediaCodec
或 CameraDevice
等媒体源一起使用。
主要特点和用途:
- 直接访问图像数据:
Image
类允许通过一个或多个ByteBuffer
实现对像素数据的高效直接访问。每个缓冲区都由一个Plane
封装,描述了该平面中像素数据的布局。这种直接访问使得图像不像Bitmap
类那样直接可用于 UI 资源。 - 图像资源管理:由于图像通常直接由硬件组件生产或消费,它们是系统中的有限资源,应在不再需要时立即关闭。
- ImageReader 使用示例:例如,使用
ImageReader
类从各种媒体源中读取图像时,不关闭旧的Image
对象将阻止新的图像的可用性,一旦达到了最大未完成图像数量,获取新图像的函数通常会抛出IllegalStateException
异常。
public abstract int getFormat();
getFormat()
方法,用于获取图像的格式。图像的格式决定了表示图像所需的 ByteBuffer
数量,以及每个 ByteBuffer
中像素数据的一般布局。
图像格式及其对应的平面数和布局细节:
- JPEG:压缩数据,只有一个平面。行和像素步长为0。要解压缩,请使用
BitmapFactory.decodeByteArray
。 - YUV_420_888:有3个平面。首先是亮度平面,然后是Cb和Cr色度平面。色度平面的宽度和高度是亮度平面的一半(4:2:0抽样)。每个平面的像素采样为8位。
- YUV_422_888:有3个平面。首先是亮度平面,然后是Cb和Cr色度平面。色度平面的宽度是亮度平面的一半,高度与亮度平面相同(4:2:2抽样)。每个平面的像素采样为8位。
- YUV_444_888:有3个平面。首先是亮度平面,然后是Cb和Cr色度平面。色度平面的宽度和高度与亮度平面相同(4:4:4抽样)。每个平面的像素采样为8位。
- FLEX_RGB_888:有3个平面。首先是红色平面,然后是绿色和蓝色平面。所有平面的宽度和高度相同,每个平面的像素采样为8位。
- FLEX_RGBA_8888:有4个平面。首先是红色平面,然后是绿色、蓝色和alpha平面。所有平面的宽度和高度相同,每个平面的像素采样为8位。
- RAW_SENSOR:只有一个平面。包含原始传感器图像数据,每个颜色采样为16位。布局的详细信息需要从原始传感器数据源(例如
CameraDevice
)查询。 - RAW_PRIVATE:只有一个平面,是私有布局的原始传感器图像数据。布局的详细信息是实现特定的。对于此格式,行步长和像素步长未定义。对
RAW_PRIVATE
图像调用Plane.getRowStride()
或Plane.getPixelStride()
将引发UnsupportedOperationException
异常。 - HEIC:压缩数据,只有一个平面。行和像素步长为0。要解压缩,请使用
BitmapFactory.decodeByteArray
。 - YCBCR_P010:有3个平面。P010是4:2:0 YCbCr半平面格式,由一个WxH的Y平面,后面是Wx(H/2)的Cb和Cr平面组成。每个样本由16位小端值表示,低6位设置为零。由于这是半平面格式,Cb平面也可以视为交错的Cb/Cr平面。
方法返回值:
- 返回图像的格式,其值是
ImageFormat
、PixelFormat
或HardwareBuffer
中的一个。
getWidth()
| getHeight()
getWidth()
和 getHeight()
方法,用于获取图像的宽度和高度。
- getWidth() 方法:
- 获取图像的宽度,单位为像素。
- 对于某些颜色通道进行子采样的格式,此宽度是具有最大分辨率平面的宽度。
- getHeight() 方法:
- 获取图像的高度,单位为像素。
- 对于某些颜色通道进行子采样的格式,此高度是具有最大分辨率平面的高度。
这些方法可用于确定图像的尺寸,以便在处理图像数据时准确地解释图像的布局和像素信息。
getTimestamp()
getTimestamp()
方法,用于获取与该帧关联的时间戳。
-
getTimestamp() 方法:
-
获取图像帧的时间戳,以纳秒为单位。
-
时间戳通常是单调递增的。
-
不同来源的图像可能具有不同的时间戳基准,因此它们可能不可比较。
-
时间戳的具体含义和基准取决于提供图像的源。可以查看 {@link android.hardware.Camera Camera}、{@link android.hardware.camera2.CameraDevice CameraDevice}、{@link MediaPlayer} 和 {@link MediaCodec} 获取更多详细信息。
-
getHardwareBuffer()
getHardwareBuffer()
方法,用于获取输入图像的 HardwareBuffer
句柄,以便进行 GPU 和/或硬件访问。
- getHardwareBuffer() 方法:
- 获取与此图像关联的
HardwareBuffer
。 - 返回与该图像关联的
HardwareBuffer
,如果该图像不支持此功能,则返回 null。 - 调用此方法前需确保图像对象有效,否则将抛出
IllegalStateException
。
- 获取与此图像关联的
该方法是用于获取与图像关联的硬件缓冲区句柄,以便进行直接的 GPU 或硬件访问。
setTimestamp()
这个方法允许你为图像设置时间戳。时间戳是以纳秒为单位的,通常是单调递增的。不同来源的图像可能具有不同的时间基准,因此它们可能无法进行直接比较。时间戳的具体含义和时间基准取决于提供图像的源
// 假设从 ImageReader 中获取了一个图像对象 image
if (image != null) {
long timestamp = System.nanoTime(); // 获取当前时间作为时间戳(以纳秒为单位)
// 设置图像的时间戳
image.setTimestamp(timestamp);
// 可以继续处理图像或将其传递给其他模块进行进一步处理
// ...
// 处理完成后记得关闭图像,释放资源
image.close();
} else {
// 处理图像对象为 null 的情况
}
getCropRect()
裁剪矩形指定图像中有效像素的区域,使用最大分辨率平面的坐标。
具体来说:
- 如果图像没有指定裁剪矩形(mCropRect为null),则返回一个包含整个图像尺寸的矩形(Rect),即从左上角(0, 0)到右下角(getWidth(), getHeight())。
- 如果图像有指定裁剪矩形(mCropRect不为null),则返回裁剪矩形的副本(copy),避免直接返回引用,以确保图像的裁剪矩形不会被外部修改。
这个方法主要用于确定图像中有效像素的范围,对于处理图像的算法或操作来说是一个重要的参考,可以确保只处理图像中指定的有效区域,提高效率和准确性。
public Rect getCropRect() {
throwISEIfImageIsInvalid();
if (mCropRect == null) {
return new Rect(0, 0, getWidth(), getHeight());
} else {
return new Rect(mCropRect); // return a copy
}
}
Image.Plane[] planes = image.getPlanes();
Rect cropRect = image.getCropRect();
// 获取裁剪矩形的左上角和右下角坐标
int left = cropRect.left;
int top = cropRect.top;
int right = cropRect.right;
int bottom = cropRect.bottom;
// 计算裁剪矩形的宽度和高度
int width = right - left;
int height = bottom - top;
// 遍历图像的每个平面并处理有效像素区域
for (Image.Plane plane : planes) {
ByteBuffer buffer = plane.getBuffer();
int pixelStride = plane.getPixelStride();
int rowStride = plane.getRowStride();
// 计算有效像素数据的起始偏移量
int offset = top * rowStride + left * pixelStride;
// 根据偏移量和宽高处理图像数据
// 这里只是一个示例,实际处理方式取决于图像格式和处理需求
buffer.position(offset);
// 在这里处理有效像素数据
}
getPlanes()
getPlanes()
方法用于获取图像的像素平面数组 Plane[]
。每个图像都由一个或多个像素平面组成,具体平面数量取决于图像的格式。
Image.Plane[] planes = image.getPlanes();
// 遍历图像的每个像素平面
for (Image.Plane plane : planes) {
ByteBuffer buffer = plane.getBuffer(); // 获取像素数据的 ByteBuffer
int pixelStride = plane.getPixelStride(); // 获取像素步幅
int rowStride = plane.getRowStride(); // 获取行步幅
// 处理图像数据
// 注意:这里只是一个示例,实际处理方式取决于图像格式和应用需求
int bufferSize = buffer.remaining(); // 获取缓冲区大小
byte[] pixels = new byte[bufferSize]; // 创建用于存储像素数据的数组
buffer.get(pixels); // 将像素数据读取到数组中
// 在这里可以对像素数据进行进一步处理,比如解码、渲染等
// ...
// 释放资源(注意:这里的示例未显示对资源的释放,实际应用中应该适时调用 image.close() 来释放资源)
}
// 在处理完所有像素平面后,记得调用 image.close() 来释放图像资源
image.close();
getPlanes()
获取图像的像素平面数组 planes
,然后遍历每个像素平面 plane
。对于每个像素平面,我们可以通过 plane.getBuffer()
获取像素数据的 ByteBuffer
,并使用 plane.getPixelStride()
和 plane.getRowStride()
获取像素步幅和行步幅等信息。接下来,我们可以根据需要对像素数据进行处理,比如解码、渲染等操作。最后,需要在适当的时候调用 image.close()
来释放图像资源,确保资源得到正确的释放和管理。
close()
close()
方法用于释放图像资源,使图像可以重新使用。调用该方法后,对图像的任何操作都将导致 IllegalStateException
异常,并且尝试读取或写入之前通过 Plane#getBuffer
调用返回的 ByteBuffer
将具有未定义的行为。如果图像是通过 ImageWriter#dequeueInputImage()
获取的,那么调用此方法后,应用程序填充的任何图像数据将丢失,并且图像将返回给 ImageWriter
以便重新使用。通过 ImageWriter#queueInputImage
提供的图像将自动关闭。
Image.Plane
/**
* <p>A single color plane of image data.</p>
*
* <p>The number and meaning of the planes in an Image are determined by the
* format of the Image.</p>
*
* <p>Once the Image has been closed, any access to the plane's
* ByteBuffer will fail.</p>
*
* @see #getFormat
*/
public static abstract class Plane {
/**
* @hide
*/
@UnsupportedAppUsage
@TestApi
protected Plane() {
}
/**
* <p>The row stride for this color plane, in bytes.</p>
*
* <p>This is the distance between the start of two consecutive rows of
* pixels in the image. Note that row stride is undefined for some formats
* such as
* {@link android.graphics.ImageFormat#RAW_PRIVATE RAW_PRIVATE},
* and calling getRowStride on images of these formats will
* cause an UnsupportedOperationException being thrown.
* For formats where row stride is well defined, the row stride
* is always greater than 0.</p>
*/
public abstract int getRowStride();
/**
* <p>The distance between adjacent pixel samples, in bytes.</p>
*
* <p>This is the distance between two consecutive pixel values in a row
* of pixels. It may be larger than the size of a single pixel to
* account for interleaved image data or padded formats.
* Note that pixel stride is undefined for some formats such as
* {@link android.graphics.ImageFormat#RAW_PRIVATE RAW_PRIVATE},
* and calling getPixelStride on images of these formats will
* cause an UnsupportedOperationException being thrown.
* For formats where pixel stride is well defined, the pixel stride
* is always greater than 0.</p>
*/
public abstract int getPixelStride();
/**
* <p>Get a direct {@link java.nio.ByteBuffer ByteBuffer}
* containing the frame data.</p>
*
* <p>In particular, the buffer returned will always have
* {@link java.nio.ByteBuffer#isDirect isDirect} return {@code true}, so
* the underlying data could be mapped as a pointer in JNI without doing
* any copies with {@code GetDirectBufferAddress}.</p>
*
* <p>For raw formats, each plane is only guaranteed to contain data
* up to the last pixel in the last row. In other words, the stride
* after the last row may not be mapped into the buffer. This is a
* necessary requirement for any interleaved format.</p>
*
* @return the byte buffer containing the image data for this plane.
*/
public abstract ByteBuffer getBuffer();
}
Plane
类表示图像数据中的单个颜色平面。图像中的平面数量和含义取决于图像的格式。
以下是 Plane
类的重要方法:
getRowStride()
方法:- 返回颜色平面的行跨度,以字节为单位。
- 行跨度是指图像中两个连续像素行的起始点之间的距离。
- 对于某些格式(如
RAW_PRIVATE
),行跨度未定义,对这些格式的图像调用getRowStride()
将导致UnsupportedOperationException
异常。
getPixelStride()
方法:- 返回相邻像素样本之间的距离,以字节为单位。
- 像素跨度是指一行像素中两个连续像素值之间的距离。
- 对于某些格式(如
RAW_PRIVATE
),像素跨度未定义,对这些格式的图像调用getPixelStride()
将导致UnsupportedOperationException
异常。
getBuffer()
方法:- 返回包含图像数据的直接
ByteBuffer
。 - 返回的
ByteBuffer
总是直接缓冲区,可以在 JNI 中作为指针映射,无需使用GetDirectBufferAddress
进行任何拷贝。 - 对于原始格式,每个平面只保证包含数据直到最后一行的最后一个像素。换句话说,最后一行后的跨度可能未映射到缓冲区中。这是任何交错格式的必要条件。
- 返回包含图像数据的直接
这些方法允许您获取有关图像颜色平面的重要信息,例如行跨度、像素跨度和包含图像数据的 ByteBuffer
。
示例
ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();
int yStride = image.getPlanes()[0].getRowStride();
int uvStride = image.getPlanes()[1].getRowStride();
int yuvSize = yStride * height + uvStride * height / 2;
int width = image.getWidth();
int height = image.getHeight();
nativeSetInputImage(mHandle, yBuffer, uBuffer, vBuffer,
index, yStride, uvStride, width, height, evValue);
public static boolean imageToNV12(Image image, byte[] dst) {
if (image == null) {
LogUtil.e("NULL Image", "image is null");
return false;
}
final int imageWidth = image.getWidth();
final int imageHeight = image.getHeight();
final Image.Plane[] planes = image.getPlanes();
int offset = 0;
for (int channel = 0; channel < planes.length - 1; channel++) {
Image.Plane plane = planes[channel];
final ByteBuffer buffer = plane.getBuffer();
if (buffer == null) {
return false;
}
final int rowStride = plane.getRowStride();
// plane.getPixelStride()==2, 说明UV交错存储在buffer上
// Experimentally, U and V planes have |pixelStride| = 2, which
// essentially means they are packed. That's silly, because we are
// forced to unpack here.
final int pixelStride = plane.getPixelStride();
// YUV420: UV数据是Y的1/2
int rows = imageHeight / pixelStride;
if (rowStride == imageWidth) {
// Copy whole plane from buffer into |data| at once.
int length = rowStride * rows;
// UV plane的buffer比实际图像UV小1byte,读buffer实际大小
buffer.get(dst, offset, buffer.remaining());
offset += length;
} else {
//图像每行有填充额外数据做字节对齐
for (int i = 0; i < rows - 1; i++) {
buffer.get(dst, offset, imageWidth);
buffer.position(i * rowStride);
offset += imageWidth;
}
// UV plane的buffer比实际图像UV小1byte,最后一行读buffer实际大小
// Last row is special in some devices and may not contain the full
// |rowStride| bytes of data. See http://crbug.com/458701 and
// http://developer.android.com/reference/android/media/Image.Plane.html#getBuffer()
int lastRowLength = Math.min(imageWidth, buffer.remaining());
buffer.get(dst, offset, lastRowLength);
offset += imageWidth;
}
buffer.rewind();
}
return true;
}
总结:
Image
类提供了访问图像数据的方法,允许应用程序直接处理图像数据。以下是 Image
类中的重要方法和功能总结:
-
图像格式和属性:
getFormat()
: 获取图像的格式,如 JPEG、YUV 等。getWidth()
: 获取图像的宽度。getHeight()
: 获取图像的高度。getTimestamp()
: 获取图像的时间戳。getCropRect()
: 获取图像的裁剪矩形。
-
图像数据访问:
getPlanes()
: 获取图像的颜色平面数组,用于访问图像数据的每个平面。getHardwareBuffer()
: 获取图像关联的HardwareBuffer
,用于直接访问 GPU 和硬件。
-
图像数据处理:
close()
: 关闭图像,释放图像资源,防止访问已关闭的图像。
-
其他功能:
setTimestamp(long timestamp)
: 设置图像的时间戳。setFence(SyncFence fence)
: 设置图像的同步屏障。getTransform()
: 获取图像的转换。getScalingMode()
: 获取图像的缩放模式。
-
Image.Plane
Image.Plane
类是Image
类中的一个内部静态抽象类,用于表示图像数据的单个颜色平面。每个Image
对象可以包含一个或多个Plane
对象,具体取决于图像的格式。以下是
Image.Plane
类中的重要方法和功能总结:-
颜色平面属性:
getRowStride()
: 获取颜色平面的行跨距,即相邻两行像素数据之间的字节距离。getPixelStride()
: 获取颜色平面的像素跨距,即相同颜色分量的两个相邻像素之间的字节距离。getBuffer()
: 获取包含颜色平面数据的ByteBuffer
对象,用于直接访问图像数据。
-
颜色平面数据访问:
getRowStride()
和getPixelStride()
方法用于了解颜色平面的布局和像素间距。getBuffer()
方法返回一个ByteBuffer
对象,用于访问颜色平面的像素数据。
Image.Plane
类提供了对图像数据的底层访问,允许应用程序直接操作图像数据的每个颜色平面。通过getBuffer()
方法返回的ByteBuffer
对象可以用于读取或写入图像数据。注意,颜色平面的行跨距和像素跨距可能因图像格式而异,对于某些特定格式,这些属性可能未定义。在使用完图像数据后,应及时释放对
Image
对象的引用,并确保不再访问已关闭的图像或颜色平面,以避免出现未定义的行为。
-
这些方法使您能够获取图像的基本属性(格式、宽度、高度、时间戳等),访问图像的像素数据(颜色平面、缓冲区等),并执行一些图像数据处理操作(设置时间戳、同步屏障等)。务必在使用完图像后调用 close()
方法以释放资源。