Skip to content

Commit

Permalink
feat: use native Go package for decoding Ogg Vorbis audio
Browse files Browse the repository at this point in the history
  • Loading branch information
aykevl committed Oct 7, 2024
1 parent 7287f59 commit f7c46d7
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 386 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,11 @@ To crosscompile for different architectures the `GOOS` and `GOARCH` environment
You need to have the following installed:

* Go 1.22 or higher
* libogg
* libvorbis
* libasound2

You can install the 3 libraries in Debian (and Ubuntu/Raspbian) using the following command:

sudo apt-get install libogg-dev libvorbis-dev libasound2-dev
sudo apt-get install libasound2-dev

You can install a newer Go version from the [Go website](https://go.dev/dl/).

Expand Down
74 changes: 40 additions & 34 deletions audio/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (

librespot "github.com/devgianlu/go-librespot"
log "github.com/sirupsen/logrus"
"github.com/xlab/vorbis-go/vorbis"
)

const (
Expand Down Expand Up @@ -49,51 +48,59 @@ type MetadataPage struct {
}

func ExtractMetadataPage(r io.ReaderAt, limit int64) (librespot.SizedReadAtSeeker, *MetadataPage, error) {
var syncState vorbis.OggSyncState
vorbis.OggSyncInit(&syncState)

defer func() {
vorbis.OggSyncClear(&syncState)
syncState.Free()
}()

rr := io.NewSectionReader(r, 0, limit)

// read enough bytes for the first ogg packet to fit
buf := vorbis.OggSyncBuffer(&syncState, 512)
n, err := io.ReadFull(rr, buf[:512])
vorbis.OggSyncWrote(&syncState, n)
buf := make([]byte, 512)
_, err := rr.ReadAt(buf, 0)
if err != nil {
return nil, nil, fmt.Errorf("failed reading vorbis stream head")
return nil, nil, fmt.Errorf("failed reading vorbis stream head: %w", err)
}

var page vorbis.OggPage
if ret := vorbis.OggSyncPageout(&syncState, &page); ret != 1 {
return nil, nil, errors.New("vorbis: not a valid Ogg bitstream")
// Read the Ogg page header (excluding the segment table).
var page struct {
CapturePattern uint32
Version uint8
Flags uint8
GranulePosition uint64
BitstreamSerialNumber uint32
PageSequenceNumber uint32
Checksum uint32
PageSegments uint8
}
bufReader := bytes.NewReader(buf)
err = binary.Read(bufReader, binary.LittleEndian, &page)
if err != nil {
return nil, nil, fmt.Errorf("failed reading ogg page header: %w", err)
}

var streamState vorbis.OggStreamState
vorbis.OggStreamInit(&streamState, vorbis.OggPageSerialno(&page))

defer func() {
vorbis.OggStreamClear(&streamState)
streamState.Free()
}()

if ret := vorbis.OggStreamPagein(&streamState, &page); ret < 0 {
return nil, nil, errors.New("vorbis: the supplied page does not belong this Vorbis stream")
// Check that the page looks like a metadata page.
if page.CapturePattern != 0x5367674f || // "OggS"
page.Version != 0 || // always 0
page.Flags != 6 || // entire "stream" is a single page (BOS and EOS set)
page.PageSegments != 1 { // there's only a single metadata segment
return nil, nil, fmt.Errorf("not a valid Ogg bitstream metadata packet")
}

var packet vorbis.OggPacket
if ret := vorbis.OggStreamPacketout(&streamState, &packet); ret != 1 {
return nil, nil, errors.New("vorbis: unable to fetch initial Vorbis packet from the first page")
// Read the segment table field, which has a somewhat odd encoding.
bodySize := int(0)
for {
b, err := bufReader.ReadByte()
if err != nil {
return nil, nil, fmt.Errorf("not a valid Ogg bitstream: %w", err)
}
if b != 255 {
bodySize += int(b)
break
}
}

defer packet.Free()
// Get a reader for the page buffer (only).
pageHeaderSize := int(bufReader.Size()) - bufReader.Len()
body := bytes.NewReader(buf[pageHeaderSize : pageHeaderSize+bodySize])
pageSize := pageHeaderSize + bodySize

// we have the ogg packet, check it is the metadata page
packet.Deref()
body := bytes.NewReader(packet.Packet[:packet.Bytes])
if b, _ := body.ReadByte(); b != 0x81 {
return nil, nil, fmt.Errorf("invalid metadata page")
}
Expand Down Expand Up @@ -178,8 +185,7 @@ func ExtractMetadataPage(r io.ReaderAt, limit int64) (librespot.SizedReadAtSeeke
}

// return a new stream without the metadata page
syncState.Deref()
return io.NewSectionReader(r, int64(syncState.Returned), limit-int64(syncState.Returned)), &metadata, nil
return io.NewSectionReader(r, int64(pageSize), limit-int64(pageSize)), &metadata, nil
}

func (m MetadataPage) GetTrackFactor(normalisationPregain float32) float32 {
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/devgianlu/shannon v0.0.0-20230613115856-82ec90b7fa7e
github.com/gofrs/flock v0.12.1
github.com/grandcat/zeroconf v1.0.0
github.com/jfreymuth/oggvorbis v1.0.5
github.com/knadh/koanf/parsers/yaml v0.1.0
github.com/knadh/koanf/providers/confmap v0.1.0
github.com/knadh/koanf/providers/file v1.1.0
Expand All @@ -15,7 +16,6 @@ require (
github.com/rs/cors v1.11.1
github.com/sirupsen/logrus v1.9.3
github.com/spf13/pflag v1.0.5
github.com/xlab/vorbis-go v0.0.0-20210911202351-b5b85f1ec645
golang.org/x/crypto v0.24.0
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
golang.org/x/net v0.26.0
Expand All @@ -29,6 +29,7 @@ require (
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
github.com/jfreymuth/vorbis v1.0.2 // indirect
github.com/klauspost/compress v1.10.3 // indirect
github.com/knadh/koanf/maps v0.1.1 // indirect
github.com/kr/text v0.2.0 // indirect
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvK
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grandcat/zeroconf v1.0.0 h1:uHhahLBKqwWBV6WZUDAT71044vwOTL+McW0mBJvo6kE=
github.com/grandcat/zeroconf v1.0.0/go.mod h1:lTKmG1zh86XyCoUeIHSA4FJMBwCJiQmGfcP2PdzytEs=
github.com/jfreymuth/oggvorbis v1.0.5 h1:u+Ck+R0eLSRhgq8WTmffYnrVtSztJcYrl588DM4e3kQ=
github.com/jfreymuth/oggvorbis v1.0.5/go.mod h1:1U4pqWmghcoVsCJJ4fRBKv9peUJMBHixthRlBeD6uII=
github.com/jfreymuth/vorbis v1.0.2 h1:m1xH6+ZI4thH927pgKD8JOH4eaGRm18rEE9/0WKjvNE=
github.com/jfreymuth/vorbis v1.0.2/go.mod h1:DoftRo4AznKnShRl1GxiTFCseHr4zR9BN3TWXyuzrqQ=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/klauspost/compress v1.10.3 h1:OP96hzwJVBIHYU52pVTI6CczrxPvrGfgqF9N5eTO0Q8=
Expand Down Expand Up @@ -101,8 +105,6 @@ github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/xlab/vorbis-go v0.0.0-20210911202351-b5b85f1ec645 h1:lYg/+vV/Fd5WM1+Ptg54Am3y4mDXaMSrT+mKUHV5uVc=
github.com/xlab/vorbis-go v0.0.0-20210911202351-b5b85f1ec645/go.mod h1:AMqfx3jFwPqem3u8mF2lsRodZs30jG/Mag5HZ3mB3sA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
Expand Down
Loading

0 comments on commit f7c46d7

Please sign in to comment.