Add auto transcoding to JPEG snapshot

This commit is contained in:
Alexey Khit
2023-05-03 07:49:48 +03:00
parent e78f9fa69d
commit 4656086985
4 changed files with 137 additions and 4 deletions
+12
View File
@@ -0,0 +1,12 @@
package ffmpeg
import (
"bytes"
"os/exec"
)
func TranscodeToJPEG(b []byte) ([]byte, error) {
cmd := exec.Command("ffmpeg", "-hide_banner", "-i", "-", "-f", "mjpeg", "-")
cmd.Stdin = bytes.NewBuffer(b)
return cmd.Output()
}
@@ -3,13 +3,17 @@ package mjpeg
import (
"errors"
"github.com/AlexxIT/go2rtc/internal/api"
"github.com/AlexxIT/go2rtc/internal/ffmpeg"
"github.com/AlexxIT/go2rtc/internal/streams"
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/mjpeg"
"github.com/AlexxIT/go2rtc/pkg/pipe"
"github.com/AlexxIT/go2rtc/pkg/tcp"
"github.com/rs/zerolog/log"
"io"
"net/http"
"strconv"
"time"
)
func Init() {
@@ -29,14 +33,16 @@ func handlerKeyframe(w http.ResponseWriter, r *http.Request) {
exit := make(chan []byte)
cons := &mjpeg.Consumer{
cons := &pipe.Keyframe{
RemoteAddr: tcp.RemoteAddr(r),
UserAgent: r.UserAgent(),
}
cons.Listen(func(msg any) {
switch msg := msg.(type) {
case []byte:
exit <- msg
if b, ok := msg.([]byte); ok {
select {
case exit <- b:
default:
}
}
})
@@ -49,6 +55,17 @@ func handlerKeyframe(w http.ResponseWriter, r *http.Request) {
stream.RemoveConsumer(cons)
switch cons.CodecName() {
case core.CodecH264, core.CodecH265:
ts := time.Now()
var err error
if data, err = ffmpeg.TranscodeToJPEG(data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Debug().Msgf("[mjpeg] transcoding time=%s", time.Since(ts))
}
h := w.Header()
h.Set("Content-Type", "image/jpeg")
h.Set("Content-Length", strconv.Itoa(len(data)))
+13
View File
@@ -26,6 +26,19 @@ func AnnexB2AVC(b []byte) []byte {
return b
}
func AVCtoAnnexB(b []byte) []byte {
b = bytes.Clone(b)
for i := 0; i < len(b); {
size := int(binary.BigEndian.Uint32(b[i:]))
b[i] = 0
b[i+1] = 0
b[i+2] = 0
b[i+3] = 1
i += 4 + size
}
return b
}
const forbiddenZeroBit = 0x80
const nalUnitType = 0x1F
+91
View File
@@ -0,0 +1,91 @@
package pipe
import (
"github.com/AlexxIT/go2rtc/pkg/core"
"github.com/AlexxIT/go2rtc/pkg/h264"
"github.com/AlexxIT/go2rtc/pkg/h265"
"github.com/AlexxIT/go2rtc/pkg/mjpeg"
"github.com/pion/rtp"
)
type Keyframe struct {
core.Listener
UserAgent string
RemoteAddr string
medias []*core.Media
sender *core.Sender
}
func (k *Keyframe) GetMedias() []*core.Media {
if k.medias == nil {
k.medias = append(k.medias, &core.Media{
Kind: core.KindVideo,
Direction: core.DirectionSendonly,
Codecs: []*core.Codec{
{Name: core.CodecH264},
{Name: core.CodecH265},
{Name: core.CodecJPEG},
},
})
}
return k.medias
}
func (k *Keyframe) AddTrack(media *core.Media, _ *core.Codec, track *core.Receiver) error {
var handler core.HandlerFunc
switch track.Codec.Name {
case core.CodecH264:
handler = func(packet *rtp.Packet) {
if !h264.IsKeyframe(packet.Payload) {
return
}
b := h264.AVCtoAnnexB(packet.Payload)
k.Fire(b)
}
if track.Codec.IsRTP() {
handler = h264.RTPDepay(track.Codec, handler)
}
case core.CodecH265:
handler = func(packet *rtp.Packet) {
if !h265.IsKeyframe(packet.Payload) {
return
}
k.Fire(packet.Payload)
}
if track.Codec.IsRTP() {
handler = h265.RTPDepay(track.Codec, handler)
}
case core.CodecJPEG:
handler = func(packet *rtp.Packet) {
k.Fire(packet.Payload)
}
if track.Codec.IsRTP() {
handler = mjpeg.RTPDepay(handler)
}
}
k.sender = core.NewSender(media, track.Codec)
k.sender.Handler = handler
k.sender.HandleRTP(track)
return nil
}
func (k *Keyframe) CodecName() string {
if k.sender != nil {
return k.sender.Codec.Name
}
return ""
}
func (k *Keyframe) Stop() error {
if k.sender != nil {
k.sender.Close()
}
return nil
}