ebiten 学习(11)-decode.go 解码(续)

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个字节组成。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值