audio/internal/reader: Move the driver part to Oto v2

Closes #1779
This commit is contained in:
Hajime Hoshi 2021-08-22 01:54:02 +09:00
parent 7a5254f48f
commit 21e0768937
148 changed files with 16 additions and 16820 deletions

View File

@ -19,7 +19,7 @@ import (
"io/ioutil"
"sync"
"github.com/hajimehoshi/ebiten/v2/audio/internal/readerdriver"
"github.com/hajimehoshi/oto/v2"
)
type (
@ -32,7 +32,7 @@ type (
}
)
func (c *dummyContext) NewPlayer(r io.Reader) readerdriver.Player {
func (c *dummyContext) NewPlayer(r io.Reader) oto.Player {
return &dummyPlayer{
r: r,
volume: 1,

View File

@ -20,7 +20,7 @@ import (
"sync"
"syscall/js"
"github.com/hajimehoshi/ebiten/v2/audio/internal/readerdriver"
"github.com/hajimehoshi/oto/v2"
)
type Context struct {
@ -40,7 +40,7 @@ func NewContext(sampleRate int, channelNum, bitDepthInBytes int) *Context {
}
}
func (c *Context) NewPlayer(r io.Reader) readerdriver.Player {
func (c *Context) NewPlayer(r io.Reader) oto.Player {
cond := sync.NewCond(&sync.Mutex{})
onwritten := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
cond.Signal()

View File

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -1,54 +0,0 @@
# Oboe [![Build CI](https://github.com/google/oboe/workflows/Build%20CI/badge.svg)](https://github.com/google/oboe/actions)
[![Introduction to Oboe video](docs/images/getting-started-video.jpg)](https://www.youtube.com/watch?v=csfHAbr5ilI&list=PLWz5rJ2EKKc_duWv9IPNvx9YBudNMmLSa)
Oboe is a C++ library which makes it easy to build high-performance audio apps on Android. It was created primarily to allow developers to target a simplified API that works across multiple API levels back to API level 16 (Jelly Bean).
## Features
- Compatible with API 16 onwards - runs on 99% of Android devices
- Chooses the audio API (OpenSL ES on API 16+ or AAudio on API 27+) which will give the best audio performance on the target Android device
- Automatic latency tuning
- Modern C++ allowing you to write clean, elegant code
- Workarounds for some known issues
- [Used by popular apps and frameworks](docs/AppsUsingOboe.md)
## Documentation
- [Getting Started Guide](docs/GettingStarted.md)
- [Full Guide to Oboe](docs/FullGuide.md)
- [API reference](https://google.github.io/oboe/reference)
- [Tech Notes](docs/notes/)
- [History of Audio features/bugs by Android version](docs/AndroidAudioHistory.md)
- [Migration guide for apps using OpenSL ES](docs/OpenSLESMigration.md)
- [Frequently Asked Questions](docs/FAQ.md) (FAQ)
- [Our roadmap](https://github.com/google/oboe/milestones) - Vote on a feature/issue by adding a thumbs up to the first comment.
### Community
- Reddit: [r/androidaudiodev](https://www.reddit.com/r/androidaudiodev/)
- StackOverflow: [#oboe](https://stackoverflow.com/questions/tagged/oboe)
## Testing
- [**OboeTester** app for measuring latency, glitches, etc.](https://github.com/google/oboe/tree/master/apps/OboeTester/docs)
- [Oboe unit tests](https://github.com/google/oboe/tree/master/tests)
## Videos
- [Getting started with Oboe](https://www.youtube.com/playlist?list=PLWz5rJ2EKKc_duWv9IPNvx9YBudNMmLSa)
- [Low Latency Audio - Because Your Ears Are Worth It](https://www.youtube.com/watch?v=8vOf_fDtur4) (Android Dev Summit '18)
- [Winning on Android](https://www.youtube.com/watch?v=tWBojmBpS74) - How to optimize an Android audio app. (ADC '18)
## Sample code and apps
- Sample apps can be found in the [samples directory](samples).
- A complete "effects processor" app called FXLab can be found in the [apps/fxlab folder](apps/fxlab).
- Also check out the [Rhythm Game codelab](https://developer.android.com/codelabs/musicalgame-using-oboe?hl=en#0).
### Third party sample code
- [Ableton Link integration demo](https://github.com/jbloit/AndroidLinkAudio) (author: jbloit)
## Contributing
We would love to receive your pull requests. Before we can though, please read the [contributing](CONTRIBUTING.md) guidelines.
## Version history
View the [releases page](../../releases).
## License
[LICENSE](LICENSE)

View File

@ -1,195 +0,0 @@
// Copyright 2021 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.
#include "binding_android.h"
#include "_cgo_export.h"
#include "oboe_oboe_Oboe_android.h"
#include <condition_variable>
#include <memory>
#include <mutex>
#include <thread>
#include <vector>
namespace {
class Player;
class Stream : public oboe::AudioStreamDataCallback {
public:
// GetInstance returns the instance of Stream. Only one Stream object is used
// in one process. It is because multiple streams can be problematic in both
// AAudio and OpenSL (#1656, #1660).
static Stream &GetInstance();
const char *Play(int sample_rate, int channel_num, int bit_depth_in_bytes);
const char *Pause();
const char *Resume();
const char *Close();
const char *AppendBuffer(float *buf, size_t len);
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboe_stream,
void *audio_data,
int32_t num_frames) override;
private:
Stream();
void Loop(int num_frames);
int sample_rate_ = 0;
int channel_num_ = 0;
int bit_depth_in_bytes_ = 0;
std::shared_ptr<oboe::AudioStream> stream_;
// All the member variables other than the thread must be initialized before
// the thread.
std::vector<float> buf_;
std::mutex mutex_;
std::condition_variable cond_;
std::unique_ptr<std::thread> thread_;
};
Stream &Stream::GetInstance() {
static Stream *stream = new Stream();
return *stream;
}
const char *Stream::Play(int sample_rate, int channel_num,
int bit_depth_in_bytes) {
sample_rate_ = sample_rate;
channel_num_ = channel_num;
bit_depth_in_bytes_ = bit_depth_in_bytes;
// TODO: Enable bit_depth_in_bytes_ == 1
if (bit_depth_in_bytes_ != 2) {
return "bit_depth_in_bytes_ must be 2 but not";
}
if (!stream_) {
oboe::AudioStreamBuilder builder;
oboe::Result result =
builder.setDirection(oboe::Direction::Output)
->setPerformanceMode(oboe::PerformanceMode::LowLatency)
->setSharingMode(oboe::SharingMode::Shared)
->setFormat(oboe::AudioFormat::Float)
->setChannelCount(channel_num_)
->setSampleRate(sample_rate_)
->setDataCallback(this)
->openStream(stream_);
if (result != oboe::Result::OK) {
return oboe::convertToText(result);
}
}
if (stream_->getSharingMode() != oboe::SharingMode::Shared) {
return "oboe::SharingMode::Shared is not available";
}
int num_frames = stream_->getBufferSizeInFrames();
thread_ =
std::make_unique<std::thread>([this, num_frames]() { Loop(num_frames); });
// What if the buffer size is not enough?
if (oboe::Result result = stream_->start(); result != oboe::Result::OK) {
return oboe::convertToText(result);
}
return nullptr;
}
const char *Stream::Pause() {
if (!stream_) {
return nullptr;
}
if (oboe::Result result = stream_->pause(); result != oboe::Result::OK) {
return oboe::convertToText(result);
}
return nullptr;
}
const char *Stream::Resume() {
if (!stream_) {
return "Play is not called yet at Resume";
}
if (oboe::Result result = stream_->start(); result != oboe::Result::OK) {
return oboe::convertToText(result);
}
return nullptr;
}
const char *Stream::Close() {
// Nobody calls this so far.
if (!stream_) {
return nullptr;
}
if (oboe::Result result = stream_->stop(); result != oboe::Result::OK) {
return oboe::convertToText(result);
}
if (oboe::Result result = stream_->close(); result != oboe::Result::OK) {
return oboe::convertToText(result);
}
stream_.reset();
return nullptr;
}
oboe::DataCallbackResult Stream::onAudioReady(oboe::AudioStream *oboe_stream,
void *audio_data,
int32_t num_frames) {
size_t num = num_frames * channel_num_;
// TODO: Do not use a lock in onAudioReady.
// https://google.github.io/oboe/reference/classoboe_1_1_audio_stream_data_callback.html#ad8a3a9f609df5fd3a5d885cbe1b2204d
{
std::unique_lock<std::mutex> lock{mutex_};
cond_.wait(lock, [this, num] { return buf_.size() >= num; });
std::copy(buf_.begin(), buf_.begin() + num,
reinterpret_cast<float *>(audio_data));
buf_.erase(buf_.begin(), buf_.begin() + num);
cond_.notify_one();
}
return oboe::DataCallbackResult::Continue;
}
Stream::Stream() = default;
void Stream::Loop(int num_frames) {
std::vector<float> tmp(num_frames * channel_num_ * 3);
for (;;) {
{
std::unique_lock<std::mutex> lock{mutex_};
cond_.wait(lock, [this, &tmp] { return buf_.size() < tmp.size(); });
}
ebiten_oboe_read(&tmp[0], tmp.size());
{
std::lock_guard<std::mutex> lock{mutex_};
buf_.insert(buf_.end(), tmp.begin(), tmp.end());
cond_.notify_one();
}
}
}
} // namespace
extern "C" {
const char *ebiten_oboe_Play(int sample_rate, int channel_num,
int bit_depth_in_bytes) {
return Stream::GetInstance().Play(sample_rate, channel_num,
bit_depth_in_bytes);
}
const char *ebiten_oboe_Suspend() { return Stream::GetInstance().Pause(); }
const char *ebiten_oboe_Resume() { return Stream::GetInstance().Resume(); }
} // extern "C"

View File

@ -1,69 +0,0 @@
// Copyright 2021 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 oboe
// Disable AAudio (#1634).
// AAudio doesn't care about plugging in/out of a headphone.
// See https://github.com/google/oboe/blob/master/docs/notes/disconnect.md
// #cgo CXXFLAGS: -std=c++17 -DOBOE_ENABLE_AAUDIO=0
// #cgo LDFLAGS: -llog -lOpenSLES -static-libstdc++
//
// #include "binding_android.h"
import "C"
import (
"fmt"
"reflect"
"unsafe"
)
var theReadFunc func(buf []float32)
func Play(sampleRate, channelNum, bitDepthInBytes int, readFunc func(buf []float32)) error {
// Play can invoke the callback. Set the callback before Play.
theReadFunc = readFunc
if msg := C.ebiten_oboe_Play(C.int(sampleRate), C.int(channelNum), C.int(bitDepthInBytes)); msg != nil {
return fmt.Errorf("oboe: Play failed: %s", C.GoString(msg))
}
return nil
}
func Suspend() error {
if msg := C.ebiten_oboe_Suspend(); msg != nil {
return fmt.Errorf("oboe: Suspend failed: %s", C.GoString(msg))
}
return nil
}
func Resume() error {
if msg := C.ebiten_oboe_Resume(); msg != nil {
return fmt.Errorf("oboe: Resume failed: %s", C.GoString(msg))
}
return nil
}
//export ebiten_oboe_read
func ebiten_oboe_read(buf *C.float, len C.size_t) {
var s []float32
h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
h.Data = uintptr(unsafe.Pointer(buf))
h.Len = int(len)
h.Cap = int(len)
for i := range s {
s[i] = 0
}
theReadFunc(s)
}

View File

@ -1,37 +0,0 @@
// Copyright 2021 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.
#ifndef OBOE_ANDROID_H_
#define OBOE_ANDROID_H_
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef uintptr_t PlayerID;
const char *ebiten_oboe_Play(int sample_rate, int channel_num,
int bit_depth_in_bytes);
const char *ebiten_oboe_Suspend();
const char *ebiten_oboe_Resume();
#ifdef __cplusplus
}
#endif
#endif // OBOE_ANDROID_H_

View File

@ -1,239 +0,0 @@
// Copyright 2021 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.
//go:build ignore
// +build ignore
package main
import (
"bytes"
"fmt"
"io"
"io/fs"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
)
const oboeVersion = "1.6.0"
func main() {
if err := run(); err != nil {
panic(err)
}
}
func run() error {
if err := clean(); err != nil {
return err
}
tmp, err := os.MkdirTemp("", "oboe-")
if err != nil {
return err
}
defer os.RemoveAll(tmp)
if err := prepareOboe(tmp); err != nil {
return err
}
return nil
}
func clean() error {
fmt.Printf("Cleaning *.cpp and *.h files\n")
if err := filepath.Walk(".", func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() && path != "." {
return filepath.SkipDir
}
base := filepath.Base(path)
if base == "README-oboe.md" {
return os.Remove(base)
}
if base == "LICENSE-oboe" {
return os.Remove(base)
}
if !strings.HasPrefix(base, "oboe_") {
return nil
}
if !strings.HasSuffix(base, ".cpp") && !strings.HasSuffix(base, ".h") {
return nil
}
return os.Remove(base)
}); err != nil {
return err
}
return nil
}
func prepareOboe(tmp string) error {
fn := oboeVersion + ".tar.gz"
if e, err := exists(fn); err != nil {
return err
} else if !e {
url := "https://github.com/google/oboe/archive/refs/tags/" + fn
fmt.Fprintf(os.Stderr, "%s not found: please download it from %s\n", fn, url)
return nil
}
fmt.Printf("Copying %s to %s\n", fn, filepath.Join(tmp, fn))
in, err := os.Open(fn)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(filepath.Join(tmp, fn))
if err != nil {
return err
}
defer out.Close()
if _, err := io.Copy(out, in); err != nil {
return err
}
fmt.Printf("Extracting %s\n", fn)
cmd := exec.Command("tar", "-xzf", fn)
cmd.Stderr = os.Stderr
cmd.Dir = tmp
if err := cmd.Run(); err != nil {
return err
}
reInclude := regexp.MustCompile(`(?m)^#include\s+([<"])(.+)[>"]$`)
fmt.Printf("Copying *.cpp and *.h files\n")
for _, dir := range []string{"src", "include"} {
dir := dir
indir := filepath.Join(tmp, "oboe-"+oboeVersion, dir)
if err := filepath.Walk(indir, func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
if !strings.HasSuffix(path, ".cpp") && !strings.HasSuffix(path, ".h") {
return nil
}
f, err := filepath.Rel(indir, path)
if err != nil {
return err
}
ext := filepath.Ext(f)
curTs := strings.Split(f[:len(f)-len(ext)], string(filepath.Separator))
outfn := "oboe_" + strings.Join(curTs, "_") + "_android" + ext
if _, err := os.Stat(outfn); err == nil {
return fmt.Errorf("%s must not exist", outfn)
}
in, err := os.ReadFile(path)
if err != nil {
return err
}
// Replace #include paths.
in = reInclude.ReplaceAllFunc(in, func(inc []byte) []byte {
m := reInclude.FindSubmatch(inc)
f := string(m[2])
searchDirs := []string{filepath.Dir(path)}
if dir == "src" {
searchDirs = append(searchDirs, filepath.Join(tmp, "oboe-"+oboeVersion, "src"))
}
searchDirs = append(searchDirs, filepath.Join(tmp, "oboe-"+oboeVersion, "include"))
for _, searchDir := range searchDirs {
path := filepath.Join(searchDir, f)
e, err := exists(path)
if err != nil {
panic(err)
}
if !e {
continue
}
f, err := filepath.Rel(filepath.Join(tmp, "oboe-"+oboeVersion), path)
if err != nil {
panic(err)
}
ext := filepath.Ext(f)
ts := strings.Split(f[:len(f)-len(ext)], string(filepath.Separator))
// The first token is 'src' or 'include'. Remove this.
ts = ts[1:]
newpath := "oboe_" + strings.Join(ts, "_") + "_android" + ext
return []byte(`#include "` + newpath + `"`)
}
return inc
})
out, err := os.Create(outfn)
if err != nil {
return err
}
defer out.Close()
if _, err := io.Copy(out, bytes.NewReader(in)); err != nil {
return err
}
return nil
}); err != nil {
return err
}
}
fmt.Printf("Copying README.md and LICENSE\n")
for _, f := range []string{"README.md", "LICENSE"} {
infn := filepath.Join(tmp, "oboe-"+oboeVersion, f)
ext := filepath.Ext(f)
outfn := f[:len(f)-len(ext)] + "-oboe" + ext
in, err := os.Open(infn)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(outfn)
if err != nil {
return err
}
defer out.Close()
if _, err := io.Copy(out, in); err != nil {
return err
}
}
return nil
}
func exists(path string) (bool, error) {
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return true, nil
}

View File

@ -1,17 +0,0 @@
// Copyright 2021 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.
//go:generate go run gen.go
package oboe

View File

@ -1,172 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_AAUDIO_EXTENSIONS_H
#define OBOE_AAUDIO_EXTENSIONS_H
#include <dlfcn.h>
#include <stdint.h>
#include <sys/system_properties.h>
#include "oboe_common_OboeDebug_android.h"
#include "oboe_oboe_Oboe_android.h"
#include "oboe_aaudio_AAudioLoader_android.h"
namespace oboe {
#define LIB_AAUDIO_NAME "libaaudio.so"
#define FUNCTION_IS_MMAP "AAudioStream_isMMapUsed"
#define FUNCTION_SET_MMAP_POLICY "AAudio_setMMapPolicy"
#define FUNCTION_GET_MMAP_POLICY "AAudio_getMMapPolicy"
#define AAUDIO_ERROR_UNAVAILABLE static_cast<aaudio_result_t>(Result::ErrorUnavailable)
typedef struct AAudioStreamStruct AAudioStream;
/**
* Call some AAudio test routines that are not part of the normal API.
*/
class AAudioExtensions {
public:
AAudioExtensions() {
int32_t policy = getIntegerProperty("aaudio.mmap_policy", 0);
mMMapSupported = isPolicyEnabled(policy);
policy = getIntegerProperty("aaudio.mmap_exclusive_policy", 0);
mMMapExclusiveSupported = isPolicyEnabled(policy);
}
static bool isPolicyEnabled(int32_t policy) {
return (policy == AAUDIO_POLICY_AUTO || policy == AAUDIO_POLICY_ALWAYS);
}
static AAudioExtensions &getInstance() {
static AAudioExtensions instance;
return instance;
}
bool isMMapUsed(oboe::AudioStream *oboeStream) {
AAudioStream *aaudioStream = (AAudioStream *) oboeStream->getUnderlyingStream();
return isMMapUsed(aaudioStream);
}
bool isMMapUsed(AAudioStream *aaudioStream) {
if (loadSymbols()) return false;
if (mAAudioStream_isMMap == nullptr) return false;
return mAAudioStream_isMMap(aaudioStream);
}
/**
* Controls whether the MMAP data path can be selected when opening a stream.
* It has no effect after the stream has been opened.
* It only affects the application that calls it. Other apps are not affected.
*
* @param enabled
* @return 0 or a negative error code
*/
int32_t setMMapEnabled(bool enabled) {
if (loadSymbols()) return AAUDIO_ERROR_UNAVAILABLE;
if (mAAudio_setMMapPolicy == nullptr) return false;
return mAAudio_setMMapPolicy(enabled ? AAUDIO_POLICY_AUTO : AAUDIO_POLICY_NEVER);
}
bool isMMapEnabled() {
if (loadSymbols()) return false;
if (mAAudio_getMMapPolicy == nullptr) return false;
int32_t policy = mAAudio_getMMapPolicy();
return isPolicyEnabled(policy);
}
bool isMMapSupported() {
return mMMapSupported;
}
bool isMMapExclusiveSupported() {
return mMMapExclusiveSupported;
}
private:
enum {
AAUDIO_POLICY_NEVER = 1,
AAUDIO_POLICY_AUTO,
AAUDIO_POLICY_ALWAYS
};
typedef int32_t aaudio_policy_t;
int getIntegerProperty(const char *name, int defaultValue) {
int result = defaultValue;
char valueText[PROP_VALUE_MAX] = {0};
if (__system_property_get(name, valueText) != 0) {
result = atoi(valueText);
}
return result;
}
/**
* Load the function pointers.
* This can be called multiple times.
* It should only be called from one thread.
*
* @return 0 if successful or negative error.
*/
aaudio_result_t loadSymbols() {
if (mAAudio_getMMapPolicy != nullptr) {
return 0;
}
void *libHandle = AAudioLoader::getInstance()->getLibHandle();
if (libHandle == nullptr) {
LOGI("%s() could not find " LIB_AAUDIO_NAME, __func__);
return AAUDIO_ERROR_UNAVAILABLE;
}
mAAudioStream_isMMap = (bool (*)(AAudioStream *stream))
dlsym(libHandle, FUNCTION_IS_MMAP);
if (mAAudioStream_isMMap == nullptr) {
LOGI("%s() could not find " FUNCTION_IS_MMAP, __func__);
return AAUDIO_ERROR_UNAVAILABLE;
}
mAAudio_setMMapPolicy = (int32_t (*)(aaudio_policy_t policy))
dlsym(libHandle, FUNCTION_SET_MMAP_POLICY);
if (mAAudio_setMMapPolicy == nullptr) {
LOGI("%s() could not find " FUNCTION_SET_MMAP_POLICY, __func__);
return AAUDIO_ERROR_UNAVAILABLE;
}
mAAudio_getMMapPolicy = (aaudio_policy_t (*)())
dlsym(libHandle, FUNCTION_GET_MMAP_POLICY);
if (mAAudio_getMMapPolicy == nullptr) {
LOGI("%s() could not find " FUNCTION_GET_MMAP_POLICY, __func__);
return AAUDIO_ERROR_UNAVAILABLE;
}
return 0;
}
bool mMMapSupported = false;
bool mMMapExclusiveSupported = false;
bool (*mAAudioStream_isMMap)(AAudioStream *stream) = nullptr;
int32_t (*mAAudio_setMMapPolicy)(aaudio_policy_t policy) = nullptr;
aaudio_policy_t (*mAAudio_getMMapPolicy)() = nullptr;
};
} // namespace oboe
#endif //OBOE_AAUDIO_EXTENSIONS_H

View File

@ -1,355 +0,0 @@
/*
* Copyright 2016 The Android Open Source Project
*
* 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.
*/
#include <dlfcn.h>
#include "oboe_oboe_Utilities_android.h"
#include "oboe_common_OboeDebug_android.h"
#include "oboe_aaudio_AAudioLoader_android.h"
#define LIB_AAUDIO_NAME "libaaudio.so"
namespace oboe {
AAudioLoader::~AAudioLoader() {
// Issue 360: thread_local variables with non-trivial destructors
// will cause segfaults if the containing library is dlclose()ed on
// devices running M or newer, or devices before M when using a static STL.
// The simple workaround is to not call dlclose.
// https://github.com/android/ndk/wiki/Changelog-r22#known-issues
//
// The libaaudio and libaaudioclient do not use thread_local.
// But, to be safe, we should avoid dlclose() if possible.
// Because AAudioLoader is a static Singleton, we can safely skip
// calling dlclose() without causing a resource leak.
LOGI("%s() dlclose(%s) not called, OK", __func__, LIB_AAUDIO_NAME);
}
AAudioLoader* AAudioLoader::getInstance() {
static AAudioLoader instance;
return &instance;
}
int AAudioLoader::open() {
if (mLibHandle != nullptr) {
return 0;
}
// Use RTLD_NOW to avoid the unpredictable behavior that RTLD_LAZY can cause.
// Also resolving all the links now will prevent a run-time penalty later.
mLibHandle = dlopen(LIB_AAUDIO_NAME, RTLD_NOW);
if (mLibHandle == nullptr) {
LOGI("AAudioLoader::open() could not find " LIB_AAUDIO_NAME);
return -1; // TODO review return code
} else {
LOGD("AAudioLoader(): dlopen(%s) returned %p", LIB_AAUDIO_NAME, mLibHandle);
}
// Load all the function pointers.
createStreamBuilder = load_I_PPB("AAudio_createStreamBuilder");
builder_openStream = load_I_PBPPS("AAudioStreamBuilder_openStream");
builder_setChannelCount = load_V_PBI("AAudioStreamBuilder_setChannelCount");
if (builder_setChannelCount == nullptr) {
// Use old deprecated alias if needed.
builder_setChannelCount = load_V_PBI("AAudioStreamBuilder_setSamplesPerFrame");
}
builder_setBufferCapacityInFrames = load_V_PBI("AAudioStreamBuilder_setBufferCapacityInFrames");
builder_setDeviceId = load_V_PBI("AAudioStreamBuilder_setDeviceId");
builder_setDirection = load_V_PBI("AAudioStreamBuilder_setDirection");
builder_setFormat = load_V_PBI("AAudioStreamBuilder_setFormat");
builder_setFramesPerDataCallback = load_V_PBI("AAudioStreamBuilder_setFramesPerDataCallback");
builder_setSharingMode = load_V_PBI("AAudioStreamBuilder_setSharingMode");
builder_setPerformanceMode = load_V_PBI("AAudioStreamBuilder_setPerformanceMode");
builder_setSampleRate = load_V_PBI("AAudioStreamBuilder_setSampleRate");
if (getSdkVersion() >= __ANDROID_API_P__){
builder_setUsage = load_V_PBI("AAudioStreamBuilder_setUsage");
builder_setContentType = load_V_PBI("AAudioStreamBuilder_setContentType");
builder_setInputPreset = load_V_PBI("AAudioStreamBuilder_setInputPreset");
builder_setSessionId = load_V_PBI("AAudioStreamBuilder_setSessionId");
}
builder_delete = load_I_PB("AAudioStreamBuilder_delete");
builder_setDataCallback = load_V_PBPDPV("AAudioStreamBuilder_setDataCallback");
builder_setErrorCallback = load_V_PBPEPV("AAudioStreamBuilder_setErrorCallback");
stream_read = load_I_PSPVIL("AAudioStream_read");
stream_write = load_I_PSCPVIL("AAudioStream_write");
stream_waitForStateChange = load_I_PSTPTL("AAudioStream_waitForStateChange");
stream_getTimestamp = load_I_PSKPLPL("AAudioStream_getTimestamp");
stream_getChannelCount = load_I_PS("AAudioStream_getChannelCount");
if (stream_getChannelCount == nullptr) {
// Use old alias if needed.
stream_getChannelCount = load_I_PS("AAudioStream_getSamplesPerFrame");
}
stream_close = load_I_PS("AAudioStream_close");
stream_getBufferSize = load_I_PS("AAudioStream_getBufferSizeInFrames");
stream_getDeviceId = load_I_PS("AAudioStream_getDeviceId");
stream_getBufferCapacity = load_I_PS("AAudioStream_getBufferCapacityInFrames");
stream_getFormat = load_F_PS("AAudioStream_getFormat");
stream_getFramesPerBurst = load_I_PS("AAudioStream_getFramesPerBurst");
stream_getFramesRead = load_L_PS("AAudioStream_getFramesRead");
stream_getFramesWritten = load_L_PS("AAudioStream_getFramesWritten");
stream_getPerformanceMode = load_I_PS("AAudioStream_getPerformanceMode");
stream_getSampleRate = load_I_PS("AAudioStream_getSampleRate");
stream_getSharingMode = load_I_PS("AAudioStream_getSharingMode");
stream_getState = load_I_PS("AAudioStream_getState");
stream_getXRunCount = load_I_PS("AAudioStream_getXRunCount");
stream_requestStart = load_I_PS("AAudioStream_requestStart");
stream_requestPause = load_I_PS("AAudioStream_requestPause");
stream_requestFlush = load_I_PS("AAudioStream_requestFlush");
stream_requestStop = load_I_PS("AAudioStream_requestStop");
stream_setBufferSize = load_I_PSI("AAudioStream_setBufferSizeInFrames");
convertResultToText = load_CPH_I("AAudio_convertResultToText");
if (getSdkVersion() >= __ANDROID_API_P__){
stream_getUsage = load_I_PS("AAudioStream_getUsage");
stream_getContentType = load_I_PS("AAudioStream_getContentType");
stream_getInputPreset = load_I_PS("AAudioStream_getInputPreset");
stream_getSessionId = load_I_PS("AAudioStream_getSessionId");
}
return 0;
}
static void AAudioLoader_check(void *proc, const char *functionName) {
if (proc == nullptr) {
LOGW("AAudioLoader could not find %s", functionName);
}
}
AAudioLoader::signature_I_PPB AAudioLoader::load_I_PPB(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PPB>(proc);
}
AAudioLoader::signature_CPH_I AAudioLoader::load_CPH_I(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_CPH_I>(proc);
}
AAudioLoader::signature_V_PBI AAudioLoader::load_V_PBI(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_V_PBI>(proc);
}
AAudioLoader::signature_V_PBPDPV AAudioLoader::load_V_PBPDPV(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_V_PBPDPV>(proc);
}
AAudioLoader::signature_V_PBPEPV AAudioLoader::load_V_PBPEPV(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_V_PBPEPV>(proc);
}
AAudioLoader::signature_I_PSI AAudioLoader::load_I_PSI(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PSI>(proc);
}
AAudioLoader::signature_I_PS AAudioLoader::load_I_PS(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PS>(proc);
}
AAudioLoader::signature_L_PS AAudioLoader::load_L_PS(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_L_PS>(proc);
}
AAudioLoader::signature_F_PS AAudioLoader::load_F_PS(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_F_PS>(proc);
}
AAudioLoader::signature_B_PS AAudioLoader::load_B_PS(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_B_PS>(proc);
}
AAudioLoader::signature_I_PB AAudioLoader::load_I_PB(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PB>(proc);
}
AAudioLoader::signature_I_PBPPS AAudioLoader::load_I_PBPPS(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PBPPS>(proc);
}
AAudioLoader::signature_I_PSCPVIL AAudioLoader::load_I_PSCPVIL(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PSCPVIL>(proc);
}
AAudioLoader::signature_I_PSPVIL AAudioLoader::load_I_PSPVIL(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PSPVIL>(proc);
}
AAudioLoader::signature_I_PSTPTL AAudioLoader::load_I_PSTPTL(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PSTPTL>(proc);
}
AAudioLoader::signature_I_PSKPLPL AAudioLoader::load_I_PSKPLPL(const char *functionName) {
void *proc = dlsym(mLibHandle, functionName);
AAudioLoader_check(proc, functionName);
return reinterpret_cast<signature_I_PSKPLPL>(proc);
}
// Ensure that all AAudio primitive data types are int32_t
#define ASSERT_INT32(type) static_assert(std::is_same<int32_t, type>::value, \
#type" must be int32_t")
#define ERRMSG "Oboe constants must match AAudio constants."
// These asserts help verify that the Oboe definitions match the equivalent AAudio definitions.
// This code is in this .cpp file so it only gets tested once.
#ifdef AAUDIO_AAUDIO_H
ASSERT_INT32(aaudio_stream_state_t);
ASSERT_INT32(aaudio_direction_t);
ASSERT_INT32(aaudio_format_t);
ASSERT_INT32(aaudio_data_callback_result_t);
ASSERT_INT32(aaudio_result_t);
ASSERT_INT32(aaudio_sharing_mode_t);
ASSERT_INT32(aaudio_performance_mode_t);
static_assert((int32_t)StreamState::Uninitialized == AAUDIO_STREAM_STATE_UNINITIALIZED, ERRMSG);
static_assert((int32_t)StreamState::Unknown == AAUDIO_STREAM_STATE_UNKNOWN, ERRMSG);
static_assert((int32_t)StreamState::Open == AAUDIO_STREAM_STATE_OPEN, ERRMSG);
static_assert((int32_t)StreamState::Starting == AAUDIO_STREAM_STATE_STARTING, ERRMSG);
static_assert((int32_t)StreamState::Started == AAUDIO_STREAM_STATE_STARTED, ERRMSG);
static_assert((int32_t)StreamState::Pausing == AAUDIO_STREAM_STATE_PAUSING, ERRMSG);
static_assert((int32_t)StreamState::Paused == AAUDIO_STREAM_STATE_PAUSED, ERRMSG);
static_assert((int32_t)StreamState::Flushing == AAUDIO_STREAM_STATE_FLUSHING, ERRMSG);
static_assert((int32_t)StreamState::Flushed == AAUDIO_STREAM_STATE_FLUSHED, ERRMSG);
static_assert((int32_t)StreamState::Stopping == AAUDIO_STREAM_STATE_STOPPING, ERRMSG);
static_assert((int32_t)StreamState::Stopped == AAUDIO_STREAM_STATE_STOPPED, ERRMSG);
static_assert((int32_t)StreamState::Closing == AAUDIO_STREAM_STATE_CLOSING, ERRMSG);
static_assert((int32_t)StreamState::Closed == AAUDIO_STREAM_STATE_CLOSED, ERRMSG);
static_assert((int32_t)StreamState::Disconnected == AAUDIO_STREAM_STATE_DISCONNECTED, ERRMSG);
static_assert((int32_t)Direction::Output == AAUDIO_DIRECTION_OUTPUT, ERRMSG);
static_assert((int32_t)Direction::Input == AAUDIO_DIRECTION_INPUT, ERRMSG);
static_assert((int32_t)AudioFormat::Invalid == AAUDIO_FORMAT_INVALID, ERRMSG);
static_assert((int32_t)AudioFormat::Unspecified == AAUDIO_FORMAT_UNSPECIFIED, ERRMSG);
static_assert((int32_t)AudioFormat::I16 == AAUDIO_FORMAT_PCM_I16, ERRMSG);
static_assert((int32_t)AudioFormat::Float == AAUDIO_FORMAT_PCM_FLOAT, ERRMSG);
static_assert((int32_t)DataCallbackResult::Continue == AAUDIO_CALLBACK_RESULT_CONTINUE, ERRMSG);
static_assert((int32_t)DataCallbackResult::Stop == AAUDIO_CALLBACK_RESULT_STOP, ERRMSG);
static_assert((int32_t)Result::OK == AAUDIO_OK, ERRMSG);
static_assert((int32_t)Result::ErrorBase == AAUDIO_ERROR_BASE, ERRMSG);
static_assert((int32_t)Result::ErrorDisconnected == AAUDIO_ERROR_DISCONNECTED, ERRMSG);
static_assert((int32_t)Result::ErrorIllegalArgument == AAUDIO_ERROR_ILLEGAL_ARGUMENT, ERRMSG);
static_assert((int32_t)Result::ErrorInternal == AAUDIO_ERROR_INTERNAL, ERRMSG);
static_assert((int32_t)Result::ErrorInvalidState == AAUDIO_ERROR_INVALID_STATE, ERRMSG);
static_assert((int32_t)Result::ErrorInvalidHandle == AAUDIO_ERROR_INVALID_HANDLE, ERRMSG);
static_assert((int32_t)Result::ErrorUnimplemented == AAUDIO_ERROR_UNIMPLEMENTED, ERRMSG);
static_assert((int32_t)Result::ErrorUnavailable == AAUDIO_ERROR_UNAVAILABLE, ERRMSG);
static_assert((int32_t)Result::ErrorNoFreeHandles == AAUDIO_ERROR_NO_FREE_HANDLES, ERRMSG);
static_assert((int32_t)Result::ErrorNoMemory == AAUDIO_ERROR_NO_MEMORY, ERRMSG);
static_assert((int32_t)Result::ErrorNull == AAUDIO_ERROR_NULL, ERRMSG);
static_assert((int32_t)Result::ErrorTimeout == AAUDIO_ERROR_TIMEOUT, ERRMSG);
static_assert((int32_t)Result::ErrorWouldBlock == AAUDIO_ERROR_WOULD_BLOCK, ERRMSG);
static_assert((int32_t)Result::ErrorInvalidFormat == AAUDIO_ERROR_INVALID_FORMAT, ERRMSG);
static_assert((int32_t)Result::ErrorOutOfRange == AAUDIO_ERROR_OUT_OF_RANGE, ERRMSG);
static_assert((int32_t)Result::ErrorNoService == AAUDIO_ERROR_NO_SERVICE, ERRMSG);
static_assert((int32_t)Result::ErrorInvalidRate == AAUDIO_ERROR_INVALID_RATE, ERRMSG);
static_assert((int32_t)SharingMode::Exclusive == AAUDIO_SHARING_MODE_EXCLUSIVE, ERRMSG);
static_assert((int32_t)SharingMode::Shared == AAUDIO_SHARING_MODE_SHARED, ERRMSG);
static_assert((int32_t)PerformanceMode::None == AAUDIO_PERFORMANCE_MODE_NONE, ERRMSG);
static_assert((int32_t)PerformanceMode::PowerSaving
== AAUDIO_PERFORMANCE_MODE_POWER_SAVING, ERRMSG);
static_assert((int32_t)PerformanceMode::LowLatency
== AAUDIO_PERFORMANCE_MODE_LOW_LATENCY, ERRMSG);
// The aaudio_ usage, content and input_preset types were added in NDK 17,
// which is the first version to support Android Pie (API 28).
#if __NDK_MAJOR__ >= 17
ASSERT_INT32(aaudio_usage_t);
ASSERT_INT32(aaudio_content_type_t);
ASSERT_INT32(aaudio_input_preset_t);
static_assert((int32_t)Usage::Media == AAUDIO_USAGE_MEDIA, ERRMSG);
static_assert((int32_t)Usage::VoiceCommunication == AAUDIO_USAGE_VOICE_COMMUNICATION, ERRMSG);
static_assert((int32_t)Usage::VoiceCommunicationSignalling
== AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING, ERRMSG);
static_assert((int32_t)Usage::Alarm == AAUDIO_USAGE_ALARM, ERRMSG);
static_assert((int32_t)Usage::Notification == AAUDIO_USAGE_NOTIFICATION, ERRMSG);
static_assert((int32_t)Usage::NotificationRingtone == AAUDIO_USAGE_NOTIFICATION_RINGTONE, ERRMSG);
static_assert((int32_t)Usage::NotificationEvent == AAUDIO_USAGE_NOTIFICATION_EVENT, ERRMSG);
static_assert((int32_t)Usage::AssistanceAccessibility == AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY, ERRMSG);
static_assert((int32_t)Usage::AssistanceNavigationGuidance
== AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, ERRMSG);
static_assert((int32_t)Usage::AssistanceSonification == AAUDIO_USAGE_ASSISTANCE_SONIFICATION, ERRMSG);
static_assert((int32_t)Usage::Game == AAUDIO_USAGE_GAME, ERRMSG);
static_assert((int32_t)Usage::Assistant == AAUDIO_USAGE_ASSISTANT, ERRMSG);
static_assert((int32_t)ContentType::Speech == AAUDIO_CONTENT_TYPE_SPEECH, ERRMSG);
static_assert((int32_t)ContentType::Music == AAUDIO_CONTENT_TYPE_MUSIC, ERRMSG);
static_assert((int32_t)ContentType::Movie == AAUDIO_CONTENT_TYPE_MOVIE, ERRMSG);
static_assert((int32_t)ContentType::Sonification == AAUDIO_CONTENT_TYPE_SONIFICATION, ERRMSG);
static_assert((int32_t)InputPreset::Generic == AAUDIO_INPUT_PRESET_GENERIC, ERRMSG);
static_assert((int32_t)InputPreset::Camcorder == AAUDIO_INPUT_PRESET_CAMCORDER, ERRMSG);
static_assert((int32_t)InputPreset::VoiceRecognition == AAUDIO_INPUT_PRESET_VOICE_RECOGNITION, ERRMSG);
static_assert((int32_t)InputPreset::VoiceCommunication
== AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION, ERRMSG);
static_assert((int32_t)InputPreset::Unprocessed == AAUDIO_INPUT_PRESET_UNPROCESSED, ERRMSG);
static_assert((int32_t)SessionId::None == AAUDIO_SESSION_ID_NONE, ERRMSG);
static_assert((int32_t)SessionId::Allocate == AAUDIO_SESSION_ID_ALLOCATE, ERRMSG);
#endif // __NDK_MAJOR__ >= 17
#endif // AAUDIO_AAUDIO_H
} // namespace oboe

View File

@ -1,234 +0,0 @@
/*
* Copyright 2016 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_AAUDIO_LOADER_H_
#define OBOE_AAUDIO_LOADER_H_
#include <unistd.h>
#include "oboe_oboe_Definitions_android.h"
// If the NDK is before O then define this in your build
// so that AAudio.h will not be included.
#ifdef OBOE_NO_INCLUDE_AAUDIO
// Define missing types from AAudio.h
typedef int32_t aaudio_stream_state_t;
typedef int32_t aaudio_direction_t;
typedef int32_t aaudio_format_t;
typedef int32_t aaudio_data_callback_result_t;
typedef int32_t aaudio_result_t;
typedef int32_t aaudio_sharing_mode_t;
typedef int32_t aaudio_performance_mode_t;
typedef struct AAudioStreamStruct AAudioStream;
typedef struct AAudioStreamBuilderStruct AAudioStreamBuilder;
typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(
AAudioStream *stream,
void *userData,
void *audioData,
int32_t numFrames);
typedef void (*AAudioStream_errorCallback)(
AAudioStream *stream,
void *userData,
aaudio_result_t error);
// These were defined in P
typedef int32_t aaudio_usage_t;
typedef int32_t aaudio_content_type_t;
typedef int32_t aaudio_input_preset_t;
typedef int32_t aaudio_session_id_t;
// There are a few definitions used by Oboe.
#define AAUDIO_OK static_cast<aaudio_result_t>(Result::OK)
#define AAUDIO_ERROR_TIMEOUT static_cast<aaudio_result_t>(Result::ErrorTimeout)
#define AAUDIO_STREAM_STATE_STARTING static_cast<aaudio_stream_state_t>(StreamState::Starting)
#define AAUDIO_STREAM_STATE_STARTED static_cast<aaudio_stream_state_t>(StreamState::Started)
#else
#include <aaudio/AAudio.h>
#include <android/ndk-version.h>
#endif
#ifndef __NDK_MAJOR__
#define __NDK_MAJOR__ 0
#endif
namespace oboe {
/**
* The AAudio API was not available in early versions of Android.
* To avoid linker errors, we dynamically link with the functions by name using dlsym().
* On older versions this linkage will safely fail.
*/
class AAudioLoader {
public:
// Use signatures for common functions.
// Key to letter abbreviations.
// S = Stream
// B = Builder
// I = int32_t
// L = int64_t
// T = sTate
// K = clocKid_t
// P = Pointer to following data type
// C = Const prefix
// H = cHar
typedef int32_t (*signature_I_PPB)(AAudioStreamBuilder **builder);
typedef const char * (*signature_CPH_I)(int32_t);
typedef int32_t (*signature_I_PBPPS)(AAudioStreamBuilder *,
AAudioStream **stream); // AAudioStreamBuilder_open()
typedef int32_t (*signature_I_PB)(AAudioStreamBuilder *); // AAudioStreamBuilder_delete()
// AAudioStreamBuilder_setSampleRate()
typedef void (*signature_V_PBI)(AAudioStreamBuilder *, int32_t);
typedef int32_t (*signature_I_PS)(AAudioStream *); // AAudioStream_getSampleRate()
typedef int64_t (*signature_L_PS)(AAudioStream *); // AAudioStream_getFramesRead()
// AAudioStream_setBufferSizeInFrames()
typedef int32_t (*signature_I_PSI)(AAudioStream *, int32_t);
typedef void (*signature_V_PBPDPV)(AAudioStreamBuilder *,
AAudioStream_dataCallback,
void *);
typedef void (*signature_V_PBPEPV)(AAudioStreamBuilder *,
AAudioStream_errorCallback,
void *);
typedef aaudio_format_t (*signature_F_PS)(AAudioStream *stream);
typedef int32_t (*signature_I_PSPVIL)(AAudioStream *, void *, int32_t, int64_t);
typedef int32_t (*signature_I_PSCPVIL)(AAudioStream *, const void *, int32_t, int64_t);
typedef int32_t (*signature_I_PSTPTL)(AAudioStream *,
aaudio_stream_state_t,
aaudio_stream_state_t *,
int64_t);
typedef int32_t (*signature_I_PSKPLPL)(AAudioStream *, clockid_t, int64_t *, int64_t *);
typedef bool (*signature_B_PS)(AAudioStream *);
static AAudioLoader* getInstance(); // singleton
/**
* Open the AAudio shared library and load the function pointers.
* This can be called multiple times.
* It should only be called from one thread.
*
* The destructor will clean up after the open.
*
* @return 0 if successful or negative error.
*/
int open();
void *getLibHandle() const { return mLibHandle; }
// Function pointers into the AAudio shared library.
signature_I_PPB createStreamBuilder = nullptr;
signature_I_PBPPS builder_openStream = nullptr;
signature_V_PBI builder_setBufferCapacityInFrames = nullptr;
signature_V_PBI builder_setChannelCount = nullptr;
signature_V_PBI builder_setDeviceId = nullptr;
signature_V_PBI builder_setDirection = nullptr;
signature_V_PBI builder_setFormat = nullptr;
signature_V_PBI builder_setFramesPerDataCallback = nullptr;
signature_V_PBI builder_setPerformanceMode = nullptr;
signature_V_PBI builder_setSampleRate = nullptr;
signature_V_PBI builder_setSharingMode = nullptr;
signature_V_PBI builder_setUsage = nullptr;
signature_V_PBI builder_setContentType = nullptr;
signature_V_PBI builder_setInputPreset = nullptr;
signature_V_PBI builder_setSessionId = nullptr;
signature_V_PBPDPV builder_setDataCallback = nullptr;
signature_V_PBPEPV builder_setErrorCallback = nullptr;
signature_I_PB builder_delete = nullptr;
signature_F_PS stream_getFormat = nullptr;
signature_I_PSPVIL stream_read = nullptr;
signature_I_PSCPVIL stream_write = nullptr;
signature_I_PSTPTL stream_waitForStateChange = nullptr;
signature_I_PSKPLPL stream_getTimestamp = nullptr;
signature_I_PS stream_close = nullptr;
signature_I_PS stream_getChannelCount = nullptr;
signature_I_PS stream_getDeviceId = nullptr;
signature_I_PS stream_getBufferSize = nullptr;
signature_I_PS stream_getBufferCapacity = nullptr;
signature_I_PS stream_getFramesPerBurst = nullptr;
signature_I_PS stream_getState = nullptr;
signature_I_PS stream_getPerformanceMode = nullptr;
signature_I_PS stream_getSampleRate = nullptr;
signature_I_PS stream_getSharingMode = nullptr;
signature_I_PS stream_getXRunCount = nullptr;
signature_I_PSI stream_setBufferSize = nullptr;
signature_I_PS stream_requestStart = nullptr;
signature_I_PS stream_requestPause = nullptr;
signature_I_PS stream_requestFlush = nullptr;
signature_I_PS stream_requestStop = nullptr;
signature_L_PS stream_getFramesRead = nullptr;
signature_L_PS stream_getFramesWritten = nullptr;
signature_CPH_I convertResultToText = nullptr;
signature_I_PS stream_getUsage = nullptr;
signature_I_PS stream_getContentType = nullptr;
signature_I_PS stream_getInputPreset = nullptr;
signature_I_PS stream_getSessionId = nullptr;
private:
AAudioLoader() {}
~AAudioLoader();
// Load function pointers for specific signatures.
signature_I_PPB load_I_PPB(const char *name);
signature_CPH_I load_CPH_I(const char *name);
signature_V_PBI load_V_PBI(const char *name);
signature_V_PBPDPV load_V_PBPDPV(const char *name);
signature_V_PBPEPV load_V_PBPEPV(const char *name);
signature_I_PB load_I_PB(const char *name);
signature_I_PBPPS load_I_PBPPS(const char *name);
signature_I_PS load_I_PS(const char *name);
signature_L_PS load_L_PS(const char *name);
signature_F_PS load_F_PS(const char *name);
signature_B_PS load_B_PS(const char *name);
signature_I_PSI load_I_PSI(const char *name);
signature_I_PSPVIL load_I_PSPVIL(const char *name);
signature_I_PSCPVIL load_I_PSCPVIL(const char *name);
signature_I_PSTPTL load_I_PSTPTL(const char *name);
signature_I_PSKPLPL load_I_PSKPLPL(const char *name);
void *mLibHandle = nullptr;
};
} // namespace oboe
#endif //OBOE_AAUDIO_LOADER_H_

View File

@ -1,694 +0,0 @@
/*
* Copyright 2016 The Android Open Source Project
*
* 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.
*/
#include <cassert>
#include <stdint.h>
#include <stdlib.h>
#include "oboe_aaudio_AAudioLoader_android.h"
#include "oboe_aaudio_AudioStreamAAudio_android.h"
#include "oboe_common_AudioClock_android.h"
#include "oboe_common_OboeDebug_android.h"
#include "oboe_oboe_Utilities_android.h"
#include "oboe_aaudio_AAudioExtensions_android.h"
#ifdef __ANDROID__
#include <sys/system_properties.h>
#include "oboe_common_QuirksManager_android.h"
#endif
#ifndef OBOE_FIX_FORCE_STARTING_TO_STARTED
// Workaround state problems in AAudio
// TODO Which versions does this occur in? Verify fixed in Q.
#define OBOE_FIX_FORCE_STARTING_TO_STARTED 1
#endif // OBOE_FIX_FORCE_STARTING_TO_STARTED
using namespace oboe;
AAudioLoader *AudioStreamAAudio::mLibLoader = nullptr;
// 'C' wrapper for the data callback method
static aaudio_data_callback_result_t oboe_aaudio_data_callback_proc(
AAudioStream *stream,
void *userData,
void *audioData,
int32_t numFrames) {
AudioStreamAAudio *oboeStream = reinterpret_cast<AudioStreamAAudio*>(userData);
if (oboeStream != nullptr) {
return static_cast<aaudio_data_callback_result_t>(
oboeStream->callOnAudioReady(stream, audioData, numFrames));
} else {
return static_cast<aaudio_data_callback_result_t>(DataCallbackResult::Stop);
}
}
// This runs in its own thread.
// Only one of these threads will be launched from internalErrorCallback().
// It calls app error callbacks from a static function in case the stream gets deleted.
static void oboe_aaudio_error_thread_proc(AudioStreamAAudio *oboeStream,
Result error) {
LOGD("%s() - entering >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", __func__);
AudioStreamErrorCallback *errorCallback = oboeStream->getErrorCallback();
if (errorCallback == nullptr) return; // should be impossible
bool isErrorHandled = errorCallback->onError(oboeStream, error);
if (!isErrorHandled) {
oboeStream->requestStop();
errorCallback->onErrorBeforeClose(oboeStream, error);
oboeStream->close();
// Warning, oboeStream may get deleted by this callback.
errorCallback->onErrorAfterClose(oboeStream, error);
}
LOGD("%s() - exiting <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<", __func__);
}
// This runs in its own thread.
// Only one of these threads will be launched from internalErrorCallback().
// Prevents deletion of the stream if the app is using AudioStreamBuilder::openSharedStream()
static void oboe_aaudio_error_thread_proc_shared(std::shared_ptr<AudioStream> sharedStream,
Result error) {
AudioStreamAAudio *oboeStream = reinterpret_cast<AudioStreamAAudio*>(sharedStream.get());
oboe_aaudio_error_thread_proc(oboeStream, error);
}
namespace oboe {
/*
* Create a stream that uses Oboe Audio API.
*/
AudioStreamAAudio::AudioStreamAAudio(const AudioStreamBuilder &builder)
: AudioStream(builder)
, mAAudioStream(nullptr) {
mCallbackThreadEnabled.store(false);
mLibLoader = AAudioLoader::getInstance();
}
bool AudioStreamAAudio::isSupported() {
mLibLoader = AAudioLoader::getInstance();
int openResult = mLibLoader->open();
return openResult == 0;
}
// Static method for the error callback.
// We use a method so we can access protected methods on the stream.
// Launch a thread to handle the error.
// That other thread can safely stop, close and delete the stream.
void AudioStreamAAudio::internalErrorCallback(
AAudioStream *stream,
void *userData,
aaudio_result_t error) {
oboe::Result oboeResult = static_cast<Result>(error);
AudioStreamAAudio *oboeStream = reinterpret_cast<AudioStreamAAudio*>(userData);
// Coerce the error code if needed to workaround a regression in RQ1A that caused
// the wrong code to be passed when headsets plugged in. See b/173928197.
if (OboeGlobals::areWorkaroundsEnabled()
&& getSdkVersion() == __ANDROID_API_R__
&& oboeResult == oboe::Result::ErrorTimeout) {
oboeResult = oboe::Result::ErrorDisconnected;
LOGD("%s() ErrorTimeout changed to ErrorDisconnected to fix b/173928197", __func__);
}
oboeStream->mErrorCallbackResult = oboeResult;
// Prevents deletion of the stream if the app is using AudioStreamBuilder::openStream(shared_ptr)
std::shared_ptr<AudioStream> sharedStream = oboeStream->lockWeakThis();
// These checks should be enough because we assume that the stream close()
// will join() any active callback threads and will not allow new callbacks.
if (oboeStream->wasErrorCallbackCalled()) { // block extra error callbacks
LOGE("%s() multiple error callbacks called!", __func__);
} else if (stream != oboeStream->getUnderlyingStream()) {
LOGW("%s() stream already closed or closing", __func__); // might happen if there are bugs
} else if (sharedStream) {
// Handle error on a separate thread using shared pointer.
std::thread t(oboe_aaudio_error_thread_proc_shared, sharedStream, oboeResult);
t.detach();
} else {
// Handle error on a separate thread.
std::thread t(oboe_aaudio_error_thread_proc, oboeStream, oboeResult);
t.detach();
}
}
void AudioStreamAAudio::logUnsupportedAttributes() {
int sdkVersion = getSdkVersion();
// These attributes are not supported pre Android "P"
if (sdkVersion < __ANDROID_API_P__) {
if (mUsage != Usage::Media) {
LOGW("Usage [AudioStreamBuilder::setUsage()] "
"is not supported on AAudio streams running on pre-Android P versions.");
}
if (mContentType != ContentType::Music) {
LOGW("ContentType [AudioStreamBuilder::setContentType()] "
"is not supported on AAudio streams running on pre-Android P versions.");
}
if (mSessionId != SessionId::None) {
LOGW("SessionId [AudioStreamBuilder::setSessionId()] "
"is not supported on AAudio streams running on pre-Android P versions.");
}
}
}
Result AudioStreamAAudio::open() {
Result result = Result::OK;
if (mAAudioStream != nullptr) {
return Result::ErrorInvalidState;
}
result = AudioStream::open();
if (result != Result::OK) {
return result;
}
AAudioStreamBuilder *aaudioBuilder;
result = static_cast<Result>(mLibLoader->createStreamBuilder(&aaudioBuilder));
if (result != Result::OK) {
return result;
}
// Do not set INPUT capacity below 4096 because that prevents us from getting a FAST track
// when using the Legacy data path.
// If the app requests > 4096 then we allow it but we are less likely to get LowLatency.
// See internal bug b/80308183 for more details.
// Fixed in Q but let's still clip the capacity because high input capacity
// does not increase latency.
int32_t capacity = mBufferCapacityInFrames;
constexpr int kCapacityRequiredForFastLegacyTrack = 4096; // matches value in AudioFinger
if (OboeGlobals::areWorkaroundsEnabled()
&& mDirection == oboe::Direction::Input
&& capacity != oboe::Unspecified
&& capacity < kCapacityRequiredForFastLegacyTrack
&& mPerformanceMode == oboe::PerformanceMode::LowLatency) {
capacity = kCapacityRequiredForFastLegacyTrack;
LOGD("AudioStreamAAudio.open() capacity changed from %d to %d for lower latency",
static_cast<int>(mBufferCapacityInFrames), capacity);
}
mLibLoader->builder_setBufferCapacityInFrames(aaudioBuilder, capacity);
mLibLoader->builder_setChannelCount(aaudioBuilder, mChannelCount);
mLibLoader->builder_setDeviceId(aaudioBuilder, mDeviceId);
mLibLoader->builder_setDirection(aaudioBuilder, static_cast<aaudio_direction_t>(mDirection));
mLibLoader->builder_setFormat(aaudioBuilder, static_cast<aaudio_format_t>(mFormat));
mLibLoader->builder_setSampleRate(aaudioBuilder, mSampleRate);
mLibLoader->builder_setSharingMode(aaudioBuilder,
static_cast<aaudio_sharing_mode_t>(mSharingMode));
mLibLoader->builder_setPerformanceMode(aaudioBuilder,
static_cast<aaudio_performance_mode_t>(mPerformanceMode));
// These were added in P so we have to check for the function pointer.
if (mLibLoader->builder_setUsage != nullptr) {
mLibLoader->builder_setUsage(aaudioBuilder,
static_cast<aaudio_usage_t>(mUsage));
}
if (mLibLoader->builder_setContentType != nullptr) {
mLibLoader->builder_setContentType(aaudioBuilder,
static_cast<aaudio_content_type_t>(mContentType));
}
if (mLibLoader->builder_setInputPreset != nullptr) {
aaudio_input_preset_t inputPreset = mInputPreset;
if (getSdkVersion() <= __ANDROID_API_P__ && inputPreset == InputPreset::VoicePerformance) {
LOGD("InputPreset::VoicePerformance not supported before Q. Using VoiceRecognition.");
inputPreset = InputPreset::VoiceRecognition; // most similar preset
}
mLibLoader->builder_setInputPreset(aaudioBuilder,
static_cast<aaudio_input_preset_t>(inputPreset));
}
if (mLibLoader->builder_setSessionId != nullptr) {
mLibLoader->builder_setSessionId(aaudioBuilder,
static_cast<aaudio_session_id_t>(mSessionId));
}
// TODO get more parameters from the builder?
if (isDataCallbackSpecified()) {
mLibLoader->builder_setDataCallback(aaudioBuilder, oboe_aaudio_data_callback_proc, this);
mLibLoader->builder_setFramesPerDataCallback(aaudioBuilder, getFramesPerDataCallback());
if (!isErrorCallbackSpecified()) {
// The app did not specify a callback so we should specify
// our own so the stream gets closed and stopped.
mErrorCallback = &mDefaultErrorCallback;
}
mLibLoader->builder_setErrorCallback(aaudioBuilder, internalErrorCallback, this);
}
// Else if the data callback is not being used then the write method will return an error
// and the app can stop and close the stream.
// ============= OPEN THE STREAM ================
{
AAudioStream *stream = nullptr;
result = static_cast<Result>(mLibLoader->builder_openStream(aaudioBuilder, &stream));
mAAudioStream.store(stream);
}
if (result != Result::OK) {
// Warn developer because ErrorInternal is not very informative.
if (result == Result::ErrorInternal && mDirection == Direction::Input) {
LOGW("AudioStreamAAudio.open() may have failed due to lack of "
"audio recording permission.");
}
goto error2;
}
// Query and cache the stream properties
mDeviceId = mLibLoader->stream_getDeviceId(mAAudioStream);
mChannelCount = mLibLoader->stream_getChannelCount(mAAudioStream);
mSampleRate = mLibLoader->stream_getSampleRate(mAAudioStream);
mFormat = static_cast<AudioFormat>(mLibLoader->stream_getFormat(mAAudioStream));
mSharingMode = static_cast<SharingMode>(mLibLoader->stream_getSharingMode(mAAudioStream));
mPerformanceMode = static_cast<PerformanceMode>(
mLibLoader->stream_getPerformanceMode(mAAudioStream));
mBufferCapacityInFrames = mLibLoader->stream_getBufferCapacity(mAAudioStream);
mBufferSizeInFrames = mLibLoader->stream_getBufferSize(mAAudioStream);
mFramesPerBurst = mLibLoader->stream_getFramesPerBurst(mAAudioStream);
// These were added in P so we have to check for the function pointer.
if (mLibLoader->stream_getUsage != nullptr) {
mUsage = static_cast<Usage>(mLibLoader->stream_getUsage(mAAudioStream));
}
if (mLibLoader->stream_getContentType != nullptr) {
mContentType = static_cast<ContentType>(mLibLoader->stream_getContentType(mAAudioStream));
}
if (mLibLoader->stream_getInputPreset != nullptr) {
mInputPreset = static_cast<InputPreset>(mLibLoader->stream_getInputPreset(mAAudioStream));
}
if (mLibLoader->stream_getSessionId != nullptr) {
mSessionId = static_cast<SessionId>(mLibLoader->stream_getSessionId(mAAudioStream));
} else {
mSessionId = SessionId::None;
}
LOGD("AudioStreamAAudio.open() format=%d, sampleRate=%d, capacity = %d",
static_cast<int>(mFormat), static_cast<int>(mSampleRate),
static_cast<int>(mBufferCapacityInFrames));
error2:
mLibLoader->builder_delete(aaudioBuilder);
LOGD("AudioStreamAAudio.open: AAudioStream_Open() returned %s",
mLibLoader->convertResultToText(static_cast<aaudio_result_t>(result)));
return result;
}
Result AudioStreamAAudio::close() {
// Prevent two threads from closing the stream at the same time and crashing.
// This could occur, for example, if an application called close() at the same
// time that an onError callback was being executed because of a disconnect.
std::lock_guard<std::mutex> lock(mLock);
AudioStream::close();
AAudioStream *stream = nullptr;
{
// Wait for any methods using mAAudioStream to finish.
std::unique_lock<std::shared_mutex> lock2(mAAudioStreamLock);
// Closing will delete *mAAudioStream so we need to null out the pointer atomically.
stream = mAAudioStream.exchange(nullptr);
}
if (stream != nullptr) {
if (OboeGlobals::areWorkaroundsEnabled()) {
// Make sure we are really stopped. Do it under mLock
// so another thread cannot call requestStart() right before the close.
requestStop_l(stream);
// Sometimes a callback can occur shortly after a stream has been stopped and
// even after a close! If the stream has been closed then the callback
// can access memory that has been freed. That causes a crash.
// This seems to be more likely in Android P or earlier.
// But it can also occur in later versions.
usleep(kDelayBeforeCloseMillis * 1000);
}
return static_cast<Result>(mLibLoader->stream_close(stream));
} else {
return Result::ErrorClosed;
}
}
DataCallbackResult AudioStreamAAudio::callOnAudioReady(AAudioStream *stream,
void *audioData,
int32_t numFrames) {
DataCallbackResult result = fireDataCallback(audioData, numFrames);
if (result == DataCallbackResult::Continue) {
return result;
} else {
if (result == DataCallbackResult::Stop) {
LOGD("Oboe callback returned DataCallbackResult::Stop");
} else {
LOGE("Oboe callback returned unexpected value = %d", result);
}
if (getSdkVersion() <= __ANDROID_API_P__) {
launchStopThread();
if (isMMapUsed()) {
return DataCallbackResult::Stop;
} else {
// Legacy stream <= API_P cannot be restarted after returning Stop.
return DataCallbackResult::Continue;
}
} else {
return DataCallbackResult::Stop; // OK >= API_Q
}
}
}
Result AudioStreamAAudio::requestStart() {
std::lock_guard<std::mutex> lock(mLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
// Avoid state machine errors in O_MR1.
if (getSdkVersion() <= __ANDROID_API_O_MR1__) {
StreamState state = static_cast<StreamState>(mLibLoader->stream_getState(stream));
if (state == StreamState::Starting || state == StreamState::Started) {
// WARNING: On P, AAudio is returning ErrorInvalidState for Output and OK for Input.
return Result::OK;
}
}
if (isDataCallbackSpecified()) {
setDataCallbackEnabled(true);
}
return static_cast<Result>(mLibLoader->stream_requestStart(stream));
} else {
return Result::ErrorClosed;
}
}
Result AudioStreamAAudio::requestPause() {
std::lock_guard<std::mutex> lock(mLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
// Avoid state machine errors in O_MR1.
if (getSdkVersion() <= __ANDROID_API_O_MR1__) {
StreamState state = static_cast<StreamState>(mLibLoader->stream_getState(stream));
if (state == StreamState::Pausing || state == StreamState::Paused) {
return Result::OK;
}
}
return static_cast<Result>(mLibLoader->stream_requestPause(stream));
} else {
return Result::ErrorClosed;
}
}
Result AudioStreamAAudio::requestFlush() {
std::lock_guard<std::mutex> lock(mLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
// Avoid state machine errors in O_MR1.
if (getSdkVersion() <= __ANDROID_API_O_MR1__) {
StreamState state = static_cast<StreamState>(mLibLoader->stream_getState(stream));
if (state == StreamState::Flushing || state == StreamState::Flushed) {
return Result::OK;
}
}
return static_cast<Result>(mLibLoader->stream_requestFlush(stream));
} else {
return Result::ErrorClosed;
}
}
Result AudioStreamAAudio::requestStop() {
std::lock_guard<std::mutex> lock(mLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
return requestStop_l(stream);
} else {
return Result::ErrorClosed;
}
}
// Call under mLock
Result AudioStreamAAudio::requestStop_l(AAudioStream *stream) {
// Avoid state machine errors in O_MR1.
if (getSdkVersion() <= __ANDROID_API_O_MR1__) {
StreamState state = static_cast<StreamState>(mLibLoader->stream_getState(stream));
if (state == StreamState::Stopping || state == StreamState::Stopped) {
return Result::OK;
}
}
return static_cast<Result>(mLibLoader->stream_requestStop(stream));
}
ResultWithValue<int32_t> AudioStreamAAudio::write(const void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) {
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
int32_t result = mLibLoader->stream_write(mAAudioStream, buffer,
numFrames, timeoutNanoseconds);
return ResultWithValue<int32_t>::createBasedOnSign(result);
} else {
return ResultWithValue<int32_t>(Result::ErrorClosed);
}
}
ResultWithValue<int32_t> AudioStreamAAudio::read(void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) {
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
int32_t result = mLibLoader->stream_read(mAAudioStream, buffer,
numFrames, timeoutNanoseconds);
return ResultWithValue<int32_t>::createBasedOnSign(result);
} else {
return ResultWithValue<int32_t>(Result::ErrorClosed);
}
}
// AAudioStream_waitForStateChange() can crash if it is waiting on a stream and that stream
// is closed from another thread. We do not want to lock the stream for the duration of the call.
// So we call AAudioStream_waitForStateChange() with a timeout of zero so that it will not block.
// Then we can do our own sleep with the lock unlocked.
Result AudioStreamAAudio::waitForStateChange(StreamState currentState,
StreamState *nextState,
int64_t timeoutNanoseconds) {
Result oboeResult = Result::ErrorTimeout;
int64_t sleepTimeNanos = 20 * kNanosPerMillisecond; // arbitrary
aaudio_stream_state_t currentAAudioState = static_cast<aaudio_stream_state_t>(currentState);
aaudio_result_t result = AAUDIO_OK;
int64_t timeLeftNanos = timeoutNanoseconds;
mLock.lock();
while (true) {
// Do we still have an AAudio stream? If not then stream must have been closed.
AAudioStream *stream = mAAudioStream.load();
if (stream == nullptr) {
if (nextState != nullptr) {
*nextState = StreamState::Closed;
}
oboeResult = Result::ErrorClosed;
break;
}
// Update and query state change with no blocking.
aaudio_stream_state_t aaudioNextState;
result = mLibLoader->stream_waitForStateChange(
mAAudioStream,
currentAAudioState,
&aaudioNextState,
0); // timeout=0 for non-blocking
// AAudio will return AAUDIO_ERROR_TIMEOUT if timeout=0 and the state does not change.
if (result != AAUDIO_OK && result != AAUDIO_ERROR_TIMEOUT) {
oboeResult = static_cast<Result>(result);
break;
}
#if OBOE_FIX_FORCE_STARTING_TO_STARTED
if (OboeGlobals::areWorkaroundsEnabled()
&& aaudioNextState == static_cast<aaudio_stream_state_t >(StreamState::Starting)) {
aaudioNextState = static_cast<aaudio_stream_state_t >(StreamState::Started);
}
#endif // OBOE_FIX_FORCE_STARTING_TO_STARTED
if (nextState != nullptr) {
*nextState = static_cast<StreamState>(aaudioNextState);
}
if (currentAAudioState != aaudioNextState) { // state changed?
oboeResult = Result::OK;
break;
}
// Did we timeout or did user ask for non-blocking?
if (timeLeftNanos <= 0) {
break;
}
// No change yet so sleep.
mLock.unlock(); // Don't sleep while locked.
if (sleepTimeNanos > timeLeftNanos) {
sleepTimeNanos = timeLeftNanos; // last little bit
}
AudioClock::sleepForNanos(sleepTimeNanos);
timeLeftNanos -= sleepTimeNanos;
mLock.lock();
}
mLock.unlock();
return oboeResult;
}
ResultWithValue<int32_t> AudioStreamAAudio::setBufferSizeInFrames(int32_t requestedFrames) {
int32_t adjustedFrames = requestedFrames;
if (adjustedFrames > mBufferCapacityInFrames) {
adjustedFrames = mBufferCapacityInFrames;
}
// This calls getBufferSize() so avoid recursive lock.
adjustedFrames = QuirksManager::getInstance().clipBufferSize(*this, adjustedFrames);
std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
int32_t newBufferSize = mLibLoader->stream_setBufferSize(mAAudioStream, adjustedFrames);
// Cache the result if it's valid
if (newBufferSize > 0) mBufferSizeInFrames = newBufferSize;
return ResultWithValue<int32_t>::createBasedOnSign(newBufferSize);
} else {
return ResultWithValue<int32_t>(Result::ErrorClosed);
}
}
StreamState AudioStreamAAudio::getState() {
std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
aaudio_stream_state_t aaudioState = mLibLoader->stream_getState(stream);
#if OBOE_FIX_FORCE_STARTING_TO_STARTED
if (OboeGlobals::areWorkaroundsEnabled()
&& aaudioState == AAUDIO_STREAM_STATE_STARTING) {
aaudioState = AAUDIO_STREAM_STATE_STARTED;
}
#endif // OBOE_FIX_FORCE_STARTING_TO_STARTED
return static_cast<StreamState>(aaudioState);
} else {
return StreamState::Closed;
}
}
int32_t AudioStreamAAudio::getBufferSizeInFrames() {
std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
mBufferSizeInFrames = mLibLoader->stream_getBufferSize(stream);
}
return mBufferSizeInFrames;
}
void AudioStreamAAudio::updateFramesRead() {
std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
AAudioStream *stream = mAAudioStream.load();
// Set to 1 for debugging race condition #1180 with mAAudioStream.
// See also DEBUG_CLOSE_RACE in OboeTester.
// This was left in the code so that we could test the fix again easily in the future.
// We could not trigger the race condition without adding these get calls and the sleeps.
#define DEBUG_CLOSE_RACE 0
#if DEBUG_CLOSE_RACE
// This is used when testing race conditions with close().
// See DEBUG_CLOSE_RACE in OboeTester
AudioClock::sleepForNanos(400 * kNanosPerMillisecond);
#endif // DEBUG_CLOSE_RACE
if (stream != nullptr) {
mFramesRead = mLibLoader->stream_getFramesRead(stream);
}
}
void AudioStreamAAudio::updateFramesWritten() {
std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
mFramesWritten = mLibLoader->stream_getFramesWritten(stream);
}
}
ResultWithValue<int32_t> AudioStreamAAudio::getXRunCount() {
std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
return ResultWithValue<int32_t>::createBasedOnSign(mLibLoader->stream_getXRunCount(stream));
} else {
return ResultWithValue<int32_t>(Result::ErrorNull);
}
}
Result AudioStreamAAudio::getTimestamp(clockid_t clockId,
int64_t *framePosition,
int64_t *timeNanoseconds) {
if (getState() != StreamState::Started) {
return Result::ErrorInvalidState;
}
std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
return static_cast<Result>(mLibLoader->stream_getTimestamp(stream, clockId,
framePosition, timeNanoseconds));
} else {
return Result::ErrorNull;
}
}
ResultWithValue<double> AudioStreamAAudio::calculateLatencyMillis() {
// Get the time that a known audio frame was presented.
int64_t hardwareFrameIndex;
int64_t hardwareFrameHardwareTime;
auto result = getTimestamp(CLOCK_MONOTONIC,
&hardwareFrameIndex,
&hardwareFrameHardwareTime);
if (result != oboe::Result::OK) {
return ResultWithValue<double>(static_cast<Result>(result));
}
// Get counter closest to the app.
bool isOutput = (getDirection() == oboe::Direction::Output);
int64_t appFrameIndex = isOutput ? getFramesWritten() : getFramesRead();
// Assume that the next frame will be processed at the current time
using namespace std::chrono;
int64_t appFrameAppTime =
duration_cast<nanoseconds>(steady_clock::now().time_since_epoch()).count();
// Calculate the number of frames between app and hardware
int64_t frameIndexDelta = appFrameIndex - hardwareFrameIndex;
// Calculate the time which the next frame will be or was presented
int64_t frameTimeDelta = (frameIndexDelta * oboe::kNanosPerSecond) / getSampleRate();
int64_t appFrameHardwareTime = hardwareFrameHardwareTime + frameTimeDelta;
// The current latency is the difference in time between when the current frame is at
// the app and when it is at the hardware.
double latencyNanos = static_cast<double>(isOutput
? (appFrameHardwareTime - appFrameAppTime) // hardware is later
: (appFrameAppTime - appFrameHardwareTime)); // hardware is earlier
double latencyMillis = latencyNanos / kNanosPerMillisecond;
return ResultWithValue<double>(latencyMillis);
}
bool AudioStreamAAudio::isMMapUsed() {
std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
AAudioStream *stream = mAAudioStream.load();
if (stream != nullptr) {
return AAudioExtensions::getInstance().isMMapUsed(stream);
} else {
return false;
}
}
} // namespace oboe

View File

@ -1,133 +0,0 @@
/*
* Copyright 2016 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_STREAM_AAUDIO_H_
#define OBOE_STREAM_AAUDIO_H_
#include <atomic>
#include <shared_mutex>
#include <mutex>
#include <thread>
#include "oboe_oboe_AudioStreamBuilder_android.h"
#include "oboe_oboe_AudioStream_android.h"
#include "oboe_oboe_Definitions_android.h"
#include "oboe_aaudio_AAudioLoader_android.h"
namespace oboe {
/**
* Implementation of OboeStream that uses AAudio.
*
* Do not create this class directly.
* Use an OboeStreamBuilder to create one.
*/
class AudioStreamAAudio : public AudioStream {
public:
AudioStreamAAudio();
explicit AudioStreamAAudio(const AudioStreamBuilder &builder);
virtual ~AudioStreamAAudio() = default;
/**
*
* @return true if AAudio is supported on this device.
*/
static bool isSupported();
// These functions override methods in AudioStream.
// See AudioStream for documentation.
Result open() override;
Result close() override;
Result requestStart() override;
Result requestPause() override;
Result requestFlush() override;
Result requestStop() override;
ResultWithValue<int32_t> write(const void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
ResultWithValue<int32_t> read(void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
ResultWithValue<int32_t> setBufferSizeInFrames(int32_t requestedFrames) override;
int32_t getBufferSizeInFrames() override;
ResultWithValue<int32_t> getXRunCount() override;
bool isXRunCountSupported() const override { return true; }
ResultWithValue<double> calculateLatencyMillis() override;
Result waitForStateChange(StreamState currentState,
StreamState *nextState,
int64_t timeoutNanoseconds) override;
Result getTimestamp(clockid_t clockId,
int64_t *framePosition,
int64_t *timeNanoseconds) override;
StreamState getState() override;
AudioApi getAudioApi() const override {
return AudioApi::AAudio;
}
DataCallbackResult callOnAudioReady(AAudioStream *stream,
void *audioData,
int32_t numFrames);
bool isMMapUsed();
protected:
static void internalErrorCallback(
AAudioStream *stream,
void *userData,
aaudio_result_t error);
void *getUnderlyingStream() const override {
return mAAudioStream.load();
}
void updateFramesRead() override;
void updateFramesWritten() override;
void logUnsupportedAttributes();
private:
// Must call under mLock. And stream must NOT be nullptr.
Result requestStop_l(AAudioStream *stream);
// Time to sleep in order to prevent a race condition with a callback after a close().
// Two milliseconds may be enough but 10 msec is even safer.
static constexpr int kDelayBeforeCloseMillis = 10;
std::atomic<bool> mCallbackThreadEnabled;
// pointer to the underlying 'C' AAudio stream, valid if open, null if closed
std::atomic<AAudioStream *> mAAudioStream{nullptr};
std::shared_mutex mAAudioStreamLock; // to protect mAAudioStream while closing
static AAudioLoader *mLibLoader;
// We may not use this but it is so small that it is not worth allocating dynamically.
AudioStreamErrorCallback mDefaultErrorCallback;
};
} // namespace oboe
#endif // OBOE_STREAM_AAUDIO_H_

View File

@ -1,75 +0,0 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_AUDIO_CLOCK_H
#define OBOE_AUDIO_CLOCK_H
#include <sys/types.h>
#include <ctime>
#include "oboe_oboe_Definitions_android.h"
namespace oboe {
// TODO: Move this class into the public headers because it is useful when calculating stream latency
class AudioClock {
public:
static int64_t getNanoseconds(clockid_t clockId = CLOCK_MONOTONIC) {
struct timespec time;
int result = clock_gettime(clockId, &time);
if (result < 0) {
return result;
}
return (time.tv_sec * kNanosPerSecond) + time.tv_nsec;
}
/**
* Sleep until the specified time.
*
* @param nanoTime time to wake up
* @param clockId CLOCK_MONOTONIC is default
* @return 0 or a negative error, eg. -EINTR
*/
static int sleepUntilNanoTime(int64_t nanoTime, clockid_t clockId = CLOCK_MONOTONIC) {
struct timespec time;
time.tv_sec = nanoTime / kNanosPerSecond;
time.tv_nsec = nanoTime - (time.tv_sec * kNanosPerSecond);
return 0 - clock_nanosleep(clockId, TIMER_ABSTIME, &time, NULL);
}
/**
* Sleep for the specified number of nanoseconds in real-time.
* Return immediately with 0 if a negative nanoseconds is specified.
*
* @param nanoseconds time to sleep
* @param clockId CLOCK_REALTIME is default
* @return 0 or a negative error, eg. -EINTR
*/
static int sleepForNanos(int64_t nanoseconds, clockid_t clockId = CLOCK_REALTIME) {
if (nanoseconds > 0) {
struct timespec time;
time.tv_sec = nanoseconds / kNanosPerSecond;
time.tv_nsec = nanoseconds - (time.tv_sec * kNanosPerSecond);
return 0 - clock_nanosleep(clockId, 0, &time, NULL);
}
return 0;
}
};
} // namespace oboe
#endif //OBOE_AUDIO_CLOCK_H

View File

@ -1,38 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#include "oboe_common_AudioSourceCaller_android.h"
using namespace oboe;
using namespace flowgraph;
int32_t AudioSourceCaller::onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) {
AudioStreamDataCallback *callback = mStream->getDataCallback();
int32_t result = 0;
int32_t numFrames = numBytes / mStream->getBytesPerFrame();
if (callback != nullptr) {
DataCallbackResult callbackResult = callback->onAudioReady(mStream, buffer, numFrames);
// onAudioReady() does not return the number of bytes processed so we have to assume all.
result = (callbackResult == DataCallbackResult::Continue)
? numBytes
: -1;
} else {
auto readResult = mStream->read(buffer, numFrames, mTimeoutNanos);
if (!readResult) return (int32_t) readResult.error();
result = readResult.value() * mStream->getBytesPerFrame();
}
return result;
}

View File

@ -1,83 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_AUDIO_SOURCE_CALLER_H
#define OBOE_AUDIO_SOURCE_CALLER_H
#include "oboe_common_OboeDebug_android.h"
#include "oboe_oboe_Oboe_android.h"
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_common_FixedBlockReader_android.h"
namespace oboe {
class AudioStreamCallback;
class AudioStream;
/**
* For output streams that use a callback, call the application for more data.
* For input streams that do not use a callback, read from the stream.
*/
class AudioSourceCaller : public flowgraph::FlowGraphSource, public FixedBlockProcessor {
public:
AudioSourceCaller(int32_t channelCount, int32_t framesPerCallback, int32_t bytesPerSample)
: FlowGraphSource(channelCount)
, mBlockReader(*this) {
mBlockReader.open(channelCount * framesPerCallback * bytesPerSample);
}
/**
* Set the stream to use as a source of data.
* @param stream
*/
void setStream(oboe::AudioStream *stream) {
mStream = stream;
}
oboe::AudioStream *getStream() {
return mStream;
}
/**
* Timeout value to use when calling audioStream->read().
* @param timeoutNanos Zero for no timeout or time in nanoseconds.
*/
void setTimeoutNanos(int64_t timeoutNanos) {
mTimeoutNanos = timeoutNanos;
}
int64_t getTimeoutNanos() const {
return mTimeoutNanos;
}
/**
* Called internally for block size adaptation.
* @param buffer
* @param numBytes
* @return
*/
int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) override;
protected:
oboe::AudioStream *mStream = nullptr;
int64_t mTimeoutNanos = 0;
FixedBlockReader mBlockReader;
};
}
#endif //OBOE_AUDIO_SOURCE_CALLER_H

View File

@ -1,224 +0,0 @@
/*
* Copyright 2016 The Android Open Source Project
*
* 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.
*/
#include <sys/types.h>
#include "oboe_aaudio_AAudioExtensions_android.h"
#include "oboe_aaudio_AudioStreamAAudio_android.h"
#include "oboe_common_FilterAudioStream_android.h"
#include "oboe_common_OboeDebug_android.h"
#include "oboe_oboe_Oboe_android.h"
#include "oboe_oboe_AudioStreamBuilder_android.h"
#include "oboe_opensles_AudioInputStreamOpenSLES_android.h"
#include "oboe_opensles_AudioOutputStreamOpenSLES_android.h"
#include "oboe_opensles_AudioStreamOpenSLES_android.h"
#include "oboe_common_QuirksManager_android.h"
bool oboe::OboeGlobals::mWorkaroundsEnabled = true;
namespace oboe {
/**
* The following default values are used when oboe does not have any better way of determining the optimal values
* for an audio stream. This can happen when:
*
* - Client is creating a stream on API < 26 (OpenSLES) but has not supplied the optimal sample
* rate and/or frames per burst
* - Client is creating a stream on API 16 (OpenSLES) where AudioManager.PROPERTY_OUTPUT_* values
* are not available
*/
int32_t DefaultStreamValues::SampleRate = 48000; // Common rate for mobile audio and video
int32_t DefaultStreamValues::FramesPerBurst = 192; // 4 msec at 48000 Hz
int32_t DefaultStreamValues::ChannelCount = 2; // Stereo
constexpr int kBufferSizeInBurstsForLowLatencyStreams = 2;
#ifndef OBOE_ENABLE_AAUDIO
// Set OBOE_ENABLE_AAUDIO to 0 if you want to disable the AAudio API.
// This might be useful if you want to force all the unit tests to use OpenSL ES.
#define OBOE_ENABLE_AAUDIO 1
#endif
bool AudioStreamBuilder::isAAudioSupported() {
return AudioStreamAAudio::isSupported() && OBOE_ENABLE_AAUDIO;
}
bool AudioStreamBuilder::isAAudioRecommended() {
// See https://github.com/google/oboe/issues/40,
// AAudio may not be stable on Android O, depending on how it is used.
// To be safe, use AAudio only on O_MR1 and above.
return (getSdkVersion() >= __ANDROID_API_O_MR1__) && isAAudioSupported();
}
AudioStream *AudioStreamBuilder::build() {
AudioStream *stream = nullptr;
if (isAAudioRecommended() && mAudioApi != AudioApi::OpenSLES) {
stream = new AudioStreamAAudio(*this);
} else if (isAAudioSupported() && mAudioApi == AudioApi::AAudio) {
stream = new AudioStreamAAudio(*this);
LOGE("Creating AAudio stream on 8.0 because it was specified. This is error prone.");
} else {
if (getDirection() == oboe::Direction::Output) {
stream = new AudioOutputStreamOpenSLES(*this);
} else if (getDirection() == oboe::Direction::Input) {
stream = new AudioInputStreamOpenSLES(*this);
}
}
return stream;
}
bool AudioStreamBuilder::isCompatible(AudioStreamBase &other) {
return (getSampleRate() == oboe::Unspecified || getSampleRate() == other.getSampleRate())
&& (getFormat() == (AudioFormat)oboe::Unspecified || getFormat() == other.getFormat())
&& (getFramesPerDataCallback() == oboe::Unspecified || getFramesPerDataCallback() == other.getFramesPerDataCallback())
&& (getChannelCount() == oboe::Unspecified || getChannelCount() == other.getChannelCount());
}
Result AudioStreamBuilder::openStream(AudioStream **streamPP) {
auto result = isValidConfig();
if (result != Result::OK) {
LOGW("%s() invalid config %d", __func__, result);
return result;
}
LOGI("%s() %s -------- %s --------",
__func__, getDirection() == Direction::Input ? "INPUT" : "OUTPUT", getVersionText());
if (streamPP == nullptr) {
return Result::ErrorNull;
}
*streamPP = nullptr;
AudioStream *streamP = nullptr;
// Maybe make a FilterInputStream.
AudioStreamBuilder childBuilder(*this);
// Check need for conversion and modify childBuilder for optimal stream.
bool conversionNeeded = QuirksManager::getInstance().isConversionNeeded(*this, childBuilder);
// Do we need to make a child stream and convert.
if (conversionNeeded) {
AudioStream *tempStream;
result = childBuilder.openStream(&tempStream);
if (result != Result::OK) {
return result;
}
if (isCompatible(*tempStream)) {
// The child stream would work as the requested stream so we can just use it directly.
*streamPP = tempStream;
return result;
} else {
AudioStreamBuilder parentBuilder = *this;
// Build a stream that is as close as possible to the childStream.
if (getFormat() == oboe::AudioFormat::Unspecified) {
parentBuilder.setFormat(tempStream->getFormat());
}
if (getChannelCount() == oboe::Unspecified) {
parentBuilder.setChannelCount(tempStream->getChannelCount());
}
if (getSampleRate() == oboe::Unspecified) {
parentBuilder.setSampleRate(tempStream->getSampleRate());
}
if (getFramesPerDataCallback() == oboe::Unspecified) {
parentBuilder.setFramesPerCallback(tempStream->getFramesPerDataCallback());
}
// Use childStream in a FilterAudioStream.
LOGI("%s() create a FilterAudioStream for data conversion.", __func__);
FilterAudioStream *filterStream = new FilterAudioStream(parentBuilder, tempStream);
result = filterStream->configureFlowGraph();
if (result != Result::OK) {
filterStream->close();
delete filterStream;
// Just open streamP the old way.
} else {
streamP = static_cast<AudioStream *>(filterStream);
}
}
}
if (streamP == nullptr) {
streamP = build();
if (streamP == nullptr) {
return Result::ErrorNull;
}
}
// If MMAP has a problem in this case then disable it temporarily.
bool wasMMapOriginallyEnabled = AAudioExtensions::getInstance().isMMapEnabled();
bool wasMMapTemporarilyDisabled = false;
if (wasMMapOriginallyEnabled) {
bool isMMapSafe = QuirksManager::getInstance().isMMapSafe(childBuilder);
if (!isMMapSafe) {
AAudioExtensions::getInstance().setMMapEnabled(false);
wasMMapTemporarilyDisabled = true;
}
}
result = streamP->open();
if (wasMMapTemporarilyDisabled) {
AAudioExtensions::getInstance().setMMapEnabled(wasMMapOriginallyEnabled); // restore original
}
if (result == Result::OK) {
int32_t optimalBufferSize = -1;
// Use a reasonable default buffer size.
if (streamP->getDirection() == Direction::Input) {
// For input, small size does not improve latency because the stream is usually
// run close to empty. And a low size can result in XRuns so always use the maximum.
optimalBufferSize = streamP->getBufferCapacityInFrames();
} else if (streamP->getPerformanceMode() == PerformanceMode::LowLatency
&& streamP->getDirection() == Direction::Output) { // Output check is redundant.
optimalBufferSize = streamP->getFramesPerBurst() *
kBufferSizeInBurstsForLowLatencyStreams;
}
if (optimalBufferSize >= 0) {
auto setBufferResult = streamP->setBufferSizeInFrames(optimalBufferSize);
if (!setBufferResult) {
LOGW("Failed to setBufferSizeInFrames(%d). Error was %s",
optimalBufferSize,
convertToText(setBufferResult.error()));
}
}
*streamPP = streamP;
} else {
delete streamP;
}
return result;
}
Result AudioStreamBuilder::openManagedStream(oboe::ManagedStream &stream) {
stream.reset();
AudioStream *streamptr;
auto result = openStream(&streamptr);
stream.reset(streamptr);
return result;
}
Result AudioStreamBuilder::openStream(std::shared_ptr<AudioStream> &sharedStream) {
sharedStream.reset();
AudioStream *streamptr;
auto result = openStream(&streamptr);
if (result == Result::OK) {
sharedStream.reset(streamptr);
// Save a weak_ptr in the stream for use with callbacks.
streamptr->setWeakThis(sharedStream);
}
return result;
}
} // namespace oboe

View File

@ -1,211 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
#include <sys/types.h>
#include <pthread.h>
#include <thread>
#include "oboe_oboe_AudioStream_android.h"
#include "oboe_common_OboeDebug_android.h"
#include "oboe_common_AudioClock_android.h"
#include "oboe_oboe_Utilities_android.h"
namespace oboe {
/*
* AudioStream
*/
AudioStream::AudioStream(const AudioStreamBuilder &builder)
: AudioStreamBase(builder) {
}
Result AudioStream::close() {
// Update local counters so they can be read after the close.
updateFramesWritten();
updateFramesRead();
return Result::OK;
}
// Call this from fireDataCallback() if you want to monitor CPU scheduler.
void AudioStream::checkScheduler() {
int scheduler = sched_getscheduler(0) & ~SCHED_RESET_ON_FORK; // for current thread
if (scheduler != mPreviousScheduler) {
LOGD("AudioStream::%s() scheduler = %s", __func__,
((scheduler == SCHED_FIFO) ? "SCHED_FIFO" :
((scheduler == SCHED_OTHER) ? "SCHED_OTHER" :
((scheduler == SCHED_RR) ? "SCHED_RR" : "UNKNOWN")))
);
mPreviousScheduler = scheduler;
}
}
DataCallbackResult AudioStream::fireDataCallback(void *audioData, int32_t numFrames) {
if (!isDataCallbackEnabled()) {
LOGW("AudioStream::%s() called with data callback disabled!", __func__);
return DataCallbackResult::Stop; // We should not be getting called any more.
}
DataCallbackResult result;
if (mDataCallback) {
result = mDataCallback->onAudioReady(this, audioData, numFrames);
} else {
result = onDefaultCallback(audioData, numFrames);
}
// On Oreo, we might get called after returning stop.
// So block that here.
setDataCallbackEnabled(result == DataCallbackResult::Continue);
return result;
}
Result AudioStream::waitForStateTransition(StreamState startingState,
StreamState endingState,
int64_t timeoutNanoseconds)
{
StreamState state;
{
std::lock_guard<std::mutex> lock(mLock);
state = getState();
if (state == StreamState::Closed) {
return Result::ErrorClosed;
} else if (state == StreamState::Disconnected) {
return Result::ErrorDisconnected;
}
}
StreamState nextState = state;
// TODO Should this be a while()?!
if (state == startingState && state != endingState) {
Result result = waitForStateChange(state, &nextState, timeoutNanoseconds);
if (result != Result::OK) {
return result;
}
}
if (nextState != endingState) {
return Result::ErrorInvalidState;
} else {
return Result::OK;
}
}
Result AudioStream::start(int64_t timeoutNanoseconds)
{
Result result = requestStart();
if (result != Result::OK) return result;
if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Starting,
StreamState::Started, timeoutNanoseconds);
}
Result AudioStream::pause(int64_t timeoutNanoseconds)
{
Result result = requestPause();
if (result != Result::OK) return result;
if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Pausing,
StreamState::Paused, timeoutNanoseconds);
}
Result AudioStream::flush(int64_t timeoutNanoseconds)
{
Result result = requestFlush();
if (result != Result::OK) return result;
if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Flushing,
StreamState::Flushed, timeoutNanoseconds);
}
Result AudioStream::stop(int64_t timeoutNanoseconds)
{
Result result = requestStop();
if (result != Result::OK) return result;
if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Stopping,
StreamState::Stopped, timeoutNanoseconds);
}
int32_t AudioStream::getBytesPerSample() const {
return convertFormatToSizeInBytes(mFormat);
}
int64_t AudioStream::getFramesRead() {
updateFramesRead();
return mFramesRead;
}
int64_t AudioStream::getFramesWritten() {
updateFramesWritten();
return mFramesWritten;
}
ResultWithValue<int32_t> AudioStream::getAvailableFrames() {
int64_t readCounter = getFramesRead();
if (readCounter < 0) return ResultWithValue<int32_t>::createBasedOnSign(readCounter);
int64_t writeCounter = getFramesWritten();
if (writeCounter < 0) return ResultWithValue<int32_t>::createBasedOnSign(writeCounter);
int32_t framesAvailable = writeCounter - readCounter;
return ResultWithValue<int32_t>(framesAvailable);
}
ResultWithValue<int32_t> AudioStream::waitForAvailableFrames(int32_t numFrames,
int64_t timeoutNanoseconds) {
if (numFrames == 0) return Result::OK;
if (numFrames < 0) return Result::ErrorOutOfRange;
int64_t framesAvailable = 0;
int64_t burstInNanos = getFramesPerBurst() * kNanosPerSecond / getSampleRate();
bool ready = false;
int64_t deadline = AudioClock::getNanoseconds() + timeoutNanoseconds;
do {
ResultWithValue<int32_t> result = getAvailableFrames();
if (!result) return result;
framesAvailable = result.value();
ready = (framesAvailable >= numFrames);
if (!ready) {
int64_t now = AudioClock::getNanoseconds();
if (now > deadline) break;
AudioClock::sleepForNanos(burstInNanos);
}
} while (!ready);
return (!ready)
? ResultWithValue<int32_t>(Result::ErrorTimeout)
: ResultWithValue<int32_t>(framesAvailable);
}
ResultWithValue<FrameTimestamp> AudioStream::getTimestamp(clockid_t clockId) {
FrameTimestamp frame;
Result result = getTimestamp(clockId, &frame.position, &frame.timestamp);
if (result == Result::OK){
return ResultWithValue<FrameTimestamp>(frame);
} else {
return ResultWithValue<FrameTimestamp>(static_cast<Result>(result));
}
}
static void oboe_stop_thread_proc(AudioStream *oboeStream) {
if (oboeStream != nullptr) {
oboeStream->requestStop();
}
}
void AudioStream::launchStopThread() {
// Stop this stream on a separate thread
std::thread t(oboe_stop_thread_proc, this);
t.detach();
}
} // namespace oboe

