Add universal PCM transcoder

This commit is contained in:
Alex X
2025-03-25 13:10:16 +03:00
parent 7415776e4d
commit f535595d1f
5 changed files with 339 additions and 251 deletions
+151 -241
View File
@@ -1,277 +1,187 @@
package pcm
import (
"sync"
import "github.com/AlexxIT/go2rtc/pkg/core"
"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 {
func Downsample(k float32) func([]int16) []int16 {
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)
return func(src []int16) (dst []int16) {
var i int
for _, sample := range oldSamples {
sampleSum += float32(toPCM(sample))
if sampleN++; sampleN >= n {
newSamples[i] = fromPCM(int16(sampleSum / n))
dst = make([]int16, int((float32(len(src))+sampleN)/k))
for _, sample := range src {
sampleSum += float32(sample)
sampleN++
if sampleN >= k {
dst[i] = int16(sampleSum / k)
i++
sampleSum = 0
sampleN -= n
sampleN -= k
}
}
ts += newLen
clone := *packet
clone.Payload = newSamples
clone.Timestamp = ts
handler(&clone)
return
}
}
// 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]
}
func Upsample(k float32) func([]int16) []int16 {
var sampleN float32
clone := *packet
clone.Payload = b
handler(&clone)
}
}
return func(src []int16) (dst []int16) {
var i int
dst = make([]int16, int(k*float32(len(src))))
for _, sample := range src {
sampleN += k
for sampleN > 0 {
dst[i] = sample
i++
// 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
sampleN -= 1
}
}
ts += newLen
clone := *packet
clone.Payload = newSamples
clone.Timestamp = ts
handler(&clone)
return
}
}
// 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
func FlipEndian(src []byte) (dst []byte) {
var i, j int
n := len(src)
dst = make([]byte, n)
for i < n {
x := src[i]
i++
dst[j] = src[i]
j++
i++
dst[j] = x
j++
}
return
}
var buf []byte
var seq uint16
var ts uint32
func Transcode(dst, src *core.Codec) func([]byte) []byte {
var reader func([]byte) []int16
var writer func([]int16) []byte
var filters []func([]int16) []int16
// 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()
switch src.Name {
case core.CodecPCML:
reader = func(src []byte) (dst []int16) {
var i, j int
n := len(src)
dst = make([]int16, n/2)
for i < n {
lo := src[i]
i++
hi := src[i]
i++
dst[j] = int16(hi)<<8 | int16(lo)
j++
}
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]
case core.CodecPCM:
reader = func(src []byte) (dst []int16) {
var i, j int
n := len(src)
dst = make([]int16, n/2)
for i < n {
hi := src[i]
i++
lo := pkt.Payload[i]
lo := src[i]
i++
payload[j] = hi
j++
payload[j] = lo
j++
payload[j] = hi
j++
payload[j] = lo
dst[j] = int16(hi)<<8 | int16(lo)
j++
}
pkt.Payload = payload
handler(pkt)
return
}
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))
case core.CodecPCMU:
reader = func(src []byte) (dst []int16) {
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
dst = make([]int16, len(src))
for _, sample := range src {
dst[i] = PCMUtoPCM(sample)
i++
}
pkt.Payload = payload
handler(pkt)
return
}
case core.CodecPCMA:
reader = func(src []byte) (dst []int16) {
var i int
dst = make([]int16, len(src))
for _, sample := range src {
dst[i] = PCMAtoPCM(sample)
i++
}
return
}
}
return nil
if src.Channels > 1 {
filters = append(filters, Downsample(float32(src.Channels)))
}
if src.ClockRate > dst.ClockRate {
filters = append(filters, Downsample(float32(src.ClockRate)/float32(dst.ClockRate)))
} else if src.ClockRate < dst.ClockRate {
filters = append(filters, Upsample(float32(dst.ClockRate)/float32(src.ClockRate)))
}
if dst.Channels > 1 {
filters = append(filters, Upsample(float32(dst.Channels)))
}
switch dst.Name {
case core.CodecPCML:
writer = func(src []int16) (dst []byte) {
var i int
dst = make([]byte, len(src)*2)
for _, sample := range src {
dst[i] = byte(sample)
i++
dst[i] = byte(sample >> 8)
i++
}
return
}
case core.CodecPCM:
writer = func(src []int16) (dst []byte) {
var i int
dst = make([]byte, len(src)*2)
for _, sample := range src {
dst[i] = byte(sample >> 8)
i++
dst[i] = byte(sample)
i++
}
return
}
case core.CodecPCMU:
writer = func(src []int16) (dst []byte) {
var i int
dst = make([]byte, len(src))
for _, sample := range src {
dst[i] = PCMtoPCMU(sample)
i++
}
return
}
case core.CodecPCMA:
writer = func(src []int16) (dst []byte) {
var i int
dst = make([]byte, len(src))
for _, sample := range src {
dst[i] = PCMtoPCMA(sample)
i++
}
return
}
}
return func(b []byte) []byte {
samples := reader(b)
for _, filter := range filters {
samples = filter(samples)
}
return writer(samples)
}
}