Skip to content

Commit

Permalink
Adds module MJPEG
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexxIT committed Sep 17, 2022
1 parent 1b14be7 commit ce0fac9
Show file tree
Hide file tree
Showing 9 changed files with 358 additions and 10 deletions.
25 changes: 17 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ Ultimate camera streaming application with support RTSP, WebRTC, HomeKit, FFmpeg

- zero-dependency and zero-config [small app](#go2rtc-binary) for all OS (Windows, macOS, Linux, ARM)
- zero-delay for many supported protocols (lowest possible streaming latency)
- 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)
- streaming to [RTSP](#module-rtsp), [WebRTC](#module-webrtc) or [MSE](#module-mp4)
- 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)
- streaming to [RTSP](#module-rtsp), [WebRTC](#module-webrtc), [MSE/MP4](#module-mp4) or [MJPEG](#module-mjpeg)
- first project in the World with support streaming from [HomeKit Cameras](#source-homekit)
- on the fly transcoding for unsupported codecs via [FFmpeg](#source-ffmpeg)
- multi-source 2-way [codecs negotiation](#codecs-negotiation)
Expand Down Expand Up @@ -327,13 +327,9 @@ api:

### Module: RTSP

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

```
rtsp://192.168.1.123/{stream_name}?video={codec}&audio={codec1}&audio={codec2}
```

- you can omit the codecs, so one first video and one first audio will be selected
- you can omit the codec filters, so one first video and one first audio will be selected
- you can set `?video=copy` or just `?video`, so only one first video without audio will be selected
- you can set multiple video or audio, so all of them will be selected

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

### Module: MJPEG

**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:

```yaml
streams:
camera1:
- rtsp://rtsp:[email protected]/av_stream/ch0
- ffmpeg:rtsp://rtsp:[email protected]/av_stream/ch0#video=mjpeg
```

Example link to MJPEG: `http://192.168.1.123:1984/api/stream.mjpeg?src=camera1`

### Module: Log

You can set different log levels for different modules.
Expand Down
2 changes: 2 additions & 0 deletions cmd/ffmpeg/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@
- https://html5test.com/
- https://trac.ffmpeg.org/wiki/Capture/Webcam
- https://trac.ffmpeg.org/wiki/DirectShow
- https://stackoverflow.com/questions/53207692/libav-mjpeg-encoding-and-huffman-table
- https://github.com/tuupola/esp_video/blob/master/README.md
1 change: 1 addition & 0 deletions cmd/ffmpeg/ffmpeg.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func Init() {
"h264/ultra": "-codec:v libx264 -g 30 -preset ultrafast -tune zerolatency",
"h264/high": "-codec:v libx264 -g 30 -preset superfast -tune zerolatency",
"h265": "-codec:v libx265 -g 30 -preset ultrafast -tune zerolatency",
"mjpeg": "-codec:v mjpeg -force_duplicated_matrix 1 -huffman 0 -pix_fmt yuvj420p",
"opus": "-codec:a libopus -ar 48000 -ac 2",
"pcmu": "-codec:a pcm_mulaw -ar 8000 -ac 1",
"pcmu/16000": "-codec:a pcm_mulaw -ar 16000 -ac 1",
Expand Down
54 changes: 54 additions & 0 deletions cmd/mjpeg/mjpeg.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package mjpeg

import (
"github.com/AlexxIT/go2rtc/cmd/api"
"github.com/AlexxIT/go2rtc/cmd/streams"
"github.com/AlexxIT/go2rtc/pkg/mjpeg"
"github.com/rs/zerolog/log"
"net/http"
"strconv"
)

func Init() {
api.HandleFunc("api/stream.mjpeg", handler)
}

const header = "--frame\r\nContent-Type: image/jpeg\r\nContent-Length: "

func handler(w http.ResponseWriter, r *http.Request) {
src := r.URL.Query().Get("src")
stream := streams.GetOrNew(src)
if stream == nil {
return
}

exit := make(chan struct{})

cons := &mjpeg.Consumer{}
cons.Listen(func(msg interface{}) {
switch msg := msg.(type) {
case []byte:
data := []byte(header + strconv.Itoa(len(msg)))
data = append(data, 0x0D, 0x0A, 0x0D, 0x0A)
data = append(data, msg...)
data = append(data, 0x0D, 0x0A)

if _, err := w.Write(data); err != nil {
exit <- struct{}{}
}
}
})

if err := stream.AddConsumer(cons); err != nil {
log.Error().Err(err).Msg("[api.mjpeg] add consumer")
return
}

w.Header().Set("Content-Type", `multipart/x-mixed-replace; boundary=frame`)

<-exit

stream.RemoveConsumer(cons)

//log.Trace().Msg("[api.mjpeg] close")
}
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/AlexxIT/go2rtc/cmd/hass"
"github.com/AlexxIT/go2rtc/cmd/homekit"
"github.com/AlexxIT/go2rtc/cmd/ivideon"
"github.com/AlexxIT/go2rtc/cmd/mjpeg"
"github.com/AlexxIT/go2rtc/cmd/mp4"
"github.com/AlexxIT/go2rtc/cmd/ngrok"
"github.com/AlexxIT/go2rtc/cmd/rtmp"
Expand Down Expand Up @@ -38,6 +39,7 @@ func main() {

webrtc.Init()
mp4.Init()
mjpeg.Init()

srtp.Init()
homekit.Init()
Expand Down
4 changes: 4 additions & 0 deletions pkg/mjpeg/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## Useful links

- https://www.rfc-editor.org/rfc/rfc2435
- https://github.com/GStreamer/gst-plugins-good/blob/master/gst/rtp/gstrtpjpegdepay.c
87 changes: 87 additions & 0 deletions pkg/mjpeg/consumer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package mjpeg

import (
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/pion/rtp"
)

type Consumer struct {
streamer.Element

UserAgent string
RemoteAddr string

codecs []*streamer.Codec
start bool

send int
}

func (c *Consumer) GetMedias() []*streamer.Media {
return []*streamer.Media{{
Kind: streamer.KindVideo,
Direction: streamer.DirectionRecvonly,
Codecs: []*streamer.Codec{{Name: streamer.CodecJPEG}},
}}
}

func (c *Consumer) AddTrack(media *streamer.Media, track *streamer.Track) *streamer.Track {
var header, payload []byte

push := func(packet *rtp.Packet) error {
//fmt.Printf(
// "[RTP] codec: %s, size: %6d, ts: %10d, pt: %2d, ssrc: %d, seq: %d, mark: %v\n",
// track.Codec.Name, len(packet.Payload), packet.Timestamp,
// packet.PayloadType, packet.SSRC, packet.SequenceNumber, packet.Marker,
//)

// https://www.rfc-editor.org/rfc/rfc2435#section-3.1
b := packet.Payload

// 3.1. JPEG header
t := b[4]

// 3.1.7. Restart Marker header
if 64 <= t && t <= 127 {
b = b[12:] // skip it
} else {
b = b[8:]
}

if header == nil {
var lqt, cqt []byte

// 3.1.8. Quantization Table header
q := packet.Payload[5]
if q >= 128 {
lqt = b[4:68]
cqt = b[68:132]
b = b[132:]
} else {
lqt, cqt = MakeTables(q)
}

w := uint16(packet.Payload[6]) << 3
h := uint16(packet.Payload[7]) << 3
//fmt.Printf("t: %d, q: %d, w: %d, h: %d\n", t, q, w, h)
header = MakeHeaders(t, w, h, lqt, cqt)
}

// 3.1.9. JPEG Payload
payload = append(payload, b...)

if packet.Marker {
b = append(header, payload...)
if end := b[len(b)-2:]; end[0] != 0xFF && end[1] != 0xD9 {
b = append(b, 0xFF, 0xD9)
}
c.Fire(b)

header = nil
payload = nil
}

return nil
}
return track.Bind(push)
}
Loading

0 comments on commit ce0fac9

Please sign in to comment.