View File

@ -1,267 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#include <memory>
#include "oboe_common_OboeDebug_android.h"
#include "oboe_common_DataConversionFlowGraph_android.h"
#include "oboe_common_SourceFloatCaller_android.h"
#include "oboe_common_SourceI16Caller_android.h"
#include "oboe_common_SourceI24Caller_android.h"
#include "oboe_common_SourceI32Caller_android.h"
#include "oboe_flowgraph_ClipToRange_android.h"
#include "oboe_flowgraph_MonoToMultiConverter_android.h"
#include "oboe_flowgraph_MultiToMonoConverter_android.h"
#include "oboe_flowgraph_RampLinear_android.h"
#include "oboe_flowgraph_SinkFloat_android.h"
#include "oboe_flowgraph_SinkI16_android.h"
#include "oboe_flowgraph_SinkI24_android.h"
#include "oboe_flowgraph_SinkI32_android.h"
#include "oboe_flowgraph_SourceFloat_android.h"
#include "oboe_flowgraph_SourceI16_android.h"
#include "oboe_flowgraph_SourceI24_android.h"
#include "oboe_flowgraph_SourceI32_android.h"
#include "oboe_flowgraph_SampleRateConverter_android.h"
using namespace oboe;
using namespace flowgraph;
using namespace resampler;
void DataConversionFlowGraph::setSource(const void *buffer, int32_t numFrames) {
mSource->setData(buffer, numFrames);
}
static MultiChannelResampler::Quality convertOboeSRQualityToMCR(SampleRateConversionQuality quality) {
switch (quality) {
case SampleRateConversionQuality::Fastest:
return MultiChannelResampler::Quality::Fastest;
case SampleRateConversionQuality::Low:
return MultiChannelResampler::Quality::Low;
default:
case SampleRateConversionQuality::Medium:
return MultiChannelResampler::Quality::Medium;
case SampleRateConversionQuality::High:
return MultiChannelResampler::Quality::High;
case SampleRateConversionQuality::Best:
return MultiChannelResampler::Quality::Best;
}
}
// Chain together multiple processors.
// Callback Output
// Use SourceCaller that calls original app callback from the flowgraph.
// The child callback from FilteredAudioStream read()s from the flowgraph.
// Callback Input
// Child callback from FilteredAudioStream writes()s to the flowgraph.
// The output of the flowgraph goes through a BlockWriter to the app callback.
// Blocking Write
// Write buffer is set on an AudioSource.
// Data is pulled through the graph and written to the child stream.
// Blocking Read
// Reads in a loop from the flowgraph Sink to fill the read buffer.
// A SourceCaller then does a blocking read from the child Stream.
//
Result DataConversionFlowGraph::configure(AudioStream *sourceStream, AudioStream *sinkStream) {
FlowGraphPortFloatOutput *lastOutput = nullptr;
bool isOutput = sourceStream->getDirection() == Direction::Output;
bool isInput = !isOutput;
mFilterStream = isOutput ? sourceStream : sinkStream;
AudioFormat sourceFormat = sourceStream->getFormat();
int32_t sourceChannelCount = sourceStream->getChannelCount();
int32_t sourceSampleRate = sourceStream->getSampleRate();
int32_t sourceFramesPerCallback = sourceStream->getFramesPerDataCallback();
AudioFormat sinkFormat = sinkStream->getFormat();
int32_t sinkChannelCount = sinkStream->getChannelCount();
int32_t sinkSampleRate = sinkStream->getSampleRate();
int32_t sinkFramesPerCallback = sinkStream->getFramesPerDataCallback();
LOGI("%s() flowgraph converts channels: %d to %d, format: %d to %d"
", rate: %d to %d, cbsize: %d to %d, qual = %d",
__func__,
sourceChannelCount, sinkChannelCount,
sourceFormat, sinkFormat,
sourceSampleRate, sinkSampleRate,
sourceFramesPerCallback, sinkFramesPerCallback,
sourceStream->getSampleRateConversionQuality());
// Source
// IF OUTPUT and using a callback then call back to the app using a SourceCaller.
// OR IF INPUT and NOT using a callback then read from the child stream using a SourceCaller.
bool isDataCallbackSpecified = sourceStream->isDataCallbackSpecified();
if ((isDataCallbackSpecified && isOutput)
|| (!isDataCallbackSpecified && isInput)) {
int32_t actualSourceFramesPerCallback = (sourceFramesPerCallback == kUnspecified)
? sourceStream->getFramesPerBurst()
: sourceFramesPerCallback;
switch (sourceFormat) {
case AudioFormat::Float:
mSourceCaller = std::make_unique<SourceFloatCaller>(sourceChannelCount,
actualSourceFramesPerCallback);
break;
case AudioFormat::I16:
mSourceCaller = std::make_unique<SourceI16Caller>(sourceChannelCount,
actualSourceFramesPerCallback);
break;
case AudioFormat::I24:
mSourceCaller = std::make_unique<SourceI24Caller>(sourceChannelCount,
actualSourceFramesPerCallback);
break;
case AudioFormat::I32:
mSourceCaller = std::make_unique<SourceI32Caller>(sourceChannelCount,
actualSourceFramesPerCallback);
break;
default:
LOGE("%s() Unsupported source caller format = %d", __func__, sourceFormat);
return Result::ErrorIllegalArgument;
}
mSourceCaller->setStream(sourceStream);
lastOutput = &mSourceCaller->output;
} else {
// IF OUTPUT and NOT using a callback then write to the child stream using a BlockWriter.
// OR IF INPUT and using a callback then write to the app using a BlockWriter.
switch (sourceFormat) {
case AudioFormat::Float:
mSource = std::make_unique<SourceFloat>(sourceChannelCount);
break;
case AudioFormat::I16:
mSource = std::make_unique<SourceI16>(sourceChannelCount);
break;
case AudioFormat::I24:
mSource = std::make_unique<SourceI24>(sourceChannelCount);
break;
case AudioFormat::I32:
mSource = std::make_unique<SourceI32>(sourceChannelCount);
break;
default:
LOGE("%s() Unsupported source format = %d", __func__, sourceFormat);
return Result::ErrorIllegalArgument;
}
if (isInput) {
int32_t actualSinkFramesPerCallback = (sinkFramesPerCallback == kUnspecified)
? sinkStream->getFramesPerBurst()
: sinkFramesPerCallback;
// The BlockWriter is after the Sink so use the SinkStream size.
mBlockWriter.open(actualSinkFramesPerCallback * sinkStream->getBytesPerFrame());
mAppBuffer = std::make_unique<uint8_t[]>(
kDefaultBufferSize * sinkStream->getBytesPerFrame());
}
lastOutput = &mSource->output;
}
// If we are going to reduce the number of channels then do it before the
// sample rate converter.
if (sourceChannelCount > sinkChannelCount) {
if (sinkChannelCount == 1) {
mMultiToMonoConverter = std::make_unique<MultiToMonoConverter>(sourceChannelCount);
lastOutput->connect(&mMultiToMonoConverter->input);
lastOutput = &mMultiToMonoConverter->output;
} else {
mChannelCountConverter = std::make_unique<ChannelCountConverter>(
sourceChannelCount,
sinkChannelCount);
lastOutput->connect(&mChannelCountConverter->input);
lastOutput = &mChannelCountConverter->output;
}
}
// Sample Rate conversion
if (sourceSampleRate != sinkSampleRate) {
// Create a resampler to do the math.
mResampler.reset(MultiChannelResampler::make(lastOutput->getSamplesPerFrame(),
sourceSampleRate,
sinkSampleRate,
convertOboeSRQualityToMCR(
sourceStream->getSampleRateConversionQuality())));
// Make a flowgraph node that uses the resampler.
mRateConverter = std::make_unique<SampleRateConverter>(lastOutput->getSamplesPerFrame(),
*mResampler.get());
lastOutput->connect(&mRateConverter->input);
lastOutput = &mRateConverter->output;
}
// Expand the number of channels if required.
if (sourceChannelCount < sinkChannelCount) {
if (sourceChannelCount == 1) {
mMonoToMultiConverter = std::make_unique<MonoToMultiConverter>(sinkChannelCount);
lastOutput->connect(&mMonoToMultiConverter->input);
lastOutput = &mMonoToMultiConverter->output;
} else {
mChannelCountConverter = std::make_unique<ChannelCountConverter>(
sourceChannelCount,
sinkChannelCount);
lastOutput->connect(&mChannelCountConverter->input);
lastOutput = &mChannelCountConverter->output;
}
}
// Sink
switch (sinkFormat) {
case AudioFormat::Float:
mSink = std::make_unique<SinkFloat>(sinkChannelCount);
break;
case AudioFormat::I16:
mSink = std::make_unique<SinkI16>(sinkChannelCount);
break;
case AudioFormat::I24:
mSink = std::make_unique<SinkI24>(sinkChannelCount);
break;
case AudioFormat::I32:
mSink = std::make_unique<SinkI32>(sinkChannelCount);
break;
default:
LOGE("%s() Unsupported sink format = %d", __func__, sinkFormat);
return Result::ErrorIllegalArgument;;
}
lastOutput->connect(&mSink->input);
return Result::OK;
}
int32_t DataConversionFlowGraph::read(void *buffer, int32_t numFrames, int64_t timeoutNanos) {
if (mSourceCaller) {
mSourceCaller->setTimeoutNanos(timeoutNanos);
}
int32_t numRead = mSink->read(buffer, numFrames);
return numRead;
}
// This is similar to pushing data through the flowgraph.
int32_t DataConversionFlowGraph::write(void *inputBuffer, int32_t numFrames) {
// Put the data from the input at the head of the flowgraph.
mSource->setData(inputBuffer, numFrames);
while (true) {
// Pull and read some data in app format into a small buffer.
int32_t framesRead = mSink->read(mAppBuffer.get(), flowgraph::kDefaultBufferSize);
if (framesRead <= 0) break;
// Write to a block adapter, which will call the destination whenever it has enough data.
int32_t bytesRead = mBlockWriter.write(mAppBuffer.get(),
framesRead * mFilterStream->getBytesPerFrame());
if (bytesRead < 0) return bytesRead; // TODO review
}
return numFrames;
}
int32_t DataConversionFlowGraph::onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) {
int32_t numFrames = numBytes / mFilterStream->getBytesPerFrame();
mCallbackResult = mFilterStream->getDataCallback()->onAudioReady(mFilterStream, buffer, numFrames);
// TODO handle STOP from callback, process data remaining in the block adapter
return numBytes;
}

