Initial commit
This commit is contained in:
commit
32bd283d4c
27
LICENSE
Normal file
27
LICENSE
Normal 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
14
README.md
Normal 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
68
cfuncs.go
Normal 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
BIN
data/sine24-00.flac
Normal file
Binary file not shown.
222
libflac.go
Normal file
222
libflac.go
Normal 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
136
libflac_test.go
Normal 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)
|
||||
}
|
Loading…
Reference in a new issue