Files
icecast-ripper/vendor/github.com/tcolgate/mp3/frames.go
Dmitry Andreev b5f23a50b7
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
Docker / build (push) Has been cancelled
golangci-lint / lint (push) Has been cancelled
Integrate MP3 duration extraction and silence handling (#6)
* feat: integrate MP3 duration extraction and silence handling

- Added a new dependency on github.com/tcolgate/mp3 for MP3 frame decoding.
- Implemented actual MP3 duration retrieval in the scanRecordings function, falling back to an estimation if retrieval fails.
- Introduced a silent frame asset for generating silence in audio streams.
- Created a silence reader to provide a continuous stream of silent frames.
- Added necessary documentation and licensing for the new MP3 package.
- Updated .gitignore to exclude MP3 files except for the internal data directory.

* feat: update README and improve application configuration and logging

* refactor: remove unused GenerateFileHash function and improve resource cleanup in MP3 and recorder modules
2025-04-13 15:56:22 +03:00

449 lines
12 KiB
Go

package mp3
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"time"
)
type (
// Decoder translates a io.Reader into a series of frames
Decoder struct {
src io.Reader
err error
}
// Frame represents one individual mp3 frame
Frame struct {
buf []byte
}
// FrameHeader represents the entire header of a frame
FrameHeader []byte
// FrameVersion is the MPEG version given in the frame header
FrameVersion byte
// FrameLayer is the MPEG layer given in the frame header
FrameLayer byte
// FrameEmphasis is the Emphasis value from the frame header
FrameEmphasis byte
// FrameChannelMode is the Channel mode from the frame header
FrameChannelMode byte
// FrameBitRate is the bit rate from the frame header
FrameBitRate int
// FrameSampleRate is the sample rate from teh frame header
FrameSampleRate int
// FrameSideInfo holds the SideInfo bytes from the frame
FrameSideInfo []byte
)
//go:generate stringer -type=FrameVersion
const (
MPEG25 FrameVersion = iota
MPEGReserved
MPEG2
MPEG1
VERSIONMAX
)
//go:generate stringer -type=FrameLayer
const (
LayerReserved FrameLayer = iota
Layer3
Layer2
Layer1
LayerMax
)
//go:generate stringer -type=FrameEmphasis
const (
EmphNone FrameEmphasis = iota
Emph5015
EmphReserved
EmphCCITJ17
EmphMax
)
//go:generate stringer -type=FrameChannelMode
const (
Stereo FrameChannelMode = iota
JointStereo
DualChannel
SingleChannel
ChannelModeMax
)
const (
// ErrInvalidBitrate indicates that the header information did not contain a recognized bitrate
ErrInvalidBitrate FrameBitRate = -1
)
var (
bitrates = [VERSIONMAX][LayerMax][15]int{
{ // MPEG 2.5
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // LayerReserved
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, // Layer3
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, // Layer2
{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}, // Layer1
},
{ // Reserved
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // LayerReserved
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Layer3
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Layer2
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Layer1
},
{ // MPEG 2
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // LayerReserved
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, // Layer3
{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, // Layer2
{0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}, // Layer1
},
{ // MPEG 1
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // LayerReserved
{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320}, // Layer3
{0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384}, // Layer2
{0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448}, // Layer1
},
}
sampleRates = [int(VERSIONMAX)][3]int{
{11025, 12000, 8000}, //MPEG25
{0, 0, 0}, //MPEGReserved
{22050, 24000, 16000}, //MPEG2
{44100, 48000, 32000}, //MPEG1
}
// ErrInvalidSampleRate indicates that no samplerate could be found for the frame header provided
ErrInvalidSampleRate = FrameSampleRate(-1)
samplesPerFrame = [VERSIONMAX][LayerMax]int{
{ // MPEG25
0,
576,
1152,
384,
},
{ // Reserved
0,
0,
0,
0,
},
{ // MPEG2
0,
576,
1152,
384,
},
{ // MPEG1
0,
1152,
1152,
384,
},
}
slotSize = [LayerMax]int{
0, // LayerReserved
1, // Layer3
1, // Layer2
4, // Layer1
}
// ErrNoSyncBits implies we could not find a valid frame header sync bit before EOF
ErrNoSyncBits = errors.New("EOF before sync bits found")
// ErrPrematureEOF indicates that the filed ended before a complete frame could be read
ErrPrematureEOF = errors.New("EOF mid stream")
)
func init() {
bitrates[MPEG25] = bitrates[MPEG2]
samplesPerFrame[MPEG25] = samplesPerFrame[MPEG2]
}
// NewDecoder returns a decoder that will process the provided reader.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{r, nil}
}
// fill slice d until it is of len l, using bytes from reader r
func fillbuf(d []byte, r io.Reader, l int) (res []byte, err error) {
if len(d) >= l {
// we already have enough bytes
return d, nil
}
// How many bytes do we need to fetch
missing := l - len(d)
// Does d have sufficient capacity? if not extent it
if cap(d) < l {
if d == nil {
d = make([]byte, l)
} else {
il := len(d)
d = d[:cap(d)] // stretch d to it's full capacity
d = append(d, make([]byte, l-cap(d))...)
d = d[:il] //we've extended the capm reset len
}
}
d = d[:l]
_, err = io.ReadFull(r, d[len(d)-missing:])
return d, err
}
// Decode reads the next complete discovered frame into the provided
// Frame struct. A count of skipped bytes will be written to skipped.
func (d *Decoder) Decode(v *Frame, skipped *int) (err error) {
// Truncate the array
v.buf = v.buf[:0]
hLen := 4
// locate a sync frame
*skipped = 0
for {
v.buf, err = fillbuf(v.buf, d.src, hLen)
if err != nil {
return err
}
if v.buf[0] == 0xFF && (v.buf[1]&0xE0 == 0xE0) &&
v.Header().Emphasis() != EmphReserved &&
v.Header().Layer() != LayerReserved &&
v.Header().Version() != MPEGReserved &&
v.Header().SampleRate() != -1 &&
v.Header().BitRate() != -1 {
break
}
switch {
case v.buf[1] == 0xFF:
v.buf = v.buf[1:]
*skipped++
default:
v.buf = v.buf[2:]
*skipped += 2
}
}
crcLen := 0
if v.Header().Protection() {
crcLen = 2
v.buf, err = fillbuf(v.buf, d.src, hLen+crcLen)
if err != nil {
return err
}
}
sideLen, err := v.SideInfoLength()
if err != nil {
return err
}
v.buf, err = fillbuf(v.buf, d.src, hLen+crcLen+sideLen)
if err != nil {
return err
}
dataLen := v.Size()
v.buf, err = fillbuf(v.buf, d.src, dataLen)
if err != nil {
return err
}
return nil
}
// SideInfoLength retursn the expected side info length for this
// mp3 frame
func (f *Frame) SideInfoLength() (int, error) {
switch f.Header().Version() {
case MPEG1:
switch f.Header().ChannelMode() {
case SingleChannel:
return 17, nil
case Stereo, JointStereo, DualChannel:
return 32, nil
default:
return 0, errors.New("bad channel mode")
}
case MPEG2, MPEG25:
switch f.Header().ChannelMode() {
case SingleChannel:
return 9, nil
case Stereo, JointStereo, DualChannel:
return 17, nil
default:
return 0, errors.New("bad channel mode")
}
default:
return 0, fmt.Errorf("bad version (%v)", f.Header().Version())
}
}
// Header returns the header for this frame
func (f *Frame) Header() FrameHeader {
return FrameHeader(f.buf[0:4])
}
// CRC returns the CRC word stored in this frame
func (f *Frame) CRC() (uint16, error) {
var crc uint16
if !f.Header().Protection() {
return 0, nil
}
crcdata := bytes.NewReader(f.buf[4:6])
err := binary.Read(crcdata, binary.BigEndian, &crc)
return crc, err
}
// SideInfo returns the side info for this frame
func (f *Frame) SideInfo() FrameSideInfo {
if f.Header().Protection() {
return FrameSideInfo(f.buf[6:])
}
return FrameSideInfo(f.buf[4:])
}
// Frame returns a string describing this frame, header and side info
func (f *Frame) String() string {
str := ""
str += fmt.Sprintf("Header: \n%s", f.Header())
str += fmt.Sprintf("SideInfo: \n%s", f.SideInfo())
crc, err := f.CRC()
str += fmt.Sprintf("CRC: %x (err: %v)\n", crc, err)
str += fmt.Sprintf("Samples: %v\n", f.Samples())
str += fmt.Sprintf("Size: %v\n", f.Size())
str += fmt.Sprintf("Duration: %v\n", f.Duration())
return str
}
// Version returns the MPEG version from the header
func (h FrameHeader) Version() FrameVersion {
return FrameVersion((h[1] >> 3) & 0x03)
}
// Layer returns the MPEG layer from the header
func (h FrameHeader) Layer() FrameLayer {
return FrameLayer((h[1] >> 1) & 0x03)
}
// Protection indicates if there is a CRC present after the header (before the side data)
func (h FrameHeader) Protection() bool {
return (h[1] & 0x01) != 0x01
}
// BitRate returns the calculated bit rate from the header
func (h FrameHeader) BitRate() FrameBitRate {
bitrateIdx := (h[2] >> 4) & 0x0F
if bitrateIdx == 0x0F {
return ErrInvalidBitrate
}
br := bitrates[h.Version()][h.Layer()][bitrateIdx] * 1000
if br == 0 {
return ErrInvalidBitrate
}
return FrameBitRate(br)
}
// SampleRate returns the samplerate from the header
func (h FrameHeader) SampleRate() FrameSampleRate {
sri := (h[2] >> 2) & 0x03
if sri == 0x03 {
return ErrInvalidSampleRate
}
return FrameSampleRate(sampleRates[h.Version()][sri])
}
// Pad returns the pad bit, indicating if there are extra samples
// in this frame to make up the correct bitrate
func (h FrameHeader) Pad() bool {
return ((h[2] >> 1) & 0x01) == 0x01
}
// Private retrusn the Private bit from the header
func (h FrameHeader) Private() bool {
return (h[2] & 0x01) == 0x01
}
// ChannelMode returns the channel mode from the header
func (h FrameHeader) ChannelMode() FrameChannelMode {
return FrameChannelMode((h[3] >> 6) & 0x03)
}
// CopyRight returns the CopyRight bit from the header
func (h FrameHeader) CopyRight() bool {
return (h[3]>>3)&0x01 == 0x01
}
// Original returns the "original content" bit from the header
func (h FrameHeader) Original() bool {
return (h[3]>>2)&0x01 == 0x01
}
// Emphasis returns the Emphasis from the header
func (h FrameHeader) Emphasis() FrameEmphasis {
return FrameEmphasis((h[3] & 0x03))
}
// String dumps the frame header as a string for display purposes
func (h FrameHeader) String() string {
str := ""
str += fmt.Sprintf(" Layer: %v\n", h.Layer())
str += fmt.Sprintf(" Version: %v\n", h.Version())
str += fmt.Sprintf(" Protection: %v\n", h.Protection())
str += fmt.Sprintf(" BitRate: %v\n", h.BitRate())
str += fmt.Sprintf(" SampleRate: %v\n", h.SampleRate())
str += fmt.Sprintf(" Pad: %v\n", h.Pad())
str += fmt.Sprintf(" Private: %v\n", h.Private())
str += fmt.Sprintf(" ChannelMode: %v\n", h.ChannelMode())
str += fmt.Sprintf(" CopyRight: %v\n", h.CopyRight())
str += fmt.Sprintf(" Original: %v\n", h.Original())
str += fmt.Sprintf(" Emphasis: %v\n", h.Emphasis())
return str
}
// NDataBegin is the number of bytes before the frame header at which the sample data begins
// 0 indicates that the data begins after the side channel information. This data is the
// data from the "bit reservoir" and can be up to 511 bytes
func (i FrameSideInfo) NDataBegin() uint16 {
return (uint16(i[0]) << 1 & (uint16(i[1]) >> 7))
}
// Samples determines the number of samples based on the MPEG version and Layer from the header
func (f *Frame) Samples() int {
return samplesPerFrame[f.Header().Version()][f.Header().Layer()]
}
// Size clculates the expected size of this frame in bytes based on the header
// information
func (f *Frame) Size() int {
bps := float64(f.Samples()) / 8
fsize := (bps * float64(f.Header().BitRate())) / float64(f.Header().SampleRate())
if f.Header().Pad() {
fsize += float64(slotSize[f.Header().Layer()])
}
return int(fsize)
}
// Duration calculates the time duration of this frame based on the samplerate and number of samples
func (f *Frame) Duration() time.Duration {
ms := (1000 / float64(f.Header().SampleRate())) * float64(f.Samples())
return time.Duration(int(float64(time.Millisecond) * ms))
}
// String renders the side info as a string for display purposes
func (i FrameSideInfo) String() string {
str := ""
str += fmt.Sprintf(" NDataBegin: %v\n", i.NDataBegin())
return str
}
// Reader returns an io.Reader that reads the individual bytes from the frame
func (f *Frame) Reader() io.Reader {
return bytes.NewReader(f.buf)
}