unwind info for arm64. use static c++ lib for android

include oboe static libs
get rid of old regalloc
This commit is contained in:
Flyinghead 2021-07-29 17:46:46 +02:00
parent 5fd00e3063
commit 7561ce753a
35 changed files with 2955 additions and 1251 deletions

View File

@ -357,9 +357,15 @@ if(ASAN)
endif()
if(ANDROID AND NOT LIBRETRO)
find_package(oboe REQUIRED CONFIG)
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_OBOE)
find_package(oboe CONFIG)
if(OBOE_FOUND)
target_link_libraries(${PROJECT_NAME} PRIVATE oboe::oboe)
else()
include("core/deps/oboe/gamesdk.cmake")
add_gamesdk_target(PACKAGE_DIR core/deps/oboe ANDROID_API_LEVEL 28 ANDROID_NDK_VERSION 21.0.0)
target_link_libraries(${PROJECT_NAME} PRIVATE oboe OpenSLES)
endif()
target_compile_definitions(${PROJECT_NAME} PRIVATE USE_OBOE)
endif()
target_sources(${PROJECT_NAME} PRIVATE
@ -638,7 +644,6 @@ target_sources(${PROJECT_NAME} PRIVATE
core/hw/sh4/dyna/decoder_opcodes.h
core/hw/sh4/dyna/driver.cpp
core/hw/sh4/dyna/ngen.h
core/hw/sh4/dyna/regalloc.h
core/hw/sh4/dyna/shil_canonical.h
core/hw/sh4/dyna/shil.cpp
core/hw/sh4/dyna/shil.h

View File

@ -0,0 +1,134 @@
include(CMakeParseArguments) # CMake 2.8 - 3.4 compatibility
set( _MY_DIR ${CMAKE_CURRENT_LIST_DIR})
# This function will use (and build if asked to) a static library target called 'gamesdk'.
# The location of the library is set according to your ANDROID_NDK_REVISION
# and ANDROID_PLATFORM, unless you explicitly set ANDROID_NDK_VERSION and/or
# ANDROID_SDK_VERSION arguments.
#
# All supported arguments are:
# PACKAGE_DIR: where the packaged version of the library is (relative to the caller working directory).
# This parameter is mandatory.
# ROOT_DIR: where the gamesdk directory is located (relative to the caller working directory), when building from sources.
# This must be specified if DO_LOCAL_BUILD is set
# LIBRARIES: the library names to build, when building from sources.
# This must be specified if DO_LOCAL_BUILD is set.
# DO_LOCAL_BUILD: whether to add a custom build command to build the gamesdk (ON/OFF).
# default value: OFF
# ANDROID_NDK_VERSION: override the version number for the NDK (major.minor.patch).
# It's recommended to use `ndkVersion` in your build.gradle file if you want to use a specific NDK.
# default value: derived from ANDROID_NDK_REVISION.
# ANDROID_API_LEVEL: override the android API level.
# It's recommended to change the SDK version in your build.gradle to use a specific SDK.
# default value: derived from ANDROID_PLATFORM.
# BUILD_TYPE: type of Game SDK build libraries to use. Can be "Release" or "Debug".
# default value: Release
function(add_gamesdk_target)
set(options DO_LOCAL_BUILD)
set(oneValueArgs LIBRARIES PACKAGE_DIR ROOT_DIR ANDROID_NDK_VERSION ANDROID_API_LEVEL BUILD_TYPE)
cmake_parse_arguments(GAMESDK "${options}" "${oneValueArgs}" "" ${ARGN} )
# Make sanity checks to avoid hard to debug errors at compile/link time.
if(NOT DEFINED GAMESDK_PACKAGE_DIR)
message(FATAL_ERROR "You must specify PACKAGE_DIR when calling add_gamesdk_target. It should be the folder where gamesdk is extracted (or where packages should be built, if compiling from sources).")
endif()
get_filename_component(INCLUDE_FULL_PATH "${GAMESDK_PACKAGE_DIR}/include/" REALPATH)
if (NOT GAMESDK_DO_LOCAL_BUILD AND NOT EXISTS ${INCLUDE_FULL_PATH})
message(FATAL_ERROR "Can't find the gamesdk includes in ${INCLUDE_FULL_PATH}. Are you sure you properly set up the path to the Game SDK using GAMESDK_PACKAGE_DIR?")
endif()
if (GAMESDK_DO_LOCAL_BUILD AND NOT DEFINED GAMESDK_LIBRARIES)
message(FATAL_ERROR "You specified DO_LOCAL_BUILD to build the game sdk from sources, but did not specified the libraries to build with LIBRARIES.")
endif()
if(GAMESDK_DO_LOCAL_BUILD AND NOT DEFINED GAMESDK_ROOT_DIR)
message(FATAL_ERROR "You specified DO_LOCAL_BUILD to build the game sdk from sources, but did not specified the gamesdk root folder with ROOT_DIR (used to run Gradle).")
endif()
if(NOT DEFINED GAMESDK_BUILD_TYPE)
set(GAMESDK_BUILD_TYPE "${CMAKE_BUILD_TYPE}")
endif()
# Infer Android SDK/NDK and STL versions
if (NOT DEFINED GAMESDK_ANDROID_NDK_VERSION)
set(GAMESDK_ANDROID_NDK_VERSION ${ANDROID_NDK_REVISION})
endif()
string(REGEX REPLACE "^([^.]+).*" "\\1" GAMESDK_ANDROID_NDK_MAJOR_VERSION ${GAMESDK_ANDROID_NDK_VERSION} ) # Get NDK major version
if (NOT DEFINED GAMESDK_ANDROID_API_LEVEL)
string(REGEX REPLACE "^android-([^.]+)" "\\1" GAMESDK_ANDROID_API_LEVEL ${ANDROID_PLATFORM} )
endif()
string(REPLACE "+" "p" GAMESDK_ANDROID_STL ${ANDROID_STL}) # Game SDK build names use a sanitized STL name (c++ => cpp)
# Set up the "gamesdk" libraries
set(BUILD_NAME ${ANDROID_ABI}_API${GAMESDK_ANDROID_API_LEVEL}_NDK${GAMESDK_ANDROID_NDK_MAJOR_VERSION}_${GAMESDK_ANDROID_STL}_${GAMESDK_BUILD_TYPE})
set(GAMESDK_LIBS_DIR "${GAMESDK_PACKAGE_DIR}/libs/${BUILD_NAME}")
include_directories( "${GAMESDK_PACKAGE_DIR}/include" ) # Games SDK Public Includes
get_filename_component(SWAPPY_DEP_LIB "${GAMESDK_LIBS_DIR}/libswappy_static.a" REALPATH)
get_filename_component(TUNINGFORK_DEP_LIB "${GAMESDK_LIBS_DIR}/libtuningfork_static.a" REALPATH)
get_filename_component(MEMORY_ADVICE_DEP_LIB "${GAMESDK_LIBS_DIR}/libmemory_advice_static.a" REALPATH)
get_filename_component(OBOE_DEP_LIB "${GAMESDK_LIBS_DIR}/liboboe_static.a" REALPATH)
add_library(swappy STATIC IMPORTED GLOBAL)
add_library(tuningfork STATIC IMPORTED GLOBAL)
add_library(memory_advice STATIC IMPORTED GLOBAL)
add_library(oboe STATIC IMPORTED GLOBAL)
if(GAMESDK_DO_LOCAL_BUILD)
# Get the absolute path for the root dir, otherwise it can't be used as a working directory for commands.
get_filename_component(GAMESDK_ROOT_DIR "${GAMESDK_ROOT_DIR}" REALPATH)
# If building from a project containing local.properties, generated by Android Studio with
# the local Android SDK and NDK paths, copy it to gamesdk to allow it to build with the local
# toolchain.
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../local.properties")
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/../local.properties
DESTINATION ${GAMESDK_ROOT_DIR})
endif()
# Build Game SDK (Gradle will use local.properties to find the Android SDK/NDK,
# or the environment variables if no local.properties - i.e: if compiling from command line).
if (${CMAKE_HOST_SYSTEM_NAME} MATCHES "Windows")
file(TO_NATIVE_PATH "${GAMESDK_ROOT_DIR}/gradlew.bat" GAMESDK_GRADLE_BIN)
else()
file(TO_NATIVE_PATH "${GAMESDK_ROOT_DIR}/gradlew" GAMESDK_GRADLE_BIN)
endif()
add_custom_command(
OUTPUT
${SWAPPY_DEP_LIB} ${TUNINGFORK_DEP_LIB} ${MEMORY_ADVICE_DEP_LIB} ${OBOE_DEP_LIB}
COMMAND
${GAMESDK_GRADLE_BIN} buildLocal -Plibraries=${GAMESDK_LIBRARIES} -PandroidApiLevel=${GAMESDK_ANDROID_API_LEVEL} -PbuildType=${GAMESDK_BUILD_TYPE} -PpackageName=local -Pndk=${GAMESDK_ANDROID_NDK_VERSION}
VERBATIM
WORKING_DIRECTORY
"${GAMESDK_ROOT_DIR}"
)
add_custom_target(swappy_lib DEPENDS ${SWAPPY_DEP_LIB})
add_custom_target(tuningfork_lib DEPENDS ${TUNINGFORK_DEP_LIB})
add_custom_target(memory_advice_lib DEPENDS ${MEMORY_ADVICE_DEP_LIB})
add_custom_target(oboe_lib DEPENDS ${OBOE_DEP_LIB})
add_dependencies(swappy swappy_lib)
add_dependencies(tuningfork tuningfork_lib)
add_dependencies(memory_advice memory_advice_lib)
add_dependencies(oboe oboe_lib)
else()
# Validity check to ensure that the library files exist
if(NOT EXISTS ${SWAPPY_DEP_LIB} AND
NOT EXISTS ${TUNINGFORK_DEP_LIB} AND
NOT EXISTS ${MEMORY_ADVICE_DEP_LIB} AND
NOT EXISTS ${OBOE_DEP_LIB})
message(FATAL_ERROR "Can't find any library in \"${GAMESDK_LIBS_DIR}\". Are you sure you are using a supported Android SDK/NDK and STL variant?")
endif()
endif()
# Set targets to use the libraries (see https://gitlab.kitware.com/cmake/community/wikis/doc/tutorials/Exporting-and-Importing-Targets)
set_target_properties(swappy PROPERTIES IMPORTED_LOCATION ${SWAPPY_DEP_LIB})
set_target_properties(tuningfork PROPERTIES IMPORTED_LOCATION ${TUNINGFORK_DEP_LIB})
set_target_properties(memory_advice PROPERTIES IMPORTED_LOCATION ${MEMORY_ADVICE_DEP_LIB})
set_target_properties(oboe PROPERTIES IMPORTED_LOCATION ${OBOE_DEP_LIB})
endfunction()
# Use this function in addition to add_gamesdk_target, to integrate GameSDK
# sources to your project - allowing the IDE to provide autocompletions and
# debugging.
function(add_gamesdk_sources)
add_subdirectory("${_MY_DIR}/../src")
endfunction()

View File

@ -0,0 +1,565 @@
/*
* 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.
}
/**
* 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() const = 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() const {
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
*/
virtual int32_t getFramesPerBurst() = 0;
/**
* 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.
*
* @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.
*
* @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.
*
* @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;
}
/**
* Launch a thread that will stop the stream.
*/
void launchStopThread();
/**
* 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.
*
* @param numFrames 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;
}
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;
}
/*
* 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;
private:
// Log the scheduler if it changes.
void checkScheduler();
int mPreviousScheduler = -1;
std::atomic<bool> mDataCallbackEnabled{false};
std::atomic<bool> mErrorCallbackCalled{false};
};
/**
* 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_ */

View File

@ -0,0 +1,260 @@
/*
* 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 "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 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;
}
protected:
/** The callback which will be fired when new data is ready to be read/written. **/
AudioStreamDataCallback *mDataCallback = nullptr;
/** The callback which will be fired when an error or a disconnect occurs. **/
AudioStreamErrorCallback *mErrorCallback = nullptr;
/** 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;
/**
* Number of frames which will be copied to/from the audio device in a single read/write
* operation
*/
int32_t mFramesPerBurst = kUnspecified;
/** 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;
// 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:
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_ */

View File

@ -0,0 +1,485 @@
/*
* 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 "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.
*/
AudioStreamBuilder *setChannelCount(int channelCount) {
mChannelCount = channelCount;
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.
*
* @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.
*
* On Android, for example, the ID could be obtained from the Java AudioManager.
* AudioManager.getDevices() returns an array of AudioDeviceInfo[], which contains
* a getId() method (as well as other type information), that should be passed
* to this method.
*
*
* 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;
}
/**
* 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>
*
* @param dataCallback
* @return pointer to the builder so calls can be chained
*/
AudioStreamBuilder *setDataCallback(oboe::AudioStreamDataCallback *dataCallback) {
mDataCallback = dataCallback;
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>
*
* @param errorCallback
* @return pointer to the builder so calls can be chained
*/
AudioStreamBuilder *setErrorCallback(oboe::AudioStreamErrorCallback *errorCallback) {
mErrorCallback = errorCallback;
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>
*
* When an error callback occurs, the associated stream will be stopped and closed in a separate thread.
*
* A note on why the streamCallback parameter is a raw pointer rather than a smart pointer:
*
* The caller should retain ownership of the object streamCallback points to. At first glance weak_ptr may seem like
* a good candidate for streamCallback as this implies temporary ownership. However, a weak_ptr can only be created
* from a shared_ptr. A shared_ptr incurs some performance overhead. The callback object is likely to be accessed
* every few milliseconds when the stream requires new data so this overhead is something we want to avoid.
*
* This leaves a raw pointer as the logical type choice. The only caveat being that the caller must not destroy
* the callback before the stream has been closed.
*
* @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 true.
*/
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 true.
*/
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;
}
/**
* @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.
*
* @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.
* @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_ */

View File

@ -0,0 +1,189 @@
/*
* 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.
*
* 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

View File

@ -0,0 +1,519 @@
/*
* 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.
*/
Unspecified = 0, // AAUDIO_FORMAT_UNSPECIFIED,
/**
* Signed 16-bit integers.
*/
I16 = 1, // AAUDIO_FORMAT_PCM_I16,
/**
* Single precision floating points.
*/
Float = 2, // AAUDIO_FORMAT_PCM_FLOAT,
};
/**
* 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,
};
/**
* 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.
*/
OpenSLES,
/**
* Try to use AAudio. Fail if unavailable.
*/
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,
Medium,
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,
};
/**
* 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

View File

@ -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_

View File

@ -0,0 +1,37 @@
/*
* 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"
#endif //OBOE_OBOE_H

View File

@ -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

View File

@ -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__)
#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

View File

@ -0,0 +1,87 @@
/*
* 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();
} // namespace oboe
#endif //OBOE_UTILITIES_H

View File

@ -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 5
// 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

View File

@ -50,7 +50,7 @@ u8* ICache;
void (*EntryPoints[ARAM_SIZE_MAX / 4])();
#ifdef _WIN32
alignas(4096) static u8 ARM7_TCB[ICacheSize];
static u8 *ARM7_TCB;
#elif defined(__unix__) || defined(__SWITCH__)
alignas(4096) static u8 ARM7_TCB[ICacheSize] __attribute__((section(".text")));
#elif defined(__APPLE__)

View File

@ -27,6 +27,7 @@
#include <aarch64/macro-assembler-aarch64.h>
using namespace vixl::aarch64;
//#include <aarch32/disasm-aarch32.h>
#include "rec-ARM64/arm64_unwind.h"
namespace aicaarm {
@ -36,6 +37,8 @@ class Arm7Compiler;
#define MAX_REGS 8
static Arm64UnwindInfo unwinder;
class AArch64ArmRegAlloc : public ArmRegAlloc<MAX_REGS, AArch64ArmRegAlloc>
{
Arm7Compiler& assembler;
@ -636,6 +639,9 @@ public:
Label arm_dofiq;
Label arm_exit;
// For stack unwinding purposes, we pretend that the entire code block is a single function
unwinder.start(GetCursorAddress<void *>());
// arm_compilecode:
arm_compilecode = (void (*)())recompiler::writeToExec(GetCursorAddress<void *>());
call((void*)recompiler::compile);
@ -643,12 +649,26 @@ public:
// arm_mainloop(regs, entry points)
arm_mainloop = (arm_mainloop_t)recompiler::writeToExec(GetCursorAddress<void *>());
Stp(x25, x26, MemOperand(sp, -96, AddrMode::PreIndex));
unwinder.allocStack(0, 96);
unwinder.saveReg(0, x25, 96);
unwinder.saveReg(0, x26, 88);
Stp(x27, x28, MemOperand(sp, 16));
unwinder.saveReg(0, x27, 80);
unwinder.saveReg(0, x28, 72);
Stp(x29, x30, MemOperand(sp, 32));
unwinder.saveReg(0, x29, 64);
unwinder.saveReg(0, x30, 56);
Stp(x19, x20, MemOperand(sp, 48));
unwinder.saveReg(0, x19, 48);
unwinder.saveReg(0, x20, 40);
Stp(x21, x22, MemOperand(sp, 64));
unwinder.saveReg(0, x21, 32);
unwinder.saveReg(0, x22, 24);
Stp(x23, x24, MemOperand(sp, 80));
unwinder.saveReg(0, x23, 16);
unwinder.saveReg(0, x24, 8);
Mov(x28, x0); // arm7 registers
Mov(x26, x1); // lookup base
@ -682,6 +702,10 @@ public:
Ret();
FinalizeCode();
size_t unwindSize = unwinder.end(recompiler::spaceLeft() - 128, (ptrdiff_t)recompiler::writeToExec(nullptr));
verify(unwindSize <= 128);
vmem_platform_flush_cache(
recompiler::writeToExec(GetBuffer()->GetStartAddress<void*>()), recompiler::writeToExec(GetBuffer()->GetEndAddress<void*>()),
GetBuffer()->GetStartAddress<void*>(), GetBuffer()->GetEndAddress<void*>());
@ -707,6 +731,7 @@ void arm7backend_compile(const std::vector<ArmOp>& block_ops, u32 cycles)
void arm7backend_flush()
{
unwinder.clear();
Arm7Compiler assembler;
assembler.generateMainLoop();
}

File diff suppressed because it is too large Load Diff

View File

@ -20,10 +20,6 @@
// Based on Asmjit unwind info registration and stack walking code for Windows, Linux and macOS
// https://gist.github.com/dpjudas/925d5c4ffef90bd8114be3b465069fff
#include "build.h"
#if HOST_CPU == CPU_X64
#include <xbyak/xbyak.h>
#include "oslib/oslib.h"
extern "C"
@ -32,6 +28,8 @@ extern "C"
void __deregister_frame(const void*);
}
#if HOST_CPU == CPU_X64
constexpr int dwarfRegId[16] = {
0, // RAX
2, // RCX
@ -50,8 +48,30 @@ constexpr int dwarfRegId[16] = {
14, // R14
15, // R15
};
inline static int registerId(int x64Id) {
return dwarfRegId[x64Id];
}
constexpr int dwarfRegRAId = 16;
constexpr int dwarfRegXmmId = 17;
constexpr int dwarfRegSP = dwarfRegId[4]; // RSP
#elif HOST_CPU == CPU_ARM64
// https://developer.arm.com/documentation/ihi0057/latest
//
// register codes:
// x0-x30 0 - 30
// SP 31
// d0-d31 64 - 95
inline static int registerId(int armId) {
return armId;
}
constexpr int dwarfRegRAId = 30;
constexpr int dwarfRegSP = 31;
#endif
#if HOST_CPU == CPU_X64 || HOST_CPU == CPU_ARM64
using ByteStream = std::vector<u8>;
@ -86,26 +106,16 @@ static void writeULEB128(ByteStream &stream, u32 v)
static void writeSLEB128(ByteStream &stream, int32_t v)
{
if (v >= 0)
{
writeULEB128(stream, v);
}
else
{
while (true)
{
if (v > -128)
{
write<u8>(stream, v & 0x7f);
break;
}
else
{
write<u8>(stream, v);
bool more;
do {
u8 byte = v & 0x7f;
v >>= 7;
}
}
}
more = !(((v == 0 && (byte & 0x40) == 0) ||
(v == -1 && (byte & 0x40) != 0)));
if (more)
byte |= 0x80; // Mark this byte to show that more bytes will follow.
write<u8>(stream, byte);
} while (more);
}
static void writePadding(ByteStream &stream)
@ -129,8 +139,8 @@ static void writeCIE(ByteStream &stream, const ByteStream &cieInstructions, u8 r
write<u8>(stream, 'z');
write<u8>(stream, 'R'); // fde encoding
write<u8>(stream, 0);
writeULEB128(stream, 1);
writeSLEB128(stream, -1);
writeULEB128(stream, 1); // code alignment. Loc multiplier
writeSLEB128(stream, -1); // data alignment. Stack offset multiplier.
writeULEB128(stream, returnAddressReg);
writeULEB128(stream, 1); // LEB128 augmentation size
@ -164,6 +174,8 @@ static void writeFDE(ByteStream &stream, const ByteStream &fdeInstructions, u32
static void writeAdvanceLoc(ByteStream &fdeInstructions, u64 offset, u64 &lastOffset)
{
u64 delta = offset - lastOffset;
if (delta == 0)
return;
if (delta < (1 << 6))
{
write<u8>(fdeInstructions, (1 << 6) | delta); // DW_CFA_advance_loc
@ -205,13 +217,27 @@ static void writeRegisterStackLocation(ByteStream &instructions, int dwarfRegId,
writeULEB128(instructions, stackLocation);
}
void UnwindInfo::start(void *address) {
static void writeRegisterStackLocationExtended(ByteStream &instructions, int dwarfRegId, int stackLocation)
{
write<u8>(instructions, 5); // DW_CFA_offset_extended
writeULEB128(instructions, dwarfRegId);
writeULEB128(instructions, stackLocation);
}
void UnwindInfo::start(void *address)
{
startAddr = (u8 *)address;
#if HOST_CPU == CPU_X64
stackOffset = 8;
#else
stackOffset = 0;
#endif
lastOffset = 0;
cieInstructions.clear();
fdeInstructions.clear();
writeDefineCFA(cieInstructions, dwarfRegId[Xbyak::Operand::RSP], stackOffset);
writeDefineCFA(cieInstructions, dwarfRegSP, stackOffset);
if (stackOffset > 0)
// Return address pushed on stack
writeRegisterStackLocation(cieInstructions, dwarfRegRAId, stackOffset);
}
@ -220,12 +246,19 @@ void UnwindInfo::pushReg(u32 offset, int reg)
stackOffset += 8;
writeAdvanceLoc(fdeInstructions, offset, lastOffset);
writeDefineStackOffset(fdeInstructions, stackOffset);
writeRegisterStackLocation(fdeInstructions, dwarfRegId[reg], stackOffset);
writeRegisterStackLocation(fdeInstructions, registerId(reg), stackOffset);
}
void UnwindInfo::pushFPReg(u32 offset, int reg)
void UnwindInfo::saveReg(u32 offset, int reg, int stackOffset)
{
// TODO
writeAdvanceLoc(fdeInstructions, offset, lastOffset);
writeRegisterStackLocation(fdeInstructions, registerId(reg), stackOffset);
}
void UnwindInfo::saveExtReg(u32 offset, int reg, int stackOffset)
{
writeAdvanceLoc(fdeInstructions, offset, lastOffset);
writeRegisterStackLocationExtended(fdeInstructions, registerId(reg), stackOffset);
}
void UnwindInfo::allocStack(u32 offset, int size)
@ -239,7 +272,7 @@ void UnwindInfo::endProlog(u32 offset)
{
}
size_t UnwindInfo::end(u32 offset)
size_t UnwindInfo::end(u32 offset, ptrdiff_t rwRxOffset)
{
ByteStream unwindInfo;
writeCIE(unwindInfo, cieInstructions, dwarfRegRAId);
@ -257,7 +290,7 @@ size_t UnwindInfo::end(u32 offset)
if (!unwindInfo.empty())
{
u64 *unwindfuncaddr = (u64 *)(unwindInfoDest + functionStart);
unwindfuncaddr[0] = (ptrdiff_t)startAddr;
unwindfuncaddr[0] = (uintptr_t)startAddr + rwRxOffset;
unwindfuncaddr[1] = (ptrdiff_t)(endAddr - startAddr);
#ifdef __APPLE__
@ -313,5 +346,4 @@ void UnwindInfo::clear()
registeredFrames.clear();
}
#endif // HOST_CPU == CPU_X64
#endif

View File

@ -60,10 +60,11 @@ class UnwindInfo
public:
void start(void *address);
void pushReg(u32 offset, int reg);
void pushFPReg(u32 offset, int reg);
void saveReg(u32 offset, int reg, int stackOffset);
void saveExtReg(u32 offset, int reg, int stackOffset);
void allocStack(u32 offset, int size);
void endProlog(u32 offset);
size_t end(u32 offset);
size_t end(u32 offset, ptrdiff_t rwRxOffset = 0);
void clear();
@ -73,7 +74,7 @@ private:
std::vector<RUNTIME_FUNCTION *> tables;
std::vector<u16> codes;
#endif
#if defined(__unix__) || defined(__APPLE__)
#if defined(__unix__) || defined(__APPLE__) || defined(__SWITCH__)
int stackOffset = 0;
u64 lastOffset = 0;
std::vector<u8> cieInstructions;
@ -82,18 +83,20 @@ private:
#endif
};
#if !defined(_WIN64) && !defined(__unix__) && !defined(__APPLE__)
#if HOST_CPU != CPU_X64 && HOST_CPU != CPU_ARM64
inline void UnwindInfo::start(void *address) {
}
inline void UnwindInfo::pushReg(u32 offset, int reg) {
}
inline void UnwindInfo::pushFPReg(u32 offset, int reg) {
inline void UnwindInfo::saveReg(u32 offset, int reg, int stackOffset) {
}
inline void UnwindInfo::saveExtReg(u32 offset, int reg, int stackOffset) {
}
inline void UnwindInfo::allocStack(u32 offset, int size) {
}
inline void UnwindInfo::endProlog(u32 offset) {
}
inline size_t UnwindInfo::end(u32 offset) {
inline size_t UnwindInfo::end(u32 offset, ptrdiff_t rwRxOffset) {
return 0;
}
inline void UnwindInfo::clear() {

View File

@ -16,15 +16,8 @@
You should have received a copy of the GNU General Public License
along with reicast. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef CORE_REC_ARM64_ARM64_REGALLOC_H_
#define CORE_REC_ARM64_ARM64_REGALLOC_H_
#ifdef OLD_REGALLOC
#include "hw/sh4/dyna/regalloc.h"
#else
#pragma once
#include "hw/sh4/dyna/ssa_regalloc.h"
#endif
#include <aarch64/macro-assembler-aarch64.h>
using namespace vixl::aarch64;
@ -64,14 +57,9 @@ struct Arm64RegAlloc : RegAlloc<eReg, eFReg>
return Register::GetWRegFromCode(ereg);
}
const VRegister& MapVRegister(const shil_param& param, u32 index = 0)
const VRegister& MapVRegister(const shil_param& param)
{
#ifdef OLD_REGALLOC
eFReg ereg = mapfv(param, index);
#else
verify(index == 0);
eFReg ereg = mapf(param);
#endif
if (ereg == (eFReg)-1)
die("VRegister not allocated");
return VRegister::GetSRegFromCode(ereg);
@ -79,5 +67,3 @@ struct Arm64RegAlloc : RegAlloc<eReg, eFReg>
Arm64Assembler *assembler;
};
#endif /* CORE_REC_ARM64_ARM64_REGALLOC_H_ */

