Skip to content

Commit 7a095e7

Browse files
authored
Support H265 (#695)
1 parent 579d845 commit 7a095e7

File tree

4 files changed

+86
-19
lines changed

4 files changed

+86
-19
lines changed

examples/filesaver/main.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ import (
2121
"os"
2222
"os/signal"
2323
"strings"
24+
"sync"
2425
"syscall"
2526

2627
"github.com/pion/rtp/codecs"
2728
"github.com/pion/webrtc/v4"
2829
"github.com/pion/webrtc/v4/pkg/media"
2930
"github.com/pion/webrtc/v4/pkg/media/h264writer"
31+
"github.com/pion/webrtc/v4/pkg/media/h265writer"
3032
"github.com/pion/webrtc/v4/pkg/media/ivfwriter"
3133
"github.com/pion/webrtc/v4/pkg/media/oggwriter"
3234

@@ -37,6 +39,8 @@ import (
3739

3840
var (
3941
host, apiKey, apiSecret, roomName, identity string
42+
43+
writeWG sync.WaitGroup
4044
)
4145

4246
func init() {
@@ -74,6 +78,7 @@ func main() {
7478

7579
<-sigChan
7680
room.Disconnect()
81+
writeWG.Wait()
7782
}
7883

7984
func onTrackSubscribed(track *webrtc.TrackRemote, publication *lksdk.RemoteTrackPublication, rp *lksdk.RemoteParticipant) {
@@ -113,6 +118,12 @@ func NewTrackWriter(track *webrtc.TrackRemote, pliWriter lksdk.PLIWriter, fileNa
113118
}))
114119
writer, err = h264writer.New(fileName + ".h264")
115120

121+
case strings.EqualFold(track.Codec().MimeType, "video/h265"):
122+
sb = samplebuilder.New(maxVideoLate, &codecs.H265Packet{}, track.Codec().ClockRate, samplebuilder.WithPacketDroppedHandler(func() {
123+
pliWriter(track.SSRC())
124+
}))
125+
writer, err = h265writer.New(fileName + ".h265")
126+
116127
case strings.EqualFold(track.Codec().MimeType, "audio/opus"):
117128
sb = samplebuilder.New(maxAudioLate, &codecs.OpusPacket{}, track.Codec().ClockRate)
118129
writer, err = oggwriter.New(fileName+".ogg", 48000, track.Codec().Channels)
@@ -130,21 +141,29 @@ func NewTrackWriter(track *webrtc.TrackRemote, pliWriter lksdk.PLIWriter, fileNa
130141
writer: writer,
131142
track: track,
132143
}
144+
writeWG.Add(1)
133145
go t.start()
134146
return t, nil
135147
}
136148

137149
func (t *TrackWriter) start() {
138-
defer t.writer.Close()
150+
defer func() {
151+
t.writer.Close()
152+
writeWG.Done()
153+
}()
154+
139155
for {
140156
pkt, _, err := t.track.ReadRTP()
141157
if err != nil {
158+
logger.Errorw("failed to read rtp packet", err)
142159
break
143160
}
144161
t.sb.Push(pkt)
145162

146163
for _, p := range t.sb.PopPackets() {
147-
t.writer.WriteRTP(p)
164+
if err := t.writer.WriteRTP(p); err != nil {
165+
logger.Errorw("failed to write rtp packet", err)
166+
}
148167
}
149168
}
150169
}

go.mod

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ require (
1515
github.com/pion/dtls/v3 v3.0.6
1616
github.com/pion/interceptor v0.1.40
1717
github.com/pion/rtcp v1.2.15
18-
github.com/pion/rtp v1.8.19
19-
github.com/pion/sdp/v3 v3.0.13
20-
github.com/pion/webrtc/v4 v4.1.2
18+
github.com/pion/rtp v1.8.20
19+
github.com/pion/sdp/v3 v3.0.14
20+
github.com/pion/webrtc/v4 v4.1.3
2121
github.com/stretchr/testify v1.10.0
2222
github.com/twitchtv/twirp v8.1.3+incompatible
2323
go.uber.org/atomic v1.11.0
@@ -58,11 +58,11 @@ require (
5858
github.com/opencontainers/runc v1.1.14 // indirect
5959
github.com/pion/datachannel v1.5.10 // indirect
6060
github.com/pion/ice/v4 v4.0.10 // indirect
61-
github.com/pion/logging v0.2.3 // indirect
61+
github.com/pion/logging v0.2.4 // indirect
6262
github.com/pion/mdns/v2 v2.0.7 // indirect
6363
github.com/pion/randutil v0.1.0 // indirect
6464
github.com/pion/sctp v1.8.39 // indirect
65-
github.com/pion/srtp/v3 v3.0.5 // indirect
65+
github.com/pion/srtp/v3 v3.0.6 // indirect
6666
github.com/pion/stun/v3 v3.0.0 // indirect
6767
github.com/pion/transport/v3 v3.0.7 // indirect
6868
github.com/pion/turn/v4 v4.0.2 // indirect

go.sum

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -133,30 +133,30 @@ github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=
133133
github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
134134
github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4=
135135
github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic=
136-
github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=
137-
github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90=
136+
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
137+
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
138138
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
139139
github.com/pion/mdns/v2 v2.0.7/go.mod h1:vAdSYNAT0Jy3Ru0zl2YiW3Rm/fJCwIeM0nToenfOJKA=
140140
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
141141
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
142142
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
143143
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
144-
github.com/pion/rtp v1.8.19 h1:jhdO/3XhL/aKm/wARFVmvTfq0lC/CvN1xwYKmduly3c=
145-
github.com/pion/rtp v1.8.19/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
144+
github.com/pion/rtp v1.8.20 h1:8zcyqohadZE8FCBeGdyEvHiclPIezcwRQH9zfapFyYI=
145+
github.com/pion/rtp v1.8.20/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
146146
github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
147147
github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
148-
github.com/pion/sdp/v3 v3.0.13 h1:uN3SS2b+QDZnWXgdr69SM8KB4EbcnPnPf2Laxhty/l4=
149-
github.com/pion/sdp/v3 v3.0.13/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
150-
github.com/pion/srtp/v3 v3.0.5 h1:8XLB6Dt3QXkMkRFpoqC3314BemkpMQK2mZeJc4pUKqo=
151-
github.com/pion/srtp/v3 v3.0.5/go.mod h1:r1G7y5r1scZRLe2QJI/is+/O83W2d+JoEsuIexpw+uM=
148+
github.com/pion/sdp/v3 v3.0.14 h1:1h7gBr9FhOWH5GjWWY5lcw/U85MtdcibTyt/o6RxRUI=
149+
github.com/pion/sdp/v3 v3.0.14/go.mod h1:88GMahN5xnScv1hIMTqLdu/cOcUkj6a9ytbncwMCq2E=
150+
github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4=
151+
github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY=
152152
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
153153
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
154154
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
155155
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
156156
github.com/pion/turn/v4 v4.0.2 h1:ZqgQ3+MjP32ug30xAbD6Mn+/K4Sxi3SdNOTFf+7mpps=
157157
github.com/pion/turn/v4 v4.0.2/go.mod h1:pMMKP/ieNAG/fN5cZiN4SDuyKsXtNTr0ccN7IToA1zs=
158-
github.com/pion/webrtc/v4 v4.1.2 h1:mpuUo/EJ1zMNKGE79fAdYNFZBX790KE7kQQpLMjjR54=
159-
github.com/pion/webrtc/v4 v4.1.2/go.mod h1:xsCXiNAmMEjIdFxAYU0MbB3RwRieJsegSB2JZsGN+8U=
158+
github.com/pion/webrtc/v4 v4.1.3 h1:YZ67Boj9X/hk190jJZ8+HFGQ6DqSZ/fYP3sLAZv7c3c=
159+
github.com/pion/webrtc/v4 v4.1.3/go.mod h1:rsq+zQ82ryfR9vbb0L1umPJ6Ogq7zm8mcn9fcGnxomM=
160160
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
161161
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
162162
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

readersampleprovider.go

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/pion/webrtc/v4"
2626
"github.com/pion/webrtc/v4/pkg/media"
2727
"github.com/pion/webrtc/v4/pkg/media/h264reader"
28+
"github.com/pion/webrtc/v4/pkg/media/h265reader"
2829
"github.com/pion/webrtc/v4/pkg/media/ivfreader"
2930

3031
"github.com/livekit/server-sdk-go/v2/pkg/oggreader"
@@ -55,6 +56,9 @@ type ReaderSampleProvider struct {
5556
// for h264
5657
h264reader *h264reader.H264Reader
5758

59+
// for h265
60+
h265reader *h265reader.H265Reader
61+
5862
// for ogg
5963
oggReader *oggreader.OggReader
6064
}
@@ -126,6 +130,8 @@ func NewLocalFileTrack(file string, options ...ReaderSampleProviderOption) (*Loc
126130
return nil, ErrCannotDetermineMime
127131
}
128132
_, _ = fp.Seek(0, 0)
133+
case ".h265":
134+
mime = webrtc.MimeTypeH265
129135
case ".ogg":
130136
mime = webrtc.MimeTypeOpus
131137
default:
@@ -156,7 +162,7 @@ func NewLocalReaderTrack(in io.ReadCloser, mime string, options ...ReaderSampleP
156162

157163
// check if mime type is supported
158164
switch provider.Mime {
159-
case webrtc.MimeTypeH264, webrtc.MimeTypeOpus, webrtc.MimeTypeVP8, webrtc.MimeTypeVP9:
165+
case webrtc.MimeTypeH264, webrtc.MimeTypeH265, webrtc.MimeTypeOpus, webrtc.MimeTypeVP8, webrtc.MimeTypeVP9:
160166
// allow
161167
default:
162168
return nil, ErrUnsupportedFileType
@@ -178,14 +184,16 @@ func NewLocalReaderTrack(in io.ReadCloser, mime string, options ...ReaderSampleP
178184

179185
func (p *ReaderSampleProvider) OnBind() error {
180186
// If we are not closing on unbind, don't do anything on rebind
181-
if p.ivfReader != nil || p.h264reader != nil || p.oggReader != nil {
187+
if p.ivfReader != nil || p.h264reader != nil || p.oggReader != nil || p.h265reader != nil {
182188
return nil
183189
}
184190

185191
var err error
186192
switch p.Mime {
187193
case webrtc.MimeTypeH264:
188194
p.h264reader, err = h264reader.NewReader(p.reader)
195+
case webrtc.MimeTypeH265:
196+
p.h265reader, err = h265reader.NewReader(p.reader)
189197
case webrtc.MimeTypeVP8, webrtc.MimeTypeVP9:
190198
var ivfHeader *ivfreader.IVFFileHeader
191199
p.ivfReader, ivfHeader, err = ivfreader.NewWith(p.reader)
@@ -244,6 +252,46 @@ func (p *ReaderSampleProvider) NextSample(ctx context.Context) (media.Sample, er
244252
return sample, nil
245253
}
246254
sample.Duration = defaultH264FrameDuration
255+
case webrtc.MimeTypeH265:
256+
var (
257+
isFrame bool
258+
needPrefix bool
259+
)
260+
261+
for {
262+
nal, err := p.h265reader.NextNAL()
263+
if err != nil {
264+
return sample, err
265+
}
266+
267+
// aggregate vps,sps,pps into a single AP packet (chrome requires this)
268+
if nal.NalUnitType == 32 || nal.NalUnitType == 33 || nal.NalUnitType == 34 {
269+
sample.Data = append(sample.Data, []byte{0, 0, 0, 1}...) // add NAL prefix
270+
sample.Data = append(sample.Data, nal.Data...)
271+
needPrefix = true
272+
continue
273+
}
274+
275+
if needPrefix {
276+
sample.Data = append(sample.Data, []byte{0, 0, 0, 1}...) // add NAL prefix
277+
sample.Data = append(sample.Data, nal.Data...)
278+
} else {
279+
sample.Data = nal.Data
280+
}
281+
282+
if !isFrame {
283+
isFrame = nal.NalUnitType < 32
284+
}
285+
286+
if !isFrame {
287+
// return it without duration
288+
return sample, nil
289+
}
290+
291+
sample.Duration = defaultH264FrameDuration
292+
break
293+
}
294+
247295
case webrtc.MimeTypeVP8, webrtc.MimeTypeVP9:
248296
frame, header, err := p.ivfReader.ParseNextFrame()
249297
if err != nil {

0 commit comments

Comments
 (0)