title: Android camera preview rendered to a texture is stretched
body: <p>I'm sending android camera preview to Unity3d using texture and it is stretched. The preview is not rendered to any view in android but directly to a texture with:</p>
  
  <pre><code>setPreviewTexture(texture);
  </code></pre>
  
  <p>Then, the bytes are sent to Unity3d and drawn on screen with every onPreviewFrame.</p>
  
  <p>The camera preview size and the texture size are set to 1024x768 and the texture container in Unity3d has the same size. The device's resolution is 960x540 so it's a different ratio. I'm selecting a supported resolution for the camera, the native size has a ratio of 4:3 so there should be no stretching.
  It seems as if android is rendering to texture only the part that can actually be rendered to screen - it takes a 16:9 image and renders it to a 4:3 texture.
  If I'm wrong please correct me but that how it seems to work in this example.
  Here is a bit of code:</p>
  
  <pre><code>public int startCamera(int idx, int width, int height) {
      nativeTexturePointer = createExternalTexture();
      texture = new SurfaceTexture(nativeTexturePointer);
  
      mCamera = Camera.open(idx);
      setupCamera(width, height);
  
      try {
          mCamera.setPreviewTexture(texture);
          mCamera.setPreviewCallback(this);
          mCamera.startPreview();
          Log.i("Unity", "JAVA: camera started");
      } catch (IOException ioe) {
          Log.w("Unity", "JAVA: CAM LAUNCH FAILED");
      }
      return nativeTexturePointer;
  }
  
  public void stopCamera() {
      mCamera.setPreviewCallback(null);
      mCamera.stopPreview();
      mCamera.release();
      GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
      Log.i("Unity", "JAVA: Camera stopped");
  }
  
  private int createExternalTexture() {
      int[] textureIdContainer = new int[1];
      GLES20.glGenTextures(1, textureIdContainer, 0);
      GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureIdContainer[0]);
      return textureIdContainer[0];
  }
  
  @SuppressLint("NewApi")
  private void setupCamera(int width, int height) {
      Camera.Parameters params = mCamera.getParameters();
      params.setRecordingHint(true);
      params.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
      params.setPreviewFormat(17);
      params.setZoom(0);
          // 16 ~ NV16 ~ YCbCr
          // 17 ~ NV21 ~ YCbCr ~ DEFAULT *
          // 4  ~ RGB_565
          // 256~ JPEG
          // 20 ~ YUY2 ~ YcbCr ...
          // 842094169 ~ YV12 ~ 4:2:0 YCrCb comprised of WXH Y plane, W/2xH/2 Cr &amp; Cb. see documentation *
      params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
      Camera.Size previewSize = getOptimalSize(width, height, mCamera.getParameters().getSupportedPreviewSizes());
      Camera.Size picSize = getOptimalSize(width, height, mCamera.getParameters().getSupportedPictureSizes());
  
      params.setPictureSize(picSize.width, picSize.height);
      params.setPreviewSize(previewSize.width, previewSize.height);
      params.setWhiteBalance(Camera.Parameters.WHITE_BALANCE_AUTO);
      params.setExposureCompensation(0);
  
      try{
          mCamera.setParameters(params);
      } catch (Exception e){
          Log.i("Unity", "ERROR: " + e.getMessage());
      }
  
      Camera.Size mCameraPreviewSize = params.getPreviewSize();
      prevWidth = mCameraPreviewSize.width;
      prevHeight = mCameraPreviewSize.height;
  
      int[] fpsRange = new int[2];
      params.getPreviewFpsRange(fpsRange);
  }
  
  private Camera.Size getOptimalSize(int width, int height, List&lt;Camera.Size&gt; sizes) {
      if(mCamera == null)
          return null;
  
      final double ASPECT_TOLERANCE = 0.1;
      double targetRatio=(double)width / height;
  
      if (sizes == null)
          return null;
  
      Camera.Size optimalSize = null;
      double minDiff = Double.MAX_VALUE;
      int targetWidth = width;
  
      for (Camera.Size size : sizes) {
          double ratio = (double) size.width / size.height;
          Log.i("Unity", "RES: size=" + size.width + "/" + size.height + "/ Aspect Ratio: " + ratio + "target width: " + width + "target height: " + height);
  
          if (Math.abs(ratio - targetRatio) &gt; ASPECT_TOLERANCE) continue;
          if (Math.abs(size.width - targetWidth) &lt; minDiff) {
              optimalSize = size;
              minDiff = Math.abs(size.width - targetWidth);
          }
      }
  
      if (optimalSize == null) {
          minDiff = Double.MAX_VALUE;
          for (Camera.Size size : sizes) {
              if (Math.abs(size.width - targetWidth) &lt; minDiff) {
                  optimalSize = size;
                  minDiff = Math.abs(size.width - targetWidth);
              }
          }
      }
      Log.i("Unity", "optimal size=" + optimalSize.width + "/" + optimalSize.height + "/ Aspect Ratio: " + (double) optimalSize.width / optimalSize.height);
      return optimalSize;
  }
  
  public int getPreviewSizeWidth() {
      return prevWidth;
  }
  
  public int getPreviewSizeHeight() { return prevHeight; }
  
  public byte[] bytes;
  @Override
  public void onPreviewFrame(byte[] data, Camera camera) {
      bytes = data;
  
      //Log.i("Unity", "JAVA: " + Integer.toString(data.length));
      UnityPlayer.UnitySendMessage(gameObjectTargetName, "GetBuffer", "");
  }
  </code></pre>
  
  <p>It is required that the texture sent to Unity3d is 1024x768 without cropping. Does anyone have any fix for that?</p>
  
  <p><strong>UPDATE(added C# code)</strong></p>
  
  <p>This code gets preview from java:</p>
  
  <pre><code>void CreateCameraTexture() {
      _texWidth = nativeCameraObject.Call&lt;int&gt;("getPreviewSizeWidth");
      _texHeight = nativeCameraObject.Call&lt;int&gt;("getPreviewSizeHeight");
      _cameraPreview = new Texture2D(_texWidth, _texHeight, TextureFormat.Alpha8, false);
      _converter = new YUVDecode(_texWidth, _texHeight);
      shaderMat.SetFloat("_Width", _texWidth);
  
      if (OnCameraTextureCreated != null)
          OnCameraTextureCreated(_cameraPreview);
  }
  
  private byte[] _bytes;
  public void GetBuffer(string str) {
      _bytes = nativeCameraObject.Get&lt;byte[]&gt;("bytes");
      SetColor(_bytes);
      _cameraPreview.LoadRawTextureData(_bytes);
      _cameraPreview.Apply();
  
      UpdateLibFrame();
  }
  
  void SetColor(byte[] bytes) {
      _converter.SetBytes(bytes);
      shaderMat.SetTexture("_YUVTex", _converter.yuvtexture);
  }
  
  void UpdateLibFrame() {
      if (OnFrameUpdate != null) OnFrameUpdate(_cameraPreview.GetRawTextureData());
  }
  </code></pre>
  
  <p>This code in another class shows the data:</p>
  
  <pre><code>private RectTransform previewRect {
      get {
          if (!_previewRect)
              _previewRect = cameraPreview.GetComponent&lt;RectTransform&gt;();
          return _previewRect;
      }
  }
  
  private void GetPreviewFromCamera() {
      NativeCamera.OnCameraTextureCreated += (Texture2D cameraTex) =&gt; {
          if (cameraPreview != null &amp;&amp; cameraPreview.texture != null)
              DestroyImmediate(cameraPreview.texture);
  
          previewRect.sizeDelta = new Vector2(1024, 768);
  
          cameraPreview.texture = cameraTex;
          cameraPreview.enabled = true;
      };
  }
  </code></pre>
  
  <p><strong>UPDATE: Added link to a sample project</strong></p>
  
  <p>The project is here:
  <a href="https://www.dropbox.com/s/wid1qa9cmq3ck6w/CameraPreview.zip?dl=0" rel="nofollow noreferrer">https://www.dropbox.com/s/wid1qa9cmq3ck6w/CameraPreview.zip?dl=0</a></p>
  
  <p>CameraJava is a gradle project. The output is a .AAR file that you have to copy to Assets/Plugins.</p>