Repository: pion/mediadevices (go)

Issue:
driver/camera: support UYVY pixel format

Pull request:
driver/camera: support UYVY camera
### Reference issue
Fixes #131 

Patch:
diff --git a/README.md b/README.md
index d4a421d..d2e316e 100644
--- a/README.md
+++ b/README.md
@@ -23,6 +23,7 @@ Go implementation of the [MediaDevices](https://developer.mozilla.org/en-US/docs
 |                     Pixel Format                      | Linux | Mac | Windows |
 | :---------------------------------------------------: | :---: | :-: | :-----: |
 | [YUY2](https://www.fourcc.org/pixel-format/yuv-yuy2/) |  ✔️   | ✖️  |   ✔️    |
+| [UYVY](https://www.fourcc.org/pixel-format/yuv-uyvy/) |  ✔️   | ✖️  |   ✖️    |
 | [I420](https://www.fourcc.org/pixel-format/yuv-i420/) |  ✔️   | ✖️  |   ✖️    |
 | [NV21](https://www.fourcc.org/pixel-format/yuv-nv21/) |  ✔️   | ✖️  |   ✖️    |
 |         [MJPEG](https://www.fourcc.org/mjpg/)         |  ✔️   | ✖️  |   ✖️    |
diff --git a/pkg/driver/camera/camera_linux.go b/pkg/driver/camera/camera_linux.go
index e6cabf9..6598a93 100644
--- a/pkg/driver/camera/camera_linux.go
+++ b/pkg/driver/camera/camera_linux.go
@@ -58,6 +58,7 @@ func init() {
 func newCamera(path string) *camera {
 	formats := map[webcam.PixelFormat]frame.Format{
 		webcam.PixelFormat(C.V4L2_PIX_FMT_YUYV):  frame.FormatYUYV,
+		webcam.PixelFormat(C.V4L2_PIX_FMT_UYVY):  frame.FormatUYVY,
 		webcam.PixelFormat(C.V4L2_PIX_FMT_NV12):  frame.FormatNV21,
 		webcam.PixelFormat(C.V4L2_PIX_FMT_MJPEG): frame.FormatMJPEG,
 	}
diff --git a/pkg/frame/const.go b/pkg/frame/const.go
index f4be199..60bfd63 100644
--- a/pkg/frame/const.go
+++ b/pkg/frame/const.go
@@ -13,6 +13,8 @@ const (
 	FormatNV21 = "NV21"
 	// FormatYUY2 https://www.fourcc.org/pixel-format/yuv-yuy2/
 	FormatYUY2 = "YUY2"
+	// FormatUYVY https://www.fourcc.org/pixel-format/yuv-uyvy/
+	FormatUYVY = "UYVY"
 
 	// RGB Formats
 
diff --git a/pkg/frame/decode.go b/pkg/frame/decode.go
index aa4df8b..a2a2a47 100644
--- a/pkg/frame/decode.go
+++ b/pkg/frame/decode.go
@@ -14,6 +14,8 @@ func NewDecoder(f Format) (Decoder, error) {
 		decoder = decodeNV21
 	case FormatYUY2:
 		decoder = decodeYUY2
+	case FormatUYVY:
+		decoder = decodeUYVY
 	case FormatMJPEG:
 		decoder = decodeMJPEG
 	default:
diff --git a/pkg/frame/yuv.c b/pkg/frame/yuv.c
index 6d7d1bd..3b58d67 100644
--- a/pkg/frame/yuv.c
+++ b/pkg/frame/yuv.c
@@ -19,3 +19,23 @@ void decodeYUY2CGO(
     ++slow;
   }
 }
+
+void decodeUYVYCGO(
+    uint8_t* y,
+    uint8_t* cb,
+    uint8_t* cr,
+    uint8_t* uyvy,
+    int width, int height)
+{
+  const int l = width * height * 2;
+  int i, fast = 0, slow = 0;
+  for (i = 0; i < l; i += 4)
+  {
+    cb[slow] = uyvy[i];
+    y[fast] = uyvy[i+1];
+    cr[slow] = uyvy[i + 2];
+    y[fast + 1] = uyvy[i + 3];
+    fast += 2;
+    ++slow;
+  }
+}
diff --git a/pkg/frame/yuv_cgo.go b/pkg/frame/yuv_cgo.go
index 756b0ad..368dd22 100644
--- a/pkg/frame/yuv_cgo.go
+++ b/pkg/frame/yuv_cgo.go
@@ -9,6 +9,7 @@ import (
 
 // #include <stdint.h>
 // void decodeYUY2CGO(uint8_t* y, uint8_t* cb, uint8_t* cr, uint8_t* yuy2, int width, int height);
+// void decodeUYVYCGO(uint8_t* y, uint8_t* cb, uint8_t* cr, uint8_t* uyvy, int width, int height);
 import "C"
 
 func decodeYUY2(frame []byte, width, height int) (image.Image, error) {
@@ -42,3 +43,35 @@ func decodeYUY2(frame []byte, width, height int) (image.Image, error) {
 		Rect:           image.Rect(0, 0, width, height),
 	}, nil
 }
+
+func decodeUYVY(frame []byte, width, height int) (image.Image, error) {
+	yi := width * height
+	ci := yi / 2
+	fi := yi + 2*ci
+
+	if len(frame) != fi {
+		return nil, fmt.Errorf("frame length (%d) less than expected (%d)", len(frame), fi)
+	}
+
+	y := make([]byte, yi)
+	cb := make([]byte, ci)
+	cr := make([]byte, ci)
+
+	C.decodeUYVYCGO(
+		(*C.uchar)(&y[0]),
+		(*C.uchar)(&cb[0]),
+		(*C.uchar)(&cr[0]),
+		(*C.uchar)(&frame[0]),
+		C.int(width), C.int(height),
+	)
+
+	return &image.YCbCr{
+		Y:              y,
+		YStride:        width,
+		Cb:             cb,
+		Cr:             cr,
+		CStride:        width / 2,
+		SubsampleRatio: image.YCbCrSubsampleRatio422,
+		Rect:           image.Rect(0, 0, width, height),
+	}, nil
+}
diff --git a/pkg/frame/yuv_nocgo.go b/pkg/frame/yuv_nocgo.go
index 7056a40..c13f751 100644
--- a/pkg/frame/yuv_nocgo.go
+++ b/pkg/frame/yuv_nocgo.go
@@ -41,3 +41,38 @@ func decodeYUY2(frame []byte, width, height int) (image.Image, error) {
 		Rect:           image.Rect(0, 0, width, height),
 	}, nil
 }
+
+func decodeUYVY(frame []byte, width, height int) (image.Image, error) {
+	yi := width * height
+	ci := yi / 2
+	fi := yi + 2*ci
+
+	if len(frame) != fi {
+		return nil, fmt.Errorf("frame length (%d) less than expected (%d)", len(frame), fi)
+	}
+
+	y := make([]byte, yi)
+	cb := make([]byte, ci)
+	cr := make([]byte, ci)
+
+	fast := 0
+	slow := 0
+	for i := 0; i < fi; i += 4 {
+		cb[slow] = frame[i]
+		y[fast] = frame[i+1]
+		cr[slow] = frame[i+2]
+		y[fast+1] = frame[i+3]
+		fast += 2
+		slow++
+	}
+
+	return &image.YCbCr{
+		Y:              y,
+		YStride:        width,
+		Cb:             cb,
+		Cr:             cr,
+		CStride:        width / 2,
+		SubsampleRatio: image.YCbCrSubsampleRatio422,
+		Rect:           image.Rect(0, 0, width, height),
+	}, nil
+}


Test patch:
diff --git a/pkg/frame/yuv_test.go b/pkg/frame/yuv_test.go
index b8e0312..0074700 100644
--- a/pkg/frame/yuv_test.go
+++ b/pkg/frame/yuv_test.go
@@ -36,6 +36,35 @@ func TestDecodeYUY2(t *testing.T) {
 	}
 }
 
+func TestDecodeUYVY(t *testing.T) {
+	const (
+		width  = 2
+		height = 2
+	)
+	input := []byte{
+		//Cb     Y    Cr     Y
+		0x82, 0x01, 0x84, 0x03,
+		0x86, 0x05, 0x88, 0x07,
+	}
+	expected := &image.YCbCr{
+		Y:              []byte{0x01, 0x03, 0x05, 0x07},
+		YStride:        width,
+		Cb:             []byte{0x82, 0x86},
+		Cr:             []byte{0x84, 0x88},
+		CStride:        width / 2,
+		SubsampleRatio: image.YCbCrSubsampleRatio422,
+		Rect:           image.Rect(0, 0, width, height),
+	}
+
+	img, err := decodeUYVY(input, width, height)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !reflect.DeepEqual(expected, img) {
+		t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, img)
+	}
+}
+
 func BenchmarkDecodeYUY2(b *testing.B) {
 	sizes := []struct {
 		width, height int