2021SC@SDUSC
一、前言
1.说明
audio 说明文档地址
audio package - github.com/hajimehoshi/ebiten/v2/audio - pkg.go.dev
本文中所有截图和代码均来自说明文档或 ebiten 源文件
笔者负责 audio 音频相关的代码分析
2.梗概
发现除了 wav 包下,mp3 包下也有个 decode.go,因此续写一篇。
二、解码 mp3
mp3 包提供 mp3 解码器。
1.解码 mp3
stream 结构体是一个解码后的流。
Read 是 io.Reader 的 Read 方法的实现。
Seek 是 io.Seeker 的 Seek 方法的实现。
Length返回解码流的大小(以字节为单位)。
DecodeWithSampleRate 对 MP3 源进行解码并返回解码后的流。
DecodeWithSampleRate 会在必要时自动对流进行重新采样,以适合采样器。
同上,不过采样率根据音频上下文确定。
2.MP3 解码的根文件
目录位置
解码器动态解码其底层源。
读文件,是 io.Reader 的 Read 方法的实现。
Seek 是 io.Seeker’s Seek。负责定位。
func (d *Decoder) Seek(offset int64, whence int) (int64, error) {
if offset == 0 && whence == io.SeekCurrent {
// Handle the special case of asking for the current position specially.
return d.pos, nil
}
npos := int64(0)
switch whence {
case io.SeekStart:
npos = offset
case io.SeekCurrent:
npos = d.pos + offset
case io.SeekEnd:
npos = d.Length() + offset
default:
return 0, errors.New("mp3: invalid whence")
}
d.pos = npos
d.buf = nil
d.frame = nil
f := d.pos / d.bytesPerFrame
// If the frame is not first, read the previous ahead of reading that
// because the previous frame can affect the targeted frame.
if f > 0 {
f--
if _, err := d.source.Seek(d.frameStarts[f], 0); err != nil {
return 0, err
}
if err := d.readFrame(); err != nil {
return 0, err
}
if err := d.readFrame(); err != nil {
return 0, err
}
d.buf = d.buf[d.bytesPerFrame+(d.pos%d.bytesPerFrame):]
} else {
if _, err := d.source.Seek(d.frameStarts[f], 0); err != nil {
return 0, err
}
if err := d.readFrame(); err != nil {
return 0, err
}
d.buf = d.buf[d.pos:]
}
return npos, nil
}
SampleRate 返回采样率。
检查合法合理性。
func (d *Decoder) ensureFrameStartsAndLength() error {
if d.length != invalidLength {
return nil
}
if _, ok := d.source.reader.(io.Seeker); !ok {
return nil
}
// Keep the current position.
pos, err := d.source.Seek(0, io.SeekCurrent)
if err != nil {
return err
}
if err := d.source.rewind(); err != nil {
return err
}
if err := d.source.skipTags(); err != nil {
return err
}
l := int64(0)
for {
h, pos, err := frameheader.Read(d.source, d.source.pos)
if err != nil {
if err == io.EOF {
break
}
if _, ok := err.(*consts.UnexpectedEOF); ok {
// TODO: Log here?
break
}
return err
}
d.frameStarts = append(d.frameStarts, pos)
d.bytesPerFrame = int64(h.BytesPerFrame())
l += d.bytesPerFrame
framesize, err := h.FrameSize()
if err != nil {
return err
}
buf := make([]byte, framesize-4)
if _, err := d.source.ReadFull(buf); err != nil {
if err == io.EOF {
break
}
return err
}
}
d.length = l
if _, err := d.source.Seek(pos, io.SeekStart); err != nil {
return err
}
return nil
}
Length 返回总长度,单位字节。
NewDecoder 解码给定的 io.Reader 并返回解码流。
流始终格式化为16位(小端)2个通道,即使源是单声道MP3。
因此,样本始终由4个字节组成。