2021SC@SDUSC
一、前言
1.说明
audio 说明文档地址
audio package - github.com/hajimehoshi/ebiten/v2/audio - pkg.go.dev
本文中所有截图和代码均来自说明文档或 ebiten 源文件
笔者负责 audio 音频相关的代码分析
2.梗概
阅读 decode.go 的代码,分析大概的逻辑和功能。
二、解码
首先 decode.go 位于 audio.wav 包内
wav 包提供 wav(RIFF)解码器。
stream 结构体是一个解码的音频流。
Read 是 io.Reader 的 Read 方法的实现。
Seek 是 io.Seeker 的 Seek 方法的实现。
Length返回解码流的大小(以字节为单位)。
DecodeWithSampleRate 将 WAV(RIFF)数据解码为可播放流。
格式必须为1或2通道、8位或16位小端PCM。
格式转换为2个通道和16位。
解码失败或发生IO错误时,DecodeWithSampleRate 返回错误。
DecodeWithSampleRate会在必要时自动对流进行重新采样,以适合采样器。
只有当 src 是 io.Seeker 时,返回流的 Seek 方法才可用。
即使 src 实现了 io.Closer ,流也不会关闭。
关闭资源是 src 所有者的责任。
func DecodeWithSampleRate(sampleRate int, src io.Reader) (*Stream, error) {
首先是一些报错条件
buf := make([]byte, 12)
n, err := io.ReadFull(src, buf)
if n != len(buf) {
return nil, fmt.Errorf("wav: invalid header")
}
if err != nil {
return nil, err
}
if !bytes.Equal(buf[0:4], []byte("RIFF")) {
return nil, fmt.Errorf("wav: invalid header: 'RIFF' not found")
}
if !bytes.Equal(buf[8:12], []byte("WAVE")) {
return nil, fmt.Errorf("wav: invalid header: 'WAVE' not found")
}
按块读取
dataSize := int64(0)
headerSize := int64(len(buf))
sampleRateFrom := 0
sampleRateTo := 0
mono := false
bitsPerSample := 0
chunks:
for {
buf := make([]byte, 8)
n, err := io.ReadFull(src, buf)
if n != len(buf) {
return nil, fmt.Errorf("wav: invalid header")
}
if err != nil {
return nil, err
}
headerSize += 8
size := int64(buf[4]) | int64(buf[5])<<8 | int64(buf[6])<<16 | int64(buf[7])<<24
switch {
case bytes.Equal(buf[0:4], []byte("fmt ")):
// Size of 'fmt' header is usually 16, but can be more than 16.
if size < 16 {
return nil, fmt.Errorf("wav: invalid header: maybe non-PCM file?")
}
buf := make([]byte, size)
n, err := io.ReadFull(src, buf)
if n != len(buf) {
return nil, fmt.Errorf("wav: invalid header")
}
if err != nil {
return nil, err
}
format := int(buf[0]) | int(buf[1])<<8
if format != 1 {
return nil, fmt.Errorf("wav: format must be linear PCM")
}
channelNum := int(buf[2]) | int(buf[3])<<8
switch channelNum {
case 1:
mono = true
case 2:
mono = false
default:
return nil, fmt.Errorf("wav: channel num must be 1 or 2 but was %d", channelNum)
}
bitsPerSample = int(buf[14]) | int(buf[15])<<8
if bitsPerSample != 8 && bitsPerSample != 16 {
return nil, fmt.Errorf("wav: bits per sample must be 8 or 16 but was %d", bitsPerSample)
}
origSampleRate := int64(buf[4]) | int64(buf[5])<<8 | int64(buf[6])<<16 | int64(buf[7])<<24
if int64(sampleRate) != origSampleRate {
sampleRateFrom = int(origSampleRate)
sampleRateTo = sampleRate
}
headerSize += size
case bytes.Equal(buf[0:4], []byte("data")):
dataSize = size
break chunks
default:
buf := make([]byte, size)
n, err := io.ReadFull(src, buf)
if n != len(buf) {
return nil, fmt.Errorf("wav: invalid header")
}
if err != nil {
return nil, err
}
headerSize += size
}
}
var s io.ReadSeeker = &stream{
src: src,
headerSize: headerSize,
dataSize: dataSize,
remaining: dataSize,
}
if mono || bitsPerSample != 16 {
s = convert.NewStereo16(s, mono, bitsPerSample != 16)
if mono {
dataSize *= 2
}
if bitsPerSample != 16 {
dataSize *= 2
}
}
if sampleRateFrom != sampleRateTo {
r := convert.NewResampling(s, dataSize, sampleRateFrom, sampleRateTo)
s = r
dataSize = r.Length()
}
ss := &Stream{inner: s, size: dataSize}
return ss, nil
}
Decode 根据上下文采样率自动设置。