View File

@ -1,86 +0,0 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_OBOE_FLOW_GRAPH_H
#define OBOE_OBOE_FLOW_GRAPH_H
#include <memory>
#include <stdint.h>
#include <sys/types.h>
#include "oboe_flowgraph_ChannelCountConverter_android.h"
#include "oboe_flowgraph_MonoToMultiConverter_android.h"
#include "oboe_flowgraph_MultiToMonoConverter_android.h"
#include "oboe_flowgraph_SampleRateConverter_android.h"
#include "oboe_oboe_Definitions_android.h"
#include "oboe_common_AudioSourceCaller_android.h"
#include "oboe_common_FixedBlockWriter_android.h"
namespace oboe {
class AudioStream;
class AudioSourceCaller;
/**
* Convert PCM channels, format and sample rate for optimal latency.
*/
class DataConversionFlowGraph : public FixedBlockProcessor {
public:
DataConversionFlowGraph()
: mBlockWriter(*this) {}
void setSource(const void *buffer, int32_t numFrames);
/** Connect several modules together to convert from source to sink.
* This should only be called once for each instance.
*
* @param sourceFormat
* @param sourceChannelCount
* @param sinkFormat
* @param sinkChannelCount
* @return
*/
oboe::Result configure(oboe::AudioStream *sourceStream, oboe::AudioStream *sinkStream);
int32_t read(void *buffer, int32_t numFrames, int64_t timeoutNanos);
int32_t write(void *buffer, int32_t numFrames);
int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) override;
DataCallbackResult getDataCallbackResult() {
return mCallbackResult;
}
private:
std::unique_ptr<flowgraph::FlowGraphSourceBuffered> mSource;
std::unique_ptr<AudioSourceCaller> mSourceCaller;
std::unique_ptr<flowgraph::MonoToMultiConverter> mMonoToMultiConverter;
std::unique_ptr<flowgraph::MultiToMonoConverter> mMultiToMonoConverter;
std::unique_ptr<flowgraph::ChannelCountConverter> mChannelCountConverter;
std::unique_ptr<resampler::MultiChannelResampler> mResampler;
std::unique_ptr<flowgraph::SampleRateConverter> mRateConverter;
std::unique_ptr<flowgraph::FlowGraphSink> mSink;
FixedBlockWriter mBlockWriter;
DataCallbackResult mCallbackResult = DataCallbackResult::Continue;
AudioStream *mFilterStream = nullptr;
std::unique_ptr<uint8_t[]> mAppBuffer;
};
}
#endif //OBOE_OBOE_FLOW_GRAPH_H

