├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── cmd └── auplay │ └── play.go ├── convert ├── float32.go ├── resampling.go └── stereo16.go ├── format.go ├── format_test.go ├── go.mod ├── go.sum ├── mp3 └── decode.go ├── ogg ├── ogg_decode.go └── ogg_notgopherjs.go └── wav ├── adpcm ├── decode.go └── status.go └── wav_decode.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.17 20 | 21 | - name: Get dependencies 22 | run: sudo apt-get update && sudo apt-get install libasound2-dev 23 | if: ${{ runner.os == 'Linux' }} 24 | 25 | - name: Build 26 | run: go build -v ./... 27 | 28 | - name: Go Test 29 | run: go test -v -coverprofile=coverage.txt -covermode=atomic ./... 30 | 31 | - name: Upload coverage report 32 | uses: codecov/codecov-action@v1.0.2 33 | with: 34 | token: 4a4ba14f-7546-4000-9113-7b67b111ebd6 35 | file: ./coverage.txt 36 | flags: unittests 37 | name: codecov-qiniu-audio 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.mp3 2 | *.wav 3 | *.js 4 | *.js.map 5 | .DS_Store 6 | 7 | # Binaries for programs and plugins 8 | *.exe 9 | *.exe~ 10 | *.dll 11 | *.so 12 | *.dylib 13 | 14 | # Test binary, built with `go test -c` 15 | *.test 16 | 17 | # Output of the go coverage tool, specifically when used with LiteIDE 18 | *.out 19 | 20 | # Dependency directories (remove the comment below to include it) 21 | # vendor/ 22 | adpcm-go/ 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Audio support for Go language 2 | 3 | [![LICENSE](https://img.shields.io/github/license/qiniu/audio.svg)](https://github.com/qiniu/audio/blob/master/LICENSE) 4 | [![Build Status](https://github.com/qiniu/audio/actions/workflows/go.yml/badge.svg)](https://github.com/qiniu/audio/actions/workflows/go.yml) 5 | [![Go Report Card](https://goreportcard.com/badge/github.com/qiniu/audio)](https://goreportcard.com/report/github.com/qiniu/audio) 6 | [![GitHub release](https://img.shields.io/github/v/tag/qiniu/audio.svg?label=release)](https://github.com/qiniu/audio/releases) 7 | [![Coverage Status](https://codecov.io/gh/qiniu/audio/branch/master/graph/badge.svg)](https://codecov.io/gh/qiniu/audio) 8 | [![GoDoc](https://pkg.go.dev/badge/github.com/qiniu/audio.svg)](https://pkg.go.dev/mod/github.com/qiniu/audio) 9 | 10 | [![Qiniu Logo](http://open.qiniudn.com/logo.png)](http://www.qiniu.com/) 11 | 12 | The package `github.com/qiniu/audio` is an extensible audio library with simple API for multi platforms in the Go programming language. 13 | 14 | ## Platforms 15 | 16 | * Windows 17 | * macOS 18 | * Linux 19 | * FreeBSD 20 | * Android 21 | * iOS 22 | * Web browsers (Chrome, Firefox, Safari and Edge) 23 | * GopherJS 24 | * WebAssembly (Experimental) 25 | 26 | ## Features 27 | 28 | * Pluggable audio decoders. And now it supports the following formats: 29 | * wav/pcm: `import _ "github.com/qiniu/audio/wav"` 30 | * wav/adpcm: `import _ "github.com/qiniu/audio/wav/adpcm"` 31 | * mp3: `import _ "github.com/qiniu/audio/mp3"` 32 | * Audio encoders (TODO). 33 | * Convert decoded audio stream. 34 | 35 | ## Example 36 | 37 | ``` 38 | import ( 39 | "io" 40 | "os" 41 | 42 | "github.com/hajimehoshi/oto" 43 | 44 | "github.com/qiniu/audio" 45 | _ "github.com/qiniu/audio/mp3" 46 | _ "github.com/qiniu/audio/wav" 47 | _ "github.com/qiniu/audio/wav/adpcm" 48 | ) 49 | 50 | func playAudio(file string) error { 51 | f, err := os.Open(file) 52 | if err != nil { 53 | return err 54 | } 55 | defer f.Close() 56 | 57 | d, _, err := audio.Decode(f) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | c, err := oto.NewContext(d.SampleRate(), d.Channels(), d.BytesPerSample(), 8192) 63 | if err != nil { 64 | return err 65 | } 66 | defer c.Close() 67 | 68 | p := c.NewPlayer() 69 | defer p.Close() 70 | 71 | _, err = io.Copy(p, d) 72 | return err 73 | } 74 | ``` 75 | 76 | ## Document 77 | 78 | * See https://godoc.org/github.com/qiniu/audio 79 | -------------------------------------------------------------------------------- /cmd/auplay/play.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "os" 8 | 9 | "github.com/hajimehoshi/oto" 10 | 11 | "github.com/qiniu/audio" 12 | _ "github.com/qiniu/audio/mp3" 13 | _ "github.com/qiniu/audio/wav" 14 | _ "github.com/qiniu/audio/wav/adpcm" 15 | ) 16 | 17 | // ------------------------------------------------------------------------------------- 18 | 19 | func play(file string) error { 20 | f, err := os.Open(file) 21 | if err != nil { 22 | return err 23 | } 24 | defer f.Close() 25 | 26 | d, format, err := audio.Decode(f) 27 | if err != nil { 28 | return err 29 | } 30 | fmt.Printf( 31 | "Format: %s\nSampleRate: %d\nChannels: %d\nBytesPerSample: %d\n", 32 | format, d.SampleRate(), d.Channels(), d.BytesPerSample()) 33 | 34 | c, err := oto.NewContext(d.SampleRate(), d.Channels(), d.BytesPerSample(), 8192) 35 | if err != nil { 36 | return err 37 | } 38 | defer c.Close() 39 | 40 | fmt.Printf("Length: %d[bytes]\n", d.Length()) 41 | p := c.NewPlayer() 42 | defer p.Close() 43 | 44 | _, err = io.Copy(p, d) 45 | return err 46 | } 47 | 48 | func main() { 49 | if len(os.Args) < 2 { 50 | fmt.Printf("Usage: auplay \n\n") 51 | return 52 | } 53 | if err := play(os.Args[1]); err != nil { 54 | log.Fatal(err) 55 | } 56 | } 57 | 58 | // ------------------------------------------------------------------------------------- 59 | -------------------------------------------------------------------------------- /convert/float32.go: -------------------------------------------------------------------------------- 1 | package convert 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | // Float32Reader interface. 8 | type Float32Reader interface { 9 | Read([]float32) (int, error) 10 | } 11 | 12 | // NewReaderFromFloat32Reader converts Float32Reader to Reader. 13 | func NewReaderFromFloat32Reader(r Float32Reader) io.Reader { 14 | return &f32Reader{r: r} 15 | } 16 | 17 | type f32Reader struct { 18 | r Float32Reader 19 | eof bool 20 | buf *byte 21 | } 22 | 23 | func (f *f32Reader) Read(buf []byte) (int, error) { 24 | if f.eof { 25 | return 0, io.EOF 26 | } 27 | if len(buf) == 0 { 28 | return 0, nil 29 | } 30 | if f.buf != nil { 31 | buf[0] = *f.buf 32 | f.buf = nil 33 | return 1, nil 34 | } 35 | 36 | bf := make([]float32, len(buf)/2) 37 | if len(buf) == 1 { 38 | bf = make([]float32, 1) 39 | } 40 | 41 | n, err := f.r.Read(bf) 42 | if err != nil && err != io.EOF { 43 | return 0, err 44 | } 45 | if err == io.EOF { 46 | f.eof = true 47 | } 48 | 49 | b := buf 50 | if len(buf) == 1 && n > 0 { 51 | b = make([]byte, 2) 52 | } 53 | for i := 0; i < n; i++ { 54 | f := bf[i] 55 | s := int16(f * (1<<15 - 1)) 56 | b[2*i] = uint8(s) 57 | b[2*i+1] = uint8(s >> 8) 58 | } 59 | 60 | if len(buf) == 1 && len(b) == 2 { 61 | buf[0] = b[0] 62 | f.buf = &b[1] 63 | return 1, err 64 | } 65 | return n * 2, err 66 | } 67 | -------------------------------------------------------------------------------- /convert/resampling.go: -------------------------------------------------------------------------------- 1 | package convert 2 | 3 | import ( 4 | "io" 5 | "math" 6 | 7 | "github.com/qiniu/audio" 8 | ) 9 | 10 | // ------------------------------------------------------------------------------------- 11 | 12 | var cosTable = [65536]float64{} 13 | 14 | func init() { 15 | for i := range cosTable { 16 | cosTable[i] = math.Cos(float64(i) * math.Pi / 2 / float64(len(cosTable))) 17 | } 18 | } 19 | 20 | func fastCos01(x float64) float64 { 21 | if x < 0 { 22 | x = -x 23 | } 24 | i := int(4 * float64(len(cosTable)) * x) 25 | if 4*len(cosTable) < i { 26 | i %= 4 * len(cosTable) 27 | } 28 | sign := 1 29 | switch { 30 | case i < len(cosTable): 31 | case i < len(cosTable)*2: 32 | i = len(cosTable)*2 - i 33 | sign = -1 34 | case i < len(cosTable)*3: 35 | i -= len(cosTable) * 2 36 | sign = -1 37 | default: 38 | i = len(cosTable)*4 - i 39 | } 40 | if i == len(cosTable) { 41 | return 0 42 | } 43 | return float64(sign) * cosTable[i] 44 | } 45 | 46 | func fastSin01(x float64) float64 { 47 | return fastCos01(x - 0.25) 48 | } 49 | 50 | func sinc01(x float64) float64 { 51 | if math.Abs(x) < 1e-8 { 52 | return 1 53 | } 54 | return fastSin01(x) / (x * 2 * math.Pi) 55 | } 56 | 57 | // Resampling class. 58 | type Resampling struct { 59 | source io.ReadSeeker 60 | size int64 61 | from int 62 | to int 63 | pos int64 64 | srcBlock int64 65 | srcBufL map[int64][]float64 66 | srcBufR map[int64][]float64 67 | lruSrcBlocks []int64 68 | } 69 | 70 | // NewResampling func. 71 | func NewResampling(source io.ReadSeeker, size int64, from, to int) *Resampling { 72 | r := &Resampling{ 73 | source: source, 74 | size: size, 75 | from: from, 76 | to: to, 77 | srcBlock: -1, 78 | srcBufL: map[int64][]float64{}, 79 | srcBufR: map[int64][]float64{}, 80 | } 81 | return r 82 | } 83 | 84 | // Length func. 85 | func (r *Resampling) Length() int64 { 86 | s := int64(float64(r.size) * float64(r.to) / float64(r.from)) 87 | return s / 4 * 4 88 | } 89 | 90 | func (r *Resampling) src(i int64) (float64, float64, error) { 91 | const resamplingBufferSize = 4096 92 | 93 | if i < 0 { 94 | return 0, 0, nil 95 | } 96 | if r.size/4 <= int64(i) { 97 | return 0, 0, nil 98 | } 99 | nextPos := int64(i) / resamplingBufferSize 100 | if _, ok := r.srcBufL[nextPos]; !ok { 101 | if r.srcBlock+1 != nextPos { 102 | if _, err := r.source.Seek(nextPos*resamplingBufferSize*4, io.SeekStart); err != nil { 103 | return 0, 0, err 104 | } 105 | } 106 | buf := make([]uint8, resamplingBufferSize*4) 107 | c := 0 108 | for c < len(buf) { 109 | n, err := r.source.Read(buf[c:]) 110 | c += n 111 | if err != nil { 112 | if err == io.EOF { 113 | break 114 | } 115 | return 0, 0, err 116 | } 117 | } 118 | buf = buf[:c] 119 | sl := make([]float64, resamplingBufferSize) 120 | sr := make([]float64, resamplingBufferSize) 121 | for i := 0; i < len(buf)/4; i++ { 122 | sl[i] = float64(int16(buf[4*i])|(int16(buf[4*i+1])<<8)) / (1<<15 - 1) 123 | sr[i] = float64(int16(buf[4*i+2])|(int16(buf[4*i+3])<<8)) / (1<<15 - 1) 124 | } 125 | r.srcBlock = nextPos 126 | r.srcBufL[r.srcBlock] = sl 127 | r.srcBufR[r.srcBlock] = sr 128 | // To keep srcBufL/R not too big, let's remove the least used buffers. 129 | if len(r.lruSrcBlocks) >= 4 { 130 | p := r.lruSrcBlocks[0] 131 | delete(r.srcBufL, p) 132 | delete(r.srcBufR, p) 133 | r.lruSrcBlocks = r.lruSrcBlocks[1:] 134 | } 135 | r.lruSrcBlocks = append(r.lruSrcBlocks, r.srcBlock) 136 | } else { 137 | r.srcBlock = nextPos 138 | idx := -1 139 | for i, p := range r.lruSrcBlocks { 140 | if p == r.srcBlock { 141 | idx = i 142 | break 143 | } 144 | } 145 | if idx == -1 { 146 | panic("not reach") 147 | } 148 | r.lruSrcBlocks = append(r.lruSrcBlocks[:idx], r.lruSrcBlocks[idx+1:]...) 149 | r.lruSrcBlocks = append(r.lruSrcBlocks, r.srcBlock) 150 | } 151 | ii := i % resamplingBufferSize 152 | return r.srcBufL[r.srcBlock][ii], r.srcBufR[r.srcBlock][ii], nil 153 | } 154 | 155 | func (r *Resampling) at(t int64) (float64, float64, error) { 156 | windowSize := 8.0 157 | tInSrc := float64(t) * float64(r.from) / float64(r.to) 158 | startN := int64(tInSrc - windowSize) 159 | if startN < 0 { 160 | startN = 0 161 | } 162 | if r.size/4 <= startN { 163 | startN = r.size/4 - 1 164 | } 165 | endN := int64(tInSrc + windowSize) 166 | if r.size/4 <= endN { 167 | endN = r.size/4 - 1 168 | } 169 | lv := 0.0 170 | rv := 0.0 171 | for n := startN; n <= endN; n++ { 172 | srcL, srcR, err := r.src(n) 173 | if err != nil { 174 | return 0, 0, err 175 | } 176 | d := tInSrc - float64(n) 177 | w := 0.5 + 0.5*fastCos01(d/(windowSize*2+1)) 178 | s := sinc01(d/2) * w 179 | lv += srcL * s 180 | rv += srcR * s 181 | } 182 | if lv < -1 { 183 | lv = -1 184 | } 185 | if lv > 1 { 186 | lv = 1 187 | } 188 | if rv < -1 { 189 | rv = -1 190 | } 191 | if rv > 1 { 192 | rv = 1 193 | } 194 | return lv, rv, nil 195 | } 196 | 197 | func (r *Resampling) Read(b []uint8) (int, error) { 198 | if r.pos == r.Length() { 199 | return 0, io.EOF 200 | } 201 | n := len(b) / 4 * 4 202 | if r.Length()-r.pos <= int64(n) { 203 | n = int(r.Length() - r.pos) 204 | } 205 | for i := 0; i < n/4; i++ { 206 | l, r, err := r.at(r.pos/4 + int64(i)) 207 | if err != nil { 208 | return 0, err 209 | } 210 | l16 := int16(l * (1<<15 - 1)) 211 | r16 := int16(r * (1<<15 - 1)) 212 | b[4*i] = uint8(l16) 213 | b[4*i+1] = uint8(l16 >> 8) 214 | b[4*i+2] = uint8(r16) 215 | b[4*i+3] = uint8(r16 >> 8) 216 | } 217 | r.pos += int64(n) 218 | return n, nil 219 | } 220 | 221 | // Seek func. 222 | func (r *Resampling) Seek(offset int64, whence int) (int64, error) { 223 | switch whence { 224 | case io.SeekStart: 225 | r.pos = offset 226 | case io.SeekCurrent: 227 | r.pos += offset 228 | case io.SeekEnd: 229 | r.pos += r.Length() + offset 230 | } 231 | if r.pos < 0 { 232 | r.pos = 0 233 | } 234 | if r.Length() <= r.pos { 235 | r.pos = r.Length() 236 | } 237 | return r.pos, nil 238 | } 239 | 240 | // ------------------------------------------------------------------------------------- 241 | 242 | type resampleDecoded struct { 243 | Resampling 244 | d audio.Decoded 245 | } 246 | 247 | // SampleRate returns the sample rate like 44100. 248 | func (p *resampleDecoded) SampleRate() int { 249 | return p.to 250 | } 251 | 252 | // Channels returns the number of channels. One channel is mono playback. 253 | // Two channels are stereo playback. No other values are supported. 254 | func (p *resampleDecoded) Channels() int { 255 | return p.d.Channels() 256 | } 257 | 258 | // BytesPerSample returns the number of bytes per sample per channel. 259 | // The usual value is 2. Only values 1 and 2 are supported. 260 | func (p *resampleDecoded) BytesPerSample() int { 261 | return p.d.BytesPerSample() 262 | } 263 | 264 | // Resample resamples an audio. 265 | func Resample(d audio.Decoded, sampleRate int) audio.Decoded { 266 | sampleRateFrom := d.SampleRate() 267 | if sampleRateFrom == sampleRate { 268 | return d 269 | } 270 | s := NewResampling(d, d.Length(), sampleRateFrom, sampleRate) 271 | return &resampleDecoded{*s, d} 272 | } 273 | 274 | // ------------------------------------------------------------------------------------- 275 | -------------------------------------------------------------------------------- /convert/stereo16.go: -------------------------------------------------------------------------------- 1 | package convert 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/qiniu/audio" 7 | "github.com/qiniu/x/bufiox" 8 | ) 9 | 10 | // ------------------------------------------------------------------------------------- 11 | 12 | // Stereo16 class. 13 | type Stereo16 struct { 14 | source *bufiox.Reader 15 | mono bool 16 | eight bool 17 | } 18 | 19 | // NewStereo16 func. 20 | func NewStereo16(source io.ReadSeeker, isMono, eightBitsPerSample bool) *Stereo16 { 21 | return &Stereo16{ 22 | source: bufiox.NewReader(source), 23 | mono: isMono, 24 | eight: eightBitsPerSample, 25 | } 26 | } 27 | 28 | func (s *Stereo16) Read(b []uint8) (int, error) { 29 | l := len(b) 30 | if s.mono { 31 | l >>= 1 32 | } 33 | if s.eight { 34 | l >>= 1 35 | } 36 | buf := b[len(b)-l:] 37 | n, err := s.source.ReadFull(buf) // ReadFull: forbidden to read odd bytes (when !mono or !eight). 38 | if err != nil { 39 | if err == io.ErrUnexpectedEOF { 40 | err = io.EOF 41 | } else if err != io.EOF { 42 | return 0, err 43 | } 44 | } 45 | switch { 46 | case s.mono && s.eight: 47 | for i := 0; i < n; i++ { 48 | v := int16(int(buf[i])*0x101 - (1 << 15)) 49 | b[4*i] = uint8(v) 50 | b[4*i+1] = uint8(v >> 8) 51 | b[4*i+2] = uint8(v) 52 | b[4*i+3] = uint8(v >> 8) 53 | } 54 | case s.mono && !s.eight: 55 | for i := 0; i < (n >> 1); i++ { 56 | b[4*i] = buf[2*i] 57 | b[4*i+1] = buf[2*i+1] 58 | b[4*i+2] = buf[2*i] 59 | b[4*i+3] = buf[2*i+1] 60 | } 61 | case !s.mono && s.eight: 62 | for i := 0; i < (n >> 1); i++ { 63 | v0 := int16(int(buf[2*i])*0x101 - (1 << 15)) 64 | v1 := int16(int(buf[2*i+1])*0x101 - (1 << 15)) 65 | b[4*i] = uint8(v0) 66 | b[4*i+1] = uint8(v0 >> 8) 67 | b[4*i+2] = uint8(v1) 68 | b[4*i+3] = uint8(v1 >> 8) 69 | } 70 | } 71 | if s.mono { 72 | n <<= 1 73 | } 74 | if s.eight { 75 | n <<= 1 76 | } 77 | return n, err 78 | } 79 | 80 | // Seek func. 81 | func (s *Stereo16) Seek(offset int64, whence int) (int64, error) { 82 | if s.mono { 83 | offset >>= 1 84 | } 85 | if s.eight { 86 | offset >>= 1 87 | } 88 | return s.source.Seek(offset, whence) 89 | } 90 | 91 | // ------------------------------------------------------------------------------------- 92 | 93 | type stereo16Decoded struct { 94 | Stereo16 95 | d audio.Decoded 96 | } 97 | 98 | // ToStereo16 convert an audio into stereo16. 99 | func ToStereo16(d audio.Decoded) audio.Decoded { 100 | mono := (d.Channels() == 1) 101 | eight := (d.BytesPerSample() == 1) 102 | if mono || eight { 103 | s := NewStereo16(d, mono, eight) 104 | return &stereo16Decoded{*s, d} 105 | } 106 | return d 107 | } 108 | 109 | // SampleRate returns the sample rate like 44100. 110 | func (p *stereo16Decoded) SampleRate() int { 111 | return p.d.SampleRate() 112 | } 113 | 114 | // Channels returns the number of channels. One channel is mono playback. 115 | // Two channels are stereo playback. No other values are supported. 116 | func (p *stereo16Decoded) Channels() int { 117 | return 2 118 | } 119 | 120 | // BytesPerSample returns the number of bytes per sample per channel. 121 | // The usual value is 2. Only values 1 and 2 are supported. 122 | func (p *stereo16Decoded) BytesPerSample() int { 123 | return 2 124 | } 125 | 126 | // Length returns the total size in bytes. It returns -1 when the total size is not 127 | // available. e.g. when the given source is not io.Seeker. 128 | func (p *stereo16Decoded) Length() int64 { 129 | d := p.d 130 | return (d.Length() << 2) / int64(d.Channels()*d.BytesPerSample()) 131 | } 132 | 133 | // ------------------------------------------------------------------------------------- 134 | -------------------------------------------------------------------------------- /format.go: -------------------------------------------------------------------------------- 1 | package audio 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "sync" 7 | "sync/atomic" 8 | 9 | "github.com/qiniu/x/bufiox" 10 | ) 11 | 12 | // ErrFormat indicates that decoding encountered an unknown format. 13 | var ErrFormat = errors.New("audio: unknown format") 14 | 15 | // ------------------------------------------------------------------------------------- 16 | 17 | // Decoded represents a decoded audio. 18 | type Decoded interface { 19 | io.ReadSeeker 20 | 21 | // SampleRate returns the sample rate like 44100. 22 | SampleRate() int 23 | 24 | // Channels returns the number of channels. One channel is mono playback. 25 | // Two channels are stereo playback. No other values are supported. 26 | Channels() int 27 | 28 | // BytesPerSample returns the number of bytes per sample per channel. 29 | // The usual value is 2. Only values 1 and 2 are supported. 30 | BytesPerSample() int 31 | 32 | // Length returns the total size in bytes. It returns -1 when the total size is not 33 | // available. e.g. when the given source is not io.Seeker. 34 | Length() int64 35 | } 36 | 37 | // Config holds an audio's configurations. 38 | type Config struct { 39 | // TODO: 40 | } 41 | 42 | // DecodeFunc prototype. 43 | type DecodeFunc = func(io.ReadSeeker) (Decoded, error) 44 | 45 | // DecodeConfigFunc prototype. 46 | type DecodeConfigFunc = func(io.ReadSeeker) (Config, error) 47 | 48 | // ------------------------------------------------------------------------------------- 49 | 50 | // A format holds an audio format's name, magic header and how to decode it. 51 | type format struct { 52 | name, magic string 53 | decode DecodeFunc 54 | decodeConfig DecodeConfigFunc 55 | } 56 | 57 | // Formats is the list of registered formats. 58 | var ( 59 | formatsMu sync.Mutex 60 | atomicFormats atomic.Value 61 | ) 62 | 63 | // RegisterFormat registers an audio format for use by Decode. 64 | // Name is the name of the format, like "mp3" or "wav". 65 | // Magic is the magic prefix that identifies the format's encoding. The magic 66 | // string can contain "?" wildcards that each match any one byte. 67 | // Decode is the function that decodes the encoded audio. 68 | // decodeCfg is the function that decodes just its configuration. 69 | func RegisterFormat(name, magic string, decode DecodeFunc, decodeCfg DecodeConfigFunc) { 70 | formatsMu.Lock() 71 | formats, _ := atomicFormats.Load().([]format) 72 | atomicFormats.Store(append(formats, format{name, magic, decode, decodeCfg})) 73 | formatsMu.Unlock() 74 | } 75 | 76 | // ------------------------------------------------------------------------------------- 77 | 78 | // A reader is an io.Reader that can also peek ahead. 79 | type reader interface { 80 | io.ReadSeeker 81 | Peek(int) ([]byte, error) 82 | } 83 | 84 | // asReader converts an io.ReadSeeker to a reader. 85 | func asReader(r io.ReadSeeker) reader { 86 | if rr, ok := r.(reader); ok { 87 | return rr 88 | } 89 | return bufiox.NewReader(r) 90 | } 91 | 92 | // Match reports whether magic matches b. Magic may contain "?" wildcards. 93 | func match(magic string, b []byte) bool { 94 | if len(magic) != len(b) { 95 | return false 96 | } 97 | for i, c := range b { 98 | if magic[i] != c && magic[i] != '?' { 99 | return false 100 | } 101 | } 102 | return true 103 | } 104 | 105 | // Sniff determines the format of r's data. 106 | func sniff(r reader) format { 107 | formats, _ := atomicFormats.Load().([]format) 108 | for _, f := range formats { 109 | b, err := r.Peek(len(f.magic)) 110 | if err == nil && match(f.magic, b) { 111 | return f 112 | } 113 | } 114 | return format{} 115 | } 116 | 117 | // Decode decodes an audio that has been encoded in a registered format. 118 | // The string returned is the format name used during format registration. 119 | // Format registration is typically done by an init function in the codec- 120 | // specific package. 121 | func Decode(r io.ReadSeeker) (Decoded, string, error) { 122 | rr := asReader(r) 123 | f := sniff(rr) 124 | if f.decode == nil { 125 | return nil, "", ErrFormat 126 | } 127 | m, err := f.decode(rr) 128 | return m, f.name, err 129 | } 130 | 131 | // DecodeConfig decodes the basic configurations of an audio that has 132 | // been encoded in a registered format. The string returned is the format name 133 | // used during format registration. Format registration is typically done by 134 | // an init function in the codec-specific package. 135 | func DecodeConfig(r io.ReadSeeker) (Config, string, error) { 136 | rr := asReader(r) 137 | f := sniff(rr) 138 | if f.decodeConfig == nil { 139 | return Config{}, "", ErrFormat 140 | } 141 | c, err := f.decodeConfig(rr) 142 | return c, f.name, err 143 | } 144 | 145 | // ------------------------------------------------------------------------------------- 146 | -------------------------------------------------------------------------------- /format_test.go: -------------------------------------------------------------------------------- 1 | package audio_test 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/qiniu/audio" 8 | _ "github.com/qiniu/audio/mp3" 9 | ) 10 | 11 | func Test(t *testing.T) { 12 | b := bytes.NewReader(nil) 13 | audio.DecodeConfig(b) 14 | } 15 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/qiniu/audio 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/hajimehoshi/go-mp3 v0.3.2 7 | github.com/hajimehoshi/oto v1.0.1 8 | github.com/qiniu/x v1.11.5 9 | ) 10 | 11 | replace github.com/hajimehoshi/oto => github.com/hajimehoshi/oto v1.0.1 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/hajimehoshi/go-mp3 v0.3.2 h1:xSYNE2F3lxtOu9BRjCWHHceg7S91IHfXfXp5+LYQI7s= 2 | github.com/hajimehoshi/go-mp3 v0.3.2/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= 3 | github.com/hajimehoshi/oto v1.0.1 h1:8AMnq0Yr2YmzaiqTg/k1Yzd6IygUGk2we9nmjgbgPn4= 4 | github.com/hajimehoshi/oto v1.0.1/go.mod h1:wovJ8WWMfFKvP587mhHgot/MBr4DnNy9m6EepeVGnos= 5 | github.com/qiniu/x v1.11.5 h1:TYr5cl4g2yoHAZeDK4MTjKF6CMoG+IHlCDvvM5qym6U= 6 | github.com/qiniu/x v1.11.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs= 7 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8 h1:idBdZTd9UioThJp8KpM/rTSinK/ChZFBE43/WtIy8zg= 8 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 9 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0= 10 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 11 | golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6 h1:vyLBGJPIl9ZYbcQFM2USFmJBK6KI+t+z6jL0lbwjrnc= 12 | golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 13 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 14 | golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872 h1:cGjJzUd8RgBw428LXP65YXni0aiGNA4Bl+ls8SmLOm8= 15 | golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 16 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 17 | -------------------------------------------------------------------------------- /mp3/decode.go: -------------------------------------------------------------------------------- 1 | package mp3 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/qiniu/audio" 7 | "github.com/qiniu/x/bufiox" 8 | 9 | mp3 "github.com/hajimehoshi/go-mp3" 10 | ) 11 | 12 | // ------------------------------------------------------------------------------------- 13 | 14 | type decoded struct { 15 | mp3.Decoder 16 | } 17 | 18 | // Channels returns the number of channels. One channel is mono playback. 19 | // Two channels are stereo playback. No other values are supported. 20 | func (p *decoded) Channels() int { 21 | return 2 22 | } 23 | 24 | // BytesPerSample returns the number of bytes per sample per channel. 25 | // The usual value is 2. Only values 1 and 2 are supported. 26 | func (p *decoded) BytesPerSample() int { 27 | return 2 28 | } 29 | 30 | // Decode decodes a mp3 audio. 31 | func Decode(r io.ReadSeeker) (audio.Decoded, error) { 32 | b := bufiox.NewReader(r) 33 | dec, err := mp3.NewDecoder(b) 34 | if err != nil { 35 | return nil, err 36 | } 37 | return &decoded{Decoder: *dec}, err 38 | } 39 | 40 | // DecodeConfig is not implemented. 41 | func DecodeConfig(r io.ReadSeeker) (cfg audio.Config, err error) { 42 | err = audio.ErrFormat 43 | return 44 | } 45 | 46 | func init() { 47 | audio.RegisterFormat("mp3", "ID3", Decode, DecodeConfig) 48 | audio.RegisterFormat("mp3", "\xff\xfb", Decode, DecodeConfig) 49 | } 50 | 51 | // ------------------------------------------------------------------------------------- 52 | -------------------------------------------------------------------------------- /ogg/ogg_decode.go: -------------------------------------------------------------------------------- 1 | package ogg 2 | 3 | /* 4 | import ( 5 | "io" 6 | 7 | "github.com/qiniu/audio" 8 | "github.com/qiniu/audio/convert" 9 | ) 10 | 11 | type decoder interface { 12 | Read([]float32) (int, error) 13 | SetPosition(int64) error 14 | Length() int64 15 | Channels() int 16 | SampleRate() int 17 | } 18 | 19 | type decoded struct { 20 | totalBytes int 21 | posInBytes int 22 | decoder decoder 23 | decoderr io.Reader 24 | } 25 | 26 | func (d *decoded) Length() int64 { 27 | return 0 // TODO 28 | } 29 | 30 | func (d *decoded) Read(b []byte) (int, error) { 31 | if d.decoderr == nil { 32 | d.decoderr = convert.NewReaderFromFloat32Reader(d.decoder) 33 | } 34 | 35 | l := d.totalBytes - d.posInBytes 36 | if l > len(b) { 37 | l = len(b) 38 | } 39 | if l < 0 { 40 | return 0, io.EOF 41 | } 42 | 43 | retry: 44 | n, err := d.decoderr.Read(b[:l]) 45 | if err != nil && err != io.EOF { 46 | return 0, err 47 | } 48 | if n == 0 && l > 0 && err != io.EOF { 49 | // When l is too small, decoder's Read might return 0 for a while. Let's retry. 50 | goto retry 51 | } 52 | 53 | d.posInBytes += n 54 | if d.posInBytes == d.totalBytes || err == io.EOF { 55 | return n, io.EOF 56 | } 57 | return n, nil 58 | } 59 | 60 | func (d *decoded) Seek(offset int64, whence int) (int64, error) { 61 | next := int64(0) 62 | switch whence { 63 | case io.SeekStart: 64 | next = offset 65 | case io.SeekCurrent: 66 | next = int64(d.posInBytes) + offset 67 | case io.SeekEnd: 68 | next = int64(d.totalBytes) + offset 69 | } 70 | // pos should be always even 71 | next = next / 2 * 2 72 | d.posInBytes = int(next) 73 | d.decoder.SetPosition(next / int64(d.decoder.Channels()) / 2) 74 | d.decoderr = nil 75 | return next, nil 76 | } 77 | 78 | // SampleRate returns the sample rate like 44100. 79 | func (d *decoded) SampleRate() int { 80 | return d.decoder.SampleRate() 81 | } 82 | 83 | // Channels func. 84 | func (d *decoded) Channels() int { 85 | return d.decoder.Channels() 86 | } 87 | 88 | // Decode accepts an ogg stream and returns a decorded audio. 89 | func Decode(in io.ReadSeeker) (audio.Decoded, error) { 90 | r, err := newDecoder(in) 91 | if err != nil { 92 | return nil, err 93 | } 94 | d := &decoded{ 95 | // TODO: r.Length() returns 0 when the format is unknown. 96 | // Should we check that? 97 | totalBytes: int(r.Length()) * r.Channels() * 2, // 2 means 16bit per sample. 98 | posInBytes: 0, 99 | decoder: r, 100 | } 101 | return d, nil 102 | } 103 | 104 | // DecodeConfig is not implemented. 105 | func DecodeConfig(r io.ReadSeeker) (cfg audio.Config, err error) { 106 | err = audio.ErrFormat 107 | return 108 | } 109 | 110 | func init() { 111 | audio.RegisterFormat("ogg", "OggS", Decode, DecodeConfig) 112 | } 113 | */ 114 | // ------------------------------------------------------------------------------------- 115 | -------------------------------------------------------------------------------- /ogg/ogg_notgopherjs.go: -------------------------------------------------------------------------------- 1 | // +build !js wasm 2 | 3 | package ogg 4 | 5 | /* 6 | import ( 7 | "io" 8 | 9 | "github.com/jfreymuth/oggvorbis" 10 | ) 11 | 12 | func newDecoder(in io.ReadSeeker) (decoder, error) { 13 | return oggvorbis.NewReader(in) 14 | } 15 | */ 16 | -------------------------------------------------------------------------------- /wav/adpcm/decode.go: -------------------------------------------------------------------------------- 1 | package adpcm 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/qiniu/audio" 8 | "github.com/qiniu/audio/wav" 9 | "github.com/qiniu/x/bufiox" 10 | ) 11 | 12 | var ( 13 | errNotImpl = errors.New("not impl") 14 | ) 15 | 16 | // ------------------------------------------------------------------------------------- 17 | 18 | type decoded struct { 19 | src *bufiox.Reader 20 | 21 | samples []byte 22 | remaining int 23 | 24 | length int64 25 | sampleRate int 26 | channelNum int 27 | blockAlign int 28 | } 29 | 30 | func newDecoded(src *bufiox.Reader, cfg *wav.Config) *decoded { 31 | samplesPerBlock := cfg.SamplesPerBlock() 32 | return &decoded{ 33 | src: bufiox.NewReaderSize(src, cfg.BlockAlign), 34 | sampleRate: cfg.SampleRate, 35 | channelNum: cfg.Channels, 36 | blockAlign: cfg.BlockAlign, 37 | samples: make([]byte, samplesPerBlock<<1), 38 | length: cfg.DataSize / int64(cfg.BlockAlign) * int64(samplesPerBlock<<1), 39 | } 40 | } 41 | 42 | func (p *decoded) nextBlock() error { 43 | block, err := p.src.Peek(p.blockAlign) 44 | if err != nil { 45 | return err 46 | } 47 | loadBlock(p.channelNum, block, p.samples) 48 | p.src.Discard(p.blockAlign) 49 | p.remaining = p.blockAlign 50 | return nil 51 | } 52 | 53 | func (p *decoded) Read(b []byte) (n int, err error) { 54 | if p.remaining <= 0 { 55 | if err = p.nextBlock(); err != nil { 56 | return 57 | } 58 | } 59 | n = copy(b, p.samples[p.blockAlign-p.remaining:]) 60 | p.remaining -= n 61 | return 62 | } 63 | 64 | func (p *decoded) Seek(offset int64, whence int) (newoff int64, err error) { 65 | return 0, errNotImpl 66 | } 67 | 68 | // Length returns the size of decoded stream in bytes. 69 | func (p *decoded) Length() int64 { 70 | return p.length 71 | } 72 | 73 | // SampleRate returns the sample rate like 44100. 74 | func (p *decoded) SampleRate() int { 75 | return p.sampleRate 76 | } 77 | 78 | // Channels returns the number of channels. One channel is mono playback. 79 | // Two channels are stereo playback. No other values are supported. 80 | func (p *decoded) Channels() int { 81 | return p.channelNum 82 | } 83 | 84 | // BytesPerSample returns the number of bytes per sample per channel. 85 | // The usual value is 2. Only values 1 and 2 are supported. 86 | func (p *decoded) BytesPerSample() int { 87 | return 2 88 | } 89 | 90 | // ------------------------------------------------------------------------------------- 91 | 92 | func decode(src *bufiox.Reader, cfg *wav.Config) (dec audio.Decoded, err error) { 93 | if cfg.BitsPerSample != 4 { 94 | return nil, fmt.Errorf("adpcm wav: bits per sample must be 4 but was %d", cfg.BitsPerSample) 95 | } 96 | d := newDecoded(src, cfg) 97 | return d, nil 98 | } 99 | 100 | const ( 101 | adpcmFormat = 0x11 102 | ) 103 | 104 | func init() { 105 | wav.RegisterFormat(adpcmFormat, decode) 106 | } 107 | 108 | // ------------------------------------------------------------------------------------- 109 | -------------------------------------------------------------------------------- /wav/adpcm/status.go: -------------------------------------------------------------------------------- 1 | package adpcm 2 | 3 | // ------------------------------------------------------------------------------------- 4 | 5 | var indexAdjust = [8]int{ 6 | -1, -1, -1, -1, 2, 4, 6, 8, 7 | } 8 | 9 | var stepTable = [89]int{ 10 | 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 11 | 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 12 | 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 13 | 130, 143, 157, 173, 190, 209, 230, 253, 279, 307, 14 | 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 15 | 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 16 | 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358, 17 | 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899, 18 | 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767, 19 | } 20 | 21 | type status struct { 22 | sample int 23 | index int 24 | } 25 | 26 | func (s *status) decodeSample(scode byte) int { 27 | // 将 scode 分离为数据和符号 28 | code := int(scode & 7) 29 | delta := ((stepTable[s.index] * code) >> 2) + (stepTable[s.index] >> 3) // 后面加的一项是为了减少误差 30 | if (scode & 8) != 0 { 31 | delta = -delta // 负数 32 | } 33 | s.sample += delta // 计算出当前的波形数据 34 | s.index += indexAdjust[code] 35 | if s.index < 0 { 36 | s.index = 0 37 | } else if s.index > 88 { 38 | s.index = 88 39 | } 40 | if s.sample > 32767 { 41 | return 32767 42 | } 43 | if s.sample < -32768 { 44 | return -32768 45 | } 46 | return s.sample 47 | } 48 | 49 | func (s *status) saveSample(samples []byte, idx int, scode byte) int { 50 | sample := s.decodeSample(scode) 51 | return saveSample(samples, idx, sample) 52 | } 53 | 54 | func saveSample(samples []byte, idx int, sample int) int { 55 | samples[idx] = byte(sample) 56 | samples[idx+1] = byte(sample >> 8) 57 | return idx + 2 58 | } 59 | 60 | // ------------------------------------------------------------------------------------- 61 | 62 | func loadStatus(b []byte) *status { 63 | return &status{ 64 | sample: int(int16(b[0]) | (int16(b[1]) << 8)), 65 | index: int(b[2]), 66 | } 67 | } 68 | 69 | func loadBlock(channels int, block []byte, samples []byte) { 70 | status1 := loadStatus(block) 71 | status2 := status1 72 | saveSample(samples, 0, status1.sample) 73 | if channels > 1 { 74 | status2 = loadStatus(block[4:]) 75 | saveSample(samples, 2, status2.sample) 76 | } 77 | idx := channels << 1 78 | for _, code := range block[(channels << 2):] { 79 | idx = status1.saveSample(samples, idx, code&0xf) 80 | idx = status2.saveSample(samples, idx, code>>4) 81 | } 82 | } 83 | 84 | // ------------------------------------------------------------------------------------- 85 | -------------------------------------------------------------------------------- /wav/wav_decode.go: -------------------------------------------------------------------------------- 1 | package wav 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "sync" 9 | "sync/atomic" 10 | 11 | "github.com/qiniu/audio" 12 | "github.com/qiniu/x/bufiox" 13 | ) 14 | 15 | // ------------------------------------------------------------------------------------- 16 | 17 | // Config type. 18 | type Config struct { 19 | Format int // Format Tag 20 | SampleRate int 21 | DataSize int64 22 | HeaderSize int64 23 | Channels int 24 | BitsPerSample int 25 | BlockAlign int 26 | } 27 | 28 | // SamplesPerBlock returns samples per block. 29 | func (p *Config) SamplesPerBlock() int { 30 | return (p.BlockAlign << 1) - p.Channels*7 31 | } 32 | 33 | // DecodeFunc prototype. 34 | type DecodeFunc = func(r *bufiox.Reader, cfg *Config) (audio.Decoded, error) 35 | 36 | // A format holds an audio format's name, magic header and how to decode it. 37 | type format struct { 38 | tag int 39 | decode DecodeFunc 40 | } 41 | 42 | // Formats is the list of registered formats. 43 | var ( 44 | formatsMu sync.Mutex 45 | atomicFormats atomic.Value 46 | ) 47 | 48 | // RegisterFormat registers a wav decoder extension. 49 | func RegisterFormat(tag int, decode DecodeFunc) { 50 | formatsMu.Lock() 51 | formats, _ := atomicFormats.Load().([]format) 52 | atomicFormats.Store(append(formats, format{tag, decode})) 53 | formatsMu.Unlock() 54 | } 55 | 56 | func decodeEx(r *bufiox.Reader, cfg *Config) (audio.Decoded, error) { 57 | formats, _ := atomicFormats.Load().([]format) 58 | for _, f := range formats { 59 | if f.tag == cfg.Format { 60 | return f.decode(r, cfg) 61 | } 62 | } 63 | return nil, audio.ErrFormat 64 | } 65 | 66 | // ------------------------------------------------------------------------------------- 67 | 68 | type stream struct { 69 | src *bufiox.Reader 70 | headerSize int64 71 | dataSize int64 72 | remaining int64 73 | channelNum int 74 | bytesPerSample int 75 | sampleRate int 76 | } 77 | 78 | func (s *stream) Read(p []byte) (int, error) { 79 | if s.remaining < int64(len(p)) { 80 | if s.remaining <= 0 { 81 | return 0, io.EOF 82 | } 83 | p = p[0:s.remaining] 84 | } 85 | n, err := s.src.Read(p) 86 | s.remaining -= int64(n) 87 | return n, err 88 | } 89 | 90 | func (s *stream) Seek(offset int64, whence int) (int64, error) { 91 | switch whence { 92 | case io.SeekStart: 93 | offset = offset + s.headerSize 94 | case io.SeekCurrent: 95 | case io.SeekEnd: 96 | offset = s.headerSize + s.dataSize + offset 97 | whence = io.SeekStart 98 | } 99 | n, err := s.src.Seek(offset, whence) 100 | if err != nil { 101 | return 0, err 102 | } 103 | if n-s.headerSize < 0 { 104 | return 0, fmt.Errorf("wav: invalid offset") 105 | } 106 | s.remaining = s.dataSize - (n - s.headerSize) 107 | // There could be a tail in wav file. 108 | if s.remaining < 0 { 109 | s.remaining = 0 110 | return s.dataSize, nil 111 | } 112 | return n - s.headerSize, nil 113 | } 114 | 115 | // Length returns the size of decoded stream in bytes. 116 | func (s *stream) Length() int64 { 117 | return s.dataSize 118 | } 119 | 120 | // SampleRate returns the sample rate like 44100. 121 | func (s *stream) SampleRate() int { 122 | return s.sampleRate 123 | } 124 | 125 | // Channels returns the number of channels. One channel is mono playback. 126 | // Two channels are stereo playback. No other values are supported. 127 | func (s *stream) Channels() int { 128 | return s.channelNum 129 | } 130 | 131 | // BytesPerSample returns the number of bytes per sample per channel. 132 | // The usual value is 2. Only values 1 and 2 are supported. 133 | func (s *stream) BytesPerSample() int { 134 | return s.bytesPerSample 135 | } 136 | 137 | var ( 138 | errInvalidFormat = errors.New("wav: invalid header") 139 | errNotRIFF = errors.New("wav: invalid header: 'RIFF' not found") 140 | errNotWAVE = errors.New("wav: invalid header: 'WAVE' not found") 141 | ) 142 | 143 | func decode(src *bufiox.Reader) (audio.Decoded, error) { 144 | b := make([]byte, 16) 145 | if _, err := src.ReadFull(b[:12]); err != nil { 146 | return nil, errInvalidFormat 147 | } 148 | if !bytes.Equal(b[0:4], []byte("RIFF")) { 149 | return nil, errNotRIFF 150 | } 151 | if !bytes.Equal(b[8:12], []byte("WAVE")) { 152 | return nil, errNotWAVE 153 | } 154 | 155 | // Read chunks 156 | cfg := Config{HeaderSize: int64(len(b))} 157 | chunks: 158 | for { 159 | if _, err := src.ReadFull(b[:8]); err != nil { 160 | return nil, errInvalidFormat 161 | } 162 | cfg.HeaderSize += 8 163 | size := int64(b[4]) | int64(b[5])<<8 | int64(b[6])<<16 | int64(b[7])<<24 164 | switch { 165 | case bytes.Equal(b[0:4], []byte("fmt ")): 166 | if size < 16 { // Size of 'fmt' header is usually 16, but can be more than 16. 167 | return nil, errInvalidFormat 168 | } 169 | buf, err := src.Peek(int(size)) 170 | if err != nil { 171 | return nil, errInvalidFormat 172 | } 173 | cfg.Format = int(buf[0]) | int(buf[1])<<8 174 | cfg.Channels = int(buf[2]) | int(buf[3])<<8 175 | switch cfg.Channels { 176 | case 1, 2: 177 | default: 178 | return nil, fmt.Errorf("wav: channel num must be 1 or 2 but was %d", cfg.Channels) 179 | } 180 | cfg.BlockAlign = int(buf[12]) | int(buf[13])<<8 181 | cfg.BitsPerSample = int(buf[14]) | int(buf[15])<<8 182 | cfg.SampleRate = int(buf[4]) | int(buf[5])<<8 | int(buf[6])<<16 | int(buf[7])<<24 183 | src.Discard(int(size)) 184 | cfg.HeaderSize += size 185 | case bytes.Equal(b[0:4], []byte("data")): 186 | cfg.DataSize = size 187 | break chunks 188 | default: 189 | if _, err := src.Discard(int(size)); err != nil { 190 | return nil, err 191 | } 192 | cfg.HeaderSize += size 193 | } 194 | } 195 | if cfg.Format != 1 { 196 | return decodeEx(src, &cfg) 197 | } 198 | if cfg.BitsPerSample != 8 && cfg.BitsPerSample != 16 { 199 | return nil, fmt.Errorf("wav: bits per sample must be 8 or 16 but was %d", cfg.BitsPerSample) 200 | } 201 | s := &stream{ 202 | src: src, 203 | headerSize: cfg.HeaderSize, 204 | dataSize: cfg.DataSize, 205 | remaining: cfg.DataSize, 206 | sampleRate: int(cfg.SampleRate), 207 | channelNum: cfg.Channels, 208 | bytesPerSample: cfg.BitsPerSample >> 3, 209 | } 210 | return s, nil 211 | } 212 | 213 | // ------------------------------------------------------------------------------------- 214 | 215 | // Decode decodes a wav audio. 216 | func Decode(r io.ReadSeeker) (audio.Decoded, error) { 217 | b := bufiox.NewReader(r) 218 | return decode(b) 219 | } 220 | 221 | // DecodeConfig is not implemented. 222 | func DecodeConfig(r io.ReadSeeker) (cfg audio.Config, err error) { 223 | err = audio.ErrFormat 224 | return 225 | } 226 | 227 | func init() { 228 | audio.RegisterFormat("wav", "RIFF????WAVE", Decode, DecodeConfig) 229 | } 230 | 231 | // ------------------------------------------------------------------------------------- 232 | --------------------------------------------------------------------------------