mirror of
				https://github.com/AlexxIT/go2rtc.git
				synced 2025-10-31 11:46:26 +08:00 
			
		
		
		
	Rewrite exec pipe, TCP and HTTP sources
This commit is contained in:
		| @@ -9,7 +9,7 @@ import ( | |||||||
| 	"github.com/AlexxIT/go2rtc/internal/rtsp" | 	"github.com/AlexxIT/go2rtc/internal/rtsp" | ||||||
| 	"github.com/AlexxIT/go2rtc/internal/streams" | 	"github.com/AlexxIT/go2rtc/internal/streams" | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/core" | 	"github.com/AlexxIT/go2rtc/pkg/core" | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/pipe" | 	"github.com/AlexxIT/go2rtc/pkg/magic" | ||||||
| 	pkg "github.com/AlexxIT/go2rtc/pkg/rtsp" | 	pkg "github.com/AlexxIT/go2rtc/pkg/rtsp" | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/shell" | 	"github.com/AlexxIT/go2rtc/pkg/shell" | ||||||
| 	"github.com/rs/zerolog" | 	"github.com/rs/zerolog" | ||||||
| @@ -38,12 +38,12 @@ func Init() { | |||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	streams.HandleFunc("exec", Handle) | 	streams.HandleFunc("exec", execHandle) | ||||||
|  |  | ||||||
| 	log = app.GetLogger("exec") | 	log = app.GetLogger("exec") | ||||||
| } | } | ||||||
|  |  | ||||||
| func Handle(url string) (core.Producer, error) { | func execHandle(url string) (core.Producer, error) { | ||||||
| 	var path string | 	var path string | ||||||
|  |  | ||||||
| 	args := shell.QuoteSplit(url[5:]) // remove `exec:` | 	args := shell.QuoteSplit(url[5:]) // remove `exec:` | ||||||
| @@ -66,9 +66,34 @@ func Handle(url string) (core.Producer, error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if path == "" { | 	if path == "" { | ||||||
| 		return pipe.NewClient(cmd) | 		return handlePipe(url, cmd) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	return handleRTSP(url, path, cmd) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func handlePipe(url string, cmd *exec.Cmd) (core.Producer, error) { | ||||||
|  | 	r, err := PipeCloser(cmd) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if err = cmd.Start(); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	client := magic.NewClient(r) | ||||||
|  | 	if err = client.Probe(); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	client.Desc = "exec active producer" | ||||||
|  | 	client.URL = url | ||||||
|  |  | ||||||
|  | 	return client, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func handleRTSP(url, path string, cmd *exec.Cmd) (core.Producer, error) { | ||||||
| 	if log.Trace().Enabled() { | 	if log.Trace().Enabled() { | ||||||
| 		cmd.Stdout = os.Stdout | 		cmd.Stdout = os.Stdout | ||||||
| 	} | 	} | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								internal/exec/pipe.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								internal/exec/pipe.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | package exec | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"github.com/AlexxIT/go2rtc/pkg/core" | ||||||
|  | 	"io" | ||||||
|  | 	"os/exec" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // PipeCloser - return StdoutPipe that Kill cmd on Close call | ||||||
|  | func PipeCloser(cmd *exec.Cmd) (io.ReadCloser, error) { | ||||||
|  | 	stdout, err := cmd.StdoutPipe() | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return pipeCloser{stdout, cmd}, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | type pipeCloser struct { | ||||||
|  | 	io.ReadCloser | ||||||
|  | 	cmd *exec.Cmd | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (p pipeCloser) Close() error { | ||||||
|  | 	return core.Any(p.ReadCloser.Close(), p.cmd.Process.Kill(), p.cmd.Wait()) | ||||||
|  | } | ||||||
| @@ -3,7 +3,6 @@ package ffmpeg | |||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"github.com/AlexxIT/go2rtc/internal/app" | 	"github.com/AlexxIT/go2rtc/internal/app" | ||||||
| 	"github.com/AlexxIT/go2rtc/internal/exec" |  | ||||||
| 	"github.com/AlexxIT/go2rtc/internal/ffmpeg/device" | 	"github.com/AlexxIT/go2rtc/internal/ffmpeg/device" | ||||||
| 	"github.com/AlexxIT/go2rtc/internal/ffmpeg/hardware" | 	"github.com/AlexxIT/go2rtc/internal/ffmpeg/hardware" | ||||||
| 	"github.com/AlexxIT/go2rtc/internal/rtsp" | 	"github.com/AlexxIT/go2rtc/internal/rtsp" | ||||||
| @@ -32,7 +31,7 @@ func Init() { | |||||||
| 		if args == nil { | 		if args == nil { | ||||||
| 			return nil, errors.New("can't generate ffmpeg command") | 			return nil, errors.New("can't generate ffmpeg command") | ||||||
| 		} | 		} | ||||||
| 		return exec.Handle("exec:" + args.String()) | 		return streams.GetProducer("exec:" + args.String()) | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	device.Init(defaults["bin"]) | 	device.Init(defaults["bin"]) | ||||||
|   | |||||||
| @@ -2,24 +2,28 @@ package http | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"errors" | 	"errors" | ||||||
| 	"fmt" |  | ||||||
| 	"github.com/AlexxIT/go2rtc/internal/streams" | 	"github.com/AlexxIT/go2rtc/internal/streams" | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/core" | 	"github.com/AlexxIT/go2rtc/pkg/core" | ||||||
|  | 	"github.com/AlexxIT/go2rtc/pkg/magic" | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/mjpeg" | 	"github.com/AlexxIT/go2rtc/pkg/mjpeg" | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/mpegts" |  | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/rtmp" | 	"github.com/AlexxIT/go2rtc/pkg/rtmp" | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/tcp" | 	"github.com/AlexxIT/go2rtc/pkg/tcp" | ||||||
|  | 	"net" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 	"time" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func Init() { | func Init() { | ||||||
| 	streams.HandleFunc("http", handle) | 	streams.HandleFunc("http", handleHTTP) | ||||||
| 	streams.HandleFunc("https", handle) | 	streams.HandleFunc("https", handleHTTP) | ||||||
| 	streams.HandleFunc("httpx", handle) | 	streams.HandleFunc("httpx", handleHTTP) | ||||||
|  |  | ||||||
|  | 	streams.HandleFunc("tcp", handleTCP) | ||||||
| } | } | ||||||
|  |  | ||||||
| func handle(url string) (core.Producer, error) { | func handleHTTP(url string) (core.Producer, error) { | ||||||
| 	// first we get the Content-Type to define supported producer | 	// first we get the Content-Type to define supported producer | ||||||
| 	req, err := http.NewRequest("GET", url, nil) | 	req, err := http.NewRequest("GET", url, nil) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -54,13 +58,38 @@ func handle(url string) (core.Producer, error) { | |||||||
| 		} | 		} | ||||||
| 		return conn, nil | 		return conn, nil | ||||||
|  |  | ||||||
| 	case "video/mpeg": | 	default: // "video/mpeg": | ||||||
| 		client := mpegts.NewClient(res) |  | ||||||
| 		if err = client.Handle(); err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		return client, nil |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return nil, fmt.Errorf("unsupported Content-Type: %s", ct) | 	client := magic.NewClient(res.Body) | ||||||
|  | 	if err = client.Probe(); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	client.Desc = "HTTP active producer" | ||||||
|  | 	client.URL = url | ||||||
|  |  | ||||||
|  | 	return client, nil | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func handleTCP(rawURL string) (core.Producer, error) { | ||||||
|  | 	u, err := url.Parse(rawURL) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	conn, err := net.DialTimeout("tcp", u.Host, time.Second*3) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	client := magic.NewClient(conn) | ||||||
|  | 	if err = client.Probe(); err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	client.Desc = "TCP active producer" | ||||||
|  | 	client.URL = rawURL | ||||||
|  |  | ||||||
|  | 	return client, nil | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,8 +6,8 @@ import ( | |||||||
| 	"github.com/AlexxIT/go2rtc/internal/ffmpeg" | 	"github.com/AlexxIT/go2rtc/internal/ffmpeg" | ||||||
| 	"github.com/AlexxIT/go2rtc/internal/streams" | 	"github.com/AlexxIT/go2rtc/internal/streams" | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/core" | 	"github.com/AlexxIT/go2rtc/pkg/core" | ||||||
|  | 	"github.com/AlexxIT/go2rtc/pkg/magic" | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/mjpeg" | 	"github.com/AlexxIT/go2rtc/pkg/mjpeg" | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/pipe" |  | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/tcp" | 	"github.com/AlexxIT/go2rtc/pkg/tcp" | ||||||
| 	"github.com/rs/zerolog/log" | 	"github.com/rs/zerolog/log" | ||||||
| 	"io" | 	"io" | ||||||
| @@ -33,7 +33,7 @@ func handlerKeyframe(w http.ResponseWriter, r *http.Request) { | |||||||
|  |  | ||||||
| 	exit := make(chan []byte) | 	exit := make(chan []byte) | ||||||
|  |  | ||||||
| 	cons := &pipe.Keyframe{ | 	cons := &magic.Keyframe{ | ||||||
| 		RemoteAddr: tcp.RemoteAddr(r), | 		RemoteAddr: tcp.RemoteAddr(r), | ||||||
| 		UserAgent:  r.UserAgent(), | 		UserAgent:  r.UserAgent(), | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1,35 +0,0 @@ | |||||||
| package tcp |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"github.com/AlexxIT/go2rtc/internal/streams" |  | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/core" |  | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/mpegts" |  | ||||||
| 	"net" |  | ||||||
| 	"net/http" |  | ||||||
| 	"net/url" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func Init() { |  | ||||||
| 	streams.HandleFunc("tcp", handle) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func handle(rawURL string) (core.Producer, error) { |  | ||||||
| 	u, err := url.Parse(rawURL) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	conn, err := net.DialTimeout("tcp", u.Host, time.Second*3) |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	req := &http.Request{URL: u} |  | ||||||
| 	res := &http.Response{Body: conn, Request: req} |  | ||||||
| 	client := mpegts.NewClient(res) |  | ||||||
| 	if err := client.Handle(); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 	return client, nil |  | ||||||
| } |  | ||||||
							
								
								
									
										2
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								main.go
									
									
									
									
									
								
							| @@ -25,7 +25,6 @@ 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/tcp" |  | ||||||
| 	"github.com/AlexxIT/go2rtc/internal/webrtc" | 	"github.com/AlexxIT/go2rtc/internal/webrtc" | ||||||
| 	"github.com/AlexxIT/go2rtc/internal/webtorrent" | 	"github.com/AlexxIT/go2rtc/internal/webtorrent" | ||||||
| 	"os" | 	"os" | ||||||
| @@ -52,7 +51,6 @@ func main() { | |||||||
| 	isapi.Init() | 	isapi.Init() | ||||||
| 	mpegts.Init() | 	mpegts.Init() | ||||||
| 	roborock.Init() | 	roborock.Init() | ||||||
| 	tcp.Init() |  | ||||||
|  |  | ||||||
| 	srtp.Init() | 	srtp.Init() | ||||||
| 	homekit.Init() | 	homekit.Init() | ||||||
|   | |||||||
| @@ -29,6 +29,15 @@ func RandString(size, base byte) string { | |||||||
| 	return string(b) | 	return string(b) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func Any(errs ...error) error { | ||||||
|  | 	for _, err := range errs { | ||||||
|  | 		if err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  |  | ||||||
| func Between(s, sub1, sub2 string) string { | func Between(s, sub1, sub2 string) string { | ||||||
| 	i := strings.Index(s, sub1) | 	i := strings.Index(s, sub1) | ||||||
| 	if i < 0 { | 	if i < 0 { | ||||||
|   | |||||||
							
								
								
									
										54
									
								
								pkg/h265/avc.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								pkg/h265/avc.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | package h265 | ||||||
|  |  | ||||||
|  | import "github.com/AlexxIT/go2rtc/pkg/h264" | ||||||
|  |  | ||||||
|  | const forbiddenZeroBit = 0x80 | ||||||
|  | const nalUnitType = 0x3F | ||||||
|  |  | ||||||
|  | // DecodeStream - find and return first AU in AVC format | ||||||
|  | // useful for processing live streams with unknown separator size | ||||||
|  | func DecodeStream(annexb []byte) ([]byte, int) { | ||||||
|  | 	startPos := -1 | ||||||
|  |  | ||||||
|  | 	i := 0 | ||||||
|  | 	for { | ||||||
|  | 		// search next separator | ||||||
|  | 		if i = h264.IndexFrom(annexb, []byte{0, 0, 1}, i); i < 0 { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// move i to next AU | ||||||
|  | 		if i += 3; i >= len(annexb) { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// check if AU type valid | ||||||
|  | 		octet := annexb[i] | ||||||
|  | 		if octet&forbiddenZeroBit != 0 { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		nalType := (octet >> 1) & nalUnitType | ||||||
|  | 		if startPos >= 0 { | ||||||
|  | 			switch nalType { | ||||||
|  | 			case NALUTypeVPS, NALUTypePFrame: | ||||||
|  | 				if annexb[i-4] == 0 { | ||||||
|  | 					return h264.DecodeAnnexB(annexb[startPos : i-4]), i - 4 | ||||||
|  | 				} else { | ||||||
|  | 					return h264.DecodeAnnexB(annexb[startPos : i-3]), i - 3 | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			switch nalType { | ||||||
|  | 			case NALUTypeVPS, NALUTypePFrame: | ||||||
|  | 				if i >= 4 && annexb[i-4] == 0 { | ||||||
|  | 					startPos = i - 4 | ||||||
|  | 				} else { | ||||||
|  | 					startPos = i - 3 | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, 0 | ||||||
|  | } | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package pipe | package magic | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
| @@ -6,17 +6,21 @@ import ( | |||||||
| 	"errors" | 	"errors" | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/core" | 	"github.com/AlexxIT/go2rtc/pkg/core" | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/h264" | 	"github.com/AlexxIT/go2rtc/pkg/h264" | ||||||
|  | 	"github.com/AlexxIT/go2rtc/pkg/h265" | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/mpegts" | 	"github.com/AlexxIT/go2rtc/pkg/mpegts" | ||||||
| 	"github.com/pion/rtp" | 	"github.com/pion/rtp" | ||||||
| 	"io" | 	"io" | ||||||
| 	"os/exec" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | // Client - can read unknown bytestream and autodetect format | ||||||
| type Client struct { | type Client struct { | ||||||
| 	cmd    *exec.Cmd | 	Desc string | ||||||
| 	stdout io.ReadCloser | 	URL  string | ||||||
| 	sniff  []byte | 
 | ||||||
| 	handle func() error | 	Handle func() error | ||||||
|  | 
 | ||||||
|  | 	r     io.ReadCloser | ||||||
|  | 	sniff []byte | ||||||
| 
 | 
 | ||||||
| 	medias   []*core.Media | 	medias   []*core.Media | ||||||
| 	receiver *core.Receiver | 	receiver *core.Receiver | ||||||
| @@ -24,47 +28,50 @@ type Client struct { | |||||||
| 	recv int | 	recv int | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func NewClient(cmd *exec.Cmd) (prod *Client, err error) { | func NewClient(r io.ReadCloser) *Client { | ||||||
| 	prod = &Client{cmd: cmd} | 	return &Client{r: r} | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| 	prod.stdout, err = cmd.StdoutPipe() | func (c *Client) Probe() (err error) { | ||||||
|  | 	c.sniff = make([]byte, mpegts.PacketSize*3) // MPEG-TS: SDT+PAT+PMT | ||||||
|  | 	c.recv, err = io.ReadFull(c.r, c.sniff) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		_ = c.Close() | ||||||
| 	} | 		return | ||||||
| 
 |  | ||||||
| 	if err = cmd.Start(); err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	prod.sniff = make([]byte, mpegts.PacketSize*3) // MPEG-TS: SDT+PAT+PMT |  | ||||||
| 	prod.recv, err = io.ReadFull(prod.stdout, prod.sniff) |  | ||||||
| 	if err != nil { |  | ||||||
| 		_ = prod.Stop() |  | ||||||
| 		return nil, err |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var codec *core.Codec | 	var codec *core.Codec | ||||||
| 
 | 
 | ||||||
| 	if bytes.HasPrefix(prod.sniff, []byte{0, 0, 0, 1}) { | 	if bytes.HasPrefix(c.sniff, []byte{0, 0, 0, 1}) { | ||||||
| 		switch { | 		switch { | ||||||
| 		case h264.NALUType(prod.sniff) == h264.NALUTypeSPS: | 		case h264.NALUType(c.sniff) == h264.NALUTypeSPS: | ||||||
| 			codec = &core.Codec{ | 			codec = &core.Codec{ | ||||||
| 				Name:        core.CodecH264, | 				Name:        core.CodecH264, | ||||||
| 				ClockRate:   90000, | 				ClockRate:   90000, | ||||||
| 				PayloadType: core.PayloadTypeRAW, | 				PayloadType: core.PayloadTypeRAW, | ||||||
| 			} | 			} | ||||||
| 			prod.handle = prod.ReadBitstreams | 			c.Handle = c.ReadBitstreams | ||||||
|  | 
 | ||||||
|  | 		case h265.NALUType(c.sniff) == h265.NALUTypeVPS: | ||||||
|  | 			codec = &core.Codec{ | ||||||
|  | 				Name:        core.CodecH265, | ||||||
|  | 				ClockRate:   90000, | ||||||
|  | 				PayloadType: core.PayloadTypeRAW, | ||||||
|  | 			} | ||||||
|  | 			c.Handle = c.ReadBitstreams | ||||||
| 		} | 		} | ||||||
| 	} else if bytes.HasPrefix(prod.sniff, []byte{0xFF, 0xD8}) { | 
 | ||||||
|  | 	} else if bytes.HasPrefix(c.sniff, []byte{0xFF, 0xD8}) { | ||||||
| 		codec = &core.Codec{ | 		codec = &core.Codec{ | ||||||
| 			Name:        core.CodecJPEG, | 			Name:        core.CodecJPEG, | ||||||
| 			ClockRate:   90000, | 			ClockRate:   90000, | ||||||
| 			PayloadType: core.PayloadTypeRAW, | 			PayloadType: core.PayloadTypeRAW, | ||||||
| 		} | 		} | ||||||
| 		prod.handle = prod.ReadMJPEG | 		c.Handle = c.ReadMJPEG | ||||||
| 	} else if prod.sniff[0] == mpegts.SyncByte { | 
 | ||||||
|  | 	} else if c.sniff[0] == mpegts.SyncByte { | ||||||
| 		ts := mpegts.NewReader() | 		ts := mpegts.NewReader() | ||||||
| 		ts.AppendBuffer(prod.sniff) | 		ts.AppendBuffer(c.sniff) | ||||||
| 		_ = ts.GetPacket() | 		_ = ts.GetPacket() | ||||||
| 		for _, streamType := range ts.GetStreamTypes() { | 		for _, streamType := range ts.GetStreamTypes() { | ||||||
| 			switch streamType { | 			switch streamType { | ||||||
| @@ -74,17 +81,17 @@ func NewClient(cmd *exec.Cmd) (prod *Client, err error) { | |||||||
| 					ClockRate:   90000, | 					ClockRate:   90000, | ||||||
| 					PayloadType: core.PayloadTypeRAW, | 					PayloadType: core.PayloadTypeRAW, | ||||||
| 				} | 				} | ||||||
| 				prod.handle = prod.ReadMPEGTS | 				c.Handle = c.ReadMPEGTS | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if codec == nil { | 	if codec == nil { | ||||||
| 		_ = prod.Stop() | 		_ = c.Close() | ||||||
| 		return nil, errors.New("unknown format: " + hex.EncodeToString(prod.sniff)) | 		return errors.New("unknown format: " + hex.EncodeToString(c.sniff[:8])) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	prod.medias = append(prod.medias, &core.Media{ | 	c.medias = append(c.medias, &core.Media{ | ||||||
| 		Kind:      core.KindVideo, | 		Kind:      core.KindVideo, | ||||||
| 		Direction: core.DirectionRecvonly, | 		Direction: core.DirectionRecvonly, | ||||||
| 		Codecs:    []*core.Codec{codec}, | 		Codecs:    []*core.Codec{codec}, | ||||||
| @@ -97,10 +104,18 @@ func (c *Client) ReadBitstreams() error { | |||||||
| 	buf := c.sniff               // total bufer | 	buf := c.sniff               // total bufer | ||||||
| 	b := make([]byte, 1024*1024) // reading buffer | 	b := make([]byte, 1024*1024) // reading buffer | ||||||
| 
 | 
 | ||||||
|  | 	var decodeStream func([]byte) ([]byte, int) | ||||||
|  | 	switch c.receiver.Codec.Name { | ||||||
|  | 	case core.CodecH264: | ||||||
|  | 		decodeStream = h264.DecodeStream | ||||||
|  | 	case core.CodecH265: | ||||||
|  | 		decodeStream = h265.DecodeStream | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	for { | 	for { | ||||||
| 		payload, n := h264.DecodeStream(buf) | 		payload, n := decodeStream(buf) | ||||||
| 		if payload == nil { | 		if payload == nil { | ||||||
| 			n, err := c.stdout.Read(b) | 			n, err := c.r.Read(b) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| @@ -130,7 +145,7 @@ func (c *Client) ReadMJPEG() error { | |||||||
| 		// one JPEG end and next start | 		// one JPEG end and next start | ||||||
| 		i := bytes.Index(buf, []byte{0xFF, 0xD9, 0xFF, 0xD8}) | 		i := bytes.Index(buf, []byte{0xFF, 0xD9, 0xFF, 0xD8}) | ||||||
| 		if i < 0 { | 		if i < 0 { | ||||||
| 			n, err := c.stdout.Read(b) | 			n, err := c.r.Read(b) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| @@ -167,7 +182,7 @@ func (c *Client) ReadMPEGTS() error { | |||||||
| 	for { | 	for { | ||||||
| 		packet := ts.GetPacket() | 		packet := ts.GetPacket() | ||||||
| 		if packet == nil { | 		if packet == nil { | ||||||
| 			n, err := c.stdout.Read(b) | 			n, err := c.r.Read(b) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return err | 				return err | ||||||
| 			} | 			} | ||||||
| @@ -186,3 +201,7 @@ func (c *Client) ReadMPEGTS() error { | |||||||
| 		c.receiver.WriteRTP(packet) | 		c.receiver.WriteRTP(packet) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func (c *Client) Close() error { | ||||||
|  | 	return c.r.Close() | ||||||
|  | } | ||||||
| @@ -1,4 +1,4 @@ | |||||||
| package pipe | package magic | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/core" | 	"github.com/AlexxIT/go2rtc/pkg/core" | ||||||
| @@ -1,9 +1,8 @@ | |||||||
| package pipe | package magic | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"encoding/json" | 	"encoding/json" | ||||||
| 	"github.com/AlexxIT/go2rtc/pkg/core" | 	"github.com/AlexxIT/go2rtc/pkg/core" | ||||||
| 	"strings" |  | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func (c *Client) GetMedias() []*core.Media { | func (c *Client) GetMedias() []*core.Media { | ||||||
| @@ -18,29 +17,20 @@ func (c *Client) GetTrack(media *core.Media, codec *core.Codec) (*core.Receiver, | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Client) Start() error { | func (c *Client) Start() error { | ||||||
| 	return c.handle() | 	return c.Handle() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Client) Stop() (err error) { | func (c *Client) Stop() (err error) { | ||||||
| 	if c.receiver != nil { | 	if c.receiver != nil { | ||||||
| 		c.receiver.Close() | 		c.receiver.Close() | ||||||
| 	} | 	} | ||||||
| 	if err1 := c.stdout.Close(); err != nil { | 	return c.Close() | ||||||
| 		err = err1 |  | ||||||
| 	} |  | ||||||
| 	if err1 := c.cmd.Process.Kill(); err != nil { |  | ||||||
| 		err = err1 |  | ||||||
| 	} |  | ||||||
| 	if err1 := c.cmd.Wait(); err != nil { |  | ||||||
| 		err = err1 |  | ||||||
| 	} |  | ||||||
| 	return |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (c *Client) MarshalJSON() ([]byte, error) { | func (c *Client) MarshalJSON() ([]byte, error) { | ||||||
| 	info := &core.Info{ | 	info := &core.Info{ | ||||||
| 		Type:   "PIPE active producer", | 		Type:   c.Desc, | ||||||
| 		URL:    c.cmd.Path + " " + strings.Join(c.cmd.Args, " "), | 		URL:    c.URL, | ||||||
| 		Medias: c.medias, | 		Medias: c.medias, | ||||||
| 		Recv:   c.recv, | 		Recv:   c.recv, | ||||||
| 	} | 	} | ||||||
		Reference in New Issue
	
	Block a user
	 Alexey Khit
					Alexey Khit