View File

@ -1,106 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#include <memory>
#include "oboe_common_OboeDebug_android.h"
#include "oboe_common_FilterAudioStream_android.h"
using namespace oboe;
using namespace flowgraph;
// Output callback uses FixedBlockReader::read()
// <= SourceFloatCaller::onProcess()
// <=== DataConversionFlowGraph::read()
// <== FilterAudioStream::onAudioReady()
//
// Output blocking uses no block adapter because AAudio can accept
// writes of any size. It uses DataConversionFlowGraph::read() <== FilterAudioStream::write() <= app
//
// Input callback uses FixedBlockWriter::write()
// <= DataConversionFlowGraph::write()
// <= FilterAudioStream::onAudioReady()
//
// Input blocking uses FixedBlockReader::read() // TODO may not need block adapter
// <= SourceFloatCaller::onProcess()
// <=== SinkFloat::read()
// <= DataConversionFlowGraph::read()
// <== FilterAudioStream::read()
// <= app
Result FilterAudioStream::configureFlowGraph() {
mFlowGraph = std::make_unique<DataConversionFlowGraph>();
bool isOutput = getDirection() == Direction::Output;
AudioStream *sourceStream = isOutput ? this : mChildStream.get();
AudioStream *sinkStream = isOutput ? mChildStream.get() : this;
mRateScaler = ((double) getSampleRate()) / mChildStream->getSampleRate();
return mFlowGraph->configure(sourceStream, sinkStream);
}
// Put the data to be written at the source end of the flowgraph.
// Then read (pull) the data from the flowgraph and write it to the
// child stream.
ResultWithValue<int32_t> FilterAudioStream::write(const void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) {
int32_t framesWritten = 0;
mFlowGraph->setSource(buffer, numFrames);
while (true) {
int32_t numRead = mFlowGraph->read(mBlockingBuffer.get(),
getFramesPerBurst(),
timeoutNanoseconds);
if (numRead < 0) {
return ResultWithValue<int32_t>::createBasedOnSign(numRead);
}
if (numRead == 0) {
break; // finished processing the source buffer
}
auto writeResult = mChildStream->write(mBlockingBuffer.get(),
numRead,
timeoutNanoseconds);
if (!writeResult) {
return writeResult;
}
framesWritten += writeResult.value();
}
return ResultWithValue<int32_t>::createBasedOnSign(framesWritten);
}
// Read (pull) the data we want from the sink end of the flowgraph.
// The necessary data will be read from the child stream using a flowgraph callback.
ResultWithValue<int32_t> FilterAudioStream::read(void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) {
int32_t framesRead = mFlowGraph->read(buffer, numFrames, timeoutNanoseconds);
return ResultWithValue<int32_t>::createBasedOnSign(framesRead);
}
DataCallbackResult FilterAudioStream::onAudioReady(AudioStream *oboeStream,
void *audioData,
int32_t numFrames) {
int32_t framesProcessed;
if (oboeStream->getDirection() == Direction::Output) {
framesProcessed = mFlowGraph->read(audioData, numFrames, 0 /* timeout */);
} else {
framesProcessed = mFlowGraph->write(audioData, numFrames);
}
return (framesProcessed < numFrames)
? DataCallbackResult::Stop
: mFlowGraph->getDataCallbackResult();
}

View File

@ -1,222 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_FILTER_AUDIO_STREAM_H
#define OBOE_FILTER_AUDIO_STREAM_H
#include <memory>
#include "oboe_oboe_AudioStream_android.h"
#include "oboe_common_DataConversionFlowGraph_android.h"
namespace oboe {
/**
* An AudioStream that wraps another AudioStream and provides audio data conversion.
* Operations may include channel conversion, data format conversion and/or sample rate conversion.
*/
class FilterAudioStream : public AudioStream, AudioStreamCallback {
public:
/**
* Construct an `AudioStream` using the given `AudioStreamBuilder` and a child AudioStream.
*
* This should only be called internally by AudioStreamBuilder.
* Ownership of childStream will be passed to this object.
*
* @param builder containing all the stream's attributes
*/
FilterAudioStream(const AudioStreamBuilder &builder, AudioStream *childStream)
: AudioStream(builder)
, mChildStream(childStream) {
// Intercept the callback if used.
if (builder.isErrorCallbackSpecified()) {
mErrorCallback = mChildStream->swapErrorCallback(this);
}
if (builder.isDataCallbackSpecified()) {
mDataCallback = mChildStream->swapDataCallback(this);
} else {
const int size = childStream->getFramesPerBurst() * childStream->getBytesPerFrame();
mBlockingBuffer = std::make_unique<uint8_t[]>(size);
}
// Copy parameters that may not match builder.
mBufferCapacityInFrames = mChildStream->getBufferCapacityInFrames();
mPerformanceMode = mChildStream->getPerformanceMode();
mInputPreset = mChildStream->getInputPreset();
mFramesPerBurst = mChildStream->getFramesPerBurst();
}
virtual ~FilterAudioStream() = default;
AudioStream *getChildStream() const {
return mChildStream.get();
}
Result configureFlowGraph();
// Close child and parent.
Result close() override {
const Result result1 = mChildStream->close();
const Result result2 = AudioStream::close();
return (result1 != Result::OK ? result1 : result2);
}
/**
* Start the stream asynchronously. Returns immediately (does not block). Equivalent to calling
* `start(0)`.
*/
Result requestStart() override {
return mChildStream->requestStart();
}
/**
* Pause the stream asynchronously. Returns immediately (does not block). Equivalent to calling
* `pause(0)`.
*/
Result requestPause() override {
return mChildStream->requestPause();
}
/**
* Flush the stream asynchronously. Returns immediately (does not block). Equivalent to calling
* `flush(0)`.
*/
Result requestFlush() override {
return mChildStream->requestFlush();
}
/**
* Stop the stream asynchronously. Returns immediately (does not block). Equivalent to calling
* `stop(0)`.
*/
Result requestStop() override {
return mChildStream->requestStop();
}
ResultWithValue<int32_t> read(void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
ResultWithValue<int32_t> write(const void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
StreamState getState() override {
return mChildStream->getState();
}
Result waitForStateChange(
StreamState inputState,
StreamState *nextState,
int64_t timeoutNanoseconds) override {
return mChildStream->waitForStateChange(inputState, nextState, timeoutNanoseconds);
}
bool isXRunCountSupported() const override {
return mChildStream->isXRunCountSupported();
}
AudioApi getAudioApi() const override {
return mChildStream->getAudioApi();
}
void updateFramesWritten() override {
// TODO for output, just count local writes?
mFramesWritten = static_cast<int64_t>(mChildStream->getFramesWritten() * mRateScaler);
}
void updateFramesRead() override {
// TODO for input, just count local reads?
mFramesRead = static_cast<int64_t>(mChildStream->getFramesRead() * mRateScaler);
}
void *getUnderlyingStream() const override {
return mChildStream->getUnderlyingStream();
}
ResultWithValue<int32_t> setBufferSizeInFrames(int32_t requestedFrames) override {
return mChildStream->setBufferSizeInFrames(requestedFrames);
}
int32_t getBufferSizeInFrames() override {
mBufferSizeInFrames = mChildStream->getBufferSizeInFrames();
return mBufferSizeInFrames;
}
ResultWithValue<int32_t> getXRunCount() override {
return mChildStream->getXRunCount();
}
ResultWithValue<double> calculateLatencyMillis() override {
// This will automatically include the latency of the flowgraph?
return mChildStream->calculateLatencyMillis();
}
Result getTimestamp(clockid_t clockId,
int64_t *framePosition,
int64_t *timeNanoseconds) override {
int64_t childPosition = 0;
Result result = mChildStream->getTimestamp(clockId, &childPosition, timeNanoseconds);
// It is OK if framePosition is null.
if (framePosition) {
*framePosition = childPosition * mRateScaler;
}
return result;
}
DataCallbackResult onAudioReady(AudioStream *oboeStream,
void *audioData,
int32_t numFrames) override;
bool onError(AudioStream * audioStream, Result error) override {
if (mErrorCallback != nullptr) {
return mErrorCallback->onError(this, error);
}
return false;
}
void onErrorBeforeClose(AudioStream *oboeStream, Result error) override {
if (mErrorCallback != nullptr) {
mErrorCallback->onErrorBeforeClose(this, error);
}
}
void onErrorAfterClose(AudioStream *oboeStream, Result error) override {
// Close this parent stream because the callback will only close the child.
AudioStream::close();
if (mErrorCallback != nullptr) {
mErrorCallback->onErrorAfterClose(this, error);
}
}
/**
* @return last result passed from an error callback
*/
oboe::Result getLastErrorCallbackResult() const override {
return mChildStream->getLastErrorCallbackResult();
}
private:
std::unique_ptr<AudioStream> mChildStream; // this stream wraps the child stream
std::unique_ptr<DataConversionFlowGraph> mFlowGraph; // for converting data
std::unique_ptr<uint8_t[]> mBlockingBuffer; // temp buffer for write()
double mRateScaler = 1.0; // ratio parent/child sample rates
};
} // oboe
#endif //OBOE_FILTER_AUDIO_STREAM_H

View File

@ -1,38 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.
*/
#include <stdint.h>
#include "oboe_common_FixedBlockAdapter_android.h"
FixedBlockAdapter::~FixedBlockAdapter() {
}
int32_t FixedBlockAdapter::open(int32_t bytesPerFixedBlock)
{
mSize = bytesPerFixedBlock;
mStorage = std::make_unique<uint8_t[]>(bytesPerFixedBlock);
mPosition = 0;
return 0;
}
int32_t FixedBlockAdapter::close()
{
mStorage.reset(nullptr);
mSize = 0;
mPosition = 0;
return 0;
}

View File

@ -1,67 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.
*/
#ifndef AAUDIO_FIXED_BLOCK_ADAPTER_H
#define AAUDIO_FIXED_BLOCK_ADAPTER_H
#include <memory>
#include <stdint.h>
#include <sys/types.h>
/**
* Interface for a class that needs fixed-size blocks.
*/
class FixedBlockProcessor {
public:
virtual ~FixedBlockProcessor() = default;
/**
*
* @param buffer Pointer to first byte of data.
* @param numBytes This will be a fixed size specified in FixedBlockAdapter::open().
* @return Number of bytes processed or a negative error code.
*/
virtual int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) = 0;
};
/**
* Base class for a variable-to-fixed-size block adapter.
*/
class FixedBlockAdapter
{
public:
FixedBlockAdapter(FixedBlockProcessor &fixedBlockProcessor)
: mFixedBlockProcessor(fixedBlockProcessor) {}
virtual ~FixedBlockAdapter();
/**
* Allocate internal resources needed for buffering data.
*/
virtual int32_t open(int32_t bytesPerFixedBlock);
/**
* Free internal resources.
*/
int32_t close();
protected:
FixedBlockProcessor &mFixedBlockProcessor;
std::unique_ptr<uint8_t[]> mStorage; // Store data here while assembling buffers.
int32_t mSize = 0; // Size in bytes of the fixed size buffer.
int32_t mPosition = 0; // Offset of the last byte read or written.
};
#endif /* AAUDIO_FIXED_BLOCK_ADAPTER_H */

View File

@ -1,73 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.
*/
#include <stdint.h>
#include <memory.h>
#include "oboe_common_FixedBlockAdapter_android.h"
#include "oboe_common_FixedBlockReader_android.h"
FixedBlockReader::FixedBlockReader(FixedBlockProcessor &fixedBlockProcessor)
: FixedBlockAdapter(fixedBlockProcessor) {
mPosition = mSize;
}
int32_t FixedBlockReader::open(int32_t bytesPerFixedBlock) {
int32_t result = FixedBlockAdapter::open(bytesPerFixedBlock);
mPosition = 0;
mValid = 0;
return result;
}
int32_t FixedBlockReader::readFromStorage(uint8_t *buffer, int32_t numBytes) {
int32_t bytesToRead = numBytes;
int32_t dataAvailable = mValid - mPosition;
if (bytesToRead > dataAvailable) {
bytesToRead = dataAvailable;
}
memcpy(buffer, mStorage.get() + mPosition, bytesToRead);
mPosition += bytesToRead;
return bytesToRead;
}
int32_t FixedBlockReader::read(uint8_t *buffer, int32_t numBytes) {
int32_t bytesRead;
int32_t bytesLeft = numBytes;
while(bytesLeft > 0) {
if (mPosition < mValid) {
// Use up bytes currently in storage.
bytesRead = readFromStorage(buffer, bytesLeft);
buffer += bytesRead;
bytesLeft -= bytesRead;
} else if (bytesLeft >= mSize) {
// Nothing in storage. Read through if enough for a complete block.
bytesRead = mFixedBlockProcessor.onProcessFixedBlock(buffer, mSize);
if (bytesRead < 0) return bytesRead;
buffer += bytesRead;
bytesLeft -= bytesRead;
} else {
// Just need a partial block so we have to reload storage.
bytesRead = mFixedBlockProcessor.onProcessFixedBlock(mStorage.get(), mSize);
if (bytesRead < 0) return bytesRead;
mPosition = 0;
mValid = bytesRead;
if (bytesRead == 0) break;
}
}
return numBytes - bytesLeft;
}

View File

@ -1,60 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.
*/
#ifndef AAUDIO_FIXED_BLOCK_READER_H
#define AAUDIO_FIXED_BLOCK_READER_H
#include <stdint.h>
#include "oboe_common_FixedBlockAdapter_android.h"
/**
* Read from a fixed-size block to a variable sized block.
*
* This can be used to convert a pull data flow from fixed sized buffers to variable sized buffers.
* An example would be an audio output callback that reads from the app.
*/
class FixedBlockReader : public FixedBlockAdapter
{
public:
FixedBlockReader(FixedBlockProcessor &fixedBlockProcessor);
virtual ~FixedBlockReader() = default;
int32_t open(int32_t bytesPerFixedBlock) override;
/**
* Read into a variable sized block.
*
* Note that if the fixed-sized blocks must be aligned, then the variable-sized blocks
* must have the same alignment.
* For example, if the fixed-size blocks must be a multiple of 8, then the variable-sized
* blocks must also be a multiple of 8.
*
* @param buffer
* @param numBytes
* @return Number of bytes read or a negative error code.
*/
int32_t read(uint8_t *buffer, int32_t numBytes);
private:
int32_t readFromStorage(uint8_t *buffer, int32_t numBytes);
int32_t mValid = 0; // Number of valid bytes in mStorage.
};
#endif /* AAUDIO_FIXED_BLOCK_READER_H */

View File

@ -1,73 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.
*/
#include <stdint.h>
#include <memory.h>
#include "oboe_common_FixedBlockAdapter_android.h"
#include "oboe_common_FixedBlockWriter_android.h"
FixedBlockWriter::FixedBlockWriter(FixedBlockProcessor &fixedBlockProcessor)
: FixedBlockAdapter(fixedBlockProcessor) {}
int32_t FixedBlockWriter::writeToStorage(uint8_t *buffer, int32_t numBytes) {
int32_t bytesToStore = numBytes;
int32_t roomAvailable = mSize - mPosition;
if (bytesToStore > roomAvailable) {
bytesToStore = roomAvailable;
}
memcpy(mStorage.get() + mPosition, buffer, bytesToStore);
mPosition += bytesToStore;
return bytesToStore;
}
int32_t FixedBlockWriter::write(uint8_t *buffer, int32_t numBytes) {
int32_t bytesLeft = numBytes;
// If we already have data in storage then add to it.
if (mPosition > 0) {
int32_t bytesWritten = writeToStorage(buffer, bytesLeft);
buffer += bytesWritten;
bytesLeft -= bytesWritten;
// If storage full then flush it out
if (mPosition == mSize) {
bytesWritten = mFixedBlockProcessor.onProcessFixedBlock(mStorage.get(), mSize);
if (bytesWritten < 0) return bytesWritten;
mPosition = 0;
if (bytesWritten < mSize) {
// Only some of the data was written! This should not happen.
return -1;
}
}
}
// Write through if enough for a complete block.
while(bytesLeft > mSize) {
int32_t bytesWritten = mFixedBlockProcessor.onProcessFixedBlock(buffer, mSize);
if (bytesWritten < 0) return bytesWritten;
buffer += bytesWritten;
bytesLeft -= bytesWritten;
}
// Save any remaining partial blocks for next time.
if (bytesLeft > 0) {
int32_t bytesWritten = writeToStorage(buffer, bytesLeft);
bytesLeft -= bytesWritten;
}
return numBytes - bytesLeft;
}

View File

@ -1,54 +0,0 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.
*/
#ifndef AAUDIO_FIXED_BLOCK_WRITER_H
#define AAUDIO_FIXED_BLOCK_WRITER_H
#include <stdint.h>
#include "oboe_common_FixedBlockAdapter_android.h"
/**
* This can be used to convert a push data flow from variable sized buffers to fixed sized buffers.
* An example would be an audio input callback.
*/
class FixedBlockWriter : public FixedBlockAdapter
{
public:
FixedBlockWriter(FixedBlockProcessor &fixedBlockProcessor);
virtual ~FixedBlockWriter() = default;
/**
* Write from a variable sized block.
*
* Note that if the fixed-sized blocks must be aligned, then the variable-sized blocks
* must have the same alignment.
* For example, if the fixed-size blocks must be a multiple of 8, then the variable-sized
* blocks must also be a multiple of 8.
*
* @param buffer
* @param numBytes
* @return Number of bytes written or a negative error code.
*/
int32_t write(uint8_t *buffer, int32_t numBytes);
private:
int32_t writeToStorage(uint8_t *buffer, int32_t numBytes);
};
#endif /* AAUDIO_FIXED_BLOCK_WRITER_H */

View File

@ -1,108 +0,0 @@
/*
* Copyright 2017 The Android Open Source Project
*
* 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.
*/
#include "oboe_oboe_LatencyTuner_android.h"
using namespace oboe;
LatencyTuner::LatencyTuner(AudioStream &stream)
: LatencyTuner(stream, stream.getBufferCapacityInFrames()) {
}
LatencyTuner::LatencyTuner(oboe::AudioStream &stream, int32_t maximumBufferSize)
: mStream(stream)
, mMaxBufferSize(maximumBufferSize) {
int32_t burstSize = stream.getFramesPerBurst();
setMinimumBufferSize(kDefaultNumBursts * burstSize);
setBufferSizeIncrement(burstSize);
reset();
}
Result LatencyTuner::tune() {
if (mState == State::Unsupported) {
return Result::ErrorUnimplemented;
}
Result result = Result::OK;
// Process reset requests.
int32_t numRequests = mLatencyTriggerRequests.load();
if (numRequests != mLatencyTriggerResponses.load()) {
mLatencyTriggerResponses.store(numRequests);
reset();
}
// Set state to Active if the idle countdown has reached zero.
if (mState == State::Idle && --mIdleCountDown <= 0) {
mState = State::Active;
}
// When state is Active attempt to change the buffer size if the number of xRuns has increased.
if (mState == State::Active) {
auto xRunCountResult = mStream.getXRunCount();
if (xRunCountResult == Result::OK) {
if ((xRunCountResult.value() - mPreviousXRuns) > 0) {
mPreviousXRuns = xRunCountResult.value();
int32_t oldBufferSize = mStream.getBufferSizeInFrames();
int32_t requestedBufferSize = oldBufferSize + getBufferSizeIncrement();
// Do not request more than the maximum buffer size (which was either user-specified
// or was from stream->getBufferCapacityInFrames())
if (requestedBufferSize > mMaxBufferSize) requestedBufferSize = mMaxBufferSize;
// Note that this will not allocate more memory. It simply determines
// how much of the existing buffer capacity will be used. The size will be
// clipped to the bufferCapacity by AAudio.
auto setBufferResult = mStream.setBufferSizeInFrames(requestedBufferSize);
if (setBufferResult != Result::OK) {
result = setBufferResult;
mState = State::Unsupported;
} else if (setBufferResult.value() == oldBufferSize) {
mState = State::AtMax;
}
}
} else {
mState = State::Unsupported;
}
}
if (mState == State::Unsupported) {
result = Result::ErrorUnimplemented;
}
if (mState == State::AtMax) {
result = Result::OK;
}
return result;
}
void LatencyTuner::requestReset() {
if (mState != State::Unsupported) {
mLatencyTriggerRequests++;
}
}
void LatencyTuner::reset() {
mState = State::Idle;
mIdleCountDown = kIdleCount;
// Set to minimal latency
mStream.setBufferSizeInFrames(getMinimumBufferSize());
}
bool LatencyTuner::isAtMaximumBufferSize() {
return mState == State::AtMax;
}

View File

@ -1,112 +0,0 @@
/*
* Copyright 2016 The Android Open Source Project
*
* 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.
*/
#ifndef COMMON_MONOTONIC_COUNTER_H
#define COMMON_MONOTONIC_COUNTER_H
#include <cstdint>
/**
* Maintain a 64-bit monotonic counter.
* Can be used to track a 32-bit counter that wraps or gets reset.
*
* Note that this is not atomic and has no interior locks.
* A caller will need to provide their own exterior locking
* if they need to use it from multiple threads.
*/
class MonotonicCounter {
public:
MonotonicCounter() {}
virtual ~MonotonicCounter() {}
/**
* @return current value of the counter
*/
int64_t get() const {
return mCounter64;
}
/**
* set the current value of the counter
*/
void set(int64_t counter) {
mCounter64 = counter;
}
/**
* Advance the counter if delta is positive.
* @return current value of the counter
*/
int64_t increment(int64_t delta) {
if (delta > 0) {
mCounter64 += delta;
}
return mCounter64;
}
/**
* Advance the 64-bit counter if (current32 - previousCurrent32) > 0.
* This can be used to convert a 32-bit counter that may be wrapping into
* a monotonic 64-bit counter.
*
* This counter32 should NOT be allowed to advance by more than 0x7FFFFFFF between calls.
* Think of the wrapping counter like a sine wave. If the frequency of the signal
* is more than half the sampling rate (Nyquist rate) then you cannot measure it properly.
* If the counter wraps around every 24 hours then we should measure it with a period
* of less than 12 hours.
*
* @return current value of the 64-bit counter
*/
int64_t update32(int32_t counter32) {
int32_t delta = counter32 - mCounter32;
// protect against the mCounter64 going backwards
if (delta > 0) {
mCounter64 += delta;
mCounter32 = counter32;
}
return mCounter64;
}
/**
* Reset the stored value of the 32-bit counter.
* This is used if your counter32 has been reset to zero.
*/
void reset32() {
mCounter32 = 0;
}
/**
* Round 64-bit counter up to a multiple of the period.
*
* The period must be positive.
*
* @param period might be, for example, a buffer capacity
*/
void roundUp64(int32_t period) {
if (period > 0) {
int64_t numPeriods = (mCounter64 + period - 1) / period;
mCounter64 = numPeriods * period;
}
}
private:
int64_t mCounter64 = 0;
int32_t mCounter32 = 0;
};
#endif //COMMON_MONOTONIC_COUNTER_H

View File

@ -1,41 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* 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.
*
*/
#ifndef OBOE_DEBUG_H
#define OBOE_DEBUG_H
#include <android/log.h>
#ifndef MODULE_NAME
#define MODULE_NAME "OboeAudio"
#endif
// Always log INFO and errors.
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, MODULE_NAME, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, MODULE_NAME, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, MODULE_NAME, __VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, MODULE_NAME, __VA_ARGS__)
#if OBOE_ENABLE_LOGGING
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, MODULE_NAME, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, MODULE_NAME, __VA_ARGS__)
#else
#define LOGV(...)
#define LOGD(...)
#endif
#endif //OBOE_DEBUG_H