View File

@ -0,0 +1,43 @@
/*
Copyright 2021 flyinghead
This file is part of Flycast.
Flycast is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
Flycast is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "oslib/oslib.h"
#include <aarch64/macro-assembler-aarch64.h>
using namespace vixl::aarch64;
class Arm64UnwindInfo : public UnwindInfo
{
public:
void saveReg(u32 offset, const Register& reg, int stackOffset) {
UnwindInfo::saveReg(offset, dwarfRegisterId(reg), stackOffset);
}
void saveReg(u32 offset, const VRegister& reg, int stackOffset) {
UnwindInfo::saveExtReg(offset, dwarfRegisterId(reg), stackOffset);
}
private:
int dwarfRegisterId(const CPURegister& reg)
{
verify(reg.Is64Bits());
if (reg.IsFPRegister())
return reg.GetCode() + 64;
else
return reg.GetCode();
}
};

View File

@ -39,6 +39,7 @@ using namespace vixl::aarch64;
#include "hw/sh4/sh4_rom.h"
#include "arm64_regalloc.h"
#include "hw/mem/_vmem.h"
#include "arm64_unwind.h"
#undef do_sqw_nommu
@ -55,6 +56,7 @@ struct DynaRBI : RuntimeBlockInfo
static u32 cycle_counter;
static u64 jmp_stack;
static Arm64UnwindInfo unwinder;
static void (*mainloop)(void *context);
static void (*handleException)();
@ -91,6 +93,7 @@ void ngen_init()
void ngen_ResetBlocks()
{
unwinder.clear();
mainloop = nullptr;
if (p_sh4rcb->cntx.CpuRunning)
@ -1334,18 +1337,43 @@ public:
// void mainloop(void *context)
mainloop = (void (*)(void *))CC_RW2RX(GetCursorAddress<uintptr_t>());
// For stack unwinding purposes, we pretend that the entire code block is just one function, with the same
// unwinding instructions everywhere. This isn't true until the end of the following prolog, but exceptions
// can only be thrown by called functions so this is good enough.
unwinder.start(CodeCache);
// Save registers
Stp(x19, x20, MemOperand(sp, -160, PreIndex));
unwinder.allocStack(0, 160);
unwinder.saveReg(0, x19, 160);
unwinder.saveReg(0, x20, 152);
Stp(x21, x22, MemOperand(sp, 16));
unwinder.saveReg(0, x21, 144);
unwinder.saveReg(0, x22, 136);
Stp(x23, x24, MemOperand(sp, 32));
unwinder.saveReg(0, x23, 128);
unwinder.saveReg(0, x24, 120);
Stp(x25, x26, MemOperand(sp, 48));
unwinder.saveReg(0, x25, 112);
unwinder.saveReg(0, x26, 104);
Stp(x27, x28, MemOperand(sp, 64));
Stp(s14, s15, MemOperand(sp, 80));
Stp(vixl::aarch64::s8, s9, MemOperand(sp, 96));
Stp(s10, s11, MemOperand(sp, 112));
Stp(s12, s13, MemOperand(sp, 128));
unwinder.saveReg(0, x27, 96);
unwinder.saveReg(0, x28, 88);
Stp(d14, d15, MemOperand(sp, 80));
unwinder.saveReg(0, d14, 80);
unwinder.saveReg(0, d15, 72);
Stp(d8, d9, MemOperand(sp, 96));
unwinder.saveReg(0, d8, 64);
unwinder.saveReg(0, d9, 56);
Stp(d10, d11, MemOperand(sp, 112));
unwinder.saveReg(0, d10, 48);
unwinder.saveReg(0, d11, 40);
Stp(d12, d13, MemOperand(sp, 128));
unwinder.saveReg(0, d12, 32);
unwinder.saveReg(0, d13, 24);
Stp(x29, x30, MemOperand(sp, 144));
unwinder.saveReg(0, x29, 16);
unwinder.saveReg(0, x30, 8);
Sub(x0, x0, sizeof(Sh4Context));
Label reenterLabel;
@ -1354,6 +1382,7 @@ public:
Ldr(x1, reinterpret_cast<uintptr_t>(&cycle_counter));
// Push context, cycle_counter address
Stp(x0, x1, MemOperand(sp, -16, PreIndex));
unwinder.allocStack(0, 16);
Mov(w0, SH4_TIMESLICE);
Str(w0, MemOperand(x1));
@ -1412,10 +1441,10 @@ public:
Add(sp, sp, 16);
// Restore registers
Ldp(x29, x30, MemOperand(sp, 144));
Ldp(s12, s13, MemOperand(sp, 128));
Ldp(s10, s11, MemOperand(sp, 112));
Ldp(vixl::aarch64::s8, s9, MemOperand(sp, 96));
Ldp(s14, s15, MemOperand(sp, 80));
Ldp(d12, d13, MemOperand(sp, 128));
Ldp(d10, d11, MemOperand(sp, 112));
Ldp(d8, d9, MemOperand(sp, 96));
Ldp(d14, d15, MemOperand(sp, 80));
Ldp(x27, x28, MemOperand(sp, 64));
Ldp(x25, x26, MemOperand(sp, 48));
Ldp(x23, x24, MemOperand(sp, 32));
@ -1489,6 +1518,9 @@ public:
FinalizeCode();
emit_Skip(GetBuffer()->GetSizeInBytes());
size_t unwindSize = unwinder.end(CODE_SIZE - 128, (ptrdiff_t)CC_RW2RX(0));
verify(unwindSize <= 128);
arm64_no_update = GetLabelAddress<DynaCode *>(&no_update);
handleException = (void (*)())CC_RW2RX(GetLabelAddress<uintptr_t>(&handleExceptionLabel));
writeStoreQueue32 = GetLabelAddress<DynaCode *>(&writeStoreQueue32Label);

View File

@ -8,7 +8,6 @@
#include "hw/sh4/sh4_core.h"
#include "hw/sh4/dyna/ngen.h"
#include "hw/sh4/sh4_mem.h"
#include "hw/sh4/dyna/regalloc.h"
#define SHIL_MODE 2
#include "hw/sh4/dyna/shil_canonical.h"

View File

@ -18,14 +18,8 @@
*/
#pragma once
//#define OLD_REGALLOC
#include <xbyak/xbyak.h>
#ifdef OLD_REGALLOC
#include "hw/sh4/dyna/regalloc.h"
#else
#include "hw/sh4/dyna/ssa_regalloc.h"
#endif
#ifdef _WIN32
static Xbyak::Operand::Code alloc_regs[] = { Xbyak::Operand::RBX, Xbyak::Operand::RBP, Xbyak::Operand::RDI, Xbyak::Operand::RSI,
@ -61,13 +55,9 @@ struct X64RegAlloc : RegAlloc<Xbyak::Operand::Code, s8>
return Xbyak::Reg32(ereg);
}
Xbyak::Xmm MapXRegister(const shil_param& param, u32 index = 0)
Xbyak::Xmm MapXRegister(const shil_param& param)
{
#ifdef OLD_REGALLOC
s8 ereg = mapfv(param, index);
#else
s8 ereg = mapf(param);
#endif
if (ereg == -1)
die("VRegister not allocated");
return Xbyak::Xmm(ereg);
@ -75,16 +65,7 @@ struct X64RegAlloc : RegAlloc<Xbyak::Operand::Code, s8>
bool IsMapped(const Xbyak::Xmm &xmm, size_t opid)
{
#ifndef OLD_REGALLOC
return regf_used((s8)xmm.getIdx());
#else
for (size_t sid = 0; sid < all_spans.size(); sid++)
{
if (all_spans[sid]->nregf == xmm.getIdx() && all_spans[sid]->contains(opid))
return true;
}
return false;
#endif
}
BlockCompiler *compiler;

View File

@ -17,14 +17,7 @@
along with Flycast. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
//#define OLD_REGALLOC
#ifdef OLD_REGALLOC
#include "hw/sh4/dyna/regalloc.h"
#else
#include "hw/sh4/dyna/ssa_regalloc.h"
#endif
class X86Compiler;
@ -47,13 +40,9 @@ struct X86RegAlloc : RegAlloc<Xbyak::Operand::Code, s8>
return Xbyak::Reg32(ereg);
}
Xbyak::Xmm MapXRegister(const shil_param& param, u32 index = 0)
Xbyak::Xmm MapXRegister(const shil_param& param)
{
#ifdef OLD_REGALLOC
s8 ereg = mapfv(param, index);
#else
s8 ereg = mapf(param);
#endif
if (ereg == -1)
die("VRegister not allocated");
return Xbyak::Xmm(ereg);
@ -61,16 +50,7 @@ struct X86RegAlloc : RegAlloc<Xbyak::Operand::Code, s8>
bool IsMapped(const Xbyak::Xmm &xmm, size_t opid)
{
#ifndef OLD_REGALLOC
return regf_used((s8)xmm.getIdx());
#else
for (size_t sid = 0; sid < all_spans.size(); sid++)
{
if (all_spans[sid]->nregf == xmm.getIdx() && all_spans[sid]->contains(opid))
return true;
}
return false;
#endif
}
X86Compiler *compiler;

View File

@ -349,6 +349,7 @@ void do_swap_automation()
glBindFramebuffer(GL_READ_FRAMEBUFFER, gl.ofbo.fbo);
glReadPixels(0, 0, gl.ofbo.width, gl.ofbo.height, GL_RGB, GL_UNSIGNED_BYTE, img);
fwrite(img, 1, bytesz, video_file);
delete[] img;
fflush(video_file);
}

View File

@ -46,12 +46,6 @@ void UnwindInfo::pushReg(u32 offset, int reg)
codes.push_back(offset | (UWOP_PUSH_NONVOL << 8) | (reg << 12));
}
void UnwindInfo::pushFPReg(u32 offset, int reg)
{
//codes.push_back(offset | (UWOP_SAVE_XMM128 << 8) | (reg << 12));
die("not implemented");
}
void UnwindInfo::allocStack(u32 offset, int size)
{
verify(size <= 128);
@ -70,7 +64,7 @@ void UnwindInfo::endProlog(u32 offset)
codes.push_back(0);
}
size_t UnwindInfo::end(u32 offset)
size_t UnwindInfo::end(u32 offset, ptrdiff_t rwRxOffset)
{
u8 *endAddr = startAddr + offset;
if ((uintptr_t)endAddr & 3)

View File

@ -36,7 +36,7 @@ android {
externalNativeBuild {
cmake {
arguments "-DANDROID_STL=c++_shared", "-DANDROID_ARM_MODE=arm"
arguments "-DANDROID_ARM_MODE=arm"
}
}