Add support v4l2 source
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/api"
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/app"
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/mjpeg"
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/streams"
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/v4l2"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/shell"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app.Init()
|
||||||
|
streams.Init()
|
||||||
|
|
||||||
|
api.Init()
|
||||||
|
mjpeg.Init()
|
||||||
|
v4l2.Init()
|
||||||
|
|
||||||
|
shell.RunUntilSignal()
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
package v4l2
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
// not supported
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package v4l2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/api"
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/streams"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/v4l2"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/v4l2/device"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
streams.HandleFunc("v4l2", func(source string) (core.Producer, error) {
|
||||||
|
return v4l2.Open(source)
|
||||||
|
})
|
||||||
|
|
||||||
|
api.HandleFunc("api/v4l2", apiV4L2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiV4L2(w http.ResponseWriter, r *http.Request) {
|
||||||
|
files, err := os.ReadDir("/dev")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var sources []*api.Source
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if !strings.HasPrefix(file.Name(), core.KindVideo) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
path := "/dev/" + file.Name()
|
||||||
|
|
||||||
|
dev, err := device.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
formats, _ := dev.ListFormats()
|
||||||
|
for _, fourCC := range formats {
|
||||||
|
source := &api.Source{}
|
||||||
|
|
||||||
|
for _, format := range device.Formats {
|
||||||
|
if format.FourCC == fourCC {
|
||||||
|
source.Name = format.Name
|
||||||
|
source.URL = "v4l2:device?video=" + path + "&input_format=" + format.FFmpeg + "&video_size="
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if source.Name != "" {
|
||||||
|
sizes, _ := dev.ListSizes(fourCC)
|
||||||
|
for i := 0; i < len(sizes); i += 2 {
|
||||||
|
size := fmt.Sprintf("%dx%d", sizes[i], sizes[i+1])
|
||||||
|
if i > 0 {
|
||||||
|
source.Info += " " + size
|
||||||
|
} else {
|
||||||
|
source.Info = size
|
||||||
|
source.URL += size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
source.Name = string(binary.LittleEndian.AppendUint32(nil, fourCC))
|
||||||
|
}
|
||||||
|
|
||||||
|
sources = append(sources, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = dev.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
api.ResponseSources(w, sources)
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/AlexxIT/go2rtc/internal/srtp"
|
"github.com/AlexxIT/go2rtc/internal/srtp"
|
||||||
"github.com/AlexxIT/go2rtc/internal/streams"
|
"github.com/AlexxIT/go2rtc/internal/streams"
|
||||||
"github.com/AlexxIT/go2rtc/internal/tapo"
|
"github.com/AlexxIT/go2rtc/internal/tapo"
|
||||||
|
"github.com/AlexxIT/go2rtc/internal/v4l2"
|
||||||
"github.com/AlexxIT/go2rtc/internal/webrtc"
|
"github.com/AlexxIT/go2rtc/internal/webrtc"
|
||||||
"github.com/AlexxIT/go2rtc/internal/webtorrent"
|
"github.com/AlexxIT/go2rtc/internal/webtorrent"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/shell"
|
"github.com/AlexxIT/go2rtc/pkg/shell"
|
||||||
@@ -84,6 +85,7 @@ func main() {
|
|||||||
expr.Init() // expr source
|
expr.Init() // expr source
|
||||||
gopro.Init() // gopro source
|
gopro.Init() // gopro source
|
||||||
doorbird.Init() // doorbird source
|
doorbird.Init() // doorbird source
|
||||||
|
v4l2.Init() // v4l2 source
|
||||||
|
|
||||||
// 6. Helper modules
|
// 6. Helper modules
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,244 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Device struct {
|
||||||
|
fd int
|
||||||
|
bufs [][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func Open(path string) (*Device, error) {
|
||||||
|
fd, err := syscall.Open(path, syscall.O_RDWR|syscall.O_CLOEXEC, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Device{fd: fd}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const buffersCount = 2
|
||||||
|
|
||||||
|
type Capability struct {
|
||||||
|
Driver string
|
||||||
|
Card string
|
||||||
|
BusInfo string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) Capability() (*Capability, error) {
|
||||||
|
c := v4l2_capability{}
|
||||||
|
if err := ioctl(d.fd, VIDIOC_QUERYCAP, unsafe.Pointer(&c)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Capability{
|
||||||
|
Driver: str(c.driver[:]),
|
||||||
|
Card: str(c.card[:]),
|
||||||
|
BusInfo: str(c.bus_info[:]),
|
||||||
|
Version: fmt.Sprintf("%d.%d.%d", byte(c.version>>16), byte(c.version>>8), byte(c.version)),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) ListFormats() ([]uint32, error) {
|
||||||
|
var items []uint32
|
||||||
|
|
||||||
|
for i := uint32(0); ; i++ {
|
||||||
|
fd := v4l2_fmtdesc{
|
||||||
|
index: i,
|
||||||
|
typ: V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
||||||
|
}
|
||||||
|
if err := ioctl(d.fd, VIDIOC_ENUM_FMT, unsafe.Pointer(&fd)); err != nil {
|
||||||
|
if !errors.Is(err, syscall.EINVAL) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, fd.pixelformat)
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) ListSizes(pixFmt uint32) ([]uint32, error) {
|
||||||
|
var items []uint32
|
||||||
|
|
||||||
|
for i := uint32(0); ; i++ {
|
||||||
|
fs := v4l2_frmsizeenum{
|
||||||
|
index: i,
|
||||||
|
pixel_format: pixFmt,
|
||||||
|
}
|
||||||
|
if err := ioctl(d.fd, VIDIOC_ENUM_FRAMESIZES, unsafe.Pointer(&fs)); err != nil {
|
||||||
|
if !errors.Is(err, syscall.EINVAL) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if fs.typ != V4L2_FRMSIZE_TYPE_DISCRETE {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, fs.discrete.width, fs.discrete.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) ListFrameRates(pixFmt, width, height uint32) ([]uint32, error) {
|
||||||
|
var items []uint32
|
||||||
|
|
||||||
|
for i := uint32(0); ; i++ {
|
||||||
|
fi := v4l2_frmivalenum{
|
||||||
|
index: i,
|
||||||
|
pixel_format: pixFmt,
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
}
|
||||||
|
if err := ioctl(d.fd, VIDIOC_ENUM_FRAMEINTERVALS, unsafe.Pointer(&fi)); err != nil {
|
||||||
|
if !errors.Is(err, syscall.EINVAL) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if fi.typ != V4L2_FRMIVAL_TYPE_DISCRETE || fi.discrete.numerator != 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
items = append(items, fi.discrete.denominator)
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) SetFormat(width, height, pixFmt uint32) error {
|
||||||
|
f := v4l2_format{
|
||||||
|
typ: V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
||||||
|
fmt: v4l2_pix_format{
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
pixelformat: pixFmt,
|
||||||
|
field: V4L2_FIELD_NONE,
|
||||||
|
colorspace: V4L2_COLORSPACE_DEFAULT,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return ioctl(d.fd, VIDIOC_S_FMT, unsafe.Pointer(&f))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) SetParam(fps uint32) error {
|
||||||
|
p := v4l2_streamparm{
|
||||||
|
typ: V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
||||||
|
capture: v4l2_captureparm{
|
||||||
|
timeperframe: v4l2_fract{numerator: 1, denominator: fps},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return ioctl(d.fd, VIDIOC_S_PARM, unsafe.Pointer(&p))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) StreamOn() (err error) {
|
||||||
|
rb := v4l2_requestbuffers{
|
||||||
|
count: buffersCount,
|
||||||
|
typ: V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
||||||
|
memory: V4L2_MEMORY_MMAP,
|
||||||
|
}
|
||||||
|
if err = ioctl(d.fd, VIDIOC_REQBUFS, unsafe.Pointer(&rb)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
d.bufs = make([][]byte, buffersCount)
|
||||||
|
for i := uint32(0); i < buffersCount; i++ {
|
||||||
|
qb := v4l2_buffer{
|
||||||
|
index: i,
|
||||||
|
typ: V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
||||||
|
memory: V4L2_MEMORY_MMAP,
|
||||||
|
}
|
||||||
|
if err = ioctl(d.fd, VIDIOC_QUERYBUF, unsafe.Pointer(&qb)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.bufs[i], err = syscall.Mmap(
|
||||||
|
d.fd, int64(qb.offset), int(qb.length), syscall.PROT_READ, syscall.MAP_SHARED,
|
||||||
|
); nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = ioctl(d.fd, VIDIOC_QBUF, unsafe.Pointer(&qb)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typ := uint32(V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
||||||
|
return ioctl(d.fd, VIDIOC_STREAMON, unsafe.Pointer(&typ))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) StreamOff() (err error) {
|
||||||
|
typ := uint32(V4L2_BUF_TYPE_VIDEO_CAPTURE)
|
||||||
|
if err = ioctl(d.fd, VIDIOC_STREAMOFF, unsafe.Pointer(&typ)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range d.bufs {
|
||||||
|
_ = syscall.Munmap(d.bufs[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
rb := v4l2_requestbuffers{
|
||||||
|
count: 0,
|
||||||
|
typ: V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
||||||
|
memory: V4L2_MEMORY_MMAP,
|
||||||
|
}
|
||||||
|
return ioctl(d.fd, VIDIOC_REQBUFS, unsafe.Pointer(&rb))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) Capture(cositedYUV bool) ([]byte, error) {
|
||||||
|
dec := v4l2_buffer{
|
||||||
|
typ: V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
||||||
|
memory: V4L2_MEMORY_MMAP,
|
||||||
|
}
|
||||||
|
if err := ioctl(d.fd, VIDIOC_DQBUF, unsafe.Pointer(&dec)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, dec.bytesused)
|
||||||
|
if cositedYUV {
|
||||||
|
YUYV2YUV(buf, d.bufs[dec.index][:dec.bytesused])
|
||||||
|
} else {
|
||||||
|
copy(buf, d.bufs[dec.index][:dec.bytesused])
|
||||||
|
}
|
||||||
|
|
||||||
|
enc := v4l2_buffer{
|
||||||
|
typ: V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
||||||
|
memory: V4L2_MEMORY_MMAP,
|
||||||
|
index: dec.index,
|
||||||
|
}
|
||||||
|
if err := ioctl(d.fd, VIDIOC_QBUF, unsafe.Pointer(&enc)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Device) Close() error {
|
||||||
|
return syscall.Close(d.fd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ioctl(fd int, req uint, arg unsafe.Pointer) error {
|
||||||
|
_, _, err := syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(req), uintptr(arg))
|
||||||
|
if err != 0 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func str(b []byte) string {
|
||||||
|
if i := bytes.IndexByte(b, 0); i >= 0 {
|
||||||
|
return string(b[:i])
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package device
|
||||||
|
|
||||||
|
const (
|
||||||
|
V4L2_PIX_FMT_YUYV = 'Y' | 'U'<<8 | 'Y'<<16 | 'V'<<24
|
||||||
|
V4L2_PIX_FMT_MJPEG = 'M' | 'J'<<8 | 'P'<<16 | 'G'<<24
|
||||||
|
)
|
||||||
|
|
||||||
|
type Format struct {
|
||||||
|
FourCC uint32
|
||||||
|
Name string
|
||||||
|
FFmpeg string
|
||||||
|
}
|
||||||
|
|
||||||
|
var Formats = []Format{
|
||||||
|
{V4L2_PIX_FMT_YUYV, "YUV 4:2:2", "yuyv422"},
|
||||||
|
{V4L2_PIX_FMT_MJPEG, "Motion-JPEG", "mjpeg"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// YUYV2YUV convert [Y0 Cb Y1 Cr] to cosited [Y0Y1... Cb... Cr...]
|
||||||
|
func YUYV2YUV(dst, src []byte) {
|
||||||
|
n := len(src)
|
||||||
|
i0 := 0
|
||||||
|
iy := 0
|
||||||
|
iu := n / 2
|
||||||
|
iv := n / 4 * 3
|
||||||
|
for i0 < n {
|
||||||
|
dst[iy] = src[i0]
|
||||||
|
i0++
|
||||||
|
iy++
|
||||||
|
dst[iu] = src[i0]
|
||||||
|
i0++
|
||||||
|
iu++
|
||||||
|
dst[iy] = src[i0]
|
||||||
|
i0++
|
||||||
|
iy++
|
||||||
|
dst[iv] = src[i0]
|
||||||
|
i0++
|
||||||
|
iv++
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package device
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSize(t *testing.T) {
|
||||||
|
switch runtime.GOARCH {
|
||||||
|
case "amd64", "arm64":
|
||||||
|
require.Equal(t, 104, int(unsafe.Sizeof(v4l2_capability{})))
|
||||||
|
require.Equal(t, 208, int(unsafe.Sizeof(v4l2_format{})))
|
||||||
|
require.Equal(t, 204, int(unsafe.Sizeof(v4l2_streamparm{})))
|
||||||
|
require.Equal(t, 20, int(unsafe.Sizeof(v4l2_requestbuffers{})))
|
||||||
|
require.Equal(t, 88, int(unsafe.Sizeof(v4l2_buffer{})))
|
||||||
|
require.Equal(t, 16, int(unsafe.Sizeof(v4l2_timecode{})))
|
||||||
|
require.Equal(t, 64, int(unsafe.Sizeof(v4l2_fmtdesc{})))
|
||||||
|
require.Equal(t, 44, int(unsafe.Sizeof(v4l2_frmsizeenum{})))
|
||||||
|
require.Equal(t, 52, int(unsafe.Sizeof(v4l2_frmivalenum{})))
|
||||||
|
case "386", "arm":
|
||||||
|
require.Equal(t, 104, int(unsafe.Sizeof(v4l2_capability{})))
|
||||||
|
require.Equal(t, 204, int(unsafe.Sizeof(v4l2_format{})))
|
||||||
|
require.Equal(t, 204, int(unsafe.Sizeof(v4l2_streamparm{})))
|
||||||
|
require.Equal(t, 20, int(unsafe.Sizeof(v4l2_requestbuffers{})))
|
||||||
|
require.Equal(t, 68, int(unsafe.Sizeof(v4l2_buffer{})))
|
||||||
|
require.Equal(t, 16, int(unsafe.Sizeof(v4l2_timecode{})))
|
||||||
|
require.Equal(t, 64, int(unsafe.Sizeof(v4l2_fmtdesc{})))
|
||||||
|
require.Equal(t, 44, int(unsafe.Sizeof(v4l2_frmsizeenum{})))
|
||||||
|
require.Equal(t, 52, int(unsafe.Sizeof(v4l2_frmivalenum{})))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
//go:build 386 || arm
|
||||||
|
|
||||||
|
package device
|
||||||
|
|
||||||
|
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/videodev2.h
|
||||||
|
|
||||||
|
const (
|
||||||
|
VIDIOC_QUERYCAP = 0x80685600
|
||||||
|
VIDIOC_ENUM_FMT = 0xc0405602
|
||||||
|
VIDIOC_G_FMT = 0xc0cc5604
|
||||||
|
VIDIOC_S_FMT = 0xc0cc5605
|
||||||
|
VIDIOC_REQBUFS = 0xc0145608
|
||||||
|
VIDIOC_QUERYBUF = 0xc0445609
|
||||||
|
|
||||||
|
VIDIOC_QBUF = 0xc044560f
|
||||||
|
VIDIOC_DQBUF = 0xc0445611
|
||||||
|
VIDIOC_STREAMON = 0x40045612
|
||||||
|
VIDIOC_STREAMOFF = 0x40045613
|
||||||
|
VIDIOC_G_PARM = 0xc0cc5615
|
||||||
|
VIDIOC_S_PARM = 0xc0cc5616
|
||||||
|
|
||||||
|
VIDIOC_ENUM_FRAMESIZES = 0xc02c564a
|
||||||
|
VIDIOC_ENUM_FRAMEINTERVALS = 0xc034564b
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
V4L2_BUF_TYPE_VIDEO_CAPTURE = 1
|
||||||
|
V4L2_COLORSPACE_DEFAULT = 0
|
||||||
|
V4L2_FIELD_NONE = 1
|
||||||
|
V4L2_FRMIVAL_TYPE_DISCRETE = 1
|
||||||
|
V4L2_FRMSIZE_TYPE_DISCRETE = 1
|
||||||
|
V4L2_MEMORY_MMAP = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type v4l2_capability struct {
|
||||||
|
driver [16]byte
|
||||||
|
card [32]byte
|
||||||
|
bus_info [32]byte
|
||||||
|
version uint32
|
||||||
|
capabilities uint32
|
||||||
|
device_caps uint32
|
||||||
|
reserved [3]uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_format struct {
|
||||||
|
typ uint32
|
||||||
|
fmt v4l2_pix_format
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_pix_format struct {
|
||||||
|
width uint32 // 0
|
||||||
|
height uint32 // 4
|
||||||
|
pixelformat uint32 // 8
|
||||||
|
field uint32 // 12
|
||||||
|
bytesperline uint32 // 16
|
||||||
|
sizeimage uint32 // 20
|
||||||
|
colorspace uint32 // 24
|
||||||
|
priv uint32 // 28
|
||||||
|
flags uint32 // 32
|
||||||
|
ycbcr_enc uint32 // 36
|
||||||
|
quantization uint32 // 40
|
||||||
|
xfer_func uint32 // 44
|
||||||
|
|
||||||
|
_ [152]byte // 48
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_streamparm struct {
|
||||||
|
typ uint32
|
||||||
|
capture v4l2_captureparm
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_captureparm struct {
|
||||||
|
capability uint32 // 0
|
||||||
|
capturemode uint32 // 4
|
||||||
|
timeperframe v4l2_fract // 8
|
||||||
|
extendedmode uint32 // 16
|
||||||
|
readbuffers uint32 // 20
|
||||||
|
|
||||||
|
_ [176]byte // 24
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_fract struct {
|
||||||
|
numerator uint32
|
||||||
|
denominator uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_requestbuffers struct {
|
||||||
|
count uint32
|
||||||
|
typ uint32
|
||||||
|
memory uint32
|
||||||
|
capabilities uint32
|
||||||
|
flags uint8
|
||||||
|
reserved [3]uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_buffer struct {
|
||||||
|
index uint32 // 0
|
||||||
|
typ uint32 // 4
|
||||||
|
bytesused uint32 // 8
|
||||||
|
flags uint32 // 12
|
||||||
|
field uint32 // 16
|
||||||
|
_ [8]byte // 20
|
||||||
|
timecode v4l2_timecode // 28
|
||||||
|
sequence uint32 // 44
|
||||||
|
memory uint32 // 48
|
||||||
|
offset uint32 // 52
|
||||||
|
length uint32 // 56
|
||||||
|
_ [8]byte // 60
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_timecode struct {
|
||||||
|
typ uint32
|
||||||
|
flags uint32
|
||||||
|
frames uint8
|
||||||
|
seconds uint8
|
||||||
|
minutes uint8
|
||||||
|
hours uint8
|
||||||
|
userbits [4]uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_fmtdesc struct {
|
||||||
|
index uint32
|
||||||
|
typ uint32
|
||||||
|
flags uint32
|
||||||
|
description [32]byte
|
||||||
|
pixelformat uint32
|
||||||
|
mbus_code uint32
|
||||||
|
reserved [3]uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_frmsizeenum struct {
|
||||||
|
index uint32 // 0
|
||||||
|
pixel_format uint32 // 4
|
||||||
|
typ uint32 // 8
|
||||||
|
discrete v4l2_frmsize_discrete // 12
|
||||||
|
_ [24]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_frmsize_discrete struct {
|
||||||
|
width uint32
|
||||||
|
height uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_frmivalenum struct {
|
||||||
|
index uint32
|
||||||
|
pixel_format uint32
|
||||||
|
width uint32
|
||||||
|
height uint32
|
||||||
|
typ uint32
|
||||||
|
discrete v4l2_fract
|
||||||
|
_ [24]byte
|
||||||
|
}
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
//go:build amd64 || arm64
|
||||||
|
|
||||||
|
package device
|
||||||
|
|
||||||
|
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/videodev2.h
|
||||||
|
|
||||||
|
const (
|
||||||
|
VIDIOC_QUERYCAP = 0x80685600
|
||||||
|
VIDIOC_ENUM_FMT = 0xc0405602
|
||||||
|
VIDIOC_G_FMT = 0xc0d05604
|
||||||
|
VIDIOC_S_FMT = 0xc0d05605
|
||||||
|
VIDIOC_REQBUFS = 0xc0145608
|
||||||
|
VIDIOC_QUERYBUF = 0xc0585609
|
||||||
|
|
||||||
|
VIDIOC_QBUF = 0xc058560f
|
||||||
|
VIDIOC_DQBUF = 0xc0585611
|
||||||
|
VIDIOC_STREAMON = 0x40045612
|
||||||
|
VIDIOC_STREAMOFF = 0x40045613
|
||||||
|
VIDIOC_G_PARM = 0xc0cc5615
|
||||||
|
VIDIOC_S_PARM = 0xc0cc5616
|
||||||
|
|
||||||
|
VIDIOC_ENUM_FRAMESIZES = 0xc02c564a
|
||||||
|
VIDIOC_ENUM_FRAMEINTERVALS = 0xc034564b
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
V4L2_BUF_TYPE_VIDEO_CAPTURE = 1
|
||||||
|
V4L2_COLORSPACE_DEFAULT = 0
|
||||||
|
V4L2_FIELD_NONE = 1
|
||||||
|
V4L2_FRMIVAL_TYPE_DISCRETE = 1
|
||||||
|
V4L2_FRMSIZE_TYPE_DISCRETE = 1
|
||||||
|
V4L2_MEMORY_MMAP = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
type v4l2_capability struct {
|
||||||
|
driver [16]byte
|
||||||
|
card [32]byte
|
||||||
|
bus_info [32]byte
|
||||||
|
version uint32
|
||||||
|
capabilities uint32
|
||||||
|
device_caps uint32
|
||||||
|
reserved [3]uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_format struct {
|
||||||
|
typ uint64
|
||||||
|
fmt v4l2_pix_format
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_pix_format struct {
|
||||||
|
width uint32 // 0
|
||||||
|
height uint32 // 4
|
||||||
|
pixelformat uint32 // 8
|
||||||
|
field uint32 // 12
|
||||||
|
bytesperline uint32 // 16
|
||||||
|
sizeimage uint32 // 20
|
||||||
|
colorspace uint32 // 24
|
||||||
|
priv uint32 // 28
|
||||||
|
flags uint32 // 32
|
||||||
|
ycbcr_enc uint32 // 36
|
||||||
|
quantization uint32 // 40
|
||||||
|
xfer_func uint32 // 44
|
||||||
|
|
||||||
|
_ [152]byte // 48
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_streamparm struct {
|
||||||
|
typ uint32
|
||||||
|
capture v4l2_captureparm
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_captureparm struct {
|
||||||
|
capability uint32 // 0
|
||||||
|
capturemode uint32 // 4
|
||||||
|
timeperframe v4l2_fract // 8
|
||||||
|
extendedmode uint32 // 16
|
||||||
|
readbuffers uint32 // 20
|
||||||
|
|
||||||
|
_ [176]byte // 24
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_fract struct {
|
||||||
|
numerator uint32
|
||||||
|
denominator uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_requestbuffers struct {
|
||||||
|
count uint32
|
||||||
|
typ uint32
|
||||||
|
memory uint32
|
||||||
|
capabilities uint32
|
||||||
|
flags uint8
|
||||||
|
reserved [3]uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_buffer struct {
|
||||||
|
index uint32 // 0
|
||||||
|
typ uint32 // 4
|
||||||
|
bytesused uint32 // 8
|
||||||
|
flags uint32 // 12
|
||||||
|
field uint32 // 16
|
||||||
|
_ [20]byte // 20
|
||||||
|
timecode v4l2_timecode // 40
|
||||||
|
sequence uint32 // 56
|
||||||
|
memory uint32 // 60
|
||||||
|
offset uint32 // 64
|
||||||
|
_ [4]byte // 68
|
||||||
|
length uint32 // 72
|
||||||
|
_ [12]byte // 76
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_timecode struct {
|
||||||
|
typ uint32
|
||||||
|
flags uint32
|
||||||
|
frames uint8
|
||||||
|
seconds uint8
|
||||||
|
minutes uint8
|
||||||
|
hours uint8
|
||||||
|
userbits [4]uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_fmtdesc struct {
|
||||||
|
index uint32
|
||||||
|
typ uint32
|
||||||
|
flags uint32
|
||||||
|
description [32]byte
|
||||||
|
pixelformat uint32
|
||||||
|
mbus_code uint32
|
||||||
|
reserved [3]uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_frmsizeenum struct {
|
||||||
|
index uint32 // 0
|
||||||
|
pixel_format uint32 // 4
|
||||||
|
typ uint32 // 8
|
||||||
|
discrete v4l2_frmsize_discrete // 12
|
||||||
|
_ [24]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_frmsize_discrete struct {
|
||||||
|
width uint32
|
||||||
|
height uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type v4l2_frmivalenum struct {
|
||||||
|
index uint32
|
||||||
|
pixel_format uint32
|
||||||
|
width uint32
|
||||||
|
height uint32
|
||||||
|
typ uint32
|
||||||
|
discrete v4l2_fract
|
||||||
|
_ [24]byte
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package v4l2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/v4l2/device"
|
||||||
|
"github.com/pion/rtp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Producer struct {
|
||||||
|
core.Connection
|
||||||
|
dev *device.Device
|
||||||
|
}
|
||||||
|
|
||||||
|
func Open(rawURL string) (*Producer, error) {
|
||||||
|
// Example (ffmpeg source compatible):
|
||||||
|
// v4l2:device?video=/dev/video0&input_format=mjpeg&video_size=1280x720
|
||||||
|
u, err := url.Parse(rawURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
query := u.Query()
|
||||||
|
|
||||||
|
dev, err := device.Open(query.Get("video"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
codec := &core.Codec{
|
||||||
|
ClockRate: 90000,
|
||||||
|
PayloadType: core.PayloadTypeRAW,
|
||||||
|
}
|
||||||
|
|
||||||
|
var width, height, pixFmt uint32
|
||||||
|
|
||||||
|
if wh := strings.Split(query.Get("video_size"), "x"); len(wh) == 2 {
|
||||||
|
codec.FmtpLine = "width=" + wh[0] + ";height=" + wh[1]
|
||||||
|
width = uint32(core.Atoi(wh[0]))
|
||||||
|
height = uint32(core.Atoi(wh[1]))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch query.Get("input_format") {
|
||||||
|
case "mjpeg":
|
||||||
|
codec.Name = core.CodecJPEG
|
||||||
|
pixFmt = device.V4L2_PIX_FMT_MJPEG
|
||||||
|
case "yuyv422":
|
||||||
|
if codec.FmtpLine == "" {
|
||||||
|
return nil, errors.New("v4l2: invalid video_size")
|
||||||
|
}
|
||||||
|
|
||||||
|
codec.Name = core.CodecRAW
|
||||||
|
codec.FmtpLine += ";colorspace=422"
|
||||||
|
pixFmt = device.V4L2_PIX_FMT_YUYV
|
||||||
|
default:
|
||||||
|
return nil, errors.New("v4l2: invalid input_format")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = dev.SetFormat(width, height, pixFmt); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
medias := []*core.Media{
|
||||||
|
{
|
||||||
|
Kind: core.KindVideo,
|
||||||
|
Direction: core.DirectionRecvonly,
|
||||||
|
Codecs: []*core.Codec{codec},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return &Producer{
|
||||||
|
Connection: core.Connection{
|
||||||
|
ID: core.NewID(),
|
||||||
|
FormatName: "v4l2",
|
||||||
|
Medias: medias,
|
||||||
|
},
|
||||||
|
dev: dev,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Producer) Start() error {
|
||||||
|
if err := c.dev.StreamOn(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cositedYUV := c.Medias[0].Codecs[0].Name == core.CodecRAW
|
||||||
|
|
||||||
|
for {
|
||||||
|
buf, err := c.dev.Capture(cositedYUV)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Recv += len(buf)
|
||||||
|
|
||||||
|
if len(c.Receivers) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pkt := &rtp.Packet{
|
||||||
|
Header: rtp.Header{Timestamp: core.Now90000()},
|
||||||
|
Payload: buf,
|
||||||
|
}
|
||||||
|
c.Receivers[0].WriteRTP(pkt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Producer) Stop() error {
|
||||||
|
_ = c.Connection.Stop()
|
||||||
|
return errors.Join(c.dev.StreamOff(), c.dev.Close())
|
||||||
|
}
|
||||||
@@ -292,6 +292,18 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<button id="v4l2">V4L2 (USB)</button>
|
||||||
|
<div class="module">
|
||||||
|
<table id="v4l2-table"></table>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
document.getElementById('v4l2').addEventListener('click', async ev => {
|
||||||
|
ev.target.nextElementSibling.style.display = 'block';
|
||||||
|
await getSources('v4l2-table', 'api/v4l2');
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<button id="webtorrent">WebTorrent Shares</button>
|
<button id="webtorrent">WebTorrent Shares</button>
|
||||||
<div class="module">
|
<div class="module">
|
||||||
<table id="webtorrent-table"></table>
|
<table id="webtorrent-table"></table>
|
||||||
|
|||||||
Reference in New Issue
Block a user