Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] feat: use native Go package for decoding Ogg Vorbis audio #118

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
go-version: '1.22.2'

- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y libasound2-dev libvorbis-dev libogg-dev
run: sudo apt-get update && sudo apt-get install -y libasound2-dev

- name: Build daemon
run: go build -v ./cmd/daemon
Expand Down
28 changes: 0 additions & 28 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,34 +49,6 @@ jobs:
make
sudo make install

- name: Download libogg sources
working-directory: /tmp
run: |
wget https://downloads.xiph.org/releases/ogg/libogg-1.3.5.tar.xz
tar -xvf libogg-1.3.5.tar.xz

- name: Compile libogg for ${{ matrix.target }}
working-directory: /tmp
run: |
cd libogg-1.3.5
./configure --host=${{ matrix.target }} --prefix=/tmp/deps/${{ matrix.target }}
make
sudo make install

- name: Download libvorbis sources
working-directory: /tmp
run: |
wget https://downloads.xiph.org/releases/vorbis/libvorbis-1.3.7.tar.xz
tar -xvf libvorbis-1.3.7.tar.xz

- name: Compile libvorbis for ${{ matrix.target }}
working-directory: /tmp
run: |
cd libvorbis-1.3.7
./configure --host=${{ matrix.target }} --prefix=/tmp/deps/${{ matrix.target }}
make
sudo make install

- name: Package dependencies
run: mkdir /tmp/out && tar -C /tmp/deps -cvf /tmp/out/${{ matrix.target }}.tar ${{ matrix.target }}

Expand Down
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 [4]byte
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 != [4]byte{'O', 'g', 'g', 'S'} ||
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
Loading