Documentation ¶
Overview ¶
Package riff is package implementing a simple Resource Interchange File Format (RIFF) parser with basic support for sub formats such as WAV. The goal of this package is to give all the tools needed for a developer to implement parse any kind of file formats using the RIFF container format.
Support for PCM wav format was added so the headers are parsed, the duration and the raw sound data of a wav file can be easily accessed (See the examples below) .
For more information about RIFF: https://en.wikipedia.org/wiki/Resource_Interchange_File_Format
Index ¶
- Variables
- func Duration(r io.Reader) (time.Duration, error)
- type Chunk
- func (ch *Chunk) DecodeWavHeader(p *Parser) error
- func (ch *Chunk) Done()
- func (ch *Chunk) Drain()
- func (ch *Chunk) IsFullyRead() bool
- func (ch *Chunk) Read(p []byte) (n int, err error)
- func (ch *Chunk) ReadBE(dst interface{}) error
- func (ch *Chunk) ReadByte() (byte, error)
- func (ch *Chunk) ReadLE(dst interface{}) error
- type Parser
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( RiffID = [4]byte{'R', 'I', 'F', 'F'} FmtID = [4]byte{'f', 'm', 't', ' '} WavFormatID = [4]byte{'W', 'A', 'V', 'E'} // DataFormatID is the Wave Data Chunk ID, it contains the digital audio sample data which can be decoded using the format // and compression method specified in the Wave Format Chunk. If the Compression Code is 1 (uncompressed PCM), then the Wave Data contains raw sample values. DataFormatID = [4]byte{'d', 'a', 't', 'a'} // ErrFmtNotSupported is a generic error reporting an unknown format. ErrFmtNotSupported = errors.New("format not supported") // ErrUnexpectedData is a generic error reporting that the parser encountered unexpected data. ErrUnexpectedData = errors.New("unexpected data content") )
Functions ¶
func Duration ¶
Duration returns the time duration of the passed reader if the sub format is supported.
Example ¶
path, _ := filepath.Abs("fixtures/sample.wav") f, err := os.Open(path) if err != nil { panic(err) } defer f.Close() d, err := Duration(f) if err != nil { panic(err) } fmt.Printf("File with a duration of %f seconds", d.Seconds())
Output: File with a duration of 0.612177 seconds
Types ¶
type Chunk ¶
type Chunk struct { ID [4]byte Size int Pos int R io.Reader Wg *sync.WaitGroup // contains filtered or unexported fields }
Chunk represents the header and containt of a sub block See https://tech.ebu.ch/docs/tech/tech3285.pdf to see how audio content is stored in a BWF/WAVE file.
func (*Chunk) DecodeWavHeader ¶
func (*Chunk) Done ¶
func (ch *Chunk) Done()
Done signals the parent parser that we are done reading the chunk if the chunk isn't fully read, this code will do so before signaling.
func (*Chunk) IsFullyRead ¶
IsFullyRead checks if we're finished reading the chunk
type Parser ¶
type Parser struct { // Chan is an Optional channel of chunks that is used to parse chunks Chan chan *Chunk // ChunkParserTimeout is the duration after which the main parser keeps going // if the dev hasn't reported the chunk parsing to be done. // By default: 2s ChunkParserTimeout time.Duration // The waitgroup is used to let the parser that it's ok to continue // after a chunk was passed to the optional parser channel. Wg *sync.WaitGroup // Must match RIFF ID [4]byte // This size is the size of the block // controlled by the RIFF header. Normally this equals the file size. Size uint32 // Format name. // The representation of data in <wave-data>, and the content of the <format-specific-fields> // of the ‘fmt’ chunk, depend on the format category. // 0001h => Microsoft Pulse Code Modulation (PCM) format // 0050h => MPEG-1 Audio (audio only) Format [4]byte // A number indicating the WAVE format category of the file. The content of the // <format-specific-fields> portion of the ‘fmt’ chunk, and the interpretation of // the waveform data, depend on this value. // PCM = 1 (i.e. Linear quantization) Values other than 1 indicate some form of compression. WavAudioFormat uint16 // The number of channels represented in the waveform data: 1 for mono or 2 for stereo. // Audio: Mono = 1, Stereo = 2, etc. // The EBU has defined the Multi-channel Broadcast Wave // Format [4] where more than two channels of audio are required. NumChannels uint16 // The sampling rate (in sample per second) at which each channel should be played. // 8000, 44100, etc. SampleRate uint32 // The average number of bytes per second at which the waveform data should be // transferred. Playback software can estimate the buffer size using this value. // SampleRate * NumChannels * BitsPerSample/8 AvgBytesPerSec uint32 // BlockAlign = SignificantBitsPerSample / 8 * NumChannels // It is the number of bytes per sample slice. This value is not affected by the number of channels and can be calculated with the formula: // NumChannels * BitsPerSample/8 The number of bytes for one sample including // all channels. // The block alignment (in bytes) of the waveform data. Playback software needs // to process a multiple of <nBlockAlign> bytes of data at a time, so the value of // <BlockAlign> can be used for buffer alignment. BlockAlign uint16 // BitsPerSample 8, 16, 24... // Only available for PCM // This value specifies the number of bits used to define each sample. This value is usually 8, 16, 24 or 32. // If the number of bits is not byte aligned (a multiple of 8) then the number of bytes used per sample is // rounded up to the nearest byte size and the unused bytes are set to 0 and ignored. // The <nBitsPerSample> field specifies the number of bits of data used to represent each sample of // each channel. If there are multiple channels, the sample size is the same for each channel. BitsPerSample uint16 // contains filtered or unexported fields }
Parser is a struct containing the overall container information.
func New ¶
New creates a parser wrapper for a reader. Note that the reader doesn't get rewinded as the container is processed.
func (*Parser) Duration ¶
Duration returns the time duration for the current RIFF container based on the sub format (wav etc...)
func (*Parser) NextChunk ¶
NextChunk returns a convenient structure to parse the next chunk. If the container is fully read, io.EOF is returned as an error.
Example ¶
// Example showing how to access the sound data path, _ := filepath.Abs("fixtures/sample.wav") f, err := os.Open(path) if err != nil { panic(err) } defer f.Close() c := New(f) if err := c.ParseHeaders(); err != nil { panic(err) } var chunk *Chunk for err == nil { chunk, err = c.NextChunk() if err != nil { panic(err) } if chunk.ID == FmtID { chunk.DecodeWavHeader(c) } else if chunk.ID == DataFormatID { break } chunk.Done() } soundData := chunk nextSample := func() []byte { s := make([]byte, c.BlockAlign) if err := soundData.ReadLE(&s); err != nil { panic(err) } return s } // jump to a specific sample since first samples are blank desideredPos := 1541 bytePos := desideredPos * 2 for i := 0; soundData.Pos < bytePos; i++ { nextSample() if i > soundData.Size { panic(fmt.Errorf("%+v read way too many bytes, we're out of bounds", soundData)) } } sample := nextSample() fmt.Printf("1542nd sample: %#X %#X\n", sample[0], sample[1])
Output: 1542nd sample: 0XFE 0XFF
func (*Parser) Parse ¶
Parse parses the content of the file and populate the useful fields. If the parser has a chan set, chunks are sent to the channel.
func (*Parser) ParseHeaders ¶
ParseHeaders reads the header of the passed container and populat the container with parsed info. Note that this code advances the container reader.