├── .gitignore ├── LICENSE ├── README.md ├── lame.go └── writer.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Pavel Vorobyov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lame 2 | ==== 3 | 4 | Simple libmp3lame powered mp3 encoder for Go 5 | 6 | **Note:** this project is obsolete, consider moving to https://github.com/viert/go-lame 7 | 8 | Example: 9 | 10 | ```Go 11 | package main 12 | 13 | import ( 14 | "bufio" 15 | "lame" 16 | "os" 17 | ) 18 | 19 | func main() { 20 | f, err := os.Open("input.raw") 21 | if err != nil { 22 | panic(err) 23 | } 24 | defer f.Close() 25 | reader := bufio.NewReader(f) 26 | 27 | of, err := os.Create("output.mp3") 28 | if err != nil { 29 | panic(err) 30 | } 31 | defer of.Close() 32 | 33 | wr := lame.NewWriter(of) 34 | wr.Encoder.SetBitrate(112) 35 | wr.Encoder.SetQuality(1) 36 | 37 | // IMPORTANT! 38 | wr.Encoder.InitParams() 39 | 40 | reader.WriteTo(wr) 41 | 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /lame.go: -------------------------------------------------------------------------------- 1 | package lame 2 | 3 | // http://www.leidinger.net/lame/doxy/html/lame_8h-source.html 4 | 5 | /* 6 | #cgo LDFLAGS: -lmp3lame 7 | #include "lame/lame.h" 8 | */ 9 | import "C" 10 | 11 | import ( 12 | "runtime" 13 | "unsafe" 14 | ) 15 | 16 | type Handle *C.struct_lame_global_struct 17 | 18 | const ( 19 | STEREO = C.STEREO 20 | JOINT_STEREO = C.JOINT_STEREO 21 | DUAL_CHANNEL = C.DUAL_CHANNEL /* LAME doesn't supports this! */ 22 | MONO = C.MONO 23 | NOT_SET = C.NOT_SET 24 | MAX_INDICATOR = C.MAX_INDICATOR 25 | BIT_DEPTH = 16 26 | 27 | VBR_OFF = C.vbr_off 28 | VBR_RH = C.vbr_rh 29 | VBR_ABR = C.vbr_abr 30 | VBR_MTRH = C.vbr_mtrh 31 | VBR_DEFAULT = C.vbr_default 32 | 33 | MAX_FRAME_SIZE = 2880 34 | ) 35 | 36 | const ( 37 | ABR_8 = C.ABR_8 38 | ABR_320 = C.ABR_320 39 | V9 = C.V9 40 | VBR_10 = C.VBR_10 41 | V8 = C.V8 42 | VBR_20 = C.VBR_20 43 | V7 = C.V7 44 | VBR_30 = C.VBR_30 45 | V6 = C.V6 46 | VBR_40 = C.VBR_40 47 | V5 = C.V5 48 | VBR_50 = C.VBR_50 49 | V4 = C.V4 50 | VBR_60 = C.VBR_60 51 | V3 = C.V3 52 | VBR_70 = C.VBR_70 53 | V2 = C.V2 54 | VBR_80 = C.VBR_80 55 | V1 = C.V1 56 | VBR_90 = C.VBR_90 57 | V0 = C.V0 58 | VBR_100 = C.VBR_100 59 | ) 60 | 61 | type Encoder struct { 62 | handle Handle 63 | remainder []byte 64 | closed bool 65 | } 66 | 67 | func Init() *Encoder { 68 | handle := C.lame_init() 69 | encoder := &Encoder{handle, make([]byte, 0), false} 70 | runtime.SetFinalizer(encoder, finalize) 71 | return encoder 72 | } 73 | 74 | func (e *Encoder) SetVBR(mode C.vbr_mode) { 75 | C.lame_set_VBR(e.handle, mode) 76 | } 77 | 78 | func (e *Encoder) SetVBRAverageBitRate(averageBitRate int) { 79 | C.lame_set_VBR_mean_bitrate_kbps(e.handle, C.int(averageBitRate)) 80 | } 81 | 82 | func (e *Encoder) SetVBRQuality(quality int) { 83 | C.lame_set_VBR_q(e.handle, C.int(quality)) 84 | } 85 | 86 | func (e *Encoder) SetLowPassFrequency(frequency int) { 87 | // Frequency in Hz 88 | C.lame_set_lowpassfreq(e.handle, C.int(frequency)) 89 | } 90 | 91 | func (e *Encoder) SetNumChannels(num int) { 92 | C.lame_set_num_channels(e.handle, C.int(num)) 93 | } 94 | 95 | func (e *Encoder) SetInSamplerate(sampleRate int) { 96 | C.lame_set_in_samplerate(e.handle, C.int(sampleRate)) 97 | } 98 | 99 | func (e *Encoder) SetBitrate(bitRate int) { 100 | C.lame_set_brate(e.handle, C.int(bitRate)) 101 | } 102 | 103 | func (e *Encoder) SetMode(mode C.MPEG_mode) { 104 | C.lame_set_mode(e.handle, mode) 105 | } 106 | 107 | func (e *Encoder) SetQuality(quality int) { 108 | C.lame_set_quality(e.handle, C.int(quality)) 109 | } 110 | 111 | func (e *Encoder) InitId3Tag() { 112 | C.id3tag_init(e.handle) 113 | } 114 | 115 | func (e *Encoder) SetWriteId3tagAutomatic(automaticWriteTag int) { 116 | C.lame_set_write_id3tag_automatic(e.handle, C.int(automaticWriteTag)) 117 | } 118 | 119 | func (e *Encoder) ID3TagAddV2() { 120 | C.id3tag_add_v2(e.handle) 121 | } 122 | 123 | func (e *Encoder) SetbWriteVbrTag(writeVbrTag int) { 124 | C.lame_set_bWriteVbrTag(e.handle, C.int(writeVbrTag)) 125 | } 126 | 127 | func (e *Encoder) GetLametagFrame() []byte { 128 | tagFrame := make([]byte, MAX_FRAME_SIZE) 129 | tagFrameLen := C.lame_get_lametag_frame(e.handle, (*C.uchar)(unsafe.Pointer(&tagFrame[0])), C.size_t(len(tagFrame))) 130 | 131 | return tagFrame[0:tagFrameLen] 132 | } 133 | 134 | func (e *Encoder) InitParams() int { 135 | retcode := C.lame_init_params(e.handle) 136 | return int(retcode) 137 | } 138 | 139 | func (e *Encoder) NumChannels() int { 140 | n := C.lame_get_num_channels(e.handle) 141 | return int(n) 142 | } 143 | 144 | func (e *Encoder) Bitrate() int { 145 | br := C.lame_get_brate(e.handle) 146 | return int(br) 147 | } 148 | 149 | func (e *Encoder) Mode() int { 150 | m := C.lame_get_mode(e.handle) 151 | return int(m) 152 | } 153 | 154 | func (e *Encoder) Quality() int { 155 | q := C.lame_get_quality(e.handle) 156 | return int(q) 157 | } 158 | 159 | func (e *Encoder) InSamplerate() int { 160 | sr := C.lame_get_in_samplerate(e.handle) 161 | return int(sr) 162 | } 163 | 164 | func (e *Encoder) Encode(buf []byte) []byte { 165 | 166 | if len(e.remainder) > 0 { 167 | buf = append(e.remainder, buf...) 168 | } 169 | 170 | if len(buf) == 0 { 171 | return make([]byte, 0) 172 | } 173 | 174 | blockAlign := BIT_DEPTH / 8 * e.NumChannels() 175 | 176 | remainBytes := len(buf) % blockAlign 177 | if remainBytes > 0 { 178 | e.remainder = buf[len(buf)-remainBytes : len(buf)] 179 | buf = buf[0 : len(buf)-remainBytes] 180 | } else { 181 | e.remainder = make([]byte, 0) 182 | } 183 | 184 | numSamples := len(buf) / blockAlign 185 | estimatedSize := int(1.25*float64(numSamples) + 7200) 186 | out := make([]byte, estimatedSize) 187 | 188 | cBuf := (*C.short)(unsafe.Pointer(&buf[0])) 189 | cOut := (*C.uchar)(unsafe.Pointer(&out[0])) 190 | 191 | var bytesOut C.int 192 | 193 | if e.NumChannels() == 1 { 194 | bytesOut = C.int(C.lame_encode_buffer( 195 | e.handle, 196 | cBuf, 197 | nil, 198 | C.int(numSamples), 199 | cOut, 200 | C.int(estimatedSize), 201 | )) 202 | } else { 203 | bytesOut = C.int(C.lame_encode_buffer_interleaved( 204 | e.handle, 205 | cBuf, 206 | C.int(numSamples), 207 | cOut, 208 | C.int(estimatedSize), 209 | )) 210 | } 211 | return out[0:bytesOut] 212 | 213 | } 214 | 215 | func (e *Encoder) Flush() []byte { 216 | estimatedSize := 7200 217 | out := make([]byte, estimatedSize) 218 | cOut := (*C.uchar)(unsafe.Pointer(&out[0])) 219 | bytesOut := C.int(C.lame_encode_flush( 220 | e.handle, 221 | cOut, 222 | C.int(estimatedSize), 223 | )) 224 | 225 | return out[0:bytesOut] 226 | } 227 | 228 | func (e *Encoder) Close() { 229 | if e.closed { 230 | return 231 | } 232 | C.lame_close(e.handle) 233 | e.closed = true 234 | } 235 | 236 | func finalize(e *Encoder) { 237 | e.Close() 238 | } 239 | -------------------------------------------------------------------------------- /writer.go: -------------------------------------------------------------------------------- 1 | package lame 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type LameWriter struct { 8 | output io.Writer 9 | Encoder *Encoder 10 | EncodedChunkSize int 11 | } 12 | 13 | func NewWriter(out io.Writer) *LameWriter { 14 | writer := &LameWriter{out, Init(), 0} 15 | return writer 16 | } 17 | 18 | func (lw *LameWriter) Write(p []byte) (int, error) { 19 | out := lw.Encoder.Encode(p) 20 | lw.EncodedChunkSize = len(out) 21 | 22 | if lw.EncodedChunkSize > 0 { 23 | _, err := lw.output.Write(out) 24 | if err != nil { 25 | return 0, err 26 | } 27 | } 28 | 29 | return len(p), nil 30 | } 31 | 32 | func (lw *LameWriter) Close() error { 33 | out := lw.Encoder.Flush() 34 | if len(out) == 0 { 35 | return nil 36 | } 37 | _, err := lw.output.Write(out) 38 | return err 39 | } 40 | --------------------------------------------------------------------------------