audio/mp3
Reading the frames, headers and VBR tags of an mp3 file.
import audio.mp3
An mp3 file is a run of frames, each with a header saying how it is coded and how long it is. This package reads those headers, so you can tell what a file is, how long it lasts, how many frames it holds and where in it a given second falls, all without decoding any sound.
That covers everything a library listing, a player's seek bar or a tag editor needs. Every MPEG version and layer is parsed, including the MPEG-2 and 2.5 half and quarter sample rates, and the Xing, Info and VBRI headers that say how long a variable bit rate file really is.
Things to know
- Audio decoding is not here yet. This package reads the structure of an mp3, not its sound, and
CanDecodein audio/codec says so. Layer III decoding needs the ISO Huffman and scalefactor tables, which are a separate and much larger job. - A frame is only trusted when another frame follows where it says one should, which is what stops
FindSynclocking onto a run of 0xFF bytes inside a tag. - The frame count in a Xing header does not include the frame the header itself sits in, so a file with 10 audio frames reports 11 when counted and 10 when asked.
- For a constant bit rate file the length is frames times samples per frame. For a variable one, only the Xing header knows, which is why
ProbeMP3prefers it. - Tags are audio/tag's job, and this package uses it to find where the audio starts.
Types
Header
struct Header| Name | Type | Description |
|---|---|---|
version | int = 0 | Which MPEG version, one of the Version numbers. |
layer | int = 0 | Which layer, one of the Layer numbers. |
bitrate | int = 0 | The bit rate in kilobits per second. |
rate | int = 0 | Frames per second. |
channels | int = 0 | How many channels, worked out from the mode. |
mode | int = 0 | The channel mode, one of the Mode numbers. |
mode_ext | int = 0 | The mode extension, which says what joint stereo is doing. |
padding | int = 0 | 1 when this frame carries one extra byte to keep the average bit rate. |
crc | bool = false | True when a CRC follows the header. |
frame_size | int = 0 | How many bytes the whole frame takes, padding included. |
samples | int = 0 | How many PCM frames this frame decodes to. |
import audio
import audio.mp3
import audio.tag
import compress
func push_frame(b &: compress.ByteBuf, h1: int, h2: int, h3: int, size: int):
compress.BufPush(b, 255)
compress.BufPush(b, h1)
compress.BufPush(b, h2)
compress.BufPush(b, h3)
mut i := 4
until i < size:
compress.BufPush(b, 0)
i += 1
end
end
func main:
mut cbr := compress.NewByteBuf()
mut n := 0
until n < 3:
push_frame(cbr, 0xFB, 0x90, 0xC0, 417)
n += 1
end
println compress.BufLen(cbr)
println mp3.IsMP3(cbr.data, cbr.len), audio.FormatName(audio.DetectFormat(cbr.data, cbr.len))
println tag.HasID3v2(cbr.data, cbr.len), mp3.AudioStart(cbr.data, cbr.len)
mut h := mp3.Header {}
println mp3.ParseHeader(cbr.data, cbr.len, 0, h)
println h.version == mp3.Version1, h.layer == mp3.Layer3
println h.rate, h.bitrate, h.channels, h.mode == mp3.ModeMono
println h.samples, h.frame_size, h.padding, h.crc
println mp3.CountFrames(cbr.data, cbr.len)
mut pok := false
info := mp3.ProbeMP3(cbr.data, cbr.len, pok)
println pok, info.rate, info.channels, info.bits, info.lossless
println info.frames
println mp3.FrameAt(cbr.data, cbr.len, 0), mp3.FrameAt(cbr.data, cbr.len, 1)
println mp3.FrameAt(cbr.data, cbr.len, 99)
println mp3.SeekToSecond(cbr.data, cbr.len, 0.03) >= 0
mut heads := mp3.Headers(cbr.data, cbr.len)
println heads.len(), heads.get(0).rate, heads.get(2).bitrate
heads.free()
mut v2 := compress.NewByteBuf()
mut p := 0
until p < 2:
push_frame(v2, 0xF3, 0x80, 0x00, 208)
p += 1
end
mut vh := mp3.Header {}
println mp3.ParseHeader(v2.data, v2.len, 0, vh)
println vh.version == mp3.Version2, vh.rate, vh.channels, vh.samples, vh.frame_size
println mp3.CountFrames(v2.data, v2.len)
println mp3.SamplesPerFrame(mp3.Version1, mp3.Layer3), mp3.SamplesPerFrame(mp3.Version2, mp3.Layer3)
println mp3.SamplesPerFrame(mp3.Version25, mp3.Layer3), mp3.SamplesPerFrame(mp3.Version1, mp3.Layer1)
println mp3.SamplesPerFrame(mp3.Version1, mp3.Layer2)
println mp3.ModeStereo, mp3.ModeJoint, mp3.ModeDual, mp3.ModeMono
mut junk := compress.NewByteBuf()
mut j := 0
until j < 32:
compress.BufPush(junk, j)
j += 1
end
println mp3.FindSync(junk.data, junk.len, 0), mp3.IsMP3(junk.data, junk.len)
println mp3.CountFrames(junk.data, junk.len)
mut bad := mp3.Header {}
println mp3.ParseHeader(junk.data, junk.len, 0, bad)
mut nok := false
mp3.ProbeMP3(junk.data, junk.len, nok)
println nok
mut vn := mp3.VbrInfo {}
println mp3.ReadVbrInfo(cbr.data, cbr.len, vn), vn.found
compress.BufFree(cbr)
compress.BufFree(v2)
compress.BufFree(junk)
endVbrInfo
struct VbrInfo| Name | Type | Description |
|---|---|---|
frames | int = 0 | How many audio frames follow. The header's own frame is not counted. |
bytes | int = 0 | How many bytes of audio there are. |
vbr | bool = false | True for a Xing header, which means the bit rate varies. |
quality | int = 0 | The quality number the encoder recorded. |
delay | int = 0 | How many samples the encoder added at the front, which gapless playback must drop. |
padding | int = 0 | How many samples it added at the end. |
found | bool = false | True when such a header was there at all. |
The example for this item is shown with the ones above: mp3-Read.salam
Constants and variables
MPEG version numbers Version…
| Name | Value |
|---|---|
Version25 | 0 |
Version2 | 2 |
Version1 | 3 |
The example for this item is shown with the ones above: mp3-Read.salam
Layer numbers Layer…
| Name | Value |
|---|---|
Layer1 | 3 |
Layer2 | 2 |
Layer3 | 1 |
The example for this item is shown with the ones above: mp3-Read.salam
Channel mode numbers Mode…
| Name | Value |
|---|---|
ModeStereo | 0 |
ModeJoint | 1 |
ModeDual | 2 |
ModeMono | 3 |
The example for this item is shown with the ones above: mp3-Read.salam
Functions
SamplesPerFrame
func SamplesPerFrame(version: int, layer: int): intThe example for this item is shown with the ones above: mp3-Read.salam
ParseHeader
func ParseHeader(p: void*, n: int, at: int, out &: Header): boolThe example for this item is shown with the ones above: mp3-Read.salam
FindSync
func FindSync(p: void*, n: int, from: int): intThe example for this item is shown with the ones above: mp3-Read.salam
AudioStart
func AudioStart(p: void*, n: int): intThe example for this item is shown with the ones above: mp3-Read.salam
IsMP3
func IsMP3(p: void*, n: int): boolThe example for this item is shown with the ones above: mp3-Read.salam
CountFrames
func CountFrames(p: void*, n: int): intThe example for this item is shown with the ones above: mp3-Read.salam
ReadVbrInfo
func ReadVbrInfo(p: void*, n: int, out &: VbrInfo): boolThe example for this item is shown with the ones above: mp3-Read.salam
ProbeMP3
func ProbeMP3(p: void*, n: int, ok &: bool): audio.InfoThe example for this item is shown with the ones above: mp3-Read.salam
FrameAt
func FrameAt(p: void*, n: int, index: int): intThe example for this item is shown with the ones above: mp3-Read.salam
SeekToSecond
func SeekToSecond(p: void*, n: int, seconds: f64): intThe example for this item is shown with the ones above: mp3-Read.salam
Headers
func Headers(p: void*, n: int): Vector<Header>The example for this item is shown with the ones above: mp3-Read.salam
ProbeFileMP3
func ProbeFileMP3(path: str, ok &: bool): audio.InfoThe example for this item is shown with the ones above: mp3-Read.salam