├── .gitignore ├── LICENSE ├── README.md ├── doc.go ├── framechannelmode_string.go ├── frameemphasis_string.go ├── framelayer_string.go ├── frames.go ├── frames_test.go ├── frameversion_string.go ├── header.go ├── internal ├── bindata.go └── data │ ├── bindata.go │ ├── silent_1frame.go │ └── silent_1frame.mp3 └── silence.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.mp3 2 | !internal/data/*.mp3 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Tristan Colgate-McFarlane and badgerodon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MP3 2 | 3 | Stream orientated mp3 frame decoder 4 | 5 | [![GoDoc](https://godoc.org/github.com/tcolgate/mp3?status.svg)](https://godoc.org/github.com/tcolgate/mp3) 6 | 7 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // The MIT License (MIT) 2 | // 3 | // Copyright (c) 2015 Tristan Colgate-McFarlane and badgerodon 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | // this software and associated documentation files (the "Software"), to deal in 7 | // the Software without restriction, including without limitation the rights to 8 | // use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | // the Software, and to permit persons to whom the Software is furnished to do so, 10 | // subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in all 13 | // copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | // FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | // Package mp3 provides decoding of mp3 files into their underlying frames. It 23 | // is primarily intended for streaming tasks with minimal internal buffering 24 | // and no requirement to seek. 25 | // 26 | // The implementation started as a reworking of github.com/badgerodon/mp3, and 27 | // has also drawn from Konrad Windszus' excellent article on mp3 frame parsing 28 | // http://www.codeproject.com/Articles/8295/MPEG-Audio-Frame-Header 29 | // 30 | // TODO CRC isn't currently checked. 31 | package mp3 32 | -------------------------------------------------------------------------------- /framechannelmode_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=FrameChannelMode"; DO NOT EDIT 2 | 3 | package mp3 4 | 5 | import "fmt" 6 | 7 | const _FrameChannelMode_name = "StereoJointStereoDualChannelSingleChannelChannelModeMax" 8 | 9 | var _FrameChannelMode_index = [...]uint8{0, 6, 17, 28, 41, 55} 10 | 11 | func (i FrameChannelMode) String() string { 12 | if i >= FrameChannelMode(len(_FrameChannelMode_index)-1) { 13 | return fmt.Sprintf("FrameChannelMode(%d)", i) 14 | } 15 | return _FrameChannelMode_name[_FrameChannelMode_index[i]:_FrameChannelMode_index[i+1]] 16 | } 17 | -------------------------------------------------------------------------------- /frameemphasis_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=FrameEmphasis"; DO NOT EDIT 2 | 3 | package mp3 4 | 5 | import "fmt" 6 | 7 | const _FrameEmphasis_name = "EmphNoneEmph5015EmphReservedEmphCCITJ17EmphMax" 8 | 9 | var _FrameEmphasis_index = [...]uint8{0, 8, 16, 28, 39, 46} 10 | 11 | func (i FrameEmphasis) String() string { 12 | if i >= FrameEmphasis(len(_FrameEmphasis_index)-1) { 13 | return fmt.Sprintf("FrameEmphasis(%d)", i) 14 | } 15 | return _FrameEmphasis_name[_FrameEmphasis_index[i]:_FrameEmphasis_index[i+1]] 16 | } 17 | -------------------------------------------------------------------------------- /framelayer_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=FrameLayer"; DO NOT EDIT 2 | 3 | package mp3 4 | 5 | import "fmt" 6 | 7 | const _FrameLayer_name = "LayerReservedLayer3Layer2Layer1LayerMax" 8 | 9 | var _FrameLayer_index = [...]uint8{0, 13, 19, 25, 31, 39} 10 | 11 | func (i FrameLayer) String() string { 12 | if i >= FrameLayer(len(_FrameLayer_index)-1) { 13 | return fmt.Sprintf("FrameLayer(%d)", i) 14 | } 15 | return _FrameLayer_name[_FrameLayer_index[i]:_FrameLayer_index[i+1]] 16 | } 17 | -------------------------------------------------------------------------------- /frames.go: -------------------------------------------------------------------------------- 1 | package mp3 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "time" 10 | ) 11 | 12 | type ( 13 | // Decoder translates a io.Reader into a series of frames 14 | Decoder struct { 15 | src io.Reader 16 | err error 17 | } 18 | 19 | // Frame represents one individual mp3 frame 20 | Frame struct { 21 | buf []byte 22 | } 23 | 24 | // FrameHeader represents the entire header of a frame 25 | FrameHeader []byte 26 | 27 | // FrameVersion is the MPEG version given in the frame header 28 | FrameVersion byte 29 | // FrameLayer is the MPEG layer given in the frame header 30 | FrameLayer byte 31 | // FrameEmphasis is the Emphasis value from the frame header 32 | FrameEmphasis byte 33 | // FrameChannelMode is the Channel mode from the frame header 34 | FrameChannelMode byte 35 | // FrameBitRate is the bit rate from the frame header 36 | FrameBitRate int 37 | // FrameSampleRate is the sample rate from teh frame header 38 | FrameSampleRate int 39 | 40 | // FrameSideInfo holds the SideInfo bytes from the frame 41 | FrameSideInfo []byte 42 | ) 43 | 44 | //go:generate stringer -type=FrameVersion 45 | const ( 46 | MPEG25 FrameVersion = iota 47 | MPEGReserved 48 | MPEG2 49 | MPEG1 50 | VERSIONMAX 51 | ) 52 | 53 | //go:generate stringer -type=FrameLayer 54 | const ( 55 | LayerReserved FrameLayer = iota 56 | Layer3 57 | Layer2 58 | Layer1 59 | LayerMax 60 | ) 61 | 62 | //go:generate stringer -type=FrameEmphasis 63 | const ( 64 | EmphNone FrameEmphasis = iota 65 | Emph5015 66 | EmphReserved 67 | EmphCCITJ17 68 | EmphMax 69 | ) 70 | 71 | //go:generate stringer -type=FrameChannelMode 72 | const ( 73 | Stereo FrameChannelMode = iota 74 | JointStereo 75 | DualChannel 76 | SingleChannel 77 | ChannelModeMax 78 | ) 79 | 80 | const ( 81 | // ErrInvalidBitrate indicates that the header information did not contain a recognized bitrate 82 | ErrInvalidBitrate FrameBitRate = -1 83 | ) 84 | 85 | var ( 86 | bitrates = [VERSIONMAX][LayerMax][15]int{ 87 | { // MPEG 2.5 88 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // LayerReserved 89 | {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, // Layer3 90 | {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, // Layer2 91 | {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}, // Layer1 92 | }, 93 | { // Reserved 94 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // LayerReserved 95 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Layer3 96 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Layer2 97 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // Layer1 98 | }, 99 | { // MPEG 2 100 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // LayerReserved 101 | {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, // Layer3 102 | {0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160}, // Layer2 103 | {0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256}, // Layer1 104 | }, 105 | { // MPEG 1 106 | {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // LayerReserved 107 | {0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320}, // Layer3 108 | {0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384}, // Layer2 109 | {0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448}, // Layer1 110 | }, 111 | } 112 | sampleRates = [int(VERSIONMAX)][3]int{ 113 | {11025, 12000, 8000}, //MPEG25 114 | {0, 0, 0}, //MPEGReserved 115 | {22050, 24000, 16000}, //MPEG2 116 | {44100, 48000, 32000}, //MPEG1 117 | } 118 | 119 | // ErrInvalidSampleRate indicates that no samplerate could be found for the frame header provided 120 | ErrInvalidSampleRate = FrameSampleRate(-1) 121 | 122 | samplesPerFrame = [VERSIONMAX][LayerMax]int{ 123 | { // MPEG25 124 | 0, 125 | 576, 126 | 1152, 127 | 384, 128 | }, 129 | { // Reserved 130 | 0, 131 | 0, 132 | 0, 133 | 0, 134 | }, 135 | { // MPEG2 136 | 0, 137 | 576, 138 | 1152, 139 | 384, 140 | }, 141 | { // MPEG1 142 | 0, 143 | 1152, 144 | 1152, 145 | 384, 146 | }, 147 | } 148 | slotSize = [LayerMax]int{ 149 | 0, // LayerReserved 150 | 1, // Layer3 151 | 1, // Layer2 152 | 4, // Layer1 153 | } 154 | 155 | // ErrNoSyncBits implies we could not find a valid frame header sync bit before EOF 156 | ErrNoSyncBits = errors.New("EOF before sync bits found") 157 | 158 | // ErrPrematureEOF indicates that the filed ended before a complete frame could be read 159 | ErrPrematureEOF = errors.New("EOF mid stream") 160 | ) 161 | 162 | func init() { 163 | bitrates[MPEG25] = bitrates[MPEG2] 164 | samplesPerFrame[MPEG25] = samplesPerFrame[MPEG2] 165 | } 166 | 167 | // NewDecoder returns a decoder that will process the provided reader. 168 | func NewDecoder(r io.Reader) *Decoder { 169 | return &Decoder{r, nil} 170 | } 171 | 172 | // fill slice d until it is of len l, using bytes from reader r 173 | func fillbuf(d []byte, r io.Reader, l int) (res []byte, err error) { 174 | if len(d) >= l { 175 | // we already have enough bytes 176 | return d, nil 177 | } 178 | 179 | // How many bytes do we need to fetch 180 | missing := l - len(d) 181 | 182 | // Does d have sufficient capacity? if not extent it 183 | if cap(d) < l { 184 | if d == nil { 185 | d = make([]byte, l) 186 | } else { 187 | il := len(d) 188 | d = d[:cap(d)] // stretch d to it's full capacity 189 | d = append(d, make([]byte, l-cap(d))...) 190 | d = d[:il] //we've extended the capm reset len 191 | } 192 | } 193 | 194 | d = d[:l] 195 | _, err = io.ReadFull(r, d[len(d)-missing:]) 196 | 197 | return d, err 198 | } 199 | 200 | // Decode reads the next complete discovered frame into the provided 201 | // Frame struct. A count of skipped bytes will be written to skipped. 202 | func (d *Decoder) Decode(v *Frame, skipped *int) (err error) { 203 | // Truncate the array 204 | v.buf = v.buf[:0] 205 | 206 | hLen := 4 207 | // locate a sync frame 208 | *skipped = 0 209 | for { 210 | v.buf, err = fillbuf(v.buf, d.src, hLen) 211 | if err != nil { 212 | return err 213 | } 214 | if v.buf[0] == 0xFF && (v.buf[1]&0xE0 == 0xE0) && 215 | v.Header().Emphasis() != EmphReserved && 216 | v.Header().Layer() != LayerReserved && 217 | v.Header().Version() != MPEGReserved && 218 | v.Header().SampleRate() != -1 && 219 | v.Header().BitRate() != -1 { 220 | break 221 | } 222 | switch { 223 | case v.buf[1] == 0xFF: 224 | v.buf = v.buf[1:] 225 | *skipped++ 226 | default: 227 | v.buf = v.buf[2:] 228 | *skipped += 2 229 | } 230 | } 231 | 232 | crcLen := 0 233 | if v.Header().Protection() { 234 | crcLen = 2 235 | v.buf, err = fillbuf(v.buf, d.src, hLen+crcLen) 236 | if err != nil { 237 | return err 238 | } 239 | } 240 | 241 | sideLen, err := v.SideInfoLength() 242 | if err != nil { 243 | return err 244 | } 245 | 246 | v.buf, err = fillbuf(v.buf, d.src, hLen+crcLen+sideLen) 247 | if err != nil { 248 | return err 249 | } 250 | 251 | dataLen := v.Size() 252 | v.buf, err = fillbuf(v.buf, d.src, dataLen) 253 | if err != nil { 254 | return err 255 | } 256 | 257 | return nil 258 | } 259 | 260 | // SideInfoLength retursn the expected side info length for this 261 | // mp3 frame 262 | func (f *Frame) SideInfoLength() (int, error) { 263 | switch f.Header().Version() { 264 | case MPEG1: 265 | switch f.Header().ChannelMode() { 266 | case SingleChannel: 267 | return 17, nil 268 | case Stereo, JointStereo, DualChannel: 269 | return 32, nil 270 | default: 271 | return 0, errors.New("bad channel mode") 272 | } 273 | case MPEG2, MPEG25: 274 | switch f.Header().ChannelMode() { 275 | case SingleChannel: 276 | return 9, nil 277 | case Stereo, JointStereo, DualChannel: 278 | return 17, nil 279 | default: 280 | return 0, errors.New("bad channel mode") 281 | } 282 | default: 283 | return 0, fmt.Errorf("bad version (%v)", f.Header().Version()) 284 | } 285 | } 286 | 287 | // Header returns the header for this frame 288 | func (f *Frame) Header() FrameHeader { 289 | return FrameHeader(f.buf[0:4]) 290 | } 291 | 292 | // CRC returns the CRC word stored in this frame 293 | func (f *Frame) CRC() (uint16, error) { 294 | var crc uint16 295 | if !f.Header().Protection() { 296 | return 0, nil 297 | } 298 | crcdata := bytes.NewReader(f.buf[4:6]) 299 | err := binary.Read(crcdata, binary.BigEndian, &crc) 300 | return crc, err 301 | } 302 | 303 | // SideInfo returns the side info for this frame 304 | func (f *Frame) SideInfo() FrameSideInfo { 305 | if f.Header().Protection() { 306 | return FrameSideInfo(f.buf[6:]) 307 | } 308 | return FrameSideInfo(f.buf[4:]) 309 | } 310 | 311 | // Frame returns a string describing this frame, header and side info 312 | func (f *Frame) String() string { 313 | str := "" 314 | str += fmt.Sprintf("Header: \n%s", f.Header()) 315 | str += fmt.Sprintf("SideInfo: \n%s", f.SideInfo()) 316 | crc, err := f.CRC() 317 | str += fmt.Sprintf("CRC: %x (err: %v)\n", crc, err) 318 | str += fmt.Sprintf("Samples: %v\n", f.Samples()) 319 | str += fmt.Sprintf("Size: %v\n", f.Size()) 320 | str += fmt.Sprintf("Duration: %v\n", f.Duration()) 321 | return str 322 | } 323 | 324 | // Version returns the MPEG version from the header 325 | func (h FrameHeader) Version() FrameVersion { 326 | return FrameVersion((h[1] >> 3) & 0x03) 327 | } 328 | 329 | // Layer returns the MPEG layer from the header 330 | func (h FrameHeader) Layer() FrameLayer { 331 | return FrameLayer((h[1] >> 1) & 0x03) 332 | } 333 | 334 | // Protection indicates if there is a CRC present after the header (before the side data) 335 | func (h FrameHeader) Protection() bool { 336 | return (h[1] & 0x01) != 0x01 337 | } 338 | 339 | // BitRate returns the calculated bit rate from the header 340 | func (h FrameHeader) BitRate() FrameBitRate { 341 | bitrateIdx := (h[2] >> 4) & 0x0F 342 | if bitrateIdx == 0x0F { 343 | return ErrInvalidBitrate 344 | } 345 | br := bitrates[h.Version()][h.Layer()][bitrateIdx] * 1000 346 | if br == 0 { 347 | return ErrInvalidBitrate 348 | } 349 | return FrameBitRate(br) 350 | } 351 | 352 | // SampleRate returns the samplerate from the header 353 | func (h FrameHeader) SampleRate() FrameSampleRate { 354 | sri := (h[2] >> 2) & 0x03 355 | if sri == 0x03 { 356 | return ErrInvalidSampleRate 357 | } 358 | return FrameSampleRate(sampleRates[h.Version()][sri]) 359 | } 360 | 361 | // Pad returns the pad bit, indicating if there are extra samples 362 | // in this frame to make up the correct bitrate 363 | func (h FrameHeader) Pad() bool { 364 | return ((h[2] >> 1) & 0x01) == 0x01 365 | } 366 | 367 | // Private retrusn the Private bit from the header 368 | func (h FrameHeader) Private() bool { 369 | return (h[2] & 0x01) == 0x01 370 | } 371 | 372 | // ChannelMode returns the channel mode from the header 373 | func (h FrameHeader) ChannelMode() FrameChannelMode { 374 | return FrameChannelMode((h[3] >> 6) & 0x03) 375 | } 376 | 377 | // CopyRight returns the CopyRight bit from the header 378 | func (h FrameHeader) CopyRight() bool { 379 | return (h[3]>>3)&0x01 == 0x01 380 | } 381 | 382 | // Original returns the "original content" bit from the header 383 | func (h FrameHeader) Original() bool { 384 | return (h[3]>>2)&0x01 == 0x01 385 | } 386 | 387 | // Emphasis returns the Emphasis from the header 388 | func (h FrameHeader) Emphasis() FrameEmphasis { 389 | return FrameEmphasis((h[3] & 0x03)) 390 | } 391 | 392 | // String dumps the frame header as a string for display purposes 393 | func (h FrameHeader) String() string { 394 | str := "" 395 | str += fmt.Sprintf(" Layer: %v\n", h.Layer()) 396 | str += fmt.Sprintf(" Version: %v\n", h.Version()) 397 | str += fmt.Sprintf(" Protection: %v\n", h.Protection()) 398 | str += fmt.Sprintf(" BitRate: %v\n", h.BitRate()) 399 | str += fmt.Sprintf(" SampleRate: %v\n", h.SampleRate()) 400 | str += fmt.Sprintf(" Pad: %v\n", h.Pad()) 401 | str += fmt.Sprintf(" Private: %v\n", h.Private()) 402 | str += fmt.Sprintf(" ChannelMode: %v\n", h.ChannelMode()) 403 | str += fmt.Sprintf(" CopyRight: %v\n", h.CopyRight()) 404 | str += fmt.Sprintf(" Original: %v\n", h.Original()) 405 | str += fmt.Sprintf(" Emphasis: %v\n", h.Emphasis()) 406 | return str 407 | } 408 | 409 | // NDataBegin is the number of bytes before the frame header at which the sample data begins 410 | // 0 indicates that the data begins after the side channel information. This data is the 411 | // data from the "bit reservoir" and can be up to 511 bytes 412 | func (i FrameSideInfo) NDataBegin() uint16 { 413 | return (uint16(i[0]) << 1 & (uint16(i[1]) >> 7)) 414 | } 415 | 416 | // Samples determines the number of samples based on the MPEG version and Layer from the header 417 | func (f *Frame) Samples() int { 418 | return samplesPerFrame[f.Header().Version()][f.Header().Layer()] 419 | } 420 | 421 | // Size clculates the expected size of this frame in bytes based on the header 422 | // information 423 | func (f *Frame) Size() int { 424 | bps := float64(f.Samples()) / 8 425 | fsize := (bps * float64(f.Header().BitRate())) / float64(f.Header().SampleRate()) 426 | if f.Header().Pad() { 427 | fsize += float64(slotSize[f.Header().Layer()]) 428 | } 429 | return int(fsize) 430 | } 431 | 432 | // Duration calculates the time duration of this frame based on the samplerate and number of samples 433 | func (f *Frame) Duration() time.Duration { 434 | ms := (1000 / float64(f.Header().SampleRate())) * float64(f.Samples()) 435 | return time.Duration(int(float64(time.Millisecond) * ms)) 436 | } 437 | 438 | // String renders the side info as a string for display purposes 439 | func (i FrameSideInfo) String() string { 440 | str := "" 441 | str += fmt.Sprintf(" NDataBegin: %v\n", i.NDataBegin()) 442 | return str 443 | } 444 | 445 | // Reader returns an io.Reader that reads the individual bytes from the frame 446 | func (f *Frame) Reader() io.Reader { 447 | return bytes.NewReader(f.buf) 448 | } 449 | -------------------------------------------------------------------------------- /frames_test.go: -------------------------------------------------------------------------------- 1 | package mp3 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | ) 8 | 9 | func BenchmarkDecode(t *testing.B) { 10 | skipped := 0 11 | t.ReportAllocs() 12 | r := MakeSilence() 13 | d := NewDecoder(r) 14 | f := Frame{} 15 | 16 | t.ResetTimer() 17 | for i := 0; i < t.N; i++ { 18 | t.SetBytes(int64(len(f.buf))) 19 | d.Decode(&f, &skipped) 20 | } 21 | } 22 | 23 | func ExampleDecoder_Decode() { 24 | skipped := 0 25 | r, err := os.Open("file.mp3") 26 | if err != nil { 27 | fmt.Println(err) 28 | return 29 | } 30 | 31 | d := NewDecoder(r) 32 | var f Frame 33 | for { 34 | 35 | if err := d.Decode(&f, &skipped); err != nil { 36 | fmt.Println(err) 37 | return 38 | } 39 | 40 | fmt.Println(&f) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /frameversion_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=FrameVersion"; DO NOT EDIT 2 | 3 | package mp3 4 | 5 | import "fmt" 6 | 7 | const _FrameVersion_name = "MPEG25MPEGReservedMPEG2MPEG1VERSIONMAX" 8 | 9 | var _FrameVersion_index = [...]uint8{0, 6, 18, 23, 28, 38} 10 | 11 | func (i FrameVersion) String() string { 12 | if i >= FrameVersion(len(_FrameVersion_index)-1) { 13 | return fmt.Sprintf("FrameVersion(%d)", i) 14 | } 15 | return _FrameVersion_name[_FrameVersion_index[i]:_FrameVersion_index[i+1]] 16 | } 17 | -------------------------------------------------------------------------------- /header.go: -------------------------------------------------------------------------------- 1 | package mp3 2 | 3 | /* 4 | func (this *FrameHeader) Parse(bs []byte) error { 5 | this.Size = 0 6 | this.Samples = 0 7 | this.Duration = 0 8 | 9 | if len(bs) < 4 { 10 | return fmt.Errorf("not enough bytes") 11 | } 12 | if bs[0] != 0xFF || (bs[1]&0xE0) != 0xE0 { 13 | return fmt.Errorf("missing sync word, got: %x, %x", bs[0], bs[1]) 14 | } 15 | this.Version = Version((bs[1] >> 3) & 0x03) 16 | if this.Version == MPEGReserved { 17 | return fmt.Errorf("reserved mpeg version") 18 | } 19 | 20 | this.Layer = Layer(((bs[1] >> 1) & 0x03)) 21 | if this.Layer == LayerReserved { 22 | return fmt.Errorf("reserved layer") 23 | } 24 | 25 | this.Protection = (bs[1] & 0x01) != 0x01 26 | 27 | bitrateIdx := (bs[2] >> 4) & 0x0F 28 | if bitrateIdx == 0x0F { 29 | return fmt.Errorf("invalid bitrate: %v", bitrateIdx) 30 | } 31 | this.Bitrate = bitrates[this.Version][this.Layer][bitrateIdx] * 1000 32 | if this.Bitrate == 0 { 33 | return fmt.Errorf("invalid bitrate: %v", bitrateIdx) 34 | } 35 | 36 | sampleRateIdx := (bs[2] >> 2) & 0x03 37 | if sampleRateIdx == 0x03 { 38 | return fmt.Errorf("invalid sample rate: %v", sampleRateIdx) 39 | } 40 | this.SampleRate = sampleRates[this.Version][sampleRateIdx] 41 | 42 | this.Pad = ((bs[2] >> 1) & 0x01) == 0x01 43 | 44 | this.Private = (bs[2] & 0x01) == 0x01 45 | 46 | this.ChannelMode = ChannelMode(bs[3]>>6) & 0x03 47 | 48 | // todo: mode extension 49 | 50 | this.CopyRight = (bs[3]>>3)&0x01 == 0x01 51 | 52 | this.Original = (bs[3]>>2)&0x01 == 0x01 53 | 54 | this.Emphasis = Emphasis(bs[3] & 0x03) 55 | if this.Emphasis == EmphReserved { 56 | return fmt.Errorf("reserved emphasis") 57 | } 58 | 59 | this.Size = this.size() 60 | this.Samples = this.samples() 61 | this.Duration = this.duration() 62 | 63 | return nil 64 | } 65 | 66 | func (this *FrameHeader) samples() int { 67 | return samplesPerFrame[this.Version][this.Layer] 68 | } 69 | 70 | func (this *FrameHeader) size() int64 { 71 | bps := float64(this.samples()) / 8 72 | fsize := (bps * float64(this.Bitrate)) / float64(this.SampleRate) 73 | if this.Pad { 74 | fsize += float64(slotSize[this.Layer]) 75 | } 76 | return int64(fsize) 77 | } 78 | 79 | func (this *FrameHeader) duration() time.Duration { 80 | ms := (1000 / float64(this.SampleRate)) * float64(this.samples()) 81 | return time.Duration(time.Duration(float64(time.Millisecond) * ms)) 82 | } 83 | */ 84 | -------------------------------------------------------------------------------- /internal/bindata.go: -------------------------------------------------------------------------------- 1 | package mp3 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "fmt" 7 | "io" 8 | "reflect" 9 | "strings" 10 | "unsafe" 11 | "os" 12 | "time" 13 | "io/ioutil" 14 | "path" 15 | "path/filepath" 16 | ) 17 | 18 | func bindata_read(data, name string) ([]byte, error) { 19 | var empty [0]byte 20 | sx := (*reflect.StringHeader)(unsafe.Pointer(&data)) 21 | b := empty[:] 22 | bx := (*reflect.SliceHeader)(unsafe.Pointer(&b)) 23 | bx.Data = sx.Data 24 | bx.Len = len(data) 25 | bx.Cap = bx.Len 26 | 27 | gz, err := gzip.NewReader(bytes.NewBuffer(b)) 28 | if err != nil { 29 | return nil, fmt.Errorf("Read %q: %v", name, err) 30 | } 31 | 32 | var buf bytes.Buffer 33 | _, err = io.Copy(&buf, gz) 34 | gz.Close() 35 | 36 | if err != nil { 37 | return nil, fmt.Errorf("Read %q: %v", name, err) 38 | } 39 | 40 | return buf.Bytes(), nil 41 | } 42 | 43 | type asset struct { 44 | bytes []byte 45 | info os.FileInfo 46 | } 47 | 48 | type bindata_file_info struct { 49 | name string 50 | size int64 51 | mode os.FileMode 52 | modTime time.Time 53 | } 54 | 55 | func (fi bindata_file_info) Name() string { 56 | return fi.name 57 | } 58 | func (fi bindata_file_info) Size() int64 { 59 | return fi.size 60 | } 61 | func (fi bindata_file_info) Mode() os.FileMode { 62 | return fi.mode 63 | } 64 | func (fi bindata_file_info) ModTime() time.Time { 65 | return fi.modTime 66 | } 67 | func (fi bindata_file_info) IsDir() bool { 68 | return false 69 | } 70 | func (fi bindata_file_info) Sys() interface{} { 71 | return nil 72 | } 73 | 74 | var _data_silent_1frame_mp3 = "\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xfa\xff\x7b\x43\x0a\x03\xff\x07\x06\x86\x4c\x06\x06\x06\x0e\x06\x06\x5e\x05\x06\x06\x46\x20\x5a\x02\xe4\x02\x99\x26\x0d\x0c\x0c\x2c\x3e\x8e\xbe\xae\xc6\x7a\x96\x96\x7a\xa6\x0c\xa3\x60\x14\x50\x08\x00\x01\x00\x00\xff\xff\xa1\x6f\x84\x53\x72\x02\x00\x00" 75 | 76 | func data_silent_1frame_mp3_bytes() ([]byte, error) { 77 | return bindata_read( 78 | _data_silent_1frame_mp3, 79 | "data/silent_1frame.mp3", 80 | ) 81 | } 82 | 83 | func data_silent_1frame_mp3() (*asset, error) { 84 | bytes, err := data_silent_1frame_mp3_bytes() 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | info := bindata_file_info{name: "data/silent_1frame.mp3", size: 626, mode: os.FileMode(420), modTime: time.Unix(1424763406, 0)} 90 | a := &asset{bytes: bytes, info: info} 91 | return a, nil 92 | } 93 | 94 | // Asset loads and returns the asset for the given name. 95 | // It returns an error if the asset could not be found or 96 | // could not be loaded. 97 | func Asset(name string) ([]byte, error) { 98 | cannonicalName := strings.Replace(name, "\\", "/", -1) 99 | if f, ok := _bindata[cannonicalName]; ok { 100 | a, err := f() 101 | if err != nil { 102 | return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) 103 | } 104 | return a.bytes, nil 105 | } 106 | return nil, fmt.Errorf("Asset %s not found", name) 107 | } 108 | 109 | // AssetInfo loads and returns the asset info for the given name. 110 | // It returns an error if the asset could not be found or 111 | // could not be loaded. 112 | func AssetInfo(name string) (os.FileInfo, error) { 113 | cannonicalName := strings.Replace(name, "\\", "/", -1) 114 | if f, ok := _bindata[cannonicalName]; ok { 115 | a, err := f() 116 | if err != nil { 117 | return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) 118 | } 119 | return a.info, nil 120 | } 121 | return nil, fmt.Errorf("AssetInfo %s not found", name) 122 | } 123 | 124 | // AssetNames returns the names of the assets. 125 | func AssetNames() []string { 126 | names := make([]string, 0, len(_bindata)) 127 | for name := range _bindata { 128 | names = append(names, name) 129 | } 130 | return names 131 | } 132 | 133 | // _bindata is a table, holding each asset generator, mapped to its name. 134 | var _bindata = map[string]func() (*asset, error){ 135 | "data/silent_1frame.mp3": data_silent_1frame_mp3, 136 | } 137 | 138 | // AssetDir returns the file names below a certain 139 | // directory embedded in the file by go-bindata. 140 | // For example if you run go-bindata on data/... and data contains the 141 | // following hierarchy: 142 | // data/ 143 | // foo.txt 144 | // img/ 145 | // a.png 146 | // b.png 147 | // then AssetDir("data") would return []string{"foo.txt", "img"} 148 | // AssetDir("data/img") would return []string{"a.png", "b.png"} 149 | // AssetDir("foo.txt") and AssetDir("notexist") would return an error 150 | // AssetDir("") will return []string{"data"}. 151 | func AssetDir(name string) ([]string, error) { 152 | node := _bintree 153 | if len(name) != 0 { 154 | cannonicalName := strings.Replace(name, "\\", "/", -1) 155 | pathList := strings.Split(cannonicalName, "/") 156 | for _, p := range pathList { 157 | node = node.Children[p] 158 | if node == nil { 159 | return nil, fmt.Errorf("Asset %s not found", name) 160 | } 161 | } 162 | } 163 | if node.Func != nil { 164 | return nil, fmt.Errorf("Asset %s not found", name) 165 | } 166 | rv := make([]string, 0, len(node.Children)) 167 | for name := range node.Children { 168 | rv = append(rv, name) 169 | } 170 | return rv, nil 171 | } 172 | 173 | type _bintree_t struct { 174 | Func func() (*asset, error) 175 | Children map[string]*_bintree_t 176 | } 177 | var _bintree = &_bintree_t{nil, map[string]*_bintree_t{ 178 | "data": &_bintree_t{nil, map[string]*_bintree_t{ 179 | "silent_1frame.mp3": &_bintree_t{data_silent_1frame_mp3, map[string]*_bintree_t{ 180 | }}, 181 | }}, 182 | }} 183 | 184 | // Restore an asset under the given directory 185 | func RestoreAsset(dir, name string) error { 186 | data, err := Asset(name) 187 | if err != nil { 188 | return err 189 | } 190 | info, err := AssetInfo(name) 191 | if err != nil { 192 | return err 193 | } 194 | err = os.MkdirAll(_filePath(dir, path.Dir(name)), os.FileMode(0755)) 195 | if err != nil { 196 | return err 197 | } 198 | err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) 199 | if err != nil { 200 | return err 201 | } 202 | err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) 203 | if err != nil { 204 | return err 205 | } 206 | return nil 207 | } 208 | 209 | // Restore assets under the given directory recursively 210 | func RestoreAssets(dir, name string) error { 211 | children, err := AssetDir(name) 212 | if err != nil { // File 213 | return RestoreAsset(dir, name) 214 | } else { // Dir 215 | for _, child := range children { 216 | err = RestoreAssets(dir, path.Join(name, child)) 217 | if err != nil { 218 | return err 219 | } 220 | } 221 | } 222 | return nil 223 | } 224 | 225 | func _filePath(dir, name string) string { 226 | cannonicalName := strings.Replace(name, "\\", "/", -1) 227 | return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) 228 | } 229 | 230 | -------------------------------------------------------------------------------- /internal/data/bindata.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "bytes" 5 | "compress/gzip" 6 | "fmt" 7 | "io" 8 | "reflect" 9 | "strings" 10 | "unsafe" 11 | "os" 12 | "time" 13 | "io/ioutil" 14 | "path" 15 | "path/filepath" 16 | ) 17 | 18 | func bindata_read(data, name string) ([]byte, error) { 19 | var empty [0]byte 20 | sx := (*reflect.StringHeader)(unsafe.Pointer(&data)) 21 | b := empty[:] 22 | bx := (*reflect.SliceHeader)(unsafe.Pointer(&b)) 23 | bx.Data = sx.Data 24 | bx.Len = len(data) 25 | bx.Cap = bx.Len 26 | 27 | gz, err := gzip.NewReader(bytes.NewBuffer(b)) 28 | if err != nil { 29 | return nil, fmt.Errorf("Read %q: %v", name, err) 30 | } 31 | 32 | var buf bytes.Buffer 33 | _, err = io.Copy(&buf, gz) 34 | gz.Close() 35 | 36 | if err != nil { 37 | return nil, fmt.Errorf("Read %q: %v", name, err) 38 | } 39 | 40 | return buf.Bytes(), nil 41 | } 42 | 43 | type asset struct { 44 | bytes []byte 45 | info os.FileInfo 46 | } 47 | 48 | type bindata_file_info struct { 49 | name string 50 | size int64 51 | mode os.FileMode 52 | modTime time.Time 53 | } 54 | 55 | func (fi bindata_file_info) Name() string { 56 | return fi.name 57 | } 58 | func (fi bindata_file_info) Size() int64 { 59 | return fi.size 60 | } 61 | func (fi bindata_file_info) Mode() os.FileMode { 62 | return fi.mode 63 | } 64 | func (fi bindata_file_info) ModTime() time.Time { 65 | return fi.modTime 66 | } 67 | func (fi bindata_file_info) IsDir() bool { 68 | return false 69 | } 70 | func (fi bindata_file_info) Sys() interface{} { 71 | return nil 72 | } 73 | 74 | var _silent_1frame_go = "\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\x64\x90\x41\x4b\x3b\x31\x10\xc5\xcf\x3b\x9f\xe2\xfd\xf7\xb4\x85\x7f\x1b\xaa\x17\x11\x7a\x50\xc1\x8b\x47\x8f\x22\x92\xee\xce\xa6\xa1\x9b\x99\x90\xa4\x4a\x91\x7e\x77\x77\xe3\x45\xf1\x10\x02\x33\x6f\x7e\xef\xcd\x44\xdb\x1f\xad\x63\x0c\xb6\x58\x22\x1f\xa2\xa6\x82\x76\x52\xd7\x12\xbd\xdb\x84\x8e\x1a\x63\xf0\xec\x27\x96\x72\x7f\x2e\x9c\xe1\x33\xca\x81\x91\xec\x47\x1d\xc2\x98\x34\xc0\x62\x7b\x75\x83\xa7\xbd\xc9\xd0\x11\x93\x0d\x0c\x96\x5e\x07\x1e\x20\x5a\x0e\x5e\x9c\x70\xce\xd4\xfc\x04\xbd\xbc\xee\xe7\x9f\x56\x44\xc6\x38\xbd\x75\x2c\x9c\x6c\x61\x38\x5d\xef\xbd\x54\xf6\x3a\x1e\xdd\xb7\xcb\x5a\x34\x70\xe8\x35\x9e\xb1\x31\x34\x9e\xa4\x87\x17\x5f\xba\x15\x3e\xa9\x59\x82\x72\xaa\x4f\xd3\x2f\x93\xff\xb5\xbe\xc3\x5d\xce\x5c\xba\x76\x41\x99\x5c\xdb\x6f\xdb\x31\xcd\x31\x37\x21\x5e\xb7\x2b\x6a\xfc\x58\x95\xff\x76\x10\x3f\x2d\xcc\x66\xbe\xc1\xe6\x71\xd6\x4f\x63\xd7\x3e\xe8\x69\xaa\x9b\x40\x23\x0b\xfe\x10\x60\x17\xfe\xc2\xb9\xd0\x85\xbe\x02\x00\x00\xff\xff\x0e\x9e\x37\x70\x54\x01\x00\x00" 75 | 76 | func silent_1frame_go_bytes() ([]byte, error) { 77 | return bindata_read( 78 | _silent_1frame_go, 79 | "silent_1frame.go", 80 | ) 81 | } 82 | 83 | func silent_1frame_go() (*asset, error) { 84 | bytes, err := silent_1frame_go_bytes() 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | info := bindata_file_info{name: "silent_1frame.go", size: 340, mode: os.FileMode(420), modTime: time.Unix(1424984811, 0)} 90 | a := &asset{bytes: bytes, info: info} 91 | return a, nil 92 | } 93 | 94 | var _silent_1frame_mp3 = "\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xfa\xff\x7b\x43\x0a\x03\xff\x07\x06\x86\x4c\x06\x06\x06\x0e\x06\x06\x5e\x05\x06\x06\x46\x20\x5a\x02\xe4\x02\x99\x26\x0d\x0c\x0c\x2c\x3e\x8e\xbe\xae\xc6\x7a\x96\x96\x7a\xa6\x0c\xa3\x60\x14\x50\x08\x00\x01\x00\x00\xff\xff\xa1\x6f\x84\x53\x72\x02\x00\x00" 95 | 96 | func silent_1frame_mp3_bytes() ([]byte, error) { 97 | return bindata_read( 98 | _silent_1frame_mp3, 99 | "silent_1frame.mp3", 100 | ) 101 | } 102 | 103 | func silent_1frame_mp3() (*asset, error) { 104 | bytes, err := silent_1frame_mp3_bytes() 105 | if err != nil { 106 | return nil, err 107 | } 108 | 109 | info := bindata_file_info{name: "silent_1frame.mp3", size: 626, mode: os.FileMode(420), modTime: time.Unix(1424763406, 0)} 110 | a := &asset{bytes: bytes, info: info} 111 | return a, nil 112 | } 113 | 114 | // Asset loads and returns the asset for the given name. 115 | // It returns an error if the asset could not be found or 116 | // could not be loaded. 117 | func Asset(name string) ([]byte, error) { 118 | cannonicalName := strings.Replace(name, "\\", "/", -1) 119 | if f, ok := _bindata[cannonicalName]; ok { 120 | a, err := f() 121 | if err != nil { 122 | return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) 123 | } 124 | return a.bytes, nil 125 | } 126 | return nil, fmt.Errorf("Asset %s not found", name) 127 | } 128 | 129 | // AssetInfo loads and returns the asset info for the given name. 130 | // It returns an error if the asset could not be found or 131 | // could not be loaded. 132 | func AssetInfo(name string) (os.FileInfo, error) { 133 | cannonicalName := strings.Replace(name, "\\", "/", -1) 134 | if f, ok := _bindata[cannonicalName]; ok { 135 | a, err := f() 136 | if err != nil { 137 | return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) 138 | } 139 | return a.info, nil 140 | } 141 | return nil, fmt.Errorf("AssetInfo %s not found", name) 142 | } 143 | 144 | // AssetNames returns the names of the assets. 145 | func AssetNames() []string { 146 | names := make([]string, 0, len(_bindata)) 147 | for name := range _bindata { 148 | names = append(names, name) 149 | } 150 | return names 151 | } 152 | 153 | // _bindata is a table, holding each asset generator, mapped to its name. 154 | var _bindata = map[string]func() (*asset, error){ 155 | "silent_1frame.go": silent_1frame_go, 156 | "silent_1frame.mp3": silent_1frame_mp3, 157 | } 158 | 159 | // AssetDir returns the file names below a certain 160 | // directory embedded in the file by go-bindata. 161 | // For example if you run go-bindata on data/... and data contains the 162 | // following hierarchy: 163 | // data/ 164 | // foo.txt 165 | // img/ 166 | // a.png 167 | // b.png 168 | // then AssetDir("data") would return []string{"foo.txt", "img"} 169 | // AssetDir("data/img") would return []string{"a.png", "b.png"} 170 | // AssetDir("foo.txt") and AssetDir("notexist") would return an error 171 | // AssetDir("") will return []string{"data"}. 172 | func AssetDir(name string) ([]string, error) { 173 | node := _bintree 174 | if len(name) != 0 { 175 | cannonicalName := strings.Replace(name, "\\", "/", -1) 176 | pathList := strings.Split(cannonicalName, "/") 177 | for _, p := range pathList { 178 | node = node.Children[p] 179 | if node == nil { 180 | return nil, fmt.Errorf("Asset %s not found", name) 181 | } 182 | } 183 | } 184 | if node.Func != nil { 185 | return nil, fmt.Errorf("Asset %s not found", name) 186 | } 187 | rv := make([]string, 0, len(node.Children)) 188 | for name := range node.Children { 189 | rv = append(rv, name) 190 | } 191 | return rv, nil 192 | } 193 | 194 | type _bintree_t struct { 195 | Func func() (*asset, error) 196 | Children map[string]*_bintree_t 197 | } 198 | var _bintree = &_bintree_t{nil, map[string]*_bintree_t{ 199 | "silent_1frame.go": &_bintree_t{silent_1frame_go, map[string]*_bintree_t{ 200 | }}, 201 | "silent_1frame.mp3": &_bintree_t{silent_1frame_mp3, map[string]*_bintree_t{ 202 | }}, 203 | }} 204 | 205 | // Restore an asset under the given directory 206 | func RestoreAsset(dir, name string) error { 207 | data, err := Asset(name) 208 | if err != nil { 209 | return err 210 | } 211 | info, err := AssetInfo(name) 212 | if err != nil { 213 | return err 214 | } 215 | err = os.MkdirAll(_filePath(dir, path.Dir(name)), os.FileMode(0755)) 216 | if err != nil { 217 | return err 218 | } 219 | err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) 220 | if err != nil { 221 | return err 222 | } 223 | err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) 224 | if err != nil { 225 | return err 226 | } 227 | return nil 228 | } 229 | 230 | // Restore assets under the given directory recursively 231 | func RestoreAssets(dir, name string) error { 232 | children, err := AssetDir(name) 233 | if err != nil { // File 234 | return RestoreAsset(dir, name) 235 | } else { // Dir 236 | for _, child := range children { 237 | err = RestoreAssets(dir, path.Join(name, child)) 238 | if err != nil { 239 | return err 240 | } 241 | } 242 | } 243 | return nil 244 | } 245 | 246 | func _filePath(dir, name string) string { 247 | cannonicalName := strings.Replace(name, "\\", "/", -1) 248 | return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) 249 | } 250 | 251 | -------------------------------------------------------------------------------- /internal/data/silent_1frame.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import "log" 4 | 5 | var ( 6 | // SilentBytes is the raw data from a 128 Kb/s of lame encoded nothingness 7 | SilentBytes []byte 8 | ) 9 | 10 | //go:generate go-bindata -pkg data -nomemcopy ./ 11 | func init() { 12 | var err error 13 | SilentBytes, err = Asset("silent_1frame.mp3") 14 | if err != nil { 15 | log.Fatalf("Could not open silent_1frame.mp3 asset") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /internal/data/silent_1frame.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tcolgate/mp3/e79c5a46d30097711c17ac6dee3b1839473cdca8/internal/data/silent_1frame.mp3 -------------------------------------------------------------------------------- /silence.go: -------------------------------------------------------------------------------- 1 | package mp3 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | 7 | "github.com/tcolgate/mp3/internal/data" 8 | ) 9 | 10 | var ( 11 | // SilentFrame is the sound of Ripley screaming on the Nostromo, from the outside 12 | SilentFrame *Frame 13 | 14 | // SilentBytes is the raw raw data behind SilentFrame 15 | SilentBytes []byte 16 | ) 17 | 18 | func init() { 19 | skipped := 0 20 | SilentBytes = data.SilentBytes 21 | 22 | dec := NewDecoder(bytes.NewBuffer(SilentBytes)) 23 | frame := Frame{} 24 | SilentFrame = &frame 25 | dec.Decode(&frame, &skipped) 26 | } 27 | 28 | type silenceReader struct { 29 | int // Location into the silence frame 30 | } 31 | 32 | func (s *silenceReader) Close() error { 33 | return nil 34 | } 35 | 36 | func (s *silenceReader) Read(out []byte) (int, error) { 37 | for i := 0; i < len(out); i++ { 38 | out[i] = SilentBytes[s.int] 39 | s.int++ 40 | if s.int >= len(SilentBytes) { 41 | s.int = 0 42 | } 43 | } 44 | 45 | return len(out), nil 46 | } 47 | 48 | // MakeSilence provides a constant stream of silenct frames. 49 | func MakeSilence() io.ReadCloser { 50 | return &silenceReader{0} 51 | } 52 | --------------------------------------------------------------------------------