1 /*
2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <cutils/compiler.h>
18 #include <gui/BufferQueue.h>
19 #include <surfacetexture/ImageConsumer.h>
20 #include <surfacetexture/SurfaceTexture.h>
21 #include <surfacetexture/surface_texture_platform.h>
22 #include <math/mat4.h>
23 #include <system/window.h>
24 #include <utils/Trace.h>
25 
26 #include <com_android_graphics_libgui_flags.h>
27 
28 namespace android {
29 
30 // Macros for including the SurfaceTexture name in log messages
31 #define SFT_LOGV(x, ...) ALOGV("[%s] " x, mName.c_str(), ##__VA_ARGS__)
32 #define SFT_LOGD(x, ...) ALOGD("[%s] " x, mName.c_str(), ##__VA_ARGS__)
33 #define SFT_LOGW(x, ...) ALOGW("[%s] " x, mName.c_str(), ##__VA_ARGS__)
34 #define SFT_LOGE(x, ...) ALOGE("[%s] " x, mName.c_str(), ##__VA_ARGS__)
35 
36 static const mat4 mtxIdentity;
37 
SurfaceTexture(const sp<IGraphicBufferConsumer> & bq,uint32_t tex,uint32_t texTarget,bool useFenceSync,bool isControlledByApp)38 SurfaceTexture::SurfaceTexture(const sp<IGraphicBufferConsumer>& bq, uint32_t tex,
39                                uint32_t texTarget, bool useFenceSync, bool isControlledByApp)
40       : ConsumerBase(bq, isControlledByApp),
41         mCurrentCrop(Rect::EMPTY_RECT),
42         mCurrentTransform(0),
43         mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
44         mCurrentFence(Fence::NO_FENCE),
45         mCurrentTimestamp(0),
46         mCurrentDataSpace(HAL_DATASPACE_UNKNOWN),
47         mCurrentFrameNumber(0),
48         mDefaultWidth(1),
49         mDefaultHeight(1),
50         mFilteringEnabled(true),
51         mTexName(tex),
52         mUseFenceSync(useFenceSync),
53         mTexTarget(texTarget),
54         mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
55         mOpMode(OpMode::attachedToGL) {
56     SFT_LOGV("SurfaceTexture");
57 
58     memcpy(mCurrentTransformMatrix, mtxIdentity.asArray(), sizeof(mCurrentTransformMatrix));
59 
60     mConsumer->setConsumerUsageBits(DEFAULT_USAGE_FLAGS);
61 }
62 
SurfaceTexture(const sp<IGraphicBufferConsumer> & bq,uint32_t texTarget,bool useFenceSync,bool isControlledByApp)63 SurfaceTexture::SurfaceTexture(const sp<IGraphicBufferConsumer>& bq, uint32_t texTarget,
64                                bool useFenceSync, bool isControlledByApp)
65       : ConsumerBase(bq, isControlledByApp),
66         mCurrentCrop(Rect::EMPTY_RECT),
67         mCurrentTransform(0),
68         mCurrentScalingMode(NATIVE_WINDOW_SCALING_MODE_FREEZE),
69         mCurrentFence(Fence::NO_FENCE),
70         mCurrentTimestamp(0),
71         mCurrentDataSpace(HAL_DATASPACE_UNKNOWN),
72         mCurrentFrameNumber(0),
73         mDefaultWidth(1),
74         mDefaultHeight(1),
75         mFilteringEnabled(true),
76         mTexName(0),
77         mUseFenceSync(useFenceSync),
78         mTexTarget(texTarget),
79         mCurrentTexture(BufferQueue::INVALID_BUFFER_SLOT),
80         mOpMode(OpMode::detached) {
81     SFT_LOGV("SurfaceTexture");
82 
83     memcpy(mCurrentTransformMatrix, mtxIdentity.asArray(), sizeof(mCurrentTransformMatrix));
84 
85     mConsumer->setConsumerUsageBits(DEFAULT_USAGE_FLAGS);
86 }
87 
setDefaultBufferSize(uint32_t w,uint32_t h)88 status_t SurfaceTexture::setDefaultBufferSize(uint32_t w, uint32_t h) {
89     Mutex::Autolock lock(mMutex);
90     if (mAbandoned) {
91         SFT_LOGE("setDefaultBufferSize: SurfaceTexture is abandoned!");
92         return NO_INIT;
93     }
94     mDefaultWidth = w;
95     mDefaultHeight = h;
96     return mConsumer->setDefaultBufferSize(w, h);
97 }
98 
updateTexImage()99 status_t SurfaceTexture::updateTexImage() {
100     ATRACE_CALL();
101     SFT_LOGV("updateTexImage");
102     Mutex::Autolock lock(mMutex);
103 
104     if (mAbandoned) {
105         SFT_LOGE("updateTexImage: SurfaceTexture is abandoned!");
106         return NO_INIT;
107     }
108 
109     return mEGLConsumer.updateTexImage(*this);
110 }
111 
releaseTexImage()112 status_t SurfaceTexture::releaseTexImage() {
113     // releaseTexImage can be invoked even when not attached to a GL context.
114     ATRACE_CALL();
115     SFT_LOGV("releaseTexImage");
116     Mutex::Autolock lock(mMutex);
117 
118     if (mAbandoned) {
119         SFT_LOGE("releaseTexImage: SurfaceTexture is abandoned!");
120         return NO_INIT;
121     }
122 
123     return mEGLConsumer.releaseTexImage(*this);
124 }
125 
acquireBufferLocked(BufferItem * item,nsecs_t presentWhen,uint64_t maxFrameNumber)126 status_t SurfaceTexture::acquireBufferLocked(BufferItem* item, nsecs_t presentWhen,
127                                              uint64_t maxFrameNumber) {
128     status_t err = ConsumerBase::acquireBufferLocked(item, presentWhen, maxFrameNumber);
129     if (err != NO_ERROR) {
130         return err;
131     }
132 
133     switch (mOpMode) {
134         case OpMode::attachedToConsumer:
135             break;
136         case OpMode::attachedToGL:
137             mEGLConsumer.onAcquireBufferLocked(item, *this);
138             break;
139         case OpMode::detached:
140             break;
141     }
142 
143     return NO_ERROR;
144 }
145 
releaseBufferLocked(int buf,sp<GraphicBuffer> graphicBuffer,EGLDisplay display,EGLSyncKHR eglFence)146 status_t SurfaceTexture::releaseBufferLocked(int buf, sp<GraphicBuffer> graphicBuffer,
147                                              EGLDisplay display, EGLSyncKHR eglFence) {
148     // release the buffer if it hasn't already been discarded by the
149     // BufferQueue. This can happen, for example, when the producer of this
150     // buffer has reallocated the original buffer slot after this buffer
151     // was acquired.
152     status_t err = ConsumerBase::releaseBufferLocked(buf, graphicBuffer, display, eglFence);
153     // We could be releasing an EGL/Vulkan buffer, even if not currently
154     // attached to a GL context.
155     mImageConsumer.onReleaseBufferLocked(buf);
156     mEGLConsumer.onReleaseBufferLocked(buf);
157     return err;
158 }
159 
detachFromContext()160 status_t SurfaceTexture::detachFromContext() {
161     ATRACE_CALL();
162     SFT_LOGV("detachFromContext");
163     Mutex::Autolock lock(mMutex);
164 
165     if (mAbandoned) {
166         SFT_LOGE("detachFromContext: abandoned SurfaceTexture");
167         return NO_INIT;
168     }
169 
170     if (mOpMode != OpMode::attachedToGL) {
171         SFT_LOGE("detachFromContext: SurfaceTexture is not attached to a GL context");
172         return INVALID_OPERATION;
173     }
174 
175     status_t err = mEGLConsumer.detachFromContext(*this);
176     if (err == OK) {
177         mOpMode = OpMode::detached;
178     }
179 
180     return err;
181 }
182 
attachToContext(uint32_t tex)183 status_t SurfaceTexture::attachToContext(uint32_t tex) {
184     ATRACE_CALL();
185     SFT_LOGV("attachToContext");
186     Mutex::Autolock lock(mMutex);
187 
188     if (mAbandoned) {
189         SFT_LOGE("attachToContext: abandoned SurfaceTexture");
190         return NO_INIT;
191     }
192 
193     if (mOpMode != OpMode::detached) {
194         SFT_LOGE("attachToContext: SurfaceTexture is already attached to a "
195                  "context");
196         return INVALID_OPERATION;
197     }
198 
199     return mEGLConsumer.attachToContext(tex, *this);
200 }
201 
takeConsumerOwnership()202 void SurfaceTexture::takeConsumerOwnership() {
203     ATRACE_CALL();
204     Mutex::Autolock _l(mMutex);
205     if (mAbandoned) {
206         SFT_LOGE("attachToView: abandoned SurfaceTexture");
207         return;
208     }
209     if (mOpMode == OpMode::detached) {
210         mOpMode = OpMode::attachedToConsumer;
211 
212         if (mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
213             // release possible EGLConsumer texture cache
214             mEGLConsumer.onFreeBufferLocked(mCurrentTexture);
215             mEGLConsumer.onAbandonLocked();
216         }
217     } else {
218         SFT_LOGE("attachToView: already attached");
219     }
220 }
221 
releaseConsumerOwnership()222 void SurfaceTexture::releaseConsumerOwnership() {
223     ATRACE_CALL();
224     Mutex::Autolock _l(mMutex);
225 
226     if (mAbandoned) {
227         SFT_LOGE("detachFromView: abandoned SurfaceTexture");
228         return;
229     }
230 
231     if (mOpMode == OpMode::attachedToConsumer) {
232         mOpMode = OpMode::detached;
233     } else {
234         SFT_LOGE("detachFromView: not attached to View");
235     }
236 }
237 
getCurrentTextureTarget() const238 uint32_t SurfaceTexture::getCurrentTextureTarget() const {
239     return mTexTarget;
240 }
241 
getTransformMatrix(float mtx[16])242 void SurfaceTexture::getTransformMatrix(float mtx[16]) {
243     Mutex::Autolock lock(mMutex);
244     memcpy(mtx, mCurrentTransformMatrix, sizeof(mCurrentTransformMatrix));
245 }
246 
setFilteringEnabled(bool enabled)247 void SurfaceTexture::setFilteringEnabled(bool enabled) {
248     Mutex::Autolock lock(mMutex);
249     if (mAbandoned) {
250         SFT_LOGE("setFilteringEnabled: SurfaceTexture is abandoned!");
251         return;
252     }
253     bool needsRecompute = mFilteringEnabled != enabled;
254     mFilteringEnabled = enabled;
255 
256     if (needsRecompute && mCurrentTexture == BufferQueue::INVALID_BUFFER_SLOT) {
257         SFT_LOGD("setFilteringEnabled called with no current item");
258     }
259 
260     if (needsRecompute && mCurrentTexture != BufferQueue::INVALID_BUFFER_SLOT) {
261         computeCurrentTransformMatrixLocked();
262     }
263 }
264 
computeCurrentTransformMatrixLocked()265 void SurfaceTexture::computeCurrentTransformMatrixLocked() {
266     SFT_LOGV("computeCurrentTransformMatrixLocked");
267     sp<GraphicBuffer> buf = (mCurrentTexture == BufferQueue::INVALID_BUFFER_SLOT)
268             ? nullptr
269             : mSlots[mCurrentTexture].mGraphicBuffer;
270     if (buf == nullptr) {
271         SFT_LOGD("computeCurrentTransformMatrixLocked: no current item");
272     }
273     computeTransformMatrix(mCurrentTransformMatrix, buf, mCurrentCrop, mCurrentTransform,
274                            mFilteringEnabled);
275 }
276 
computeTransformMatrix(float outTransform[16],const sp<GraphicBuffer> & buf,const Rect & cropRect,uint32_t transform,bool filtering)277 void SurfaceTexture::computeTransformMatrix(float outTransform[16], const sp<GraphicBuffer>& buf,
278                                             const Rect& cropRect, uint32_t transform,
279                                             bool filtering) {
280     // Transform matrices
281     static const mat4 mtxFlipH(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1);
282     static const mat4 mtxFlipV(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1);
283     static const mat4 mtxRot90(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1);
284 
285     mat4 xform;
286     if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_H) {
287         xform *= mtxFlipH;
288     }
289     if (transform & NATIVE_WINDOW_TRANSFORM_FLIP_V) {
290         xform *= mtxFlipV;
291     }
292     if (transform & NATIVE_WINDOW_TRANSFORM_ROT_90) {
293         xform *= mtxRot90;
294     }
295 
296     if (!cropRect.isEmpty() && buf.get()) {
297         float tx = 0.0f, ty = 0.0f, sx = 1.0f, sy = 1.0f;
298         float bufferWidth = buf->getWidth();
299         float bufferHeight = buf->getHeight();
300         float shrinkAmount = 0.0f;
301         if (filtering) {
302             // In order to prevent bilinear sampling beyond the edge of the
303             // crop rectangle we may need to shrink it by 2 texels in each
304             // dimension.  Normally this would just need to take 1/2 a texel
305             // off each end, but because the chroma channels of YUV420 images
306             // are subsampled we may need to shrink the crop region by a whole
307             // texel on each side.
308             switch (buf->getPixelFormat()) {
309                 case PIXEL_FORMAT_RGBA_8888:
310                 case PIXEL_FORMAT_RGBX_8888:
311                 case PIXEL_FORMAT_RGBA_FP16:
312                 case PIXEL_FORMAT_RGBA_1010102:
313                 case PIXEL_FORMAT_RGB_888:
314                 case PIXEL_FORMAT_RGB_565:
315                 case PIXEL_FORMAT_BGRA_8888:
316                     // We know there's no subsampling of any channels, so we
317                     // only need to shrink by a half a pixel.
318                     shrinkAmount = 0.5;
319                     break;
320 
321                 default:
322                     // If we don't recognize the format, we must assume the
323                     // worst case (that we care about), which is YUV420.
324                     shrinkAmount = 1.0;
325                     break;
326             }
327         }
328 
329         // Only shrink the dimensions that are not the size of the buffer.
330         if (cropRect.width() < bufferWidth) {
331             tx = (float(cropRect.left) + shrinkAmount) / bufferWidth;
332             sx = (float(cropRect.width()) - (2.0f * shrinkAmount)) / bufferWidth;
333         }
334         if (cropRect.height() < bufferHeight) {
335             ty = (float(bufferHeight - cropRect.bottom) + shrinkAmount) / bufferHeight;
336             sy = (float(cropRect.height()) - (2.0f * shrinkAmount)) / bufferHeight;
337         }
338 
339         mat4 crop(sx, 0, 0, 0, 0, sy, 0, 0, 0, 0, 1, 0, tx, ty, 0, 1);
340         xform = crop * xform;
341     }
342 
343     // SurfaceFlinger expects the top of its window textures to be at a Y
344     // coordinate of 0, so SurfaceTexture must behave the same way.  We don't
345     // want to expose this to applications, however, so we must add an
346     // additional vertical flip to the transform after all the other transforms.
347     xform = mtxFlipV * xform;
348 
349     memcpy(outTransform, xform.asArray(), sizeof(xform));
350 }
351 
scaleDownCrop(const Rect & crop,uint32_t bufferWidth,uint32_t bufferHeight)352 Rect SurfaceTexture::scaleDownCrop(const Rect& crop, uint32_t bufferWidth, uint32_t bufferHeight) {
353     Rect outCrop = crop;
354 
355     uint32_t newWidth = static_cast<uint32_t>(crop.width());
356     uint32_t newHeight = static_cast<uint32_t>(crop.height());
357 
358     if (newWidth * bufferHeight > newHeight * bufferWidth) {
359         newWidth = newHeight * bufferWidth / bufferHeight;
360         ALOGV("too wide: newWidth = %d", newWidth);
361     } else if (newWidth * bufferHeight < newHeight * bufferWidth) {
362         newHeight = newWidth * bufferHeight / bufferWidth;
363         ALOGV("too tall: newHeight = %d", newHeight);
364     }
365 
366     uint32_t currentWidth = static_cast<uint32_t>(crop.width());
367     uint32_t currentHeight = static_cast<uint32_t>(crop.height());
368 
369     // The crop is too wide
370     if (newWidth < currentWidth) {
371         uint32_t dw = currentWidth - newWidth;
372         auto halfdw = dw / 2;
373         outCrop.left += halfdw;
374         // Not halfdw because it would subtract 1 too few when dw is odd
375         outCrop.right -= (dw - halfdw);
376         // The crop is too tall
377     } else if (newHeight < currentHeight) {
378         uint32_t dh = currentHeight - newHeight;
379         auto halfdh = dh / 2;
380         outCrop.top += halfdh;
381         // Not halfdh because it would subtract 1 too few when dh is odd
382         outCrop.bottom -= (dh - halfdh);
383     }
384 
385     ALOGV("getCurrentCrop final crop [%d,%d,%d,%d]", outCrop.left, outCrop.top, outCrop.right,
386           outCrop.bottom);
387 
388     return outCrop;
389 }
390 
getTimestamp()391 nsecs_t SurfaceTexture::getTimestamp() {
392     SFT_LOGV("getTimestamp");
393     Mutex::Autolock lock(mMutex);
394     return mCurrentTimestamp;
395 }
396 
getCurrentDataSpace()397 android_dataspace SurfaceTexture::getCurrentDataSpace() {
398     SFT_LOGV("getCurrentDataSpace");
399     Mutex::Autolock lock(mMutex);
400     return mCurrentDataSpace;
401 }
402 
getFrameNumber()403 uint64_t SurfaceTexture::getFrameNumber() {
404     SFT_LOGV("getFrameNumber");
405     Mutex::Autolock lock(mMutex);
406     return mCurrentFrameNumber;
407 }
408 
getCurrentCrop() const409 Rect SurfaceTexture::getCurrentCrop() const {
410     Mutex::Autolock lock(mMutex);
411     return (mCurrentScalingMode == NATIVE_WINDOW_SCALING_MODE_SCALE_CROP)
412             ? scaleDownCrop(mCurrentCrop, mDefaultWidth, mDefaultHeight)
413             : mCurrentCrop;
414 }
415 
getCurrentTransform() const416 uint32_t SurfaceTexture::getCurrentTransform() const {
417     Mutex::Autolock lock(mMutex);
418     return mCurrentTransform;
419 }
420 
getCurrentScalingMode() const421 uint32_t SurfaceTexture::getCurrentScalingMode() const {
422     Mutex::Autolock lock(mMutex);
423     return mCurrentScalingMode;
424 }
425 
getCurrentFence() const426 sp<Fence> SurfaceTexture::getCurrentFence() const {
427     Mutex::Autolock lock(mMutex);
428     return mCurrentFence;
429 }
430 
getCurrentFenceTime() const431 std::shared_ptr<FenceTime> SurfaceTexture::getCurrentFenceTime() const {
432     Mutex::Autolock lock(mMutex);
433     return mCurrentFenceTime;
434 }
435 
freeBufferLocked(int slotIndex)436 void SurfaceTexture::freeBufferLocked(int slotIndex) {
437     SFT_LOGV("freeBufferLocked: slotIndex=%d", slotIndex);
438     if (slotIndex == mCurrentTexture) {
439         mCurrentTexture = BufferQueue::INVALID_BUFFER_SLOT;
440     }
441     // The slotIndex buffer could have EGL cache, but there is no way to tell
442     // for sure. Buffers can be freed after SurfaceTexture has detached from GL
443     // context or View.
444     mEGLConsumer.onFreeBufferLocked(slotIndex);
445     ConsumerBase::freeBufferLocked(slotIndex);
446 }
447 
abandonLocked()448 void SurfaceTexture::abandonLocked() {
449     SFT_LOGV("abandonLocked");
450     mEGLConsumer.onAbandonLocked();
451     ConsumerBase::abandonLocked();
452 }
453 
setConsumerUsageBits(uint64_t usage)454 status_t SurfaceTexture::setConsumerUsageBits(uint64_t usage) {
455     return ConsumerBase::setConsumerUsageBits(usage | DEFAULT_USAGE_FLAGS);
456 }
457 
dumpLocked(String8 & result,const char * prefix) const458 void SurfaceTexture::dumpLocked(String8& result, const char* prefix) const {
459     result.appendFormat("%smTexName=%d mCurrentTexture=%d\n"
460                         "%smCurrentCrop=[%d,%d,%d,%d] mCurrentTransform=%#x\n",
461                         prefix, mTexName, mCurrentTexture, prefix, mCurrentCrop.left,
462                         mCurrentCrop.top, mCurrentCrop.right, mCurrentCrop.bottom,
463                         mCurrentTransform);
464 
465     ConsumerBase::dumpLocked(result, prefix);
466 }
467 
dequeueBuffer(int * outSlotid,android_dataspace * outDataspace,HdrMetadata * outHdrMetadata,float * outTransformMatrix,uint32_t * outTransform,bool * outQueueEmpty,SurfaceTexture_createReleaseFence createFence,SurfaceTexture_fenceWait fenceWait,void * fencePassThroughHandle,ARect * currentCrop)468 sp<GraphicBuffer> SurfaceTexture::dequeueBuffer(int* outSlotid, android_dataspace* outDataspace,
469                                                 HdrMetadata* outHdrMetadata,
470                                                 float* outTransformMatrix, uint32_t* outTransform,
471                                                 bool* outQueueEmpty,
472                                                 SurfaceTexture_createReleaseFence createFence,
473                                                 SurfaceTexture_fenceWait fenceWait,
474                                                 void* fencePassThroughHandle, ARect* currentCrop) {
475     Mutex::Autolock _l(mMutex);
476     sp<GraphicBuffer> buffer;
477 
478     if (mAbandoned) {
479         SFT_LOGE("dequeueImage: SurfaceTexture is abandoned!");
480         return buffer;
481     }
482 
483     if (mOpMode != OpMode::attachedToConsumer) {
484         SFT_LOGE("dequeueImage: SurfaceTexture is not attached to a View");
485         return buffer;
486     }
487 
488     buffer = mImageConsumer.dequeueBuffer(outSlotid, outDataspace, outHdrMetadata, outQueueEmpty,
489                                           *this, createFence, fenceWait, fencePassThroughHandle);
490     memcpy(outTransformMatrix, mCurrentTransformMatrix, sizeof(mCurrentTransformMatrix));
491     *outTransform = mCurrentTransform;
492     *currentCrop = mCurrentCrop;
493     return buffer;
494 }
495 
setSurfaceTextureListener(const sp<android::SurfaceTexture::SurfaceTextureListener> & listener)496 void SurfaceTexture::setSurfaceTextureListener(
497         const sp<android::SurfaceTexture::SurfaceTextureListener>& listener) {
498     SFT_LOGV("setSurfaceTextureListener");
499 
500     Mutex::Autolock _l(mMutex);
501     mSurfaceTextureListener = listener;
502     if (mSurfaceTextureListener != nullptr) {
503         mFrameAvailableListenerProxy =
504                 sp<FrameAvailableListenerProxy>::make(mSurfaceTextureListener);
505         setFrameAvailableListener(mFrameAvailableListenerProxy);
506     } else {
507         mFrameAvailableListenerProxy.clear();
508     }
509 }
510 
onFrameAvailable(const BufferItem & item)511 void SurfaceTexture::FrameAvailableListenerProxy::onFrameAvailable(const BufferItem& item) {
512     const auto listener = mSurfaceTextureListener.promote();
513     if (listener) {
514         listener->onFrameAvailable(item);
515     }
516 }
517 
518 #if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
onSetFrameRate(float frameRate,int8_t compatibility,int8_t changeFrameRateStrategy)519 void SurfaceTexture::onSetFrameRate(float frameRate, int8_t compatibility,
520                                     int8_t changeFrameRateStrategy) {
521     SFT_LOGV("onSetFrameRate: %.2f", frameRate);
522 
523     auto listener = [&] {
524         Mutex::Autolock _l(mMutex);
525         return mSurfaceTextureListener;
526     }();
527 
528     if (listener) {
529         listener->onSetFrameRate(frameRate, compatibility, changeFrameRateStrategy);
530     }
531 }
532 #endif
533 
534 } // namespace android
535