Adds support H265 for MP4

This commit is contained in:
Alexey Khit
2022-09-18 17:16:18 +03:00
parent 34f5b99126
commit 9596c6139f
6 changed files with 199 additions and 1 deletions
+3
View File
@@ -0,0 +1,3 @@
## Useful links
- https://datatracker.ietf.org/doc/html/rfc7798
+35
View File
@@ -0,0 +1,35 @@
package h265
import (
"encoding/base64"
"github.com/AlexxIT/go2rtc/pkg/streamer"
)
const (
NALUnitTypeIFrame = 19
)
func NALUnitType(b []byte) byte {
return b[4] >> 1
}
func IsKeyframe(b []byte) bool {
return NALUnitType(b) == NALUnitTypeIFrame
}
func GetParameterSet(fmtp string) (vps, sps, pps []byte) {
if fmtp == "" {
return
}
s := streamer.Between(fmtp, "sprop-vps=", ";")
vps, _ = base64.StdEncoding.DecodeString(s)
s = streamer.Between(fmtp, "sprop-sps=", ";")
sps, _ = base64.StdEncoding.DecodeString(s)
s = streamer.Between(fmtp, "sprop-pps=", ";")
pps, _ = base64.StdEncoding.DecodeString(s)
return
}
+57
View File
@@ -0,0 +1,57 @@
package h265
import (
"encoding/binary"
"github.com/AlexxIT/go2rtc/pkg/h264"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/deepch/vdk/codec/h265parser"
"github.com/pion/rtp"
)
func RTPDepay(track *streamer.Track) streamer.WrapperFunc {
var buffer []byte
return func(push streamer.WriterFunc) streamer.WriterFunc {
return func(packet *rtp.Packet) error {
naluType := (packet.Payload[0] >> 1) & 0x3f
//fmt.Printf(
// "[RTP] codec: %s, nalu: %2d, size: %6d, ts: %10d, pt: %2d, ssrc: %d, seq: %d\n",
// track.Codec.Name, naluType, len(packet.Payload), packet.Timestamp,
// packet.PayloadType, packet.SSRC, packet.SequenceNumber,
//)
switch naluType {
case h265parser.NAL_UNIT_CODED_SLICE_TRAIL_R:
case h265parser.NAL_UNIT_VPS:
case h265parser.NAL_UNIT_SPS:
case h265parser.NAL_UNIT_PPS:
case h265parser.NAL_UNIT_UNSPECIFIED_49:
data := packet.Payload
switch data[2] >> 6 {
case 2: // begin
buffer = []byte{
(data[0] & 0x81) | (data[2] & 0x3f << 1), data[1],
}
buffer = append(buffer, data[3:]...)
return nil
case 0: // continue
buffer = append(buffer, data[3:]...)
return nil
case 1: // end
packet.Payload = append(buffer, data[3:]...)
}
default:
//panic("not implemented")
}
size := make([]byte, 4)
binary.BigEndian.PutUint32(size, uint32(len(packet.Payload)))
clone := *packet
clone.Version = h264.RTPPacketVersionAVC
clone.Payload = append(size, packet.Payload...)
return push(&clone)
}
}
}
+23
View File
@@ -0,0 +1,23 @@
## HEVC
Browser | avc1 | hvc1 | hev1
------------|------|------|---
Mac Chrome | + | - | +
Mac Safari | + | + | -
iOS 15? | + | + | -
Mac Firefox | + | - | -
iOS 12 | + | - | -
Android 13 | + | - | -
```
ffmpeg -i input-hev1.mp4 -c:v copy -tag:v hvc1 -c:a copy output-hvc1.mp4
Stream #0:0(eng): Video: hevc (Main) (hev1 / 0x31766568), yuv420p(tv, progressive), 720x404, 164 kb/s, 29.97 fps,
Stream #0:0(eng): Video: hevc (Main) (hvc1 / 0x31637668), yuv420p(tv, progressive), 720x404, 164 kb/s, 29.97 fps,
```
## Useful links
- https://stackoverflow.com/questions/63468587/what-hevc-codec-tag-to-use-with-fmp4-hvc1-or-hev1
- https://stackoverflow.com/questions/32152090/encode-h265-to-hvc1-codec
- https://jellyfin.org/docs/general/clients/codec-support.html
- https://github.com/StaZhu/enable-chromium-hevc-hardware-decoding
+32
View File
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"github.com/AlexxIT/go2rtc/pkg/h264"
"github.com/AlexxIT/go2rtc/pkg/h265"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/pion/rtp"
)
@@ -28,6 +29,7 @@ func (c *Consumer) GetMedias() []*streamer.Media {
Direction: streamer.DirectionRecvonly,
Codecs: []*streamer.Codec{
{Name: streamer.CodecH264, ClockRate: 90000},
{Name: streamer.CodecH265, ClockRate: 90000},
},
},
//{
@@ -74,6 +76,36 @@ func (c *Consumer) AddTrack(media *streamer.Media, track *streamer.Track) *strea
push = wrapper(push)
}
return track.Bind(push)
case streamer.CodecH265:
c.codecs = append(c.codecs, track.Codec)
push := func(packet *rtp.Packet) error {
if packet.Version != h264.RTPPacketVersionAVC {
return nil
}
if !c.start {
if h265.IsKeyframe(packet.Payload) {
c.start = true
} else {
return nil
}
}
buf := c.muxer.Marshal(packet)
c.send += len(buf)
c.Fire(buf)
return nil
}
if !h264.IsAVC(codec) {
wrapper := h265.RTPDepay(track)
push = wrapper(push)
}
return track.Bind(push)
}
+49 -1
View File
@@ -4,8 +4,10 @@ import (
"encoding/binary"
"fmt"
"github.com/AlexxIT/go2rtc/pkg/h264"
"github.com/AlexxIT/go2rtc/pkg/h265"
"github.com/AlexxIT/go2rtc/pkg/streamer"
"github.com/deepch/vdk/codec/h264parser"
"github.com/deepch/vdk/codec/h265parser"
"github.com/deepch/vdk/format/fmp4/fmp4io"
"github.com/deepch/vdk/format/mp4/mp4io"
"github.com/deepch/vdk/format/mp4f/mp4fio"
@@ -27,6 +29,9 @@ func (m *Muxer) MimeType(codecs []*streamer.Codec) string {
switch codec.Name {
case streamer.CodecH264:
s += "avc1." + h264.GetProfileLevelID(codec.FmtpLine)
case streamer.CodecH265:
// +Safari +Chrome +Edge -iOS15 -Android13
s += "hvc1.1.6.L93.B0" // hev1.1.6.L93.B0
}
}
@@ -80,6 +85,49 @@ func (m *Muxer) GetInit(codecs []*streamer.Codec) ([]byte, error) {
Name: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'm', 'a', 'i', 'n', 0},
}
moov.Tracks = append(moov.Tracks, trak)
case streamer.CodecH265:
vps, sps, pps := h265.GetParameterSet(codec.FmtpLine)
if sps == nil {
return nil, fmt.Errorf("empty SPS: %#v", codec)
}
codecData, err := h265parser.NewCodecDataFromVPSAndSPSAndPPS(vps, sps, pps)
if err != nil {
return nil, err
}
width := codecData.Width()
height := codecData.Height()
trak := TRAK()
trak.Media.Header.TimeScale = int32(codec.ClockRate)
trak.Header.TrackWidth = float64(width)
trak.Header.TrackHeight = float64(height)
trak.Media.Info.Video = &mp4io.VideoMediaInfo{
Flags: 0x000001,
}
trak.Media.Info.Sample.SampleDesc.HV1Desc = &mp4io.HV1Desc{
DataRefIdx: 1,
HorizontalResolution: 72,
VorizontalResolution: 72,
Width: int16(width),
Height: int16(height),
FrameCount: 1,
Depth: 24,
ColorTableId: -1,
Conf: &mp4io.HV1Conf{
Data: codecData.AVCDecoderConfRecordBytes(),
},
}
trak.Media.Handler = &mp4io.HandlerRefer{
SubType: [4]byte{'v', 'i', 'd', 'e'},
Name: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'm', 'a', 'i', 'n', 0},
}
moov.Tracks = append(moov.Tracks, trak)
}
}
@@ -126,7 +174,7 @@ func (m *Muxer) Marshal(packet *rtp.Packet) []byte {
entry := mp4io.TrackFragRunEntry{
//Duration: 90000,
Size: uint32(len(packet.Payload)),
Size: uint32(len(packet.Payload)),
}
newTime := packet.Timestamp