Home | History | Annotate | Download | only in legacy
      1 /*
      2  * Copyright (C) 2014 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 package android.hardware.camera2.legacy;
     18 
     19 import android.graphics.ImageFormat;
     20 import android.graphics.SurfaceTexture;
     21 import android.hardware.Camera;
     22 import android.hardware.camera2.CameraCharacteristics;
     23 import android.hardware.camera2.CaptureRequest;
     24 import android.hardware.camera2.impl.CameraDeviceImpl;
     25 import android.hardware.camera2.impl.CaptureResultExtras;
     26 import android.hardware.camera2.ICameraDeviceCallbacks;
     27 import android.hardware.camera2.params.StreamConfiguration;
     28 import android.hardware.camera2.params.StreamConfigurationMap;
     29 import android.hardware.camera2.utils.ArrayUtils;
     30 import android.hardware.camera2.utils.CameraBinderDecorator;
     31 import android.hardware.camera2.utils.LongParcelable;
     32 import android.hardware.camera2.impl.CameraMetadataNative;
     33 import android.hardware.camera2.utils.CameraRuntimeException;
     34 import android.os.ConditionVariable;
     35 import android.os.Handler;
     36 import android.os.HandlerThread;
     37 import android.os.RemoteException;
     38 import android.util.Log;
     39 import android.util.Size;
     40 import android.view.Surface;
     41 
     42 import java.util.ArrayList;
     43 import java.util.Arrays;
     44 import java.util.Collection;
     45 import java.util.List;
     46 
     47 import static android.hardware.camera2.legacy.LegacyExceptionUtils.*;
     48 import static android.hardware.camera2.utils.CameraBinderDecorator.*;
     49 import static com.android.internal.util.Preconditions.*;
     50 
     51 /**
     52  * This class emulates the functionality of a Camera2 device using a the old Camera class.
     53  *
     54  * <p>
     55  * There are two main components that are used to implement this:
     56  * - A state machine containing valid Camera2 device states ({@link CameraDeviceState}).
     57  * - A message-queue based pipeline that manages an old Camera class, and executes capture and
     58  *   configuration requests.
     59  * </p>
     60  */
     61 public class LegacyCameraDevice implements AutoCloseable {
     62     public static final String DEBUG_PROP = "HAL1ShimLogging";
     63     private final String TAG;
     64 
     65     private static final boolean DEBUG = Log.isLoggable(LegacyCameraDevice.DEBUG_PROP, Log.DEBUG);
     66     private final int mCameraId;
     67     private final CameraCharacteristics mStaticCharacteristics;
     68     private final ICameraDeviceCallbacks mDeviceCallbacks;
     69     private final CameraDeviceState mDeviceState = new CameraDeviceState();
     70     private List<Surface> mConfiguredSurfaces;
     71     private boolean mClosed = false;
     72 
     73     private final ConditionVariable mIdle = new ConditionVariable(/*open*/true);
     74 
     75     private final HandlerThread mResultThread = new HandlerThread("ResultThread");
     76     private final HandlerThread mCallbackHandlerThread = new HandlerThread("CallbackThread");
     77     private final Handler mCallbackHandler;
     78     private final Handler mResultHandler;
     79     private static final int ILLEGAL_VALUE = -1;
     80 
     81     private CaptureResultExtras getExtrasFromRequest(RequestHolder holder) {
     82         if (holder == null) {
     83             return new CaptureResultExtras(ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE,
     84                     ILLEGAL_VALUE, ILLEGAL_VALUE, ILLEGAL_VALUE);
     85         }
     86         return new CaptureResultExtras(holder.getRequestId(), holder.getSubsequeceId(),
     87                 /*afTriggerId*/0, /*precaptureTriggerId*/0, holder.getFrameNumber(),
     88                 /*partialResultCount*/1);
     89     }
     90 
     91     /**
     92      * Listener for the camera device state machine.  Calls the appropriate
     93      * {@link ICameraDeviceCallbacks} for each state transition.
     94      */
     95     private final CameraDeviceState.CameraDeviceStateListener mStateListener =
     96             new CameraDeviceState.CameraDeviceStateListener() {
     97         @Override
     98         public void onError(final int errorCode, final RequestHolder holder) {
     99             if (DEBUG) {
    100                 Log.d(TAG, "onError called, errorCode = " + errorCode);
    101             }
    102             switch (errorCode) {
    103                 /*
    104                  * Only be considered idle if we hit a fatal error
    105                  * and no further requests can be processed.
    106                  */
    107                 case CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DISCONNECTED:
    108                 case CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_SERVICE:
    109                 case CameraDeviceImpl.CameraDeviceCallbacks.ERROR_CAMERA_DEVICE: {
    110                     mIdle.open();
    111 
    112                     if (DEBUG) {
    113                         Log.d(TAG, "onError - opening idle");
    114                     }
    115                 }
    116             }
    117 
    118             final CaptureResultExtras extras = getExtrasFromRequest(holder);
    119             mResultHandler.post(new Runnable() {
    120                 @Override
    121                 public void run() {
    122                     if (DEBUG) {
    123                         Log.d(TAG, "doing onError callback for request " + holder.getRequestId() +
    124                                 ", with error code " + errorCode);
    125                     }
    126                     try {
    127                         mDeviceCallbacks.onDeviceError(errorCode, extras);
    128                     } catch (RemoteException e) {
    129                         throw new IllegalStateException(
    130                                 "Received remote exception during onCameraError callback: ", e);
    131                     }
    132                 }
    133             });
    134         }
    135 
    136         @Override
    137         public void onConfiguring() {
    138             // Do nothing
    139             if (DEBUG) {
    140                 Log.d(TAG, "doing onConfiguring callback.");
    141             }
    142         }
    143 
    144         @Override
    145         public void onIdle() {
    146             if (DEBUG) {
    147                 Log.d(TAG, "onIdle called");
    148             }
    149 
    150             mIdle.open();
    151 
    152             mResultHandler.post(new Runnable() {
    153                 @Override
    154                 public void run() {
    155                     if (DEBUG) {
    156                         Log.d(TAG, "doing onIdle callback.");
    157                     }
    158                     try {
    159                         mDeviceCallbacks.onDeviceIdle();
    160                     } catch (RemoteException e) {
    161                         throw new IllegalStateException(
    162                                 "Received remote exception during onCameraIdle callback: ", e);
    163                     }
    164                 }
    165             });
    166         }
    167 
    168         @Override
    169         public void onBusy() {
    170             mIdle.close();
    171 
    172             if (DEBUG) {
    173                 Log.d(TAG, "onBusy called");
    174             }
    175         }
    176 
    177         @Override
    178         public void onCaptureStarted(final RequestHolder holder, final long timestamp) {
    179             final CaptureResultExtras extras = getExtrasFromRequest(holder);
    180 
    181             mResultHandler.post(new Runnable() {
    182                 @Override
    183                 public void run() {
    184                     if (DEBUG) {
    185                         Log.d(TAG, "doing onCaptureStarted callback for request " +
    186                                 holder.getRequestId());
    187                     }
    188                     try {
    189                         mDeviceCallbacks.onCaptureStarted(extras, timestamp);
    190                     } catch (RemoteException e) {
    191                         throw new IllegalStateException(
    192                                 "Received remote exception during onCameraError callback: ", e);
    193                     }
    194                 }
    195             });
    196         }
    197 
    198         @Override
    199         public void onCaptureResult(final CameraMetadataNative result, final RequestHolder holder) {
    200             final CaptureResultExtras extras = getExtrasFromRequest(holder);
    201 
    202             mResultHandler.post(new Runnable() {
    203                 @Override
    204                 public void run() {
    205                     if (DEBUG) {
    206                         Log.d(TAG, "doing onCaptureResult callback for request " +
    207                                 holder.getRequestId());
    208                     }
    209                     try {
    210                         mDeviceCallbacks.onResultReceived(result, extras);
    211                     } catch (RemoteException e) {
    212                         throw new IllegalStateException(
    213                                 "Received remote exception during onCameraError callback: ", e);
    214                     }
    215                 }
    216             });
    217         }
    218     };
    219 
    220     private final RequestThreadManager mRequestThreadManager;
    221 
    222     /**
    223      * Check if a given surface uses {@link ImageFormat#YUV_420_888} or format that can be readily
    224      * converted to this; YV12 and NV21 are the two currently supported formats.
    225      *
    226      * @param s the surface to check.
    227      * @return {@code true} if the surfaces uses {@link ImageFormat#YUV_420_888} or a compatible
    228      *          format.
    229      */
    230     static boolean needsConversion(Surface s) throws BufferQueueAbandonedException {
    231         int nativeType = detectSurfaceType(s);
    232         return nativeType == ImageFormat.YUV_420_888 || nativeType == ImageFormat.YV12 ||
    233                 nativeType == ImageFormat.NV21;
    234     }
    235 
    236     /**
    237      * Create a new emulated camera device from a given Camera 1 API camera.
    238      *
    239      * <p>
    240      * The {@link Camera} provided to this constructor must already have been successfully opened,
    241      * and ownership of the provided camera is passed to this object.  No further calls to the
    242      * camera methods should be made following this constructor.
    243      * </p>
    244      *
    245      * @param cameraId the id of the camera.
    246      * @param camera an open {@link Camera} device.
    247      * @param characteristics the static camera characteristics for this camera device
    248      * @param callbacks {@link ICameraDeviceCallbacks} callbacks to call for Camera2 API operations.
    249      */
    250     public LegacyCameraDevice(int cameraId, Camera camera, CameraCharacteristics characteristics,
    251             ICameraDeviceCallbacks callbacks) {
    252         mCameraId = cameraId;
    253         mDeviceCallbacks = callbacks;
    254         TAG = String.format("CameraDevice-%d-LE", mCameraId);
    255 
    256         mResultThread.start();
    257         mResultHandler = new Handler(mResultThread.getLooper());
    258         mCallbackHandlerThread.start();
    259         mCallbackHandler = new Handler(mCallbackHandlerThread.getLooper());
    260         mDeviceState.setCameraDeviceCallbacks(mCallbackHandler, mStateListener);
    261         mStaticCharacteristics = characteristics;
    262         mRequestThreadManager =
    263                 new RequestThreadManager(cameraId, camera, characteristics, mDeviceState);
    264         mRequestThreadManager.start();
    265     }
    266 
    267     /**
    268      * Configure the device with a set of output surfaces.
    269      *
    270      * <p>Using empty or {@code null} {@code outputs} is the same as unconfiguring.</p>
    271      *
    272      * <p>Every surface in {@code outputs} must be non-{@code null}.</p>
    273      *
    274      * @param outputs a list of surfaces to set.
    275      * @return an error code for this binder operation, or {@link NO_ERROR}
    276      *          on success.
    277      */
    278     public int configureOutputs(List<Surface> outputs) {
    279         if (outputs != null) {
    280             for (Surface output : outputs) {
    281                 if (output == null) {
    282                     Log.e(TAG, "configureOutputs - null outputs are not allowed");
    283                     return BAD_VALUE;
    284                 }
    285                 StreamConfigurationMap streamConfigurations = mStaticCharacteristics.
    286                         get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
    287 
    288                 // Validate surface size and format.
    289                 try {
    290                     Size s = getSurfaceSize(output);
    291                     int surfaceType = detectSurfaceType(output);
    292                     Size[] sizes = streamConfigurations.getOutputSizes(surfaceType);
    293 
    294                     if (sizes == null) {
    295                         // WAR: Override default format to IMPLEMENTATION_DEFINED for b/9487482
    296                         if ((surfaceType >= LegacyMetadataMapper.HAL_PIXEL_FORMAT_RGBA_8888 &&
    297                             surfaceType <= LegacyMetadataMapper.HAL_PIXEL_FORMAT_BGRA_8888)) {
    298 
    299                             // YUV_420_888 is always present in LEGACY for all IMPLEMENTATION_DEFINED
    300                             // output sizes, and is publicly visible in the API (i.e.
    301                             // {@code #getOutputSizes} works here).
    302                             sizes = streamConfigurations.getOutputSizes(ImageFormat.YUV_420_888);
    303                         } else if (surfaceType == LegacyMetadataMapper.HAL_PIXEL_FORMAT_BLOB) {
    304                             sizes = streamConfigurations.getOutputSizes(ImageFormat.JPEG);
    305                         }
    306                     }
    307 
    308                     if (!ArrayUtils.contains(sizes, s)) {
    309                         String reason = (sizes == null) ? "format is invalid." :
    310                                 ("size not in valid set: " + Arrays.toString(sizes));
    311                         Log.e(TAG, String.format("Surface with size (w=%d, h=%d) and format 0x%x is"
    312                                 + " not valid, %s", s.getWidth(), s.getHeight(), surfaceType,
    313                                 reason));
    314                         return BAD_VALUE;
    315                     }
    316                 } catch (BufferQueueAbandonedException e) {
    317                     Log.e(TAG, "Surface bufferqueue is abandoned, cannot configure as output: ", e);
    318                     return BAD_VALUE;
    319                 }
    320 
    321             }
    322         }
    323 
    324         boolean success = false;
    325         if (mDeviceState.setConfiguring()) {
    326             mRequestThreadManager.configure(outputs);
    327             success = mDeviceState.setIdle();
    328         }
    329 
    330         if (success) {
    331             mConfiguredSurfaces = outputs != null ? new ArrayList<>(outputs) : null;
    332         } else {
    333             return CameraBinderDecorator.INVALID_OPERATION;
    334         }
    335         return CameraBinderDecorator.NO_ERROR;
    336     }
    337 
    338     /**
    339      * Submit a burst of capture requests.
    340      *
    341      * @param requestList a list of capture requests to execute.
    342      * @param repeating {@code true} if this burst is repeating.
    343      * @param frameNumber an output argument that contains either the frame number of the last frame
    344      *                    that will be returned for this request, or the frame number of the last
    345      *                    frame that will be returned for the current repeating request if this
    346      *                    burst is set to be repeating.
    347      * @return the request id.
    348      */
    349     public int submitRequestList(List<CaptureRequest> requestList, boolean repeating,
    350             /*out*/LongParcelable frameNumber) {
    351         if (requestList == null || requestList.isEmpty()) {
    352             Log.e(TAG, "submitRequestList - Empty/null requests are not allowed");
    353             return BAD_VALUE;
    354         }
    355 
    356         List<Long> surfaceIds = (mConfiguredSurfaces == null) ? new ArrayList<Long>() :
    357                 getSurfaceIds(mConfiguredSurfaces);
    358 
    359         // Make sure that there all requests have at least 1 surface; all surfaces are non-null
    360         for (CaptureRequest request : requestList) {
    361             if (request.getTargets().isEmpty()) {
    362                 Log.e(TAG, "submitRequestList - "
    363                         + "Each request must have at least one Surface target");
    364                 return BAD_VALUE;
    365             }
    366 
    367             for (Surface surface : request.getTargets()) {
    368                 if (surface == null) {
    369                     Log.e(TAG, "submitRequestList - Null Surface targets are not allowed");
    370                     return BAD_VALUE;
    371                 } else if (mConfiguredSurfaces == null) {
    372                     Log.e(TAG, "submitRequestList - must configure " +
    373                             " device with valid surfaces before submitting requests");
    374                     return INVALID_OPERATION;
    375                 } else if (!containsSurfaceId(surface, surfaceIds)) {
    376                     Log.e(TAG, "submitRequestList - cannot use a surface that wasn't configured");
    377                     return BAD_VALUE;
    378                 }
    379             }
    380         }
    381 
    382         // TODO: further validation of request here
    383         mIdle.close();
    384         return mRequestThreadManager.submitCaptureRequests(requestList, repeating,
    385                 frameNumber);
    386     }
    387 
    388     /**
    389      * Submit a single capture request.
    390      *
    391      * @param request the capture request to execute.
    392      * @param repeating {@code true} if this request is repeating.
    393      * @param frameNumber an output argument that contains either the frame number of the last frame
    394      *                    that will be returned for this request, or the frame number of the last
    395      *                    frame that will be returned for the current repeating request if this
    396      *                    request is set to be repeating.
    397      * @return the request id.
    398      */
    399     public int submitRequest(CaptureRequest request, boolean repeating,
    400             /*out*/LongParcelable frameNumber) {
    401         ArrayList<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
    402         requestList.add(request);
    403         return submitRequestList(requestList, repeating, frameNumber);
    404     }
    405 
    406     /**
    407      * Cancel the repeating request with the given request id.
    408      *
    409      * @param requestId the request id of the request to cancel.
    410      * @return the last frame number to be returned from the HAL for the given repeating request, or
    411      *          {@code INVALID_FRAME} if none exists.
    412      */
    413     public long cancelRequest(int requestId) {
    414         return mRequestThreadManager.cancelRepeating(requestId);
    415     }
    416 
    417     /**
    418      * Block until the {@link ICameraDeviceCallbacks#onCameraIdle()} callback is received.
    419      */
    420     public void waitUntilIdle()  {
    421         mIdle.block();
    422     }
    423 
    424     /**
    425      * Flush any pending requests.
    426      *
    427      * @return the last frame number.
    428      */
    429     public long flush() {
    430         long lastFrame = mRequestThreadManager.flush();
    431         waitUntilIdle();
    432         return lastFrame;
    433     }
    434 
    435     /**
    436      * Return {@code true} if the device has been closed.
    437      */
    438     public boolean isClosed() {
    439         return mClosed;
    440     }
    441 
    442     @Override
    443     public void close() {
    444         mRequestThreadManager.quit();
    445         mCallbackHandlerThread.quitSafely();
    446         mResultThread.quitSafely();
    447 
    448         try {
    449             mCallbackHandlerThread.join();
    450         } catch (InterruptedException e) {
    451             Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
    452                     mCallbackHandlerThread.getName(), mCallbackHandlerThread.getId()));
    453         }
    454 
    455         try {
    456             mResultThread.join();
    457         } catch (InterruptedException e) {
    458             Log.e(TAG, String.format("Thread %s (%d) interrupted while quitting.",
    459                     mResultThread.getName(), mResultThread.getId()));
    460         }
    461 
    462         mClosed = true;
    463     }
    464 
    465     @Override
    466     protected void finalize() throws Throwable {
    467         try {
    468             close();
    469         } catch (CameraRuntimeException e) {
    470             Log.e(TAG, "Got error while trying to finalize, ignoring: " + e.getMessage());
    471         } finally {
    472             super.finalize();
    473         }
    474     }
    475 
    476     /**
    477      * Query the surface for its currently configured default buffer size.
    478      * @param surface a non-{@code null} {@code Surface}
    479      * @return the width and height of the surface
    480      *
    481      * @throws NullPointerException if the {@code surface} was {@code null}
    482      * @throws IllegalStateException if the {@code surface} was invalid
    483      */
    484     static Size getSurfaceSize(Surface surface) throws BufferQueueAbandonedException {
    485         checkNotNull(surface);
    486 
    487         int[] dimens = new int[2];
    488         LegacyExceptionUtils.throwOnError(nativeDetectSurfaceDimens(surface, /*out*/dimens));
    489 
    490         return new Size(dimens[0], dimens[1]);
    491     }
    492 
    493     static int detectSurfaceType(Surface surface) throws BufferQueueAbandonedException {
    494         checkNotNull(surface);
    495         return LegacyExceptionUtils.throwOnError(nativeDetectSurfaceType(surface));
    496     }
    497 
    498     static void configureSurface(Surface surface, int width, int height,
    499                                  int pixelFormat) throws BufferQueueAbandonedException {
    500         checkNotNull(surface);
    501         checkArgumentPositive(width, "width must be positive.");
    502         checkArgumentPositive(height, "height must be positive.");
    503 
    504         LegacyExceptionUtils.throwOnError(nativeConfigureSurface(surface, width, height,
    505                 pixelFormat));
    506     }
    507 
    508     static void produceFrame(Surface surface, byte[] pixelBuffer, int width,
    509                              int height, int pixelFormat)
    510             throws BufferQueueAbandonedException {
    511         checkNotNull(surface);
    512         checkNotNull(pixelBuffer);
    513         checkArgumentPositive(width, "width must be positive.");
    514         checkArgumentPositive(height, "height must be positive.");
    515 
    516         LegacyExceptionUtils.throwOnError(nativeProduceFrame(surface, pixelBuffer, width, height,
    517                 pixelFormat));
    518     }
    519 
    520     static void setSurfaceFormat(Surface surface, int pixelFormat)
    521             throws BufferQueueAbandonedException {
    522         checkNotNull(surface);
    523 
    524         LegacyExceptionUtils.throwOnError(nativeSetSurfaceFormat(surface, pixelFormat));
    525     }
    526 
    527     static void setSurfaceDimens(Surface surface, int width, int height)
    528             throws BufferQueueAbandonedException {
    529         checkNotNull(surface);
    530         checkArgumentPositive(width, "width must be positive.");
    531         checkArgumentPositive(height, "height must be positive.");
    532 
    533         LegacyExceptionUtils.throwOnError(nativeSetSurfaceDimens(surface, width, height));
    534     }
    535 
    536     static long getSurfaceId(Surface surface) {
    537         checkNotNull(surface);
    538         return nativeGetSurfaceId(surface);
    539     }
    540 
    541     static List<Long> getSurfaceIds(Collection<Surface> surfaces) {
    542         if (surfaces == null) {
    543             throw new NullPointerException("Null argument surfaces");
    544         }
    545         List<Long> surfaceIds = new ArrayList<>();
    546         for (Surface s : surfaces) {
    547             long id = getSurfaceId(s);
    548             if (id == 0) {
    549                 throw new IllegalStateException(
    550                         "Configured surface had null native GraphicBufferProducer pointer!");
    551             }
    552             surfaceIds.add(id);
    553         }
    554         return surfaceIds;
    555     }
    556 
    557     static boolean containsSurfaceId(Surface s, Collection<Long> ids) {
    558         long id = getSurfaceId(s);
    559         return ids.contains(id);
    560     }
    561 
    562     static void setSurfaceOrientation(Surface surface, int facing, int sensorOrientation)
    563             throws BufferQueueAbandonedException {
    564         checkNotNull(surface);
    565         LegacyExceptionUtils.throwOnError(nativeSetSurfaceOrientation(surface, facing,
    566                 sensorOrientation));
    567     }
    568 
    569     static Size getTextureSize(SurfaceTexture surfaceTexture)
    570             throws BufferQueueAbandonedException {
    571         checkNotNull(surfaceTexture);
    572 
    573         int[] dimens = new int[2];
    574         LegacyExceptionUtils.throwOnError(nativeDetectTextureDimens(surfaceTexture,
    575                 /*out*/dimens));
    576 
    577         return new Size(dimens[0], dimens[1]);
    578     }
    579 
    580     static void setNextTimestamp(Surface surface, long timestamp)
    581             throws BufferQueueAbandonedException {
    582         checkNotNull(surface);
    583         LegacyExceptionUtils.throwOnError(nativeSetNextTimestamp(surface, timestamp));
    584     }
    585 
    586     private static native int nativeDetectSurfaceType(Surface surface);
    587 
    588     private static native int nativeDetectSurfaceDimens(Surface surface,
    589             /*out*/int[/*2*/] dimens);
    590 
    591     private static native int nativeConfigureSurface(Surface surface, int width, int height,
    592                                                         int pixelFormat);
    593 
    594     private static native int nativeProduceFrame(Surface surface, byte[] pixelBuffer, int width,
    595                                                     int height, int pixelFormat);
    596 
    597     private static native int nativeSetSurfaceFormat(Surface surface, int pixelFormat);
    598 
    599     private static native int nativeSetSurfaceDimens(Surface surface, int width, int height);
    600 
    601     private static native long nativeGetSurfaceId(Surface surface);
    602 
    603     private static native int nativeSetSurfaceOrientation(Surface surface, int facing,
    604                                                              int sensorOrientation);
    605 
    606     private static native int nativeDetectTextureDimens(SurfaceTexture surfaceTexture,
    607             /*out*/int[/*2*/] dimens);
    608 
    609     private static native int nativeSetNextTimestamp(Surface surface, long timestamp);
    610 
    611     static native int nativeGetJpegFooterSize();
    612 }
    613