Repository: pion/mediadevices (go)

Issue:
Support more pixel format in x11 screen capture driver
Current code assumes that the pixel format is 32bit RGBA.

Pull request description:
driver/screen: support X11 16 bit color frame buffer
### Reference issue
Fixes #120 


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/go.mod b/go.mod
index d5a56f9..6aa1149 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ require (
 	github.com/faiface/beep v1.0.2
 	github.com/jfreymuth/pulse v0.0.0-20200118113426-7cf5f487291e
 	github.com/lherman-cs/opus v0.0.0-20200223204610-6a4b98199ea4
-	github.com/pion/webrtc/v2 v2.2.3
+	github.com/pion/webrtc/v2 v2.2.4
 	github.com/satori/go.uuid v1.2.0
 	golang.org/x/image v0.0.0-20200119044424-58c23975cae1
 	golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527
diff --git a/go.sum b/go.sum
index 2b3784e..f1363f2 100644
--- a/go.sum
+++ b/go.sum
@@ -54,6 +54,8 @@ github.com/pion/datachannel v1.4.16 h1:dvuDC0IBMUDQvwO+gRu0Dv+W5j7rrgNpCmtheb6iY
 github.com/pion/datachannel v1.4.16/go.mod h1:gRGhxZv7X2/30Qxes4WEXtimKBXcwj/3WsDtBlHnvJY=
 github.com/pion/dtls/v2 v2.0.0-rc.7 h1:LDAIQDt1pcuAIJs7Q2EZ3PSl8MseCFA2nCW0YYSYCx0=
 github.com/pion/dtls/v2 v2.0.0-rc.7/go.mod h1:U199DvHpRBN0muE9+tVN4TMy1jvEhZIZ63lk4xkvVSk=
+github.com/pion/dtls/v2 v2.0.0-rc.9 h1:wPb0JKmYoleAM2o8vQSPaUM+geJq7l0AdeUlPsg19ec=
+github.com/pion/dtls/v2 v2.0.0-rc.9/go.mod h1:6eFkFvpo0T+odQ+39HFEtOO7LX5cUlFqXdSo4ucZtGg=
 github.com/pion/ice v0.7.10 h1:ydViRi+ZqvCzbJYNLuQoOqOJflSPVIGVC3NL6PDecm8=
 github.com/pion/ice v0.7.10/go.mod h1:YufCtyMmPeZXGTuARCracYfe0mb3EXbcRUS+vHJB5Cs=
 github.com/pion/logging v0.2.2 h1:M9+AIj/+pxNsDfAT64+MAVgJO0rsyLnoJKCqf//DoeY=
@@ -64,24 +66,25 @@ github.com/pion/quic v0.1.1 h1:D951FV+TOqI9A0rTF7tHx0Loooqz+nyzjEyj8o3PuMA=
 github.com/pion/quic v0.1.1/go.mod h1:zEU51v7ru8Mp4AUBJvj6psrSth5eEFNnVQK5K48oV3k=
 github.com/pion/rtcp v1.2.1 h1:S3yG4KpYAiSmBVqKAfgRa5JdwBNj4zK3RLUa8JYdhak=
 github.com/pion/rtcp v1.2.1/go.mod h1:a5dj2d6BKIKHl43EnAOIrCczcjESrtPuMgfmL6/K6QM=
-github.com/pion/rtp v1.3.0/go.mod h1:q9wPnA96pu2urCcW/sK/RiDn597bhGoAQQ+y2fDwHuY=
 github.com/pion/rtp v1.3.2 h1:Yfzf1mU4Zmg7XWHitzYe2i+l+c68iO+wshzIUW44p1c=
 github.com/pion/rtp v1.3.2/go.mod h1:q9wPnA96pu2urCcW/sK/RiDn597bhGoAQQ+y2fDwHuY=
 github.com/pion/sctp v1.7.6 h1:8qZTdJtbKfAns/Hv5L0PAj8FyXcsKhMH1pKUCGisQg4=
 github.com/pion/sctp v1.7.6/go.mod h1:ichkYQ5tlgCQwEwvgfdcAolqx1nHbYCxo4D7zK/K0X8=
 github.com/pion/sdp/v2 v2.3.4 h1:+f3F5Xl7ynVhc9Il8Dc7BFroYJWG3PMbfWtwFlVI+kg=
 github.com/pion/sdp/v2 v2.3.4/go.mod h1:jccXVYW0fuK6ds2pwKr89SVBDYlCjhgMI6nucl5R5rA=
-github.com/pion/srtp v1.2.7 h1:UYyLs5MXwbFtXWduBA5+RUWhaEBX7GmetXDZSKP+uPM=
-github.com/pion/srtp v1.2.7/go.mod h1:KIgLSadhg/ioogO/LqIkRjZrwuJo0c9RvKIaGQj4Yew=
+github.com/pion/srtp v1.3.1 h1:WNDLN41ST0P6cXRpzx97JJW//vChAEo1+Etdqo+UMnM=
+github.com/pion/srtp v1.3.1/go.mod h1:nxEytDDGTN+eNKJ1l5gzOCWQFuksgijorsSlgEjc40Y=
 github.com/pion/stun v0.3.3 h1:brYuPl9bN9w/VM7OdNzRSLoqsnwlyNvD9MVeJrHjDQw=
 github.com/pion/stun v0.3.3/go.mod h1:xrCld6XM+6GWDZdvjPlLMsTU21rNxnO6UO8XsAvHr/M=
 github.com/pion/transport v0.6.0/go.mod h1:iWZ07doqOosSLMhZ+FXUTq+TamDoXSllxpbGcfkCmbE=
 github.com/pion/transport v0.8.10 h1:lTiobMEw2PG6BH/mgIVqTV2mBp/mPT+IJLaN8ZxgdHk=
 github.com/pion/transport v0.8.10/go.mod h1:tBmha/UCjpum5hqTWhfAEs3CO4/tHSg0MYRhSzR+CZ8=
+github.com/pion/transport v0.10.0 h1:9M12BSneJm6ggGhJyWpDveFOstJsTiQjkLf4M44rm80=
+github.com/pion/transport v0.10.0/go.mod h1:BnHnUipd0rZQyTVB2SBGojFHT9CBt5C5TcsJSQGkvSE=
 github.com/pion/turn/v2 v2.0.3 h1:SJUUIbcPoehlyZgMyIUbBBDhI03sBx32x3JuSIBKBWA=
 github.com/pion/turn/v2 v2.0.3/go.mod h1:kl1hmT3NxcLynpXVnwJgObL8C9NaCyPTeqI2DcCpSZs=
-github.com/pion/webrtc/v2 v2.2.3 h1:blCOvERzU95W83FkKw/c9vWkxPO0P21e2a/gDnXfyBc=
-github.com/pion/webrtc/v2 v2.2.3/go.mod h1:H2gjRwGvP91FfvF5BPfCGNwBz4fAw32Agli9Nwl3Bz8=
+github.com/pion/webrtc/v2 v2.2.4 h1:elWyBI/6S2kNoJ5rcTj2EMAvp/fmeiEqiuYbUd0ShRA=
+github.com/pion/webrtc/v2 v2.2.4/go.mod h1:9vTqkEISnB5AXlggrFSBLGSz/kopQuYUYzHubuFpnCU=
 github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -101,6 +104,8 @@ golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACk
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U=
 golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
+golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd h1:nLIcFw7GiqKXUS7HiChg6OAYWgASB2H97dZKd1GhDSs=
 golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81 h1:00VmoueYNlNz/aHIilyyQz/MHSqGoWJzpFv/HW8xpzI=
@@ -112,6 +117,7 @@ golang.org/x/mobile v0.0.0-20180806140643-507816974b79/go.mod h1:z+o9i4GpDbdi3rU
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20191126235420-ef20fe5d7933/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
 golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
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/driver/screen/x11capture_linux.go b/pkg/driver/screen/x11capture_linux.go
index fb1ba44..58b9de0 100644
--- a/pkg/driver/screen/x11capture_linux.go
+++ b/pkg/driver/screen/x11capture_linux.go
@@ -7,7 +7,8 @@ package screen
 // #define XUTIL_DEFINE_FUNCTIONS
 // #include <X11/Xutil.h>
 // #include <X11/extensions/XShm.h>
-// void copyRGBA(void *dst, char *src, size_t l) { // 64bit aligned copy
+//
+// void copyBGR24(void *dst, char *src, size_t l) { // 64bit aligned copy
 //   uint64_t *d = (uint64_t*)dst;
 //   uint64_t *s = (uint64_t*)src;
 //   l /= 8;
@@ -21,6 +22,22 @@ package screen
 //     s++;
 //   }
 // }
+//
+// void copyBGR16(void *dst, char *src, size_t l) { // 64bit aligned copy
+//   uint64_t *d = (uint64_t*)dst;
+//   uint32_t *s = (uint32_t*)src;
+//   l /= 8;
+//   for (size_t i = 0; i < l; i ++) {
+//     uint64_t v = *s;
+//     // Reorder BGR to RGB
+//     *d = 0xFF000000FF000000 |
+//          ((v & 0xF8000000) << 8)  | ((v & 0x7E00000) << 21) | ((v & 0x1F0000) << 35) |
+//          ((v & 0xF800) >> 8) | ((v & 0x7E0) << 5) | ((v & 0x1F) << 19);
+//     d++;
+//     s++;
+//   }
+// }
+//
 // char *align64(char *ptr) { // return 64bit aligned pointer
 //   if (((size_t)ptr & 0x07) == 0) {
 //     return ptr;
@@ -35,6 +52,7 @@ import "C"
 
 import (
 	"errors"
+	"fmt"
 	"image"
 	"image/color"
 	"unsafe"
@@ -44,6 +62,15 @@ const shmaddrInvalid = ^uintptr(0)
 
 type display C.Display
 
+type pixelFormat int
+
+const (
+	pixFmtBGR24 pixelFormat = iota
+	pixFmtRGB24
+	pixFmtBGR16
+	pixFmtRGB16
+)
+
 func openDisplay() (*display, error) {
 	dp := C.XOpenDisplay(nil)
 	if dp == nil {
@@ -65,10 +92,11 @@ func (d *display) NumScreen() int {
 }
 
 type shmImage struct {
-	dp  *C.Display
-	img *C.XImage
-	shm C.XShmSegmentInfo
-	b   []byte
+	dp     *C.Display
+	img    *C.XImage
+	shm    C.XShmSegmentInfo
+	b      []byte
+	pixFmt pixelFormat
 }
 
 func (s *shmImage) Free() {
@@ -96,21 +124,47 @@ func (c colorFunc) RGBA() (r, g, b, a uint32) {
 }
 
 func (s *shmImage) At(x, y int) color.Color {
-	addr := (x + y*int(s.img.width)) * 4
-	b := uint32(s.b[addr]) * 0x100
-	g := uint32(s.b[addr+1]) * 0x100
-	r := uint32(s.b[addr+2]) * 0x100
-	return colorFunc(func() (_, _, _, _ uint32) {
-		return r, g, b, 0xFFFF
-	})
+	switch s.pixFmt {
+	case pixFmtBGR24:
+		addr := (x + y*int(s.img.width)) * 4
+		b := uint32(s.b[addr]) * 0x100
+		g := uint32(s.b[addr+1]) * 0x100
+		r := uint32(s.b[addr+2]) * 0x100
+		return colorFunc(func() (_, _, _, _ uint32) {
+			return r, g, b, 0xFFFF
+		})
+	case pixFmtBGR16:
+		addr := (x + y*int(s.img.width)) * 2
+		b1, b2 := s.b[addr], s.b[addr+1]
+		b := uint32(b1>>3) * 0x100
+		g := uint32((b1&0x7)<<3|(b2&0xE0)>>5) * 0x100
+		r := uint32(b2&0x1F) * 0x100
+		return colorFunc(func() (_, _, _, _ uint32) {
+			return r, g, b, 0xFFFF
+		})
+	default:
+		panic("unsupported pixel format")
+	}
 }
 
 func (s *shmImage) RGBAAt(x, y int) color.RGBA {
-	addr := (x + y*int(s.img.width)) * 4
-	b := s.b[addr]
-	g := s.b[addr+1]
-	r := s.b[addr+2]
-	return color.RGBA{R: r, G: g, B: b, A: 0xFF}
+	switch s.pixFmt {
+	case pixFmtBGR24:
+		addr := (x + y*int(s.img.width)) * 4
+		b := s.b[addr]
+		g := s.b[addr+1]
+		r := s.b[addr+2]
+		return color.RGBA{R: r, G: g, B: b, A: 0xFF}
+	case pixFmtBGR16:
+		addr := (x + y*int(s.img.width)) * 2
+		b1, b2 := s.b[addr], s.b[addr+1]
+		b := b1 >> 3
+		g := (b1&0x7)<<3 | (b2&0xE0)>>5
+		r := b2 & 0x1F
+		return color.RGBA{R: r, G: g, B: b, A: 0xFF}
+	default:
+		panic("unsupported pixel format")
+	}
 }
 
 func (s *shmImage) ToRGBA(dst *image.RGBA) *image.RGBA {
@@ -123,8 +177,16 @@ func (s *shmImage) ToRGBA(dst *image.RGBA) *image.RGBA {
 		}
 		dst.Pix = dst.Pix[:l]
 	}
-	C.copyRGBA(unsafe.Pointer(&dst.Pix[0]), s.img.data, C.ulong(len(dst.Pix)))
-	return dst
+	switch s.pixFmt {
+	case pixFmtBGR24:
+		C.copyBGR24(unsafe.Pointer(&dst.Pix[0]), s.img.data, C.ulong(len(dst.Pix)))
+		return dst
+	case pixFmtBGR16:
+		C.copyBGR16(unsafe.Pointer(&dst.Pix[0]), s.img.data, C.ulong(len(dst.Pix)))
+		return dst
+	default:
+		panic("unsupported pixel format")
+	}
 }
 
 func newShmImage(dp *C.Display, screen int) (*shmImage, error) {
@@ -136,6 +198,17 @@ func newShmImage(dp *C.Display, screen int) (*shmImage, error) {
 
 	s := &shmImage{dp: dp}
 
+	switch {
+	case v.red_mask == 0xFF0000 && v.green_mask == 0xFF00 && v.blue_mask == 0xFF:
+		s.pixFmt = pixFmtBGR24
+	case v.red_mask == 0xF800 && v.green_mask == 0x7E0 && v.blue_mask == 0x1F:
+		s.pixFmt = pixFmtBGR16
+	default:
+		fmt.Printf("x11capture: unsupported pixel format (R: %0x, G: %0x, B: %0x)\n",
+			v.red_mask, v.green_mask, v.blue_mask)
+		return nil, errors.New("unsupported pixel format")
+	}
+
 	s.shm.shmid = C.shmget(C.IPC_PRIVATE, C.ulong(w*h*4+8), C.IPC_CREAT|0600)
 	if s.shm.shmid == -1 {
 		return nil, errors.New("failed to get shared memory")
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