Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion ZXing.Net.Mobile/Android/CameraAccess/CameraAnalyzer.android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class CameraAnalyzer
bool wasScanned;
IScannerSessionHost scannerHost;
CameraManager cameraManager;
BarcodeReader barcodeReader;

public CameraAnalyzer(SurfaceView surfaceView, IScannerSessionHost scannerHost)
{
Expand Down Expand Up @@ -63,6 +64,7 @@ public void AutoFocus(int x, int y)

public void RefreshCamera()
{
barcodeReader = null;
cameraController.RefreshCamera();
}

Expand Down Expand Up @@ -120,8 +122,14 @@ void DecodeFrame(byte[] data)
var previewSize = cameraController.IdealPhotoSize;
var width = previewSize.Width;
var height = previewSize.Height;
var barcodeReader = scannerHost.ScanningOptions.BuildBarcodeReader();

if (barcodeReader == null)
{
barcodeReader = scannerHost.ScanningOptions.BuildBarcodeReader();

Android.Util.Log.Debug(MobileBarcodeScanner.TAG, "Created Barcode Reader");
}

ZXing.Result result = null;
var start = PerformanceCounter.Start();

Expand Down
109 changes: 108 additions & 1 deletion ZXing.Net.Mobile/Android/CameraAccess/CameraEventsListener.android.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using Android.Content;
using Android.Graphics;
using Android.Media;
using Android.Renderscripts;
using ApxLabs.FastAndroidCamera;

using Java.IO;
using Java.Nio;
using static Android.Media.ImageReader;
Expand All @@ -20,17 +22,122 @@ public void OnImageAvailable(ImageReader reader)
Image image = null;
try
{
//Returns a yuv420888 image
image = reader.AcquireLatestImage();

if (image is null) return;
var yuvBytes = ImageToByteArray(image);

//On Pixel5, the original Yuv42088 to nv21 conversion fails
//var yuvBytes = ImageToByteArray(image);
var yuvBytes = Yuv420888toNv21(image);

OnPreviewFrameReady?.Invoke(this, yuvBytes);

//To check that the yuv conversion is correct, you can save a jpg version
//SaveJpegBytes(yuvBytes, image.Width, image.Height);
}
finally
{
image?.Close();
}
}

//Use this to save the converted yuv image to external storage
//You may need to include Permissions.StorageWrite in PermissionsHandler.android.cs and in the manifest
//void SaveJpegBytes(byte[] yuvBytes, int width, int height)
//{
// var dcimDir = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDcim).AbsolutePath;
// var filename = System.IO.Path.Combine(dcimDir, $"nv21-output.jpg");

// //Convert the nv21 bytes to a jpeg
// var stream = new MemoryStream();
// var yuvImage = new YuvImage(yuvBytes, ImageFormatType.Nv21, width, height, null);
// yuvImage.CompressToJpeg(new Rect(0, 0, width, height), 50, stream);

// System.IO.File.WriteAllBytes(filename, stream.ToArray());
//}

//https://stackoverflow.com/questions/52726002/camera2-captured-picture-conversion-from-yuv-420-888-to-nv21
byte[] Yuv420888toNv21(Image image)
{
var width = image.Width;
var height = image.Height;
var ySize = width * height;
var uvSize = width * height / 4;

var nv21 = new byte[ySize + uvSize * 2];

var yBuffer = image.GetPlanes()[0].Buffer; // Y
var uBuffer = image.GetPlanes()[1].Buffer; // U
var vBuffer = image.GetPlanes()[2].Buffer; // V

var rowStride = image.GetPlanes()[0].RowStride;
var pos = 0;

if (rowStride == width)
{
// likely
yBuffer.Get(nv21, 0, ySize);
pos += ySize;
}
else
{
var yBufferPos = -rowStride; // not an actual position
for (; pos < ySize; pos += width)
{
yBufferPos += rowStride;
yBuffer.Position(yBufferPos);
yBuffer.Get(nv21, pos, width);
}
}

rowStride = image.GetPlanes()[2].RowStride;
var pixelStride = image.GetPlanes()[2].PixelStride;

if (pixelStride == 2 && rowStride == width && uBuffer.Get(0) == vBuffer.Get(1))
{
// maybe V and U planes overlap as per NV21, which means vBuffer[1] is alias of uBuffer[0]
var savePixel = vBuffer.Get(1);
try
{
vBuffer.Put(1, (sbyte)~savePixel);
if (uBuffer.Get(0) == (sbyte)~savePixel)
{
vBuffer.Put(1, savePixel);
vBuffer.Position(0);
uBuffer.Position(0);
vBuffer.Get(nv21, ySize, 1);
uBuffer.Get(nv21, ySize + 1, uBuffer.Remaining());

return nv21; // shortcut
}
}
catch (ReadOnlyBufferException)
{
// unfortunately, we cannot check if vBuffer and uBuffer overlap
}

// unfortunately, the check failed. We must save U and V pixel by pixel
vBuffer.Put(1, savePixel);
}

// other optimizations could check if (pixelStride == 1) or (pixelStride == 2),
// but performance gain would be less significant

for (var row = 0; row < height / 2; row++)
{
for (var col = 0; col < width / 2; col++)
{
var vuPos = col * pixelStride + row * rowStride;
nv21[pos++] = (byte)vBuffer.Get(vuPos);
nv21[pos++] = (byte)uBuffer.Get(vuPos);
}
}

return nv21;
}

//Convert to NV21
byte[] ImageToByteArray(Image image)
{
byte[] result;
Expand Down