Add support H264, H265, NV12 for V4L2 source #1546
This commit is contained in:
@@ -83,3 +83,15 @@ func TestDahua(t *testing.T) {
|
|||||||
n := naluTypes(b)
|
n := naluTypes(b)
|
||||||
require.Equal(t, []byte{0x40, 0x42, 0x44, 0x26}, n)
|
require.Equal(t, []byte{0x40, 0x42, 0x44, 0x26}, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUSB(t *testing.T) {
|
||||||
|
s := "00 00 00 01 67 4D 00 1F 8D 8D 40 28 02 DD 37 01 01 01 40 00 01 C2 00 00 57 E4 01 00 00 00 01 68 EE 3C 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 65 88 80 00"
|
||||||
|
b := EncodeToAVCC(decode(s))
|
||||||
|
n := naluTypes(b)
|
||||||
|
require.Equal(t, []byte{0x67, 0x68, 0x65}, n)
|
||||||
|
|
||||||
|
s = "00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 41 9A 00 4C"
|
||||||
|
b = EncodeToAVCC(decode(s))
|
||||||
|
n = naluTypes(b)
|
||||||
|
require.Equal(t, []byte{0x41}, n)
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Device struct {
|
type Device struct {
|
||||||
fd int
|
fd int
|
||||||
bufs [][]byte
|
bufs [][]byte
|
||||||
|
pixFmt uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func Open(path string) (*Device, error) {
|
func Open(path string) (*Device, error) {
|
||||||
@@ -119,6 +120,8 @@ func (d *Device) ListFrameRates(pixFmt, width, height uint32) ([]uint32, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Device) SetFormat(width, height, pixFmt uint32) error {
|
func (d *Device) SetFormat(width, height, pixFmt uint32) error {
|
||||||
|
d.pixFmt = pixFmt
|
||||||
|
|
||||||
f := v4l2_format{
|
f := v4l2_format{
|
||||||
typ: V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
typ: V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
||||||
pix: v4l2_pix_format{
|
pix: v4l2_pix_format{
|
||||||
@@ -196,7 +199,7 @@ func (d *Device) StreamOff() (err error) {
|
|||||||
return ioctl(d.fd, VIDIOC_REQBUFS, unsafe.Pointer(&rb))
|
return ioctl(d.fd, VIDIOC_REQBUFS, unsafe.Pointer(&rb))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Device) Capture(planarYUV bool) ([]byte, error) {
|
func (d *Device) Capture() ([]byte, error) {
|
||||||
dec := v4l2_buffer{
|
dec := v4l2_buffer{
|
||||||
typ: V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
typ: V4L2_BUF_TYPE_VIDEO_CAPTURE,
|
||||||
memory: V4L2_MEMORY_MMAP,
|
memory: V4L2_MEMORY_MMAP,
|
||||||
@@ -205,11 +208,16 @@ func (d *Device) Capture(planarYUV bool) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := make([]byte, dec.bytesused)
|
src := d.bufs[dec.index][:dec.bytesused]
|
||||||
if planarYUV {
|
dst := make([]byte, dec.bytesused)
|
||||||
YUYV2YUV(buf, d.bufs[dec.index][:dec.bytesused])
|
|
||||||
} else {
|
switch d.pixFmt {
|
||||||
copy(buf, d.bufs[dec.index][:dec.bytesused])
|
case V4L2_PIX_FMT_YUYV:
|
||||||
|
YUYVtoYUV(dst, src)
|
||||||
|
case V4L2_PIX_FMT_NV12:
|
||||||
|
NV12toYUV(dst, src)
|
||||||
|
default:
|
||||||
|
copy(dst, d.bufs[dec.index][:dec.bytesused])
|
||||||
}
|
}
|
||||||
|
|
||||||
enc := v4l2_buffer{
|
enc := v4l2_buffer{
|
||||||
@@ -221,7 +229,7 @@ func (d *Device) Capture(planarYUV bool) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf, nil
|
return dst, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Device) Close() error {
|
func (d *Device) Close() error {
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ package device
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
V4L2_PIX_FMT_YUYV = 'Y' | 'U'<<8 | 'Y'<<16 | 'V'<<24
|
V4L2_PIX_FMT_YUYV = 'Y' | 'U'<<8 | 'Y'<<16 | 'V'<<24
|
||||||
|
V4L2_PIX_FMT_NV12 = 'N' | 'V'<<8 | '1'<<16 | '2'<<24
|
||||||
V4L2_PIX_FMT_MJPEG = 'M' | 'J'<<8 | 'P'<<16 | 'G'<<24
|
V4L2_PIX_FMT_MJPEG = 'M' | 'J'<<8 | 'P'<<16 | 'G'<<24
|
||||||
|
V4L2_PIX_FMT_H264 = 'H' | '2'<<8 | '6'<<16 | '4'<<24
|
||||||
|
V4L2_PIX_FMT_HEVC = 'H' | 'E'<<8 | 'V'<<16 | 'C'<<24
|
||||||
)
|
)
|
||||||
|
|
||||||
type Format struct {
|
type Format struct {
|
||||||
@@ -13,11 +16,13 @@ type Format struct {
|
|||||||
|
|
||||||
var Formats = []Format{
|
var Formats = []Format{
|
||||||
{V4L2_PIX_FMT_YUYV, "YUV 4:2:2", "yuyv422"},
|
{V4L2_PIX_FMT_YUYV, "YUV 4:2:2", "yuyv422"},
|
||||||
|
{V4L2_PIX_FMT_NV12, "Y/UV 4:2:0", "nv12"},
|
||||||
{V4L2_PIX_FMT_MJPEG, "Motion-JPEG", "mjpeg"},
|
{V4L2_PIX_FMT_MJPEG, "Motion-JPEG", "mjpeg"},
|
||||||
|
{V4L2_PIX_FMT_H264, "H.264", "h264"},
|
||||||
|
{V4L2_PIX_FMT_HEVC, "HEVC", "hevc"},
|
||||||
}
|
}
|
||||||
|
|
||||||
// YUYV2YUV convert packed YUV to planar YUV
|
func YUYVtoYUV(dst, src []byte) {
|
||||||
func YUYV2YUV(dst, src []byte) {
|
|
||||||
n := len(src)
|
n := len(src)
|
||||||
i0 := 0
|
i0 := 0
|
||||||
iy := 0
|
iy := 0
|
||||||
@@ -38,3 +43,20 @@ func YUYV2YUV(dst, src []byte) {
|
|||||||
iv++
|
iv++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NV12toYUV(dst, src []byte) {
|
||||||
|
n := len(src)
|
||||||
|
k := n / 6
|
||||||
|
i0 := k * 4
|
||||||
|
iu := i0
|
||||||
|
iv := i0 + k
|
||||||
|
copy(dst, src[:i0]) // copy Y
|
||||||
|
for i0 < n {
|
||||||
|
dst[iu] = src[i0]
|
||||||
|
i0++
|
||||||
|
iu++
|
||||||
|
dst[iv] = src[i0]
|
||||||
|
i0++
|
||||||
|
iv++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+27
-6
@@ -8,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||||
|
"github.com/AlexxIT/go2rtc/pkg/h264/annexb"
|
||||||
"github.com/AlexxIT/go2rtc/pkg/v4l2/device"
|
"github.com/AlexxIT/go2rtc/pkg/v4l2/device"
|
||||||
"github.com/pion/rtp"
|
"github.com/pion/rtp"
|
||||||
)
|
)
|
||||||
@@ -46,17 +47,29 @@ func Open(rawURL string) (*Producer, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch query.Get("input_format") {
|
switch query.Get("input_format") {
|
||||||
case "mjpeg":
|
|
||||||
codec.Name = core.CodecJPEG
|
|
||||||
pixFmt = device.V4L2_PIX_FMT_MJPEG
|
|
||||||
case "yuyv422":
|
case "yuyv422":
|
||||||
if codec.FmtpLine == "" {
|
if codec.FmtpLine == "" {
|
||||||
return nil, errors.New("v4l2: invalid video_size")
|
return nil, errors.New("v4l2: invalid video_size")
|
||||||
}
|
}
|
||||||
|
|
||||||
codec.Name = core.CodecRAW
|
codec.Name = core.CodecRAW
|
||||||
codec.FmtpLine += ";colorspace=422"
|
codec.FmtpLine += ";colorspace=422"
|
||||||
pixFmt = device.V4L2_PIX_FMT_YUYV
|
pixFmt = device.V4L2_PIX_FMT_YUYV
|
||||||
|
case "nv12":
|
||||||
|
if codec.FmtpLine == "" {
|
||||||
|
return nil, errors.New("v4l2: invalid video_size")
|
||||||
|
}
|
||||||
|
codec.Name = core.CodecRAW
|
||||||
|
codec.FmtpLine += ";colorspace=420mpeg2" // maybe 420jpeg
|
||||||
|
pixFmt = device.V4L2_PIX_FMT_NV12
|
||||||
|
case "mjpeg":
|
||||||
|
codec.Name = core.CodecJPEG
|
||||||
|
pixFmt = device.V4L2_PIX_FMT_MJPEG
|
||||||
|
case "h264":
|
||||||
|
codec.Name = core.CodecH264
|
||||||
|
pixFmt = device.V4L2_PIX_FMT_H264
|
||||||
|
case "hevc":
|
||||||
|
codec.Name = core.CodecH265
|
||||||
|
pixFmt = device.V4L2_PIX_FMT_HEVC
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("v4l2: invalid input_format")
|
return nil, errors.New("v4l2: invalid input_format")
|
||||||
}
|
}
|
||||||
@@ -93,10 +106,14 @@ func (c *Producer) Start() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
planarYUV := c.Medias[0].Codecs[0].Name == core.CodecRAW
|
var bitstream bool
|
||||||
|
switch c.Medias[0].Codecs[0].Name {
|
||||||
|
case core.CodecH264, core.CodecH265:
|
||||||
|
bitstream = true
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
buf, err := c.dev.Capture(planarYUV)
|
buf, err := c.dev.Capture()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -107,6 +124,10 @@ func (c *Producer) Start() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if bitstream {
|
||||||
|
buf = annexb.EncodeToAVCC(buf)
|
||||||
|
}
|
||||||
|
|
||||||
pkt := &rtp.Packet{
|
pkt := &rtp.Packet{
|
||||||
Header: rtp.Header{Timestamp: core.Now90000()},
|
Header: rtp.Header{Timestamp: core.Now90000()},
|
||||||
Payload: buf,
|
Payload: buf,
|
||||||
|
|||||||
Reference in New Issue
Block a user