feat(capability): 转码能力包(vendor 纯 Go AMR-NB 解码器 + 黄金向量逐样本对照 + 8 哨兵 + 注册面)
This commit is contained in:
1 parent
89715d1813
commit
4a2d276881
100 files changed
+11319
No files matched your search
@@ -0,0 +1,104 @@
|
||||
package transcode
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
// WAV/RIFF 的 44 字节规范头(design/25 §0.5 登记的 WAV/RIFF 规范)。
|
||||
// 四块:RIFF + WAVE + fmt + data。本期只发 PCM16 一种载荷,且**不引入音频库**
|
||||
// ——头是手写的,只用 encoding/binary。
|
||||
//
|
||||
// 之所以目标容器选 WAV 而不是 mp3/ogg:WAV 是「只要解码器、不要编码器」的一条路
|
||||
// (PCM + 44 字节头),而纯 Go 的 mp3 编码器生态远不如解码器成熟(design/25 §3.1)。
|
||||
const (
|
||||
// wavHeaderBytes 是规范头的字节数。
|
||||
wavHeaderBytes = 44
|
||||
// wavFormatPCM 是 fmt 块里的音频格式号:1 = PCM(线性量化)。
|
||||
wavFormatPCM = 1
|
||||
// wavBitsPerSample 是每样本位数:AMR-NB 解码出来就是 16 位定点。
|
||||
wavBitsPerSample = 16
|
||||
// wavFmtChunkBytes 是 fmt 子块的长度(PCM 的规范值 16)。
|
||||
wavFmtChunkBytes = 16
|
||||
// wavPCMBytesPerSample 是单个样本占的字节数。
|
||||
wavPCMBytesPerSample = wavBitsPerSample / 8
|
||||
|
||||
// riffChunkSizeBase 是 RIFF 长度字段里除 data 之外的部分:
|
||||
// chunkSize = riffChunkSizeBase + dataBytes。
|
||||
riffChunkSizeBase = wavHeaderBytes - 8
|
||||
|
||||
// 四块的标识与头内各字段的偏移(规范里定死的值,具名以免裸数字)。
|
||||
wavRiffID = "RIFF"
|
||||
wavWaveID = "WAVE"
|
||||
wavFmtID = "fmt "
|
||||
wavDataID = "data"
|
||||
|
||||
wavOffRiffID = 0
|
||||
wavOffChunkSize = 4
|
||||
wavOffWaveID = 8
|
||||
wavOffFmtID = 12
|
||||
wavOffFmtSize = 16
|
||||
wavOffAudioFormat = 20
|
||||
wavOffChannels = 22
|
||||
wavOffSampleRate = 24
|
||||
wavOffByteRate = 28
|
||||
wavOffBlockAlign = 32
|
||||
wavOffBitsPerSamp = 34
|
||||
wavOffDataID = 36
|
||||
wavOffDataSize = 40
|
||||
)
|
||||
|
||||
// wavMaxDataBytes 是 RIFF 的 32 位长度字段能表达的 data 上限。超出即无法用
|
||||
// 44 字节头如实描述 ⇒ ErrEncodeFailed(绝不写出一个长度撒谎的头)。
|
||||
const wavMaxDataBytes = math.MaxUint32 - riffChunkSizeBase
|
||||
|
||||
// wavHeader 装配一个 44 字节的 RIFF/WAVE 头,声明 dataBytes 字节的 PCM16 载荷。
|
||||
func wavHeader(dataBytes, sampleRate, channels int) ([wavHeaderBytes]byte, error) {
|
||||
var hdr [wavHeaderBytes]byte
|
||||
if sampleRate <= 0 || channels <= 0 {
|
||||
return hdr, fmt.Errorf("transcode: wav header: %w: sample_rate=%d channels=%d",
|
||||
ErrEncodeFailed, sampleRate, channels)
|
||||
}
|
||||
if dataBytes < 0 || dataBytes > wavMaxDataBytes {
|
||||
return hdr, fmt.Errorf("transcode: wav header: %w: %d data bytes exceed the RIFF limit",
|
||||
ErrEncodeFailed, dataBytes)
|
||||
}
|
||||
blockAlign := channels * wavPCMBytesPerSample
|
||||
copy(hdr[wavOffRiffID:], wavRiffID)
|
||||
binary.LittleEndian.PutUint32(hdr[wavOffChunkSize:], uint32(riffChunkSizeBase+dataBytes))
|
||||
copy(hdr[wavOffWaveID:], wavWaveID)
|
||||
copy(hdr[wavOffFmtID:], wavFmtID)
|
||||
binary.LittleEndian.PutUint32(hdr[wavOffFmtSize:], wavFmtChunkBytes)
|
||||
binary.LittleEndian.PutUint16(hdr[wavOffAudioFormat:], wavFormatPCM)
|
||||
binary.LittleEndian.PutUint16(hdr[wavOffChannels:], uint16(channels))
|
||||
binary.LittleEndian.PutUint32(hdr[wavOffSampleRate:], uint32(sampleRate))
|
||||
binary.LittleEndian.PutUint32(hdr[wavOffByteRate:], uint32(sampleRate*blockAlign))
|
||||
binary.LittleEndian.PutUint16(hdr[wavOffBlockAlign:], uint16(blockAlign))
|
||||
binary.LittleEndian.PutUint16(hdr[wavOffBitsPerSamp:], wavBitsPerSample)
|
||||
copy(hdr[wavOffDataID:], wavDataID)
|
||||
binary.LittleEndian.PutUint32(hdr[wavOffDataSize:], uint32(dataBytes))
|
||||
return hdr, nil
|
||||
}
|
||||
|
||||
// writePCM16LE 把样本按小端 PCM16 写进 dst(dst 必须够长:2 字节/样本)。
|
||||
func writePCM16LE(dst []byte, samples []int16) {
|
||||
for i, s := range samples {
|
||||
binary.LittleEndian.PutUint16(dst[i*wavPCMBytesPerSample:], uint16(s))
|
||||
}
|
||||
}
|
||||
|
||||
// encodePCM16WAV 把样本装成一份完整 WAV(44 字节头 + PCM16 数据)。
|
||||
// 主路径(codec_amr_nb.go)不用它:那条路直接写进目标缓冲,避免「先攒一份 PCM、
|
||||
// 再复制一份 WAV」把峰值内存翻倍(design/25 §2.5 的瞬时上限)。
|
||||
func encodePCM16WAV(samples []int16, sampleRate, channels int) ([]byte, error) {
|
||||
dataBytes := len(samples) * wavPCMBytesPerSample
|
||||
hdr, err := wavHeader(dataBytes, sampleRate, channels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]byte, wavHeaderBytes+dataBytes)
|
||||
copy(out, hdr[:])
|
||||
writePCM16LE(out[wavHeaderBytes:], samples)
|
||||
return out, nil
|
||||
}
|
||||
Reference in new issue
Block a user