mirror of
https://github.com/jech/galene.git
synced 2024-11-22 08:35:57 +01:00
Implement saving to disk of VP9.
This commit is contained in:
parent
80cb0a2f30
commit
81e155a45f
1 changed files with 116 additions and 28 deletions
|
@ -238,6 +238,7 @@ type diskTrack struct {
|
||||||
origin uint64
|
origin uint64
|
||||||
|
|
||||||
lastKf uint32
|
lastKf uint32
|
||||||
|
savedKf *rtp.Packet
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDiskConn(client *Client, directory string, up conn.Up, remoteTracks []conn.UpTrack) (*diskConn, error) {
|
func newDiskConn(client *Client, directory string, up conn.Up, remoteTracks []conn.UpTrack) (*diskConn, error) {
|
||||||
|
@ -271,6 +272,17 @@ func newDiskConn(client *Client, directory string, up conn.Up, remoteTracks []co
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
conn.hasVideo = true
|
conn.hasVideo = true
|
||||||
|
case "video/vp9":
|
||||||
|
if conn.hasVideo {
|
||||||
|
return nil, errors.New("multiple video tracks not supported")
|
||||||
|
}
|
||||||
|
builder = samplebuilder.New(
|
||||||
|
128, &codecs.VP9Packet{}, codec.ClockRate,
|
||||||
|
samplebuilder.WithPartitionHeadChecker(
|
||||||
|
&codecs.VP9PartitionHeadChecker{},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
conn.hasVideo = true
|
||||||
default:
|
default:
|
||||||
client.group.WallOps(
|
client.group.WallOps(
|
||||||
"Cannot record codec " + codec.MimeType,
|
"Cannot record codec " + codec.MimeType,
|
||||||
|
@ -313,6 +325,72 @@ func clonePacket(packet *rtp.Packet) *rtp.Packet {
|
||||||
return &p
|
return &p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isKeyframe(codec string, data []byte) bool {
|
||||||
|
switch strings.ToLower(codec) {
|
||||||
|
case "video/vp8":
|
||||||
|
if len(data) < 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (data[0] & 0x1) == 0
|
||||||
|
case "video/vp9":
|
||||||
|
if len(data) < 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if data[0]&0xC0 != 0x80 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
profile := (data[0] >> 4) & 0x3
|
||||||
|
if profile != 3 {
|
||||||
|
return (data[0] & 0xC) == 0
|
||||||
|
}
|
||||||
|
return (data[0] & 0x6) == 0
|
||||||
|
default:
|
||||||
|
panic("Eek!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyframeDimensions(codec string, data []byte, packet *rtp.Packet) (uint32, uint32) {
|
||||||
|
switch strings.ToLower(codec) {
|
||||||
|
case "video/vp8":
|
||||||
|
if len(data) < 10 {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
raw := uint32(data[6]) | uint32(data[7])<<8 |
|
||||||
|
uint32(data[8])<<16 | uint32(data[9])<<24
|
||||||
|
width := raw & 0x3FFF
|
||||||
|
height := (raw >> 16) & 0x3FFF
|
||||||
|
return width, height
|
||||||
|
case "video/vp9":
|
||||||
|
if packet == nil {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
var vp9 codecs.VP9Packet
|
||||||
|
_, err := vp9.Unmarshal(packet.Payload)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
if(!vp9.V) {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
w := uint32(0)
|
||||||
|
h := uint32(0)
|
||||||
|
for i := range vp9.Width {
|
||||||
|
if i >= len(vp9.Height) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if w < uint32(vp9.Width[i]) {
|
||||||
|
w = uint32(vp9.Width[i])
|
||||||
|
}
|
||||||
|
if h < uint32(vp9.Height[i]) {
|
||||||
|
h = uint32(vp9.Height[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return w, h
|
||||||
|
default:
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (t *diskTrack) WriteRTP(packet *rtp.Packet) error {
|
func (t *diskTrack) WriteRTP(packet *rtp.Packet) error {
|
||||||
// since we call initWriter, we take the connection lock for simplicity.
|
// since we call initWriter, we take the connection lock for simplicity.
|
||||||
t.conn.mu.Lock()
|
t.conn.mu.Lock()
|
||||||
|
@ -322,11 +400,30 @@ func (t *diskTrack) WriteRTP(packet *rtp.Packet) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
codec := t.remote.Codec()
|
||||||
|
|
||||||
p := clonePacket(packet)
|
p := clonePacket(packet)
|
||||||
if p == nil {
|
if p == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.ToLower(codec.MimeType) == "video/vp9" {
|
||||||
|
var vp9 codecs.VP9Packet
|
||||||
|
_, err := vp9.Unmarshal(p.Payload)
|
||||||
|
if err == nil && vp9.B && len(vp9.Payload) >= 1 {
|
||||||
|
profile := (vp9.Payload[0] >> 4) & 0x3
|
||||||
|
kf := false
|
||||||
|
if profile != 3 {
|
||||||
|
kf = (vp9.Payload[0] & 0xC) == 0
|
||||||
|
} else {
|
||||||
|
kf = (vp9.Payload[0] & 0x6) == 0
|
||||||
|
}
|
||||||
|
if kf {
|
||||||
|
t.savedKf = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
kfNeeded := false
|
kfNeeded := false
|
||||||
|
|
||||||
t.builder.Push(p)
|
t.builder.Push(p)
|
||||||
|
@ -342,15 +439,16 @@ func (t *diskTrack) WriteRTP(packet *rtp.Packet) error {
|
||||||
|
|
||||||
keyframe := true
|
keyframe := true
|
||||||
|
|
||||||
codec := t.remote.Codec()
|
|
||||||
switch strings.ToLower(codec.MimeType) {
|
switch strings.ToLower(codec.MimeType) {
|
||||||
case "video/vp8":
|
case "video/vp8", "video/vp9":
|
||||||
if len(sample.Data) < 1 {
|
keyframe = isKeyframe(codec.MimeType, sample.Data)
|
||||||
continue
|
|
||||||
}
|
|
||||||
keyframe = (sample.Data[0]&0x1 == 0)
|
|
||||||
if keyframe {
|
if keyframe {
|
||||||
err := t.initWriter(sample.Data)
|
err := t.conn.initWriter(
|
||||||
|
keyframeDimensions(
|
||||||
|
codec.MimeType, sample.Data,
|
||||||
|
t.savedKf,
|
||||||
|
),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.conn.warn(
|
t.conn.warn(
|
||||||
"Write to disk " + err.Error(),
|
"Write to disk " + err.Error(),
|
||||||
|
@ -400,27 +498,6 @@ func (t *diskTrack) WriteRTP(packet *rtp.Packet) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// called locked
|
|
||||||
func (t *diskTrack) initWriter(data []byte) error {
|
|
||||||
codec := t.remote.Codec()
|
|
||||||
switch strings.ToLower(codec.MimeType) {
|
|
||||||
case "video/vp8":
|
|
||||||
if len(data) < 10 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
keyframe := (data[0]&0x1 == 0)
|
|
||||||
if !keyframe {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
raw := uint32(data[6]) | uint32(data[7])<<8 |
|
|
||||||
uint32(data[8])<<16 | uint32(data[9])<<24
|
|
||||||
width := raw & 0x3FFF
|
|
||||||
height := (raw >> 16) & 0x3FFF
|
|
||||||
return t.conn.initWriter(width, height)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// called locked
|
// called locked
|
||||||
func (conn *diskConn) initWriter(width, height uint32) error {
|
func (conn *diskConn) initWriter(width, height uint32) error {
|
||||||
if conn.file != nil && width == conn.width && height == conn.height {
|
if conn.file != nil && width == conn.width && height == conn.height {
|
||||||
|
@ -453,6 +530,17 @@ func (conn *diskConn) initWriter(width, height uint32) error {
|
||||||
PixelHeight: uint64(height),
|
PixelHeight: uint64(height),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
case "video/vp9":
|
||||||
|
entry = webm.TrackEntry{
|
||||||
|
Name: "Video",
|
||||||
|
TrackNumber: uint64(i + 1),
|
||||||
|
CodecID: "V_VP9",
|
||||||
|
TrackType: 1,
|
||||||
|
Video: &webm.Video{
|
||||||
|
PixelWidth: uint64(width),
|
||||||
|
PixelHeight: uint64(height),
|
||||||
|
},
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return errors.New("unknown track type")
|
return errors.New("unknown track type")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue