Skip to content

Commit 8d6607e

Browse files
Merge remote-tracking branch 'origin/main'
2 parents 6420f3b + b8abb1d commit 8d6607e

7 files changed

Lines changed: 337 additions & 73 deletions

File tree

amrwb/amrwb.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ func init() {
4646
Priority: -4,
4747
FileExt: "amrwb",
4848
Disabled: true,
49+
ReqParams: []media.CodecParam{
50+
{Key: "octet-align", Val: "0"},
51+
},
4952
}, func(w media.PCM16Writer) media.WriteCloser[Sample] {
5053
return Decode(w, RTPBandwidthEfficient)
5154
}, func(w media.WriteCloser[Sample]) media.PCM16Writer {

codecs.go

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,72 @@ import (
1919
"strings"
2020
)
2121

22+
type CodecParams []CodecParam
23+
24+
func (arr CodecParams) String() string {
25+
params := make([]string, 0, len(arr))
26+
for _, p := range arr {
27+
params = append(params, p.String())
28+
}
29+
return strings.Join(params, ";")
30+
}
31+
32+
func (arr CodecParams) Get(key string) (string, bool) {
33+
for _, p := range arr {
34+
if p.Key == key {
35+
return p.Val, true
36+
}
37+
}
38+
return "", false
39+
}
40+
func (arr CodecParams) Has(key string) bool {
41+
for _, p := range arr {
42+
if p.Key == key {
43+
return true
44+
}
45+
}
46+
return false
47+
}
48+
func (arr CodecParams) HasValue(key, val string) bool {
49+
for _, p := range arr {
50+
if p.Key == key {
51+
return p.Val == val
52+
}
53+
}
54+
return false
55+
}
56+
func (arr CodecParams) HasParam(p2 CodecParam) bool {
57+
for _, p := range arr {
58+
if p.Key == p2.Key {
59+
return p.Val == p2.Val
60+
}
61+
}
62+
return false
63+
}
64+
65+
type CodecParam struct {
66+
Key string
67+
Val string
68+
}
69+
70+
func (p CodecParam) String() string {
71+
if p.Val == "" {
72+
return p.Key
73+
}
74+
return p.Key + "=" + p.Val
75+
}
76+
2277
type CodecInfo struct {
2378
SDPName string
2479
SampleRate int
2580
RTPClockRate int
2681
RTPDefType byte
2782
RTPIsStatic bool
28-
Priority int
83+
Priority int // higher is preferable
2984
Disabled bool // codec is disabled in GlobalCodecs by default
3085
Hidden bool // codec should not appear in SDP offer, but can be used in the answer
3186
FileExt string
87+
ReqParams CodecParams // a list of required codec params (fmtp)
3288
}
3389

3490
type Codec interface {

dtmf/dtmf.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,15 @@ var eventToChar = [16]byte{
132132
codeA: 'a', codeB: 'b', codeC: 'c', codeD: 'd',
133133
}
134134

135+
// CodeToChar converts a DTMF event code to its character representation.
136+
// It returns 0 for unknown codes.
137+
func CodeToChar(code byte) byte {
138+
if int(code) < len(eventToChar) {
139+
return eventToChar[code]
140+
}
141+
return 0
142+
}
143+
135144
var charToEvent = map[byte]byte{
136145
'0': code0, '1': code1, '2': code2, '3': code3, '4': code4,
137146
'5': code5, '6': code6, '7': code7, '8': code8, '9': code9,

media.go

Lines changed: 90 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,8 @@ func NewSwitchWriter(sampleRate int) *SwitchWriter {
8585
}
8686

8787
type SwitchWriter struct {
88-
ptr atomic.Pointer[PCM16Writer]
89-
sampleRate atomic.Int32
90-
disabled atomic.Bool
88+
WriteCloserSwitch[PCM16Sample]
89+
disabled atomic.Bool
9190
}
9291

9392
func (s *SwitchWriter) Enable() {
@@ -99,29 +98,26 @@ func (s *SwitchWriter) Disable() {
9998
}
10099

101100
func (s *SwitchWriter) Get() PCM16Writer {
102-
ptr := s.ptr.Load()
101+
ptr := s.WriteCloserSwitch.Get()
103102
if ptr == nil {
104-
return nil
103+
return nil // Untyped nil
105104
}
106-
return *ptr
105+
return ptr
107106
}
108107

109108
// Swap sets an underlying writer and returns the old one.
110109
// Caller is responsible for closing the old writer.
111110
func (s *SwitchWriter) Swap(w PCM16Writer) PCM16Writer {
112-
var old *PCM16Writer
113-
if w == nil {
114-
old = s.ptr.Swap(nil)
115-
} else {
111+
if w != nil {
116112
if rate := s.SampleRate(); rate != w.SampleRate() {
117113
w = ResampleWriter(w, rate)
118114
}
119-
old = s.ptr.Swap(&w)
120115
}
116+
old := s.WriteCloserSwitch.Swap(w)
121117
if old == nil {
122-
return nil
118+
return nil // Untyped nil
123119
}
124-
return *old
120+
return old
125121
}
126122

127123
func (s *SwitchWriter) String() string {
@@ -135,14 +131,14 @@ func (s *SwitchWriter) SetSampleRate(rate int) {
135131
if rate <= 0 {
136132
panic("invalid sample rate")
137133
}
138-
if !s.sampleRate.CompareAndSwap(-1, int32(rate)) {
134+
if !s.WriteCloserSwitch.sampleRate.CompareAndSwap(-1, int32(rate)) {
139135
panic("sample rate can only be changed once")
140136
}
141137
}
142138

143139
// SampleRate returns an expected sample rate for this writer. It panics if the sample rate is not specified.
144140
func (s *SwitchWriter) SampleRate() int {
145-
rate := int(s.sampleRate.Load())
141+
rate := s.WriteCloserSwitch.SampleRate()
146142
if rate == 0 {
147143
panic("switch writer not initialized")
148144
} else if rate < 0 {
@@ -151,23 +147,94 @@ func (s *SwitchWriter) SampleRate() int {
151147
return rate
152148
}
153149

154-
func (s *SwitchWriter) Close() error {
155-
ptr := s.ptr.Swap(nil)
156-
if ptr == nil {
150+
func (s *SwitchWriter) WriteSample(sample PCM16Sample) error {
151+
if s.disabled.Load() {
157152
return nil
158153
}
159-
return (*ptr).Close()
154+
return s.WriteCloserSwitch.WriteSample(sample)
160155
}
161156

162-
func (s *SwitchWriter) WriteSample(sample PCM16Sample) error {
163-
if s.disabled.Load() {
157+
// NewWriteCloserSwitch creates a switch that expects writers with the given sample rate.
158+
// If a positive sample rate is provided, it is locked in at the start.
159+
// If a zero or negative sample rate is provided, the real rate will be taken
160+
// from the first downstream writer, and locked to that rate at that time.
161+
func NewWriteCloserSwitch[T any](sampleRate int) *WriteCloserSwitch[T] {
162+
s := &WriteCloserSwitch[T]{}
163+
if sampleRate > 0 {
164+
s.sampleRate.Store(int32(sampleRate))
165+
}
166+
return s
167+
}
168+
169+
// WriteCloserSwitch is a WriteCloser that forwards samples to an underlying writer,
170+
// which can be replaced atomically with Swap. Writes are dropped while no writer is attached.
171+
// All writers must agree on the sample rate.
172+
type WriteCloserSwitch[T any] struct { // msdk.WriteCloser[T]
173+
sampleRate atomic.Int32 // Prevents changing sample rate after the switch is created
174+
w atomic.Pointer[WriteCloser[T]]
175+
}
176+
177+
func (s *WriteCloserSwitch[T]) String() string {
178+
w := s.w.Load()
179+
if w == nil {
180+
return "WriteCloserSwitch(nil)"
181+
}
182+
return fmt.Sprintf("WriteCloserSwitch(%d) -> %v", s.SampleRate(), *w)
183+
}
184+
185+
// SampleRate returns the sample rate expected from the underlying writers,
186+
// or -1 if it is still unset.
187+
func (s *WriteCloserSwitch[T]) SampleRate() int {
188+
if rate := s.sampleRate.Load(); rate > 0 {
189+
return int(rate)
190+
}
191+
return -1
192+
}
193+
194+
func (s *WriteCloserSwitch[T]) WriteSample(sample T) error {
195+
w := s.w.Load()
196+
if w == nil {
164197
return nil
165198
}
166-
w := s.Get()
199+
return (*w).WriteSample(sample)
200+
}
201+
202+
func (s *WriteCloserSwitch[T]) Close() error {
203+
w := s.w.Load()
167204
if w == nil {
168205
return nil
169206
}
170-
return w.WriteSample(sample)
207+
return (*w).Close()
208+
}
209+
210+
func (s *WriteCloserSwitch[T]) Get() WriteCloser[T] {
211+
ptr := s.w.Load()
212+
if ptr == nil {
213+
return nil
214+
}
215+
return *ptr
216+
}
217+
218+
// Swap sets an underlying writer and returns the old one.
219+
// Caller is responsible for closing the old writer.
220+
// When switch sample rate is unset, it is set to the new writer's sample rate.
221+
// If sample rate is already set, this method panics on sample rate mismatch.
222+
func (s *WriteCloserSwitch[T]) Swap(w WriteCloser[T]) WriteCloser[T] {
223+
var old *WriteCloser[T]
224+
if w != nil {
225+
newRate := int32(w.SampleRate())
226+
oldRate := s.sampleRate.Swap(newRate)
227+
if oldRate > 0 && oldRate != newRate {
228+
panic(fmt.Sprintf("sample rate mismatch: newRate %d, oldRate %d", newRate, oldRate))
229+
}
230+
old = s.w.Swap(&w)
231+
} else {
232+
old = s.w.Swap(nil)
233+
}
234+
if old == nil {
235+
return nil
236+
}
237+
return *old
171238
}
172239

173240
type MultiWriter[T any] []WriteCloser[T]

sdp/codecs.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,20 +39,25 @@ func init() {
3939

4040
// CodecByNameWith finds the codec with a given SDP name.
4141
// If the codec is not found or disabled in the codec set, it returns nil.
42-
func CodecByNameWith(s *media.CodecSet, name string) media.Codec {
42+
func CodecByNameWith(s *media.CodecSet, name string, params media.CodecParams) media.Codec {
4343
if s == nil {
4444
s = media.GlobalCodecs()
4545
}
4646
c := codecByName[strings.ToLower(name)]
4747
if !s.IsEnabled(c) {
4848
return nil
4949
}
50+
for _, p := range c.Info().ReqParams {
51+
if val, ok := params.Get(p.Key); ok && val != p.Val {
52+
return nil
53+
}
54+
}
5055
return c
5156
}
5257

5358
// CodecByName finds the codec with a given SDP name.
5459
//
5560
// Deprecated: use CodecByNameWith
5661
func CodecByName(name string) media.Codec {
57-
return CodecByNameWith(media.GlobalCodecs(), name)
62+
return CodecByNameWith(media.GlobalCodecs(), name, nil)
5863
}

0 commit comments

Comments
 (0)