├── .gitignore ├── LICENSE ├── README.md ├── camera.go ├── camera_test.go ├── cmd └── inspect │ └── inspect.go ├── consts.go ├── error.go ├── go.mod ├── go.sum ├── pkg ├── decode │ ├── README.md │ ├── decode.go │ ├── image.go │ ├── libavcodec.go │ ├── mjpeg.go │ └── uncompressed.go ├── descriptors │ ├── audio_streaming_interface_descriptors.go │ ├── consts.go │ ├── controls.go │ ├── descriptors.go │ ├── errors.go │ ├── frame.go │ ├── frame_dv.go │ ├── frame_frame_based.go │ ├── frame_h264.go │ ├── frame_mjpeg.go │ ├── frame_mpeg2ts.go │ ├── frame_stream_based.go │ ├── frame_uncompressed.go │ ├── frame_vp8.go │ ├── interface_association_descriptors.go │ ├── processing_unit_controls.go │ ├── terminal_control_consts.go │ ├── video_control_endpoint_descriptors.go │ ├── video_control_interface_descriptors.go │ ├── video_streaming_endpoint_descriptors.go │ └── video_streaming_interface_descriptors.go ├── formats │ └── compression.go ├── requests │ └── requests.go └── transfers │ ├── bulk_reader.go │ ├── frame.go │ ├── isochronous_reader.go │ ├── payload.go │ └── streaming_interface.go ├── processing_unit.go ├── uvc.go └── uvc_test.go /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevmo314/go-uvc/b9a5b9679df2a9cd3205185545cbe1c3922b1c1f/.gitignore -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # github.com/kevmo314/go-uvc 2 | 3 | An almost-pure-Go library for accessing UVC devices. The library currently depends on libusb via cgo 4 | but _not_ libuvc. One day this may change but libusb is much more complex. 5 | 6 | The non-Go equivalent of this library is [libuvc](https://github.com/libuvc/libuvc). 7 | 8 | ![image](https://github.com/kevmo314/go-uvc/assets/511342/1e4d4a0b-37ad-44c0-b97d-9e2a3d4551d2) 9 | 10 | ## Features 11 | 12 | - [x] UVC 1.5 support 13 | - [x] Input terminals (recording from cameras) 14 | - [ ] Output terminals (displaying images on a device) 15 | - [x] Isochronous and bulk transfer support 16 | - [x] Android support 17 | - [x] Video decoding API 18 | - [ ] Camera controls 19 | - [ ] UAC Audio support 20 | 21 | ## Demo 22 | 23 | `go-uvc` includes a debugging tool (the screenshot above) to connect to and debug cameras. To use it, 24 | 25 | ```sh 26 | go run github.com/kevmo314/go-uvc/cmd/inspect -path /dev/bus/usb/001/002 27 | ``` 28 | 29 | ## Usage 30 | 31 | A minimal example of how you might use `go-uvc`. 32 | 33 | ```go 34 | package main 35 | 36 | import ( 37 | "image/jpeg" 38 | "fmt" 39 | "log" 40 | "syscall" 41 | 42 | "github.com/kevmo314/go-uvc" 43 | "github.com/kevmo314/go-uvc/pkg/descriptors" 44 | ) 45 | 46 | func main() { 47 | fd, err := syscall.Open("/dev/bus/usb/001/002", syscall.O_RDWR, 0) 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | ctx, err := uvc.NewUVCDevice(uintptr(fd)) 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | info, err := ctx.DeviceInfo() 58 | if err != nil { 59 | panic(err) 60 | } 61 | 62 | for _, iface := range info.StreamingInterfaces { 63 | for i, desc := range iface.Descriptors { 64 | fd, ok := desc.(*descriptors.MJPEGFormatDescriptor) 65 | if !ok { 66 | continue 67 | } 68 | frd := iface.Descriptors[i+1].(*descriptors.MJPEGFrameDescriptor) 69 | 70 | resp, err := iface.ClaimFrameReader(fd.Index(), frd.Index()) 71 | if err != nil { 72 | panic(err) 73 | } 74 | 75 | for i := 0; ; i++ { 76 | fr, err := resp.ReadFrame() 77 | if err != nil { 78 | panic(err) 79 | } 80 | img, err := jpeg.Decode(fr) 81 | if err != nil { 82 | continue 83 | } 84 | // do something with img 85 | } 86 | } 87 | } 88 | } 89 | ``` 90 | -------------------------------------------------------------------------------- /camera.go: -------------------------------------------------------------------------------- 1 | package uvc 2 | 3 | /* 4 | #cgo LDFLAGS: -lusb-1.0 5 | #include 6 | #include 7 | */ 8 | import "C" 9 | import ( 10 | "fmt" 11 | "unsafe" 12 | 13 | "github.com/kevmo314/go-uvc/pkg/descriptors" 14 | "github.com/kevmo314/go-uvc/pkg/requests" 15 | ) 16 | 17 | var availableDescriptors = []descriptors.CameraTerminalControlDescriptor{ 18 | &descriptors.ScanningModeControl{}, 19 | &descriptors.AutoExposureModeControl{}, 20 | &descriptors.AutoExposurePriorityControl{}, 21 | &descriptors.DigitalWindowControl{}, 22 | &descriptors.PrivacyControl{}, 23 | &descriptors.FocusAbsoluteControl{}, 24 | &descriptors.FocusAutoControl{}, 25 | &descriptors.ExposureTimeAbsoluteControl{}, 26 | &descriptors.ExposureTimeRelativeControl{}, 27 | &descriptors.FocusRelativeControl{}, 28 | &descriptors.FocusSimpleRangeControl{}, 29 | &descriptors.RollAbsoluteControl{}, 30 | &descriptors.IrisAbsoluteControl{}, 31 | &descriptors.IrisRelativeControl{}, 32 | &descriptors.PanTiltAbsoluteControl{}, 33 | &descriptors.PanTiltRelativeControl{}, 34 | &descriptors.RegionOfInterestControl{}, 35 | &descriptors.RollRelativeControl{}, 36 | &descriptors.ZoomAbsoluteControl{}, 37 | &descriptors.ZoomRelativeControl{}, 38 | } 39 | 40 | type CameraTerminal struct { 41 | usb *C.struct_libusb_interface 42 | deviceHandle *C.struct_libusb_device_handle 43 | CameraDescriptor *descriptors.CameraTerminalDescriptor 44 | } 45 | 46 | func (ct *CameraTerminal) GetSupportedControls() []descriptors.CameraTerminalControlDescriptor { 47 | var supportedControls []descriptors.CameraTerminalControlDescriptor 48 | 49 | for _, desc := range availableDescriptors { 50 | if ct.IsControlRequestSupported(desc) { 51 | supportedControls = append(supportedControls, desc) 52 | } 53 | } 54 | return supportedControls 55 | } 56 | 57 | func (ct *CameraTerminal) IsControlRequestSupported(desc descriptors.CameraTerminalControlDescriptor) bool { 58 | byteIndex := desc.FeatureBit() / 8 59 | bitIndex := desc.FeatureBit() % 8 60 | return (ct.CameraDescriptor.ControlsBitmask[byteIndex] & (1 << bitIndex)) != 0 61 | } 62 | 63 | func (ct *CameraTerminal) Get(desc descriptors.CameraTerminalControlDescriptor) error { 64 | ifnum := ct.usb.altsetting.bInterfaceNumber 65 | 66 | bufLen := 16 67 | buf := C.malloc(C.ulong(bufLen)) 68 | defer C.free(buf) 69 | 70 | if ret := C.libusb_control_transfer( 71 | ct.deviceHandle, 72 | C.uint8_t(requests.RequestTypeVideoInterfaceGetRequest), /* bmRequestType */ 73 | C.uint8_t(requests.RequestCodeGetCur), /* bRequest*/ 74 | C.uint16_t(desc.Value()<<8), /* wValue: on the hight byte */ 75 | C.uint16_t(uint16(ct.CameraDescriptor.InputTerminalDescriptor.TerminalID)<<8|uint16(ifnum)), /* wIndex*/ 76 | (*C.uchar)(buf), /* data */ 77 | C.uint16_t(bufLen), /* len */ 78 | 0, /* timeout */ 79 | ); ret < 0 { 80 | return fmt.Errorf("libusb_control_transfer failed: %w", libusberror(ret)) 81 | } 82 | 83 | if err := desc.UnmarshalBinary(unsafe.Slice((*byte)(buf), bufLen)); err != nil { 84 | return err 85 | } 86 | 87 | return nil 88 | } 89 | 90 | func (ct *CameraTerminal) Set(desc descriptors.CameraTerminalControlDescriptor) error { 91 | ifnum := ct.usb.altsetting.bInterfaceNumber 92 | 93 | buf, err := desc.MarshalBinary() 94 | if err != nil { 95 | return err 96 | } 97 | 98 | cPtr := (*C.uchar)(C.CBytes(buf)) 99 | defer C.free(unsafe.Pointer(cPtr)) 100 | 101 | if ret := C.libusb_control_transfer( 102 | ct.deviceHandle, 103 | C.uint8_t(requests.RequestTypeVideoInterfaceSetRequest), /* bmRequestType */ 104 | C.uint8_t(requests.RequestCodeSetCur), /* bRequest */ 105 | C.uint16_t(desc.Value()<<8), /* wValue: on the hight byte */ 106 | C.uint16_t(uint16(ct.CameraDescriptor.InputTerminalDescriptor.TerminalID)<<8|uint16(ifnum)), /* wIndex */ 107 | (*C.uchar)(cPtr), /* data */ 108 | C.uint16_t(len(buf)), /* len */ 109 | 0, /* timeout */ 110 | ); ret < 0 { 111 | return fmt.Errorf("libusb_control_transfer failed: %w", libusberror(ret)) 112 | } 113 | 114 | return nil 115 | } 116 | -------------------------------------------------------------------------------- /camera_test.go: -------------------------------------------------------------------------------- 1 | package uvc 2 | 3 | import ( 4 | "log" 5 | "syscall" 6 | "testing" 7 | 8 | "github.com/kevmo314/go-uvc/pkg/descriptors" 9 | ) 10 | 11 | func TestAutoExposureMode(t *testing.T) { 12 | fd, err := syscall.Open("/dev/bus/usb/001/002", syscall.O_RDWR, 0) 13 | if err != nil { 14 | t.Fatal(err) 15 | } 16 | 17 | ctx, err := NewUVCDevice(uintptr(fd)) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | 22 | info, err := ctx.DeviceInfo() 23 | if err != nil { 24 | t.Fatal(err) 25 | } 26 | 27 | // get format descriptors 28 | for _, iface := range info.ControlInterfaces { 29 | log.Printf("got control interface: %#v", iface) 30 | if iface.CameraTerminal != nil { 31 | setControl := &descriptors.AutoExposureModeControl{Mode: descriptors.AutoExposureModeManual} 32 | err := iface.CameraTerminal.Set(setControl) 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | 37 | control := &descriptors.AutoExposureModeControl{} 38 | if err = iface.CameraTerminal.Get(control); err != nil { 39 | t.Fatal(err) 40 | } 41 | 42 | if control.Mode != descriptors.AutoExposureModeManual { 43 | t.Fatalf("TestAutoExposure: expected ae mode 1 (manual), got %d", control.Mode) 44 | } 45 | } 46 | } 47 | } 48 | 49 | func TestAutoFocus(t *testing.T) { 50 | fd, err := syscall.Open("/dev/bus/usb/001/002", syscall.O_RDWR, 0) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | 55 | ctx, err := NewUVCDevice(uintptr(fd)) 56 | if err != nil { 57 | t.Fatal(err) 58 | } 59 | 60 | info, err := ctx.DeviceInfo() 61 | if err != nil { 62 | t.Fatal(err) 63 | } 64 | 65 | // get format descriptors 66 | for _, iface := range info.ControlInterfaces { 67 | log.Printf("got control interface: %#v", iface) 68 | if iface.CameraTerminal != nil { 69 | 70 | supported := iface.CameraTerminal.IsControlRequestSupported(&descriptors.FocusAutoControl{}) 71 | if !supported { 72 | t.Fatal("feature not supported") 73 | } 74 | 75 | setControl := &descriptors.FocusAutoControl{FocusAuto: true} 76 | err := iface.CameraTerminal.Set(setControl) 77 | if err != nil { 78 | t.Fatal(err) 79 | } 80 | 81 | control := &descriptors.FocusAutoControl{} 82 | if err = iface.CameraTerminal.Get(control); err != nil { 83 | t.Fatal(err) 84 | } 85 | 86 | if !control.FocusAuto { 87 | t.Fatalf("TestAutoFocus: expected true, got %t", control.FocusAuto) 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /cmd/inspect/inspect.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "image" 7 | "log" 8 | "os" 9 | "strconv" 10 | "sync/atomic" 11 | "time" 12 | 13 | "golang.org/x/image/draw" 14 | 15 | "github.com/gdamore/tcell/v2" 16 | "github.com/hajimehoshi/ebiten/v2" 17 | "github.com/kevmo314/go-uvc" 18 | "github.com/kevmo314/go-uvc/pkg/decode" 19 | "github.com/kevmo314/go-uvc/pkg/descriptors" 20 | "github.com/rivo/tview" 21 | ) 22 | 23 | type Display struct { 24 | frame atomic.Value 25 | } 26 | 27 | func (g *Display) Update() error { 28 | return nil 29 | } 30 | 31 | func (g *Display) Draw(screen *ebiten.Image) { 32 | screen.DrawImage(g.frame.Load().(*ebiten.Image), &ebiten.DrawImageOptions{}) 33 | } 34 | 35 | func (g *Display) Layout(outsideWidth, outsideHeight int) (int, int) { 36 | frame := g.frame.Load().(*ebiten.Image) 37 | return frame.Bounds().Dx(), frame.Bounds().Dy() 38 | } 39 | 40 | func main() { 41 | path := flag.String("path", "", "path to the usb device") 42 | render := flag.Bool("render", false, "render the frames to screen (higher performance but requires a display)") 43 | 44 | flag.Parse() 45 | 46 | fd, err := os.OpenFile(*path, os.O_RDWR, 0) 47 | if err != nil { 48 | panic(err) 49 | } 50 | defer fd.Close() 51 | 52 | dev, err := uvc.NewUVCDevice(fd.Fd()) 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | info, err := dev.DeviceInfo() 58 | if err != nil { 59 | panic(err) 60 | } 61 | 62 | app := tview.NewApplication() 63 | 64 | streamingIfaces := tview.NewList() 65 | streamingIfaces.SetBorder(true).SetTitle("Streaming Interfaces") 66 | 67 | controlIfaces := tview.NewList().ShowSecondaryText(false) 68 | controlIfaces.SetBorder(true).SetTitle("Control Interfaces") 69 | 70 | controlRequests := tview.NewList().ShowSecondaryText(false) 71 | controlRequests.SetBorder(true).SetTitle("Control Requests") 72 | controlRequests.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { 73 | if event.Key() == tcell.KeyEscape { 74 | controlRequests.Clear() 75 | app.SetFocus(controlIfaces) 76 | return nil 77 | } 78 | return event 79 | }) 80 | 81 | ifaces := tview.NewFlex().SetDirection(tview.FlexRow).AddItem(streamingIfaces, 0, 1, true).AddItem(controlIfaces, 0, 1, false) 82 | 83 | secondColumn := tview.NewFlex() 84 | 85 | formats := tview.NewList() 86 | formats.SetBorder(true).SetTitle("Formats") 87 | 88 | secondColumn.SetDirection(tview.FlexRow).AddItem(formats, 0, 1, false).AddItem(controlRequests, 0, 1, false) 89 | 90 | frames := tview.NewList() 91 | frames.SetBorder(true).SetTitle("Frames") 92 | 93 | preview := tview.NewImage() 94 | preview.SetColors(256).SetDithering(tview.DitheringNone).SetBorder(true).SetTitle("Preview") 95 | 96 | logText := tview.NewTextView() 97 | logText.SetMaxLines(10).SetBorder(true).SetTitle("Log") 98 | 99 | log.SetOutput(logText) 100 | 101 | active := &atomic.Uint32{} 102 | 103 | for _, si := range info.StreamingInterfaces { 104 | streamingIfaces.AddItem(fmt.Sprintf("Interface %d", si.InterfaceNumber()), fmt.Sprintf("v%s", si.UVCVersionString()), 0, func() { 105 | for fdIndex, d := range si.Descriptors { 106 | if fd, ok := d.(descriptors.FormatDescriptor); ok { 107 | formats.AddItem(formatDescriptorTitle(fd), formatDescriptorSubtitle(fd), 0, func() { 108 | frs := si.Descriptors[fdIndex+1 : fdIndex+int(NumFrameDescriptors(fd))+1] 109 | for _, fr := range frs { 110 | if fr, ok := fr.(descriptors.FrameDescriptor); ok { 111 | frames.AddItem(frameDescriptorTitle(fr), frameDescriptorSubtitle(fr), 0, func() { 112 | track := active.Add(1) 113 | reader, err := si.ClaimFrameReader(fd.Index(), fr.Index()) 114 | if err != nil { 115 | log.Printf("error claiming frame reader: %s", err) 116 | return 117 | } 118 | decoder, err := decode.NewFrameReaderDecoder(reader, fd, fr) 119 | if err != nil { 120 | log.Printf("error creating decoder: %s", err) 121 | return 122 | } 123 | if *render { 124 | g := &Display{} 125 | go func() { 126 | defer reader.Close() 127 | for active.Load() == track { 128 | img, err := decoder.ReadFrame() 129 | if err != nil { 130 | log.Printf("error reading frame: %s", err) 131 | continue 132 | } 133 | if g.frame.Swap(ebiten.NewImageFromImage(img)) == nil { 134 | go func() { 135 | if err := ebiten.RunGame(g); err != nil { 136 | log.Printf("ebiten error: %s", err) 137 | } 138 | }() 139 | } 140 | } 141 | }() 142 | } else { 143 | go func() { 144 | defer reader.Close() 145 | t0 := time.Now().Add(-1 * time.Second) 146 | for active.Load() == track { 147 | img, err := decoder.ReadFrame() 148 | if err != nil { 149 | log.Printf("error reading frame: %s", err) 150 | return 151 | } 152 | t1 := time.Now() 153 | if t1.Sub(t0) < 50*time.Millisecond { 154 | continue 155 | } 156 | t0 = t1 157 | w := 64 158 | h := img.Bounds().Dy() * w / img.Bounds().Dx() 159 | preview.SetImage(resize(img, w, h)) 160 | app.ForceDraw() 161 | } 162 | }() 163 | } 164 | app.SetFocus(controlIfaces) 165 | }) 166 | } 167 | } 168 | app.SetFocus(frames) 169 | }) 170 | } 171 | } 172 | app.SetFocus(formats) 173 | }) 174 | } 175 | 176 | for _, ci := range info.ControlInterfaces { 177 | controlIfaces.AddItem(controlInterfaceTitle(ci), "", 0, func() { 178 | switch ci.Descriptor.(type) { 179 | case *descriptors.CameraTerminalDescriptor: 180 | app.SetFocus(controlRequests) 181 | 182 | controls := ci.CameraTerminal.GetSupportedControls() 183 | uiControls := formatCameraControls(ci, app, secondColumn, controls) 184 | for _, option := range uiControls { 185 | controlRequests.AddItem(option.title, "", 0, option.handler) 186 | } 187 | case *descriptors.ProcessingUnitDescriptor: 188 | app.SetFocus(controlRequests) 189 | 190 | controls := ci.ProcessingUnit.GetSupportedControls() 191 | uiControls := formatProcessingControls(ci, app, secondColumn, controls) 192 | for _, option := range uiControls { 193 | controlRequests.AddItem(option.title, "", 0, option.handler) 194 | } 195 | } 196 | }) 197 | } 198 | 199 | // Create the layout. 200 | 201 | flex := tview.NewFlex(). 202 | AddItem(ifaces, 0, 1, true). 203 | AddItem(secondColumn, 0, 1, false). 204 | AddItem(frames, 0, 1, false) 205 | 206 | if !*render { 207 | flex.AddItem(preview, 0, 3, false) 208 | } 209 | 210 | if err := app.SetRoot(tview.NewFlex().SetDirection(tview.FlexRow).AddItem(flex, 0, 1, true).AddItem(logText, 10, 0, false), true).Run(); err != nil { 211 | panic(err) 212 | } 213 | } 214 | 215 | func resize(img image.Image, w, h int) *image.RGBA { 216 | dst := image.NewRGBA(image.Rect(0, 0, w, h)) 217 | draw.NearestNeighbor.Scale(dst, dst.Bounds(), img, img.Bounds(), draw.Over, nil) 218 | return dst 219 | } 220 | 221 | func NumFrameDescriptors(fd descriptors.FormatDescriptor) uint8 { 222 | // darn you golang and your lack of structural typing. 223 | switch fd := fd.(type) { 224 | case *descriptors.MJPEGFormatDescriptor: 225 | return fd.NumFrameDescriptors 226 | case *descriptors.H264FormatDescriptor: 227 | return fd.NumFrameDescriptors 228 | case *descriptors.VP8FormatDescriptor: 229 | return fd.NumFrameDescriptors 230 | case *descriptors.UncompressedFormatDescriptor: 231 | return fd.NumFrameDescriptors 232 | case *descriptors.FrameBasedFormatDescriptor: 233 | return fd.NumFrameDescriptors 234 | default: 235 | return 0 236 | } 237 | } 238 | 239 | type ControlRequestListItem struct { 240 | title string 241 | handler func() 242 | } 243 | 244 | func formatProcessingControls(ci *uvc.ControlInterface, app *tview.Application, secondColumn *tview.Flex, 245 | controls []descriptors.ProcessingUnitControlDescriptor) []*ControlRequestListItem { 246 | var uiControls []*ControlRequestListItem 247 | for _, control := range controls { 248 | controlUI := generatePUDUI(control, app, ci, secondColumn) 249 | 250 | if controlUI != nil { 251 | uiControls = append(uiControls, controlUI) 252 | } 253 | 254 | } 255 | return uiControls 256 | } 257 | 258 | func generatePUDUI(control descriptors.ProcessingUnitControlDescriptor, app *tview.Application, ci *uvc.ControlInterface, secondColumn *tview.Flex) *ControlRequestListItem { 259 | switch control.(type) { 260 | case *descriptors.BacklightCompensationControl: 261 | case *descriptors.BrightnessControl: 262 | return &ControlRequestListItem{ 263 | title: "Brightness", 264 | handler: func() { 265 | initFocus := app.GetFocus() 266 | controlRequestInput := tview.NewInputField() 267 | controlRequestInput.SetLabel("Enter brightness value: "). 268 | SetFieldWidth(10). 269 | SetAcceptanceFunc(tview.InputFieldInteger). 270 | SetDoneFunc(func(key tcell.Key) { 271 | capture, err := strconv.ParseUint(controlRequestInput.GetText(), 10, 16) 272 | if err != nil { 273 | log.Printf("failed parsing value %s", err) 274 | } 275 | setBrightness := &descriptors.BrightnessControl{Brightness: uint16(capture)} 276 | err = ci.ProcessingUnit.Set(setBrightness) 277 | if err != nil { 278 | log.Printf("brightness request failed %s", err) 279 | } 280 | secondColumn.RemoveItem(controlRequestInput) 281 | app.SetFocus(initFocus) 282 | }) 283 | secondColumn.AddItem(controlRequestInput, 1, 1, false) 284 | app.SetFocus(controlRequestInput) 285 | }, 286 | } 287 | case *descriptors.ContrastControl: 288 | return &ControlRequestListItem{ 289 | title: "Contrast", 290 | handler: func() { 291 | initFocus := app.GetFocus() 292 | controlRequestInput := tview.NewInputField() 293 | controlRequestInput.SetLabel("Enter contrast value: "). 294 | SetFieldWidth(10). 295 | SetAcceptanceFunc(tview.InputFieldInteger). 296 | SetDoneFunc(func(key tcell.Key) { 297 | capture, err := strconv.ParseUint(controlRequestInput.GetText(), 10, 16) 298 | if err != nil { 299 | log.Printf("failed parsing value %s", err) 300 | } 301 | setContrast := &descriptors.ContrastControl{Contrast: uint16(capture)} 302 | err = ci.ProcessingUnit.Set(setContrast) 303 | if err != nil { 304 | log.Printf("contrast request failed %s", err) 305 | } 306 | secondColumn.RemoveItem(controlRequestInput) 307 | app.SetFocus(initFocus) 308 | }) 309 | secondColumn.AddItem(controlRequestInput, 1, 1, false) 310 | app.SetFocus(controlRequestInput) 311 | }, 312 | } 313 | case *descriptors.ContrastAutoControl: 314 | case *descriptors.GainControl: 315 | return &ControlRequestListItem{ 316 | title: "Gain", 317 | handler: func() { 318 | initFocus := app.GetFocus() 319 | controlRequestInput := tview.NewInputField() 320 | controlRequestInput.SetLabel("Enter value: "). 321 | SetFieldWidth(10). 322 | SetAcceptanceFunc(tview.InputFieldInteger). 323 | SetDoneFunc(func(key tcell.Key) { 324 | capture, err := strconv.ParseUint(controlRequestInput.GetText(), 10, 16) 325 | if err != nil { 326 | log.Printf("failed parsing value %s", err) 327 | } 328 | setGain := &descriptors.GainControl{Gain: uint16(capture)} 329 | err = ci.ProcessingUnit.Set(setGain) 330 | if err != nil { 331 | log.Printf("gain request failed %s", err) 332 | } 333 | secondColumn.RemoveItem(controlRequestInput) 334 | app.SetFocus(initFocus) 335 | }) 336 | secondColumn.AddItem(controlRequestInput, 1, 1, false) 337 | app.SetFocus(controlRequestInput) 338 | }} 339 | case *descriptors.PowerLineFrequencyControl: 340 | case *descriptors.HueControl: 341 | case *descriptors.HueAutoControl: 342 | case *descriptors.SaturationControl: 343 | return &ControlRequestListItem{ 344 | title: "Saturation", 345 | handler: func() { 346 | initFocus := app.GetFocus() 347 | controlRequestInput := tview.NewInputField() 348 | controlRequestInput.SetLabel("Enter saturation value: "). 349 | SetFieldWidth(10). 350 | SetAcceptanceFunc(tview.InputFieldInteger). 351 | SetDoneFunc(func(key tcell.Key) { 352 | capture, err := strconv.ParseUint(controlRequestInput.GetText(), 10, 16) 353 | if err != nil { 354 | log.Printf("failed parsing value %s", err) 355 | } 356 | setSaturation := &descriptors.SaturationControl{Saturation: uint16(capture)} 357 | err = ci.ProcessingUnit.Set(setSaturation) 358 | if err != nil { 359 | log.Printf("saturation request failed %s", err) 360 | } 361 | secondColumn.RemoveItem(controlRequestInput) 362 | app.SetFocus(initFocus) 363 | }) 364 | secondColumn.AddItem(controlRequestInput, 1, 1, false) 365 | app.SetFocus(controlRequestInput) 366 | }} 367 | case *descriptors.SharpnessControl: 368 | return &ControlRequestListItem{ 369 | title: "Sharpness", 370 | handler: func() { 371 | initFocus := app.GetFocus() 372 | controlRequestInput := tview.NewInputField() 373 | controlRequestInput.SetLabel("Enter sharpness value: "). 374 | SetFieldWidth(10). 375 | SetAcceptanceFunc(tview.InputFieldInteger). 376 | SetDoneFunc(func(key tcell.Key) { 377 | capture, err := strconv.ParseUint(controlRequestInput.GetText(), 10, 16) 378 | if err != nil { 379 | log.Printf("failed parsing value %s", err) 380 | } 381 | setSaturation := &descriptors.SharpnessControl{Sharpness: uint16(capture)} 382 | err = ci.ProcessingUnit.Set(setSaturation) 383 | if err != nil { 384 | log.Printf("sharpness request failed %s", err) 385 | } 386 | secondColumn.RemoveItem(controlRequestInput) 387 | app.SetFocus(initFocus) 388 | }) 389 | secondColumn.AddItem(controlRequestInput, 1, 1, false) 390 | app.SetFocus(controlRequestInput) 391 | }} 392 | case *descriptors.GammaControl: 393 | case *descriptors.WhiteBalanceTemperatureControl: 394 | case *descriptors.WhiteBalanceTemperatureAutoControl: 395 | case *descriptors.WhiteBalanceComponentControl: 396 | case *descriptors.WhiteBalanceComponentAutoControl: 397 | case *descriptors.DigitalMultiplerControl: 398 | case *descriptors.DigitalMultiplerLimitControl: 399 | case *descriptors.AnalogVideoStandardControl: 400 | case *descriptors.AnalogVideoLockStatusControl: 401 | } 402 | return nil 403 | } 404 | 405 | func formatCameraControls(ci *uvc.ControlInterface, app *tview.Application, secondColumn *tview.Flex, 406 | controls []descriptors.CameraTerminalControlDescriptor) []*ControlRequestListItem { 407 | var uiControls []*ControlRequestListItem 408 | for _, control := range controls { 409 | controlUI := generateCTUI(control, app, ci, secondColumn) 410 | 411 | if controlUI != nil { 412 | uiControls = append(uiControls, controlUI) 413 | } 414 | } 415 | return uiControls 416 | } 417 | 418 | func generateCTUI(control descriptors.CameraTerminalControlDescriptor, app *tview.Application, 419 | ci *uvc.ControlInterface, secondColumn *tview.Flex) *ControlRequestListItem { 420 | switch control.(type) { 421 | case *descriptors.ScanningModeControl: 422 | case *descriptors.AutoExposurePriorityControl: 423 | case *descriptors.DigitalWindowControl: 424 | case *descriptors.PrivacyControl: 425 | case *descriptors.FocusAbsoluteControl: 426 | return &ControlRequestListItem{ 427 | title: "Focus (Absolute)", 428 | handler: func() { 429 | initFocus := app.GetFocus() 430 | controlRequestInput := tview.NewInputField() 431 | controlRequestInput.SetLabel("Enter focus value: "). 432 | SetFieldWidth(10). 433 | SetAcceptanceFunc(tview.InputFieldInteger). 434 | SetDoneFunc(func(key tcell.Key) { 435 | manualFocus := &descriptors.FocusAutoControl{FocusAuto: false} 436 | err := ci.CameraTerminal.Set(manualFocus) 437 | if err != nil { 438 | log.Printf("manual focus request failed %s", err) 439 | } 440 | 441 | capture, err := strconv.ParseUint(controlRequestInput.GetText(), 10, 16) 442 | if err != nil { 443 | log.Printf("failed parsing value %s", err) 444 | } 445 | setExposure := &descriptors.FocusAbsoluteControl{Focus: uint16(capture)} 446 | err = ci.CameraTerminal.Set(setExposure) 447 | if err != nil { 448 | log.Printf("absolute focus request failed %s", err) 449 | } 450 | secondColumn.RemoveItem(controlRequestInput) 451 | app.SetFocus(initFocus) 452 | }) 453 | secondColumn.AddItem(controlRequestInput, 1, 1, false) 454 | app.SetFocus(controlRequestInput) 455 | }} 456 | case *descriptors.FocusAutoControl: 457 | return &ControlRequestListItem{ 458 | title: "Enable Automatic Focus", 459 | handler: func() { 460 | manualFocus := &descriptors.FocusAutoControl{FocusAuto: true} 461 | err := ci.CameraTerminal.Set(manualFocus) 462 | if err != nil { 463 | log.Printf("auto focus request failed %s", err) 464 | } 465 | }} 466 | case *descriptors.ExposureTimeAbsoluteControl: 467 | return &ControlRequestListItem{ 468 | title: "Exposure Time (Absolute)", 469 | handler: func() { 470 | initFocus := app.GetFocus() 471 | controlRequestInput := tview.NewInputField() 472 | controlRequestInput.SetLabel("Enter exposure value: "). 473 | SetFieldWidth(10). 474 | SetAcceptanceFunc(tview.InputFieldInteger). 475 | SetDoneFunc(func(key tcell.Key) { 476 | capture, err := strconv.ParseUint(controlRequestInput.GetText(), 10, 16) 477 | if err != nil { 478 | log.Printf("failed parsing value %s", err) 479 | } 480 | 481 | manualExposure := &descriptors.AutoExposureModeControl{Mode: descriptors.AutoExposureModeManual} 482 | err = ci.CameraTerminal.Set(manualExposure) 483 | if err != nil { 484 | log.Printf("manual focus request failed %s", err) 485 | } 486 | 487 | setExposure := &descriptors.ExposureTimeAbsoluteControl{Time: uint32(capture)} 488 | err = ci.CameraTerminal.Set(setExposure) 489 | if err != nil { 490 | log.Printf("control request failed %s", err) 491 | } 492 | secondColumn.RemoveItem(controlRequestInput) 493 | app.SetFocus(initFocus) 494 | }) 495 | secondColumn.AddItem(controlRequestInput, 1, 1, false) 496 | app.SetFocus(controlRequestInput) 497 | }} 498 | case *descriptors.ExposureTimeRelativeControl: 499 | case *descriptors.FocusRelativeControl: 500 | case *descriptors.FocusSimpleRangeControl: 501 | case *descriptors.RollAbsoluteControl: 502 | case *descriptors.IrisAbsoluteControl: 503 | case *descriptors.IrisRelativeControl: 504 | case *descriptors.PanTiltAbsoluteControl: 505 | case *descriptors.PanTiltRelativeControl: 506 | case *descriptors.RegionOfInterestControl: 507 | case *descriptors.RollRelativeControl: 508 | case *descriptors.ZoomAbsoluteControl: 509 | return &ControlRequestListItem{ 510 | title: "Zoom (Absolute)", 511 | handler: func() { 512 | initFocus := app.GetFocus() 513 | controlRequestInput := tview.NewInputField() 514 | controlRequestInput.SetLabel("Enter zoom value (>= 100): "). 515 | SetFieldWidth(10). 516 | SetAcceptanceFunc(tview.InputFieldInteger). 517 | SetDoneFunc(func(key tcell.Key) { 518 | capture, err := strconv.ParseUint(controlRequestInput.GetText(), 10, 16) 519 | if err != nil { 520 | log.Printf("failed parsing value %s", err) 521 | } 522 | setControl := &descriptors.ZoomAbsoluteControl{ObjectiveFocalLength: uint16(capture)} 523 | err = ci.CameraTerminal.Set(setControl) 524 | if err != nil { 525 | log.Printf("control request failed %s", err) 526 | } 527 | secondColumn.RemoveItem(controlRequestInput) 528 | app.SetFocus(initFocus) 529 | }) 530 | secondColumn.AddItem(controlRequestInput, 1, 1, false) 531 | app.SetFocus(controlRequestInput) 532 | }} 533 | case *descriptors.ZoomRelativeControl: 534 | } 535 | return nil 536 | } 537 | 538 | func formatDescriptorTitle(fd descriptors.FormatDescriptor) string { 539 | switch fd := fd.(type) { 540 | case *descriptors.MJPEGFormatDescriptor: 541 | return fmt.Sprintf("MJPEG (%d formats)", fd.NumFrameDescriptors) 542 | case *descriptors.H264FormatDescriptor: 543 | return fmt.Sprintf("H264 (%d formats)", fd.NumFrameDescriptors) 544 | case *descriptors.VP8FormatDescriptor: 545 | return fmt.Sprintf("VP8 (%d formats)", fd.NumFrameDescriptors) 546 | case *descriptors.DVFormatDescriptor: 547 | return "DV" 548 | case *descriptors.UncompressedFormatDescriptor: 549 | return "Uncompressed" 550 | case *descriptors.MPEG2TSFormatDescriptor: 551 | return "MPEG2TS" 552 | case *descriptors.FrameBasedFormatDescriptor: 553 | fourcc, err := fd.FourCC() 554 | if err != nil { 555 | return fmt.Sprintf("Frame-Based (%d formats)", fd.NumFrameDescriptors) 556 | } 557 | return fmt.Sprintf("Frame-Based %s (%d formats)", fourcc, fd.NumFrameDescriptors) 558 | case *descriptors.StreamBasedFormatDescriptor: 559 | return "Stream-Based" 560 | default: 561 | return "Unknown" 562 | } 563 | } 564 | 565 | func formatDescriptorSubtitle(fd descriptors.FormatDescriptor) string { 566 | switch fd := fd.(type) { 567 | case *descriptors.MJPEGFormatDescriptor: 568 | return fmt.Sprintf("Aspect Ratio: %d:%d", fd.AspectRatioX, fd.AspectRatioY) 569 | case *descriptors.H264FormatDescriptor: 570 | return "" 571 | case *descriptors.VP8FormatDescriptor: 572 | return fmt.Sprintf("Max Mbps: %d", fd.MaxMBPerSec) 573 | case *descriptors.DVFormatDescriptor: 574 | return fmt.Sprintf("Format Type: %d", fd.FormatType) 575 | case *descriptors.UncompressedFormatDescriptor: 576 | return fd.GUIDFormat.String() 577 | case *descriptors.MPEG2TSFormatDescriptor: 578 | return fd.GUIDStrideFormat.String() 579 | case *descriptors.FrameBasedFormatDescriptor: 580 | return fmt.Sprintf("%s, Aspect Ratio: %d:%d, bpp: %d", fd.GUIDFormat, fd.AspectRatioX, fd.AspectRatioY, fd.BitsPerPixel) 581 | case *descriptors.StreamBasedFormatDescriptor: 582 | return fd.GUIDFormat.String() 583 | default: 584 | return "Unknown" 585 | } 586 | } 587 | 588 | func frameDescriptorTitle(fd descriptors.FrameDescriptor) string { 589 | switch fd := fd.(type) { 590 | case *descriptors.MJPEGFrameDescriptor: 591 | return fmt.Sprintf("MJPEG (%dx%d)", fd.Width, fd.Height) 592 | case *descriptors.H264FrameDescriptor: 593 | return fmt.Sprintf("H264 (%dx%d)", fd.Width, fd.Height) 594 | case *descriptors.VP8FrameDescriptor: 595 | return fmt.Sprintf("VP8 (%dx%d)", fd.Width, fd.Height) 596 | case *descriptors.UncompressedFrameDescriptor: 597 | return fmt.Sprintf("Uncompressed (%dx%d)", fd.Width, fd.Height) 598 | case *descriptors.FrameBasedFrameDescriptor: 599 | return fmt.Sprintf("Frame-Based (%dx%d)", fd.Width, fd.Height) 600 | default: 601 | return "Unknown" 602 | } 603 | } 604 | 605 | func frameDescriptorSubtitle(fd descriptors.FrameDescriptor) string { 606 | switch fd := fd.(type) { 607 | case *descriptors.MJPEGFrameDescriptor: 608 | return fmt.Sprintf("Bitrate: %d-%d Mbps", fd.MinBitRate, fd.MaxBitRate) 609 | case *descriptors.H264FrameDescriptor: 610 | return fmt.Sprintf("Level: %x, Profile: %x", fd.LevelIDC, fd.Profile) 611 | case *descriptors.VP8FrameDescriptor: 612 | return fmt.Sprintf("Bitrate: %d-%d Mbps", fd.MinBitRate, fd.MaxBitRate) 613 | case *descriptors.UncompressedFrameDescriptor: 614 | return fmt.Sprintf("Bitrate: %d-%d Mbps", fd.MinBitRate, fd.MaxBitRate) 615 | case *descriptors.FrameBasedFrameDescriptor: 616 | return fmt.Sprintf("Bitrate: %d-%d Mbps", fd.MinBitRate, fd.MaxBitRate) 617 | default: 618 | return "Unknown" 619 | } 620 | } 621 | 622 | func controlInterfaceTitle(ci *uvc.ControlInterface) string { 623 | switch ci.Descriptor.(type) { 624 | case *descriptors.HeaderDescriptor: 625 | return "Header" 626 | case *descriptors.InputTerminalDescriptor: 627 | return "Input Terminal" 628 | case *descriptors.CameraTerminalDescriptor: 629 | return "Camera Terminal" 630 | case *descriptors.OutputTerminalDescriptor: 631 | return "Output Terminal" 632 | case *descriptors.SelectorUnitDescriptor: 633 | return "Selector Unit" 634 | case *descriptors.ProcessingUnitDescriptor: 635 | return "Processing Unit" 636 | case *descriptors.EncodingUnitDescriptor: 637 | return "Encoding Unit" 638 | case *descriptors.ExtensionUnitDescriptor: 639 | return "Extension Unit" 640 | default: 641 | return "Unknown" 642 | } 643 | } 644 | -------------------------------------------------------------------------------- /consts.go: -------------------------------------------------------------------------------- 1 | package uvc 2 | 3 | type EndpointDescriptorSubtype int 4 | 5 | const ( 6 | EndpointDescriptorSubtypeUndefined EndpointDescriptorSubtype = 0x00 7 | EndpointDescriptorSubtypeGeneral = 0x01 8 | EndpointDescriptorSubtypeEndpoint = 0x02 9 | EndpointDescriptorSubtypeInterrupt = 0x03 10 | ) 11 | 12 | type RequestCodes int 13 | 14 | const ( 15 | RequestCodesUndefined RequestCodes = 0x00 16 | RequestCodesSetCur = 0x01 17 | RequestCodesSetCurAll = 0x11 18 | RequestCodesGetCur = 0x81 19 | RequestCodesGetMin = 0x82 20 | RequestCodesGetMax = 0x83 21 | RequestCodesGetRes = 0x84 22 | RequestCodesGetLen = 0x85 23 | RequestCodesGetInfo = 0x86 24 | RequestCodesGetDef = 0x87 25 | RequestCodesGetCurAll = 0x91 26 | RequestCodesGetMinAll = 0x92 27 | RequestCodesGetMaxAll = 0x93 28 | RequestCodesGetResAll = 0x94 29 | RequestCodesGetDefAll = 0x97 30 | ) 31 | 32 | type InterfaceControlSelector int 33 | 34 | const ( 35 | InterfaceControlSelectorUndefined InterfaceControlSelector = 0x00 36 | InterfaceControlSelectorVideoPowerModeControl = 0x01 37 | InterfaceControlSelectorRequestErrorCodeControl = 0x02 38 | ) 39 | 40 | type TerminalControlSelector int 41 | 42 | const ( 43 | TerminalControlSelectorUndefined TerminalControlSelector = 0x00 44 | ) 45 | 46 | type SelectorUnitControlSelector int 47 | 48 | const ( 49 | SelectorUnitControlSelectorUndefined SelectorUnitControlSelector = 0x00 50 | SelectorUnitInputSelectControl = 0x01 51 | ) 52 | 53 | type EncodingUnitControlSelector int 54 | 55 | const ( 56 | EncodingUnitControlSelectorUndefined EncodingUnitControlSelector = 0x00 57 | EncodingUnitControlSelectorSelectLayerControl = 0x01 58 | EncodingUnitControlSelectorProfileToolsetControl = 0x02 59 | EncodingUnitControlSelectorVideoResolutionControl = 0x03 60 | EncodingUnitControlSelectorMinFrameIntervalControl = 0x04 61 | EncodingUnitControlSelectorSliceModeControl = 0x05 62 | EncodingUnitControlSelectorRateControlModeControl = 0x06 63 | EncodingUnitControlSelectorAverageBitrateControl = 0x07 64 | EncodingUnitControlSelectorCPBSizeControl = 0x08 65 | EncodingUnitControlSelectorPeakBitRateControl = 0x09 66 | EncodingUnitControlSelectorQuantizationParamsControl = 0x0A 67 | EncodingUnitControlSelectorSyncRefFrameControl = 0x0B 68 | EncodingUnitControlSelectorLTRBufferControl = 0x0C 69 | EncodingUnitControlSelectorLTRPictureControl = 0x0D 70 | EncodingUnitControlSelectorLTRValidationControl = 0x0E 71 | EncodingUnitControlSelectorLevelIDCControl = 0x0F 72 | EncodingUnitControlSelectorSEIPayloadTypeControl = 0x10 73 | EncodingUnitControlSelectorQPRangeControl = 0x11 74 | EncodingUnitControlSelectorPriorityControl = 0x12 75 | EncodingUnitControlSelectorStartOrStopLayerControl = 0x13 76 | EncodingUnitControlSelectorErrorResiliencyControl = 0x14 77 | ) 78 | 79 | type ExtensionUnitControlSelector int 80 | 81 | const ( 82 | ExtensionUnitControlSelectorUndefined ExtensionUnitControlSelector = 0x00 83 | ) 84 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package uvc 2 | 3 | /* 4 | #cgo LDFLAGS: -lusb-1.0 5 | #include 6 | */ 7 | import "C" 8 | import "errors" 9 | 10 | var ( 11 | ErrInvalidDescriptor = errors.New("invalid descriptor") 12 | ) 13 | 14 | func libusberror(err C.int) error { 15 | return errors.New(C.GoString(C.libusb_error_name(err))) 16 | } 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kevmo314/go-uvc 2 | 3 | go 1.22.2 4 | 5 | require ( 6 | github.com/google/uuid v1.6.0 7 | github.com/hajimehoshi/ebiten/v2 v2.7.4 8 | github.com/mattn/go-pointer v0.0.1 9 | github.com/rivo/tview v0.0.0-20240505185119-ed116790de0f 10 | golang.org/x/image v0.16.0 11 | ) 12 | 13 | require ( 14 | github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 // indirect 15 | github.com/ebitengine/hideconsole v1.0.0 // indirect 16 | github.com/ebitengine/purego v0.7.0 // indirect 17 | github.com/gdamore/encoding v1.0.0 // indirect 18 | github.com/gdamore/tcell/v2 v2.7.1 // indirect 19 | github.com/jezek/xgb v1.1.1 // indirect 20 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 21 | github.com/mattn/go-runewidth v0.0.15 // indirect 22 | github.com/rivo/uniseg v0.4.7 // indirect 23 | golang.org/x/sync v0.7.0 // indirect 24 | golang.org/x/sys v0.20.0 // indirect 25 | golang.org/x/term v0.17.0 // indirect 26 | golang.org/x/text v0.15.0 // indirect 27 | ) 28 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895 h1:48bCqKTuD7Z0UovDfvpCn7wZ0GUZ+yosIteNDthn3FU= 2 | github.com/ebitengine/gomobile v0.0.0-20240518074828-e86332849895/go.mod h1:XZdLv05c5hOZm3fM2NlJ92FyEZjnslcMcNRrhxs8+8M= 3 | github.com/ebitengine/hideconsole v1.0.0 h1:5J4U0kXF+pv/DhiXt5/lTz0eO5ogJ1iXb8Yj1yReDqE= 4 | github.com/ebitengine/hideconsole v1.0.0/go.mod h1:hTTBTvVYWKBuxPr7peweneWdkUwEuHuB3C1R/ielR1A= 5 | github.com/ebitengine/purego v0.7.0 h1:HPZpl61edMGCEW6XK2nsR6+7AnJ3unUxpTZBkkIXnMc= 6 | github.com/ebitengine/purego v0.7.0/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ= 7 | github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko= 8 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= 9 | github.com/gdamore/tcell/v2 v2.7.1 h1:TiCcmpWHiAU7F0rA2I3S2Y4mmLmO9KHxJ7E1QhYzQbc= 10 | github.com/gdamore/tcell/v2 v2.7.1/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg= 11 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 12 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 13 | github.com/hajimehoshi/ebiten/v2 v2.7.4 h1:X+heODRQ3Ie9F9QFjm24gEZqQd5FSfR9XuT2XfHwgf8= 14 | github.com/hajimehoshi/ebiten/v2 v2.7.4/go.mod h1:H2pHVgq29rfm5yeQ7jzWOM3VHsjo7/AyucODNLOhsVY= 15 | github.com/jezek/xgb v1.1.1 h1:bE/r8ZZtSv7l9gk6nU0mYx51aXrvnyb44892TwSaqS4= 16 | github.com/jezek/xgb v1.1.1/go.mod h1:nrhwO0FX/enq75I7Y7G8iN1ubpSGZEiA3v9e9GyRFlk= 17 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 18 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 19 | github.com/mattn/go-pointer v0.0.1 h1:n+XhsuGeVO6MEAp7xyEukFINEa+Quek5psIR/ylA6o0= 20 | github.com/mattn/go-pointer v0.0.1/go.mod h1:2zXcozF6qYGgmsG+SeTZz3oAbFLdD3OWqnUbNvJZAlc= 21 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 22 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 23 | github.com/rivo/tview v0.0.0-20240505185119-ed116790de0f h1:DAbaKhyPcZQp/TqlSdUd6Z445PkJb3bI0VccXg22oeg= 24 | github.com/rivo/tview v0.0.0-20240505185119-ed116790de0f/go.mod h1:02iFIz7K/A9jGCvrizLPvoqr4cEIx7q54RH5Qudkrss= 25 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 26 | github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 27 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 28 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 29 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 30 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 31 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 32 | golang.org/x/image v0.16.0 h1:9kloLAKhUufZhA12l5fwnx2NZW39/we1UhBesW433jw= 33 | golang.org/x/image v0.16.0/go.mod h1:ugSZItdV4nOxyqp56HmXwH0Ry0nBCpjnZdpDaIHdoPs= 34 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 35 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 36 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 37 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 38 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 39 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 40 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 41 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 42 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 43 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= 44 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 45 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 46 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 47 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 48 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 49 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 50 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 51 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 52 | golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= 53 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 54 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 55 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 56 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 57 | golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= 58 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 59 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 60 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 61 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 62 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 63 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 64 | golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= 65 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 66 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 67 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 68 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 69 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 70 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 71 | -------------------------------------------------------------------------------- /pkg/decode/README.md: -------------------------------------------------------------------------------- 1 | # go-uvc/pkg/decode 2 | 3 | This package provides a simple wrapper around FFmpeg's avcodec to decode video frames. 4 | 5 | The goal is for ease of use, not flexibility. If you wish to use your own decoder you 6 | don't need to use this package. 7 | -------------------------------------------------------------------------------- /pkg/decode/decode.go: -------------------------------------------------------------------------------- 1 | package decode 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | 7 | "github.com/kevmo314/go-uvc/pkg/descriptors" 8 | "github.com/kevmo314/go-uvc/pkg/transfers" 9 | ) 10 | 11 | type VideoDecoder interface { 12 | ReadFrame() (image.Image, error) 13 | Write(pkt []byte) (int, error) 14 | WriteUSBFrame(fr *transfers.Frame) error 15 | Close() error 16 | } 17 | 18 | func NewDescriptorDecoder(fd descriptors.FormatDescriptor, fr descriptors.FrameDescriptor) (VideoDecoder, error) { 19 | switch fd := fd.(type) { 20 | case *descriptors.MJPEGFormatDescriptor: 21 | return NewMJPEGDecoder() 22 | case *descriptors.H264FormatDescriptor: 23 | return NewH264Decoder() 24 | case *descriptors.VP8FormatDescriptor: 25 | return NewVP8Decoder() 26 | case *descriptors.FrameBasedFormatDescriptor: 27 | fcc, err := fd.FourCC() 28 | if err != nil { 29 | return nil, err 30 | } 31 | switch fcc { 32 | case [4]byte{'h', '2', '6', '4'}, [4]byte{'H', '2', '6', '4'}: 33 | return NewH264Decoder() 34 | case [4]byte{'v', 'p', '8', '0'}: 35 | return NewVP8Decoder() 36 | case [4]byte{'m', 'j', 'p', 'g'}: 37 | return NewMJPEGDecoder() 38 | } 39 | case *descriptors.UncompressedFormatDescriptor: 40 | fcc, err := fd.FourCC() 41 | if err != nil { 42 | return nil, err 43 | } 44 | fr := fr.(*descriptors.UncompressedFrameDescriptor) 45 | return NewUncompressedDecoder(fcc, int(fr.Width), int(fr.Height)) 46 | } 47 | return nil, fmt.Errorf("unsupported frame descriptor: %#v", fd) 48 | } 49 | 50 | type FrameReaderDecoder struct { 51 | reader *transfers.FrameReader 52 | dec VideoDecoder 53 | } 54 | 55 | func NewFrameReaderDecoder(reader *transfers.FrameReader, fd descriptors.FormatDescriptor, fr descriptors.FrameDescriptor) (*FrameReaderDecoder, error) { 56 | dec, err := NewDescriptorDecoder(fd, fr) 57 | if err != nil { 58 | return nil, err 59 | } 60 | return &FrameReaderDecoder{reader: reader, dec: dec}, nil 61 | } 62 | 63 | func (d *FrameReaderDecoder) ReadFrame() (image.Image, error) { 64 | for { 65 | img, err := d.dec.ReadFrame() 66 | if err == nil { 67 | return img, nil 68 | } 69 | if err != ErrEAGAIN { 70 | return nil, err 71 | } 72 | fr, err := d.reader.ReadFrame() 73 | if err != nil { 74 | return nil, err 75 | } 76 | if err := d.dec.WriteUSBFrame(fr); err != nil { 77 | return nil, err 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /pkg/decode/image.go: -------------------------------------------------------------------------------- 1 | package decode 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | ) 7 | 8 | // RGB is an in-memory image whose At method returns [color.RGB] values. 9 | type RGB struct { 10 | // Pix holds the image's pixels, in R, G, B, A order. The pixel at 11 | // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*3]. 12 | Pix []uint8 13 | // Stride is the Pix stride (in bytes) between vertically adjacent pixels. 14 | Stride int 15 | // Rect is the image's bounds. 16 | Rect image.Rectangle 17 | } 18 | 19 | var _ image.Image = &RGB{} 20 | 21 | func (p *RGB) ColorModel() color.Model { return color.RGBAModel } 22 | 23 | func (p *RGB) Bounds() image.Rectangle { return p.Rect } 24 | 25 | func (p *RGB) At(x, y int) color.Color { 26 | return p.RGBAAt(x, y) 27 | } 28 | 29 | func (p *RGB) RGBAAt(x, y int) color.RGBA { 30 | if !(image.Point{x, y}.In(p.Rect)) { 31 | return color.RGBA{} 32 | } 33 | i := p.PixOffset(x, y) 34 | s := p.Pix[i : i+3 : i+3] // Small cap improves performance, see https://golang.org/issue/27857 35 | return color.RGBA{s[0], s[1], s[2], 0} 36 | } 37 | 38 | // PixOffset returns the index of the first element of Pix that corresponds to 39 | // the pixel at (x, y). 40 | func (p *RGB) PixOffset(x, y int) int { 41 | return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*3 42 | } 43 | 44 | // SubImage returns an image representing the portion of the image p visible 45 | // through r. The returned value shares pixels with the original image. 46 | func (p *RGB) SubImage(r image.Rectangle) image.Image { 47 | r = r.Intersect(p.Rect) 48 | // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside 49 | // either r1 or r2 if the intersection is empty. Without explicitly checking for 50 | // this, the Pix[i:] expression below can panic. 51 | if r.Empty() { 52 | return &RGB{} 53 | } 54 | i := p.PixOffset(r.Min.X, r.Min.Y) 55 | return &RGB{ 56 | Pix: p.Pix[i:], 57 | Stride: p.Stride, 58 | Rect: r, 59 | } 60 | } 61 | 62 | // BGR is an in-memory image whose At method returns [color.BGR] values. 63 | type BGR struct { 64 | // Pix holds the image's pixels, in R, G, B, A order. The pixel at 65 | // (x, y) starts at Pix[(y-Rect.Min.Y)*Stride + (x-Rect.Min.X)*3]. 66 | Pix []uint8 67 | // Stride is the Pix stride (in bytes) between vertically adjacent pixels. 68 | Stride int 69 | // Rect is the image's bounds. 70 | Rect image.Rectangle 71 | } 72 | 73 | var _ image.Image = &BGR{} 74 | 75 | func (p *BGR) ColorModel() color.Model { return color.RGBAModel } 76 | 77 | func (p *BGR) Bounds() image.Rectangle { return p.Rect } 78 | 79 | func (p *BGR) At(x, y int) color.Color { 80 | return p.BGRAAt(x, y) 81 | } 82 | 83 | func (p *BGR) BGRAAt(x, y int) color.RGBA { 84 | if !(image.Point{x, y}.In(p.Rect)) { 85 | return color.RGBA{} 86 | } 87 | i := p.PixOffset(x, y) 88 | s := p.Pix[i : i+3 : i+3] // Small cap improves performance, see https://golang.org/issue/27857 89 | return color.RGBA{s[2], s[1], s[0], 0} 90 | } 91 | 92 | // PixOffset returns the index of the first element of Pix that corresponds to 93 | // the pixel at (x, y). 94 | func (p *BGR) PixOffset(x, y int) int { 95 | return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*3 96 | } 97 | 98 | // SubImage returns an image representing the portion of the image p visible 99 | // through r. The returned value shares pixels with the original image. 100 | func (p *BGR) SubImage(r image.Rectangle) image.Image { 101 | r = r.Intersect(p.Rect) 102 | // If r1 and r2 are Rectangles, r1.Intersect(r2) is not guaranteed to be inside 103 | // either r1 or r2 if the intersection is empty. Without explicitly checking for 104 | // this, the Pix[i:] expression below can panic. 105 | if r.Empty() { 106 | return &BGR{} 107 | } 108 | i := p.PixOffset(r.Min.X, r.Min.Y) 109 | return &BGR{ 110 | Pix: p.Pix[i:], 111 | Stride: p.Stride, 112 | Rect: r, 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /pkg/decode/libavcodec.go: -------------------------------------------------------------------------------- 1 | package decode 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "unsafe" 7 | 8 | "github.com/kevmo314/go-uvc/pkg/transfers" 9 | ) 10 | 11 | /* 12 | #cgo LDFLAGS: -lavcodec -lavutil 13 | #include 14 | #include 15 | #include 16 | */ 17 | import "C" 18 | 19 | const averror_eagain = -C.EAGAIN 20 | 21 | var ErrEAGAIN = fmt.Errorf("EAGAIN") 22 | 23 | type LibAVCodecDecoder struct { 24 | ctx *C.AVCodecContext 25 | pkt *C.AVPacket 26 | frame *C.AVFrame 27 | } 28 | 29 | func newDecoder(codecID uint32) (*LibAVCodecDecoder, error) { 30 | codec := C.avcodec_find_decoder(codecID) 31 | if codec == nil { 32 | return nil, fmt.Errorf("avcodec_find_decoder() failed") 33 | } 34 | 35 | ctx := C.avcodec_alloc_context3(codec) 36 | if ctx == nil { 37 | return nil, fmt.Errorf("avcodec_alloc_context3() failed") 38 | } 39 | 40 | if res := C.avcodec_open2(ctx, codec, nil); res < 0 { 41 | C.avcodec_free_context(&ctx) 42 | return nil, fmt.Errorf("avcodec_open2() failed") 43 | } 44 | 45 | pkt := C.av_packet_alloc() 46 | if pkt == nil { 47 | C.avcodec_free_context(&ctx) 48 | return nil, fmt.Errorf("av_packet_alloc() failed") 49 | } 50 | 51 | frame := C.av_frame_alloc() 52 | if frame == nil { 53 | C.av_packet_free(&pkt) 54 | C.avcodec_free_context(&ctx) 55 | return nil, fmt.Errorf("av_frame_alloc() failed") 56 | } 57 | 58 | return &LibAVCodecDecoder{ctx: ctx, pkt: pkt, frame: frame}, nil 59 | } 60 | 61 | func NewH264Decoder() (*LibAVCodecDecoder, error) { 62 | return newDecoder(C.AV_CODEC_ID_H264) 63 | } 64 | 65 | func NewVP8Decoder() (*LibAVCodecDecoder, error) { 66 | return newDecoder(C.AV_CODEC_ID_VP8) 67 | } 68 | 69 | func (d *LibAVCodecDecoder) Close() error { 70 | C.av_frame_free(&d.frame) 71 | C.av_packet_free(&d.pkt) 72 | C.avcodec_free_context(&d.ctx) 73 | return nil 74 | } 75 | 76 | func (d *LibAVCodecDecoder) Write(pkt []byte) (int, error) { 77 | d.pkt.data = (*C.uint8_t)(C.CBytes(pkt)) 78 | d.pkt.size = C.int(len(pkt)) 79 | 80 | if res := C.avcodec_send_packet(d.ctx, d.pkt); res < 0 { 81 | return 0, fmt.Errorf("avcodec_send_packet() failed: %d", res) 82 | } 83 | return len(pkt), nil 84 | } 85 | 86 | func (d *LibAVCodecDecoder) WriteUSBFrame(fr *transfers.Frame) error { 87 | for _, p := range fr.Payloads { 88 | d.pkt.data = (*C.uint8_t)(C.CBytes(p.Data)) 89 | d.pkt.size = C.int(len(p.Data)) 90 | 91 | if res := C.avcodec_send_packet(d.ctx, d.pkt); res < 0 { 92 | return fmt.Errorf("avcodec_send_packet() failed: %d", res) 93 | } 94 | } 95 | return nil 96 | } 97 | 98 | func (d *LibAVCodecDecoder) ReadFrame() (image.Image, error) { 99 | if res := C.avcodec_receive_frame(d.ctx, d.frame); res < 0 { 100 | if res == averror_eagain { 101 | return nil, ErrEAGAIN 102 | } 103 | return nil, fmt.Errorf("avcodec_receive_frame() failed: %d", res) 104 | } 105 | switch d.frame.format { 106 | case C.AV_PIX_FMT_YUV420P, C.AV_PIX_FMT_YUV422P, C.AV_PIX_FMT_YUV444P, C.AV_PIX_FMT_YUV410P, C.AV_PIX_FMT_YUV411P, C.AV_PIX_FMT_YUVJ420P, C.AV_PIX_FMT_YUVJ422P, C.AV_PIX_FMT_YUVJ444P: 107 | img := &image.YCbCr{ 108 | Y: unsafe.Slice((*uint8)(d.frame.data[0]), d.frame.height*d.frame.linesize[0]), 109 | Cb: unsafe.Slice((*uint8)(d.frame.data[1]), d.frame.height*d.frame.linesize[1]), 110 | Cr: unsafe.Slice((*uint8)(d.frame.data[2]), d.frame.height*d.frame.linesize[2]), 111 | Rect: image.Rect(0, 0, int(d.frame.width), int(d.frame.height)), 112 | YStride: int(d.frame.linesize[0]), 113 | CStride: int(d.frame.linesize[1]), 114 | } 115 | switch d.frame.format { 116 | case C.AV_PIX_FMT_YUV420P, C.AV_PIX_FMT_YUVJ420P: 117 | img.SubsampleRatio = image.YCbCrSubsampleRatio420 118 | case C.AV_PIX_FMT_YUV422P, C.AV_PIX_FMT_YUVJ422P: 119 | img.SubsampleRatio = image.YCbCrSubsampleRatio422 120 | case C.AV_PIX_FMT_YUV444P, C.AV_PIX_FMT_YUVJ444P: 121 | img.SubsampleRatio = image.YCbCrSubsampleRatio444 122 | case C.AV_PIX_FMT_YUV410P: 123 | img.SubsampleRatio = image.YCbCrSubsampleRatio410 124 | case C.AV_PIX_FMT_YUV411P: 125 | img.SubsampleRatio = image.YCbCrSubsampleRatio411 126 | } 127 | return img, nil 128 | case C.AV_PIX_FMT_RGB24: 129 | return &RGB{ 130 | Pix: unsafe.Slice((*uint8)(d.frame.data[0]), d.frame.height*d.frame.linesize[0]), 131 | Stride: int(d.frame.linesize[0]), 132 | Rect: image.Rect(0, 0, int(d.frame.width), int(d.frame.height)), 133 | }, nil 134 | case C.AV_PIX_FMT_BGR24: 135 | return &BGR{ 136 | Pix: unsafe.Slice((*uint8)(d.frame.data[0]), d.frame.height*d.frame.linesize[0]), 137 | Stride: int(d.frame.linesize[0]), 138 | Rect: image.Rect(0, 0, int(d.frame.width), int(d.frame.height)), 139 | }, nil 140 | case C.AV_PIX_FMT_GRAY8: 141 | return &image.Gray{ 142 | Pix: unsafe.Slice((*uint8)(d.frame.data[0]), d.frame.height*d.frame.linesize[0]), 143 | Stride: int(d.frame.linesize[0]), 144 | Rect: image.Rect(0, 0, int(d.frame.width), int(d.frame.height)), 145 | }, nil 146 | case C.AV_PIX_FMT_GRAY16BE: 147 | return &image.Gray16{ 148 | Pix: unsafe.Slice((*uint8)(d.frame.data[0]), d.frame.height*d.frame.linesize[0]), 149 | Stride: int(d.frame.linesize[0]), 150 | Rect: image.Rect(0, 0, int(d.frame.width), int(d.frame.height)), 151 | }, nil 152 | } 153 | return nil, fmt.Errorf("unsupported pixel format: %s", C.GoString(C.av_get_pix_fmt_name(int32(d.frame.format)))) 154 | } 155 | -------------------------------------------------------------------------------- /pkg/decode/mjpeg.go: -------------------------------------------------------------------------------- 1 | package decode 2 | 3 | import ( 4 | "bytes" 5 | "image" 6 | "image/jpeg" 7 | 8 | "github.com/kevmo314/go-uvc/pkg/transfers" 9 | ) 10 | 11 | type MJPEGDecoder struct { 12 | imagesBuf []image.Image 13 | } 14 | 15 | func NewMJPEGDecoder() (*MJPEGDecoder, error) { 16 | return &MJPEGDecoder{}, nil 17 | } 18 | 19 | func (d *MJPEGDecoder) Write(pkt []byte) (int, error) { 20 | img, err := jpeg.Decode(bytes.NewReader(pkt)) 21 | if err != nil { 22 | return 0, err 23 | } 24 | d.imagesBuf = append(d.imagesBuf, img) 25 | return len(pkt), nil 26 | } 27 | 28 | func (d *MJPEGDecoder) WriteUSBFrame(fr *transfers.Frame) error { 29 | img, err := jpeg.Decode(fr) 30 | if err != nil { 31 | return err 32 | } 33 | d.imagesBuf = append(d.imagesBuf, img) 34 | return nil 35 | } 36 | 37 | func (d *MJPEGDecoder) ReadFrame() (image.Image, error) { 38 | if len(d.imagesBuf) == 0 { 39 | return nil, ErrEAGAIN 40 | } 41 | img := d.imagesBuf[0] 42 | d.imagesBuf = d.imagesBuf[1:] 43 | return img, nil 44 | } 45 | 46 | func (d *MJPEGDecoder) Close() error { 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /pkg/decode/uncompressed.go: -------------------------------------------------------------------------------- 1 | package decode 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "io" 7 | 8 | "github.com/kevmo314/go-uvc/pkg/transfers" 9 | ) 10 | 11 | type UncompressedDecoder struct { 12 | images []image.Image 13 | fourcc [4]byte 14 | width, height int 15 | } 16 | 17 | func NewUncompressedDecoder(fourcc [4]byte, width, height int) (*UncompressedDecoder, error) { 18 | return &UncompressedDecoder{fourcc: fourcc, width: width, height: height}, nil 19 | } 20 | 21 | func (d *UncompressedDecoder) ReadFrame() (image.Image, error) { 22 | if len(d.images) == 0 { 23 | return nil, ErrEAGAIN 24 | } 25 | img := d.images[0] 26 | d.images = d.images[1:] 27 | return img, nil 28 | } 29 | 30 | func (d *UncompressedDecoder) Write(pkt []byte) (int, error) { 31 | switch d.fourcc { 32 | case [4]byte{'I', '4', '2', '0'}: 33 | img := image.NewYCbCr(image.Rect(0, 0, d.width, d.height), image.YCbCrSubsampleRatio420) 34 | img.Y = pkt[:d.width*d.height] 35 | img.Cb = pkt[d.width*d.height : d.width*d.height*2] 36 | img.Cr = pkt[d.width*d.height*2 : d.width*d.height*3] 37 | return len(pkt), nil 38 | case [4]byte{'Y', 'U', 'Y', '2'}: 39 | img := image.NewYCbCr(image.Rect(0, 0, d.width, d.height), image.YCbCrSubsampleRatio422) 40 | img.Y = pkt[:d.width*d.height] 41 | img.Cb = pkt[d.width*d.height : d.width*d.height*2] 42 | img.Cr = pkt[d.width*d.height*2 : d.width*d.height*3] 43 | return len(pkt), nil 44 | } 45 | return 0, fmt.Errorf("unknown FourCC for GUID %s", d.fourcc) 46 | } 47 | 48 | func (d *UncompressedDecoder) WriteUSBFrame(fr *transfers.Frame) error { 49 | buf, err := io.ReadAll(fr) 50 | if err != nil { 51 | return err 52 | } 53 | if _, err := d.Write(buf); err != nil { 54 | return err 55 | } 56 | return nil 57 | } 58 | 59 | func (d *UncompressedDecoder) Close() error { 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /pkg/descriptors/audio_streaming_interface_descriptors.go: -------------------------------------------------------------------------------- 1 | package descriptors 2 | 3 | import "io" 4 | 5 | type AudioStreamingInterfaceDescriptorSubtype byte 6 | 7 | const ( 8 | AudioStreamingInterfaceDescriptorSubtypeUndefined AudioStreamingInterfaceDescriptorSubtype = 0x00 9 | AudioStreamingInterfaceDescriptorSubtypeHeader AudioStreamingInterfaceDescriptorSubtype = 0x01 10 | AudioStreamingInterfaceDescriptorSubtypeInputTerminal AudioStreamingInterfaceDescriptorSubtype = 0x02 11 | AudioStreamingInterfaceDescriptorSubtypeOutputTerminal AudioStreamingInterfaceDescriptorSubtype = 0x03 12 | AudioStreamingInterfaceDescriptorSubtypeMixerUnit AudioStreamingInterfaceDescriptorSubtype = 0x04 13 | AudioStreamingInterfaceDescriptorSubtypeSelectorUnit AudioStreamingInterfaceDescriptorSubtype = 0x05 14 | AudioStreamingInterfaceDescriptorSubtypeFeatureUnit AudioStreamingInterfaceDescriptorSubtype = 0x06 15 | AudioStreamingInterfaceDescriptorSubtypeProcessingUnit AudioStreamingInterfaceDescriptorSubtype = 0x07 16 | AudioStreamingInterfaceDescriptorSubtypeExtensionUnit AudioStreamingInterfaceDescriptorSubtype = 0x08 17 | ) 18 | 19 | // StandardAudioStreamingInterfaceDescriptor as defined in UAC spec 1.0, section 4.5.1 20 | type StandardAudioStreamingInterfaceDescriptor struct { 21 | InterfaceNumber uint8 22 | AlternateSetting uint8 23 | NumEndpoints uint8 24 | InterfaceClass uint8 25 | InterfaceSubClass uint8 26 | InterfaceProtocol uint8 27 | DescriptionIndex uint8 28 | } 29 | 30 | func (sasid *StandardAudioStreamingInterfaceDescriptor) UnmarshalBinary(buf []byte) error { 31 | if len(buf) < int(buf[0]) { 32 | return io.ErrShortBuffer 33 | } 34 | // TODO: check the descriptor type, this is not the class specific one. 35 | // if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 36 | // return ErrInvalidDescriptor 37 | // } 38 | sasid.InterfaceNumber = buf[2] 39 | sasid.AlternateSetting = buf[3] 40 | sasid.NumEndpoints = buf[4] 41 | sasid.InterfaceClass = buf[5] 42 | sasid.InterfaceSubClass = buf[6] 43 | sasid.InterfaceProtocol = buf[7] 44 | sasid.DescriptionIndex = buf[8] 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /pkg/descriptors/consts.go: -------------------------------------------------------------------------------- 1 | package descriptors 2 | 3 | type ClassCode byte 4 | 5 | const ( 6 | ClassCodeVideo ClassCode = 0x0E 7 | ) 8 | 9 | type SubclassCode byte 10 | 11 | const ( 12 | SubclassCodeUndefined SubclassCode = 0x00 13 | SubclassCodeVideoControl SubclassCode = 0x01 14 | SubclassCodeVideoStreaming SubclassCode = 0x02 15 | SubclassCodeVideoInterfaceCollection SubclassCode = 0x03 16 | ) 17 | 18 | type ProtocolCode byte 19 | 20 | const ( 21 | ProtocolCodeUndefined ProtocolCode = 0x00 22 | ProtocolCode15 ProtocolCode = 0x01 23 | ) 24 | 25 | type ClassSpecificDescriptorType int 26 | 27 | const ( 28 | ClassSpecificDescriptorTypeUndefined ClassSpecificDescriptorType = 0x20 29 | ClassSpecificDescriptorTypeDevice ClassSpecificDescriptorType = 0x21 30 | ClassSpecificDescriptorTypeConfiguration ClassSpecificDescriptorType = 0x22 31 | ClassSpecificDescriptorTypeString ClassSpecificDescriptorType = 0x23 32 | ClassSpecificDescriptorTypeInterface ClassSpecificDescriptorType = 0x24 33 | ClassSpecificDescriptorTypeEndpoint ClassSpecificDescriptorType = 0x25 34 | ) 35 | -------------------------------------------------------------------------------- /pkg/descriptors/controls.go: -------------------------------------------------------------------------------- 1 | package descriptors 2 | 3 | import ( 4 | "encoding" 5 | "encoding/binary" 6 | "time" 7 | ) 8 | 9 | type CameraTerminalControlDescriptor interface { 10 | Value() CameraTerminalControlSelector 11 | FeatureBit() int //Indicates the position of the control on the controls bitmap 12 | encoding.BinaryMarshaler 13 | encoding.BinaryUnmarshaler 14 | } 15 | 16 | type VideoProbeCommitControl struct { 17 | HintBitmask uint16 18 | FormatIndex uint8 19 | FrameIndex uint8 20 | FrameInterval time.Duration 21 | KeyFrameRate uint16 22 | PFrameRate uint16 23 | CompQuality uint16 24 | CompWindowSize uint16 25 | Delay uint16 26 | MaxVideoFrameSize uint32 27 | MaxPayloadTransferSize uint32 28 | 29 | // added in uvc 1.1 30 | ClockFrequency uint32 31 | FramingInfoBitmask uint8 32 | PreferedVersion uint8 33 | MinVersion uint8 34 | MaxVersion uint8 35 | 36 | // added in uvc 1.5 37 | Usage uint8 38 | BitDepthLuma uint8 39 | SettingsBitmask uint8 40 | MaxNumberOfRefFramesPlus1 uint8 41 | RateControlModes uint16 42 | LayoutPerStream [4]uint16 43 | } 44 | 45 | func (vpcc *VideoProbeCommitControl) MarshalInto(buf []byte) error { 46 | binary.LittleEndian.PutUint16(buf[0:2], vpcc.HintBitmask) 47 | buf[2] = vpcc.FormatIndex 48 | buf[3] = vpcc.FrameIndex 49 | binary.LittleEndian.PutUint32(buf[4:8], uint32(vpcc.FrameInterval/100/time.Nanosecond)) 50 | binary.LittleEndian.PutUint16(buf[8:10], vpcc.KeyFrameRate) 51 | binary.LittleEndian.PutUint16(buf[10:12], vpcc.PFrameRate) 52 | binary.LittleEndian.PutUint16(buf[12:14], vpcc.CompQuality) 53 | binary.LittleEndian.PutUint16(buf[14:16], vpcc.CompWindowSize) 54 | binary.LittleEndian.PutUint16(buf[16:18], vpcc.Delay) 55 | binary.LittleEndian.PutUint32(buf[18:22], vpcc.MaxVideoFrameSize) 56 | binary.LittleEndian.PutUint32(buf[22:26], vpcc.MaxPayloadTransferSize) 57 | if len(buf) > 26 { 58 | binary.LittleEndian.PutUint32(buf[26:30], vpcc.ClockFrequency) 59 | buf[30] = vpcc.FramingInfoBitmask 60 | buf[31] = vpcc.PreferedVersion 61 | buf[32] = vpcc.MinVersion 62 | buf[33] = vpcc.MaxVersion 63 | } 64 | 65 | if len(buf) > 34 { 66 | buf[34] = vpcc.Usage 67 | buf[35] = vpcc.BitDepthLuma 68 | buf[36] = vpcc.SettingsBitmask 69 | buf[37] = vpcc.MaxNumberOfRefFramesPlus1 70 | binary.LittleEndian.PutUint16(buf[38:40], vpcc.RateControlModes) 71 | binary.LittleEndian.PutUint16(buf[40:42], vpcc.LayoutPerStream[0]) 72 | binary.LittleEndian.PutUint16(buf[42:44], vpcc.LayoutPerStream[1]) 73 | binary.LittleEndian.PutUint16(buf[44:46], vpcc.LayoutPerStream[2]) 74 | binary.LittleEndian.PutUint16(buf[46:48], vpcc.LayoutPerStream[3]) 75 | } 76 | return nil 77 | } 78 | 79 | func (vpcc *VideoProbeCommitControl) MarshalBinary() ([]byte, error) { 80 | buf := make([]byte, 48) 81 | return buf, vpcc.MarshalInto(buf) 82 | } 83 | 84 | func (vpcc *VideoProbeCommitControl) UnmarshalBinary(buf []byte) error { 85 | // this descriptor is not length and control-selector prefixed because 86 | // libusb unwraps the control transfers for us. 87 | vpcc.HintBitmask = binary.LittleEndian.Uint16(buf[0:2]) 88 | vpcc.FormatIndex = buf[2] 89 | vpcc.FrameIndex = buf[3] 90 | vpcc.FrameInterval = time.Duration(binary.LittleEndian.Uint32(buf[4:8])) * 100 * time.Nanosecond 91 | 92 | vpcc.KeyFrameRate = binary.LittleEndian.Uint16(buf[8:10]) 93 | vpcc.PFrameRate = binary.LittleEndian.Uint16(buf[10:12]) 94 | 95 | vpcc.CompQuality = binary.LittleEndian.Uint16(buf[12:14]) 96 | vpcc.CompWindowSize = binary.LittleEndian.Uint16(buf[14:16]) 97 | 98 | vpcc.Delay = binary.LittleEndian.Uint16(buf[16:18]) 99 | 100 | vpcc.MaxVideoFrameSize = binary.LittleEndian.Uint32(buf[18:22]) 101 | vpcc.MaxPayloadTransferSize = binary.LittleEndian.Uint32(buf[22:26]) 102 | 103 | if len(buf) > 26 { 104 | vpcc.ClockFrequency = binary.LittleEndian.Uint32(buf[26:30]) 105 | vpcc.FramingInfoBitmask = buf[30] 106 | vpcc.PreferedVersion = buf[31] 107 | vpcc.MinVersion = buf[32] 108 | vpcc.MaxVersion = buf[33] 109 | } 110 | 111 | if len(buf) > 34 { 112 | vpcc.Usage = buf[34] 113 | vpcc.BitDepthLuma = buf[35] 114 | vpcc.SettingsBitmask = buf[36] 115 | vpcc.MaxNumberOfRefFramesPlus1 = buf[37] 116 | vpcc.RateControlModes = binary.LittleEndian.Uint16(buf[38:40]) 117 | vpcc.LayoutPerStream[0] = binary.LittleEndian.Uint16(buf[40:42]) 118 | vpcc.LayoutPerStream[1] = binary.LittleEndian.Uint16(buf[42:44]) 119 | vpcc.LayoutPerStream[2] = binary.LittleEndian.Uint16(buf[44:46]) 120 | vpcc.LayoutPerStream[3] = binary.LittleEndian.Uint16(buf[46:48]) 121 | } 122 | return nil 123 | } 124 | 125 | // Control Request for Scanning Mode as defined in UVC spec 1.5, 4.2.2.1.1 126 | type ScanningModeControl struct { 127 | Mode ScanningMode 128 | } 129 | 130 | func (smc *ScanningModeControl) FeatureBit() int { 131 | return 0 132 | } 133 | 134 | func (smc *ScanningModeControl) Value() CameraTerminalControlSelector { 135 | return CameraTerminalControlSelectorScanningModeControl 136 | } 137 | 138 | func (smc *ScanningModeControl) MarshalBinary() ([]byte, error) { 139 | buf := make([]byte, 1) 140 | buf[0] = byte(smc.Mode) 141 | return buf, nil 142 | } 143 | 144 | func (smc *ScanningModeControl) UnmarshalBinary(buf []byte) error { 145 | smc.Mode = ScanningMode(buf[0]) 146 | return nil 147 | } 148 | 149 | // Control Request for Auto-Exposure Mode as defined in UVC spec 1.5, 4.2.2.1.2 150 | type AutoExposureModeControl struct { 151 | Mode AutoExposureMode 152 | } 153 | 154 | func (aemc *AutoExposureModeControl) FeatureBit() int { 155 | return 1 156 | } 157 | 158 | func (aemc *AutoExposureModeControl) Value() CameraTerminalControlSelector { 159 | return CameraTerminalControlSelectorAutoExposureModeControl 160 | } 161 | 162 | func (aemc *AutoExposureModeControl) MarshalBinary() ([]byte, error) { 163 | buf := make([]byte, 1) 164 | buf[0] = byte(aemc.Mode) 165 | return buf, nil 166 | } 167 | 168 | func (aemc *AutoExposureModeControl) UnmarshalBinary(buf []byte) error { 169 | aemc.Mode = AutoExposureMode(buf[0]) 170 | return nil 171 | } 172 | 173 | // Control Request for Auto-Exposure Priority as defined in UVC spec 1.5, 4.2.2.1.3 174 | type AutoExposurePriorityControl struct { 175 | Priority AutoExposurePriority 176 | } 177 | 178 | func (aepc *AutoExposurePriorityControl) FeatureBit() int { 179 | return 2 180 | } 181 | 182 | func (aepc *AutoExposurePriorityControl) Value() CameraTerminalControlSelector { 183 | return CameraTerminalControlSelectorAutoExposurePriorityControl 184 | } 185 | 186 | func (aepc *AutoExposurePriorityControl) MarshalBinary() ([]byte, error) { 187 | buf := make([]byte, 1) 188 | buf[0] = byte(aepc.Priority) 189 | return buf, nil 190 | } 191 | 192 | func (aepc *AutoExposurePriorityControl) UnmarshalBinary(buf []byte) error { 193 | aepc.Priority = AutoExposurePriority(buf[0]) 194 | return nil 195 | } 196 | 197 | // Control Request for Exposure Time (Absolute) as defined in UVC spec 1.5, 4.2.2.1.4 198 | type ExposureTimeAbsoluteControl struct { 199 | Time uint32 200 | } 201 | 202 | func (etac *ExposureTimeAbsoluteControl) FeatureBit() int { 203 | return 3 204 | } 205 | 206 | func (etac *ExposureTimeAbsoluteControl) Value() CameraTerminalControlSelector { 207 | return CameraTerminalControlSelectorExposureTimeAbsoluteControl 208 | } 209 | 210 | func (etac *ExposureTimeAbsoluteControl) MarshalBinary() ([]byte, error) { 211 | buf := make([]byte, 4) 212 | binary.LittleEndian.PutUint32(buf, etac.Time) 213 | return buf, nil 214 | } 215 | 216 | func (etrc *ExposureTimeAbsoluteControl) UnmarshalBinary(buf []byte) error { 217 | etrc.Time = binary.LittleEndian.Uint32(buf) 218 | return nil 219 | } 220 | 221 | // Control Request for Exposure Time (Relative) as defined in UVC spec 1.5, 4.2.2.1.5 222 | type ExposureTimeRelativeControl struct { 223 | Time ExposureTimeRelative 224 | } 225 | 226 | func (etrc *ExposureTimeRelativeControl) FeatureBit() int { 227 | return 4 228 | } 229 | 230 | func (etrc *ExposureTimeRelativeControl) Value() CameraTerminalControlSelector { 231 | return CameraTerminalControlSelectorExposureTimeRelativeControl 232 | } 233 | 234 | func (etrc *ExposureTimeRelativeControl) MarshalBinary() ([]byte, error) { 235 | buf := make([]byte, 1) 236 | buf[0] = byte(etrc.Time) 237 | return buf, nil 238 | } 239 | 240 | func (etrc *ExposureTimeRelativeControl) UnmarshalBinary(buf []byte) error { 241 | etrc.Time = ExposureTimeRelative(buf[0]) 242 | return nil 243 | } 244 | 245 | // Control Request for Focus Absolute as defined in UVC spec 1.5, 4.2.2.1.6 246 | type FocusAbsoluteControl struct { 247 | Focus uint16 248 | } 249 | 250 | func (fac *FocusAbsoluteControl) FeatureBit() int { 251 | return 5 252 | } 253 | 254 | func (fac *FocusAbsoluteControl) Value() CameraTerminalControlSelector { 255 | return CameraTerminalControlSelectorFocusAbsoluteControl 256 | } 257 | 258 | func (fac *FocusAbsoluteControl) MarshalBinary() ([]byte, error) { 259 | buf := make([]byte, 2) 260 | binary.LittleEndian.PutUint16(buf, fac.Focus) 261 | return buf, nil 262 | } 263 | 264 | func (fac *FocusAbsoluteControl) UnmarshalBinary(buf []byte) error { 265 | fac.Focus = binary.LittleEndian.Uint16(buf) 266 | return nil 267 | } 268 | 269 | // Control Request for Focus Relative as defined in UVC spec 1.5, 4.2.2.1.7 270 | type FocusRelativeControl struct { 271 | Focus FocusRelative 272 | Speed uint8 273 | } 274 | 275 | func (frc *FocusRelativeControl) FeatureBit() int { 276 | return 6 277 | } 278 | 279 | func (frc *FocusRelativeControl) Value() CameraTerminalControlSelector { 280 | return CameraTerminalControlSelectorFocusRelativeControl 281 | } 282 | 283 | func (frc *FocusRelativeControl) MarshalBinary() ([]byte, error) { 284 | buf := make([]byte, 2) 285 | buf[0] = uint8(frc.Focus) 286 | buf[1] = frc.Speed 287 | return buf, nil 288 | } 289 | 290 | func (frc *FocusRelativeControl) UnmarshalBinary(buf []byte) error { 291 | frc.Focus = FocusRelative(buf[0]) 292 | frc.Speed = buf[1] 293 | return nil 294 | } 295 | 296 | // Control Request for Focus Simple Range as defined in UVC spec 1.5, 4.2.2.1.8 297 | type FocusSimpleRangeControl struct { 298 | Focus FocusSimple 299 | } 300 | 301 | func (fsrc *FocusSimpleRangeControl) FeatureBit() int { 302 | return 19 303 | } 304 | 305 | func (fsrc *FocusSimpleRangeControl) Value() CameraTerminalControlSelector { 306 | return CameraTerminalControlSelectorFocusSimpleControl 307 | } 308 | 309 | func (fsrc *FocusSimpleRangeControl) MarshalBinary() ([]byte, error) { 310 | buf := make([]byte, 1) 311 | buf[0] = uint8(fsrc.Focus) 312 | return buf, nil 313 | } 314 | 315 | func (fsrc *FocusSimpleRangeControl) UnmarshalBinary(buf []byte) error { 316 | fsrc.Focus = FocusSimple(buf[0]) 317 | return nil 318 | } 319 | 320 | // Control Request for Focus, Auto Control as defined in UVC spec 1.5, 4.2.2.1.9 321 | type FocusAutoControl struct { 322 | FocusAuto bool 323 | } 324 | 325 | func (fac *FocusAutoControl) FeatureBit() int { 326 | return 17 327 | } 328 | 329 | func (fac *FocusAutoControl) Value() CameraTerminalControlSelector { 330 | return CameraTerminalControlSelectorFocusAutoControl 331 | } 332 | 333 | func (fac *FocusAutoControl) MarshalBinary() ([]byte, error) { 334 | buf := make([]byte, 1) 335 | byteValue := byte(0) 336 | if fac.FocusAuto { 337 | byteValue = byte(1) 338 | } 339 | buf[0] = byteValue 340 | return buf, nil 341 | } 342 | 343 | func (fac *FocusAutoControl) UnmarshalBinary(buf []byte) error { 344 | fac.FocusAuto = buf[0] == 1 345 | return nil 346 | } 347 | 348 | // Control Request for Iris Absolute as defined in UVC spec 1.5, 4.2.2.1.10 349 | type IrisAbsoluteControl struct { 350 | Aperture uint16 351 | } 352 | 353 | func (iac *IrisAbsoluteControl) FeatureBit() int { 354 | return 7 355 | } 356 | 357 | func (iac *IrisAbsoluteControl) Value() CameraTerminalControlSelector { 358 | return CameraTerminalControlSelectorIrisAbsoluteControl 359 | } 360 | 361 | func (iac *IrisAbsoluteControl) MarshalBinary() ([]byte, error) { 362 | buf := make([]byte, 2) 363 | binary.LittleEndian.PutUint16(buf, iac.Aperture) 364 | return buf, nil 365 | } 366 | 367 | func (iac *IrisAbsoluteControl) UnmarshalBinary(buf []byte) error { 368 | iac.Aperture = binary.LittleEndian.Uint16(buf) 369 | return nil 370 | } 371 | 372 | // Control Request for Iris Relative as defined in UVC spec 1.5, 4.2.2.1.11 373 | type IrisRelativeControl struct { 374 | Aperture IrisRelative 375 | } 376 | 377 | func (irc *IrisRelativeControl) FeatureBit() int { 378 | return 8 379 | } 380 | 381 | func (irc *IrisRelativeControl) Value() CameraTerminalControlSelector { 382 | return CameraTerminalControlSelectorIrisRelativeControl 383 | } 384 | 385 | func (irc *IrisRelativeControl) MarshalBinary() ([]byte, error) { 386 | buf := make([]byte, 1) 387 | buf[0] = byte(irc.Aperture) 388 | return buf, nil 389 | } 390 | 391 | func (irc *IrisRelativeControl) UnmarshalBinary(buf []byte) error { 392 | irc.Aperture = IrisRelative(buf[0]) 393 | return nil 394 | } 395 | 396 | // Control Request for Zoom Absolute as defined in UVC spec 1.5, 4.2.2.1.12 397 | type ZoomAbsoluteControl struct { 398 | ObjectiveFocalLength uint16 399 | } 400 | 401 | func (zac *ZoomAbsoluteControl) FeatureBit() int { 402 | return 9 403 | } 404 | 405 | func (zac *ZoomAbsoluteControl) Value() CameraTerminalControlSelector { 406 | return CameraTerminalControlSelectorZoomAbsoluteControl 407 | } 408 | 409 | func (zac *ZoomAbsoluteControl) MarshalBinary() ([]byte, error) { 410 | buf := make([]byte, 2) 411 | binary.LittleEndian.PutUint16(buf, uint16(zac.ObjectiveFocalLength)) 412 | return buf, nil 413 | } 414 | 415 | func (zac *ZoomAbsoluteControl) UnmarshalBinary(buf []byte) error { 416 | zac.ObjectiveFocalLength = binary.LittleEndian.Uint16(buf) 417 | return nil 418 | } 419 | 420 | // Control Request for Zoom Relative as defined in UVC spec 1.5, 4.2.2.1.13 421 | type ZoomRelativeControl struct { 422 | Zoom ZoomRelative 423 | DigitalZoom bool 424 | Speed uint8 425 | } 426 | 427 | func (zrc *ZoomRelativeControl) FeatureBit() int { 428 | return 10 429 | } 430 | 431 | func (zrc *ZoomRelativeControl) Value() CameraTerminalControlSelector { 432 | return CameraTerminalControlSelectorZoomRelativeControl 433 | } 434 | 435 | func (zrc *ZoomRelativeControl) MarshalBinary() ([]byte, error) { 436 | buf := make([]byte, 3) 437 | buf[0] = byte(zrc.Zoom) 438 | 439 | byteValue := byte(0) 440 | if zrc.DigitalZoom { 441 | byteValue = byte(1) 442 | } 443 | buf[1] = byteValue 444 | 445 | buf[2] = zrc.Speed 446 | return buf, nil 447 | } 448 | 449 | func (zrc *ZoomRelativeControl) UnmarshalBinary(buf []byte) error { 450 | zrc.Zoom = ZoomRelative(buf[0]) 451 | zrc.DigitalZoom = buf[1] == 1 452 | zrc.Speed = buf[2] 453 | return nil 454 | } 455 | 456 | // Control Request for Pan Tilt Absolute as defined in UVC spec 1.5, 4.2.2.1.14 457 | type PanTiltAbsoluteControl struct { 458 | PanAbsolute int32 459 | TiltAbsolute int32 460 | } 461 | 462 | func (ptac *PanTiltAbsoluteControl) FeatureBit() int { 463 | return 11 464 | } 465 | 466 | func (ptac *PanTiltAbsoluteControl) Value() CameraTerminalControlSelector { 467 | return CameraTerminalControlSelectorPanTiltAbsoluteControl 468 | } 469 | 470 | func (ptac *PanTiltAbsoluteControl) MarshalBinary() ([]byte, error) { 471 | buf := make([]byte, 8) 472 | binary.LittleEndian.PutUint32(buf[0:4], uint32(ptac.PanAbsolute)) 473 | binary.LittleEndian.PutUint32(buf[4:8], uint32(ptac.TiltAbsolute)) 474 | return buf, nil 475 | } 476 | 477 | func (ptac *PanTiltAbsoluteControl) UnmarshalBinary(buf []byte) error { 478 | ptac.PanAbsolute = int32(binary.LittleEndian.Uint32(buf[0:4])) 479 | ptac.TiltAbsolute = int32(binary.LittleEndian.Uint32(buf[4:8])) 480 | return nil 481 | } 482 | 483 | // Control Request for Pan Tilt Relative as defined in UVC spec 1.5, 4.2.2.1.15 484 | type PanTiltRelativeControl struct { 485 | PanRelative PanRelative 486 | PanSpeed uint8 487 | TiltRelative TiltRelative 488 | TiltSpeed uint8 489 | } 490 | 491 | func (ptrc *PanTiltRelativeControl) FeatureBit() int { 492 | return 12 493 | } 494 | 495 | func (ptrc *PanTiltRelativeControl) Value() CameraTerminalControlSelector { 496 | return CameraTerminalControlSelectorPanTiltRelativeControl 497 | } 498 | 499 | func (ptrc *PanTiltRelativeControl) MarshalBinary() ([]byte, error) { 500 | buf := make([]byte, 8) 501 | buf[0] = byte(ptrc.PanRelative) 502 | buf[1] = byte(ptrc.PanSpeed) 503 | buf[2] = byte(ptrc.TiltRelative) 504 | buf[3] = byte(ptrc.TiltSpeed) 505 | return buf, nil 506 | } 507 | 508 | func (ptrc *PanTiltRelativeControl) UnmarshalBinary(buf []byte) error { 509 | ptrc.PanRelative = PanRelative(buf[0]) 510 | ptrc.PanSpeed = uint8(buf[1]) 511 | ptrc.TiltRelative = TiltRelative(buf[2]) 512 | ptrc.TiltSpeed = uint8(buf[3]) 513 | return nil 514 | } 515 | 516 | // Control Request for Roll Absolute as defined in UVC spec 1.5, 4.2.2.1.16 517 | type RollAbsoluteControl struct { 518 | RollAbsolute int16 519 | } 520 | 521 | func (rac *RollAbsoluteControl) FeatureBit() int { 522 | return 13 523 | } 524 | 525 | func (rac *RollAbsoluteControl) Value() CameraTerminalControlSelector { 526 | return CameraTerminalControlSelectorRollAbsoluteControl 527 | } 528 | 529 | func (rac *RollAbsoluteControl) MarshalBinary() ([]byte, error) { 530 | buf := make([]byte, 2) 531 | binary.LittleEndian.PutUint16(buf, uint16(rac.RollAbsolute)) 532 | return buf, nil 533 | } 534 | 535 | func (rac *RollAbsoluteControl) UnmarshalBinary(buf []byte) error { 536 | rac.RollAbsolute = int16(binary.LittleEndian.Uint32(buf)) 537 | return nil 538 | } 539 | 540 | // Control Request for Roll Relative as defined in UVC spec 1.5, 4.2.2.1.17 541 | type RollRelativeControl struct { 542 | RollRelative RollRelative 543 | Speed uint8 544 | } 545 | 546 | func (rrc *RollRelativeControl) FeatureBit() int { 547 | return 14 548 | } 549 | 550 | func (rrc *RollRelativeControl) Value() CameraTerminalControlSelector { 551 | return CameraTerminalControlSelectorRollRelativeControl 552 | } 553 | 554 | func (rrc *RollRelativeControl) MarshalBinary() ([]byte, error) { 555 | buf := make([]byte, 2) 556 | buf[0] = byte(rrc.RollRelative) 557 | buf[1] = rrc.Speed 558 | return buf, nil 559 | } 560 | 561 | func (rrc *RollRelativeControl) UnmarshalBinary(buf []byte) error { 562 | rrc.RollRelative = RollRelative(buf[0]) 563 | rrc.Speed = buf[1] 564 | return nil 565 | } 566 | 567 | // Control Request for Privacy Control as defined in UVC spec 1.5, 4.2.2.1.18 568 | type PrivacyControl struct { 569 | Privacy bool 570 | } 571 | 572 | func (pc *PrivacyControl) FeatureBit() int { 573 | return 18 574 | } 575 | 576 | func (pc *PrivacyControl) Value() CameraTerminalControlSelector { 577 | return CameraTerminalControlSelectorPrivacyControl 578 | } 579 | 580 | func (pc *PrivacyControl) MarshalBinary() ([]byte, error) { 581 | buf := make([]byte, 1) 582 | 583 | byteValue := byte(0) 584 | if pc.Privacy { 585 | byteValue = byte(1) 586 | } 587 | buf[0] = byteValue 588 | 589 | return buf, nil 590 | } 591 | 592 | func (pc *PrivacyControl) UnmarshalBinary(buf []byte) error { 593 | pc.Privacy = buf[0] == 1 594 | return nil 595 | } 596 | 597 | // Control Request for Digital Window as defined in UVC spec 1.5, 4.2.2.1.19 598 | type DigitalWindowControl struct { 599 | //TODO where should we validate bottom >= top and right >= left ? 600 | Top int16 // Pixels 601 | Left int16 // Pixels 602 | Bottom int16 // Pixels 603 | Right int16 // Pixels 604 | 605 | Steps int16 606 | StepsUnits StepUnits 607 | } 608 | 609 | func (dwc *DigitalWindowControl) FeatureBit() int { 610 | return 20 611 | } 612 | 613 | func (dwc *DigitalWindowControl) Value() CameraTerminalControlSelector { 614 | return CameraTerminalControlSelectorWindowControl 615 | } 616 | 617 | func (dwc *DigitalWindowControl) MarshalBinary() ([]byte, error) { 618 | buf := make([]byte, 12) 619 | 620 | binary.LittleEndian.PutUint16(buf[0:2], uint16(dwc.Top)) 621 | binary.LittleEndian.PutUint16(buf[2:4], uint16(dwc.Left)) 622 | binary.LittleEndian.PutUint16(buf[4:6], uint16(dwc.Bottom)) 623 | binary.LittleEndian.PutUint16(buf[6:8], uint16(dwc.Right)) 624 | 625 | binary.LittleEndian.PutUint16(buf[8:10], uint16(dwc.Steps)) 626 | binary.LittleEndian.PutUint16(buf[10:12], uint16(dwc.StepsUnits)) 627 | 628 | return buf, nil 629 | } 630 | 631 | func (dwc *DigitalWindowControl) UnmarshalBinary(buf []byte) error { 632 | dwc.Top = int16(binary.LittleEndian.Uint16(buf[0:2])) 633 | dwc.Left = int16(binary.LittleEndian.Uint16(buf[2:4])) 634 | dwc.Bottom = int16(binary.LittleEndian.Uint16(buf[4:6])) 635 | dwc.Right = int16(binary.LittleEndian.Uint16(buf[6:8])) 636 | dwc.Steps = int16(binary.LittleEndian.Uint16(buf[8:10])) 637 | dwc.StepsUnits = StepUnits(binary.LittleEndian.Uint16(buf[10:12])) 638 | return nil 639 | } 640 | 641 | // Control Request for Digital Region of Interest as defined in UVC spec 1.5, 4.2.2.1.20 642 | type RegionOfInterestControl struct { 643 | Top int16 644 | Left int16 645 | Bottom int16 646 | Right int16 647 | AutoControls RegionOfInterestAutoControl 648 | } 649 | 650 | func (roic *RegionOfInterestControl) FeatureBit() int { 651 | return 21 652 | } 653 | 654 | func (roic *RegionOfInterestControl) Value() CameraTerminalControlSelector { 655 | return CameraTerminalControlSelectorRegionOfInterestControl 656 | } 657 | 658 | func (roic *RegionOfInterestControl) MarshalBinary() ([]byte, error) { 659 | buf := make([]byte, 10) 660 | 661 | binary.LittleEndian.PutUint16(buf[0:2], uint16(roic.Top)) 662 | binary.LittleEndian.PutUint16(buf[2:4], uint16(roic.Left)) 663 | binary.LittleEndian.PutUint16(buf[4:6], uint16(roic.Bottom)) 664 | binary.LittleEndian.PutUint16(buf[6:8], uint16(roic.Right)) 665 | 666 | binary.LittleEndian.PutUint16(buf[8:10], uint16(roic.AutoControls)) 667 | 668 | return buf, nil 669 | } 670 | 671 | func (roic *RegionOfInterestControl) UnmarshalBinary(buf []byte) error { 672 | roic.Top = int16(binary.LittleEndian.Uint16(buf[0:2])) 673 | roic.Left = int16(binary.LittleEndian.Uint16(buf[2:4])) 674 | roic.Bottom = int16(binary.LittleEndian.Uint16(buf[4:6])) 675 | roic.Right = int16(binary.LittleEndian.Uint16(buf[6:8])) 676 | roic.AutoControls = RegionOfInterestAutoControl(binary.LittleEndian.Uint16(buf[8:10])) 677 | return nil 678 | } 679 | -------------------------------------------------------------------------------- /pkg/descriptors/descriptors.go: -------------------------------------------------------------------------------- 1 | package descriptors 2 | 3 | func copyGUID(dst, src []byte) { 4 | // copy according to the GUID format defined in UVC spec 1.5, section 2.9. 5 | dst[0] = src[3] 6 | dst[1] = src[2] 7 | dst[2] = src[1] 8 | dst[3] = src[0] 9 | dst[4] = src[5] 10 | dst[5] = src[4] 11 | dst[6] = src[7] 12 | dst[7] = src[6] 13 | dst[8] = src[8] 14 | dst[9] = src[9] 15 | dst[10] = src[10] 16 | dst[11] = src[11] 17 | dst[12] = src[12] 18 | dst[13] = src[13] 19 | dst[14] = src[14] 20 | dst[15] = src[15] 21 | } 22 | -------------------------------------------------------------------------------- /pkg/descriptors/errors.go: -------------------------------------------------------------------------------- 1 | package descriptors 2 | 3 | import "errors" 4 | 5 | var ( 6 | ErrInvalidDescriptor = errors.New("invalid descriptor") 7 | ) 8 | -------------------------------------------------------------------------------- /pkg/descriptors/frame.go: -------------------------------------------------------------------------------- 1 | package descriptors 2 | 3 | type FormatDescriptor interface { 4 | isStreamingInterface() 5 | isFormatDescriptor() 6 | // Index returns the index of the format descriptor in the format descriptor array. 7 | // This is identical to retrieving FormatIndex from the descriptor but is provided 8 | // for convenience. 9 | Index() uint8 10 | } 11 | 12 | type FrameDescriptor interface { 13 | isStreamingInterface() 14 | isFrameDescriptor() 15 | // Index returns the index of the frame descriptor in the frame descriptor array. 16 | // This is identical to retrieving FrameIndex from the descriptor but is provided 17 | // for convenience. 18 | Index() uint8 19 | } 20 | -------------------------------------------------------------------------------- /pkg/descriptors/frame_dv.go: -------------------------------------------------------------------------------- 1 | package descriptors 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | ) 7 | 8 | type DVStreamHeader struct { 9 | BitFieldHeader uint8 10 | PTS uint32 11 | SCR uint64 12 | } 13 | 14 | func (dvsh *DVStreamHeader) UnmarshalBinary(buf []byte) error { 15 | if len(buf) < int(buf[0]) { 16 | return io.ErrShortBuffer 17 | } 18 | dvsh.BitFieldHeader = buf[1] 19 | offset := 2 20 | if dvsh.HasPTS() { 21 | dvsh.PTS = binary.LittleEndian.Uint32(buf[offset : offset+4]) 22 | offset += 4 23 | } 24 | if dvsh.HasSCR() { 25 | dvsh.SCR = binary.LittleEndian.Uint64(buf[offset : offset+8]) 26 | offset += 8 27 | } 28 | return nil 29 | } 30 | 31 | func (dvsh *DVStreamHeader) FrameIdentifier() bool { 32 | return dvsh.BitFieldHeader&0b00000001 != 0 33 | } 34 | 35 | func (dvsh *DVStreamHeader) EndOfFrame() bool { 36 | return dvsh.BitFieldHeader&0b00000010 != 0 37 | } 38 | 39 | func (dvsh *DVStreamHeader) HasPTS() bool { 40 | return dvsh.BitFieldHeader&0b00000100 != 0 41 | } 42 | 43 | func (dvsh *DVStreamHeader) HasSCR() bool { 44 | return dvsh.BitFieldHeader&0b00001000 != 0 45 | } 46 | 47 | func (dvsh *DVStreamHeader) StillImage() bool { 48 | return dvsh.BitFieldHeader&0b00100000 != 0 49 | } 50 | 51 | func (dvsh *DVStreamHeader) Error() bool { 52 | return dvsh.BitFieldHeader&0b01000000 != 0 53 | } 54 | 55 | func (dvsh *DVStreamHeader) EndOfHeader() bool { 56 | return dvsh.BitFieldHeader&0b10000000 != 0 57 | } 58 | 59 | type DVFormatDescriptor struct { 60 | FormatIndex uint8 61 | MaxVideoFrameBufferSize uint32 62 | FormatType uint8 63 | } 64 | 65 | func (dvfd *DVFormatDescriptor) UnmarshalBinary(buf []byte) error { 66 | if len(buf) < int(buf[0]) { 67 | return io.ErrShortBuffer 68 | } 69 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 70 | return ErrInvalidDescriptor 71 | } 72 | if VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeFormatDV { 73 | return ErrInvalidDescriptor 74 | } 75 | dvfd.FormatIndex = buf[3] 76 | dvfd.MaxVideoFrameBufferSize = binary.LittleEndian.Uint32(buf[4:8]) 77 | dvfd.FormatType = buf[8] 78 | return nil 79 | } 80 | 81 | func (dvfd *DVFormatDescriptor) isStreamingInterface() {} 82 | 83 | func (dvfd *DVFormatDescriptor) isFormatDescriptor() {} 84 | 85 | func (dvfd *DVFormatDescriptor) Index() uint8 { 86 | return dvfd.FormatIndex 87 | } 88 | -------------------------------------------------------------------------------- /pkg/descriptors/frame_frame_based.go: -------------------------------------------------------------------------------- 1 | package descriptors 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | "strings" 8 | "time" 9 | 10 | "github.com/google/uuid" 11 | ) 12 | 13 | type FrameBasedStreamHeader struct { 14 | BitFieldHeader uint8 15 | PTS uint32 16 | SCR uint64 17 | } 18 | 19 | func (fbsh *FrameBasedStreamHeader) UnmarshalBinary(buf []byte) error { 20 | if len(buf) < int(buf[0]) { 21 | return io.ErrShortBuffer 22 | } 23 | fbsh.BitFieldHeader = buf[1] 24 | offset := 2 25 | if fbsh.HasPTS() { 26 | fbsh.PTS = binary.LittleEndian.Uint32(buf[offset : offset+4]) 27 | offset += 4 28 | } 29 | if fbsh.HasSCR() { 30 | fbsh.SCR = binary.LittleEndian.Uint64(buf[offset : offset+8]) 31 | offset += 8 32 | } 33 | return nil 34 | } 35 | 36 | func (fbsh *FrameBasedStreamHeader) FrameIdentifier() bool { 37 | return fbsh.BitFieldHeader&0b00000001 != 0 38 | } 39 | 40 | func (fbsh *FrameBasedStreamHeader) EndOfFrame() bool { 41 | return fbsh.BitFieldHeader&0b00000010 != 0 42 | } 43 | 44 | func (fbsh *FrameBasedStreamHeader) HasPTS() bool { 45 | return fbsh.BitFieldHeader&0b00000100 != 0 46 | } 47 | 48 | func (fbsh *FrameBasedStreamHeader) HasSCR() bool { 49 | return fbsh.BitFieldHeader&0b00001000 != 0 50 | } 51 | 52 | func (fbsh *FrameBasedStreamHeader) StillImage() bool { 53 | return fbsh.BitFieldHeader&0b00100000 != 0 54 | } 55 | 56 | func (fbsh *FrameBasedStreamHeader) Error() bool { 57 | return fbsh.BitFieldHeader&0b01000000 != 0 58 | } 59 | 60 | func (fbsh *FrameBasedStreamHeader) EndOfHeader() bool { 61 | return fbsh.BitFieldHeader&0b10000000 != 0 62 | } 63 | 64 | type FrameBasedFormatDescriptor struct { 65 | FormatIndex uint8 66 | NumFrameDescriptors uint8 67 | GUIDFormat uuid.UUID 68 | BitsPerPixel uint8 69 | DefaultFrameIndex uint8 70 | AspectRatioX uint8 71 | AspectRatioY uint8 72 | InterlaceFlags uint8 73 | CopyProtect uint8 74 | VariableSize bool 75 | } 76 | 77 | func (fbfd *FrameBasedFormatDescriptor) UnmarshalBinary(buf []byte) error { 78 | if len(buf) < int(buf[0]) { 79 | return io.ErrShortBuffer 80 | } 81 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 82 | return ErrInvalidDescriptor 83 | } 84 | if VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeFormatFrameBased { 85 | return ErrInvalidDescriptor 86 | } 87 | fbfd.FormatIndex = buf[3] 88 | fbfd.NumFrameDescriptors = buf[4] 89 | copyGUID(fbfd.GUIDFormat[:], buf[5:21]) 90 | fbfd.BitsPerPixel = buf[21] 91 | fbfd.DefaultFrameIndex = buf[22] 92 | fbfd.AspectRatioX = buf[23] 93 | fbfd.AspectRatioY = buf[24] 94 | fbfd.InterlaceFlags = buf[25] 95 | fbfd.CopyProtect = buf[26] 96 | fbfd.VariableSize = buf[27] != 0 97 | return nil 98 | } 99 | 100 | func (fbfd *FrameBasedFormatDescriptor) FourCC() ([4]byte, error) { 101 | if strings.HasSuffix(fbfd.GUIDFormat.String(), "-0000-0010-8000-00aa00389b71") { 102 | buf := [4]byte{} 103 | binary.LittleEndian.PutUint32(buf[:], fbfd.GUIDFormat.ID()) 104 | return buf, nil 105 | } 106 | return [4]byte{}, fmt.Errorf("unknown FourCC for GUID %s", fbfd.GUIDFormat) 107 | } 108 | 109 | func (fbfd *FrameBasedFormatDescriptor) isStreamingInterface() {} 110 | 111 | func (fbfd *FrameBasedFormatDescriptor) isFormatDescriptor() {} 112 | 113 | func (fbfd *FrameBasedFormatDescriptor) Index() uint8 { 114 | return fbfd.FormatIndex 115 | } 116 | 117 | type FrameBasedFrameDescriptor struct { 118 | FrameIndex uint8 119 | Capabilities uint8 120 | Width, Height uint16 121 | MinBitRate, MaxBitRate uint32 122 | DefaultFrameInterval time.Duration 123 | 124 | BytesPerLine uint32 125 | 126 | ContinuousFrameInterval struct { 127 | MinFrameInterval, MaxFrameInterval, FrameIntervalStep time.Duration 128 | } 129 | DiscreteFrameIntervals []time.Duration 130 | } 131 | 132 | func (fbfd *FrameBasedFrameDescriptor) UnmarshalBinary(buf []byte) error { 133 | if len(buf) < int(buf[0]) { 134 | return io.ErrShortBuffer 135 | } 136 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 137 | return ErrInvalidDescriptor 138 | } 139 | if VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeFrameFrameBased { 140 | return ErrInvalidDescriptor 141 | } 142 | fbfd.FrameIndex = buf[3] 143 | fbfd.Capabilities = buf[4] 144 | fbfd.Width = binary.LittleEndian.Uint16(buf[5:7]) 145 | fbfd.Height = binary.LittleEndian.Uint16(buf[7:9]) 146 | fbfd.MinBitRate = binary.LittleEndian.Uint32(buf[9:13]) 147 | fbfd.MaxBitRate = binary.LittleEndian.Uint32(buf[13:17]) 148 | fbfd.DefaultFrameInterval = time.Duration(binary.LittleEndian.Uint32(buf[17:21])) * 100 * time.Nanosecond 149 | 150 | n := buf[21] 151 | 152 | fbfd.BytesPerLine = binary.LittleEndian.Uint32(buf[22:26]) 153 | 154 | if n == 0 { 155 | // Continuous frame intervals 156 | fbfd.ContinuousFrameInterval.MinFrameInterval = time.Duration(binary.LittleEndian.Uint32(buf[26:30])) * 100 * time.Nanosecond 157 | fbfd.ContinuousFrameInterval.MaxFrameInterval = time.Duration(binary.LittleEndian.Uint32(buf[30:34])) * 100 * time.Nanosecond 158 | fbfd.ContinuousFrameInterval.FrameIntervalStep = time.Duration(binary.LittleEndian.Uint32(buf[34:38])) * 100 * time.Nanosecond 159 | return nil 160 | } else { 161 | fbfd.DiscreteFrameIntervals = make([]time.Duration, n) 162 | for i := uint8(0); i < n; i++ { 163 | fbfd.DiscreteFrameIntervals[i] = time.Duration(binary.LittleEndian.Uint32(buf[26+i*4:30+i*4])) * 100 * time.Nanosecond 164 | } 165 | return nil 166 | } 167 | } 168 | 169 | func (fbfd *FrameBasedFrameDescriptor) isStreamingInterface() {} 170 | 171 | func (fbfd *FrameBasedFrameDescriptor) isFrameDescriptor() {} 172 | 173 | func (fbfd *FrameBasedFrameDescriptor) Index() uint8 { 174 | return fbfd.FrameIndex 175 | } 176 | -------------------------------------------------------------------------------- /pkg/descriptors/frame_h264.go: -------------------------------------------------------------------------------- 1 | package descriptors 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "time" 7 | ) 8 | 9 | type H264StreamHeader struct { 10 | BitFieldHeader uint8 11 | PTS uint32 12 | SCR uint64 13 | SLI uint16 14 | } 15 | 16 | func (hsh *H264StreamHeader) UnmarshalBinary(buf []byte) error { 17 | if len(buf) < int(buf[0]) { 18 | return io.ErrShortBuffer 19 | } 20 | hsh.BitFieldHeader = buf[1] 21 | offset := 2 22 | if hsh.HasPTS() { 23 | hsh.PTS = binary.LittleEndian.Uint32(buf[offset : offset+4]) 24 | offset += 4 25 | } 26 | if hsh.HasSCR() { 27 | hsh.SCR = binary.LittleEndian.Uint64(buf[offset : offset+8]) 28 | offset += 8 29 | } 30 | if len(buf) >= offset+2 { 31 | hsh.SLI = binary.LittleEndian.Uint16(buf[offset : offset+2]) 32 | offset += 2 33 | } 34 | return nil 35 | } 36 | 37 | func (hsh *H264StreamHeader) FrameIdentifier() bool { 38 | return hsh.BitFieldHeader&0b00000001 != 0 39 | } 40 | 41 | func (hsh *H264StreamHeader) EndOfFrame() bool { 42 | return hsh.BitFieldHeader&0b00000010 != 0 43 | } 44 | 45 | func (hsh *H264StreamHeader) HasPTS() bool { 46 | return hsh.BitFieldHeader&0b00000100 != 0 47 | } 48 | 49 | func (hsh *H264StreamHeader) HasSCR() bool { 50 | return hsh.BitFieldHeader&0b00001000 != 0 51 | } 52 | 53 | func (hsh *H264StreamHeader) EndOfSlice() bool { 54 | return hsh.BitFieldHeader&0b00010000 != 0 55 | } 56 | 57 | func (hsh *H264StreamHeader) StillImage() bool { 58 | return hsh.BitFieldHeader&0b00100000 != 0 59 | } 60 | 61 | func (hsh *H264StreamHeader) Error() bool { 62 | return hsh.BitFieldHeader&0b01000000 != 0 63 | } 64 | 65 | func (hsh *H264StreamHeader) EndOfHeader() bool { 66 | return hsh.BitFieldHeader&0b10000000 != 0 67 | } 68 | 69 | type H264FormatDescriptor struct { 70 | FormatIndex uint8 71 | NumFrameDescriptors uint8 72 | DefaultFrameIndex uint8 73 | MaxCodecConfigDelay uint8 74 | SupportedSliceModesBitmask uint8 75 | SupportedSyncFrameTypesBitmask uint8 76 | ResolutionScaling uint8 77 | SupportedRateControlModesBitmask uint8 78 | MaxMBPerSecOneResolutionNoScalability uint16 79 | MaxMBPerSecTwoResolutionsNoScalability uint16 80 | MaxMBPerSecThreeResolutionsNoScalability uint16 81 | MaxMBPerSecFourResolutionsNoScalability uint16 82 | MaxMBPerSecOneResolutionTemporalScalability uint16 83 | MaxMBPerSecTwoResolutionsTemporalScalability uint16 84 | MaxMBPerSecThreeResolutionsTemporalScalability uint16 85 | MaxMBPerSecFourResolutionsTemporalScalability uint16 86 | MaxMBPerSecOneResolutionTemporalQualityScalability uint16 87 | MaxMBPerSecTwoResolutionsTemporalQualityScalability uint16 88 | MaxMBPerSecThreeResolutionsTemporalQualityScalability uint16 89 | MaxMBPerSecFourResolutionsTemporalQualityScalability uint16 90 | MaxMBPerSecOneResolutionTemporalSpatialScalability uint16 91 | MaxMBPerSecTwoResolutionsTemporalSpatialScalability uint16 92 | MaxMBPerSecThreeResolutionsTemporalSpatialScalability uint16 93 | MaxMBPerSecFourResolutionsTemporalSpatialScalability uint16 94 | MaxMBPerSecOneResolutionFullScalability uint16 95 | MaxMBPerSecTwoResolutionsFullScalability uint16 96 | MaxMBPerSecThreeResolutionsFullScalability uint16 97 | MaxMBPerSecFourResolutionsFullScalability uint16 98 | } 99 | 100 | func (hfd *H264FormatDescriptor) UnmarshalBinary(buf []byte) error { 101 | if len(buf) < int(buf[0]) { 102 | return io.ErrShortBuffer 103 | } 104 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 105 | return ErrInvalidDescriptor 106 | } 107 | if VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeFormatH264 && 108 | VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeFormatH264Simulcast { 109 | return ErrInvalidDescriptor 110 | } 111 | hfd.FormatIndex = buf[3] 112 | hfd.NumFrameDescriptors = buf[4] 113 | hfd.DefaultFrameIndex = buf[5] 114 | hfd.MaxCodecConfigDelay = buf[6] 115 | hfd.SupportedSliceModesBitmask = buf[7] 116 | hfd.SupportedSyncFrameTypesBitmask = buf[8] 117 | hfd.ResolutionScaling = buf[9] 118 | // buf[10] reserved 119 | hfd.SupportedRateControlModesBitmask = buf[11] 120 | hfd.MaxMBPerSecOneResolutionNoScalability = binary.LittleEndian.Uint16(buf[12:14]) 121 | hfd.MaxMBPerSecTwoResolutionsNoScalability = binary.LittleEndian.Uint16(buf[14:16]) 122 | hfd.MaxMBPerSecThreeResolutionsNoScalability = binary.LittleEndian.Uint16(buf[16:18]) 123 | hfd.MaxMBPerSecFourResolutionsNoScalability = binary.LittleEndian.Uint16(buf[18:20]) 124 | hfd.MaxMBPerSecOneResolutionTemporalScalability = binary.LittleEndian.Uint16(buf[20:22]) 125 | hfd.MaxMBPerSecTwoResolutionsTemporalScalability = binary.LittleEndian.Uint16(buf[22:24]) 126 | hfd.MaxMBPerSecThreeResolutionsTemporalScalability = binary.LittleEndian.Uint16(buf[24:26]) 127 | hfd.MaxMBPerSecFourResolutionsTemporalScalability = binary.LittleEndian.Uint16(buf[26:28]) 128 | hfd.MaxMBPerSecOneResolutionTemporalQualityScalability = binary.LittleEndian.Uint16(buf[28:30]) 129 | hfd.MaxMBPerSecTwoResolutionsTemporalQualityScalability = binary.LittleEndian.Uint16(buf[30:32]) 130 | hfd.MaxMBPerSecThreeResolutionsTemporalQualityScalability = binary.LittleEndian.Uint16(buf[32:34]) 131 | hfd.MaxMBPerSecFourResolutionsTemporalQualityScalability = binary.LittleEndian.Uint16(buf[34:36]) 132 | hfd.MaxMBPerSecOneResolutionTemporalSpatialScalability = binary.LittleEndian.Uint16(buf[36:38]) 133 | hfd.MaxMBPerSecTwoResolutionsTemporalSpatialScalability = binary.LittleEndian.Uint16(buf[38:40]) 134 | hfd.MaxMBPerSecThreeResolutionsTemporalSpatialScalability = binary.LittleEndian.Uint16(buf[40:42]) 135 | hfd.MaxMBPerSecFourResolutionsTemporalSpatialScalability = binary.LittleEndian.Uint16(buf[42:44]) 136 | hfd.MaxMBPerSecOneResolutionFullScalability = binary.LittleEndian.Uint16(buf[44:46]) 137 | hfd.MaxMBPerSecTwoResolutionsFullScalability = binary.LittleEndian.Uint16(buf[46:48]) 138 | hfd.MaxMBPerSecThreeResolutionsFullScalability = binary.LittleEndian.Uint16(buf[48:50]) 139 | hfd.MaxMBPerSecFourResolutionsFullScalability = binary.LittleEndian.Uint16(buf[50:52]) 140 | return nil 141 | } 142 | 143 | func (hfd *H264FormatDescriptor) isStreamingInterface() {} 144 | 145 | func (hfd *H264FormatDescriptor) isFormatDescriptor() {} 146 | 147 | func (hfd *H264FormatDescriptor) Index() uint8 { 148 | return hfd.FormatIndex 149 | } 150 | 151 | type H264FrameDescriptor struct { 152 | FrameIndex uint8 153 | Width, Height uint16 154 | SARWidth, SARHeight uint16 155 | Profile uint16 156 | LevelIDC uint8 157 | SupportedUsagesBitmask uint32 158 | CapabilitiesBitmask uint16 159 | SVCCapabilitiesBitmask uint32 160 | MVCCapabilitiesBitmask uint32 161 | MinBitRate, MaxBitRate uint32 162 | DefaultFrameInterval time.Duration 163 | FrameIntervals []time.Duration 164 | } 165 | 166 | func (hfd *H264FrameDescriptor) UnmarshalBinary(buf []byte) error { 167 | if len(buf) < int(buf[0]) { 168 | return io.ErrShortBuffer 169 | } 170 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 171 | return ErrInvalidDescriptor 172 | } 173 | if VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeFrameH264 { 174 | return ErrInvalidDescriptor 175 | } 176 | hfd.FrameIndex = buf[3] 177 | hfd.Width = binary.LittleEndian.Uint16(buf[4:6]) 178 | hfd.Height = binary.LittleEndian.Uint16(buf[6:8]) 179 | hfd.SARWidth = binary.LittleEndian.Uint16(buf[8:10]) 180 | hfd.SARHeight = binary.LittleEndian.Uint16(buf[10:12]) 181 | hfd.Profile = binary.LittleEndian.Uint16(buf[12:14]) 182 | hfd.LevelIDC = buf[14] 183 | // buf[15:17] reserved 184 | hfd.SupportedUsagesBitmask = binary.LittleEndian.Uint32(buf[17:21]) 185 | hfd.CapabilitiesBitmask = binary.LittleEndian.Uint16(buf[21:23]) 186 | hfd.SVCCapabilitiesBitmask = binary.LittleEndian.Uint32(buf[23:27]) 187 | hfd.MVCCapabilitiesBitmask = binary.LittleEndian.Uint32(buf[27:31]) 188 | hfd.MinBitRate = binary.LittleEndian.Uint32(buf[31:35]) 189 | hfd.MaxBitRate = binary.LittleEndian.Uint32(buf[35:39]) 190 | hfd.DefaultFrameInterval = time.Duration(binary.LittleEndian.Uint32(buf[39:43])) * 100 * time.Nanosecond 191 | n := buf[43] 192 | for i := uint8(0); i < n; i++ { 193 | hfd.FrameIntervals[i] = time.Duration(binary.LittleEndian.Uint32(buf[44+i*4:48+i*4])) * 100 * time.Nanosecond 194 | } 195 | return nil 196 | } 197 | 198 | func (hfd *H264FrameDescriptor) isStreamingInterface() {} 199 | 200 | func (hfd *H264FrameDescriptor) isFrameDescriptor() {} 201 | 202 | func (hfd *H264FrameDescriptor) Index() uint8 { 203 | return hfd.FrameIndex 204 | } 205 | -------------------------------------------------------------------------------- /pkg/descriptors/frame_mjpeg.go: -------------------------------------------------------------------------------- 1 | package descriptors 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "time" 7 | ) 8 | 9 | type MJPEGStreamHeader struct { 10 | BitFieldHeader uint8 11 | PTS uint32 12 | SCR uint64 13 | } 14 | 15 | func (msh *MJPEGStreamHeader) UnmarshalBinary(buf []byte) error { 16 | if len(buf) < int(buf[0]) { 17 | return io.ErrShortBuffer 18 | } 19 | msh.BitFieldHeader = buf[1] 20 | offset := 2 21 | if msh.HasPTS() { 22 | msh.PTS = binary.LittleEndian.Uint32(buf[offset : offset+4]) 23 | offset += 4 24 | } 25 | if msh.HasSCR() { 26 | msh.SCR = binary.LittleEndian.Uint64(buf[offset : offset+8]) 27 | offset += 8 28 | } 29 | return nil 30 | } 31 | 32 | func (msh *MJPEGStreamHeader) FrameIdentifier() bool { 33 | return msh.BitFieldHeader&0b00000001 != 0 34 | } 35 | 36 | func (msh *MJPEGStreamHeader) EndOfFrame() bool { 37 | return msh.BitFieldHeader&0b00000010 != 0 38 | } 39 | 40 | func (msh *MJPEGStreamHeader) HasPTS() bool { 41 | return msh.BitFieldHeader&0b00000100 != 0 42 | } 43 | 44 | func (msh *MJPEGStreamHeader) HasSCR() bool { 45 | return msh.BitFieldHeader&0b00001000 != 0 46 | } 47 | 48 | func (msh *MJPEGStreamHeader) StillImage() bool { 49 | return msh.BitFieldHeader&0b00100000 != 0 50 | } 51 | 52 | func (msh *MJPEGStreamHeader) Error() bool { 53 | return msh.BitFieldHeader&0b01000000 != 0 54 | } 55 | 56 | func (msh *MJPEGStreamHeader) EndOfHeader() bool { 57 | return msh.BitFieldHeader&0b10000000 != 0 58 | } 59 | 60 | type MJPEGFormatDescriptor struct { 61 | FormatIndex uint8 62 | NumFrameDescriptors uint8 63 | Flags uint8 64 | DefaultFrameIndex uint8 65 | AspectRatioX, AspectRatioY uint8 66 | InterlaceFlags uint8 67 | CopyProtect uint8 68 | } 69 | 70 | func (mfd *MJPEGFormatDescriptor) UnmarshalBinary(buf []byte) error { 71 | if len(buf) < int(buf[0]) { 72 | return io.ErrShortBuffer 73 | } 74 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 75 | return ErrInvalidDescriptor 76 | } 77 | if VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeFormatMJPEG { 78 | return ErrInvalidDescriptor 79 | } 80 | mfd.FormatIndex = buf[3] 81 | mfd.NumFrameDescriptors = buf[4] 82 | mfd.Flags = buf[5] 83 | mfd.DefaultFrameIndex = buf[6] 84 | mfd.AspectRatioX = buf[7] 85 | mfd.AspectRatioY = buf[8] 86 | mfd.InterlaceFlags = buf[9] 87 | mfd.CopyProtect = buf[10] 88 | return nil 89 | } 90 | 91 | func (mfd *MJPEGFormatDescriptor) isStreamingInterface() {} 92 | 93 | func (mfd *MJPEGFormatDescriptor) isFormatDescriptor() {} 94 | 95 | func (mfd *MJPEGFormatDescriptor) Index() uint8 { 96 | return mfd.FormatIndex 97 | } 98 | 99 | type MJPEGFrameDescriptor struct { 100 | FrameIndex uint8 101 | Capabilities uint8 102 | Width, Height uint16 103 | MinBitRate, MaxBitRate uint32 104 | MaxVideoFrameBufferSize uint32 105 | DefaultFrameInterval time.Duration 106 | 107 | ContinuousFrameInterval struct { 108 | MinFrameInterval, MaxFrameInterval, FrameIntervalStep time.Duration 109 | } 110 | DiscreteFrameIntervals []time.Duration 111 | } 112 | 113 | func (mfd *MJPEGFrameDescriptor) UnmarshalBinary(buf []byte) error { 114 | if len(buf) < int(buf[0]) { 115 | return io.ErrShortBuffer 116 | } 117 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 118 | return ErrInvalidDescriptor 119 | } 120 | if VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeFrameMJPEG { 121 | return ErrInvalidDescriptor 122 | } 123 | mfd.FrameIndex = buf[3] 124 | mfd.Capabilities = buf[4] 125 | mfd.Width = binary.LittleEndian.Uint16(buf[5:7]) 126 | mfd.Height = binary.LittleEndian.Uint16(buf[7:9]) 127 | mfd.MinBitRate = binary.LittleEndian.Uint32(buf[9:13]) 128 | mfd.MaxBitRate = binary.LittleEndian.Uint32(buf[13:17]) 129 | mfd.MaxVideoFrameBufferSize = binary.LittleEndian.Uint32(buf[17:21]) 130 | mfd.DefaultFrameInterval = time.Duration(binary.LittleEndian.Uint32(buf[21:25])) * 100 * time.Nanosecond 131 | 132 | n := buf[25] 133 | 134 | if n == 0 { 135 | // Continuous frame intervals 136 | mfd.ContinuousFrameInterval.MinFrameInterval = time.Duration(binary.LittleEndian.Uint32(buf[26:30])) * 100 * time.Nanosecond 137 | mfd.ContinuousFrameInterval.MaxFrameInterval = time.Duration(binary.LittleEndian.Uint32(buf[30:34])) * 100 * time.Nanosecond 138 | mfd.ContinuousFrameInterval.FrameIntervalStep = time.Duration(binary.LittleEndian.Uint32(buf[34:38])) * 100 * time.Nanosecond 139 | return nil 140 | } else { 141 | mfd.DiscreteFrameIntervals = make([]time.Duration, n) 142 | for i := uint8(0); i < n; i++ { 143 | mfd.DiscreteFrameIntervals[i] = time.Duration(binary.LittleEndian.Uint32(buf[26+i*4:30+i*4])) * 100 * time.Nanosecond 144 | } 145 | return nil 146 | } 147 | } 148 | 149 | func (mfd *MJPEGFrameDescriptor) isStreamingInterface() {} 150 | 151 | func (mfd *MJPEGFrameDescriptor) isFrameDescriptor() {} 152 | 153 | func (mfd *MJPEGFrameDescriptor) Index() uint8 { 154 | return mfd.FrameIndex 155 | } 156 | -------------------------------------------------------------------------------- /pkg/descriptors/frame_mpeg2ts.go: -------------------------------------------------------------------------------- 1 | package descriptors 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/google/uuid" 7 | ) 8 | 9 | type MPEG2TSStreamHeader struct { 10 | BitFieldHeader uint8 11 | } 12 | 13 | func (msh *MPEG2TSStreamHeader) UnmarshalBinary(buf []byte) error { 14 | if len(buf) < int(buf[0]) { 15 | return io.ErrShortBuffer 16 | } 17 | msh.BitFieldHeader = buf[1] 18 | return nil 19 | } 20 | 21 | func (msh *MPEG2TSStreamHeader) FrameIdentifier() bool { 22 | return msh.BitFieldHeader&0b00000001 != 0 23 | } 24 | 25 | func (msh *MPEG2TSStreamHeader) EndOfFrame() bool { 26 | return msh.BitFieldHeader&0b00000010 != 0 27 | } 28 | 29 | func (msh *MPEG2TSStreamHeader) StillImage() bool { 30 | return msh.BitFieldHeader&0b00100000 != 0 31 | } 32 | 33 | func (msh *MPEG2TSStreamHeader) Error() bool { 34 | return msh.BitFieldHeader&0b01000000 != 0 35 | } 36 | 37 | func (msh *MPEG2TSStreamHeader) EndOfHeader() bool { 38 | return msh.BitFieldHeader&0b10000000 != 0 39 | } 40 | 41 | type MPEG2TSFormatDescriptor struct { 42 | FormatIndex uint8 43 | DataOffset uint8 44 | PacketLength uint8 45 | StrideLength uint8 46 | GUIDStrideFormat uuid.UUID 47 | } 48 | 49 | func (mfd *MPEG2TSFormatDescriptor) UnmarshalBinary(buf []byte) error { 50 | if len(buf) < int(buf[0]) { 51 | return io.ErrShortBuffer 52 | } 53 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 54 | return ErrInvalidDescriptor 55 | } 56 | if VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeFormatMPEG2TS { 57 | return ErrInvalidDescriptor 58 | } 59 | mfd.FormatIndex = buf[3] 60 | mfd.DataOffset = buf[4] 61 | mfd.PacketLength = buf[5] 62 | mfd.StrideLength = buf[6] 63 | copyGUID(mfd.GUIDStrideFormat[:], buf[7:23]) 64 | return nil 65 | } 66 | 67 | func (mfd *MPEG2TSFormatDescriptor) isStreamingInterface() {} 68 | 69 | func (mfd *MPEG2TSFormatDescriptor) isFormatDescriptor() {} 70 | 71 | func (mfd *MPEG2TSFormatDescriptor) Index() uint8 { 72 | return mfd.FormatIndex 73 | } 74 | -------------------------------------------------------------------------------- /pkg/descriptors/frame_stream_based.go: -------------------------------------------------------------------------------- 1 | package descriptors 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | 7 | "github.com/google/uuid" 8 | ) 9 | 10 | type StreamBasedStreamHeader struct { 11 | BitFieldHeader uint8 12 | PTS uint32 13 | SCR uint64 14 | } 15 | 16 | func (sbsh *StreamBasedStreamHeader) UnmarshalBinary(buf []byte) error { 17 | if len(buf) < int(buf[0]) { 18 | return io.ErrShortBuffer 19 | } 20 | sbsh.BitFieldHeader = buf[1] 21 | offset := 2 22 | if sbsh.HasPTS() { 23 | sbsh.PTS = binary.LittleEndian.Uint32(buf[offset : offset+4]) 24 | offset += 4 25 | } 26 | if sbsh.HasSCR() { 27 | sbsh.SCR = binary.LittleEndian.Uint64(buf[offset : offset+8]) 28 | offset += 8 29 | } 30 | return nil 31 | } 32 | 33 | func (sbsh *StreamBasedStreamHeader) FrameIdentifier() bool { 34 | return sbsh.BitFieldHeader&0b00000001 != 0 35 | } 36 | 37 | func (sbsh *StreamBasedStreamHeader) EndOfFrame() bool { 38 | return sbsh.BitFieldHeader&0b00000010 != 0 39 | } 40 | 41 | func (sbsh *StreamBasedStreamHeader) HasPTS() bool { 42 | return sbsh.BitFieldHeader&0b00000100 != 0 43 | } 44 | 45 | func (sbsh *StreamBasedStreamHeader) HasSCR() bool { 46 | return sbsh.BitFieldHeader&0b00001000 != 0 47 | } 48 | 49 | func (sbsh *StreamBasedStreamHeader) StillImage() bool { 50 | return sbsh.BitFieldHeader&0b00100000 != 0 51 | } 52 | 53 | func (sbsh *StreamBasedStreamHeader) Error() bool { 54 | return sbsh.BitFieldHeader&0b01000000 != 0 55 | } 56 | 57 | func (sbsh *StreamBasedStreamHeader) EndOfHeader() bool { 58 | return sbsh.BitFieldHeader&0b10000000 != 0 59 | } 60 | 61 | type StreamBasedFormatDescriptor struct { 62 | FormatIndex uint8 63 | GUIDFormat uuid.UUID 64 | PacketLength uint32 65 | } 66 | 67 | func (sbfd *StreamBasedFormatDescriptor) UnmarshalBinary(buf []byte) error { 68 | if len(buf) < int(buf[0]) { 69 | return io.ErrShortBuffer 70 | } 71 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 72 | return ErrInvalidDescriptor 73 | } 74 | if VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeFormatStreamBased { 75 | return ErrInvalidDescriptor 76 | } 77 | sbfd.FormatIndex = buf[3] 78 | copyGUID(sbfd.GUIDFormat[:], buf[4:20]) 79 | sbfd.PacketLength = binary.LittleEndian.Uint32(buf[20:24]) 80 | return nil 81 | } 82 | 83 | func (sbfd *StreamBasedFormatDescriptor) isStreamingInterface() {} 84 | 85 | func (sbfd *StreamBasedFormatDescriptor) isFormatDescriptor() {} 86 | 87 | func (sbfd *StreamBasedFormatDescriptor) Index() uint8 { 88 | return sbfd.FormatIndex 89 | } 90 | -------------------------------------------------------------------------------- /pkg/descriptors/frame_uncompressed.go: -------------------------------------------------------------------------------- 1 | package descriptors 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | "strings" 8 | "time" 9 | 10 | "github.com/google/uuid" 11 | ) 12 | 13 | type UncompressedStreamHeader struct { 14 | BitFieldHeader uint8 15 | PTS uint32 16 | SCR uint64 17 | } 18 | 19 | func (ush *UncompressedStreamHeader) UnmarshalBinary(buf []byte) error { 20 | if len(buf) < int(buf[0]) { 21 | return io.ErrShortBuffer 22 | } 23 | ush.BitFieldHeader = buf[1] 24 | offset := 2 25 | if ush.HasPTS() { 26 | ush.PTS = binary.LittleEndian.Uint32(buf[offset : offset+4]) 27 | offset += 4 28 | } 29 | if ush.HasSCR() { 30 | ush.SCR = binary.LittleEndian.Uint64(buf[offset : offset+8]) 31 | offset += 8 32 | } 33 | return nil 34 | } 35 | 36 | func (ush *UncompressedStreamHeader) FrameIdentifier() bool { 37 | return ush.BitFieldHeader&0b00000001 != 0 38 | } 39 | 40 | func (ush *UncompressedStreamHeader) EndOfFrame() bool { 41 | return ush.BitFieldHeader&0b00000010 != 0 42 | } 43 | 44 | func (ush *UncompressedStreamHeader) HasPTS() bool { 45 | return ush.BitFieldHeader&0b00000100 != 0 46 | } 47 | 48 | func (ush *UncompressedStreamHeader) HasSCR() bool { 49 | return ush.BitFieldHeader&0b00001000 != 0 50 | } 51 | 52 | func (ush *UncompressedStreamHeader) StillImage() bool { 53 | return ush.BitFieldHeader&0b00100000 != 0 54 | } 55 | 56 | func (ush *UncompressedStreamHeader) Error() bool { 57 | return ush.BitFieldHeader&0b01000000 != 0 58 | } 59 | 60 | func (ush *UncompressedStreamHeader) EndOfHeader() bool { 61 | return ush.BitFieldHeader&0b10000000 != 0 62 | } 63 | 64 | type UncompressedFormatDescriptor struct { 65 | FormatIndex uint8 66 | NumFrameDescriptors uint8 67 | GUIDFormat uuid.UUID 68 | BitsPerPixel uint8 69 | DefaultFrameIndex uint8 70 | AspectRatioX uint8 71 | AspectRatioY uint8 72 | InterlaceFlagsBitmask uint8 73 | CopyProtect uint8 74 | } 75 | 76 | func (ufd *UncompressedFormatDescriptor) UnmarshalBinary(buf []byte) error { 77 | if len(buf) < int(buf[0]) { 78 | return io.ErrShortBuffer 79 | } 80 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 81 | return ErrInvalidDescriptor 82 | } 83 | if VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeFormatUncompressed { 84 | return ErrInvalidDescriptor 85 | } 86 | ufd.FormatIndex = buf[3] 87 | ufd.NumFrameDescriptors = buf[4] 88 | copyGUID(ufd.GUIDFormat[:], buf[5:21]) 89 | ufd.BitsPerPixel = buf[21] 90 | ufd.DefaultFrameIndex = buf[22] 91 | ufd.AspectRatioX = buf[23] 92 | ufd.AspectRatioY = buf[24] 93 | ufd.InterlaceFlagsBitmask = buf[25] 94 | ufd.CopyProtect = buf[26] 95 | return nil 96 | } 97 | 98 | func (ufd *UncompressedFormatDescriptor) FourCC() ([4]byte, error) { 99 | if strings.HasSuffix(ufd.GUIDFormat.String(), "-0000-0010-8000-00aa00389b71") { 100 | buf := [4]byte{} 101 | binary.LittleEndian.PutUint32(buf[:], ufd.GUIDFormat.ID()) 102 | return buf, nil 103 | } 104 | return [4]byte{}, fmt.Errorf("unknown FourCC for GUID %s", ufd.GUIDFormat) 105 | } 106 | 107 | func (ufd *UncompressedFormatDescriptor) isStreamingInterface() {} 108 | 109 | func (ufd *UncompressedFormatDescriptor) isFormatDescriptor() {} 110 | 111 | func (ufd *UncompressedFormatDescriptor) Index() uint8 { 112 | return ufd.FormatIndex 113 | } 114 | 115 | type UncompressedFrameDescriptor struct { 116 | FrameIndex uint8 117 | Capabilities uint8 118 | Width, Height uint16 119 | MinBitRate, MaxBitRate uint32 120 | MaxVideoFrameBufferSize uint32 121 | DefaultFrameInterval time.Duration 122 | 123 | ContinuousFrameInterval struct { 124 | MinFrameInterval, MaxFrameInterval, FrameIntervalStep time.Duration 125 | } 126 | DiscreteFrameIntervals []time.Duration 127 | } 128 | 129 | func (ufd *UncompressedFrameDescriptor) UnmarshalBinary(buf []byte) error { 130 | if len(buf) < int(buf[0]) { 131 | return io.ErrShortBuffer 132 | } 133 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 134 | return ErrInvalidDescriptor 135 | } 136 | if VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeFrameUncompressed { 137 | return ErrInvalidDescriptor 138 | } 139 | ufd.FrameIndex = buf[3] 140 | ufd.Capabilities = buf[4] 141 | ufd.Width = binary.LittleEndian.Uint16(buf[5:7]) 142 | ufd.Height = binary.LittleEndian.Uint16(buf[7:9]) 143 | ufd.MinBitRate = binary.LittleEndian.Uint32(buf[9:13]) 144 | ufd.MaxBitRate = binary.LittleEndian.Uint32(buf[13:17]) 145 | ufd.MaxVideoFrameBufferSize = binary.LittleEndian.Uint32(buf[17:21]) 146 | ufd.DefaultFrameInterval = time.Duration(binary.LittleEndian.Uint32(buf[21:25])) * 100 * time.Nanosecond 147 | 148 | n := buf[25] 149 | 150 | if n == 0 { 151 | // Continuous frame intervals 152 | ufd.ContinuousFrameInterval.MinFrameInterval = time.Duration(binary.LittleEndian.Uint32(buf[26:30])) * 100 * time.Nanosecond 153 | ufd.ContinuousFrameInterval.MaxFrameInterval = time.Duration(binary.LittleEndian.Uint32(buf[30:34])) * 100 * time.Nanosecond 154 | ufd.ContinuousFrameInterval.FrameIntervalStep = time.Duration(binary.LittleEndian.Uint32(buf[34:38])) * 100 * time.Nanosecond 155 | return nil 156 | } else { 157 | ufd.DiscreteFrameIntervals = make([]time.Duration, n) 158 | for i := uint8(0); i < n; i++ { 159 | ufd.DiscreteFrameIntervals[i] = time.Duration(binary.LittleEndian.Uint32(buf[26+i*4:30+i*4])) * 100 * time.Nanosecond 160 | } 161 | return nil 162 | } 163 | } 164 | 165 | func (ufd *UncompressedFrameDescriptor) isStreamingInterface() {} 166 | 167 | func (ufd *UncompressedFrameDescriptor) isFrameDescriptor() {} 168 | 169 | func (ufd *UncompressedFrameDescriptor) Index() uint8 { 170 | return ufd.FrameIndex 171 | } 172 | -------------------------------------------------------------------------------- /pkg/descriptors/frame_vp8.go: -------------------------------------------------------------------------------- 1 | package descriptors 2 | 3 | import ( 4 | "encoding/binary" 5 | "io" 6 | "time" 7 | ) 8 | 9 | type VP8StreamHeader struct { 10 | BitFieldHeader0 uint8 11 | BitFieldHeader1 uint8 12 | BitFieldHeader2 uint8 13 | PTS uint32 14 | SCR uint64 15 | SLI uint16 16 | } 17 | 18 | func (vph *VP8StreamHeader) UnmarshalBinary(buf []byte) error { 19 | if len(buf) < int(buf[0]) { 20 | return io.ErrShortBuffer 21 | } 22 | vph.BitFieldHeader0 = buf[1] 23 | vph.BitFieldHeader1 = buf[2] 24 | vph.BitFieldHeader2 = buf[3] 25 | offset := 4 26 | if vph.HasPTS() { 27 | vph.PTS = binary.LittleEndian.Uint32(buf[offset : offset+4]) 28 | offset += 4 29 | } 30 | if vph.HasSCR() { 31 | vph.SCR = binary.LittleEndian.Uint64(buf[offset : offset+8]) 32 | offset += 8 33 | } 34 | if vph.HasSLI() { 35 | vph.SLI = binary.LittleEndian.Uint16(buf[offset : offset+2]) 36 | offset += 2 37 | } 38 | return nil 39 | } 40 | 41 | func (vph *VP8StreamHeader) FrameIdentifier() bool { 42 | return vph.BitFieldHeader0&0b00000001 != 0 43 | } 44 | 45 | func (vph *VP8StreamHeader) EndOfFrame() bool { 46 | return vph.BitFieldHeader0&0b00000010 != 0 47 | } 48 | 49 | func (vph *VP8StreamHeader) HasPTS() bool { 50 | return vph.BitFieldHeader0&0b00000100 != 0 51 | } 52 | 53 | func (vph *VP8StreamHeader) HasSCR() bool { 54 | return vph.BitFieldHeader0&0b00001000 != 0 55 | } 56 | 57 | func (vph *VP8StreamHeader) EndOfSlice() bool { 58 | return vph.BitFieldHeader0&0b00010000 != 0 59 | } 60 | 61 | func (vph *VP8StreamHeader) StillImage() bool { 62 | return vph.BitFieldHeader0&0b00100000 != 0 63 | } 64 | 65 | func (vph *VP8StreamHeader) Error() bool { 66 | return vph.BitFieldHeader0&0b01000000 != 0 67 | } 68 | 69 | func (vph *VP8StreamHeader) HasSLI() bool { 70 | return vph.BitFieldHeader0&0b10000000 != 0 71 | } 72 | 73 | func (vph *VP8StreamHeader) PreviousReferenceFrame() bool { 74 | return vph.BitFieldHeader1&0b00000001 != 0 75 | } 76 | 77 | func (vph *VP8StreamHeader) AlternateReferenceFrame() bool { 78 | return vph.BitFieldHeader1&0b00000010 != 0 79 | } 80 | 81 | func (vph *VP8StreamHeader) GoldenReferenceFrame() bool { 82 | return vph.BitFieldHeader1&0b00000100 != 0 83 | } 84 | 85 | func (vph *VP8StreamHeader) EndOfHeader() bool { 86 | return vph.BitFieldHeader2&0b10000000 != 0 87 | } 88 | 89 | type VP8FormatDescriptor struct { 90 | FormatIndex uint8 91 | NumFrameDescriptors uint8 92 | DefaultFrameIndex uint8 93 | MaxCodecConfigDelay uint8 94 | SupportedPartitionCount uint8 95 | SupportedSyncFrameTypesBitmask uint8 96 | ResolutionScaling uint8 97 | SupportedRateControlModesBitmask uint8 98 | MaxMBPerSec uint16 99 | } 100 | 101 | func (vfd *VP8FormatDescriptor) UnmarshalBinary(buf []byte) error { 102 | if len(buf) < int(buf[0]) { 103 | return io.ErrShortBuffer 104 | } 105 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 106 | return ErrInvalidDescriptor 107 | } 108 | if VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeFormatVP8 && VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeFormatVP8Simulcast { 109 | return ErrInvalidDescriptor 110 | } 111 | vfd.FormatIndex = buf[3] 112 | vfd.NumFrameDescriptors = buf[4] 113 | vfd.DefaultFrameIndex = buf[5] 114 | vfd.MaxCodecConfigDelay = buf[6] 115 | vfd.SupportedPartitionCount = buf[7] 116 | vfd.SupportedSyncFrameTypesBitmask = buf[8] 117 | vfd.ResolutionScaling = buf[9] 118 | vfd.SupportedRateControlModesBitmask = buf[10] 119 | vfd.MaxMBPerSec = binary.LittleEndian.Uint16(buf[11:13]) 120 | return nil 121 | } 122 | 123 | func (vfd *VP8FormatDescriptor) isStreamingInterface() {} 124 | 125 | func (vfd *VP8FormatDescriptor) isFormatDescriptor() {} 126 | 127 | func (vfd *VP8FormatDescriptor) Index() uint8 { 128 | return vfd.FormatIndex 129 | } 130 | 131 | type VP8FrameDescriptor struct { 132 | FrameIndex uint8 133 | Width, Height uint16 134 | SupportedUsagesBitmask uint32 135 | CapabilitiesBitmask uint16 136 | ScalabilityCapabilitiesBitmask uint32 137 | MinBitRate, MaxBitRate uint32 138 | DefaultFrameInterval time.Duration 139 | FrameIntervals []time.Duration 140 | } 141 | 142 | func (vfd *VP8FrameDescriptor) UnmarshalBinary(buf []byte) error { 143 | if len(buf) < int(buf[0]) { 144 | return io.ErrShortBuffer 145 | } 146 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 147 | return ErrInvalidDescriptor 148 | } 149 | if VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeFrameVP8 { 150 | return ErrInvalidDescriptor 151 | } 152 | vfd.FrameIndex = buf[3] 153 | vfd.Width = binary.LittleEndian.Uint16(buf[4:6]) 154 | vfd.Height = binary.LittleEndian.Uint16(buf[6:8]) 155 | vfd.SupportedUsagesBitmask = binary.LittleEndian.Uint32(buf[8:12]) 156 | vfd.CapabilitiesBitmask = binary.LittleEndian.Uint16(buf[12:14]) 157 | vfd.ScalabilityCapabilitiesBitmask = binary.LittleEndian.Uint32(buf[14:18]) 158 | vfd.MinBitRate = binary.LittleEndian.Uint32(buf[18:22]) 159 | vfd.MaxBitRate = binary.LittleEndian.Uint32(buf[22:26]) 160 | vfd.DefaultFrameInterval = time.Duration(binary.LittleEndian.Uint32(buf[26:30])) * 100 * time.Nanosecond 161 | n := buf[30] 162 | for i := uint8(0); i < n; i++ { 163 | vfd.FrameIntervals[i] = time.Duration(binary.LittleEndian.Uint32(buf[31+i*4:35+i*4])) * 100 * time.Nanosecond 164 | } 165 | return nil 166 | } 167 | 168 | func (vfd *VP8FrameDescriptor) isStreamingInterface() {} 169 | 170 | func (vfd *VP8FrameDescriptor) isFrameDescriptor() {} 171 | 172 | func (vfd *VP8FrameDescriptor) Index() uint8 { 173 | return vfd.FrameIndex 174 | } 175 | -------------------------------------------------------------------------------- /pkg/descriptors/interface_association_descriptors.go: -------------------------------------------------------------------------------- 1 | // This file implements the descriptors as defined in the UVC spec 1.5, section 3.6. 2 | package descriptors 3 | 4 | import "io" 5 | 6 | type InterfaceAssociationDescriptor struct { 7 | InterfaceCount uint8 8 | DescriptionIndex uint8 9 | } 10 | 11 | func (iad *InterfaceAssociationDescriptor) UnmarshalBinary(buf []byte) error { 12 | if len(buf) < int(buf[0]) { 13 | return io.ErrShortBuffer 14 | } 15 | if buf[1] != 0xEF { // TODO: where does this come from 16 | return ErrInvalidDescriptor 17 | } 18 | iad.InterfaceCount = buf[2] 19 | if ClassCode(buf[3]) != ClassCodeVideo { 20 | return ErrInvalidDescriptor 21 | } 22 | if SubclassCode(buf[4]) != SubclassCodeVideoInterfaceCollection { 23 | return ErrInvalidDescriptor 24 | } 25 | if ProtocolCode(buf[5]) != ProtocolCodeUndefined { 26 | return ErrInvalidDescriptor 27 | } 28 | iad.DescriptionIndex = buf[6] 29 | return nil 30 | } 31 | -------------------------------------------------------------------------------- /pkg/descriptors/processing_unit_controls.go: -------------------------------------------------------------------------------- 1 | package descriptors 2 | 3 | import ( 4 | "encoding" 5 | "encoding/binary" 6 | ) 7 | 8 | type ProcessingUnitControlSelector int 9 | 10 | const ( 11 | ProcessingUnitControlSelectorUndefined ProcessingUnitControlSelector = 0x00 12 | ProcessingUnitBacklightCompensationControl ProcessingUnitControlSelector = 0x01 13 | ProcessingUnitBrightnessControl ProcessingUnitControlSelector = 0x02 14 | ProcessingUnitContrastControl ProcessingUnitControlSelector = 0x03 15 | ProcessingUnitGainControl ProcessingUnitControlSelector = 0x04 16 | ProcessingUnitPowerLineFrequencyControl ProcessingUnitControlSelector = 0x05 17 | ProcessingUnitHueControl ProcessingUnitControlSelector = 0x06 18 | ProcessingUnitSaturationControl ProcessingUnitControlSelector = 0x07 19 | ProcessingUnitSharpnessControl ProcessingUnitControlSelector = 0x08 20 | ProcessingUnitGammaControl ProcessingUnitControlSelector = 0x09 21 | ProcessingUnitWhiteBalanceTemperatureControl ProcessingUnitControlSelector = 0x0A 22 | ProcessingUnitWhiteBalanceTemperatureAutoControl ProcessingUnitControlSelector = 0x0B 23 | ProcessingUnitWhiteBalanceComponentControl ProcessingUnitControlSelector = 0x0C 24 | ProcessingUnitWhiteBalanceComponentAutoControl ProcessingUnitControlSelector = 0x0D 25 | ProcessingUnitDigitalMultiplierControl ProcessingUnitControlSelector = 0x0E // Deprecated 26 | ProcessingUnitDigitalMultiplierLimitControl ProcessingUnitControlSelector = 0x0F 27 | ProcessingUnitHueAutoControl ProcessingUnitControlSelector = 0x10 28 | ProcessingUnitAnalogVideoStandardControl ProcessingUnitControlSelector = 0x11 29 | ProcessingUnitAnalogVideoLockStatusControl ProcessingUnitControlSelector = 0x12 30 | ProcessingUnitContrastAutoControl ProcessingUnitControlSelector = 0x13 31 | ) 32 | 33 | type PowerLineFrequency int 34 | 35 | const ( 36 | PowerLineFrequencyDisabled PowerLineFrequency = 0 37 | PowerLineFrequency50Hz PowerLineFrequency = 1 38 | PowerLineFrequency60Hz PowerLineFrequency = 2 39 | PowerLineFrequencyAuto PowerLineFrequency = 3 40 | ) 41 | 42 | type AnalogVideoStandard int 43 | 44 | const ( 45 | AnalogVideoStandardNone AnalogVideoStandard = 0 46 | AnalogVideoStandardNTSC525 AnalogVideoStandard = 1 // NTSC 525/60 47 | AnalogVideoStandardPAL625 AnalogVideoStandard = 2 // PAL 625/50 48 | AnalogVideoStandardSECAM AnalogVideoStandard = 3 // SECAM 625/50 49 | AnalogVideoStandardNTSC625 AnalogVideoStandard = 4 // NTSC 625/50 50 | AnalogVideoStandardPAL525 AnalogVideoStandard = 5 // PAL 525/60 51 | ) 52 | 53 | type AnalogVideoLockStatus int 54 | 55 | const ( 56 | AnalogVideoLockStatusLocked AnalogVideoLockStatus = 0 57 | AnalogVideoLockStatusNotLocked AnalogVideoLockStatus = 1 58 | ) 59 | 60 | type ProcessingUnitControlDescriptor interface { 61 | Value() ProcessingUnitControlSelector 62 | FeatureBit() int //Indicates the position of the control on the controls bitmap 63 | encoding.BinaryMarshaler 64 | encoding.BinaryUnmarshaler 65 | } 66 | 67 | type BacklightCompensationControl struct { 68 | BacklightCompensation uint16 69 | } 70 | 71 | func (bcc *BacklightCompensationControl) FeatureBit() int { 72 | return 8 73 | } 74 | 75 | func (bcc *BacklightCompensationControl) Value() ProcessingUnitControlSelector { 76 | return ProcessingUnitBacklightCompensationControl 77 | } 78 | 79 | func (bcc *BacklightCompensationControl) MarshalBinary() ([]byte, error) { 80 | buf := make([]byte, 2) 81 | binary.LittleEndian.PutUint16(buf, bcc.BacklightCompensation) 82 | return buf, nil 83 | } 84 | 85 | func (bcc *BacklightCompensationControl) UnmarshalBinary(buf []byte) error { 86 | bcc.BacklightCompensation = binary.LittleEndian.Uint16(buf) 87 | return nil 88 | } 89 | 90 | type BrightnessControl struct { 91 | Brightness uint16 92 | } 93 | 94 | func (bc *BrightnessControl) FeatureBit() int { 95 | return 0 96 | } 97 | 98 | func (bc *BrightnessControl) Value() ProcessingUnitControlSelector { 99 | return ProcessingUnitBrightnessControl 100 | } 101 | 102 | func (bc *BrightnessControl) MarshalBinary() ([]byte, error) { 103 | buf := make([]byte, 2) 104 | binary.LittleEndian.PutUint16(buf, bc.Brightness) 105 | return buf, nil 106 | } 107 | 108 | func (bc *BrightnessControl) UnmarshalBinary(buf []byte) error { 109 | bc.Brightness = binary.LittleEndian.Uint16(buf) 110 | return nil 111 | } 112 | 113 | type ContrastControl struct { 114 | Contrast uint16 115 | } 116 | 117 | func (cc *ContrastControl) FeatureBit() int { 118 | return 1 119 | } 120 | 121 | func (cc *ContrastControl) Value() ProcessingUnitControlSelector { 122 | return ProcessingUnitContrastControl 123 | } 124 | 125 | func (cc *ContrastControl) MarshalBinary() ([]byte, error) { 126 | buf := make([]byte, 2) 127 | binary.LittleEndian.PutUint16(buf, cc.Contrast) 128 | return buf, nil 129 | } 130 | 131 | func (cc *ContrastControl) UnmarshalBinary(buf []byte) error { 132 | cc.Contrast = binary.LittleEndian.Uint16(buf) 133 | return nil 134 | } 135 | 136 | type ContrastAutoControl struct { 137 | Auto uint16 138 | } 139 | 140 | func (cac *ContrastAutoControl) FeatureBit() int { 141 | return 18 142 | } 143 | 144 | func (cac *ContrastAutoControl) Value() ProcessingUnitControlSelector { 145 | return ProcessingUnitContrastAutoControl 146 | } 147 | 148 | func (cac *ContrastAutoControl) MarshalBinary() ([]byte, error) { 149 | buf := make([]byte, 2) 150 | binary.LittleEndian.PutUint16(buf, cac.Auto) 151 | return buf, nil 152 | } 153 | 154 | func (cac *ContrastAutoControl) UnmarshalBinary(buf []byte) error { 155 | cac.Auto = binary.LittleEndian.Uint16(buf) 156 | return nil 157 | } 158 | 159 | type GainControl struct { 160 | Gain uint16 161 | } 162 | 163 | func (gc *GainControl) FeatureBit() int { 164 | return 9 165 | } 166 | 167 | func (gc *GainControl) Value() ProcessingUnitControlSelector { 168 | return ProcessingUnitGainControl 169 | } 170 | 171 | func (gc *GainControl) MarshalBinary() ([]byte, error) { 172 | buf := make([]byte, 2) 173 | binary.LittleEndian.PutUint16(buf, gc.Gain) 174 | return buf, nil 175 | } 176 | 177 | func (gc *GainControl) UnmarshalBinary(buf []byte) error { 178 | gc.Gain = binary.LittleEndian.Uint16(buf) 179 | return nil 180 | } 181 | 182 | type PowerLineFrequencyControl struct { 183 | Frequency PowerLineFrequency 184 | } 185 | 186 | func (plfc *PowerLineFrequencyControl) FeatureBit() int { 187 | return 10 188 | } 189 | 190 | func (plfc *PowerLineFrequencyControl) Value() ProcessingUnitControlSelector { 191 | return ProcessingUnitPowerLineFrequencyControl 192 | } 193 | 194 | func (plfc *PowerLineFrequencyControl) MarshalBinary() ([]byte, error) { 195 | buf := make([]byte, 2) 196 | binary.LittleEndian.PutUint16(buf, uint16(plfc.Frequency)) 197 | return buf, nil 198 | } 199 | 200 | func (plfc *PowerLineFrequencyControl) UnmarshalBinary(buf []byte) error { 201 | plfc.Frequency = PowerLineFrequency(binary.LittleEndian.Uint16(buf)) 202 | return nil 203 | } 204 | 205 | type HueControl struct { 206 | Hue uint16 207 | } 208 | 209 | func (hc *HueControl) FeatureBit() int { 210 | return 2 211 | } 212 | 213 | func (hc *HueControl) Value() ProcessingUnitControlSelector { 214 | return ProcessingUnitHueControl 215 | } 216 | 217 | func (hc *HueControl) MarshalBinary() ([]byte, error) { 218 | buf := make([]byte, 2) 219 | binary.LittleEndian.PutUint16(buf, hc.Hue) 220 | return buf, nil 221 | } 222 | 223 | func (hc *HueControl) UnmarshalBinary(buf []byte) error { 224 | hc.Hue = binary.LittleEndian.Uint16(buf) 225 | return nil 226 | } 227 | 228 | type HueAutoControl struct { 229 | Auto uint8 230 | } 231 | 232 | func (hac *HueAutoControl) FeatureBit() int { 233 | return 11 234 | } 235 | 236 | func (hac *HueAutoControl) Value() ProcessingUnitControlSelector { 237 | return ProcessingUnitHueAutoControl 238 | } 239 | 240 | func (hac *HueAutoControl) MarshalBinary() ([]byte, error) { 241 | buf := make([]byte, 1) 242 | buf[0] = uint8(hac.Auto) 243 | return buf, nil 244 | } 245 | 246 | func (hac *HueAutoControl) UnmarshalBinary(buf []byte) error { 247 | hac.Auto = buf[0] 248 | return nil 249 | } 250 | 251 | type SaturationControl struct { 252 | Saturation uint16 253 | } 254 | 255 | func (sc *SaturationControl) FeatureBit() int { 256 | return 3 257 | } 258 | 259 | func (sc *SaturationControl) Value() ProcessingUnitControlSelector { 260 | return ProcessingUnitSaturationControl 261 | } 262 | 263 | func (sc *SaturationControl) MarshalBinary() ([]byte, error) { 264 | buf := make([]byte, 2) 265 | binary.LittleEndian.PutUint16(buf, sc.Saturation) 266 | return buf, nil 267 | } 268 | 269 | func (sc *SaturationControl) UnmarshalBinary(buf []byte) error { 270 | sc.Saturation = binary.LittleEndian.Uint16(buf) 271 | return nil 272 | } 273 | 274 | type SharpnessControl struct { 275 | Sharpness uint16 276 | } 277 | 278 | func (sc *SharpnessControl) FeatureBit() int { 279 | return 4 280 | } 281 | 282 | func (sc *SharpnessControl) Value() ProcessingUnitControlSelector { 283 | return ProcessingUnitSharpnessControl 284 | } 285 | 286 | func (sc *SharpnessControl) MarshalBinary() ([]byte, error) { 287 | buf := make([]byte, 2) 288 | binary.LittleEndian.PutUint16(buf, sc.Sharpness) 289 | return buf, nil 290 | } 291 | 292 | func (sc *SharpnessControl) UnmarshalBinary(buf []byte) error { 293 | sc.Sharpness = binary.LittleEndian.Uint16(buf) 294 | return nil 295 | } 296 | 297 | type GammaControl struct { 298 | Gamma uint16 299 | } 300 | 301 | func (gc *GammaControl) FeatureBit() int { 302 | return 5 303 | } 304 | 305 | func (gc *GammaControl) Value() ProcessingUnitControlSelector { 306 | return ProcessingUnitGammaControl 307 | } 308 | 309 | func (gc *GammaControl) MarshalBinary() ([]byte, error) { 310 | buf := make([]byte, 2) 311 | binary.LittleEndian.PutUint16(buf, gc.Gamma) 312 | return buf, nil 313 | } 314 | 315 | func (gc *GammaControl) UnmarshalBinary(buf []byte) error { 316 | gc.Gamma = binary.LittleEndian.Uint16(buf) 317 | return nil 318 | } 319 | 320 | type WhiteBalanceTemperatureControl struct { 321 | WhiteBalanceTemperature uint16 322 | } 323 | 324 | func (wbt *WhiteBalanceTemperatureControl) FeatureBit() int { 325 | return 6 326 | } 327 | 328 | func (wbt *WhiteBalanceTemperatureControl) Value() ProcessingUnitControlSelector { 329 | return ProcessingUnitWhiteBalanceTemperatureControl 330 | } 331 | 332 | func (wbt *WhiteBalanceTemperatureControl) MarshalBinary() ([]byte, error) { 333 | buf := make([]byte, 2) 334 | binary.LittleEndian.PutUint16(buf, wbt.WhiteBalanceTemperature) 335 | return buf, nil 336 | } 337 | 338 | func (wbt *WhiteBalanceTemperatureControl) UnmarshalBinary(buf []byte) error { 339 | wbt.WhiteBalanceTemperature = binary.LittleEndian.Uint16(buf) 340 | return nil 341 | } 342 | 343 | type WhiteBalanceTemperatureAutoControl struct { 344 | WhiteBalanceTemperatureAuto uint8 345 | } 346 | 347 | func (wbtac *WhiteBalanceTemperatureAutoControl) FeatureBit() int { 348 | return 12 349 | } 350 | 351 | func (wbtac *WhiteBalanceTemperatureAutoControl) Value() ProcessingUnitControlSelector { 352 | return ProcessingUnitWhiteBalanceTemperatureAutoControl 353 | } 354 | 355 | func (wbtac *WhiteBalanceTemperatureAutoControl) MarshalBinary() ([]byte, error) { 356 | buf := make([]byte, 1) 357 | buf[0] = uint8(wbtac.WhiteBalanceTemperatureAuto) 358 | return buf, nil 359 | } 360 | 361 | func (wbtac *WhiteBalanceTemperatureAutoControl) UnmarshalBinary(buf []byte) error { 362 | wbtac.WhiteBalanceTemperatureAuto = buf[0] 363 | return nil 364 | } 365 | 366 | type WhiteBalanceComponentControl struct { 367 | Blue uint16 368 | Red uint16 369 | } 370 | 371 | func (wbcc *WhiteBalanceComponentControl) FeatureBit() int { 372 | return 7 373 | } 374 | 375 | func (wbcc *WhiteBalanceComponentControl) Value() ProcessingUnitControlSelector { 376 | return ProcessingUnitWhiteBalanceComponentControl 377 | } 378 | 379 | func (wbcc *WhiteBalanceComponentControl) MarshalBinary() ([]byte, error) { 380 | buf := make([]byte, 4) 381 | binary.LittleEndian.AppendUint16(buf[0:2], wbcc.Blue) 382 | binary.LittleEndian.AppendUint16(buf[2:4], wbcc.Red) 383 | return buf, nil 384 | } 385 | 386 | func (wbcc *WhiteBalanceComponentControl) UnmarshalBinary(buf []byte) error { 387 | wbcc.Blue = binary.LittleEndian.Uint16(buf[0:2]) 388 | wbcc.Blue = binary.LittleEndian.Uint16(buf[2:4]) 389 | return nil 390 | } 391 | 392 | type WhiteBalanceComponentAutoControl struct { 393 | WhiteBalanceComponentAuto uint8 394 | } 395 | 396 | func (wbcac *WhiteBalanceComponentAutoControl) FeatureBit() int { 397 | return 13 398 | } 399 | 400 | func (wbcac *WhiteBalanceComponentAutoControl) Value() ProcessingUnitControlSelector { 401 | return ProcessingUnitWhiteBalanceComponentAutoControl 402 | } 403 | 404 | func (wbcac *WhiteBalanceComponentAutoControl) MarshalBinary() ([]byte, error) { 405 | buf := make([]byte, 1) 406 | buf[0] = uint8(wbcac.WhiteBalanceComponentAuto) 407 | return buf, nil 408 | } 409 | 410 | func (wbcac *WhiteBalanceComponentAutoControl) UnmarshalBinary(buf []byte) error { 411 | wbcac.WhiteBalanceComponentAuto = buf[0] 412 | return nil 413 | } 414 | 415 | // Deprecated in 1.5 416 | type DigitalMultiplerControl struct { 417 | DigitalMultipler uint16 418 | } 419 | 420 | func (dmc *DigitalMultiplerControl) FeatureBit() int { 421 | return 14 422 | } 423 | 424 | func (dmc *DigitalMultiplerControl) Value() ProcessingUnitControlSelector { 425 | return ProcessingUnitDigitalMultiplierControl 426 | } 427 | 428 | func (dmc *DigitalMultiplerControl) MarshalBinary() ([]byte, error) { 429 | buf := make([]byte, 2) 430 | binary.LittleEndian.PutUint16(buf, dmc.DigitalMultipler) 431 | return buf, nil 432 | } 433 | 434 | func (dmc *DigitalMultiplerControl) UnmarshalBinary(buf []byte) error { 435 | dmc.DigitalMultipler = binary.LittleEndian.Uint16(buf) 436 | return nil 437 | } 438 | 439 | type DigitalMultiplerLimitControl struct { 440 | DigitalMultiplerLimit uint16 441 | } 442 | 443 | func (dmlc *DigitalMultiplerLimitControl) FeatureBit() int { 444 | return 15 445 | } 446 | 447 | func (dmlc *DigitalMultiplerLimitControl) Value() ProcessingUnitControlSelector { 448 | return ProcessingUnitDigitalMultiplierLimitControl 449 | } 450 | 451 | func (dmlc *DigitalMultiplerLimitControl) MarshalBinary() ([]byte, error) { 452 | buf := make([]byte, 2) 453 | binary.LittleEndian.PutUint16(buf, dmlc.DigitalMultiplerLimit) 454 | return buf, nil 455 | } 456 | 457 | func (dmlc *DigitalMultiplerLimitControl) UnmarshalBinary(buf []byte) error { 458 | dmlc.DigitalMultiplerLimit = binary.LittleEndian.Uint16(buf) 459 | return nil 460 | } 461 | 462 | type AnalogVideoStandardControl struct { 463 | AnalogVideoStandard AnalogVideoStandard 464 | } 465 | 466 | func (avsc *AnalogVideoStandardControl) FeatureBit() int { 467 | return 16 468 | } 469 | 470 | func (avsc *AnalogVideoStandardControl) Value() ProcessingUnitControlSelector { 471 | return ProcessingUnitAnalogVideoStandardControl 472 | } 473 | 474 | func (avsc *AnalogVideoStandardControl) MarshalBinary() ([]byte, error) { 475 | buf := make([]byte, 1) 476 | buf[0] = uint8(avsc.AnalogVideoStandard) 477 | return buf, nil 478 | } 479 | 480 | func (avsc *AnalogVideoStandardControl) UnmarshalBinary(buf []byte) error { 481 | avsc.AnalogVideoStandard = AnalogVideoStandard(buf[0]) 482 | return nil 483 | } 484 | 485 | type AnalogVideoLockStatusControl struct { 486 | AnalogVideoLockStatus AnalogVideoLockStatus 487 | } 488 | 489 | func (avlsc *AnalogVideoLockStatusControl) FeatureBit() int { 490 | return 17 491 | } 492 | 493 | func (avlsc *AnalogVideoLockStatusControl) Value() ProcessingUnitControlSelector { 494 | return ProcessingUnitAnalogVideoLockStatusControl 495 | } 496 | 497 | func (avlsc *AnalogVideoLockStatusControl) MarshalBinary() ([]byte, error) { 498 | buf := make([]byte, 1) 499 | buf[0] = uint8(avlsc.AnalogVideoLockStatus) 500 | return buf, nil 501 | } 502 | 503 | func (avlsc *AnalogVideoLockStatusControl) UnmarshalBinary(buf []byte) error { 504 | avlsc.AnalogVideoLockStatus = AnalogVideoLockStatus(buf[0]) 505 | return nil 506 | } 507 | -------------------------------------------------------------------------------- /pkg/descriptors/terminal_control_consts.go: -------------------------------------------------------------------------------- 1 | package descriptors 2 | 3 | type CameraTerminalControlSelector int 4 | 5 | const ( 6 | CameraTerminalControlSelectorUndefined CameraTerminalControlSelector = 0x00 7 | CameraTerminalControlSelectorScanningModeControl CameraTerminalControlSelector = 0x01 8 | CameraTerminalControlSelectorAutoExposureModeControl CameraTerminalControlSelector = 0x02 9 | CameraTerminalControlSelectorAutoExposurePriorityControl CameraTerminalControlSelector = 0x03 10 | CameraTerminalControlSelectorExposureTimeAbsoluteControl CameraTerminalControlSelector = 0x04 11 | CameraTerminalControlSelectorExposureTimeRelativeControl CameraTerminalControlSelector = 0x05 12 | CameraTerminalControlSelectorFocusAbsoluteControl CameraTerminalControlSelector = 0x06 13 | CameraTerminalControlSelectorFocusRelativeControl CameraTerminalControlSelector = 0x07 14 | CameraTerminalControlSelectorFocusAutoControl CameraTerminalControlSelector = 0x08 15 | CameraTerminalControlSelectorIrisAbsoluteControl CameraTerminalControlSelector = 0x09 16 | CameraTerminalControlSelectorIrisRelativeControl CameraTerminalControlSelector = 0x0A 17 | CameraTerminalControlSelectorZoomAbsoluteControl CameraTerminalControlSelector = 0x0B 18 | CameraTerminalControlSelectorZoomRelativeControl CameraTerminalControlSelector = 0x0C 19 | CameraTerminalControlSelectorPanTiltAbsoluteControl CameraTerminalControlSelector = 0x0D 20 | CameraTerminalControlSelectorPanTiltRelativeControl CameraTerminalControlSelector = 0x0E 21 | CameraTerminalControlSelectorRollAbsoluteControl CameraTerminalControlSelector = 0x0F 22 | CameraTerminalControlSelectorRollRelativeControl CameraTerminalControlSelector = 0x10 23 | CameraTerminalControlSelectorPrivacyControl CameraTerminalControlSelector = 0x11 24 | CameraTerminalControlSelectorFocusSimpleControl CameraTerminalControlSelector = 0x12 25 | CameraTerminalControlSelectorWindowControl CameraTerminalControlSelector = 0x13 26 | CameraTerminalControlSelectorRegionOfInterestControl CameraTerminalControlSelector = 0x14 27 | ) 28 | 29 | type ScanningMode int 30 | 31 | const ( 32 | ScanningModeInterlaced ScanningMode = 0 33 | ScanningModeProgressive ScanningMode = 1 34 | ) 35 | 36 | type AutoExposureMode int 37 | 38 | const ( 39 | AutoExposureModeManual AutoExposureMode = 1 40 | AutoExposureModeAuto AutoExposureMode = 2 41 | AutoExposureModeShutterPriority AutoExposureMode = 4 42 | AutoExposureModeAperturePriority AutoExposureMode = 8 43 | ) 44 | 45 | type AutoExposurePriority int 46 | 47 | const ( 48 | AutoExposurePriorityConstant AutoExposurePriority = 0 49 | AutoExposurePriorityDynamic AutoExposurePriority = 1 50 | ) 51 | 52 | type ExposureTimeRelative int 53 | 54 | const ( 55 | ExposureTimeRelativeDefault ExposureTimeRelative = 0x00 56 | ExposureTimeRelativeIncrement ExposureTimeRelative = 0x01 57 | ExposureTimeRelativeDecrement ExposureTimeRelative = 0xFF 58 | ) 59 | 60 | type FocusRelative uint8 61 | 62 | const ( 63 | FocusRelativeStop FocusRelative = 0x00 64 | FocusRelativeNearDirection FocusRelative = 0x01 65 | FocusRelativeInfiniteDirection FocusRelative = 0xFF 66 | ) 67 | 68 | type FocusSimple uint8 69 | 70 | const ( 71 | FocusSimpleFullRange FocusSimple = 0x00 72 | FocusSimpleMacro FocusSimple = 0x01 // Less than 0.3 meters 73 | FocusSimplePeople FocusSimple = 0x02 // 0.3 meters to 3 meters 74 | FocusSimpleScene FocusSimple = 0x03 // 3 meters to infinity 75 | ) 76 | 77 | type IrisRelative uint8 78 | 79 | const ( 80 | IrisRelativeDefault IrisRelative = 0x00 81 | IrisRelativeOpenStep IrisRelative = 0x01 82 | IrisRelativeCloseStep IrisRelative = 0x02 83 | ) 84 | 85 | type ZoomRelative uint8 86 | 87 | const ( 88 | ZoomRelativeStop ZoomRelative = 0x00 89 | ZoomRelativeTelephoto ZoomRelative = 0x01 90 | ZoomRelativeWideAngle ZoomRelative = 0xFF 91 | ) 92 | 93 | type PanRelative int 94 | 95 | const ( 96 | PanRelativeStop PanRelative = 0x00 97 | PanRelativeClockwise PanRelative = 0x01 98 | PanRelativeCounterClockwise PanRelative = 0xFF 99 | ) 100 | 101 | type TiltRelative int 102 | 103 | const ( 104 | TiltRelativeStop TiltRelative = 0x00 105 | TiltRelativeUp TiltRelative = 0x01 106 | TiltRelativeDown TiltRelative = 0xFF 107 | ) 108 | 109 | type RollRelative int 110 | 111 | const ( 112 | RollRelativeStop RollRelative = 0x00 113 | RollRelativeClockwise RollRelative = 0x01 114 | RollRelativeCounterClockwise RollRelative = 0xFF 115 | ) 116 | 117 | type StepUnits int 118 | 119 | const ( 120 | StepUnitsVideoFrames StepUnits = 0x00 121 | StepUnitsMiliseconds StepUnits = 0x01 122 | ) 123 | 124 | type RegionOfInterestAutoControl int 125 | 126 | const ( 127 | RegionOfInterestAutoControlExposure RegionOfInterestAutoControl = 0x00 128 | RegionOfInterestAutoControlIris RegionOfInterestAutoControl = 0x01 129 | RegionOfInterestAutoControlWhiteBalance RegionOfInterestAutoControl = 0x02 130 | RegionOfInterestAutoControlFocus RegionOfInterestAutoControl = 0x03 131 | RegionOfInterestAutoControlFaceDetect RegionOfInterestAutoControl = 0x04 132 | RegionOfInterestAutoControlDetectTrack RegionOfInterestAutoControl = 0x05 133 | RegionOfInterestAutoControlImageStabilization RegionOfInterestAutoControl = 0x06 134 | RegionOfInterestAutoControlHigherQuality RegionOfInterestAutoControl = 0x07 135 | ) 136 | -------------------------------------------------------------------------------- /pkg/descriptors/video_control_endpoint_descriptors.go: -------------------------------------------------------------------------------- 1 | // This file implements the descriptors as defined in the UVC spec 1.5, section 3.8. 2 | package descriptors 3 | 4 | import ( 5 | "encoding/binary" 6 | "io" 7 | ) 8 | 9 | type VideoControlEndpointDescriptorSubtype byte 10 | 11 | const ( 12 | VideoControlEndpointDescriptorSubtypeUndefined VideoControlEndpointDescriptorSubtype = 0x00 13 | VideoControlEndpointDescriptorSubtypeGeneral VideoControlEndpointDescriptorSubtype = 0x01 14 | VideoControlEndpointDescriptorSubtypeEndpoint VideoControlEndpointDescriptorSubtype = 0x02 15 | VideoControlEndpointDescriptorSubtypeInterrupt VideoControlEndpointDescriptorSubtype = 0x03 16 | ) 17 | 18 | type StandardVideoControlInterruptEndpointDescriptor struct { 19 | MaxTransferSize uint16 20 | } 21 | 22 | func (svcie *StandardVideoControlInterruptEndpointDescriptor) UnmarshalBinary(buf []byte) error { 23 | if len(buf) < int(buf[0]) { 24 | return io.ErrShortBuffer 25 | } 26 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeEndpoint { 27 | return ErrInvalidDescriptor 28 | } 29 | if VideoControlEndpointDescriptorSubtype(buf[2]) != VideoControlEndpointDescriptorSubtypeInterrupt { 30 | return ErrInvalidDescriptor 31 | } 32 | svcie.MaxTransferSize = binary.LittleEndian.Uint16(buf[2:4]) 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /pkg/descriptors/video_control_interface_descriptors.go: -------------------------------------------------------------------------------- 1 | // This file implements the descriptors as defined in the UVC spec 1.5, section 3.7. 2 | package descriptors 3 | 4 | import ( 5 | "encoding" 6 | "encoding/binary" 7 | "io" 8 | ) 9 | 10 | type ControlInterface interface { 11 | encoding.BinaryUnmarshaler 12 | isControlInterface() 13 | } 14 | 15 | func UnmarshalInputTerminal(buf []byte) (ControlInterface, error) { 16 | var desc ControlInterface 17 | switch InputTerminalType(binary.LittleEndian.Uint16(buf[4:6])) { 18 | case InputTerminalTypeCamera: 19 | desc = &CameraTerminalDescriptor{} 20 | } 21 | return desc, desc.UnmarshalBinary(buf) 22 | } 23 | 24 | func UnmarshalControlInterface(buf []byte) (ControlInterface, error) { 25 | var desc ControlInterface 26 | switch VideoControlInterfaceDescriptorSubtype(buf[2]) { 27 | case VideoControlInterfaceDescriptorSubtypeHeader: 28 | desc = &HeaderDescriptor{} 29 | case VideoControlInterfaceDescriptorSubtypeInputTerminal: 30 | desc = &InputTerminalDescriptor{} 31 | case VideoControlInterfaceDescriptorSubtypeOutputTerminal: 32 | desc = &OutputTerminalDescriptor{} 33 | case VideoControlInterfaceDescriptorSubtypeSelectorUnit: 34 | desc = &SelectorUnitDescriptor{} 35 | case VideoControlInterfaceDescriptorSubtypeProcessingUnit: 36 | desc = &ProcessingUnitDescriptor{} 37 | case VideoControlInterfaceDescriptorSubtypeEncodingUnit: 38 | desc = &EncodingUnitDescriptor{} 39 | case VideoControlInterfaceDescriptorSubtypeExtensionUnit: 40 | desc = &ExtensionUnitDescriptor{} 41 | } 42 | return desc, desc.UnmarshalBinary(buf) 43 | } 44 | 45 | type VideoControlInterfaceDescriptorSubtype byte 46 | 47 | const ( 48 | VideoControlInterfaceDescriptorSubtypeUndefined VideoControlInterfaceDescriptorSubtype = 0x00 49 | VideoControlInterfaceDescriptorSubtypeHeader VideoControlInterfaceDescriptorSubtype = 0x01 50 | VideoControlInterfaceDescriptorSubtypeInputTerminal VideoControlInterfaceDescriptorSubtype = 0x02 51 | VideoControlInterfaceDescriptorSubtypeOutputTerminal VideoControlInterfaceDescriptorSubtype = 0x03 52 | VideoControlInterfaceDescriptorSubtypeSelectorUnit VideoControlInterfaceDescriptorSubtype = 0x04 53 | VideoControlInterfaceDescriptorSubtypeProcessingUnit VideoControlInterfaceDescriptorSubtype = 0x05 54 | VideoControlInterfaceDescriptorSubtypeExtensionUnit VideoControlInterfaceDescriptorSubtype = 0x06 55 | VideoControlInterfaceDescriptorSubtypeEncodingUnit VideoControlInterfaceDescriptorSubtype = 0x07 56 | ) 57 | 58 | type TerminalType uint16 59 | 60 | const ( 61 | TerminalTypeVendorSpecific TerminalType = 0x0100 62 | TerminalTypeStreaming TerminalType = 0x0101 63 | ) 64 | 65 | type InputTerminalType uint16 66 | 67 | const ( 68 | InputTerminalTypeVendorSpecific InputTerminalType = 0x0200 69 | InputTerminalTypeCamera InputTerminalType = 0x0201 70 | InputTerminalTypeMediaTransportInput InputTerminalType = 0x0202 71 | ) 72 | 73 | type OutputTerminalType uint16 74 | 75 | const ( 76 | OutputTerminalTypeVendorSpecific OutputTerminalType = 0x0300 77 | OutputTerminalTypeCamera OutputTerminalType = 0x0301 78 | OutputTerminalTypeMediaTransportOutput OutputTerminalType = 0x0302 79 | ) 80 | 81 | type ExternalTerminalType uint16 82 | 83 | const ( 84 | ExternalTerminalTypeVendorSpecific ExternalTerminalType = 0x0400 85 | ExternalTerminalTypeCompositeConnector ExternalTerminalType = 0x0401 86 | ExternalTerminalTypeSVideoConnector ExternalTerminalType = 0x0402 87 | ExternalTerminalTypeComponentConnector ExternalTerminalType = 0x0403 88 | ) 89 | 90 | // StandardVideoControlInterfaceDescriptor as defined in UVC spec 1.5, 3.7.1 91 | type StandardVideoControlInterfaceDescriptor struct { 92 | InterfaceNumber uint8 93 | AlternateSetting uint8 94 | NumEndpoints uint8 95 | DescriptionIndex uint8 96 | } 97 | 98 | func (svcid *StandardVideoControlInterfaceDescriptor) UnmarshalBinary(buf []byte) error { 99 | if len(buf) < int(buf[0]) { 100 | return io.ErrShortBuffer 101 | } 102 | // TODO: check the descriptor type, this is not the class specific one. 103 | // if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 104 | // return ErrInvalidDescriptor 105 | // } 106 | svcid.InterfaceNumber = buf[2] 107 | svcid.AlternateSetting = buf[3] 108 | svcid.NumEndpoints = buf[4] 109 | if ClassCode(buf[5]) != ClassCodeVideo { 110 | return ErrInvalidDescriptor 111 | } 112 | if SubclassCode(buf[6]) != SubclassCodeVideoControl { 113 | return ErrInvalidDescriptor 114 | } 115 | if ProtocolCode(buf[7]) != ProtocolCode15 { 116 | return ErrInvalidDescriptor 117 | } 118 | svcid.DescriptionIndex = buf[8] 119 | return nil 120 | } 121 | 122 | func (svcid *StandardVideoControlInterfaceDescriptor) isControlInterface() {} 123 | 124 | // HeaderDescriptor as defined in UVC spec 1.5, 3.7.2.1 125 | type HeaderDescriptor struct { 126 | UVC uint16 127 | TotalLength uint16 128 | ClockFrequency uint32 129 | VideoStreamingInterfaceIndexes []uint8 130 | } 131 | 132 | func (hd *HeaderDescriptor) UnmarshalBinary(buf []byte) error { 133 | if len(buf) < int(buf[0]) { 134 | return io.ErrShortBuffer 135 | } 136 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 137 | return ErrInvalidDescriptor 138 | } 139 | if VideoControlInterfaceDescriptorSubtype(buf[2]) != VideoControlInterfaceDescriptorSubtypeHeader { 140 | return ErrInvalidDescriptor 141 | } 142 | hd.UVC = binary.LittleEndian.Uint16(buf[3:5]) 143 | hd.TotalLength = binary.LittleEndian.Uint16(buf[5:7]) 144 | hd.ClockFrequency = binary.LittleEndian.Uint32(buf[7:11]) 145 | n := buf[11] 146 | hd.VideoStreamingInterfaceIndexes = buf[12 : 12+n] 147 | return nil 148 | } 149 | 150 | func (hd *HeaderDescriptor) isControlInterface() {} 151 | 152 | // InputTerminalDescriptor as defined in UVC spec 1.5, 3.7.2.1 153 | type InputTerminalDescriptor struct { 154 | TerminalID uint8 155 | TerminalType InputTerminalType 156 | AssociatedTerminalID uint8 157 | DescriptionIndex uint8 158 | } 159 | 160 | func (itd *InputTerminalDescriptor) UnmarshalBinary(buf []byte) error { 161 | if len(buf) < int(buf[0]) { 162 | return io.ErrShortBuffer 163 | } 164 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 165 | return ErrInvalidDescriptor 166 | } 167 | if VideoControlInterfaceDescriptorSubtype(buf[2]) != VideoControlInterfaceDescriptorSubtypeInputTerminal { 168 | return ErrInvalidDescriptor 169 | } 170 | itd.TerminalID = buf[3] 171 | itd.TerminalType = InputTerminalType(binary.LittleEndian.Uint16(buf[4:6])) 172 | itd.AssociatedTerminalID = buf[6] 173 | itd.DescriptionIndex = buf[7] 174 | return nil 175 | } 176 | 177 | func (itd *InputTerminalDescriptor) isControlInterface() {} 178 | 179 | // OutputTerminalDescriptor as defined in UVC spec 1.5, 3.7.2.2 180 | type OutputTerminalDescriptor struct { 181 | TerminalID uint8 182 | TerminalType OutputTerminalType 183 | AssociatedTerminalID uint8 184 | SourceID uint8 185 | } 186 | 187 | func (otd *OutputTerminalDescriptor) UnmarshalBinary(buf []byte) error { 188 | if len(buf) < int(buf[0]) { 189 | return io.ErrShortBuffer 190 | } 191 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 192 | return ErrInvalidDescriptor 193 | } 194 | if VideoControlInterfaceDescriptorSubtype(buf[2]) != VideoControlInterfaceDescriptorSubtypeOutputTerminal { 195 | return ErrInvalidDescriptor 196 | } 197 | otd.TerminalID = buf[3] 198 | otd.TerminalType = OutputTerminalType(binary.LittleEndian.Uint16(buf[4:6])) 199 | otd.AssociatedTerminalID = buf[6] 200 | otd.SourceID = buf[7] 201 | return nil 202 | } 203 | 204 | func (otd *OutputTerminalDescriptor) isControlInterface() {} 205 | 206 | // CameraTerminalDescriptor as defined in UVC spec 1.5, 3.7.2.3 207 | type CameraTerminalDescriptor struct { 208 | InputTerminalDescriptor 209 | ObjectiveFocalLengthMin uint16 210 | ObjectiveFocalLengthMax uint16 211 | OcularFocalLength uint16 212 | ControlsBitmask []byte // Size 3 bitmap that indicates which controls are supported 213 | } 214 | 215 | func (ctd *CameraTerminalDescriptor) UnmarshalBinary(buf []byte) error { 216 | if len(buf) < int(buf[0]) { 217 | return io.ErrShortBuffer 218 | } 219 | 220 | inputTerminalDesc := &InputTerminalDescriptor{} 221 | err := inputTerminalDesc.UnmarshalBinary(buf) 222 | if err != nil { 223 | return err 224 | } 225 | ctd.InputTerminalDescriptor = *inputTerminalDesc 226 | 227 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 228 | return ErrInvalidDescriptor 229 | } 230 | if VideoControlInterfaceDescriptorSubtype(buf[2]) != VideoControlInterfaceDescriptorSubtypeInputTerminal { 231 | return ErrInvalidDescriptor 232 | } 233 | if InputTerminalType(binary.LittleEndian.Uint16(buf[4:6])) != InputTerminalTypeCamera { 234 | return ErrInvalidDescriptor 235 | } 236 | ctd.ObjectiveFocalLengthMin = binary.LittleEndian.Uint16(buf[8:10]) 237 | ctd.ObjectiveFocalLengthMax = binary.LittleEndian.Uint16(buf[10:12]) 238 | ctd.OcularFocalLength = binary.LittleEndian.Uint16(buf[12:14]) 239 | ctd.ControlsBitmask = buf[15:18] 240 | return nil 241 | } 242 | 243 | func (ctd *CameraTerminalDescriptor) isControlInterface() {} 244 | 245 | // SelectorUnitDescriptor as defined in UVC spec 1.5, 3.7.2.4 246 | type SelectorUnitDescriptor struct { 247 | UnitID uint8 248 | SourceID []uint8 249 | DescriptionIndex uint8 250 | } 251 | 252 | func (sud *SelectorUnitDescriptor) UnmarshalBinary(buf []byte) error { 253 | if len(buf) < int(buf[0]) { 254 | return io.ErrShortBuffer 255 | } 256 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 257 | return ErrInvalidDescriptor 258 | } 259 | if VideoControlInterfaceDescriptorSubtype(buf[2]) != VideoControlInterfaceDescriptorSubtypeSelectorUnit { 260 | return ErrInvalidDescriptor 261 | } 262 | sud.UnitID = buf[3] 263 | p := buf[4] 264 | sud.SourceID = buf[5 : 5+p] 265 | sud.DescriptionIndex = buf[5+p] 266 | return nil 267 | } 268 | 269 | func (sud *SelectorUnitDescriptor) isControlInterface() {} 270 | 271 | // ProcessingUnitDescriptor as defined in UVC spec 1.5, 3.7.2.5 272 | type ProcessingUnitDescriptor struct { 273 | UnitID uint8 274 | SourceID uint8 275 | MaxMultiplier uint16 276 | ControlsBitmask []byte 277 | DescriptionIndex uint8 278 | VideoStandardsBitmask uint8 279 | } 280 | 281 | func (pud *ProcessingUnitDescriptor) UnmarshalBinary(buf []byte) error { 282 | if len(buf) < int(buf[0]) { 283 | return io.ErrShortBuffer 284 | } 285 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 286 | return ErrInvalidDescriptor 287 | } 288 | if VideoControlInterfaceDescriptorSubtype(buf[2]) != VideoControlInterfaceDescriptorSubtypeProcessingUnit { 289 | return ErrInvalidDescriptor 290 | } 291 | pud.UnitID = buf[3] 292 | pud.SourceID = buf[4] 293 | pud.MaxMultiplier = binary.LittleEndian.Uint16(buf[5:7]) 294 | n := buf[7] 295 | pud.ControlsBitmask = make([]byte, n) 296 | copy(pud.ControlsBitmask, buf[8:8+n]) 297 | pud.DescriptionIndex = buf[8+n] 298 | if len(buf) > int(9+n) { 299 | // TODO: did this not exist in USB spec 1.0? 300 | pud.VideoStandardsBitmask = buf[9+n] 301 | } 302 | return nil 303 | } 304 | 305 | func (pud *ProcessingUnitDescriptor) isControlInterface() {} 306 | 307 | // EncodingUnitDescriptor as defined in UVC spec 1.5, 3.7.2.6 308 | type EncodingUnitDescriptor struct { 309 | UnitID uint8 310 | SourceID uint8 311 | DescriptionIndex uint8 312 | ControlsBitmask uint32 313 | ControlsRuntimeBitmask uint32 314 | } 315 | 316 | func (eud *EncodingUnitDescriptor) UnmarshalBinary(buf []byte) error { 317 | if len(buf) < int(buf[0]) { 318 | return io.ErrShortBuffer 319 | } 320 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 321 | return ErrInvalidDescriptor 322 | } 323 | if VideoControlInterfaceDescriptorSubtype(buf[2]) != VideoControlInterfaceDescriptorSubtypeEncodingUnit { 324 | return ErrInvalidDescriptor 325 | } 326 | eud.UnitID = buf[3] 327 | eud.SourceID = buf[4] 328 | eud.DescriptionIndex = buf[5] 329 | eud.ControlsBitmask = binary.LittleEndian.Uint32(buf[6:10]) 330 | // this is off by one because the bitmask is actually only the lower 3 bytes. 331 | eud.ControlsRuntimeBitmask = binary.LittleEndian.Uint32(buf[9:13]) 332 | return nil 333 | } 334 | 335 | func (eud *EncodingUnitDescriptor) isControlInterface() {} 336 | 337 | // ExtensionUnitDescriptor as defined in UVC spec 1.5, 3.7.2.7 338 | type ExtensionUnitDescriptor struct { 339 | UnitID uint8 340 | GUIDExtensionCode [16]byte 341 | NumControls uint8 342 | SourceIDs []uint8 343 | ControlsBitmask []byte 344 | DescriptionIndex uint8 345 | } 346 | 347 | func (eud *ExtensionUnitDescriptor) UnmarshalBinary(buf []byte) error { 348 | if len(buf) < int(buf[0]) { 349 | return io.ErrShortBuffer 350 | } 351 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 352 | return ErrInvalidDescriptor 353 | } 354 | if VideoControlInterfaceDescriptorSubtype(buf[2]) != VideoControlInterfaceDescriptorSubtypeExtensionUnit { 355 | return ErrInvalidDescriptor 356 | } 357 | eud.UnitID = buf[3] 358 | copyGUID(eud.GUIDExtensionCode[:], buf[4:20]) 359 | eud.NumControls = buf[20] 360 | p := buf[21] 361 | eud.SourceIDs = make([]uint8, p) 362 | copy(eud.SourceIDs, buf[22:22+p]) 363 | n := buf[22+p] 364 | eud.ControlsBitmask = make([]byte, n) 365 | copy(eud.ControlsBitmask, buf[23+p:23+p+n]) 366 | eud.DescriptionIndex = buf[23+p+n] 367 | return nil 368 | } 369 | 370 | func (eud *ExtensionUnitDescriptor) isControlInterface() {} 371 | -------------------------------------------------------------------------------- /pkg/descriptors/video_streaming_endpoint_descriptors.go: -------------------------------------------------------------------------------- 1 | // This file implements the descriptors as defined in the UVC spec 1.5, section 3.10. 2 | package descriptors 3 | 4 | import ( 5 | "encoding/binary" 6 | "io" 7 | ) 8 | 9 | // StandardVideoStreamingIsochronousVideoDataEndpointDescriptor as defined in UVC spec 1.5, 3.10.1.1 10 | type StandardVideoStreamingIsochronousVideoDataEndpointDescriptor struct { 11 | EndpointAddress uint8 12 | AttributesBitmask uint8 13 | MaxPacketSize uint16 14 | Interval uint8 15 | } 16 | 17 | func (svsived *StandardVideoStreamingIsochronousVideoDataEndpointDescriptor) UnmarshalBinary(buf []byte) error { 18 | if len(buf) < int(buf[0]) { 19 | return io.ErrShortBuffer 20 | } 21 | // TODO: fix the descriptor type, this is not the class specific one. 22 | // if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeEndpoint { 23 | // return ErrInvalidDescriptor 24 | // } 25 | svsived.EndpointAddress = buf[2] 26 | svsived.AttributesBitmask = buf[3] 27 | svsived.MaxPacketSize = binary.LittleEndian.Uint16(buf[4:6]) 28 | svsived.Interval = buf[6] 29 | return nil 30 | } 31 | 32 | // StandardVideoStreamingBulkVideoDataEndpointDescriptor as defined in UVC spec 1.5, 3.10.1.2 33 | type StandardVideoStreamingBulkVideoDataEndpointDescriptor struct { 34 | EndpointAddress uint8 35 | MaxPacketSize uint16 36 | Interval uint8 37 | } 38 | 39 | func (svsbded *StandardVideoStreamingBulkVideoDataEndpointDescriptor) UnmarshalBinary(buf []byte) error { 40 | if len(buf) < int(buf[0]) { 41 | return io.ErrShortBuffer 42 | } 43 | // TODO: fix the descriptor type, this is not the class specific one. 44 | // if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeEndpoint { 45 | // return ErrInvalidDescriptor 46 | // } 47 | svsbded.EndpointAddress = buf[2] 48 | if buf[3] != 0b10 { // 0b10 == Bulk 49 | return ErrInvalidDescriptor 50 | } 51 | svsbded.MaxPacketSize = binary.LittleEndian.Uint16(buf[4:6]) 52 | svsbded.Interval = buf[6] 53 | return nil 54 | } 55 | 56 | // StandardVideoStreamingBulkStillImageDataEndpointDescriptor as defined in UVC spec 1.5, 3.10.1.3 57 | type StandardVideoStreamingBulkStillImageDataEndpointDescriptor struct { 58 | EndpointAddress uint8 59 | MaxPacketSize uint16 60 | } 61 | 62 | func (svsbied *StandardVideoStreamingBulkStillImageDataEndpointDescriptor) UnmarshalBinary(buf []byte) error { 63 | if len(buf) < int(buf[0]) { 64 | return io.ErrShortBuffer 65 | } 66 | // TODO: fix the descriptor type, this is not the class specific one. 67 | // if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeEndpoint { 68 | // return ErrInvalidDescriptor 69 | // } 70 | svsbied.EndpointAddress = buf[2] 71 | if svsbied.EndpointAddress&0b10000000 == 0 { // Direction == IN 72 | return ErrInvalidDescriptor 73 | } 74 | if svsbied.EndpointAddress&0b01111000 != 0 { // Reserved bits 75 | return ErrInvalidDescriptor 76 | } 77 | if buf[3] != 0b10 { // 0b10 == Bulk 78 | return ErrInvalidDescriptor 79 | } 80 | svsbied.MaxPacketSize = binary.LittleEndian.Uint16(buf[4:6]) 81 | if buf[6] != 0 { 82 | return ErrInvalidDescriptor 83 | } 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /pkg/descriptors/video_streaming_interface_descriptors.go: -------------------------------------------------------------------------------- 1 | // This file implements the descriptors as defined in the UVC spec 1.5, section 3.9. 2 | package descriptors 3 | 4 | import ( 5 | "encoding" 6 | "encoding/binary" 7 | "io" 8 | ) 9 | 10 | type StreamingInterface interface { 11 | encoding.BinaryUnmarshaler 12 | isStreamingInterface() 13 | } 14 | 15 | func UnmarshalStreamingInterface(buf []byte) (StreamingInterface, error) { 16 | var desc StreamingInterface 17 | switch VideoStreamingInterfaceDescriptorSubtype(buf[2]) { 18 | case VideoStreamingInterfaceDescriptorSubtypeInputHeader: 19 | desc = &InputHeaderDescriptor{} 20 | case VideoStreamingInterfaceDescriptorSubtypeOutputHeader: 21 | desc = &OutputHeaderDescriptor{} 22 | case VideoStreamingInterfaceDescriptorSubtypeStillImageFrame: 23 | desc = &StillImageFrameDescriptor{} 24 | case VideoStreamingInterfaceDescriptorSubtypeFormatUncompressed: 25 | desc = &UncompressedFormatDescriptor{} 26 | case VideoStreamingInterfaceDescriptorSubtypeFrameUncompressed: 27 | desc = &UncompressedFrameDescriptor{} 28 | case VideoStreamingInterfaceDescriptorSubtypeFormatMJPEG: 29 | desc = &MJPEGFormatDescriptor{} 30 | case VideoStreamingInterfaceDescriptorSubtypeFrameMJPEG: 31 | desc = &MJPEGFrameDescriptor{} 32 | case VideoStreamingInterfaceDescriptorSubtypeFormatMPEG2TS: 33 | desc = &MPEG2TSFormatDescriptor{} 34 | case VideoStreamingInterfaceDescriptorSubtypeFormatDV: 35 | desc = &DVFormatDescriptor{} 36 | case VideoStreamingInterfaceDescriptorSubtypeColorFormat: 37 | desc = &ColorMatchingDescriptor{} 38 | case VideoStreamingInterfaceDescriptorSubtypeFormatFrameBased: 39 | desc = &FrameBasedFormatDescriptor{} 40 | case VideoStreamingInterfaceDescriptorSubtypeFrameFrameBased: 41 | desc = &FrameBasedFrameDescriptor{} 42 | case VideoStreamingInterfaceDescriptorSubtypeFormatStreamBased: 43 | desc = &StreamBasedFormatDescriptor{} 44 | case VideoStreamingInterfaceDescriptorSubtypeFormatH264: 45 | desc = &H264FormatDescriptor{} 46 | case VideoStreamingInterfaceDescriptorSubtypeFrameH264: 47 | desc = &H264FrameDescriptor{} 48 | case VideoStreamingInterfaceDescriptorSubtypeFormatH264Simulcast: 49 | desc = &H264FormatDescriptor{} 50 | case VideoStreamingInterfaceDescriptorSubtypeFormatVP8: 51 | desc = &VP8FormatDescriptor{} 52 | case VideoStreamingInterfaceDescriptorSubtypeFrameVP8: 53 | desc = &VP8FrameDescriptor{} 54 | case VideoStreamingInterfaceDescriptorSubtypeFormatVP8Simulcast: 55 | desc = &VP8FormatDescriptor{} 56 | } 57 | return desc, desc.UnmarshalBinary(buf) 58 | } 59 | 60 | type VideoStreamingInterfaceDescriptorSubtype byte 61 | 62 | const ( 63 | VideoStreamingInterfaceDescriptorSubtypeUndefined VideoStreamingInterfaceDescriptorSubtype = 0x00 64 | VideoStreamingInterfaceDescriptorSubtypeInputHeader VideoStreamingInterfaceDescriptorSubtype = 0x01 65 | VideoStreamingInterfaceDescriptorSubtypeOutputHeader VideoStreamingInterfaceDescriptorSubtype = 0x02 66 | VideoStreamingInterfaceDescriptorSubtypeStillImageFrame VideoStreamingInterfaceDescriptorSubtype = 0x03 67 | VideoStreamingInterfaceDescriptorSubtypeFormatUncompressed VideoStreamingInterfaceDescriptorSubtype = 0x04 68 | VideoStreamingInterfaceDescriptorSubtypeFrameUncompressed VideoStreamingInterfaceDescriptorSubtype = 0x05 69 | VideoStreamingInterfaceDescriptorSubtypeFormatMJPEG VideoStreamingInterfaceDescriptorSubtype = 0x06 70 | VideoStreamingInterfaceDescriptorSubtypeFrameMJPEG VideoStreamingInterfaceDescriptorSubtype = 0x07 71 | VideoStreamingInterfaceDescriptorSubtypeFormatMPEG2TS VideoStreamingInterfaceDescriptorSubtype = 0x0A 72 | VideoStreamingInterfaceDescriptorSubtypeFormatDV VideoStreamingInterfaceDescriptorSubtype = 0x0C 73 | VideoStreamingInterfaceDescriptorSubtypeColorFormat VideoStreamingInterfaceDescriptorSubtype = 0x0D 74 | VideoStreamingInterfaceDescriptorSubtypeFormatFrameBased VideoStreamingInterfaceDescriptorSubtype = 0x10 75 | VideoStreamingInterfaceDescriptorSubtypeFrameFrameBased VideoStreamingInterfaceDescriptorSubtype = 0x11 76 | VideoStreamingInterfaceDescriptorSubtypeFormatStreamBased VideoStreamingInterfaceDescriptorSubtype = 0x12 77 | VideoStreamingInterfaceDescriptorSubtypeFormatH264 VideoStreamingInterfaceDescriptorSubtype = 0x13 78 | VideoStreamingInterfaceDescriptorSubtypeFrameH264 VideoStreamingInterfaceDescriptorSubtype = 0x14 79 | VideoStreamingInterfaceDescriptorSubtypeFormatH264Simulcast VideoStreamingInterfaceDescriptorSubtype = 0x15 80 | VideoStreamingInterfaceDescriptorSubtypeFormatVP8 VideoStreamingInterfaceDescriptorSubtype = 0x16 81 | VideoStreamingInterfaceDescriptorSubtypeFrameVP8 VideoStreamingInterfaceDescriptorSubtype = 0x17 82 | VideoStreamingInterfaceDescriptorSubtypeFormatVP8Simulcast VideoStreamingInterfaceDescriptorSubtype = 0x18 83 | ) 84 | 85 | // StandardVideoStreamingInterfaceDescriptor as defined in UVC spec 1.5, 3.9.1 86 | type StandardVideoStreamingInterfaceDescriptor struct { 87 | InterfaceNumber uint8 88 | AlternateSetting uint8 89 | NumEndpoints uint8 90 | DescriptionIndex uint8 91 | } 92 | 93 | func (svsid *StandardVideoStreamingInterfaceDescriptor) UnmarshalBinary(buf []byte) error { 94 | if len(buf) < int(buf[0]) { 95 | return io.ErrShortBuffer 96 | } 97 | // TODO: fix the descriptor type, this is not the class specific one. 98 | // if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 99 | // return ErrInvalidDescriptor 100 | // } 101 | svsid.InterfaceNumber = buf[2] 102 | svsid.AlternateSetting = buf[3] 103 | svsid.NumEndpoints = buf[4] 104 | if ClassCode(buf[5]) != ClassCodeVideo { 105 | return ErrInvalidDescriptor 106 | } 107 | if SubclassCode(buf[6]) != SubclassCodeVideoStreaming { 108 | return ErrInvalidDescriptor 109 | } 110 | if ProtocolCode(buf[7]) != ProtocolCode15 { 111 | return ErrInvalidDescriptor 112 | } 113 | svsid.DescriptionIndex = buf[8] 114 | return nil 115 | } 116 | 117 | func (svsid *StandardVideoStreamingInterfaceDescriptor) isStreamingInterface() {} 118 | 119 | // InputHeaderDescriptor as defined in UVC spec 1.5, 3.9.2.1 120 | type InputHeaderDescriptor struct { 121 | TotalLength uint16 122 | EndpointAddress uint8 123 | InfoBitmask uint8 124 | TerminalLink uint8 125 | StillCaptureMethod uint8 126 | TriggerSupport uint8 127 | TriggerUsage uint8 128 | ControlBitmasks [][]byte 129 | } 130 | 131 | func (ihd *InputHeaderDescriptor) UnmarshalBinary(buf []byte) error { 132 | if len(buf) < int(buf[0]) { 133 | return io.ErrShortBuffer 134 | } 135 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 136 | return ErrInvalidDescriptor 137 | } 138 | if VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeInputHeader { 139 | return ErrInvalidDescriptor 140 | } 141 | p := buf[3] 142 | ihd.TotalLength = binary.LittleEndian.Uint16(buf[4:6]) 143 | ihd.EndpointAddress = buf[6] 144 | ihd.InfoBitmask = buf[7] 145 | ihd.TerminalLink = buf[8] 146 | ihd.StillCaptureMethod = buf[9] 147 | ihd.TriggerSupport = buf[10] 148 | ihd.TriggerUsage = buf[11] 149 | n := buf[12] 150 | ihd.ControlBitmasks = make([][]byte, p) 151 | for i := uint8(0); i < p; i++ { 152 | ihd.ControlBitmasks[i] = buf[13+i*n : 13+(i+1)*n] 153 | } 154 | return nil 155 | } 156 | 157 | func (ihd *InputHeaderDescriptor) isStreamingInterface() {} 158 | 159 | // OutputHeaderDescriptor as defined in UVC spec 1.5, 3.9.2.2 160 | type OutputHeaderDescriptor struct { 161 | TotalLength uint16 162 | EndpointAddress uint8 163 | TerminalLink uint8 164 | ControlBitmasks [][]byte 165 | } 166 | 167 | func (ohd *OutputHeaderDescriptor) UnmarshalBinary(buf []byte) error { 168 | if len(buf) < int(buf[0]) { 169 | return io.ErrShortBuffer 170 | } 171 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 172 | return ErrInvalidDescriptor 173 | } 174 | if VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeOutputHeader { 175 | return ErrInvalidDescriptor 176 | } 177 | p := buf[3] 178 | ohd.TotalLength = binary.LittleEndian.Uint16(buf[4:6]) 179 | ohd.EndpointAddress = buf[6] 180 | ohd.TerminalLink = buf[7] 181 | n := buf[8] 182 | for i := uint8(0); i < p; i++ { 183 | ohd.ControlBitmasks[i] = buf[9+i*n : 9+(i+1)*n] 184 | } 185 | return nil 186 | } 187 | 188 | func (ohd *OutputHeaderDescriptor) isStreamingInterface() {} 189 | 190 | // PayloadFormatDescriptor and VideoFrameDescriptor are implemented in the corresponding subpackages. 191 | 192 | // StillImageFrameDescriptor as defined in UVC spec 1.5, 3.9.2.5 193 | type StillImageFrameDescriptor struct { 194 | EndpointAddress uint8 195 | ImageSizePatterns []struct { 196 | Width, Height uint16 197 | } 198 | CompressionPatterns []uint8 199 | } 200 | 201 | func (sifd *StillImageFrameDescriptor) UnmarshalBinary(buf []byte) error { 202 | if len(buf) < int(buf[0]) { 203 | return io.ErrShortBuffer 204 | } 205 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 206 | return ErrInvalidDescriptor 207 | } 208 | if VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeStillImageFrame { 209 | return ErrInvalidDescriptor 210 | } 211 | sifd.EndpointAddress = buf[3] 212 | n := buf[4] 213 | for i := uint8(0); i < n; i++ { 214 | sifd.ImageSizePatterns[i].Width = binary.LittleEndian.Uint16(buf[5+4*i : 7+4*i]) 215 | sifd.ImageSizePatterns[i].Height = binary.LittleEndian.Uint16(buf[7+4*i : 9+4*i]) 216 | } 217 | m := buf[5+n*4] 218 | for i := uint8(0); i < m; i++ { 219 | sifd.CompressionPatterns[i] = buf[6+n*4+i] 220 | } 221 | return nil 222 | } 223 | 224 | func (sifd *StillImageFrameDescriptor) isStreamingInterface() {} 225 | 226 | // ColorMatchingDescriptor as defined in UVC spec 1.5, 3.9.2.6 227 | type ColorMatchingDescriptor struct { 228 | ColorPrimaries uint8 229 | TransferCharacteristics uint8 230 | MatrixCoefficients uint8 231 | } 232 | 233 | func (cmd *ColorMatchingDescriptor) UnmarshalBinary(buf []byte) error { 234 | if len(buf) < int(buf[0]) { 235 | return io.ErrShortBuffer 236 | } 237 | if ClassSpecificDescriptorType(buf[1]) != ClassSpecificDescriptorTypeInterface { 238 | return ErrInvalidDescriptor 239 | } 240 | if VideoStreamingInterfaceDescriptorSubtype(buf[2]) != VideoStreamingInterfaceDescriptorSubtypeColorFormat { 241 | return ErrInvalidDescriptor 242 | } 243 | cmd.ColorPrimaries = buf[3] 244 | cmd.TransferCharacteristics = buf[4] 245 | cmd.MatrixCoefficients = buf[5] 246 | return nil 247 | } 248 | 249 | func (cmd *ColorMatchingDescriptor) isStreamingInterface() {} 250 | -------------------------------------------------------------------------------- /pkg/formats/compression.go: -------------------------------------------------------------------------------- 1 | package formats 2 | 3 | import "github.com/google/uuid" 4 | 5 | type CompressionFormat [16]byte 6 | 7 | var ( 8 | CompressionFormatYUY2 = CompressionFormat(uuid.MustParse("32595559-0000-0010-8000-00AA00389B71")) 9 | CompressionFormatNV12 = CompressionFormat(uuid.MustParse("3231564E-0000-0010-8000-00AA00389B71")) 10 | CompressionFormatM420 = CompressionFormat(uuid.MustParse("3032344D-0000-0010-8000-00AA00389B71")) 11 | CompressionFormatI420 = CompressionFormat(uuid.MustParse("30323449-0000-0010-8000-00AA00389B71")) 12 | ) 13 | -------------------------------------------------------------------------------- /pkg/requests/requests.go: -------------------------------------------------------------------------------- 1 | package requests 2 | 3 | type RequestType uint8 4 | 5 | const ( 6 | RequestTypeVideoInterfaceSetRequest RequestType = 0b00100001 7 | RequestTypeDataEndpointSetRequest RequestType = 0b00100010 8 | RequestTypeVideoInterfaceGetRequest RequestType = 0b10100001 9 | RequestTypeDataEndpointGetRequest RequestType = 0b10100010 10 | ) 11 | 12 | type RequestCode uint8 13 | 14 | const ( 15 | RequestCodeUndefined RequestCode = 0x00 16 | RequestCodeSetCur RequestCode = 0x01 17 | RequestCodeSetCurAll RequestCode = 0x11 18 | RequestCodeGetCur RequestCode = 0x81 19 | RequestCodeGetMin RequestCode = 0x82 20 | RequestCodeGetMax RequestCode = 0x83 21 | RequestCodeGetRes RequestCode = 0x84 22 | RequestCodeGetLen RequestCode = 0x85 23 | RequestCodeGetInfo RequestCode = 0x86 24 | RequestCodeGetDef RequestCode = 0x87 25 | RequestCodeGetCurAll RequestCode = 0x91 26 | RequestCodeGetMinAll RequestCode = 0x92 27 | RequestCodeGetMaxAll RequestCode = 0x93 28 | RequestCodeGetResAll RequestCode = 0x94 29 | RequestCodeGetDefAll RequestCode = 0x97 30 | ) 31 | -------------------------------------------------------------------------------- /pkg/transfers/bulk_reader.go: -------------------------------------------------------------------------------- 1 | package transfers 2 | 3 | /* 4 | #cgo LDFLAGS: -lusb-1.0 5 | #include 6 | #include 7 | 8 | void bulkReaderTransferCallback(struct libusb_transfer *transfer); 9 | */ 10 | import "C" 11 | import ( 12 | "fmt" 13 | "unsafe" 14 | 15 | "github.com/mattn/go-pointer" 16 | ) 17 | 18 | type BulkReader struct { 19 | ctx *C.libusb_context 20 | txReqs []*C.struct_libusb_transfer 21 | 22 | // circular buffer of completed transfers 23 | completedTxReqs []*C.struct_libusb_transfer 24 | head, size int 25 | } 26 | 27 | //export bulkReaderTransferCallback 28 | func bulkReaderTransferCallback(transfer *C.struct_libusb_transfer) { 29 | r := pointer.Restore(transfer.user_data).(*BulkReader) 30 | 31 | r.completedTxReqs[r.head] = transfer 32 | r.head = (r.head + 1) % len(r.completedTxReqs) 33 | if r.size < len(r.completedTxReqs) { 34 | r.size++ 35 | } else { 36 | panic("illegal state") 37 | } 38 | } 39 | 40 | func (si *StreamingInterface) NewBulkReader(endpointAddress uint8, mtu uint32) (*BulkReader, error) { 41 | // the libusb sync api seems to result in some partial reads on some devices so we use the async api 42 | r := &BulkReader{ 43 | ctx: si.ctx, 44 | txReqs: make([]*C.struct_libusb_transfer, 0, 100), 45 | } 46 | for i := 0; i < 100; i++ { 47 | tx := C.libusb_alloc_transfer(0) 48 | if tx == nil { 49 | return nil, fmt.Errorf("libusb_alloc_transfer failed") 50 | } 51 | buf := C.malloc(C.ulong(mtu)) 52 | if buf == nil { 53 | return nil, fmt.Errorf("malloc failed") 54 | } 55 | C.libusb_fill_bulk_transfer( 56 | tx, 57 | (*C.struct_libusb_device_handle)(si.handle), 58 | C.uchar(endpointAddress), 59 | (*C.uchar)(buf), 60 | C.int(mtu), 61 | (*[0]byte)(C.libusb_transfer_cb_fn(C.bulkReaderTransferCallback)), 62 | pointer.Save(r), 63 | 0) 64 | if ret := C.libusb_submit_transfer(tx); ret < 0 { 65 | if i == 0 { 66 | return nil, fmt.Errorf("libusb_submit_transfer failed: %s", C.GoString(C.libusb_error_name(ret))) 67 | } 68 | break 69 | } 70 | r.txReqs = append(r.txReqs, tx) 71 | } 72 | r.completedTxReqs = make([]*C.struct_libusb_transfer, len(r.txReqs)) 73 | return r, nil 74 | } 75 | 76 | func (r *BulkReader) Read(buf []byte) (int, error) { 77 | for r.size == 0 { 78 | // pump events. 79 | C.libusb_handle_events(r.ctx) 80 | } 81 | 82 | activeTx := r.completedTxReqs[(r.head-r.size+len(r.completedTxReqs))%len(r.completedTxReqs)] 83 | r.size-- 84 | n := copy(buf, unsafe.Slice((*byte)(activeTx.buffer), activeTx.actual_length)) 85 | if ret := C.libusb_submit_transfer(activeTx); ret < 0 { 86 | return 0, fmt.Errorf("libusb_submit_transfer failed: %s", C.GoString(C.libusb_error_name(ret))) 87 | } 88 | return n, nil 89 | } 90 | 91 | func (r *BulkReader) Close() error { 92 | for _, t := range r.txReqs { 93 | C.free(unsafe.Pointer(t.buffer)) 94 | C.libusb_free_transfer(t) 95 | } 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /pkg/transfers/frame.go: -------------------------------------------------------------------------------- 1 | package transfers 2 | 3 | /* 4 | #cgo LDFLAGS: -lusb-1.0 5 | #include 6 | #include 7 | */ 8 | import "C" 9 | import ( 10 | "fmt" 11 | "io" 12 | "unsafe" 13 | 14 | "github.com/kevmo314/go-uvc/pkg/descriptors" 15 | ) 16 | 17 | type FrameReader struct { 18 | ctx *C.libusb_context 19 | handle *C.struct_libusb_device_handle 20 | iface *C.struct_libusb_interface 21 | vpcc *descriptors.VideoProbeCommitControl 22 | pr io.Reader 23 | 24 | fid *bool 25 | buffer []byte 26 | size, patch int 27 | } 28 | 29 | type Frame struct { 30 | Payloads []*Payload 31 | index, offset int 32 | } 33 | 34 | // Read reads the payload datas concatenated together. 35 | func (f *Frame) Read(buf []byte) (int, error) { 36 | total := 0 37 | for _, p := range f.Payloads { 38 | total += len(p.Data) 39 | } 40 | n := 0 41 | for n < len(buf) { 42 | if f.index == len(f.Payloads) { 43 | if n == 0 { 44 | return 0, io.EOF 45 | } 46 | return n, nil 47 | } 48 | p := f.Payloads[f.index] 49 | m := copy(buf[n:], p.Data[f.offset:]) 50 | f.offset += m 51 | n += m 52 | if f.offset >= len(p.Data) { 53 | f.index++ 54 | f.offset = 0 55 | } 56 | } 57 | return n, nil 58 | } 59 | 60 | func (si *StreamingInterface) NewFrameReader(endpointAddress uint8, vpcc *descriptors.VideoProbeCommitControl) (*FrameReader, error) { 61 | useIsochronous := si.iface.num_altsetting > 1 62 | if useIsochronous { 63 | altsetting, packetSize, err := findIsochronousAltSetting(si.ctx, si.iface, C.uchar(endpointAddress), vpcc.MaxPayloadTransferSize) 64 | if err != nil { 65 | return nil, err 66 | } 67 | if ret := C.libusb_set_interface_alt_setting(si.handle, C.int(altsetting.bInterfaceNumber), C.int(altsetting.bAlternateSetting)); ret < 0 { 68 | return nil, fmt.Errorf("libusb_set_interface_alt_setting failed: %s", C.GoString(C.libusb_error_name(ret))) 69 | } 70 | packets := min((vpcc.MaxVideoFrameSize+packetSize-1)/packetSize, 128) 71 | ir, err := si.NewIsochronousReader(endpointAddress, packets, packetSize) 72 | if err != nil { 73 | return nil, err 74 | } 75 | return &FrameReader{ 76 | handle: si.handle, 77 | iface: si.iface, 78 | vpcc: vpcc, 79 | pr: ir, 80 | buffer: make([]byte, vpcc.MaxVideoFrameSize), 81 | }, nil 82 | } else { 83 | br, err := si.NewBulkReader(endpointAddress, vpcc.MaxPayloadTransferSize) 84 | if err != nil { 85 | return nil, err 86 | } 87 | return &FrameReader{ 88 | ctx: si.ctx, 89 | handle: si.handle, 90 | iface: si.iface, 91 | vpcc: vpcc, 92 | pr: br, 93 | buffer: make([]byte, vpcc.MaxVideoFrameSize), 94 | }, nil 95 | } 96 | } 97 | 98 | func findAltEndpoint(endpoints []C.struct_libusb_endpoint_descriptor, endpointAddress C.uchar) (int, error) { 99 | for i, endpoint := range endpoints { 100 | if endpoint.bEndpointAddress == endpointAddress { 101 | return i, nil 102 | } 103 | } 104 | return 0, fmt.Errorf("endpoint not found") 105 | } 106 | 107 | func getEndpointMaxPacketSize(ctx *C.struct_libusb_context, endpoint C.struct_libusb_endpoint_descriptor) uint32 { 108 | var ssdesc *C.struct_libusb_ss_endpoint_companion_descriptor 109 | ret := C.libusb_get_ss_endpoint_companion_descriptor(ctx, &endpoint, &ssdesc) 110 | if ret == 0 { 111 | defer C.libusb_free_ss_endpoint_companion_descriptor(ssdesc) 112 | return uint32(ssdesc.wBytesPerInterval) 113 | } 114 | val := endpoint.wMaxPacketSize & 0x07ff 115 | endpointType := endpoint.bmAttributes & 0x03 116 | if endpointType == C.LIBUSB_TRANSFER_TYPE_ISOCHRONOUS || endpointType == C.LIBUSB_TRANSFER_TYPE_INTERRUPT { 117 | val *= 1 + ((val >> 1) & 3) 118 | } 119 | return uint32(val) 120 | 121 | } 122 | 123 | // findIsochronousAltSetting sets the isochronous alternate setting for the given interface and endpoint address to the 124 | // first alternate setting that has a max packet size of at least mtu. 125 | // 126 | // UVC spec 1.5, section 2.4.3: A typical use of alternate settings is to provide a way to change the bandwidth requirements an active 127 | // isochronous pipe imposes on the USB. 128 | func findIsochronousAltSetting(ctx *C.struct_libusb_context, iface *C.struct_libusb_interface, endpointAddress C.uchar, payloadSize uint32) (*C.struct_libusb_interface_descriptor, uint32, error) { 129 | altsettings := unsafe.Slice(iface.altsetting, iface.num_altsetting) 130 | for i, altsetting := range altsettings { 131 | if altsetting.bNumEndpoints == 0 { 132 | // UVC spec 1.5, section 2.4.3: All devices that transfer isochronous video data must 133 | // incorporate a zero-bandwidth alternate setting for each VideoStreaming interface that has an 134 | // isochronous video endpoint, and it must be the default alternate setting (alternate setting zero). 135 | // 136 | // in other words, if there aren't any endpoints on this alternate setting it's reserved for a zero-bandwidth 137 | // alternate setting so we can't use it and should skip it. 138 | continue 139 | } 140 | endpoints := unsafe.Slice(altsetting.endpoint, altsetting.bNumEndpoints) 141 | 142 | j, err := findAltEndpoint(endpoints, endpointAddress) 143 | if err != nil { 144 | return nil, 0, err 145 | } 146 | 147 | packetSize := getEndpointMaxPacketSize(ctx, endpoints[j]) 148 | if packetSize >= payloadSize || i == len(altsettings)-1 { 149 | return &altsetting, packetSize, nil 150 | } 151 | } 152 | panic("invalid state") 153 | } 154 | 155 | // ReadFrame reads individual payloads from the USB device and returns a constructed frame. 156 | func (r *FrameReader) ReadFrame() (*Frame, error) { 157 | var f *Frame 158 | for { 159 | p := &Payload{} 160 | n := 0 161 | if r.patch == 0 { 162 | m, err := r.pr.Read(r.buffer[r.size:]) 163 | if err != nil { 164 | return nil, err 165 | } 166 | n = m 167 | if err := p.UnmarshalBinary(r.buffer[r.size : r.size+n]); err != nil { 168 | return nil, err 169 | } 170 | } else { 171 | if copy(r.buffer, r.buffer[r.size:r.size+r.patch]) != r.patch { 172 | return nil, fmt.Errorf("copy failed") 173 | } 174 | r.size = r.patch 175 | n = r.patch 176 | r.patch = 0 177 | if err := p.UnmarshalBinary(r.buffer[:r.size]); err != nil { 178 | return nil, err 179 | } 180 | } 181 | if r.fid == nil || p.FrameID() != *r.fid { 182 | // frame id bit flipped, this is a new frame 183 | if f != nil { 184 | // set the patch to the size of the payload to indicate that 185 | // the next payload should read from the existing buffer. 186 | r.patch = n 187 | return f, nil 188 | } 189 | f = &Frame{} 190 | fid := p.FrameID() 191 | r.fid = &fid 192 | } 193 | if f == nil { 194 | // if there's no frame, ignore this payload. 195 | // this can happen if the device sends frames after an end of frame bit. 196 | continue 197 | } 198 | r.size += n 199 | f.Payloads = append(f.Payloads, p) 200 | if p.EndOfFrame() { 201 | // reset the buffer 202 | r.size = 0 203 | return f, nil 204 | } 205 | } 206 | } 207 | 208 | func (r *FrameReader) Close() error { 209 | if ret := C.libusb_release_interface(r.handle, C.int(r.iface.altsetting.bInterfaceNumber)); ret < 0 { 210 | return fmt.Errorf("libusb_release_interface failed: %s", C.GoString(C.libusb_error_name(ret))) 211 | } 212 | return nil 213 | } 214 | -------------------------------------------------------------------------------- /pkg/transfers/isochronous_reader.go: -------------------------------------------------------------------------------- 1 | package transfers 2 | 3 | /* 4 | #cgo LDFLAGS: -lusb-1.0 5 | #include 6 | #include 7 | 8 | void isochronousReaderTransferCallback(struct libusb_transfer *transfer); 9 | */ 10 | import "C" 11 | import ( 12 | "fmt" 13 | "io" 14 | "unsafe" 15 | 16 | "github.com/mattn/go-pointer" 17 | ) 18 | 19 | //export isochronousReaderTransferCallback 20 | func isochronousReaderTransferCallback(transfer *C.struct_libusb_transfer) { 21 | r := pointer.Restore(transfer.user_data).(*IsochronousReader) 22 | 23 | r.completedTxReqs[r.head] = transfer 24 | r.head = (r.head + 1) % len(r.completedTxReqs) 25 | if r.size < len(r.completedTxReqs) { 26 | r.size++ 27 | } else { 28 | panic("illegal state") 29 | } 30 | } 31 | 32 | type IsochronousReader struct { 33 | ctx *C.libusb_context 34 | // reference to all the transfers 35 | txReqs []*C.struct_libusb_transfer 36 | 37 | // circular buffer of completed transfers 38 | completedTxReqs []*C.struct_libusb_transfer 39 | head, size int 40 | index int 41 | } 42 | 43 | func (si *StreamingInterface) NewIsochronousReader(endpointAddress uint8, packets, packetSize uint32) (*IsochronousReader, error) { 44 | r := &IsochronousReader{ 45 | ctx: si.ctx, 46 | } 47 | for i := 0; i < 100; i++ { 48 | tx := C.libusb_alloc_transfer(C.int(packets)) 49 | if tx == nil { 50 | return nil, fmt.Errorf("libusb_alloc_transfer failed") 51 | } 52 | buf := C.malloc(C.ulong(packets * packetSize)) 53 | if buf == nil { 54 | return nil, fmt.Errorf("malloc failed") 55 | } 56 | C.libusb_fill_iso_transfer( 57 | tx, 58 | (*C.struct_libusb_device_handle)(si.handle), 59 | C.uchar(endpointAddress), 60 | (*C.uchar)(buf), 61 | C.int(packets*packetSize), 62 | C.int(packets), 63 | (*[0]byte)(C.libusb_transfer_cb_fn(C.isochronousReaderTransferCallback)), 64 | pointer.Save(r), 65 | 0) 66 | C.libusb_set_iso_packet_lengths(tx, C.uint(packetSize)) 67 | if ret := C.libusb_submit_transfer(tx); ret < 0 { 68 | if i == 0 { 69 | return nil, fmt.Errorf("libusb_submit_transfer failed: %s", C.GoString(C.libusb_error_name(ret))) 70 | } 71 | break 72 | } 73 | r.txReqs = append(r.txReqs, tx) 74 | } 75 | r.completedTxReqs = make([]*C.struct_libusb_transfer, len(r.txReqs)) 76 | return r, nil 77 | } 78 | 79 | func (r *IsochronousReader) Read(buf []byte) (int, error) { 80 | for { 81 | for r.size == 0 { 82 | // pump events. 83 | if ret := C.libusb_handle_events(r.ctx); ret < 0 { 84 | return 0, fmt.Errorf("libusb_handle_events failed: %d", ret) 85 | } 86 | } 87 | 88 | activeTx := r.completedTxReqs[(r.head-r.size+len(r.completedTxReqs))%len(r.completedTxReqs)] 89 | descs := unsafe.Slice(unsafe.SliceData(activeTx.iso_packet_desc[:]), activeTx.num_iso_packets) 90 | if r.index == len(descs) { 91 | // this tx is done, get the next one. 92 | r.size-- 93 | r.index = 0 94 | if ret := C.libusb_submit_transfer(activeTx); ret < 0 { 95 | return 0, fmt.Errorf("libusb_submit_transfer failed: %s", C.GoString(C.libusb_error_name(ret))) 96 | } 97 | continue 98 | } 99 | pkt := descs[r.index] 100 | if pkt.status != C.LIBUSB_TRANSFER_COMPLETED { 101 | return 0, fmt.Errorf("libusb_iso_transfer failed: %d", pkt.status) 102 | } 103 | if pkt.actual_length == 0 { 104 | r.index++ 105 | continue 106 | } 107 | if len(buf) < int(pkt.actual_length) { 108 | return 0, io.ErrShortBuffer 109 | } 110 | pktbuf := C.libusb_get_iso_packet_buffer_simple(activeTx, C.uint(r.index)) 111 | r.index++ 112 | return copy(buf, unsafe.Slice((*byte)(pktbuf), int(pkt.actual_length))), nil 113 | } 114 | } 115 | 116 | func (r *IsochronousReader) Close() error { 117 | for _, t := range r.txReqs { 118 | C.free(unsafe.Pointer(t.buffer)) 119 | C.libusb_free_transfer(t) 120 | } 121 | return nil 122 | } 123 | -------------------------------------------------------------------------------- /pkg/transfers/payload.go: -------------------------------------------------------------------------------- 1 | package transfers 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | "io" 7 | ) 8 | 9 | type Payload struct { 10 | HeaderInfoBitmask uint8 11 | PTS uint32 12 | SCR struct { 13 | SourceTimeClock uint32 14 | TokenCounter uint16 15 | } 16 | Data []byte 17 | } 18 | 19 | func (f *Payload) FrameID() bool { 20 | return f.HeaderInfoBitmask&0b00000001 != 0 21 | } 22 | 23 | func (f *Payload) EndOfFrame() bool { 24 | return f.HeaderInfoBitmask&0b00000010 != 0 25 | } 26 | 27 | func (f *Payload) HasPTS() bool { 28 | return f.HeaderInfoBitmask&0b00000100 != 0 29 | } 30 | 31 | func (f *Payload) HasSCR() bool { 32 | return f.HeaderInfoBitmask&0b00001000 != 0 33 | } 34 | 35 | func (f *Payload) PayloadSpecificBit() bool { 36 | return f.HeaderInfoBitmask&0b00010000 != 0 37 | } 38 | 39 | func (f *Payload) StillImage() bool { 40 | return f.HeaderInfoBitmask&0b00100000 != 0 41 | } 42 | 43 | func (f *Payload) Error() bool { 44 | return f.HeaderInfoBitmask&0b01000000 != 0 45 | } 46 | 47 | func (f *Payload) EndOfHeader() bool { 48 | return f.HeaderInfoBitmask&0b10000000 != 0 49 | } 50 | 51 | func (f *Payload) UnmarshalBinary(buf []byte) error { 52 | if len(buf) < int(buf[0]) { 53 | return io.ErrShortBuffer 54 | } 55 | f.HeaderInfoBitmask = buf[1] 56 | offset := 2 57 | if f.HasPTS() { 58 | f.PTS = binary.LittleEndian.Uint32(buf[offset : offset+4]) 59 | offset += 4 60 | } 61 | if f.HasSCR() { 62 | f.SCR.SourceTimeClock = binary.LittleEndian.Uint32(buf[offset : offset+4]) 63 | offset += 4 64 | f.SCR.TokenCounter = binary.LittleEndian.Uint16(buf[offset : offset+2]) 65 | offset += 2 66 | } 67 | f.Data = buf[offset:] 68 | return nil 69 | } 70 | 71 | func (f *Payload) String() string { 72 | if len(f.Data) > 16 { 73 | return fmt.Sprintf("Payload{Header: %08b, PTS: %d, SCR: %#v, Data (%d): %x...%x}", f.HeaderInfoBitmask, f.PTS, f.SCR, len(f.Data), f.Data[:16], f.Data[len(f.Data)-16:]) 74 | } else { 75 | return fmt.Sprintf("Payload{Header: %08b, PTS: %d, SCR: %#v, Data (%d): %x}", f.HeaderInfoBitmask, f.PTS, f.SCR, len(f.Data), f.Data) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /pkg/transfers/streaming_interface.go: -------------------------------------------------------------------------------- 1 | package transfers 2 | 3 | import ( 4 | "fmt" 5 | "unsafe" 6 | 7 | "github.com/kevmo314/go-uvc/pkg/descriptors" 8 | "github.com/kevmo314/go-uvc/pkg/requests" 9 | ) 10 | 11 | /* 12 | #cgo LDFLAGS: -lusb-1.0 13 | #include 14 | #include 15 | */ 16 | import "C" 17 | 18 | type VideoStreamingInterfaceControlSelector int 19 | 20 | const ( 21 | VideoStreamingInterfaceControlSelectorUndefined VideoStreamingInterfaceControlSelector = 0x00 22 | VideoStreamingInterfaceControlSelectorProbeControl = 0x01 23 | VideoStreamingInterfaceControlSelectorCommitControl = 0x02 24 | VideoStreamingInterfaceControlSelectorStillProbeControl = 0x03 25 | VideoStreamingInterfaceControlSelectorStillCommitControl = 0x04 26 | VideoStreamingInterfaceControlSelectorStillImageTriggerControl = 0x05 27 | VideoStreamingInterfaceControlSelectorStreamErrorCodeControl = 0x06 28 | VideoStreamingInterfaceControlSelectorGenerateKeyFrameControl = 0x07 29 | VideoStreamingInterfaceControlSelectorUpdateFrameSegmentControl = 0x08 30 | VideoStreamingInterfaceControlSelectorSynchDelayControl = 0x09 31 | ) 32 | 33 | type StreamingInterface struct { 34 | bcdUVC uint16 // cached since it's used a lot 35 | ctx *C.libusb_context 36 | handle *C.struct_libusb_device_handle 37 | iface *C.struct_libusb_interface 38 | Descriptors []descriptors.StreamingInterface 39 | } 40 | 41 | func NewStreamingInterface(ctxp, handlep, ifacep unsafe.Pointer, bcdUVC uint16) *StreamingInterface { 42 | ctx := (*C.struct_libusb_context)(ctxp) 43 | handle := (*C.struct_libusb_device_handle)(handlep) 44 | iface := (*C.struct_libusb_interface)(ifacep) 45 | return &StreamingInterface{ctx: ctx, handle: handle, iface: iface, bcdUVC: bcdUVC} 46 | } 47 | 48 | func (si *StreamingInterface) InterfaceNumber() uint8 { 49 | return uint8(si.iface.altsetting.bInterfaceNumber) 50 | } 51 | 52 | func (si *StreamingInterface) UVCVersionString() string { 53 | return fmt.Sprintf("%x.%02x", si.bcdUVC>>8, si.bcdUVC&0xff) 54 | } 55 | 56 | func (si *StreamingInterface) FormatDescriptors() []descriptors.FormatDescriptor { 57 | var descs []descriptors.FormatDescriptor 58 | for _, desc := range si.Descriptors { 59 | if d, ok := desc.(descriptors.FormatDescriptor); ok { 60 | descs = append(descs, d) 61 | } 62 | } 63 | return descs 64 | } 65 | 66 | func (si *StreamingInterface) FrameDescriptors() []descriptors.FrameDescriptor { 67 | var descs []descriptors.FrameDescriptor 68 | for _, desc := range si.Descriptors { 69 | if d, ok := desc.(descriptors.FrameDescriptor); ok { 70 | descs = append(descs, d) 71 | } 72 | } 73 | return descs 74 | } 75 | 76 | func (si *StreamingInterface) InputHeaderDescriptors() []*descriptors.InputHeaderDescriptor { 77 | var descs []*descriptors.InputHeaderDescriptor 78 | for _, desc := range si.Descriptors { 79 | if d, ok := desc.(*descriptors.InputHeaderDescriptor); ok { 80 | descs = append(descs, d) 81 | } 82 | } 83 | return descs 84 | } 85 | 86 | func (si *StreamingInterface) ClaimFrameReader(formatIndex, frameIndex uint8) (*FrameReader, error) { 87 | ifnum := si.iface.altsetting.bInterfaceNumber 88 | 89 | // claim the control interface 90 | if ret := C.libusb_detach_kernel_driver(si.handle, C.int(ifnum)); ret < 0 { 91 | // return nil, fmt.Errorf("libusb_detach_kernel_driver failed: %s", C.GoString(C.libusb_error_name(ret))) 92 | } 93 | if ret := C.libusb_claim_interface(si.handle, C.int(ifnum)); ret < 0 { 94 | return nil, fmt.Errorf("libusb_claim_interface failed: %s", C.GoString(C.libusb_error_name(ret))) 95 | } 96 | vpcc := &descriptors.VideoProbeCommitControl{} 97 | size := 48 98 | 99 | buf := C.malloc(C.ulong(size)) 100 | defer C.free(buf) 101 | 102 | // get the bounds 103 | if ret := C.libusb_control_transfer( 104 | si.handle, 105 | C.uint8_t(requests.RequestTypeVideoInterfaceGetRequest), /* bmRequestType */ 106 | C.uint8_t(requests.RequestCodeGetMax), /* bRequest */ 107 | VideoStreamingInterfaceControlSelectorProbeControl<<8, /* wValue */ 108 | C.uint16_t(ifnum), /* wIndex */ 109 | (*C.uchar)(buf), /* data */ 110 | C.uint16_t(size), /* len */ 111 | 0, /* timeout */ 112 | ); ret < 0 { 113 | return nil, fmt.Errorf("libusb_control_transfer failed: %s", C.GoString(C.libusb_error_name(ret))) 114 | } 115 | 116 | // assign the values 117 | if err := vpcc.UnmarshalBinary(unsafe.Slice((*byte)(buf), size)); err != nil { 118 | return nil, err 119 | } 120 | 121 | vpcc.FormatIndex = formatIndex 122 | vpcc.FrameIndex = frameIndex 123 | 124 | if err := vpcc.MarshalInto(unsafe.Slice((*byte)(buf), size)); err != nil { 125 | return nil, err 126 | } 127 | 128 | // call set 129 | if ret := C.libusb_control_transfer( 130 | si.handle, 131 | C.uint8_t(requests.RequestTypeVideoInterfaceSetRequest), /* bmRequestType */ 132 | C.uint8_t(requests.RequestCodeSetCur), /* bRequest */ 133 | VideoStreamingInterfaceControlSelectorProbeControl<<8, /* wValue */ 134 | C.uint16_t(ifnum), /* wIndex */ 135 | (*C.uchar)(buf), /* data */ 136 | C.uint16_t(size), /* len */ 137 | 0, /* timeout */ 138 | ); ret < 0 { 139 | return nil, fmt.Errorf("libusb_control_transfer failed: %s", C.GoString(C.libusb_error_name(ret))) 140 | } 141 | 142 | // call get to get the negotiated values 143 | if ret := C.libusb_control_transfer( 144 | si.handle, 145 | C.uint8_t(requests.RequestTypeVideoInterfaceGetRequest), /* bmRequestType */ 146 | C.uint8_t(requests.RequestCodeGetCur), /* bRequest */ 147 | VideoStreamingInterfaceControlSelectorProbeControl<<8, /* wValue */ 148 | C.uint16_t(ifnum), /* wIndex */ 149 | (*C.uchar)(buf), /* data */ 150 | C.uint16_t(size), /* len */ 151 | 0, /* timeout */ 152 | ); ret < 0 { 153 | return nil, fmt.Errorf("libusb_control_transfer failed: %s", C.GoString(C.libusb_error_name(ret))) 154 | } 155 | 156 | // perform a commit set 157 | if ret := C.libusb_control_transfer( 158 | si.handle, 159 | C.uint8_t(requests.RequestTypeVideoInterfaceSetRequest), /* bmRequestType */ 160 | C.uint8_t(requests.RequestCodeSetCur), /* bRequest */ 161 | VideoStreamingInterfaceControlSelectorCommitControl<<8, /* wValue */ 162 | C.uint16_t(ifnum), /* wIndex */ 163 | (*C.uchar)(buf), /* data */ 164 | C.uint16_t(size), /* len */ 165 | 0, /* timeout */ 166 | ); ret < 0 { 167 | return nil, fmt.Errorf("libusb_control_transfer failed: %s", C.GoString(C.libusb_error_name(ret))) 168 | } 169 | 170 | // unmarshal the negotiated values 171 | if err := vpcc.UnmarshalBinary(unsafe.Slice((*byte)(buf), size)); err != nil { 172 | return nil, err 173 | } 174 | 175 | inputs := si.InputHeaderDescriptors() 176 | if len(inputs) == 0 { 177 | return nil, fmt.Errorf("no input header descriptors found") 178 | } 179 | endpointAddress := inputs[0].EndpointAddress // take the first input header. TODO: should we select an input header? 180 | 181 | return si.NewFrameReader(endpointAddress, vpcc) 182 | } 183 | -------------------------------------------------------------------------------- /processing_unit.go: -------------------------------------------------------------------------------- 1 | package uvc 2 | 3 | import ( 4 | "fmt" 5 | "unsafe" 6 | 7 | "github.com/kevmo314/go-uvc/pkg/descriptors" 8 | "github.com/kevmo314/go-uvc/pkg/requests" 9 | ) 10 | 11 | /* 12 | #cgo LDFLAGS: -lusb-1.0 13 | #include 14 | #include 15 | */ 16 | import "C" 17 | 18 | var puControls = []descriptors.ProcessingUnitControlDescriptor{ 19 | &descriptors.BacklightCompensationControl{}, 20 | &descriptors.BrightnessControl{}, 21 | &descriptors.ContrastControl{}, 22 | &descriptors.ContrastAutoControl{}, 23 | &descriptors.GainControl{}, 24 | &descriptors.PowerLineFrequencyControl{}, 25 | &descriptors.HueControl{}, 26 | &descriptors.HueAutoControl{}, 27 | &descriptors.SaturationControl{}, 28 | &descriptors.SharpnessControl{}, 29 | &descriptors.GammaControl{}, 30 | &descriptors.WhiteBalanceTemperatureControl{}, 31 | &descriptors.WhiteBalanceTemperatureAutoControl{}, 32 | &descriptors.WhiteBalanceComponentControl{}, 33 | &descriptors.WhiteBalanceComponentAutoControl{}, 34 | &descriptors.DigitalMultiplerControl{}, 35 | &descriptors.DigitalMultiplerLimitControl{}, 36 | &descriptors.AnalogVideoStandardControl{}, 37 | &descriptors.AnalogVideoLockStatusControl{}, 38 | } 39 | 40 | type ProcessingUnit struct { 41 | usb *C.struct_libusb_interface 42 | deviceHandle *C.struct_libusb_device_handle 43 | UnitDescriptor *descriptors.ProcessingUnitDescriptor 44 | } 45 | 46 | func (pu *ProcessingUnit) GetSupportedControls() []descriptors.ProcessingUnitControlDescriptor { 47 | var supportedControls []descriptors.ProcessingUnitControlDescriptor 48 | 49 | for _, desc := range puControls { 50 | if pu.IsControlRequestSupported(desc) { 51 | supportedControls = append(supportedControls, desc) 52 | } 53 | } 54 | return supportedControls 55 | } 56 | 57 | func (pu *ProcessingUnit) IsControlRequestSupported(desc descriptors.ProcessingUnitControlDescriptor) bool { 58 | byteIndex := desc.FeatureBit() / 8 59 | bitIndex := desc.FeatureBit() % 8 60 | 61 | // Support devices that follow older UVC versions (PUD lenght 10+n vs 13). See UVC 1.1 62 | if byteIndex >= len(pu.UnitDescriptor.ControlsBitmask) { 63 | return false 64 | } 65 | 66 | return (pu.UnitDescriptor.ControlsBitmask[byteIndex] & (1 << bitIndex)) != 0 67 | } 68 | 69 | func (pu *ProcessingUnit) Get(desc descriptors.ProcessingUnitControlDescriptor) error { 70 | ifnum := pu.usb.altsetting.bInterfaceNumber 71 | 72 | bufLen := 16 73 | buf := C.malloc(C.ulong(bufLen)) 74 | defer C.free(buf) 75 | 76 | if ret := C.libusb_control_transfer( 77 | pu.deviceHandle, 78 | C.uint8_t(requests.RequestTypeVideoInterfaceGetRequest), 79 | C.uint8_t(requests.RequestCodeGetCur), 80 | C.uint16_t(desc.Value()<<8), 81 | C.uint16_t(uint16(pu.UnitDescriptor.UnitID)<<8|uint16(ifnum)), 82 | (*C.uchar)(buf), 83 | C.uint16_t(bufLen), 84 | 0, 85 | ); ret < 0 { 86 | return fmt.Errorf("libusb_control_transfer failed: %w", libusberror(ret)) 87 | } 88 | 89 | if err := desc.UnmarshalBinary(unsafe.Slice((*byte)(buf), bufLen)); err != nil { 90 | return err 91 | } 92 | 93 | return nil 94 | } 95 | 96 | func (pu *ProcessingUnit) Set(desc descriptors.ProcessingUnitControlDescriptor) error { 97 | ifnum := pu.usb.altsetting.bInterfaceNumber 98 | 99 | buf, err := desc.MarshalBinary() 100 | if err != nil { 101 | return err 102 | } 103 | 104 | cPtr := (*C.uchar)(C.CBytes(buf)) 105 | defer C.free(unsafe.Pointer(cPtr)) 106 | 107 | if ret := C.libusb_control_transfer( 108 | pu.deviceHandle, 109 | C.uint8_t(requests.RequestTypeVideoInterfaceSetRequest), 110 | C.uint8_t(requests.RequestCodeSetCur), 111 | C.uint16_t(desc.Value()<<8), 112 | C.uint16_t(uint16(pu.UnitDescriptor.UnitID)<<8|uint16(ifnum)), 113 | (*C.uchar)(cPtr), 114 | C.uint16_t(len(buf)), 115 | 0, 116 | ); ret < 0 { 117 | return fmt.Errorf("libusb_control_transfer failed: %w", libusberror(ret)) 118 | } 119 | 120 | return nil 121 | } 122 | -------------------------------------------------------------------------------- /uvc.go: -------------------------------------------------------------------------------- 1 | package uvc 2 | 3 | /* 4 | #cgo LDFLAGS: -lusb-1.0 5 | #include 6 | #include 7 | */ 8 | import "C" 9 | import ( 10 | "fmt" 11 | "sync/atomic" 12 | "unsafe" 13 | 14 | "github.com/kevmo314/go-uvc/pkg/descriptors" 15 | "github.com/kevmo314/go-uvc/pkg/transfers" 16 | ) 17 | 18 | type UVCDevice struct { 19 | usbctx *C.libusb_context 20 | handle *C.libusb_device_handle 21 | device *C.libusb_device 22 | closed *atomic.Bool 23 | } 24 | 25 | func NewUVCDevice(fd uintptr) (*UVCDevice, error) { 26 | dev := &UVCDevice{closed: &atomic.Bool{}} 27 | if ret := C.libusb_init(&dev.usbctx); ret < 0 { 28 | return nil, fmt.Errorf("libusb_init_context failed: %d", libusberror(ret)) 29 | } 30 | if ret := C.libusb_wrap_sys_device(dev.usbctx, C.intptr_t(fd), &dev.handle); ret < 0 { 31 | return nil, fmt.Errorf("libusb_wrap_sys_device failed: %d", libusberror(ret)) 32 | } 33 | if dev.device = C.libusb_get_device(dev.handle); dev.device == nil { 34 | return nil, fmt.Errorf("libusb_get_device failed") 35 | } 36 | // TODO: libuvc appears to check if the interrupt endpoint is readable, is that necessary? 37 | 38 | return dev, nil 39 | } 40 | 41 | func (d *UVCDevice) IsTISCamera() (bool, error) { 42 | var desc C.struct_libusb_device_descriptor 43 | if ret := C.libusb_get_device_descriptor(d.device, &desc); ret < 0 { 44 | return false, fmt.Errorf("libusb_get_device_descriptor failed: %d", libusberror(ret)) 45 | } 46 | return desc.idVendor == 0x199e && (desc.idProduct == 0x8101 || desc.idProduct == 0x8102), nil 47 | } 48 | 49 | func (d *UVCDevice) Close() error { 50 | d.closed.Store(true) 51 | return nil 52 | } 53 | 54 | type ControlInterface struct { 55 | CameraTerminal *CameraTerminal 56 | ProcessingUnit *ProcessingUnit 57 | Descriptor descriptors.ControlInterface 58 | } 59 | 60 | type DeviceInfo struct { 61 | bcdUVC uint16 // cached since it's used a lot 62 | deviceHandle *C.struct_libusb_device_handle 63 | configDesc *C.struct_libusb_config_descriptor 64 | ControlInterfaces []*ControlInterface 65 | StreamingInterfaces []*transfers.StreamingInterface 66 | } 67 | 68 | func (d *UVCDevice) DeviceInfo() (*DeviceInfo, error) { 69 | var configDesc *C.struct_libusb_config_descriptor 70 | if ret := C.libusb_get_config_descriptor(d.device, 0, &configDesc); ret < 0 { 71 | return nil, fmt.Errorf("libusb_get_active_config_descriptor failed: %d", libusberror(ret)) 72 | } 73 | 74 | // scan control interfaces 75 | isTISCamera, err := d.IsTISCamera() 76 | if err != nil { 77 | return nil, err 78 | } 79 | ifaceIdx := -1 80 | ifaces := unsafe.Slice(configDesc._interface, configDesc.bNumInterfaces) 81 | for i, iface := range ifaces { 82 | if isTISCamera && iface.altsetting.bInterfaceClass == 255 && iface.altsetting.bInterfaceSubClass == 1 { 83 | ifaceIdx = i 84 | break 85 | } else if !isTISCamera && iface.altsetting.bInterfaceClass == 14 && iface.altsetting.bInterfaceSubClass == 1 { 86 | ifaceIdx = i 87 | break 88 | } 89 | } 90 | if ifaceIdx == -1 { 91 | return nil, fmt.Errorf("control interface not found") 92 | } 93 | info := &DeviceInfo{deviceHandle: d.handle, configDesc: configDesc} 94 | 95 | videoInterface := &ifaces[ifaceIdx] 96 | 97 | vcbuf := unsafe.Slice((*byte)(videoInterface.altsetting.extra), videoInterface.altsetting.extra_length) 98 | 99 | for i := 0; i != len(vcbuf); i += int(vcbuf[i]) { 100 | block := vcbuf[i : i+int(vcbuf[i])] 101 | if block[1] != 0x24 { 102 | // ignore blocks that are not CS_INTERFACE 0x24 103 | continue 104 | } 105 | ci, err := descriptors.UnmarshalControlInterface(block) 106 | if err != nil { 107 | return nil, err 108 | } 109 | switch ci := ci.(type) { 110 | case *descriptors.ProcessingUnitDescriptor: 111 | processingUnit := &ProcessingUnit{ 112 | usb: &ifaces[ifaceIdx], 113 | deviceHandle: d.handle, 114 | UnitDescriptor: ci, 115 | } 116 | info.ControlInterfaces = append(info.ControlInterfaces, &ControlInterface{ProcessingUnit: processingUnit, Descriptor: ci}) 117 | case *descriptors.InputTerminalDescriptor: 118 | it, err := descriptors.UnmarshalInputTerminal(block) 119 | if err != nil { 120 | return nil, err 121 | } 122 | 123 | switch descriptor := it.(type) { 124 | case *descriptors.CameraTerminalDescriptor: 125 | camera := &CameraTerminal{ 126 | usb: &ifaces[ifaceIdx], 127 | deviceHandle: d.handle, 128 | CameraDescriptor: descriptor, 129 | } 130 | info.ControlInterfaces = append(info.ControlInterfaces, &ControlInterface{CameraTerminal: camera, Descriptor: descriptor}) 131 | } 132 | case *descriptors.HeaderDescriptor: 133 | info.bcdUVC = ci.UVC 134 | // pull the streaming interfaces too 135 | for _, i := range ci.VideoStreamingInterfaceIndexes { 136 | vsbuf := unsafe.Slice((*byte)(ifaces[i].altsetting.extra), ifaces[i].altsetting.extra_length) 137 | asi := transfers.NewStreamingInterface(unsafe.Pointer(d.usbctx), unsafe.Pointer(d.handle), unsafe.Pointer(&ifaces[i]), ci.UVC) 138 | for j := 0; j != len(vsbuf); j += int(vsbuf[j]) { 139 | block := vsbuf[j : j+int(vsbuf[j])] 140 | si, err := descriptors.UnmarshalStreamingInterface(block) 141 | if err != nil { 142 | return nil, err 143 | } 144 | asi.Descriptors = append(asi.Descriptors, si) 145 | } 146 | info.StreamingInterfaces = append(info.StreamingInterfaces, asi) 147 | } 148 | default: 149 | // This is an interface that we have not yet parsed 150 | info.ControlInterfaces = append(info.ControlInterfaces, &ControlInterface{Descriptor: ci}) 151 | } 152 | } 153 | 154 | return info, nil 155 | } 156 | 157 | func (d *DeviceInfo) Close() error { 158 | C.libusb_free_config_descriptor(d.configDesc) 159 | return nil 160 | } 161 | -------------------------------------------------------------------------------- /uvc_test.go: -------------------------------------------------------------------------------- 1 | package uvc 2 | 3 | import ( 4 | "image/jpeg" 5 | "log" 6 | "os" 7 | "sync/atomic" 8 | "syscall" 9 | "testing" 10 | 11 | "github.com/hajimehoshi/ebiten/v2" 12 | "github.com/kevmo314/go-uvc/pkg/decode" 13 | "github.com/kevmo314/go-uvc/pkg/descriptors" 14 | ) 15 | 16 | type Display struct { 17 | frame atomic.Value 18 | } 19 | 20 | func (g *Display) Update() error { 21 | return nil 22 | } 23 | 24 | func (g *Display) Draw(screen *ebiten.Image) { 25 | screen.DrawImage(g.frame.Load().(*ebiten.Image), &ebiten.DrawImageOptions{}) 26 | } 27 | 28 | func (g *Display) Layout(outsideWidth, outsideHeight int) (int, int) { 29 | frame := g.frame.Load().(*ebiten.Image) 30 | return frame.Bounds().Dx(), frame.Bounds().Dy() 31 | } 32 | 33 | func TestDeviceInfo(t *testing.T) { 34 | fd, err := syscall.Open("/dev/bus/usb/001/009", syscall.O_RDWR, 0) 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | 39 | ctx, err := NewUVCDevice(uintptr(fd)) 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | 44 | info, err := ctx.DeviceInfo() 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | 49 | // get format descriptors 50 | for _, iface := range info.ControlInterfaces { 51 | log.Printf("got control interface: %#v", iface) 52 | } 53 | 54 | for _, iface := range info.StreamingInterfaces { 55 | for i, desc := range iface.Descriptors { 56 | fd, ok := desc.(*descriptors.MJPEGFormatDescriptor) 57 | if !ok { 58 | continue 59 | } 60 | fr := iface.Descriptors[i+1].(*descriptors.MJPEGFrameDescriptor) 61 | 62 | resp, err := iface.ClaimFrameReader(fd.Index(), fr.Index()) 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | decoder, err := decode.NewFrameReaderDecoder(resp, fd, fr) 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | 71 | g := &Display{} 72 | for i := 0; ; i++ { 73 | img, err := decoder.ReadFrame() 74 | if err != nil { 75 | log.Printf("got error: %s", err) 76 | continue 77 | } 78 | // write fr to a file 79 | log.Printf("got frame: %#v", img.Bounds()) 80 | if g.frame.Swap(ebiten.NewImageFromImage(img)) == nil { 81 | go func() { 82 | if err := ebiten.RunGame(g); err != nil { 83 | panic(err) 84 | } 85 | }() 86 | } 87 | } 88 | } 89 | } 90 | log.Printf("done") 91 | t.Fail() 92 | } 93 | 94 | func TestJPEGDecode(t *testing.T) { 95 | // open frame-1.jpg and decode it 96 | f, err := os.Open("frame-0.jpg") 97 | if err != nil { 98 | t.Fatal(err) 99 | } 100 | defer f.Close() 101 | 102 | img, err := jpeg.Decode(f) 103 | if err != nil { 104 | t.Fatal(err) 105 | } 106 | 107 | log.Printf("got frame: %#v", img.Bounds()) 108 | } 109 | --------------------------------------------------------------------------------