mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-09-26 20:31:11 +08:00
278 lines
6.1 KiB
Go
278 lines
6.1 KiB
Go
package pcm
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"github.com/AlexxIT/go2rtc/pkg/core"
|
|
"github.com/pion/rtp"
|
|
)
|
|
|
|
// ResampleToG711 - convert PCMA/PCM/PCML to PCMA and PCMU to PCMU with decreasing sample rate
|
|
func ResampleToG711(codec *core.Codec, sampleRate uint32, handler core.HandlerFunc) core.HandlerFunc {
|
|
n := float32(codec.ClockRate) / float32(sampleRate)
|
|
|
|
if codec.Channels == 2 {
|
|
n *= 2 // hacky way for support two channels audio
|
|
}
|
|
|
|
switch codec.Name {
|
|
case core.CodecPCMA:
|
|
return DownsampleByte(PCMAtoPCM, PCMtoPCMA, n, handler)
|
|
case core.CodecPCMU:
|
|
return DownsampleByte(PCMUtoPCM, PCMtoPCMU, n, handler)
|
|
case core.CodecPCM, core.CodecPCML:
|
|
if n == 1 {
|
|
handler = ResamplePCM(PCMtoPCMA, handler)
|
|
} else {
|
|
handler = DownsamplePCM(PCMtoPCMA, n, handler)
|
|
}
|
|
|
|
if codec.Name == core.CodecPCML {
|
|
return LittleToBig(handler)
|
|
}
|
|
|
|
return handler
|
|
}
|
|
|
|
panic(core.Caller())
|
|
}
|
|
|
|
// DownsampleByte - convert PCMA/PCMU to PCMA/PCMU with decreasing sample rate (N times)
|
|
func DownsampleByte(
|
|
toPCM func(byte) int16, fromPCM func(int16) byte, n float32, handler core.HandlerFunc,
|
|
) core.HandlerFunc {
|
|
var sampleN, sampleSum float32
|
|
var ts uint32
|
|
|
|
return func(packet *rtp.Packet) {
|
|
samples := len(packet.Payload)
|
|
newLen := uint32((float32(samples) + sampleN) / n)
|
|
|
|
oldSamples := packet.Payload
|
|
newSamples := make([]byte, newLen)
|
|
|
|
var i int
|
|
for _, sample := range oldSamples {
|
|
sampleSum += float32(toPCM(sample))
|
|
if sampleN++; sampleN >= n {
|
|
newSamples[i] = fromPCM(int16(sampleSum / n))
|
|
i++
|
|
|
|
sampleSum = 0
|
|
sampleN -= n
|
|
}
|
|
}
|
|
|
|
ts += newLen
|
|
|
|
clone := *packet
|
|
clone.Payload = newSamples
|
|
clone.Timestamp = ts
|
|
handler(&clone)
|
|
}
|
|
}
|
|
|
|
// LittleToBig - conver PCM little endian to PCM big endian
|
|
func LittleToBig(handler core.HandlerFunc) core.HandlerFunc {
|
|
return func(packet *rtp.Packet) {
|
|
size := len(packet.Payload)
|
|
b := make([]byte, size)
|
|
for i := 0; i < size; i += 2 {
|
|
b[i] = packet.Payload[i+1]
|
|
b[i+1] = packet.Payload[i]
|
|
}
|
|
|
|
clone := *packet
|
|
clone.Payload = b
|
|
handler(&clone)
|
|
}
|
|
}
|
|
|
|
// ResamplePCM - convert PCM to PCMA/PCMU with same sample rate
|
|
func ResamplePCM(fromPCM func(int16) byte, handler core.HandlerFunc) core.HandlerFunc {
|
|
var ts uint32
|
|
|
|
return func(packet *rtp.Packet) {
|
|
len1 := len(packet.Payload)
|
|
len2 := len1 / 2
|
|
|
|
oldSamples := packet.Payload
|
|
newSamples := make([]byte, len2)
|
|
|
|
var i2 int
|
|
for i1 := 0; i1 < len1; i1 += 2 {
|
|
sample := int16(uint16(oldSamples[i1])<<8 | uint16(oldSamples[i1+1]))
|
|
newSamples[i2] = fromPCM(sample)
|
|
i2++
|
|
}
|
|
|
|
ts += uint32(len2)
|
|
|
|
clone := *packet
|
|
clone.Payload = newSamples
|
|
clone.Timestamp = ts
|
|
handler(&clone)
|
|
}
|
|
}
|
|
|
|
// DownsamplePCM - convert PCM to PCMA/PCMU with decreasing sample rate (N times)
|
|
func DownsamplePCM(fromPCM func(int16) byte, n float32, handler core.HandlerFunc) core.HandlerFunc {
|
|
var sampleN, sampleSum float32
|
|
var ts uint32
|
|
|
|
return func(packet *rtp.Packet) {
|
|
samples := len(packet.Payload) / 2
|
|
newLen := uint32((float32(samples) + sampleN) / n)
|
|
|
|
oldSamples := packet.Payload
|
|
newSamples := make([]byte, newLen)
|
|
|
|
var i2 int
|
|
for i1 := 0; i1 < len(packet.Payload); i1 += 2 {
|
|
sampleSum += float32(int16(uint16(oldSamples[i1])<<8 | uint16(oldSamples[i1+1])))
|
|
if sampleN++; sampleN >= n {
|
|
newSamples[i2] = fromPCM(int16(sampleSum / n))
|
|
i2++
|
|
|
|
sampleSum = 0
|
|
sampleN -= n
|
|
}
|
|
}
|
|
|
|
ts += newLen
|
|
|
|
clone := *packet
|
|
clone.Payload = newSamples
|
|
clone.Timestamp = ts
|
|
handler(&clone)
|
|
}
|
|
}
|
|
|
|
// RepackG711 - Repack G.711 PCMA/PCMU into frames of size 1024
|
|
// 1. Fixes WebRTC audio quality issue (monotonic timestamp)
|
|
// 2. Fixes Reolink Doorbell backchannel issue (zero timestamp)
|
|
// https://github.com/AlexxIT/go2rtc/issues/331
|
|
func RepackG711(zeroTS bool, handler core.HandlerFunc) core.HandlerFunc {
|
|
const PacketSize = 1024
|
|
|
|
var buf []byte
|
|
var seq uint16
|
|
var ts uint32
|
|
|
|
// fix https://github.com/AlexxIT/go2rtc/issues/432
|
|
var mu sync.Mutex
|
|
|
|
return func(packet *rtp.Packet) {
|
|
mu.Lock()
|
|
|
|
buf = append(buf, packet.Payload...)
|
|
if len(buf) < PacketSize {
|
|
mu.Unlock()
|
|
return
|
|
}
|
|
|
|
pkt := &rtp.Packet{
|
|
Header: rtp.Header{
|
|
Version: 2,
|
|
Marker: true, // should be true
|
|
PayloadType: packet.PayloadType, // will be owerwriten
|
|
SequenceNumber: seq,
|
|
SSRC: packet.SSRC,
|
|
},
|
|
Payload: buf[:PacketSize],
|
|
}
|
|
|
|
seq++
|
|
|
|
// don't know if zero TS important for Reolink Doorbell
|
|
// don't have this strange devices for tests
|
|
if !zeroTS {
|
|
pkt.Timestamp = ts
|
|
ts += PacketSize
|
|
}
|
|
|
|
buf = buf[PacketSize:]
|
|
|
|
mu.Unlock()
|
|
|
|
handler(pkt)
|
|
}
|
|
}
|
|
|
|
func Convert(in, out *core.Codec, handler core.HandlerFunc) core.HandlerFunc {
|
|
if in.Name == out.Name && in.Channels == out.Channels && in.ClockRate == out.ClockRate {
|
|
return handler
|
|
}
|
|
|
|
switch {
|
|
case in.Name == core.CodecPCML && in.Channels <= 1 &&
|
|
out.Name == core.CodecPCML && out.Channels == 2:
|
|
return func(pkt *core.Packet) {
|
|
n := len(pkt.Payload)
|
|
payload := make([]byte, 2*n)
|
|
for i, j := 0, 0; i < n; {
|
|
hi := pkt.Payload[i]
|
|
i++
|
|
lo := pkt.Payload[i]
|
|
i++
|
|
payload[j] = hi
|
|
j++
|
|
payload[j] = lo
|
|
j++
|
|
payload[j] = hi
|
|
j++
|
|
payload[j] = lo
|
|
j++
|
|
}
|
|
pkt.Payload = payload
|
|
handler(pkt)
|
|
}
|
|
|
|
case in.Name == core.CodecPCM && in.Channels <= 1 &&
|
|
out.Name == core.CodecPCML && out.Channels == 2:
|
|
return func(pkt *core.Packet) {
|
|
n := len(pkt.Payload)
|
|
payload := make([]byte, 2*n)
|
|
for i, j := 0, 0; i < n; {
|
|
hi := pkt.Payload[i]
|
|
i++
|
|
lo := pkt.Payload[i]
|
|
i++
|
|
payload[j] = lo
|
|
j++
|
|
payload[j] = hi
|
|
j++
|
|
payload[j] = lo
|
|
j++
|
|
payload[j] = hi
|
|
j++
|
|
}
|
|
pkt.Payload = payload
|
|
handler(pkt)
|
|
}
|
|
|
|
case in.Name == core.CodecPCMA && in.Channels <= 1 &&
|
|
out.Name == core.CodecPCML && out.Channels == 2:
|
|
return func(pkt *core.Packet) {
|
|
payload := make([]byte, 4*len(pkt.Payload))
|
|
var i int
|
|
for _, b := range pkt.Payload {
|
|
s16 := PCMAtoPCM(b)
|
|
hi := byte(s16 >> 8)
|
|
lo := byte(s16)
|
|
payload[i] = hi
|
|
i++
|
|
payload[i] = lo
|
|
i++
|
|
payload[i] = hi
|
|
i++
|
|
payload[i] = lo
|
|
i++
|
|
}
|
|
pkt.Payload = payload
|
|
handler(pkt)
|
|
}
|
|
}
|
|
return nil
|
|
}
|