git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce

subrepo:
  subdir:   "deps/juce"
  merged:   "b13f9084e"
upstream:
  origin:   "https://github.com/essej/JUCE.git"
  branch:   "sono6good"
  commit:   "b13f9084e"
git-subrepo:
  version:  "0.4.3"
  origin:   "https://github.com/ingydotnet/git-subrepo.git"
  commit:   "2f68596"
This commit is contained in:
essej
2022-04-18 17:51:22 -04:00
parent 63e175fee6
commit 25bd5d8adb
3210 changed files with 1045392 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,561 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Manages the state of some audio and midi i/o devices.
This class keeps tracks of a currently-selected audio device, through
with which it continuously streams data from an audio callback, as well as
one or more midi inputs.
The idea is that your application will create one global instance of this object,
and let it take care of creating and deleting specific types of audio devices
internally. So when the device is changed, your callbacks will just keep running
without having to worry about this.
The manager can save and reload all of its device settings as XML, which
makes it very easy for you to save and reload the audio setup of your
application.
And to make it easy to let the user change its settings, there's a component
to do just that - the AudioDeviceSelectorComponent class, which contains a set of
device selection/sample-rate/latency controls.
To use an AudioDeviceManager, create one, and use initialise() to set it up. Then
call addAudioCallback() to register your audio callback with it, and use that to process
your audio data.
The manager also acts as a handy hub for incoming midi messages, allowing a
listener to register for messages from either a specific midi device, or from whatever
the current default midi input device is. The listener then doesn't have to worry about
re-registering with different midi devices if they are changed or deleted.
And yet another neat trick is that amount of CPU time being used is measured and
available with the getCpuUsage() method.
The AudioDeviceManager is a ChangeBroadcaster, and will send a change message to
listeners whenever one of its settings is changed.
@see AudioDeviceSelectorComponent, AudioIODevice, AudioIODeviceType
@tags{Audio}
*/
class JUCE_API AudioDeviceManager : public ChangeBroadcaster
{
public:
//==============================================================================
/** Creates a default AudioDeviceManager.
Initially no audio device will be selected. You should call the initialise() method
and register an audio callback with setAudioCallback() before it'll be able to
actually make any noise.
*/
AudioDeviceManager();
/** Destructor. */
~AudioDeviceManager() override;
//==============================================================================
/**
This structure holds a set of properties describing the current audio setup.
An AudioDeviceManager uses this class to save/load its current settings, and to
specify your preferred options when opening a device.
@see AudioDeviceManager::setAudioDeviceSetup(), AudioDeviceManager::initialise()
*/
struct JUCE_API AudioDeviceSetup
{
/** The name of the audio device used for output.
The name has to be one of the ones listed by the AudioDeviceManager's currently
selected device type.
This may be the same as the input device.
*/
String outputDeviceName;
/** The name of the audio device used for input.
This may be the same as the output device.
*/
String inputDeviceName;
/** The current sample rate.
This rate is used for both the input and output devices.
A value of 0 indicates that you don't care what rate is used, and the
device will choose a sensible rate for you.
*/
double sampleRate = 0;
/** The buffer size, in samples.
This buffer size is used for both the input and output devices.
A value of 0 indicates the default buffer size.
*/
int bufferSize = 0;
/** The set of active input channels.
The bits that are set in this array indicate the channels of the
input device that are active.
If useDefaultInputChannels is true, this value is ignored.
*/
BigInteger inputChannels;
/** If this is true, it indicates that the inputChannels array
should be ignored, and instead, the device's default channels
should be used.
*/
bool useDefaultInputChannels = true;
/** The set of active output channels.
The bits that are set in this array indicate the channels of the
input device that are active.
If useDefaultOutputChannels is true, this value is ignored.
*/
BigInteger outputChannels;
/** If this is true, it indicates that the outputChannels array
should be ignored, and instead, the device's default channels
should be used.
*/
bool useDefaultOutputChannels = true;
bool operator== (const AudioDeviceSetup&) const;
bool operator!= (const AudioDeviceSetup&) const;
};
//==============================================================================
/** Opens a set of audio devices ready for use.
This will attempt to open either a default audio device, or one that was
previously saved as XML.
@param numInputChannelsNeeded the maximum number of input channels your app would like to
use (the actual number of channels opened may be less than
the number requested)
@param numOutputChannelsNeeded the maximum number of output channels your app would like to
use (the actual number of channels opened may be less than
the number requested)
@param savedState either a previously-saved state that was produced
by createStateXml(), or nullptr if you want the manager
to choose the best device to open.
@param selectDefaultDeviceOnFailure if true, then if the device specified in the XML
fails to open, then a default device will be used
instead. If false, then on failure, no device is
opened.
@param preferredDefaultDeviceName if this is not empty, and there's a device with this
name, then that will be used as the default device
(assuming that there wasn't one specified in the XML).
The string can actually be a simple wildcard, containing "*"
and "?" characters
@param preferredSetupOptions if this is non-null, the structure will be used as the
set of preferred settings when opening the device. If you
use this parameter, the preferredDefaultDeviceName
field will be ignored. If you set the outputDeviceName
or inputDeviceName data members of the AudioDeviceSetup
to empty strings, then a default device will be used.
@returns an error message if anything went wrong, or an empty string if it worked ok.
*/
String initialise (int numInputChannelsNeeded,
int numOutputChannelsNeeded,
const XmlElement* savedState,
bool selectDefaultDeviceOnFailure,
const String& preferredDefaultDeviceName = String(),
const AudioDeviceSetup* preferredSetupOptions = nullptr);
/** Resets everything to a default device setup, clearing any stored settings. */
String initialiseWithDefaultDevices (int numInputChannelsNeeded,
int numOutputChannelsNeeded);
/** Returns some XML representing the current state of the manager.
This stores the current device, its samplerate, block size, etc, and
can be restored later with initialise().
Note that this can return a null pointer if no settings have been explicitly changed
(i.e. if the device manager has just been left in its default state).
*/
std::unique_ptr<XmlElement> createStateXml() const;
//==============================================================================
/** Returns the current device properties that are in use.
@see setAudioDeviceSetup
*/
AudioDeviceSetup getAudioDeviceSetup() const;
/** Returns the current device properties that are in use.
This is an old method, kept around for compatibility, but you should prefer the new
version which returns the result rather than taking an out-parameter.
@see getAudioDeviceSetup()
*/
void getAudioDeviceSetup (AudioDeviceSetup& result) const;
/** Changes the current device or its settings.
If you want to change a device property, like the current sample rate or
block size, you can call getAudioDeviceSetup() to retrieve the current
settings, then tweak the appropriate fields in the AudioDeviceSetup structure,
and pass it back into this method to apply the new settings.
@param newSetup the settings that you'd like to use.
If you don't need an input or output device, set the
inputDeviceName or outputDeviceName data members respectively
to empty strings. Note that this behaviour differs from
the behaviour of initialise().
@param treatAsChosenDevice if this is true and if the device opens correctly, these new
settings will be taken as having been explicitly chosen by the
user, and the next time createStateXml() is called, these settings
will be returned. If it's false, then the device is treated as a
temporary or default device, and a call to createStateXml() will
return either the last settings that were made with treatAsChosenDevice
as true, or the last XML settings that were passed into initialise().
@returns an error message if anything went wrong, or an empty string if it worked ok.
@see getAudioDeviceSetup
*/
String setAudioDeviceSetup (const AudioDeviceSetup& newSetup, bool treatAsChosenDevice);
/** Returns the currently-active audio device. */
AudioIODevice* getCurrentAudioDevice() const noexcept { return currentAudioDevice.get(); }
/** Returns the type of audio device currently in use.
@see setCurrentAudioDeviceType
*/
String getCurrentAudioDeviceType() const { return currentDeviceType; }
/** Returns the currently active audio device type object.
Don't keep a copy of this pointer - it's owned by the device manager and could
change at any time.
*/
AudioIODeviceType* getCurrentDeviceTypeObject() const;
/** Changes the class of audio device being used.
This switches between, e.g. ASIO and DirectSound. On the Mac you probably won't ever call
this because there's only one type: CoreAudio.
For a list of types, see getAvailableDeviceTypes().
*/
void setCurrentAudioDeviceType (const String& type, bool treatAsChosenDevice);
/** Closes the currently-open device.
You can call restartLastAudioDevice() later to reopen it in the same state
that it was just in.
*/
void closeAudioDevice();
/** Tries to reload the last audio device that was running.
Note that this only reloads the last device that was running before
closeAudioDevice() was called - it doesn't reload any kind of saved-state,
and can only be called after a device has been opened with setAudioDeviceSetup().
If a device is already open, this call will do nothing.
*/
void restartLastAudioDevice();
//==============================================================================
/** Registers an audio callback to be used.
The manager will redirect callbacks from whatever audio device is currently
in use to all registered callback objects. If more than one callback is
active, they will all be given the same input data, and their outputs will
be summed.
If necessary, this method will invoke audioDeviceAboutToStart() on the callback
object before returning.
To remove a callback, use removeAudioCallback().
*/
void addAudioCallback (AudioIODeviceCallback* newCallback);
/** Deregisters a previously added callback.
If necessary, this method will invoke audioDeviceStopped() on the callback
object before returning.
@see addAudioCallback
*/
void removeAudioCallback (AudioIODeviceCallback* callback);
//==============================================================================
/** Returns the average proportion of available CPU being spent inside the audio callbacks.
@returns A value between 0 and 1.0 to indicate the approximate proportion of CPU
time spent in the callbacks.
*/
double getCpuUsage() const;
//==============================================================================
/** Enables or disables a midi input device.
The list of devices can be obtained with the MidiInput::getAvailableDevices() method.
Any incoming messages from enabled input devices will be forwarded on to all the
listeners that have been registered with the addMidiInputDeviceCallback() method. They
can either register for messages from a particular device, or from just the "default"
midi input.
Routing the midi input via an AudioDeviceManager means that when a listener
registers for the default midi input, this default device can be changed by the
manager without the listeners having to know about it or re-register.
It also means that a listener can stay registered for a midi input that is disabled
or not present, so that when the input is re-enabled, the listener will start
receiving messages again.
@see addMidiInputDeviceCallback, isMidiInputDeviceEnabled
*/
void setMidiInputDeviceEnabled (const String& deviceIdentifier, bool enabled);
/** Returns true if a given midi input device is being used.
@see setMidiInputDeviceEnabled
*/
bool isMidiInputDeviceEnabled (const String& deviceIdentifier) const;
/** Registers a listener for callbacks when midi events arrive from a midi input.
The device identifier can be empty to indicate that it wants to receive all incoming
events from all the enabled MIDI inputs. Or it can be the identifier of one of the
MIDI input devices if it just wants the events from that device. (see
MidiInput::getAvailableDevices() for the list of devices).
Only devices which are enabled (see the setMidiInputDeviceEnabled() method) will have their
events forwarded on to listeners.
*/
void addMidiInputDeviceCallback (const String& deviceIdentifier,
MidiInputCallback* callback);
/** Removes a listener that was previously registered with addMidiInputDeviceCallback(). */
void removeMidiInputDeviceCallback (const String& deviceIdentifier,
MidiInputCallback* callback);
//==============================================================================
/** Sets a midi output device to use as the default.
The list of devices can be obtained with the MidiOutput::getAvailableDevices() method.
The specified device will be opened automatically and can be retrieved with the
getDefaultMidiOutput() method.
Pass in an empty string to deselect all devices. For the default device, you
can use MidiOutput::getDefaultDevice().
@see getDefaultMidiOutput, getDefaultMidiOutputIdentifier
*/
void setDefaultMidiOutputDevice (const String& deviceIdentifier);
/** Returns the name of the default midi output.
@see setDefaultMidiOutputDevice, getDefaultMidiOutput
*/
const String& getDefaultMidiOutputIdentifier() const noexcept { return defaultMidiOutputDeviceInfo.identifier; }
/** Returns the current default midi output device. If no device has been selected, or the
device can't be opened, this will return nullptr.
@see getDefaultMidiOutputIdentifier
*/
MidiOutput* getDefaultMidiOutput() const noexcept { return defaultMidiOutput.get(); }
//==============================================================================
/** Returns a list of the types of device supported. */
const OwnedArray<AudioIODeviceType>& getAvailableDeviceTypes();
/** Creates a list of available types.
This will add a set of new AudioIODeviceType objects to the specified list, to
represent each available types of device.
You can override this if your app needs to do something specific, like avoid
using DirectSound devices, etc.
*/
virtual void createAudioDeviceTypes (OwnedArray<AudioIODeviceType>& types);
/** Adds a new device type to the list of types. */
void addAudioDeviceType (std::unique_ptr<AudioIODeviceType> newDeviceType);
/** Removes a previously added device type from the manager. */
void removeAudioDeviceType (AudioIODeviceType* deviceTypeToRemove);
//==============================================================================
/** Plays a beep through the current audio device.
This is here to allow the audio setup UI panels to easily include a "test"
button so that the user can check where the audio is coming from.
*/
void playTestSound();
//==============================================================================
/**
A simple reference-counted struct that holds a level-meter value that can be read
using getCurrentLevel().
This is used to ensure that the level processing code is only executed when something
holds a reference to one of these objects and will be bypassed otherwise.
@see getInputLevelGetter, getOutputLevelGetter
*/
struct LevelMeter : public ReferenceCountedObject
{
LevelMeter() noexcept;
double getCurrentLevel() const noexcept;
using Ptr = ReferenceCountedObjectPtr<LevelMeter>;
private:
friend class AudioDeviceManager;
Atomic<float> level { 0 };
void updateLevel (const float* const*, int numChannels, int numSamples) noexcept;
};
/** Returns a reference-counted object that can be used to get the current input level.
You need to store this object locally to ensure that the reference count is incremented
and decremented properly. The current input level value can be read using getCurrentLevel().
*/
LevelMeter::Ptr getInputLevelGetter() noexcept { return inputLevelGetter; }
/** Returns a reference-counted object that can be used to get the current output level.
You need to store this object locally to ensure that the reference count is incremented
and decremented properly. The current output level value can be read using getCurrentLevel().
*/
LevelMeter::Ptr getOutputLevelGetter() noexcept { return outputLevelGetter; }
//==============================================================================
/** Returns the a lock that can be used to synchronise access to the audio callback.
Obviously while this is locked, you're blocking the audio thread from running, so
it must only be used for very brief periods when absolutely necessary.
*/
CriticalSection& getAudioCallbackLock() noexcept { return audioCallbackLock; }
/** Returns the a lock that can be used to synchronise access to the midi callback.
Obviously while this is locked, you're blocking the midi system from running, so
it must only be used for very brief periods when absolutely necessary.
*/
CriticalSection& getMidiCallbackLock() noexcept { return midiCallbackLock; }
//==============================================================================
/** Returns the number of under- or over runs reported.
This method will use the underlying device's native getXRunCount if it supports
it. Otherwise it will estimate the number of under-/overruns by measuring the
time it spent in the audio callback.
*/
int getXRunCount() const noexcept;
//==============================================================================
#ifndef DOXYGEN
[[deprecated ("Use setMidiInputDeviceEnabled instead.")]]
void setMidiInputEnabled (const String&, bool);
[[deprecated ("Use isMidiInputDeviceEnabled instead.")]]
bool isMidiInputEnabled (const String&) const;
[[deprecated ("Use addMidiInputDeviceCallback instead.")]]
void addMidiInputCallback (const String&, MidiInputCallback*);
[[deprecated ("Use removeMidiInputDeviceCallback instead.")]]
void removeMidiInputCallback (const String&, MidiInputCallback*);
[[deprecated ("Use setDefaultMidiOutputDevice instead.")]]
void setDefaultMidiOutput (const String&);
[[deprecated ("Use getDefaultMidiOutputIdentifier instead.")]]
const String& getDefaultMidiOutputName() const noexcept { return defaultMidiOutputDeviceInfo.name; }
#endif
private:
//==============================================================================
OwnedArray<AudioIODeviceType> availableDeviceTypes;
OwnedArray<AudioDeviceSetup> lastDeviceTypeConfigs;
AudioDeviceSetup currentSetup;
std::unique_ptr<AudioIODevice> currentAudioDevice;
Array<AudioIODeviceCallback*> callbacks;
int numInputChansNeeded = 0, numOutputChansNeeded = 2;
String preferredDeviceName, currentDeviceType;
std::unique_ptr<XmlElement> lastExplicitSettings;
mutable bool listNeedsScanning = true;
AudioBuffer<float> tempBuffer;
struct MidiCallbackInfo
{
String deviceIdentifier;
MidiInputCallback* callback;
};
Array<MidiDeviceInfo> midiDeviceInfosFromXml;
std::vector<std::unique_ptr<MidiInput>> enabledMidiInputs;
Array<MidiCallbackInfo> midiCallbacks;
MidiDeviceInfo defaultMidiOutputDeviceInfo;
std::unique_ptr<MidiOutput> defaultMidiOutput;
CriticalSection audioCallbackLock, midiCallbackLock;
std::unique_ptr<AudioBuffer<float>> testSound;
int testSoundPosition = 0;
AudioProcessLoadMeasurer loadMeasurer;
LevelMeter::Ptr inputLevelGetter { new LevelMeter() },
outputLevelGetter { new LevelMeter() };
//==============================================================================
class CallbackHandler;
std::unique_ptr<CallbackHandler> callbackHandler;
void audioDeviceIOCallbackInt (const float** inputChannelData, int totalNumInputChannels,
float** outputChannelData, int totalNumOutputChannels, int numSamples);
void audioDeviceAboutToStartInt (AudioIODevice*);
void audioDeviceStoppedInt();
void audioDeviceErrorInt (const String&);
void handleIncomingMidiMessageInt (MidiInput*, const MidiMessage&);
void audioDeviceListChanged();
String restartDevice (int blockSizeToUse, double sampleRateToUse,
const BigInteger& ins, const BigInteger& outs);
void stopDevice();
void updateXml();
void updateCurrentSetup();
void createDeviceTypesIfNeeded();
void scanDevicesIfNeeded();
void deleteCurrentDevice();
double chooseBestSampleRate (double preferred) const;
int chooseBestBufferSize (int preferred) const;
void insertDefaultDeviceNames (AudioDeviceSetup&) const;
String initialiseDefault (const String& preferredDefaultDeviceName, const AudioDeviceSetup*);
String initialiseFromXML (const XmlElement&, bool selectDefaultDeviceOnFailure,
const String& preferredDefaultDeviceName, const AudioDeviceSetup*);
AudioIODeviceType* findType (const String& inputName, const String& outputName);
AudioIODeviceType* findType (const String& typeName);
void pickCurrentDeviceTypeWithDevices();
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioDeviceManager)
};
} // namespace juce

View File

@ -0,0 +1,45 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
AudioIODevice::AudioIODevice (const String& deviceName, const String& deviceTypeName)
: name (deviceName), typeName (deviceTypeName)
{
}
AudioIODevice::~AudioIODevice() {}
void AudioIODeviceCallback::audioDeviceError (const String&) {}
bool AudioIODevice::setAudioPreprocessingEnabled (bool) { return false; }
bool AudioIODevice::hasControlPanel() const { return false; }
int AudioIODevice::getXRunCount() const noexcept { return -1; }
bool AudioIODevice::showControlPanel()
{
jassertfalse; // this should only be called for devices which return true from
// their hasControlPanel() method.
return false;
}
} // namespace juce

View File

@ -0,0 +1,325 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
class AudioIODevice;
//==============================================================================
/**
One of these is passed to an AudioIODevice object to stream the audio data
in and out.
The AudioIODevice will repeatedly call this class's audioDeviceIOCallback()
method on its own high-priority audio thread, when it needs to send or receive
the next block of data.
@see AudioIODevice, AudioDeviceManager
@tags{Audio}
*/
class JUCE_API AudioIODeviceCallback
{
public:
/** Destructor. */
virtual ~AudioIODeviceCallback() = default;
/** Processes a block of incoming and outgoing audio data.
The subclass's implementation should use the incoming audio for whatever
purposes it needs to, and must fill all the output channels with the next
block of output data before returning.
The channel data is arranged with the same array indices as the channel name
array returned by AudioIODevice::getOutputChannelNames(), but those channels
that aren't specified in AudioIODevice::open() will have a null pointer for their
associated channel, so remember to check for this.
@param inputChannelData a set of arrays containing the audio data for each
incoming channel - this data is valid until the function
returns. There will be one channel of data for each input
channel that was enabled when the audio device was opened
(see AudioIODevice::open())
@param numInputChannels the number of pointers to channel data in the
inputChannelData array.
@param outputChannelData a set of arrays which need to be filled with the data
that should be sent to each outgoing channel of the device.
There will be one channel of data for each output channel
that was enabled when the audio device was opened (see
AudioIODevice::open())
The initial contents of the array is undefined, so the
callback function must fill all the channels with zeros if
its output is silence. Failing to do this could cause quite
an unpleasant noise!
@param numOutputChannels the number of pointers to channel data in the
outputChannelData array.
@param numSamples the number of samples in each channel of the input and
output arrays. The number of samples will depend on the
audio device's buffer size and will usually remain constant,
although this isn't guaranteed. For example, on Android,
on devices which support it, Android will chop up your audio
processing into several smaller callbacks to ensure higher audio
performance. So make sure your code can cope with reasonable
changes in the buffer size from one callback to the next.
*/
virtual void audioDeviceIOCallback (const float** inputChannelData,
int numInputChannels,
float** outputChannelData,
int numOutputChannels,
int numSamples) = 0;
/** Called to indicate that the device is about to start calling back.
This will be called just before the audio callbacks begin, either when this
callback has just been added to an audio device, or after the device has been
restarted because of a sample-rate or block-size change.
You can use this opportunity to find out the sample rate and block size
that the device is going to use by calling the AudioIODevice::getCurrentSampleRate()
and AudioIODevice::getCurrentBufferSizeSamples() on the supplied pointer.
@param device the audio IO device that will be used to drive the callback.
Note that if you're going to store this this pointer, it is
only valid until the next time that audioDeviceStopped is called.
*/
virtual void audioDeviceAboutToStart (AudioIODevice* device) = 0;
/** Called to indicate that the device has stopped. */
virtual void audioDeviceStopped() = 0;
/** This can be overridden to be told if the device generates an error while operating.
Be aware that this could be called by any thread! And not all devices perform
this callback.
*/
virtual void audioDeviceError (const String& errorMessage);
};
//==============================================================================
/**
Base class for an audio device with synchronised input and output channels.
Subclasses of this are used to implement different protocols such as DirectSound,
ASIO, CoreAudio, etc.
To create one of these, you'll need to use the AudioIODeviceType class - see the
documentation for that class for more info.
For an easier way of managing audio devices and their settings, have a look at the
AudioDeviceManager class.
@see AudioIODeviceType, AudioDeviceManager
@tags{Audio}
*/
class JUCE_API AudioIODevice
{
public:
/** Destructor. */
virtual ~AudioIODevice();
//==============================================================================
/** Returns the device's name, (as set in the constructor). */
const String& getName() const noexcept { return name; }
/** Returns the type of the device.
E.g. "CoreAudio", "ASIO", etc. - this comes from the AudioIODeviceType that created it.
*/
const String& getTypeName() const noexcept { return typeName; }
//==============================================================================
/** Returns the names of all the available output channels on this device.
To find out which of these are currently in use, call getActiveOutputChannels().
*/
virtual StringArray getOutputChannelNames() = 0;
/** Returns the names of all the available input channels on this device.
To find out which of these are currently in use, call getActiveInputChannels().
*/
virtual StringArray getInputChannelNames() = 0;
//==============================================================================
/** Returns the set of sample-rates this device supports.
@see getCurrentSampleRate
*/
virtual Array<double> getAvailableSampleRates() = 0;
/** Returns the set of buffer sizes that are available.
@see getCurrentBufferSizeSamples, getDefaultBufferSize
*/
virtual Array<int> getAvailableBufferSizes() = 0;
/** Returns the default buffer-size to use.
@returns a number of samples
@see getAvailableBufferSizes
*/
virtual int getDefaultBufferSize() = 0;
//==============================================================================
/** Tries to open the device ready to play.
@param inputChannels a BigInteger in which a set bit indicates that the corresponding
input channel should be enabled
@param outputChannels a BigInteger in which a set bit indicates that the corresponding
output channel should be enabled
@param sampleRate the sample rate to try to use - to find out which rates are
available, see getAvailableSampleRates()
@param bufferSizeSamples the size of i/o buffer to use - to find out the available buffer
sizes, see getAvailableBufferSizes()
@returns an error description if there's a problem, or an empty string if it succeeds in
opening the device
@see close
*/
virtual String open (const BigInteger& inputChannels,
const BigInteger& outputChannels,
double sampleRate,
int bufferSizeSamples) = 0;
/** Closes and releases the device if it's open. */
virtual void close() = 0;
/** Returns true if the device is still open.
A device might spontaneously close itself if something goes wrong, so this checks if
it's still open.
*/
virtual bool isOpen() = 0;
/** Starts the device actually playing.
This must be called after the device has been opened.
@param callback the callback to use for streaming the data.
@see AudioIODeviceCallback, open
*/
virtual void start (AudioIODeviceCallback* callback) = 0;
/** Stops the device playing.
Once a device has been started, this will stop it. Any pending calls to the
callback class will be flushed before this method returns.
*/
virtual void stop() = 0;
/** Returns true if the device is still calling back.
The device might mysteriously stop, so this checks whether it's
still playing.
*/
virtual bool isPlaying() = 0;
/** Returns the last error that happened if anything went wrong. */
virtual String getLastError() = 0;
//==============================================================================
/** Returns the buffer size that the device is currently using.
If the device isn't actually open, this value doesn't really mean much.
*/
virtual int getCurrentBufferSizeSamples() = 0;
/** Returns the sample rate that the device is currently using.
If the device isn't actually open, this value doesn't really mean much.
*/
virtual double getCurrentSampleRate() = 0;
/** Returns the device's current physical bit-depth.
If the device isn't actually open, this value doesn't really mean much.
*/
virtual int getCurrentBitDepth() = 0;
/** Returns a mask showing which of the available output channels are currently
enabled.
@see getOutputChannelNames
*/
virtual BigInteger getActiveOutputChannels() const = 0;
/** Returns a mask showing which of the available input channels are currently
enabled.
@see getInputChannelNames
*/
virtual BigInteger getActiveInputChannels() const = 0;
/** Returns the device's output latency.
This is the delay in samples between a callback getting a block of data, and
that data actually getting played.
*/
virtual int getOutputLatencyInSamples() = 0;
/** Returns the device's input latency.
This is the delay in samples between some audio actually arriving at the soundcard,
and the callback getting passed this block of data.
*/
virtual int getInputLatencyInSamples() = 0;
//==============================================================================
/** True if this device can show a pop-up control panel for editing its settings.
This is generally just true of ASIO devices. If true, you can call showControlPanel()
to display it.
*/
virtual bool hasControlPanel() const;
/** Shows a device-specific control panel if there is one.
This should only be called for devices which return true from hasControlPanel().
*/
virtual bool showControlPanel();
/** On devices which support it, this allows automatic gain control or other
mic processing to be disabled.
If the device doesn't support this operation, it'll return false.
*/
virtual bool setAudioPreprocessingEnabled (bool shouldBeEnabled);
//==============================================================================
/** Returns the number of under- or over runs reported by the OS since
playback/recording has started.
This number may be different than determining the Xrun count manually (by
measuring the time spent in the audio callback) as the OS may be doing
some buffering internally - especially on mobile devices.
Returns -1 if playback/recording has not started yet or if getting the underrun
count is not supported for this device (Android SDK 23 and lower).
*/
virtual int getXRunCount() const noexcept;
//==============================================================================
protected:
/** Creates a device, setting its name and type member variables. */
AudioIODevice (const String& deviceName,
const String& typeName);
/** @internal */
String name, typeName;
};
} // namespace juce

View File

@ -0,0 +1,146 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
AudioIODeviceType::AudioIODeviceType (const String& name)
: typeName (name)
{
}
AudioIODeviceType::~AudioIODeviceType()
{
}
//==============================================================================
void AudioIODeviceType::addListener (Listener* l) { listeners.add (l); }
void AudioIODeviceType::removeListener (Listener* l) { listeners.remove (l); }
void AudioIODeviceType::callDeviceChangeListeners()
{
listeners.call ([] (Listener& l) { l.audioDeviceListChanged(); });
}
//==============================================================================
#if JUCE_MAC
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() { return new CoreAudioClasses::CoreAudioIODeviceType(); }
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_CoreAudio() { return nullptr; }
#endif
#if JUCE_IOS
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() { return new iOSAudioIODeviceType(); }
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_iOSAudio() { return nullptr; }
#endif
#if JUCE_WINDOWS && JUCE_WASAPI
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (WASAPIDeviceMode deviceMode)
{
auto windowsVersion = SystemStats::getOperatingSystemType();
if (windowsVersion < SystemStats::WinVista
|| (WasapiClasses::isLowLatencyMode (deviceMode) && windowsVersion < SystemStats::Windows10))
return nullptr;
return new WasapiClasses::WASAPIAudioIODeviceType (deviceMode);
}
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (bool exclusiveMode)
{
return createAudioIODeviceType_WASAPI (exclusiveMode ? WASAPIDeviceMode::exclusive
: WASAPIDeviceMode::shared);
}
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (WASAPIDeviceMode) { return nullptr; }
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_WASAPI (bool) { return nullptr; }
#endif
#if JUCE_WINDOWS && JUCE_DIRECTSOUND
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() { return new DSoundAudioIODeviceType(); }
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_DirectSound() { return nullptr; }
#endif
#if JUCE_WINDOWS && JUCE_ASIO
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() { return new ASIOAudioIODeviceType(); }
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ASIO() { return nullptr; }
#endif
#if (JUCE_LINUX || JUCE_BSD) && JUCE_ALSA
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() { return createAudioIODeviceType_ALSA_PCMDevices(); }
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_ALSA() { return nullptr; }
#endif
#if (JUCE_LINUX || JUCE_BSD) && JUCE_JACK
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() { return new JackAudioIODeviceType(); }
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_JACK() { return nullptr; }
#endif
#if JUCE_LINUX && JUCE_BELA
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() { return new BelaAudioIODeviceType(); }
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Bela() { return nullptr; }
#endif
#if JUCE_ANDROID
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android()
{
#if JUCE_USE_ANDROID_OBOE
if (isOboeAvailable())
return nullptr;
#endif
#if JUCE_USE_ANDROID_OPENSLES
if (isOpenSLAvailable())
return nullptr;
#endif
return new AndroidAudioIODeviceType();
}
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Android() { return nullptr; }
#endif
#if JUCE_ANDROID && JUCE_USE_ANDROID_OPENSLES
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES()
{
return isOpenSLAvailable() ? new OpenSLAudioDeviceType() : nullptr;
}
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_OpenSLES() { return nullptr; }
#endif
#if JUCE_ANDROID && JUCE_USE_ANDROID_OBOE
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Oboe()
{
return isOboeAvailable() ? new OboeAudioIODeviceType() : nullptr;
}
#else
AudioIODeviceType* AudioIODeviceType::createAudioIODeviceType_Oboe() { return nullptr; }
#endif
} // namespace juce

View File

@ -0,0 +1,188 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Represents a type of audio driver, such as DirectSound, ASIO, CoreAudio, etc.
To get a list of available audio driver types, use the AudioDeviceManager::createAudioDeviceTypes()
method. Each of the objects returned can then be used to list the available
devices of that type. E.g.
@code
OwnedArray<AudioIODeviceType> types;
myAudioDeviceManager.createAudioDeviceTypes (types);
for (int i = 0; i < types.size(); ++i)
{
String typeName (types[i]->getTypeName()); // This will be things like "DirectSound", "CoreAudio", etc.
types[i]->scanForDevices(); // This must be called before getting the list of devices
StringArray deviceNames (types[i]->getDeviceNames()); // This will now return a list of available devices of this type
for (int j = 0; j < deviceNames.size(); ++j)
{
AudioIODevice* device = types[i]->createDevice (deviceNames [j]);
...
}
}
@endcode
For an easier way of managing audio devices and their settings, have a look at the
AudioDeviceManager class.
@see AudioIODevice, AudioDeviceManager
@tags{Audio}
*/
class JUCE_API AudioIODeviceType
{
public:
//==============================================================================
/** Returns the name of this type of driver that this object manages.
This will be something like "DirectSound", "ASIO", "CoreAudio", "ALSA", etc.
*/
const String& getTypeName() const noexcept { return typeName; }
//==============================================================================
/** Refreshes the object's cached list of known devices.
This must be called at least once before calling getDeviceNames() or any of
the other device creation methods.
*/
virtual void scanForDevices() = 0;
/** Returns the list of available devices of this type.
The scanForDevices() method must have been called to create this list.
@param wantInputNames for devices which have separate inputs and outputs
this determines which list of names is returned
*/
virtual StringArray getDeviceNames (bool wantInputNames = false) const = 0;
/** Returns the name of the default device.
This will be one of the names from the getDeviceNames() list.
@param forInput if true, this means that a default input device should be
returned; if false, it should return the default output
*/
virtual int getDefaultDeviceIndex (bool forInput) const = 0;
/** Returns the index of a given device in the list of device names.
If asInput is true, it shows the index in the inputs list, otherwise it
looks for it in the outputs list.
*/
virtual int getIndexOfDevice (AudioIODevice* device, bool asInput) const = 0;
/** Returns true if two different devices can be used for the input and output.
*/
virtual bool hasSeparateInputsAndOutputs() const = 0;
/** Creates one of the devices of this type.
The deviceName must be one of the strings returned by getDeviceNames(), and
scanForDevices() must have been called before this method is used.
*/
virtual AudioIODevice* createDevice (const String& outputDeviceName,
const String& inputDeviceName) = 0;
//==============================================================================
/**
A class for receiving events when audio devices are inserted or removed.
You can register an AudioIODeviceType::Listener with an~AudioIODeviceType object
using the AudioIODeviceType::addListener() method, and it will be called when
devices of that type are added or removed.
@see AudioIODeviceType::addListener, AudioIODeviceType::removeListener
*/
class Listener
{
public:
virtual ~Listener() = default;
/** Called when the list of available audio devices changes. */
virtual void audioDeviceListChanged() = 0;
};
/** Adds a listener that will be called when this type of device is added or
removed from the system.
*/
void addListener (Listener* listener);
/** Removes a listener that was previously added with addListener(). */
void removeListener (Listener* listener);
//==============================================================================
/** Destructor. */
virtual ~AudioIODeviceType();
//==============================================================================
/** Creates a CoreAudio device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_CoreAudio();
/** Creates an iOS device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_iOSAudio();
/** Creates a WASAPI device type in the specified mode if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_WASAPI (WASAPIDeviceMode deviceMode);
/** Creates a DirectSound device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_DirectSound();
/** Creates an ASIO device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_ASIO();
/** Creates an ALSA device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_ALSA();
/** Creates a JACK device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_JACK();
/** Creates an Android device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_Android();
/** Creates an Android OpenSLES device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_OpenSLES();
/** Creates an Oboe device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_Oboe();
/** Creates a Bela device type if it's available on this platform, or returns null. */
static AudioIODeviceType* createAudioIODeviceType_Bela();
#ifndef DOXYGEN
[[deprecated ("You should call the method which takes a WASAPIDeviceMode instead.")]]
static AudioIODeviceType* createAudioIODeviceType_WASAPI (bool exclusiveMode);
#endif
protected:
explicit AudioIODeviceType (const String& typeName);
/** Synchronously calls all the registered device list change listeners. */
void callDeviceChangeListeners();
private:
String typeName;
ListenerList<Listener> listeners;
JUCE_DECLARE_NON_COPYABLE (AudioIODeviceType)
};
} // namespace juce

View File

@ -0,0 +1,59 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Contains functions to control the system's master volume.
@tags{Audio}
*/
class JUCE_API SystemAudioVolume
{
public:
//==============================================================================
/** Returns the operating system's current volume level in the range 0 to 1.0 */
static float JUCE_CALLTYPE getGain();
/** Attempts to set the operating system's current volume level.
@param newGain the level, between 0 and 1.0
@returns true if the operation succeeds
*/
static bool JUCE_CALLTYPE setGain (float newGain);
/** Returns true if the system's audio output is currently muted. */
static bool JUCE_CALLTYPE isMuted();
/** Attempts to mute the operating system's audio output.
@param shouldBeMuted true if you want it to be muted
@returns true if the operation succeeds
*/
static bool JUCE_CALLTYPE setMuted (bool shouldBeMuted);
private:
SystemAudioVolume(); // Don't instantiate this class, just call its static fns.
JUCE_DECLARE_NON_COPYABLE (SystemAudioVolume)
};
} // namespace juce

View File

@ -0,0 +1,240 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#ifdef JUCE_AUDIO_DEVICES_H_INCLUDED
/* When you add this cpp file to your project, you mustn't include it in a file where you've
already included any other headers - just put it inside a file on its own, possibly with your config
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix
header files that the compiler may be using.
*/
#error "Incorrect use of JUCE cpp file"
#endif
#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1
#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1
#define JUCE_CORE_INCLUDE_JNI_HELPERS 1
#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1
#define JUCE_EVENTS_INCLUDE_WIN32_MESSAGE_WINDOW 1
#ifndef JUCE_USE_WINRT_MIDI
#define JUCE_USE_WINRT_MIDI 0
#endif
#if JUCE_USE_WINRT_MIDI
#define JUCE_EVENTS_INCLUDE_WINRT_WRAPPER 1
#endif
#include "juce_audio_devices.h"
//==============================================================================
#if JUCE_MAC || JUCE_IOS
#include <juce_audio_basics/midi/ump/juce_UMP.h>
#include "midi_io/ump/juce_UMPBytestreamInputHandler.h"
#include "midi_io/ump/juce_UMPU32InputHandler.h"
#endif
#if JUCE_MAC
#define Point CarbonDummyPointName
#define Component CarbonDummyCompName
#import <CoreAudio/AudioHardware.h>
#import <CoreMIDI/MIDIServices.h>
#import <AudioToolbox/AudioServices.h>
#undef Point
#undef Component
#include "native/juce_mac_CoreAudio.cpp"
#include "native/juce_mac_CoreMidi.mm"
#elif JUCE_IOS
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>
#import <CoreMIDI/MIDIServices.h>
#if TARGET_OS_SIMULATOR
#import <CoreMIDI/MIDINetworkSession.h>
#endif
#include "native/juce_ios_Audio.cpp"
#include "native/juce_mac_CoreMidi.mm"
//==============================================================================
#elif JUCE_WINDOWS
#if JUCE_WASAPI
#include <mmreg.h>
#include "native/juce_win32_WASAPI.cpp"
#endif
#if JUCE_DIRECTSOUND
#include "native/juce_win32_DirectSound.cpp"
#endif
#if JUCE_USE_WINRT_MIDI && (JUCE_MSVC || JUCE_CLANG)
/* If you cannot find any of the header files below then you are probably
attempting to use the Windows 10 Bluetooth Low Energy API. For this to work you
need to install version 10.0.14393.0 of the Windows Standalone SDK and you may
need to add the path to the WinRT headers to your build system. This path should
have the form "C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\winrt".
Also please note that Microsoft's Bluetooth MIDI stack has multiple issues, so
this API is EXPERIMENTAL - use at your own risk!
*/
#include <windows.devices.h>
#include <windows.devices.midi.h>
#include <windows.devices.enumeration.h>
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4265)
#include <wrl/event.h>
JUCE_END_IGNORE_WARNINGS_MSVC
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4467)
#include <robuffer.h>
JUCE_END_IGNORE_WARNINGS_MSVC
#endif
#include <juce_audio_basics/midi/juce_MidiDataConcatenator.h>
#include "native/juce_win32_Midi.cpp"
#if JUCE_ASIO
/* This is very frustrating - we only need to use a handful of definitions from
a couple of the header files in Steinberg's ASIO SDK, and it'd be easy to copy
about 30 lines of code into this cpp file to create a fully stand-alone ASIO
implementation...
..unfortunately that would break Steinberg's license agreement for use of
their SDK, so I'm not allowed to do this.
This means that anyone who wants to use JUCE's ASIO abilities will have to:
1) Agree to Steinberg's licensing terms and download the ASIO SDK
(see http://www.steinberg.net/en/company/developers.html).
2) Enable this code with a global definition #define JUCE_ASIO 1.
3) Make sure that your header search path contains the iasiodrv.h file that
comes with the SDK. (Only about a handful of the SDK header files are actually
needed - so to simplify things, you could just copy these into your JUCE directory).
*/
#include <iasiodrv.h>
#include "native/juce_win32_ASIO.cpp"
#endif
//==============================================================================
#elif JUCE_LINUX || JUCE_BSD
#if JUCE_ALSA
/* Got an include error here? If so, you've either not got ALSA installed, or you've
not got your paths set up correctly to find its header files.
The package you need to install to get ASLA support is "libasound2-dev".
If you don't have the ALSA library and don't want to build JUCE with audio support,
just set the JUCE_ALSA flag to 0.
*/
#include <alsa/asoundlib.h>
#include "native/juce_linux_ALSA.cpp"
#endif
#if JUCE_JACK
/* Got an include error here? If so, you've either not got jack-audio-connection-kit
installed, or you've not got your paths set up correctly to find its header files.
The package you need to install to get JACK support is "libjack-dev".
If you don't have the jack-audio-connection-kit library and don't want to build
JUCE with low latency audio support, just set the JUCE_JACK flag to 0.
*/
#include <jack/jack.h>
#include "native/juce_linux_JackAudio.cpp"
#endif
#if (JUCE_LINUX && JUCE_BELA)
/* Got an include error here? If so, you've either not got the bela headers
installed, or you've not got your paths set up correctly to find its header
files.
*/
#include <Bela.h>
#include <Midi.h>
#include <juce_audio_basics/midi/juce_MidiDataConcatenator.h>
#include "native/juce_linux_Bela.cpp"
#endif
#undef SIZEOF
#if ! JUCE_BELA
#include <juce_audio_basics/midi/juce_MidiDataConcatenator.h>
#include "native/juce_linux_Midi.cpp"
#endif
//==============================================================================
#elif JUCE_ANDROID
#include "native/juce_android_Audio.cpp"
#include <juce_audio_basics/midi/juce_MidiDataConcatenator.h>
#include "native/juce_android_Midi.cpp"
#if JUCE_USE_ANDROID_OPENSLES || JUCE_USE_ANDROID_OBOE
#include "native/juce_android_HighPerformanceAudioHelpers.h"
#if JUCE_USE_ANDROID_OPENSLES
#include <SLES/OpenSLES.h>
#include <SLES/OpenSLES_Android.h>
#include <SLES/OpenSLES_AndroidConfiguration.h>
#include "native/juce_android_OpenSL.cpp"
#endif
#if JUCE_USE_ANDROID_OBOE
#if JUCE_USE_ANDROID_OPENSLES
#error "Oboe cannot be enabled at the same time as openSL! Please disable JUCE_USE_ANDROID_OPENSLES"
#endif
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wunused-parameter",
"-Wzero-as-null-pointer-constant",
"-Winconsistent-missing-destructor-override",
"-Wshadow-field-in-constructor",
"-Wshadow-field")
#include <oboe/Oboe.h>
JUCE_END_IGNORE_WARNINGS_GCC_LIKE
#include "native/juce_android_Oboe.cpp"
#endif
#endif
#endif
#if ! JUCE_SYSTEMAUDIOVOL_IMPLEMENTED
namespace juce
{
// None of these methods are available. (On Windows you might need to enable WASAPI for this)
float JUCE_CALLTYPE SystemAudioVolume::getGain() { jassertfalse; return 0.0f; }
bool JUCE_CALLTYPE SystemAudioVolume::setGain (float) { jassertfalse; return false; }
bool JUCE_CALLTYPE SystemAudioVolume::isMuted() { jassertfalse; return false; }
bool JUCE_CALLTYPE SystemAudioVolume::setMuted (bool) { jassertfalse; return false; }
}
#endif
#include "audio_io/juce_AudioDeviceManager.cpp"
#include "audio_io/juce_AudioIODevice.cpp"
#include "audio_io/juce_AudioIODeviceType.cpp"
#include "midi_io/juce_MidiMessageCollector.cpp"
#include "midi_io/juce_MidiDevices.cpp"
#include "sources/juce_AudioSourcePlayer.cpp"
#include "sources/juce_AudioTransportSource.cpp"

View File

@ -0,0 +1,190 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
/*******************************************************************************
The block below describes the properties of this module, and is read by
the Projucer to automatically generate project code that uses it.
For details about the syntax and how to create or use a module, see the
JUCE Module Format.md file.
BEGIN_JUCE_MODULE_DECLARATION
ID: juce_audio_devices
vendor: juce
version: 6.1.2
name: JUCE audio and MIDI I/O device classes
description: Classes to play and record from audio and MIDI I/O devices
website: http://www.juce.com/juce
license: ISC
minimumCppStandard: 14
dependencies: juce_audio_basics, juce_events
OSXFrameworks: CoreAudio CoreMIDI AudioToolbox
iOSFrameworks: CoreAudio CoreMIDI AudioToolbox AVFoundation
linuxPackages: alsa
mingwLibs: winmm
END_JUCE_MODULE_DECLARATION
*******************************************************************************/
#pragma once
#define JUCE_AUDIO_DEVICES_H_INCLUDED
#include <juce_events/juce_events.h>
#include <juce_audio_basics/juce_audio_basics.h>
#if JUCE_MODULE_AVAILABLE_juce_graphics
#include <juce_graphics/juce_graphics.h>
#endif
//==============================================================================
/** Config: JUCE_USE_WINRT_MIDI
Enables the use of the Windows Runtime API for MIDI, allowing connections
to Bluetooth Low Energy devices on Windows 10 version 1809 (October 2018
Update) and later. If you enable this flag then older versions of Windows
will automatically fall back to using the regular Win32 MIDI API.
You will need version 10.0.14393.0 of the Windows Standalone SDK to compile
and you may need to add the path to the WinRT headers. The path to the
headers will be something similar to
"C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\winrt".
*/
#ifndef JUCE_USE_WINRT_MIDI
#define JUCE_USE_WINRT_MIDI 0
#endif
/** Config: JUCE_ASIO
Enables ASIO audio devices (MS Windows only).
Turning this on means that you'll need to have the Steinberg ASIO SDK installed
on your Windows build machine.
See the comments in the ASIOAudioIODevice class's header file for more
info about this.
*/
#ifndef JUCE_ASIO
#define JUCE_ASIO 0
#endif
/** Config: JUCE_WASAPI
Enables WASAPI audio devices (Windows Vista and above).
*/
#ifndef JUCE_WASAPI
#define JUCE_WASAPI 1
#endif
/** Config: JUCE_DIRECTSOUND
Enables DirectSound audio (MS Windows only).
*/
#ifndef JUCE_DIRECTSOUND
#define JUCE_DIRECTSOUND 1
#endif
/** Config: JUCE_ALSA
Enables ALSA audio devices (Linux only).
*/
#ifndef JUCE_ALSA
#define JUCE_ALSA 1
#endif
/** Config: JUCE_JACK
Enables JACK audio devices (Linux only).
*/
#ifndef JUCE_JACK
#define JUCE_JACK 0
#endif
/** Config: JUCE_BELA
Enables Bela audio devices on Bela boards.
*/
#ifndef JUCE_BELA
#define JUCE_BELA 0
#endif
/** Config: JUCE_USE_ANDROID_OBOE
Enables Oboe devices (Android only).
*/
#ifndef JUCE_USE_ANDROID_OBOE
#define JUCE_USE_ANDROID_OBOE 1
#endif
/** Config: JUCE_USE_OBOE_STABILIZED_CALLBACK
If JUCE_USE_ANDROID_OBOE is enabled, enabling this will wrap output audio
streams in the oboe::StabilizedCallback class. This class attempts to keep
the CPU spinning to avoid it being scaled down on certain devices.
(Android only).
*/
#ifndef JUCE_USE_ANDROID_OBOE_STABILIZED_CALLBACK
#define JUCE_USE_ANDROID_OBOE_STABILIZED_CALLBACK 0
#endif
/** Config: JUCE_USE_ANDROID_OPENSLES
Enables OpenSLES devices (Android only).
*/
#ifndef JUCE_USE_ANDROID_OPENSLES
#if ! JUCE_USE_ANDROID_OBOE
#define JUCE_USE_ANDROID_OPENSLES 1
#else
#define JUCE_USE_ANDROID_OPENSLES 0
#endif
#endif
/** Config: JUCE_DISABLE_AUDIO_MIXING_WITH_OTHER_APPS
Turning this on gives your app exclusive access to the system's audio
on platforms which support it (currently iOS only).
*/
#ifndef JUCE_DISABLE_AUDIO_MIXING_WITH_OTHER_APPS
#define JUCE_DISABLE_AUDIO_MIXING_WITH_OTHER_APPS 0
#endif
//==============================================================================
#include "midi_io/juce_MidiDevices.h"
#include "midi_io/juce_MidiMessageCollector.h"
namespace juce
{
/** Available modes for the WASAPI audio device.
Pass one of these to the AudioIODeviceType::createAudioIODeviceType_WASAPI()
method to create a WASAPI AudioIODeviceType object in this mode.
*/
enum class WASAPIDeviceMode
{
shared,
exclusive,
sharedLowLatency
};
}
#include "audio_io/juce_AudioIODevice.h"
#include "audio_io/juce_AudioIODeviceType.h"
#include "audio_io/juce_SystemAudioVolume.h"
#include "sources/juce_AudioSourcePlayer.h"
#include "sources/juce_AudioTransportSource.h"
#include "audio_io/juce_AudioDeviceManager.h"
#if JUCE_IOS
#include "native/juce_ios_Audio.h"
#endif

View File

@ -0,0 +1,23 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
#include "juce_audio_devices.cpp"

View File

@ -0,0 +1,153 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
MidiOutput::MidiOutput (const String& deviceName, const String& deviceIdentifier)
: Thread ("midi out"), deviceInfo (deviceName, deviceIdentifier)
{
}
void MidiOutput::sendBlockOfMessagesNow (const MidiBuffer& buffer)
{
for (const auto metadata : buffer)
sendMessageNow (metadata.getMessage());
}
void MidiOutput::sendBlockOfMessages (const MidiBuffer& buffer,
double millisecondCounterToStartAt,
double samplesPerSecondForBuffer)
{
// You've got to call startBackgroundThread() for this to actually work..
jassert (isThreadRunning());
// this needs to be a value in the future - RTFM for this method!
jassert (millisecondCounterToStartAt > 0);
auto timeScaleFactor = 1000.0 / samplesPerSecondForBuffer;
for (const auto metadata : buffer)
{
auto eventTime = millisecondCounterToStartAt + timeScaleFactor * metadata.samplePosition;
auto* m = new PendingMessage (metadata.data, metadata.numBytes, eventTime);
const ScopedLock sl (lock);
if (firstMessage == nullptr || firstMessage->message.getTimeStamp() > eventTime)
{
m->next = firstMessage;
firstMessage = m;
}
else
{
auto* mm = firstMessage;
while (mm->next != nullptr && mm->next->message.getTimeStamp() <= eventTime)
mm = mm->next;
m->next = mm->next;
mm->next = m;
}
}
notify();
}
void MidiOutput::clearAllPendingMessages()
{
const ScopedLock sl (lock);
while (firstMessage != nullptr)
{
auto* m = firstMessage;
firstMessage = firstMessage->next;
delete m;
}
}
void MidiOutput::startBackgroundThread()
{
startThread (9);
}
void MidiOutput::stopBackgroundThread()
{
stopThread (5000);
}
void MidiOutput::run()
{
while (! threadShouldExit())
{
auto now = Time::getMillisecondCounter();
uint32 eventTime = 0;
uint32 timeToWait = 500;
PendingMessage* message;
{
const ScopedLock sl (lock);
message = firstMessage;
if (message != nullptr)
{
eventTime = (uint32) roundToInt (message->message.getTimeStamp());
if (eventTime > now + 20)
{
timeToWait = eventTime - (now + 20);
message = nullptr;
}
else
{
firstMessage = message->next;
}
}
}
if (message != nullptr)
{
std::unique_ptr<PendingMessage> messageDeleter (message);
if (eventTime > now)
{
Time::waitForMillisecondCounter (eventTime);
if (threadShouldExit())
break;
}
if (eventTime > now - 200)
sendMessageNow (message->message);
}
else
{
jassert (timeToWait < 1000 * 30);
wait ((int) timeToWait);
}
}
clearAllPendingMessages();
}
} // namespace juce

View File

@ -0,0 +1,391 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
This struct contains information about a MIDI input or output device.
You can get one of these structs by calling the static getAvailableDevices() or
getDefaultDevice() methods of MidiInput and MidiOutput or by calling getDeviceInfo()
on an instance of these classes. Devices can be opened by passing the identifier to
the openDevice() method.
@tags{Audio}
*/
struct MidiDeviceInfo
{
MidiDeviceInfo() = default;
MidiDeviceInfo (const String& deviceName, const String& deviceIdentifier)
: name (deviceName), identifier (deviceIdentifier)
{
}
/** The name of this device.
This will be provided by the OS unless the device has been created with the
createNewDevice() method.
Note that the name is not guaranteed to be unique and two devices with the
same name will be indistinguishable. If you want to address a specific device
it is better to use the identifier.
*/
String name;
/** The identifier for this device.
This will be provided by the OS and it's format will differ on different systems
e.g. on macOS it will be a number whereas on Windows it will be a long alphanumeric string.
*/
String identifier;
//==============================================================================
bool operator== (const MidiDeviceInfo& other) const noexcept { return name == other.name && identifier == other.identifier; }
bool operator!= (const MidiDeviceInfo& other) const noexcept { return ! operator== (other); }
};
class MidiInputCallback;
//==============================================================================
/**
Represents a midi input device.
To create one of these, use the static getAvailableDevices() method to find out what
inputs are available, and then use the openDevice() method to try to open one.
@see MidiOutput
@tags{Audio}
*/
class JUCE_API MidiInput final
{
public:
//==============================================================================
/** Returns a list of the available midi input devices.
You can open one of the devices by passing its identifier into the openDevice() method.
@see MidiDeviceInfo, getDevices, getDefaultDeviceIndex, openDevice
*/
static Array<MidiDeviceInfo> getAvailableDevices();
/** Returns the MidiDeviceInfo of the default midi input device to use. */
static MidiDeviceInfo getDefaultDevice();
/** Tries to open one of the midi input devices.
This will return a MidiInput object if it manages to open it, you can then
call start() and stop() on this device.
If the device can't be opened, this will return an empty object.
@param deviceIdentifier the ID of the device to open - use the getAvailableDevices() method to
find the available devices that can be opened
@param callback the object that will receive the midi messages from this device
@see MidiInputCallback, getDevices
*/
static std::unique_ptr<MidiInput> openDevice (const String& deviceIdentifier, MidiInputCallback* callback);
#if JUCE_LINUX || JUCE_BSD || JUCE_MAC || JUCE_IOS || DOXYGEN
/** This will try to create a new midi input device (only available on Linux, macOS and iOS).
This will attempt to create a new midi input device with the specified name for other
apps to connect to.
NB - if you are calling this method on iOS you must have enabled the "Audio Background Capability"
setting in the iOS exporter otherwise this method will fail.
Returns an empty object if a device can't be created.
@param deviceName the name of the device to create
@param callback the object that will receive the midi messages from this device
*/
static std::unique_ptr<MidiInput> createNewDevice (const String& deviceName, MidiInputCallback* callback);
#endif
//==============================================================================
/** Destructor. */
~MidiInput();
/** Starts the device running.
After calling this, the device will start sending midi messages to the MidiInputCallback
object that was specified when the openDevice() method was called.
@see stop
*/
void start();
/** Stops the device running.
@see start
*/
void stop();
/** Returns the MidiDeviceInfo struct containing some information about this device. */
MidiDeviceInfo getDeviceInfo() const noexcept { return deviceInfo; }
/** Returns the identifier of this device. */
String getIdentifier() const noexcept { return deviceInfo.identifier; }
/** Returns the name of this device. */
String getName() const noexcept { return deviceInfo.name; }
/** Sets a custom name for the device. */
void setName (const String& newName) noexcept { deviceInfo.name = newName; }
//==============================================================================
#ifndef DOXYGEN
[[deprecated ("Use getAvailableDevices instead.")]]
static StringArray getDevices();
[[deprecated ("Use getDefaultDevice instead.")]]
static int getDefaultDeviceIndex();
[[deprecated ("Use openDevice that takes a device identifier instead.")]]
static std::unique_ptr<MidiInput> openDevice (int, MidiInputCallback*);
#endif
/** @internal */
class Pimpl;
private:
//==============================================================================
explicit MidiInput (const String&, const String&);
MidiDeviceInfo deviceInfo;
std::unique_ptr<Pimpl> internal;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiInput)
};
//==============================================================================
/**
Receives incoming messages from a physical MIDI input device.
This class is overridden to handle incoming midi messages. See the MidiInput
class for more details.
@see MidiInput
@tags{Audio}
*/
class JUCE_API MidiInputCallback
{
public:
/** Destructor. */
virtual ~MidiInputCallback() = default;
/** Receives an incoming message.
A MidiInput object will call this method when a midi event arrives. It'll be
called on a high-priority system thread, so avoid doing anything time-consuming
in here, and avoid making any UI calls. You might find the MidiBuffer class helpful
for queueing incoming messages for use later.
@param source the MidiInput object that generated the message
@param message the incoming message. The message's timestamp is set to a value
equivalent to (Time::getMillisecondCounter() / 1000.0) to specify the
time when the message arrived
*/
virtual void handleIncomingMidiMessage (MidiInput* source,
const MidiMessage& message) = 0;
/** Notification sent each time a packet of a multi-packet sysex message arrives.
If a long sysex message is broken up into multiple packets, this callback is made
for each packet that arrives until the message is finished, at which point
the normal handleIncomingMidiMessage() callback will be made with the entire
message.
The message passed in will contain the start of a sysex, but won't be finished
with the terminating 0xf7 byte.
*/
virtual void handlePartialSysexMessage (MidiInput* source,
const uint8* messageData,
int numBytesSoFar,
double timestamp)
{
ignoreUnused (source, messageData, numBytesSoFar, timestamp);
}
};
//==============================================================================
/**
Represents a midi output device.
To create one of these, use the static getAvailableDevices() method to find out what
outputs are available, and then use the openDevice() method to try to open one.
@see MidiInput
@tags{Audio}
*/
class JUCE_API MidiOutput final : private Thread
{
public:
//==============================================================================
/** Returns a list of the available midi output devices.
You can open one of the devices by passing its identifier into the openDevice() method.
@see MidiDeviceInfo, getDevices, getDefaultDeviceIndex, openDevice
*/
static Array<MidiDeviceInfo> getAvailableDevices();
/** Returns the MidiDeviceInfo of the default midi output device to use. */
static MidiDeviceInfo getDefaultDevice();
/** Tries to open one of the midi output devices.
This will return a MidiOutput object if it manages to open it, you can then
send messages to this device.
If the device can't be opened, this will return an empty object.
@param deviceIdentifier the ID of the device to open - use the getAvailableDevices() method to
find the available devices that can be opened
@see getDevices
*/
static std::unique_ptr<MidiOutput> openDevice (const String& deviceIdentifier);
#if JUCE_LINUX || JUCE_BSD || JUCE_MAC || JUCE_IOS || DOXYGEN
/** This will try to create a new midi output device (only available on Linux, macOS and iOS).
This will attempt to create a new midi output device with the specified name that other
apps can connect to and use as their midi input.
NB - if you are calling this method on iOS you must have enabled the "Audio Background Capability"
setting in the iOS exporter otherwise this method will fail.
Returns an empty object if a device can't be created.
@param deviceName the name of the device to create
*/
static std::unique_ptr<MidiOutput> createNewDevice (const String& deviceName);
#endif
//==============================================================================
/** Destructor. */
~MidiOutput() override;
/** Returns the MidiDeviceInfo struct containing some information about this device. */
MidiDeviceInfo getDeviceInfo() const noexcept { return deviceInfo; }
/** Returns the identifier of this device. */
String getIdentifier() const noexcept { return deviceInfo.identifier; }
/** Returns the name of this device. */
String getName() const noexcept { return deviceInfo.name; }
/** Sets a custom name for the device. */
void setName (const String& newName) noexcept { deviceInfo.name = newName; }
//==============================================================================
/** Sends out a MIDI message immediately. */
void sendMessageNow (const MidiMessage& message);
/** Sends out a sequence of MIDI messages immediately. */
void sendBlockOfMessagesNow (const MidiBuffer& buffer);
/** This lets you supply a block of messages that will be sent out at some point
in the future.
The MidiOutput class has an internal thread that can send out timestamped
messages - this appends a set of messages to its internal buffer, ready for
sending.
This will only work if you've already started the thread with startBackgroundThread().
A time is specified, at which the block of messages should be sent. This time uses
the same time base as Time::getMillisecondCounter(), and must be in the future.
The samplesPerSecondForBuffer parameter indicates the number of samples per second
used by the MidiBuffer. Each event in a MidiBuffer has a sample position, and the
samplesPerSecondForBuffer value is needed to convert this sample position to a
real time.
*/
void sendBlockOfMessages (const MidiBuffer& buffer,
double millisecondCounterToStartAt,
double samplesPerSecondForBuffer);
/** Gets rid of any midi messages that had been added by sendBlockOfMessages(). */
void clearAllPendingMessages();
/** Starts up a background thread so that the device can send blocks of data.
Call this to get the device ready, before using sendBlockOfMessages().
*/
void startBackgroundThread();
/** Stops the background thread, and clears any pending midi events.
@see startBackgroundThread
*/
void stopBackgroundThread();
/** Returns true if the background thread used to send blocks of data is running.
@see startBackgroundThread, stopBackgroundThread
*/
bool isBackgroundThreadRunning() const noexcept { return isThreadRunning(); }
//==============================================================================
#ifndef DOXYGEN
[[deprecated ("Use getAvailableDevices instead.")]]
static StringArray getDevices();
[[deprecated ("Use getDefaultDevice instead.")]]
static int getDefaultDeviceIndex();
[[deprecated ("Use openDevice that takes a device identifier instead.")]]
static std::unique_ptr<MidiOutput> openDevice (int);
#endif
/** @internal */
class Pimpl;
private:
//==============================================================================
struct PendingMessage
{
PendingMessage (const void* data, int len, double timeStamp)
: message (data, len, timeStamp)
{
}
MidiMessage message;
PendingMessage* next;
};
//==============================================================================
explicit MidiOutput (const String&, const String&);
void run() override;
MidiDeviceInfo deviceInfo;
std::unique_ptr<Pimpl> internal;
CriticalSection lock;
PendingMessage* firstMessage = nullptr;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiOutput)
};
} // namespace juce

View File

@ -0,0 +1,158 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
MidiMessageCollector::MidiMessageCollector()
{
}
MidiMessageCollector::~MidiMessageCollector()
{
}
//==============================================================================
void MidiMessageCollector::reset (const double newSampleRate)
{
const ScopedLock sl (midiCallbackLock);
jassert (newSampleRate > 0);
#if JUCE_DEBUG
hasCalledReset = true;
#endif
sampleRate = newSampleRate;
incomingMessages.clear();
lastCallbackTime = Time::getMillisecondCounterHiRes();
}
void MidiMessageCollector::addMessageToQueue (const MidiMessage& message)
{
const ScopedLock sl (midiCallbackLock);
#if JUCE_DEBUG
jassert (hasCalledReset); // you need to call reset() to set the correct sample rate before using this object
#endif
// the messages that come in here need to be time-stamped correctly - see MidiInput
// for details of what the number should be.
jassert (message.getTimeStamp() != 0);
auto sampleNumber = (int) ((message.getTimeStamp() - 0.001 * lastCallbackTime) * sampleRate);
incomingMessages.addEvent (message, sampleNumber);
// if the messages don't get used for over a second, we'd better
// get rid of any old ones to avoid the queue getting too big
if (sampleNumber > sampleRate)
incomingMessages.clear (0, sampleNumber - (int) sampleRate);
}
void MidiMessageCollector::removeNextBlockOfMessages (MidiBuffer& destBuffer,
const int numSamples)
{
const ScopedLock sl (midiCallbackLock);
#if JUCE_DEBUG
jassert (hasCalledReset); // you need to call reset() to set the correct sample rate before using this object
#endif
jassert (numSamples > 0);
auto timeNow = Time::getMillisecondCounterHiRes();
auto msElapsed = timeNow - lastCallbackTime;
lastCallbackTime = timeNow;
if (! incomingMessages.isEmpty())
{
int numSourceSamples = jmax (1, roundToInt (msElapsed * 0.001 * sampleRate));
int startSample = 0;
int scale = 1 << 16;
if (numSourceSamples > numSamples)
{
// if our list of events is longer than the buffer we're being
// asked for, scale them down to squeeze them all in..
const int maxBlockLengthToUse = numSamples << 5;
auto iter = incomingMessages.cbegin();
if (numSourceSamples > maxBlockLengthToUse)
{
startSample = numSourceSamples - maxBlockLengthToUse;
numSourceSamples = maxBlockLengthToUse;
iter = incomingMessages.findNextSamplePosition (startSample);
}
scale = (numSamples << 10) / numSourceSamples;
std::for_each (iter, incomingMessages.cend(), [&] (const MidiMessageMetadata& meta)
{
const auto pos = ((meta.samplePosition - startSample) * scale) >> 10;
destBuffer.addEvent (meta.data, meta.numBytes, jlimit (0, numSamples - 1, pos));
});
}
else
{
// if our event list is shorter than the number we need, put them
// towards the end of the buffer
startSample = numSamples - numSourceSamples;
for (const auto metadata : incomingMessages)
destBuffer.addEvent (metadata.data, metadata.numBytes,
jlimit (0, numSamples - 1, metadata.samplePosition + startSample));
}
incomingMessages.clear();
}
}
void MidiMessageCollector::ensureStorageAllocated (size_t bytes)
{
incomingMessages.ensureSize (bytes);
}
//==============================================================================
void MidiMessageCollector::handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
{
MidiMessage m (MidiMessage::noteOn (midiChannel, midiNoteNumber, velocity));
m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
addMessageToQueue (m);
}
void MidiMessageCollector::handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity)
{
MidiMessage m (MidiMessage::noteOff (midiChannel, midiNoteNumber, velocity));
m.setTimeStamp (Time::getMillisecondCounterHiRes() * 0.001);
addMessageToQueue (m);
}
void MidiMessageCollector::handleIncomingMidiMessage (MidiInput*, const MidiMessage& message)
{
addMessageToQueue (message);
}
} // namespace juce

View File

@ -0,0 +1,113 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Collects incoming realtime MIDI messages and turns them into blocks suitable for
processing by a block-based audio callback.
The class can also be used as either a MidiKeyboardState::Listener or a MidiInputCallback
so it can easily use a midi input or keyboard component as its source.
@see MidiMessage, MidiInput
@tags{Audio}
*/
class JUCE_API MidiMessageCollector : public MidiKeyboardState::Listener,
public MidiInputCallback
{
public:
//==============================================================================
/** Creates a MidiMessageCollector. */
MidiMessageCollector();
/** Destructor. */
~MidiMessageCollector() override;
//==============================================================================
/** Clears any messages from the queue.
You need to call this method before starting to use the collector, so that
it knows the correct sample rate to use.
*/
void reset (double sampleRate);
/** Takes an incoming real-time message and adds it to the queue.
The message's timestamp is taken, and it will be ready for retrieval as part
of the block returned by the next call to removeNextBlockOfMessages().
This method is fully thread-safe when overlapping calls are made with
removeNextBlockOfMessages().
*/
void addMessageToQueue (const MidiMessage& message);
/** Removes all the pending messages from the queue as a buffer.
This will also correct the messages' timestamps to make sure they're in
the range 0 to numSamples - 1.
This call should be made regularly by something like an audio processing
callback, because the time that it happens is used in calculating the
midi event positions.
This method is fully thread-safe when overlapping calls are made with
addMessageToQueue().
Precondition: numSamples must be greater than 0.
*/
void removeNextBlockOfMessages (MidiBuffer& destBuffer, int numSamples);
/** Preallocates storage for collected messages.
This can be called before audio processing begins to ensure that there
is sufficient space for the expected MIDI messages, in order to avoid
allocations within the audio callback.
*/
void ensureStorageAllocated (size_t bytes);
//==============================================================================
/** @internal */
void handleNoteOn (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override;
/** @internal */
void handleNoteOff (MidiKeyboardState*, int midiChannel, int midiNoteNumber, float velocity) override;
/** @internal */
void handleIncomingMidiMessage (MidiInput*, const MidiMessage&) override;
private:
//==============================================================================
double lastCallbackTime = 0;
CriticalSection midiCallbackLock;
MidiBuffer incomingMessages;
double sampleRate = 44100.0;
#if JUCE_DEBUG
bool hasCalledReset = false;
#endif
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MidiMessageCollector)
};
} // namespace juce

View File

@ -0,0 +1,140 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
namespace universal_midi_packets
{
/**
A base class for classes which convert bytestream midi to other formats.
@tags{Audio}
*/
struct BytestreamInputHandler
{
virtual ~BytestreamInputHandler() noexcept = default;
virtual void reset() = 0;
virtual void pushMidiData (const void* data, int bytes, double time) = 0;
};
/**
Parses a continuous bytestream and emits complete MidiMessages whenever a full
message is received.
@tags{Audio}
*/
struct BytestreamToBytestreamHandler : public BytestreamInputHandler
{
BytestreamToBytestreamHandler (MidiInput& i, MidiInputCallback& c)
: input (i), callback (c), concatenator (2048) {}
/**
Provides an `operator()` which can create an input handler for a given
MidiInput.
All handler classes should have a similar Factory to facilitate
creation of handlers in generic contexts.
*/
class Factory
{
public:
explicit Factory (MidiInputCallback* c)
: callback (c) {}
std::unique_ptr<BytestreamToBytestreamHandler> operator() (MidiInput& i) const
{
if (callback != nullptr)
return std::make_unique<BytestreamToBytestreamHandler> (i, *callback);
jassertfalse;
return {};
}
private:
MidiInputCallback* callback = nullptr;
};
void reset() override { concatenator.reset(); }
void pushMidiData (const void* data, int bytes, double time) override
{
concatenator.pushMidiData (data, bytes, time, &input, callback);
}
MidiInput& input;
MidiInputCallback& callback;
MidiDataConcatenator concatenator;
};
/**
Parses a continuous MIDI 1.0 bytestream, and emits full messages in the requested
UMP format.
@tags{Audio}
*/
struct BytestreamToUMPHandler : public BytestreamInputHandler
{
BytestreamToUMPHandler (PacketProtocol protocol, Receiver& c)
: recipient (c), dispatcher (protocol, 2048) {}
/**
Provides an `operator()` which can create an input handler for a given
MidiInput.
All handler classes should have a similar Factory to facilitate
creation of handlers in generic contexts.
*/
class Factory
{
public:
Factory (PacketProtocol p, Receiver& c)
: protocol (p), callback (c) {}
std::unique_ptr<BytestreamToUMPHandler> operator() (MidiInput&) const
{
return std::make_unique<BytestreamToUMPHandler> (protocol, callback);
}
private:
PacketProtocol protocol;
Receiver& callback;
};
void reset() override { dispatcher.reset(); }
void pushMidiData (const void* data, int bytes, double time) override
{
const auto* ptr = static_cast<const uint8_t*> (data);
dispatcher.dispatch (ptr, ptr + bytes, time, [&] (const View& v)
{
recipient.packetReceived (v, time);
});
}
Receiver& recipient;
BytestreamToUMPDispatcher dispatcher;
};
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,151 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
namespace universal_midi_packets
{
/**
A base class for classes which convert Universal MIDI Packets to other
formats.
@tags{Audio}
*/
struct U32InputHandler
{
virtual ~U32InputHandler() noexcept = default;
virtual void reset() = 0;
virtual void pushMidiData (const uint32_t* begin, const uint32_t* end, double time) = 0;
};
/**
Parses a continuous stream of U32 words and emits complete MidiMessages whenever a full
message is received.
@tags{Audio}
*/
struct U32ToBytestreamHandler : public U32InputHandler
{
U32ToBytestreamHandler (MidiInput& i, MidiInputCallback& c)
: input (i), callback (c), dispatcher (2048) {}
/**
Provides an `operator()` which can create an input handler for a given
MidiInput.
All handler classes should have a similar Factory to facilitate
creation of handlers in generic contexts.
*/
class Factory
{
public:
explicit Factory (MidiInputCallback* c)
: callback (c) {}
std::unique_ptr<U32ToBytestreamHandler> operator() (MidiInput& i) const
{
if (callback != nullptr)
return std::make_unique<U32ToBytestreamHandler> (i, *callback);
jassertfalse;
return {};
}
private:
MidiInputCallback* callback = nullptr;
};
void reset() override { dispatcher.reset(); }
void pushMidiData (const uint32_t* begin, const uint32_t* end, double time) override
{
dispatcher.dispatch (begin, end, time, [this] (const MidiMessage& m)
{
callback.handleIncomingMidiMessage (&input, m);
});
}
MidiInput& input;
MidiInputCallback& callback;
ToBytestreamDispatcher dispatcher;
};
/**
Parses a continuous stream of U32 words and emits full messages in the requested
UMP format.
@tags{Audio}
*/
struct U32ToUMPHandler : public U32InputHandler
{
U32ToUMPHandler (PacketProtocol protocol, Receiver& c)
: recipient (c), converter (protocol) {}
/**
Provides an `operator()` which can create an input handler for a given
MidiInput.
All handler classes should have a similar Factory to facilitate
creation of handlers in generic contexts.
*/
class Factory
{
public:
Factory (PacketProtocol p, Receiver& c)
: protocol (p), callback (c) {}
std::unique_ptr<U32ToUMPHandler> operator() (MidiInput&) const
{
return std::make_unique<U32ToUMPHandler> (protocol, callback);
}
private:
PacketProtocol protocol;
Receiver& callback;
};
void reset() override
{
dispatcher.reset();
converter.reset();
}
void pushMidiData (const uint32_t* begin, const uint32_t* end, double time) override
{
dispatcher.dispatch (begin, end, time, [this] (const View& view, double thisTime)
{
converter.convert (view, [&] (const View& converted)
{
recipient.packetReceived (converted, thisTime);
});
});
}
Receiver& recipient;
Dispatcher dispatcher;
GenericUMPConverter converter;
};
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,481 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \
STATICMETHOD (getNativeOutputSampleRate, "getNativeOutputSampleRate", "(I)I") \
METHOD (constructor, "<init>", "(IIIIII)V") \
METHOD (getState, "getState", "()I") \
METHOD (play, "play", "()V") \
METHOD (stop, "stop", "()V") \
METHOD (release, "release", "()V") \
METHOD (flush, "flush", "()V") \
METHOD (write, "write", "([SII)I") \
DECLARE_JNI_CLASS (AudioTrack, "android/media/AudioTrack")
#undef JNI_CLASS_MEMBERS
//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
STATICMETHOD (getMinBufferSize, "getMinBufferSize", "(III)I") \
METHOD (constructor, "<init>", "(IIIII)V") \
METHOD (getState, "getState", "()I") \
METHOD (startRecording, "startRecording", "()V") \
METHOD (stop, "stop", "()V") \
METHOD (read, "read", "([SII)I") \
METHOD (release, "release", "()V") \
DECLARE_JNI_CLASS (AudioRecord, "android/media/AudioRecord")
#undef JNI_CLASS_MEMBERS
//==============================================================================
enum
{
CHANNEL_OUT_STEREO = 12,
CHANNEL_IN_STEREO = 12,
CHANNEL_IN_MONO = 16,
ENCODING_PCM_16BIT = 2,
STREAM_MUSIC = 3,
MODE_STREAM = 1,
STATE_UNINITIALIZED = 0
};
const char* const javaAudioTypeName = "Android Audio";
//==============================================================================
class AndroidAudioIODevice : public AudioIODevice,
public Thread
{
public:
//==============================================================================
AndroidAudioIODevice (const String& deviceName)
: AudioIODevice (deviceName, javaAudioTypeName),
Thread ("audio"),
minBufferSizeOut (0), minBufferSizeIn (0), callback (nullptr), sampleRate (0),
numClientInputChannels (0), numDeviceInputChannels (0), numDeviceInputChannelsAvailable (2),
numClientOutputChannels (0), numDeviceOutputChannels (0),
actualBufferSize (0), isRunning (false),
inputChannelBuffer (1, 1),
outputChannelBuffer (1, 1)
{
JNIEnv* env = getEnv();
sampleRate = env->CallStaticIntMethod (AudioTrack, AudioTrack.getNativeOutputSampleRate, MODE_STREAM);
minBufferSizeOut = (int) env->CallStaticIntMethod (AudioTrack, AudioTrack.getMinBufferSize, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT);
minBufferSizeIn = (int) env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_STEREO, ENCODING_PCM_16BIT);
if (minBufferSizeIn <= 0)
{
minBufferSizeIn = env->CallStaticIntMethod (AudioRecord, AudioRecord.getMinBufferSize, sampleRate, CHANNEL_IN_MONO, ENCODING_PCM_16BIT);
if (minBufferSizeIn > 0)
numDeviceInputChannelsAvailable = 1;
else
numDeviceInputChannelsAvailable = 0;
}
DBG ("Audio device - min buffers: " << minBufferSizeOut << ", " << minBufferSizeIn << "; "
<< sampleRate << " Hz; input chans: " << numDeviceInputChannelsAvailable);
}
~AndroidAudioIODevice() override
{
close();
}
StringArray getOutputChannelNames() override
{
StringArray s;
s.add ("Left");
s.add ("Right");
return s;
}
StringArray getInputChannelNames() override
{
StringArray s;
if (numDeviceInputChannelsAvailable == 2)
{
s.add ("Left");
s.add ("Right");
}
else if (numDeviceInputChannelsAvailable == 1)
{
s.add ("Audio Input");
}
return s;
}
Array<double> getAvailableSampleRates() override
{
Array<double> r;
r.add ((double) sampleRate);
return r;
}
Array<int> getAvailableBufferSizes() override
{
Array<int> b;
int n = 16;
for (int i = 0; i < 50; ++i)
{
b.add (n);
n += n < 64 ? 16
: (n < 512 ? 32
: (n < 1024 ? 64
: (n < 2048 ? 128 : 256)));
}
return b;
}
int getDefaultBufferSize() override { return 2048; }
String open (const BigInteger& inputChannels,
const BigInteger& outputChannels,
double requestedSampleRate,
int bufferSize) override
{
close();
if (sampleRate != (int) requestedSampleRate)
return "Sample rate not allowed";
lastError.clear();
int preferredBufferSize = (bufferSize <= 0) ? getDefaultBufferSize() : bufferSize;
numDeviceInputChannels = 0;
numDeviceOutputChannels = 0;
activeOutputChans = outputChannels;
activeOutputChans.setRange (2, activeOutputChans.getHighestBit(), false);
numClientOutputChannels = activeOutputChans.countNumberOfSetBits();
activeInputChans = inputChannels;
activeInputChans.setRange (2, activeInputChans.getHighestBit(), false);
numClientInputChannels = activeInputChans.countNumberOfSetBits();
actualBufferSize = preferredBufferSize;
inputChannelBuffer.setSize (2, actualBufferSize);
inputChannelBuffer.clear();
outputChannelBuffer.setSize (2, actualBufferSize);
outputChannelBuffer.clear();
JNIEnv* env = getEnv();
if (numClientOutputChannels > 0)
{
numDeviceOutputChannels = 2;
outputDevice = GlobalRef (LocalRef<jobject>(env->NewObject (AudioTrack, AudioTrack.constructor,
STREAM_MUSIC, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT,
(jint) (minBufferSizeOut * numDeviceOutputChannels * static_cast<int> (sizeof (int16))), MODE_STREAM)));
const bool supportsUnderrunCount = (getAndroidSDKVersion() >= 24);
getUnderrunCount = supportsUnderrunCount ? env->GetMethodID (AudioTrack, "getUnderrunCount", "()I") : nullptr;
int outputDeviceState = env->CallIntMethod (outputDevice, AudioTrack.getState);
if (outputDeviceState > 0)
{
isRunning = true;
}
else
{
// failed to open the device
outputDevice.clear();
lastError = "Error opening audio output device: android.media.AudioTrack failed with state = " + String (outputDeviceState);
}
}
if (numClientInputChannels > 0 && numDeviceInputChannelsAvailable > 0)
{
if (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio))
{
// If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio
// before trying to open an audio input device. This is not going to work!
jassertfalse;
inputDevice.clear();
lastError = "Error opening audio input device: the app was not granted android.permission.RECORD_AUDIO";
}
else
{
numDeviceInputChannels = jmin (numClientInputChannels, numDeviceInputChannelsAvailable);
inputDevice = GlobalRef (LocalRef<jobject>(env->NewObject (AudioRecord, AudioRecord.constructor,
0 /* (default audio source) */, sampleRate,
numDeviceInputChannelsAvailable > 1 ? CHANNEL_IN_STEREO : CHANNEL_IN_MONO,
ENCODING_PCM_16BIT,
(jint) (minBufferSizeIn * numDeviceInputChannels * static_cast<int> (sizeof (int16))))));
int inputDeviceState = env->CallIntMethod (inputDevice, AudioRecord.getState);
if (inputDeviceState > 0)
{
isRunning = true;
}
else
{
// failed to open the device
inputDevice.clear();
lastError = "Error opening audio input device: android.media.AudioRecord failed with state = " + String (inputDeviceState);
}
}
}
if (isRunning)
{
if (outputDevice != nullptr)
env->CallVoidMethod (outputDevice, AudioTrack.play);
if (inputDevice != nullptr)
env->CallVoidMethod (inputDevice, AudioRecord.startRecording);
startThread (8);
}
else
{
closeDevices();
}
return lastError;
}
void close() override
{
if (isRunning)
{
stopThread (2000);
isRunning = false;
closeDevices();
}
}
int getOutputLatencyInSamples() override { return (minBufferSizeOut * 3) / 4; }
int getInputLatencyInSamples() override { return (minBufferSizeIn * 3) / 4; }
bool isOpen() override { return isRunning; }
int getCurrentBufferSizeSamples() override { return actualBufferSize; }
int getCurrentBitDepth() override { return 16; }
double getCurrentSampleRate() override { return sampleRate; }
BigInteger getActiveOutputChannels() const override { return activeOutputChans; }
BigInteger getActiveInputChannels() const override { return activeInputChans; }
String getLastError() override { return lastError; }
bool isPlaying() override { return isRunning && callback != nullptr; }
int getXRunCount() const noexcept override
{
if (outputDevice != nullptr && getUnderrunCount != nullptr)
return getEnv()->CallIntMethod (outputDevice, getUnderrunCount);
return -1;
}
void start (AudioIODeviceCallback* newCallback) override
{
if (isRunning && callback != newCallback)
{
if (newCallback != nullptr)
newCallback->audioDeviceAboutToStart (this);
const ScopedLock sl (callbackLock);
callback = newCallback;
}
}
void stop() override
{
if (isRunning)
{
AudioIODeviceCallback* lastCallback;
{
const ScopedLock sl (callbackLock);
lastCallback = callback;
callback = nullptr;
}
if (lastCallback != nullptr)
lastCallback->audioDeviceStopped();
}
}
void run() override
{
JNIEnv* env = getEnv();
jshortArray audioBuffer = env->NewShortArray (actualBufferSize * jmax (numDeviceOutputChannels, numDeviceInputChannels));
while (! threadShouldExit())
{
if (inputDevice != nullptr)
{
jint numRead = env->CallIntMethod (inputDevice, AudioRecord.read, audioBuffer, 0, actualBufferSize * numDeviceInputChannels);
if (numRead < actualBufferSize * numDeviceInputChannels)
{
DBG ("Audio read under-run! " << numRead);
}
jshort* const src = env->GetShortArrayElements (audioBuffer, nullptr);
for (int chan = 0; chan < inputChannelBuffer.getNumChannels(); ++chan)
{
AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst> d (inputChannelBuffer.getWritePointer (chan));
if (chan < numDeviceInputChannels)
{
AudioData::Pointer <AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::Const> s (src + chan, numDeviceInputChannels);
d.convertSamples (s, actualBufferSize);
}
else
{
d.clearSamples (actualBufferSize);
}
}
env->ReleaseShortArrayElements (audioBuffer, src, 0);
}
if (threadShouldExit())
break;
{
const ScopedLock sl (callbackLock);
if (callback != nullptr)
{
callback->audioDeviceIOCallback (inputChannelBuffer.getArrayOfReadPointers(), numClientInputChannels,
outputChannelBuffer.getArrayOfWritePointers(), numClientOutputChannels,
actualBufferSize);
}
else
{
outputChannelBuffer.clear();
}
}
if (outputDevice != nullptr)
{
if (threadShouldExit())
break;
jshort* const dest = env->GetShortArrayElements (audioBuffer, nullptr);
for (int chan = 0; chan < numDeviceOutputChannels; ++chan)
{
AudioData::Pointer <AudioData::Int16, AudioData::NativeEndian, AudioData::Interleaved, AudioData::NonConst> d (dest + chan, numDeviceOutputChannels);
const float* const sourceChanData = outputChannelBuffer.getReadPointer (jmin (chan, outputChannelBuffer.getNumChannels() - 1));
AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const> s (sourceChanData);
d.convertSamples (s, actualBufferSize);
}
env->ReleaseShortArrayElements (audioBuffer, dest, 0);
jint numWritten = env->CallIntMethod (outputDevice, AudioTrack.write, audioBuffer, 0, actualBufferSize * numDeviceOutputChannels);
if (numWritten < actualBufferSize * numDeviceOutputChannels)
{
DBG ("Audio write underrun! " << numWritten);
}
}
}
}
int minBufferSizeOut, minBufferSizeIn;
private:
//==============================================================================
CriticalSection callbackLock;
AudioIODeviceCallback* callback;
jint sampleRate;
int numClientInputChannels, numDeviceInputChannels, numDeviceInputChannelsAvailable;
int numClientOutputChannels, numDeviceOutputChannels;
int actualBufferSize;
bool isRunning;
String lastError;
BigInteger activeOutputChans, activeInputChans;
GlobalRef outputDevice, inputDevice;
AudioBuffer<float> inputChannelBuffer, outputChannelBuffer;
jmethodID getUnderrunCount = nullptr;
void closeDevices()
{
if (outputDevice != nullptr)
{
outputDevice.callVoidMethod (AudioTrack.stop);
outputDevice.callVoidMethod (AudioTrack.release);
outputDevice.clear();
}
if (inputDevice != nullptr)
{
inputDevice.callVoidMethod (AudioRecord.stop);
inputDevice.callVoidMethod (AudioRecord.release);
inputDevice.clear();
}
}
JUCE_DECLARE_NON_COPYABLE (AndroidAudioIODevice)
};
//==============================================================================
class AndroidAudioIODeviceType : public AudioIODeviceType
{
public:
AndroidAudioIODeviceType() : AudioIODeviceType (javaAudioTypeName) {}
//==============================================================================
void scanForDevices() {}
StringArray getDeviceNames (bool) const { return StringArray (javaAudioTypeName); }
int getDefaultDeviceIndex (bool) const { return 0; }
int getIndexOfDevice (AudioIODevice* device, bool) const { return device != nullptr ? 0 : -1; }
bool hasSeparateInputsAndOutputs() const { return false; }
AudioIODevice* createDevice (const String& outputDeviceName,
const String& inputDeviceName)
{
std::unique_ptr<AndroidAudioIODevice> dev;
if (outputDeviceName.isNotEmpty() || inputDeviceName.isNotEmpty())
{
dev.reset (new AndroidAudioIODevice (outputDeviceName.isNotEmpty() ? outputDeviceName
: inputDeviceName));
if (dev->getCurrentSampleRate() <= 0 || dev->getDefaultBufferSize() <= 0)
dev = nullptr;
}
return dev.release();
}
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidAudioIODeviceType)
};
//==============================================================================
extern bool isOboeAvailable();
extern bool isOpenSLAvailable();
} // namespace juce

View File

@ -0,0 +1,131 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
/**
Some shared helpers methods for using the high-performance audio paths on
Android devices (OpenSL and Oboe).
@tags{Audio}
*/
namespace AndroidHighPerformanceAudioHelpers
{
//==============================================================================
static double getNativeSampleRate()
{
return audioManagerGetProperty ("android.media.property.OUTPUT_SAMPLE_RATE").getDoubleValue();
}
static int getNativeBufferSizeHint()
{
// This property is a hint of a native buffer size but it does not guarantee the size used.
auto deviceBufferSize = audioManagerGetProperty ("android.media.property.OUTPUT_FRAMES_PER_BUFFER").getIntValue();
if (deviceBufferSize == 0)
return 192;
return deviceBufferSize;
}
static bool isProAudioDevice()
{
static bool isSapaSupported = SystemStats::getDeviceManufacturer().containsIgnoreCase ("SAMSUNG")
&& DynamicLibrary().open ("libapa_jni.so");
return androidHasSystemFeature ("android.hardware.audio.pro") || isSapaSupported;
}
static bool hasLowLatencyAudioPath()
{
return androidHasSystemFeature ("android.hardware.audio.low_latency");
}
static bool canUseHighPerformanceAudioPath (int nativeBufferSize, int requestedBufferSize, int requestedSampleRate)
{
return ((requestedBufferSize % nativeBufferSize) == 0)
&& (requestedSampleRate == getNativeSampleRate())
&& isProAudioDevice();
}
//==============================================================================
static int getMinimumBuffersToEnqueue (int nativeBufferSize, double requestedSampleRate)
{
if (canUseHighPerformanceAudioPath (nativeBufferSize, nativeBufferSize, (int) requestedSampleRate))
{
// see https://developer.android.com/ndk/guides/audio/opensl/opensl-prog-notes.html#sandp
// "For Android 4.2 (API level 17) and earlier, a buffer count of two or more is required
// for lower latency. Beginning with Android 4.3 (API level 18), a buffer count of one
// is sufficient for lower latency."
return (getAndroidSDKVersion() >= 18 ? 1 : 2);
}
// not using low-latency path so we can use the absolute minimum number of buffers to queue
return 1;
}
static int buffersToQueueForBufferDuration (int nativeBufferSize, int bufferDurationInMs, double sampleRate) noexcept
{
auto maxBufferFrames = static_cast<int> (std::ceil (bufferDurationInMs * sampleRate / 1000.0));
auto maxNumBuffers = static_cast<int> (std::ceil (static_cast<double> (maxBufferFrames)
/ static_cast<double> (nativeBufferSize)));
return jmax (getMinimumBuffersToEnqueue (nativeBufferSize, sampleRate), maxNumBuffers);
}
static int getMaximumBuffersToEnqueue (int nativeBufferSize, double maximumSampleRate) noexcept
{
static constexpr int maxBufferSizeMs = 200;
return jmax (8, buffersToQueueForBufferDuration (nativeBufferSize, maxBufferSizeMs, maximumSampleRate));
}
static Array<int> getAvailableBufferSizes (int nativeBufferSize, Array<double> availableSampleRates)
{
auto minBuffersToQueue = getMinimumBuffersToEnqueue (nativeBufferSize, getNativeSampleRate());
auto maxBuffersToQueue = getMaximumBuffersToEnqueue (nativeBufferSize, findMaximum (availableSampleRates.getRawDataPointer(),
availableSampleRates.size()));
Array<int> bufferSizes;
for (int i = minBuffersToQueue; i <= maxBuffersToQueue; ++i)
bufferSizes.add (i * nativeBufferSize);
return bufferSizes;
}
static int getDefaultBufferSize (int nativeBufferSize, double currentSampleRate)
{
static constexpr int defaultBufferSizeForLowLatencyDeviceMs = 40;
static constexpr int defaultBufferSizeForStandardLatencyDeviceMs = 100;
auto defaultBufferLength = (hasLowLatencyAudioPath() ? defaultBufferSizeForLowLatencyDeviceMs
: defaultBufferSizeForStandardLatencyDeviceMs);
auto defaultBuffersToEnqueue = buffersToQueueForBufferDuration (nativeBufferSize, defaultBufferLength, currentSampleRate);
return defaultBuffersToEnqueue * nativeBufferSize;
}
}
} // namespace juce

View File

@ -0,0 +1,701 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
// This byte-code is generated from native/java/com/rmsl/juce/JuceMidiSupport.java with min sdk version 23
// See juce_core/native/java/README.txt on how to generate this byte-code.
static const uint8 javaMidiByteCode[] =
{31,139,8,8,43,113,161,94,0,3,106,97,118,97,77,105,100,105,66,121,116,101,67,111,100,101,46,100,101,120,0,149,124,11,124,220,
69,181,255,153,223,99,119,179,217,36,155,77,218,164,105,178,217,164,73,179,165,205,171,233,35,109,146,182,121,180,77,218,164,45,
201,182,72,195,5,183,201,182,217,146,236,134,236,166,180,114,189,20,244,210,162,168,40,80,65,177,162,2,242,18,81,65,80,17,81,
80,81,81,122,149,63,214,39,138,112,69,69,64,20,17,229,218,255,247,204,204,110,126,109,3,213,246,243,221,51,191,51,103,206,204,
156,57,115,230,204,111,183,29,141,237,247,54,181,44,167,234,195,55,252,236,231,159,31,184,160,244,71,71,143,188,248,212,248,
199,142,188,62,189,243,225,179,11,151,53,157,77,52,73,68,251,119,44,11,144,254,243,246,109,68,231,10,197,95,15,60,105,19,157,3,
250,188,139,40,4,250,134,151,232,179,76,115,137,114,64,211,133,68,55,174,33,186,22,26,254,86,79,244,119,224,255,0,163,129,200,
6,22,3,13,64,43,176,14,232,1,54,1,219,128,93,192,81,224,105,224,31,192,63,1,163,145,200,13,132,129,173,192,32,240,54,224,66,
224,82,224,125,192,167,128,91,129,59,128,207,2,247,2,95,6,30,2,30,1,190,3,188,4,20,55,17,173,4,118,1,215,0,15,3,127,0,252,205,
68,109,192,249,192,101,192,221,192,143,128,23,129,130,165,68,29,192,46,224,74,224,51,192,47,129,146,22,162,85,192,249,192,101,
192,17,224,46,224,155,192,79,128,63,2,198,50,216,14,120,47,240,16,240,10,16,90,78,148,0,238,5,126,11,204,89,65,180,2,216,9,188,
3,248,24,240,32,112,28,120,9,48,86,162,47,96,49,176,22,216,1,164,129,107,128,219,129,135,1,187,149,168,9,232,1,222,6,76,0,151,
1,71,128,187,128,175,2,79,0,214,42,244,7,132,129,181,192,32,112,29,112,59,112,63,240,75,224,215,192,115,192,239,129,151,129,191,
1,111,0,98,53,214,1,200,7,138,128,82,32,8,212,0,139,129,165,192,10,96,21,208,1,116,2,235,129,56,112,61,240,16,240,35,224,121,
224,85,64,180,17,121,129,2,160,20,88,8,180,0,107,129,141,192,185,192,52,112,37,240,81,224,115,192,35,192,15,128,227,192,239,
129,87,128,215,1,119,59,244,0,149,192,66,160,30,88,1,116,3,3,192,78,96,4,184,8,216,15,92,2,28,2,62,0,220,0,124,10,248,60,240,53,
224,7,192,79,129,231,128,63,1,175,3,86,7,244,3,107,129,62,96,28,120,31,112,45,240,73,224,110,224,94,224,107,192,163,192,49,224,
23,192,235,192,92,236,133,122,160,11,56,15,72,1,239,4,174,6,62,10,220,9,220,15,60,12,252,1,120,5,120,29,48,214,98,46,192,86,
224,0,112,45,112,23,112,31,240,13,224,127,128,223,0,127,4,222,0,242,214,97,254,64,24,88,9,172,3,54,1,219,128,97,224,124,96,4,
136,3,147,192,37,192,97,224,58,224,40,240,105,224,94,224,33,224,49,224,41,224,103,192,111,128,151,128,191,1,255,4,188,157,68,
21,64,61,176,26,88,3,116,3,189,192,22,96,59,112,1,16,3,246,2,83,192,21,192,199,129,251,128,199,129,231,128,215,0,111,23,209,60,
96,17,176,14,56,27,24,3,46,2,46,5,62,8,220,9,124,9,248,54,240,4,240,28,240,119,192,213,13,95,6,26,128,94,224,60,96,28,184,24,
184,12,184,10,184,6,248,56,112,43,240,21,224,199,192,203,192,95,129,55,0,119,15,81,33,48,31,88,12,44,7,122,128,45,192,249,64,
12,216,11,92,14,188,7,56,2,124,10,120,8,248,22,240,4,240,99,224,23,192,51,192,171,128,23,65,178,8,168,0,106,129,197,192,70,224,
108,96,23,144,4,46,5,174,2,62,4,220,0,220,12,124,1,248,10,240,117,224,123,192,83,192,207,129,103,129,151,128,191,1,198,6,216,
11,88,6,108,1,182,3,5,136,185,197,64,53,176,0,168,1,106,129,133,64,29,16,6,22,1,103,1,139,129,37,0,194,49,33,180,18,66,34,33,
252,17,194,28,33,164,17,66,22,33,68,17,194,18,33,244,16,66,11,33,108,16,182,63,97,203,18,182,26,97,59,16,220,154,224,114,132,
37,36,44,5,193,148,212,163,207,7,12,137,54,2,189,64,31,176,9,216,12,244,3,3,192,22,96,43,128,99,133,112,220,208,32,48,4,68,128,
29,192,219,128,97,224,63,128,11,248,252,1,118,1,163,64,12,216,13,140,3,255,9,92,10,28,4,46,3,46,7,222,5,188,155,148,77,50,127,
252,154,78,98,226,133,186,188,31,229,50,80,67,63,115,217,212,229,74,93,158,212,50,150,230,87,233,242,65,205,247,56,228,113,4,
210,85,154,159,171,249,243,129,60,224,90,205,207,119,244,85,224,40,7,28,242,197,90,158,203,165,142,182,101,142,190,202,245,216,
88,38,168,101,42,117,121,82,151,25,55,106,153,106,45,83,161,203,55,47,81,178,92,190,75,203,215,56,218,214,234,182,220,15,251,
208,3,122,12,13,142,113,54,58,198,214,228,24,27,151,31,94,162,242,2,46,63,182,100,134,159,177,103,179,67,79,179,99,252,92,62,
230,40,103,230,184,204,209,87,171,163,47,246,201,227,154,191,90,243,217,47,58,116,121,66,151,185,109,66,151,127,133,114,82,151,
159,95,162,114,26,46,255,5,229,139,116,217,194,230,216,175,203,62,148,167,116,185,20,229,148,46,135,80,222,167,203,75,80,190,
88,151,151,57,202,235,234,103,116,246,59,202,55,58,250,138,56,248,231,57,250,29,117,240,39,29,229,253,142,126,15,58,248,135,29,
109,175,70,249,64,166,47,135,252,109,40,191,67,151,239,113,180,61,230,24,15,175,93,70,254,49,7,127,210,81,126,208,209,215,163,
40,79,103,244,160,124,137,46,31,119,216,234,87,40,167,117,249,133,122,181,111,215,232,53,122,167,46,243,26,253,151,46,179,253,
51,229,135,29,252,140,255,116,234,182,92,238,114,248,67,183,195,31,122,52,127,190,46,95,43,125,190,137,238,39,69,215,10,110,
83,64,87,201,182,205,244,1,73,87,210,135,36,245,80,135,96,31,46,165,247,242,90,163,247,231,37,21,244,71,73,107,169,74,214,47,164,
197,130,227,66,177,148,171,210,252,42,205,95,160,159,153,110,19,188,199,44,250,48,49,245,211,95,36,85,245,53,186,190,86,143,167,
22,145,247,136,164,93,116,167,164,37,244,138,164,203,232,53,93,95,46,20,13,10,181,71,111,39,166,107,232,247,164,227,190,224,
216,95,73,31,228,50,36,95,37,142,117,30,122,84,82,147,190,37,169,77,63,37,142,117,110,250,184,164,213,244,85,77,159,228,117,
192,137,241,49,77,63,43,169,69,223,150,116,11,45,135,126,27,124,55,113,28,236,165,62,193,116,5,13,8,190,3,40,190,55,75,189,116,
189,164,57,180,30,245,62,173,39,79,215,231,129,115,189,164,185,212,45,20,237,17,28,35,243,232,235,196,180,138,126,70,28,199,
213,120,252,136,164,63,144,180,128,74,4,83,63,205,23,28,219,213,184,57,198,63,165,233,207,73,197,215,239,75,58,72,199,37,45,164,
159,104,62,215,23,107,189,197,56,165,214,65,207,28,61,174,18,156,74,223,145,180,137,230,8,166,171,105,174,164,29,212,44,105,
59,237,16,28,167,85,251,82,216,255,168,166,108,175,121,90,79,25,198,255,32,113,60,13,208,151,136,227,176,65,183,72,63,92,47,235,
217,239,20,21,244,136,164,181,244,61,73,183,211,15,37,221,72,66,250,235,98,42,148,116,9,5,36,61,155,106,36,221,68,155,36,221,
64,219,165,95,174,147,250,66,122,92,76,239,149,84,217,39,132,72,254,11,73,7,232,15,186,62,79,182,235,167,34,73,55,83,151,80,252,
94,77,251,165,95,175,149,122,171,180,222,42,173,183,74,235,173,210,250,170,116,251,42,221,190,74,183,175,214,237,170,181,124,
181,150,175,214,242,213,90,190,90,203,47,192,78,231,254,22,32,43,49,228,243,50,50,53,181,36,93,74,182,164,203,201,165,169,91,
243,243,53,45,144,180,153,252,154,22,203,253,214,37,245,214,160,255,143,72,90,77,223,144,212,69,223,37,117,22,62,46,233,89,
180,90,238,51,181,62,181,122,190,181,240,148,251,36,157,71,95,148,116,33,61,36,169,90,191,90,248,205,99,146,238,160,39,36,221,78,
199,52,253,31,73,139,232,71,146,214,208,255,147,116,62,253,88,210,85,228,145,253,181,82,142,166,94,161,248,185,146,182,145,79,
168,120,80,42,233,92,154,39,105,41,149,73,186,149,170,37,109,164,5,146,118,83,139,164,27,40,34,227,68,189,156,199,66,100,94,
247,232,56,241,180,140,15,103,97,230,138,186,37,157,67,95,147,180,140,30,38,62,235,23,75,126,163,150,135,118,26,18,76,43,232,
109,130,207,118,213,174,73,219,167,9,158,254,77,226,51,92,245,211,12,59,255,142,56,183,236,145,114,45,240,124,222,15,203,116,
187,101,144,59,172,159,111,212,207,55,73,90,71,47,232,231,165,66,229,1,27,37,141,208,160,224,28,53,76,239,35,206,83,149,158,
21,186,253,10,200,127,66,210,74,217,207,10,100,191,47,75,26,162,38,161,248,172,111,165,110,183,82,247,191,82,247,179,82,247,179,
82,247,211,138,241,255,146,152,6,233,159,196,121,135,26,215,106,77,219,180,158,54,100,187,107,4,231,199,234,185,93,251,23,159,
77,96,203,119,35,36,227,2,206,50,36,226,55,32,17,62,178,69,229,97,194,53,147,71,113,253,213,168,127,98,139,122,14,233,246,204,
127,251,18,69,111,66,253,31,116,125,149,174,111,114,212,63,128,250,186,173,170,126,129,214,107,59,244,31,67,253,144,174,175,209,
252,118,71,253,175,80,255,30,93,95,171,245,207,1,198,180,254,151,81,255,57,93,191,80,183,115,142,127,29,228,22,109,83,207,
117,142,241,101,234,183,161,190,91,215,115,14,30,197,197,96,108,64,201,165,52,189,124,96,166,238,26,71,249,227,186,254,14,7,
239,11,186,252,16,232,55,29,229,99,3,42,151,103,153,159,1,255,171,219,254,73,83,99,139,162,69,154,134,53,237,208,52,162,105,108,
203,76,95,251,53,239,93,91,88,183,33,203,231,111,80,247,140,73,127,30,158,171,225,59,147,254,79,226,121,216,111,33,234,15,251,
13,26,14,24,56,183,88,158,245,36,55,168,123,66,4,53,23,249,175,32,62,21,19,161,113,172,181,87,222,13,44,45,183,111,131,186,67,
92,36,123,241,137,68,200,192,126,130,172,223,150,207,124,30,152,168,99,217,119,109,80,103,94,36,100,81,164,202,130,204,45,168,
241,138,5,184,224,38,66,183,98,124,62,248,98,143,148,177,101,22,128,188,17,109,230,130,78,249,111,71,159,62,49,229,255,52,183,
49,90,141,60,240,110,67,153,219,248,40,16,72,52,173,130,39,133,95,206,215,35,35,58,186,65,217,129,239,53,46,57,51,220,179,55,
168,123,99,160,112,105,177,77,129,170,150,226,66,140,163,16,253,249,176,127,114,41,210,204,227,226,91,148,207,72,132,62,5,223,
13,116,182,20,87,34,126,205,161,50,227,66,186,40,212,12,222,76,139,192,41,45,110,150,181,150,182,69,59,34,105,190,156,11,247,
253,205,13,234,94,227,180,85,39,180,32,122,106,253,95,208,250,3,162,64,68,154,149,229,133,148,252,79,105,169,240,171,94,104,98,
237,79,109,80,239,56,3,37,1,151,214,7,61,94,42,179,160,199,206,147,122,34,232,59,33,47,151,62,177,74,100,234,124,186,46,252,
74,107,206,114,170,54,188,240,4,182,89,153,101,161,191,38,182,178,149,8,249,113,6,85,155,121,168,11,192,114,137,80,49,178,101,
230,207,193,45,215,103,5,106,185,20,161,5,233,117,232,161,0,173,125,246,128,109,185,46,242,127,68,181,247,23,161,149,207,78,
172,203,165,206,255,14,127,53,17,242,225,6,28,254,18,101,253,203,187,81,221,73,79,246,175,75,225,95,249,200,211,92,202,231,55,
170,123,232,164,191,1,109,134,23,228,208,112,141,139,134,107,189,180,115,161,7,150,63,47,228,150,107,107,75,255,18,84,183,81,
197,146,128,25,233,116,81,171,112,19,211,132,127,49,234,34,157,57,224,228,72,26,233,242,162,175,255,130,157,135,187,161,179,219,
5,45,121,122,5,2,122,5,194,207,171,120,196,186,133,168,199,241,37,228,152,218,209,7,199,206,132,159,51,254,164,255,42,237,95,
25,31,239,217,168,226,104,36,132,126,170,184,159,41,233,215,133,50,142,8,233,151,155,54,170,189,26,128,229,132,230,109,219,56,
227,171,249,152,63,223,221,119,108,84,123,171,35,215,71,67,151,121,200,125,208,125,141,184,89,220,103,125,107,159,167,83,203,
90,250,246,191,219,209,222,208,99,153,218,168,98,98,196,159,163,60,213,15,171,64,98,187,223,45,125,133,159,19,161,37,24,95,192,
127,158,223,125,82,219,75,222,162,109,107,182,109,61,183,165,76,91,30,11,143,225,221,122,109,39,253,124,235,24,22,62,26,54,114,
105,24,222,148,159,93,171,107,29,107,149,171,215,42,23,86,93,32,215,202,167,215,202,135,181,202,203,174,21,244,116,231,254,
27,107,117,123,118,173,250,102,93,171,207,101,215,10,253,84,229,205,186,86,247,103,214,10,158,232,214,51,252,42,120,69,220,
174,89,143,28,52,177,174,134,58,99,51,99,235,20,122,108,175,169,119,50,122,108,222,204,122,63,233,88,175,12,239,167,14,158,41,71,
137,115,110,163,122,143,51,44,10,96,79,182,234,176,145,47,227,180,122,27,244,130,163,77,198,23,254,58,11,207,232,117,198,66,
75,206,41,191,87,197,227,64,168,197,46,128,15,36,224,179,38,172,193,81,195,195,251,23,113,231,157,242,54,51,163,167,162,247,116,
221,139,102,225,45,159,133,215,217,123,242,252,248,207,192,44,188,115,28,60,91,90,14,231,90,47,71,8,182,67,17,236,240,25,105,
7,156,91,102,33,13,91,60,66,75,90,209,164,169,94,245,30,168,220,168,161,10,35,32,134,155,3,88,249,187,81,3,159,109,246,99,189,
10,36,77,192,103,133,46,113,180,97,73,63,158,11,33,225,149,52,225,47,213,252,66,10,26,184,235,137,160,81,39,242,68,248,53,158,
205,60,212,85,202,241,153,50,255,112,73,159,90,116,105,253,146,69,186,108,208,7,122,85,78,90,110,98,44,102,100,16,186,141,5,
196,52,225,159,199,49,83,36,252,213,242,52,11,44,107,233,154,11,110,21,71,123,163,204,186,6,190,216,136,8,204,103,155,137,253,
230,201,158,48,65,179,16,40,195,242,133,223,200,67,169,206,80,249,195,66,180,172,215,190,36,112,255,202,248,239,103,122,85,125,
196,239,207,250,53,215,124,161,55,115,190,171,209,200,248,235,87,111,10,213,249,174,246,233,151,122,249,30,137,57,8,204,65,68,
154,2,24,77,30,69,154,10,249,14,64,252,28,89,10,13,161,125,136,220,65,193,254,31,20,117,164,250,44,146,125,21,99,223,168,179,
252,209,94,245,14,53,96,77,250,107,97,185,225,72,49,13,239,40,134,69,138,169,204,252,51,180,148,224,70,228,51,42,141,18,26,30,
44,1,191,132,86,225,124,42,51,176,163,204,49,185,219,113,115,194,153,181,2,62,240,81,62,19,6,231,225,105,25,158,62,36,159,74,
79,170,43,59,233,105,142,212,151,240,47,100,203,83,149,25,48,150,47,245,210,6,76,51,17,74,35,214,29,55,12,172,173,148,105,90,
68,131,86,248,113,143,62,171,254,212,171,114,207,200,72,41,218,31,225,157,97,113,30,98,145,215,108,53,155,100,30,98,201,113,7,
41,130,160,86,105,250,165,77,205,153,21,54,3,203,91,134,126,123,66,175,176,89,102,171,21,30,202,174,240,79,78,232,21,54,19,161,
247,227,172,101,205,79,156,40,52,2,70,248,159,46,61,142,185,125,234,253,120,164,171,12,250,63,206,243,48,166,252,119,104,250,
105,80,110,53,95,142,199,144,154,27,41,210,13,217,208,81,57,150,74,172,97,194,63,34,71,192,189,12,73,249,103,79,20,138,128,8,
255,147,227,140,242,158,230,62,245,174,189,220,174,165,10,59,146,230,89,95,207,179,181,22,116,117,241,89,144,86,118,192,156,93,
66,102,100,46,212,181,90,115,101,207,46,104,175,52,131,116,28,241,46,130,43,81,165,165,172,193,121,193,128,101,8,33,194,191,13,
218,133,70,158,21,180,235,44,246,139,6,217,107,163,244,19,254,59,214,167,222,209,151,91,232,31,190,50,193,185,150,57,60,52,
151,130,214,204,106,39,66,23,82,140,103,162,86,196,228,113,152,114,28,65,57,14,53,227,133,50,170,207,145,185,203,53,216,81,9,
255,229,188,30,214,69,254,43,117,166,195,220,240,51,121,102,208,170,51,121,158,108,197,5,77,156,145,222,192,86,196,92,144,145,
154,60,78,142,24,43,179,123,108,5,185,244,110,186,70,175,75,185,129,241,26,145,78,101,29,49,51,38,209,42,74,51,99,194,170,4,
229,109,183,82,40,187,200,252,206,95,41,243,187,150,179,159,63,17,52,56,222,4,40,252,127,42,226,168,189,84,39,123,10,19,231,157,
220,239,93,125,234,59,136,114,47,250,244,182,250,139,41,48,47,209,148,162,27,243,124,88,211,45,20,249,250,60,204,224,99,200,
197,185,119,55,86,45,232,45,164,192,162,240,139,131,205,243,105,50,116,17,238,192,62,119,171,123,21,69,246,97,44,46,68,70,87,11,
164,90,225,175,131,205,21,104,59,31,121,183,15,121,118,14,110,11,33,114,127,221,122,118,159,139,223,144,182,98,6,172,189,218,
106,129,158,171,97,197,68,211,77,212,108,5,189,225,99,24,177,183,78,168,246,101,220,222,211,234,249,221,137,106,156,131,147,235,
122,233,225,214,240,51,65,47,102,246,32,201,251,124,55,102,196,223,189,168,44,104,163,244,63,94,255,122,48,249,253,98,192,21,
73,97,55,134,206,194,126,80,190,16,73,205,129,205,110,228,83,42,165,246,128,90,249,59,100,182,201,214,182,165,79,151,72,107,219,
210,3,26,149,44,246,64,145,92,77,222,3,187,32,31,254,157,90,243,128,57,140,250,32,116,207,59,85,163,99,151,151,57,118,249,66,
146,178,208,184,64,106,108,65,187,15,202,118,149,102,53,228,182,177,246,223,12,239,155,7,185,217,34,70,117,70,151,242,5,83,80,
43,34,6,211,74,211,146,39,147,233,120,114,201,167,76,84,41,101,221,63,83,122,175,69,153,179,26,206,190,56,143,89,4,251,45,211,
249,141,192,109,182,64,223,21,70,54,169,247,211,51,123,39,242,212,92,170,218,19,48,197,242,229,211,45,216,107,46,59,225,15,113,
244,241,84,93,9,238,178,229,215,214,32,50,122,144,199,7,101,244,106,249,205,92,170,246,46,204,156,16,185,129,162,150,53,240,
166,162,132,191,130,235,125,147,235,14,210,151,31,225,189,244,73,58,110,90,66,44,11,63,29,48,195,127,58,110,218,66,44,15,255,160,
208,176,245,222,190,114,147,218,43,129,80,185,192,138,194,243,63,192,86,49,86,25,33,120,83,28,62,193,62,135,155,153,63,200,39,
245,210,57,210,139,187,228,141,207,141,179,37,252,87,117,194,76,134,246,106,89,139,185,127,8,138,128,204,3,121,190,243,49,38,
158,77,142,180,67,69,54,167,61,186,73,249,93,192,63,25,74,200,155,99,97,182,238,230,76,93,104,166,46,160,199,124,199,38,245,253,
158,7,188,237,158,114,106,245,108,193,189,75,237,60,15,242,163,72,46,172,249,96,192,35,174,92,254,157,77,176,91,110,78,192,
208,59,218,195,109,6,243,230,83,203,241,85,196,54,134,230,188,170,95,6,60,203,159,111,166,13,86,158,135,45,140,249,187,90,246,
177,117,43,164,135,200,54,5,21,212,242,231,114,240,202,217,83,108,182,19,124,27,251,41,63,147,39,120,202,114,254,33,79,145,235,
81,95,105,55,243,14,183,3,117,225,123,143,123,60,34,124,236,184,39,71,136,43,195,247,7,189,101,72,142,195,127,206,243,96,111,
122,56,131,28,68,235,183,103,227,216,5,217,187,241,156,205,234,59,56,142,172,221,50,42,201,40,230,184,115,23,56,238,220,11,101,
44,133,55,24,45,131,47,159,96,107,241,25,98,105,159,59,107,179,186,187,241,249,199,185,87,160,185,197,111,194,79,35,56,227,3,
34,177,46,76,77,129,240,235,252,78,219,144,57,212,50,200,223,196,241,44,15,86,205,75,139,121,188,91,61,108,69,15,172,19,112,7,
114,248,198,23,241,149,225,182,248,30,62,99,242,217,47,94,128,239,173,242,157,47,123,129,156,47,176,166,229,133,38,142,132,176,
144,135,124,190,178,124,117,206,190,32,45,132,115,214,86,183,115,68,6,75,233,83,254,213,143,182,173,190,118,154,225,29,5,207,
231,174,116,91,14,222,39,192,171,206,173,145,28,15,252,205,99,32,23,61,39,74,185,85,167,141,13,17,241,133,220,234,252,54,236,
183,59,49,235,214,156,38,42,242,29,115,95,246,176,237,231,156,17,59,121,221,93,244,144,63,152,151,159,25,143,143,117,240,14,120,
152,124,222,86,47,188,234,46,65,238,135,43,97,77,55,226,180,71,198,5,183,140,7,110,74,227,60,41,162,96,94,248,215,121,190,96,
94,157,175,200,55,74,225,31,230,249,194,175,241,89,49,141,21,226,239,215,216,155,142,100,115,185,235,197,165,245,71,197,245,130,
243,62,67,230,218,15,108,86,223,195,149,187,97,115,55,159,69,94,156,232,108,115,156,231,70,228,136,154,143,193,235,0,27,93,129,
117,104,117,33,154,30,65,236,9,93,71,183,226,121,149,171,150,78,150,59,10,57,159,171,210,197,81,118,84,230,2,39,215,127,2,245,
172,161,218,19,164,201,166,165,116,155,201,81,227,10,10,186,243,73,143,192,226,213,227,220,162,204,163,86,239,10,25,199,177,122,
162,92,90,75,200,92,85,82,23,91,44,142,213,109,181,225,91,67,42,102,182,154,30,29,69,85,244,228,168,233,165,240,19,121,174,
240,247,243,92,65,119,157,75,157,169,156,87,76,234,120,121,177,220,15,136,82,151,214,167,247,209,62,105,35,142,13,158,126,245,
61,127,57,102,90,33,109,227,37,175,205,231,231,185,242,253,207,64,54,54,71,4,122,23,22,5,138,3,115,90,144,248,4,172,200,117,
234,68,177,229,217,116,135,166,242,140,194,138,190,120,66,159,81,242,68,25,172,155,79,90,187,167,229,208,139,39,100,91,88,179,
86,122,174,58,97,108,89,86,39,12,34,100,113,248,241,86,225,209,55,25,117,139,137,92,199,107,243,97,212,6,93,200,197,237,160,
171,206,86,115,61,87,238,245,157,217,119,99,155,251,79,190,15,242,253,121,168,63,27,31,183,93,66,75,35,62,153,109,24,178,238,
63,250,213,61,148,223,197,5,140,201,193,75,168,219,207,245,57,114,191,27,20,235,87,191,169,8,20,113,236,227,93,195,107,196,223,
90,114,70,102,82,129,161,78,112,126,135,232,134,5,91,109,68,115,43,252,42,238,48,86,157,17,153,154,67,186,149,29,112,241,238,
25,116,69,166,138,193,155,43,179,219,192,156,106,215,2,248,203,14,218,231,78,172,195,9,24,243,33,171,193,138,59,218,201,86,34,
104,137,165,225,239,242,189,245,16,133,255,174,222,121,122,49,83,254,158,117,177,180,65,105,54,70,189,191,95,189,31,200,196,
164,58,196,36,117,15,85,86,250,176,182,71,196,63,79,238,126,126,55,165,252,194,162,155,250,213,111,63,120,142,94,185,51,56,186,
169,157,20,57,162,162,202,173,146,143,123,204,17,21,81,110,149,107,13,255,52,212,170,25,114,213,12,93,255,9,212,243,169,118,
187,244,230,0,234,98,236,41,50,91,177,179,187,140,178,187,136,61,31,178,176,46,124,175,251,164,91,135,43,17,186,24,187,66,101,
11,65,119,248,94,229,245,121,162,204,133,155,148,167,72,222,164,174,192,153,160,222,211,244,192,42,91,244,253,251,108,29,47,
206,209,249,174,65,67,151,214,239,24,202,190,219,249,75,191,243,221,206,185,98,62,157,103,148,211,185,102,133,124,107,165,110,
157,214,128,122,39,31,16,171,16,115,11,112,142,188,79,102,78,76,57,147,111,89,250,218,9,247,54,62,65,6,187,42,104,16,109,91,
150,190,120,98,123,87,57,109,55,203,81,126,254,196,96,215,124,240,113,106,46,125,230,68,160,48,252,116,230,29,31,206,168,1,245,
125,68,53,238,11,131,157,243,233,135,129,131,160,21,84,100,30,164,229,205,69,178,124,79,213,100,232,16,199,88,255,97,121,62,
109,239,196,153,141,157,18,248,211,231,171,10,68,145,184,148,194,47,65,235,223,213,13,25,121,211,128,246,123,216,172,18,8,98,46,
153,119,72,77,3,42,167,80,243,205,215,177,212,160,229,3,250,253,149,152,121,251,106,82,161,208,239,91,113,86,254,227,68,185,
81,135,251,192,46,17,164,85,2,89,180,168,196,154,181,32,35,143,131,19,148,252,240,243,153,28,191,80,234,16,250,187,18,33,51,24,
83,247,181,117,64,125,71,83,70,234,142,108,200,222,44,249,61,113,57,114,161,10,177,11,245,171,136,51,245,90,244,177,7,109,121,
38,65,201,15,191,152,185,147,231,233,62,42,178,125,148,203,223,192,10,34,202,216,130,231,122,83,64,125,199,116,27,232,61,217,
95,217,170,63,15,234,231,140,60,255,198,234,49,240,142,157,194,223,164,249,63,63,165,253,243,167,60,255,37,160,250,204,252,134,
137,223,57,90,69,234,183,69,197,69,234,187,152,242,34,245,155,9,31,232,152,214,27,7,173,193,243,133,160,75,138,212,111,63,150,
129,182,23,157,172,191,247,148,103,65,250,251,44,82,177,140,41,255,150,199,144,180,89,198,202,2,100,203,182,174,43,119,204,73,
144,202,105,12,253,100,106,186,134,102,126,167,149,249,30,201,144,180,73,62,47,212,252,238,172,156,79,183,85,191,50,81,125,173,
205,182,103,152,89,204,149,177,203,208,54,50,29,182,82,60,151,230,185,36,79,149,221,89,93,57,154,250,53,13,104,153,128,214,43,
223,179,211,204,247,103,114,143,201,12,90,217,158,101,249,251,228,133,186,95,254,46,119,161,222,139,181,248,107,105,234,215,
241,97,149,110,179,74,223,73,88,174,93,239,165,53,186,110,173,30,191,149,45,203,89,135,201,12,247,225,14,179,136,106,154,90,186,
90,155,214,47,239,172,95,223,179,190,181,126,89,87,75,75,125,231,202,229,205,245,43,186,215,183,44,91,223,189,172,123,101,
19,76,139,124,173,125,100,60,158,136,167,215,144,171,93,81,99,77,27,89,107,218,22,237,224,79,148,253,93,227,211,177,116,50,153,
30,27,136,38,162,123,98,83,180,250,84,78,40,54,53,149,156,90,29,26,73,78,143,143,134,18,201,116,104,79,44,29,202,74,133,250,
215,135,82,35,209,68,2,109,215,254,107,109,71,99,187,163,211,227,78,29,209,209,232,100,26,10,202,122,166,39,38,14,100,249,27,
163,233,116,119,116,124,124,87,116,228,66,18,125,100,244,245,147,217,215,223,79,149,125,91,67,235,247,143,196,38,211,241,36,130,
249,88,124,60,22,26,25,79,166,226,137,61,161,201,228,84,154,106,251,182,190,89,253,68,124,52,142,33,236,139,143,196,72,108,34,
107,211,246,238,245,84,184,105,122,36,54,128,154,190,196,228,116,122,27,171,8,100,88,91,167,211,25,158,47,195,147,79,197,153,
167,161,233,73,238,181,97,111,116,95,148,68,63,25,253,125,100,246,247,201,15,244,128,15,100,22,24,182,217,143,15,171,191,127,
103,63,213,244,71,19,163,83,201,248,104,227,174,204,108,27,179,243,238,84,230,104,163,5,111,37,213,35,231,208,70,85,111,37,196,
38,108,163,69,103,18,201,88,185,141,26,207,40,58,22,157,138,142,96,120,241,84,58,62,210,70,139,207,212,160,39,150,26,153,138,
79,166,147,83,179,15,100,60,54,35,223,31,27,82,190,52,251,220,33,202,245,51,163,125,19,125,44,180,33,62,142,65,214,116,77,199,
199,71,89,223,108,102,58,73,244,45,69,6,99,41,184,236,236,179,213,34,67,177,116,26,14,150,154,233,242,45,166,144,17,110,163,
121,89,161,145,100,34,29,75,164,27,187,153,238,71,103,149,217,170,137,216,104,60,218,200,174,219,200,14,151,89,250,37,111,45,208,
151,216,157,172,97,87,229,130,115,56,111,42,221,70,181,111,45,52,148,142,166,167,49,234,234,55,19,203,110,32,167,43,157,34,163,
163,67,141,82,57,179,154,43,207,212,96,107,66,53,217,58,25,75,196,70,251,225,129,49,233,43,161,51,52,124,139,185,207,236,110,
231,250,159,34,52,24,27,137,197,247,177,158,162,172,72,50,213,216,53,157,24,29,199,50,20,59,153,189,81,102,66,180,196,201,221,
22,157,26,137,141,111,159,142,143,182,81,32,91,49,157,142,143,55,246,39,247,156,198,219,22,141,79,57,250,202,242,218,104,251,
233,204,246,51,184,201,25,227,3,14,130,166,254,145,228,68,227,212,68,106,188,113,47,162,90,227,41,161,173,230,212,200,222,70,
205,103,104,113,90,68,109,163,165,255,98,19,231,154,44,249,23,219,40,233,254,51,72,207,88,37,235,131,111,122,226,180,81,207,191,
173,109,134,195,46,26,137,166,46,60,179,161,78,211,114,230,73,103,38,188,45,154,30,227,48,241,150,210,188,89,71,163,227,251,
226,23,54,34,180,38,177,129,113,40,54,174,79,232,3,177,123,60,154,194,134,14,206,34,211,199,145,88,215,87,205,82,63,16,155,216,
165,5,98,16,169,152,69,100,40,190,39,129,136,49,133,93,82,54,75,117,100,108,42,121,49,154,206,233,231,179,179,49,158,108,116,
28,220,109,84,168,216,227,209,196,158,70,61,142,34,7,171,15,113,82,218,43,224,96,110,221,181,55,54,146,62,153,55,148,158,194,
76,179,221,72,158,236,58,186,139,247,111,185,131,61,21,219,221,120,78,44,122,225,96,108,119,108,42,150,64,146,80,241,86,181,
188,249,101,181,220,141,157,83,83,209,3,28,150,50,61,157,204,109,163,238,217,216,237,255,206,106,175,225,67,111,86,37,167,77,119,
77,214,8,51,162,169,147,121,189,209,20,118,244,100,198,170,78,222,233,130,56,179,78,19,4,239,100,19,244,225,36,141,202,179,190,
192,193,149,54,241,159,194,104,163,150,83,56,237,103,60,128,215,156,172,87,118,95,232,96,68,226,19,236,16,115,78,101,169,173,
88,120,218,94,163,206,211,88,179,39,173,142,211,36,148,58,128,131,103,34,148,138,77,201,44,50,112,250,174,39,159,115,209,168,
214,121,228,55,116,119,246,247,119,117,118,111,190,32,114,238,182,245,23,12,116,70,186,123,47,232,223,58,20,33,177,131,140,29,
200,26,119,32,207,181,118,244,237,236,35,215,142,77,200,35,55,129,141,236,113,7,210,74,107,7,231,149,246,14,201,5,71,126,176,
116,191,170,68,217,230,207,77,138,32,23,221,177,147,4,210,79,40,51,144,119,26,195,93,84,61,124,230,84,168,126,248,223,74,45,106,
254,5,113,236,221,225,89,246,233,73,204,204,70,205,141,142,140,196,82,169,13,227,209,61,41,242,34,221,156,142,142,203,156,219,
157,185,42,152,209,209,81,126,26,157,130,28,249,116,239,125,137,209,216,126,180,86,79,178,133,55,58,57,169,51,42,114,69,83,202,
19,119,157,146,106,83,89,150,211,191,94,238,61,181,182,219,183,247,245,80,96,215,105,233,169,67,67,198,145,138,103,56,217,105,
167,28,114,23,232,59,71,206,174,116,167,30,181,103,87,90,201,65,76,151,82,124,160,195,4,228,218,149,230,195,136,236,93,156,77,
146,111,68,159,74,145,3,147,49,114,97,20,72,39,40,127,228,164,100,156,236,145,241,88,116,138,73,50,21,35,55,18,202,4,108,76,
185,186,32,21,122,56,205,140,198,19,41,201,150,165,205,177,3,82,88,218,200,167,11,145,228,118,232,176,177,11,18,105,18,163,228,
29,205,230,241,228,210,115,241,40,10,27,101,74,163,148,151,41,41,5,185,163,89,7,72,101,234,50,38,243,170,71,153,236,228,140,198,
167,48,68,132,125,176,227,169,204,208,93,177,139,176,244,41,202,145,155,178,59,57,10,3,198,50,7,4,53,236,142,226,106,55,26,
74,39,67,35,83,177,104,58,22,218,53,61,174,239,148,74,119,104,247,84,114,34,148,113,19,207,238,120,34,58,30,127,71,140,170,80,
26,157,89,168,13,201,41,199,237,75,9,87,178,72,102,67,207,38,96,239,142,79,193,153,124,187,97,162,209,204,130,123,185,67,229,
198,100,237,97,131,231,240,167,50,134,137,72,66,94,124,100,84,228,114,121,92,186,118,138,202,248,65,121,238,105,215,242,249,
51,117,167,199,176,57,92,57,57,57,30,31,145,167,106,198,219,139,192,62,109,208,165,78,166,51,167,151,90,78,191,136,145,7,108,121,
246,82,33,74,61,234,238,158,217,54,57,146,37,125,33,63,91,84,107,237,205,62,167,200,141,178,116,190,69,40,244,78,79,112,56,199,
70,198,225,171,44,53,171,117,33,10,199,146,100,84,106,96,189,84,141,2,31,144,167,25,99,75,116,130,153,125,61,41,170,59,93,70,
102,161,167,9,134,79,23,84,185,231,105,146,243,32,201,213,167,14,19,147,155,171,171,122,178,206,140,233,232,33,179,6,185,200,
188,194,242,33,47,243,48,205,185,19,249,245,35,31,19,220,172,71,218,91,249,131,20,157,74,78,198,166,210,113,244,83,128,199,193,
216,68,50,29,203,4,13,48,134,228,81,164,163,149,236,82,6,136,188,49,121,11,209,247,22,114,143,69,83,91,216,37,60,40,140,201,
93,100,141,37,225,187,57,252,169,124,83,196,201,140,143,238,39,43,206,102,182,227,114,17,115,226,217,247,33,185,241,84,118,242,
252,208,173,118,104,12,19,141,167,214,79,76,166,15,112,65,218,153,171,103,94,164,120,226,58,37,32,15,167,55,189,220,175,111,175,
243,69,138,121,33,2,144,11,31,156,97,120,199,147,136,117,42,144,187,39,180,135,91,124,158,144,119,34,107,102,42,156,56,109,
27,228,79,156,180,10,148,59,225,8,196,198,196,4,153,19,169,61,248,72,79,147,149,224,181,176,249,19,81,33,17,187,152,247,0,140,
146,96,35,153,201,93,123,201,149,220,189,59,133,225,4,146,137,174,104,122,100,108,38,7,73,81,9,246,216,73,129,23,79,137,61,176,
68,241,169,21,236,230,52,231,84,238,57,83,48,137,212,162,108,136,61,43,251,87,106,200,159,76,204,188,51,145,26,10,157,28,213,
58,47,169,239,194,112,68,244,156,159,60,233,106,204,125,58,159,123,98,227,209,3,96,23,100,216,236,72,251,156,114,42,8,100,38,
226,78,38,54,140,79,167,198,200,151,76,12,164,167,51,108,140,140,199,163,188,112,48,149,138,83,41,115,198,227,188,149,229,184,
186,147,19,147,136,192,144,69,75,153,80,200,8,157,121,82,22,132,113,145,13,37,164,189,180,235,166,122,56,230,227,138,13,217,
34,184,124,226,148,24,69,94,102,234,114,30,151,103,28,172,132,31,79,186,106,158,19,79,143,97,43,149,102,42,102,46,148,186,38,
144,169,113,240,242,153,231,120,217,151,195,207,106,39,122,146,153,188,46,39,83,66,128,194,224,248,16,75,206,52,177,147,23,115,
200,44,154,132,251,157,58,129,178,89,152,67,233,216,100,228,226,36,149,156,84,55,19,76,200,154,228,244,209,146,239,52,115,38,
101,186,197,251,194,51,169,51,47,85,146,129,37,63,83,210,17,75,214,200,236,51,47,83,82,27,93,86,200,40,145,159,41,69,146,27,112,
214,145,61,41,103,107,242,22,158,59,21,219,195,239,87,166,78,126,73,67,174,41,233,57,228,85,84,133,6,85,86,249,214,188,41,28,
217,177,84,122,198,183,183,77,197,147,240,141,3,220,86,46,191,123,74,111,36,48,210,251,162,227,100,77,177,47,153,83,211,9,42,
76,101,179,80,253,30,141,138,82,142,236,57,195,116,103,94,58,123,82,35,99,177,81,28,251,228,74,197,144,54,140,146,149,98,223,
42,227,79,245,182,119,44,58,26,234,219,26,154,201,27,60,92,199,102,166,2,236,241,110,103,106,149,11,6,123,234,0,7,201,124,126,
208,153,224,116,124,20,149,99,124,41,192,94,193,68,173,20,39,18,118,74,62,228,72,194,13,41,79,21,211,201,73,249,232,74,169,227,
213,74,129,131,158,51,252,28,120,79,102,149,211,99,113,24,131,63,107,154,80,129,11,11,26,77,76,146,59,157,148,183,54,242,164,
147,58,167,152,51,157,152,205,187,230,157,194,118,248,80,233,116,226,77,214,210,134,237,167,113,58,72,178,117,55,69,196,205,
194,157,111,188,76,109,251,141,171,47,109,171,167,152,120,15,24,116,129,36,135,76,58,34,44,218,73,224,124,85,8,178,172,7,140,213,
163,238,252,19,38,221,111,228,237,180,137,62,33,196,231,89,254,22,97,124,68,220,111,184,243,47,236,55,233,54,97,213,95,111,83,
199,254,126,23,53,28,121,7,196,62,32,164,190,195,82,95,195,254,16,237,21,223,54,220,75,32,251,1,97,54,24,21,23,27,123,42,250,
77,241,65,145,211,240,158,134,157,166,241,160,145,251,225,157,166,249,85,35,127,243,206,142,71,250,182,218,134,109,210,141,74,
201,17,250,190,176,94,23,135,196,103,140,103,240,216,94,143,63,237,244,186,32,119,197,150,183,111,62,80,95,111,76,87,84,154,
244,5,209,64,199,193,204,111,111,167,163,6,207,224,105,126,162,107,101,249,253,134,245,119,113,153,113,139,248,33,198,92,127,
11,221,96,152,234,25,117,207,177,220,163,59,59,232,165,76,225,78,195,84,29,170,238,232,167,198,44,157,221,107,168,206,94,146,
29,124,69,126,126,111,70,237,150,11,140,75,50,162,95,151,149,223,146,159,55,153,6,6,143,65,180,215,211,189,166,241,21,113,35,
143,225,30,211,228,210,227,232,145,62,239,40,127,154,203,143,27,255,132,76,199,230,15,211,23,249,241,211,170,234,1,71,249,33,
46,255,67,149,31,228,242,151,13,89,254,50,119,32,75,119,103,75,223,54,45,250,140,184,77,60,8,157,59,121,118,199,76,140,171,163,
29,139,243,37,99,109,255,206,225,53,91,206,95,83,111,147,177,191,205,69,244,146,172,236,143,155,226,57,81,116,224,17,185,160,
245,231,219,100,139,249,149,171,232,160,197,83,122,183,252,124,15,127,118,28,218,31,44,167,235,45,118,179,10,227,176,213,102,
188,126,201,146,250,71,251,141,252,139,141,125,21,251,247,239,63,16,71,55,162,91,233,91,189,198,22,244,126,151,92,102,17,240,
91,198,171,162,178,243,144,179,171,111,115,79,182,65,71,181,208,92,191,73,183,139,38,200,28,53,106,111,227,74,186,220,195,253,
30,54,141,255,21,221,65,147,158,21,66,184,109,50,5,10,143,88,38,107,20,134,45,92,36,114,109,114,137,74,219,172,151,26,127,105,
137,187,97,142,142,120,229,176,101,220,105,44,31,22,197,126,83,220,110,212,237,55,14,124,131,37,214,187,12,140,245,255,196,122,
186,218,35,238,224,5,16,129,2,139,88,225,175,67,54,205,175,60,139,254,100,153,183,139,95,139,63,112,101,187,153,243,89,67,244,
155,38,84,52,30,50,106,150,24,219,43,108,211,206,89,238,50,93,57,123,45,247,221,104,215,176,217,116,221,38,10,27,224,22,47,137,
5,13,123,77,227,19,198,188,122,12,143,118,217,6,246,206,173,205,152,145,105,187,108,183,113,17,140,143,150,46,151,123,175,233,
249,173,152,35,165,132,233,34,195,87,1,33,136,216,158,74,186,7,54,175,24,238,24,22,115,11,48,118,209,244,77,91,172,110,175,228,
39,227,101,81,7,147,218,70,3,27,22,91,180,178,179,127,255,59,174,180,105,184,157,94,112,203,169,99,222,247,24,203,119,30,49,
177,3,138,238,229,185,7,226,183,236,91,213,191,200,22,219,176,135,239,182,217,228,21,29,123,227,43,140,253,21,135,154,91,227,
13,149,116,92,46,245,143,228,231,119,221,226,86,168,57,96,186,49,241,142,251,228,178,5,77,227,21,33,110,189,213,180,160,13,179,
189,219,16,152,180,121,155,33,246,110,54,237,59,140,165,113,225,181,237,6,151,93,203,38,198,92,45,219,182,93,198,72,133,237,94,
238,18,46,195,101,241,148,141,75,218,80,225,50,74,54,177,20,252,236,117,183,113,183,113,59,59,64,113,129,73,159,54,234,46,196,
0,239,113,161,71,119,254,37,229,244,172,75,60,193,107,185,217,244,96,12,232,246,118,33,130,166,251,43,194,174,52,115,158,23,
107,159,57,16,124,196,180,57,20,109,54,173,235,196,210,91,133,199,118,215,163,159,242,139,121,0,247,154,185,240,166,251,68,161,
223,206,13,26,163,21,24,132,125,139,229,125,141,87,108,111,67,251,218,117,182,119,133,26,40,91,221,206,89,197,99,116,121,92,57,
174,92,35,221,102,231,178,60,125,204,173,198,0,71,68,239,143,30,62,193,219,208,62,98,4,111,182,205,160,177,187,2,61,99,108,
143,152,66,13,131,48,140,67,150,45,251,232,119,153,13,237,93,114,233,237,205,77,210,48,22,61,160,102,215,113,216,34,237,58,240,
229,194,1,83,200,89,89,247,25,107,59,130,188,29,120,161,15,139,194,130,96,53,214,186,125,205,48,154,195,170,193,114,24,232,
105,151,218,241,159,178,13,118,83,148,110,179,5,91,145,224,224,71,132,123,137,26,134,49,103,204,72,84,116,196,141,188,37,198,
190,182,91,132,223,47,59,105,248,72,195,60,250,133,20,204,143,211,29,46,185,216,59,233,25,233,22,249,70,120,204,24,174,232,56,
124,136,227,138,141,142,207,179,13,172,48,236,200,147,131,5,218,113,34,108,182,172,215,102,198,126,73,167,109,194,139,97,72,
172,47,124,27,86,171,52,5,199,199,191,27,230,157,226,179,226,189,58,188,211,251,77,113,208,112,87,180,204,251,98,189,73,55,25,
117,116,136,163,16,182,159,73,191,17,77,159,183,169,29,145,233,81,236,251,138,122,106,103,87,252,130,71,124,10,190,138,16,118,
88,152,134,111,137,113,113,197,133,29,155,247,195,115,110,145,53,249,135,106,141,119,84,40,142,17,22,63,54,74,235,140,69,198,
127,11,43,231,31,162,36,215,88,12,78,185,85,114,126,137,93,50,84,226,81,143,118,137,40,137,130,177,164,36,71,138,214,228,184,
32,91,58,211,108,174,177,132,229,68,233,210,25,94,137,82,94,171,56,6,56,249,51,197,248,140,220,30,227,44,110,107,148,214,148,
46,200,116,119,158,236,189,102,166,127,102,156,93,178,33,195,112,151,156,3,198,26,160,9,82,222,12,147,165,122,75,182,227,179,
39,195,244,96,232,67,37,102,86,54,171,209,144,42,122,51,12,23,24,51,50,114,112,94,12,110,175,26,156,171,180,182,116,97,105,85,
105,168,180,178,180,90,148,88,194,20,57,102,153,129,63,194,104,57,120,208,58,182,112,153,56,88,39,196,109,192,147,192,225,48,
220,30,56,14,60,185,72,136,155,206,18,130,255,145,50,185,182,93,230,33,49,11,200,54,114,92,250,135,56,130,58,46,59,104,189,123,
137,117,185,65,107,196,189,75,132,117,125,189,16,15,212,27,226,231,160,47,3,239,110,16,226,30,224,81,224,96,35,2,188,59,87,182,
235,69,187,7,27,251,196,175,26,133,245,104,147,16,207,2,135,155,133,184,17,248,21,240,151,102,50,132,55,223,16,87,135,118,64,
244,240,210,115,196,109,75,133,120,16,56,6,60,11,92,223,34,196,93,192,195,192,147,192,243,192,27,45,100,9,219,143,201,10,110,
26,69,211,171,151,237,18,15,44,195,8,150,11,241,216,10,104,7,14,175,36,183,59,80,172,196,244,223,61,144,125,114,165,97,92,181,
74,24,215,174,54,141,55,86,11,227,141,54,211,184,171,195,107,220,180,102,204,122,99,173,41,30,239,130,165,186,77,241,100,15,
102,215,99,136,171,214,99,164,27,48,132,141,120,6,158,221,4,221,3,232,99,11,248,192,213,91,13,113,207,86,240,183,193,18,103,195,
186,103,195,2,198,124,193,127,14,10,116,120,253,208,101,152,212,16,38,19,225,159,231,5,189,194,251,46,113,248,160,245,66,132,
107,15,111,23,57,55,1,87,237,200,252,255,74,206,223,244,100,254,239,64,254,173,74,230,255,15,228,223,169,100,254,15,65,254,157,
74,136,212,255,35,200,191,213,201,252,95,130,46,154,249,255,4,77,191,250,29,141,252,61,85,72,253,63,82,219,192,112,133,148,12,
255,123,122,225,87,191,125,231,127,3,111,132,84,191,252,255,15,154,90,158,255,141,182,21,82,191,75,226,127,199,109,135,212,248,
248,223,224,147,214,195,255,38,159,127,204,195,124,254,127,15,255,63,171,27,97,244,48,81,0,0,0,0};
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
STATICMETHOD (getAndroidMidiDeviceManager, "getAndroidMidiDeviceManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$MidiDeviceManager;") \
STATICMETHOD (getAndroidBluetoothManager, "getAndroidBluetoothManager", "(Landroid/content/Context;)Lcom/rmsl/juce/JuceMidiSupport$BluetoothManager;")
DECLARE_JNI_CLASS_WITH_BYTECODE (JuceMidiSupport, "com/rmsl/juce/JuceMidiSupport", 23, javaMidiByteCode, sizeof (javaMidiByteCode))
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (getJuceAndroidMidiInputDeviceNameAndIDs, "getJuceAndroidMidiInputDeviceNameAndIDs", "()[Ljava/lang/String;") \
METHOD (getJuceAndroidMidiOutputDeviceNameAndIDs, "getJuceAndroidMidiOutputDeviceNameAndIDs", "()[Ljava/lang/String;") \
METHOD (openMidiInputPortWithID, "openMidiInputPortWithID", "(IJ)Lcom/rmsl/juce/JuceMidiSupport$JuceMidiPort;") \
METHOD (openMidiOutputPortWithID, "openMidiOutputPortWithID", "(I)Lcom/rmsl/juce/JuceMidiSupport$JuceMidiPort;")
DECLARE_JNI_CLASS_WITH_MIN_SDK (MidiDeviceManager, "com/rmsl/juce/JuceMidiSupport$MidiDeviceManager", 23)
#undef JNI_CLASS_MEMBERS
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
METHOD (start, "start", "()V") \
METHOD (stop, "stop", "()V") \
METHOD (close, "close", "()V") \
METHOD (sendMidi, "sendMidi", "([BII)V") \
METHOD (getName, "getName", "()Ljava/lang/String;")
DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiPort", 23)
#undef JNI_CLASS_MEMBERS
//==============================================================================
class MidiInput::Pimpl
{
public:
Pimpl (MidiInput* midiInput, int deviceID, juce::MidiInputCallback* midiInputCallback, jobject deviceManager)
: juceMidiInput (midiInput), callback (midiInputCallback), midiConcatenator (2048),
javaMidiDevice (LocalRef<jobject>(getEnv()->CallObjectMethod (deviceManager, MidiDeviceManager.openMidiInputPortWithID,
(jint) deviceID, (jlong) this)))
{
}
~Pimpl()
{
if (jobject d = javaMidiDevice.get())
{
getEnv()->CallVoidMethod (d, JuceMidiPort.close);
javaMidiDevice.clear();
}
}
bool isOpen() const noexcept
{
return javaMidiDevice != nullptr;
}
void start()
{
if (jobject d = javaMidiDevice.get())
getEnv()->CallVoidMethod (d, JuceMidiPort.start);
}
void stop()
{
if (jobject d = javaMidiDevice.get())
getEnv()->CallVoidMethod (d, JuceMidiPort.stop);
callback = nullptr;
}
String getName() const noexcept
{
if (jobject d = javaMidiDevice.get())
return juceString (LocalRef<jstring> ((jstring) getEnv()->CallObjectMethod (d, JuceMidiPort.getName)));
return {};
}
void handleMidi (jbyteArray byteArray, jlong offset, jint len, jlong timestamp)
{
auto* env = getEnv();
jassert (byteArray != nullptr);
auto* data = env->GetByteArrayElements (byteArray, nullptr);
HeapBlock<uint8> buffer (static_cast<size_t> (len));
std::memcpy (buffer.get(), data + offset, static_cast<size_t> (len));
midiConcatenator.pushMidiData (buffer.get(),
len, static_cast<double> (timestamp) * 1.0e-9,
juceMidiInput, *callback);
env->ReleaseByteArrayElements (byteArray, data, 0);
}
static void handleReceive (JNIEnv*, jobject, jlong host, jbyteArray byteArray,
jint offset, jint len, jlong timestamp)
{
auto* myself = reinterpret_cast<Pimpl*> (host);
myself->handleMidi (byteArray, offset, len, timestamp);
}
private:
MidiInput* juceMidiInput;
MidiInputCallback* callback;
MidiDataConcatenator midiConcatenator;
GlobalRef javaMidiDevice;
};
//==============================================================================
class MidiOutput::Pimpl
{
public:
Pimpl (const LocalRef<jobject>& midiDevice)
: javaMidiDevice (midiDevice)
{
}
~Pimpl()
{
if (jobject d = javaMidiDevice.get())
{
getEnv()->CallVoidMethod (d, JuceMidiPort.close);
javaMidiDevice.clear();
}
}
void send (jbyteArray byteArray, jint offset, jint len)
{
if (jobject d = javaMidiDevice.get())
getEnv()->CallVoidMethod (d,
JuceMidiPort.sendMidi,
byteArray, offset, len);
}
String getName() const noexcept
{
if (jobject d = javaMidiDevice.get())
return juceString (LocalRef<jstring> ((jstring) getEnv()->CallObjectMethod (d, JuceMidiPort.getName)));
return {};
}
private:
GlobalRef javaMidiDevice;
};
//==============================================================================
#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \
CALLBACK (MidiInput::Pimpl::handleReceive, "handleReceive", "(J[BIIJ)V" )
DECLARE_JNI_CLASS_WITH_MIN_SDK (JuceMidiInputPort, "com/rmsl/juce/JuceMidiSupport$JuceMidiInputPort", 23)
#undef JNI_CLASS_MEMBERS
//==============================================================================
class AndroidMidiDeviceManager
{
public:
AndroidMidiDeviceManager()
: deviceManager (LocalRef<jobject>(getEnv()->CallStaticObjectMethod (JuceMidiSupport,
JuceMidiSupport.getAndroidMidiDeviceManager,
getAppContext().get())))
{
}
Array<MidiDeviceInfo> getDevices (bool input)
{
if (jobject dm = deviceManager.get())
{
jobjectArray jDeviceNameAndIDs
= (jobjectArray) getEnv()->CallObjectMethod (dm, input ? MidiDeviceManager.getJuceAndroidMidiInputDeviceNameAndIDs
: MidiDeviceManager.getJuceAndroidMidiOutputDeviceNameAndIDs);
// Create a local reference as converting this to a JUCE string will call into JNI
LocalRef<jobjectArray> localDeviceNameAndIDs (jDeviceNameAndIDs);
auto deviceNameAndIDs = javaStringArrayToJuce (localDeviceNameAndIDs);
deviceNameAndIDs.appendNumbersToDuplicates (false, false, CharPointer_UTF8 ("-"), CharPointer_UTF8 (""));
Array<MidiDeviceInfo> devices;
for (int i = 0; i < deviceNameAndIDs.size(); i += 2)
devices.add ({ deviceNameAndIDs[i], deviceNameAndIDs[i + 1] });
return devices;
}
return {};
}
MidiInput::Pimpl* openMidiInputPortWithID (int deviceID, MidiInput* juceMidiInput, juce::MidiInputCallback* callback)
{
if (auto dm = deviceManager.get())
{
auto androidMidiInput = std::make_unique<MidiInput::Pimpl> (juceMidiInput, deviceID, callback, dm);
if (androidMidiInput->isOpen())
return androidMidiInput.release();
}
return nullptr;
}
MidiOutput::Pimpl* openMidiOutputPortWithID (int deviceID)
{
if (auto dm = deviceManager.get())
if (auto javaMidiPort = getEnv()->CallObjectMethod (dm, MidiDeviceManager.openMidiOutputPortWithID, (jint) deviceID))
return new MidiOutput::Pimpl (LocalRef<jobject>(javaMidiPort));
return nullptr;
}
private:
GlobalRef deviceManager;
};
//==============================================================================
Array<MidiDeviceInfo> MidiInput::getAvailableDevices()
{
if (getAndroidSDKVersion() < 23)
return {};
AndroidMidiDeviceManager manager;
return manager.getDevices (true);
}
MidiDeviceInfo MidiInput::getDefaultDevice()
{
if (getAndroidSDKVersion() < 23)
return {};
return getAvailableDevices().getFirst();
}
std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback)
{
if (getAndroidSDKVersion() < 23 || deviceIdentifier.isEmpty())
return {};
AndroidMidiDeviceManager manager;
std::unique_ptr<MidiInput> midiInput (new MidiInput ({}, deviceIdentifier));
if (auto* port = manager.openMidiInputPortWithID (deviceIdentifier.getIntValue(), midiInput.get(), callback))
{
midiInput->internal.reset (port);
midiInput->setName (port->getName());
return midiInput;
}
return {};
}
StringArray MidiInput::getDevices()
{
if (getAndroidSDKVersion() < 23)
return {};
StringArray deviceNames;
for (auto& d : getAvailableDevices())
deviceNames.add (d.name);
return deviceNames;
}
int MidiInput::getDefaultDeviceIndex()
{
return (getAndroidSDKVersion() < 23 ? -1 : 0);
}
std::unique_ptr<MidiInput> MidiInput::openDevice (int index, MidiInputCallback* callback)
{
return openDevice (getAvailableDevices()[index].identifier, callback);
}
MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier)
: deviceInfo (deviceName, deviceIdentifier)
{
}
MidiInput::~MidiInput() = default;
void MidiInput::start()
{
if (auto* mi = internal.get())
mi->start();
}
void MidiInput::stop()
{
if (auto* mi = internal.get())
mi->stop();
}
//==============================================================================
Array<MidiDeviceInfo> MidiOutput::getAvailableDevices()
{
if (getAndroidSDKVersion() < 23)
return {};
AndroidMidiDeviceManager manager;
return manager.getDevices (false);
}
MidiDeviceInfo MidiOutput::getDefaultDevice()
{
if (getAndroidSDKVersion() < 23)
return {};
return getAvailableDevices().getFirst();
}
std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifier)
{
if (getAndroidSDKVersion() < 23 || deviceIdentifier.isEmpty())
return {};
AndroidMidiDeviceManager manager;
if (auto* port = manager.openMidiOutputPortWithID (deviceIdentifier.getIntValue()))
{
std::unique_ptr<MidiOutput> midiOutput (new MidiOutput ({}, deviceIdentifier));
midiOutput->internal.reset (port);
midiOutput->setName (port->getName());
return midiOutput;
}
return {};
}
StringArray MidiOutput::getDevices()
{
if (getAndroidSDKVersion() < 23)
return {};
StringArray deviceNames;
for (auto& d : getAvailableDevices())
deviceNames.add (d.name);
return deviceNames;
}
int MidiOutput::getDefaultDeviceIndex()
{
return (getAndroidSDKVersion() < 23 ? -1 : 0);
}
std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index)
{
return openDevice (getAvailableDevices()[index].identifier);
}
MidiOutput::~MidiOutput()
{
stopBackgroundThread();
}
void MidiOutput::sendMessageNow (const MidiMessage& message)
{
if (auto* androidMidi = internal.get())
{
auto* env = getEnv();
auto messageSize = message.getRawDataSize();
LocalRef<jbyteArray> messageContent (env->NewByteArray (messageSize));
auto content = messageContent.get();
auto* rawBytes = env->GetByteArrayElements (content, nullptr);
std::memcpy (rawBytes, message.getRawData(), static_cast<size_t> (messageSize));
env->ReleaseByteArrayElements (content, rawBytes, 0);
androidMidi->send (content, (jint) 0, (jint) messageSize);
}
}
} // namespace juce

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,100 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
class iOSAudioIODeviceType;
class iOSAudioIODevice : public AudioIODevice
{
public:
//==============================================================================
String open (const BigInteger&, const BigInteger&, double, int) override;
void close() override;
void start (AudioIODeviceCallback*) override;
void stop() override;
Array<double> getAvailableSampleRates() override;
Array<int> getAvailableBufferSizes() override;
bool setAudioPreprocessingEnabled (bool) override;
//==============================================================================
bool isPlaying() override;
bool isOpen() override;
String getLastError() override;
//==============================================================================
StringArray getOutputChannelNames() override;
StringArray getInputChannelNames() override;
int getDefaultBufferSize() override;
int getCurrentBufferSizeSamples() override;
double getCurrentSampleRate() override;
int getCurrentBitDepth() override;
BigInteger getActiveOutputChannels() const override;
BigInteger getActiveInputChannels() const override;
int getOutputLatencyInSamples() override;
int getInputLatencyInSamples() override;
int getXRunCount() const noexcept override;
//==============================================================================
void setMidiMessageCollector (MidiMessageCollector*);
AudioPlayHead* getAudioPlayHead() const;
//==============================================================================
bool isInterAppAudioConnected() const;
#if JUCE_MODULE_AVAILABLE_juce_graphics
Image getIcon (int size);
#endif
void switchApplication();
bool isHeadphonesConnected() const;
bool setInputGain(float val);
float getInputGain() const;
void setAllowBluetoothInput(bool flag);
bool getAllowBluetoothInput() const;
private:
//==============================================================================
iOSAudioIODevice (iOSAudioIODeviceType*, const String&, const String&);
//==============================================================================
friend class iOSAudioIODeviceType;
friend struct AudioSessionHolder;
struct Pimpl;
std::unique_ptr<Pimpl> pimpl;
JUCE_DECLARE_NON_COPYABLE (iOSAudioIODevice)
};
} // namespace juce

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,597 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
//==============================================================================
class MidiInput::Pimpl
{
public:
static Array<Pimpl*> midiInputs;
Pimpl (const String& port, MidiInput* input, MidiInputCallback* callback)
: midiInput (input), midiPort (port), midiCallback (callback)
{
jassert (midiCallback != nullptr);
midiInputs.add (this);
buffer.resize (32);
}
~Pimpl()
{
stop();
midiInputs.removeAllInstancesOf (this);
}
void start()
{
midi.readFrom (midiPort.toRawUTF8());
}
void stop()
{
midi.enableParser (false);
}
void poll()
{
size_t receivedBytes = 0;
for (;;)
{
auto data = midi.getInput();
if (data < 0)
break;
buffer[receivedBytes] = (uint8) data;
receivedBytes++;
if (receivedBytes == buffer.size())
{
pushMidiData (static_cast<int> (receivedBytes));
receivedBytes = 0;
}
}
if (receivedBytes > 0)
pushMidiData ((int) receivedBytes);
}
static Array<MidiDeviceInfo> getDevices (bool input)
{
Array<MidiDeviceInfo> devices;
for (auto& card : findAllALSACardIDs())
findMidiDevices (devices, input, card);
return devices;
}
void pushMidiMessage (juce::MidiMessage& message)
{
concatenator.pushMidiData (message.getRawData(), message.getRawDataSize(), Time::getMillisecondCounter() * 0.001, midiInput, *midiCallback);
}
private:
void pushMidiData (int length)
{
concatenator.pushMidiData (buffer.data(), length, Time::getMillisecondCounter() * 0.001, midiInput, *midiCallback);
}
std::vector<uint8> buffer;
static Array<int> findAllALSACardIDs()
{
Array<int> cards;
int card = -1;
for (;;)
{
auto status = snd_card_next (&card);
if (status != 0 || card < 0)
break;
cards.add (card);
}
return cards;
}
// Adds all midi devices to the devices array of the given input/output type on the given card
static void findMidiDevices (Array<MidiDeviceInfo>& devices, bool input, int cardNum)
{
snd_ctl_t* ctl = nullptr;
auto status = snd_ctl_open (&ctl, ("hw:" + String (cardNum)).toRawUTF8(), 0);
if (status < 0)
return;
int device = -1;
for (;;)
{
status = snd_ctl_rawmidi_next_device (ctl, &device);
if (status < 0 || device < 0)
break;
snd_rawmidi_info_t* info;
snd_rawmidi_info_alloca (&info);
snd_rawmidi_info_set_device (info, (unsigned int) device);
snd_rawmidi_info_set_stream (info, input ? SND_RAWMIDI_STREAM_INPUT
: SND_RAWMIDI_STREAM_OUTPUT);
snd_ctl_rawmidi_info (ctl, info);
auto subCount = snd_rawmidi_info_get_subdevices_count (info);
for (size_t sub = 0; sub < subCount; ++sub)
{
snd_rawmidi_info_set_subdevice (info, sub);
status = snd_ctl_rawmidi_info (ctl, info);
if (status == 0)
{
String deviceName ("hw:" + String (cardNum) + "," + String (device) + "," + String (sub));
devices.add (MidiDeviceInfo (deviceName, deviceName));
}
}
}
snd_ctl_close (ctl);
}
MidiInput* const midiInput;
String midiPort;
MidiInputCallback* const midiCallback;
Midi midi;
MidiDataConcatenator concatenator { 512 };
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl)
};
Array<MidiInput::Pimpl*> MidiInput::Pimpl::midiInputs;
//==============================================================================
class BelaAudioIODevice : public AudioIODevice
{
public:
BelaAudioIODevice() : AudioIODevice (BelaAudioIODevice::belaTypeName,
BelaAudioIODevice::belaTypeName)
{
Bela_defaultSettings (&defaultSettings);
}
~BelaAudioIODevice()
{
close();
}
//==============================================================================
StringArray getOutputChannelNames() override
{
StringArray result;
for (int i = 1; i <= actualNumberOfOutputs; i++)
result.add ("Out #" + std::to_string (i));
return result;
}
StringArray getInputChannelNames() override
{
StringArray result;
for (int i = 1; i <= actualNumberOfInputs; i++)
result.add ("In #" + std::to_string (i));
return result;
}
Array<double> getAvailableSampleRates() override { return { 44100.0 }; }
Array<int> getAvailableBufferSizes() override { /* TODO: */ return { getDefaultBufferSize() }; }
int getDefaultBufferSize() override { return defaultSettings.periodSize; }
//==============================================================================
String open (const BigInteger& inputChannels,
const BigInteger& outputChannels,
double sampleRate,
int bufferSizeSamples) override
{
if (sampleRate != 44100.0 && sampleRate != 0.0)
{
lastError = "Bela audio outputs only support 44.1 kHz sample rate";
return lastError;
}
settings = defaultSettings;
auto numIns = getNumContiguousSetBits (inputChannels);
auto numOuts = getNumContiguousSetBits (outputChannels);
// Input and Output channels are numbered as follows
//
// 0 .. 1 - audio
// 2 .. 9 - analog
if (numIns > 2 || numOuts > 2)
{
settings.useAnalog = true;
settings.numAnalogInChannels = std::max (numIns - 2, 8);
settings.numAnalogOutChannels = std::max (numOuts - 2, 8);
settings.uniformSampleRate = true;
}
settings.numAudioInChannels = std::max (numIns, 2);
settings.numAudioOutChannels = std::max (numOuts, 2);
settings.detectUnderruns = 1;
settings.setup = setupCallback;
settings.render = renderCallback;
settings.cleanup = cleanupCallback;
settings.interleave = 0;
if (bufferSizeSamples > 0)
settings.periodSize = bufferSizeSamples;
isBelaOpen = false;
isRunning = false;
callback = nullptr;
underruns = 0;
if (Bela_initAudio (&settings, this) != 0 || ! isBelaOpen)
{
lastError = "Bela_initAutio failed";
return lastError;
}
actualNumberOfInputs = jmin (numIns, actualNumberOfInputs);
actualNumberOfOutputs = jmin (numOuts, actualNumberOfOutputs);
channelInBuffer.calloc (actualNumberOfInputs);
channelOutBuffer.calloc (actualNumberOfOutputs);
return {};
}
void close() override
{
stop();
if (isBelaOpen)
{
Bela_cleanupAudio();
isBelaOpen = false;
callback = nullptr;
underruns = 0;
actualBufferSize = 0;
actualNumberOfInputs = 0;
actualNumberOfOutputs = 0;
channelInBuffer.free();
channelOutBuffer.free();
}
}
bool isOpen() override { return isBelaOpen; }
void start (AudioIODeviceCallback* newCallback) override
{
if (! isBelaOpen)
return;
if (isRunning)
{
if (newCallback != callback)
{
if (newCallback != nullptr)
newCallback->audioDeviceAboutToStart (this);
{
ScopedLock lock (callbackLock);
std::swap (callback, newCallback);
}
if (newCallback != nullptr)
newCallback->audioDeviceStopped();
}
}
else
{
callback = newCallback;
isRunning = (Bela_startAudio() == 0);
if (callback != nullptr)
{
if (isRunning)
{
callback->audioDeviceAboutToStart (this);
}
else
{
lastError = "Bela_StartAudio failed";
callback->audioDeviceError (lastError);
}
}
}
}
void stop() override
{
AudioIODeviceCallback* oldCallback = nullptr;
if (callback != nullptr)
{
ScopedLock lock (callbackLock);
std::swap (callback, oldCallback);
}
isRunning = false;
Bela_stopAudio();
if (oldCallback != nullptr)
oldCallback->audioDeviceStopped();
}
bool isPlaying() override { return isRunning; }
String getLastError() override { return lastError; }
//==============================================================================
int getCurrentBufferSizeSamples() override { return (int) actualBufferSize; }
double getCurrentSampleRate() override { return 44100.0; }
int getCurrentBitDepth() override { return 16; }
BigInteger getActiveOutputChannels() const override { BigInteger b; b.setRange (0, actualNumberOfOutputs, true); return b; }
BigInteger getActiveInputChannels() const override { BigInteger b; b.setRange (0, actualNumberOfInputs, true); return b; }
int getOutputLatencyInSamples() override { /* TODO */ return 0; }
int getInputLatencyInSamples() override { /* TODO */ return 0; }
int getXRunCount() const noexcept override { return underruns; }
//==============================================================================
static const char* const belaTypeName;
private:
//==============================================================================
bool setup (BelaContext& context)
{
actualBufferSize = context.audioFrames;
actualNumberOfInputs = (int) (context.audioInChannels + context.analogInChannels);
actualNumberOfOutputs = (int) (context.audioOutChannels + context.analogOutChannels);
isBelaOpen = true;
firstCallback = true;
ScopedLock lock (callbackLock);
if (callback != nullptr)
callback->audioDeviceAboutToStart (this);
return true;
}
void render (BelaContext& context)
{
// check for xruns
calculateXruns (context.audioFramesElapsed, context.audioFrames);
ScopedLock lock (callbackLock);
// Check for and process and midi
for (auto midiInput : MidiInput::Pimpl::midiInputs)
midiInput->poll();
if (callback != nullptr)
{
jassert (context.audioFrames <= actualBufferSize);
jassert ((context.flags & BELA_FLAG_INTERLEAVED) == 0);
using Frames = decltype (context.audioFrames);
// Setup channelInBuffers
for (int ch = 0; ch < actualNumberOfInputs; ++ch)
{
if (ch < analogChannelStart)
channelInBuffer[ch] = &context.audioIn[(Frames) ch * context.audioFrames];
else
channelInBuffer[ch] = &context.analogIn[(Frames) (ch - analogChannelStart) * context.analogFrames];
}
// Setup channelOutBuffers
for (int ch = 0; ch < actualNumberOfOutputs; ++ch)
{
if (ch < analogChannelStart)
channelOutBuffer[ch] = &context.audioOut[(Frames) ch * context.audioFrames];
else
channelOutBuffer[ch] = &context.analogOut[(Frames) (ch - analogChannelStart) * context.audioFrames];
}
callback->audioDeviceIOCallback (channelInBuffer.getData(), actualNumberOfInputs,
channelOutBuffer.getData(), actualNumberOfOutputs,
(int) context.audioFrames);
}
}
void cleanup (BelaContext&)
{
ScopedLock lock (callbackLock);
if (callback != nullptr)
callback->audioDeviceStopped();
}
const int analogChannelStart = 2;
//==============================================================================
uint64_t expectedElapsedAudioSamples = 0;
int underruns = 0;
bool firstCallback = false;
void calculateXruns (uint64_t audioFramesElapsed, uint32_t numSamples)
{
if (audioFramesElapsed > expectedElapsedAudioSamples && ! firstCallback)
++underruns;
firstCallback = false;
expectedElapsedAudioSamples = audioFramesElapsed + numSamples;
}
//==============================================================================
static int getNumContiguousSetBits (const BigInteger& value) noexcept
{
int bit = 0;
while (value[bit])
++bit;
return bit;
}
//==============================================================================
static bool setupCallback (BelaContext* context, void* userData) noexcept { return static_cast<BelaAudioIODevice*> (userData)->setup (*context); }
static void renderCallback (BelaContext* context, void* userData) noexcept { static_cast<BelaAudioIODevice*> (userData)->render (*context); }
static void cleanupCallback (BelaContext* context, void* userData) noexcept { static_cast<BelaAudioIODevice*> (userData)->cleanup (*context); }
//==============================================================================
BelaInitSettings defaultSettings, settings;
bool isBelaOpen = false, isRunning = false;
CriticalSection callbackLock;
AudioIODeviceCallback* callback = nullptr;
String lastError;
uint32_t actualBufferSize = 0;
int actualNumberOfInputs = 0, actualNumberOfOutputs = 0;
HeapBlock<const float*> channelInBuffer;
HeapBlock<float*> channelOutBuffer;
bool includeAnalogSupport;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BelaAudioIODevice)
};
const char* const BelaAudioIODevice::belaTypeName = "Bela Analog";
//==============================================================================
struct BelaAudioIODeviceType : public AudioIODeviceType
{
BelaAudioIODeviceType() : AudioIODeviceType ("Bela") {}
StringArray getDeviceNames (bool) const override { return StringArray (BelaAudioIODevice::belaTypeName); }
void scanForDevices() override {}
int getDefaultDeviceIndex (bool) const override { return 0; }
int getIndexOfDevice (AudioIODevice* device, bool) const override { return device != nullptr ? 0 : -1; }
bool hasSeparateInputsAndOutputs() const override { return false; }
AudioIODevice* createDevice (const String& outputName, const String& inputName) override
{
// TODO: switching whether to support analog/digital with possible multiple Bela device types?
if (outputName == BelaAudioIODevice::belaTypeName || inputName == BelaAudioIODevice::belaTypeName)
return new BelaAudioIODevice();
return nullptr;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BelaAudioIODeviceType)
};
//==============================================================================
MidiInput::MidiInput (const String& deviceName, const String& deviceID)
: deviceInfo (deviceName, deviceID)
{
}
MidiInput::~MidiInput() = default;
void MidiInput::start() { internal->start(); }
void MidiInput::stop() { internal->stop(); }
Array<MidiDeviceInfo> MidiInput::getAvailableDevices()
{
return Pimpl::getDevices (true);
}
MidiDeviceInfo MidiInput::getDefaultDevice()
{
return getAvailableDevices().getFirst();
}
std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback)
{
if (deviceIdentifier.isEmpty())
return {};
std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceIdentifier, deviceIdentifier));
midiInput->internal = std::make_unique<Pimpl> (deviceIdentifier, midiInput.get(), callback);
return midiInput;
}
std::unique_ptr<MidiInput> MidiInput::createNewDevice (const String&, MidiInputCallback*)
{
// N/A on Bela
jassertfalse;
return {};
}
StringArray MidiInput::getDevices()
{
StringArray deviceNames;
for (auto& d : getAvailableDevices())
deviceNames.add (d.name);
return deviceNames;
}
int MidiInput::getDefaultDeviceIndex()
{
return 0;
}
std::unique_ptr<MidiInput> MidiInput::openDevice (int index, MidiInputCallback* callback)
{
return openDevice (getAvailableDevices()[index].identifier, callback);
}
//==============================================================================
// TODO: Add Bela MidiOutput support
class MidiOutput::Pimpl {};
MidiOutput::~MidiOutput() = default;
void MidiOutput::sendMessageNow (const MidiMessage&) {}
Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() { return {}; }
MidiDeviceInfo MidiOutput::getDefaultDevice() { return {}; }
std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String&) { return {}; }
std::unique_ptr<MidiOutput> MidiOutput::createNewDevice (const String&) { return {}; }
StringArray MidiOutput::getDevices() { return {}; }
int MidiOutput::getDefaultDeviceIndex() { return 0;}
std::unique_ptr<MidiOutput> MidiOutput::openDevice (int) { return {}; }
} // namespace juce

View File

@ -0,0 +1,668 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
static void* juce_libjackHandle = nullptr;
static void* juce_loadJackFunction (const char* const name)
{
if (juce_libjackHandle == nullptr)
return nullptr;
return dlsym (juce_libjackHandle, name);
}
#define JUCE_DECL_JACK_FUNCTION(return_type, fn_name, argument_types, arguments) \
return_type fn_name argument_types \
{ \
using ReturnType = return_type; \
typedef return_type (*fn_type) argument_types; \
static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \
jassert (fn != nullptr); \
return (fn != nullptr) ? ((*fn) arguments) : ReturnType(); \
}
#define JUCE_DECL_VOID_JACK_FUNCTION(fn_name, argument_types, arguments) \
void fn_name argument_types \
{ \
typedef void (*fn_type) argument_types; \
static fn_type fn = (fn_type) juce_loadJackFunction (#fn_name); \
jassert (fn != nullptr); \
if (fn != nullptr) (*fn) arguments; \
}
//==============================================================================
JUCE_DECL_JACK_FUNCTION (jack_client_t*, jack_client_open, (const char* client_name, jack_options_t options, jack_status_t* status, ...), (client_name, options, status))
JUCE_DECL_JACK_FUNCTION (int, jack_client_close, (jack_client_t *client), (client))
JUCE_DECL_JACK_FUNCTION (int, jack_activate, (jack_client_t* client), (client))
JUCE_DECL_JACK_FUNCTION (int, jack_deactivate, (jack_client_t* client), (client))
JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_buffer_size, (jack_client_t* client), (client))
JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_get_sample_rate, (jack_client_t* client), (client))
JUCE_DECL_VOID_JACK_FUNCTION (jack_on_shutdown, (jack_client_t* client, void (*function)(void* arg), void* arg), (client, function, arg))
JUCE_DECL_VOID_JACK_FUNCTION (jack_on_info_shutdown, (jack_client_t* client, JackInfoShutdownCallback function, void* arg), (client, function, arg))
JUCE_DECL_JACK_FUNCTION (void* , jack_port_get_buffer, (jack_port_t* port, jack_nframes_t nframes), (port, nframes))
JUCE_DECL_JACK_FUNCTION (jack_nframes_t, jack_port_get_total_latency, (jack_client_t* client, jack_port_t* port), (client, port))
JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_register, (jack_client_t* client, const char* port_name, const char* port_type, unsigned long flags, unsigned long buffer_size), (client, port_name, port_type, flags, buffer_size))
JUCE_DECL_VOID_JACK_FUNCTION (jack_set_error_function, (void (*func)(const char*)), (func))
JUCE_DECL_JACK_FUNCTION (int, jack_set_process_callback, (jack_client_t* client, JackProcessCallback process_callback, void* arg), (client, process_callback, arg))
JUCE_DECL_JACK_FUNCTION (const char**, jack_get_ports, (jack_client_t* client, const char* port_name_pattern, const char* type_name_pattern, unsigned long flags), (client, port_name_pattern, type_name_pattern, flags))
JUCE_DECL_JACK_FUNCTION (int, jack_connect, (jack_client_t* client, const char* source_port, const char* destination_port), (client, source_port, destination_port))
JUCE_DECL_JACK_FUNCTION (const char*, jack_port_name, (const jack_port_t* port), (port))
JUCE_DECL_JACK_FUNCTION (void*, jack_set_port_connect_callback, (jack_client_t* client, JackPortConnectCallback connect_callback, void* arg), (client, connect_callback, arg))
JUCE_DECL_JACK_FUNCTION (jack_port_t* , jack_port_by_id, (jack_client_t* client, jack_port_id_t port_id), (client, port_id))
JUCE_DECL_JACK_FUNCTION (int, jack_port_connected, (const jack_port_t* port), (port))
JUCE_DECL_JACK_FUNCTION (int, jack_port_connected_to, (const jack_port_t* port, const char* port_name), (port, port_name))
JUCE_DECL_JACK_FUNCTION (int, jack_set_xrun_callback, (jack_client_t* client, JackXRunCallback xrun_callback, void* arg), (client, xrun_callback, arg))
JUCE_DECL_JACK_FUNCTION (int, jack_port_flags, (const jack_port_t* port), (port))
JUCE_DECL_JACK_FUNCTION (jack_port_t*, jack_port_by_name, (jack_client_t* client, const char* name), (client, name))
JUCE_DECL_VOID_JACK_FUNCTION (jack_free, (void* ptr), (ptr))
#if JUCE_DEBUG
#define JACK_LOGGING_ENABLED 1
#endif
#if JACK_LOGGING_ENABLED
namespace
{
void jack_Log (const String& s)
{
std::cerr << s << std::endl;
}
const char* getJackErrorMessage (const jack_status_t status)
{
if (status & JackServerFailed
|| status & JackServerError) return "Unable to connect to JACK server";
if (status & JackVersionError) return "Client's protocol version does not match";
if (status & JackInvalidOption) return "The operation contained an invalid or unsupported option";
if (status & JackNameNotUnique) return "The desired client name was not unique";
if (status & JackNoSuchClient) return "Requested client does not exist";
if (status & JackInitFailure) return "Unable to initialize client";
return nullptr;
}
}
#define JUCE_JACK_LOG_STATUS(x) { if (const char* m = getJackErrorMessage (x)) jack_Log (m); }
#define JUCE_JACK_LOG(x) jack_Log(x)
#else
#define JUCE_JACK_LOG_STATUS(x) {}
#define JUCE_JACK_LOG(x) {}
#endif
//==============================================================================
#ifndef JUCE_JACK_CLIENT_NAME
#ifdef JucePlugin_Name
#define JUCE_JACK_CLIENT_NAME JucePlugin_Name
#else
#define JUCE_JACK_CLIENT_NAME "JUCEJack"
#endif
#endif
struct JackPortIterator
{
JackPortIterator (jack_client_t* const client, const bool forInput)
{
if (client != nullptr)
ports.reset (juce::jack_get_ports (client, nullptr, nullptr,
forInput ? JackPortIsInput : JackPortIsOutput));
}
bool next()
{
if (ports == nullptr || ports.get()[index + 1] == nullptr)
return false;
name = CharPointer_UTF8 (ports.get()[++index]);
return true;
}
String getClientName() const
{
return name.upToFirstOccurrenceOf (":", false, false);
}
String getChannelName() const
{
return name.fromFirstOccurrenceOf (":", false, false);
}
struct Free
{
void operator() (const char** ptr) const noexcept { juce::jack_free (ptr); }
};
std::unique_ptr<const char*, Free> ports;
int index = -1;
String name;
};
//==============================================================================
class JackAudioIODevice : public AudioIODevice
{
public:
JackAudioIODevice (const String& inName,
const String& outName,
std::function<void()> notifyIn)
: AudioIODevice (outName.isEmpty() ? inName : outName, "JACK"),
inputName (inName),
outputName (outName),
notifyChannelsChanged (std::move (notifyIn))
{
jassert (outName.isNotEmpty() || inName.isNotEmpty());
jack_status_t status = {};
client = juce::jack_client_open (JUCE_JACK_CLIENT_NAME, JackNoStartServer, &status);
if (client == nullptr)
{
JUCE_JACK_LOG_STATUS (status);
}
else
{
juce::jack_set_error_function (errorCallback);
// open input ports
const StringArray inputChannels (getInputChannelNames());
for (int i = 0; i < inputChannels.size(); ++i)
{
String inputChannelName;
inputChannelName << "in_" << ++totalNumberOfInputChannels;
inputPorts.add (juce::jack_port_register (client, inputChannelName.toUTF8(),
JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0));
}
// open output ports
const StringArray outputChannels (getOutputChannelNames());
for (int i = 0; i < outputChannels.size(); ++i)
{
String outputChannelName;
outputChannelName << "out_" << ++totalNumberOfOutputChannels;
outputPorts.add (juce::jack_port_register (client, outputChannelName.toUTF8(),
JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0));
}
inChans.calloc (totalNumberOfInputChannels + 2);
outChans.calloc (totalNumberOfOutputChannels + 2);
}
}
~JackAudioIODevice() override
{
close();
if (client != nullptr)
{
juce::jack_client_close (client);
client = nullptr;
}
}
StringArray getChannelNames (const String& clientName, bool forInput) const
{
StringArray names;
for (JackPortIterator i (client, forInput); i.next();)
if (i.getClientName() == clientName)
names.add (i.getChannelName());
return names;
}
StringArray getOutputChannelNames() override { return getChannelNames (outputName, true); }
StringArray getInputChannelNames() override { return getChannelNames (inputName, false); }
Array<double> getAvailableSampleRates() override
{
Array<double> rates;
if (client != nullptr)
rates.add (juce::jack_get_sample_rate (client));
return rates;
}
Array<int> getAvailableBufferSizes() override
{
Array<int> sizes;
if (client != nullptr)
sizes.add (static_cast<int> (juce::jack_get_buffer_size (client)));
return sizes;
}
int getDefaultBufferSize() override { return getCurrentBufferSizeSamples(); }
int getCurrentBufferSizeSamples() override { return client != nullptr ? static_cast<int> (juce::jack_get_buffer_size (client)) : 0; }
double getCurrentSampleRate() override { return client != nullptr ? static_cast<int> (juce::jack_get_sample_rate (client)) : 0; }
template <typename Fn>
void forEachClientChannel (const String& clientName, bool isInput, Fn&& fn)
{
auto index = 0;
for (JackPortIterator i (client, isInput); i.next();)
{
if (i.getClientName() != clientName)
continue;
fn (i.ports.get()[i.index], index);
index += 1;
}
}
String open (const BigInteger& inputChannels, const BigInteger& outputChannels,
double /* sampleRate */, int /* bufferSizeSamples */) override
{
if (client == nullptr)
{
lastError = "No JACK client running";
return lastError;
}
lastError.clear();
close();
xruns.store (0, std::memory_order_relaxed);
juce::jack_set_process_callback (client, processCallback, this);
juce::jack_set_port_connect_callback (client, portConnectCallback, this);
juce::jack_on_shutdown (client, shutdownCallback, this);
juce::jack_on_info_shutdown (client, infoShutdownCallback, this);
juce::jack_set_xrun_callback (client, xrunCallback, this);
juce::jack_activate (client);
deviceIsOpen = true;
if (! inputChannels.isZero())
{
forEachClientChannel (inputName, false, [&] (const char* portName, int index)
{
if (! inputChannels[index])
return;
jassert (index < inputPorts.size());
const auto* source = portName;
const auto* inputPort = inputPorts[index];
jassert (juce::jack_port_flags (juce::jack_port_by_name (client, source)) & JackPortIsOutput);
jassert (juce::jack_port_flags (inputPort) & JackPortIsInput);
auto error = juce::jack_connect (client, source, juce::jack_port_name (inputPort));
if (error != 0)
JUCE_JACK_LOG ("Cannot connect input port " + String (index) + " (" + portName + "), error " + String (error));
});
}
if (! outputChannels.isZero())
{
forEachClientChannel (outputName, true, [&] (const char* portName, int index)
{
if (! outputChannels[index])
return;
jassert (index < outputPorts.size());
const auto* outputPort = outputPorts[index];
const auto* destination = portName;
jassert (juce::jack_port_flags (outputPort) & JackPortIsOutput);
jassert (juce::jack_port_flags (juce::jack_port_by_name (client, destination)) & JackPortIsInput);
auto error = juce::jack_connect (client, juce::jack_port_name (outputPort), destination);
if (error != 0)
JUCE_JACK_LOG ("Cannot connect output port " + String (index) + " (" + portName + "), error " + String (error));
});
}
updateActivePorts();
return lastError;
}
void close() override
{
stop();
if (client != nullptr)
{
const auto result = juce::jack_deactivate (client);
jassertquiet (result == 0);
juce::jack_set_xrun_callback (client, xrunCallback, nullptr);
juce::jack_set_process_callback (client, processCallback, nullptr);
juce::jack_set_port_connect_callback (client, portConnectCallback, nullptr);
juce::jack_on_shutdown (client, shutdownCallback, nullptr);
juce::jack_on_info_shutdown (client, infoShutdownCallback, nullptr);
}
deviceIsOpen = false;
}
void start (AudioIODeviceCallback* newCallback) override
{
if (deviceIsOpen && newCallback != callback)
{
if (newCallback != nullptr)
newCallback->audioDeviceAboutToStart (this);
AudioIODeviceCallback* const oldCallback = callback;
{
const ScopedLock sl (callbackLock);
callback = newCallback;
}
if (oldCallback != nullptr)
oldCallback->audioDeviceStopped();
}
}
void stop() override
{
start (nullptr);
}
bool isOpen() override { return deviceIsOpen; }
bool isPlaying() override { return callback != nullptr; }
int getCurrentBitDepth() override { return 32; }
String getLastError() override { return lastError; }
int getXRunCount() const noexcept override { return xruns.load (std::memory_order_relaxed); }
BigInteger getActiveOutputChannels() const override { return activeOutputChannels; }
BigInteger getActiveInputChannels() const override { return activeInputChannels; }
int getOutputLatencyInSamples() override
{
int latency = 0;
for (int i = 0; i < outputPorts.size(); i++)
latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, outputPorts[i]));
return latency;
}
int getInputLatencyInSamples() override
{
int latency = 0;
for (int i = 0; i < inputPorts.size(); i++)
latency = jmax (latency, (int) juce::jack_port_get_total_latency (client, inputPorts[i]));
return latency;
}
String inputName, outputName;
private:
//==============================================================================
class MainThreadDispatcher : private AsyncUpdater
{
public:
explicit MainThreadDispatcher (JackAudioIODevice& device) : ref (device) {}
~MainThreadDispatcher() override { cancelPendingUpdate(); }
void updateActivePorts()
{
if (MessageManager::getInstance()->isThisTheMessageThread())
handleAsyncUpdate();
else
triggerAsyncUpdate();
}
private:
void handleAsyncUpdate() override { ref.updateActivePorts(); }
JackAudioIODevice& ref;
};
//==============================================================================
void process (const int numSamples)
{
int numActiveInChans = 0, numActiveOutChans = 0;
for (int i = 0; i < totalNumberOfInputChannels; ++i)
{
if (activeInputChannels[i])
if (auto* in = (jack_default_audio_sample_t*) juce::jack_port_get_buffer (inputPorts.getUnchecked (i),
static_cast<jack_nframes_t> (numSamples)))
inChans[numActiveInChans++] = (float*) in;
}
for (int i = 0; i < totalNumberOfOutputChannels; ++i)
{
if (activeOutputChannels[i])
if (auto* out = (jack_default_audio_sample_t*) juce::jack_port_get_buffer (outputPorts.getUnchecked (i),
static_cast<jack_nframes_t> (numSamples)))
outChans[numActiveOutChans++] = (float*) out;
}
const ScopedLock sl (callbackLock);
if (callback != nullptr)
{
if ((numActiveInChans + numActiveOutChans) > 0)
callback->audioDeviceIOCallback (const_cast<const float**> (inChans.getData()), numActiveInChans,
outChans, numActiveOutChans, numSamples);
}
else
{
for (int i = 0; i < numActiveOutChans; ++i)
zeromem (outChans[i], static_cast<size_t> (numSamples) * sizeof (float));
}
}
static int processCallback (jack_nframes_t nframes, void* callbackArgument)
{
if (callbackArgument != nullptr)
((JackAudioIODevice*) callbackArgument)->process (static_cast<int> (nframes));
return 0;
}
static int xrunCallback (void* callbackArgument)
{
if (callbackArgument != nullptr)
((JackAudioIODevice*) callbackArgument)->xruns++;
return 0;
}
void updateActivePorts()
{
BigInteger newOutputChannels, newInputChannels;
for (int i = 0; i < outputPorts.size(); ++i)
if (juce::jack_port_connected (outputPorts.getUnchecked (i)))
newOutputChannels.setBit (i);
for (int i = 0; i < inputPorts.size(); ++i)
if (juce::jack_port_connected (inputPorts.getUnchecked (i)))
newInputChannels.setBit (i);
if (newOutputChannels != activeOutputChannels
|| newInputChannels != activeInputChannels)
{
AudioIODeviceCallback* const oldCallback = callback;
stop();
activeOutputChannels = newOutputChannels;
activeInputChannels = newInputChannels;
if (oldCallback != nullptr)
start (oldCallback);
if (notifyChannelsChanged != nullptr)
notifyChannelsChanged();
}
}
static void portConnectCallback (jack_port_id_t, jack_port_id_t, int, void* arg)
{
if (JackAudioIODevice* device = static_cast<JackAudioIODevice*> (arg))
device->mainThreadDispatcher.updateActivePorts();
}
static void threadInitCallback (void* /* callbackArgument */)
{
JUCE_JACK_LOG ("JackAudioIODevice::initialise");
}
static void shutdownCallback (void* callbackArgument)
{
JUCE_JACK_LOG ("JackAudioIODevice::shutdown");
if (JackAudioIODevice* device = (JackAudioIODevice*) callbackArgument)
{
device->client = nullptr;
device->close();
}
}
static void infoShutdownCallback (jack_status_t code, const char* reason, void* arg)
{
jassertquiet (code == 0);
JUCE_JACK_LOG ("Shutting down with message:");
JUCE_JACK_LOG (reason);
ignoreUnused (reason);
shutdownCallback (arg);
}
static void errorCallback (const char* msg)
{
JUCE_JACK_LOG ("JackAudioIODevice::errorCallback " + String (msg));
ignoreUnused (msg);
}
bool deviceIsOpen = false;
jack_client_t* client = nullptr;
String lastError;
AudioIODeviceCallback* callback = nullptr;
CriticalSection callbackLock;
HeapBlock<float*> inChans, outChans;
int totalNumberOfInputChannels = 0;
int totalNumberOfOutputChannels = 0;
Array<jack_port_t*> inputPorts, outputPorts;
BigInteger activeInputChannels, activeOutputChannels;
std::atomic<int> xruns { 0 };
std::function<void()> notifyChannelsChanged;
MainThreadDispatcher mainThreadDispatcher { *this };
};
//==============================================================================
class JackAudioIODeviceType;
class JackAudioIODeviceType : public AudioIODeviceType
{
public:
JackAudioIODeviceType()
: AudioIODeviceType ("JACK")
{}
void scanForDevices()
{
hasScanned = true;
inputNames.clear();
outputNames.clear();
if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so.0", RTLD_LAZY);
if (juce_libjackHandle == nullptr) juce_libjackHandle = dlopen ("libjack.so", RTLD_LAZY);
if (juce_libjackHandle == nullptr) return;
jack_status_t status = {};
// open a dummy client
if (auto* const client = juce::jack_client_open ("JuceJackDummy", JackNoStartServer, &status))
{
// scan for output devices
for (JackPortIterator i (client, false); i.next();)
if (i.getClientName() != (JUCE_JACK_CLIENT_NAME) && ! inputNames.contains (i.getClientName()))
inputNames.add (i.getClientName());
// scan for input devices
for (JackPortIterator i (client, true); i.next();)
if (i.getClientName() != (JUCE_JACK_CLIENT_NAME) && ! outputNames.contains (i.getClientName()))
outputNames.add (i.getClientName());
juce::jack_client_close (client);
}
else
{
JUCE_JACK_LOG_STATUS (status);
}
}
StringArray getDeviceNames (bool wantInputNames) const
{
jassert (hasScanned); // need to call scanForDevices() before doing this
return wantInputNames ? inputNames : outputNames;
}
int getDefaultDeviceIndex (bool /* forInput */) const
{
jassert (hasScanned); // need to call scanForDevices() before doing this
return 0;
}
bool hasSeparateInputsAndOutputs() const { return true; }
int getIndexOfDevice (AudioIODevice* device, bool asInput) const
{
jassert (hasScanned); // need to call scanForDevices() before doing this
if (JackAudioIODevice* d = dynamic_cast<JackAudioIODevice*> (device))
return asInput ? inputNames.indexOf (d->inputName)
: outputNames.indexOf (d->outputName);
return -1;
}
AudioIODevice* createDevice (const String& outputDeviceName,
const String& inputDeviceName)
{
jassert (hasScanned); // need to call scanForDevices() before doing this
const int inputIndex = inputNames.indexOf (inputDeviceName);
const int outputIndex = outputNames.indexOf (outputDeviceName);
if (inputIndex >= 0 || outputIndex >= 0)
return new JackAudioIODevice (inputDeviceName, outputDeviceName,
[this] { callDeviceChangeListeners(); });
return nullptr;
}
private:
StringArray inputNames, outputNames;
bool hasScanned = false;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JackAudioIODeviceType)
};
} // namespace juce

View File

@ -0,0 +1,698 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
JUCE is an open source library subject to commercial or open-source
licensing.
The code included in this file is provided under the terms of the ISC license
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
To use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted provided that the above copyright notice and
this permission notice appear in all copies.
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
DISCLAIMED.
==============================================================================
*/
namespace juce
{
#if JUCE_ALSA
//==============================================================================
class AlsaClient : public ReferenceCountedObject
{
public:
AlsaClient()
{
jassert (instance == nullptr);
snd_seq_open (&handle, "default", SND_SEQ_OPEN_DUPLEX, 0);
if (handle != nullptr)
{
snd_seq_nonblock (handle, SND_SEQ_NONBLOCK);
snd_seq_set_client_name (handle, getAlsaMidiName().toRawUTF8());
clientId = snd_seq_client_id (handle);
// It's good idea to pre-allocate a good number of elements
ports.ensureStorageAllocated (32);
}
}
~AlsaClient()
{
jassert (instance != nullptr);
instance = nullptr;
jassert (activeCallbacks.get() == 0);
if (inputThread)
inputThread->stopThread (3000);
if (handle != nullptr)
snd_seq_close (handle);
}
static String getAlsaMidiName()
{
#ifdef JUCE_ALSA_MIDI_NAME
return JUCE_ALSA_MIDI_NAME;
#else
if (auto* app = JUCEApplicationBase::getInstance())
return app->getApplicationName();
return "JUCE";
#endif
}
using Ptr = ReferenceCountedObjectPtr<AlsaClient>;
//==============================================================================
// represents an input or output port of the supplied AlsaClient
struct Port
{
Port (AlsaClient& c, bool forInput) noexcept
: client (c), isInput (forInput)
{}
~Port()
{
if (isValid())
{
if (isInput)
enableCallback (false);
else
snd_midi_event_free (midiParser);
snd_seq_delete_simple_port (client.get(), portId);
}
}
void connectWith (int sourceClient, int sourcePort) const noexcept
{
if (isInput)
snd_seq_connect_from (client.get(), portId, sourceClient, sourcePort);
else
snd_seq_connect_to (client.get(), portId, sourceClient, sourcePort);
}
bool isValid() const noexcept
{
return client.get() != nullptr && portId >= 0;
}
void setupInput (MidiInput* input, MidiInputCallback* cb)
{
jassert (cb != nullptr && input != nullptr);
callback = cb;
midiInput = input;
}
void setupOutput()
{
jassert (! isInput);
snd_midi_event_new ((size_t) maxEventSize, &midiParser);
}
void enableCallback (bool enable)
{
const auto oldValue = callbackEnabled.exchange (enable);
if (oldValue != enable)
{
if (enable)
client.registerCallback();
else
client.unregisterCallback();
}
}
bool sendMessageNow (const MidiMessage& message)
{
if (message.getRawDataSize() > maxEventSize)
{
maxEventSize = message.getRawDataSize();
snd_midi_event_free (midiParser);
snd_midi_event_new ((size_t) maxEventSize, &midiParser);
}
snd_seq_event_t event;
snd_seq_ev_clear (&event);
auto numBytes = (long) message.getRawDataSize();
auto* data = message.getRawData();
auto seqHandle = client.get();
bool success = true;
while (numBytes > 0)
{
auto numSent = snd_midi_event_encode (midiParser, data, numBytes, &event);
if (numSent <= 0)
{
success = numSent == 0;
break;
}
numBytes -= numSent;
data += numSent;
snd_seq_ev_set_source (&event, (unsigned char) portId);
snd_seq_ev_set_subs (&event);
snd_seq_ev_set_direct (&event);
if (snd_seq_event_output_direct (seqHandle, &event) < 0)
{
success = false;
break;
}
}
snd_midi_event_reset_encode (midiParser);
return success;
}
bool operator== (const Port& lhs) const noexcept
{
return portId != -1 && portId == lhs.portId;
}
void createPort (const String& name, bool enableSubscription)
{
if (auto seqHandle = client.get())
{
const unsigned int caps =
isInput ? (SND_SEQ_PORT_CAP_WRITE | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_WRITE : 0))
: (SND_SEQ_PORT_CAP_READ | (enableSubscription ? SND_SEQ_PORT_CAP_SUBS_READ : 0));
portName = name;
portId = snd_seq_create_simple_port (seqHandle, portName.toUTF8(), caps,
SND_SEQ_PORT_TYPE_MIDI_GENERIC |
SND_SEQ_PORT_TYPE_APPLICATION);
}
}
void handleIncomingMidiMessage (const MidiMessage& message) const
{
if (callbackEnabled)
callback->handleIncomingMidiMessage (midiInput, message);
}
void handlePartialSysexMessage (const uint8* messageData, int numBytesSoFar, double timeStamp)
{
if (callbackEnabled)
callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp);
}
int getPortId() const { return portId; }
const String& getPortName() const { return portName; }
private:
AlsaClient& client;
MidiInputCallback* callback = nullptr;
snd_midi_event_t* midiParser = nullptr;
MidiInput* midiInput = nullptr;
String portName;
int maxEventSize = 4096, portId = -1;
std::atomic<bool> callbackEnabled { false };
bool isInput = false;
};
static Ptr getInstance()
{
if (instance == nullptr)
instance = new AlsaClient();
return instance;
}
void registerCallback()
{
if (inputThread == nullptr)
inputThread.reset (new MidiInputThread (*this));
if (++activeCallbacks == 1)
inputThread->startThread();
}
void unregisterCallback()
{
jassert (activeCallbacks.get() > 0);
if (--activeCallbacks == 0 && inputThread->isThreadRunning())
inputThread->signalThreadShouldExit();
}
void handleIncomingMidiMessage (snd_seq_event* event, const MidiMessage& message)
{
const ScopedLock sl (callbackLock);
if (auto* port = ports[event->dest.port])
port->handleIncomingMidiMessage (message);
}
void handlePartialSysexMessage (snd_seq_event* event, const uint8* messageData, int numBytesSoFar, double timeStamp)
{
const ScopedLock sl (callbackLock);
if (auto* port = ports[event->dest.port])
port->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp);
}
snd_seq_t* get() const noexcept { return handle; }
int getId() const noexcept { return clientId; }
Port* createPort (const String& name, bool forInput, bool enableSubscription)
{
const ScopedLock sl (callbackLock);
auto port = new Port (*this, forInput);
port->createPort (name, enableSubscription);
ports.set (port->getPortId(), port);
incReferenceCount();
return port;
}
void deletePort (Port* port)
{
const ScopedLock sl (callbackLock);
ports.set (port->getPortId(), nullptr);
decReferenceCount();
}
private:
snd_seq_t* handle = nullptr;
int clientId = 0;
OwnedArray<Port> ports;
Atomic<int> activeCallbacks;
CriticalSection callbackLock;
static AlsaClient* instance;
//==============================================================================
class MidiInputThread : public Thread
{
public:
MidiInputThread (AlsaClient& c)
: Thread ("JUCE MIDI Input"), client (c)
{
jassert (client.get() != nullptr);
}
void run() override
{
auto seqHandle = client.get();
const int maxEventSize = 16 * 1024;
snd_midi_event_t* midiParser;
if (snd_midi_event_new (maxEventSize, &midiParser) >= 0)
{
auto numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN);
HeapBlock<pollfd> pfd (numPfds);
snd_seq_poll_descriptors (seqHandle, pfd, (unsigned int) numPfds, POLLIN);
HeapBlock<uint8> buffer (maxEventSize);
while (! threadShouldExit())
{
if (poll (pfd, (nfds_t) numPfds, 100) > 0) // there was a "500" here which is a bit long when we exit the program and have to wait for a timeout on this poll call
{
if (threadShouldExit())
break;
do
{
snd_seq_event_t* inputEvent = nullptr;
if (snd_seq_event_input (seqHandle, &inputEvent) >= 0)
{
// xxx what about SYSEXes that are too big for the buffer?
auto numBytes = snd_midi_event_decode (midiParser, buffer,
maxEventSize, inputEvent);
snd_midi_event_reset_decode (midiParser);
concatenator.pushMidiData (buffer, (int) numBytes,
Time::getMillisecondCounter() * 0.001,
inputEvent, client);
snd_seq_free_event (inputEvent);
}
}
while (snd_seq_event_input_pending (seqHandle, 0) > 0);
}
}
snd_midi_event_free (midiParser);
}
}
private:
AlsaClient& client;
MidiDataConcatenator concatenator { 2048 };
};
std::unique_ptr<MidiInputThread> inputThread;
};
AlsaClient* AlsaClient::instance = nullptr;
//==============================================================================
static String getFormattedPortIdentifier (int clientId, int portId)
{
return String (clientId) + "-" + String (portId);
}
static AlsaClient::Port* iterateMidiClient (const AlsaClient::Ptr& client,
snd_seq_client_info_t* clientInfo,
bool forInput,
Array<MidiDeviceInfo>& devices,
const String& deviceIdentifierToOpen)
{
AlsaClient::Port* port = nullptr;
auto seqHandle = client->get();
snd_seq_port_info_t* portInfo = nullptr;
snd_seq_port_info_alloca (&portInfo);
jassert (portInfo != nullptr);
auto numPorts = snd_seq_client_info_get_num_ports (clientInfo);
auto sourceClient = snd_seq_client_info_get_client (clientInfo);
snd_seq_port_info_set_client (portInfo, sourceClient);
snd_seq_port_info_set_port (portInfo, -1);
while (--numPorts >= 0)
{
if (snd_seq_query_next_port (seqHandle, portInfo) == 0
&& (snd_seq_port_info_get_capability (portInfo)
& (forInput ? SND_SEQ_PORT_CAP_SUBS_READ : SND_SEQ_PORT_CAP_SUBS_WRITE)) != 0)
{
String portName (snd_seq_port_info_get_name (portInfo));
auto portID = snd_seq_port_info_get_port (portInfo);
MidiDeviceInfo device (portName, getFormattedPortIdentifier (sourceClient, portID));
devices.add (device);
if (deviceIdentifierToOpen.isNotEmpty() && deviceIdentifierToOpen == device.identifier)
{
if (portID != -1)
{
port = client->createPort (portName, forInput, false);
jassert (port->isValid());
port->connectWith (sourceClient, portID);
break;
}
}
}
}
return port;
}
static AlsaClient::Port* iterateMidiDevices (bool forInput,
Array<MidiDeviceInfo>& devices,
const String& deviceIdentifierToOpen)
{
AlsaClient::Port* port = nullptr;
auto client = AlsaClient::getInstance();
if (auto seqHandle = client->get())
{
snd_seq_system_info_t* systemInfo = nullptr;
snd_seq_client_info_t* clientInfo = nullptr;
snd_seq_system_info_alloca (&systemInfo);
jassert (systemInfo != nullptr);
if (snd_seq_system_info (seqHandle, systemInfo) == 0)
{
snd_seq_client_info_alloca (&clientInfo);
jassert (clientInfo != nullptr);
auto numClients = snd_seq_system_info_get_cur_clients (systemInfo);
while (--numClients >= 0)
{
if (snd_seq_query_next_client (seqHandle, clientInfo) == 0)
{
port = iterateMidiClient (client, clientInfo, forInput,
devices, deviceIdentifierToOpen);
if (port != nullptr)
break;
}
}
}
}
return port;
}
struct AlsaPortPtr
{
explicit AlsaPortPtr (AlsaClient::Port* p)
: ptr (p) {}
~AlsaPortPtr() noexcept { AlsaClient::getInstance()->deletePort (ptr); }
AlsaClient::Port* ptr = nullptr;
};
//==============================================================================
class MidiInput::Pimpl : public AlsaPortPtr
{
public:
using AlsaPortPtr::AlsaPortPtr;
};
Array<MidiDeviceInfo> MidiInput::getAvailableDevices()
{
Array<MidiDeviceInfo> devices;
iterateMidiDevices (true, devices, {});
return devices;
}
MidiDeviceInfo MidiInput::getDefaultDevice()
{
return getAvailableDevices().getFirst();
}
std::unique_ptr<MidiInput> MidiInput::openDevice (const String& deviceIdentifier, MidiInputCallback* callback)
{
if (deviceIdentifier.isEmpty())
return {};
Array<MidiDeviceInfo> devices;
auto* port = iterateMidiDevices (true, devices, deviceIdentifier);
if (port == nullptr || ! port->isValid())
return {};
jassert (port->isValid());
std::unique_ptr<MidiInput> midiInput (new MidiInput (port->getPortName(), deviceIdentifier));
port->setupInput (midiInput.get(), callback);
midiInput->internal = std::make_unique<Pimpl> (port);
return midiInput;
}
std::unique_ptr<MidiInput> MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback)
{
auto client = AlsaClient::getInstance();
auto* port = client->createPort (deviceName, true, true);
if (port == nullptr || ! port->isValid())
return {};
std::unique_ptr<MidiInput> midiInput (new MidiInput (deviceName, getFormattedPortIdentifier (client->getId(), port->getPortId())));
port->setupInput (midiInput.get(), callback);
midiInput->internal = std::make_unique<Pimpl> (port);
return midiInput;
}
StringArray MidiInput::getDevices()
{
StringArray deviceNames;
for (auto& d : getAvailableDevices())
deviceNames.add (d.name);
deviceNames.appendNumbersToDuplicates (true, true);
return deviceNames;
}
int MidiInput::getDefaultDeviceIndex()
{
return 0;
}
std::unique_ptr<MidiInput> MidiInput::openDevice (int index, MidiInputCallback* callback)
{
return openDevice (getAvailableDevices()[index].identifier, callback);
}
MidiInput::MidiInput (const String& deviceName, const String& deviceIdentifier)
: deviceInfo (deviceName, deviceIdentifier)
{
}
MidiInput::~MidiInput()
{
stop();
}
void MidiInput::start()
{
internal->ptr->enableCallback (true);
}
void MidiInput::stop()
{
internal->ptr->enableCallback (false);
}
//==============================================================================
class MidiOutput::Pimpl : public AlsaPortPtr
{
public:
using AlsaPortPtr::AlsaPortPtr;
};
Array<MidiDeviceInfo> MidiOutput::getAvailableDevices()
{
Array<MidiDeviceInfo> devices;
iterateMidiDevices (false, devices, {});
return devices;
}
MidiDeviceInfo MidiOutput::getDefaultDevice()
{
return getAvailableDevices().getFirst();
}
std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String& deviceIdentifier)
{
if (deviceIdentifier.isEmpty())
return {};
Array<MidiDeviceInfo> devices;
auto* port = iterateMidiDevices (false, devices, deviceIdentifier);
if (port == nullptr || ! port->isValid())
return {};
std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (port->getPortName(), deviceIdentifier));
port->setupOutput();
midiOutput->internal = std::make_unique<Pimpl> (port);
return midiOutput;
}
std::unique_ptr<MidiOutput> MidiOutput::createNewDevice (const String& deviceName)
{
auto client = AlsaClient::getInstance();
auto* port = client->createPort (deviceName, false, true);
if (port == nullptr || ! port->isValid())
return {};
std::unique_ptr<MidiOutput> midiOutput (new MidiOutput (deviceName, getFormattedPortIdentifier (client->getId(), port->getPortId())));
port->setupOutput();
midiOutput->internal = std::make_unique<Pimpl> (port);
return midiOutput;
}
StringArray MidiOutput::getDevices()
{
StringArray deviceNames;
for (auto& d : getAvailableDevices())
deviceNames.add (d.name);
deviceNames.appendNumbersToDuplicates (true, true);
return deviceNames;
}
int MidiOutput::getDefaultDeviceIndex()
{
return 0;
}
std::unique_ptr<MidiOutput> MidiOutput::openDevice (int index)
{
return openDevice (getAvailableDevices()[index].identifier);
}
MidiOutput::~MidiOutput()
{
stopBackgroundThread();
}
void MidiOutput::sendMessageNow (const MidiMessage& message)
{
internal->ptr->sendMessageNow (message);
}
//==============================================================================
#else
class MidiInput::Pimpl {};
// (These are just stub functions if ALSA is unavailable...)
MidiInput::MidiInput (const String& deviceName, const String& deviceID)
: deviceInfo (deviceName, deviceID)
{
}
MidiInput::~MidiInput() {}
void MidiInput::start() {}
void MidiInput::stop() {}
Array<MidiDeviceInfo> MidiInput::getAvailableDevices() { return {}; }
MidiDeviceInfo MidiInput::getDefaultDevice() { return {}; }
std::unique_ptr<MidiInput> MidiInput::openDevice (const String&, MidiInputCallback*) { return {}; }
std::unique_ptr<MidiInput> MidiInput::createNewDevice (const String&, MidiInputCallback*) { return {}; }
StringArray MidiInput::getDevices() { return {}; }
int MidiInput::getDefaultDeviceIndex() { return 0;}
std::unique_ptr<MidiInput> MidiInput::openDevice (int, MidiInputCallback*) { return {}; }
class MidiOutput::Pimpl {};
MidiOutput::~MidiOutput() {}
void MidiOutput::sendMessageNow (const MidiMessage&) {}
Array<MidiDeviceInfo> MidiOutput::getAvailableDevices() { return {}; }
MidiDeviceInfo MidiOutput::getDefaultDevice() { return {}; }
std::unique_ptr<MidiOutput> MidiOutput::openDevice (const String&) { return {}; }
std::unique_ptr<MidiOutput> MidiOutput::createNewDevice (const String&) { return {}; }
StringArray MidiOutput::getDevices() { return {}; }
int MidiOutput::getDefaultDeviceIndex() { return 0;}
std::unique_ptr<MidiOutput> MidiOutput::openDevice (int) { return {}; }
#endif
} // namespace juce

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,97 @@
cmake_minimum_required(VERSION 3.4.1)
# Set the name of the project and store it in PROJECT_NAME. Also set the following variables:
# PROJECT_SOURCE_DIR (usually the root directory where Oboe has been cloned e.g.)
# PROJECT_BINARY_DIR (usually the containing project's binary directory,
# e.g. ${OBOE_HOME}/samples/RhythmGame/.externalNativeBuild/cmake/ndkExtractorDebug/x86/oboe-bin)
project(oboe)
set (oboe_sources
src/aaudio/AAudioLoader.cpp
src/aaudio/AudioStreamAAudio.cpp
src/common/AudioSourceCaller.cpp
src/common/AudioStream.cpp
src/common/AudioStreamBuilder.cpp
src/common/DataConversionFlowGraph.cpp
src/common/FilterAudioStream.cpp
src/common/FixedBlockAdapter.cpp
src/common/FixedBlockReader.cpp
src/common/FixedBlockWriter.cpp
src/common/LatencyTuner.cpp
src/common/SourceFloatCaller.cpp
src/common/SourceI16Caller.cpp
src/common/SourceI24Caller.cpp
src/common/SourceI32Caller.cpp
src/common/Utilities.cpp
src/common/QuirksManager.cpp
src/fifo/FifoBuffer.cpp
src/fifo/FifoController.cpp
src/fifo/FifoControllerBase.cpp
src/fifo/FifoControllerIndirect.cpp
src/flowgraph/FlowGraphNode.cpp
src/flowgraph/ChannelCountConverter.cpp
src/flowgraph/ClipToRange.cpp
src/flowgraph/ManyToMultiConverter.cpp
src/flowgraph/MonoToMultiConverter.cpp
src/flowgraph/MultiToMonoConverter.cpp
src/flowgraph/RampLinear.cpp
src/flowgraph/SampleRateConverter.cpp
src/flowgraph/SinkFloat.cpp
src/flowgraph/SinkI16.cpp
src/flowgraph/SinkI24.cpp
src/flowgraph/SinkI32.cpp
src/flowgraph/SourceFloat.cpp
src/flowgraph/SourceI16.cpp
src/flowgraph/SourceI24.cpp
src/flowgraph/SourceI32.cpp
src/flowgraph/resampler/IntegerRatio.cpp
src/flowgraph/resampler/LinearResampler.cpp
src/flowgraph/resampler/MultiChannelResampler.cpp
src/flowgraph/resampler/PolyphaseResampler.cpp
src/flowgraph/resampler/PolyphaseResamplerMono.cpp
src/flowgraph/resampler/PolyphaseResamplerStereo.cpp
src/flowgraph/resampler/SincResampler.cpp
src/flowgraph/resampler/SincResamplerStereo.cpp
src/opensles/AudioInputStreamOpenSLES.cpp
src/opensles/AudioOutputStreamOpenSLES.cpp
src/opensles/AudioStreamBuffered.cpp
src/opensles/AudioStreamOpenSLES.cpp
src/opensles/EngineOpenSLES.cpp
src/opensles/OpenSLESUtilities.cpp
src/opensles/OutputMixerOpenSLES.cpp
src/common/StabilizedCallback.cpp
src/common/Trace.cpp
src/common/Version.cpp
)
add_library(oboe ${oboe_sources})
# Specify directories which the compiler should look for headers
target_include_directories(oboe
PRIVATE src
PUBLIC include)
# JUCE CHANGE STARTS HERE
# This comment provided for Apache License compliance. We've removed the extra warnings flags and
# the `-Werror` option, to avoid cases where compilers produce unexpected errors and fail the build.
# We've also removed the explicit `-std=c++17` compile option, and replaced it with a more
# cmake-friendly way of specifying the language standard.
target_compile_options(oboe PRIVATE -Ofast)
set_target_properties(oboe PROPERTIES CXX_STANDARD 17 CXX_STANDARD_REQUIRED TRUE CXX_EXTENSIONS FALSE)
# JUCE CHANGE ENDS HERE
# Enable logging of D,V for debug builds
target_compile_definitions(oboe PUBLIC $<$<CONFIG:DEBUG>:OBOE_ENABLE_LOGGING=1>)
target_link_libraries(oboe PRIVATE log OpenSLES)
# When installing oboe put the libraries in the lib/<ABI> folder e.g. lib/arm64-v8a
install(TARGETS oboe
LIBRARY DESTINATION lib/${ANDROID_ABI}
ARCHIVE DESTINATION lib/${ANDROID_ABI})
# Also install the headers
install(DIRECTORY include/oboe DESTINATION include)

View File

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

View File

@ -0,0 +1,10 @@
The files in this directory are reproduced from the official Oboe repository, which can be found at
github.com/google/oboe.
These files are from tag 1.6.1 (855ea841).
We've included only those parts of the original repository which are required to build the Oboe
library. Documentation, samples, tests, and other non-library items have been omitted.
Files in this directory and below are licensed under the terms of the license in the LICENSE file
which you can find in the same directory as this readme.

View File

@ -0,0 +1,567 @@
/*
* 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() = 0;
/**
* Wait until the stream's current state no longer matches the input state.
* The input state is passed to avoid race conditions caused by the state
* changing between calls.
*
* Note that generally applications do not need to call this. It is considered
* an advanced technique and is mostly used for testing.
*
* <pre><code>
* int64_t timeoutNanos = 500 * kNanosPerMillisecond; // arbitrary 1/2 second
* StreamState currentState = stream->getState();
* StreamState nextState = StreamState::Unknown;
* while (result == Result::OK && currentState != StreamState::Paused) {
* result = stream->waitForStateChange(
* currentState, &nextState, timeoutNanos);
* currentState = nextState;
* }
* </code></pre>
*
* If the state does not change within the timeout period then it will
* return ErrorTimeout. This is true even if timeoutNanoseconds is zero.
*
* @param inputState The state we want to change away from.
* @param nextState Pointer to a variable that will be set to the new state.
* @param timeoutNanoseconds The maximum time to wait in nanoseconds.
* @return Result::OK or a Result::Error.
*/
virtual Result waitForStateChange(StreamState inputState,
StreamState *nextState,
int64_t timeoutNanoseconds) = 0;
/**
* This can be used to adjust the latency of the buffer by changing
* the threshold where blocking will occur.
* By combining this with getXRunCount(), the latency can be tuned
* at run-time for each device.
*
* This cannot be set higher than getBufferCapacity().
*
* @param requestedFrames requested number of frames that can be filled without blocking
* @return the resulting buffer size in frames (obtained using value()) or an error (obtained
* using error())
*/
virtual ResultWithValue<int32_t> setBufferSizeInFrames(int32_t /* requestedFrames */) {
return Result::ErrorUnimplemented;
}
/**
* An XRun is an Underrun or an Overrun.
* During playing, an underrun will occur if the stream is not written in time
* and the system runs out of valid data.
* During recording, an overrun will occur if the stream is not read in time
* and there is no place to put the incoming data so it is discarded.
*
* An underrun or overrun can cause an audible "pop" or "glitch".
*
* @return a result which is either Result::OK with the xRun count as the value, or a
* Result::Error* code
*/
virtual ResultWithValue<int32_t> getXRunCount() {
return ResultWithValue<int32_t>(Result::ErrorUnimplemented);
}
/**
* @return true if XRun counts are supported on the stream
*/
virtual bool isXRunCountSupported() const = 0;
/**
* Query the number of frames that are read or written by the endpoint at one time.
*
* @return burst size
*/
int32_t getFramesPerBurst() const {
return mFramesPerBurst;
}
/**
* Get the number of bytes in each audio frame. This is calculated using the channel count
* and the sample format. For example, a 2 channel floating point stream will have
* 2 * 4 = 8 bytes per frame.
*
* @return number of bytes in each audio frame.
*/
int32_t getBytesPerFrame() const { return mChannelCount * getBytesPerSample(); }
/**
* Get the number of bytes per sample. This is calculated using the sample format. For example,
* a stream using 16-bit integer samples will have 2 bytes per sample.
*
* @return the number of bytes per sample.
*/
int32_t getBytesPerSample() const;
/**
* The number of audio frames written into the stream.
* This monotonic counter will never get reset.
*
* @return the number of frames written so far
*/
virtual int64_t getFramesWritten();
/**
* The number of audio frames read from the stream.
* This monotonic counter will never get reset.
*
* @return the number of frames read so far
*/
virtual int64_t getFramesRead();
/**
* Calculate the latency of a stream based on getTimestamp().
*
* Output latency is the time it takes for a given frame to travel from the
* app to some type of digital-to-analog converter. If the DAC is external, for example
* in a USB interface or a TV connected by HDMI, then there may be additional latency
* that the Android device is unaware of.
*
* Input latency is the time it takes to a given frame to travel from an analog-to-digital
* converter (ADC) to the app.
*
* Note that the latency of an OUTPUT stream will increase abruptly when you write data to it
* and then decrease slowly over time as the data is consumed.
*
* The latency of an INPUT stream will decrease abruptly when you read data from it
* and then increase slowly over time as more data arrives.
*
* The latency of an OUTPUT stream is generally higher than the INPUT latency
* because an app generally tries to keep the OUTPUT buffer full and the INPUT buffer empty.
*
* @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;
}
/**
* 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;
/**
* Number of frames which will be copied to/from the audio device in a single read/write
* operation
*/
int32_t mFramesPerBurst = kUnspecified;
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,265 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_STREAM_BASE_H_
#define OBOE_STREAM_BASE_H_
#include <memory>
#include <string>
#include "oboe/AudioStreamCallback.h"
#include "oboe/Definitions.h"
namespace oboe {
/**
* Base class containing parameters for audio streams and builders.
**/
class AudioStreamBase {
public:
AudioStreamBase() {}
virtual ~AudioStreamBase() = default;
// This class only contains primitives so we can use default constructor and copy methods.
/**
* Default copy constructor
*/
AudioStreamBase(const AudioStreamBase&) = default;
/**
* Default assignment operator
*/
AudioStreamBase& operator=(const AudioStreamBase&) = default;
/**
* @return number of channels, for example 2 for stereo, or kUnspecified
*/
int32_t getChannelCount() const { return mChannelCount; }
/**
* @return Direction::Input or Direction::Output
*/
Direction getDirection() const { return mDirection; }
/**
* @return sample rate for the stream or kUnspecified
*/
int32_t getSampleRate() const { return mSampleRate; }
/**
* @deprecated use `getFramesPerDataCallback` instead.
*/
int32_t getFramesPerCallback() const { return getFramesPerDataCallback(); }
/**
* @return the number of frames in each data callback or kUnspecified.
*/
int32_t getFramesPerDataCallback() const { return mFramesPerCallback; }
/**
* @return the audio sample format (e.g. Float or I16)
*/
AudioFormat getFormat() const { return mFormat; }
/**
* Query the maximum number of frames that can be filled without blocking.
* If the stream has been closed the last known value will be returned.
*
* @return buffer size
*/
virtual int32_t getBufferSizeInFrames() { return mBufferSizeInFrames; }
/**
* @return capacityInFrames or kUnspecified
*/
virtual int32_t getBufferCapacityInFrames() const { return mBufferCapacityInFrames; }
/**
* @return the sharing mode of the stream.
*/
SharingMode getSharingMode() const { return mSharingMode; }
/**
* @return the performance mode of the stream.
*/
PerformanceMode getPerformanceMode() const { return mPerformanceMode; }
/**
* @return the device ID of the stream.
*/
int32_t getDeviceId() const { return mDeviceId; }
/**
* For internal use only.
* @return the data callback object for this stream, if set.
*/
AudioStreamDataCallback *getDataCallback() const {
return mDataCallback;
}
/**
* For internal use only.
* @return the error callback object for this stream, if set.
*/
AudioStreamErrorCallback *getErrorCallback() const {
return mErrorCallback;
}
/**
* @return true if a data callback was set for this stream
*/
bool isDataCallbackSpecified() const {
return mDataCallback != nullptr;
}
/**
* Note that if the app does not set an error callback then a
* default one may be provided.
* @return true if an error callback was set for this stream
*/
bool isErrorCallbackSpecified() const {
return mErrorCallback != nullptr;
}
/**
* @return the usage for this stream.
*/
Usage getUsage() const { return mUsage; }
/**
* @return the stream's content type.
*/
ContentType getContentType() const { return mContentType; }
/**
* @return the stream's input preset.
*/
InputPreset getInputPreset() const { return mInputPreset; }
/**
* @return the stream's session ID allocation strategy (None or Allocate).
*/
SessionId getSessionId() const { return mSessionId; }
/**
* @return 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;
/** 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 the name of the package creating the stream. Only active on Android 31+ */
std::string mPackageName;
/** Control the attribution tag of the context creating the stream. Only active on Android 31+ */
std::string mAttributionTag;
// Control whether Oboe can convert channel counts to achieve optimal results.
bool mChannelConversionAllowed = false;
// Control whether Oboe can convert data formats to achieve optimal results.
bool mFormatConversionAllowed = false;
// Control whether and how Oboe can convert sample rates to achieve optimal results.
SampleRateConversionQuality mSampleRateConversionQuality = SampleRateConversionQuality::None;
/** Validate stream parameters that might not be checked in lower layers */
virtual Result isValidConfig() {
switch (mFormat) {
case AudioFormat::Unspecified:
case AudioFormat::I16:
case AudioFormat::Float:
case AudioFormat::I24:
case AudioFormat::I32:
break;
// JUCE CHANGE STARTS HERE
case AudioFormat::Invalid:
// JUCE CHANGE ENDS HERE
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,520 @@
/*
* 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 false.
*/
AudioStreamBuilder *setFormatConversionAllowed(bool allowed) {
mFormatConversionAllowed = allowed;
return this;
}
/**
* Specify the quality of the sample rate converter in Oboe.
*
* If set to None then Oboe will not do sample rate conversion. But the underlying APIs might
* still do sample rate conversion if you specify a sample rate.
* That can prevent you from getting a low latency stream.
*
* If you do the conversion in Oboe then you might still get a low latency stream.
*
* Default is SampleRateConversionQuality::None
*/
AudioStreamBuilder *setSampleRateConversionQuality(SampleRateConversionQuality quality) {
mSampleRateConversionQuality = quality;
return this;
}
/**
* Declare the name of the package creating the stream.
*
* This is usually Context#getPackageName()
*
* The default, if you do not call this function, is a random package in the calling uid.
*
* Added in API level 31.
*
* @param packageName packageName of the calling app.
*/
AudioStreamBuilder *setPackageName(std::string packageName) {
mPackageName = packageName;
return this;
}
/**
* Declare the attribution tag of the context creating the stream.
*
* This is usually Context#getAttributionTag()
*
* The default, if you do not call this function, is the default attribution tag.
*
* Added in API level 31.
*
* @param attributionTag attributionTag of the calling context.
*/
AudioStreamBuilder *setAttributionTag(std::string attributionTag) {
mAttributionTag = attributionTag;
return this;
}
/**
* @return true if AAudio will be used based on the current settings.
*/
bool willUseAAudio() const {
return (mAudioApi == AudioApi::AAudio && isAAudioSupported())
|| (mAudioApi == AudioApi::Unspecified && isAAudioRecommended());
}
/**
* Create and open a stream object based on the current settings.
*
* The caller owns the pointer to the AudioStream object
* and must delete it when finished.
*
* @deprecated Use openStream(std::shared_ptr<oboe::AudioStream> &stream) instead.
* @param stream pointer to a variable to receive the stream address
* @return OBOE_OK if successful or a negative error code
*/
Result openStream(AudioStream **stream);
/**
* Create and open a stream object based on the current settings.
*
* The caller shares the pointer to the AudioStream object.
* The shared_ptr is used internally by Oboe to prevent the stream from being
* deleted while it is being used by callbacks.
*
* @param stream reference to a shared_ptr to receive the stream address
* @return OBOE_OK if successful or a negative error code
*/
Result openStream(std::shared_ptr<oboe::AudioStream> &stream);
/**
* Create and open a ManagedStream object based on the current builder state.
*
* The caller must create a unique ptr, and pass by reference so it can be
* modified to point to an opened stream. The caller owns the unique ptr,
* and it will be automatically closed and deleted when going out of scope.
*
* @deprecated Use openStream(std::shared_ptr<oboe::AudioStream> &stream) instead.
* @param stream Reference to the ManagedStream (uniqueptr) used to keep track of stream
* @return OBOE_OK if successful or a negative error code.
*/
Result openManagedStream(ManagedStream &stream);
private:
/**
* @param other
* @return true if channels, format and sample rate match
*/
bool isCompatible(AudioStreamBase &other);
/**
* Create an AudioStream object. The AudioStream must be opened before use.
*
* The caller owns the pointer.
*
* @return pointer to an AudioStream object or nullptr.
*/
oboe::AudioStream *build();
AudioApi mAudioApi = AudioApi::Unspecified;
};
} // namespace oboe
#endif /* OBOE_STREAM_BUILDER_H_ */

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,545 @@
/*
* 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 point.
*
* This is the recommended format for most applications.
* But note that the use of Float may prevent the opening of
* a low-latency input path on OpenSL ES or Legacy AAudio streams.
*/
Float = 2, // AAUDIO_FORMAT_PCM_FLOAT,
/**
* Signed 24-bit integers, packed into 3 bytes.
*
* Note that the use of this format does not guarantee that
* the full precision will be provided. The underlying device may
* be using I16 format.
*
* Added in API 31 (S).
*/
I24 = 3, // AAUDIO_FORMAT_PCM_I24_PACKED
/**
* Signed 32-bit integers.
*
* Note that the use of this format does not guarantee that
* the full precision will be provided. The underlying device may
* be using I16 format.
*
* Added in API 31 (S).
*/
I32 = 4, // AAUDIO_FORMAT_PCM_I32
};
/**
* The result of an audio callback.
*/
enum class DataCallbackResult : int32_t { // aaudio_data_callback_result_t
// Indicates to the caller that the callbacks should continue.
Continue = 0, // AAUDIO_CALLBACK_RESULT_CONTINUE,
// Indicates to the caller that the callbacks should stop immediately.
Stop = 1, // AAUDIO_CALLBACK_RESULT_STOP,
};
/**
* The result of an operation. All except the `OK` result indicates that an error occurred.
* The `Result` can be converted into a human readable string using `convertToText`.
*/
enum class Result : int32_t { // aaudio_result_t
OK = 0, // AAUDIO_OK
ErrorBase = -900, // AAUDIO_ERROR_BASE,
ErrorDisconnected = -899, // AAUDIO_ERROR_DISCONNECTED,
ErrorIllegalArgument = -898, // AAUDIO_ERROR_ILLEGAL_ARGUMENT,
ErrorInternal = -896, // AAUDIO_ERROR_INTERNAL,
ErrorInvalidState = -895, // AAUDIO_ERROR_INVALID_STATE,
ErrorInvalidHandle = -892, // AAUDIO_ERROR_INVALID_HANDLE,
ErrorUnimplemented = -890, // AAUDIO_ERROR_UNIMPLEMENTED,
ErrorUnavailable = -889, // AAUDIO_ERROR_UNAVAILABLE,
ErrorNoFreeHandles = -888, // AAUDIO_ERROR_NO_FREE_HANDLES,
ErrorNoMemory = -887, // AAUDIO_ERROR_NO_MEMORY,
ErrorNull = -886, // AAUDIO_ERROR_NULL,
ErrorTimeout = -885, // AAUDIO_ERROR_TIMEOUT,
ErrorWouldBlock = -884, // AAUDIO_ERROR_WOULD_BLOCK,
ErrorInvalidFormat = -883, // AAUDIO_ERROR_INVALID_FORMAT,
ErrorOutOfRange = -882, // AAUDIO_ERROR_OUT_OF_RANGE,
ErrorNoService = -881, // AAUDIO_ERROR_NO_SERVICE,
ErrorInvalidRate = -880, // AAUDIO_ERROR_INVALID_RATE,
// Reserved for future AAudio result types
Reserved1,
Reserved2,
Reserved3,
Reserved4,
Reserved5,
Reserved6,
Reserved7,
Reserved8,
Reserved9,
Reserved10,
ErrorClosed = -869,
};
/**
* The sharing mode of the audio stream.
*/
enum class SharingMode : int32_t { // aaudio_sharing_mode_t
/**
* This will be the only stream using a particular source or sink.
* This mode will provide the lowest possible latency.
* You should close EXCLUSIVE streams immediately when you are not using them.
*
* If you do not need the lowest possible latency then we recommend using Shared,
* which is the default.
*/
Exclusive = 0, // AAUDIO_SHARING_MODE_EXCLUSIVE,
/**
* Multiple applications can share the same device.
* The data from output streams will be mixed by the audio service.
* The data for input streams will be distributed by the audio service.
*
* This will have higher latency than the EXCLUSIVE mode.
*/
Shared = 1, // AAUDIO_SHARING_MODE_SHARED,
};
/**
* The performance mode of the audio stream.
*/
enum class PerformanceMode : int32_t { // aaudio_performance_mode_t
/**
* No particular performance needs. Default.
*/
None = 10, // AAUDIO_PERFORMANCE_MODE_NONE,
/**
* Extending battery life is most important.
*/
PowerSaving = 11, // AAUDIO_PERFORMANCE_MODE_POWER_SAVING,
/**
* Reducing latency is most important.
*/
LowLatency = 12, // AAUDIO_PERFORMANCE_MODE_LOW_LATENCY
};
/**
* The underlying audio API used by the audio stream.
*/
enum class AudioApi : int32_t {
/**
* Try to use AAudio. If not available then use OpenSL ES.
*/
Unspecified = kUnspecified,
/**
* Use OpenSL ES.
*/
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 6
// Type: 16-bit unsigned int. Min value: 0 Max value: 65535. See below for description.
#define OBOE_VERSION_PATCH 1
#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

@ -0,0 +1,172 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_AAUDIO_EXTENSIONS_H
#define OBOE_AAUDIO_EXTENSIONS_H
#include <dlfcn.h>
#include <stdint.h>
#include <sys/system_properties.h>
#include "common/OboeDebug.h"
#include "oboe/Oboe.h"
#include "AAudioLoader.h"
namespace oboe {
#define LIB_AAUDIO_NAME "libaaudio.so"
#define FUNCTION_IS_MMAP "AAudioStream_isMMapUsed"
#define FUNCTION_SET_MMAP_POLICY "AAudio_setMMapPolicy"
#define FUNCTION_GET_MMAP_POLICY "AAudio_getMMapPolicy"
#define AAUDIO_ERROR_UNAVAILABLE static_cast<aaudio_result_t>(Result::ErrorUnavailable)
typedef struct AAudioStreamStruct AAudioStream;
/**
* Call some AAudio test routines that are not part of the normal API.
*/
class AAudioExtensions {
public:
AAudioExtensions() {
int32_t policy = getIntegerProperty("aaudio.mmap_policy", 0);
mMMapSupported = isPolicyEnabled(policy);
policy = getIntegerProperty("aaudio.mmap_exclusive_policy", 0);
mMMapExclusiveSupported = isPolicyEnabled(policy);
}
static bool isPolicyEnabled(int32_t policy) {
return (policy == AAUDIO_POLICY_AUTO || policy == AAUDIO_POLICY_ALWAYS);
}
static AAudioExtensions &getInstance() {
static AAudioExtensions instance;
return instance;
}
bool isMMapUsed(oboe::AudioStream *oboeStream) {
AAudioStream *aaudioStream = (AAudioStream *) oboeStream->getUnderlyingStream();
return isMMapUsed(aaudioStream);
}
bool isMMapUsed(AAudioStream *aaudioStream) {
if (loadSymbols()) return false;
if (mAAudioStream_isMMap == nullptr) return false;
return mAAudioStream_isMMap(aaudioStream);
}
/**
* Controls whether the MMAP data path can be selected when opening a stream.
* It has no effect after the stream has been opened.
* It only affects the application that calls it. Other apps are not affected.
*
* @param enabled
* @return 0 or a negative error code
*/
int32_t setMMapEnabled(bool enabled) {
if (loadSymbols()) return AAUDIO_ERROR_UNAVAILABLE;
if (mAAudio_setMMapPolicy == nullptr) return false;
return mAAudio_setMMapPolicy(enabled ? AAUDIO_POLICY_AUTO : AAUDIO_POLICY_NEVER);
}
bool isMMapEnabled() {
if (loadSymbols()) return false;
if (mAAudio_getMMapPolicy == nullptr) return false;
int32_t policy = mAAudio_getMMapPolicy();
return isPolicyEnabled(policy);
}
bool isMMapSupported() {
return mMMapSupported;
}
bool isMMapExclusiveSupported() {
return mMMapExclusiveSupported;
}
private:
enum {
AAUDIO_POLICY_NEVER = 1,
AAUDIO_POLICY_AUTO,
AAUDIO_POLICY_ALWAYS
};
typedef int32_t aaudio_policy_t;
int getIntegerProperty(const char *name, int defaultValue) {
int result = defaultValue;
char valueText[PROP_VALUE_MAX] = {0};
if (__system_property_get(name, valueText) != 0) {
result = atoi(valueText);
}
return result;
}
/**
* Load the function pointers.
* This can be called multiple times.
* It should only be called from one thread.
*
* @return 0 if successful or negative error.
*/
aaudio_result_t loadSymbols() {
if (mAAudio_getMMapPolicy != nullptr) {
return 0;
}
void *libHandle = AAudioLoader::getInstance()->getLibHandle();
if (libHandle == nullptr) {
LOGI("%s() could not find " LIB_AAUDIO_NAME, __func__);
return AAUDIO_ERROR_UNAVAILABLE;
}
mAAudioStream_isMMap = (bool (*)(AAudioStream *stream))
dlsym(libHandle, FUNCTION_IS_MMAP);
if (mAAudioStream_isMMap == nullptr) {
LOGI("%s() could not find " FUNCTION_IS_MMAP, __func__);
return AAUDIO_ERROR_UNAVAILABLE;
}
mAAudio_setMMapPolicy = (int32_t (*)(aaudio_policy_t policy))
dlsym(libHandle, FUNCTION_SET_MMAP_POLICY);
if (mAAudio_setMMapPolicy == nullptr) {
LOGI("%s() could not find " FUNCTION_SET_MMAP_POLICY, __func__);
return AAUDIO_ERROR_UNAVAILABLE;
}
mAAudio_getMMapPolicy = (aaudio_policy_t (*)())
dlsym(libHandle, FUNCTION_GET_MMAP_POLICY);
if (mAAudio_getMMapPolicy == nullptr) {
LOGI("%s() could not find " FUNCTION_GET_MMAP_POLICY, __func__);
return AAUDIO_ERROR_UNAVAILABLE;
}
return 0;
}
bool mMMapSupported = false;
bool mMMapExclusiveSupported = false;
bool (*mAAudioStream_isMMap)(AAudioStream *stream) = nullptr;
int32_t (*mAAudio_setMMapPolicy)(aaudio_policy_t policy) = nullptr;
aaudio_policy_t (*mAAudio_getMMapPolicy)() = nullptr;
};
} // namespace oboe
#endif //OBOE_AAUDIO_EXTENSIONS_H

View File

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

View File

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

View File

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

View File

@ -0,0 +1,139 @@
/*
* Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_STREAM_AAUDIO_H_
#define OBOE_STREAM_AAUDIO_H_
#include <atomic>
#include <shared_mutex>
#include <mutex>
#include <thread>
#include "oboe/AudioStreamBuilder.h"
#include "oboe/AudioStream.h"
#include "oboe/Definitions.h"
#include "AAudioLoader.h"
namespace oboe {
/**
* Implementation of OboeStream that uses AAudio.
*
* Do not create this class directly.
* Use an OboeStreamBuilder to create one.
*/
class AudioStreamAAudio : public AudioStream {
public:
AudioStreamAAudio();
explicit AudioStreamAAudio(const AudioStreamBuilder &builder);
virtual ~AudioStreamAAudio() = default;
/**
*
* @return true if AAudio is supported on this device.
*/
static bool isSupported();
// These functions override methods in AudioStream.
// See AudioStream for documentation.
Result open() override;
Result close() override;
Result requestStart() override;
Result requestPause() override;
Result requestFlush() override;
Result requestStop() override;
ResultWithValue<int32_t> write(const void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
ResultWithValue<int32_t> read(void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
ResultWithValue<int32_t> setBufferSizeInFrames(int32_t requestedFrames) override;
int32_t getBufferSizeInFrames() override;
ResultWithValue<int32_t> getXRunCount() override;
bool isXRunCountSupported() const override { return true; }
ResultWithValue<double> calculateLatencyMillis() override;
Result waitForStateChange(StreamState currentState,
StreamState *nextState,
int64_t timeoutNanoseconds) override;
Result getTimestamp(clockid_t clockId,
int64_t *framePosition,
int64_t *timeNanoseconds) override;
StreamState getState() override;
AudioApi getAudioApi() const override {
return AudioApi::AAudio;
}
DataCallbackResult callOnAudioReady(AAudioStream *stream,
void *audioData,
int32_t numFrames);
bool isMMapUsed();
protected:
static void internalErrorCallback(
AAudioStream *stream,
void *userData,
aaudio_result_t error);
void *getUnderlyingStream() const override {
return mAAudioStream.load();
}
void updateFramesRead() override;
void updateFramesWritten() override;
void logUnsupportedAttributes();
private:
// Must call under mLock. And stream must NOT be nullptr.
Result requestStop_l(AAudioStream *stream);
/**
* Launch a thread that will stop the stream.
*/
void launchStopThread();
// Time to sleep in order to prevent a race condition with a callback after a close().
// Two milliseconds may be enough but 10 msec is even safer.
static constexpr int kDelayBeforeCloseMillis = 10;
std::atomic<bool> mCallbackThreadEnabled;
std::atomic<bool> mStopThreadAllowed{false};
// pointer to the underlying 'C' AAudio stream, valid if open, null if closed
std::atomic<AAudioStream *> mAAudioStream{nullptr};
std::shared_mutex mAAudioStreamLock; // to protect mAAudioStream while closing
static AAudioLoader *mLibLoader;
// We may not use this but it is so small that it is not worth allocating dynamically.
AudioStreamErrorCallback mDefaultErrorCallback;
};
} // namespace oboe
#endif // OBOE_STREAM_AAUDIO_H_

View File

@ -0,0 +1,75 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_AUDIO_CLOCK_H
#define OBOE_AUDIO_CLOCK_H
#include <sys/types.h>
#include <ctime>
#include "oboe/Definitions.h"
namespace oboe {
// TODO: Move this class into the public headers because it is useful when calculating stream latency
class AudioClock {
public:
static int64_t getNanoseconds(clockid_t clockId = CLOCK_MONOTONIC) {
struct timespec time;
int result = clock_gettime(clockId, &time);
if (result < 0) {
return result;
}
return (time.tv_sec * kNanosPerSecond) + time.tv_nsec;
}
/**
* Sleep until the specified time.
*
* @param nanoTime time to wake up
* @param clockId CLOCK_MONOTONIC is default
* @return 0 or a negative error, eg. -EINTR
*/
static int sleepUntilNanoTime(int64_t nanoTime, clockid_t clockId = CLOCK_MONOTONIC) {
struct timespec time;
time.tv_sec = nanoTime / kNanosPerSecond;
time.tv_nsec = nanoTime - (time.tv_sec * kNanosPerSecond);
return 0 - clock_nanosleep(clockId, TIMER_ABSTIME, &time, NULL);
}
/**
* Sleep for the specified number of nanoseconds in real-time.
* Return immediately with 0 if a negative nanoseconds is specified.
*
* @param nanoseconds time to sleep
* @param clockId CLOCK_REALTIME is default
* @return 0 or a negative error, eg. -EINTR
*/
static int sleepForNanos(int64_t nanoseconds, clockid_t clockId = CLOCK_REALTIME) {
if (nanoseconds > 0) {
struct timespec time;
time.tv_sec = nanoseconds / kNanosPerSecond;
time.tv_nsec = nanoseconds - (time.tv_sec * kNanosPerSecond);
return 0 - clock_nanosleep(clockId, 0, &time, NULL);
}
return 0;
}
};
} // namespace oboe
#endif //OBOE_AUDIO_CLOCK_H

View File

@ -0,0 +1,38 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "AudioSourceCaller.h"
using namespace oboe;
using namespace flowgraph;
int32_t AudioSourceCaller::onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) {
AudioStreamDataCallback *callback = mStream->getDataCallback();
int32_t result = 0;
int32_t numFrames = numBytes / mStream->getBytesPerFrame();
if (callback != nullptr) {
DataCallbackResult callbackResult = callback->onAudioReady(mStream, buffer, numFrames);
// onAudioReady() does not return the number of bytes processed so we have to assume all.
result = (callbackResult == DataCallbackResult::Continue)
? numBytes
: -1;
} else {
auto readResult = mStream->read(buffer, numFrames, mTimeoutNanos);
if (!readResult) return (int32_t) readResult.error();
result = readResult.value() * mStream->getBytesPerFrame();
}
return result;
}

View File

@ -0,0 +1,83 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_AUDIO_SOURCE_CALLER_H
#define OBOE_AUDIO_SOURCE_CALLER_H
#include "OboeDebug.h"
#include "oboe/Oboe.h"
#include "flowgraph/FlowGraphNode.h"
#include "FixedBlockReader.h"
namespace oboe {
class AudioStreamCallback;
class AudioStream;
/**
* For output streams that use a callback, call the application for more data.
* For input streams that do not use a callback, read from the stream.
*/
class AudioSourceCaller : public flowgraph::FlowGraphSource, public FixedBlockProcessor {
public:
AudioSourceCaller(int32_t channelCount, int32_t framesPerCallback, int32_t bytesPerSample)
: FlowGraphSource(channelCount)
, mBlockReader(*this) {
mBlockReader.open(channelCount * framesPerCallback * bytesPerSample);
}
/**
* Set the stream to use as a source of data.
* @param stream
*/
void setStream(oboe::AudioStream *stream) {
mStream = stream;
}
oboe::AudioStream *getStream() {
return mStream;
}
/**
* Timeout value to use when calling audioStream->read().
* @param timeoutNanos Zero for no timeout or time in nanoseconds.
*/
void setTimeoutNanos(int64_t timeoutNanos) {
mTimeoutNanos = timeoutNanos;
}
int64_t getTimeoutNanos() const {
return mTimeoutNanos;
}
/**
* Called internally for block size adaptation.
* @param buffer
* @param numBytes
* @return
*/
int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) override;
protected:
oboe::AudioStream *mStream = nullptr;
int64_t mTimeoutNanos = 0;
FixedBlockReader mBlockReader;
};
}
#endif //OBOE_AUDIO_SOURCE_CALLER_H

View File

@ -0,0 +1,199 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <sys/types.h>
#include <pthread.h>
#include <thread>
#include <oboe/AudioStream.h>
#include "OboeDebug.h"
#include "AudioClock.h"
#include <oboe/Utilities.h>
namespace oboe {
/*
* AudioStream
*/
AudioStream::AudioStream(const AudioStreamBuilder &builder)
: AudioStreamBase(builder) {
}
Result AudioStream::close() {
// Update local counters so they can be read after the close.
updateFramesWritten();
updateFramesRead();
return Result::OK;
}
// Call this from fireDataCallback() if you want to monitor CPU scheduler.
void AudioStream::checkScheduler() {
int scheduler = sched_getscheduler(0) & ~SCHED_RESET_ON_FORK; // for current thread
if (scheduler != mPreviousScheduler) {
LOGD("AudioStream::%s() scheduler = %s", __func__,
((scheduler == SCHED_FIFO) ? "SCHED_FIFO" :
((scheduler == SCHED_OTHER) ? "SCHED_OTHER" :
((scheduler == SCHED_RR) ? "SCHED_RR" : "UNKNOWN")))
);
mPreviousScheduler = scheduler;
}
}
DataCallbackResult AudioStream::fireDataCallback(void *audioData, int32_t numFrames) {
if (!isDataCallbackEnabled()) {
LOGW("AudioStream::%s() called with data callback disabled!", __func__);
return DataCallbackResult::Stop; // Should not be getting called
}
DataCallbackResult result;
if (mDataCallback) {
result = mDataCallback->onAudioReady(this, audioData, numFrames);
} else {
result = onDefaultCallback(audioData, numFrames);
}
// On Oreo, we might get called after returning stop.
// So block that here.
setDataCallbackEnabled(result == DataCallbackResult::Continue);
return result;
}
Result AudioStream::waitForStateTransition(StreamState startingState,
StreamState endingState,
int64_t timeoutNanoseconds)
{
StreamState state;
{
std::lock_guard<std::mutex> lock(mLock);
state = getState();
if (state == StreamState::Closed) {
return Result::ErrorClosed;
} else if (state == StreamState::Disconnected) {
return Result::ErrorDisconnected;
}
}
StreamState nextState = state;
// TODO Should this be a while()?!
if (state == startingState && state != endingState) {
Result result = waitForStateChange(state, &nextState, timeoutNanoseconds);
if (result != Result::OK) {
return result;
}
}
if (nextState != endingState) {
return Result::ErrorInvalidState;
} else {
return Result::OK;
}
}
Result AudioStream::start(int64_t timeoutNanoseconds)
{
Result result = requestStart();
if (result != Result::OK) return result;
if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Starting,
StreamState::Started, timeoutNanoseconds);
}
Result AudioStream::pause(int64_t timeoutNanoseconds)
{
Result result = requestPause();
if (result != Result::OK) return result;
if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Pausing,
StreamState::Paused, timeoutNanoseconds);
}
Result AudioStream::flush(int64_t timeoutNanoseconds)
{
Result result = requestFlush();
if (result != Result::OK) return result;
if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Flushing,
StreamState::Flushed, timeoutNanoseconds);
}
Result AudioStream::stop(int64_t timeoutNanoseconds)
{
Result result = requestStop();
if (result != Result::OK) return result;
if (timeoutNanoseconds <= 0) return result;
return waitForStateTransition(StreamState::Stopping,
StreamState::Stopped, timeoutNanoseconds);
}
int32_t AudioStream::getBytesPerSample() const {
return convertFormatToSizeInBytes(mFormat);
}
int64_t AudioStream::getFramesRead() {
updateFramesRead();
return mFramesRead;
}
int64_t AudioStream::getFramesWritten() {
updateFramesWritten();
return mFramesWritten;
}
ResultWithValue<int32_t> AudioStream::getAvailableFrames() {
int64_t readCounter = getFramesRead();
if (readCounter < 0) return ResultWithValue<int32_t>::createBasedOnSign(readCounter);
int64_t writeCounter = getFramesWritten();
if (writeCounter < 0) return ResultWithValue<int32_t>::createBasedOnSign(writeCounter);
int32_t framesAvailable = writeCounter - readCounter;
return ResultWithValue<int32_t>(framesAvailable);
}
ResultWithValue<int32_t> AudioStream::waitForAvailableFrames(int32_t numFrames,
int64_t timeoutNanoseconds) {
if (numFrames == 0) return Result::OK;
if (numFrames < 0) return Result::ErrorOutOfRange;
int64_t framesAvailable = 0;
int64_t burstInNanos = getFramesPerBurst() * kNanosPerSecond / getSampleRate();
bool ready = false;
int64_t deadline = AudioClock::getNanoseconds() + timeoutNanoseconds;
do {
ResultWithValue<int32_t> result = getAvailableFrames();
if (!result) return result;
framesAvailable = result.value();
ready = (framesAvailable >= numFrames);
if (!ready) {
int64_t now = AudioClock::getNanoseconds();
if (now > deadline) break;
AudioClock::sleepForNanos(burstInNanos);
}
} while (!ready);
return (!ready)
? ResultWithValue<int32_t>(Result::ErrorTimeout)
: ResultWithValue<int32_t>(framesAvailable);
}
ResultWithValue<FrameTimestamp> AudioStream::getTimestamp(clockid_t clockId) {
FrameTimestamp frame;
Result result = getTimestamp(clockId, &frame.position, &frame.timestamp);
if (result == Result::OK){
return ResultWithValue<FrameTimestamp>(frame);
} else {
return ResultWithValue<FrameTimestamp>(static_cast<Result>(result));
}
}
} // namespace oboe

View File

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

View File

@ -0,0 +1,267 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <memory>
#include "OboeDebug.h"
#include "DataConversionFlowGraph.h"
#include "SourceFloatCaller.h"
#include "SourceI16Caller.h"
#include "SourceI24Caller.h"
#include "SourceI32Caller.h"
#include <flowgraph/ClipToRange.h>
#include <flowgraph/MonoToMultiConverter.h>
#include <flowgraph/MultiToMonoConverter.h>
#include <flowgraph/RampLinear.h>
#include <flowgraph/SinkFloat.h>
#include <flowgraph/SinkI16.h>
#include <flowgraph/SinkI24.h>
#include <flowgraph/SinkI32.h>
#include <flowgraph/SourceFloat.h>
#include <flowgraph/SourceI16.h>
#include <flowgraph/SourceI24.h>
#include <flowgraph/SourceI32.h>
#include <flowgraph/SampleRateConverter.h>
using namespace oboe;
using namespace flowgraph;
using namespace resampler;
void DataConversionFlowGraph::setSource(const void *buffer, int32_t numFrames) {
mSource->setData(buffer, numFrames);
}
static MultiChannelResampler::Quality convertOboeSRQualityToMCR(SampleRateConversionQuality quality) {
switch (quality) {
case SampleRateConversionQuality::Fastest:
return MultiChannelResampler::Quality::Fastest;
case SampleRateConversionQuality::Low:
return MultiChannelResampler::Quality::Low;
default:
case SampleRateConversionQuality::Medium:
return MultiChannelResampler::Quality::Medium;
case SampleRateConversionQuality::High:
return MultiChannelResampler::Quality::High;
case SampleRateConversionQuality::Best:
return MultiChannelResampler::Quality::Best;
}
}
// Chain together multiple processors.
// Callback Output
// Use SourceCaller that calls original app callback from the flowgraph.
// The child callback from FilteredAudioStream read()s from the flowgraph.
// Callback Input
// Child callback from FilteredAudioStream writes()s to the flowgraph.
// The output of the flowgraph goes through a BlockWriter to the app callback.
// Blocking Write
// Write buffer is set on an AudioSource.
// Data is pulled through the graph and written to the child stream.
// Blocking Read
// Reads in a loop from the flowgraph Sink to fill the read buffer.
// A SourceCaller then does a blocking read from the child Stream.
//
Result DataConversionFlowGraph::configure(AudioStream *sourceStream, AudioStream *sinkStream) {
FlowGraphPortFloatOutput *lastOutput = nullptr;
bool isOutput = sourceStream->getDirection() == Direction::Output;
bool isInput = !isOutput;
mFilterStream = isOutput ? sourceStream : sinkStream;
AudioFormat sourceFormat = sourceStream->getFormat();
int32_t sourceChannelCount = sourceStream->getChannelCount();
int32_t sourceSampleRate = sourceStream->getSampleRate();
int32_t sourceFramesPerCallback = sourceStream->getFramesPerDataCallback();
AudioFormat sinkFormat = sinkStream->getFormat();
int32_t sinkChannelCount = sinkStream->getChannelCount();
int32_t sinkSampleRate = sinkStream->getSampleRate();
int32_t sinkFramesPerCallback = sinkStream->getFramesPerDataCallback();
LOGI("%s() flowgraph converts channels: %d to %d, format: %d to %d"
", rate: %d to %d, cbsize: %d to %d, qual = %d",
__func__,
sourceChannelCount, sinkChannelCount,
sourceFormat, sinkFormat,
sourceSampleRate, sinkSampleRate,
sourceFramesPerCallback, sinkFramesPerCallback,
sourceStream->getSampleRateConversionQuality());
// Source
// IF OUTPUT and using a callback then call back to the app using a SourceCaller.
// OR IF INPUT and NOT using a callback then read from the child stream using a SourceCaller.
bool isDataCallbackSpecified = sourceStream->isDataCallbackSpecified();
if ((isDataCallbackSpecified && isOutput)
|| (!isDataCallbackSpecified && isInput)) {
int32_t actualSourceFramesPerCallback = (sourceFramesPerCallback == kUnspecified)
? sourceStream->getFramesPerBurst()
: sourceFramesPerCallback;
switch (sourceFormat) {
case AudioFormat::Float:
mSourceCaller = std::make_unique<SourceFloatCaller>(sourceChannelCount,
actualSourceFramesPerCallback);
break;
case AudioFormat::I16:
mSourceCaller = std::make_unique<SourceI16Caller>(sourceChannelCount,
actualSourceFramesPerCallback);
break;
case AudioFormat::I24:
mSourceCaller = std::make_unique<SourceI24Caller>(sourceChannelCount,
actualSourceFramesPerCallback);
break;
case AudioFormat::I32:
mSourceCaller = std::make_unique<SourceI32Caller>(sourceChannelCount,
actualSourceFramesPerCallback);
break;
default:
LOGE("%s() Unsupported source caller format = %d", __func__, sourceFormat);
return Result::ErrorIllegalArgument;
}
mSourceCaller->setStream(sourceStream);
lastOutput = &mSourceCaller->output;
} else {
// IF OUTPUT and NOT using a callback then write to the child stream using a BlockWriter.
// OR IF INPUT and using a callback then write to the app using a BlockWriter.
switch (sourceFormat) {
case AudioFormat::Float:
mSource = std::make_unique<SourceFloat>(sourceChannelCount);
break;
case AudioFormat::I16:
mSource = std::make_unique<SourceI16>(sourceChannelCount);
break;
case AudioFormat::I24:
mSource = std::make_unique<SourceI24>(sourceChannelCount);
break;
case AudioFormat::I32:
mSource = std::make_unique<SourceI32>(sourceChannelCount);
break;
default:
LOGE("%s() Unsupported source format = %d", __func__, sourceFormat);
return Result::ErrorIllegalArgument;
}
if (isInput) {
int32_t actualSinkFramesPerCallback = (sinkFramesPerCallback == kUnspecified)
? sinkStream->getFramesPerBurst()
: sinkFramesPerCallback;
// The BlockWriter is after the Sink so use the SinkStream size.
mBlockWriter.open(actualSinkFramesPerCallback * sinkStream->getBytesPerFrame());
mAppBuffer = std::make_unique<uint8_t[]>(
kDefaultBufferSize * sinkStream->getBytesPerFrame());
}
lastOutput = &mSource->output;
}
// If we are going to reduce the number of channels then do it before the
// sample rate converter.
if (sourceChannelCount > sinkChannelCount) {
if (sinkChannelCount == 1) {
mMultiToMonoConverter = std::make_unique<MultiToMonoConverter>(sourceChannelCount);
lastOutput->connect(&mMultiToMonoConverter->input);
lastOutput = &mMultiToMonoConverter->output;
} else {
mChannelCountConverter = std::make_unique<ChannelCountConverter>(
sourceChannelCount,
sinkChannelCount);
lastOutput->connect(&mChannelCountConverter->input);
lastOutput = &mChannelCountConverter->output;
}
}
// Sample Rate conversion
if (sourceSampleRate != sinkSampleRate) {
// Create a resampler to do the math.
mResampler.reset(MultiChannelResampler::make(lastOutput->getSamplesPerFrame(),
sourceSampleRate,
sinkSampleRate,
convertOboeSRQualityToMCR(
sourceStream->getSampleRateConversionQuality())));
// Make a flowgraph node that uses the resampler.
mRateConverter = std::make_unique<SampleRateConverter>(lastOutput->getSamplesPerFrame(),
*mResampler.get());
lastOutput->connect(&mRateConverter->input);
lastOutput = &mRateConverter->output;
}
// Expand the number of channels if required.
if (sourceChannelCount < sinkChannelCount) {
if (sourceChannelCount == 1) {
mMonoToMultiConverter = std::make_unique<MonoToMultiConverter>(sinkChannelCount);
lastOutput->connect(&mMonoToMultiConverter->input);
lastOutput = &mMonoToMultiConverter->output;
} else {
mChannelCountConverter = std::make_unique<ChannelCountConverter>(
sourceChannelCount,
sinkChannelCount);
lastOutput->connect(&mChannelCountConverter->input);
lastOutput = &mChannelCountConverter->output;
}
}
// Sink
switch (sinkFormat) {
case AudioFormat::Float:
mSink = std::make_unique<SinkFloat>(sinkChannelCount);
break;
case AudioFormat::I16:
mSink = std::make_unique<SinkI16>(sinkChannelCount);
break;
case AudioFormat::I24:
mSink = std::make_unique<SinkI24>(sinkChannelCount);
break;
case AudioFormat::I32:
mSink = std::make_unique<SinkI32>(sinkChannelCount);
break;
default:
LOGE("%s() Unsupported sink format = %d", __func__, sinkFormat);
return Result::ErrorIllegalArgument;;
}
lastOutput->connect(&mSink->input);
return Result::OK;
}
int32_t DataConversionFlowGraph::read(void *buffer, int32_t numFrames, int64_t timeoutNanos) {
if (mSourceCaller) {
mSourceCaller->setTimeoutNanos(timeoutNanos);
}
int32_t numRead = mSink->read(buffer, numFrames);
return numRead;
}
// This is similar to pushing data through the flowgraph.
int32_t DataConversionFlowGraph::write(void *inputBuffer, int32_t numFrames) {
// Put the data from the input at the head of the flowgraph.
mSource->setData(inputBuffer, numFrames);
while (true) {
// Pull and read some data in app format into a small buffer.
int32_t framesRead = mSink->read(mAppBuffer.get(), flowgraph::kDefaultBufferSize);
if (framesRead <= 0) break;
// Write to a block adapter, which will call the destination whenever it has enough data.
int32_t bytesRead = mBlockWriter.write(mAppBuffer.get(),
framesRead * mFilterStream->getBytesPerFrame());
if (bytesRead < 0) return bytesRead; // TODO review
}
return numFrames;
}
int32_t DataConversionFlowGraph::onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) {
int32_t numFrames = numBytes / mFilterStream->getBytesPerFrame();
mCallbackResult = mFilterStream->getDataCallback()->onAudioReady(mFilterStream, buffer, numFrames);
// TODO handle STOP from callback, process data remaining in the block adapter
return numBytes;
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_OBOE_FLOW_GRAPH_H
#define OBOE_OBOE_FLOW_GRAPH_H
#include <memory>
#include <stdint.h>
#include <sys/types.h>
#include <flowgraph/ChannelCountConverter.h>
#include <flowgraph/MonoToMultiConverter.h>
#include <flowgraph/MultiToMonoConverter.h>
#include <flowgraph/SampleRateConverter.h>
#include <oboe/Definitions.h>
#include "AudioSourceCaller.h"
#include "FixedBlockWriter.h"
namespace oboe {
class AudioStream;
class AudioSourceCaller;
/**
* Convert PCM channels, format and sample rate for optimal latency.
*/
class DataConversionFlowGraph : public FixedBlockProcessor {
public:
DataConversionFlowGraph()
: mBlockWriter(*this) {}
void setSource(const void *buffer, int32_t numFrames);
/** Connect several modules together to convert from source to sink.
* This should only be called once for each instance.
*
* @param sourceFormat
* @param sourceChannelCount
* @param sinkFormat
* @param sinkChannelCount
* @return
*/
oboe::Result configure(oboe::AudioStream *sourceStream, oboe::AudioStream *sinkStream);
int32_t read(void *buffer, int32_t numFrames, int64_t timeoutNanos);
int32_t write(void *buffer, int32_t numFrames);
int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) override;
DataCallbackResult getDataCallbackResult() {
return mCallbackResult;
}
private:
std::unique_ptr<flowgraph::FlowGraphSourceBuffered> mSource;
std::unique_ptr<AudioSourceCaller> mSourceCaller;
std::unique_ptr<flowgraph::MonoToMultiConverter> mMonoToMultiConverter;
std::unique_ptr<flowgraph::MultiToMonoConverter> mMultiToMonoConverter;
std::unique_ptr<flowgraph::ChannelCountConverter> mChannelCountConverter;
std::unique_ptr<resampler::MultiChannelResampler> mResampler;
std::unique_ptr<flowgraph::SampleRateConverter> mRateConverter;
std::unique_ptr<flowgraph::FlowGraphSink> mSink;
FixedBlockWriter mBlockWriter;
DataCallbackResult mCallbackResult = DataCallbackResult::Continue;
AudioStream *mFilterStream = nullptr;
std::unique_ptr<uint8_t[]> mAppBuffer;
};
}
#endif //OBOE_OBOE_FLOW_GRAPH_H

View File

@ -0,0 +1,106 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <memory>
#include "OboeDebug.h"
#include "FilterAudioStream.h"
using namespace oboe;
using namespace flowgraph;
// Output callback uses FixedBlockReader::read()
// <= SourceFloatCaller::onProcess()
// <=== DataConversionFlowGraph::read()
// <== FilterAudioStream::onAudioReady()
//
// Output blocking uses no block adapter because AAudio can accept
// writes of any size. It uses DataConversionFlowGraph::read() <== FilterAudioStream::write() <= app
//
// Input callback uses FixedBlockWriter::write()
// <= DataConversionFlowGraph::write()
// <= FilterAudioStream::onAudioReady()
//
// Input blocking uses FixedBlockReader::read() // TODO may not need block adapter
// <= SourceFloatCaller::onProcess()
// <=== SinkFloat::read()
// <= DataConversionFlowGraph::read()
// <== FilterAudioStream::read()
// <= app
Result FilterAudioStream::configureFlowGraph() {
mFlowGraph = std::make_unique<DataConversionFlowGraph>();
bool isOutput = getDirection() == Direction::Output;
AudioStream *sourceStream = isOutput ? this : mChildStream.get();
AudioStream *sinkStream = isOutput ? mChildStream.get() : this;
mRateScaler = ((double) getSampleRate()) / mChildStream->getSampleRate();
return mFlowGraph->configure(sourceStream, sinkStream);
}
// Put the data to be written at the source end of the flowgraph.
// Then read (pull) the data from the flowgraph and write it to the
// child stream.
ResultWithValue<int32_t> FilterAudioStream::write(const void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) {
int32_t framesWritten = 0;
mFlowGraph->setSource(buffer, numFrames);
while (true) {
int32_t numRead = mFlowGraph->read(mBlockingBuffer.get(),
getFramesPerBurst(),
timeoutNanoseconds);
if (numRead < 0) {
return ResultWithValue<int32_t>::createBasedOnSign(numRead);
}
if (numRead == 0) {
break; // finished processing the source buffer
}
auto writeResult = mChildStream->write(mBlockingBuffer.get(),
numRead,
timeoutNanoseconds);
if (!writeResult) {
return writeResult;
}
framesWritten += writeResult.value();
}
return ResultWithValue<int32_t>::createBasedOnSign(framesWritten);
}
// Read (pull) the data we want from the sink end of the flowgraph.
// The necessary data will be read from the child stream using a flowgraph callback.
ResultWithValue<int32_t> FilterAudioStream::read(void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) {
int32_t framesRead = mFlowGraph->read(buffer, numFrames, timeoutNanoseconds);
return ResultWithValue<int32_t>::createBasedOnSign(framesRead);
}
DataCallbackResult FilterAudioStream::onAudioReady(AudioStream *oboeStream,
void *audioData,
int32_t numFrames) {
int32_t framesProcessed;
if (oboeStream->getDirection() == Direction::Output) {
framesProcessed = mFlowGraph->read(audioData, numFrames, 0 /* timeout */);
} else {
framesProcessed = mFlowGraph->write(audioData, numFrames);
}
return (framesProcessed < numFrames)
? DataCallbackResult::Stop
: mFlowGraph->getDataCallbackResult();
}

View File

@ -0,0 +1,223 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_FILTER_AUDIO_STREAM_H
#define OBOE_FILTER_AUDIO_STREAM_H
#include <memory>
#include <oboe/AudioStream.h>
#include "DataConversionFlowGraph.h"
namespace oboe {
/**
* An AudioStream that wraps another AudioStream and provides audio data conversion.
* Operations may include channel conversion, data format conversion and/or sample rate conversion.
*/
class FilterAudioStream : public AudioStream, AudioStreamCallback {
public:
/**
* Construct an `AudioStream` using the given `AudioStreamBuilder` and a child AudioStream.
*
* This should only be called internally by AudioStreamBuilder.
* Ownership of childStream will be passed to this object.
*
* @param builder containing all the stream's attributes
*/
FilterAudioStream(const AudioStreamBuilder &builder, AudioStream *childStream)
: AudioStream(builder)
, mChildStream(childStream) {
// Intercept the callback if used.
if (builder.isErrorCallbackSpecified()) {
mErrorCallback = mChildStream->swapErrorCallback(this);
}
if (builder.isDataCallbackSpecified()) {
mDataCallback = mChildStream->swapDataCallback(this);
} else {
const int size = childStream->getFramesPerBurst() * childStream->getBytesPerFrame();
mBlockingBuffer = std::make_unique<uint8_t[]>(size);
}
// Copy parameters that may not match builder.
mBufferCapacityInFrames = mChildStream->getBufferCapacityInFrames();
mPerformanceMode = mChildStream->getPerformanceMode();
mInputPreset = mChildStream->getInputPreset();
mFramesPerBurst = mChildStream->getFramesPerBurst();
mDeviceId = mChildStream->getDeviceId();
}
virtual ~FilterAudioStream() = default;
AudioStream *getChildStream() const {
return mChildStream.get();
}
Result configureFlowGraph();
// Close child and parent.
Result close() override {
const Result result1 = mChildStream->close();
const Result result2 = AudioStream::close();
return (result1 != Result::OK ? result1 : result2);
}
/**
* Start the stream asynchronously. Returns immediately (does not block). Equivalent to calling
* `start(0)`.
*/
Result requestStart() override {
return mChildStream->requestStart();
}
/**
* Pause the stream asynchronously. Returns immediately (does not block). Equivalent to calling
* `pause(0)`.
*/
Result requestPause() override {
return mChildStream->requestPause();
}
/**
* Flush the stream asynchronously. Returns immediately (does not block). Equivalent to calling
* `flush(0)`.
*/
Result requestFlush() override {
return mChildStream->requestFlush();
}
/**
* Stop the stream asynchronously. Returns immediately (does not block). Equivalent to calling
* `stop(0)`.
*/
Result requestStop() override {
return mChildStream->requestStop();
}
ResultWithValue<int32_t> read(void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
ResultWithValue<int32_t> write(const void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
StreamState getState() override {
return mChildStream->getState();
}
Result waitForStateChange(
StreamState inputState,
StreamState *nextState,
int64_t timeoutNanoseconds) override {
return mChildStream->waitForStateChange(inputState, nextState, timeoutNanoseconds);
}
bool isXRunCountSupported() const override {
return mChildStream->isXRunCountSupported();
}
AudioApi getAudioApi() const override {
return mChildStream->getAudioApi();
}
void updateFramesWritten() override {
// TODO for output, just count local writes?
mFramesWritten = static_cast<int64_t>(mChildStream->getFramesWritten() * mRateScaler);
}
void updateFramesRead() override {
// TODO for input, just count local reads?
mFramesRead = static_cast<int64_t>(mChildStream->getFramesRead() * mRateScaler);
}
void *getUnderlyingStream() const override {
return mChildStream->getUnderlyingStream();
}
ResultWithValue<int32_t> setBufferSizeInFrames(int32_t requestedFrames) override {
return mChildStream->setBufferSizeInFrames(requestedFrames);
}
int32_t getBufferSizeInFrames() override {
mBufferSizeInFrames = mChildStream->getBufferSizeInFrames();
return mBufferSizeInFrames;
}
ResultWithValue<int32_t> getXRunCount() override {
return mChildStream->getXRunCount();
}
ResultWithValue<double> calculateLatencyMillis() override {
// This will automatically include the latency of the flowgraph?
return mChildStream->calculateLatencyMillis();
}
Result getTimestamp(clockid_t clockId,
int64_t *framePosition,
int64_t *timeNanoseconds) override {
int64_t childPosition = 0;
Result result = mChildStream->getTimestamp(clockId, &childPosition, timeNanoseconds);
// It is OK if framePosition is null.
if (framePosition) {
*framePosition = childPosition * mRateScaler;
}
return result;
}
DataCallbackResult onAudioReady(AudioStream *oboeStream,
void *audioData,
int32_t numFrames) override;
bool onError(AudioStream * /*audioStream*/, Result error) override {
if (mErrorCallback != nullptr) {
return mErrorCallback->onError(this, error);
}
return false;
}
void onErrorBeforeClose(AudioStream * /*oboeStream*/, Result error) override {
if (mErrorCallback != nullptr) {
mErrorCallback->onErrorBeforeClose(this, error);
}
}
void onErrorAfterClose(AudioStream * /*oboeStream*/, Result error) override {
// Close this parent stream because the callback will only close the child.
AudioStream::close();
if (mErrorCallback != nullptr) {
mErrorCallback->onErrorAfterClose(this, error);
}
}
/**
* @return last result passed from an error callback
*/
oboe::Result getLastErrorCallbackResult() const override {
return mChildStream->getLastErrorCallbackResult();
}
private:
std::unique_ptr<AudioStream> mChildStream; // this stream wraps the child stream
std::unique_ptr<DataConversionFlowGraph> mFlowGraph; // for converting data
std::unique_ptr<uint8_t[]> mBlockingBuffer; // temp buffer for write()
double mRateScaler = 1.0; // ratio parent/child sample rates
};
} // oboe
#endif //OBOE_FILTER_AUDIO_STREAM_H

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
#include "FixedBlockAdapter.h"
FixedBlockAdapter::~FixedBlockAdapter() {
}
int32_t FixedBlockAdapter::open(int32_t bytesPerFixedBlock)
{
mSize = bytesPerFixedBlock;
mStorage = std::make_unique<uint8_t[]>(bytesPerFixedBlock);
mPosition = 0;
return 0;
}
int32_t FixedBlockAdapter::close()
{
mStorage.reset(nullptr);
mSize = 0;
mPosition = 0;
return 0;
}

View File

@ -0,0 +1,67 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef AAUDIO_FIXED_BLOCK_ADAPTER_H
#define AAUDIO_FIXED_BLOCK_ADAPTER_H
#include <memory>
#include <stdint.h>
#include <sys/types.h>
/**
* Interface for a class that needs fixed-size blocks.
*/
class FixedBlockProcessor {
public:
virtual ~FixedBlockProcessor() = default;
/**
*
* @param buffer Pointer to first byte of data.
* @param numBytes This will be a fixed size specified in FixedBlockAdapter::open().
* @return Number of bytes processed or a negative error code.
*/
virtual int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) = 0;
};
/**
* Base class for a variable-to-fixed-size block adapter.
*/
class FixedBlockAdapter
{
public:
FixedBlockAdapter(FixedBlockProcessor &fixedBlockProcessor)
: mFixedBlockProcessor(fixedBlockProcessor) {}
virtual ~FixedBlockAdapter();
/**
* Allocate internal resources needed for buffering data.
*/
virtual int32_t open(int32_t bytesPerFixedBlock);
/**
* Free internal resources.
*/
int32_t close();
protected:
FixedBlockProcessor &mFixedBlockProcessor;
std::unique_ptr<uint8_t[]> mStorage; // Store data here while assembling buffers.
int32_t mSize = 0; // Size in bytes of the fixed size buffer.
int32_t mPosition = 0; // Offset of the last byte read or written.
};
#endif /* AAUDIO_FIXED_BLOCK_ADAPTER_H */

View File

@ -0,0 +1,73 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
#include <memory.h>
#include "FixedBlockAdapter.h"
#include "FixedBlockReader.h"
FixedBlockReader::FixedBlockReader(FixedBlockProcessor &fixedBlockProcessor)
: FixedBlockAdapter(fixedBlockProcessor) {
mPosition = mSize;
}
int32_t FixedBlockReader::open(int32_t bytesPerFixedBlock) {
int32_t result = FixedBlockAdapter::open(bytesPerFixedBlock);
mPosition = 0;
mValid = 0;
return result;
}
int32_t FixedBlockReader::readFromStorage(uint8_t *buffer, int32_t numBytes) {
int32_t bytesToRead = numBytes;
int32_t dataAvailable = mValid - mPosition;
if (bytesToRead > dataAvailable) {
bytesToRead = dataAvailable;
}
memcpy(buffer, mStorage.get() + mPosition, bytesToRead);
mPosition += bytesToRead;
return bytesToRead;
}
int32_t FixedBlockReader::read(uint8_t *buffer, int32_t numBytes) {
int32_t bytesRead;
int32_t bytesLeft = numBytes;
while(bytesLeft > 0) {
if (mPosition < mValid) {
// Use up bytes currently in storage.
bytesRead = readFromStorage(buffer, bytesLeft);
buffer += bytesRead;
bytesLeft -= bytesRead;
} else if (bytesLeft >= mSize) {
// Nothing in storage. Read through if enough for a complete block.
bytesRead = mFixedBlockProcessor.onProcessFixedBlock(buffer, mSize);
if (bytesRead < 0) return bytesRead;
buffer += bytesRead;
bytesLeft -= bytesRead;
} else {
// Just need a partial block so we have to reload storage.
bytesRead = mFixedBlockProcessor.onProcessFixedBlock(mStorage.get(), mSize);
if (bytesRead < 0) return bytesRead;
mPosition = 0;
mValid = bytesRead;
if (bytesRead == 0) break;
}
}
return numBytes - bytesLeft;
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef AAUDIO_FIXED_BLOCK_READER_H
#define AAUDIO_FIXED_BLOCK_READER_H
#include <stdint.h>
#include "FixedBlockAdapter.h"
/**
* Read from a fixed-size block to a variable sized block.
*
* This can be used to convert a pull data flow from fixed sized buffers to variable sized buffers.
* An example would be an audio output callback that reads from the app.
*/
class FixedBlockReader : public FixedBlockAdapter
{
public:
FixedBlockReader(FixedBlockProcessor &fixedBlockProcessor);
virtual ~FixedBlockReader() = default;
int32_t open(int32_t bytesPerFixedBlock) override;
/**
* Read into a variable sized block.
*
* Note that if the fixed-sized blocks must be aligned, then the variable-sized blocks
* must have the same alignment.
* For example, if the fixed-size blocks must be a multiple of 8, then the variable-sized
* blocks must also be a multiple of 8.
*
* @param buffer
* @param numBytes
* @return Number of bytes read or a negative error code.
*/
int32_t read(uint8_t *buffer, int32_t numBytes);
private:
int32_t readFromStorage(uint8_t *buffer, int32_t numBytes);
int32_t mValid = 0; // Number of valid bytes in mStorage.
};
#endif /* AAUDIO_FIXED_BLOCK_READER_H */

View File

@ -0,0 +1,73 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
#include <memory.h>
#include "FixedBlockAdapter.h"
#include "FixedBlockWriter.h"
FixedBlockWriter::FixedBlockWriter(FixedBlockProcessor &fixedBlockProcessor)
: FixedBlockAdapter(fixedBlockProcessor) {}
int32_t FixedBlockWriter::writeToStorage(uint8_t *buffer, int32_t numBytes) {
int32_t bytesToStore = numBytes;
int32_t roomAvailable = mSize - mPosition;
if (bytesToStore > roomAvailable) {
bytesToStore = roomAvailable;
}
memcpy(mStorage.get() + mPosition, buffer, bytesToStore);
mPosition += bytesToStore;
return bytesToStore;
}
int32_t FixedBlockWriter::write(uint8_t *buffer, int32_t numBytes) {
int32_t bytesLeft = numBytes;
// If we already have data in storage then add to it.
if (mPosition > 0) {
int32_t bytesWritten = writeToStorage(buffer, bytesLeft);
buffer += bytesWritten;
bytesLeft -= bytesWritten;
// If storage full then flush it out
if (mPosition == mSize) {
bytesWritten = mFixedBlockProcessor.onProcessFixedBlock(mStorage.get(), mSize);
if (bytesWritten < 0) return bytesWritten;
mPosition = 0;
if (bytesWritten < mSize) {
// Only some of the data was written! This should not happen.
return -1;
}
}
}
// Write through if enough for a complete block.
while(bytesLeft > mSize) {
int32_t bytesWritten = mFixedBlockProcessor.onProcessFixedBlock(buffer, mSize);
if (bytesWritten < 0) return bytesWritten;
buffer += bytesWritten;
bytesLeft -= bytesWritten;
}
// Save any remaining partial blocks for next time.
if (bytesLeft > 0) {
int32_t bytesWritten = writeToStorage(buffer, bytesLeft);
bytesLeft -= bytesWritten;
}
return numBytes - bytesLeft;
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef AAUDIO_FIXED_BLOCK_WRITER_H
#define AAUDIO_FIXED_BLOCK_WRITER_H
#include <stdint.h>
#include "FixedBlockAdapter.h"
/**
* This can be used to convert a push data flow from variable sized buffers to fixed sized buffers.
* An example would be an audio input callback.
*/
class FixedBlockWriter : public FixedBlockAdapter
{
public:
FixedBlockWriter(FixedBlockProcessor &fixedBlockProcessor);
virtual ~FixedBlockWriter() = default;
/**
* Write from a variable sized block.
*
* Note that if the fixed-sized blocks must be aligned, then the variable-sized blocks
* must have the same alignment.
* For example, if the fixed-size blocks must be a multiple of 8, then the variable-sized
* blocks must also be a multiple of 8.
*
* @param buffer
* @param numBytes
* @return Number of bytes written or a negative error code.
*/
int32_t write(uint8_t *buffer, int32_t numBytes);
private:
int32_t writeToStorage(uint8_t *buffer, int32_t numBytes);
};
#endif /* AAUDIO_FIXED_BLOCK_WRITER_H */

View File

@ -0,0 +1,108 @@
/*
* Copyright 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "oboe/LatencyTuner.h"
using namespace oboe;
LatencyTuner::LatencyTuner(AudioStream &stream)
: LatencyTuner(stream, stream.getBufferCapacityInFrames()) {
}
LatencyTuner::LatencyTuner(oboe::AudioStream &stream, int32_t maximumBufferSize)
: mStream(stream)
, mMaxBufferSize(maximumBufferSize) {
int32_t burstSize = stream.getFramesPerBurst();
setMinimumBufferSize(kDefaultNumBursts * burstSize);
setBufferSizeIncrement(burstSize);
reset();
}
Result LatencyTuner::tune() {
if (mState == State::Unsupported) {
return Result::ErrorUnimplemented;
}
Result result = Result::OK;
// Process reset requests.
int32_t numRequests = mLatencyTriggerRequests.load();
if (numRequests != mLatencyTriggerResponses.load()) {
mLatencyTriggerResponses.store(numRequests);
reset();
}
// Set state to Active if the idle countdown has reached zero.
if (mState == State::Idle && --mIdleCountDown <= 0) {
mState = State::Active;
}
// When state is Active attempt to change the buffer size if the number of xRuns has increased.
if (mState == State::Active) {
auto xRunCountResult = mStream.getXRunCount();
if (xRunCountResult == Result::OK) {
if ((xRunCountResult.value() - mPreviousXRuns) > 0) {
mPreviousXRuns = xRunCountResult.value();
int32_t oldBufferSize = mStream.getBufferSizeInFrames();
int32_t requestedBufferSize = oldBufferSize + getBufferSizeIncrement();
// Do not request more than the maximum buffer size (which was either user-specified
// or was from stream->getBufferCapacityInFrames())
if (requestedBufferSize > mMaxBufferSize) requestedBufferSize = mMaxBufferSize;
// Note that this will not allocate more memory. It simply determines
// how much of the existing buffer capacity will be used. The size will be
// clipped to the bufferCapacity by AAudio.
auto setBufferResult = mStream.setBufferSizeInFrames(requestedBufferSize);
if (setBufferResult != Result::OK) {
result = setBufferResult;
mState = State::Unsupported;
} else if (setBufferResult.value() == oldBufferSize) {
mState = State::AtMax;
}
}
} else {
mState = State::Unsupported;
}
}
if (mState == State::Unsupported) {
result = Result::ErrorUnimplemented;
}
if (mState == State::AtMax) {
result = Result::OK;
}
return result;
}
void LatencyTuner::requestReset() {
if (mState != State::Unsupported) {
mLatencyTriggerRequests++;
}
}
void LatencyTuner::reset() {
mState = State::Idle;
mIdleCountDown = kIdleCount;
// Set to minimal latency
mStream.setBufferSizeInFrames(getMinimumBufferSize());
}
bool LatencyTuner::isAtMaximumBufferSize() {
return mState == State::AtMax;
}

View File

@ -0,0 +1,112 @@
/*
* Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef COMMON_MONOTONIC_COUNTER_H
#define COMMON_MONOTONIC_COUNTER_H
#include <cstdint>
/**
* Maintain a 64-bit monotonic counter.
* Can be used to track a 32-bit counter that wraps or gets reset.
*
* Note that this is not atomic and has no interior locks.
* A caller will need to provide their own exterior locking
* if they need to use it from multiple threads.
*/
class MonotonicCounter {
public:
MonotonicCounter() {}
virtual ~MonotonicCounter() {}
/**
* @return current value of the counter
*/
int64_t get() const {
return mCounter64;
}
/**
* set the current value of the counter
*/
void set(int64_t counter) {
mCounter64 = counter;
}
/**
* Advance the counter if delta is positive.
* @return current value of the counter
*/
int64_t increment(int64_t delta) {
if (delta > 0) {
mCounter64 += delta;
}
return mCounter64;
}
/**
* Advance the 64-bit counter if (current32 - previousCurrent32) > 0.
* This can be used to convert a 32-bit counter that may be wrapping into
* a monotonic 64-bit counter.
*
* This counter32 should NOT be allowed to advance by more than 0x7FFFFFFF between calls.
* Think of the wrapping counter like a sine wave. If the frequency of the signal
* is more than half the sampling rate (Nyquist rate) then you cannot measure it properly.
* If the counter wraps around every 24 hours then we should measure it with a period
* of less than 12 hours.
*
* @return current value of the 64-bit counter
*/
int64_t update32(int32_t counter32) {
int32_t delta = counter32 - mCounter32;
// protect against the mCounter64 going backwards
if (delta > 0) {
mCounter64 += delta;
mCounter32 = counter32;
}
return mCounter64;
}
/**
* Reset the stored value of the 32-bit counter.
* This is used if your counter32 has been reset to zero.
*/
void reset32() {
mCounter32 = 0;
}
/**
* Round 64-bit counter up to a multiple of the period.
*
* The period must be positive.
*
* @param period might be, for example, a buffer capacity
*/
void roundUp64(int32_t period) {
if (period > 0) {
int64_t numPeriods = (mCounter64 + period - 1) / period;
mCounter64 = numPeriods * period;
}
}
private:
int64_t mCounter64 = 0;
int32_t mCounter32 = 0;
};
#endif //COMMON_MONOTONIC_COUNTER_H

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef OBOE_DEBUG_H
#define OBOE_DEBUG_H
#include <android/log.h>
#ifndef MODULE_NAME
#define MODULE_NAME "OboeAudio"
#endif
// Always log INFO and errors.
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, MODULE_NAME, __VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, MODULE_NAME, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, MODULE_NAME, __VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, MODULE_NAME, __VA_ARGS__)
#if OBOE_ENABLE_LOGGING
#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, MODULE_NAME, __VA_ARGS__)
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, MODULE_NAME, __VA_ARGS__)
#else
#define LOGV(...)
#define LOGD(...)
#endif
#endif //OBOE_DEBUG_H

View File

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

View File

@ -0,0 +1,131 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_QUIRKS_MANAGER_H
#define OBOE_QUIRKS_MANAGER_H
#include <memory>
#include <oboe/AudioStreamBuilder.h>
#include <aaudio/AudioStreamAAudio.h>
#ifndef __ANDROID_API_R__
#define __ANDROID_API_R__ 30
#endif
namespace oboe {
/**
* INTERNAL USE ONLY.
*
* Based on manufacturer, model and Android version number
* decide whether data conversion needs to occur.
*
* This also manages device and version specific workarounds.
*/
class QuirksManager {
public:
static QuirksManager &getInstance() {
static QuirksManager instance; // singleton
return instance;
}
QuirksManager();
virtual ~QuirksManager() = default;
/**
* Do we need to do channel, format or rate conversion to provide a low latency
* stream for this builder? If so then provide a builder for the native child stream
* that will be used to get low latency.
*
* @param builder builder provided by application
* @param childBuilder modified builder appropriate for the underlying device
* @return true if conversion is needed
*/
bool isConversionNeeded(const AudioStreamBuilder &builder, AudioStreamBuilder &childBuilder);
static bool isMMapUsed(AudioStream &stream) {
bool answer = false;
if (stream.getAudioApi() == AudioApi::AAudio) {
AudioStreamAAudio *streamAAudio =
reinterpret_cast<AudioStreamAAudio *>(&stream);
answer = streamAAudio->isMMapUsed();
}
return answer;
}
virtual int32_t clipBufferSize(AudioStream &stream, int32_t bufferSize) {
return mDeviceQuirks->clipBufferSize(stream, bufferSize);
}
class DeviceQuirks {
public:
virtual ~DeviceQuirks() = default;
/**
* Restrict buffer size. This is mainly to avoid glitches caused by MMAP
* timestamp inaccuracies.
* @param stream
* @param requestedSize
* @return
*/
int32_t clipBufferSize(AudioStream &stream, int32_t requestedSize);
// Exclusive MMAP streams can have glitches because they are using a timing
// model of the DSP to control IO instead of direct synchronization.
virtual int32_t getExclusiveBottomMarginInBursts() const {
return kDefaultBottomMarginInBursts;
}
virtual int32_t getExclusiveTopMarginInBursts() const {
return kDefaultTopMarginInBursts;
}
// On some devices, you can open a mono stream but it is actually running in stereo!
virtual bool isMonoMMapActuallyStereo() const {
return false;
}
virtual bool isAAudioMMapPossible(const AudioStreamBuilder &builder) const;
virtual bool isMMapSafe(const AudioStreamBuilder & /* builder */ ) {
return true;
}
static constexpr int32_t kDefaultBottomMarginInBursts = 0;
static constexpr int32_t kDefaultTopMarginInBursts = 0;
// For Legacy streams, do not let the buffer go below one burst.
// b/129545119 | AAudio Legacy allows setBufferSizeInFrames too low
// Fixed in Q
static constexpr int32_t kLegacyBottomMarginInBursts = 1;
static constexpr int32_t kCommonNativeRate = 48000; // very typical native sample rate
};
bool isMMapSafe(AudioStreamBuilder &builder);
private:
static constexpr int32_t kChannelCountMono = 1;
static constexpr int32_t kChannelCountStereo = 2;
std::unique_ptr<DeviceQuirks> mDeviceQuirks{};
};
}
#endif //OBOE_QUIRKS_MANAGER_H

View File

@ -0,0 +1,30 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <unistd.h>
#include "flowgraph/FlowGraphNode.h"
#include "SourceFloatCaller.h"
using namespace oboe;
using namespace flowgraph;
int32_t SourceFloatCaller::onProcess(int32_t numFrames) {
int32_t numBytes = mStream->getBytesPerFrame() * numFrames;
int32_t bytesRead = mBlockReader.read((uint8_t *) output.getBuffer(), numBytes);
int32_t framesRead = bytesRead / mStream->getBytesPerFrame();
return framesRead;
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_SOURCE_FLOAT_CALLER_H
#define OBOE_SOURCE_FLOAT_CALLER_H
#include <unistd.h>
#include <sys/types.h>
#include "flowgraph/FlowGraphNode.h"
#include "AudioSourceCaller.h"
#include "FixedBlockReader.h"
namespace oboe {
/**
* AudioSource that uses callback to get more float data.
*/
class SourceFloatCaller : public AudioSourceCaller {
public:
SourceFloatCaller(int32_t channelCount, int32_t framesPerCallback)
: AudioSourceCaller(channelCount, framesPerCallback, (int32_t)sizeof(float)) {}
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceFloatCaller";
}
};
}
#endif //OBOE_SOURCE_FLOAT_CALLER_H

View File

@ -0,0 +1,47 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <unistd.h>
#include "flowgraph/FlowGraphNode.h"
#include "SourceI16Caller.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace oboe;
using namespace flowgraph;
int32_t SourceI16Caller::onProcess(int32_t numFrames) {
int32_t numBytes = mStream->getBytesPerFrame() * numFrames;
int32_t bytesRead = mBlockReader.read((uint8_t *) mConversionBuffer.get(), numBytes);
int32_t framesRead = bytesRead / mStream->getBytesPerFrame();
float *floatData = output.getBuffer();
const int16_t *shortData = mConversionBuffer.get();
int32_t numSamples = framesRead * output.getSamplesPerFrame();
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_i16(floatData, shortData, numSamples);
#else
for (int i = 0; i < numSamples; i++) {
*floatData++ = *shortData++ * (1.0f / 32768);
}
#endif
return framesRead;
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_SOURCE_I16_CALLER_H
#define OBOE_SOURCE_I16_CALLER_H
#include <unistd.h>
#include <sys/types.h>
#include "flowgraph/FlowGraphNode.h"
#include "AudioSourceCaller.h"
#include "FixedBlockReader.h"
namespace oboe {
/**
* AudioSource that uses callback to get more data.
*/
class SourceI16Caller : public AudioSourceCaller {
public:
SourceI16Caller(int32_t channelCount, int32_t framesPerCallback)
: AudioSourceCaller(channelCount, framesPerCallback, sizeof(int16_t)) {
mConversionBuffer = std::make_unique<int16_t[]>(channelCount * output.getFramesPerBuffer());
}
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI16Caller";
}
private:
std::unique_ptr<int16_t[]> mConversionBuffer;
};
}
#endif //OBOE_SOURCE_I16_CALLER_H

View File

@ -0,0 +1,56 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <unistd.h>
#include "flowgraph/FlowGraphNode.h"
#include "SourceI24Caller.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace oboe;
using namespace flowgraph;
int32_t SourceI24Caller::onProcess(int32_t numFrames) {
int32_t numBytes = mStream->getBytesPerFrame() * numFrames;
int32_t bytesRead = mBlockReader.read((uint8_t *) mConversionBuffer.get(), numBytes);
int32_t framesRead = bytesRead / mStream->getBytesPerFrame();
float *floatData = output.getBuffer();
const uint8_t *byteData = mConversionBuffer.get();
int32_t numSamples = framesRead * output.getSamplesPerFrame();
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_p24(floatData, byteData, numSamples);
#else
static const float scale = 1. / (float)(1UL << 31);
for (int i = 0; i < numSamples; i++) {
// Assemble the data assuming Little Endian format.
int32_t pad = byteData[2];
pad <<= 8;
pad |= byteData[1];
pad <<= 8;
pad |= byteData[0];
pad <<= 8; // Shift to 32 bit data so the sign is correct.
byteData += kBytesPerI24Packed;
*floatData++ = pad * scale; // scale to range -1.0 to 1.0
}
#endif
return framesRead;
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_SOURCE_I24_CALLER_H
#define OBOE_SOURCE_I24_CALLER_H
#include <unistd.h>
#include <sys/types.h>
#include "flowgraph/FlowGraphNode.h"
#include "AudioSourceCaller.h"
#include "FixedBlockReader.h"
namespace oboe {
/**
* AudioSource that uses callback to get more data.
*/
class SourceI24Caller : public AudioSourceCaller {
public:
SourceI24Caller(int32_t channelCount, int32_t framesPerCallback)
: AudioSourceCaller(channelCount, framesPerCallback, kBytesPerI24Packed) {
mConversionBuffer = std::make_unique<uint8_t[]>(
kBytesPerI24Packed * channelCount * output.getFramesPerBuffer());
}
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI24Caller";
}
private:
std::unique_ptr<uint8_t[]> mConversionBuffer;
static constexpr int kBytesPerI24Packed = 3;
};
}
#endif //OBOE_SOURCE_I16_CALLER_H

View File

@ -0,0 +1,47 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <unistd.h>
#include "flowgraph/FlowGraphNode.h"
#include "SourceI32Caller.h"
#if FLOWGRAPH_ANDROID_INTERNAL
#include <audio_utils/primitives.h>
#endif
using namespace oboe;
using namespace flowgraph;
int32_t SourceI32Caller::onProcess(int32_t numFrames) {
int32_t numBytes = mStream->getBytesPerFrame() * numFrames;
int32_t bytesRead = mBlockReader.read((uint8_t *) mConversionBuffer.get(), numBytes);
int32_t framesRead = bytesRead / mStream->getBytesPerFrame();
float *floatData = output.getBuffer();
const int32_t *intData = mConversionBuffer.get();
int32_t numSamples = framesRead * output.getSamplesPerFrame();
#if FLOWGRAPH_ANDROID_INTERNAL
memcpy_to_float_from_i32(floatData, shortData, numSamples);
#else
for (int i = 0; i < numSamples; i++) {
*floatData++ = *intData++ * kScale;
}
#endif
return framesRead;
}

View File

@ -0,0 +1,52 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_SOURCE_I32_CALLER_H
#define OBOE_SOURCE_I32_CALLER_H
#include <memory.h>
#include <unistd.h>
#include <sys/types.h>
#include "flowgraph/FlowGraphNode.h"
#include "AudioSourceCaller.h"
#include "FixedBlockReader.h"
namespace oboe {
/**
* AudioSource that uses callback to get more data.
*/
class SourceI32Caller : public AudioSourceCaller {
public:
SourceI32Caller(int32_t channelCount, int32_t framesPerCallback)
: AudioSourceCaller(channelCount, framesPerCallback, sizeof(int32_t)) {
mConversionBuffer = std::make_unique<int32_t[]>(channelCount * output.getFramesPerBuffer());
}
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "SourceI32Caller";
}
private:
std::unique_ptr<int32_t[]> mConversionBuffer;
static constexpr float kScale = 1.0 / (1UL << 31);
};
}
#endif //OBOE_SOURCE_I32_CALLER_H

View File

@ -0,0 +1,112 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "oboe/StabilizedCallback.h"
#include "common/AudioClock.h"
#include "common/Trace.h"
constexpr int32_t kLoadGenerationStepSizeNanos = 20000;
constexpr float kPercentageOfCallbackToUse = 0.8;
using namespace oboe;
StabilizedCallback::StabilizedCallback(AudioStreamCallback *callback) : mCallback(callback){
Trace::initialize();
}
/**
* An audio callback which attempts to do work for a fixed amount of time.
*
* @param oboeStream
* @param audioData
* @param numFrames
* @return
*/
DataCallbackResult
StabilizedCallback::onAudioReady(AudioStream *oboeStream, void *audioData, int32_t numFrames) {
int64_t startTimeNanos = AudioClock::getNanoseconds();
if (mFrameCount == 0){
mEpochTimeNanos = startTimeNanos;
}
int64_t durationSinceEpochNanos = startTimeNanos - mEpochTimeNanos;
// In an ideal world the callback start time will be exactly the same as the duration of the
// frames already read/written into the stream. In reality the callback can start early
// or late. By finding the delta we can calculate the target duration for our stabilized
// callback.
int64_t idealStartTimeNanos = (mFrameCount * kNanosPerSecond) / oboeStream->getSampleRate();
int64_t lateStartNanos = durationSinceEpochNanos - idealStartTimeNanos;
if (lateStartNanos < 0){
// This was an early start which indicates that our previous epoch was a late callback.
// Update our epoch to this more accurate time.
mEpochTimeNanos = startTimeNanos;
mFrameCount = 0;
}
int64_t numFramesAsNanos = (numFrames * kNanosPerSecond) / oboeStream->getSampleRate();
int64_t targetDurationNanos = static_cast<int64_t>(
(numFramesAsNanos * kPercentageOfCallbackToUse) - lateStartNanos);
Trace::beginSection("Actual load");
DataCallbackResult result = mCallback->onAudioReady(oboeStream, audioData, numFrames);
Trace::endSection();
int64_t executionDurationNanos = AudioClock::getNanoseconds() - startTimeNanos;
int64_t stabilizingLoadDurationNanos = targetDurationNanos - executionDurationNanos;
Trace::beginSection("Stabilized load for %lldns", stabilizingLoadDurationNanos);
generateLoad(stabilizingLoadDurationNanos);
Trace::endSection();
// Wraparound: At 48000 frames per second mFrameCount wraparound will occur after 6m years,
// significantly longer than the average lifetime of an Android phone.
mFrameCount += numFrames;
return result;
}
void StabilizedCallback::generateLoad(int64_t durationNanos) {
int64_t currentTimeNanos = AudioClock::getNanoseconds();
int64_t deadlineTimeNanos = currentTimeNanos + durationNanos;
// opsPerStep gives us an estimated number of operations which need to be run to fully utilize
// the CPU for a fixed amount of time (specified by kLoadGenerationStepSizeNanos).
// After each step the opsPerStep value is re-calculated based on the actual time taken to
// execute those operations.
auto opsPerStep = (int)(mOpsPerNano * kLoadGenerationStepSizeNanos);
int64_t stepDurationNanos = 0;
int64_t previousTimeNanos = 0;
while (currentTimeNanos <= deadlineTimeNanos){
for (int i = 0; i < opsPerStep; i++) cpu_relax();
previousTimeNanos = currentTimeNanos;
currentTimeNanos = AudioClock::getNanoseconds();
stepDurationNanos = currentTimeNanos - previousTimeNanos;
// Calculate exponential moving average to smooth out values, this acts as a low pass filter.
// @see https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
static const float kFilterCoefficient = 0.1;
auto measuredOpsPerNano = (double) opsPerStep / stepDurationNanos;
mOpsPerNano = kFilterCoefficient * measuredOpsPerNano + (1.0 - kFilterCoefficient) * mOpsPerNano;
opsPerStep = (int) (mOpsPerNano * kLoadGenerationStepSizeNanos);
}
}

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

View File

@ -0,0 +1,31 @@
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_TRACE_H
#define OBOE_TRACE_H
class Trace {
public:
static void beginSection(const char *format, ...);
static void endSection();
static void initialize();
private:
static bool mIsTracingSupported;
};
#endif //OBOE_TRACE_H

View File

@ -0,0 +1,313 @@
/*
* Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdlib.h>
#include <unistd.h>
#include <sstream>
#ifdef __ANDROID__
#include <sys/system_properties.h>
#endif
#include <oboe/AudioStream.h>
#include "oboe/Definitions.h"
#include "oboe/Utilities.h"
namespace oboe {
constexpr float kScaleI16ToFloat = (1.0f / 32768.0f);
void convertFloatToPcm16(const float *source, int16_t *destination, int32_t numSamples) {
for (int i = 0; i < numSamples; i++) {
float fval = source[i];
fval += 1.0; // to avoid discontinuity at 0.0 caused by truncation
fval *= 32768.0f;
auto sample = static_cast<int32_t>(fval);
// clip to 16-bit range
if (sample < 0) sample = 0;
else if (sample > 0x0FFFF) sample = 0x0FFFF;
sample -= 32768; // center at zero
destination[i] = static_cast<int16_t>(sample);
}
}
void convertPcm16ToFloat(const int16_t *source, float *destination, int32_t numSamples) {
for (int i = 0; i < numSamples; i++) {
destination[i] = source[i] * kScaleI16ToFloat;
}
}
int32_t convertFormatToSizeInBytes(AudioFormat format) {
int32_t size = 0;
switch (format) {
case AudioFormat::I16:
size = sizeof(int16_t);
break;
case AudioFormat::Float:
size = sizeof(float);
break;
case AudioFormat::I24:
size = 3; // packed 24-bit data
break;
case AudioFormat::I32:
size = sizeof(int32_t);
break;
default:
break;
}
return size;
}
template<>
const char *convertToText<Result>(Result returnCode) {
switch (returnCode) {
case Result::OK: return "OK";
case Result::ErrorDisconnected: return "ErrorDisconnected";
case Result::ErrorIllegalArgument: return "ErrorIllegalArgument";
case Result::ErrorInternal: return "ErrorInternal";
case Result::ErrorInvalidState: return "ErrorInvalidState";
case Result::ErrorInvalidHandle: return "ErrorInvalidHandle";
case Result::ErrorUnimplemented: return "ErrorUnimplemented";
case Result::ErrorUnavailable: return "ErrorUnavailable";
case Result::ErrorNoFreeHandles: return "ErrorNoFreeHandles";
case Result::ErrorNoMemory: return "ErrorNoMemory";
case Result::ErrorNull: return "ErrorNull";
case Result::ErrorTimeout: return "ErrorTimeout";
case Result::ErrorWouldBlock: return "ErrorWouldBlock";
case Result::ErrorInvalidFormat: return "ErrorInvalidFormat";
case Result::ErrorOutOfRange: return "ErrorOutOfRange";
case Result::ErrorNoService: return "ErrorNoService";
case Result::ErrorInvalidRate: return "ErrorInvalidRate";
case Result::ErrorClosed: return "ErrorClosed";
default: return "Unrecognized result";
}
}
template<>
const char *convertToText<AudioFormat>(AudioFormat format) {
switch (format) {
case AudioFormat::Invalid: return "Invalid";
case AudioFormat::Unspecified: return "Unspecified";
case AudioFormat::I16: return "I16";
case AudioFormat::Float: return "Float";
case AudioFormat::I24: return "I24";
case AudioFormat::I32: return "I32";
default: return "Unrecognized format";
}
}
template<>
const char *convertToText<PerformanceMode>(PerformanceMode mode) {
switch (mode) {
case PerformanceMode::LowLatency: return "LowLatency";
case PerformanceMode::None: return "None";
case PerformanceMode::PowerSaving: return "PowerSaving";
default: return "Unrecognized performance mode";
}
}
template<>
const char *convertToText<SharingMode>(SharingMode mode) {
switch (mode) {
case SharingMode::Exclusive: return "Exclusive";
case SharingMode::Shared: return "Shared";
default: return "Unrecognized sharing mode";
}
}
template<>
const char *convertToText<DataCallbackResult>(DataCallbackResult result) {
switch (result) {
case DataCallbackResult::Continue: return "Continue";
case DataCallbackResult::Stop: return "Stop";
default: return "Unrecognized data callback result";
}
}
template<>
const char *convertToText<Direction>(Direction direction) {
switch (direction) {
case Direction::Input: return "Input";
case Direction::Output: return "Output";
default: return "Unrecognized direction";
}
}
template<>
const char *convertToText<StreamState>(StreamState state) {
switch (state) {
case StreamState::Closed: return "Closed";
case StreamState::Closing: return "Closing";
case StreamState::Disconnected: return "Disconnected";
case StreamState::Flushed: return "Flushed";
case StreamState::Flushing: return "Flushing";
case StreamState::Open: return "Open";
case StreamState::Paused: return "Paused";
case StreamState::Pausing: return "Pausing";
case StreamState::Started: return "Started";
case StreamState::Starting: return "Starting";
case StreamState::Stopped: return "Stopped";
case StreamState::Stopping: return "Stopping";
case StreamState::Uninitialized: return "Uninitialized";
case StreamState::Unknown: return "Unknown";
default: return "Unrecognized stream state";
}
}
template<>
const char *convertToText<AudioApi>(AudioApi audioApi) {
switch (audioApi) {
case AudioApi::Unspecified: return "Unspecified";
case AudioApi::OpenSLES: return "OpenSLES";
case AudioApi::AAudio: return "AAudio";
default: return "Unrecognized audio API";
}
}
template<>
const char *convertToText<AudioStream*>(AudioStream* stream) {
static std::string streamText;
std::stringstream s;
s<<"StreamID: "<< static_cast<void*>(stream)<<std::endl
<<"DeviceId: "<<stream->getDeviceId()<<std::endl
<<"Direction: "<<oboe::convertToText(stream->getDirection())<<std::endl
<<"API type: "<<oboe::convertToText(stream->getAudioApi())<<std::endl
<<"BufferCapacity: "<<stream->getBufferCapacityInFrames()<<std::endl
<<"BufferSize: "<<stream->getBufferSizeInFrames()<<std::endl
<<"FramesPerBurst: "<< stream->getFramesPerBurst()<<std::endl
<<"FramesPerDataCallback: "<<stream->getFramesPerDataCallback()<<std::endl
<<"SampleRate: "<<stream->getSampleRate()<<std::endl
<<"ChannelCount: "<<stream->getChannelCount()<<std::endl
<<"Format: "<<oboe::convertToText(stream->getFormat())<<std::endl
<<"SharingMode: "<<oboe::convertToText(stream->getSharingMode())<<std::endl
<<"PerformanceMode: "<<oboe::convertToText(stream->getPerformanceMode())
<<std::endl
<<"CurrentState: "<<oboe::convertToText(stream->getState())<<std::endl
<<"XRunCount: "<<stream->getXRunCount()<<std::endl
<<"FramesRead: "<<stream->getFramesRead()<<std::endl
<<"FramesWritten: "<<stream->getFramesWritten()<<std::endl;
streamText = s.str();
return streamText.c_str();
}
template<>
const char *convertToText<Usage>(Usage usage) {
switch (usage) {
case Usage::Media: return "Media";
case Usage::VoiceCommunication: return "VoiceCommunication";
case Usage::VoiceCommunicationSignalling: return "VoiceCommunicationSignalling";
case Usage::Alarm: return "Alarm";
case Usage::Notification: return "Notification";
case Usage::NotificationRingtone: return "NotificationRingtone";
case Usage::NotificationEvent: return "NotificationEvent";
case Usage::AssistanceAccessibility: return "AssistanceAccessibility";
case Usage::AssistanceNavigationGuidance: return "AssistanceNavigationGuidance";
case Usage::AssistanceSonification: return "AssistanceSonification";
case Usage::Game: return "Game";
case Usage::Assistant: return "Assistant";
default: return "Unrecognized usage";
}
}
template<>
const char *convertToText<ContentType>(ContentType contentType) {
switch (contentType) {
case ContentType::Speech: return "Speech";
case ContentType::Music: return "Music";
case ContentType::Movie: return "Movie";
case ContentType::Sonification: return "Sonification";
default: return "Unrecognized content type";
}
}
template<>
const char *convertToText<InputPreset>(InputPreset inputPreset) {
switch (inputPreset) {
case InputPreset::Generic: return "Generic";
case InputPreset::Camcorder: return "Camcorder";
case InputPreset::VoiceRecognition: return "VoiceRecognition";
case InputPreset::VoiceCommunication: return "VoiceCommunication";
case InputPreset::Unprocessed: return "Unprocessed";
case InputPreset::VoicePerformance: return "VoicePerformance";
default: return "Unrecognized input preset";
}
}
template<>
const char *convertToText<SessionId>(SessionId sessionId) {
switch (sessionId) {
case SessionId::None: return "None";
case SessionId::Allocate: return "Allocate";
default: return "Unrecognized session id";
}
}
template<>
const char *convertToText<ChannelCount>(ChannelCount channelCount) {
switch (channelCount) {
case ChannelCount::Unspecified: return "Unspecified";
case ChannelCount::Mono: return "Mono";
case ChannelCount::Stereo: return "Stereo";
default: return "Unrecognized channel count";
}
}
std::string getPropertyString(const char * name) {
std::string result;
#ifdef __ANDROID__
char valueText[PROP_VALUE_MAX] = {0};
if (__system_property_get(name, valueText) != 0) {
result = valueText;
}
#else
(void) name;
#endif
return result;
}
int getPropertyInteger(const char * name, int defaultValue) {
int result = defaultValue;
#ifdef __ANDROID__
char valueText[PROP_VALUE_MAX] = {0};
if (__system_property_get(name, valueText) != 0) {
result = atoi(valueText);
}
#else
(void) name;
#endif
return result;
}
int getSdkVersion() {
static int sCachedSdkVersion = -1;
#ifdef __ANDROID__
if (sCachedSdkVersion == -1) {
sCachedSdkVersion = getPropertyInteger("ro.build.version.sdk", -1);
}
#endif
return sCachedSdkVersion;
}
}// namespace oboe

View File

@ -0,0 +1,28 @@
/*
* Copyright 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "oboe/Version.h"
namespace oboe {
// This variable enables the version information to be read from the resulting binary e.g.
// by running `objdump -s --section=.data <binary>`
// Please do not optimize or change in any way.
char kVersionText[] = "OboeVersion" OBOE_VERSION_TEXT;
const char * getVersionText(){
return kVersionText;
}
} // namespace oboe

View File

@ -0,0 +1,178 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <memory.h>
#include <stdint.h>
#include "fifo/FifoControllerBase.h"
#include "fifo/FifoController.h"
#include "fifo/FifoControllerIndirect.h"
#include "fifo/FifoBuffer.h"
namespace oboe {
FifoBuffer::FifoBuffer(uint32_t bytesPerFrame, uint32_t capacityInFrames)
: mBytesPerFrame(bytesPerFrame)
, mStorage(nullptr)
, mFramesReadCount(0)
, mFramesUnderrunCount(0)
{
mFifo = std::make_unique<FifoController>(capacityInFrames);
// allocate buffer
int32_t bytesPerBuffer = bytesPerFrame * capacityInFrames;
mStorage = new uint8_t[bytesPerBuffer];
mStorageOwned = true;
}
FifoBuffer::FifoBuffer( uint32_t bytesPerFrame,
uint32_t capacityInFrames,
std::atomic<uint64_t> *readCounterAddress,
std::atomic<uint64_t> *writeCounterAddress,
uint8_t *dataStorageAddress
)
: mBytesPerFrame(bytesPerFrame)
, mStorage(dataStorageAddress)
, mFramesReadCount(0)
, mFramesUnderrunCount(0)
{
mFifo = std::make_unique<FifoControllerIndirect>(capacityInFrames,
readCounterAddress,
writeCounterAddress);
mStorage = dataStorageAddress;
mStorageOwned = false;
}
FifoBuffer::~FifoBuffer() {
if (mStorageOwned) {
delete[] mStorage;
}
}
int32_t FifoBuffer::convertFramesToBytes(int32_t frames) {
return frames * mBytesPerFrame;
}
int32_t FifoBuffer::read(void *buffer, int32_t numFrames) {
if (numFrames <= 0) {
return 0;
}
// safe because numFrames is guaranteed positive
uint32_t framesToRead = static_cast<uint32_t>(numFrames);
uint32_t framesAvailable = mFifo->getFullFramesAvailable();
framesToRead = std::min(framesToRead, framesAvailable);
uint32_t readIndex = mFifo->getReadIndex(); // ranges 0 to capacity
uint8_t *destination = reinterpret_cast<uint8_t *>(buffer);
uint8_t *source = &mStorage[convertFramesToBytes(readIndex)];
if ((readIndex + framesToRead) > mFifo->getFrameCapacity()) {
// read in two parts, first part here is at the end of the mStorage buffer
int32_t frames1 = static_cast<int32_t>(mFifo->getFrameCapacity() - readIndex);
int32_t numBytes = convertFramesToBytes(frames1);
if (numBytes < 0) {
return static_cast<int32_t>(Result::ErrorOutOfRange);
}
memcpy(destination, source, static_cast<size_t>(numBytes));
destination += numBytes;
// read second part, which is at the beginning of mStorage
source = &mStorage[0];
int32_t frames2 = static_cast<uint32_t>(framesToRead - frames1);
numBytes = convertFramesToBytes(frames2);
if (numBytes < 0) {
return static_cast<int32_t>(Result::ErrorOutOfRange);
}
memcpy(destination, source, static_cast<size_t>(numBytes));
} else {
// just read in one shot
int32_t numBytes = convertFramesToBytes(framesToRead);
if (numBytes < 0) {
return static_cast<int32_t>(Result::ErrorOutOfRange);
}
memcpy(destination, source, static_cast<size_t>(numBytes));
}
mFifo->advanceReadIndex(framesToRead);
return framesToRead;
}
int32_t FifoBuffer::write(const void *buffer, int32_t numFrames) {
if (numFrames <= 0) {
return 0;
}
// Guaranteed positive.
uint32_t framesToWrite = static_cast<uint32_t>(numFrames);
uint32_t framesAvailable = mFifo->getEmptyFramesAvailable();
framesToWrite = std::min(framesToWrite, framesAvailable);
uint32_t writeIndex = mFifo->getWriteIndex();
int byteIndex = convertFramesToBytes(writeIndex);
const uint8_t *source = reinterpret_cast<const uint8_t *>(buffer);
uint8_t *destination = &mStorage[byteIndex];
if ((writeIndex + framesToWrite) > mFifo->getFrameCapacity()) {
// write in two parts, first part here
int32_t frames1 = static_cast<uint32_t>(mFifo->getFrameCapacity() - writeIndex);
int32_t numBytes = convertFramesToBytes(frames1);
if (numBytes < 0) {
return static_cast<int32_t>(Result::ErrorOutOfRange);
}
memcpy(destination, source, static_cast<size_t>(numBytes));
// read second part
source += convertFramesToBytes(frames1);
destination = &mStorage[0];
int frames2 = static_cast<uint32_t>(framesToWrite - frames1);
numBytes = convertFramesToBytes(frames2);
if (numBytes < 0) {
return static_cast<int32_t>(Result::ErrorOutOfRange);
}
memcpy(destination, source, static_cast<size_t>(numBytes));
} else {
// just write in one shot
int32_t numBytes = convertFramesToBytes(framesToWrite);
if (numBytes < 0) {
return static_cast<int32_t>(Result::ErrorOutOfRange);
}
memcpy(destination, source, static_cast<size_t>(numBytes));
}
mFifo->advanceWriteIndex(framesToWrite);
return framesToWrite;
}
int32_t FifoBuffer::readNow(void *buffer, int32_t numFrames) {
int32_t framesRead = read(buffer, numFrames);
if (framesRead < 0) {
return framesRead;
}
int32_t framesLeft = numFrames - framesRead;
mFramesReadCount += framesRead;
mFramesUnderrunCount += framesLeft;
// Zero out any samples we could not set.
if (framesLeft > 0) {
uint8_t *destination = reinterpret_cast<uint8_t *>(buffer);
destination += convertFramesToBytes(framesRead); // point to first byte not set
int32_t bytesToZero = convertFramesToBytes(framesLeft);
memset(destination, 0, static_cast<size_t>(bytesToZero));
}
return framesRead;
}
uint32_t FifoBuffer::getBufferCapacityInFrames() const {
return mFifo->getFrameCapacity();
}
} // namespace oboe

View File

@ -0,0 +1,99 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef OBOE_FIFOPROCESSOR_H
#define OBOE_FIFOPROCESSOR_H
#include <memory>
#include <stdint.h>
#include "oboe/Definitions.h"
#include "FifoControllerBase.h"
namespace oboe {
class FifoBuffer {
public:
FifoBuffer(uint32_t bytesPerFrame, uint32_t capacityInFrames);
FifoBuffer(uint32_t bytesPerFrame,
uint32_t capacityInFrames,
std::atomic<uint64_t> *readCounterAddress,
std::atomic<uint64_t> *writeCounterAddress,
uint8_t *dataStorageAddress);
~FifoBuffer();
int32_t convertFramesToBytes(int32_t frames);
/**
* Read framesToRead or, if not enough, then read as many as are available.
* @param destination
* @param framesToRead number of frames requested
* @return number of frames actually read
*/
int32_t read(void *destination, int32_t framesToRead);
int32_t write(const void *source, int32_t framesToWrite);
uint32_t getBufferCapacityInFrames() const;
/**
* Calls read(). If all of the frames cannot be read then the remainder of the buffer
* is set to zero.
*
* @param destination
* @param framesToRead number of frames requested
* @return number of frames actually read
*/
int32_t readNow(void *destination, int32_t numFrames);
uint32_t getFullFramesAvailable() {
return mFifo->getFullFramesAvailable();
}
uint32_t getBytesPerFrame() const {
return mBytesPerFrame;
}
uint64_t getReadCounter() const {
return mFifo->getReadCounter();
}
void setReadCounter(uint64_t n) {
mFifo->setReadCounter(n);
}
uint64_t getWriteCounter() {
return mFifo->getWriteCounter();
}
void setWriteCounter(uint64_t n) {
mFifo->setWriteCounter(n);
}
private:
uint32_t mBytesPerFrame;
uint8_t* mStorage;
bool mStorageOwned; // did this object allocate the storage?
std::unique_ptr<FifoControllerBase> mFifo;
uint64_t mFramesReadCount;
uint64_t mFramesUnderrunCount;
};
} // namespace oboe
#endif //OBOE_FIFOPROCESSOR_H

View File

@ -0,0 +1,31 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
#include "FifoControllerBase.h"
#include "FifoController.h"
namespace oboe {
FifoController::FifoController(uint32_t numFrames)
: FifoControllerBase(numFrames)
{
setReadCounter(0);
setWriteCounter(0);
}
} // namespace oboe

View File

@ -0,0 +1,62 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef NATIVEOBOE_FIFOCONTROLLER_H
#define NATIVEOBOE_FIFOCONTROLLER_H
#include <atomic>
#include <stdint.h>
#include "FifoControllerBase.h"
namespace oboe {
/**
* A FifoControllerBase with counters contained in the class.
*/
class FifoController : public FifoControllerBase
{
public:
FifoController(uint32_t bufferSize);
virtual ~FifoController() = default;
virtual uint64_t getReadCounter() const override {
return mReadCounter.load(std::memory_order_acquire);
}
virtual void setReadCounter(uint64_t n) override {
mReadCounter.store(n, std::memory_order_release);
}
virtual void incrementReadCounter(uint64_t n) override {
mReadCounter.fetch_add(n, std::memory_order_acq_rel);
}
virtual uint64_t getWriteCounter() const override {
return mWriteCounter.load(std::memory_order_acquire);
}
virtual void setWriteCounter(uint64_t n) override {
mWriteCounter.store(n, std::memory_order_release);
}
virtual void incrementWriteCounter(uint64_t n) override {
mWriteCounter.fetch_add(n, std::memory_order_acq_rel);
}
private:
std::atomic<uint64_t> mReadCounter{};
std::atomic<uint64_t> mWriteCounter{};
};
} // namespace oboe
#endif //NATIVEOBOE_FIFOCONTROLLER_H

View File

@ -0,0 +1,68 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <cassert>
#include <stdint.h>
#include "FifoControllerBase.h"
namespace oboe {
FifoControllerBase::FifoControllerBase(uint32_t capacityInFrames)
: mTotalFrames(capacityInFrames)
{
// Avoid ridiculously large buffers and the arithmetic wraparound issues that can follow.
assert(capacityInFrames <= (UINT32_MAX / 4));
}
uint32_t FifoControllerBase::getFullFramesAvailable() const {
uint64_t writeCounter = getWriteCounter();
uint64_t readCounter = getReadCounter();
if (readCounter > writeCounter) {
return 0;
}
uint64_t delta = writeCounter - readCounter;
if (delta >= mTotalFrames) {
return mTotalFrames;
}
// delta is now guaranteed to fit within the range of a uint32_t
return static_cast<uint32_t>(delta);
}
uint32_t FifoControllerBase::getReadIndex() const {
// % works with non-power of two sizes
return static_cast<uint32_t>(getReadCounter() % mTotalFrames);
}
void FifoControllerBase::advanceReadIndex(uint32_t numFrames) {
incrementReadCounter(numFrames);
}
uint32_t FifoControllerBase::getEmptyFramesAvailable() const {
return static_cast<uint32_t>(mTotalFrames - getFullFramesAvailable());
}
uint32_t FifoControllerBase::getWriteIndex() const {
// % works with non-power of two sizes
return static_cast<uint32_t>(getWriteCounter() % mTotalFrames);
}
void FifoControllerBase::advanceWriteIndex(uint32_t numFrames) {
incrementWriteCounter(numFrames);
}
} // namespace oboe

View File

@ -0,0 +1,92 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef NATIVEOBOE_FIFOCONTROLLERBASE_H
#define NATIVEOBOE_FIFOCONTROLLERBASE_H
#include <stdint.h>
namespace oboe {
/**
* Manage the read/write indices of a circular buffer.
*
* The caller is responsible for reading and writing the actual data.
* Note that the span of available frames may not be contiguous. They
* may wrap around from the end to the beginning of the buffer. In that
* case the data must be read or written in at least two blocks of frames.
*
*/
class FifoControllerBase {
public:
/**
* @param totalFrames capacity of the circular buffer in frames.
*/
FifoControllerBase(uint32_t totalFrames);
virtual ~FifoControllerBase() = default;
/**
* The frames available to read will be calculated from the read and write counters.
* The result will be clipped to the capacity of the buffer.
* If the buffer has underflowed then this will return zero.
* @return number of valid frames available to read.
*/
uint32_t getFullFramesAvailable() const;
/**
* The index in a circular buffer of the next frame to read.
*/
uint32_t getReadIndex() const;
/**
* @param numFrames number of frames to advance the read index
*/
void advanceReadIndex(uint32_t numFrames);
/**
* @return maximum number of frames that can be written without exceeding the threshold.
*/
uint32_t getEmptyFramesAvailable() const;
/**
* The index in a circular buffer of the next frame to write.
*/
uint32_t getWriteIndex() const;
/**
* @param numFrames number of frames to advance the write index
*/
void advanceWriteIndex(uint32_t numFrames);
uint32_t getFrameCapacity() const { return mTotalFrames; }
virtual uint64_t getReadCounter() const = 0;
virtual void setReadCounter(uint64_t n) = 0;
virtual void incrementReadCounter(uint64_t n) = 0;
virtual uint64_t getWriteCounter() const = 0;
virtual void setWriteCounter(uint64_t n) = 0;
virtual void incrementWriteCounter(uint64_t n) = 0;
private:
uint32_t mTotalFrames;
};
} // namespace oboe
#endif //NATIVEOBOE_FIFOCONTROLLERBASE_H

View File

@ -0,0 +1,32 @@
/*
* Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdint.h>
#include "FifoControllerIndirect.h"
namespace oboe {
FifoControllerIndirect::FifoControllerIndirect(uint32_t numFrames,
std::atomic<uint64_t> *readCounterAddress,
std::atomic<uint64_t> *writeCounterAddress)
: FifoControllerBase(numFrames)
, mReadCounterAddress(readCounterAddress)
, mWriteCounterAddress(writeCounterAddress)
{
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef NATIVEOBOE_FIFOCONTROLLERINDIRECT_H
#define NATIVEOBOE_FIFOCONTROLLERINDIRECT_H
#include <atomic>
#include <stdint.h>
#include "FifoControllerBase.h"
namespace oboe {
/**
* A FifoControllerBase with counters external to the class.
*/
class FifoControllerIndirect : public FifoControllerBase {
public:
FifoControllerIndirect(uint32_t bufferSize,
std::atomic<uint64_t> *readCounterAddress,
std::atomic<uint64_t> *writeCounterAddress);
virtual ~FifoControllerIndirect() = default;
virtual uint64_t getReadCounter() const override {
return mReadCounterAddress->load(std::memory_order_acquire);
}
virtual void setReadCounter(uint64_t n) override {
mReadCounterAddress->store(n, std::memory_order_release);
}
virtual void incrementReadCounter(uint64_t n) override {
mReadCounterAddress->fetch_add(n, std::memory_order_acq_rel);
}
virtual uint64_t getWriteCounter() const override {
return mWriteCounterAddress->load(std::memory_order_acquire);
}
virtual void setWriteCounter(uint64_t n) override {
mWriteCounterAddress->store(n, std::memory_order_release);
}
virtual void incrementWriteCounter(uint64_t n) override {
mWriteCounterAddress->fetch_add(n, std::memory_order_acq_rel);
}
private:
std::atomic<uint64_t> *mReadCounterAddress;
std::atomic<uint64_t> *mWriteCounterAddress;
};
} // namespace oboe
#endif //NATIVEOBOE_FIFOCONTROLLERINDIRECT_H

View File

@ -0,0 +1,52 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <unistd.h>
#include "FlowGraphNode.h"
#include "ChannelCountConverter.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
ChannelCountConverter::ChannelCountConverter(
int32_t inputChannelCount,
int32_t outputChannelCount)
: input(*this, inputChannelCount)
, output(*this, outputChannelCount) {
}
ChannelCountConverter::~ChannelCountConverter() { }
int32_t ChannelCountConverter::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t inputChannelCount = input.getSamplesPerFrame();
int32_t outputChannelCount = output.getSamplesPerFrame();
for (int i = 0; i < numFrames; i++) {
int inputChannel = 0;
for (int outputChannel = 0; outputChannel < outputChannelCount; outputChannel++) {
// Copy input channels to output channels.
// Wrap if we run out of inputs.
// Discard if we run out of outputs.
outputBuffer[outputChannel] = inputBuffer[inputChannel];
inputChannel = (inputChannel == inputChannelCount)
? 0 : inputChannel + 1;
}
inputBuffer += inputChannelCount;
outputBuffer += outputChannelCount;
}
return numFrames;
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FLOWGRAPH_CHANNEL_COUNT_CONVERTER_H
#define FLOWGRAPH_CHANNEL_COUNT_CONVERTER_H
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
/**
* Change the number of number of channels without mixing.
* When increasing the channel count, duplicate input channels.
* When decreasing the channel count, drop input channels.
*/
class ChannelCountConverter : public FlowGraphNode {
public:
explicit ChannelCountConverter(
int32_t inputChannelCount,
int32_t outputChannelCount);
virtual ~ChannelCountConverter();
int32_t onProcess(int32_t numFrames) override;
const char *getName() override {
return "ChannelCountConverter";
}
FlowGraphPortFloatInput input;
FlowGraphPortFloatOutput output;
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_CHANNEL_COUNT_CONVERTER_H

View File

@ -0,0 +1,38 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <algorithm>
#include <unistd.h>
#include "FlowGraphNode.h"
#include "ClipToRange.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
ClipToRange::ClipToRange(int32_t channelCount)
: FlowGraphFilter(channelCount) {
}
int32_t ClipToRange::onProcess(int32_t numFrames) {
const float *inputBuffer = input.getBuffer();
float *outputBuffer = output.getBuffer();
int32_t numSamples = numFrames * output.getSamplesPerFrame();
for (int32_t i = 0; i < numSamples; i++) {
*outputBuffer++ = std::min(mMaximum, std::max(mMinimum, *inputBuffer++));
}
return numFrames;
}

View File

@ -0,0 +1,70 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FLOWGRAPH_CLIP_TO_RANGE_H
#define FLOWGRAPH_CLIP_TO_RANGE_H
#include <atomic>
#include <unistd.h>
#include <sys/types.h>
#include "FlowGraphNode.h"
namespace FLOWGRAPH_OUTER_NAMESPACE {
namespace flowgraph {
// This is 3 dB, (10^(3/20)), to match the maximum headroom in AudioTrack for float data.
// It is designed to allow occasional transient peaks.
constexpr float kDefaultMaxHeadroom = 1.41253754f;
constexpr float kDefaultMinHeadroom = -kDefaultMaxHeadroom;
class ClipToRange : public FlowGraphFilter {
public:
explicit ClipToRange(int32_t channelCount);
virtual ~ClipToRange() = default;
int32_t onProcess(int32_t numFrames) override;
void setMinimum(float min) {
mMinimum = min;
}
float getMinimum() const {
return mMinimum;
}
void setMaximum(float min) {
mMaximum = min;
}
float getMaximum() const {
return mMaximum;
}
const char *getName() override {
return "ClipToRange";
}
private:
float mMinimum = kDefaultMinHeadroom;
float mMaximum = kDefaultMaxHeadroom;
};
} /* namespace flowgraph */
} /* namespace FLOWGRAPH_OUTER_NAMESPACE */
#endif //FLOWGRAPH_CLIP_TO_RANGE_H

View File

@ -0,0 +1,114 @@
/*
* Copyright 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "stdio.h"
#include <algorithm>
#include <sys/types.h>
#include "FlowGraphNode.h"
using namespace FLOWGRAPH_OUTER_NAMESPACE::flowgraph;
/***************************************************************************/
int32_t FlowGraphNode::pullData(int32_t numFrames, int64_t callCount) {
int32_t frameCount = numFrames;
// Prevent recursion and multiple execution of nodes.
if (callCount > mLastCallCount) {
mLastCallCount = callCount;
if (mDataPulledAutomatically) {
// Pull from all the upstream nodes.
for (auto &port : mInputPorts) {
// TODO fix bug of leaving unused data in some ports if using multiple AudioSource
frameCount = port.get().pullData(callCount, frameCount);
}
}
if (frameCount > 0) {
frameCount = onProcess(frameCount);
}
mLastFrameCount = frameCount;
} else {
frameCount = mLastFrameCount;
}
return frameCount;
}
void FlowGraphNode::pullReset() {
if (!mBlockRecursion) {
mBlockRecursion = true; // for cyclic graphs
// Pull reset from all the upstream nodes.
for (auto &port : mInputPorts) {
port.get().pullReset();
}
mBlockRecursion = false;
reset();
}
}
void FlowGraphNode::reset() {
mLastFrameCount = 0;
mLastCallCount = kInitialCallCount;
}
/***************************************************************************/
FlowGraphPortFloat::FlowGraphPortFloat(FlowGraphNode &parent,
int32_t samplesPerFrame,
int32_t framesPerBuffer)
: FlowGraphPort(parent, samplesPerFrame)
, mFramesPerBuffer(framesPerBuffer)
, mBuffer(nullptr) {
size_t numFloats = static_cast<size_t>(framesPerBuffer * getSamplesPerFrame());
mBuffer = std::make_unique<float[]>(numFloats);
}
/***************************************************************************/
int32_t FlowGraphPortFloatOutput::pullData(int64_t callCount, int32_t numFrames) {
numFrames = std::min(getFramesPerBuffer(), numFrames);
return mContainingNode.pullData(numFrames, callCount);
}
void FlowGraphPortFloatOutput::pullReset() {
mContainingNode.pullReset();
}
// These need to be in the .cpp file because of forward cross references.
void FlowGraphPortFloatOutput::connect(FlowGraphPortFloatInput *port) {
port->connect(this);
}
void FlowGraphPortFloatOutput::disconnect(FlowGraphPortFloatInput *port) {
port->disconnect(this);
}
/***************************************************************************/
int32_t FlowGraphPortFloatInput::pullData(int64_t callCount, int32_t numFrames) {
return (mConnected == nullptr)
? std::min(getFramesPerBuffer(), numFrames)
: mConnected->pullData(callCount, numFrames);
}
void FlowGraphPortFloatInput::pullReset() {
if (mConnected != nullptr) mConnected->pullReset();
}
float *FlowGraphPortFloatInput::getBuffer() {
if (mConnected == nullptr) {
return FlowGraphPortFloat::getBuffer(); // loaded using setValue()
} else {
return mConnected->getBuffer();
}
}
int32_t FlowGraphSink::pullData(int32_t numFrames) {
return FlowGraphNode::pullData(numFrames, getLastCallCount() + 1);
}

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