View File

@ -1,237 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#include "oboe_oboe_AudioStreamBuilder_android.h"
#include "oboe_oboe_Oboe_android.h"
#include "oboe_common_OboeDebug_android.h"
#include "oboe_common_QuirksManager_android.h"
using namespace oboe;
int32_t QuirksManager::DeviceQuirks::clipBufferSize(AudioStream &stream,
int32_t requestedSize) {
if (!OboeGlobals::areWorkaroundsEnabled()) {
return requestedSize;
}
int bottomMargin = kDefaultBottomMarginInBursts;
int topMargin = kDefaultTopMarginInBursts;
if (isMMapUsed(stream)) {
if (stream.getSharingMode() == SharingMode::Exclusive) {
bottomMargin = getExclusiveBottomMarginInBursts();
topMargin = getExclusiveTopMarginInBursts();
}
} else {
bottomMargin = kLegacyBottomMarginInBursts;
}
int32_t burst = stream.getFramesPerBurst();
int32_t minSize = bottomMargin * burst;
int32_t adjustedSize = requestedSize;
if (adjustedSize < minSize ) {
adjustedSize = minSize;
} else {
int32_t maxSize = stream.getBufferCapacityInFrames() - (topMargin * burst);
if (adjustedSize > maxSize ) {
adjustedSize = maxSize;
}
}
return adjustedSize;
}
bool QuirksManager::DeviceQuirks::isAAudioMMapPossible(const AudioStreamBuilder &builder) const {
bool isSampleRateCompatible =
builder.getSampleRate() == oboe::Unspecified
|| builder.getSampleRate() == kCommonNativeRate
|| builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None;
return builder.getPerformanceMode() == PerformanceMode::LowLatency
&& isSampleRateCompatible
&& builder.getChannelCount() <= kChannelCountStereo;
}
class SamsungDeviceQuirks : public QuirksManager::DeviceQuirks {
public:
SamsungDeviceQuirks() {
std::string arch = getPropertyString("ro.arch");
isExynos = (arch.rfind("exynos", 0) == 0); // starts with?
std::string chipname = getPropertyString("ro.hardware.chipname");
isExynos9810 = (chipname == "exynos9810");
isExynos990 = (chipname == "exynos990");
isExynos850 = (chipname == "exynos850");
mBuildChangelist = getPropertyInteger("ro.build.changelist", 0);
}
virtual ~SamsungDeviceQuirks() = default;
int32_t getExclusiveBottomMarginInBursts() const override {
// TODO Make this conditional on build version when MMAP timing improves.
return isExynos ? kBottomMarginExynos : kBottomMarginOther;
}
int32_t getExclusiveTopMarginInBursts() const override {
return kTopMargin;
}
// See Oboe issues #824 and #1247 for more information.
bool isMonoMMapActuallyStereo() const override {
return isExynos9810 || isExynos850; // TODO We can make this version specific if it gets fixed.
}
bool isAAudioMMapPossible(const AudioStreamBuilder &builder) const override {
return DeviceQuirks::isAAudioMMapPossible(builder)
// Samsung says they use Legacy for Camcorder
&& builder.getInputPreset() != oboe::InputPreset::Camcorder;
}
bool isMMapSafe(const AudioStreamBuilder &builder) override {
const bool isInput = builder.getDirection() == Direction::Input;
// This detects b/159066712 , S20 LSI has corrupt low latency audio recording
// and turns off MMAP.
// See also https://github.com/google/oboe/issues/892
bool mRecordingCorrupted = isInput
&& isExynos990
&& mBuildChangelist < 19350896;
return !mRecordingCorrupted;
}
private:
// Stay farther away from DSP position on Exynos devices.
static constexpr int32_t kBottomMarginExynos = 2;
static constexpr int32_t kBottomMarginOther = 1;
static constexpr int32_t kTopMargin = 1;
bool isExynos = false;
bool isExynos9810 = false;
bool isExynos990 = false;
bool isExynos850 = false;
int mBuildChangelist = 0;
};
QuirksManager::QuirksManager() {
std::string manufacturer = getPropertyString("ro.product.manufacturer");
if (manufacturer == "samsung") {
mDeviceQuirks = std::make_unique<SamsungDeviceQuirks>();
} else {
mDeviceQuirks = std::make_unique<DeviceQuirks>();
}
}
bool QuirksManager::isConversionNeeded(
const AudioStreamBuilder &builder,
AudioStreamBuilder &childBuilder) {
bool conversionNeeded = false;
const bool isLowLatency = builder.getPerformanceMode() == PerformanceMode::LowLatency;
const bool isInput = builder.getDirection() == Direction::Input;
const bool isFloat = builder.getFormat() == AudioFormat::Float;
// There are multiple bugs involving using callback with a specified callback size.
// Issue #778: O to Q had a problem with Legacy INPUT streams for FLOAT streams
// and a specified callback size. It would assert because of a bad buffer size.
//
// Issue #973: O to R had a problem with Legacy output streams using callback and a specified callback size.
// An AudioTrack stream could still be running when the AAudio FixedBlockReader was closed.
// Internally b/161914201#comment25
//
// Issue #983: O to R would glitch if the framesPerCallback was too small.
//
// Most of these problems were related to Legacy stream. MMAP was OK. But we don't
// know if we will get an MMAP stream. So, to be safe, just do the conversion in Oboe.
if (OboeGlobals::areWorkaroundsEnabled()
&& builder.willUseAAudio()
&& builder.isDataCallbackSpecified()
&& builder.getFramesPerDataCallback() != 0
&& getSdkVersion() <= __ANDROID_API_R__) {
LOGI("QuirksManager::%s() avoid setFramesPerCallback(n>0)", __func__);
childBuilder.setFramesPerCallback(oboe::Unspecified);
conversionNeeded = true;
}
// If a SAMPLE RATE is specified for low latency then let the native code choose an optimal rate.
// TODO There may be a problem if the devices supports low latency
// at a higher rate than the default.
if (builder.getSampleRate() != oboe::Unspecified
&& builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None
&& isLowLatency
) {
childBuilder.setSampleRate(oboe::Unspecified); // native API decides the best sample rate
conversionNeeded = true;
}
// Data Format
// OpenSL ES and AAudio before P do not support FAST path for FLOAT capture.
if (isFloat
&& isInput
&& builder.isFormatConversionAllowed()
&& isLowLatency
&& (!builder.willUseAAudio() || (getSdkVersion() < __ANDROID_API_P__))
) {
childBuilder.setFormat(AudioFormat::I16); // needed for FAST track
conversionNeeded = true;
LOGI("QuirksManager::%s() forcing internal format to I16 for low latency", __func__);
}
// Add quirk for float output on API <21
if (isFloat
&& !isInput
&& getSdkVersion() < __ANDROID_API_L__
&& builder.isFormatConversionAllowed()
) {
childBuilder.setFormat(AudioFormat::I16);
conversionNeeded = true;
LOGI("QuirksManager::%s() float was requested but not supported on pre-L devices, "
"creating an underlying I16 stream and using format conversion to provide a float "
"stream", __func__);
}
// Channel Count conversions
if (OboeGlobals::areWorkaroundsEnabled()
&& builder.isChannelConversionAllowed()
&& builder.getChannelCount() == kChannelCountStereo
&& isInput
&& isLowLatency
&& (!builder.willUseAAudio() && (getSdkVersion() == __ANDROID_API_O__))
) {
// Workaround for heap size regression in O.
// b/66967812 AudioRecord does not allow FAST track for stereo capture in O
childBuilder.setChannelCount(kChannelCountMono);
conversionNeeded = true;
LOGI("QuirksManager::%s() using mono internally for low latency on O", __func__);
} else if (OboeGlobals::areWorkaroundsEnabled()
&& builder.getChannelCount() == kChannelCountMono
&& isInput
&& mDeviceQuirks->isMonoMMapActuallyStereo()
&& builder.willUseAAudio()
// Note: we might use this workaround on a device that supports
// MMAP but will use Legacy for this stream. But this will only happen
// on devices that have the broken mono.
&& mDeviceQuirks->isAAudioMMapPossible(builder)
) {
// Workaround for mono actually running in stereo mode.
childBuilder.setChannelCount(kChannelCountStereo); // Use stereo and extract first channel.
conversionNeeded = true;
LOGI("QuirksManager::%s() using stereo internally to avoid broken mono", __func__);
}
// Note that MMAP does not support mono in 8.1. But that would only matter on Pixel 1
// phones and they have almost all been updated to 9.0.
return conversionNeeded;
}
bool QuirksManager::isMMapSafe(AudioStreamBuilder &builder) {
if (!OboeGlobals::areWorkaroundsEnabled()) return true;
return mDeviceQuirks->isMMapSafe(builder);
}

View File

@ -1,131 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_QUIRKS_MANAGER_H
#define OBOE_QUIRKS_MANAGER_H
#include <memory>
#include "oboe_oboe_AudioStreamBuilder_android.h"
#include "oboe_aaudio_AudioStreamAAudio_android.h"
#ifndef __ANDROID_API_R__
#define __ANDROID_API_R__ 30
#endif
namespace oboe {
/**
* INTERNAL USE ONLY.
*
* Based on manufacturer, model and Android version number
* decide whether data conversion needs to occur.
*
* This also manages device and version specific workarounds.
*/
class QuirksManager {
public:
static QuirksManager &getInstance() {
static QuirksManager instance; // singleton
return instance;
}
QuirksManager();
virtual ~QuirksManager() = default;
/**
* Do we need to do channel, format or rate conversion to provide a low latency
* stream for this builder? If so then provide a builder for the native child stream
* that will be used to get low latency.
*
* @param builder builder provided by application
* @param childBuilder modified builder appropriate for the underlying device
* @return true if conversion is needed
*/
bool isConversionNeeded(const AudioStreamBuilder &builder, AudioStreamBuilder &childBuilder);
static bool isMMapUsed(AudioStream &stream) {
bool answer = false;
if (stream.getAudioApi() == AudioApi::AAudio) {
AudioStreamAAudio *streamAAudio =
reinterpret_cast<AudioStreamAAudio *>(&stream);
answer = streamAAudio->isMMapUsed();
}
return answer;
}
virtual int32_t clipBufferSize(AudioStream &stream, int32_t bufferSize) {
return mDeviceQuirks->clipBufferSize(stream, bufferSize);
}
class DeviceQuirks {
public:
virtual ~DeviceQuirks() = default;
/**
* Restrict buffer size. This is mainly to avoid glitches caused by MMAP
* timestamp inaccuracies.
* @param stream
* @param requestedSize
* @return
*/
int32_t clipBufferSize(AudioStream &stream, int32_t requestedSize);
// Exclusive MMAP streams can have glitches because they are using a timing
// model of the DSP to control IO instead of direct synchronization.
virtual int32_t getExclusiveBottomMarginInBursts() const {
return kDefaultBottomMarginInBursts;
}
virtual int32_t getExclusiveTopMarginInBursts() const {
return kDefaultTopMarginInBursts;
}
// On some devices, you can open a mono stream but it is actually running in stereo!
virtual bool isMonoMMapActuallyStereo() const {
return false;
}
virtual bool isAAudioMMapPossible(const AudioStreamBuilder &builder) const;
virtual bool isMMapSafe(const AudioStreamBuilder & /* builder */ ) {
return true;
}
static constexpr int32_t kDefaultBottomMarginInBursts = 0;
static constexpr int32_t kDefaultTopMarginInBursts = 0;
// For Legacy streams, do not let the buffer go below one burst.
// b/129545119 | AAudio Legacy allows setBufferSizeInFrames too low
// Fixed in Q
static constexpr int32_t kLegacyBottomMarginInBursts = 1;
static constexpr int32_t kCommonNativeRate = 48000; // very typical native sample rate
};
bool isMMapSafe(AudioStreamBuilder &builder);
private:
static constexpr int32_t kChannelCountMono = 1;
static constexpr int32_t kChannelCountStereo = 2;
std::unique_ptr<DeviceQuirks> mDeviceQuirks{};
};
}
#endif //OBOE_QUIRKS_MANAGER_H

View File

@ -1,30 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#include <algorithm>
#include <unistd.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_common_SourceFloatCaller_android.h"
using namespace oboe;
using namespace flowgraph;
int32_t SourceFloatCaller::onProcess(int32_t numFrames) {
int32_t numBytes = mStream->getBytesPerFrame() * numFrames;
int32_t bytesRead = mBlockReader.read((uint8_t *) output.getBuffer(), numBytes);
int32_t framesRead = bytesRead / mStream->getBytesPerFrame();
return framesRead;
}

View File

@ -1,44 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_SOURCE_FLOAT_CALLER_H
#define OBOE_SOURCE_FLOAT_CALLER_H
#include <unistd.h>
#include <sys/types.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_common_AudioSourceCaller_android.h"
#include "oboe_common_FixedBlockReader_android.h"
namespace oboe {
/**
* AudioSource that uses callback to get more float data.
*/
class SourceFloatCaller : public AudioSourceCaller {
public:
SourceFloatCaller(int32_t channelCount, int32_t framesPerCallback)
: AudioSourceCaller(channelCount, framesPerCallback, (int32_t)sizeof(float)) {}
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceFloatCaller";
}
};
}
#endif //OBOE_SOURCE_FLOAT_CALLER_H

View File

@ -1,47 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#include <algorithm>
#include <unistd.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_common_SourceI16Caller_android.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace oboe;
using namespace flowgraph;
int32_t SourceI16Caller::onProcess(int32_t numFrames) {
int32_t numBytes = mStream->getBytesPerFrame() * numFrames;
int32_t bytesRead = mBlockReader.read((uint8_t *) mConversionBuffer.get(), numBytes);
int32_t framesRead = bytesRead / mStream->getBytesPerFrame();
float *floatData = output.getBuffer();
const int16_t *shortData = mConversionBuffer.get();
int32_t numSamples = framesRead * output.getSamplesPerFrame();
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_i16(floatData, shortData, numSamples);
#else
for (int i = 0; i < numSamples; i++) {
*floatData++ = *shortData++ * (1.0f / 32768);
}
#endif
return framesRead;
}

View File

@ -1,48 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_SOURCE_I16_CALLER_H
#define OBOE_SOURCE_I16_CALLER_H
#include <unistd.h>
#include <sys/types.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_common_AudioSourceCaller_android.h"
#include "oboe_common_FixedBlockReader_android.h"
namespace oboe {
/**
* AudioSource that uses callback to get more data.
*/
class SourceI16Caller : public AudioSourceCaller {
public:
SourceI16Caller(int32_t channelCount, int32_t framesPerCallback)
: AudioSourceCaller(channelCount, framesPerCallback, sizeof(int16_t)) {
mConversionBuffer = std::make_unique<int16_t[]>(channelCount * output.getFramesPerBuffer());
}
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI16Caller";
}
private:
std::unique_ptr<int16_t[]> mConversionBuffer;
};
}
#endif //OBOE_SOURCE_I16_CALLER_H

View File

@ -1,56 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#include <algorithm>
#include <unistd.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_common_SourceI24Caller_android.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace oboe;
using namespace flowgraph;
int32_t SourceI24Caller::onProcess(int32_t numFrames) {
int32_t numBytes = mStream->getBytesPerFrame() * numFrames;
int32_t bytesRead = mBlockReader.read((uint8_t *) mConversionBuffer.get(), numBytes);
int32_t framesRead = bytesRead / mStream->getBytesPerFrame();
float *floatData = output.getBuffer();
const uint8_t *byteData = mConversionBuffer.get();
int32_t numSamples = framesRead * output.getSamplesPerFrame();
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_p24(floatData, byteData, numSamples);
#else
static const float scale = 1. / (float)(1UL << 31);
for (int i = 0; i < numSamples; i++) {
// Assemble the data assuming Little Endian format.
int32_t pad = byteData[2];
pad <<= 8;
pad |= byteData[1];
pad <<= 8;
pad |= byteData[0];
pad <<= 8; // Shift to 32 bit data so the sign is correct.
byteData += kBytesPerI24Packed;
*floatData++ = pad * scale; // scale to range -1.0 to 1.0
}
#endif
return framesRead;
}

View File

@ -1,52 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_SOURCE_I24_CALLER_H
#define OBOE_SOURCE_I24_CALLER_H
#include <unistd.h>
#include <sys/types.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_common_AudioSourceCaller_android.h"
#include "oboe_common_FixedBlockReader_android.h"
namespace oboe {
/**
* AudioSource that uses callback to get more data.
*/
class SourceI24Caller : public AudioSourceCaller {
public:
SourceI24Caller(int32_t channelCount, int32_t framesPerCallback)
: AudioSourceCaller(channelCount, framesPerCallback, kBytesPerI24Packed) {
mConversionBuffer = std::make_unique<uint8_t[]>(
kBytesPerI24Packed * channelCount * output.getFramesPerBuffer());
}
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI24Caller";
}
private:
std::unique_ptr<uint8_t[]> mConversionBuffer;
static constexpr int kBytesPerI24Packed = 3;
};
}
#endif //OBOE_SOURCE_I16_CALLER_H

View File

@ -1,47 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#include <algorithm>
#include <unistd.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_common_SourceI32Caller_android.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace oboe;
using namespace flowgraph;
int32_t SourceI32Caller::onProcess(int32_t numFrames) {
int32_t numBytes = mStream->getBytesPerFrame() * numFrames;
int32_t bytesRead = mBlockReader.read((uint8_t *) mConversionBuffer.get(), numBytes);
int32_t framesRead = bytesRead / mStream->getBytesPerFrame();
float *floatData = output.getBuffer();
const int32_t *intData = mConversionBuffer.get();
int32_t numSamples = framesRead * output.getSamplesPerFrame();
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_i32(floatData, shortData, numSamples);
#else
for (int i = 0; i < numSamples; i++) {
*floatData++ = *intData++ * kScale;
}
#endif
return framesRead;
}

View File

@ -1,52 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_SOURCE_I32_CALLER_H
#define OBOE_SOURCE_I32_CALLER_H
#include <memory.h>
#include <unistd.h>
#include <sys/types.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_common_AudioSourceCaller_android.h"
#include "oboe_common_FixedBlockReader_android.h"
namespace oboe {
/**
* AudioSource that uses callback to get more data.
*/
class SourceI32Caller : public AudioSourceCaller {
public:
SourceI32Caller(int32_t channelCount, int32_t framesPerCallback)
: AudioSourceCaller(channelCount, framesPerCallback, sizeof(int32_t)) {
mConversionBuffer = std::make_unique<int32_t[]>(channelCount * output.getFramesPerBuffer());
}
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI32Caller";
}
private:
std::unique_ptr<int32_t[]> mConversionBuffer;
static constexpr float kScale = 1.0 / (1UL << 31);
};
}
#endif //OBOE_SOURCE_I32_CALLER_H

View File

@ -1,112 +0,0 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* 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.
*/
#include "oboe_oboe_StabilizedCallback_android.h"
#include "oboe_common_AudioClock_android.h"
#include "oboe_common_Trace_android.h"
constexpr int32_t kLoadGenerationStepSizeNanos = 20000;
constexpr float kPercentageOfCallbackToUse = 0.8;
using namespace oboe;
StabilizedCallback::StabilizedCallback(AudioStreamCallback *callback) : mCallback(callback){
Trace::initialize();
}
/**
* An audio callback which attempts to do work for a fixed amount of time.
*
* @param oboeStream
* @param audioData
* @param numFrames
* @return
*/
DataCallbackResult
StabilizedCallback::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) {
int64_t startTimeNanos = AudioClock::getNanoseconds();
if (mFrameCount == 0){
mEpochTimeNanos = startTimeNanos;
}
int64_t durationSinceEpochNanos = startTimeNanos - mEpochTimeNanos;
// In an ideal world the callback start time will be exactly the same as the duration of the
// frames already read/written into the stream. In reality the callback can start early
// or late. By finding the delta we can calculate the target duration for our stabilized
// callback.
int64_t idealStartTimeNanos = (mFrameCount * kNanosPerSecond) / oboeStream->getSampleRate();
int64_t lateStartNanos = durationSinceEpochNanos - idealStartTimeNanos;
if (lateStartNanos < 0){
// This was an early start which indicates that our previous epoch was a late callback.
// Update our epoch to this more accurate time.
mEpochTimeNanos = startTimeNanos;
mFrameCount = 0;
}
int64_t numFramesAsNanos = (numFrames * kNanosPerSecond) / oboeStream->getSampleRate();
int64_t targetDurationNanos = static_cast<int64_t>(
(numFramesAsNanos * kPercentageOfCallbackToUse) - lateStartNanos);
Trace::beginSection("Actual load");
DataCallbackResult result = mCallback->onAudioReady(oboeStream, audioData, numFrames);
Trace::endSection();
int64_t executionDurationNanos = AudioClock::getNanoseconds() - startTimeNanos;
int64_t stabilizingLoadDurationNanos = targetDurationNanos - executionDurationNanos;
Trace::beginSection("Stabilized load for %lldns", stabilizingLoadDurationNanos);
generateLoad(stabilizingLoadDurationNanos);
Trace::endSection();
// Wraparound: At 48000 frames per second mFrameCount wraparound will occur after 6m years,
// significantly longer than the average lifetime of an Android phone.
mFrameCount += numFrames;
return result;
}
void StabilizedCallback::generateLoad(int64_t durationNanos) {
int64_t currentTimeNanos = AudioClock::getNanoseconds();
int64_t deadlineTimeNanos = currentTimeNanos + durationNanos;
// opsPerStep gives us an estimated number of operations which need to be run to fully utilize
// the CPU for a fixed amount of time (specified by kLoadGenerationStepSizeNanos).
// After each step the opsPerStep value is re-calculated based on the actual time taken to
// execute those operations.
auto opsPerStep = (int)(mOpsPerNano * kLoadGenerationStepSizeNanos);
int64_t stepDurationNanos = 0;
int64_t previousTimeNanos = 0;
while (currentTimeNanos <= deadlineTimeNanos){
for (int i = 0; i < opsPerStep; i++) cpu_relax();
previousTimeNanos = currentTimeNanos;
currentTimeNanos = AudioClock::getNanoseconds();
stepDurationNanos = currentTimeNanos - previousTimeNanos;
// Calculate exponential moving average to smooth out values, this acts as a low pass filter.
// @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
static const float kFilterCoefficient = 0.1;
auto measuredOpsPerNano = (double) opsPerStep / stepDurationNanos;
mOpsPerNano = kFilterCoefficient * measuredOpsPerNano + (1.0 - kFilterCoefficient) * mOpsPerNano;
opsPerStep = (int) (mOpsPerNano * kLoadGenerationStepSizeNanos);
}
}

View File

