From 32299544248ea35352934e3183fd8a82fe1e1387 Mon Sep 17 00:00:00 2001 From: David Sutton Date: Thu, 23 Nov 2023 17:15:40 +1030 Subject: [PATCH] codec/wav: Implement a wav encoder A wav encoder will be useful for returning easily playable audio files to users of vidgrind that are not familiar with PCM audio. Currently the encoder only support PCM data, but can be updated with other types. The encoder implements the writer interface. --- codec/wav/wav.go | 131 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 codec/wav/wav.go diff --git a/codec/wav/wav.go b/codec/wav/wav.go new file mode 100644 index 00000000..a4aef63d --- /dev/null +++ b/codec/wav/wav.go @@ -0,0 +1,131 @@ +/* +NAME + wav.go + +DESCRIPTION + wav.go contains functions for processing wav. + +AUTHOR + David Sutton + +LICENSE + wav.go is Copyright (C) 2023 the Australian Ocean Lab (AusOcean) + + It is free software: you can redistribute it and/or modify them + under the terms of the GNU General Public License as published by the + Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + It is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License in gpl.txt. + If not, see [GNU licenses](http://www.gnu.org/licenses). +*/ + +// Package wav provides functions for converting wav audio. +package wav + +import ( + "encoding/binary" + "fmt" +) + +const PCMFormat = 1 // PCMFormat defines the value for pcm audio as defined by the wav std. + +var ( + errInvalidFormat = fmt.Errorf("invalid or no format defined") + errInvalidRate = fmt.Errorf("invalid or no sample rate defined") + errInvalidChannels = fmt.Errorf("invalid or no number of channels defined") + errInvalidBitDepth = fmt.Errorf("invalid or no bit depth defined") +) + +// Metadata defines the format of the audio file for reading. +type Metadata struct { + AudioFormat int + Channels int + SampleRate int + BitDepth int +} + +type WAV struct { + Metadata Metadata + Audio []byte +} + +// Write writes the given audio byte slice to the WAV, encoding the appropriate headings. +func (w *WAV) Write(p []byte) (n int, err error) { + // Create header slice. + header := make([]byte, 44) + + // Write RIFF type. + copy(header[0:4], []byte("RIFF")) + + // Write the size of overall file. + buf := make([]byte, 4) + binary.LittleEndian.PutUint32(buf, uint32(len(p)+44)) + copy(header[4:8], buf) + + // Write WAVE type. + copy(header[8:12], []byte("WAVE")) + + // Write fmt chunk marker. + copy(header[12:16], []byte("fmt ")) + + // Write the subchunk1 Size. + binary.LittleEndian.PutUint32(buf, 16) + copy(header[16:20], buf) + + // Write the encoded audio format. + if w.Metadata.AudioFormat != PCMFormat { // TODO: allow for more encoding formats. + return 0, errInvalidFormat + } + binary.LittleEndian.PutUint16(buf[0:2], 1) + copy(header[20:22], buf[0:2]) + + // Write the number of channels. + if w.Metadata.Channels == 0 { + return 0, errInvalidChannels + } + binary.LittleEndian.PutUint16(buf[0:2], uint16(w.Metadata.Channels)) + copy(header[22:24], buf[0:2]) + + // Write the sample rate. + if w.Metadata.SampleRate == 0 { + return 0, errInvalidRate + } + binary.LittleEndian.PutUint32(buf[0:4], uint32(w.Metadata.SampleRate)) + copy(header[24:28], buf[0:4]) + + // Write bit depth values. + if w.Metadata.BitDepth == 0 { + return 0, errInvalidBitDepth + } + var val uint32 = uint32((w.Metadata.SampleRate * w.Metadata.BitDepth * w.Metadata.Channels) / 8) + binary.LittleEndian.PutUint32(buf[0:4], val) + copy(header[28:32], buf[0:4]) + + val = uint32((w.Metadata.BitDepth * w.Metadata.Channels) / 8) + binary.LittleEndian.PutUint32(buf[0:4], val) + copy(header[32:34], buf[0:4]) + + binary.LittleEndian.PutUint32(buf[0:4], uint32(w.Metadata.BitDepth)) + copy(header[34:36], buf[0:4]) + + // Mark start of data. + copy(header[36:40], []byte("data")) + + // Write size of data chunk. + binary.LittleEndian.PutUint32(buf[0:4], uint32(len(p))) + copy(header[40:44], buf[0:4]) + + // Append audio data. + w.Audio = header + w.Audio = append(w.Audio, p...) + + // Return successful write. + return len(p) + 44, nil + +}