Initial commit

This commit is contained in:
Will Newton 2015-03-05 11:09:17 +00:00
commit 32bd283d4c
6 changed files with 467 additions and 0 deletions

27
LICENSE Normal file
View file

@ -0,0 +1,27 @@
Copyright (c) 2015 Cocoon Alarm Ltd. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

14
README.md Normal file
View file

@ -0,0 +1,14 @@
# Go libFLAC bindings
These bindings allow decoding and encoding of [FLAC](https://xiph.org/flac/)
format audio data from [Go](http://golang.org/) using the
[libFLAC](https://xiph.org/flac/api/) library.
### Installation
go get github.com/cocoonlife/goflac
### Status
The code has support for decoding and encoding with various parameters
however it is only quite lightly tested so it is likely that bugs remain.

68
cfuncs.go Normal file
View file

@ -0,0 +1,68 @@
// Copyright 2015 Cocoon Alarm Ltd.
//
// See LICENSE file for terms and conditions.
package libflac
/*
#include "FLAC/stream_decoder.h"
#include "FLAC/stream_encoder.h"
void
decoderErrorCallback_cgo(const FLAC__StreamDecoder *decoder,
FLAC__StreamDecoderErrorStatus status,
void *data)
{
decoderErrorCallback(decoder, status, data);
}
void
decoderMetadataCallback_cgo(const FLAC__StreamDecoder *decoder,
const FLAC__StreamMetadata *metadata,
void *data)
{
decoderMetadataCallback(decoder, metadata, data);
}
FLAC__StreamDecoderWriteStatus
decoderWriteCallback_cgo(const FLAC__StreamDecoder *decoder,
const FLAC__Frame *frame,
const FLAC__int32 **buffer,
void *data)
{
return decoderWriteCallback(decoder, frame, buffer, data);
}
extern const char *
get_decoder_error_str(FLAC__StreamDecoderErrorStatus status)
{
return FLAC__StreamDecoderErrorStatusString[status];
}
extern int
get_decoder_channels(FLAC__StreamMetadata *metadata)
{
return metadata->data.stream_info.channels;
}
extern int
get_decoder_depth(FLAC__StreamMetadata *metadata)
{
return metadata->data.stream_info.bits_per_sample;
}
extern int
get_decoder_rate(FLAC__StreamMetadata *metadata)
{
return metadata->data.stream_info.sample_rate;
}
extern int
get_audio_sample(const FLAC__int32 **buffer, int off, int ch)
{
return buffer[ch][off];
}
*/
import "C"

BIN
data/sine24-00.flac Normal file

Binary file not shown.

222
libflac.go Normal file
View file

@ -0,0 +1,222 @@
// Copyright 2015 Cocoon Alarm Ltd.
//
// See LICENSE file for terms and conditions.
// Package libflac provides Go bindings to the libFLAC codec library.
package libflac
import (
"errors"
"fmt"
"io"
"runtime"
"unsafe"
)
/*
#cgo LDFLAGS: -lFLAC
#include <stdlib.h>
#include "FLAC/stream_decoder.h"
#include "FLAC/stream_encoder.h"
extern void
decoderErrorCallback_cgo(const FLAC__StreamDecoder *,
FLAC__StreamDecoderErrorStatus,
void *);
typedef void (*decoderErrorCallbackFn)(const FLAC__StreamDecoder *,
FLAC__StreamDecoderErrorStatus,
void *);
extern void
decoderMetadataCallback_cgo(const FLAC__StreamDecoder *,
const FLAC__StreamMetadata *,
void *);
typedef void (*decoderMetadataCallbackFn)(const FLAC__StreamDecoder *,
FLAC__StreamMetadata *,
void *);
extern FLAC__StreamDecoderWriteStatus
decoderWriteCallback_cgo(const FLAC__StreamDecoder *,
const FLAC__Frame *,
const FLAC__int32 **,
void *);
typedef FLAC__StreamDecoderWriteStatus (*decoderWriteCallbackFn)(const FLAC__StreamDecoder *,
const FLAC__Frame *,
const FLAC__int32 **,
void *);
extern const char *
get_decoder_error_str(FLAC__StreamDecoderErrorStatus status);
extern int
get_decoder_channels(FLAC__StreamMetadata *metadata);
extern int
get_decoder_depth(FLAC__StreamMetadata *metadata);
extern int
get_decoder_rate(FLAC__StreamMetadata *metadata);
extern int
get_audio_sample(const FLAC__int32 **buffer, int off, int ch);
*/
import "C"
// Frame is an interleaved buffer of audio data with the specified parameters.
type Frame struct {
Channels int
Depth int
Rate int
Buffer []int32
}
// Decoder is a FLAC decoder.
type Decoder struct {
d *C.FLAC__StreamDecoder
Channels int
Depth int
Rate int
error bool
errorStr string
frame *Frame
}
// Encoder is a FLAC encoder.
type Encoder struct {
e *C.FLAC__StreamEncoder
Channels int
Depth int
Rate int
}
//export decoderErrorCallback
func decoderErrorCallback(d *C.FLAC__StreamDecoder, status C.FLAC__StreamDecoderErrorStatus, data unsafe.Pointer) {
decoder := (*Decoder)(data)
decoder.error = true
decoder.errorStr = C.GoString(C.get_decoder_error_str(status))
}
//export decoderMetadataCallback
func decoderMetadataCallback(d *C.FLAC__StreamDecoder, metadata *C.FLAC__StreamMetadata, data unsafe.Pointer) {
decoder := (*Decoder)(data)
if metadata._type == C.FLAC__METADATA_TYPE_STREAMINFO {
decoder.Channels = int(C.get_decoder_channels(metadata))
decoder.Depth = int(C.get_decoder_depth(metadata))
decoder.Rate = int(C.get_decoder_rate(metadata))
}
}
//export decoderWriteCallback
func decoderWriteCallback(d *C.FLAC__StreamDecoder, frame *C.FLAC__Frame, buffer **C.FLAC__int32, data unsafe.Pointer) (C.FLAC__StreamDecoderWriteStatus) {
decoder := (*Decoder)(data)
blocksize := int(frame.header.blocksize)
decoder.frame = new(Frame)
f := decoder.frame
f.Channels = decoder.Channels
f.Depth = decoder.Depth
f.Rate = decoder.Rate
f.Buffer = make([]int32, blocksize)
for i := 0; i < blocksize; i++ {
for ch := 0; ch < decoder.Channels; ch++ {
f.Buffer[i + ch] = int32(C.get_audio_sample(buffer, C.int(i), C.int(ch)))
}
}
return C.FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE
}
// NewDecoder creates a new Decoder object.
func NewDecoder(name string) (d *Decoder, err error) {
d = new(Decoder)
d.d = C.FLAC__stream_decoder_new()
if d.d == nil {
return nil, errors.New("failed to create decoder")
}
c := C.CString(name)
defer C.free(unsafe.Pointer(c))
runtime.SetFinalizer(d, (*Decoder).Close)
status := C.FLAC__stream_decoder_init_file(d.d, c,
(C.decoderWriteCallbackFn)(unsafe.Pointer(C.decoderWriteCallback_cgo)),
(C.decoderMetadataCallbackFn)(unsafe.Pointer(C.decoderMetadataCallback_cgo)),
(C.decoderErrorCallbackFn)(unsafe.Pointer(C.decoderErrorCallback_cgo)),
unsafe.Pointer(d))
if status != C.FLAC__STREAM_DECODER_INIT_STATUS_OK {
return nil, errors.New("failed to open file")
}
ret := C.FLAC__stream_decoder_process_until_end_of_metadata(d.d)
if ret == 0 || d.error == true || d.Channels == 0 {
return nil, fmt.Errorf("failed to process metadata %s", d.errorStr)
}
return
}
// Close closes a decoder and frees the resources.
func (d *Decoder) Close() {
if d.d != nil {
C.FLAC__stream_decoder_delete(d.d)
d.d = nil
}
runtime.SetFinalizer(d, nil)
}
// ReadFrame reads a frame of audio data from the decoder.
func (d *Decoder) ReadFrame() (f *Frame, err error) {
ret := C.FLAC__stream_decoder_process_single(d.d)
if ret == 0 || d.error == true {
return nil, errors.New("error reading frame")
}
state := C.FLAC__stream_decoder_get_state(d.d)
if state == C.FLAC__STREAM_DECODER_END_OF_STREAM {
err = io.EOF
}
f = d.frame
d.frame = nil
return
}
// NewEncoder creates a new Encoder object.
func NewEncoder(name string, channels int, depth int, rate int) (e *Encoder, err error) {
e = new(Encoder)
e.e = C.FLAC__stream_encoder_new()
if e.e == nil {
return nil, errors.New("failed to create decoder")
}
c := C.CString(name)
defer C.free(unsafe.Pointer(c))
runtime.SetFinalizer(e, (*Encoder).Close)
C.FLAC__stream_encoder_set_channels(e.e, C.uint(channels))
C.FLAC__stream_encoder_set_bits_per_sample(e.e, C.uint(depth))
C.FLAC__stream_encoder_set_sample_rate(e.e, C.uint(rate))
status := C.FLAC__stream_encoder_init_file(e.e, c, nil, nil)
if status != C.FLAC__STREAM_ENCODER_INIT_STATUS_OK {
return nil, errors.New("failed to open file")
}
e.Channels = channels
e.Depth = depth
e.Rate = rate
return
}
// WriteFrame writes a frame of audio data to the encoder.
func (e *Encoder) WriteFrame(f Frame) (err error) {
if f.Channels != e.Channels || f.Depth != e.Depth || f.Rate != e.Rate {
return errors.New("frame type does not match encoder")
}
ret := C.FLAC__stream_encoder_process_interleaved(e.e, (*C.FLAC__int32)(&f.Buffer[0]), C.uint(len(f.Buffer) / e.Channels))
if ret == 0 {
return errors.New("error encoding frame")
}
return
}
// Close closes an encoder and frees the resources.
func (e *Encoder) Close() {
if e.e != nil {
C.FLAC__stream_encoder_finish(e.e)
e.e = nil
}
runtime.SetFinalizer(e, nil)
}

136
libflac_test.go Normal file
View file

@ -0,0 +1,136 @@
// Copyright 2015 Cocoon Alarm Ltd.
//
// See LICENSE file for terms and conditions.
package libflac
import (
"crypto/md5"
"io"
"io/ioutil"
"os"
"testing"
"github.com/jbert/testify/assert"
)
func TestDecode(t *testing.T) {
a := assert.New(t)
d, err := NewDecoder("data/nonexistent.flac")
a.Equal(d, (*Decoder)(nil), "decoder is nil")
d, err = NewDecoder("data/sine24-00.flac")
a.Equal(err, nil, "err is nil")
a.Equal(d.Channels, 1, "channels is 1")
a.Equal(d.Depth, 24, "depth is 24")
a.Equal(d.Rate, 48000, "depth is 48000")
samples := 0
f, err := d.ReadFrame()
a.Equal(err, nil, "err is nil")
a.Equal(f.Channels, 1, "channels is 1")
a.Equal(f.Depth, 24, "depth is 24")
a.Equal(f.Rate, 48000, "depth is 48000")
samples = samples + len(f.Buffer)
for {
f, err := d.ReadFrame()
if (err == nil || err == io.EOF) {
if (f != nil) {
samples = samples + len(f.Buffer)
}
} else {
a.Equal(err, nil, "error reported")
break
}
if (err == io.EOF) {
break
}
}
a.Equal(samples, 200000, "all samples read")
d.Close()
}
func TestEncode(t *testing.T) {
a := assert.New(t)
fileName := "data/test.flac"
e, err := NewEncoder(fileName, 2, 24, 48000)
a.Equal(err, nil, "err is nil")
f := Frame{Channels: 1, Depth: 24, Rate: 48000}
err = e.WriteFrame(f)
a.Error(err, "channels mismatch")
f.Channels = 2
f.Buffer = make([]int32, 2 * 100)
err = e.WriteFrame(f)
a.Equal(err, nil, "frame encoded")
e.Close()
os.Remove(fileName)
}
func TestRoundTrip(t *testing.T) {
a := assert.New(t)
inputFile := "data/sine24-00.flac"
outputFile := "data/test.flac"
d, err := NewDecoder(inputFile)
a.Equal(err, nil, "err is nil")
e, err := NewEncoder(outputFile, d.Channels, d.Depth, d.Rate)
samples := 0
for {
f, err := d.ReadFrame()
if (err == nil || err == io.EOF) {
if (f != nil) {
_ = e.WriteFrame(*f)
samples = samples + len(f.Buffer)
}
} else {
a.Equal(err, nil, "error reported")
break
}
if (err == io.EOF) {
break
}
}
a.Equal(samples, 200000, "all samples read")
d.Close()
e.Close()
inFile, err := os.Open(inputFile)
a.Equal(err, nil, "err is nil")
outFile, err := os.Open(outputFile)
a.Equal(err, nil, "err is nil")
inData, _ := ioutil.ReadAll(inFile)
outData, _ := ioutil.ReadAll(outFile)
a.Equal(md5.Sum(inData), md5.Sum(outData), "files md5sum the same")
os.Remove(outputFile)
}