@ -1,75 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* 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.
*/
#include <dlfcn.h>
#include <cstdio>
#include "oboe_common_Trace_android.h"
#include "oboe_common_OboeDebug_android.h"
static char buffer[256];
// Tracing functions
static void *(*ATrace_beginSection)(const char *sectionName);
static void *(*ATrace_endSection)();
typedef void *(*fp_ATrace_beginSection)(const char *sectionName);
typedef void *(*fp_ATrace_endSection)();
bool Trace::mIsTracingSupported = false;
void Trace::beginSection(const char *format, ...){
if (mIsTracingSupported) {
va_list va;
va_start(va, format);
vsprintf(buffer, format, va);
ATrace_beginSection(buffer);
va_end(va);
} else {
LOGE("Tracing is either not initialized (call Trace::initialize()) "
"or not supported on this device");
}
}
void Trace::endSection() {
if (mIsTracingSupported) {
ATrace_endSection();
}
}
void Trace::initialize() {
// Using dlsym allows us to use tracing on API 21+ without needing android/trace.h which wasn't
// published until API 23
void *lib = dlopen("libandroid.so", RTLD_NOW | RTLD_LOCAL);
if (lib == nullptr) {
LOGE("Could not open libandroid.so to dynamically load tracing symbols");
} else {
ATrace_beginSection =
reinterpret_cast<fp_ATrace_beginSection >(
dlsym(lib, "ATrace_beginSection"));
ATrace_endSection =
reinterpret_cast<fp_ATrace_endSection >(
dlsym(lib, "ATrace_endSection"));
if (ATrace_beginSection != nullptr && ATrace_endSection != nullptr){
mIsTracingSupported = true;
}
}
}

View File

@ -1,31 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_TRACE_H
#define OBOE_TRACE_H
class Trace {
public:
static void beginSection(const char *format, ...);
static void endSection();
static void initialize();
private:
static bool mIsTracingSupported;
};
#endif //OBOE_TRACE_H

View File

@ -1,313 +0,0 @@
/*
* Copyright 2016 The Android Open Source Project
*
* 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.
*/
#include <stdlib.h>
#include <unistd.h>
#include <sstream>
#ifdef __ANDROID__
#include <sys/system_properties.h>
#endif
#include "oboe_oboe_AudioStream_android.h"
#include "oboe_oboe_Definitions_android.h"
#include "oboe_oboe_Utilities_android.h"
namespace oboe {
constexpr float kScaleI16ToFloat = (1.0f / 32768.0f);
void convertFloatToPcm16(const float *source, int16_t *destination, int32_t numSamples) {
for (int i = 0; i < numSamples; i++) {
float fval = source[i];
fval += 1.0; // to avoid discontinuity at 0.0 caused by truncation
fval *= 32768.0f;
auto sample = static_cast<int32_t>(fval);
// clip to 16-bit range
if (sample < 0) sample = 0;
else if (sample > 0x0FFFF) sample = 0x0FFFF;
sample -= 32768; // center at zero
destination[i] = static_cast<int16_t>(sample);
}
}
void convertPcm16ToFloat(const int16_t *source, float *destination, int32_t numSamples) {
for (int i = 0; i < numSamples; i++) {
destination[i] = source[i] * kScaleI16ToFloat;
}
}
int32_t convertFormatToSizeInBytes(AudioFormat format) {
int32_t size = 0;
switch (format) {
case AudioFormat::I16:
size = sizeof(int16_t);
break;
case AudioFormat::Float:
size = sizeof(float);
break;
case AudioFormat::I24:
size = 3; // packed 24-bit data
break;
case AudioFormat::I32:
size = sizeof(int32_t);
break;
default:
break;
}
return size;
}
template<>
const char *convertToText<Result>(Result returnCode) {
switch (returnCode) {
case Result::OK: return "OK";
case Result::ErrorDisconnected: return "ErrorDisconnected";
case Result::ErrorIllegalArgument: return "ErrorIllegalArgument";
case Result::ErrorInternal: return "ErrorInternal";
case Result::ErrorInvalidState: return "ErrorInvalidState";
case Result::ErrorInvalidHandle: return "ErrorInvalidHandle";
case Result::ErrorUnimplemented: return "ErrorUnimplemented";
case Result::ErrorUnavailable: return "ErrorUnavailable";
case Result::ErrorNoFreeHandles: return "ErrorNoFreeHandles";
case Result::ErrorNoMemory: return "ErrorNoMemory";
case Result::ErrorNull: return "ErrorNull";
case Result::ErrorTimeout: return "ErrorTimeout";
case Result::ErrorWouldBlock: return "ErrorWouldBlock";
case Result::ErrorInvalidFormat: return "ErrorInvalidFormat";
case Result::ErrorOutOfRange: return "ErrorOutOfRange";
case Result::ErrorNoService: return "ErrorNoService";
case Result::ErrorInvalidRate: return "ErrorInvalidRate";
case Result::ErrorClosed: return "ErrorClosed";
default: return "Unrecognized result";
}
}
template<>
const char *convertToText<AudioFormat>(AudioFormat format) {
switch (format) {
case AudioFormat::Invalid: return "Invalid";
case AudioFormat::Unspecified: return "Unspecified";
case AudioFormat::I16: return "I16";
case AudioFormat::Float: return "Float";
case AudioFormat::I24: return "I24";
case AudioFormat::I32: return "I32";
default: return "Unrecognized format";
}
}
template<>
const char *convertToText<PerformanceMode>(PerformanceMode mode) {
switch (mode) {
case PerformanceMode::LowLatency: return "LowLatency";
case PerformanceMode::None: return "None";
case PerformanceMode::PowerSaving: return "PowerSaving";
default: return "Unrecognized performance mode";
}
}
template<>
const char *convertToText<SharingMode>(SharingMode mode) {
switch (mode) {
case SharingMode::Exclusive: return "Exclusive";
case SharingMode::Shared: return "Shared";
default: return "Unrecognized sharing mode";
}
}
template<>
const char *convertToText<DataCallbackResult>(DataCallbackResult result) {
switch (result) {
case DataCallbackResult::Continue: return "Continue";
case DataCallbackResult::Stop: return "Stop";
default: return "Unrecognized data callback result";
}
}
template<>
const char *convertToText<Direction>(Direction direction) {
switch (direction) {
case Direction::Input: return "Input";
case Direction::Output: return "Output";
default: return "Unrecognized direction";
}
}
template<>
const char *convertToText<StreamState>(StreamState state) {
switch (state) {
case StreamState::Closed: return "Closed";
case StreamState::Closing: return "Closing";
case StreamState::Disconnected: return "Disconnected";
case StreamState::Flushed: return "Flushed";
case StreamState::Flushing: return "Flushing";
case StreamState::Open: return "Open";
case StreamState::Paused: return "Paused";
case StreamState::Pausing: return "Pausing";
case StreamState::Started: return "Started";
case StreamState::Starting: return "Starting";
case StreamState::Stopped: return "Stopped";
case StreamState::Stopping: return "Stopping";
case StreamState::Uninitialized: return "Uninitialized";
case StreamState::Unknown: return "Unknown";
default: return "Unrecognized stream state";
}
}
template<>
const char *convertToText<AudioApi>(AudioApi audioApi) {
switch (audioApi) {
case AudioApi::Unspecified: return "Unspecified";
case AudioApi::OpenSLES: return "OpenSLES";
case AudioApi::AAudio: return "AAudio";
default: return "Unrecognized audio API";
}
}
template<>
const char *convertToText<AudioStream*>(AudioStream* stream) {
static std::string streamText;
std::stringstream s;
s<<"StreamID: "<< static_cast<void*>(stream)<<std::endl
<<"DeviceId: "<<stream->getDeviceId()<<std::endl
<<"Direction: "<<oboe::convertToText(stream->getDirection())<<std::endl
<<"API type: "<<oboe::convertToText(stream->getAudioApi())<<std::endl
<<"BufferCapacity: "<<stream->getBufferCapacityInFrames()<<std::endl
<<"BufferSize: "<<stream->getBufferSizeInFrames()<<std::endl
<<"FramesPerBurst: "<< stream->getFramesPerBurst()<<std::endl
<<"FramesPerDataCallback: "<<stream->getFramesPerDataCallback()<<std::endl
<<"SampleRate: "<<stream->getSampleRate()<<std::endl
<<"ChannelCount: "<<stream->getChannelCount()<<std::endl
<<"Format: "<<oboe::convertToText(stream->getFormat())<<std::endl
<<"SharingMode: "<<oboe::convertToText(stream->getSharingMode())<<std::endl
<<"PerformanceMode: "<<oboe::convertToText(stream->getPerformanceMode())
<<std::endl
<<"CurrentState: "<<oboe::convertToText(stream->getState())<<std::endl
<<"XRunCount: "<<stream->getXRunCount()<<std::endl
<<"FramesRead: "<<stream->getFramesRead()<<std::endl
<<"FramesWritten: "<<stream->getFramesWritten()<<std::endl;
streamText = s.str();
return streamText.c_str();
}
template<>
const char *convertToText<Usage>(Usage usage) {
switch (usage) {
case Usage::Media: return "Media";
case Usage::VoiceCommunication: return "VoiceCommunication";
case Usage::VoiceCommunicationSignalling: return "VoiceCommunicationSignalling";
case Usage::Alarm: return "Alarm";
case Usage::Notification: return "Notification";
case Usage::NotificationRingtone: return "NotificationRingtone";
case Usage::NotificationEvent: return "NotificationEvent";
case Usage::AssistanceAccessibility: return "AssistanceAccessibility";
case Usage::AssistanceNavigationGuidance: return "AssistanceNavigationGuidance";
case Usage::AssistanceSonification: return "AssistanceSonification";
case Usage::Game: return "Game";
case Usage::Assistant: return "Assistant";
default: return "Unrecognized usage";
}
}
template<>
const char *convertToText<ContentType>(ContentType contentType) {
switch (contentType) {
case ContentType::Speech: return "Speech";
case ContentType::Music: return "Music";
case ContentType::Movie: return "Movie";
case ContentType::Sonification: return "Sonification";
default: return "Unrecognized content type";
}
}
template<>
const char *convertToText<InputPreset>(InputPreset inputPreset) {
switch (inputPreset) {
case InputPreset::Generic: return "Generic";
case InputPreset::Camcorder: return "Camcorder";
case InputPreset::VoiceRecognition: return "VoiceRecognition";
case InputPreset::VoiceCommunication: return "VoiceCommunication";
case InputPreset::Unprocessed: return "Unprocessed";
case InputPreset::VoicePerformance: return "VoicePerformance";
default: return "Unrecognized input preset";
}
}
template<>
const char *convertToText<SessionId>(SessionId sessionId) {
switch (sessionId) {
case SessionId::None: return "None";
case SessionId::Allocate: return "Allocate";
default: return "Unrecognized session id";
}
}
template<>
const char *convertToText<ChannelCount>(ChannelCount channelCount) {
switch (channelCount) {
case ChannelCount::Unspecified: return "Unspecified";
case ChannelCount::Mono: return "Mono";
case ChannelCount::Stereo: return "Stereo";
default: return "Unrecognized channel count";
}
}
std::string getPropertyString(const char * name) {
std::string result;
#ifdef __ANDROID__
char valueText[PROP_VALUE_MAX] = {0};
if (__system_property_get(name, valueText) != 0) {
result = valueText;
}
#else
(void) name;
#endif
return result;
}
int getPropertyInteger(const char * name, int defaultValue) {
int result = defaultValue;
#ifdef __ANDROID__
char valueText[PROP_VALUE_MAX] = {0};
if (__system_property_get(name, valueText) != 0) {
result = atoi(valueText);
}
#else
(void) name;
#endif
return result;
}
int getSdkVersion() {
static int sCachedSdkVersion = -1;
#ifdef __ANDROID__
if (sCachedSdkVersion == -1) {
sCachedSdkVersion = getPropertyInteger("ro.build.version.sdk", -1);
}
#endif
return sCachedSdkVersion;
}
}// namespace oboe

View File

@ -1,28 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#include "oboe_oboe_Version_android.h"
namespace oboe {
// This variable enables the version information to be read from the resulting binary e.g.
// by running `objdump -s --section=.data <binary>`
// Please do not optimize or change in any way.
char kVersionText[] = "OboeVersion" OBOE_VERSION_TEXT;
const char * getVersionText(){
return kVersionText;
}
} // namespace oboe

View File

@ -1,178 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
#include <algorithm>
#include <memory.h>
#include <stdint.h>
#include "oboe_fifo_FifoControllerBase_android.h"
#include "oboe_fifo_FifoController_android.h"
#include "oboe_fifo_FifoControllerIndirect_android.h"
#include "oboe_fifo_FifoBuffer_android.h"
namespace oboe {
FifoBuffer::FifoBuffer(uint32_t bytesPerFrame, uint32_t capacityInFrames)
: mBytesPerFrame(bytesPerFrame)
, mStorage(nullptr)
, mFramesReadCount(0)
, mFramesUnderrunCount(0)
{
mFifo = std::make_unique<FifoController>(capacityInFrames);
// allocate buffer
int32_t bytesPerBuffer = bytesPerFrame * capacityInFrames;
mStorage = new uint8_t[bytesPerBuffer];
mStorageOwned = true;
}
FifoBuffer::FifoBuffer( uint32_t bytesPerFrame,
uint32_t capacityInFrames,
std::atomic<uint64_t> *readCounterAddress,
std::atomic<uint64_t> *writeCounterAddress,
uint8_t *dataStorageAddress
)
: mBytesPerFrame(bytesPerFrame)
, mStorage(dataStorageAddress)
, mFramesReadCount(0)
, mFramesUnderrunCount(0)
{
mFifo = std::make_unique<FifoControllerIndirect>(capacityInFrames,
readCounterAddress,
writeCounterAddress);
mStorage = dataStorageAddress;
mStorageOwned = false;
}
FifoBuffer::~FifoBuffer() {
if (mStorageOwned) {
delete[] mStorage;
}
}
int32_t FifoBuffer::convertFramesToBytes(int32_t frames) {
return frames * mBytesPerFrame;
}
int32_t FifoBuffer::read(void *buffer, int32_t numFrames) {
if (numFrames <= 0) {
return 0;
}
// safe because numFrames is guaranteed positive
uint32_t framesToRead = static_cast<uint32_t>(numFrames);
uint32_t framesAvailable = mFifo->getFullFramesAvailable();
framesToRead = std::min(framesToRead, framesAvailable);
uint32_t readIndex = mFifo->getReadIndex(); // ranges 0 to capacity
uint8_t *destination = reinterpret_cast<uint8_t *>(buffer);
uint8_t *source = &mStorage[convertFramesToBytes(readIndex)];
if ((readIndex + framesToRead) > mFifo->getFrameCapacity()) {
// read in two parts, first part here is at the end of the mStorage buffer
int32_t frames1 = static_cast<int32_t>(mFifo->getFrameCapacity() - readIndex);
int32_t numBytes = convertFramesToBytes(frames1);
if (numBytes < 0) {
return static_cast<int32_t>(Result::ErrorOutOfRange);
}
memcpy(destination, source, static_cast<size_t>(numBytes));
destination += numBytes;
// read second part, which is at the beginning of mStorage
source = &mStorage[0];
int32_t frames2 = static_cast<uint32_t>(framesToRead - frames1);
numBytes = convertFramesToBytes(frames2);
if (numBytes < 0) {
return static_cast<int32_t>(Result::ErrorOutOfRange);
}
memcpy(destination, source, static_cast<size_t>(numBytes));
} else {
// just read in one shot
int32_t numBytes = convertFramesToBytes(framesToRead);
if (numBytes < 0) {
return static_cast<int32_t>(Result::ErrorOutOfRange);
}
memcpy(destination, source, static_cast<size_t>(numBytes));
}
mFifo->advanceReadIndex(framesToRead);
return framesToRead;
}
int32_t FifoBuffer::write(const void *buffer, int32_t numFrames) {
if (numFrames <= 0) {
return 0;
}
// Guaranteed positive.
uint32_t framesToWrite = static_cast<uint32_t>(numFrames);
uint32_t framesAvailable = mFifo->getEmptyFramesAvailable();
framesToWrite = std::min(framesToWrite, framesAvailable);
uint32_t writeIndex = mFifo->getWriteIndex();
int byteIndex = convertFramesToBytes(writeIndex);
const uint8_t *source = reinterpret_cast<const uint8_t *>(buffer);
uint8_t *destination = &mStorage[byteIndex];
if ((writeIndex + framesToWrite) > mFifo->getFrameCapacity()) {
// write in two parts, first part here
int32_t frames1 = static_cast<uint32_t>(mFifo->getFrameCapacity() - writeIndex);
int32_t numBytes = convertFramesToBytes(frames1);
if (numBytes < 0) {
return static_cast<int32_t>(Result::ErrorOutOfRange);
}
memcpy(destination, source, static_cast<size_t>(numBytes));
// read second part
source += convertFramesToBytes(frames1);
destination = &mStorage[0];
int frames2 = static_cast<uint32_t>(framesToWrite - frames1);
numBytes = convertFramesToBytes(frames2);
if (numBytes < 0) {
return static_cast<int32_t>(Result::ErrorOutOfRange);
}
memcpy(destination, source, static_cast<size_t>(numBytes));
} else {
// just write in one shot
int32_t numBytes = convertFramesToBytes(framesToWrite);
if (numBytes < 0) {
return static_cast<int32_t>(Result::ErrorOutOfRange);
}
memcpy(destination, source, static_cast<size_t>(numBytes));
}
mFifo->advanceWriteIndex(framesToWrite);
return framesToWrite;
}
int32_t FifoBuffer::readNow(void *buffer, int32_t numFrames) {
int32_t framesRead = read(buffer, numFrames);
if (framesRead < 0) {
return framesRead;
}
int32_t framesLeft = numFrames - framesRead;
mFramesReadCount += framesRead;
mFramesUnderrunCount += framesLeft;
// Zero out any samples we could not set.
if (framesLeft > 0) {
uint8_t *destination = reinterpret_cast<uint8_t *>(buffer);
destination += convertFramesToBytes(framesRead); // point to first byte not set
int32_t bytesToZero = convertFramesToBytes(framesLeft);
memset(destination, 0, static_cast<size_t>(bytesToZero));
}
return framesRead;
}
uint32_t FifoBuffer::getBufferCapacityInFrames() const {
return mFifo->getFrameCapacity();
}
} // namespace oboe

View File

@ -1,99 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_FIFOPROCESSOR_H
#define OBOE_FIFOPROCESSOR_H
#include <memory>
#include <stdint.h>
#include "oboe_oboe_Definitions_android.h"
#include "oboe_fifo_FifoControllerBase_android.h"
namespace oboe {
class FifoBuffer {
public:
FifoBuffer(uint32_t bytesPerFrame, uint32_t capacityInFrames);
FifoBuffer(uint32_t bytesPerFrame,
uint32_t capacityInFrames,
std::atomic<uint64_t> *readCounterAddress,
std::atomic<uint64_t> *writeCounterAddress,
uint8_t *dataStorageAddress);
~FifoBuffer();
int32_t convertFramesToBytes(int32_t frames);
/**
* Read framesToRead or, if not enough, then read as many as are available.
* @param destination
* @param framesToRead number of frames requested
* @return number of frames actually read
*/
int32_t read(void *destination, int32_t framesToRead);
int32_t write(const void *source, int32_t framesToWrite);
uint32_t getBufferCapacityInFrames() const;
/**
* Calls read(). If all of the frames cannot be read then the remainder of the buffer
* is set to zero.
*
* @param destination
* @param framesToRead number of frames requested
* @return number of frames actually read
*/
int32_t readNow(void *destination, int32_t numFrames);
uint32_t getFullFramesAvailable() {
return mFifo->getFullFramesAvailable();
}
uint32_t getBytesPerFrame() const {
return mBytesPerFrame;
}
uint64_t getReadCounter() const {
return mFifo->getReadCounter();
}
void setReadCounter(uint64_t n) {
mFifo->setReadCounter(n);
}
uint64_t getWriteCounter() {
return mFifo->getWriteCounter();
}
void setWriteCounter(uint64_t n) {
mFifo->setWriteCounter(n);
}
private:
uint32_t mBytesPerFrame;
uint8_t* mStorage;
bool mStorageOwned; // did this object allocate the storage?
std::unique_ptr<FifoControllerBase> mFifo;
uint64_t mFramesReadCount;
uint64_t mFramesUnderrunCount;
};
} // namespace oboe
#endif //OBOE_FIFOPROCESSOR_H

View File

@ -1,68 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
#include <algorithm>
#include <cassert>
#include <stdint.h>
#include "oboe_fifo_FifoControllerBase_android.h"
namespace oboe {
FifoControllerBase::FifoControllerBase(uint32_t capacityInFrames)
: mTotalFrames(capacityInFrames)
{
// Avoid ridiculously large buffers and the arithmetic wraparound issues that can follow.
assert(capacityInFrames <= (UINT32_MAX / 4));
}
uint32_t FifoControllerBase::getFullFramesAvailable() const {
uint64_t writeCounter = getWriteCounter();
uint64_t readCounter = getReadCounter();
if (readCounter > writeCounter) {
return 0;
}
uint64_t delta = writeCounter - readCounter;
if (delta >= mTotalFrames) {
return mTotalFrames;
}
// delta is now guaranteed to fit within the range of a uint32_t
return static_cast<uint32_t>(delta);
}
uint32_t FifoControllerBase::getReadIndex() const {
// % works with non-power of two sizes
return static_cast<uint32_t>(getReadCounter() % mTotalFrames);
}
void FifoControllerBase::advanceReadIndex(uint32_t numFrames) {
incrementReadCounter(numFrames);
}
uint32_t FifoControllerBase::getEmptyFramesAvailable() const {
return static_cast<uint32_t>(mTotalFrames - getFullFramesAvailable());
}
uint32_t FifoControllerBase::getWriteIndex() const {
// % works with non-power of two sizes
return static_cast<uint32_t>(getWriteCounter() % mTotalFrames);
}
void FifoControllerBase::advanceWriteIndex(uint32_t numFrames) {
incrementWriteCounter(numFrames);
}
} // namespace oboe

View File

@ -1,92 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
#ifndef NATIVEOBOE_FIFOCONTROLLERBASE_H
#define NATIVEOBOE_FIFOCONTROLLERBASE_H
#include <stdint.h>
namespace oboe {
/**
* Manage the read/write indices of a circular buffer.
*
* The caller is responsible for reading and writing the actual data.
* Note that the span of available frames may not be contiguous. They
* may wrap around from the end to the beginning of the buffer. In that
* case the data must be read or written in at least two blocks of frames.
*
*/
class FifoControllerBase {
public:
/**
* @param totalFrames capacity of the circular buffer in frames.
*/
FifoControllerBase(uint32_t totalFrames);
virtual ~FifoControllerBase() = default;
/**
* The frames available to read will be calculated from the read and write counters.
* The result will be clipped to the capacity of the buffer.
* If the buffer has underflowed then this will return zero.
* @return number of valid frames available to read.
*/
uint32_t getFullFramesAvailable() const;
/**
* The index in a circular buffer of the next frame to read.
*/
uint32_t getReadIndex() const;
/**
* @param numFrames number of frames to advance the read index
*/
void advanceReadIndex(uint32_t numFrames);
/**
* @return maximum number of frames that can be written without exceeding the threshold.
*/
uint32_t getEmptyFramesAvailable() const;
/**
* The index in a circular buffer of the next frame to write.
*/
uint32_t getWriteIndex() const;
/**
* @param numFrames number of frames to advance the write index
*/
void advanceWriteIndex(uint32_t numFrames);
uint32_t getFrameCapacity() const { return mTotalFrames; }
virtual uint64_t getReadCounter() const = 0;
virtual void setReadCounter(uint64_t n) = 0;
virtual void incrementReadCounter(uint64_t n) = 0;
virtual uint64_t getWriteCounter() const = 0;
virtual void setWriteCounter(uint64_t n) = 0;
virtual void incrementWriteCounter(uint64_t n) = 0;
private:
uint32_t mTotalFrames;
};
} // namespace oboe
#endif //NATIVEOBOE_FIFOCONTROLLERBASE_H

View File

@ -1,32 +0,0 @@
/*
* Copyright 2016 The Android Open Source Project
*
* 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.
*/
#include <stdint.h>
#include "oboe_fifo_FifoControllerIndirect_android.h"
namespace oboe {
FifoControllerIndirect::FifoControllerIndirect(uint32_t numFrames,
std::atomic<uint64_t> *readCounterAddress,
std::atomic<uint64_t> *writeCounterAddress)
: FifoControllerBase(numFrames)
, mReadCounterAddress(readCounterAddress)
, mWriteCounterAddress(writeCounterAddress)
{
}
}

View File

@ -1,66 +0,0 @@
/*
* Copyright 2016 The Android Open Source Project
*
* 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.
*/
#ifndef NATIVEOBOE_FIFOCONTROLLERINDIRECT_H
#define NATIVEOBOE_FIFOCONTROLLERINDIRECT_H
#include <atomic>
#include <stdint.h>
#include "oboe_fifo_FifoControllerBase_android.h"
namespace oboe {
/**
* A FifoControllerBase with counters external to the class.
*/
class FifoControllerIndirect : public FifoControllerBase {
public:
FifoControllerIndirect(uint32_t bufferSize,
std::atomic<uint64_t> *readCounterAddress,
std::atomic<uint64_t> *writeCounterAddress);
virtual ~FifoControllerIndirect() = default;
virtual uint64_t getReadCounter() const override {
return mReadCounterAddress->load(std::memory_order_acquire);
}
virtual void setReadCounter(uint64_t n) override {
mReadCounterAddress->store(n, std::memory_order_release);
}
virtual void incrementReadCounter(uint64_t n) override {
mReadCounterAddress->fetch_add(n, std::memory_order_acq_rel);
}
virtual uint64_t getWriteCounter() const override {
return mWriteCounterAddress->load(std::memory_order_acquire);
}
virtual void setWriteCounter(uint64_t n) override {
mWriteCounterAddress->store(n, std::memory_order_release);
}
virtual void incrementWriteCounter(uint64_t n) override {
mWriteCounterAddress->fetch_add(n, std::memory_order_acq_rel);
}
private:
std::atomic<uint64_t> *mReadCounterAddress;
std::atomic<uint64_t> *mWriteCounterAddress;
};
} // namespace oboe
#endif //NATIVEOBOE_FIFOCONTROLLERINDIRECT_H

View File

@ -1,31 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
#include <stdint.h>
#include "oboe_fifo_FifoControllerBase_android.h"
#include "oboe_fifo_FifoController_android.h"
namespace oboe {
FifoController::FifoController(uint32_t numFrames)
: FifoControllerBase(numFrames)
{
setReadCounter(0);
setWriteCounter(0);
}
} // namespace oboe

View File

@ -1,62 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
#ifndef NATIVEOBOE_FIFOCONTROLLER_H
#define NATIVEOBOE_FIFOCONTROLLER_H
#include <atomic>
#include <stdint.h>
#include "oboe_fifo_FifoControllerBase_android.h"
namespace oboe {
/**
* A FifoControllerBase with counters contained in the class.
*/
class FifoController : public FifoControllerBase
{
public:
FifoController(uint32_t bufferSize);
virtual ~FifoController() = default;
virtual uint64_t getReadCounter() const override {
return mReadCounter.load(std::memory_order_acquire);
}
virtual void setReadCounter(uint64_t n) override {
mReadCounter.store(n, std::memory_order_release);
}
virtual void incrementReadCounter(uint64_t n) override {
mReadCounter.fetch_add(n, std::memory_order_acq_rel);
}
virtual uint64_t getWriteCounter() const override {
return mWriteCounter.load(std::memory_order_acquire);
}
virtual void setWriteCounter(uint64_t n) override {
mWriteCounter.store(n, std::memory_order_release);
}
virtual void incrementWriteCounter(uint64_t n) override {
mWriteCounter.fetch_add(n, std::memory_order_acq_rel);
}
private:
std::atomic<uint64_t> mReadCounter{};
std::atomic<uint64_t> mWriteCounter{};
};
} // namespace oboe
#endif //NATIVEOBOE_FIFOCONTROLLER_H

View File

@ -1,52 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
#include <unistd.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_flowgraph_ChannelCountConverter_android.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
ChannelCountConverter::ChannelCountConverter(
int32_t inputChannelCount,
int32_t outputChannelCount)
: input(*this, inputChannelCount)
, output(*this, outputChannelCount) {
}
ChannelCountConverter::~ChannelCountConverter() { }
int32_t ChannelCountConverter::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t inputChannelCount = input.getSamplesPerFrame();
int32_t outputChannelCount = output.getSamplesPerFrame();
for (int i = 0; i < numFrames; i++) {
int inputChannel = 0;
for (int outputChannel = 0; outputChannel < outputChannelCount; outputChannel++) {
// Copy input channels to output channels.
// Wrap if we run out of inputs.
// Discard if we run out of outputs.
outputBuffer[outputChannel] = inputBuffer[inputChannel];
inputChannel = (inputChannel == inputChannelCount)
? 0 : inputChannel + 1;
}
inputBuffer += inputChannelCount;
outputBuffer += outputChannelCount;
}
return numFrames;
}

View File

@ -1,54 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
#ifndef FLOWGRAPH_CHANNEL_COUNT_CONVERTER_H
#define FLOWGRAPH_CHANNEL_COUNT_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* Change the number of number of channels without mixing.
* When increasing the channel count, duplicate input channels.
* When decreasing the channel count, drop input channels.
*/
class ChannelCountConverter : public FlowGraphNode {
public:
explicit ChannelCountConverter(
int32_t inputChannelCount,
int32_t outputChannelCount);
virtual ~ChannelCountConverter();
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "ChannelCountConverter";
}
FlowGraphPortFloatInput input;
FlowGraphPortFloatOutput output;
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_CHANNEL_COUNT_CONVERTER_H

View File

@ -1,38 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
#include <algorithm>
#include <unistd.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_flowgraph_ClipToRange_android.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
ClipToRange::ClipToRange(int32_t channelCount)
: FlowGraphFilter(channelCount) {
}
int32_t ClipToRange::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t numSamples = numFrames * output.getSamplesPerFrame();
for (int32_t i = 0; i < numSamples; i++) {
*outputBuffer++ = std::min(mMaximum, std::max(mMinimum, *inputBuffer++));
}
return numFrames;
}

View File

@ -1,70 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
#ifndef FLOWGRAPH_CLIP_TO_RANGE_H
#define FLOWGRAPH_CLIP_TO_RANGE_H
#include <atomic>
#include <unistd.h>
#include <sys/types.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
// This is 3 dB, (10^(3/20)), to match the maximum headroom in AudioTrack for float data.
// It is designed to allow occasional transient peaks.
constexpr float kDefaultMaxHeadroom = 1.41253754f;
constexpr float kDefaultMinHeadroom = -kDefaultMaxHeadroom;
class ClipToRange : public FlowGraphFilter {
public:
explicit ClipToRange(int32_t channelCount);
virtual ~ClipToRange() = default;
int32_t onProcess(int32_t numFrames) override;
void setMinimum(float min) {
mMinimum = min;
}
float getMinimum() const {
return mMinimum;
}
void setMaximum(float min) {
mMaximum = min;
}
float getMaximum() const {
return mMaximum;
}
const char *getName() override {
return "ClipToRange";
}
private:
float mMinimum = kDefaultMinHeadroom;
float mMaximum = kDefaultMaxHeadroom;
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_CLIP_TO_RANGE_H

View File

@ -1,114 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
#include "stdio.h"
#include <algorithm>
#include <sys/types.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
/***************************************************************************/
int32_t FlowGraphNode::pullData(int32_t numFrames, int64_t callCount) {
int32_t frameCount = numFrames;
// Prevent recursion and multiple execution of nodes.
if (callCount > mLastCallCount) {
mLastCallCount = callCount;
if (mDataPulledAutomatically) {
// Pull from all the upstream nodes.
for (auto &port : mInputPorts) {
// TODO fix bug of leaving unused data in some ports if using multiple AudioSource
frameCount = port.get().pullData(callCount, frameCount);
}
}
if (frameCount > 0) {
frameCount = onProcess(frameCount);
}
mLastFrameCount = frameCount;
} else {
frameCount = mLastFrameCount;
}
return frameCount;
}
void FlowGraphNode::pullReset() {
if (!mBlockRecursion) {
mBlockRecursion = true; // for cyclic graphs
// Pull reset from all the upstream nodes.
for (auto &port : mInputPorts) {
port.get().pullReset();
}
mBlockRecursion = false;
reset();
}
}
void FlowGraphNode::reset() {
mLastFrameCount = 0;
mLastCallCount = kInitialCallCount;
}
/***************************************************************************/
FlowGraphPortFloat::FlowGraphPortFloat(FlowGraphNode &parent,
int32_t samplesPerFrame,
int32_t framesPerBuffer)
: FlowGraphPort(parent, samplesPerFrame)
, mFramesPerBuffer(framesPerBuffer)
, mBuffer(nullptr) {
size_t numFloats = static_cast<size_t>(framesPerBuffer * getSamplesPerFrame());
mBuffer = std::make_unique<float[]>(numFloats);
}
/***************************************************************************/
int32_t FlowGraphPortFloatOutput::pullData(int64_t callCount, int32_t numFrames) {
numFrames = std::min(getFramesPerBuffer(), numFrames);
return mContainingNode.pullData(numFrames, callCount);
}
void FlowGraphPortFloatOutput::pullReset() {
mContainingNode.pullReset();
}
// These need to be in the .cpp file because of forward cross references.
void FlowGraphPortFloatOutput::connect(FlowGraphPortFloatInput *port) {
port->connect(this);
}
void FlowGraphPortFloatOutput::disconnect(FlowGraphPortFloatInput *port) {
port->disconnect(this);
}
/***************************************************************************/
int32_t FlowGraphPortFloatInput::pullData(int64_t callCount, int32_t numFrames) {
return (mConnected == nullptr)
? std::min(getFramesPerBuffer(), numFrames)
: mConnected->pullData(callCount, numFrames);
}
void FlowGraphPortFloatInput::pullReset() {
if (mConnected != nullptr) mConnected->pullReset();
}
float *FlowGraphPortFloatInput::getBuffer() {
if (mConnected == nullptr) {
return FlowGraphPortFloat::getBuffer(); // loaded using setValue()
} else {
return mConnected->getBuffer();
}
}
int32_t FlowGraphSink::pullData(int32_t numFrames) {
return FlowGraphNode::pullData(numFrames, getLastCallCount() + 1);
}

View File

@ -1,448 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
/*
* FlowGraph.h
*
* Processing node and ports that can be used in a simple data flow graph.
* This was designed to work with audio but could be used for other
* types of data.
*/
#ifndef FLOWGRAPH_FLOW_GRAPH_NODE_H
#define FLOWGRAPH_FLOW_GRAPH_NODE_H
#include <cassert>
#include <cstring>
#include <math.h>
#include <memory>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <vector>
// TODO Move these classes into separate files.
// TODO Review use of raw pointers for connect(). Maybe use smart pointers but need to avoid
// run-time deallocation in audio thread.
// Set this to 1 if using it inside the Android framework.
// This code is kept here so that it can be moved easily between Oboe and AAudio.
#ifndef FLOWGRAPH_ANDROID_INTERNAL
#define FLOWGRAPH_ANDROID_INTERNAL 0
#endif
// Set this to a name that will prevent AAudio from calling into Oboe.
// AAudio and Oboe both use a version of this flowgraph package.
// There was a problem in the unit tests where AAudio would call a constructor
// in AAudio and then call a destructor in Oboe! That caused memory corruption.
// For more details, see Issue #930.
#ifndef FLOWGRAPH_OUTER_NAMESPACE
#define FLOWGRAPH_OUTER_NAMESPACE oboe
#endif
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
// Default block size that can be overridden when the FlowGraphPortFloat is created.
// If it is too small then we will have too much overhead from switching between nodes.
// If it is too high then we will thrash the caches.
constexpr int kDefaultBufferSize = 8; // arbitrary
class FlowGraphPort;
class FlowGraphPortFloatInput;
/***************************************************************************/
/**
* Base class for all nodes in the flowgraph.
*/
class FlowGraphNode {
public:
FlowGraphNode() {}
virtual ~FlowGraphNode() = default;
/**
* Read from the input ports,
* generate multiple frames of data then write the results to the output ports.
*
* @param numFrames maximum number of frames requested for processing
* @return number of frames actually processed
*/
virtual int32_t onProcess(int32_t numFrames) = 0;
/**
* If the callCount is at or after the previous callCount then call
* pullData on all of the upstreamNodes.
* Then call onProcess().
* This prevents infinite recursion in case of cyclic graphs.
* It also prevents nodes upstream from a branch from being executed twice.
*
* @param callCount
* @param numFrames
* @return number of frames valid
*/
int32_t pullData(int32_t numFrames, int64_t callCount);
/**
* Recursively reset all the nodes in the graph, starting from a Sink.
*
* This must not be called at the same time as pullData!
*/
void pullReset();
/**
* Reset framePosition counters.
*/
virtual void reset();
void addInputPort(FlowGraphPort &port) {
mInputPorts.push_back(port);
}
bool isDataPulledAutomatically() const {
return mDataPulledAutomatically;
}
/**
* Set true if you want the data pulled through the graph automatically.
* This is the default.
*
* Set false if you want to pull the data from the input ports in the onProcess() method.
* You might do this, for example, in a sample rate converting node.
*
* @param automatic
*/
void setDataPulledAutomatically(bool automatic) {
mDataPulledAutomatically = automatic;
}
virtual const char *getName() {
return "FlowGraph";
}
int64_t getLastCallCount() {
return mLastCallCount;
}
protected:
static constexpr int64_t kInitialCallCount = -1;
int64_t mLastCallCount = kInitialCallCount;
std::vector<std::reference_wrapper<FlowGraphPort>> mInputPorts;
private:
bool mDataPulledAutomatically = true;
bool mBlockRecursion = false;
int32_t mLastFrameCount = 0;
};
/***************************************************************************/
/**
* This is a connector that allows data to flow between modules.
*
* The ports are the primary means of interacting with a module.
* So they are generally declared as public.
*
*/
class FlowGraphPort {
public:
FlowGraphPort(FlowGraphNode &parent, int32_t samplesPerFrame)
: mContainingNode(parent)
, mSamplesPerFrame(samplesPerFrame) {
}
virtual ~FlowGraphPort() = default;
// Ports are often declared public. So let's make them non-copyable.
FlowGraphPort(const FlowGraphPort&) = delete;
FlowGraphPort& operator=(const FlowGraphPort&) = delete;
int32_t getSamplesPerFrame() const {
return mSamplesPerFrame;
}
virtual int32_t pullData(int64_t framePosition, int32_t numFrames) = 0;
virtual void pullReset() {}
protected:
FlowGraphNode &mContainingNode;
private:
const int32_t mSamplesPerFrame = 1;
};
/***************************************************************************/
/**
* This port contains a 32-bit float buffer that can contain several frames of data.
* Processing the data in a block improves performance.
*
* The size is framesPerBuffer * samplesPerFrame).
*/
class FlowGraphPortFloat : public FlowGraphPort {
public:
FlowGraphPortFloat(FlowGraphNode &parent,
int32_t samplesPerFrame,
int32_t framesPerBuffer = kDefaultBufferSize
);
virtual ~FlowGraphPortFloat() = default;
int32_t getFramesPerBuffer() const {
return mFramesPerBuffer;
}
protected:
/**
* @return buffer internal to the port or from a connected port
*/
virtual float *getBuffer() {
return mBuffer.get();
}
private:
const int32_t mFramesPerBuffer = 1;
std::unique_ptr<float[]> mBuffer; // allocated in constructor
};
/***************************************************************************/
/**
* The results of a node's processing are stored in the buffers of the output ports.
*/
class FlowGraphPortFloatOutput : public FlowGraphPortFloat {
public:
FlowGraphPortFloatOutput(FlowGraphNode &parent, int32_t samplesPerFrame)
: FlowGraphPortFloat(parent, samplesPerFrame) {
}
virtual ~FlowGraphPortFloatOutput() = default;
using FlowGraphPortFloat::getBuffer;
/**
* Connect to the input of another module.
* An input port can only have one connection.
* An output port can have multiple connections.
* If you connect a second output port to an input port
* then it overwrites the previous connection.
*
* This not thread safe. Do not modify the graph topology from another thread while running.
* Also do not delete a module while it is connected to another port if the graph is running.
*/
void connect(FlowGraphPortFloatInput *port);
/**
* Disconnect from the input of another module.
* This not thread safe.
*/
void disconnect(FlowGraphPortFloatInput *port);
/**
* Call the parent module's onProcess() method.
* That may pull data from its inputs and recursively
* process the entire graph.
* @return number of frames actually pulled
*/
int32_t pullData(int64_t framePosition, int32_t numFrames) override;
void pullReset() override;
};
/***************************************************************************/
/**
* An input port for streaming audio data.
* You can set a value that will be used for processing.
* If you connect an output port to this port then its value will be used instead.
*/
class FlowGraphPortFloatInput : public FlowGraphPortFloat {
public:
FlowGraphPortFloatInput(FlowGraphNode &parent, int32_t samplesPerFrame)
: FlowGraphPortFloat(parent, samplesPerFrame) {
// Add to parent so it can pull data from each input.
parent.addInputPort(*this);
}
virtual ~FlowGraphPortFloatInput() = default;
/**
* If connected to an output port then this will return
* that output ports buffers.
* If not connected then it returns the input ports own buffer
* which can be loaded using setValue().
*/
float *getBuffer() override;
/**
* Write every value of the float buffer.
* This value will be ignored if an output port is connected
* to this port.
*/
void setValue(float value) {
int numFloats = kDefaultBufferSize * getSamplesPerFrame();
float *buffer = getBuffer();
for (int i = 0; i < numFloats; i++) {
*buffer++ = value;
}
}
/**
* Connect to the output of another module.
* An input port can only have one connection.
* An output port can have multiple connections.
* This not thread safe.
*/
void connect(FlowGraphPortFloatOutput *port) {
assert(getSamplesPerFrame() == port->getSamplesPerFrame());
mConnected = port;
}
void disconnect(FlowGraphPortFloatOutput *port) {
assert(mConnected == port);
(void) port;
mConnected = nullptr;
}
void disconnect() {
mConnected = nullptr;
}
/**
* Pull data from any output port that is connected.
*/
int32_t pullData(int64_t framePosition, int32_t numFrames) override;
void pullReset() override;
private:
FlowGraphPortFloatOutput *mConnected = nullptr;
};
/***************************************************************************/
/**
* Base class for an edge node in a graph that has no upstream nodes.
* It outputs data but does not consume data.
* By default, it will read its data from an external buffer.
*/
class FlowGraphSource : public FlowGraphNode {
public:
explicit FlowGraphSource(int32_t channelCount)
: output(*this, channelCount) {
}
virtual ~FlowGraphSource() = default;
FlowGraphPortFloatOutput output;
};
/***************************************************************************/
/**
* Base class for an edge node in a graph that has no upstream nodes.
* It outputs data but does not consume data.
* By default, it will read its data from an external buffer.
*/
class FlowGraphSourceBuffered : public FlowGraphSource {
public:
explicit FlowGraphSourceBuffered(int32_t channelCount)
: FlowGraphSource(channelCount) {}
virtual ~FlowGraphSourceBuffered() = default;
/**
* Specify buffer that the node will read from.
*
* @param data TODO Consider using std::shared_ptr.
* @param numFrames
*/
void setData(const void *data, int32_t numFrames) {
mData = data;
mSizeInFrames = numFrames;
mFrameIndex = 0;
}
protected:
const void *mData = nullptr;
int32_t mSizeInFrames = 0; // number of frames in mData
int32_t mFrameIndex = 0; // index of next frame to be processed
};
/***************************************************************************/
/**
* Base class for an edge node in a graph that has no downstream nodes.
* It consumes data but does not output data.
* This graph will be executed when data is read() from this node
* by pulling data from upstream nodes.
*/
class FlowGraphSink : public FlowGraphNode {
public:
explicit FlowGraphSink(int32_t channelCount)
: input(*this, channelCount) {
}
virtual ~FlowGraphSink() = default;
FlowGraphPortFloatInput input;
/**
* Dummy processor. The work happens in the read() method.
*
* @param numFrames
* @return number of frames actually processed
*/
int32_t onProcess(int32_t numFrames) override {
return numFrames;
}
virtual int32_t read(void *data, int32_t numFrames) = 0;
protected:
/**
* Pull data through the graph using this nodes last callCount.
* @param numFrames
* @return
*/
int32_t pullData(int32_t numFrames);
};
/***************************************************************************/
/**
* Base class for a node that has an input and an output with the same number of channels.
* This may include traditional filters, eg. FIR, but also include
* any processing node that converts input to output.
*/
class FlowGraphFilter : public FlowGraphNode {
public:
explicit FlowGraphFilter(int32_t channelCount)
: input(*this, channelCount)
, output(*this, channelCount) {
}
virtual ~FlowGraphFilter() = default;
FlowGraphPortFloatInput input;
FlowGraphPortFloatOutput output;
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif /* FLOWGRAPH_FLOW_GRAPH_NODE_H */

View File

@ -1,55 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* 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.
*/
#ifndef FLOWGRAPH_UTILITIES_H
#define FLOWGRAPH_UTILITIES_H
#include <unistd.h>
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
class FlowgraphUtilities {
public:
// This was copied from audio_utils/primitives.h
/**
* Convert a single-precision floating point value to a Q0.31 integer value.
* Rounds to nearest, ties away from 0.
*
* Values outside the range [-1.0, 1.0) are properly clamped to -2147483648 and 2147483647,
* including -Inf and +Inf. NaN values are considered undefined, and behavior may change
* depending on hardware and future implementation of this function.
*/
static int32_t clamp32FromFloat(float f)
{
static const float scale = (float)(1UL << 31);
static const float limpos = 1.;
static const float limneg = -1.;
if (f <= limneg) {
return -0x80000000; /* or 0x80000000 */
} else if (f >= limpos) {
return 0x7fffffff;
}
f *= scale;
/* integer conversion is through truncation (though int to float is not).
* ensure that we round to nearest, ties away from 0.
*/
return f > 0 ? f + 0.5 : f - 0.5;
}
};
#endif // FLOWGRAPH_UTILITIES_H

View File

@ -1,47 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* 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.
*/
#include <unistd.h>
#include "oboe_flowgraph_ManyToMultiConverter_android.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
ManyToMultiConverter::ManyToMultiConverter(int32_t channelCount)
: inputs(channelCount)
, output(*this, channelCount) {
for (int i = 0; i < channelCount; i++) {
inputs[i] = std::make_unique<FlowGraphPortFloatInput>(*this, 1);
}
}
int32_t ManyToMultiConverter::onProcess(int32_t numFrames) {
int32_t channelCount = output.getSamplesPerFrame();
for (int ch = 0; ch < channelCount; ch++) {
const float *inputBuffer = inputs[ch]->getBuffer();
float *outputBuffer = output.getBuffer() + ch;
for (int i = 0; i < numFrames; i++) {
// read one, write into the proper interleaved output channel
float sample = *inputBuffer++;
*outputBuffer = sample;
outputBuffer += channelCount; // advance to next multichannel frame
}
}
return numFrames;
}

View File

@ -1,55 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* 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.
*/
#ifndef FLOWGRAPH_MANY_TO_MULTI_CONVERTER_H
#define FLOWGRAPH_MANY_TO_MULTI_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include <vector>
#include "oboe_flowgraph_FlowGraphNode_android.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* Combine multiple mono inputs into one interleaved multi-channel output.
*/
class ManyToMultiConverter : public flowgraph::FlowGraphNode {
public:
explicit ManyToMultiConverter(int32_t channelCount);
virtual ~ManyToMultiConverter() = default;
int32_t onProcess(int numFrames) override;
void setEnabled(bool enabled) {}
std::vector<std::unique_ptr<flowgraph::FlowGraphPortFloatInput>> inputs;
flowgraph::FlowGraphPortFloatOutput output;
const char *getName() override {
return "ManyToMultiConverter";
}
private:
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_MANY_TO_MULTI_CONVERTER_H

View File

@ -1,43 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
#include <unistd.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_flowgraph_MonoToMultiConverter_android.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
MonoToMultiConverter::MonoToMultiConverter(int32_t outputChannelCount)
: input(*this, 1)
, output(*this, outputChannelCount) {
}
MonoToMultiConverter::~MonoToMultiConverter() { }
int32_t MonoToMultiConverter::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t channelCount = output.getSamplesPerFrame();
for (int i = 0; i < numFrames; i++) {
// read one, write many
float sample = *inputBuffer++;
for (int channel = 0; channel < channelCount; channel++) {
*outputBuffer++ = sample;
}
}
return numFrames;
}

View File

@ -1,51 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
#ifndef FLOWGRAPH_MONO_TO_MULTI_CONVERTER_H
#define FLOWGRAPH_MONO_TO_MULTI_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* Convert a monophonic stream to a multi-channel interleaved stream
* with the same signal on each channel.
*/
class MonoToMultiConverter : public FlowGraphNode {
public:
explicit MonoToMultiConverter(int32_t outputChannelCount);
virtual ~MonoToMultiConverter();
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "MonoToMultiConverter";
}
FlowGraphPortFloatInput input;
FlowGraphPortFloatOutput output;
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_MONO_TO_MULTI_CONVERTER_H

View File

@ -1,41 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
#include <unistd.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_flowgraph_MultiToMonoConverter_android.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
MultiToMonoConverter::MultiToMonoConverter(int32_t inputChannelCount)
: input(*this, inputChannelCount)
, output(*this, 1) {
}
MultiToMonoConverter::~MultiToMonoConverter() { }
int32_t MultiToMonoConverter::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t channelCount = input.getSamplesPerFrame();
for (int i = 0; i < numFrames; i++) {
// read first channel of multi stream, write many
*outputBuffer++ = *inputBuffer;
inputBuffer += channelCount;
}
return numFrames;
}

View File

@ -1,51 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
#ifndef FLOWGRAPH_MULTI_TO_MONO_CONVERTER_H
#define FLOWGRAPH_MULTI_TO_MONO_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* Convert a multi-channel interleaved stream to a monophonic stream
* by extracting channel[0].
*/
class MultiToMonoConverter : public FlowGraphNode {
public:
explicit MultiToMonoConverter(int32_t inputChannelCount);
virtual ~MultiToMonoConverter();
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "MultiToMonoConverter";
}
FlowGraphPortFloatInput input;
FlowGraphPortFloatOutput output;
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_MULTI_TO_MONO_CONVERTER_H

View File

@ -1,77 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
#include <algorithm>
#include <unistd.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_flowgraph_RampLinear_android.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
RampLinear::RampLinear(int32_t channelCount)
: FlowGraphFilter(channelCount) {
mTarget.store(1.0f);
}
void RampLinear::setLengthInFrames(int32_t frames) {
mLengthInFrames = frames;
}
void RampLinear::setTarget(float target) {
mTarget.store(target);
}
float RampLinear::interpolateCurrent() {
return mLevelTo - (mRemaining * mScaler);
}
int32_t RampLinear::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t channelCount = output.getSamplesPerFrame();
float target = getTarget();
if (target != mLevelTo) {
// Start new ramp. Continue from previous level.
mLevelFrom = interpolateCurrent();
mLevelTo = target;
mRemaining = mLengthInFrames;
mScaler = (mLevelTo - mLevelFrom) / mLengthInFrames; // for interpolation
}
int32_t framesLeft = numFrames;
if (mRemaining > 0) { // Ramping? This doesn't happen very often.
int32_t framesToRamp = std::min(framesLeft, mRemaining);
framesLeft -= framesToRamp;
while (framesToRamp > 0) {
float currentLevel = interpolateCurrent();
for (int ch = 0; ch < channelCount; ch++) {
*outputBuffer++ = *inputBuffer++ * currentLevel;
}
mRemaining--;
framesToRamp--;
}
}
// Process any frames after the ramp.
int32_t samplesLeft = framesLeft * channelCount;
for (int i = 0; i < samplesLeft; i++) {
*outputBuffer++ = *inputBuffer++ * mLevelTo;
}
return numFrames;
}

View File

@ -1,98 +0,0 @@
/*
* Copyright 2015 The Android Open Source Project
*
* 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.
*/
#ifndef FLOWGRAPH_RAMP_LINEAR_H
#define FLOWGRAPH_RAMP_LINEAR_H
#include <atomic>
#include <unistd.h>
#include <sys/types.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* When the target is modified then the output will ramp smoothly
* between the original and the new target value.
* This can be used to smooth out control values and reduce pops.
*
* The target may be updated while a ramp is in progress, which will trigger
* a new ramp from the current value.
*/
class RampLinear : public FlowGraphFilter {
public:
explicit RampLinear(int32_t channelCount);
virtual ~RampLinear() = default;
int32_t onProcess(int32_t numFrames) override;
/**
* This is used for the next ramp.
* Calling this does not affect a ramp that is in progress.
*/
void setLengthInFrames(int32_t frames);
int32_t getLengthInFrames() const {
return mLengthInFrames;
}
/**
* This may be safely called by another thread.
* @param target
*/
void setTarget(float target);
float getTarget() const {
return mTarget.load();
}
/**
* Force the nextSegment to start from this level.
*
* WARNING: this can cause a discontinuity if called while the ramp is being used.
* Only call this when setting the initial ramp.
*
* @param level
*/
void forceCurrent(float level) {
mLevelFrom = level;
mLevelTo = level;
}
const char *getName() override {
return "RampLinear";
}
private:
float interpolateCurrent();
std::atomic<float> mTarget;
int32_t mLengthInFrames = 48000.0f / 100.0f ; // 10 msec at 48000 Hz;
int32_t mRemaining = 0;
float mScaler = 0.0f;
float mLevelFrom = 0.0f;
float mLevelTo = 0.0f;
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_RAMP_LINEAR_H

View File

@ -1,70 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#include "oboe_flowgraph_SampleRateConverter_android.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
using namespace resampler;
SampleRateConverter::SampleRateConverter(int32_t channelCount, MultiChannelResampler &resampler)
: FlowGraphFilter(channelCount)
, mResampler(resampler) {
setDataPulledAutomatically(false);
}
void SampleRateConverter::reset() {
FlowGraphNode::reset();
mInputCursor = kInitialCallCount;
}
// Return true if there is a sample available.
bool SampleRateConverter::isInputAvailable() {
// If we have consumed all of the input data then go out and get some more.
if (mInputCursor >= mNumValidInputFrames) {
mInputCallCount++;
mNumValidInputFrames = input.pullData(mInputCallCount, input.getFramesPerBuffer());
mInputCursor = 0;
}
return (mInputCursor < mNumValidInputFrames);
}
const float *SampleRateConverter::getNextInputFrame() {
const float *inputBuffer = input.getBuffer();
return &inputBuffer[mInputCursor++ * input.getSamplesPerFrame()];
}
int32_t SampleRateConverter::onProcess(int32_t numFrames) {
float *outputBuffer = output.getBuffer();
int32_t channelCount = output.getSamplesPerFrame();
int framesLeft = numFrames;
while (framesLeft > 0) {
// Gather input samples as needed.
if(mResampler.isWriteNeeded()) {
if (isInputAvailable()) {
const float *frame = getNextInputFrame();
mResampler.writeNextFrame(frame);
} else {
break;
}
} else {
// Output frame is interpolated from input samples.
mResampler.readNextFrame(outputBuffer);
outputBuffer += channelCount;
framesLeft--;
}
}
return numFrames - framesLeft;
}

View File

@ -1,64 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_SAMPLE_RATE_CONVERTER_H
#define OBOE_SAMPLE_RATE_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_flowgraph_resampler_MultiChannelResampler_android.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
class SampleRateConverter : public FlowGraphFilter {
public:
explicit SampleRateConverter(int32_t channelCount, resampler::MultiChannelResampler &mResampler);
virtual ~SampleRateConverter() = default;
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SampleRateConverter";
}
void reset() override;
private:
// Return true if there is a sample available.
bool isInputAvailable();
// This assumes data is available. Only call after calling isInputAvailable().
const float *getNextInputFrame();
resampler::MultiChannelResampler &mResampler;
int32_t mInputCursor = 0; // offset into the input port buffer
int32_t mNumValidInputFrames = 0; // number of valid frames currently in the input port buffer
// We need our own callCount for upstream calls because calls occur at a different rate.
// This means we cannot have cyclic graphs or merges that contain an SRC.
int64_t mInputCallCount = 0;
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //OBOE_SAMPLE_RATE_CONVERTER_H

View File

@ -1,49 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* 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.
*/
#include <algorithm>
#include <unistd.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_flowgraph_SinkFloat_android.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SinkFloat::SinkFloat(int32_t channelCount)
: FlowGraphSink(channelCount) {
}
int32_t SinkFloat::read(void *data, int32_t numFrames) {
// printf("SinkFloat::read(,,%d)\n", numFrames);
float *floatData = (float *) data;
const int32_t channelCount = input.getSamplesPerFrame();
int32_t framesLeft = numFrames;
while (framesLeft > 0) {
// Run the graph and pull data through the input port.
int32_t framesPulled = pullData(framesLeft);
// printf("SinkFloat::read: framesLeft = %d, framesPulled = %d\n", framesLeft, framesPulled);
if (framesPulled <= 0) {
break;
}
const float *signal = input.getBuffer();
int32_t numSamples = framesPulled * channelCount;
memcpy(floatData, signal, numSamples * sizeof(float));
floatData += numSamples;
framesLeft -= framesPulled;
}
// printf("SinkFloat returning %d\n", numFrames - framesLeft);
return numFrames - framesLeft;
}

View File

@ -1,47 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* 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.
*/
#ifndef FLOWGRAPH_SINK_FLOAT_H
#define FLOWGRAPH_SINK_FLOAT_H
#include <unistd.h>
#include <sys/types.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* AudioSink that lets you read data as 32-bit floats.
*/
class SinkFloat : public FlowGraphSink {
public:
explicit SinkFloat(int32_t channelCount);
~SinkFloat() override = default;
int32_t read(void *data, int32_t numFrames) override;
const char *getName() override {
return "SinkFloat";
}
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_SINK_FLOAT_H

View File

@ -1,57 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* 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.
*/
#include <algorithm>
#include <unistd.h>
#include "oboe_flowgraph_SinkI16_android.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SinkI16::SinkI16(int32_t channelCount)
: FlowGraphSink(channelCount) {}
int32_t SinkI16::read(void *data, int32_t numFrames) {
int16_t *shortData = (int16_t *) data;
const int32_t channelCount = input.getSamplesPerFrame();
int32_t framesLeft = numFrames;
while (framesLeft > 0) {
// Run the graph and pull data through the input port.
int32_t framesRead = pullData(framesLeft);
if (framesRead <= 0) {
break;
}
const float *signal = input.getBuffer();
int32_t numSamples = framesRead * channelCount;
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_i16_from_float(shortData, signal, numSamples);
shortData += numSamples;
signal += numSamples;
#else
for (int i = 0; i < numSamples; i++) {
int32_t n = (int32_t) (*signal++ * 32768.0f);
*shortData++ = std::min(INT16_MAX, std::max(INT16_MIN, n)); // clip
}
#endif
framesLeft -= framesRead;
}
return numFrames - framesLeft;
}

View File

@ -1,45 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* 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.
*/
#ifndef FLOWGRAPH_SINK_I16_H
#define FLOWGRAPH_SINK_I16_H
#include <unistd.h>
#include <sys/types.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* AudioSink that lets you read data as 16-bit signed integers.
*/
class SinkI16 : public FlowGraphSink {
public:
explicit SinkI16(int32_t channelCount);
int32_t read(void *data, int32_t numFrames) override;
const char *getName() override {
return "SinkI16";
}
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_SINK_I16_H

View File

@ -1,66 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* 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.
*/
#include <algorithm>
#include <unistd.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_flowgraph_SinkI24_android.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SinkI24::SinkI24(int32_t channelCount)
: FlowGraphSink(channelCount) {}
int32_t SinkI24::read(void *data, int32_t numFrames) {
uint8_t *byteData = (uint8_t *) data;
const int32_t channelCount = input.getSamplesPerFrame();
int32_t framesLeft = numFrames;
while (framesLeft > 0) {
// Run the graph and pull data through the input port.
int32_t framesRead = pullData(framesLeft);
if (framesRead <= 0) {
break;
}
const float *floatData = input.getBuffer();
int32_t numSamples = framesRead * channelCount;
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_p24_from_float(byteData, floatData, numSamples);
static const int kBytesPerI24Packed = 3;
byteData += numSamples * kBytesPerI24Packed;
floatData += numSamples;
#else
const int32_t kI24PackedMax = 0x007FFFFF;
const int32_t kI24PackedMin = 0xFF800000;
for (int i = 0; i < numSamples; i++) {
int32_t n = (int32_t) (*floatData++ * 0x00800000);
n = std::min(kI24PackedMax, std::max(kI24PackedMin, n)); // clip
// Write as a packed 24-bit integer in Little Endian format.
*byteData++ = (uint8_t) n;
*byteData++ = (uint8_t) (n >> 8);
*byteData++ = (uint8_t) (n >> 16);
}
#endif
framesLeft -= framesRead;
}
return numFrames - framesLeft;
}

View File

@ -1,46 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* 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.
*/
#ifndef FLOWGRAPH_SINK_I24_H
#define FLOWGRAPH_SINK_I24_H
#include <unistd.h>
#include <sys/types.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* AudioSink that lets you read data as packed 24-bit signed integers.
* The sample size is 3 bytes.
*/
class SinkI24 : public FlowGraphSink {
public:
explicit SinkI24(int32_t channelCount);
int32_t read(void *data, int32_t numFrames) override;
const char *getName() override {
return "SinkI24";
}
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_SINK_I24_H

View File

@ -1,55 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* 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.
*/
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_flowgraph_FlowgraphUtilities_android.h"
#include "oboe_flowgraph_SinkI32_android.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SinkI32::SinkI32(int32_t channelCount)
: FlowGraphSink(channelCount) {}
int32_t SinkI32::read(void *data, int32_t numFrames) {
int32_t *intData = (int32_t *) data;
const int32_t channelCount = input.getSamplesPerFrame();
int32_t framesLeft = numFrames;
while (framesLeft > 0) {
// Run the graph and pull data through the input port.
int32_t framesRead = pullData(framesLeft);
if (framesRead <= 0) {
break;
}
const float *signal = input.getBuffer();
int32_t numSamples = framesRead * channelCount;
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_i32_from_float(intData, signal, numSamples);
intData += numSamples;
signal += numSamples;
#else
for (int i = 0; i < numSamples; i++) {
*intData++ = FlowgraphUtilities::clamp32FromFloat(*signal++);
}
#endif
framesLeft -= framesRead;
}
return numFrames - framesLeft;
}

View File

@ -1,42 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* 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.
*/
#ifndef FLOWGRAPH_SINK_I32_H
#define FLOWGRAPH_SINK_I32_H
#include <stdint.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
class SinkI32 : public FlowGraphSink {
public:
explicit SinkI32(int32_t channelCount);
~SinkI32() override = default;
int32_t read(void *data, int32_t numFrames) override;
const char *getName() override {
return "SinkI32";
}
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_SINK_I32_H

View File

@ -1,43 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* 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.
*/
#include "oboe_common_OboeDebug_android.h"
#include <algorithm>
#include <unistd.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_flowgraph_SourceFloat_android.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SourceFloat::SourceFloat(int32_t channelCount)
: FlowGraphSourceBuffered(channelCount) {
}
int32_t SourceFloat::onProcess(int32_t numFrames) {
float *outputBuffer = output.getBuffer();
const int32_t channelCount = output.getSamplesPerFrame();
const int32_t framesLeft = mSizeInFrames - mFrameIndex;
const int32_t framesToProcess = std::min(numFrames, framesLeft);
const int32_t numSamples = framesToProcess * channelCount;
const float *floatBase = (float *) mData;
const float *floatData = &floatBase[mFrameIndex * channelCount];
memcpy(outputBuffer, floatData, numSamples * sizeof(float));
mFrameIndex += framesToProcess;
return framesToProcess;
}

View File

@ -1,46 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* 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.
*/
#ifndef FLOWGRAPH_SOURCE_FLOAT_H
#define FLOWGRAPH_SOURCE_FLOAT_H
#include <unistd.h>
#include <sys/types.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* AudioSource that reads a block of pre-defined float data.
*/
class SourceFloat : public FlowGraphSourceBuffered {
public:
explicit SourceFloat(int32_t channelCount);
~SourceFloat() override = default;
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceFloat";
}
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_SOURCE_FLOAT_H

View File

@ -1,54 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* 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.
*/
#include <algorithm>
#include <unistd.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_flowgraph_SourceI16_android.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SourceI16::SourceI16(int32_t channelCount)
: FlowGraphSourceBuffered(channelCount) {
}
int32_t SourceI16::onProcess(int32_t numFrames) {
float *floatData = output.getBuffer();
int32_t channelCount = output.getSamplesPerFrame();
int32_t framesLeft = mSizeInFrames - mFrameIndex;
int32_t framesToProcess = std::min(numFrames, framesLeft);
int32_t numSamples = framesToProcess * channelCount;
const int16_t *shortBase = static_cast<const int16_t *>(mData);
const int16_t *shortData = &shortBase[mFrameIndex * channelCount];
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_i16(floatData, shortData, numSamples);
#else
for (int i = 0; i < numSamples; i++) {
*floatData++ = *shortData++ * (1.0f / 32768);
}
#endif
mFrameIndex += framesToProcess;
return framesToProcess;
}

View File

@ -1,44 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* 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.
*/
#ifndef FLOWGRAPH_SOURCE_I16_H
#define FLOWGRAPH_SOURCE_I16_H
#include <unistd.h>
#include <sys/types.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* AudioSource that reads a block of pre-defined 16-bit integer data.
*/
class SourceI16 : public FlowGraphSourceBuffered {
public:
explicit SourceI16(int32_t channelCount);
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI16";
}
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_SOURCE_I16_H

View File

@ -1,65 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* 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.
*/
#include <algorithm>
#include <unistd.h>
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_flowgraph_SourceI24_android.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
constexpr int kBytesPerI24Packed = 3;
SourceI24::SourceI24(int32_t channelCount)
: FlowGraphSourceBuffered(channelCount) {
}
int32_t SourceI24::onProcess(int32_t numFrames) {
float *floatData = output.getBuffer();
int32_t channelCount = output.getSamplesPerFrame();
int32_t framesLeft = mSizeInFrames - mFrameIndex;
int32_t framesToProcess = std::min(numFrames, framesLeft);
int32_t numSamples = framesToProcess * channelCount;
const uint8_t *byteBase = (uint8_t *) mData;
const uint8_t *byteData = &byteBase[mFrameIndex * channelCount * kBytesPerI24Packed];
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_p24(floatData, byteData, numSamples);
#else
static const float scale = 1. / (float)(1UL << 31);
for (int i = 0; i < numSamples; i++) {
// Assemble the data assuming Little Endian format.
int32_t pad = byteData[2];
pad <<= 8;
pad |= byteData[1];
pad <<= 8;
pad |= byteData[0];
pad <<= 8; // Shift to 32 bit data so the sign is correct.
byteData += kBytesPerI24Packed;
*floatData++ = pad * scale; // scale to range -1.0 to 1.0
}
#endif
mFrameIndex += framesToProcess;
return framesToProcess;
}

View File

@ -1,45 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* 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.
*/
#ifndef FLOWGRAPH_SOURCE_I24_H
#define FLOWGRAPH_SOURCE_I24_H
#include <unistd.h>
#include <sys/types.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* AudioSource that reads a block of pre-defined 24-bit packed integer data.
*/
class SourceI24 : public FlowGraphSourceBuffered {
public:
explicit SourceI24(int32_t channelCount);
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI24";
}
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_SOURCE_I24_H

View File

@ -1,54 +0,0 @@
/*
* Copyright 2020 The Android Open Source Project
*
* 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.
*/
#include <algorithm>
#include <unistd.h>
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
#include "oboe_flowgraph_FlowGraphNode_android.h"
#include "oboe_flowgraph_SourceI32_android.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
SourceI32::SourceI32(int32_t channelCount)
: FlowGraphSourceBuffered(channelCount) {
}
int32_t SourceI32::onProcess(int32_t numFrames) {
float *floatData = output.getBuffer();
const int32_t channelCount = output.getSamplesPerFrame();
const int32_t framesLeft = mSizeInFrames - mFrameIndex;
const int32_t framesToProcess = std::min(numFrames, framesLeft);
const int32_t numSamples = framesToProcess * channelCount;
const int32_t *intBase = static_cast<const int32_t *>(mData);
const int32_t *intData = &intBase[mFrameIndex * channelCount];
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_i32(floatData, intData, numSamples);
#else
for (int i = 0; i < numSamples; i++) {
*floatData++ = *intData++ * kScale;
}
#endif
mFrameIndex += framesToProcess;
return framesToProcess;
}

View File

@ -1,44 +0,0 @@
/*
* Copyright 2018 The Android Open Source Project
*
* 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.
*/
#ifndef FLOWGRAPH_SOURCE_I32_H
#define FLOWGRAPH_SOURCE_I32_H
#include <stdint.h>
#include "oboe_flowgraph_FlowGraphNode_android.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
class SourceI32 : public FlowGraphSourceBuffered {
public:
explicit SourceI32(int32_t channelCount);
~SourceI32() override = default;
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI32";
}
private:
static constexpr float kScale = 1.0 / (1UL << 31);
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_SOURCE_I32_H

View File

@ -1,68 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#ifndef RESAMPLER_HYPERBOLIC_COSINE_WINDOW_H
#define RESAMPLER_HYPERBOLIC_COSINE_WINDOW_H
#include <math.h>
namespace resampler {
/**
* Calculate a HyperbolicCosineWindow window centered at 0.
* This can be used in place of a Kaiser window.
*
* The code is based on an anonymous contribution by "a concerned citizen":
* https://dsp.stackexchange.com/questions/37714/kaiser-window-approximation
*/
class HyperbolicCosineWindow {
public:
HyperbolicCosineWindow() {
setStopBandAttenuation(60);
}
/**
* @param attenuation typical values range from 30 to 90 dB
* @return beta
*/
double setStopBandAttenuation(double attenuation) {
double alpha = ((-325.1e-6 * attenuation + 0.1677) * attenuation) - 3.149;
setAlpha(alpha);
return alpha;
}
void setAlpha(double alpha) {
mAlpha = alpha;
mInverseCoshAlpha = 1.0 / cosh(alpha);
}
/**
* @param x ranges from -1.0 to +1.0
*/
double operator()(double x) {
double x2 = x * x;
if (x2 >= 1.0) return 0.0;
double w = mAlpha * sqrt(1.0 - x2);
return cosh(w) * mInverseCoshAlpha;
}
private:
double mAlpha = 0.0;
double mInverseCoshAlpha = 1.0;
};
} // namespace resampler
#endif //RESAMPLER_HYPERBOLIC_COSINE_WINDOW_H

View File

@ -1,50 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#include "oboe_flowgraph_resampler_IntegerRatio_android.h"
using namespace resampler;
// Enough primes to cover the common sample rates.
static const int kPrimes[] = {
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41,
43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97,
101, 103, 107, 109, 113, 127, 131, 137, 139, 149,
151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199};
void IntegerRatio::reduce() {
for (int prime : kPrimes) {
if (mNumerator < prime || mDenominator < prime) {
break;
}
// Find biggest prime factor for numerator.
while (true) {
int top = mNumerator / prime;
int bottom = mDenominator / prime;
if ((top >= 1)
&& (bottom >= 1)
&& (top * prime == mNumerator) // divided evenly?
&& (bottom * prime == mDenominator)) {
mNumerator = top;
mDenominator = bottom;
} else {
break;
}
}
}
}

View File

@ -1,52 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_INTEGER_RATIO_H
#define OBOE_INTEGER_RATIO_H
#include <sys/types.h>
namespace resampler {
/**
* Represent the ratio of two integers.
*/
class IntegerRatio {
public:
IntegerRatio(int32_t numerator, int32_t denominator)
: mNumerator(numerator), mDenominator(denominator) {}
/**
* Reduce by removing common prime factors.
*/
void reduce();
int32_t getNumerator() {
return mNumerator;
}
int32_t getDenominator() {
return mDenominator;
}
private:
int32_t mNumerator;
int32_t mDenominator;
};
}
#endif //OBOE_INTEGER_RATIO_H

View File

@ -1,87 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#ifndef RESAMPLER_KAISER_WINDOW_H
#define RESAMPLER_KAISER_WINDOW_H
#include <math.h>
namespace resampler {
/**
* Calculate a Kaiser window centered at 0.
*/
class KaiserWindow {
public:
KaiserWindow() {
setStopBandAttenuation(60);
}
/**
* @param attenuation typical values range from 30 to 90 dB
* @return beta
*/
double setStopBandAttenuation(double attenuation) {
double beta = 0.0;
if (attenuation > 50) {
beta = 0.1102 * (attenuation - 8.7);
} else if (attenuation >= 21) {
double a21 = attenuation - 21;
beta = 0.5842 * pow(a21, 0.4) + (0.07886 * a21);
}
setBeta(beta);
return beta;
}
void setBeta(double beta) {
mBeta = beta;
mInverseBesselBeta = 1.0 / bessel(beta);
}
/**
* @param x ranges from -1.0 to +1.0
*/
double operator()(double x) {
double x2 = x * x;
if (x2 >= 1.0) return 0.0;
double w = mBeta * sqrt(1.0 - x2);
return bessel(w) * mInverseBesselBeta;
}
// Approximation of a
// modified zero order Bessel function of the first kind.
// Based on a discussion at:
// https://dsp.stackexchange.com/questions/37714/kaiser-window-approximation
static double bessel(double x) {
double y = cosh(0.970941817426052 * x);
y += cosh(0.8854560256532099 * x);
y += cosh(0.7485107481711011 * x);
y += cosh(0.5680647467311558 * x);
y += cosh(0.3546048870425356 * x);
y += cosh(0.120536680255323 * x);
y *= 2;
y += cosh(x);
y /= 13;
return y;
}
private:
double mBeta = 0.0;
double mInverseBesselBeta = 1.0;
};
} // namespace resampler
#endif //RESAMPLER_KAISER_WINDOW_H

View File

@ -1,42 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#include "oboe_flowgraph_resampler_LinearResampler_android.h"
using namespace resampler;
LinearResampler::LinearResampler(const MultiChannelResampler::Builder &builder)
: MultiChannelResampler(builder) {
mPreviousFrame = std::make_unique<float[]>(getChannelCount());
mCurrentFrame = std::make_unique<float[]>(getChannelCount());
}
void LinearResampler::writeFrame(const float *frame) {
memcpy(mPreviousFrame.get(), mCurrentFrame.get(), sizeof(float) * getChannelCount());
memcpy(mCurrentFrame.get(), frame, sizeof(float) * getChannelCount());
}
void LinearResampler::readFrame(float *frame) {
float *previous = mPreviousFrame.get();
float *current = mCurrentFrame.get();
float phase = (float) getIntegerPhase() / mDenominator;
// iterate across samples in the frame
for (int channel = 0; channel < getChannelCount(); channel++) {
float f0 = *previous++;
float f1 = *current++;
*frame++ = f0 + (phase * (f1 - f0));
}
}

View File

@ -1,44 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_LINEAR_RESAMPLER_H
#define OBOE_LINEAR_RESAMPLER_H
#include <memory>
#include <sys/types.h>
#include <unistd.h>
#include "oboe_flowgraph_resampler_MultiChannelResampler_android.h"
namespace resampler {
/**
* Simple resampler that uses bi-linear interpolation.
*/
class LinearResampler : public MultiChannelResampler {
public:
LinearResampler(const MultiChannelResampler::Builder &builder);
void writeFrame(const float *frame) override;
void readFrame(float *frame) override;
private:
std::unique_ptr<float[]> mPreviousFrame;
std::unique_ptr<float[]> mCurrentFrame;
};
} // namespace resampler
#endif //OBOE_LINEAR_RESAMPLER_H

View File

@ -1,171 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#include <math.h>
#include "oboe_flowgraph_resampler_IntegerRatio_android.h"
#include "oboe_flowgraph_resampler_LinearResampler_android.h"
#include "oboe_flowgraph_resampler_MultiChannelResampler_android.h"
#include "oboe_flowgraph_resampler_PolyphaseResampler_android.h"
#include "oboe_flowgraph_resampler_PolyphaseResamplerMono_android.h"
#include "oboe_flowgraph_resampler_PolyphaseResamplerStereo_android.h"
#include "oboe_flowgraph_resampler_SincResampler_android.h"
#include "oboe_flowgraph_resampler_SincResamplerStereo_android.h"
using namespace resampler;
MultiChannelResampler::MultiChannelResampler(const MultiChannelResampler::Builder &builder)
: mNumTaps(builder.getNumTaps())
, mX(builder.getChannelCount() * builder.getNumTaps() * 2)
, mSingleFrame(builder.getChannelCount())
, mChannelCount(builder.getChannelCount())
{
// Reduce sample rates to the smallest ratio.
// For example 44100/48000 would become 147/160.
IntegerRatio ratio(builder.getInputRate(), builder.getOutputRate());
ratio.reduce();
mNumerator = ratio.getNumerator();
mDenominator = ratio.getDenominator();
mIntegerPhase = mDenominator;
}
// static factory method
MultiChannelResampler *MultiChannelResampler::make(int32_t channelCount,
int32_t inputRate,
int32_t outputRate,
Quality quality) {
Builder builder;
builder.setInputRate(inputRate);
builder.setOutputRate(outputRate);
builder.setChannelCount(channelCount);
switch (quality) {
case Quality::Fastest:
builder.setNumTaps(2);
break;
case Quality::Low:
builder.setNumTaps(4);
break;
case Quality::Medium:
default:
builder.setNumTaps(8);
break;
case Quality::High:
builder.setNumTaps(16);
break;
case Quality::Best:
builder.setNumTaps(32);
break;
}
// Set the cutoff frequency so that we do not get aliasing when down-sampling.
if (inputRate > outputRate) {
builder.setNormalizedCutoff(kDefaultNormalizedCutoff);
}
return builder.build();
}
MultiChannelResampler *MultiChannelResampler::Builder::build() {
if (getNumTaps() == 2) {
// Note that this does not do low pass filteringh.
return new LinearResampler(*this);
}
IntegerRatio ratio(getInputRate(), getOutputRate());
ratio.reduce();
bool usePolyphase = (getNumTaps() * ratio.getDenominator()) <= kMaxCoefficients;
if (usePolyphase) {
if (getChannelCount() == 1) {
return new PolyphaseResamplerMono(*this);
} else if (getChannelCount() == 2) {
return new PolyphaseResamplerStereo(*this);
} else {
return new PolyphaseResampler(*this);
}
} else {
// Use less optimized resampler that uses a float phaseIncrement.
// TODO mono resampler
if (getChannelCount() == 2) {
return new SincResamplerStereo(*this);
} else {
return new SincResampler(*this);
}
}
}
void MultiChannelResampler::writeFrame(const float *frame) {
// Move cursor before write so that cursor points to last written frame in read.
if (--mCursor < 0) {
mCursor = getNumTaps() - 1;
}
float *dest = &mX[mCursor * getChannelCount()];
int offset = getNumTaps() * getChannelCount();
for (int channel = 0; channel < getChannelCount(); channel++) {
// Write twice so we avoid having to wrap when reading.
dest[channel] = dest[channel + offset] = frame[channel];
}
}
float MultiChannelResampler::sinc(float radians) {
if (abs(radians) < 1.0e-9) return 1.0f; // avoid divide by zero
return sinf(radians) / radians; // Sinc function
}
// Generate coefficients in the order they will be used by readFrame().
// This is more complicated but readFrame() is called repeatedly and should be optimized.
void MultiChannelResampler::generateCoefficients(int32_t inputRate,
int32_t outputRate,
int32_t numRows,
double phaseIncrement,
float normalizedCutoff) {
mCoefficients.resize(getNumTaps() * numRows);
int coefficientIndex = 0;
double phase = 0.0; // ranges from 0.0 to 1.0, fraction between samples
// Stretch the sinc function for low pass filtering.
const float cutoffScaler = normalizedCutoff *
((outputRate < inputRate)
? ((float)outputRate / inputRate)
: ((float)inputRate / outputRate));
const int numTapsHalf = getNumTaps() / 2; // numTaps must be even.
const float numTapsHalfInverse = 1.0f / numTapsHalf;
for (int i = 0; i < numRows; i++) {
float tapPhase = phase - numTapsHalf;
float gain = 0.0; // sum of raw coefficients
int gainCursor = coefficientIndex;
for (int tap = 0; tap < getNumTaps(); tap++) {
float radians = tapPhase * M_PI;
#if MCR_USE_KAISER
float window = mKaiserWindow(tapPhase * numTapsHalfInverse);
#else
float window = mCoshWindow(tapPhase * numTapsHalfInverse);
#endif
float coefficient = sinc(radians * cutoffScaler) * window;
mCoefficients.at(coefficientIndex++) = coefficient;
gain += coefficient;
tapPhase += 1.0;
}
phase += phaseIncrement;
while (phase >= 1.0) {
phase -= 1.0;
}
// Correct for gain variations.
float gainCorrection = 1.0 / gain; // normalize the gain
for (int tap = 0; tap < getNumTaps(); tap++) {
mCoefficients.at(gainCursor + tap) *= gainCorrection;
}
}
}

View File

@ -1,271 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_MULTICHANNEL_RESAMPLER_H
#define OBOE_MULTICHANNEL_RESAMPLER_H
#include <memory>
#include <vector>
#include <sys/types.h>
#include <unistd.h>
#ifndef MCR_USE_KAISER
// It appears from the spectrogram that the HyperbolicCosine window leads to fewer artifacts.
// And it is faster to calculate.
#define MCR_USE_KAISER 0
#endif
#if MCR_USE_KAISER
#include "oboe_flowgraph_resampler_KaiserWindow_android.h"
#else
#include "oboe_flowgraph_resampler_HyperbolicCosineWindow_android.h"
#endif
namespace resampler {
class MultiChannelResampler {
public:
enum class Quality : int32_t {
Fastest,
Low,
Medium,
High,
Best,
};
class Builder {
public:
/**
* Construct an optimal resampler based on the specified parameters.
* @return address of a resampler
*/
MultiChannelResampler *build();
/**
* The number of taps in the resampling filter.
* More taps gives better quality but uses more CPU time.
* This typically ranges from 4 to 64. Default is 16.
*
* For polyphase filters, numTaps must be a multiple of four for loop unrolling.
* @param numTaps number of taps for the filter
* @return address of this builder for chaining calls
*/
Builder *setNumTaps(int32_t numTaps) {
mNumTaps = numTaps;
return this;
}
/**
* Use 1 for mono, 2 for stereo, etc. Default is 1.
*
* @param channelCount number of channels
* @return address of this builder for chaining calls
*/
Builder *setChannelCount(int32_t channelCount) {
mChannelCount = channelCount;
return this;
}
/**
* Default is 48000.
*
* @param inputRate sample rate of the input stream
* @return address of this builder for chaining calls
*/
Builder *setInputRate(int32_t inputRate) {
mInputRate = inputRate;
return this;
}
/**
* Default is 48000.
*
* @param outputRate sample rate of the output stream
* @return address of this builder for chaining calls
*/
Builder *setOutputRate(int32_t outputRate) {
mOutputRate = outputRate;
return this;
}
/**
* Set cutoff frequency relative to the Nyquist rate of the output sample rate.
* Set to 1.0 to match the Nyquist frequency.
* Set lower to reduce aliasing.
* Default is 0.70.
*
* @param normalizedCutoff anti-aliasing filter cutoff
* @return address of this builder for chaining calls
*/
Builder *setNormalizedCutoff(float normalizedCutoff) {
mNormalizedCutoff = normalizedCutoff;
return this;
}
int32_t getNumTaps() const {
return mNumTaps;
}
int32_t getChannelCount() const {
return mChannelCount;
}
int32_t getInputRate() const {
return mInputRate;
}
int32_t getOutputRate() const {
return mOutputRate;
}
float getNormalizedCutoff() const {
return mNormalizedCutoff;
}
protected:
int32_t mChannelCount = 1;
int32_t mNumTaps = 16;
int32_t mInputRate = 48000;
int32_t mOutputRate = 48000;
float mNormalizedCutoff = kDefaultNormalizedCutoff;
};
virtual ~MultiChannelResampler() = default;
/**
* Factory method for making a resampler that is optimal for the given inputs.
*
* @param channelCount number of channels, 2 for stereo
* @param inputRate sample rate of the input stream
* @param outputRate sample rate of the output stream
* @param quality higher quality sounds better but uses more CPU
* @return an optimal resampler
*/
static MultiChannelResampler *make(int32_t channelCount,
int32_t inputRate,
int32_t outputRate,
Quality quality);
bool isWriteNeeded() const {
return mIntegerPhase >= mDenominator;
}
/**
* Write a frame containing N samples.
*
* @param frame pointer to the first sample in a frame
*/
void writeNextFrame(const float *frame) {
writeFrame(frame);
advanceWrite();
}
/**
* Read a frame containing N samples.
*
* @param frame pointer to the first sample in a frame
*/
void readNextFrame(float *frame) {
readFrame(frame);
advanceRead();
}
int getNumTaps() const {
return mNumTaps;
}
int getChannelCount() const {
return mChannelCount;
}
static float hammingWindow(float radians, float spread);
static float sinc(float radians);
protected:
explicit MultiChannelResampler(const MultiChannelResampler::Builder &builder);
/**
* Write a frame containing N samples.
* Call advanceWrite() after calling this.
* @param frame pointer to the first sample in a frame
*/
virtual void writeFrame(const float *frame);
/**
* Read a frame containing N samples using interpolation.
* Call advanceRead() after calling this.
* @param frame pointer to the first sample in a frame
*/
virtual void readFrame(float *frame) = 0;
void advanceWrite() {
mIntegerPhase -= mDenominator;
}
void advanceRead() {
mIntegerPhase += mNumerator;
}
/**
* Generate the filter coefficients in optimal order.
* @param inputRate sample rate of the input stream
* @param outputRate sample rate of the output stream
* @param numRows number of rows in the array that contain a set of tap coefficients
* @param phaseIncrement how much to increment the phase between rows
* @param normalizedCutoff filter cutoff frequency normalized to Nyquist rate of output
*/
void generateCoefficients(int32_t inputRate,
int32_t outputRate,
int32_t numRows,
double phaseIncrement,
float normalizedCutoff);
int32_t getIntegerPhase() {
return mIntegerPhase;
}
static constexpr int kMaxCoefficients = 8 * 1024;
std::vector<float> mCoefficients;
const int mNumTaps;
int mCursor = 0;
std::vector<float> mX; // delayed input values for the FIR
std::vector<float> mSingleFrame; // one frame for temporary use
int32_t mIntegerPhase = 0;
int32_t mNumerator = 0;
int32_t mDenominator = 0;
private:
#if MCR_USE_KAISER
KaiserWindow mKaiserWindow;
#else
HyperbolicCosineWindow mCoshWindow;
#endif
static constexpr float kDefaultNormalizedCutoff = 0.70f;
const int mChannelCount;
};
}
#endif //OBOE_MULTICHANNEL_RESAMPLER_H

View File

@ -1,63 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#include <cassert>
#include "oboe_flowgraph_resampler_PolyphaseResamplerMono_android.h"
using namespace resampler;
#define MONO 1
PolyphaseResamplerMono::PolyphaseResamplerMono(const MultiChannelResampler::Builder &builder)
: PolyphaseResampler(builder) {
assert(builder.getChannelCount() == MONO);
}
void PolyphaseResamplerMono::writeFrame(const float *frame) {
// Move cursor before write so that cursor points to last written frame in read.
if (--mCursor < 0) {
mCursor = getNumTaps() - 1;
}
float *dest = &mX[mCursor * MONO];
const int offset = mNumTaps * MONO;
// Write each channel twice so we avoid having to wrap when running the FIR.
const float sample = frame[0];
// Put ordered writes together.
dest[0] = sample;
dest[offset] = sample;
}
void PolyphaseResamplerMono::readFrame(float *frame) {
// Clear accumulator.
float sum = 0.0;
// Multiply input times precomputed windowed sinc function.
const float *coefficients = &mCoefficients[mCoefficientCursor];
float *xFrame = &mX[mCursor * MONO];
const int numLoops = mNumTaps >> 2; // n/4
for (int i = 0; i < numLoops; i++) {
// Manual loop unrolling, might get converted to SIMD.
sum += *xFrame++ * *coefficients++;
sum += *xFrame++ * *coefficients++;
sum += *xFrame++ * *coefficients++;
sum += *xFrame++ * *coefficients++;
}
mCoefficientCursor = (mCoefficientCursor + mNumTaps) % mCoefficients.size();
// Copy accumulator to output.
frame[0] = sum;
}

View File

@ -1,39 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_POLYPHASE_RESAMPLER_MONO_H
#define OBOE_POLYPHASE_RESAMPLER_MONO_H
#include <sys/types.h>
#include <unistd.h>
#include "oboe_flowgraph_resampler_PolyphaseResampler_android.h"
namespace resampler {
class PolyphaseResamplerMono : public PolyphaseResampler {
public:
explicit PolyphaseResamplerMono(const MultiChannelResampler::Builder &builder);
virtual ~PolyphaseResamplerMono() = default;
void writeFrame(const float *frame) override;
void readFrame(float *frame) override;
};
}
#endif //OBOE_POLYPHASE_RESAMPLER_MONO_H

View File

@ -1,79 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#include <cassert>
#include "oboe_flowgraph_resampler_PolyphaseResamplerStereo_android.h"
using namespace resampler;
#define STEREO 2
PolyphaseResamplerStereo::PolyphaseResamplerStereo(const MultiChannelResampler::Builder &builder)
: PolyphaseResampler(builder) {
assert(builder.getChannelCount() == STEREO);
}
void PolyphaseResamplerStereo::writeFrame(const float *frame) {
// Move cursor before write so that cursor points to last written frame in read.
if (--mCursor < 0) {
mCursor = getNumTaps() - 1;
}
float *dest = &mX[mCursor * STEREO];
const int offset = mNumTaps * STEREO;
// Write each channel twice so we avoid having to wrap when running the FIR.
const float left = frame[0];
const float right = frame[1];
// Put ordered writes together.
dest[0] = left;
dest[1] = right;
dest[offset] = left;
dest[1 + offset] = right;
}
void PolyphaseResamplerStereo::readFrame(float *frame) {
// Clear accumulators.
float left = 0.0;
float right = 0.0;
// Multiply input times precomputed windowed sinc function.
const float *coefficients = &mCoefficients[mCoefficientCursor];
float *xFrame = &mX[mCursor * STEREO];
const int numLoops = mNumTaps >> 2; // n/4
for (int i = 0; i < numLoops; i++) {
// Manual loop unrolling, might get converted to SIMD.
float coefficient = *coefficients++;
left += *xFrame++ * coefficient;
right += *xFrame++ * coefficient;
coefficient = *coefficients++; // next tap
left += *xFrame++ * coefficient;
right += *xFrame++ * coefficient;
coefficient = *coefficients++; // next tap
left += *xFrame++ * coefficient;
right += *xFrame++ * coefficient;
coefficient = *coefficients++; // next tap
left += *xFrame++ * coefficient;
right += *xFrame++ * coefficient;
}
mCoefficientCursor = (mCoefficientCursor + mNumTaps) % mCoefficients.size();
// Copy accumulators to output.
frame[0] = left;
frame[1] = right;
}

View File

@ -1,39 +0,0 @@
/*
* Copyright 2019 The Android Open Source Project
*
* 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.
*/
#ifndef OBOE_POLYPHASE_RESAMPLER_STEREO_H
#define OBOE_POLYPHASE_RESAMPLER_STEREO_H
#include <sys/types.h>
#include <unistd.h>
#include "oboe_flowgraph_resampler_PolyphaseResampler_android.h"
namespace resampler {
class PolyphaseResamplerStereo : public PolyphaseResampler {
public:
explicit PolyphaseResamplerStereo(const MultiChannelResampler::Builder &builder);
virtual ~PolyphaseResamplerStereo() = default;
void writeFrame(const float *frame) override;
void readFrame(float *frame) override;
};
}
#endif //OBOE_POLYPHASE_RESAMPLER_STEREO_H

Some files were not shown because too many files have changed in this diff Show More