mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-10-16 13:20:51 +08:00
Add support rotate and scale to MP4 stream
This commit is contained in:
@@ -1,15 +1,16 @@
|
||||
package mp4
|
||||
|
||||
import (
|
||||
"github.com/AlexxIT/go2rtc/internal/api/ws"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/internal/api"
|
||||
"github.com/AlexxIT/go2rtc/internal/api/ws"
|
||||
"github.com/AlexxIT/go2rtc/internal/app"
|
||||
"github.com/AlexxIT/go2rtc/internal/streams"
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"github.com/AlexxIT/go2rtc/pkg/mp4"
|
||||
"github.com/AlexxIT/go2rtc/pkg/tcp"
|
||||
"github.com/rs/zerolog"
|
||||
@@ -153,6 +154,16 @@ func handlerMP4(w http.ResponseWriter, r *http.Request) {
|
||||
header.Set("Content-Disposition", `attachment; filename="`+filename+`"`)
|
||||
}
|
||||
|
||||
if rotate := query.Get("rotate"); rotate != "" {
|
||||
mp4.PatchVideoRotate(data, core.Atoi(rotate))
|
||||
}
|
||||
|
||||
if scale := query.Get("scale"); scale != "" {
|
||||
if sx, sy, ok := strings.Cut(scale, ":"); ok {
|
||||
mp4.PatchVideoScale(data, core.Atoi(sx), core.Atoi(sy))
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = w.Write(data); err != nil {
|
||||
log.Error().Err(err).Caller().Send()
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
|
@@ -41,6 +41,11 @@ func (m *Movie) WriteVideo(codec string, width, height uint16, conf []byte) {
|
||||
m.Write(conf)
|
||||
m.EndAtom() // AVCC
|
||||
|
||||
m.StartAtom("pasp") // Pixel Aspect Ratio
|
||||
m.WriteUint32(1) // hSpacing
|
||||
m.WriteUint32(1) // vSpacing
|
||||
m.EndAtom()
|
||||
|
||||
m.EndAtom() // AVC1
|
||||
}
|
||||
|
||||
|
@@ -1,8 +1,11 @@
|
||||
package mp4
|
||||
|
||||
import (
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"strings"
|
||||
|
||||
"github.com/AlexxIT/go2rtc/pkg/core"
|
||||
)
|
||||
|
||||
// ParseQuery - like usual parse, but with mp4 param handler
|
||||
@@ -99,6 +102,67 @@ func ParseCodecs(codecs string, parseAudio bool) (medias []*core.Media) {
|
||||
return
|
||||
}
|
||||
|
||||
// PatchVideoRotate - update video track transformation matrix.
|
||||
// Rotation supported by many players and browsers (except Safari).
|
||||
// Scale has low support and better not to use it.
|
||||
// Supported only 0, 90, 180, 270 degrees.
|
||||
func PatchVideoRotate(init []byte, degrees int) bool {
|
||||
// search video atom
|
||||
i := bytes.Index(init, []byte("vide"))
|
||||
if i < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// seek to video matrix position
|
||||
i -= 4 + 3 + 1 + 8 + 32 + 8 + 4 + 4 + 4*9
|
||||
|
||||
// Rotation matrix:
|
||||
// [ cos sin 0]
|
||||
// [ -sin cos 0]
|
||||
// [ 0 0 16384]
|
||||
var cos, sin uint16
|
||||
|
||||
switch degrees {
|
||||
case 0:
|
||||
cos = 1
|
||||
sin = 0
|
||||
case 90:
|
||||
cos = 0
|
||||
sin = 1
|
||||
case 180:
|
||||
cos = 0xFFFF // -1
|
||||
sin = 0
|
||||
case 270:
|
||||
cos = 0
|
||||
sin = 0xFFFF // -1
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint16(init[i:], cos)
|
||||
binary.BigEndian.PutUint16(init[i+4:], sin)
|
||||
binary.BigEndian.PutUint16(init[i+12:], -sin)
|
||||
binary.BigEndian.PutUint16(init[i+16:], cos)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// PatchVideoScale - update "Pixel Aspect Ratio" atom.
|
||||
// Supported by many players and browsers (except Firefox).
|
||||
// Supported only positive integers.
|
||||
func PatchVideoScale(init []byte, scaleX, scaleY int) bool {
|
||||
// search video atom
|
||||
i := bytes.Index(init, []byte("pasp"))
|
||||
if i < 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint32(init[i+4:], uint32(scaleX))
|
||||
binary.BigEndian.PutUint32(init[i+8:], uint32(scaleY))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const (
|
||||
stateNone byte = iota
|
||||
stateInit
|
||||
|
Reference in New Issue
Block a user