audio/vorbis: Use stb_vorbis.c on browsers

This change adds a Ogg/Vorbis decoder stb_vorbis.c usage that is
compiled by Emscripten as WebAssembly.

Fixes #623
This commit is contained in:
Hajime Hoshi 2018-07-03 21:09:12 +09:00
parent 21fd6a2edc
commit 992f6c767a
15 changed files with 7832 additions and 5 deletions

4
audio/vorbis/internal/stb/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.wasm
*.wasm.map
*.wast

View File

@ -0,0 +1,2 @@
emcc -Os -o stbvorbis.js -g -DSTB_VORBIS_NO_STDIO -s WASM=1 -s EXPORTED_FUNCTIONS='["_stb_vorbis_decode_memory"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall","cwrap"]' -s ALLOW_MEMORY_GROWTH=1 stb_vorbis.c
go run genwasmjs.go < stbvorbis.wasm > wasm.js

View File

@ -0,0 +1,47 @@
// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package stb
import (
"fmt"
"github.com/gopherjs/gopherwasm/js"
)
func init() {
// Eval wasm.js first to set the Wasm binary to Module.
js.Global().Get("window").Call("eval", string(wasm_js))
js.Global().Get("window").Call("eval", string(stbvorbis_js))
js.Global().Get("window").Call("eval", string(decode_js))
ch := make(chan struct{})
js.Global().Get("_ebiten").Call("initializeVorbisDecoder", js.NewCallback(func([]js.Value) {
close(ch)
}))
<-ch
}
func DecodeVorbis(buf []byte) ([]int16, int, int, error) {
r := js.Global().Get("_ebiten").Call("decodeVorbis", buf)
if r == js.Null() {
return nil, 0, 0, fmt.Errorf("audio/vorbis/internal/stb: decode failed")
}
data := make([]int16, r.Get("data").Get("length").Int())
// TODO: Use js.TypeArrayOf
arr := js.ValueOf(data)
arr.Call("set", r.Get("data"))
return data, r.Get("channels").Int(), r.Get("sampleRate").Int(), nil
}

View File

@ -0,0 +1,80 @@
// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
var _ebiten = {};
(() => {
var decodeMemory = null;
var vorbisDecoderInitialized = null;
_ebiten.initializeVorbisDecoder = (callback) => {
Module.run();
vorbisDecoderInitialized = callback;
};
Module.onRuntimeInitialized = () => {
decodeMemory = Module.cwrap('stb_vorbis_decode_memory', 'number', ['number', 'number', 'number', 'number', 'number']);
if (vorbisDecoderInitialized) {
vorbisDecoderInitialized();
}
}
function arrayToHeap(typedArray){
const ptr = Module._malloc(typedArray.byteLength);
const heapBytes = new Uint8Array(Module.HEAPU8.buffer, ptr, typedArray.byteLength);
heapBytes.set(new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength));
return heapBytes;
}
function ptrToInt32(ptr) {
const a = new Int32Array(Module.HEAPU8.buffer, ptr, 1);
return a[0];
}
function ptrToFloat32(ptr) {
const a = new Float32Array(Module.HEAPU8.buffer, ptr, 1);
return a[0];
}
function ptrToInt16s(ptr, length) {
const buf = new ArrayBuffer(length * Int16Array.BYTES_PER_ELEMENT);
const copied = new Int16Array(buf);
copied.set(new Int16Array(Module.HEAPU8.buffer, ptr, length));
return copied;
}
_ebiten.decodeVorbis = (buf) => {
const copiedBuf = arrayToHeap(buf);
const channelsPtr = Module._malloc(4);
const sampleRatePtr = Module._malloc(4);
const outputPtr = Module._malloc(4);
const length = decodeMemory(copiedBuf.byteOffset, copiedBuf.length, channelsPtr, sampleRatePtr, outputPtr);
if (length < 0) {
return null;
}
const channels = ptrToInt32(channelsPtr);
const result = {
data: ptrToInt16s(ptrToInt32(outputPtr), length * channels),
channels: channels,
sampleRate: ptrToInt32(sampleRatePtr),
};
Module._free(copiedBuf.byteOffset);
Module._free(channelsPtr);
Module._free(sampleRatePtr);
Module._free(ptrToInt32(outputPtr));
Module._free(outputPtr);
return result;
};
})();

View File

