qoa

package module
v1.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 4, 2025 License: MIT Imports: 5 Imported by: 2

README

QOA

Go Reference

The Quite OK Audio Format for Fast, Lossy Compression.

The qoa package is a pure Go implementation.

Decode a .qoa file:

data, _ := os.ReadFile("groovy-tunes.qoa")
qoaMetadata, decodedData, err = qoa.Decode(inputData)
// Do stuff with decodedData

Or encode audio samples. This example shows a WAV file:

// Read a WAV
data, _ := os.ReadFile("groovy-tunes.wav")
wavReader := bytes.NewReader(data)
wavDecoder := wav.NewDecoder(wavReader)
wavBuffer, err := wavDecoder.FullPCMBuffer()

// Figure out audio metadata and create a new QOA encoder using the info
numSamples := uint32(len(wavBuffer.Data) / wavBuffer.Format.NumChannels)
qoaFormat := qoa.NewEncoder(
  uint32(wavBuffer.Format.SampleRate),
  uint32(wavBuffer.Format.NumChannels),
  numSamples)
// Convert the audio data to int16 (QOA format)
decodedData = make([]int16, len(wavBuffer.Data))
for i, val := range wavBuffer.Data {
  decodedData[i] = int16(val)
}

// Finally, encode the audio data
qoaEncodedData, err := qoa.Encode(decodedData)

Commit History

Most of this package was developed in goqoa and the commit history can be found there.

I moved it out of that project because I didn't want the version of the library to be dependent on the version of the higher level goqoa tool and I didn't know (at the time) that Go has an opinionated way of handling module versions.

Documentation

Overview

Package qoa provides functionality for encoding and decoding audio data in the QOA format.

The following is from the QOA specification:

Data Format

QOA encodes pulse-code modulated (PCM) audio data with up to 255 channels, sample rates from 1 up to 16777215 hertz and a bit depth of 16 bits.

The compression method employed in QOA is lossy; it discards some information from the uncompressed PCM data. For many types of audio signals this compression is "transparent", i.e. the difference from the original file is often not audible.

QOA encodes 20 samples of 16 bit PCM data into slices of 64 bits. A single sample therefore requires 3.2 bits of storage space, resulting in a 5x compression (16 / 3.2).

A QOA file consists of an 8 byte file header, followed by a number of frames. Each frame contains an 8 byte frame header, the current 16 byte en-/decoder state per channel and 256 slices per channel. Each slice is 8 bytes wide and encodes 20 samples of audio data.

All values, including the slices, are big endian. The file layout is as follows:

struct {
	struct {
		char     magic[4];         // magic bytes "qoaf"
		uint32_t samples;          // samples per channel in this file
	} file_header;

	struct {
		struct {
			uint8_t  num_channels; // no. of channels
			uint24_t samplerate;   // samplerate in hz
			uint16_t fsamples;     // samples per channel in this frame
			uint16_t fsize;        // frame size (includes this header)
		} frame_header;

		struct {
			int16_t history[4];    // most recent last
			int16_t weights[4];    // most recent last
		} lms_state[num_channels];

		qoa_slice_t slices[256][num_channels];

	} frames[ceil(samples / (256 * 20))];
} qoa_file_t;

Each qoa_slice_t contains a quantized scalefactor sf_quant and 20 quantized residuals qrNN:

.- QOA_SLICE -- 64 bits, 20 samples --------------------------/  /------------.
|        Byte[0]         |        Byte[1]         |  Byte[2]  \  \  Byte[7]   |
| 7  6  5  4  3  2  1  0 | 7  6  5  4  3  2  1  0 | 7  6  5   /  /    2  1  0 |
|------------+--------+--------+--------+---------+---------+-\  \--+---------|
|  sf_quant  |  qr00  |  qr01  |  qr02  |  qr03   |  qr04   | /  /  |  qr19   |
`-------------------------------------------------------------\  \------------`

