Skip to content

Commit ce0fac9

Browse files
committed
Adds module MJPEG
1 parent 1b14be7 commit ce0fac9

File tree

9 files changed

+358
-10
lines changed

9 files changed

+358
-10
lines changed

README.md

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ Ultimate camera streaming application with support RTSP, WebRTC, HomeKit, FFmpeg
66

77
- zero-dependency and zero-config [small app](#go2rtc-binary) for all OS (Windows, macOS, Linux, ARM)
88
- zero-delay for many supported protocols (lowest possible streaming latency)
9-
- streaming from [RTSP](#source-rtsp), [RTMP](#source-rtmp), [MJPEG](#source-ffmpeg), [HLS](#source-ffmpeg), [USB Cameras](#source-ffmpeg-device), [files](#source-ffmpeg) and [other sources](#module-streams)
10-
- streaming to [RTSP](#module-rtsp), [WebRTC](#module-webrtc) or [MSE](#module-mp4)
9+
- streaming from [RTSP](#source-rtsp), [RTMP](#source-rtmp), [MJPEG](#source-ffmpeg), [HLS/HTTP](#source-ffmpeg), [USB Cameras](#source-ffmpeg-device) and [other sources](#module-streams)
10+
- streaming to [RTSP](#module-rtsp), [WebRTC](#module-webrtc), [MSE/MP4](#module-mp4) or [MJPEG](#module-mjpeg)
1111
- first project in the World with support streaming from [HomeKit Cameras](#source-homekit)
1212
- on the fly transcoding for unsupported codecs via [FFmpeg](#source-ffmpeg)
1313
- multi-source 2-way [codecs negotiation](#codecs-negotiation)
@@ -327,13 +327,9 @@ api:
327327

328328
### Module: RTSP
329329

330-
You can get any stream as RTSP-stream with codecs filter:
330+
You can get any stream as RTSP-stream: `rtsp://192.168.1.123:8554/{stream_name}`
331331

332-
```
333-
rtsp://192.168.1.123/{stream_name}?video={codec}&audio={codec1}&audio={codec2}
334-
```
335-
336-
- you can omit the codecs, so one first video and one first audio will be selected
332+
- you can omit the codec filters, so one first video and one first audio will be selected
337333
- you can set `?video=copy` or just `?video`, so only one first video without audio will be selected
338334
- you can set multiple video or audio, so all of them will be selected
339335

@@ -497,6 +493,19 @@ Provides several features:
497493
2. Camera snapshots in MP4 format (single frame), can be sent to [Telegram](https://www.telegram.org/)
498494
3. Progressive MP4 stream - bad format for streaming because of high latency, doesn't work in Safari
499495

496+
### Module: MJPEG
497+
498+
**Important.** For stream as MJPEG format, your source MUST contain the MJPEG codec. If your camera outputs H264/H265 - you SHOULD use transcoding. With this example, your stream will have both H264 and MJPEG codecs:
499+
500+
```yaml
501+
streams:
502+
camera1:
503+
- rtsp://rtsp:[email protected]/av_stream/ch0
504+
- ffmpeg:rtsp://rtsp:[email protected]/av_stream/ch0#video=mjpeg
505+
```
506+
507+
Example link to MJPEG: `http://192.168.1.123:1984/api/stream.mjpeg?src=camera1`
508+
500509
### Module: Log
501510

502511
You can set different log levels for different modules.

cmd/ffmpeg/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,5 @@
3939
- https://html5test.com/
4040
- https://trac.ffmpeg.org/wiki/Capture/Webcam
4141
- https://trac.ffmpeg.org/wiki/DirectShow
42+
- https://stackoverflow.com/questions/53207692/libav-mjpeg-encoding-and-huffman-table
43+
- https://github.com/tuupola/esp_video/blob/master/README.md

cmd/ffmpeg/ffmpeg.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func Init() {
3737
"h264/ultra": "-codec:v libx264 -g 30 -preset ultrafast -tune zerolatency",
3838
"h264/high": "-codec:v libx264 -g 30 -preset superfast -tune zerolatency",
3939
"h265": "-codec:v libx265 -g 30 -preset ultrafast -tune zerolatency",
40+
"mjpeg": "-codec:v mjpeg -force_duplicated_matrix 1 -huffman 0 -pix_fmt yuvj420p",
4041
"opus": "-codec:a libopus -ar 48000 -ac 2",
4142
"pcmu": "-codec:a pcm_mulaw -ar 8000 -ac 1",
4243
"pcmu/16000": "-codec:a pcm_mulaw -ar 16000 -ac 1",

cmd/mjpeg/mjpeg.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package mjpeg
2+
3+
import (
4+
"github.com/AlexxIT/go2rtc/cmd/api"
5+
"github.com/AlexxIT/go2rtc/cmd/streams"
6+
"github.com/AlexxIT/go2rtc/pkg/mjpeg"
7+
"github.com/rs/zerolog/log"
8+
"net/http"
9+
"strconv"
10+
)
11+
12+
func Init() {
13+
api.HandleFunc("api/stream.mjpeg", handler)
14+
}
15+
16+
const header = "--frame\r\nContent-Type: image/jpeg\r\nContent-Length: "
17+
18+
func handler(w http.ResponseWriter, r *http.Request) {
19+
src := r.URL.Query().Get("src")
20+
stream := streams.GetOrNew(src)
21+
if stream == nil {
22+
return
23+
}
24+
25+
exit := make(chan struct{})
26+
27+
cons := &mjpeg.Consumer{}
28+
cons.Listen(func(msg interface{}) {
29+
switch msg := msg.(type) {
30+
case []byte:
31+
data := []byte(header + strconv.Itoa(len(msg)))
32+
data = append(data, 0x0D, 0x0A, 0x0D, 0x0A)
33+
data = append(data, msg...)
34+
data = append(data, 0x0D, 0x0A)
35+
36+
if _, err := w.Write(data); err != nil {
37+
exit <- struct{}{}
38+
}
39+
}
40+
})
41+
42+
if err := stream.AddConsumer(cons); err != nil {
43+
log.Error().Err(err).Msg("[api.mjpeg] add consumer")
44+
return
45+
}
46+
47+
w.Header().Set("Content-Type", `multipart/x-mixed-replace; boundary=frame`)
48+
49+
<-exit
50+
51+
stream.RemoveConsumer(cons)
52+
53+
//log.Trace().Msg("[api.mjpeg] close")
54+
}

main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/AlexxIT/go2rtc/cmd/hass"
1111
"github.com/AlexxIT/go2rtc/cmd/homekit"
1212
"github.com/AlexxIT/go2rtc/cmd/ivideon"
13+
"github.com/AlexxIT/go2rtc/cmd/mjpeg"
1314
"github.com/AlexxIT/go2rtc/cmd/mp4"
1415
"github.com/AlexxIT/go2rtc/cmd/ngrok"
1516
"github.com/AlexxIT/go2rtc/cmd/rtmp"
@@ -38,6 +39,7 @@ func main() {
3839

3940
webrtc.Init()
4041
mp4.Init()
42+
mjpeg.Init()
4143

4244
srtp.Init()
4345
homekit.Init()

pkg/mjpeg/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
## Useful links
2+
3+
- https://www.rfc-editor.org/rfc/rfc2435
4+
- https://github.com/GStreamer/gst-plugins-good/blob/master/gst/rtp/gstrtpjpegdepay.c

pkg/mjpeg/consumer.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package mjpeg
2+
3+
import (
4+
"github.com/AlexxIT/go2rtc/pkg/streamer"
5+
"github.com/pion/rtp"
6+
)
7+
8+
type Consumer struct {
9+
streamer.Element
10+
11+
UserAgent string
12+
RemoteAddr string
13+
14+
codecs []*streamer.Codec
15+
start bool
16+
17+
send int
18+
}
19+
20+
func (c *Consumer) GetMedias() []*streamer.Media {
21+
return []*streamer.Media{{
22+
Kind: streamer.KindVideo,
23+
Direction: streamer.DirectionRecvonly,
24+
Codecs: []*streamer.Codec{{Name: streamer.CodecJPEG}},
25+
}}
26+
}
27+
28+
func (c *Consumer) AddTrack(media *streamer.Media, track *streamer.Track) *streamer.Track {
29+
var header, payload []byte
30+
31+
push := func(packet *rtp.Packet) error {
32+
//fmt.Printf(
33+
// "[RTP] codec: %s, size: %6d, ts: %10d, pt: %2d, ssrc: %d, seq: %d, mark: %v\n",
34+
// track.Codec.Name, len(packet.Payload), packet.Timestamp,
35+
// packet.PayloadType, packet.SSRC, packet.SequenceNumber, packet.Marker,
36+
//)
37+
38+
// https://www.rfc-editor.org/rfc/rfc2435#section-3.1
39+
b := packet.Payload
40+
41+
// 3.1. JPEG header
42+
t := b[4]
43+
44+
// 3.1.7. Restart Marker header
45+
if 64 <= t && t <= 127 {
46+
b = b[12:] // skip it
47+
} else {
48+
b = b[8:]
49+
}
50+
51+
if header == nil {
52+
var lqt, cqt []byte
53+
54+
// 3.1.8. Quantization Table header
55+
q := packet.Payload[5]
56+
if q >= 128 {
57+
lqt = b[4:68]
58+
cqt = b[68:132]
59+
b = b[132:]
60+
} else {
61+
lqt, cqt = MakeTables(q)
62+
}
63+
64+
w := uint16(packet.Payload[6]) << 3
65+
h := uint16(packet.Payload[7]) << 3
66+
//fmt.Printf("t: %d, q: %d, w: %d, h: %d\n", t, q, w, h)
67+
header = MakeHeaders(t, w, h, lqt, cqt)
68+
}
69+
70+
// 3.1.9. JPEG Payload
71+
payload = append(payload, b...)
72+
73+
if packet.Marker {
74+
b = append(header, payload...)
75+
if end := b[len(b)-2:]; end[0] != 0xFF && end[1] != 0xD9 {
76+
b = append(b, 0xFF, 0xD9)
77+
}
78+
c.Fire(b)
79+
80+
header = nil
81+
payload = nil
82+
}
83+
84+
return nil
85+
}
86+
return track.Bind(push)
87+
}

0 commit comments

Comments
 (0)