ImageReader
允许应用程序直接获取渲染到
surface
的图形数据,并转换为图片,这里我用
Presentation
+
VirtualDisplay
+
ImageReader
做一个Demo来探讨
ImageReader
的实现原理。
Demo实现的功能是:
Presentation
指定显示在
VirtualDisplay
上,然后由
ImageReader
去获取
VirtualDisplay
上渲染的
Presentation
图像,获取之后将其转换为
Bitmap
,设置到一个
ImageView
中,并且可以通过修改
Presentation
的UI数据实现
ImageView
的刷新。
MainActivity onCreate
:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mDisplayManager = (DisplayManager)this.getSystemService(Context.DISPLAY_SERVICE); getWindowManager().getDefaultDisplay().getMetrics(outMetrics); findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //测试ImageReader testVirtualDisplay(); }); imageView = findViewById(R.id.image);
testVirtualDisplay
:
private void testVirtualDisplay(){ final int screenWidth = outMetrics.widthPixels; final int screenHeight = outMetrics.heightPixels; @SuppressLint("WrongConstant") ImageReader imageReader = ImageReader.newInstance( screenWidth, screenHeight, PixelFormat.RGBA_8888, 1); //创建虚拟屏,传入ImageReader的Surface VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay("testVirtualDisplay", WIDTH, HEIGHT, DENSITY, imageReader.getSurface(), DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION); //设置回调 imageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() { //回调中获取Surface的渲染数据 @Override public void onImageAvailable(ImageReader reader) { mImageReaderLock.lock(); try { // Get the latest buffer. Image image = reader.acquireLatestImage(); Log.d("dongjiao", "11image = :"+image); if (image != null) { Image.Plane[] planes = image.getPlanes(); ByteBuffer buffer = planes[0].getBuffer(); int pixelStride = planes[0].getPixelStride(); int rowStride = planes[0].getRowStride(); int rowPadding = rowStride - pixelStride * screenWidth; Bitmap bitmap = Bitmap.createBitmap(screenWidth + rowPadding / pixelStride, screenHeight, Bitmap.Config.ARGB_8888); bitmap.copyPixelsFromBuffer(buffer); //virtualDisplay.release(); imageView.setImageBitmap(bitmap); image.close(); } finally { mImageReaderLock.unlock(); }, handler); DisplayManager displayManager = (DisplayManager) this.getSystemService(Context.DISPLAY_SERVICE); Display[] presentationDisplays = displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION); if (presentationDisplays.length > 0) { Display display = presentationDisplays[0]; //TestPresentation指定显示在虚拟屏 TestPresentation presentation = new TestPresentation(this, display); presentation.show(); findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //更新TestPresentation的UI presentation.changeText(); });
Demo代码很简单,首先
MainActivity
有两个button
,BUTTON
创建ImageReader
,创建虚拟屏,设置ImageReader
回调,创建TestPresentation
,并将TestPresentation
显示在虚拟屏上,此时ImageReader
回调中就会收到TestPresentation
渲染的UI数据,拿到数据之后将其转换为Bitmap
,并设置到ImageView
中。
BUTTON1
会更新TestPresentation
的UI数据,更新之后同样会回调ImageReader
,进而再次更新ImageView
。
![]()
1.3 图像数据流分析
来看
TestPresentation
的图像是如何传递到ImageReader
的。1.3.1 ImageReader初始化
protected ImageReader(int width, int height, int format, int maxImages, long usage) { ...... nativeInit(new WeakReference<>(this), width, height, format, maxImages, usage); mSurface = nativeGetSurface(); ......
这里关注两个方法,
nativeInit
和nativeGetSurface
,maxImages
代码能同时获取buffer
的数量,最小为1,
1.3.2 ImageReader_init
static void ImageReader_init(JNIEnv* env, jobject thiz, jobject weakThiz, jint width, jint height, jint format, jint maxImages, jlong ndkUsage) status_t res; ... sp<JNIImageReaderContext> ctx(new JNIImageReaderContext(env, weakThiz, clazz, maxImages)); sp<IGraphicBufferProducer> gbProducer; sp<IGraphicBufferConsumer> gbConsumer; //创建BufferQueue BufferQueue::createBufferQueue(&gbProducer, &gbConsumer); //自定义Consumer sp<BufferItemConsumer> bufferConsumer; String8 consumerName = String8::format("ImageReader-%dx%df%xm%d-%d-%d", width, height, format, maxImages, getpid(), createProcessUniqueId()); ...... ctx->setBufferConsumer(bufferConsumer); bufferConsumer->setName(consumerName); ctx->setProducer(gbProducer); bufferConsumer->setFrameAvailableListener(ctx); ImageReader_setNativeContext(env, thiz, ctx); ctx->setBufferFormat(nativeFormat); ctx->setBufferDataspace(nativeDataspace); ctx->setBufferWidth(width); ctx->setBufferHeight(height); ....
这里核心是创建了
ImageReader
的native层对象JNIImageReaderContext
,并且创建了一套生产者消费者模型,自定义了Consumer(BufferItemConsumer)
,最重要的是
setFrameAvailableListener
的对象是JNIImageReaderContext
,说明JNIImageReaderContext
实现了onFrameAvailable
接口,看看其具体实现:
void JNIImageReaderContext::onFrameAvailable(const BufferItem& /*item*/) ALOGV("%s: frame available", __FUNCTION__); bool needsDetach = false; JNIEnv* env = getJNIEnv(&needsDetach); if (env != NULL) { env->CallStaticVoidMethod(mClazz, gImageReaderClassInfo.postEventFromNative, mWeakThiz); } else { ALOGW("onFrameAvailable event will not posted"); if (needsDetach) { detachJNI();
onFrameAvailable
会回调到java层ImageReader
的postEventFromNative
方法,等下在回到java层看它的具体实现,先看Surface
是怎么构建的:
1.3.3 ImageReader_getSurface
static jobject ImageReader_getSurface(JNIEnv* env, jobject thiz) ALOGV("%s: ", __FUNCTION__); //从JNIImageReaderContext中获取IGraphicBufferProducer IGraphicBufferProducer* gbp = ImageReader_getProducer(env, thiz); if (gbp == NULL) { jniThrowRuntimeException(env, "Buffer consumer is uninitialized"); return NULL; // 用Surface对IGraphicBufferProducer进行包装 return android_view_Surface_createFromIGraphicBufferProducer(env, gbp); jobject android_view_Surface_createFromIGraphicBufferProducer(JNIEnv* env, const sp<IGraphicBufferProducer>& bufferProducer) { if (bufferProducer == NULL) { return NULL; sp<Surface> surface(new Surface(bufferProducer, true)); return android_view_Surface_createFromSurface(env, surface);
这里就很简单了,new了一个
Surface
对IGraphicBufferProducer
进行包装,这个Surface
最终就是传给虚拟屏的。接着回到java层来看
postEventFromNative
的实现:
1.3.4 ImageReader.postEventFromNative
private static void postEventFromNative(Object selfRef) { @SuppressWarnings("unchecked") WeakReference<ImageReader> weakSelf = (WeakReference<ImageReader>)selfRef; final ImageReader ir = weakSelf.get(); if (ir == null) { return; final Handler handler; synchronized (ir. mListenerLock) { handler = ir.mListenerHandler; if (handler != null) { handler.sendEmptyMessage(0);
这里通过
ListenerHandler
发送消息:
1.3.5 ListenerHandler.handleMessage
private final class ListenerHandler extends Handler { public ListenerHandler(Looper looper) { super(looper, null, true /*async*/); @Override public void handleMessage(Message msg) { OnImageAvailableListener listener; synchronized (mListenerLock) { listener = mListener; // It's dangerous to fire onImageAvailable() callback when the ImageReader is being // closed, as application could acquire next image in the onImageAvailable() callback. boolean isReaderValid = false; synchronized (mCloseLock) { isReaderValid = mIsReaderValid; if (listener != null && isReaderValid) { listener.onImageAvailable(ImageReader.this);
看到了吗,回调了
OnImageAvailableListener
的onImageAvailable
方法,这个就是前面Demo中我们给ImageReader
设置的回调,回过头再看Demo,在onImageAvailable
方法中做了什么:
ImageReader.OnImageAvailableListener() { //回调中获取Surface的渲染数据 @Override public void onImageAvailable(ImageReader reader) { mImageReaderLock.lock(); try { Image image = reader.acquireLatestImage(); Log.d("dongjiao", "11image = :"+image); if (image != null) { Image.Plane[] planes = image.getPlanes(); ByteBuffer buffer = planes[0].getBuffer(); int pixelStride = planes[0].getPixelStride(); int rowStride = planes[0].getRowStride(); int rowPadding = rowStride - pixelStride * screenWidth; Bitmap bitmap = Bitmap.createBitmap(screenWidth + rowPadding / pixelStride, screenHeight, Bitmap.Config.ARGB_8888); bitmap.copyPixelsFromBuffer(buffer); //virtualDisplay.release(); imageView.setImageBitmap(bitmap); image.close(); } finally { mImageReaderLock.unlock(); }, handler);
调用了
acquireLatestImage
获取数据:
1.3.6 ImageReader.acquireLatestImage
public Image acquireLatestImage() { Image image = acquireNextImage(); if (image == null) { return null; try { for (;;) { Image next = acquireNextImageNoThrowISE(); if (next == null) { Image result = image; image = null; return result; image.close(); image = next; } finally { if (image != null) { image.close();
acquireLatestImage
会获取最新的一帧,所以里面有个循环,直到下一帧为空才返回,acquireNextImage
和acquireNextImageNoThrowISE
最终都是调用的acquireNextSurfaceImage
,
1.3.7 ImageReader.acquireNextSurfaceImage
private int acquireNextSurfaceImage(SurfaceImage si) { synchronized (mCloseLock) { // A null image will eventually be returned if ImageReader is already closed. int status = ACQUIRE_NO_BUFS; if (mIsReaderValid) { status = nativeImageSetup(si); switch (status) { case ACQUIRE_SUCCESS: si.mIsImageValid = true; case ACQUIRE_NO_BUFS: case ACQUIRE_MAX_IMAGES: break; default: throw new AssertionError("Unknown nativeImageSetup return code " + status); // Only keep track the successfully acquired image, as the native buffer is only mapped // for such case. if (status == ACQUIRE_SUCCESS) { mAcquiredImages.add(si); return status;
调用到native层去获取帧的数据,参数
SurfaceImage
用于保存帧的指针,
1.3.8 ImageReader_imageSetup
static jint ImageReader_imageSetup(JNIEnv* env, jobject thiz, jobject image) { JNIImageReaderContext* ctx = ImageReader_getContext(env, thiz); if (ctx == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "ImageReader is not initialized or was already closed"); return -1; //获取自定义BufferItemConsumer BufferItemConsumer* bufferConsumer = ctx->getBufferConsumer(); //保证maxImages的作用,同一时间能够获取的maxImages数量的buffer //当buffer为空时,需要Image close之后才能继续获取 BufferItem* buffer = ctx->getBufferItem(); if (buffer == NULL) { ALOGW("Unable to acquire a buffer item, very likely client tried to acquire more than" " maxImages buffers"); return ACQUIRE_MAX_IMAGES; //获取buffer status_t res = bufferConsumer->acquireBuffer(buffer, 0); ...... // 将获取到的buffer指针保存到java层传下来的SurfaceImage中 Image_setBufferItem(env, image, buffer); //将buffer的一些参数保存到SurfaceImage中 env->SetLongField(image, gSurfaceImageClassInfo.mTimestamp, static_cast<jlong>(buffer->mTimestamp)); auto transform = buffer->mTransform; if (buffer->mTransformToDisplayInverse) { transform |= NATIVE_WINDOW_TRANSFORM_INVERSE_DISPLAY; env->SetIntField(image, gSurfaceImageClassInfo.mTransform, static_cast<jint>(transform)); env->SetIntField(image, gSurfaceImageClassInfo.mScalingMode, static_cast<jint>(buffer->mScalingMode)); return ACQUIRE_SUCCESS;
acquireBuffer
用于获取当前可用的buffer
1.3.9 BufferItemConsumer.acquireBuffer
status_t BufferItemConsumer::acquireBuffer(BufferItem *item, nsecs_t presentWhen, bool waitForFence) { status_t err; if (!item) return BAD_VALUE; Mutex::Autolock _l(mMutex); //获取buffer err = acquireBufferLocked(item, presentWhen); if (err != OK) { if (err != NO_BUFFER_AVAILABLE) { BI_LOGE("Error acquiring buffer: %s (%d)", strerror(err), err); return err; //传入的waitForFence等于0,不需要等Fence if (waitForFence) { err = item->mFence->waitForever("BufferItemConsumer::acquireBuffer"); if (err != OK) { BI_LOGE("Failed to wait for fence of acquired buffer: %s (%d)", strerror(-err), err); return err; item->mGraphicBuffer = mSlots[item->mSlot].mGraphicBuffer; return OK;
真正获取buffer还是通过父类
ConsumerBase
,之后就到SurfaceFlinger
流程了,后续buffer到java层之后还需要各种格式转换等处理,变成一个ByteBuffer
,就可以写入Bitmap
了,最后将此Bitmap
设置到ImageView
就完成了Demo的效果,Demo中的刷新UI不过是再走了一遍整个流程。再来整理一遍
ImageReader
的数据流:首先native层
JNIImageReaderContext
依靠onFrameAvailable
接收到BufferQueue
中有可用buffer的信号,接着通过JNI调用postEventFromNative
告知java层ImageReader
,postEventFromNative
又继续通过回调onImageAvailable
告知某个注册了OnImageAvailableListener
的客户端,客户端在onImageAvailable
中通过acquireLatestImage
去获取BufferQueue
中可用的buffer,最终是通过acquireBufferLocked
获取,拿到的buffer之后就可以做自己的处理了。这是
ImageReader
的数据流,那么数据是怎么流向ImageReader
的呢?
ImageReader
的数据来自VirtualDisplay
,流向是这样的:首先
VirtualDisplay
创建时接收了ImageReader
的Surface
,APP(Presentation
)是一个可以显示在其他Display
的Dialog
,当它显示在VirtualDisplay
时,SurfaceFlinger
就会将它合成的数据渲染在VirtualDisplay
的Surface
上,因为此时VirtualDisplay
的Surface
来自ImageReader
,自然渲染的数据就被放入了ImageReader
的Surface
的BufferQueue
中,所以ImageReader
就收到了1. ImageReader1.1 概述ImageReader允许应用程序直接获取渲染到surface的图形数据,并转换为图片,这里我用Presentation+VirtualDisplay+ImageReader做一个Demo来探讨ImageReader的实现原理。Demo实现的功能是:Presentation指定显示在VirtualDisplay上,然后由ImageReader去获取VirtualDisplay上渲染的Presentation图像,获取之后将其转换为Bitmap,设置到一个Imag ImagerReader: 官网:https://developer.android.com/reference/android/media/ImageReader.html ImageReader可以直接获取屏幕渲染数据,得到屏幕数据后自己想干嘛就干嘛。得到的数据是image格式,这个数据从系统内核分发到我们get到,平均延迟是30ms。如下图。 需要获取相应服务的权限,然后 创建虚拟显示器,物理屏幕画面会不断被投影到虚拟现实器,输出到创建虚拟显示器时设置的Surface上。使用过程一般会结合ImageReader或OpenGL来进行。 (在更低的版本,如Android4.4,获取屏幕画面需要通过ADB指令来进行。但目前市面上基本已经见不到比Android5.0版本还低的安卓手机) import android.graphics.ImageFormat; import android.media.Image; import android.os.Build; import android.support.annotation.RequiresApi; import android.util.Log; import java.n... private void initImageReader() { //创建图片读取器,参数为分辨率宽度和高度/图片格式/需要缓存几张图片,我这里写的2意思是获取2张照片 mImageReader = ImageReader.newInstance(1080, 1920, ImageFormat.YUV_420_888, 2); mImageRe 1、ImageReader.newInstance中参数maxImages如果设置为1,则有时会取不到数据,原因不详。具体什么值合适,本人未详细测试,随意改成3 2、mImageReader.acquireLatestImage取相之前,先sleep(200),据说 mImag...
onFrameAvailable
回调。Android ImageReader使用ImageReader类允许应用程序直接访问呈现表面的图像数据 创建ImageReader对像ImageReader ir = ImageReader.newInstance(int width, int height, int format, int maxImages);参数 默认图像的宽度像素 默认图像的高度像素 图像的格式 用户想要读图像的最大数量