Each frame except the last must contain exactly 256 slices per channel. The last frame may contain between 1 .. 256 (inclusive) slices per channel. The last slice (for each channel) in the last frame may contain less than 20 samples; the slice still must be 8 bytes wide, with the unused samples zeroed out.

Channels are interleaved per slice. E.g. for 2 channel stereo: slice[0] = L, slice[1] = R, slice[2] = L, slice[3] = R ...

A valid QOA file or stream must have at least one frame. Each frame must contain at least one channel and one sample with a samplerate between 1 .. 16777215 (inclusive).

If the total number of samples is not known by the encoder, the samples in the file header may be set to 0x00000000 to indicate that the encoder is "streaming". In a streaming context, the samplerate and number of channels may differ from frame to frame. For static files (those with samples set to a non-zero value), each frame must have the same number of channels and same samplerate.

Note that this implementation of QOA only handles files with a known total number of samples.

A decoder should support at least 8 channels. The channel layout for channel counts 1 .. 8 is:

  1. Mono
  2. L, R
  3. L, R, C
  4. FL, FR, B/SL, B/SR
  5. FL, FR, C, B/SL, B/SR
  6. FL, FR, C, LFE, B/SL, B/SR
  7. FL, FR, C, LFE, B, SL, SR
  8. FL, FR, C, LFE, BL, BR, SL, SR

QOA predicts each audio sample based on the previously decoded ones using a "Sign-Sign Least Mean Squares Filter" (LMS). This prediction plus the dequantized residual forms the final output sample.

Index

Constants

View Source
const (
	// QOAMagic is the magic number identifying a QOA file
	QOAMagic = 0x716f6166 // 'qoaf'
	// QOAMinFilesize is the minimum valid size of a QOA file.
	QOAMinFilesize = 16
	// QOAMaxChannels is the maximum number of audio channels supported by QOA.
	QOAMaxChannels = 8
	// QOASliceLen is the length of each QOA audio slice.
	QOASliceLen = 20
	// QOASlicesPerFrame is the number of slices per QOA frame.
	QOASlicesPerFrame = 256
	// QOAFrameLen is the length of a QOA frame.
	QOAFrameLen = QOASlicesPerFrame * QOASliceLen
	// QOALMSLen is the length of the LMS state per channel.
	QOALMSLen = 4
)

Variables

View Source
var ErrInvalidArgument = errors.New("invalid argument")

Functions

func IsValidQOAFile

func IsValidQOAFile(inputFile string) (bool, error)

Types

type QOA

type QOA struct {
	Channels   uint32 // Number of audio channels
	SampleRate uint32 // Sample rate of the audio
	Samples    uint32 // Total number of audio samples

	ErrorCount int // Sum of best LMS errors encountered during encoding
	// contains filtered or unexported fields
}

QOA stores the QOA audio file description.

func Decode

func Decode(bytes []byte) (*QOA, []int16, error)

Decode decodes the provided QOA encoded bytes and returns the QOA struct and the decoded audio sample data.

func DecodeHeader

func DecodeHeader(bytes []byte) (*QOA, error)

DecodeHeader decodes the QOA header and initializes the QOA struct with header information.

func NewEncoder

func NewEncoder(sampleRate, channels, samples uint32) *QOA

NewEncoder creates a new QOA encoder with the specified sample rate, channels, and samples.

func (*QOA) Encode

func (q *QOA) Encode(sampleData []int16) ([]byte, error)

Encode encodes the provided audio sample data in QOA format and returns the encoded bytes.

type Reader

type Reader struct {
	// contains filtered or unexported fields
}

Reader is a custom io.Reader that reads from QOA audio data.

func NewReader

func NewReader(data []int16, channels int) *Reader

NewReader creates a new Reader instance.

func (*Reader) Position

func (r *Reader) Position() int

Position returns the number of bytes that have been read

func (*Reader) Read

func (r *Reader) Read(p []byte) (n int, err error)

Read implements the io.Reader interface

func (*Reader) Seek

func (r *Reader) Seek(offset int64, whence int) (int64, error)

Seek implements the io.Seeker interface

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL