build(deps): vendor JUCE 7.0.12
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
# We want to ignore this directory, but we need to enable at least a single
|
||||
# check to avoid an error
|
||||
Checks: -*,darwin-dispatch-once-nonstatic
|
||||
@@ -0,0 +1,105 @@
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
|
||||
# Set the name of the project and store it in PROJECT_NAME. Also set the following variables:
|
||||
# PROJECT_SOURCE_DIR (usually the root directory where Oboe has been cloned e.g.)
|
||||
# PROJECT_BINARY_DIR (usually the containing project's binary directory,
|
||||
# e.g. ${OBOE_HOME}/samples/RhythmGame/.externalNativeBuild/cmake/ndkExtractorDebug/x86/oboe-bin)
|
||||
project(oboe)
|
||||
|
||||
set (oboe_sources
|
||||
src/aaudio/AAudioLoader.cpp
|
||||
src/aaudio/AudioStreamAAudio.cpp
|
||||
src/common/AdpfWrapper.cpp
|
||||
src/common/AudioSourceCaller.cpp
|
||||
src/common/AudioStream.cpp
|
||||
src/common/AudioStreamBuilder.cpp
|
||||
src/common/DataConversionFlowGraph.cpp
|
||||
src/common/FilterAudioStream.cpp
|
||||
src/common/FixedBlockAdapter.cpp
|
||||
src/common/FixedBlockReader.cpp
|
||||
src/common/FixedBlockWriter.cpp
|
||||
src/common/LatencyTuner.cpp
|
||||
src/common/OboeExtensions.cpp
|
||||
src/common/SourceFloatCaller.cpp
|
||||
src/common/SourceI16Caller.cpp
|
||||
src/common/SourceI24Caller.cpp
|
||||
src/common/SourceI32Caller.cpp
|
||||
src/common/Utilities.cpp
|
||||
src/common/QuirksManager.cpp
|
||||
src/fifo/FifoBuffer.cpp
|
||||
src/fifo/FifoController.cpp
|
||||
src/fifo/FifoControllerBase.cpp
|
||||
src/fifo/FifoControllerIndirect.cpp
|
||||
src/flowgraph/FlowGraphNode.cpp
|
||||
src/flowgraph/ChannelCountConverter.cpp
|
||||
src/flowgraph/ClipToRange.cpp
|
||||
src/flowgraph/Limiter.cpp
|
||||
src/flowgraph/ManyToMultiConverter.cpp
|
||||
src/flowgraph/MonoBlend.cpp
|
||||
src/flowgraph/MonoToMultiConverter.cpp
|
||||
src/flowgraph/MultiToManyConverter.cpp
|
||||
src/flowgraph/MultiToMonoConverter.cpp
|
||||
src/flowgraph/RampLinear.cpp
|
||||
src/flowgraph/SampleRateConverter.cpp
|
||||
src/flowgraph/SinkFloat.cpp
|
||||
src/flowgraph/SinkI16.cpp
|
||||
src/flowgraph/SinkI24.cpp
|
||||
src/flowgraph/SinkI32.cpp
|
||||
src/flowgraph/SourceFloat.cpp
|
||||
src/flowgraph/SourceI16.cpp
|
||||
src/flowgraph/SourceI24.cpp
|
||||
src/flowgraph/SourceI32.cpp
|
||||
src/flowgraph/resampler/IntegerRatio.cpp
|
||||
src/flowgraph/resampler/LinearResampler.cpp
|
||||
src/flowgraph/resampler/MultiChannelResampler.cpp
|
||||
src/flowgraph/resampler/PolyphaseResampler.cpp
|
||||
src/flowgraph/resampler/PolyphaseResamplerMono.cpp
|
||||
src/flowgraph/resampler/PolyphaseResamplerStereo.cpp
|
||||
src/flowgraph/resampler/SincResampler.cpp
|
||||
src/flowgraph/resampler/SincResamplerStereo.cpp
|
||||
src/opensles/AudioInputStreamOpenSLES.cpp
|
||||
src/opensles/AudioOutputStreamOpenSLES.cpp
|
||||
src/opensles/AudioStreamBuffered.cpp
|
||||
src/opensles/AudioStreamOpenSLES.cpp
|
||||
src/opensles/EngineOpenSLES.cpp
|
||||
src/opensles/OpenSLESUtilities.cpp
|
||||
src/opensles/OutputMixerOpenSLES.cpp
|
||||
src/common/StabilizedCallback.cpp
|
||||
src/common/Trace.cpp
|
||||
src/common/Version.cpp
|
||||
)
|
||||
|
||||
add_library(oboe ${oboe_sources})
|
||||
|
||||
# Specify directories which the compiler should look for headers
|
||||
target_include_directories(oboe
|
||||
PRIVATE src
|
||||
PUBLIC include)
|
||||
|
||||
# JUCE CHANGE STARTS HERE
|
||||
|
||||
# This comment provided for Apache License compliance. We've removed the extra warnings flags and
|
||||
# the `-Werror` option, to avoid cases where compilers produce unexpected errors and fail the build.
|
||||
# We've also removed the explicit `-std=c++17` compile option, and replaced it with a more
|
||||
# cmake-friendly way of specifying the language standard.
|
||||
|
||||
target_compile_options(oboe
|
||||
PRIVATE
|
||||
"$<$<CONFIG:RELEASE>:-Ofast>"
|
||||
"$<$<CONFIG:DEBUG>:-O3>")
|
||||
target_compile_features(oboe PRIVATE cxx_std_17)
|
||||
|
||||
# JUCE CHANGE ENDS HERE
|
||||
|
||||
# Enable logging of D,V for debug builds
|
||||
target_compile_definitions(oboe PUBLIC $<$<CONFIG:DEBUG>:OBOE_ENABLE_LOGGING=1>)
|
||||
|
||||
target_link_libraries(oboe PRIVATE log OpenSLES)
|
||||
|
||||
# When installing oboe put the libraries in the lib/<ABI> folder e.g. lib/arm64-v8a
|
||||
install(TARGETS oboe
|
||||
LIBRARY DESTINATION lib/${ANDROID_ABI}
|
||||
ARCHIVE DESTINATION lib/${ANDROID_ABI})
|
||||
|
||||
# Also install the headers
|
||||
install(DIRECTORY include/oboe DESTINATION include)
|
||||
@@ -0,0 +1,202 @@
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,10 @@
|
||||
The files in this directory are reproduced from the official Oboe repository, which can be found at
|
||||
github.com/google/oboe.
|
||||
|
||||
These files are from tag 1.8.0 (987538b).
|
||||
|
||||
We've included only those parts of the original repository which are required to build the Oboe
|
||||
library. Documentation, samples, tests, and other non-library items have been omitted.
|
||||
|
||||
Files in this directory and below are licensed under the terms of the license in the LICENSE file
|
||||
which you can find in the same directory as this readme.
|
||||
+706
@@ -0,0 +1,706 @@
|
||||
/*
|
||||
* 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_H_
|
||||
#define OBOE_STREAM_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <mutex>
|
||||
#include "oboe/Definitions.h"
|
||||
#include "oboe/ResultWithValue.h"
|
||||
#include "oboe/AudioStreamBuilder.h"
|
||||
#include "oboe/AudioStreamBase.h"
|
||||
|
||||
/** WARNING - UNDER CONSTRUCTION - THIS API WILL CHANGE. */
|
||||
|
||||
namespace oboe {
|
||||
|
||||
/**
|
||||
* The default number of nanoseconds to wait for when performing state change operations on the
|
||||
* stream, such as `start` and `stop`.
|
||||
*
|
||||
* @see oboe::AudioStream::start
|
||||
*/
|
||||
constexpr int64_t kDefaultTimeoutNanos = (2000 * kNanosPerMillisecond);
|
||||
|
||||
/**
|
||||
* Base class for Oboe C++ audio stream.
|
||||
*/
|
||||
class AudioStream : public AudioStreamBase {
|
||||
friend class AudioStreamBuilder; // allow access to setWeakThis() and lockWeakThis()
|
||||
public:
|
||||
|
||||
AudioStream() {}
|
||||
|
||||
/**
|
||||
* Construct an `AudioStream` using the given `AudioStreamBuilder`
|
||||
*
|
||||
* @param builder containing all the stream's attributes
|
||||
*/
|
||||
explicit AudioStream(const AudioStreamBuilder &builder);
|
||||
|
||||
virtual ~AudioStream() = default;
|
||||
|
||||
/**
|
||||
* Open a stream based on the current settings.
|
||||
*
|
||||
* Note that we do not recommend re-opening a stream that has been closed.
|
||||
* TODO Should we prevent re-opening?
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
virtual Result open() {
|
||||
return Result::OK; // Called by subclasses. Might do more in the future.
|
||||
}
|
||||
|
||||
/**
|
||||
* Free the audio resources associated with a stream created by AAudioStreamBuilder_openStream().
|
||||
*
|
||||
* AAudioStream_close() should be called at some point after calling this function.
|
||||
*
|
||||
* After this call, the stream will be in AAUDIO_STREAM_STATE_CLOSING
|
||||
*
|
||||
* This function is useful if you want to release the audio resources immediately, but still allow
|
||||
* queries to the stream to occur from other threads. This often happens if you are monitoring
|
||||
* stream progress from a UI thread.
|
||||
*
|
||||
* NOTE: This function is only fully implemented for MMAP streams, which are low latency streams
|
||||
* supported by some devices. On other "Legacy" streams some audio resources will still be in use
|
||||
* and some callbacks may still be in process after this call.
|
||||
*
|
||||
* Available in AAudio since API level 30. Returns Result::ErrorUnimplemented otherwise.
|
||||
*
|
||||
* * @return either Result::OK or an error.
|
||||
*/
|
||||
virtual Result release() {
|
||||
return Result::ErrorUnimplemented;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the stream and deallocate any resources from the open() call.
|
||||
*/
|
||||
virtual Result close();
|
||||
|
||||
/**
|
||||
* Start the stream. This will block until the stream has been started, an error occurs
|
||||
* or `timeoutNanoseconds` has been reached.
|
||||
*/
|
||||
virtual Result start(int64_t timeoutNanoseconds = kDefaultTimeoutNanos);
|
||||
|
||||
/**
|
||||
* Pause the stream. This will block until the stream has been paused, an error occurs
|
||||
* or `timeoutNanoseconds` has been reached.
|
||||
*/
|
||||
virtual Result pause(int64_t timeoutNanoseconds = kDefaultTimeoutNanos);
|
||||
|
||||
/**
|
||||
* Flush the stream. This will block until the stream has been flushed, an error occurs
|
||||
* or `timeoutNanoseconds` has been reached.
|
||||
*/
|
||||
virtual Result flush(int64_t timeoutNanoseconds = kDefaultTimeoutNanos);
|
||||
|
||||
/**
|
||||
* Stop the stream. This will block until the stream has been stopped, an error occurs
|
||||
* or `timeoutNanoseconds` has been reached.
|
||||
*/
|
||||
virtual Result stop(int64_t timeoutNanoseconds = kDefaultTimeoutNanos);
|
||||
|
||||
/* Asynchronous requests.
|
||||
* Use waitForStateChange() if you need to wait for completion.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Start the stream asynchronously. Returns immediately (does not block). Equivalent to calling
|
||||
* `start(0)`.
|
||||
*/
|
||||
virtual Result requestStart() = 0;
|
||||
|
||||
/**
|
||||
* Pause the stream asynchronously. Returns immediately (does not block). Equivalent to calling
|
||||
* `pause(0)`.
|
||||
*/
|
||||
virtual Result requestPause() = 0;
|
||||
|
||||
/**
|
||||
* Flush the stream asynchronously. Returns immediately (does not block). Equivalent to calling
|
||||
* `flush(0)`.
|
||||
*/
|
||||
virtual Result requestFlush() = 0;
|
||||
|
||||
/**
|
||||
* Stop the stream asynchronously. Returns immediately (does not block). Equivalent to calling
|
||||
* `stop(0)`.
|
||||
*/
|
||||
virtual Result requestStop() = 0;
|
||||
|
||||
/**
|
||||
* Query the current state, eg. StreamState::Pausing
|
||||
*
|
||||
* @return state or a negative error.
|
||||
*/
|
||||
virtual StreamState getState() = 0;
|
||||
|
||||
/**
|
||||
* Wait until the stream's current state no longer matches the input state.
|
||||
* The input state is passed to avoid race conditions caused by the state
|
||||
* changing between calls.
|
||||
*
|
||||
* Note that generally applications do not need to call this. It is considered
|
||||
* an advanced technique and is mostly used for testing.
|
||||
*
|
||||
* <pre><code>
|
||||
* int64_t timeoutNanos = 500 * kNanosPerMillisecond; // arbitrary 1/2 second
|
||||
* StreamState currentState = stream->getState();
|
||||
* StreamState nextState = StreamState::Unknown;
|
||||
* while (result == Result::OK && currentState != StreamState::Paused) {
|
||||
* result = stream->waitForStateChange(
|
||||
* currentState, &nextState, timeoutNanos);
|
||||
* currentState = nextState;
|
||||
* }
|
||||
* </code></pre>
|
||||
*
|
||||
* If the state does not change within the timeout period then it will
|
||||
* return ErrorTimeout. This is true even if timeoutNanoseconds is zero.
|
||||
*
|
||||
* @param inputState The state we want to change away from.
|
||||
* @param nextState Pointer to a variable that will be set to the new state.
|
||||
* @param timeoutNanoseconds The maximum time to wait in nanoseconds.
|
||||
* @return Result::OK or a Result::Error.
|
||||
*/
|
||||
virtual Result waitForStateChange(StreamState inputState,
|
||||
StreamState *nextState,
|
||||
int64_t timeoutNanoseconds) = 0;
|
||||
|
||||
/**
|
||||
* This can be used to adjust the latency of the buffer by changing
|
||||
* the threshold where blocking will occur.
|
||||
* By combining this with getXRunCount(), the latency can be tuned
|
||||
* at run-time for each device.
|
||||
*
|
||||
* This cannot be set higher than getBufferCapacity().
|
||||
*
|
||||
* @param requestedFrames requested number of frames that can be filled without blocking
|
||||
* @return the resulting buffer size in frames (obtained using value()) or an error (obtained
|
||||
* using error())
|
||||
*/
|
||||
virtual ResultWithValue<int32_t> setBufferSizeInFrames(int32_t /* requestedFrames */) {
|
||||
return Result::ErrorUnimplemented;
|
||||
}
|
||||
|
||||
/**
|
||||
* An XRun is an Underrun or an Overrun.
|
||||
* During playing, an underrun will occur if the stream is not written in time
|
||||
* and the system runs out of valid data.
|
||||
* During recording, an overrun will occur if the stream is not read in time
|
||||
* and there is no place to put the incoming data so it is discarded.
|
||||
*
|
||||
* An underrun or overrun can cause an audible "pop" or "glitch".
|
||||
*
|
||||
* @return a result which is either Result::OK with the xRun count as the value, or a
|
||||
* Result::Error* code
|
||||
*/
|
||||
virtual ResultWithValue<int32_t> getXRunCount() {
|
||||
return ResultWithValue<int32_t>(Result::ErrorUnimplemented);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if XRun counts are supported on the stream
|
||||
*/
|
||||
virtual bool isXRunCountSupported() const = 0;
|
||||
|
||||
/**
|
||||
* Query the number of frames that are read or written by the endpoint at one time.
|
||||
*
|
||||
* @return burst size
|
||||
*/
|
||||
int32_t getFramesPerBurst() const {
|
||||
return mFramesPerBurst;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of bytes in each audio frame. This is calculated using the channel count
|
||||
* and the sample format. For example, a 2 channel floating point stream will have
|
||||
* 2 * 4 = 8 bytes per frame.
|
||||
*
|
||||
* @return number of bytes in each audio frame.
|
||||
*/
|
||||
int32_t getBytesPerFrame() const { return mChannelCount * getBytesPerSample(); }
|
||||
|
||||
/**
|
||||
* Get the number of bytes per sample. This is calculated using the sample format. For example,
|
||||
* a stream using 16-bit integer samples will have 2 bytes per sample.
|
||||
*
|
||||
* @return the number of bytes per sample.
|
||||
*/
|
||||
int32_t getBytesPerSample() const;
|
||||
|
||||
/**
|
||||
* The number of audio frames written into the stream.
|
||||
* This monotonic counter will never get reset.
|
||||
*
|
||||
* @return the number of frames written so far
|
||||
*/
|
||||
virtual int64_t getFramesWritten();
|
||||
|
||||
/**
|
||||
* The number of audio frames read from the stream.
|
||||
* This monotonic counter will never get reset.
|
||||
*
|
||||
* @return the number of frames read so far
|
||||
*/
|
||||
virtual int64_t getFramesRead();
|
||||
|
||||
/**
|
||||
* Calculate the latency of a stream based on getTimestamp().
|
||||
*
|
||||
* Output latency is the time it takes for a given frame to travel from the
|
||||
* app to some type of digital-to-analog converter. If the DAC is external, for example
|
||||
* in a USB interface or a TV connected by HDMI, then there may be additional latency
|
||||
* that the Android device is unaware of.
|
||||
*
|
||||
* Input latency is the time it takes to a given frame to travel from an analog-to-digital
|
||||
* converter (ADC) to the app.
|
||||
*
|
||||
* Note that the latency of an OUTPUT stream will increase abruptly when you write data to it
|
||||
* and then decrease slowly over time as the data is consumed.
|
||||
*
|
||||
* The latency of an INPUT stream will decrease abruptly when you read data from it
|
||||
* and then increase slowly over time as more data arrives.
|
||||
*
|
||||
* The latency of an OUTPUT stream is generally higher than the INPUT latency
|
||||
* because an app generally tries to keep the OUTPUT buffer full and the INPUT buffer empty.
|
||||
*
|
||||
* Note that due to issues in Android before R, we recommend NOT calling
|
||||
* this method from a data callback. See this tech note for more details.
|
||||
* https://github.com/google/oboe/wiki/TechNote_ReleaseBuffer
|
||||
*
|
||||
* @return a ResultWithValue which has a result of Result::OK and a value containing the latency
|
||||
* in milliseconds, or a result of Result::Error*.
|
||||
*/
|
||||
virtual ResultWithValue<double> calculateLatencyMillis() {
|
||||
return ResultWithValue<double>(Result::ErrorUnimplemented);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the estimated time that the frame at `framePosition` entered or left the audio processing
|
||||
* pipeline.
|
||||
*
|
||||
* This can be used to coordinate events and interactions with the external environment, and to
|
||||
* estimate the latency of an audio stream. An example of usage can be found in the hello-oboe
|
||||
* sample (search for "calculateCurrentOutputLatencyMillis").
|
||||
*
|
||||
* The time is based on the implementation's best effort, using whatever knowledge is available
|
||||
* to the system, but cannot account for any delay unknown to the implementation.
|
||||
*
|
||||
* Note that due to issues in Android before R, we recommend NOT calling
|
||||
* this method from a data callback. See this tech note for more details.
|
||||
* https://github.com/google/oboe/wiki/TechNote_ReleaseBuffer
|
||||
*
|
||||
* @deprecated since 1.0, use AudioStream::getTimestamp(clockid_t clockId) instead, which
|
||||
* returns ResultWithValue
|
||||
* @param clockId the type of clock to use e.g. CLOCK_MONOTONIC
|
||||
* @param framePosition the frame number to query
|
||||
* @param timeNanoseconds an output parameter which will contain the presentation timestamp
|
||||
*/
|
||||
virtual Result getTimestamp(clockid_t /* clockId */,
|
||||
int64_t* /* framePosition */,
|
||||
int64_t* /* timeNanoseconds */) {
|
||||
return Result::ErrorUnimplemented;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the estimated time that the frame at `framePosition` entered or left the audio processing
|
||||
* pipeline.
|
||||
*
|
||||
* This can be used to coordinate events and interactions with the external environment, and to
|
||||
* estimate the latency of an audio stream. An example of usage can be found in the hello-oboe
|
||||
* sample (search for "calculateCurrentOutputLatencyMillis").
|
||||
*
|
||||
* The time is based on the implementation's best effort, using whatever knowledge is available
|
||||
* to the system, but cannot account for any delay unknown to the implementation.
|
||||
*
|
||||
* Note that due to issues in Android before R, we recommend NOT calling
|
||||
* this method from a data callback. See this tech note for more details.
|
||||
* https://github.com/google/oboe/wiki/TechNote_ReleaseBuffer
|
||||
*
|
||||
* See
|
||||
* @param clockId the type of clock to use e.g. CLOCK_MONOTONIC
|
||||
* @return a FrameTimestamp containing the position and time at which a particular audio frame
|
||||
* entered or left the audio processing pipeline, or an error if the operation failed.
|
||||
*/
|
||||
virtual ResultWithValue<FrameTimestamp> getTimestamp(clockid_t /* clockId */);
|
||||
|
||||
// ============== I/O ===========================
|
||||
/**
|
||||
* Write data from the supplied buffer into the stream. This method will block until the write
|
||||
* is complete or it runs out of time.
|
||||
*
|
||||
* If `timeoutNanoseconds` is zero then this call will not wait.
|
||||
*
|
||||
* @param buffer The address of the first sample.
|
||||
* @param numFrames Number of frames to write. Only complete frames will be written.
|
||||
* @param timeoutNanoseconds Maximum number of nanoseconds to wait for completion.
|
||||
* @return a ResultWithValue which has a result of Result::OK and a value containing the number
|
||||
* of frames actually written, or result of Result::Error*.
|
||||
*/
|
||||
virtual ResultWithValue<int32_t> write(const void* /* buffer */,
|
||||
int32_t /* numFrames */,
|
||||
int64_t /* timeoutNanoseconds */ ) {
|
||||
return ResultWithValue<int32_t>(Result::ErrorUnimplemented);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data into the supplied buffer from the stream. This method will block until the read
|
||||
* is complete or it runs out of time.
|
||||
*
|
||||
* If `timeoutNanoseconds` is zero then this call will not wait.
|
||||
*
|
||||
* @param buffer The address of the first sample.
|
||||
* @param numFrames Number of frames to read. Only complete frames will be read.
|
||||
* @param timeoutNanoseconds Maximum number of nanoseconds to wait for completion.
|
||||
* @return a ResultWithValue which has a result of Result::OK and a value containing the number
|
||||
* of frames actually read, or result of Result::Error*.
|
||||
*/
|
||||
virtual ResultWithValue<int32_t> read(void* /* buffer */,
|
||||
int32_t /* numFrames */,
|
||||
int64_t /* timeoutNanoseconds */) {
|
||||
return ResultWithValue<int32_t>(Result::ErrorUnimplemented);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying audio API which the stream uses.
|
||||
*
|
||||
* @return the API that this stream uses.
|
||||
*/
|
||||
virtual AudioApi getAudioApi() const = 0;
|
||||
|
||||
/**
|
||||
* Returns true if the underlying audio API is AAudio.
|
||||
*
|
||||
* @return true if this stream is implemented using the AAudio API.
|
||||
*/
|
||||
bool usesAAudio() const {
|
||||
return getAudioApi() == AudioApi::AAudio;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only for debugging. Do not use in production.
|
||||
* If you need to call this method something is wrong.
|
||||
* If you think you need it for production then please let us know
|
||||
* so we can modify Oboe so that you don't need this.
|
||||
*
|
||||
* @return nullptr or a pointer to a stream from the system API
|
||||
*/
|
||||
virtual void *getUnderlyingStream() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update mFramesWritten.
|
||||
* For internal use only.
|
||||
*/
|
||||
virtual void updateFramesWritten() = 0;
|
||||
|
||||
/**
|
||||
* Update mFramesRead.
|
||||
* For internal use only.
|
||||
*/
|
||||
virtual void updateFramesRead() = 0;
|
||||
|
||||
/*
|
||||
* Swap old callback for new callback.
|
||||
* This not atomic.
|
||||
* This should only be used internally.
|
||||
* @param dataCallback
|
||||
* @return previous dataCallback
|
||||
*/
|
||||
AudioStreamDataCallback *swapDataCallback(AudioStreamDataCallback *dataCallback) {
|
||||
AudioStreamDataCallback *previousCallback = mDataCallback;
|
||||
mDataCallback = dataCallback;
|
||||
return previousCallback;
|
||||
}
|
||||
|
||||
/*
|
||||
* Swap old callback for new callback.
|
||||
* This not atomic.
|
||||
* This should only be used internally.
|
||||
* @param errorCallback
|
||||
* @return previous errorCallback
|
||||
*/
|
||||
AudioStreamErrorCallback *swapErrorCallback(AudioStreamErrorCallback *errorCallback) {
|
||||
AudioStreamErrorCallback *previousCallback = mErrorCallback;
|
||||
mErrorCallback = errorCallback;
|
||||
return previousCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return number of frames of data currently in the buffer
|
||||
*/
|
||||
ResultWithValue<int32_t> getAvailableFrames();
|
||||
|
||||
/**
|
||||
* Wait until the stream has a minimum amount of data available in its buffer.
|
||||
* This can be used with an EXCLUSIVE MMAP input stream to avoid reading data too close to
|
||||
* the DSP write position, which may cause glitches.
|
||||
*
|
||||
* Starting with Oboe 1.7.1, the numFrames will be clipped internally against the
|
||||
* BufferCapacity minus BurstSize. This is to prevent trying to wait for more frames
|
||||
* than could possibly be available. In this case, the return value may be less than numFrames.
|
||||
* Note that there may still be glitching if numFrames is too high.
|
||||
*
|
||||
* @param numFrames requested minimum frames available
|
||||
* @param timeoutNanoseconds
|
||||
* @return number of frames available, ErrorTimeout
|
||||
*/
|
||||
ResultWithValue<int32_t> waitForAvailableFrames(int32_t numFrames,
|
||||
int64_t timeoutNanoseconds);
|
||||
|
||||
/**
|
||||
* @return last result passed from an error callback
|
||||
*/
|
||||
virtual oboe::Result getLastErrorCallbackResult() const {
|
||||
return mErrorCallbackResult;
|
||||
}
|
||||
|
||||
|
||||
int32_t getDelayBeforeCloseMillis() const {
|
||||
return mDelayBeforeCloseMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the time to sleep before closing the internal 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
|
||||
* might access memory that has been freed, which could cause a crash.
|
||||
* This seems to be more likely in Android P or earlier.
|
||||
* But it can also occur in later versions. By sleeping, we give time for
|
||||
* the callback threads to finish.
|
||||
*
|
||||
* Note that this only has an effect when OboeGlobals::areWorkaroundsEnabled() is true.
|
||||
*
|
||||
* @param delayBeforeCloseMillis time to sleep before close.
|
||||
*/
|
||||
void setDelayBeforeCloseMillis(int32_t delayBeforeCloseMillis) {
|
||||
mDelayBeforeCloseMillis = delayBeforeCloseMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable a device specific CPU performance hint.
|
||||
* Runtime benchmarks such as the callback duration may be used to
|
||||
* speed up the CPU and improve real-time performance.
|
||||
*
|
||||
* Note that this feature is device specific and may not be implemented.
|
||||
* Also the benefits may vary by device.
|
||||
*
|
||||
* The flag will be checked in the Oboe data callback. If it transitions from false to true
|
||||
* then the PerformanceHint feature will be started.
|
||||
* This only needs to be called once.
|
||||
*
|
||||
* You may want to enable this if you have a dynamically changing workload
|
||||
* and you notice that you are getting underruns and glitches when your workload increases.
|
||||
* This might happen, for example, if you suddenly go from playing one note to
|
||||
* ten notes on a synthesizer.
|
||||
*
|
||||
* Try the CPU Load test in OboeTester if you would like to experiment with this interactively.
|
||||
*
|
||||
* On some devices, this may be implemented using the "ADPF" library.
|
||||
*
|
||||
* @param enabled true if you would like a performance boost
|
||||
*/
|
||||
void setPerformanceHintEnabled(bool enabled) {
|
||||
mPerformanceHintEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* This only tells you if the feature has been requested.
|
||||
* It does not tell you if the PerformanceHint feature is implemented or active on the device.
|
||||
*
|
||||
* @return true if set using setPerformanceHintEnabled().
|
||||
*/
|
||||
bool isPerformanceHintEnabled() {
|
||||
return mPerformanceHintEnabled;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
* This is used to detect more than one error callback from a stream.
|
||||
* These were bugs in some versions of Android that caused multiple error callbacks.
|
||||
* Internal bug b/63087953
|
||||
*
|
||||
* Calling this sets an atomic<bool> true and returns the previous value.
|
||||
*
|
||||
* @return false on first call, true on subsequent calls
|
||||
*/
|
||||
bool wasErrorCallbackCalled() {
|
||||
return mErrorCallbackCalled.exchange(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for a transition from one state to another.
|
||||
* @return OK if the endingState was observed, or ErrorUnexpectedState
|
||||
* if any state that was not the startingState or endingState was observed
|
||||
* or ErrorTimeout.
|
||||
*/
|
||||
virtual Result waitForStateTransition(StreamState startingState,
|
||||
StreamState endingState,
|
||||
int64_t timeoutNanoseconds);
|
||||
|
||||
/**
|
||||
* Override this to provide a default for when the application did not specify a callback.
|
||||
*
|
||||
* @param audioData
|
||||
* @param numFrames
|
||||
* @return result
|
||||
*/
|
||||
virtual DataCallbackResult onDefaultCallback(void* /* audioData */, int /* numFrames */) {
|
||||
return DataCallbackResult::Stop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override this to provide your own behaviour for the audio callback
|
||||
*
|
||||
* @param audioData container array which audio frames will be written into or read from
|
||||
* @param numFrames number of frames which were read/written
|
||||
* @return the result of the callback: stop or continue
|
||||
*
|
||||
*/
|
||||
DataCallbackResult fireDataCallback(void *audioData, int numFrames);
|
||||
|
||||
/**
|
||||
* @return true if callbacks may be called
|
||||
*/
|
||||
bool isDataCallbackEnabled() {
|
||||
return mDataCallbackEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* This can be set false internally to prevent callbacks
|
||||
* after DataCallbackResult::Stop has been returned.
|
||||
*/
|
||||
void setDataCallbackEnabled(bool enabled) {
|
||||
mDataCallbackEnabled = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* This should only be called as a stream is being opened.
|
||||
* Otherwise we might override setDelayBeforeCloseMillis().
|
||||
*/
|
||||
void calculateDefaultDelayBeforeCloseMillis();
|
||||
|
||||
/**
|
||||
* Try to avoid a race condition when closing.
|
||||
*/
|
||||
void sleepBeforeClose() {
|
||||
if (mDelayBeforeCloseMillis > 0) {
|
||||
usleep(mDelayBeforeCloseMillis * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This may be called internally at the beginning of a callback.
|
||||
*/
|
||||
virtual void beginPerformanceHintInCallback() {}
|
||||
|
||||
/**
|
||||
* This may be called internally at the end of a callback.
|
||||
* @param numFrames passed to the callback
|
||||
*/
|
||||
virtual void endPerformanceHintInCallback(int32_t numFrames) {}
|
||||
|
||||
/**
|
||||
* This will be called when the stream is closed just in case performance hints were enabled.
|
||||
*/
|
||||
virtual void closePerformanceHint() {}
|
||||
|
||||
/*
|
||||
* Set a weak_ptr to this stream from the shared_ptr so that we can
|
||||
* later use a shared_ptr in the error callback.
|
||||
*/
|
||||
void setWeakThis(std::shared_ptr<oboe::AudioStream> &sharedStream) {
|
||||
mWeakThis = sharedStream;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make a shared_ptr that will prevent this stream from being deleted.
|
||||
*/
|
||||
std::shared_ptr<oboe::AudioStream> lockWeakThis() {
|
||||
return mWeakThis.lock();
|
||||
}
|
||||
|
||||
std::weak_ptr<AudioStream> mWeakThis; // weak pointer to this object
|
||||
|
||||
/**
|
||||
* Number of frames which have been written into the stream
|
||||
*
|
||||
* This is signed integer to match the counters in AAudio.
|
||||
* At audio rates, the counter will overflow in about six million years.
|
||||
*/
|
||||
std::atomic<int64_t> mFramesWritten{};
|
||||
|
||||
/**
|
||||
* Number of frames which have been read from the stream.
|
||||
*
|
||||
* This is signed integer to match the counters in AAudio.
|
||||
* At audio rates, the counter will overflow in about six million years.
|
||||
*/
|
||||
std::atomic<int64_t> mFramesRead{};
|
||||
|
||||
std::mutex mLock; // for synchronizing start/stop/close
|
||||
|
||||
oboe::Result mErrorCallbackResult = oboe::Result::OK;
|
||||
|
||||
/**
|
||||
* Number of frames which will be copied to/from the audio device in a single read/write
|
||||
* operation
|
||||
*/
|
||||
int32_t mFramesPerBurst = kUnspecified;
|
||||
|
||||
// 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 kMinDelayBeforeCloseMillis = 10;
|
||||
int32_t mDelayBeforeCloseMillis = kMinDelayBeforeCloseMillis;
|
||||
|
||||
private:
|
||||
|
||||
// Log the scheduler if it changes.
|
||||
void checkScheduler();
|
||||
int mPreviousScheduler = -1;
|
||||
|
||||
std::atomic<bool> mDataCallbackEnabled{false};
|
||||
std::atomic<bool> mErrorCallbackCalled{false};
|
||||
|
||||
std::atomic<bool> mPerformanceHintEnabled{false}; // set only by app
|
||||
};
|
||||
|
||||
/**
|
||||
* This struct is a stateless functor which closes an AudioStream prior to its deletion.
|
||||
* This means it can be used to safely delete a smart pointer referring to an open stream.
|
||||
*/
|
||||
struct StreamDeleterFunctor {
|
||||
void operator()(AudioStream *audioStream) {
|
||||
if (audioStream) {
|
||||
audioStream->close();
|
||||
}
|
||||
delete audioStream;
|
||||
}
|
||||
};
|
||||
} // namespace oboe
|
||||
|
||||
#endif /* OBOE_STREAM_H_ */
|
||||
+343
@@ -0,0 +1,343 @@
|
||||
/*
|
||||
* 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_STREAM_BASE_H_
|
||||
#define OBOE_STREAM_BASE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "oboe/AudioStreamCallback.h"
|
||||
#include "oboe/Definitions.h"
|
||||
|
||||
namespace oboe {
|
||||
|
||||
/**
|
||||
* Base class containing parameters for audio streams and builders.
|
||||
**/
|
||||
class AudioStreamBase {
|
||||
|
||||
public:
|
||||
|
||||
AudioStreamBase() {}
|
||||
|
||||
virtual ~AudioStreamBase() = default;
|
||||
|
||||
// This class only contains primitives so we can use default constructor and copy methods.
|
||||
|
||||
/**
|
||||
* Default copy constructor
|
||||
*/
|
||||
AudioStreamBase(const AudioStreamBase&) = default;
|
||||
|
||||
/**
|
||||
* Default assignment operator
|
||||
*/
|
||||
AudioStreamBase& operator=(const AudioStreamBase&) = default;
|
||||
|
||||
/**
|
||||
* @return number of channels, for example 2 for stereo, or kUnspecified
|
||||
*/
|
||||
int32_t getChannelCount() const { return mChannelCount; }
|
||||
|
||||
/**
|
||||
* @return Direction::Input or Direction::Output
|
||||
*/
|
||||
Direction getDirection() const { return mDirection; }
|
||||
|
||||
/**
|
||||
* @return sample rate for the stream or kUnspecified
|
||||
*/
|
||||
int32_t getSampleRate() const { return mSampleRate; }
|
||||
|
||||
/**
|
||||
* @deprecated use `getFramesPerDataCallback` instead.
|
||||
*/
|
||||
int32_t getFramesPerCallback() const { return getFramesPerDataCallback(); }
|
||||
|
||||
/**
|
||||
* @return the number of frames in each data callback or kUnspecified.
|
||||
*/
|
||||
int32_t getFramesPerDataCallback() const { return mFramesPerCallback; }
|
||||
|
||||
/**
|
||||
* @return the audio sample format (e.g. Float or I16)
|
||||
*/
|
||||
AudioFormat getFormat() const { return mFormat; }
|
||||
|
||||
/**
|
||||
* Query the maximum number of frames that can be filled without blocking.
|
||||
* If the stream has been closed the last known value will be returned.
|
||||
*
|
||||
* @return buffer size
|
||||
*/
|
||||
virtual int32_t getBufferSizeInFrames() { return mBufferSizeInFrames; }
|
||||
|
||||
/**
|
||||
* @return capacityInFrames or kUnspecified
|
||||
*/
|
||||
virtual int32_t getBufferCapacityInFrames() const { return mBufferCapacityInFrames; }
|
||||
|
||||
/**
|
||||
* @return the sharing mode of the stream.
|
||||
*/
|
||||
SharingMode getSharingMode() const { return mSharingMode; }
|
||||
|
||||
/**
|
||||
* @return the performance mode of the stream.
|
||||
*/
|
||||
PerformanceMode getPerformanceMode() const { return mPerformanceMode; }
|
||||
|
||||
/**
|
||||
* @return the device ID of the stream.
|
||||
*/
|
||||
int32_t getDeviceId() const { return mDeviceId; }
|
||||
|
||||
/**
|
||||
* For internal use only.
|
||||
* @return the data callback object for this stream, if set.
|
||||
*/
|
||||
AudioStreamDataCallback *getDataCallback() const {
|
||||
return mDataCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* For internal use only.
|
||||
* @return the error callback object for this stream, if set.
|
||||
*/
|
||||
AudioStreamErrorCallback *getErrorCallback() const {
|
||||
return mErrorCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if a data callback was set for this stream
|
||||
*/
|
||||
bool isDataCallbackSpecified() const {
|
||||
return mDataCallback != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that if the app does not set an error callback then a
|
||||
* default one may be provided.
|
||||
* @return true if an error callback was set for this stream
|
||||
*/
|
||||
bool isErrorCallbackSpecified() const {
|
||||
return mErrorCallback != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the usage for this stream.
|
||||
*/
|
||||
Usage getUsage() const { return mUsage; }
|
||||
|
||||
/**
|
||||
* @return the stream's content type.
|
||||
*/
|
||||
ContentType getContentType() const { return mContentType; }
|
||||
|
||||
/**
|
||||
* @return the stream's input preset.
|
||||
*/
|
||||
InputPreset getInputPreset() const { return mInputPreset; }
|
||||
|
||||
/**
|
||||
* @return the stream's session ID allocation strategy (None or Allocate).
|
||||
*/
|
||||
SessionId getSessionId() const { return mSessionId; }
|
||||
|
||||
/**
|
||||
* @return whether the content of the stream is spatialized.
|
||||
*/
|
||||
bool isContentSpatialized() const { return mIsContentSpatialized; }
|
||||
|
||||
/**
|
||||
* @return the spatialization behavior for the stream.
|
||||
*/
|
||||
SpatializationBehavior getSpatializationBehavior() const { return mSpatializationBehavior; }
|
||||
|
||||
/**
|
||||
* Return the policy that determines whether the audio may or may not be captured
|
||||
* by other apps or the system.
|
||||
*
|
||||
* See AudioStreamBuilder_setAllowedCapturePolicy().
|
||||
*
|
||||
* Added in API level 29 to AAudio.
|
||||
*
|
||||
* @return the allowed capture policy, for example AllowedCapturePolicy::All
|
||||
*/
|
||||
AllowedCapturePolicy getAllowedCapturePolicy() const { return mAllowedCapturePolicy; }
|
||||
|
||||
/**
|
||||
* Return whether this input stream is marked as privacy sensitive.
|
||||
*
|
||||
* See AudioStreamBuilder_setPrivacySensitiveMode().
|
||||
*
|
||||
* Added in API level 30 to AAudio.
|
||||
*
|
||||
* @return PrivacySensitiveMode::Enabled if privacy sensitive,
|
||||
* PrivacySensitiveMode::Disabled if not privacy sensitive, and
|
||||
* PrivacySensitiveMode::Unspecified if API is not supported.
|
||||
*/
|
||||
PrivacySensitiveMode getPrivacySensitiveMode() const { return mPrivacySensitiveMode; }
|
||||
|
||||
/**
|
||||
* @return true if Oboe can convert channel counts to achieve optimal results.
|
||||
*/
|
||||
bool isChannelConversionAllowed() const {
|
||||
return mChannelConversionAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if Oboe can convert data formats to achieve optimal results.
|
||||
*/
|
||||
bool isFormatConversionAllowed() const {
|
||||
return mFormatConversionAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether and how Oboe can convert sample rates to achieve optimal results.
|
||||
*/
|
||||
SampleRateConversionQuality getSampleRateConversionQuality() const {
|
||||
return mSampleRateConversionQuality;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the stream's channel mask.
|
||||
*/
|
||||
ChannelMask getChannelMask() const {
|
||||
return mChannelMask;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return number of channels for the hardware, for example 2 for stereo, or kUnspecified.
|
||||
*/
|
||||
int32_t getHardwareChannelCount() const { return mHardwareChannelCount; }
|
||||
|
||||
/**
|
||||
* @return hardware sample rate for the stream or kUnspecified
|
||||
*/
|
||||
int32_t getHardwareSampleRate() const { return mHardwareSampleRate; }
|
||||
|
||||
/**
|
||||
* @return the audio sample format of the hardware (e.g. Float or I16)
|
||||
*/
|
||||
AudioFormat getHardwareFormat() const { return mHardwareFormat; }
|
||||
|
||||
protected:
|
||||
/** The callback which will be fired when new data is ready to be read/written. **/
|
||||
AudioStreamDataCallback *mDataCallback = nullptr;
|
||||
std::shared_ptr<AudioStreamDataCallback> mSharedDataCallback;
|
||||
|
||||
/** The callback which will be fired when an error or a disconnect occurs. **/
|
||||
AudioStreamErrorCallback *mErrorCallback = nullptr;
|
||||
std::shared_ptr<AudioStreamErrorCallback> mSharedErrorCallback;
|
||||
|
||||
/** Number of audio frames which will be requested in each callback */
|
||||
int32_t mFramesPerCallback = kUnspecified;
|
||||
/** Stream channel count */
|
||||
int32_t mChannelCount = kUnspecified;
|
||||
/** Stream sample rate */
|
||||
int32_t mSampleRate = kUnspecified;
|
||||
/** Stream audio device ID */
|
||||
int32_t mDeviceId = kUnspecified;
|
||||
/** Stream buffer capacity specified as a number of audio frames */
|
||||
int32_t mBufferCapacityInFrames = kUnspecified;
|
||||
/** Stream buffer size specified as a number of audio frames */
|
||||
int32_t mBufferSizeInFrames = kUnspecified;
|
||||
/** Stream channel mask. Only active on Android 32+ */
|
||||
ChannelMask mChannelMask = ChannelMask::Unspecified;
|
||||
|
||||
/** Stream sharing mode */
|
||||
SharingMode mSharingMode = SharingMode::Shared;
|
||||
/** Format of audio frames */
|
||||
AudioFormat mFormat = AudioFormat::Unspecified;
|
||||
/** Stream direction */
|
||||
Direction mDirection = Direction::Output;
|
||||
/** Stream performance mode */
|
||||
PerformanceMode mPerformanceMode = PerformanceMode::None;
|
||||
|
||||
/** Stream usage. Only active on Android 28+ */
|
||||
Usage mUsage = Usage::Media;
|
||||
/** Stream content type. Only active on Android 28+ */
|
||||
ContentType mContentType = ContentType::Music;
|
||||
/** Stream input preset. Only active on Android 28+
|
||||
* TODO InputPreset::Unspecified should be considered as a possible default alternative.
|
||||
*/
|
||||
InputPreset mInputPreset = InputPreset::VoiceRecognition;
|
||||
/** Stream session ID allocation strategy. Only active on Android 28+ */
|
||||
SessionId mSessionId = SessionId::None;
|
||||
|
||||
/** Allowed Capture Policy. Only active on Android 29+ */
|
||||
AllowedCapturePolicy mAllowedCapturePolicy = AllowedCapturePolicy::Unspecified;
|
||||
|
||||
/** Privacy Sensitive Mode. Only active on Android 30+ */
|
||||
PrivacySensitiveMode mPrivacySensitiveMode = PrivacySensitiveMode::Unspecified;
|
||||
|
||||
/** Control the name of the package creating the stream. Only active on Android 31+ */
|
||||
std::string mPackageName;
|
||||
/** Control the attribution tag of the context creating the stream. Only active on Android 31+ */
|
||||
std::string mAttributionTag;
|
||||
|
||||
/** Whether the content is already spatialized. Only used on Android 32+ */
|
||||
bool mIsContentSpatialized = false;
|
||||
/** Spatialization Behavior. Only active on Android 32+ */
|
||||
SpatializationBehavior mSpatializationBehavior = SpatializationBehavior::Unspecified;
|
||||
|
||||
/** Hardware channel count. Only specified on Android 34+ AAudio streams */
|
||||
int32_t mHardwareChannelCount = kUnspecified;
|
||||
/** Hardware sample rate. Only specified on Android 34+ AAudio streams */
|
||||
int32_t mHardwareSampleRate = kUnspecified;
|
||||
/** Hardware format. Only specified on Android 34+ AAudio streams */
|
||||
AudioFormat mHardwareFormat = AudioFormat::Unspecified;
|
||||
|
||||
// Control whether Oboe can convert channel counts to achieve optimal results.
|
||||
bool mChannelConversionAllowed = false;
|
||||
// Control whether Oboe can convert data formats to achieve optimal results.
|
||||
bool mFormatConversionAllowed = false;
|
||||
// Control whether and how Oboe can convert sample rates to achieve optimal results.
|
||||
SampleRateConversionQuality mSampleRateConversionQuality = SampleRateConversionQuality::None;
|
||||
|
||||
/** Validate stream parameters that might not be checked in lower layers */
|
||||
virtual Result isValidConfig() {
|
||||
switch (mFormat) {
|
||||
case AudioFormat::Unspecified:
|
||||
case AudioFormat::I16:
|
||||
case AudioFormat::Float:
|
||||
case AudioFormat::I24:
|
||||
case AudioFormat::I32:
|
||||
case AudioFormat::IEC61937:
|
||||
break;
|
||||
|
||||
default:
|
||||
return Result::ErrorInvalidFormat;
|
||||
}
|
||||
|
||||
switch (mSampleRateConversionQuality) {
|
||||
case SampleRateConversionQuality::None:
|
||||
case SampleRateConversionQuality::Fastest:
|
||||
case SampleRateConversionQuality::Low:
|
||||
case SampleRateConversionQuality::Medium:
|
||||
case SampleRateConversionQuality::High:
|
||||
case SampleRateConversionQuality::Best:
|
||||
return Result::OK;
|
||||
default:
|
||||
return Result::ErrorIllegalArgument;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace oboe
|
||||
|
||||
#endif /* OBOE_STREAM_BASE_H_ */
|
||||
+670
@@ -0,0 +1,670 @@
|
||||
/*
|
||||
* 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_STREAM_BUILDER_H_
|
||||
#define OBOE_STREAM_BUILDER_H_
|
||||
|
||||
#include "oboe/Definitions.h"
|
||||
#include "oboe/AudioStreamBase.h"
|
||||
#include "oboe/Utilities.h"
|
||||
#include "ResultWithValue.h"
|
||||
|
||||
namespace oboe {
|
||||
|
||||
// This depends on AudioStream, so we use forward declaration, it will close and delete the stream
|
||||
struct StreamDeleterFunctor;
|
||||
using ManagedStream = std::unique_ptr<AudioStream, StreamDeleterFunctor>;
|
||||
|
||||
/**
|
||||
* Factory class for an audio Stream.
|
||||
*/
|
||||
class AudioStreamBuilder : public AudioStreamBase {
|
||||
public:
|
||||
|
||||
AudioStreamBuilder() : AudioStreamBase() {}
|
||||
|
||||
AudioStreamBuilder(const AudioStreamBase &audioStreamBase): AudioStreamBase(audioStreamBase) {}
|
||||
|
||||
/**
|
||||
* Request a specific number of channels.
|
||||
*
|
||||
* Default is kUnspecified. If the value is unspecified then
|
||||
* the application should query for the actual value after the stream is opened.
|
||||
*
|
||||
* As the channel count here may be different from the corresponding channel count of
|
||||
* provided channel mask used in setChannelMask(). The last called will be respected
|
||||
* if this function and setChannelMask() are called.
|
||||
*/
|
||||
AudioStreamBuilder *setChannelCount(int channelCount) {
|
||||
mChannelCount = channelCount;
|
||||
mChannelMask = ChannelMask::Unspecified;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a specific channel mask.
|
||||
*
|
||||
* Default is kUnspecified. If the value is unspecified then the application
|
||||
* should query for the actual value after the stream is opened.
|
||||
*
|
||||
* As the corresponding channel count of provided channel mask here may be different
|
||||
* from the channel count used in setChannelCount(). The last called will be respected
|
||||
* if this function and setChannelCount() are called.
|
||||
*
|
||||
* As the setChannelMask API is available on Android 32+, this call will only take effects
|
||||
* on Android 32+.
|
||||
*/
|
||||
AudioStreamBuilder *setChannelMask(ChannelMask channelMask) {
|
||||
mChannelMask = channelMask;
|
||||
mChannelCount = getChannelCountFromChannelMask(channelMask);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the direction for a stream. The default is Direction::Output.
|
||||
*
|
||||
* @param direction Direction::Output or Direction::Input
|
||||
*/
|
||||
AudioStreamBuilder *setDirection(Direction direction) {
|
||||
mDirection = direction;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a specific sample rate in Hz.
|
||||
*
|
||||
* Default is kUnspecified. If the value is unspecified then
|
||||
* the application should query for the actual value after the stream is opened.
|
||||
*
|
||||
* Technically, this should be called the "frame rate" or "frames per second",
|
||||
* because it refers to the number of complete frames transferred per second.
|
||||
* But it is traditionally called "sample rate". Se we use that term.
|
||||
*
|
||||
*/
|
||||
AudioStreamBuilder *setSampleRate(int32_t sampleRate) {
|
||||
mSampleRate = sampleRate;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use `setFramesPerDataCallback` instead.
|
||||
*/
|
||||
AudioStreamBuilder *setFramesPerCallback(int framesPerCallback) {
|
||||
return setFramesPerDataCallback(framesPerCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a specific number of frames for the data callback.
|
||||
*
|
||||
* Default is kUnspecified. If the value is unspecified then
|
||||
* the actual number may vary from callback to callback.
|
||||
*
|
||||
* If an application can handle a varying number of frames then we recommend
|
||||
* leaving this unspecified. This allow the underlying API to optimize
|
||||
* the callbacks. But if your application is, for example, doing FFTs or other block
|
||||
* oriented operations, then call this function to get the sizes you need.
|
||||
*
|
||||
* Calling setFramesPerDataCallback() does not guarantee anything about timing.
|
||||
* This just collects the data into a the number of frames that your app requires.
|
||||
* We encourage leaving this unspecified in most cases.
|
||||
*
|
||||
* If this number is larger than the burst size, some bursts will not receive a callback.
|
||||
* If this number is smaller than the burst size, there may be multiple callbacks in a single
|
||||
* burst.
|
||||
*
|
||||
* @param framesPerCallback
|
||||
* @return pointer to the builder so calls can be chained
|
||||
*/
|
||||
AudioStreamBuilder *setFramesPerDataCallback(int framesPerCallback) {
|
||||
mFramesPerCallback = framesPerCallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a sample data format, for example Format::Float.
|
||||
*
|
||||
* Default is Format::Unspecified. If the value is unspecified then
|
||||
* the application should query for the actual value after the stream is opened.
|
||||
*/
|
||||
AudioStreamBuilder *setFormat(AudioFormat format) {
|
||||
mFormat = format;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the requested buffer capacity in frames.
|
||||
* BufferCapacityInFrames is the maximum possible BufferSizeInFrames.
|
||||
*
|
||||
* The final stream capacity may differ. For AAudio it should be at least this big.
|
||||
* For OpenSL ES, it could be smaller.
|
||||
*
|
||||
* Default is kUnspecified.
|
||||
*
|
||||
* @param bufferCapacityInFrames the desired buffer capacity in frames or kUnspecified
|
||||
* @return pointer to the builder so calls can be chained
|
||||
*/
|
||||
AudioStreamBuilder *setBufferCapacityInFrames(int32_t bufferCapacityInFrames) {
|
||||
mBufferCapacityInFrames = bufferCapacityInFrames;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the audio API which will be requested when opening the stream. No guarantees that this is
|
||||
* the API which will actually be used. Query the stream itself to find out the API which is
|
||||
* being used.
|
||||
*
|
||||
* If you do not specify the API, then AAudio will be used if isAAudioRecommended()
|
||||
* returns true. Otherwise OpenSL ES will be used.
|
||||
*
|
||||
* @return the requested audio API
|
||||
*/
|
||||
AudioApi getAudioApi() const { return mAudioApi; }
|
||||
|
||||
/**
|
||||
* If you leave this unspecified then Oboe will choose the best API
|
||||
* for the device and SDK version at runtime.
|
||||
*
|
||||
* This should almost always be left unspecified, except for debugging purposes.
|
||||
* Specifying AAudio will force Oboe to use AAudio on 8.0, which is extremely risky.
|
||||
* Specifying OpenSLES should mainly be used to test legacy performance/functionality.
|
||||
*
|
||||
* If the caller requests AAudio and it is supported then AAudio will be used.
|
||||
*
|
||||
* @param audioApi Must be AudioApi::Unspecified, AudioApi::OpenSLES or AudioApi::AAudio.
|
||||
* @return pointer to the builder so calls can be chained
|
||||
*/
|
||||
AudioStreamBuilder *setAudioApi(AudioApi audioApi) {
|
||||
mAudioApi = audioApi;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the AAudio API supported on this device?
|
||||
*
|
||||
* AAudio was introduced in the Oreo 8.0 release.
|
||||
*
|
||||
* @return true if supported
|
||||
*/
|
||||
static bool isAAudioSupported();
|
||||
|
||||
/**
|
||||
* Is the AAudio API recommended this device?
|
||||
*
|
||||
* AAudio may be supported but not recommended because of version specific issues.
|
||||
* AAudio is not recommended for Android 8.0 or earlier versions.
|
||||
*
|
||||
* @return true if recommended
|
||||
*/
|
||||
static bool isAAudioRecommended();
|
||||
|
||||
/**
|
||||
* Request a mode for sharing the device.
|
||||
* The requested sharing mode may not be available.
|
||||
* So the application should query for the actual mode after the stream is opened.
|
||||
*
|
||||
* @param sharingMode SharingMode::Shared or SharingMode::Exclusive
|
||||
* @return pointer to the builder so calls can be chained
|
||||
*/
|
||||
AudioStreamBuilder *setSharingMode(SharingMode sharingMode) {
|
||||
mSharingMode = sharingMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a performance level for the stream.
|
||||
* This will determine the latency, the power consumption, and the level of
|
||||
* protection from glitches.
|
||||
*
|
||||
* @param performanceMode for example, PerformanceMode::LowLatency
|
||||
* @return pointer to the builder so calls can be chained
|
||||
*/
|
||||
AudioStreamBuilder *setPerformanceMode(PerformanceMode performanceMode) {
|
||||
mPerformanceMode = performanceMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the intended use case for an output stream.
|
||||
*
|
||||
* The system will use this information to optimize the behavior of the stream.
|
||||
* This could, for example, affect how volume and focus is handled for the stream.
|
||||
* The usage is ignored for input streams.
|
||||
*
|
||||
* The default, if you do not call this function, is Usage::Media.
|
||||
*
|
||||
* Added in API level 28.
|
||||
*
|
||||
* @param usage the desired usage, eg. Usage::Game
|
||||
*/
|
||||
AudioStreamBuilder *setUsage(Usage usage) {
|
||||
mUsage = usage;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type of audio data that an output stream will carry.
|
||||
*
|
||||
* The system will use this information to optimize the behavior of the stream.
|
||||
* This could, for example, affect whether a stream is paused when a notification occurs.
|
||||
* The contentType is ignored for input streams.
|
||||
*
|
||||
* The default, if you do not call this function, is ContentType::Music.
|
||||
*
|
||||
* Added in API level 28.
|
||||
*
|
||||
* @param contentType the type of audio data, eg. ContentType::Speech
|
||||
*/
|
||||
AudioStreamBuilder *setContentType(ContentType contentType) {
|
||||
mContentType = contentType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the input (capture) preset for the stream.
|
||||
*
|
||||
* The system will use this information to optimize the behavior of the stream.
|
||||
* This could, for example, affect which microphones are used and how the
|
||||
* recorded data is processed.
|
||||
*
|
||||
* The default, if you do not call this function, is InputPreset::VoiceRecognition.
|
||||
* That is because VoiceRecognition is the preset with the lowest latency
|
||||
* on many platforms.
|
||||
*
|
||||
* Added in API level 28.
|
||||
*
|
||||
* @param inputPreset the desired configuration for recording
|
||||
*/
|
||||
AudioStreamBuilder *setInputPreset(InputPreset inputPreset) {
|
||||
mInputPreset = inputPreset;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Set the requested session ID.
|
||||
*
|
||||
* The session ID can be used to associate a stream with effects processors.
|
||||
* The effects are controlled using the Android AudioEffect Java API.
|
||||
*
|
||||
* The default, if you do not call this function, is SessionId::None.
|
||||
*
|
||||
* If set to SessionId::Allocate then a session ID will be allocated
|
||||
* when the stream is opened.
|
||||
*
|
||||
* The allocated session ID can be obtained by calling AudioStream::getSessionId()
|
||||
* and then used with this function when opening another stream.
|
||||
* This allows effects to be shared between streams.
|
||||
*
|
||||
* Session IDs from Oboe can be used the Android Java APIs and vice versa.
|
||||
* So a session ID from an Oboe stream can be passed to Java
|
||||
* and effects applied using the Java AudioEffect API.
|
||||
*
|
||||
* Allocated session IDs will always be positive and nonzero.
|
||||
*
|
||||
* Added in API level 28.
|
||||
*
|
||||
* @param sessionId an allocated sessionID or SessionId::Allocate
|
||||
*/
|
||||
AudioStreamBuilder *setSessionId(SessionId sessionId) {
|
||||
mSessionId = sessionId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request a stream to a specific audio input/output device given an audio device ID.
|
||||
*
|
||||
* In most cases, the primary device will be the appropriate device to use, and the
|
||||
* deviceId can be left kUnspecified.
|
||||
*
|
||||
* The ID could be obtained from the Java AudioManager.
|
||||
* AudioManager.getDevices() returns an array of AudioDeviceInfo,
|
||||
* which contains a getId() method. That ID can be passed to this function.
|
||||
*
|
||||
* It is possible that you may not get the device that you requested.
|
||||
* So if it is important to you, you should call
|
||||
* stream->getDeviceId() after the stream is opened to
|
||||
* verify the actual ID.
|
||||
*
|
||||
* Note that when using OpenSL ES, this will be ignored and the created
|
||||
* stream will have deviceId kUnspecified.
|
||||
*
|
||||
* @param deviceId device identifier or kUnspecified
|
||||
* @return pointer to the builder so calls can be chained
|
||||
*/
|
||||
AudioStreamBuilder *setDeviceId(int32_t deviceId) {
|
||||
mDeviceId = deviceId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether this stream audio may or may not be captured by other apps or the system.
|
||||
*
|
||||
* The default is AllowedCapturePolicy::Unspecified which maps to AAUDIO_ALLOW_CAPTURE_BY_ALL.
|
||||
*
|
||||
* Note that an application can also set its global policy, in which case the most restrictive
|
||||
* policy is always applied. See android.media.AudioAttributes.setAllowedCapturePolicy.
|
||||
*
|
||||
* Added in API level 29 to AAudio.
|
||||
*
|
||||
* @param inputPreset the desired level of opt-out from being captured.
|
||||
* @return pointer to the builder so calls can be chained
|
||||
*/
|
||||
AudioStreamBuilder *setAllowedCapturePolicy(AllowedCapturePolicy allowedCapturePolicy) {
|
||||
mAllowedCapturePolicy = allowedCapturePolicy;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Indicates whether this input stream must be marked as privacy sensitive or not.
|
||||
*
|
||||
* When PrivacySensitiveMode::Enabled, this input stream is privacy sensitive and any
|
||||
* concurrent capture is not permitted.
|
||||
*
|
||||
* This is off (PrivacySensitiveMode::Disabled) by default except when the input preset is
|
||||
* InputPreset::VoiceRecognition or InputPreset::Camcorder
|
||||
*
|
||||
* Always takes precedence over default from input preset when set explicitly.
|
||||
*
|
||||
* Only relevant if the stream direction is Direction::Input and AAudio is used.
|
||||
*
|
||||
* Added in API level 30 to AAudio.
|
||||
*
|
||||
* @param privacySensitive PrivacySensitiveMode::Enabled if capture from this stream must be
|
||||
* marked as privacy sensitive, PrivacySensitiveMode::Disabled if stream should be marked as
|
||||
* not sensitive.
|
||||
* @return pointer to the builder so calls can be chained
|
||||
*/
|
||||
AudioStreamBuilder *setPrivacySensitiveMode(PrivacySensitiveMode privacySensitiveMode) {
|
||||
mPrivacySensitiveMode = privacySensitiveMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies whether the audio data of this output stream has already been processed for spatialization.
|
||||
*
|
||||
* If the stream has been processed for spatialization, setting this to true will prevent issues such as
|
||||
* double-processing on platforms that will spatialize audio data.
|
||||
*
|
||||
* This is false by default.
|
||||
*
|
||||
* Available since API level 32.
|
||||
*
|
||||
* @param isContentSpatialized whether the content is already spatialized
|
||||
* @return pointer to the builder so calls can be chained
|
||||
*/
|
||||
AudioStreamBuilder *setIsContentSpatialized(bool isContentSpatialized) {
|
||||
mIsContentSpatialized = isContentSpatialized;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the behavior affecting whether spatialization will be used.
|
||||
*
|
||||
* The AAudio system will use this information to select whether the stream will go through a
|
||||
* spatializer effect or not when the effect is supported and enabled.
|
||||
*
|
||||
* This is SpatializationBehavior::Never by default.
|
||||
*
|
||||
* Available since API level 32.
|
||||
*
|
||||
* @param spatializationBehavior the desired spatialization behavior
|
||||
* @return pointer to the builder so calls can be chained
|
||||
*/
|
||||
AudioStreamBuilder *setSpatializationBehavior(SpatializationBehavior spatializationBehavior) {
|
||||
mSpatializationBehavior = spatializationBehavior;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies an object to handle data related callbacks from the underlying API.
|
||||
*
|
||||
* <strong>Important: See AudioStreamCallback for restrictions on what may be called
|
||||
* from the callback methods.</strong>
|
||||
*
|
||||
* We pass a shared_ptr so that the sharedDataCallback object cannot be deleted
|
||||
* before the stream is deleted.
|
||||
*
|
||||
* @param sharedDataCallback
|
||||
* @return pointer to the builder so calls can be chained
|
||||
*/
|
||||
AudioStreamBuilder *setDataCallback(std::shared_ptr<AudioStreamDataCallback> sharedDataCallback) {
|
||||
// Use this raw pointer in the rest of the code to retain backwards compatibility.
|
||||
mDataCallback = sharedDataCallback.get();
|
||||
// Hold a shared_ptr to protect the raw pointer for the lifetime of the stream.
|
||||
mSharedDataCallback = sharedDataCallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass a raw pointer to a data callback. This is not recommended because the dataCallback
|
||||
* object might get deleted by the app while it is being used.
|
||||
*
|
||||
* @deprecated Call setDataCallback(std::shared_ptr<AudioStreamDataCallback>) instead.
|
||||
* @param dataCallback
|
||||
* @return pointer to the builder so calls can be chained
|
||||
*/
|
||||
AudioStreamBuilder *setDataCallback(AudioStreamDataCallback *dataCallback) {
|
||||
mDataCallback = dataCallback;
|
||||
mSharedDataCallback = nullptr;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies an object to handle error related callbacks from the underlying API.
|
||||
* This can occur when a stream is disconnected because a headset is plugged in or unplugged.
|
||||
* It can also occur if the audio service fails or if an exclusive stream is stolen by
|
||||
* another stream.
|
||||
*
|
||||
* <strong>Important: See AudioStreamCallback for restrictions on what may be called
|
||||
* from the callback methods.</strong>
|
||||
*
|
||||
* <strong>When an error callback occurs, the associated stream must be stopped and closed
|
||||
* in a separate thread.</strong>
|
||||
*
|
||||
* We pass a shared_ptr so that the errorCallback object cannot be deleted before the stream is deleted.
|
||||
* If the stream was created using a shared_ptr then the stream cannot be deleted before the
|
||||
* error callback has finished running.
|
||||
*
|
||||
* @param sharedErrorCallback
|
||||
* @return pointer to the builder so calls can be chained
|
||||
*/
|
||||
AudioStreamBuilder *setErrorCallback(std::shared_ptr<AudioStreamErrorCallback> sharedErrorCallback) {
|
||||
// Use this raw pointer in the rest of the code to retain backwards compatibility.
|
||||
mErrorCallback = sharedErrorCallback.get();
|
||||
// Hold a shared_ptr to protect the raw pointer for the lifetime of the stream.
|
||||
mSharedErrorCallback = sharedErrorCallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass a raw pointer to an error callback. This is not recommended because the errorCallback
|
||||
* object might get deleted by the app while it is being used.
|
||||
*
|
||||
* @deprecated Call setErrorCallback(std::shared_ptr<AudioStreamErrorCallback>) instead.
|
||||
* @param errorCallback
|
||||
* @return pointer to the builder so calls can be chained
|
||||
*/
|
||||
AudioStreamBuilder *setErrorCallback(AudioStreamErrorCallback *errorCallback) {
|
||||
mErrorCallback = errorCallback;
|
||||
mSharedErrorCallback = nullptr;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies an object to handle data or error related callbacks from the underlying API.
|
||||
*
|
||||
* This is the equivalent of calling both setDataCallback() and setErrorCallback().
|
||||
*
|
||||
* <strong>Important: See AudioStreamCallback for restrictions on what may be called
|
||||
* from the callback methods.</strong>
|
||||
*
|
||||
* @deprecated Call setDataCallback(std::shared_ptr<AudioStreamDataCallback>) and
|
||||
* setErrorCallback(std::shared_ptr<AudioStreamErrorCallback>) instead.
|
||||
* @param streamCallback
|
||||
* @return pointer to the builder so calls can be chained
|
||||
*/
|
||||
AudioStreamBuilder *setCallback(AudioStreamCallback *streamCallback) {
|
||||
// Use the same callback object for both, dual inheritance.
|
||||
mDataCallback = streamCallback;
|
||||
mErrorCallback = streamCallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true then Oboe might convert channel counts to achieve optimal results.
|
||||
* On some versions of Android for example, stereo streams could not use a FAST track.
|
||||
* So a mono stream might be used instead and duplicated to two channels.
|
||||
* On some devices, mono streams might be broken, so a stereo stream might be opened
|
||||
* and converted to mono.
|
||||
*
|
||||
* Default is false.
|
||||
*/
|
||||
AudioStreamBuilder *setChannelConversionAllowed(bool allowed) {
|
||||
mChannelConversionAllowed = allowed;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true then Oboe might convert data formats to achieve optimal results.
|
||||
* On some versions of Android, for example, a float stream could not get a
|
||||
* low latency data path. So an I16 stream might be opened and converted to float.
|
||||
*
|
||||
* Default is false.
|
||||
*/
|
||||
AudioStreamBuilder *setFormatConversionAllowed(bool allowed) {
|
||||
mFormatConversionAllowed = allowed;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the quality of the sample rate converter in Oboe.
|
||||
*
|
||||
* If set to None then Oboe will not do sample rate conversion. But the underlying APIs might
|
||||
* still do sample rate conversion if you specify a sample rate.
|
||||
* That can prevent you from getting a low latency stream.
|
||||
*
|
||||
* If you do the conversion in Oboe then you might still get a low latency stream.
|
||||
*
|
||||
* Default is SampleRateConversionQuality::None
|
||||
*/
|
||||
AudioStreamBuilder *setSampleRateConversionQuality(SampleRateConversionQuality quality) {
|
||||
mSampleRateConversionQuality = quality;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare the name of the package creating the stream.
|
||||
*
|
||||
* This is usually {@code Context#getPackageName()}.
|
||||
*
|
||||
* The default, if you do not call this function, is a random package in the calling uid.
|
||||
* The vast majority of apps have only one package per calling UID.
|
||||
* If an invalid package name is set, input streams may not be given permission to
|
||||
* record when started.
|
||||
*
|
||||
* The package name is usually the applicationId in your app's build.gradle file.
|
||||
*
|
||||
* Available since API level 31.
|
||||
*
|
||||
* @param packageName packageName of the calling app.
|
||||
*/
|
||||
AudioStreamBuilder *setPackageName(std::string packageName) {
|
||||
mPackageName = packageName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare the attribution tag of the context creating the stream.
|
||||
*
|
||||
* This is usually {@code Context#getAttributionTag()}.
|
||||
*
|
||||
* The default, if you do not call this function, is null.
|
||||
*
|
||||
* Available since API level 31.
|
||||
*
|
||||
* @param attributionTag attributionTag of the calling context.
|
||||
*/
|
||||
AudioStreamBuilder *setAttributionTag(std::string attributionTag) {
|
||||
mAttributionTag = attributionTag;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if AAudio will be used based on the current settings.
|
||||
*/
|
||||
bool willUseAAudio() const {
|
||||
return (mAudioApi == AudioApi::AAudio && isAAudioSupported())
|
||||
|| (mAudioApi == AudioApi::Unspecified && isAAudioRecommended());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and open a stream object based on the current settings.
|
||||
*
|
||||
* The caller owns the pointer to the AudioStream object
|
||||
* and must delete it when finished.
|
||||
*
|
||||
* @deprecated Use openStream(std::shared_ptr<oboe::AudioStream> &stream) instead.
|
||||
* @param stream pointer to a variable to receive the stream address
|
||||
* @return OBOE_OK if successful or a negative error code
|
||||
*/
|
||||
Result openStream(AudioStream **stream);
|
||||
|
||||
/**
|
||||
* Create and open a stream object based on the current settings.
|
||||
*
|
||||
* The caller shares the pointer to the AudioStream object.
|
||||
* The shared_ptr is used internally by Oboe to prevent the stream from being
|
||||
* deleted while it is being used by callbacks.
|
||||
*
|
||||
* @param stream reference to a shared_ptr to receive the stream address
|
||||
* @return OBOE_OK if successful or a negative error code
|
||||
*/
|
||||
Result openStream(std::shared_ptr<oboe::AudioStream> &stream);
|
||||
|
||||
/**
|
||||
* Create and open a ManagedStream object based on the current builder state.
|
||||
*
|
||||
* The caller must create a unique ptr, and pass by reference so it can be
|
||||
* modified to point to an opened stream. The caller owns the unique ptr,
|
||||
* and it will be automatically closed and deleted when going out of scope.
|
||||
*
|
||||
* @deprecated Use openStream(std::shared_ptr<oboe::AudioStream> &stream) instead.
|
||||
* @param stream Reference to the ManagedStream (uniqueptr) used to keep track of stream
|
||||
* @return OBOE_OK if successful or a negative error code.
|
||||
*/
|
||||
Result openManagedStream(ManagedStream &stream);
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* @param other
|
||||
* @return true if channels, format and sample rate match
|
||||
*/
|
||||
bool isCompatible(AudioStreamBase &other);
|
||||
|
||||
/**
|
||||
* Create an AudioStream object. The AudioStream must be opened before use.
|
||||
*
|
||||
* The caller owns the pointer.
|
||||
*
|
||||
* @return pointer to an AudioStream object or nullptr.
|
||||
*/
|
||||
oboe::AudioStream *build();
|
||||
|
||||
AudioApi mAudioApi = AudioApi::Unspecified;
|
||||
};
|
||||
|
||||
} // namespace oboe
|
||||
|
||||
#endif /* OBOE_STREAM_BUILDER_H_ */
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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_STREAM_CALLBACK_H
|
||||
#define OBOE_STREAM_CALLBACK_H
|
||||
|
||||
#include "oboe/Definitions.h"
|
||||
|
||||
namespace oboe {
|
||||
|
||||
class AudioStream;
|
||||
|
||||
/**
|
||||
* AudioStreamDataCallback defines a callback interface for
|
||||
* moving data to/from an audio stream using `onAudioReady`
|
||||
* 2) being alerted when a stream has an error using `onError*` methods
|
||||
*
|
||||
* It is used with AudioStreamBuilder::setDataCallback().
|
||||
*/
|
||||
|
||||
class AudioStreamDataCallback {
|
||||
public:
|
||||
virtual ~AudioStreamDataCallback() = default;
|
||||
|
||||
/**
|
||||
* A buffer is ready for processing.
|
||||
*
|
||||
* For an output stream, this function should render and write numFrames of data
|
||||
* in the stream's current data format to the audioData buffer.
|
||||
*
|
||||
* For an input stream, this function should read and process numFrames of data
|
||||
* from the audioData buffer.
|
||||
*
|
||||
* The audio data is passed through the buffer. So do NOT call read() or
|
||||
* write() on the stream that is making the callback.
|
||||
*
|
||||
* Note that numFrames can vary unless AudioStreamBuilder::setFramesPerCallback()
|
||||
* is called.
|
||||
*
|
||||
* Also note that this callback function should be considered a "real-time" function.
|
||||
* It must not do anything that could cause an unbounded delay because that can cause the
|
||||
* audio to glitch or pop.
|
||||
*
|
||||
* These are things the function should NOT do:
|
||||
* <ul>
|
||||
* <li>allocate memory using, for example, malloc() or new</li>
|
||||
* <li>any file operations such as opening, closing, reading or writing</li>
|
||||
* <li>any network operations such as streaming</li>
|
||||
* <li>use any mutexes or other synchronization primitives</li>
|
||||
* <li>sleep</li>
|
||||
* <li>oboeStream->stop(), pause(), flush() or close()</li>
|
||||
* <li>oboeStream->read()</li>
|
||||
* <li>oboeStream->write()</li>
|
||||
* </ul>
|
||||
*
|
||||
* The following are OK to call from the data callback:
|
||||
* <ul>
|
||||
* <li>oboeStream->get*()</li>
|
||||
* <li>oboe::convertToText()</li>
|
||||
* <li>oboeStream->setBufferSizeInFrames()</li>
|
||||
* </ul>
|
||||
*
|
||||
* If you need to move data, eg. MIDI commands, in or out of the callback function then
|
||||
* we recommend the use of non-blocking techniques such as an atomic FIFO.
|
||||
*
|
||||
* @param audioStream pointer to the associated stream
|
||||
* @param audioData buffer containing input data or a place to put output data
|
||||
* @param numFrames number of frames to be processed
|
||||
* @return DataCallbackResult::Continue or DataCallbackResult::Stop
|
||||
*/
|
||||
virtual DataCallbackResult onAudioReady(
|
||||
AudioStream *audioStream,
|
||||
void *audioData,
|
||||
int32_t numFrames) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* AudioStreamErrorCallback defines a callback interface for
|
||||
* being alerted when a stream has an error or is disconnected
|
||||
* using `onError*` methods.
|
||||
*
|
||||
* Note: This callback is only fired when an AudioStreamCallback is set.
|
||||
* If you use AudioStream::write() you have to evaluate the return codes of
|
||||
* AudioStream::write() to notice errors in the stream.
|
||||
*
|
||||
* It is used with AudioStreamBuilder::setErrorCallback().
|
||||
*/
|
||||
class AudioStreamErrorCallback {
|
||||
public:
|
||||
virtual ~AudioStreamErrorCallback() = default;
|
||||
|
||||
/**
|
||||
* This will be called before other `onError` methods when an error occurs on a stream,
|
||||
* such as when the stream is disconnected.
|
||||
*
|
||||
* It can be used to override and customize the normal error processing.
|
||||
* Use of this method is considered an advanced technique.
|
||||
* It might, for example, be used if an app want to use a high level lock when
|
||||
* closing and reopening a stream.
|
||||
* Or it might be used when an app want to signal a management thread that handles
|
||||
* all of the stream state.
|
||||
*
|
||||
* If this method returns false it indicates that the stream has *not been stopped and closed
|
||||
* by the application. In this case it will be stopped by Oboe in the following way:
|
||||
* onErrorBeforeClose() will be called, then the stream will be closed and onErrorAfterClose()
|
||||
* will be closed.
|
||||
*
|
||||
* If this method returns true it indicates that the stream *has* been stopped and closed
|
||||
* by the application and Oboe will not do this.
|
||||
* In that case, the app MUST stop() and close() the stream.
|
||||
*
|
||||
* This method will be called on a thread created by Oboe.
|
||||
*
|
||||
* @param audioStream pointer to the associated stream
|
||||
* @param error
|
||||
* @return true if the stream has been stopped and closed, false if not
|
||||
*/
|
||||
virtual bool onError(AudioStream* /* audioStream */, Result /* error */) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This will be called when an error occurs on a stream,
|
||||
* such as when the stream is disconnected,
|
||||
* and if onError() returns false (indicating that the error has not already been handled).
|
||||
*
|
||||
* Note that this will be called on a thread created by Oboe.
|
||||
*
|
||||
* The underlying stream will already be stopped by Oboe but not yet closed.
|
||||
* So the stream can be queried.
|
||||
*
|
||||
* Do not close or delete the stream in this method because it will be
|
||||
* closed after this method returns.
|
||||
*
|
||||
* @param audioStream pointer to the associated stream
|
||||
* @param error
|
||||
*/
|
||||
virtual void onErrorBeforeClose(AudioStream* /* audioStream */, Result /* error */) {}
|
||||
|
||||
/**
|
||||
* This will be called when an error occurs on a stream,
|
||||
* such as when the stream is disconnected,
|
||||
* and if onError() returns false (indicating that the error has not already been handled).
|
||||
*
|
||||
* The underlying AAudio or OpenSL ES stream will already be stopped AND closed by Oboe.
|
||||
* So the underlying stream cannot be referenced.
|
||||
* But you can still query most parameters.
|
||||
*
|
||||
* This callback could be used to reopen a new stream on another device.
|
||||
*
|
||||
* @param audioStream pointer to the associated stream
|
||||
* @param error
|
||||
*/
|
||||
virtual void onErrorAfterClose(AudioStream* /* audioStream */, Result /* error */) {}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* AudioStreamCallback defines a callback interface for:
|
||||
*
|
||||
* 1) moving data to/from an audio stream using `onAudioReady`
|
||||
* 2) being alerted when a stream has an error using `onError*` methods
|
||||
*
|
||||
* It is used with AudioStreamBuilder::setCallback().
|
||||
*
|
||||
* It combines the interfaces defined by AudioStreamDataCallback and AudioStreamErrorCallback.
|
||||
* This was the original callback object. We now recommend using the individual interfaces
|
||||
* and using setDataCallback() and setErrorCallback().
|
||||
*
|
||||
* @deprecated Use `AudioStreamDataCallback` and `AudioStreamErrorCallback` instead
|
||||
*/
|
||||
class AudioStreamCallback : public AudioStreamDataCallback,
|
||||
public AudioStreamErrorCallback {
|
||||
public:
|
||||
virtual ~AudioStreamCallback() = default;
|
||||
};
|
||||
|
||||
} // namespace oboe
|
||||
|
||||
#endif //OBOE_STREAM_CALLBACK_H
|
||||
+897
@@ -0,0 +1,897 @@
|
||||
/*
|
||||
* 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_DEFINITIONS_H
|
||||
#define OBOE_DEFINITIONS_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
// Oboe needs to be able to build on old NDKs so we use hard coded constants.
|
||||
// The correctness of these constants is verified in "aaudio/AAudioLoader.cpp".
|
||||
|
||||
namespace oboe {
|
||||
|
||||
/**
|
||||
* Represents any attribute, property or value which hasn't been specified.
|
||||
*/
|
||||
constexpr int32_t kUnspecified = 0;
|
||||
|
||||
// TODO: Investigate using std::chrono
|
||||
/**
|
||||
* The number of nanoseconds in a microsecond. 1,000.
|
||||
*/
|
||||
constexpr int64_t kNanosPerMicrosecond = 1000;
|
||||
|
||||
/**
|
||||
* The number of nanoseconds in a millisecond. 1,000,000.
|
||||
*/
|
||||
constexpr int64_t kNanosPerMillisecond = kNanosPerMicrosecond * 1000;
|
||||
|
||||
/**
|
||||
* The number of milliseconds in a second. 1,000.
|
||||
*/
|
||||
constexpr int64_t kMillisPerSecond = 1000;
|
||||
|
||||
/**
|
||||
* The number of nanoseconds in a second. 1,000,000,000.
|
||||
*/
|
||||
constexpr int64_t kNanosPerSecond = kNanosPerMillisecond * kMillisPerSecond;
|
||||
|
||||
/**
|
||||
* The state of the audio stream.
|
||||
*/
|
||||
enum class StreamState : int32_t { // aaudio_stream_state_t
|
||||
Uninitialized = 0, // AAUDIO_STREAM_STATE_UNINITIALIZED,
|
||||
Unknown = 1, // AAUDIO_STREAM_STATE_UNKNOWN,
|
||||
Open = 2, // AAUDIO_STREAM_STATE_OPEN,
|
||||
Starting = 3, // AAUDIO_STREAM_STATE_STARTING,
|
||||
Started = 4, // AAUDIO_STREAM_STATE_STARTED,
|
||||
Pausing = 5, // AAUDIO_STREAM_STATE_PAUSING,
|
||||
Paused = 6, // AAUDIO_STREAM_STATE_PAUSED,
|
||||
Flushing = 7, // AAUDIO_STREAM_STATE_FLUSHING,
|
||||
Flushed = 8, // AAUDIO_STREAM_STATE_FLUSHED,
|
||||
Stopping = 9, // AAUDIO_STREAM_STATE_STOPPING,
|
||||
Stopped = 10, // AAUDIO_STREAM_STATE_STOPPED,
|
||||
Closing = 11, // AAUDIO_STREAM_STATE_CLOSING,
|
||||
Closed = 12, // AAUDIO_STREAM_STATE_CLOSED,
|
||||
Disconnected = 13, // AAUDIO_STREAM_STATE_DISCONNECTED,
|
||||
};
|
||||
|
||||
/**
|
||||
* The direction of the stream.
|
||||
*/
|
||||
enum class Direction : int32_t { // aaudio_direction_t
|
||||
|
||||
/**
|
||||
* Used for playback.
|
||||
*/
|
||||
Output = 0, // AAUDIO_DIRECTION_OUTPUT,
|
||||
|
||||
/**
|
||||
* Used for recording.
|
||||
*/
|
||||
Input = 1, // AAUDIO_DIRECTION_INPUT,
|
||||
};
|
||||
|
||||
/**
|
||||
* The format of audio samples.
|
||||
*/
|
||||
enum class AudioFormat : int32_t { // aaudio_format_t
|
||||
/**
|
||||
* Invalid format.
|
||||
*/
|
||||
Invalid = -1, // AAUDIO_FORMAT_INVALID,
|
||||
|
||||
/**
|
||||
* Unspecified format. Format will be decided by Oboe.
|
||||
* When calling getHardwareFormat(), this will be returned if
|
||||
* the API is not supported.
|
||||
*/
|
||||
Unspecified = 0, // AAUDIO_FORMAT_UNSPECIFIED,
|
||||
|
||||
/**
|
||||
* Signed 16-bit integers.
|
||||
*/
|
||||
I16 = 1, // AAUDIO_FORMAT_PCM_I16,
|
||||
|
||||
/**
|
||||
* Single precision floating point.
|
||||
*
|
||||
* This is the recommended format for most applications.
|
||||
* But note that the use of Float may prevent the opening of
|
||||
* a low-latency input path on OpenSL ES or Legacy AAudio streams.
|
||||
*/
|
||||
Float = 2, // AAUDIO_FORMAT_PCM_FLOAT,
|
||||
|
||||
/**
|
||||
* Signed 24-bit integers, packed into 3 bytes.
|
||||
*
|
||||
* Note that the use of this format does not guarantee that
|
||||
* the full precision will be provided. The underlying device may
|
||||
* be using I16 format.
|
||||
*
|
||||
* Added in API 31 (S).
|
||||
*/
|
||||
I24 = 3, // AAUDIO_FORMAT_PCM_I24_PACKED
|
||||
|
||||
/**
|
||||
* Signed 32-bit integers.
|
||||
*
|
||||
* Note that the use of this format does not guarantee that
|
||||
* the full precision will be provided. The underlying device may
|
||||
* be using I16 format.
|
||||
*
|
||||
* Added in API 31 (S).
|
||||
*/
|
||||
I32 = 4, // AAUDIO_FORMAT_PCM_I32
|
||||
|
||||
/**
|
||||
* This format is used for compressed audio wrapped in IEC61937 for HDMI
|
||||
* or S/PDIF passthrough.
|
||||
*
|
||||
* Unlike PCM playback, the Android framework is not able to do format
|
||||
* conversion for IEC61937. In that case, when IEC61937 is requested, sampling
|
||||
* rate and channel count or channel mask must be specified. Otherwise, it may
|
||||
* fail when opening the stream. Apps are able to get the correct configuration
|
||||
* for the playback by calling AudioManager#getDevices(int).
|
||||
*
|
||||
* Available since API 34 (U).
|
||||
*/
|
||||
IEC61937 = 5, // AAUDIO_FORMAT_IEC61937
|
||||
};
|
||||
|
||||
/**
|
||||
* The result of an audio callback.
|
||||
*/
|
||||
enum class DataCallbackResult : int32_t { // aaudio_data_callback_result_t
|
||||
// Indicates to the caller that the callbacks should continue.
|
||||
Continue = 0, // AAUDIO_CALLBACK_RESULT_CONTINUE,
|
||||
|
||||
// Indicates to the caller that the callbacks should stop immediately.
|
||||
Stop = 1, // AAUDIO_CALLBACK_RESULT_STOP,
|
||||
};
|
||||
|
||||
/**
|
||||
* The result of an operation. All except the `OK` result indicates that an error occurred.
|
||||
* The `Result` can be converted into a human readable string using `convertToText`.
|
||||
*/
|
||||
enum class Result : int32_t { // aaudio_result_t
|
||||
OK = 0, // AAUDIO_OK
|
||||
ErrorBase = -900, // AAUDIO_ERROR_BASE,
|
||||
ErrorDisconnected = -899, // AAUDIO_ERROR_DISCONNECTED,
|
||||
ErrorIllegalArgument = -898, // AAUDIO_ERROR_ILLEGAL_ARGUMENT,
|
||||
ErrorInternal = -896, // AAUDIO_ERROR_INTERNAL,
|
||||
ErrorInvalidState = -895, // AAUDIO_ERROR_INVALID_STATE,
|
||||
ErrorInvalidHandle = -892, // AAUDIO_ERROR_INVALID_HANDLE,
|
||||
ErrorUnimplemented = -890, // AAUDIO_ERROR_UNIMPLEMENTED,
|
||||
ErrorUnavailable = -889, // AAUDIO_ERROR_UNAVAILABLE,
|
||||
ErrorNoFreeHandles = -888, // AAUDIO_ERROR_NO_FREE_HANDLES,
|
||||
ErrorNoMemory = -887, // AAUDIO_ERROR_NO_MEMORY,
|
||||
ErrorNull = -886, // AAUDIO_ERROR_NULL,
|
||||
ErrorTimeout = -885, // AAUDIO_ERROR_TIMEOUT,
|
||||
ErrorWouldBlock = -884, // AAUDIO_ERROR_WOULD_BLOCK,
|
||||
ErrorInvalidFormat = -883, // AAUDIO_ERROR_INVALID_FORMAT,
|
||||
ErrorOutOfRange = -882, // AAUDIO_ERROR_OUT_OF_RANGE,
|
||||
ErrorNoService = -881, // AAUDIO_ERROR_NO_SERVICE,
|
||||
ErrorInvalidRate = -880, // AAUDIO_ERROR_INVALID_RATE,
|
||||
// Reserved for future AAudio result types
|
||||
Reserved1,
|
||||
Reserved2,
|
||||
Reserved3,
|
||||
Reserved4,
|
||||
Reserved5,
|
||||
Reserved6,
|
||||
Reserved7,
|
||||
Reserved8,
|
||||
Reserved9,
|
||||
Reserved10,
|
||||
ErrorClosed = -869,
|
||||
};
|
||||
|
||||
/**
|
||||
* The sharing mode of the audio stream.
|
||||
*/
|
||||
enum class SharingMode : int32_t { // aaudio_sharing_mode_t
|
||||
|
||||
/**
|
||||
* This will be the only stream using a particular source or sink.
|
||||
* This mode will provide the lowest possible latency.
|
||||
* You should close EXCLUSIVE streams immediately when you are not using them.
|
||||
*
|
||||
* If you do not need the lowest possible latency then we recommend using Shared,
|
||||
* which is the default.
|
||||
*/
|
||||
Exclusive = 0, // AAUDIO_SHARING_MODE_EXCLUSIVE,
|
||||
|
||||
/**
|
||||
* Multiple applications can share the same device.
|
||||
* The data from output streams will be mixed by the audio service.
|
||||
* The data for input streams will be distributed by the audio service.
|
||||
*
|
||||
* This will have higher latency than the EXCLUSIVE mode.
|
||||
*/
|
||||
Shared = 1, // AAUDIO_SHARING_MODE_SHARED,
|
||||
};
|
||||
|
||||
/**
|
||||
* The performance mode of the audio stream.
|
||||
*/
|
||||
enum class PerformanceMode : int32_t { // aaudio_performance_mode_t
|
||||
|
||||
/**
|
||||
* No particular performance needs. Default.
|
||||
*/
|
||||
None = 10, // AAUDIO_PERFORMANCE_MODE_NONE,
|
||||
|
||||
/**
|
||||
* Extending battery life is most important.
|
||||
*/
|
||||
PowerSaving = 11, // AAUDIO_PERFORMANCE_MODE_POWER_SAVING,
|
||||
|
||||
/**
|
||||
* Reducing latency is most important.
|
||||
*/
|
||||
LowLatency = 12, // AAUDIO_PERFORMANCE_MODE_LOW_LATENCY
|
||||
};
|
||||
|
||||
/**
|
||||
* The underlying audio API used by the audio stream.
|
||||
*/
|
||||
enum class AudioApi : int32_t {
|
||||
/**
|
||||
* Try to use AAudio. If not available then use OpenSL ES.
|
||||
*/
|
||||
Unspecified = kUnspecified,
|
||||
|
||||
/**
|
||||
* Use OpenSL ES.
|
||||
* Note that OpenSL ES is deprecated in Android 13, API 30 and above.
|
||||
*/
|
||||
OpenSLES,
|
||||
|
||||
/**
|
||||
* Try to use AAudio. Fail if unavailable.
|
||||
* AAudio was first supported in Android 8, API 26 and above.
|
||||
* It is only recommended for API 27 and above.
|
||||
*/
|
||||
AAudio
|
||||
};
|
||||
|
||||
/**
|
||||
* Specifies the quality of the sample rate conversion performed by Oboe.
|
||||
* Higher quality will require more CPU load.
|
||||
* Higher quality conversion will probably be implemented using a sinc based resampler.
|
||||
*/
|
||||
enum class SampleRateConversionQuality : int32_t {
|
||||
/**
|
||||
* No conversion by Oboe. Underlying APIs may still do conversion.
|
||||
*/
|
||||
None,
|
||||
/**
|
||||
* Fastest conversion but may not sound great.
|
||||
* This may be implemented using bilinear interpolation.
|
||||
*/
|
||||
Fastest,
|
||||
/**
|
||||
* Low quality conversion with 8 taps.
|
||||
*/
|
||||
Low,
|
||||
/**
|
||||
* Medium quality conversion with 16 taps.
|
||||
*/
|
||||
Medium,
|
||||
/**
|
||||
* High quality conversion with 32 taps.
|
||||
*/
|
||||
High,
|
||||
/**
|
||||
* Highest quality conversion, which may be expensive in terms of CPU.
|
||||
*/
|
||||
Best,
|
||||
};
|
||||
|
||||
/**
|
||||
* The Usage attribute expresses *why* you are playing a sound, what is this sound used for.
|
||||
* This information is used by certain platforms or routing policies
|
||||
* to make more refined volume or routing decisions.
|
||||
*
|
||||
* Note that these match the equivalent values in AudioAttributes in the Android Java API.
|
||||
*
|
||||
* This attribute only has an effect on Android API 28+.
|
||||
*/
|
||||
enum class Usage : int32_t { // aaudio_usage_t
|
||||
/**
|
||||
* Use this for streaming media, music performance, video, podcasts, etcetera.
|
||||
*/
|
||||
Media = 1, // AAUDIO_USAGE_MEDIA
|
||||
|
||||
/**
|
||||
* Use this for voice over IP, telephony, etcetera.
|
||||
*/
|
||||
VoiceCommunication = 2, // AAUDIO_USAGE_VOICE_COMMUNICATION
|
||||
|
||||
/**
|
||||
* Use this for sounds associated with telephony such as busy tones, DTMF, etcetera.
|
||||
*/
|
||||
VoiceCommunicationSignalling = 3, // AAUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING
|
||||
|
||||
/**
|
||||
* Use this to demand the users attention.
|
||||
*/
|
||||
Alarm = 4, // AAUDIO_USAGE_ALARM
|
||||
|
||||
/**
|
||||
* Use this for notifying the user when a message has arrived or some
|
||||
* other background event has occured.
|
||||
*/
|
||||
Notification = 5, // AAUDIO_USAGE_NOTIFICATION
|
||||
|
||||
/**
|
||||
* Use this when the phone rings.
|
||||
*/
|
||||
NotificationRingtone = 6, // AAUDIO_USAGE_NOTIFICATION_RINGTONE
|
||||
|
||||
/**
|
||||
* Use this to attract the users attention when, for example, the battery is low.
|
||||
*/
|
||||
NotificationEvent = 10, // AAUDIO_USAGE_NOTIFICATION_EVENT
|
||||
|
||||
/**
|
||||
* Use this for screen readers, etcetera.
|
||||
*/
|
||||
AssistanceAccessibility = 11, // AAUDIO_USAGE_ASSISTANCE_ACCESSIBILITY
|
||||
|
||||
/**
|
||||
* Use this for driving or navigation directions.
|
||||
*/
|
||||
AssistanceNavigationGuidance = 12, // AAUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE
|
||||
|
||||
/**
|
||||
* Use this for user interface sounds, beeps, etcetera.
|
||||
*/
|
||||
AssistanceSonification = 13, // AAUDIO_USAGE_ASSISTANCE_SONIFICATION
|
||||
|
||||
/**
|
||||
* Use this for game audio and sound effects.
|
||||
*/
|
||||
Game = 14, // AAUDIO_USAGE_GAME
|
||||
|
||||
/**
|
||||
* Use this for audio responses to user queries, audio instructions or help utterances.
|
||||
*/
|
||||
Assistant = 16, // AAUDIO_USAGE_ASSISTANT
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The ContentType attribute describes *what* you are playing.
|
||||
* It expresses the general category of the content. This information is optional.
|
||||
* But in case it is known (for instance {@link Movie} for a
|
||||
* movie streaming service or {@link Speech} for
|
||||
* an audio book application) this information might be used by the audio framework to
|
||||
* enforce audio focus.
|
||||
*
|
||||
* Note that these match the equivalent values in AudioAttributes in the Android Java API.
|
||||
*
|
||||
* This attribute only has an effect on Android API 28+.
|
||||
*/
|
||||
enum ContentType : int32_t { // aaudio_content_type_t
|
||||
|
||||
/**
|
||||
* Use this for spoken voice, audio books, etcetera.
|
||||
*/
|
||||
Speech = 1, // AAUDIO_CONTENT_TYPE_SPEECH
|
||||
|
||||
/**
|
||||
* Use this for pre-recorded or live music.
|
||||
*/
|
||||
Music = 2, // AAUDIO_CONTENT_TYPE_MUSIC
|
||||
|
||||
/**
|
||||
* Use this for a movie or video soundtrack.
|
||||
*/
|
||||
Movie = 3, // AAUDIO_CONTENT_TYPE_MOVIE
|
||||
|
||||
/**
|
||||
* Use this for sound is designed to accompany a user action,
|
||||
* such as a click or beep sound made when the user presses a button.
|
||||
*/
|
||||
Sonification = 4, // AAUDIO_CONTENT_TYPE_SONIFICATION
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines the audio source.
|
||||
* An audio source defines both a default physical source of audio signal, and a recording
|
||||
* configuration.
|
||||
*
|
||||
* Note that these match the equivalent values in MediaRecorder.AudioSource in the Android Java API.
|
||||
*
|
||||
* This attribute only has an effect on Android API 28+.
|
||||
*/
|
||||
enum InputPreset : int32_t { // aaudio_input_preset_t
|
||||
/**
|
||||
* Use this preset when other presets do not apply.
|
||||
*/
|
||||
Generic = 1, // AAUDIO_INPUT_PRESET_GENERIC
|
||||
|
||||
/**
|
||||
* Use this preset when recording video.
|
||||
*/
|
||||
Camcorder = 5, // AAUDIO_INPUT_PRESET_CAMCORDER
|
||||
|
||||
/**
|
||||
* Use this preset when doing speech recognition.
|
||||
*/
|
||||
VoiceRecognition = 6, // AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
|
||||
|
||||
/**
|
||||
* Use this preset when doing telephony or voice messaging.
|
||||
*/
|
||||
VoiceCommunication = 7, // AAUDIO_INPUT_PRESET_VOICE_COMMUNICATION
|
||||
|
||||
/**
|
||||
* Use this preset to obtain an input with no effects.
|
||||
* Note that this input will not have automatic gain control
|
||||
* so the recorded volume may be very low.
|
||||
*/
|
||||
Unprocessed = 9, // AAUDIO_INPUT_PRESET_UNPROCESSED
|
||||
|
||||
/**
|
||||
* Use this preset for capturing audio meant to be processed in real time
|
||||
* and played back for live performance (e.g karaoke).
|
||||
* The capture path will minimize latency and coupling with playback path.
|
||||
*/
|
||||
VoicePerformance = 10, // AAUDIO_INPUT_PRESET_VOICE_PERFORMANCE
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* This attribute can be used to allocate a session ID to the audio stream.
|
||||
*
|
||||
* This attribute only has an effect on Android API 28+.
|
||||
*/
|
||||
enum SessionId {
|
||||
/**
|
||||
* Do not allocate a session ID.
|
||||
* Effects cannot be used with this stream.
|
||||
* Default.
|
||||
*/
|
||||
None = -1, // AAUDIO_SESSION_ID_NONE
|
||||
|
||||
/**
|
||||
* Allocate a session ID that can be used to attach and control
|
||||
* effects using the Java AudioEffects API.
|
||||
* Note that the use of this flag may result in higher latency.
|
||||
*
|
||||
* Note that this matches the value of AudioManager.AUDIO_SESSION_ID_GENERATE.
|
||||
*/
|
||||
Allocate = 0, // AAUDIO_SESSION_ID_ALLOCATE
|
||||
};
|
||||
|
||||
/**
|
||||
* The channel count of the audio stream. The underlying type is `int32_t`.
|
||||
* Use of this enum is convenient to avoid "magic"
|
||||
* numbers when specifying the channel count.
|
||||
*
|
||||
* For example, you can write
|
||||
* `builder.setChannelCount(ChannelCount::Stereo)`
|
||||
* rather than `builder.setChannelCount(2)`
|
||||
*
|
||||
*/
|
||||
enum ChannelCount : int32_t {
|
||||
/**
|
||||
* Audio channel count definition, use Mono or Stereo
|
||||
*/
|
||||
Unspecified = kUnspecified,
|
||||
|
||||
/**
|
||||
* Use this for mono audio
|
||||
*/
|
||||
Mono = 1,
|
||||
|
||||
/**
|
||||
* Use this for stereo audio.
|
||||
*/
|
||||
Stereo = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* The channel mask of the audio stream. The underlying type is `uint32_t`.
|
||||
* Use of this enum is convenient.
|
||||
*
|
||||
* ChannelMask::Unspecified means this is not specified.
|
||||
* The rest of the enums are channel position masks.
|
||||
* Use the combinations of the channel position masks defined below instead of
|
||||
* using those values directly.
|
||||
*
|
||||
* Channel masks are for input only, output only, or both input and output.
|
||||
* These channel masks are different than those defined in AudioFormat.java.
|
||||
* If an app gets a channel mask from Java API and wants to use it in Oboe,
|
||||
* conversion should be done by the app.
|
||||
*/
|
||||
enum class ChannelMask : uint32_t { // aaudio_channel_mask_t
|
||||
Unspecified = kUnspecified,
|
||||
FrontLeft = 1 << 0,
|
||||
FrontRight = 1 << 1,
|
||||
FrontCenter = 1 << 2,
|
||||
LowFrequency = 1 << 3,
|
||||
BackLeft = 1 << 4,
|
||||
BackRight = 1 << 5,
|
||||
FrontLeftOfCenter = 1 << 6,
|
||||
FrontRightOfCenter = 1 << 7,
|
||||
BackCenter = 1 << 8,
|
||||
SideLeft = 1 << 9,
|
||||
SideRight = 1 << 10,
|
||||
TopCenter = 1 << 11,
|
||||
TopFrontLeft = 1 << 12,
|
||||
TopFrontCenter = 1 << 13,
|
||||
TopFrontRight = 1 << 14,
|
||||
TopBackLeft = 1 << 15,
|
||||
TopBackCenter = 1 << 16,
|
||||
TopBackRight = 1 << 17,
|
||||
TopSideLeft = 1 << 18,
|
||||
TopSideRight = 1 << 19,
|
||||
BottomFrontLeft = 1 << 20,
|
||||
BottomFrontCenter = 1 << 21,
|
||||
BottomFrontRight = 1 << 22,
|
||||
LowFrequency2 = 1 << 23,
|
||||
FrontWideLeft = 1 << 24,
|
||||
FrontWideRight = 1 << 25,
|
||||
|
||||
/**
|
||||
* Supported for Input and Output
|
||||
*/
|
||||
Mono = FrontLeft,
|
||||
|
||||
/**
|
||||
* Supported for Input and Output
|
||||
*/
|
||||
Stereo = FrontLeft |
|
||||
FrontRight,
|
||||
|
||||
/**
|
||||
* Supported for only Output
|
||||
*/
|
||||
CM2Point1 = FrontLeft |
|
||||
FrontRight |
|
||||
LowFrequency,
|
||||
|
||||
/**
|
||||
* Supported for only Output
|
||||
*/
|
||||
Tri = FrontLeft |
|
||||
FrontRight |
|
||||
FrontCenter,
|
||||
|
||||
/**
|
||||
* Supported for only Output
|
||||
*/
|
||||
TriBack = FrontLeft |
|
||||
FrontRight |
|
||||
BackCenter,
|
||||
|
||||
/**
|
||||
* Supported for only Output
|
||||
*/
|
||||
CM3Point1 = FrontLeft |
|
||||
FrontRight |
|
||||
FrontCenter |
|
||||
LowFrequency,
|
||||
|
||||
/**
|
||||
* Supported for Input and Output
|
||||
*/
|
||||
CM2Point0Point2 = FrontLeft |
|
||||
FrontRight |
|
||||
TopSideLeft |
|
||||
TopSideRight,
|
||||
|
||||
/**
|
||||
* Supported for Input and Output
|
||||
*/
|
||||
CM2Point1Point2 = CM2Point0Point2 |
|
||||
LowFrequency,
|
||||
|
||||
/**
|
||||
* Supported for Input and Output
|
||||
*/
|
||||
CM3Point0Point2 = FrontLeft |
|
||||
FrontRight |
|
||||
FrontCenter |
|
||||
TopSideLeft |
|
||||
TopSideRight,
|
||||
|
||||
/**
|
||||
* Supported for Input and Output
|
||||
*/
|
||||
CM3Point1Point2 = CM3Point0Point2 |
|
||||
LowFrequency,
|
||||
|
||||
/**
|
||||
* Supported for only Output
|
||||
*/
|
||||
Quad = FrontLeft |
|
||||
FrontRight |
|
||||
BackLeft |
|
||||
BackRight,
|
||||
|
||||
/**
|
||||
* Supported for only Output
|
||||
*/
|
||||
QuadSide = FrontLeft |
|
||||
FrontRight |
|
||||
SideLeft |
|
||||
SideRight,
|
||||
|
||||
/**
|
||||
* Supported for only Output
|
||||
*/
|
||||
Surround = FrontLeft |
|
||||
FrontRight |
|
||||
FrontCenter |
|
||||
BackCenter,
|
||||
|
||||
/**
|
||||
* Supported for only Output
|
||||
*/
|
||||
Penta = Quad |
|
||||
FrontCenter,
|
||||
|
||||
/**
|
||||
* Supported for Input and Output. aka 5Point1Back
|
||||
*/
|
||||
CM5Point1 = FrontLeft |
|
||||
FrontRight |
|
||||
FrontCenter |
|
||||
LowFrequency |
|
||||
BackLeft |
|
||||
BackRight,
|
||||
|
||||
/**
|
||||
* Supported for only Output
|
||||
*/
|
||||
CM5Point1Side = FrontLeft |
|
||||
FrontRight |
|
||||
FrontCenter |
|
||||
LowFrequency |
|
||||
SideLeft |
|
||||
SideRight,
|
||||
|
||||
/**
|
||||
* Supported for only Output
|
||||
*/
|
||||
CM6Point1 = FrontLeft |
|
||||
FrontRight |
|
||||
FrontCenter |
|
||||
LowFrequency |
|
||||
BackLeft |
|
||||
BackRight |
|
||||
BackCenter,
|
||||
|
||||
/**
|
||||
* Supported for only Output
|
||||
*/
|
||||
CM7Point1 = CM5Point1 |
|
||||
SideLeft |
|
||||
SideRight,
|
||||
|
||||
/**
|
||||
* Supported for only Output
|
||||
*/
|
||||
CM5Point1Point2 = CM5Point1 |
|
||||
TopSideLeft |
|
||||
TopSideRight,
|
||||
|
||||
/**
|
||||
* Supported for only Output
|
||||
*/
|
||||
CM5Point1Point4 = CM5Point1 |
|
||||
TopFrontLeft |
|
||||
TopFrontRight |
|
||||
TopBackLeft |
|
||||
TopBackRight,
|
||||
|
||||
/**
|
||||
* Supported for only Output
|
||||
*/
|
||||
CM7Point1Point2 = CM7Point1 |
|
||||
TopSideLeft |
|
||||
TopSideRight,
|
||||
|
||||
/**
|
||||
* Supported for only Output
|
||||
*/
|
||||
CM7Point1Point4 = CM7Point1 |
|
||||
TopFrontLeft |
|
||||
TopFrontRight |
|
||||
TopBackLeft |
|
||||
TopBackRight,
|
||||
|
||||
/**
|
||||
* Supported for only Output
|
||||
*/
|
||||
CM9Point1Point4 = CM7Point1Point4 |
|
||||
FrontWideLeft |
|
||||
FrontWideRight,
|
||||
|
||||
/**
|
||||
* Supported for only Output
|
||||
*/
|
||||
CM9Point1Point6 = CM9Point1Point4 |
|
||||
TopSideLeft |
|
||||
TopSideRight,
|
||||
|
||||
/**
|
||||
* Supported for only Input
|
||||
*/
|
||||
FrontBack = FrontCenter |
|
||||
BackCenter,
|
||||
};
|
||||
|
||||
/**
|
||||
* The spatialization behavior of the audio stream.
|
||||
*/
|
||||
enum class SpatializationBehavior : int32_t {
|
||||
|
||||
/**
|
||||
* Constant indicating that the spatialization behavior is not specified.
|
||||
*/
|
||||
Unspecified = kUnspecified,
|
||||
|
||||
/**
|
||||
* Constant indicating the audio content associated with these attributes will follow the
|
||||
* default platform behavior with regards to which content will be spatialized or not.
|
||||
*/
|
||||
Auto = 1,
|
||||
|
||||
/**
|
||||
* Constant indicating the audio content associated with these attributes should never
|
||||
* be spatialized.
|
||||
*/
|
||||
Never = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* The PrivacySensitiveMode attribute determines whether an input stream can be shared
|
||||
* with another privileged app, for example the Assistant.
|
||||
*
|
||||
* This allows to override the default behavior tied to the audio source (e.g
|
||||
* InputPreset::VoiceCommunication is private by default but InputPreset::Unprocessed is not).
|
||||
*/
|
||||
enum class PrivacySensitiveMode : int32_t {
|
||||
|
||||
/**
|
||||
* When not explicitly requested, set privacy sensitive mode according to input preset:
|
||||
* communication and camcorder captures are considered privacy sensitive by default.
|
||||
*/
|
||||
Unspecified = kUnspecified,
|
||||
|
||||
/**
|
||||
* Privacy sensitive mode disabled.
|
||||
*/
|
||||
Disabled = 1,
|
||||
|
||||
/**
|
||||
* Privacy sensitive mode enabled.
|
||||
*/
|
||||
Enabled = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* Specifies whether audio may or may not be captured by other apps or the system for an
|
||||
* output stream.
|
||||
*
|
||||
* Note that these match the equivalent values in AudioAttributes in the Android Java API.
|
||||
*
|
||||
* Added in API level 29 for AAudio.
|
||||
*/
|
||||
enum class AllowedCapturePolicy : int32_t {
|
||||
/**
|
||||
* When not explicitly requested, set privacy sensitive mode according to the Usage.
|
||||
* This should behave similarly to setting AllowedCapturePolicy::All.
|
||||
*/
|
||||
Unspecified = kUnspecified,
|
||||
/**
|
||||
* Indicates that the audio may be captured by any app.
|
||||
*
|
||||
* For privacy, the following Usages can not be recorded: VoiceCommunication*,
|
||||
* Notification*, Assistance* and Assistant.
|
||||
*
|
||||
* On Android Q, only Usage::Game and Usage::Media may be captured.
|
||||
*
|
||||
* See ALLOW_CAPTURE_BY_ALL in the AudioAttributes Java API.
|
||||
*/
|
||||
All = 1,
|
||||
/**
|
||||
* Indicates that the audio may only be captured by system apps.
|
||||
*
|
||||
* System apps can capture for many purposes like accessibility, user guidance...
|
||||
* but have strong restriction. See ALLOW_CAPTURE_BY_SYSTEM in the AudioAttributes Java API
|
||||
* for what the system apps can do with the capture audio.
|
||||
*/
|
||||
System = 2,
|
||||
/**
|
||||
* Indicates that the audio may not be recorded by any app, even if it is a system app.
|
||||
*
|
||||
* It is encouraged to use AllowedCapturePolicy::System instead of this value as system apps
|
||||
* provide significant and useful features for the user (eg. accessibility).
|
||||
* See ALLOW_CAPTURE_BY_NONE in the AudioAttributes Java API
|
||||
*/
|
||||
None = 3,
|
||||
};
|
||||
|
||||
/**
|
||||
* On API 16 to 26 OpenSL ES will be used. When using OpenSL ES the optimal values for sampleRate and
|
||||
* framesPerBurst are not known by the native code.
|
||||
* On API 17+ these values should be obtained from the AudioManager using this code:
|
||||
*
|
||||
* <pre><code>
|
||||
* // Note that this technique only works for built-in speakers and headphones.
|
||||
* AudioManager myAudioMgr = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
|
||||
* String sampleRateStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
|
||||
* int defaultSampleRate = Integer.parseInt(sampleRateStr);
|
||||
* String framesPerBurstStr = myAudioMgr.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
|
||||
* int defaultFramesPerBurst = Integer.parseInt(framesPerBurstStr);
|
||||
* </code></pre>
|
||||
*
|
||||
* It can then be passed down to Oboe through JNI.
|
||||
*
|
||||
* AAudio will get the optimal framesPerBurst from the HAL and will ignore this value.
|
||||
*/
|
||||
class DefaultStreamValues {
|
||||
|
||||
public:
|
||||
|
||||
/** The default sample rate to use when opening new audio streams */
|
||||
static int32_t SampleRate;
|
||||
/** The default frames per burst to use when opening new audio streams */
|
||||
static int32_t FramesPerBurst;
|
||||
/** The default channel count to use when opening new audio streams */
|
||||
static int32_t ChannelCount;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* The time at which the frame at `position` was presented
|
||||
*/
|
||||
struct FrameTimestamp {
|
||||
int64_t position; // in frames
|
||||
int64_t timestamp; // in nanoseconds
|
||||
};
|
||||
|
||||
class OboeGlobals {
|
||||
public:
|
||||
|
||||
static bool areWorkaroundsEnabled() {
|
||||
return mWorkaroundsEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable this when writing tests to reproduce bugs in AAudio or OpenSL ES
|
||||
* that have workarounds in Oboe.
|
||||
* @param enabled
|
||||
*/
|
||||
static void setWorkaroundsEnabled(bool enabled) {
|
||||
mWorkaroundsEnabled = enabled;
|
||||
}
|
||||
|
||||
private:
|
||||
static bool mWorkaroundsEnabled;
|
||||
};
|
||||
} // namespace oboe
|
||||
|
||||
#endif // OBOE_DEFINITIONS_H
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* 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/Definitions.h"
|
||||
|
||||
#include "oboe/FifoControllerBase.h"
|
||||
|
||||
namespace oboe {
|
||||
|
||||
class FifoBuffer {
|
||||
public:
|
||||
/**
|
||||
* Construct a `FifoBuffer`.
|
||||
*
|
||||
* @param bytesPerFrame amount of bytes for one frame
|
||||
* @param capacityInFrames the capacity of frames in fifo
|
||||
*/
|
||||
FifoBuffer(uint32_t bytesPerFrame, uint32_t capacityInFrames);
|
||||
|
||||
/**
|
||||
* Construct a `FifoBuffer`.
|
||||
* To be used if the storage allocation is done outside of FifoBuffer.
|
||||
*
|
||||
* @param bytesPerFrame amount of bytes for one frame
|
||||
* @param capacityInFrames capacity of frames in fifo
|
||||
* @param readCounterAddress address of read counter
|
||||
* @param writeCounterAddress address of write counter
|
||||
* @param dataStorageAddress address of storage
|
||||
*/
|
||||
FifoBuffer(uint32_t bytesPerFrame,
|
||||
uint32_t capacityInFrames,
|
||||
std::atomic<uint64_t> *readCounterAddress,
|
||||
std::atomic<uint64_t> *writeCounterAddress,
|
||||
uint8_t *dataStorageAddress);
|
||||
|
||||
~FifoBuffer();
|
||||
|
||||
/**
|
||||
* Convert a number of frames in bytes.
|
||||
*
|
||||
* @return number of bytes
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* Write framesToWrite or, if too enough, then write as many as the fifo are not empty.
|
||||
*
|
||||
* @param destination
|
||||
* @param framesToWrite number of frames requested
|
||||
* @return number of frames actually write
|
||||
*/
|
||||
int32_t write(const void *source, int32_t framesToWrite);
|
||||
|
||||
/**
|
||||
* Get the buffer capacity in frames.
|
||||
*
|
||||
* @return number of frames
|
||||
*/
|
||||
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);
|
||||
|
||||
/**
|
||||
* Get the number of frames in the fifo.
|
||||
*
|
||||
* @return number of frames actually in the buffer
|
||||
*/
|
||||
uint32_t getFullFramesAvailable() {
|
||||
return mFifo->getFullFramesAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the amount of bytes per frame.
|
||||
*
|
||||
* @return number of bytes per frame
|
||||
*/
|
||||
uint32_t getBytesPerFrame() const {
|
||||
return mBytesPerFrame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the position of read counter.
|
||||
*
|
||||
* @return position of read counter
|
||||
*/
|
||||
uint64_t getReadCounter() const {
|
||||
return mFifo->getReadCounter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the position of read counter.
|
||||
*
|
||||
* @param n position of read counter
|
||||
*/
|
||||
void setReadCounter(uint64_t n) {
|
||||
mFifo->setReadCounter(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the position of write counter.
|
||||
*
|
||||
* @return position of write counter
|
||||
*/
|
||||
uint64_t getWriteCounter() {
|
||||
return mFifo->getWriteCounter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the position of write counter.
|
||||
*
|
||||
* @param n position of write counter
|
||||
*/
|
||||
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
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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:
|
||||
/**
|
||||
* Construct a `FifoControllerBase`.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @return read index position
|
||||
*/
|
||||
uint32_t getReadIndex() const;
|
||||
|
||||
/**
|
||||
* Advance read index from a number of frames.
|
||||
* Equivalent of incrementReadCounter(numFrames).
|
||||
*
|
||||
* @param numFrames number of frames to advance the read index
|
||||
*/
|
||||
void advanceReadIndex(uint32_t numFrames);
|
||||
|
||||
/**
|
||||
* Get the number of frame that are not written yet.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @return index of the next frame to write
|
||||
*/
|
||||
uint32_t getWriteIndex() const;
|
||||
|
||||
/**
|
||||
* Advance write index from a number of frames.
|
||||
* Equivalent of incrementWriteCounter(numFrames).
|
||||
*
|
||||
* @param numFrames number of frames to advance the write index
|
||||
*/
|
||||
void advanceWriteIndex(uint32_t numFrames);
|
||||
|
||||
/**
|
||||
* Get the frame capacity of the fifo.
|
||||
*
|
||||
* @return frame capacity
|
||||
*/
|
||||
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
|
||||
+324
@@ -0,0 +1,324 @@
|
||||
/*
|
||||
* Copyright 2023 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_FULL_DUPLEX_STREAM_
|
||||
#define OBOE_FULL_DUPLEX_STREAM_
|
||||
|
||||
#include <cstdint>
|
||||
#include "oboe/Definitions.h"
|
||||
#include "oboe/AudioStream.h"
|
||||
#include "oboe/AudioStreamCallback.h"
|
||||
|
||||
namespace oboe {
|
||||
|
||||
/**
|
||||
* FullDuplexStream can be used to synchronize an input and output stream.
|
||||
*
|
||||
* For the builder of the output stream, call setDataCallback() with this object.
|
||||
*
|
||||
* When both streams are ready, onAudioReady() of the output stream will call onBothStreamsReady().
|
||||
* Callers must override onBothStreamsReady().
|
||||
*
|
||||
* To ensure best results, open an output stream before the input stream.
|
||||
* Call inputBuilder.setBufferCapacityInFrames(mOutputStream->getBufferCapacityInFrames() * 2).
|
||||
* Also, call inputBuilder.setSampleRate(mOutputStream->getSampleRate()).
|
||||
*
|
||||
* Callers must call setInputStream() and setOutputStream().
|
||||
* Call start() to start both streams and stop() to stop both streams.
|
||||
* Caller is responsible for closing both streams.
|
||||
*
|
||||
* Callers should handle error callbacks with setErrorCallback() for the output stream.
|
||||
* When an error callback occurs for the output stream, Oboe will stop and close the output stream.
|
||||
* The caller is responsible for stopping and closing the input stream.
|
||||
* The caller should also reopen and restart both streams when the error callback is ErrorDisconnected.
|
||||
* See the LiveEffect sample as an example of this.
|
||||
*
|
||||
*/
|
||||
class FullDuplexStream : public AudioStreamDataCallback {
|
||||
public:
|
||||
FullDuplexStream() {}
|
||||
virtual ~FullDuplexStream() = default;
|
||||
|
||||
/**
|
||||
* Sets the input stream. Calling this is mandatory.
|
||||
*
|
||||
* @param stream the output stream
|
||||
*/
|
||||
void setInputStream(AudioStream *stream) {
|
||||
mInputStream = stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the input stream
|
||||
*
|
||||
* @return the input stream
|
||||
*/
|
||||
AudioStream *getInputStream() {
|
||||
return mInputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the output stream. Calling this is mandatory.
|
||||
*
|
||||
* @param stream the output stream
|
||||
*/
|
||||
void setOutputStream(AudioStream *stream) {
|
||||
mOutputStream = stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the output stream
|
||||
*
|
||||
* @return the output stream
|
||||
*/
|
||||
AudioStream *getOutputStream() {
|
||||
return mOutputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to start both streams. Please call setInputStream() and setOutputStream() before
|
||||
* calling this function.
|
||||
*
|
||||
* @return result of the operation
|
||||
*/
|
||||
virtual Result start() {
|
||||
mCountCallbacksToDrain = kNumCallbacksToDrain;
|
||||
mCountInputBurstsCushion = mNumInputBurstsCushion;
|
||||
mCountCallbacksToDiscard = kNumCallbacksToDiscard;
|
||||
|
||||
// Determine maximum size that could possibly be called.
|
||||
int32_t bufferSize = getOutputStream()->getBufferCapacityInFrames()
|
||||
* getOutputStream()->getChannelCount();
|
||||
if (bufferSize > mBufferSize) {
|
||||
mInputBuffer = std::make_unique<float[]>(bufferSize);
|
||||
mBufferSize = bufferSize;
|
||||
}
|
||||
|
||||
oboe::Result result = getInputStream()->requestStart();
|
||||
if (result != oboe::Result::OK) {
|
||||
return result;
|
||||
}
|
||||
return getOutputStream()->requestStart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops both streams. Returns Result::OK if neither stream had an error during close.
|
||||
*
|
||||
* @return result of the operation
|
||||
*/
|
||||
virtual Result stop() {
|
||||
Result outputResult = Result::OK;
|
||||
Result inputResult = Result::OK;
|
||||
if (getOutputStream()) {
|
||||
outputResult = mOutputStream->requestStop();
|
||||
}
|
||||
if (getInputStream()) {
|
||||
inputResult = mInputStream->requestStop();
|
||||
}
|
||||
if (outputResult != Result::OK) {
|
||||
return outputResult;
|
||||
} else {
|
||||
return inputResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads input from the input stream. Callers should not call this directly as this is called
|
||||
* in onAudioReady().
|
||||
*
|
||||
* @param numFrames
|
||||
* @return result of the operation
|
||||
*/
|
||||
virtual ResultWithValue<int32_t> readInput(int32_t numFrames) {
|
||||
return getInputStream()->read(mInputBuffer.get(), numFrames, 0 /* timeout */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when data is available on both streams.
|
||||
* Caller should override this method.
|
||||
* numInputFrames and numOutputFrames may be zero.
|
||||
*
|
||||
* @param inputData buffer containing input data
|
||||
* @param numInputFrames number of input frames
|
||||
* @param outputData a place to put output data
|
||||
* @param numOutputFrames number of output frames
|
||||
* @return DataCallbackResult::Continue or DataCallbackResult::Stop
|
||||
*/
|
||||
virtual DataCallbackResult onBothStreamsReady(
|
||||
const void *inputData,
|
||||
int numInputFrames,
|
||||
void *outputData,
|
||||
int numOutputFrames
|
||||
) = 0;
|
||||
|
||||
/**
|
||||
* Called when the output stream is ready to process audio.
|
||||
* This in return calls onBothStreamsReady() when data is available on both streams.
|
||||
* Callers should call this function when the output stream is ready.
|
||||
* Callers must override onBothStreamsReady().
|
||||
*
|
||||
* @param audioStream pointer to the associated stream
|
||||
* @param audioData a place to put output data
|
||||
* @param numFrames number of frames to be processed
|
||||
* @return DataCallbackResult::Continue or DataCallbackResult::Stop
|
||||
*
|
||||
*/
|
||||
DataCallbackResult onAudioReady(
|
||||
AudioStream *audioStream,
|
||||
void *audioData,
|
||||
int numFrames) {
|
||||
DataCallbackResult callbackResult = DataCallbackResult::Continue;
|
||||
int32_t actualFramesRead = 0;
|
||||
|
||||
// Silence the output.
|
||||
int32_t numBytes = numFrames * getOutputStream()->getBytesPerFrame();
|
||||
memset(audioData, 0 /* value */, numBytes);
|
||||
|
||||
if (mCountCallbacksToDrain > 0) {
|
||||
// Drain the input.
|
||||
int32_t totalFramesRead = 0;
|
||||
do {
|
||||
ResultWithValue<int32_t> result = readInput(numFrames);
|
||||
if (!result) {
|
||||
// Ignore errors because input stream may not be started yet.
|
||||
break;
|
||||
}
|
||||
actualFramesRead = result.value();
|
||||
totalFramesRead += actualFramesRead;
|
||||
} while (actualFramesRead > 0);
|
||||
// Only counts if we actually got some data.
|
||||
if (totalFramesRead > 0) {
|
||||
mCountCallbacksToDrain--;
|
||||
}
|
||||
|
||||
} else if (mCountInputBurstsCushion > 0) {
|
||||
// Let the input fill up a bit so we are not so close to the write pointer.
|
||||
mCountInputBurstsCushion--;
|
||||
|
||||
} else if (mCountCallbacksToDiscard > 0) {
|
||||
mCountCallbacksToDiscard--;
|
||||
// Ignore. Allow the input to reach to equilibrium with the output.
|
||||
ResultWithValue<int32_t> resultAvailable = getInputStream()->getAvailableFrames();
|
||||
if (!resultAvailable) {
|
||||
callbackResult = DataCallbackResult::Stop;
|
||||
} else {
|
||||
int32_t framesAvailable = resultAvailable.value();
|
||||
if (framesAvailable >= mMinimumFramesBeforeRead) {
|
||||
ResultWithValue<int32_t> resultRead = readInput(numFrames);
|
||||
if (!resultRead) {
|
||||
callbackResult = DataCallbackResult::Stop;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int32_t framesRead = 0;
|
||||
ResultWithValue<int32_t> resultAvailable = getInputStream()->getAvailableFrames();
|
||||
if (!resultAvailable) {
|
||||
callbackResult = DataCallbackResult::Stop;
|
||||
} else {
|
||||
int32_t framesAvailable = resultAvailable.value();
|
||||
if (framesAvailable >= mMinimumFramesBeforeRead) {
|
||||
// Read data into input buffer.
|
||||
ResultWithValue<int32_t> resultRead = readInput(numFrames);
|
||||
if (!resultRead) {
|
||||
callbackResult = DataCallbackResult::Stop;
|
||||
} else {
|
||||
framesRead = resultRead.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (callbackResult == DataCallbackResult::Continue) {
|
||||
callbackResult = onBothStreamsReady(mInputBuffer.get(), framesRead,
|
||||
audioData, numFrames);
|
||||
}
|
||||
}
|
||||
|
||||
if (callbackResult == DataCallbackResult::Stop) {
|
||||
getInputStream()->requestStop();
|
||||
}
|
||||
|
||||
return callbackResult;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This is a cushion between the DSP and the application processor cursors to prevent collisions.
|
||||
* Typically 0 for latency measurements or 1 for glitch tests.
|
||||
*
|
||||
* @param numBursts number of bursts to leave in the input buffer as a cushion
|
||||
*/
|
||||
void setNumInputBurstsCushion(int32_t numBursts) {
|
||||
mNumInputBurstsCushion = numBursts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of bursts left in the input buffer as a cushion.
|
||||
*
|
||||
* @return number of bursts in the input buffer as a cushion
|
||||
*/
|
||||
int32_t getNumInputBurstsCushion() const {
|
||||
return mNumInputBurstsCushion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimum number of frames in the input stream buffer before calling readInput().
|
||||
*
|
||||
* @param numFrames number of bursts in the input buffer as a cushion
|
||||
*/
|
||||
void setMinimumFramesBeforeRead(int32_t numFrames) {
|
||||
mMinimumFramesBeforeRead = numFrames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the minimum number of frames in the input stream buffer before calling readInput().
|
||||
*
|
||||
* @return minimum number of frames before reading
|
||||
*/
|
||||
int32_t getMinimumFramesBeforeRead() const {
|
||||
return mMinimumFramesBeforeRead;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// TODO add getters and setters
|
||||
static constexpr int32_t kNumCallbacksToDrain = 20;
|
||||
static constexpr int32_t kNumCallbacksToDiscard = 30;
|
||||
|
||||
// let input fill back up, usually 0 or 1
|
||||
int32_t mNumInputBurstsCushion = 0;
|
||||
int32_t mMinimumFramesBeforeRead = 0;
|
||||
|
||||
// We want to reach a state where the input buffer is empty and
|
||||
// the output buffer is full.
|
||||
// These are used in order.
|
||||
// Drain several callback so that input is empty.
|
||||
int32_t mCountCallbacksToDrain = kNumCallbacksToDrain;
|
||||
// Let the input fill back up slightly so we don't run dry.
|
||||
int32_t mCountInputBurstsCushion = mNumInputBurstsCushion;
|
||||
// Discard some callbacks so the input and output reach equilibrium.
|
||||
int32_t mCountCallbacksToDiscard = kNumCallbacksToDiscard;
|
||||
|
||||
AudioStream *mInputStream = nullptr;
|
||||
AudioStream *mOutputStream = nullptr;
|
||||
|
||||
int32_t mBufferSize = 0;
|
||||
std::unique_ptr<float[]> mInputBuffer;
|
||||
};
|
||||
|
||||
} // namespace oboe
|
||||
|
||||
#endif //OBOE_FULL_DUPLEX_STREAM_
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef OBOE_LATENCY_TUNER_
|
||||
#define OBOE_LATENCY_TUNER_
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include "oboe/Definitions.h"
|
||||
#include "oboe/AudioStream.h"
|
||||
|
||||
namespace oboe {
|
||||
|
||||
/**
|
||||
* LatencyTuner can be used to dynamically tune the latency of an output stream.
|
||||
* It adjusts the stream's bufferSize by monitoring the number of underruns.
|
||||
*
|
||||
* This only affects the latency associated with the first level of buffering that is closest
|
||||
* to the application. It does not affect low latency in the HAL, or touch latency in the UI.
|
||||
*
|
||||
* Call tune() right before returning from your data callback function if using callbacks.
|
||||
* Call tune() right before calling write() if using blocking writes.
|
||||
*
|
||||
* If you want to see the ongoing results of this tuning process then call
|
||||
* stream->getBufferSize() periodically.
|
||||
*
|
||||
*/
|
||||
class LatencyTuner {
|
||||
public:
|
||||
|
||||
/**
|
||||
* Construct a new LatencyTuner object which will act on the given audio stream
|
||||
*
|
||||
* @param stream the stream who's latency will be tuned
|
||||
*/
|
||||
explicit LatencyTuner(AudioStream &stream);
|
||||
|
||||
/**
|
||||
* Construct a new LatencyTuner object which will act on the given audio stream.
|
||||
*
|
||||
* @param stream the stream who's latency will be tuned
|
||||
* @param the maximum buffer size which the tune() operation will set the buffer size to
|
||||
*/
|
||||
explicit LatencyTuner(AudioStream &stream, int32_t maximumBufferSize);
|
||||
|
||||
/**
|
||||
* Adjust the bufferSizeInFrames to optimize latency.
|
||||
* It will start with a low latency and then raise it if an underrun occurs.
|
||||
*
|
||||
* Latency tuning is only supported for AAudio.
|
||||
*
|
||||
* @return OK or negative error, ErrorUnimplemented for OpenSL ES
|
||||
*/
|
||||
Result tune();
|
||||
|
||||
/**
|
||||
* This may be called from another thread. Then tune() will call reset(),
|
||||
* which will lower the latency to the minimum and then allow it to rise back up
|
||||
* if there are glitches.
|
||||
*
|
||||
* This is typically called in response to a user decision to minimize latency. In other words,
|
||||
* call this from a button handler.
|
||||
*/
|
||||
void requestReset();
|
||||
|
||||
/**
|
||||
* @return true if the audio stream's buffer size is at the maximum value. If no maximum value
|
||||
* was specified when constructing the LatencyTuner then the value of
|
||||
* stream->getBufferCapacityInFrames is used
|
||||
*/
|
||||
bool isAtMaximumBufferSize();
|
||||
|
||||
/**
|
||||
* Set the minimum bufferSize in frames that is used when the tuner is reset.
|
||||
* You may wish to call requestReset() after calling this.
|
||||
* @param bufferSize
|
||||
*/
|
||||
void setMinimumBufferSize(int32_t bufferSize) {
|
||||
mMinimumBufferSize = bufferSize;
|
||||
}
|
||||
|
||||
int32_t getMinimumBufferSize() const {
|
||||
return mMinimumBufferSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the amount the bufferSize will be incremented while tuning.
|
||||
* By default, this will be one burst.
|
||||
*
|
||||
* Note that AAudio will quantize the buffer size to a multiple of the burstSize.
|
||||
* So the final buffer sizes may not be a multiple of this increment.
|
||||
*
|
||||
* @param sizeIncrement
|
||||
*/
|
||||
void setBufferSizeIncrement(int32_t sizeIncrement) {
|
||||
mBufferSizeIncrement = sizeIncrement;
|
||||
}
|
||||
|
||||
int32_t getBufferSizeIncrement() const {
|
||||
return mBufferSizeIncrement;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
/**
|
||||
* Drop the latency down to the minimum and then let it rise back up.
|
||||
* This is useful if a glitch caused the latency to increase and it hasn't gone back down.
|
||||
*
|
||||
* This should only be called in the same thread as tune().
|
||||
*/
|
||||
void reset();
|
||||
|
||||
enum class State {
|
||||
Idle,
|
||||
Active,
|
||||
AtMax,
|
||||
Unsupported
|
||||
} ;
|
||||
|
||||
// arbitrary number of calls to wait before bumping up the latency
|
||||
static constexpr int32_t kIdleCount = 8;
|
||||
static constexpr int32_t kDefaultNumBursts = 2;
|
||||
|
||||
AudioStream &mStream;
|
||||
State mState = State::Idle;
|
||||
int32_t mMaxBufferSize = 0;
|
||||
int32_t mPreviousXRuns = 0;
|
||||
int32_t mIdleCountDown = 0;
|
||||
int32_t mMinimumBufferSize;
|
||||
int32_t mBufferSizeIncrement;
|
||||
std::atomic<int32_t> mLatencyTriggerRequests{0}; // TODO user atomic requester from AAudio
|
||||
std::atomic<int32_t> mLatencyTriggerResponses{0};
|
||||
};
|
||||
|
||||
} // namespace oboe
|
||||
|
||||
#endif // OBOE_LATENCY_TUNER_
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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_OBOE_H
|
||||
#define OBOE_OBOE_H
|
||||
|
||||
/**
|
||||
* \mainpage API reference
|
||||
*
|
||||
* All documentation is found in the <a href="namespaceoboe.html">oboe namespace section</a>
|
||||
*
|
||||
*/
|
||||
|
||||
#include "oboe/Definitions.h"
|
||||
#include "oboe/ResultWithValue.h"
|
||||
#include "oboe/LatencyTuner.h"
|
||||
#include "oboe/AudioStream.h"
|
||||
#include "oboe/AudioStreamBase.h"
|
||||
#include "oboe/AudioStreamBuilder.h"
|
||||
#include "oboe/Utilities.h"
|
||||
#include "oboe/Version.h"
|
||||
#include "oboe/StabilizedCallback.h"
|
||||
#include "oboe/FifoBuffer.h"
|
||||
#include "oboe/OboeExtensions.h"
|
||||
#include "oboe/FullDuplexStream.h"
|
||||
|
||||
#endif //OBOE_OBOE_H
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2022 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_EXTENSIONS_
|
||||
#define OBOE_EXTENSIONS_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "oboe/Definitions.h"
|
||||
#include "oboe/AudioStream.h"
|
||||
|
||||
namespace oboe {
|
||||
|
||||
/**
|
||||
* The definitions below are only for testing.
|
||||
* They are not recommended for use in an application.
|
||||
* They may change or be removed at any time.
|
||||
*/
|
||||
class OboeExtensions {
|
||||
public:
|
||||
|
||||
/**
|
||||
* @returns true if the device supports AAudio MMAP
|
||||
*/
|
||||
static bool isMMapSupported();
|
||||
|
||||
/**
|
||||
* @returns true if the AAudio MMAP data path can be selected
|
||||
*/
|
||||
static bool isMMapEnabled();
|
||||
|
||||
/**
|
||||
* Controls whether the AAudio 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
|
||||
*/
|
||||
static int32_t setMMapEnabled(bool enabled);
|
||||
|
||||
/**
|
||||
* @param oboeStream
|
||||
* @return true if the AAudio MMAP data path is used on the stream
|
||||
*/
|
||||
static bool isMMapUsed(oboe::AudioStream *oboeStream);
|
||||
};
|
||||
|
||||
} // namespace oboe
|
||||
|
||||
#endif // OBOE_LATENCY_TUNER_
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef OBOE_RESULT_WITH_VALUE_H
|
||||
#define OBOE_RESULT_WITH_VALUE_H
|
||||
|
||||
#include "oboe/Definitions.h"
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
namespace oboe {
|
||||
|
||||
/**
|
||||
* A ResultWithValue can store both the result of an operation (either OK or an error) and a value.
|
||||
*
|
||||
* It has been designed for cases where the caller needs to know whether an operation succeeded and,
|
||||
* if it did, a value which was obtained during the operation.
|
||||
*
|
||||
* For example, when reading from a stream the caller needs to know the result of the read operation
|
||||
* and, if it was successful, how many frames were read. Note that ResultWithValue can be evaluated
|
||||
* as a boolean so it's simple to check whether the result is OK.
|
||||
*
|
||||
* <code>
|
||||
* ResultWithValue<int32_t> resultOfRead = myStream.read(&buffer, numFrames, timeoutNanoseconds);
|
||||
*
|
||||
* if (resultOfRead) {
|
||||
* LOGD("Frames read: %d", resultOfRead.value());
|
||||
* } else {
|
||||
* LOGD("Error reading from stream: %s", resultOfRead.error());
|
||||
* }
|
||||
* </code>
|
||||
*/
|
||||
template <typename T>
|
||||
class ResultWithValue {
|
||||
public:
|
||||
|
||||
/**
|
||||
* Construct a ResultWithValue containing an error result.
|
||||
*
|
||||
* @param error The error
|
||||
*/
|
||||
ResultWithValue(oboe::Result error)
|
||||
: mValue{}
|
||||
, mError(error) {}
|
||||
|
||||
/**
|
||||
* Construct a ResultWithValue containing an OK result and a value.
|
||||
*
|
||||
* @param value the value to store
|
||||
*/
|
||||
explicit ResultWithValue(T value)
|
||||
: mValue(value)
|
||||
, mError(oboe::Result::OK) {}
|
||||
|
||||
/**
|
||||
* Get the result.
|
||||
*
|
||||
* @return the result
|
||||
*/
|
||||
oboe::Result error() const {
|
||||
return mError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value
|
||||
* @return
|
||||
*/
|
||||
T value() const {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if OK
|
||||
*/
|
||||
explicit operator bool() const { return mError == oboe::Result::OK; }
|
||||
|
||||
/**
|
||||
* Quick way to check for an error.
|
||||
*
|
||||
* The caller could write something like this:
|
||||
* <code>
|
||||
* if (!result) { printf("Got error %s\n", convertToText(result.error())); }
|
||||
* </code>
|
||||
*
|
||||
* @return true if an error occurred
|
||||
*/
|
||||
bool operator !() const { return mError != oboe::Result::OK; }
|
||||
|
||||
/**
|
||||
* Implicitly convert to a Result. This enables easy comparison with Result values. Example:
|
||||
*
|
||||
* <code>
|
||||
* ResultWithValue result = openStream();
|
||||
* if (result == Result::ErrorNoMemory){ // tell user they're out of memory }
|
||||
* </code>
|
||||
*/
|
||||
operator Result() const {
|
||||
return mError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ResultWithValue from a number. If the number is positive the ResultWithValue will
|
||||
* have a result of Result::OK and the value will contain the number. If the number is negative
|
||||
* the result will be obtained from the negative number (numeric error codes can be found in
|
||||
* AAudio.h) and the value will be null.
|
||||
*
|
||||
*/
|
||||
static ResultWithValue<T> createBasedOnSign(T numericResult){
|
||||
|
||||
// Ensure that the type is either an integer or float
|
||||
static_assert(std::is_arithmetic<T>::value,
|
||||
"createBasedOnSign can only be called for numeric types (int or float)");
|
||||
|
||||
if (numericResult >= 0){
|
||||
return ResultWithValue<T>(numericResult);
|
||||
} else {
|
||||
return ResultWithValue<T>(static_cast<Result>(numericResult));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const T mValue;
|
||||
const oboe::Result mError;
|
||||
};
|
||||
|
||||
/**
|
||||
* If the result is `OK` then return the value, otherwise return a human-readable error message.
|
||||
*/
|
||||
template <typename T>
|
||||
std::ostream& operator<<(std::ostream &strm, const ResultWithValue<T> &result) {
|
||||
if (!result) {
|
||||
strm << convertToText(result.error());
|
||||
} else {
|
||||
strm << result.value();
|
||||
}
|
||||
return strm;
|
||||
}
|
||||
|
||||
} // namespace oboe
|
||||
|
||||
|
||||
#endif //OBOE_RESULT_WITH_VALUE_H
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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_STABILIZEDCALLBACK_H
|
||||
#define OBOE_STABILIZEDCALLBACK_H
|
||||
|
||||
#include <cstdint>
|
||||
#include "oboe/AudioStream.h"
|
||||
|
||||
namespace oboe {
|
||||
|
||||
class StabilizedCallback : public AudioStreamCallback {
|
||||
|
||||
public:
|
||||
explicit StabilizedCallback(AudioStreamCallback *callback);
|
||||
|
||||
DataCallbackResult
|
||||
onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) override;
|
||||
|
||||
void onErrorBeforeClose(AudioStream *oboeStream, Result error) override {
|
||||
return mCallback->onErrorBeforeClose(oboeStream, error);
|
||||
}
|
||||
|
||||
void onErrorAfterClose(AudioStream *oboeStream, Result error) override {
|
||||
|
||||
// Reset all fields now that the stream has been closed
|
||||
mFrameCount = 0;
|
||||
mEpochTimeNanos = 0;
|
||||
mOpsPerNano = 1;
|
||||
return mCallback->onErrorAfterClose(oboeStream, error);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
AudioStreamCallback *mCallback = nullptr;
|
||||
int64_t mFrameCount = 0;
|
||||
int64_t mEpochTimeNanos = 0;
|
||||
double mOpsPerNano = 1;
|
||||
|
||||
void generateLoad(int64_t durationNanos);
|
||||
};
|
||||
|
||||
/**
|
||||
* cpu_relax is an architecture specific method of telling the CPU that you don't want it to
|
||||
* do much work. asm volatile keeps the compiler from optimising these instructions out.
|
||||
*/
|
||||
#if defined(__i386__) || defined(__x86_64__)
|
||||
#define cpu_relax() asm volatile("rep; nop" ::: "memory");
|
||||
|
||||
#elif defined(__arm__) || defined(__mips__) || defined(__riscv)
|
||||
#define cpu_relax() asm volatile("":::"memory")
|
||||
|
||||
#elif defined(__aarch64__)
|
||||
#define cpu_relax() asm volatile("yield" ::: "memory")
|
||||
|
||||
#else
|
||||
#error "cpu_relax is not defined for this architecture"
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif //OBOE_STABILIZEDCALLBACK_H
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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_UTILITIES_H
|
||||
#define OBOE_UTILITIES_H
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <string>
|
||||
#include "oboe/Definitions.h"
|
||||
|
||||
namespace oboe {
|
||||
|
||||
/**
|
||||
* Convert an array of floats to an array of 16-bit integers.
|
||||
*
|
||||
* @param source the input array.
|
||||
* @param destination the output array.
|
||||
* @param numSamples the number of values to convert.
|
||||
*/
|
||||
void convertFloatToPcm16(const float *source, int16_t *destination, int32_t numSamples);
|
||||
|
||||
/**
|
||||
* Convert an array of 16-bit integers to an array of floats.
|
||||
*
|
||||
* @param source the input array.
|
||||
* @param destination the output array.
|
||||
* @param numSamples the number of values to convert.
|
||||
*/
|
||||
void convertPcm16ToFloat(const int16_t *source, float *destination, int32_t numSamples);
|
||||
|
||||
/**
|
||||
* @return the size of a sample of the given format in bytes or 0 if format is invalid
|
||||
*/
|
||||
int32_t convertFormatToSizeInBytes(AudioFormat format);
|
||||
|
||||
/**
|
||||
* The text is the ASCII symbol corresponding to the supplied Oboe enum value,
|
||||
* or an English message saying the value is unrecognized.
|
||||
* This is intended for developers to use when debugging.
|
||||
* It is not for displaying to users.
|
||||
*
|
||||
* @param input object to convert from. @see common/Utilities.cpp for concrete implementations
|
||||
* @return text representation of an Oboe enum value. There is no need to call free on this.
|
||||
*/
|
||||
template <typename FromType>
|
||||
const char * convertToText(FromType input);
|
||||
|
||||
/**
|
||||
* @param name
|
||||
* @return the value of a named system property in a string or empty string
|
||||
*/
|
||||
std::string getPropertyString(const char * name);
|
||||
|
||||
/**
|
||||
* @param name
|
||||
* @param defaultValue
|
||||
* @return integer value associated with a property or the default value
|
||||
*/
|
||||
int getPropertyInteger(const char * name, int defaultValue);
|
||||
|
||||
/**
|
||||
* Return the version of the SDK that is currently running.
|
||||
*
|
||||
* For example, on Android, this would return 27 for Oreo 8.1.
|
||||
* If the version number cannot be determined then this will return -1.
|
||||
*
|
||||
* @return version number or -1
|
||||
*/
|
||||
int getSdkVersion();
|
||||
|
||||
/**
|
||||
* Returns whether a device is on a pre-release SDK that is at least the specified codename
|
||||
* version.
|
||||
*
|
||||
* @param codename the code name to verify.
|
||||
* @return boolean of whether the device is on a pre-release SDK and is at least the specified
|
||||
* codename
|
||||
*/
|
||||
bool isAtLeastPreReleaseCodename(const std::string& codename);
|
||||
|
||||
int getChannelCountFromChannelMask(ChannelMask channelMask);
|
||||
|
||||
} // namespace oboe
|
||||
|
||||
#endif //OBOE_UTILITIES_H
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef OBOE_VERSIONINFO_H
|
||||
#define OBOE_VERSIONINFO_H
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* A note on use of preprocessor defines:
|
||||
*
|
||||
* This is one of the few times when it's suitable to use preprocessor defines rather than constexpr
|
||||
* Why? Because C++11 requires a lot of boilerplate code to convert integers into compile-time
|
||||
* string literals. The preprocessor, despite it's lack of type checking, is more suited to the task
|
||||
*
|
||||
* See: https://stackoverflow.com/questions/6713420/c-convert-integer-to-string-at-compile-time/26824971#26824971
|
||||
*
|
||||
*/
|
||||
|
||||
// Type: 8-bit unsigned int. Min value: 0 Max value: 255. See below for description.
|
||||
#define OBOE_VERSION_MAJOR 1
|
||||
|
||||
// Type: 8-bit unsigned int. Min value: 0 Max value: 255. See below for description.
|
||||
#define OBOE_VERSION_MINOR 8
|
||||
|
||||
// Type: 16-bit unsigned int. Min value: 0 Max value: 65535. See below for description.
|
||||
#define OBOE_VERSION_PATCH 0
|
||||
|
||||
#define OBOE_STRINGIFY(x) #x
|
||||
#define OBOE_TOSTRING(x) OBOE_STRINGIFY(x)
|
||||
|
||||
// Type: String literal. See below for description.
|
||||
#define OBOE_VERSION_TEXT \
|
||||
OBOE_TOSTRING(OBOE_VERSION_MAJOR) "." \
|
||||
OBOE_TOSTRING(OBOE_VERSION_MINOR) "." \
|
||||
OBOE_TOSTRING(OBOE_VERSION_PATCH)
|
||||
|
||||
// Type: 32-bit unsigned int. See below for description.
|
||||
#define OBOE_VERSION_NUMBER ((OBOE_VERSION_MAJOR << 24) | (OBOE_VERSION_MINOR << 16) | OBOE_VERSION_PATCH)
|
||||
|
||||
namespace oboe {
|
||||
|
||||
const char * getVersionText();
|
||||
|
||||
/**
|
||||
* Oboe versioning object
|
||||
*/
|
||||
struct Version {
|
||||
/**
|
||||
* This is incremented when we make breaking API changes. Based loosely on https://semver.org/.
|
||||
*/
|
||||
static constexpr uint8_t Major = OBOE_VERSION_MAJOR;
|
||||
|
||||
/**
|
||||
* This is incremented when we add backwards compatible functionality. Or set to zero when MAJOR is
|
||||
* incremented.
|
||||
*/
|
||||
static constexpr uint8_t Minor = OBOE_VERSION_MINOR;
|
||||
|
||||
/**
|
||||
* This is incremented when we make backwards compatible bug fixes. Or set to zero when MINOR is
|
||||
* incremented.
|
||||
*/
|
||||
static constexpr uint16_t Patch = OBOE_VERSION_PATCH;
|
||||
|
||||
/**
|
||||
* Version string in the form MAJOR.MINOR.PATCH.
|
||||
*/
|
||||
static constexpr const char * Text = OBOE_VERSION_TEXT;
|
||||
|
||||
/**
|
||||
* Integer representation of the current Oboe library version. This will always increase when the
|
||||
* version number changes so can be compared using integer comparison.
|
||||
*/
|
||||
static constexpr uint32_t Number = OBOE_VERSION_NUMBER;
|
||||
};
|
||||
|
||||
} // namespace oboe
|
||||
#endif //OBOE_VERSIONINFO_H
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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 "common/OboeDebug.h"
|
||||
#include "oboe/Oboe.h"
|
||||
#include "AAudioLoader.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 {
|
||||
private: // Because it is a singleton. Call getInstance() instead.
|
||||
AAudioExtensions() {
|
||||
int32_t policy = getIntegerProperty("aaudio.mmap_policy", 0);
|
||||
mMMapSupported = isPolicyEnabled(policy);
|
||||
|
||||
policy = getIntegerProperty("aaudio.mmap_exclusive_policy", 0);
|
||||
mMMapExclusiveSupported = isPolicyEnabled(policy);
|
||||
}
|
||||
|
||||
public:
|
||||
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;
|
||||
}
|
||||
|
||||
AAudioLoader *libLoader = AAudioLoader::getInstance();
|
||||
int openResult = libLoader->open();
|
||||
if (openResult != 0) {
|
||||
LOGD("%s() could not open " LIB_AAUDIO_NAME, __func__);
|
||||
return AAUDIO_ERROR_UNAVAILABLE;
|
||||
}
|
||||
|
||||
void *libHandle = AAudioLoader::getInstance()->getLibHandle();
|
||||
if (libHandle == nullptr) {
|
||||
LOGE("%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
|
||||
+506
@@ -0,0 +1,506 @@
|
||||
/*
|
||||
* 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/Utilities.h>
|
||||
#include "common/OboeDebug.h"
|
||||
#include "AAudioLoader.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");
|
||||
}
|
||||
|
||||
if (getSdkVersion() >= __ANDROID_API_Q__){
|
||||
builder_setAllowedCapturePolicy = load_V_PBI("AAudioStreamBuilder_setAllowedCapturePolicy");
|
||||
}
|
||||
|
||||
if (getSdkVersion() >= __ANDROID_API_R__){
|
||||
builder_setPrivacySensitive = load_V_PBO("AAudioStreamBuilder_setPrivacySensitive");
|
||||
}
|
||||
|
||||
if (getSdkVersion() >= __ANDROID_API_S__){
|
||||
builder_setPackageName = load_V_PBCPH("AAudioStreamBuilder_setPackageName");
|
||||
builder_setAttributionTag = load_V_PBCPH("AAudioStreamBuilder_setAttributionTag");
|
||||
}
|
||||
|
||||
if (getSdkVersion() >= __ANDROID_API_S_V2__) {
|
||||
builder_setChannelMask = load_V_PBU("AAudioStreamBuilder_setChannelMask");
|
||||
builder_setIsContentSpatialized = load_V_PBO("AAudioStreamBuilder_setIsContentSpatialized");
|
||||
builder_setSpatializationBehavior = load_V_PBI("AAudioStreamBuilder_setSpatializationBehavior");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
if (getSdkVersion() >= __ANDROID_API_R__) {
|
||||
stream_release = load_I_PS("AAudioStream_release");
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
if (getSdkVersion() >= __ANDROID_API_Q__){
|
||||
stream_getAllowedCapturePolicy = load_I_PS("AAudioStream_getAllowedCapturePolicy");
|
||||
}
|
||||
|
||||
if (getSdkVersion() >= __ANDROID_API_R__){
|
||||
stream_isPrivacySensitive = load_O_PS("AAudioStream_isPrivacySensitive");
|
||||
}
|
||||
|
||||
if (getSdkVersion() >= __ANDROID_API_S_V2__) {
|
||||
stream_getChannelMask = load_U_PS("AAudioStream_getChannelMask");
|
||||
stream_isContentSpatialized = load_O_PS("AAudioStream_isContentSpatialized");
|
||||
stream_getSpatializationBehavior = load_I_PS("AAudioStream_getSpatializationBehavior");
|
||||
}
|
||||
|
||||
if (getSdkVersion() >= __ANDROID_API_U__) {
|
||||
stream_getHardwareChannelCount = load_I_PS("AAudioStream_getHardwareChannelCount");
|
||||
stream_getHardwareSampleRate = load_I_PS("AAudioStream_getHardwareSampleRate");
|
||||
stream_getHardwareFormat = load_F_PS("AAudioStream_getHardwareFormat");
|
||||
}
|
||||
|
||||
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_PBCPH AAudioLoader::load_V_PBCPH(const char *functionName) {
|
||||
void *proc = dlsym(mLibHandle, functionName);
|
||||
AAudioLoader_check(proc, functionName);
|
||||
return reinterpret_cast<signature_V_PBCPH>(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_O_PS AAudioLoader::load_O_PS(const char *functionName) {
|
||||
void *proc = dlsym(mLibHandle, functionName);
|
||||
AAudioLoader_check(proc, functionName);
|
||||
return reinterpret_cast<signature_O_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);
|
||||
}
|
||||
|
||||
AAudioLoader::signature_V_PBU AAudioLoader::load_V_PBU(const char *functionName) {
|
||||
void *proc = dlsym(mLibHandle, functionName);
|
||||
AAudioLoader_check(proc, functionName);
|
||||
return reinterpret_cast<signature_V_PBU>(proc);
|
||||
}
|
||||
|
||||
AAudioLoader::signature_U_PS AAudioLoader::load_U_PS(const char *functionName) {
|
||||
void *proc = dlsym(mLibHandle, functionName);
|
||||
AAudioLoader_check(proc, functionName);
|
||||
return reinterpret_cast<signature_U_PS>(proc);
|
||||
}
|
||||
|
||||
AAudioLoader::signature_V_PBO AAudioLoader::load_V_PBO(const char *functionName) {
|
||||
void *proc = dlsym(mLibHandle, functionName);
|
||||
AAudioLoader_check(proc, functionName);
|
||||
return reinterpret_cast<signature_V_PBO>(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")
|
||||
|
||||
// Ensure that all AAudio primitive data types are uint32_t
|
||||
#define ASSERT_UINT32(type) static_assert(std::is_same<uint32_t, type>::value, \
|
||||
#type" must be uint32_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
|
||||
|
||||
// aaudio_allowed_capture_policy_t was added in NDK 20,
|
||||
// which is the first version to support Android Q (API 29).
|
||||
#if __NDK_MAJOR__ >= 20
|
||||
|
||||
ASSERT_INT32(aaudio_allowed_capture_policy_t);
|
||||
|
||||
static_assert((int32_t)AllowedCapturePolicy::Unspecified == AAUDIO_UNSPECIFIED, ERRMSG);
|
||||
static_assert((int32_t)AllowedCapturePolicy::All == AAUDIO_ALLOW_CAPTURE_BY_ALL, ERRMSG);
|
||||
static_assert((int32_t)AllowedCapturePolicy::System == AAUDIO_ALLOW_CAPTURE_BY_SYSTEM, ERRMSG);
|
||||
static_assert((int32_t)AllowedCapturePolicy::None == AAUDIO_ALLOW_CAPTURE_BY_NONE, ERRMSG);
|
||||
|
||||
#endif // __NDK_MAJOR__ >= 20
|
||||
|
||||
// The aaudio channel masks and spatialization behavior were added in NDK 24,
|
||||
// which is the first version to support Android SC_V2 (API 32).
|
||||
#if __NDK_MAJOR__ >= 24
|
||||
|
||||
ASSERT_UINT32(aaudio_channel_mask_t);
|
||||
|
||||
static_assert((uint32_t)ChannelMask::FrontLeft == AAUDIO_CHANNEL_FRONT_LEFT, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::FrontRight == AAUDIO_CHANNEL_FRONT_RIGHT, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::FrontCenter == AAUDIO_CHANNEL_FRONT_CENTER, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::LowFrequency == AAUDIO_CHANNEL_LOW_FREQUENCY, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::BackLeft == AAUDIO_CHANNEL_BACK_LEFT, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::BackRight == AAUDIO_CHANNEL_BACK_RIGHT, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::FrontLeftOfCenter == AAUDIO_CHANNEL_FRONT_LEFT_OF_CENTER, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::FrontRightOfCenter == AAUDIO_CHANNEL_FRONT_RIGHT_OF_CENTER, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::BackCenter == AAUDIO_CHANNEL_BACK_CENTER, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::SideLeft == AAUDIO_CHANNEL_SIDE_LEFT, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::SideRight == AAUDIO_CHANNEL_SIDE_RIGHT, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::TopCenter == AAUDIO_CHANNEL_TOP_CENTER, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::TopFrontLeft == AAUDIO_CHANNEL_TOP_FRONT_LEFT, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::TopFrontCenter == AAUDIO_CHANNEL_TOP_FRONT_CENTER, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::TopFrontRight == AAUDIO_CHANNEL_TOP_FRONT_RIGHT, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::TopBackLeft == AAUDIO_CHANNEL_TOP_BACK_LEFT, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::TopBackCenter == AAUDIO_CHANNEL_TOP_BACK_CENTER, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::TopBackRight == AAUDIO_CHANNEL_TOP_BACK_RIGHT, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::TopSideLeft == AAUDIO_CHANNEL_TOP_SIDE_LEFT, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::TopSideRight == AAUDIO_CHANNEL_TOP_SIDE_RIGHT, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::BottomFrontLeft == AAUDIO_CHANNEL_BOTTOM_FRONT_LEFT, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::BottomFrontCenter == AAUDIO_CHANNEL_BOTTOM_FRONT_CENTER, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::BottomFrontRight == AAUDIO_CHANNEL_BOTTOM_FRONT_RIGHT, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::LowFrequency2 == AAUDIO_CHANNEL_LOW_FREQUENCY_2, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::FrontWideLeft == AAUDIO_CHANNEL_FRONT_WIDE_LEFT, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::FrontWideRight == AAUDIO_CHANNEL_FRONT_WIDE_RIGHT, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::Mono == AAUDIO_CHANNEL_MONO, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::Stereo == AAUDIO_CHANNEL_STEREO, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::CM2Point1 == AAUDIO_CHANNEL_2POINT1, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::Tri == AAUDIO_CHANNEL_TRI, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::TriBack == AAUDIO_CHANNEL_TRI_BACK, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::CM3Point1 == AAUDIO_CHANNEL_3POINT1, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::CM2Point0Point2 == AAUDIO_CHANNEL_2POINT0POINT2, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::CM2Point1Point2 == AAUDIO_CHANNEL_2POINT1POINT2, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::CM3Point0Point2 == AAUDIO_CHANNEL_3POINT0POINT2, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::CM3Point1Point2 == AAUDIO_CHANNEL_3POINT1POINT2, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::Quad == AAUDIO_CHANNEL_QUAD, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::QuadSide == AAUDIO_CHANNEL_QUAD_SIDE, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::Surround == AAUDIO_CHANNEL_SURROUND, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::Penta == AAUDIO_CHANNEL_PENTA, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::CM5Point1 == AAUDIO_CHANNEL_5POINT1, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::CM5Point1Side == AAUDIO_CHANNEL_5POINT1_SIDE, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::CM6Point1 == AAUDIO_CHANNEL_6POINT1, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::CM7Point1 == AAUDIO_CHANNEL_7POINT1, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::CM5Point1Point2 == AAUDIO_CHANNEL_5POINT1POINT2, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::CM5Point1Point4 == AAUDIO_CHANNEL_5POINT1POINT4, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::CM7Point1Point2 == AAUDIO_CHANNEL_7POINT1POINT2, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::CM7Point1Point4 == AAUDIO_CHANNEL_7POINT1POINT4, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::CM9Point1Point4 == AAUDIO_CHANNEL_9POINT1POINT4, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::CM9Point1Point6 == AAUDIO_CHANNEL_9POINT1POINT6, ERRMSG);
|
||||
static_assert((uint32_t)ChannelMask::FrontBack == AAUDIO_CHANNEL_FRONT_BACK, ERRMSG);
|
||||
|
||||
ASSERT_INT32(aaudio_spatialization_behavior_t);
|
||||
|
||||
static_assert((int32_t)SpatializationBehavior::Unspecified == AAUDIO_UNSPECIFIED, ERRMSG);
|
||||
static_assert((int32_t)SpatializationBehavior::Auto == AAUDIO_SPATIALIZATION_BEHAVIOR_AUTO, ERRMSG);
|
||||
static_assert((int32_t)SpatializationBehavior::Never == AAUDIO_SPATIALIZATION_BEHAVIOR_NEVER, ERRMSG);
|
||||
|
||||
#endif
|
||||
|
||||
#endif // AAUDIO_AAUDIO_H
|
||||
|
||||
} // namespace oboe
|
||||
+299
@@ -0,0 +1,299 @@
|
||||
/*
|
||||
* 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/Definitions.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>
|
||||
#endif
|
||||
|
||||
#ifndef __NDK_MAJOR__
|
||||
#define __NDK_MAJOR__ 0
|
||||
#endif
|
||||
|
||||
#if __NDK_MAJOR__ < 24
|
||||
// Defined in SC_V2
|
||||
typedef uint32_t aaudio_channel_mask_t;
|
||||
typedef int32_t aaudio_spatialization_behavior_t;
|
||||
#endif
|
||||
|
||||
#ifndef __ANDROID_API_Q__
|
||||
#define __ANDROID_API_Q__ 29
|
||||
#endif
|
||||
|
||||
#ifndef __ANDROID_API_R__
|
||||
#define __ANDROID_API_R__ 30
|
||||
#endif
|
||||
|
||||
#ifndef __ANDROID_API_S__
|
||||
#define __ANDROID_API_S__ 31
|
||||
#endif
|
||||
|
||||
#ifndef __ANDROID_API_S_V2__
|
||||
#define __ANDROID_API_S_V2__ 32
|
||||
#endif
|
||||
|
||||
#ifndef __ANDROID_API_U__
|
||||
#define __ANDROID_API_U__ 34
|
||||
#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
|
||||
// U = uint32_t
|
||||
// O = bOol
|
||||
|
||||
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);
|
||||
|
||||
// AAudioStreamBuilder_setChannelMask()
|
||||
typedef void (*signature_V_PBU)(AAudioStreamBuilder *, uint32_t);
|
||||
|
||||
typedef void (*signature_V_PBCPH)(AAudioStreamBuilder *, const char *);
|
||||
|
||||
// AAudioStreamBuilder_setPrivacySensitive
|
||||
typedef void (*signature_V_PBO)(AAudioStreamBuilder *, bool);
|
||||
|
||||
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_O_PS)(AAudioStream *);
|
||||
|
||||
typedef uint32_t (*signature_U_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_PBU builder_setChannelMask = 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_PBO builder_setPrivacySensitive = nullptr;
|
||||
signature_V_PBI builder_setAllowedCapturePolicy = nullptr;
|
||||
|
||||
signature_V_PBCPH builder_setPackageName = nullptr;
|
||||
signature_V_PBCPH builder_setAttributionTag = nullptr;
|
||||
|
||||
signature_V_PBO builder_setIsContentSpatialized = nullptr;
|
||||
signature_V_PBI builder_setSpatializationBehavior = 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_release = 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;
|
||||
|
||||
signature_O_PS stream_isPrivacySensitive = nullptr;
|
||||
signature_I_PS stream_getAllowedCapturePolicy = nullptr;
|
||||
|
||||
signature_U_PS stream_getChannelMask = nullptr;
|
||||
|
||||
signature_O_PS stream_isContentSpatialized = nullptr;
|
||||
signature_I_PS stream_getSpatializationBehavior = nullptr;
|
||||
|
||||
signature_I_PS stream_getHardwareChannelCount = nullptr;
|
||||
signature_I_PS stream_getHardwareSampleRate = nullptr;
|
||||
signature_F_PS stream_getHardwareFormat = 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_PBCPH load_V_PBCPH(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_O_PS load_O_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);
|
||||
signature_V_PBU load_V_PBU(const char *name);
|
||||
signature_U_PS load_U_PS(const char *name);
|
||||
signature_V_PBO load_V_PBO(const char *name);
|
||||
|
||||
void *mLibHandle = nullptr;
|
||||
};
|
||||
|
||||
} // namespace oboe
|
||||
|
||||
#endif //OBOE_AAUDIO_LOADER_H_
|
||||
+868
@@ -0,0 +1,868 @@
|
||||
/*
|
||||
* 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 "aaudio/AAudioLoader.h"
|
||||
#include "aaudio/AudioStreamAAudio.h"
|
||||
#include "common/AudioClock.h"
|
||||
#include "common/OboeDebug.h"
|
||||
#include "oboe/Utilities.h"
|
||||
#include "AAudioExtensions.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <sys/system_properties.h>
|
||||
#include <common/QuirksManager.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(,%d) - entering >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>", __func__, error);
|
||||
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::beginPerformanceHintInCallback() {
|
||||
if (isPerformanceHintEnabled()) {
|
||||
if (!mAdpfOpenAttempted) {
|
||||
int64_t targetDurationNanos = (mFramesPerBurst * 1e9) / getSampleRate();
|
||||
// This has to be called from the callback thread so we get the right TID.
|
||||
int adpfResult = mAdpfWrapper.open(gettid(), targetDurationNanos);
|
||||
if (adpfResult < 0) {
|
||||
LOGW("WARNING ADPF not supported, %d\n", adpfResult);
|
||||
} else {
|
||||
LOGD("ADPF is now active\n");
|
||||
}
|
||||
mAdpfOpenAttempted = true;
|
||||
}
|
||||
mAdpfWrapper.onBeginCallback();
|
||||
} else if (!isPerformanceHintEnabled() && mAdpfOpenAttempted) {
|
||||
LOGD("ADPF closed\n");
|
||||
mAdpfWrapper.close();
|
||||
mAdpfOpenAttempted = false;
|
||||
}
|
||||
}
|
||||
|
||||
void AudioStreamAAudio::endPerformanceHintInCallback(int32_t numFrames) {
|
||||
if (mAdpfWrapper.isOpen()) {
|
||||
// Scale the measured duration based on numFrames so it is normalized to a full burst.
|
||||
double durationScaler = static_cast<double>(mFramesPerBurst) / numFrames;
|
||||
// Skip this callback if numFrames is very small.
|
||||
// This can happen when buffers wrap around, particularly when doing sample rate conversion.
|
||||
if (durationScaler < 2.0) {
|
||||
mAdpfWrapper.onEndCallback(durationScaler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (mLibLoader->builder_setSessionId != nullptr) {
|
||||
mLibLoader->builder_setSessionId(aaudioBuilder,
|
||||
static_cast<aaudio_session_id_t>(mSessionId));
|
||||
// Output effects do not support PerformanceMode::LowLatency.
|
||||
if (OboeGlobals::areWorkaroundsEnabled()
|
||||
&& mSessionId != SessionId::None
|
||||
&& mDirection == oboe::Direction::Output
|
||||
&& mPerformanceMode == PerformanceMode::LowLatency) {
|
||||
mPerformanceMode = PerformanceMode::None;
|
||||
LOGD("AudioStreamAAudio.open() performance mode changed to None when session "
|
||||
"id is requested");
|
||||
}
|
||||
}
|
||||
|
||||
// Channel mask was added in SC_V2. Given the corresponding channel count of selected channel
|
||||
// mask may be different from selected channel count, the last set value will be respected.
|
||||
// If channel count is set after channel mask, the previously set channel mask will be cleared.
|
||||
// If channel mask is set after channel count, the channel count will be automatically
|
||||
// calculated from selected channel mask. In that case, only set channel mask when the API
|
||||
// is available and the channel mask is specified.
|
||||
if (mLibLoader->builder_setChannelMask != nullptr && mChannelMask != ChannelMask::Unspecified) {
|
||||
mLibLoader->builder_setChannelMask(aaudioBuilder,
|
||||
static_cast<aaudio_channel_mask_t>(mChannelMask));
|
||||
} else {
|
||||
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));
|
||||
}
|
||||
|
||||
// These were added in S so we have to check for the function pointer.
|
||||
if (mLibLoader->builder_setPackageName != nullptr && !mPackageName.empty()) {
|
||||
mLibLoader->builder_setPackageName(aaudioBuilder,
|
||||
mPackageName.c_str());
|
||||
}
|
||||
|
||||
if (mLibLoader->builder_setAttributionTag != nullptr && !mAttributionTag.empty()) {
|
||||
mLibLoader->builder_setAttributionTag(aaudioBuilder,
|
||||
mAttributionTag.c_str());
|
||||
}
|
||||
|
||||
// This was added in Q so we have to check for the function pointer.
|
||||
if (mLibLoader->builder_setAllowedCapturePolicy != nullptr && mDirection == oboe::Direction::Output) {
|
||||
mLibLoader->builder_setAllowedCapturePolicy(aaudioBuilder,
|
||||
static_cast<aaudio_allowed_capture_policy_t>(mAllowedCapturePolicy));
|
||||
}
|
||||
|
||||
if (mLibLoader->builder_setPrivacySensitive != nullptr && mDirection == oboe::Direction::Input
|
||||
&& mPrivacySensitiveMode != PrivacySensitiveMode::Unspecified) {
|
||||
mLibLoader->builder_setPrivacySensitive(aaudioBuilder,
|
||||
mPrivacySensitiveMode == PrivacySensitiveMode::Enabled);
|
||||
}
|
||||
|
||||
if (mLibLoader->builder_setIsContentSpatialized != nullptr) {
|
||||
mLibLoader->builder_setIsContentSpatialized(aaudioBuilder, mIsContentSpatialized);
|
||||
}
|
||||
|
||||
if (mLibLoader->builder_setSpatializationBehavior != nullptr) {
|
||||
// Override Unspecified as Never to reduce latency.
|
||||
if (mSpatializationBehavior == SpatializationBehavior::Unspecified) {
|
||||
mSpatializationBehavior = SpatializationBehavior::Never;
|
||||
}
|
||||
mLibLoader->builder_setSpatializationBehavior(aaudioBuilder,
|
||||
static_cast<aaudio_spatialization_behavior_t>(mSpatializationBehavior));
|
||||
} else {
|
||||
mSpatializationBehavior = SpatializationBehavior::Never;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// This was added in Q so we have to check for the function pointer.
|
||||
if (mLibLoader->stream_getAllowedCapturePolicy != nullptr && mDirection == oboe::Direction::Output) {
|
||||
mAllowedCapturePolicy = static_cast<AllowedCapturePolicy>(mLibLoader->stream_getAllowedCapturePolicy(mAAudioStream));
|
||||
} else {
|
||||
mAllowedCapturePolicy = AllowedCapturePolicy::Unspecified;
|
||||
}
|
||||
|
||||
if (mLibLoader->stream_isPrivacySensitive != nullptr && mDirection == oboe::Direction::Input) {
|
||||
bool isPrivacySensitive = mLibLoader->stream_isPrivacySensitive(mAAudioStream);
|
||||
mPrivacySensitiveMode = isPrivacySensitive ? PrivacySensitiveMode::Enabled :
|
||||
PrivacySensitiveMode::Disabled;
|
||||
} else {
|
||||
mPrivacySensitiveMode = PrivacySensitiveMode::Unspecified;
|
||||
}
|
||||
|
||||
if (mLibLoader->stream_getChannelMask != nullptr) {
|
||||
mChannelMask = static_cast<ChannelMask>(mLibLoader->stream_getChannelMask(mAAudioStream));
|
||||
}
|
||||
|
||||
if (mLibLoader->stream_isContentSpatialized != nullptr) {
|
||||
mIsContentSpatialized = mLibLoader->stream_isContentSpatialized(mAAudioStream);
|
||||
}
|
||||
|
||||
if (mLibLoader->stream_getSpatializationBehavior != nullptr) {
|
||||
mSpatializationBehavior = static_cast<SpatializationBehavior>(
|
||||
mLibLoader->stream_getSpatializationBehavior(mAAudioStream));
|
||||
}
|
||||
|
||||
if (mLibLoader->stream_getHardwareChannelCount != nullptr) {
|
||||
mHardwareChannelCount = mLibLoader->stream_getHardwareChannelCount(mAAudioStream);
|
||||
}
|
||||
if (mLibLoader->stream_getHardwareSampleRate != nullptr) {
|
||||
mHardwareSampleRate = mLibLoader->stream_getHardwareSampleRate(mAAudioStream);
|
||||
}
|
||||
if (mLibLoader->stream_getHardwareFormat != nullptr) {
|
||||
mHardwareFormat = static_cast<AudioFormat>(mLibLoader->stream_getHardwareFormat(mAAudioStream));
|
||||
}
|
||||
|
||||
LOGD("AudioStreamAAudio.open() format=%d, sampleRate=%d, capacity = %d",
|
||||
static_cast<int>(mFormat), static_cast<int>(mSampleRate),
|
||||
static_cast<int>(mBufferCapacityInFrames));
|
||||
|
||||
calculateDefaultDelayBeforeCloseMillis();
|
||||
|
||||
error2:
|
||||
mLibLoader->builder_delete(aaudioBuilder);
|
||||
if (static_cast<int>(result) > 0) {
|
||||
// Possibly due to b/267531411
|
||||
LOGW("AudioStreamAAudio.open: AAudioStream_Open() returned positive error = %d",
|
||||
static_cast<int>(result));
|
||||
if (OboeGlobals::areWorkaroundsEnabled()) {
|
||||
result = Result::ErrorInternal; // Coerce to negative error.
|
||||
}
|
||||
} else {
|
||||
LOGD("AudioStreamAAudio.open: AAudioStream_Open() returned %s = %d",
|
||||
mLibLoader->convertResultToText(static_cast<aaudio_result_t>(result)),
|
||||
static_cast<int>(result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Result AudioStreamAAudio::release() {
|
||||
if (getSdkVersion() < __ANDROID_API_R__) {
|
||||
return Result::ErrorUnimplemented;
|
||||
}
|
||||
|
||||
// AAudioStream_release() is buggy on Android R.
|
||||
if (OboeGlobals::areWorkaroundsEnabled() && getSdkVersion() == __ANDROID_API_R__) {
|
||||
LOGW("Skipping release() on Android R");
|
||||
return Result::ErrorUnimplemented;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(mLock);
|
||||
AAudioStream *stream = mAAudioStream.load();
|
||||
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);
|
||||
}
|
||||
return static_cast<Result>(mLibLoader->stream_release(stream));
|
||||
} else {
|
||||
return Result::ErrorClosed;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
sleepBeforeClose();
|
||||
}
|
||||
return static_cast<Result>(mLibLoader->stream_close(stream));
|
||||
} else {
|
||||
return Result::ErrorClosed;
|
||||
}
|
||||
}
|
||||
|
||||
static void oboe_stop_thread_proc(AudioStream *oboeStream) {
|
||||
if (oboeStream != nullptr) {
|
||||
oboeStream->requestStop();
|
||||
}
|
||||
}
|
||||
|
||||
void AudioStreamAAudio::launchStopThread() {
|
||||
// Prevent multiple stop threads from being launched.
|
||||
if (mStopThreadAllowed.exchange(false)) {
|
||||
// Stop this stream on a separate thread
|
||||
std::thread t(oboe_stop_thread_proc, this);
|
||||
t.detach();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Returning Stop caused various problems before S. See #1230
|
||||
if (OboeGlobals::areWorkaroundsEnabled() && getSdkVersion() <= __ANDROID_API_R__) {
|
||||
launchStopThread();
|
||||
return DataCallbackResult::Continue;
|
||||
} else {
|
||||
return DataCallbackResult::Stop; // OK >= API_S
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
mStopThreadAllowed = true;
|
||||
closePerformanceHint();
|
||||
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) {
|
||||
std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
|
||||
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) {
|
||||
std::shared_lock<std::shared_mutex> lock(mAAudioStreamLock);
|
||||
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
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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 <common/AdpfWrapper.h>
|
||||
#include "oboe/AudioStreamBuilder.h"
|
||||
#include "oboe/AudioStream.h"
|
||||
#include "oboe/Definitions.h"
|
||||
#include "AAudioLoader.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 release() 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();
|
||||
|
||||
void closePerformanceHint() override {
|
||||
mAdpfWrapper.close();
|
||||
mAdpfOpenAttempted = false;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
void beginPerformanceHintInCallback() override;
|
||||
|
||||
void endPerformanceHintInCallback(int32_t numFrames) override;
|
||||
|
||||
// set by callback (or app when idle)
|
||||
std::atomic<bool> mAdpfOpenAttempted{false};
|
||||
AdpfWrapper mAdpfWrapper;
|
||||
|
||||
private:
|
||||
// Must call under mLock. And stream must NOT be nullptr.
|
||||
Result requestStop_l(AAudioStream *stream);
|
||||
|
||||
/**
|
||||
* Launch a thread that will stop the stream.
|
||||
*/
|
||||
void launchStopThread();
|
||||
|
||||
private:
|
||||
|
||||
std::atomic<bool> mCallbackThreadEnabled;
|
||||
std::atomic<bool> mStopThreadAllowed{false};
|
||||
|
||||
// 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_
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright 2021 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 <stdint.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "AdpfWrapper.h"
|
||||
#include "AudioClock.h"
|
||||
#include "OboeDebug.h"
|
||||
|
||||
typedef APerformanceHintManager* (*APH_getManager)();
|
||||
typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*,
|
||||
size_t, int64_t);
|
||||
typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
|
||||
typedef void (*APH_closeSession)(APerformanceHintSession* session);
|
||||
|
||||
static bool gAPerformanceHintBindingInitialized = false;
|
||||
static APH_getManager gAPH_getManagerFn = nullptr;
|
||||
static APH_createSession gAPH_createSessionFn = nullptr;
|
||||
static APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
|
||||
static APH_closeSession gAPH_closeSessionFn = nullptr;
|
||||
|
||||
static int loadAphFunctions() {
|
||||
if (gAPerformanceHintBindingInitialized) return true;
|
||||
|
||||
void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
|
||||
if (handle_ == nullptr) {
|
||||
return -1000;
|
||||
}
|
||||
|
||||
gAPH_getManagerFn = (APH_getManager)dlsym(handle_, "APerformanceHint_getManager");
|
||||
if (gAPH_getManagerFn == nullptr) {
|
||||
return -1001;
|
||||
}
|
||||
|
||||
gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
|
||||
if (gAPH_getManagerFn == nullptr) {
|
||||
return -1002;
|
||||
}
|
||||
|
||||
gAPH_reportActualWorkDurationFn = (APH_reportActualWorkDuration)dlsym(
|
||||
handle_, "APerformanceHint_reportActualWorkDuration");
|
||||
if (gAPH_getManagerFn == nullptr) {
|
||||
return -1003;
|
||||
}
|
||||
|
||||
gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession");
|
||||
if (gAPH_getManagerFn == nullptr) {
|
||||
return -1004;
|
||||
}
|
||||
|
||||
gAPerformanceHintBindingInitialized = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AdpfWrapper::sUseAlternativeHack = false; // TODO remove hack
|
||||
|
||||
int AdpfWrapper::open(pid_t threadId,
|
||||
int64_t targetDurationNanos) {
|
||||
std::lock_guard<std::mutex> lock(mLock);
|
||||
int result = loadAphFunctions();
|
||||
if (result < 0) return result;
|
||||
|
||||
// This is a singleton.
|
||||
APerformanceHintManager* manager = gAPH_getManagerFn();
|
||||
|
||||
int32_t thread32 = threadId;
|
||||
if (sUseAlternativeHack) {
|
||||
// TODO Remove this hack when we finish experimenting with alternative algorithms.
|
||||
// The A5 is an arbitrary signal to a hacked version of ADPF to try an alternative
|
||||
// algorithm that is not based on PID.
|
||||
targetDurationNanos = (targetDurationNanos & ~0xFF) | 0xA5;
|
||||
}
|
||||
mHintSession = gAPH_createSessionFn(manager, &thread32, 1 /* size */, targetDurationNanos);
|
||||
if (mHintSession == nullptr) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AdpfWrapper::reportActualDuration(int64_t actualDurationNanos) {
|
||||
//LOGD("ADPF Oboe %s(dur=%lld)", __func__, (long long)actualDurationNanos);
|
||||
std::lock_guard<std::mutex> lock(mLock);
|
||||
if (mHintSession != nullptr) {
|
||||
gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos);
|
||||
}
|
||||
}
|
||||
|
||||
void AdpfWrapper::close() {
|
||||
std::lock_guard<std::mutex> lock(mLock);
|
||||
if (mHintSession != nullptr) {
|
||||
gAPH_closeSessionFn(mHintSession);
|
||||
mHintSession = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void AdpfWrapper::onBeginCallback() {
|
||||
if (isOpen()) {
|
||||
mBeginCallbackNanos = oboe::AudioClock::getNanoseconds(CLOCK_REALTIME);
|
||||
}
|
||||
}
|
||||
|
||||
void AdpfWrapper::onEndCallback(double durationScaler) {
|
||||
if (isOpen()) {
|
||||
int64_t endCallbackNanos = oboe::AudioClock::getNanoseconds(CLOCK_REALTIME);
|
||||
int64_t actualDurationNanos = endCallbackNanos - mBeginCallbackNanos;
|
||||
int64_t scaledDurationNanos = static_cast<int64_t>(actualDurationNanos * durationScaler);
|
||||
reportActualDuration(scaledDurationNanos);
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2021 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 SYNTHMARK_ADPF_WRAPPER_H
|
||||
#define SYNTHMARK_ADPF_WRAPPER_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <stdint.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
#include <mutex>
|
||||
|
||||
struct APerformanceHintManager;
|
||||
struct APerformanceHintSession;
|
||||
|
||||
typedef struct APerformanceHintManager APerformanceHintManager;
|
||||
typedef struct APerformanceHintSession APerformanceHintSession;
|
||||
|
||||
class AdpfWrapper {
|
||||
public:
|
||||
/**
|
||||
* Create an ADPF session that can be used to boost performance.
|
||||
* @param threadId
|
||||
* @param targetDurationNanos - nominal period of isochronous task
|
||||
* @return zero or negative error
|
||||
*/
|
||||
int open(pid_t threadId,
|
||||
int64_t targetDurationNanos);
|
||||
|
||||
bool isOpen() const {
|
||||
return (mHintSession != nullptr);
|
||||
}
|
||||
|
||||
void close();
|
||||
|
||||
/**
|
||||
* Call this at the beginning of the callback that you are measuring.
|
||||
*/
|
||||
void onBeginCallback();
|
||||
|
||||
/**
|
||||
* Call this at the end of the callback that you are measuring.
|
||||
* It is OK to skip this if you have a short callback.
|
||||
*/
|
||||
void onEndCallback(double durationScaler);
|
||||
|
||||
/**
|
||||
* For internal use only!
|
||||
* This is a hack for communicating with experimental versions of ADPF.
|
||||
* @param enabled
|
||||
*/
|
||||
static void setUseAlternative(bool enabled) {
|
||||
sUseAlternativeHack = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the measured duration of a callback.
|
||||
* This is normally called by onEndCallback().
|
||||
* You may want to call this directly in order to give an advance hint of a jump in workload.
|
||||
* @param actualDurationNanos
|
||||
*/
|
||||
void reportActualDuration(int64_t actualDurationNanos);
|
||||
|
||||
private:
|
||||
std::mutex mLock;
|
||||
APerformanceHintSession* mHintSession = nullptr;
|
||||
int64_t mBeginCallbackNanos = 0;
|
||||
static bool sUseAlternativeHack;
|
||||
};
|
||||
|
||||
#endif //SYNTHMARK_ADPF_WRAPPER_H
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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/Definitions.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
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 "AudioSourceCaller.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;
|
||||
}
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 "OboeDebug.h"
|
||||
#include "oboe/Oboe.h"
|
||||
|
||||
#include "flowgraph/FlowGraphNode.h"
|
||||
#include "FixedBlockReader.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
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
* 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/AudioStream.h>
|
||||
#include "OboeDebug.h"
|
||||
#include "AudioClock.h"
|
||||
#include <oboe/Utilities.h>
|
||||
|
||||
namespace oboe {
|
||||
|
||||
/*
|
||||
* AudioStream
|
||||
*/
|
||||
AudioStream::AudioStream(const AudioStreamBuilder &builder)
|
||||
: AudioStreamBase(builder) {
|
||||
}
|
||||
|
||||
Result AudioStream::close() {
|
||||
closePerformanceHint();
|
||||
// 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; // Should not be getting called
|
||||
}
|
||||
|
||||
beginPerformanceHintInCallback();
|
||||
|
||||
// Call the app to do the work.
|
||||
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);
|
||||
|
||||
endPerformanceHintInCallback(numFrames);
|
||||
|
||||
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;
|
||||
|
||||
// Make sure we don't try to wait for more frames than the buffer can hold.
|
||||
// Subtract framesPerBurst because this is often called from a callback
|
||||
// and we don't want to be sleeping if the buffer is close to overflowing.
|
||||
const int32_t maxAvailableFrames = getBufferCapacityInFrames() - getFramesPerBurst();
|
||||
numFrames = std::min(numFrames, maxAvailableFrames);
|
||||
// The capacity should never be less than one burst. But clip to zero just in case.
|
||||
numFrames = std::max(0, numFrames);
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
void AudioStream::calculateDefaultDelayBeforeCloseMillis() {
|
||||
// Calculate delay time before close based on burst duration.
|
||||
// Start with a burst duration then add 1 msec as a safety margin.
|
||||
mDelayBeforeCloseMillis = std::max(kMinDelayBeforeCloseMillis,
|
||||
1 + ((mFramesPerBurst * 1000) / getSampleRate()));
|
||||
LOGD("calculateDefaultDelayBeforeCloseMillis() default = %d",
|
||||
static_cast<int>(mDelayBeforeCloseMillis));
|
||||
}
|
||||
|
||||
} // namespace oboe
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* 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 "aaudio/AAudioExtensions.h"
|
||||
#include "aaudio/AudioStreamAAudio.h"
|
||||
#include "FilterAudioStream.h"
|
||||
#include "OboeDebug.h"
|
||||
#include "oboe/Oboe.h"
|
||||
#include "oboe/AudioStreamBuilder.h"
|
||||
#include "opensles/AudioInputStreamOpenSLES.h"
|
||||
#include "opensles/AudioOutputStreamOpenSLES.h"
|
||||
#include "opensles/AudioStreamOpenSLES.h"
|
||||
#include "QuirksManager.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
|
||||
Vendored
+266
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
* 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 "OboeDebug.h"
|
||||
#include "DataConversionFlowGraph.h"
|
||||
#include "SourceFloatCaller.h"
|
||||
#include "SourceI16Caller.h"
|
||||
#include "SourceI24Caller.h"
|
||||
#include "SourceI32Caller.h"
|
||||
|
||||
#include <flowgraph/MonoToMultiConverter.h>
|
||||
#include <flowgraph/MultiToMonoConverter.h>
|
||||
#include <flowgraph/RampLinear.h>
|
||||
#include <flowgraph/SinkFloat.h>
|
||||
#include <flowgraph/SinkI16.h>
|
||||
#include <flowgraph/SinkI24.h>
|
||||
#include <flowgraph/SinkI32.h>
|
||||
#include <flowgraph/SourceFloat.h>
|
||||
#include <flowgraph/SourceI16.h>
|
||||
#include <flowgraph/SourceI24.h>
|
||||
#include <flowgraph/SourceI32.h>
|
||||
#include <flowgraph/SampleRateConverter.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;
|
||||
}
|
||||
Vendored
+86
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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 <flowgraph/ChannelCountConverter.h>
|
||||
#include <flowgraph/MonoToMultiConverter.h>
|
||||
#include <flowgraph/MultiToMonoConverter.h>
|
||||
#include <flowgraph/SampleRateConverter.h>
|
||||
#include <oboe/Definitions.h>
|
||||
#include "AudioSourceCaller.h"
|
||||
#include "FixedBlockWriter.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
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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 "OboeDebug.h"
|
||||
#include "FilterAudioStream.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();
|
||||
}
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
/*
|
||||
* 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/AudioStream.h>
|
||||
#include "DataConversionFlowGraph.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();
|
||||
mSharingMode = mChildStream->getSharingMode();
|
||||
mInputPreset = mChildStream->getInputPreset();
|
||||
mFramesPerBurst = mChildStream->getFramesPerBurst();
|
||||
mDeviceId = mChildStream->getDeviceId();
|
||||
mHardwareSampleRate = mChildStream->getHardwareSampleRate();
|
||||
mHardwareChannelCount = mChildStream->getHardwareChannelCount();
|
||||
mHardwareFormat = mChildStream->getHardwareFormat();
|
||||
}
|
||||
|
||||
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
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 "FixedBlockAdapter.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;
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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 */
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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 "FixedBlockAdapter.h"
|
||||
|
||||
#include "FixedBlockReader.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;
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 "FixedBlockAdapter.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 */
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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 "FixedBlockAdapter.h"
|
||||
#include "FixedBlockWriter.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;
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 "FixedBlockAdapter.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 */
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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/LatencyTuner.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;
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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/OboeExtensions.h"
|
||||
#include "aaudio/AAudioExtensions.h"
|
||||
|
||||
using namespace oboe;
|
||||
|
||||
bool OboeExtensions::isMMapSupported(){
|
||||
return AAudioExtensions::getInstance().isMMapSupported();
|
||||
}
|
||||
|
||||
bool OboeExtensions::isMMapEnabled(){
|
||||
return AAudioExtensions::getInstance().isMMapEnabled();
|
||||
}
|
||||
|
||||
int32_t OboeExtensions::setMMapEnabled(bool enabled){
|
||||
return AAudioExtensions::getInstance().setMMapEnabled(enabled);
|
||||
}
|
||||
|
||||
bool OboeExtensions::isMMapUsed(oboe::AudioStream *oboeStream){
|
||||
return AAudioExtensions::getInstance().isMMapUsed(oboeStream);
|
||||
}
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
/*
|
||||
* 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/AudioStreamBuilder.h>
|
||||
#include <oboe/Oboe.h>
|
||||
|
||||
#include "OboeDebug.h"
|
||||
#include "QuirksManager.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;
|
||||
}
|
||||
|
||||
bool QuirksManager::DeviceQuirks::shouldConvertFloatToI16ForOutputStreams() {
|
||||
std::string productManufacturer = getPropertyString("ro.product.manufacturer");
|
||||
if (getSdkVersion() < __ANDROID_API_L__) {
|
||||
return true;
|
||||
} else if ((productManufacturer == "vivo") && (getSdkVersion() < __ANDROID_API_M__)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is for Samsung Exynos quirks. Samsung Mobile uses Qualcomm chips so
|
||||
* the QualcommDeviceQuirks would apply.
|
||||
*/
|
||||
class SamsungExynosDeviceQuirks : public QuirksManager::DeviceQuirks {
|
||||
public:
|
||||
SamsungExynosDeviceQuirks() {
|
||||
std::string chipname = getPropertyString("ro.hardware.chipname");
|
||||
isExynos9810 = (chipname == "exynos9810");
|
||||
isExynos990 = (chipname == "exynos990");
|
||||
isExynos850 = (chipname == "exynos850");
|
||||
|
||||
mBuildChangelist = getPropertyInteger("ro.build.changelist", 0);
|
||||
}
|
||||
|
||||
virtual ~SamsungExynosDeviceQuirks() = default;
|
||||
|
||||
int32_t getExclusiveBottomMarginInBursts() const override {
|
||||
return kBottomMargin;
|
||||
}
|
||||
|
||||
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 isRecordingCorrupted = isInput
|
||||
&& isExynos990
|
||||
&& mBuildChangelist < 19350896;
|
||||
|
||||
// Certain S9+ builds record silence when using MMAP and not using the VoiceCommunication
|
||||
// preset.
|
||||
// See https://github.com/google/oboe/issues/1110
|
||||
bool wouldRecordSilence = isInput
|
||||
&& isExynos9810
|
||||
&& mBuildChangelist <= 18847185
|
||||
&& (builder.getInputPreset() != InputPreset::VoiceCommunication);
|
||||
|
||||
if (wouldRecordSilence){
|
||||
LOGI("QuirksManager::%s() Requested stream configuration would result in silence on "
|
||||
"this device. Switching off MMAP.", __func__);
|
||||
}
|
||||
|
||||
return !isRecordingCorrupted && !wouldRecordSilence;
|
||||
}
|
||||
|
||||
private:
|
||||
// Stay farther away from DSP position on Exynos devices.
|
||||
static constexpr int32_t kBottomMargin = 2;
|
||||
static constexpr int32_t kTopMargin = 1;
|
||||
bool isExynos9810 = false;
|
||||
bool isExynos990 = false;
|
||||
bool isExynos850 = false;
|
||||
int mBuildChangelist = 0;
|
||||
};
|
||||
|
||||
class QualcommDeviceQuirks : public QuirksManager::DeviceQuirks {
|
||||
public:
|
||||
QualcommDeviceQuirks() {
|
||||
std::string modelName = getPropertyString("ro.soc.model");
|
||||
isSM8150 = (modelName == "SDM8150");
|
||||
}
|
||||
|
||||
virtual ~QualcommDeviceQuirks() = default;
|
||||
|
||||
int32_t getExclusiveBottomMarginInBursts() const override {
|
||||
return kBottomMargin;
|
||||
}
|
||||
|
||||
bool isMMapSafe(const AudioStreamBuilder &builder) override {
|
||||
// See https://github.com/google/oboe/issues/1121#issuecomment-897957749
|
||||
bool isMMapBroken = false;
|
||||
if (isSM8150 && (getSdkVersion() <= __ANDROID_API_P__)) {
|
||||
LOGI("QuirksManager::%s() MMAP not actually supported on this chip."
|
||||
" Switching off MMAP.", __func__);
|
||||
isMMapBroken = true;
|
||||
}
|
||||
|
||||
return !isMMapBroken;
|
||||
}
|
||||
|
||||
private:
|
||||
bool isSM8150 = false;
|
||||
static constexpr int32_t kBottomMargin = 1;
|
||||
};
|
||||
|
||||
QuirksManager::QuirksManager() {
|
||||
std::string productManufacturer = getPropertyString("ro.product.manufacturer");
|
||||
if (productManufacturer == "samsung") {
|
||||
std::string arch = getPropertyString("ro.arch");
|
||||
bool isExynos = (arch.rfind("exynos", 0) == 0); // starts with?
|
||||
if (isExynos) {
|
||||
mDeviceQuirks = std::make_unique<SamsungExynosDeviceQuirks>();
|
||||
}
|
||||
}
|
||||
if (!mDeviceQuirks) {
|
||||
std::string socManufacturer = getPropertyString("ro.soc.manufacturer");
|
||||
if (socManufacturer == "Qualcomm") {
|
||||
// This may include Samsung Mobile devices.
|
||||
mDeviceQuirks = std::make_unique<QualcommDeviceQuirks>();
|
||||
} 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;
|
||||
const bool isIEC61937 = builder.getFormat() == AudioFormat::IEC61937;
|
||||
|
||||
// There should be no conversion for IEC61937. Sample rates and channel counts must be set explicitly.
|
||||
if (isIEC61937) {
|
||||
LOGI("QuirksManager::%s() conversion not needed for IEC61937", __func__);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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, let the native code choose an optimal rate.
|
||||
// This isn't really a workaround. It is an Oboe feature that is convenient to place here.
|
||||
// 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 (OboeGlobals::areWorkaroundsEnabled()
|
||||
&& 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 when needed.
|
||||
if (OboeGlobals::areWorkaroundsEnabled()
|
||||
&& isFloat
|
||||
&& !isInput
|
||||
&& builder.isFormatConversionAllowed()
|
||||
&& mDeviceQuirks->shouldConvertFloatToI16ForOutputStreams()
|
||||
) {
|
||||
childBuilder.setFormat(AudioFormat::I16);
|
||||
conversionNeeded = true;
|
||||
LOGI("QuirksManager::%s() float was requested but not supported on pre-L devices "
|
||||
"and some devices like Vivo devices may have issues on 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);
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* 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/AudioStreamBuilder.h>
|
||||
#include <aaudio/AudioStreamAAudio.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;
|
||||
}
|
||||
|
||||
// On some devices, Float does not work so it should be converted to I16.
|
||||
static bool shouldConvertFloatToI16ForOutputStreams();
|
||||
|
||||
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
|
||||
@@ -0,0 +1,33 @@
|
||||
# Notes on Implementation
|
||||
|
||||
## Latency from Resampling
|
||||
|
||||
There are two components of the latency. The resampler itself, and a buffer that
|
||||
is used to adapt the block sizes.
|
||||
|
||||
1) The resampler is an FIR running at the target sample rate. So its latency is the number of taps.
|
||||
From MultiChannelResampler.cpp, numTaps is
|
||||
|
||||
Fastest: 2
|
||||
Low: 4
|
||||
Medium: 8
|
||||
High: 16
|
||||
Best: 32
|
||||
|
||||
For output, the device sampling rate is used, which is typically 48000.For input, the app sampling rate is used.
|
||||
|
||||
2) There is a block size adapter that collects odd sized blocks into larger blocks of the correct size.
|
||||
|
||||
The adapter contains one burst of frames, from getFramesPerBurst(). But if the app specifies a
|
||||
particular size using setFramesPerCallback() then that size will be used.
|
||||
Here is some pseudo-code to calculate the latency.
|
||||
|
||||
latencyMillis = 0
|
||||
targetRate = isOutput ? deviceRate : applicationRate
|
||||
// Add latency from FIR
|
||||
latencyMillis += numTaps * 1000.0 / targetRate
|
||||
// Add latency from block size adaptation
|
||||
adapterSize = (callbackSize > 0) ? callbackSize : burstSize
|
||||
if (isOutput && isCallbackUsed) latencyMillis += adapterSize * 1000.0 / deviceRate
|
||||
else if (isInput && isCallbackUsed) latencyMillis += adapterSize * 1000.0 / applicationRate
|
||||
else if (isInput && !isCallbackUsed) latencyMillis += adapterSize * 1000.0 / deviceRate
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 "flowgraph/FlowGraphNode.h"
|
||||
#include "SourceFloatCaller.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;
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 "flowgraph/FlowGraphNode.h"
|
||||
#include "AudioSourceCaller.h"
|
||||
#include "FixedBlockReader.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
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 "flowgraph/FlowGraphNode.h"
|
||||
#include "SourceI16Caller.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;
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 "flowgraph/FlowGraphNode.h"
|
||||
#include "AudioSourceCaller.h"
|
||||
#include "FixedBlockReader.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[]>(static_cast<size_t>(channelCount)
|
||||
* static_cast<size_t>(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
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 "flowgraph/FlowGraphNode.h"
|
||||
#include "SourceI24Caller.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;
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 "flowgraph/FlowGraphNode.h"
|
||||
#include "AudioSourceCaller.h"
|
||||
#include "FixedBlockReader.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[]>(static_cast<size_t>(kBytesPerI24Packed)
|
||||
* static_cast<size_t>(channelCount)
|
||||
* static_cast<size_t>(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
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 "flowgraph/FlowGraphNode.h"
|
||||
#include "SourceI32Caller.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;
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 "flowgraph/FlowGraphNode.h"
|
||||
#include "AudioSourceCaller.h"
|
||||
#include "FixedBlockReader.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[]>(static_cast<size_t>(channelCount)
|
||||
* static_cast<size_t>(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
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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/StabilizedCallback.h"
|
||||
#include "common/AudioClock.h"
|
||||
#include "common/Trace.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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 "Trace.h"
|
||||
#include "OboeDebug.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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
|
||||
+333
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* 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/AudioStream.h>
|
||||
#include "oboe/Definitions.h"
|
||||
#include "oboe/Utilities.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;
|
||||
case AudioFormat::IEC61937:
|
||||
size = sizeof(int16_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";
|
||||
case AudioFormat::IEC61937: return "IEC61937";
|
||||
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;
|
||||
}
|
||||
|
||||
bool isAtLeastPreReleaseCodename(const std::string& codename) {
|
||||
std::string buildCodename = getPropertyString("ro.build.version.codename");
|
||||
// Special case "REL", which means the build is not a pre-release build.
|
||||
if ("REL" == buildCodename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise lexically compare them. Return true if the build codename is equal to or
|
||||
// greater than the requested codename.
|
||||
return buildCodename.compare(codename) >= 0;
|
||||
}
|
||||
|
||||
int getChannelCountFromChannelMask(ChannelMask channelMask) {
|
||||
return __builtin_popcount(static_cast<uint32_t>(channelMask));
|
||||
}
|
||||
|
||||
}// namespace oboe
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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/Version.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
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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/FifoControllerBase.h"
|
||||
#include "fifo/FifoController.h"
|
||||
#include "fifo/FifoControllerIndirect.h"
|
||||
#include "oboe/FifoBuffer.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
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 "FifoController.h"
|
||||
|
||||
namespace oboe {
|
||||
|
||||
FifoController::FifoController(uint32_t numFrames)
|
||||
: FifoControllerBase(numFrames)
|
||||
{
|
||||
setReadCounter(0);
|
||||
setWriteCounter(0);
|
||||
}
|
||||
|
||||
} // namespace oboe
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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/FifoControllerBase.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
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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/FifoControllerBase.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
|
||||
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 "FifoControllerIndirect.h"
|
||||
|
||||
namespace oboe {
|
||||
|
||||
FifoControllerIndirect::FifoControllerIndirect(uint32_t numFrames,
|
||||
std::atomic<uint64_t> *readCounterAddress,
|
||||
std::atomic<uint64_t> *writeCounterAddress)
|
||||
: FifoControllerBase(numFrames)
|
||||
, mReadCounterAddress(readCounterAddress)
|
||||
, mWriteCounterAddress(writeCounterAddress)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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/FifoControllerBase.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
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
#include "ChannelCountConverter.h"
|
||||
|
||||
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
|
||||
|
||||
ChannelCountConverter::ChannelCountConverter(
|
||||
int32_t inputChannelCount,
|
||||
int32_t outputChannelCount)
|
||||
: input(*this, inputChannelCount)
|
||||
, output(*this, outputChannelCount) {
|
||||
}
|
||||
|
||||
ChannelCountConverter::~ChannelCountConverter() = default;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Vendored
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
|
||||
namespace FLOWGRAPH_OUTER_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_OUTER_NAMESPACE::flowgraph */
|
||||
|
||||
#endif //FLOWGRAPH_CHANNEL_COUNT_CONVERTER_H
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
#include "ClipToRange.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;
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
|
||||
namespace FLOWGRAPH_OUTER_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_OUTER_NAMESPACE::flowgraph */
|
||||
|
||||
#endif //FLOWGRAPH_CLIP_TO_RANGE_H
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.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);
|
||||
}
|
||||
+450
@@ -0,0 +1,450 @@
|
||||
/*
|
||||
* 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 flags FLOWGRAPH_ANDROID_INTERNAL and FLOWGRAPH_OUTER_NAMESPACE based on whether compiler
|
||||
// flag __ANDROID_NDK__ is defined. __ANDROID_NDK__ should be defined in oboe and not aaudio.
|
||||
|
||||
#ifndef FLOWGRAPH_ANDROID_INTERNAL
|
||||
#ifdef __ANDROID_NDK__
|
||||
#define FLOWGRAPH_ANDROID_INTERNAL 0
|
||||
#else
|
||||
#define FLOWGRAPH_ANDROID_INTERNAL 1
|
||||
#endif // __ANDROID_NDK__
|
||||
#endif // FLOWGRAPH_ANDROID_INTERNAL
|
||||
|
||||
#ifndef FLOWGRAPH_OUTER_NAMESPACE
|
||||
#ifdef __ANDROID_NDK__
|
||||
#define FLOWGRAPH_OUTER_NAMESPACE oboe
|
||||
#else
|
||||
#define FLOWGRAPH_OUTER_NAMESPACE aaudio
|
||||
#endif // __ANDROID_NDK__
|
||||
#endif // FLOWGRAPH_OUTER_NAMESPACE
|
||||
|
||||
namespace FLOWGRAPH_OUTER_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() = default;
|
||||
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.emplace_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;
|
||||
|
||||
/**
|
||||
* Do nothing. 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_OUTER_NAMESPACE::flowgraph */
|
||||
|
||||
#endif /* FLOWGRAPH_FLOW_GRAPH_NODE_H */
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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 INT32_MIN;
|
||||
} else if (f >= limpos) {
|
||||
return INT32_MAX;
|
||||
}
|
||||
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
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2022 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 <math.h>
|
||||
#include <unistd.h>
|
||||
#include "FlowGraphNode.h"
|
||||
#include "Limiter.h"
|
||||
|
||||
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
|
||||
|
||||
Limiter::Limiter(int32_t channelCount)
|
||||
: FlowGraphFilter(channelCount) {
|
||||
}
|
||||
|
||||
int32_t Limiter::onProcess(int32_t numFrames) {
|
||||
const float *inputBuffer = input.getBuffer();
|
||||
float *outputBuffer = output.getBuffer();
|
||||
|
||||
int32_t numSamples = numFrames * output.getSamplesPerFrame();
|
||||
|
||||
// Cache the last valid output to reduce memory read/write
|
||||
float lastValidOutput = mLastValidOutput;
|
||||
|
||||
for (int32_t i = 0; i < numSamples; i++) {
|
||||
// Use the previous output if the input is NaN
|
||||
if (!isnan(*inputBuffer)) {
|
||||
lastValidOutput = processFloat(*inputBuffer);
|
||||
}
|
||||
inputBuffer++;
|
||||
*outputBuffer++ = lastValidOutput;
|
||||
}
|
||||
mLastValidOutput = lastValidOutput;
|
||||
|
||||
return numFrames;
|
||||
}
|
||||
|
||||
float Limiter::processFloat(float in)
|
||||
{
|
||||
float in_abs = fabsf(in);
|
||||
if (in_abs <= 1) {
|
||||
return in;
|
||||
}
|
||||
float out;
|
||||
if (in_abs < kXWhenYis3Decibels) {
|
||||
out = (kPolynomialSplineA * in_abs + kPolynomialSplineB) * in_abs + kPolynomialSplineC;
|
||||
} else {
|
||||
out = M_SQRT2;
|
||||
}
|
||||
if (in < 0) {
|
||||
out = -out;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2022 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_LIMITER_H
|
||||
#define FLOWGRAPH_LIMITER_H
|
||||
|
||||
#include <atomic>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "FlowGraphNode.h"
|
||||
|
||||
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
|
||||
|
||||
class Limiter : public FlowGraphFilter {
|
||||
public:
|
||||
explicit Limiter(int32_t channelCount);
|
||||
|
||||
int32_t onProcess(int32_t numFrames) override;
|
||||
|
||||
const char *getName() override {
|
||||
return "Limiter";
|
||||
}
|
||||
|
||||
private:
|
||||
// These numbers are based on a polynomial spline for a quadratic solution Ax^2 + Bx + C
|
||||
// The range is up to 3 dB, (10^(3/20)), to match AudioTrack for float data.
|
||||
static constexpr float kPolynomialSplineA = -0.6035533905; // -(1+sqrt(2))/4
|
||||
static constexpr float kPolynomialSplineB = 2.2071067811; // (3+sqrt(2))/2
|
||||
static constexpr float kPolynomialSplineC = -0.6035533905; // -(1+sqrt(2))/4
|
||||
static constexpr float kXWhenYis3Decibels = 1.8284271247; // -1+2sqrt(2)
|
||||
|
||||
/**
|
||||
* Process an input based on the following:
|
||||
* If between -1 and 1, return the input value.
|
||||
* If above kXWhenYis3Decibels, return sqrt(2).
|
||||
* If below -kXWhenYis3Decibels, return -sqrt(2).
|
||||
* If between 1 and kXWhenYis3Decibels, use a quadratic spline (Ax^2 + Bx + C).
|
||||
* If between -kXWhenYis3Decibels and -1, use the absolute value for the spline and flip it.
|
||||
* The derivative of the spline is 1 at 1 and 0 at kXWhenYis3Decibels.
|
||||
* This way, the graph is both continuous and differentiable.
|
||||
*/
|
||||
float processFloat(float in);
|
||||
|
||||
// Use the previous valid output for NaN inputs
|
||||
float mLastValidOutput = 0.0f;
|
||||
};
|
||||
|
||||
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
|
||||
|
||||
#endif //FLOWGRAPH_LIMITER_H
|
||||
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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 "ManyToMultiConverter.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;
|
||||
}
|
||||
|
||||
Vendored
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
|
||||
namespace FLOWGRAPH_OUTER_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_OUTER_NAMESPACE::flowgraph */
|
||||
|
||||
#endif //FLOWGRAPH_MANY_TO_MULTI_CONVERTER_H
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2021 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 "MonoBlend.h"
|
||||
|
||||
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
|
||||
|
||||
MonoBlend::MonoBlend(int32_t channelCount)
|
||||
: FlowGraphFilter(channelCount)
|
||||
, mInvChannelCount(1. / channelCount)
|
||||
{
|
||||
}
|
||||
|
||||
int32_t MonoBlend::onProcess(int32_t numFrames) {
|
||||
int32_t channelCount = output.getSamplesPerFrame();
|
||||
const float *inputBuffer = input.getBuffer();
|
||||
float *outputBuffer = output.getBuffer();
|
||||
|
||||
for (size_t i = 0; i < numFrames; ++i) {
|
||||
float accum = 0;
|
||||
for (size_t j = 0; j < channelCount; ++j) {
|
||||
accum += *inputBuffer++;
|
||||
}
|
||||
accum *= mInvChannelCount;
|
||||
for (size_t j = 0; j < channelCount; ++j) {
|
||||
*outputBuffer++ = accum;
|
||||
}
|
||||
}
|
||||
|
||||
return numFrames;
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2021 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_BLEND_H
|
||||
#define FLOWGRAPH_MONO_BLEND_H
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "FlowGraphNode.h"
|
||||
|
||||
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
|
||||
|
||||
/**
|
||||
* Combine data between multiple channels so each channel is an average
|
||||
* of all channels.
|
||||
*/
|
||||
class MonoBlend : public FlowGraphFilter {
|
||||
public:
|
||||
explicit MonoBlend(int32_t channelCount);
|
||||
|
||||
virtual ~MonoBlend() = default;
|
||||
|
||||
int32_t onProcess(int32_t numFrames) override;
|
||||
|
||||
const char *getName() override {
|
||||
return "MonoBlend";
|
||||
}
|
||||
private:
|
||||
const float mInvChannelCount;
|
||||
};
|
||||
|
||||
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
|
||||
|
||||
#endif //FLOWGRAPH_MONO_BLEND
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
#include "MonoToMultiConverter.h"
|
||||
|
||||
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
|
||||
|
||||
MonoToMultiConverter::MonoToMultiConverter(int32_t outputChannelCount)
|
||||
: input(*this, 1)
|
||||
, output(*this, outputChannelCount) {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
|
||||
namespace FLOWGRAPH_OUTER_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() = default;
|
||||
|
||||
int32_t onProcess(int32_t numFrames) override;
|
||||
|
||||
const char *getName() override {
|
||||
return "MonoToMultiConverter";
|
||||
}
|
||||
|
||||
FlowGraphPortFloatInput input;
|
||||
FlowGraphPortFloatOutput output;
|
||||
};
|
||||
|
||||
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
|
||||
|
||||
#endif //FLOWGRAPH_MONO_TO_MULTI_CONVERTER_H
|
||||
Vendored
+47
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2021 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 "FlowGraphNode.h"
|
||||
#include "MultiToManyConverter.h"
|
||||
|
||||
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
|
||||
|
||||
MultiToManyConverter::MultiToManyConverter(int32_t channelCount)
|
||||
: outputs(channelCount)
|
||||
, input(*this, channelCount) {
|
||||
for (int i = 0; i < channelCount; i++) {
|
||||
outputs[i] = std::make_unique<FlowGraphPortFloatOutput>(*this, 1);
|
||||
}
|
||||
}
|
||||
|
||||
MultiToManyConverter::~MultiToManyConverter() = default;
|
||||
|
||||
int32_t MultiToManyConverter::onProcess(int32_t numFrames) {
|
||||
int32_t channelCount = input.getSamplesPerFrame();
|
||||
|
||||
for (int ch = 0; ch < channelCount; ch++) {
|
||||
const float *inputBuffer = input.getBuffer() + ch;
|
||||
float *outputBuffer = outputs[ch]->getBuffer();
|
||||
|
||||
for (int i = 0; i < numFrames; i++) {
|
||||
*outputBuffer++ = *inputBuffer;
|
||||
inputBuffer += channelCount;
|
||||
}
|
||||
}
|
||||
|
||||
return numFrames;
|
||||
}
|
||||
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2021 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_MANY_CONVERTER_H
|
||||
#define FLOWGRAPH_MULTI_TO_MANY_CONVERTER_H
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "FlowGraphNode.h"
|
||||
|
||||
namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph {
|
||||
|
||||
/**
|
||||
* Convert a multi-channel interleaved stream to multiple mono-channel
|
||||
* outputs
|
||||
*/
|
||||
class MultiToManyConverter : public FlowGraphNode {
|
||||
public:
|
||||
explicit MultiToManyConverter(int32_t channelCount);
|
||||
|
||||
virtual ~MultiToManyConverter();
|
||||
|
||||
int32_t onProcess(int32_t numFrames) override;
|
||||
|
||||
const char *getName() override {
|
||||
return "MultiToManyConverter";
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<flowgraph::FlowGraphPortFloatOutput>> outputs;
|
||||
flowgraph::FlowGraphPortFloatInput input;
|
||||
};
|
||||
|
||||
} /* namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph */
|
||||
|
||||
#endif //FLOWGRAPH_MULTI_TO_MANY_CONVERTER_H
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
#include "MultiToMonoConverter.h"
|
||||
|
||||
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
|
||||
|
||||
MultiToMonoConverter::MultiToMonoConverter(int32_t inputChannelCount)
|
||||
: input(*this, inputChannelCount)
|
||||
, output(*this, 1) {
|
||||
}
|
||||
|
||||
MultiToMonoConverter::~MultiToMonoConverter() = default;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Vendored
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
|
||||
namespace FLOWGRAPH_OUTER_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_OUTER_NAMESPACE::flowgraph */
|
||||
|
||||
#endif //FLOWGRAPH_MULTI_TO_MONO_CONVERTER_H
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
#include "RampLinear.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);
|
||||
// If the ramp has not been used then start immediately at this level.
|
||||
if (mLastCallCount == kInitialCallCount) {
|
||||
forceCurrent(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;
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
|
||||
namespace FLOWGRAPH_OUTER_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_OUTER_NAMESPACE::flowgraph */
|
||||
|
||||
#endif //FLOWGRAPH_RAMP_LINEAR_H
|
||||
Vendored
+71
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* 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 "SampleRateConverter.h"
|
||||
|
||||
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
|
||||
using namespace RESAMPLER_OUTER_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;
|
||||
}
|
||||
Vendored
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 FLOWGRAPH_SAMPLE_RATE_CONVERTER_H
|
||||
#define FLOWGRAPH_SAMPLE_RATE_CONVERTER_H
|
||||
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "FlowGraphNode.h"
|
||||
#include "resampler/MultiChannelResampler.h"
|
||||
|
||||
namespace FLOWGRAPH_OUTER_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_OUTER_NAMESPACE::flowgraph */
|
||||
|
||||
#endif //FLOWGRAPH_SAMPLE_RATE_CONVERTER_H
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
#include "SinkFloat.h"
|
||||
|
||||
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
|
||||
|
||||
SinkFloat::SinkFloat(int32_t channelCount)
|
||||
: FlowGraphSink(channelCount) {
|
||||
}
|
||||
|
||||
int32_t SinkFloat::read(void *data, int32_t 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);
|
||||
if (framesPulled <= 0) {
|
||||
break;
|
||||
}
|
||||
const float *signal = input.getBuffer();
|
||||
int32_t numSamples = framesPulled * channelCount;
|
||||
memcpy(floatData, signal, numSamples * sizeof(float));
|
||||
floatData += numSamples;
|
||||
framesLeft -= framesPulled;
|
||||
}
|
||||
return numFrames - framesLeft;
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
|
||||
namespace FLOWGRAPH_OUTER_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_OUTER_NAMESPACE::flowgraph */
|
||||
|
||||
#endif //FLOWGRAPH_SINK_FLOAT_H
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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 "SinkI16.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;
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
|
||||
namespace FLOWGRAPH_OUTER_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_OUTER_NAMESPACE::flowgraph */
|
||||
|
||||
#endif //FLOWGRAPH_SINK_I16_H
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
#include "SinkI24.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;
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
|
||||
namespace FLOWGRAPH_OUTER_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_OUTER_NAMESPACE::flowgraph */
|
||||
|
||||
#endif //FLOWGRAPH_SINK_I24_H
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
#include "FlowgraphUtilities.h"
|
||||
#include "SinkI32.h"
|
||||
|
||||
#if FLOWGRAPH_ANDROID_INTERNAL
|
||||
#include <audio_utils/primitives.h>
|
||||
#endif
|
||||
|
||||
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;
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
|
||||
namespace FLOWGRAPH_OUTER_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_OUTER_NAMESPACE::flowgraph */
|
||||
|
||||
#endif //FLOWGRAPH_SINK_I32_H
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
#include "SourceFloat.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;
|
||||
}
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 "FlowGraphNode.h"
|
||||
|
||||
namespace FLOWGRAPH_OUTER_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_OUTER_NAMESPACE::flowgraph */
|
||||
|
||||
#endif //FLOWGRAPH_SOURCE_FLOAT_H
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user