-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathencode.go
More file actions
2747 lines (2576 loc) · 100 KB
/
Copy pathencode.go
File metadata and controls
2747 lines (2576 loc) · 100 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package goav1
// encode.go is the public realtime encoding surface. An Encoder turns
// caller-owned pictures into AV1 low-overhead temporal units (a keyframe first,
// then motion-compensated inter frames) under fixed quality or CBR rate
// control, with optional temporal layering and WebRTC dependency-descriptor
// packaging through RTCEncoder. I420/NV12/NV21 preserve 4:2:0 chroma samples;
// I422 and I444 inputs are preserved when an RTCEncoder is explicitly
// configured for profile-2 native 4:2:2 or profile-1 native 4:4:4 and otherwise
// resampled.
// I400 and monochrome Frame inputs fill neutral chroma unless an RTCEncoder is
// explicitly configured for native monochrome across WebRTC L*/S* modes.
// Explicit high-bit-depth RTC configs preserve 10/12-bit I400/monochrome,
// I420/4:2:0, I422/4:2:2, and I444/4:4:4 Frame input across single-spatial,
// simulcast, and shared-reference SVC streams. Native 10/12-bit monochrome
// lossless/lossy keyframes and P-frames, plus standalone native 10/12-bit
// 4:2:0 color lossless/lossy keyframes and lossy P-frames, are also available through
// EncodeI400HighBitDepthKeyframe, EncodeI400HighBitDepthPFrame,
// EncodeI420HighBitDepthLosslessKeyframe, EncodeI420HighBitDepthKeyframe, and
// EncodeI420HighBitDepthPFrame. Every
// emitted stream decodes bit-exactly to the encoder's own reconstruction in
// this package's Decoder and in the reference decoders.
import (
"fmt"
"github.com/thesyncim/goav1/internal/av1/encoder"
)
// I420Frame is one 8-bit 4:2:0 picture. Y holds Width x Height luma samples
// at YStride; U and V hold the half-resolution chroma planes at ChromaStride.
type I420Frame = encoder.SourceFrame420
type EncoderDecisionStats = encoder.EncoderDecisionStats
// I420HighBitDepthFrame is one 10/12-bit 4:2:0 picture. Y holds Width x Height
// samples at YStride; U and V hold half-resolution chroma planes at
// ChromaStride, in uint16 values whose high bits above BitDepth must be zero.
type I420HighBitDepthFrame = encoder.SourceFrame42016
// I422Frame is one 8-bit 4:2:2 picture. Y holds Width x Height luma samples
// at YStride; U and V hold half-width, full-height chroma planes at
// ChromaStride. The friendly realtime encoder resamples this input to 4:2:0
// unless RTCEncoder is configured for native profile-2 4:2:2.
type I422Frame struct {
Y []byte
U []byte
V []byte
YStride int
ChromaStride int
Width int
Height int
}
// I422HighBitDepthFrame is one 10/12-bit 4:2:2 picture. Y holds Width x Height
// samples at YStride; U and V hold half-width, full-height chroma planes at
// ChromaStride, in uint16 values whose high bits above BitDepth must be zero.
type I422HighBitDepthFrame struct {
Y []uint16
U []uint16
V []uint16
YStride int
ChromaStride int
Width int
Height int
BitDepth uint8
}
// I444Frame is one 8-bit 4:4:4 picture. Y, U, and V all hold Width x Height
// samples at their respective strides. The friendly realtime encoder resamples
// this input to 4:2:0 unless RTCEncoder is configured for native profile-1
// 4:4:4.
type I444Frame struct {
Y []byte
U []byte
V []byte
YStride int
UStride int
VStride int
Width int
Height int
}
// I444HighBitDepthFrame is one 10/12-bit 4:4:4 picture. Y, U, and V all hold
// Width x Height samples at their respective strides, in uint16 values whose
// high bits above BitDepth must be zero.
type I444HighBitDepthFrame struct {
Y []uint16
U []uint16
V []uint16
YStride int
UStride int
VStride int
Width int
Height int
BitDepth uint8
}
// I400Frame is one 8-bit monochrome picture. Y holds Width x Height luma
// samples at YStride. The friendly realtime stream encoder fills neutral chroma
// for I400 inputs unless an RTCEncoder is explicitly configured for native
// monochrome. EncodeI400Keyframe, EncodeI400PFrame,
// EncodeI400HighBitDepthKeyframe, and EncodeI400HighBitDepthPFrame always emit
// native AV1 monochrome pictures.
type I400Frame struct {
Y []byte
YStride int
Width int
Height int
}
// I400HighBitDepthFrame is one 10/12-bit monochrome picture. Y holds
// Width x Height samples at YStride, in uint16 values whose high bits above
// BitDepth must be zero.
type I400HighBitDepthFrame struct {
Y []uint16
YStride int
Width int
Height int
BitDepth uint8
}
// EncodeI400Keyframe encodes one native AV1 monochrome keyframe.
//
// qIndex 0 emits a lossless keyframe. qIndex 1..255 emits a non-lossless
// keyframe and returns the encoder-side reconstruction a conformant decoder
// must reproduce exactly. Frame dimensions must be positive multiples of 8.
func EncodeI400Keyframe(frame I400Frame, qIndex uint8) ([]byte, I400Frame, error) {
src := encoder.SourceFrameMono(frame)
if qIndex == 0 {
tu, err := encoder.EncodeLosslessMonochromeKeyframe(src)
if err != nil {
return nil, I400Frame{}, err
}
recon := I400Frame{
Y: make([]byte, len(frame.Y)),
YStride: frame.YStride,
Width: frame.Width,
Height: frame.Height,
}
copy(recon.Y, frame.Y)
return tu, recon, nil
}
tu, recon, err := encoder.EncodeMonochromeKeyframe(src, qIndex)
if err != nil {
return nil, I400Frame{}, err
}
return tu, I400Frame(recon), nil
}
// EncodeI400HighBitDepthKeyframe encodes one native 10/12-bit AV1 monochrome
// keyframe.
//
// qIndex 0 emits a lossless keyframe. qIndex 1..255 emits a non-lossless
// keyframe and returns the encoder-side reconstruction a conformant decoder
// must reproduce exactly. Frame dimensions must be positive multiples of 8.
func EncodeI400HighBitDepthKeyframe(frame I400HighBitDepthFrame, qIndex uint8) ([]byte, I400HighBitDepthFrame, error) {
if qIndex == 0 {
return EncodeI400HighBitDepthLosslessKeyframe(frame)
}
src := encoder.SourceFrameMono16(frame)
tu, recon, err := encoder.EncodeHighBitDepthMonochromeKeyframe(src, qIndex)
if err != nil {
return nil, I400HighBitDepthFrame{}, err
}
return tu, I400HighBitDepthFrame(recon), nil
}
// EncodeI400HighBitDepthLosslessKeyframe encodes one native 10/12-bit AV1
// monochrome lossless keyframe. The returned reconstruction aliases a fresh
// copy of frame.Y because lossless output must reproduce the source exactly.
func EncodeI400HighBitDepthLosslessKeyframe(frame I400HighBitDepthFrame) ([]byte, I400HighBitDepthFrame, error) {
src := encoder.SourceFrameMono16(frame)
tu, err := encoder.EncodeLosslessHighBitDepthMonochromeKeyframe(src)
if err != nil {
return nil, I400HighBitDepthFrame{}, err
}
recon := I400HighBitDepthFrame{
Y: make([]uint16, len(frame.Y)),
YStride: frame.YStride,
Width: frame.Width,
Height: frame.Height,
BitDepth: frame.BitDepth,
}
copy(recon.Y, frame.Y)
return tu, recon, nil
}
// EncodeI420HighBitDepthLosslessKeyframe encodes one native 10/12-bit AV1
// 4:2:0 lossless keyframe. The returned reconstruction aliases a fresh copy of
// frame planes because lossless output must reproduce the source exactly.
func EncodeI420HighBitDepthLosslessKeyframe(frame I420HighBitDepthFrame) ([]byte, I420HighBitDepthFrame, error) {
tu, recon, err := encoder.EncodeHighBitDepth420Keyframe(encoder.SourceFrame42016(frame), 0)
if err != nil {
return nil, I420HighBitDepthFrame{}, err
}
return tu, I420HighBitDepthFrame(recon), nil
}
// EncodeI420HighBitDepthKeyframe encodes one native 10/12-bit AV1 4:2:0
// keyframe and returns the encoder-side reconstruction a conformant decoder
// must reproduce exactly. qIndex 0 emits a lossless keyframe; qIndex 1..255
// emits a non-lossless keyframe.
func EncodeI420HighBitDepthKeyframe(frame I420HighBitDepthFrame, qIndex uint8) ([]byte, I420HighBitDepthFrame, error) {
if qIndex == 0 {
return EncodeI420HighBitDepthLosslessKeyframe(frame)
}
tu, recon, err := encoder.EncodeHighBitDepth420Keyframe(encoder.SourceFrame42016(frame), qIndex)
if err != nil {
return nil, I420HighBitDepthFrame{}, err
}
return tu, I420HighBitDepthFrame(recon), nil
}
// EncodeI420HighBitDepthPFrame encodes one native 10/12-bit AV1 4:2:0 inter
// frame.
//
// ref must be the previous native high-bit-depth 4:2:0 reconstruction for the
// same coded geometry and bit depth, usually the reconstruction returned by
// EncodeI420HighBitDepthKeyframe or a prior EncodeI420HighBitDepthPFrame call.
// qIndex 0 emits a lossless inter frame; qIndex 1..255 emits a non-lossless
// inter frame.
func EncodeI420HighBitDepthPFrame(frame I420HighBitDepthFrame, ref I420HighBitDepthFrame, qIndex uint8) ([]byte, I420HighBitDepthFrame, error) {
tu, recon, err := encoder.EncodeHighBitDepth420PFrame(encoder.SourceFrame42016(frame), encoder.SourceFrame42016(ref), qIndex)
if err != nil {
return nil, I420HighBitDepthFrame{}, err
}
return tu, I420HighBitDepthFrame(recon), nil
}
// EncodeI400HighBitDepthPFrame encodes one native 10/12-bit AV1 monochrome
// inter frame.
//
// ref must be the previous native high-bit-depth monochrome reconstruction for
// the same coded geometry and bit depth, usually the reconstruction returned by
// EncodeI400HighBitDepthKeyframe or a prior EncodeI400HighBitDepthPFrame call.
// qIndex 0 emits a lossless inter frame; qIndex 1..255 emits a non-lossless
// inter frame.
func EncodeI400HighBitDepthPFrame(frame I400HighBitDepthFrame, ref I400HighBitDepthFrame, qIndex uint8) ([]byte, I400HighBitDepthFrame, error) {
tu, recon, err := encoder.EncodeHighBitDepthMonochromePFrame(encoder.SourceFrameMono16(frame), encoder.SourceFrameMono16(ref), qIndex)
if err != nil {
return nil, I400HighBitDepthFrame{}, err
}
return tu, I400HighBitDepthFrame(recon), nil
}
// EncodeI400PFrame encodes one native AV1 monochrome inter frame.
//
// ref must be the previous native monochrome reconstruction for the same coded
// geometry, usually the reconstruction returned by EncodeI400Keyframe or a prior
// EncodeI400PFrame call. qIndex 0 emits a lossless inter frame; qIndex 1..255
// emits a non-lossless inter frame.
func EncodeI400PFrame(frame I400Frame, ref I400Frame, qIndex uint8) ([]byte, I400Frame, error) {
tu, recon, err := encoder.EncodeMonochromePFrame(encoder.SourceFrameMono(frame), encoder.SourceFrameMono(ref), qIndex)
if err != nil {
return nil, I400Frame{}, err
}
return tu, I400Frame(recon), nil
}
// NV12Frame is one 8-bit 4:2:0 picture in semi-planar NV12 layout. Y holds
// Width x Height luma samples at YStride; UV holds interleaved U,V pairs for
// the half-resolution chroma plane at UVStride bytes per chroma row.
type NV12Frame struct {
Y []byte
UV []byte
YStride int
UVStride int
Width int
Height int
}
// NV21Frame is one 8-bit 4:2:0 picture in semi-planar NV21 layout. Y holds
// Width x Height luma samples at YStride; VU holds interleaved V,U pairs for
// the half-resolution chroma plane at VUStride bytes per chroma row.
type NV21Frame struct {
Y []byte
VU []byte
YStride int
VUStride int
Width int
Height int
}
const (
EncoderDecisionBlockLevelCount = encoder.EncoderDecisionBlockLevelCount
EncoderDecisionPartitionCount = encoder.EncoderDecisionPartitionCount
EncoderDecisionBlockSizeCount = encoder.EncoderDecisionBlockSizeCount
EncoderDecisionInterModeCount = encoder.EncoderDecisionInterModeCount
EncoderDecisionCompoundInterModeCount = encoder.EncoderDecisionCompoundInterModeCount
EncoderDecisionReferenceFrameCount = encoder.EncoderDecisionReferenceFrameCount
EncoderDecisionTransformTypeCount = encoder.EncoderDecisionTransformTypeCount
EncoderDecisionBlockSize8x8 = encoder.EncoderDecisionBlockSize8x8
EncoderDecisionBlockSize16x16 = encoder.EncoderDecisionBlockSize16x16
EncoderDecisionBlockSize32x32 = encoder.EncoderDecisionBlockSize32x32
EncoderDecisionBlockSize64x64 = encoder.EncoderDecisionBlockSize64x64
EncoderDecisionBlockSize16x8 = encoder.EncoderDecisionBlockSize16x8
EncoderDecisionBlockSize8x16 = encoder.EncoderDecisionBlockSize8x16
EncoderDecisionBlockSize32x16 = encoder.EncoderDecisionBlockSize32x16
EncoderDecisionBlockSize16x32 = encoder.EncoderDecisionBlockSize16x32
EncoderDecisionReferenceLast = encoder.EncoderDecisionReferenceLast
EncoderDecisionReferenceGolden = encoder.EncoderDecisionReferenceGolden
EncoderDecisionTransformDCTDCT = encoder.EncoderDecisionTransformDCTDCT
EncoderDecisionTransformADSTDCT = encoder.EncoderDecisionTransformADSTDCT
EncoderDecisionTransformDCTADST = encoder.EncoderDecisionTransformDCTADST
EncoderDecisionTransformADSTADST = encoder.EncoderDecisionTransformADSTADST
EncoderDecisionTransformIDTX = encoder.EncoderDecisionTransformIDTX
)
// VideoEncoderConfig configures a realtime encoder.
type VideoEncoderConfig struct {
// Width and Height are the frame dimensions in pixels; both must be
// even and at least 16. Dimensions that are not multiples of 8 encode
// at the next padded coded size with render_size carrying the true
// dimensions; decoded surfaces are the coded size and display crops to
// the render size.
Width, Height int
// QIndex selects fixed-quality encoding (1..255) when TargetBitrate is
// zero.
QIndex uint8
// TargetBitrate, when positive, enables CBR rate control at this many
// bits per second; Framerate must then also be positive. MinQIndex and
// MaxQIndex bound the controller (defaults 20 and 200).
TargetBitrate int
Framerate int
MinQIndex uint8
MaxQIndex uint8
// TemporalLayers selects the layering mode: 0 or 1 for a flat stream,
// 2 for L1T2 with droppable odd frames, 3 for L1T3 (T0/T2/T1/T2 groups
// with a droppable top layer and a middle layer the trailing T2
// references).
TemporalLayers int
// TileColumns overrides the tile-column count used for parallel inter
// encoding (rounded down to a power of two, clamped to the legal range);
// zero selects automatically from the frame width. Ignored when MaxThreads
// is positive.
TileColumns int
// MaxThreads bounds encoder execution lanes. Positive values select the
// same tile-column count; MaxThreads=1 also disables internal worker fan-out
// so one-lane realtime deployments avoid scheduler noise. Zero keeps the
// automatic tile-column policy unless TileColumns is set.
MaxThreads int
// Speed selects the WebRTC realtime effort level. Zero is the default
// quality/speed balance; EncoderWebRTCMinEffortLevel is fastest and
// EncoderWebRTCMaxEffortLevel is slowest.
Speed int8
// GoldenInterval is the number of base-layer frames between golden
// anchor refreshes; zero keeps the default (16) and a negative value
// disables golden references.
GoldenInterval int
// DisableSceneCutKeyframes disables automatic keyframe promotion on hard
// content cuts. The default keeps the stream encoder's historical behavior.
DisableSceneCutKeyframes bool
}
// EncodedFrame is one encoded picture as a low-overhead temporal unit.
type EncodedFrame struct {
// Data is the temporal unit. It aliases an encoder-owned buffer reused by
// the next Encode call; copy it before retaining or sending asynchronously.
Data []byte
// Keyframe reports whether this frame restarts the decode chain.
Keyframe bool
// TemporalID is the frame's temporal layer (always 0 without layering).
TemporalID uint8
}
// KeyframeEncoder encodes repeated shown 8-bit 4:2:0 keyframes with reusable
// state. Returned Data and Reconstruction planes alias encoder-owned storage and
// remain valid until the next Encode call.
type KeyframeEncoder struct {
enc *encoder.KeyframeEncoder
recon I420Frame
}
// NewKeyframeEncoder creates a reusable keyframe-only encoder for same-sized
// 8-bit 4:2:0 frames. qIndex must be 1..255.
func NewKeyframeEncoder(width, height int, qIndex uint8) (*KeyframeEncoder, error) {
enc, err := encoder.NewKeyframeEncoder(width, height, qIndex)
if err != nil {
return nil, err
}
if err := enc.Prewarm(); err != nil {
_ = enc.Close()
return nil, err
}
return &KeyframeEncoder{enc: enc}, nil
}
// SetQIndex updates the keyframe quantizer for subsequent Encode calls.
func (e *KeyframeEncoder) SetQIndex(qIndex uint8) error {
if e == nil || e.enc == nil {
return fmt.Errorf("goav1: KeyframeEncoder is not initialized")
}
return e.enc.SetQIndex(qIndex)
}
// Encode emits frame as a shown keyframe.
func (e *KeyframeEncoder) Encode(frame I420Frame) (EncodedFrame, error) {
if e == nil || e.enc == nil {
return EncodedFrame{}, fmt.Errorf("goav1: KeyframeEncoder is not initialized")
}
if err := validateI420Frame(frame); err != nil {
return EncodedFrame{}, err
}
tu, recon, err := e.enc.Encode(frame)
if err != nil {
return EncodedFrame{}, err
}
e.recon = recon
return EncodedFrame{Data: tu, Keyframe: true}, nil
}
// Reconstruction returns the most recent keyframe reconstruction. The planes
// alias encoder-owned buffers that are reused by the next Encode call.
func (e *KeyframeEncoder) Reconstruction() I420Frame {
if e == nil || e.enc == nil {
return I420Frame{}
}
return e.recon
}
// Close waits for any background encoder work to finish and releases
// persistent workers. It is safe to call more than once.
func (e *KeyframeEncoder) Close() error {
if e == nil || e.enc == nil {
return nil
}
err := e.enc.Close()
e.enc = nil
return err
}
// VideoEncoder encodes a stream of same-sized 4:2:0 frames.
type VideoEncoder struct {
enc *encoder.VideoEncoder
yuv420Scratch I420Frame
}
// NewVideoEncoder creates an encoder from cfg.
func NewVideoEncoder(cfg VideoEncoderConfig) (*VideoEncoder, error) {
enc, err := newVideoEncoder(cfg)
if err != nil {
return nil, err
}
return &VideoEncoder{enc: enc}, nil
}
func newVideoEncoder(cfg VideoEncoderConfig) (*encoder.VideoEncoder, error) {
var enc *encoder.VideoEncoder
var err error
if cfg.TargetBitrate > 0 {
rc := encoder.RateControlConfig{
TargetBitsPerSecond: cfg.TargetBitrate,
FramesPerSecond: cfg.Framerate,
MinQIndex: cfg.MinQIndex,
MaxQIndex: cfg.MaxQIndex,
}
if rc.MinQIndex == 0 {
rc.MinQIndex = 20
}
if rc.MaxQIndex == 0 {
rc.MaxQIndex = 200
}
enc, err = encoder.NewVideoEncoderCBR(cfg.Width, cfg.Height, rc)
} else {
if cfg.QIndex == 0 {
return nil, fmt.Errorf("goav1: VideoEncoderConfig needs QIndex or TargetBitrate")
}
enc, err = encoder.NewVideoEncoder(cfg.Width, cfg.Height, cfg.QIndex)
}
if err != nil {
return nil, err
}
switch cfg.TemporalLayers {
case 0, 1:
default:
if err := enc.SetTemporalLayers(cfg.TemporalLayers); err != nil {
_ = enc.Close()
return nil, err
}
}
if cfg.MaxThreads > 0 {
enc.SetMaxThreads(cfg.MaxThreads)
} else if cfg.TileColumns > 0 {
enc.SetTileColumns(cfg.TileColumns)
}
if cfg.Speed < EncoderWebRTCMinEffortLevel || cfg.Speed > EncoderWebRTCMaxEffortLevel {
_ = enc.Close()
return nil, fmt.Errorf("goav1: VideoEncoderConfig Speed=%d outside supported WebRTC effort range [%d,%d]", cfg.Speed, EncoderWebRTCMinEffortLevel, EncoderWebRTCMaxEffortLevel)
}
if cfg.Speed != 0 {
if err := enc.SetEffortLevel(cfg.Speed); err != nil {
_ = enc.Close()
return nil, err
}
}
if cfg.GoldenInterval < 0 {
enc.SetGoldenInterval(0)
} else if cfg.GoldenInterval > 0 {
enc.SetGoldenInterval(cfg.GoldenInterval)
}
if cfg.DisableSceneCutKeyframes {
enc.SetSceneCutKeyframes(false)
}
// Every buffer, pool and per-coder scratch is sized now, so the first
// real frame pays no initialization latency and steady-state encoding
// allocates nothing.
if err := enc.Prewarm(); err != nil {
_ = enc.Close()
return nil, err
}
return enc, nil
}
// SetGoldenInterval sets how many base-layer inter frames pass between golden
// reference refreshes. Zero disables golden references.
func (e *VideoEncoder) SetGoldenInterval(n int) {
if e != nil && e.enc != nil {
e.enc.SetGoldenInterval(n)
}
}
// SetSceneCutKeyframes controls whether the encoder may promote a delta frame
// to a keyframe when the motion search sees a hard content cut.
func (e *VideoEncoder) SetSceneCutKeyframes(enabled bool) {
if e != nil && e.enc != nil {
e.enc.SetSceneCutKeyframes(enabled)
}
}
// SetTileColumns sets the desired tile-column count for subsequent encoded
// frames. The encoder rounds down to a legal power-of-two tile layout.
func (e *VideoEncoder) SetTileColumns(cols int) {
if e != nil && e.enc != nil {
e.enc.SetTileColumns(cols)
}
}
// SetMaxThreads bounds encoder execution lanes for subsequent frames.
// SetMaxThreads(1) also disables internal worker fan-out in the 8-bit realtime
// path. Zero restores the automatic tile-column policy.
func (e *VideoEncoder) SetMaxThreads(n int) {
if e != nil && e.enc != nil {
e.enc.SetMaxThreads(n)
}
}
// SetEffortLevel selects the realtime encoder effort level for subsequent
// frames. Zero restores the default quality/speed balance.
func (e *VideoEncoder) SetEffortLevel(level int8) error {
if e == nil || e.enc == nil {
return fmt.Errorf("goav1: VideoEncoder is not initialized")
}
return e.enc.SetEffortLevel(level)
}
// Encode encodes one frame. forceKey restarts the stream with a keyframe.
// The returned Data aliases an encoder-owned buffer that is reused by the
// next Encode call - send or copy it before encoding the next frame, the
// same lifetime the Reconstruction planes have.
func (e *VideoEncoder) Encode(frame I420Frame, forceKey bool) (EncodedFrame, error) {
if e == nil || e.enc == nil {
return EncodedFrame{}, fmt.Errorf("goav1: VideoEncoder is not initialized")
}
if err := validateI420Frame(frame); err != nil {
return EncodedFrame{}, err
}
tid := e.enc.TemporalID()
tu, key, err := e.enc.Encode(frame, forceKey)
if err != nil {
return EncodedFrame{}, err
}
if key {
tid = 0
}
return EncodedFrame{Data: tu, Keyframe: key, TemporalID: tid}, nil
}
// EncodeI422 encodes one I422 frame after resampling chroma into the encoder's
// reusable I420 scratch.
func (e *VideoEncoder) EncodeI422(frame I422Frame, forceKey bool) (EncodedFrame, error) {
if e == nil || e.enc == nil {
return EncodedFrame{}, fmt.Errorf("goav1: VideoEncoder is not initialized")
}
i420, err := i422ToI420Scratch(&e.yuv420Scratch, frame)
if err != nil {
return EncodedFrame{}, err
}
return e.Encode(i420, forceKey)
}
// EncodeI444 encodes one I444 frame after resampling chroma into the encoder's
// reusable I420 scratch.
func (e *VideoEncoder) EncodeI444(frame I444Frame, forceKey bool) (EncodedFrame, error) {
if e == nil || e.enc == nil {
return EncodedFrame{}, fmt.Errorf("goav1: VideoEncoder is not initialized")
}
i420, err := i444ToI420Scratch(&e.yuv420Scratch, frame)
if err != nil {
return EncodedFrame{}, err
}
return e.Encode(i420, forceKey)
}
// EncodeI400 encodes one monochrome frame after filling neutral chroma into
// the encoder's reusable I420 scratch.
func (e *VideoEncoder) EncodeI400(frame I400Frame, forceKey bool) (EncodedFrame, error) {
if e == nil || e.enc == nil {
return EncodedFrame{}, fmt.Errorf("goav1: VideoEncoder is not initialized")
}
i420, err := i400ToI420Scratch(&e.yuv420Scratch, frame)
if err != nil {
return EncodedFrame{}, err
}
return e.Encode(i420, forceKey)
}
// EncodeFrame encodes one generic Frame after adapting 8/10/12-bit 4:2:0,
// 4:2:2, 4:4:4, or monochrome samples into the current 8-bit 4:2:0 encode
// path. 10/12-bit input is downshifted to the most significant 8 bits; the
// emitted bitstream is still an 8-bit profile-0 WebRTC stream.
func (e *VideoEncoder) EncodeFrame(frame Frame, forceKey bool) (EncodedFrame, error) {
if e == nil || e.enc == nil {
return EncodedFrame{}, fmt.Errorf("goav1: VideoEncoder is not initialized")
}
i420, err := frameToI420Scratch(&e.yuv420Scratch, frame)
if err != nil {
return EncodedFrame{}, err
}
return e.Encode(i420, forceKey)
}
// EncodeNV12 encodes one NV12 frame. The input is converted into the
// encoder's reusable I420 scratch before entering the same encode path as
// Encode.
func (e *VideoEncoder) EncodeNV12(frame NV12Frame, forceKey bool) (EncodedFrame, error) {
if e == nil || e.enc == nil {
return EncodedFrame{}, fmt.Errorf("goav1: VideoEncoder is not initialized")
}
i420, err := nv12ToI420Scratch(&e.yuv420Scratch, frame)
if err != nil {
return EncodedFrame{}, err
}
return e.Encode(i420, forceKey)
}
// EncodeNV21 encodes one NV21 frame. The input is converted into the
// encoder's reusable I420 scratch before entering the same encode path as
// Encode.
func (e *VideoEncoder) EncodeNV21(frame NV21Frame, forceKey bool) (EncodedFrame, error) {
if e == nil || e.enc == nil {
return EncodedFrame{}, fmt.Errorf("goav1: VideoEncoder is not initialized")
}
i420, err := nv21ToI420Scratch(&e.yuv420Scratch, frame)
if err != nil {
return EncodedFrame{}, err
}
return e.Encode(i420, forceKey)
}
// Close waits for any background encoder work to finish and releases
// persistent workers. It is safe to call more than once.
func (e *VideoEncoder) Close() error {
if e == nil || e.enc == nil {
return nil
}
err := e.enc.Close()
e.enc = nil
return err
}
// Reconstruction returns the most recent frame's reconstruction — exactly
// what a conformant decoder outputs for it. The planes alias encoder-owned
// buffers that are recycled two frames later; copy for longer-lived use.
func (e *VideoEncoder) Reconstruction() I420Frame {
if e == nil || e.enc == nil {
return I420Frame{}
}
return e.enc.Recon()
}
// SetDecisionStatsEnabled toggles encoder-decision diagnostics. It is disabled
// by default; enable it only around measurement runs.
func (e *VideoEncoder) SetDecisionStatsEnabled(enabled bool) {
if e == nil || e.enc == nil {
return
}
e.enc.SetDecisionStatsEnabled(enabled)
}
// ResetDecisionStats clears the accumulated encoder-decision diagnostics.
func (e *VideoEncoder) ResetDecisionStats() {
if e == nil || e.enc == nil {
return
}
e.enc.ResetDecisionStats()
}
// DecisionStats returns a copy of the accumulated encoder-decision diagnostics.
func (e *VideoEncoder) DecisionStats() EncoderDecisionStats {
if e == nil || e.enc == nil {
return EncoderDecisionStats{}
}
return e.enc.DecisionStats()
}
// QIndex reports the current working quantizer index (the CBR controller
// moves it between frames).
func (e *VideoEncoder) QIndex() uint8 {
if e == nil || e.enc == nil {
return 0
}
return e.enc.QIndex()
}
// RTCFrame is one encoded frame with WebRTC packaging metadata.
type RTCFrame struct {
// Data is the temporal unit (the RTP payload content). It aliases an
// encoder-owned buffer reused by the next Encode call.
Data []byte
// Keyframe reports whether this frame belongs to a key picture.
Keyframe bool
// CodedKeyframe reports whether this frame is coded as an AV1 keyframe.
// For multi-spatial SVC key pictures, enhancement layers can belong to the
// key picture while still being coded as inter frames.
CodedKeyframe bool
// LastFrameInPicture reports whether this frame is the last frame in the
// WebRTC picture. AppendRTPPackets uses it to set the RTP marker bit.
LastFrameInPicture bool
// TemporalID is the frame's temporal layer.
TemporalID uint8
// SpatialID is the frame's spatial layer.
SpatialID uint8
// FrameID is the dependency-descriptor frame number.
FrameID uint64
// DependencyDescriptor is the serialized RTP dependency descriptor for a
// single-packet frame; keyframes attach the dependency structure. It aliases
// encoder-owned storage reused by the next Encode call; copy it before
// retaining or sending asynchronously. Use AppendRTPPackets when the frame
// is fragmented across multiple RTP payloads.
DependencyDescriptor []byte
frameInfo encoder.WebRTCGenericFrameInfo
dependencyStructure encoder.WebRTCFrameDependencyStructure
attachDependencyStructure bool
}
// RTCPicture is one encoded WebRTC picture. Single-spatial streams have one
// frame; supported SVC and simulcast streams have one frame per active spatial
// layer.
type RTCPicture struct {
Frames [EncoderWebRTCMaxSpatialLayers]RTCFrame
FrameNum int
Keyframe bool
}
// AllDecodeTargetsMask returns a dependency-descriptor active decode target
// mask with every target in p enabled.
func (p RTCPicture) AllDecodeTargetsMask() (uint32, error) {
structure, err := p.dependencyStructure()
if err != nil {
return 0, err
}
return encoder.WebRTCAllDecodeTargetsMask(structure)
}
// ActiveDecodeTargetsMask returns a dependency-descriptor active decode target
// mask that enables every target at or below the supplied spatial and temporal
// layer IDs.
func (p RTCPicture) ActiveDecodeTargetsMask(maxSpatialID uint8, maxTemporalID uint8) (uint32, error) {
structure, err := p.dependencyStructure()
if err != nil {
return 0, err
}
return encoder.WebRTCActiveDecodeTargetsMask(structure, maxSpatialID, maxTemporalID)
}
// SpatialDecodeTargetsMask returns a dependency-descriptor active decode target
// mask that enables decode targets for one spatial layer up to maxTemporalID.
// This is useful when forwarding a browser-compatible simulcast stream or base
// SVC stream from a multi-spatial encoded picture.
func (p RTCPicture) SpatialDecodeTargetsMask(spatialID uint8, maxTemporalID uint8) (uint32, error) {
structure, err := p.dependencyStructure()
if err != nil {
return 0, err
}
return encoder.WebRTCSpatialDecodeTargetsMask(structure, spatialID, maxTemporalID)
}
// ActiveDecodeTargetsRTPOptions returns packetization options that write the
// active decode-target mask for maxSpatialID/maxTemporalID on the first RTP
// packet of each frame in the picture.
func (p RTCPicture) ActiveDecodeTargetsRTPOptions(maxSpatialID uint8, maxTemporalID uint8) (EncoderWebRTCRTPPacketDependencyDescriptorOptions, error) {
mask, err := p.ActiveDecodeTargetsMask(maxSpatialID, maxTemporalID)
if err != nil {
return EncoderWebRTCRTPPacketDependencyDescriptorOptions{}, err
}
return EncoderWebRTCRTPPacketDependencyDescriptorOptions{
ActiveDecodeTargetsPresentOnFirstPacket: true,
ActiveDecodeTargetsMask: mask,
}, nil
}
// SpatialDecodeTargetsRTPOptions returns packetization options that write an
// exact spatial-layer active decode-target mask on the first RTP packet of each
// frame in the picture.
func (p RTCPicture) SpatialDecodeTargetsRTPOptions(spatialID uint8, maxTemporalID uint8) (EncoderWebRTCRTPPacketDependencyDescriptorOptions, error) {
mask, err := p.SpatialDecodeTargetsMask(spatialID, maxTemporalID)
if err != nil {
return EncoderWebRTCRTPPacketDependencyDescriptorOptions{}, err
}
return EncoderWebRTCRTPPacketDependencyDescriptorOptions{
ActiveDecodeTargetsPresentOnFirstPacket: true,
ActiveDecodeTargetsMask: mask,
}, nil
}
func (p RTCPicture) dependencyStructure() (encoder.WebRTCFrameDependencyStructure, error) {
if p.FrameNum <= 0 || p.FrameNum > EncoderWebRTCMaxSpatialLayers {
return encoder.WebRTCFrameDependencyStructure{}, ErrEncoderInvalidFrame
}
structure := p.Frames[0].dependencyStructure
for i := 1; i < p.FrameNum; i++ {
if p.Frames[i].dependencyStructure != structure {
return encoder.WebRTCFrameDependencyStructure{}, ErrEncoderInvalidFrame
}
}
return structure, nil
}
// RTCFrameRTPScratchSize reports caller-owned scratch needed to packetize one
// RTCFrame into AV1 RTP payload bodies and dependency descriptors.
type RTCFrameRTPScratchSize struct {
Packetizer RTPPacketizerScratchSize
MaxPayloadBytes int
MaxDescriptorBytes int
}
// AllDecodeTargetsMask returns a dependency-descriptor active decode target
// mask with every target in f enabled.
func (f RTCFrame) AllDecodeTargetsMask() (uint32, error) {
return encoder.WebRTCAllDecodeTargetsMask(f.dependencyStructure)
}
// ActiveDecodeTargetsMask returns a dependency-descriptor active decode target
// mask that enables every target at or below the supplied spatial and temporal
// layer IDs.
func (f RTCFrame) ActiveDecodeTargetsMask(maxSpatialID uint8, maxTemporalID uint8) (uint32, error) {
return encoder.WebRTCActiveDecodeTargetsMask(f.dependencyStructure, maxSpatialID, maxTemporalID)
}
// RTPPacketScratchLen reports scratch sizes for AppendRTPPackets. Callers may
// first pass nil or short obuScratch to learn the OBU count, allocate that many
// RTPPacketizerOBU slots, then call again to learn packet/work-plan sizes.
func (f RTCFrame) RTPPacketScratchLen(limits RTPPayloadSizeLimits, obuScratch []RTPPacketizerOBU) (RTCFrameRTPScratchSize, error) {
return f.RTPPacketScratchLenWithOptions(limits, obuScratch, EncoderWebRTCRTPPacketDependencyDescriptorOptions{})
}
// RTPPacketScratchLenWithOptions reports scratch sizes for AppendRTPPacketsWithOptions.
func (f RTCFrame) RTPPacketScratchLenWithOptions(limits RTPPayloadSizeLimits, obuScratch []RTPPacketizerOBU, options EncoderWebRTCRTPPacketDependencyDescriptorOptions) (RTCFrameRTPScratchSize, error) {
packetizer, err := RTPPacketizerScratchLen(f.Data, limits, obuScratch)
size := RTCFrameRTPScratchSize{Packetizer: packetizer}
if err != nil {
return size, err
}
if f.attachDependencyStructure {
options.AttachStructureOnFirstPacket = true
}
firstFlags := RTPPacketDependencyDescriptorFlags{
FirstPacketInFrame: true,
LastPacketInFrame: packetizer.Packets <= 1,
}
descriptor, err := encoder.WebRTCDependencyDescriptorSizeWithOptions(f.dependencyStructure, f.frameInfo, encoderWebRTCRTPPacketDependencyDescriptorOptions(firstFlags, options))
if err != nil {
return size, err
}
size.MaxDescriptorBytes = descriptor
if packetizer.Packets > 1 {
nextDescriptor, err := encoder.WebRTCDependencyDescriptorSizeWithOptions(f.dependencyStructure, f.frameInfo, encoderWebRTCRTPPacketDependencyDescriptorOptions(RTPPacketDependencyDescriptorFlags{}, options))
if err != nil {
return size, err
}
if nextDescriptor > size.MaxDescriptorBytes {
size.MaxDescriptorBytes = nextDescriptor
}
}
if packetizer.OBUs != 0 {
size.MaxPayloadBytes = limits.MaxPayloadLen
}
return size, nil
}
// AppendRTPPackets packetizes f.Data into AV1 RTP payload bodies and appends the
// corresponding RTP dependency descriptor bytes. Packet and descriptor spans are
// written into spans; the caller owns RTP headers, header-extension IDs, SRTP,
// pacing, retransmission, and network transport.
func (f RTCFrame) AppendRTPPackets(payloadDst []byte, descriptorDst []byte, spans []EncoderWebRTCRTPPacketSpan, limits RTPPayloadSizeLimits, obuScratch []RTPPacketizerOBU, packetScratch []RTPPacketPlan, workScratch []RTPPacketPlan) (rtpPayloads []byte, descriptors []byte, packetCount int, err error) {
return f.AppendRTPPacketsWithOptions(payloadDst, descriptorDst, spans, limits, obuScratch, packetScratch, workScratch, EncoderWebRTCRTPPacketDependencyDescriptorOptions{})
}
// AppendRTPPacketsWithOptions is AppendRTPPackets with dependency descriptor
// options for WebRTC control-plane events such as active decode target changes.
func (f RTCFrame) AppendRTPPacketsWithOptions(payloadDst []byte, descriptorDst []byte, spans []EncoderWebRTCRTPPacketSpan, limits RTPPayloadSizeLimits, obuScratch []RTPPacketizerOBU, packetScratch []RTPPacketPlan, workScratch []RTPPacketPlan, options EncoderWebRTCRTPPacketDependencyDescriptorOptions) (rtpPayloads []byte, descriptors []byte, packetCount int, err error) {
packetizer, err := NewRTPPacketizer(f.Data, limits, f.CodedKeyframe, f.LastFrameInPicture, obuScratch, packetScratch, workScratch)
if err != nil {
return payloadDst, descriptorDst, 0, err
}
control := EncoderWebRTCFrameControl{
GenericFrameInfo: f.frameInfo,
AttachDependencyStructure: f.attachDependencyStructure,
}
if f.attachDependencyStructure {
options.AttachStructureOnFirstPacket = true
}
rtpPayloads = payloadDst
descriptors = descriptorDst
for {
if packetCount >= len(spans) {
if packetizer.NumPackets() == 0 {
return rtpPayloads, descriptors, packetCount, nil
}
return payloadDst, descriptorDst, 0, ErrRTPPacketPlanTooSmall
}
payloadStart := len(rtpPayloads)
descriptorStart := len(descriptors)
nextPayloads, nextDescriptors, marker, ok, err := AppendEncoderWebRTCFrameControlRTPPacketWithOptions(rtpPayloads, descriptors, &packetizer, control, f.dependencyStructure, options)
if err != nil {
return payloadDst, descriptorDst, 0, err
}
if !ok {
return rtpPayloads, descriptors, packetCount, nil
}
spans[packetCount] = EncoderWebRTCRTPPacketSpan{
PayloadOffset: payloadStart,
PayloadLength: len(nextPayloads) - payloadStart,
DescriptorOffset: descriptorStart,
DescriptorLength: len(nextDescriptors) - descriptorStart,
Marker: marker,
}
rtpPayloads = nextPayloads
descriptors = nextDescriptors
packetCount++
}
}
// RTCEncoder encodes WebRTC AV1 streams from I420, I422, I444, I400, NV12, or
// NV21 input with per-frame dependency descriptors. I422 and I444 inputs are
// adapted to 4:2:0 unless NewRTCEncoderWithConfig is given an explicit
// profile-2 4:2:2 or profile-1 4:4:4 color config, in which case EncodeI422 /
// EncodeI422Picture or EncodeI444 / EncodeI444Picture emit native AV1 color
// streams. I400 inputs are adapted to 4:2:0 unless NewRTCEncoderWithConfig is
// given an explicit monochrome color config, in which case EncodeI400 and
// EncodeI400Picture emit native AV1 monochrome streams for WebRTC L*/S* modes.
// Explicit 10/12-bit color configs preserve EncodeI420HighBitDepth,
// EncodeI422HighBitDepth, EncodeI444HighBitDepth and their Picture variants,
// plus matching generic Frame input. NewRTCEncoder covers single-spatial L1T* temporal ladders;
// NewRTCEncoderWithConfig additionally covers supported multi-spatial
// WebRTC SVC and simulcast modes under CBR or CQP rate control. NewRTCEncoder
// is the CBR convenience constructor and still requires TargetBitrate and
// Framerate.
type RTCEncoder struct {
stream *encoder.WebRTCStream
yuv420Scratch I420Frame
i400Scratch I400Frame
i400HighBitDepthScratch I400HighBitDepthFrame
i420HighBitDepthScratch I420HighBitDepthFrame
i422HighBitDepthScratch I420HighBitDepthFrame
i444HighBitDepthScratch I420HighBitDepthFrame
}
// NewRTCEncoder creates a WebRTC encoder from cfg.
func NewRTCEncoder(cfg VideoEncoderConfig) (*RTCEncoder, error) {
if cfg.TargetBitrate <= 0 || cfg.Framerate <= 0 {
return nil, fmt.Errorf("goav1: RTCEncoder requires TargetBitrate and Framerate")
}
rc := encoder.RateControlConfig{
TargetBitsPerSecond: cfg.TargetBitrate,
FramesPerSecond: cfg.Framerate,
MinQIndex: cfg.MinQIndex,
MaxQIndex: cfg.MaxQIndex,
}
if rc.MinQIndex == 0 {
rc.MinQIndex = 20
}
if rc.MaxQIndex == 0 {
rc.MaxQIndex = 200
}
layers := cfg.TemporalLayers
if layers == 0 {
layers = 1