@ -0,0 +1,6 @@
// Code generated by file2byteslice. DO NOT EDIT.
// (gofmt is fine after generating)
package stb
var decode_js = []byte("// Copyright 2018 The Ebiten Authors\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n// http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n\nvar _ebiten = {};\n\n(() => {\n var decodeMemory = null;\n var vorbisDecoderInitialized = null;\n\n _ebiten.initializeVorbisDecoder = (callback) => {\n Module.run();\n vorbisDecoderInitialized = callback;\n };\n\n Module.onRuntimeInitialized = () => {\n decodeMemory = Module.cwrap('stb_vorbis_decode_memory', 'number', ['number', 'number', 'number', 'number', 'number']);\n if (vorbisDecoderInitialized) {\n vorbisDecoderInitialized();\n }\n }\n\n function arrayToHeap(typedArray){\n const ptr = Module._malloc(typedArray.byteLength);\n const heapBytes = new Uint8Array(Module.HEAPU8.buffer, ptr, typedArray.byteLength);\n heapBytes.set(new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength));\n return heapBytes;\n }\n\n function ptrToInt32(ptr) {\n const a = new Int32Array(Module.HEAPU8.buffer, ptr, 1);\n return a[0];\n }\n\n function ptrToFloat32(ptr) {\n const a = new Float32Array(Module.HEAPU8.buffer, ptr, 1);\n return a[0];\n }\n\n function ptrToInt16s(ptr, length) {\n const buf = new ArrayBuffer(length * Int16Array.BYTES_PER_ELEMENT);\n const copied = new Int16Array(buf);\n copied.set(new Int16Array(Module.HEAPU8.buffer, ptr, length));\n return copied;\n }\n\n _ebiten.decodeVorbis = (buf) => {\n const copiedBuf = arrayToHeap(buf);\n const channelsPtr = Module._malloc(4);\n const sampleRatePtr = Module._malloc(4);\n const outputPtr = Module._malloc(4);\n const length = decodeMemory(copiedBuf.byteOffset, copiedBuf.length, channelsPtr, sampleRatePtr, outputPtr);\n if (length < 0) {\n return null;\n }\n const channels = ptrToInt32(channelsPtr);\n const result = {\n data: ptrToInt16s(ptrToInt32(outputPtr), length * channels),\n channels: channels,\n sampleRate: ptrToInt32(sampleRatePtr),\n };\n\n Module._free(copiedBuf.byteOffset);\n Module._free(channelsPtr);\n Module._free(sampleRatePtr);\n Module._free(ptrToInt32(outputPtr));\n Module._free(outputPtr);\n return result;\n };\n})();\n")

View File

@ -0,0 +1,20 @@
// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package stb
//go:generate bash build.sh
//go:generate file2byteslice -package=stb -input=decode.js -output=decodejs_file_js.go -var=decode_js
//go:generate file2byteslice -package=stb -input=stbvorbis.js -output=stbvorbisjs_file_js.go -var=stbvorbis_js
//go:generate file2byteslice -package=stb -input=wasm.js -output=wasmjs_file_js.go -var=wasm_js

View File

@ -0,0 +1,43 @@
// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build ignore
package main
import (
"encoding/base64"
"fmt"
"io/ioutil"
"os"
)
func run() error {
bin, err := ioutil.ReadAll(os.Stdin)
if err != nil {
return err
}
fmt.Println("// Code generated by genwasmjs.go. DO NOT EDIT.")
fmt.Println("")
fmt.Println("var Module = typeof Module !== 'undefined' ? Module : {};")
fmt.Printf("Module['wasmBinary'] = Uint8Array.from(atob(%q), c => c.charCodeAt(0));\n", base64.StdEncoding.EncodeToString(bin))
return nil
}
func main() {
if err := run(); err != nil {
panic(err)
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -20,8 +20,6 @@ import (
"io"
"runtime"
"github.com/jfreymuth/oggvorbis"
"github.com/hajimehoshi/ebiten/audio"
"github.com/hajimehoshi/ebiten/audio/internal/convert"
)
@ -63,13 +61,20 @@ func (s *Stream) Size() int64 {
return s.Length()
}
type decoder interface {
Read([]float32) (int, error)
Length() int64
Channels() int
SampleRate() int
}
type decoded struct {
data []float32
totalBytes int
readBytes int
posInBytes int
source io.Closer
decoder *oggvorbis.Reader
decoder decoder
}
func (d *decoded) readUntil(posInBytes int) error {
@ -93,7 +98,6 @@ func (d *decoded) readUntil(posInBytes int) error {
if err != nil {
return err
}
runtime.Gosched()
}
return nil
}
@ -157,7 +161,7 @@ func (d *decoded) Length() int64 {
// decode accepts an ogg stream and returns a decorded stream.
func decode(in audio.ReadSeekCloser) (*decoded, int, int, error) {
r, err := oggvorbis.NewReader(in)
r, err := newDecoder(in)
if err != nil {
return nil, 0, 0, err
}

72
audio/vorbis/vorbis_js.go Normal file
View File

@ -0,0 +1,72 @@
// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build js
package vorbis
import (
"io"
"io/ioutil"
"github.com/hajimehoshi/ebiten/audio"
"github.com/hajimehoshi/ebiten/audio/vorbis/internal/stb"
)
type decoderImpl struct {
data []int16
channels int
sampleRate int
}
func (d *decoderImpl) Read(buf []float32) (int, error) {
if len(d.data) == 0 {
return 0, io.EOF
}
n := len(buf)
if n > len(d.data) {
n = len(d.data)
}
for i := 0; i < n; i++ {
buf[i] = float32(d.data[i]) / 32768
}
d.data = d.data[n:]
return n, nil
}
func (d *decoderImpl) Length() int64 {
return int64(len(d.data) / d.channels)
}
func (d *decoderImpl) Channels() int {
return d.channels
}
func (d *decoderImpl) SampleRate() int {
return d.sampleRate
}
func newDecoder(in audio.ReadSeekCloser) (decoder, error) {
buf, err := ioutil.ReadAll(in)
if err != nil {
return nil, err
}
data, channels, sampleRate, err := stb.DecodeVorbis(buf)
if err != nil {
return nil, err
}
return &decoderImpl{data, channels, sampleRate}, nil
}

View File

@ -0,0 +1,27 @@
// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build !js
package vorbis
import (
"github.com/jfreymuth/oggvorbis"
"github.com/hajimehoshi/ebiten/audio"
)
func newDecoder(in audio.ReadSeekCloser) (decoder, error) {
return oggvorbis.NewReader(in)
}