-
Notifications
You must be signed in to change notification settings - Fork 1
/
audiofile.go
198 lines (178 loc) · 6.2 KB
/
audiofile.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
package mactts
/*
#cgo CFLAGS: -I/System/Library/Frameworks/AudioToolbox.framework/Headers
#cgo LDFLAGS: -framework AudioToolbox
#include <AudioFile.h>
#include <ExtendedAudioFile.h>
extern OSStatus go_audiofile_readproc(void *data, SInt64 inPosition, UInt32 requestCount, void *buffer, UInt32 *actualCount);
extern OSStatus go_audiofile_writeproc(void *data, SInt64 inPosition, UInt32 requestCount, void *buffer, UInt32 *actualCount);
extern SInt64 go_audiofile_getsizeproc(void *data);
*/
import "C"
import "fmt"
import "unsafe"
import "io"
import "reflect"
import "runtime"
//export go_audiofile_getsizeproc
func go_audiofile_getsizeproc(data unsafe.Pointer) C.SInt64 {
af := (*AudioFile)(data)
return C.SInt64(af.fileSize)
}
//export go_audiofile_readproc
func go_audiofile_readproc(data unsafe.Pointer, inPosition C.SInt64, requestCount C.UInt32, buffer unsafe.Pointer, actualCount *C.UInt32) C.OSStatus {
af := (*AudioFile)(data)
length := int(requestCount)
hdr := reflect.SliceHeader{
Data: uintptr(buffer),
Len: length,
Cap: length,
}
bslice := *(*[]byte)(unsafe.Pointer(&hdr))
n, err := af.target.ReadAt(bslice, int64(inPosition))
*actualCount = C.UInt32(n)
if err == io.EOF {
return C.kAudioFileEndOfFileError
} else if err != nil {
return C.kAudioFileUnspecifiedError
}
return C.OSStatus(0)
}
//export go_audiofile_writeproc
func go_audiofile_writeproc(data unsafe.Pointer, inPosition C.SInt64, requestCount C.UInt32, buffer unsafe.Pointer, actualCount *C.UInt32) C.OSStatus {
af := (*AudioFile)(data)
length := int(requestCount)
hdr := reflect.SliceHeader{
Data: uintptr(buffer),
Len: length,
Cap: length,
}
bslice := *(*[]byte)(unsafe.Pointer(&hdr))
npos := int64(inPosition)
n, err := af.target.WriteAt(bslice, npos)
*actualCount = C.UInt32(n)
if err != nil {
return C.kAudioFileUnspecifiedError
}
if npos+int64(n) > af.fileSize {
af.fileSize = npos + int64(n)
}
return C.OSStatus(0)
}
func osStatToString(t C.OSStatus) string {
return string([]byte{byte((t >> 24) & 0xFF), byte((t >> 16) & 0xFF), byte((t >> 8) & 0xFF), byte(t & 0xFF)})
}
func osStatus(stat C.OSStatus) error {
if stat == 0 {
return nil
}
return fmt.Errorf("OSStatus: %v", osStatToString(stat))
}
// ReadWriterAt is a composed interface of a standard io.ReaderAt and io.WriterAt.
type ReadWriterAt interface {
io.ReaderAt
io.WriterAt
}
// AudioFile wraps a CoreAudio AudioFile handle and allows handling file operations within Go.
type AudioFile struct {
id C.AudioFileID
target ReadWriterAt
fileSize int64
}
func newOutputFile(target ReadWriterAt, asbd *C.AudioStreamBasicDescription, fileType C.AudioFileTypeID) (*AudioFile, error) {
af := AudioFile{
target: target,
}
stat := C.AudioFileInitializeWithCallbacks(unsafe.Pointer(&af), (*[0]byte)(C.go_audiofile_readproc), (*[0]byte)(C.go_audiofile_writeproc),
(*[0]byte)(C.go_audiofile_getsizeproc), nil, fileType, asbd, 0, &af.id)
if stat != 0 {
return nil, osStatus(stat)
}
runtime.SetFinalizer(&af, func(af *AudioFile) {
if af.id != nil {
C.AudioFileClose(af.id)
}
})
return &af, nil
}
// NewOutputWAVEFile opens a CoreAudio WAVE file suitable for output to target.
//
// rate is the sample rate, numchan is the number of channels in the output, and numbits is the number of bits per channel.
// NOTE: In order to prevent unnecessary copying, the calls to target use buffers that are owned by CoreAudio. This means that
// slices should not be made of buffers that will outlive the call to the WriteAt method.
func NewOutputWAVEFile(target ReadWriterAt, rate float64, numchan int, numbits int) (*AudioFile, error) {
bpf := C.UInt32(numbits * numchan / 8)
asbd := C.AudioStreamBasicDescription{
mSampleRate: C.Float64(rate),
mFormatID: C.kAudioFormatLinearPCM,
mFormatFlags: C.kAudioFormatFlagIsSignedInteger | C.kAudioFormatFlagIsPacked,
mBytesPerPacket: bpf,
mFramesPerPacket: 1,
mBytesPerFrame: bpf,
mChannelsPerFrame: C.UInt32(numchan),
mBitsPerChannel: C.UInt32(numbits),
}
return newOutputFile(target, &asbd, C.kAudioFileWAVEType)
}
// NewOutputAACFile opens a CoreAudio MP4 encapsulated AAC file suitable for output to target.
//
// rate is the sample rate, numchan is the number of channels in the output, and numbits is the number of bits per channel.
// NOTE: In order to prevent unnecessary copying, the calls to target use buffers that are owned by CoreAudio. This means that
// slices should not be made of buffers that will outlive the call to the WriteAt method.
func NewOutputAACFile(target ReadWriterAt, rate float64, numchan int, numbits int) (*AudioFile, error) {
asbd := C.AudioStreamBasicDescription{
mSampleRate: C.Float64(rate),
mFormatID: C.kAudioFormatMPEG4AAC,
mFormatFlags: C.kMPEG4Object_AAC_Main,
mChannelsPerFrame: C.UInt32(numchan),
mFramesPerPacket: 1024,
}
return newOutputFile(target, &asbd, C.kAudioFileM4AType)
}
// ExtAudioFile returns a ExtAudioFile that wraps the CoreAudio AudioFile
func (af *AudioFile) ExtAudioFile() (*ExtAudioFile, error) {
var eaf = ExtAudioFile{af: af}
stat := C.ExtAudioFileWrapAudioFileID(af.id, 1, &eaf.ceaf)
if stat != 0 {
return nil, osStatus(stat)
}
runtime.SetFinalizer(&eaf, func(eaf *ExtAudioFile) {
if eaf.ceaf != nil {
C.ExtAudioFileDispose(eaf.ceaf)
}
})
return &eaf, nil
}
// Close closes the AudioFile and releases the reference to it.
//
// When used with ExtAudioFile this function must not be called while the ExtAudioFile is still in use.
func (af *AudioFile) Close() error {
if af.id == nil {
return nil
}
stat := C.AudioFileClose(af.id)
af.id = nil
runtime.SetFinalizer(af, nil)
return osStatus(stat)
}
// ExtAudioFile wraps a CoreAudio ExtAudioFile handle.
type ExtAudioFile struct {
ceaf C.ExtAudioFileRef
af *AudioFile
}
// Tell returns the file offset for the internal ExtAudioFile in sample frames.
func (eaf *ExtAudioFile) Tell() (ofs int64, err error) {
err = osStatus(C.ExtAudioFileTell(eaf.ceaf, (*C.SInt64)(&ofs)))
return
}
// Close closes the ExtAudioFile and releases the reference to it.
func (eaf *ExtAudioFile) Close() error {
if eaf.ceaf == nil {
return nil
}
stat := C.ExtAudioFileDispose(eaf.ceaf)
eaf.ceaf = nil
eaf.af = nil
runtime.SetFinalizer(eaf, nil)
return osStatus(stat)
}