Merge remote-tracking branch 'origin/stable'
This commit is contained in:
@@ -25,7 +25,6 @@
|
||||
|
||||
namespace Audio
|
||||
{
|
||||
|
||||
class AndroidEnumerator: public Enumerator
|
||||
{
|
||||
public:
|
||||
|
||||
245
src/engine/audio/Audio_AndroidOboe.cpp
Normal file
245
src/engine/audio/Audio_AndroidOboe.cpp
Normal file
@@ -0,0 +1,245 @@
|
||||
#include "Audio_AndroidOboe.h"
|
||||
#include "../helper/HL_Sync.h"
|
||||
#include "../helper/HL_Log.h"
|
||||
#include <mutex>
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "../helper/HL_String.h"
|
||||
#include "../helper/HL_Time.h"
|
||||
|
||||
#ifdef TARGET_ANDROID
|
||||
|
||||
#define LOG_SUBSYSTEM "Audio"
|
||||
|
||||
using namespace Audio;
|
||||
|
||||
|
||||
// -------------------- AndroidEnumerator -----------------------------
|
||||
|
||||
AndroidEnumerator::AndroidEnumerator()
|
||||
{}
|
||||
|
||||
AndroidEnumerator::~AndroidEnumerator()
|
||||
{}
|
||||
|
||||
int AndroidEnumerator::indexOfDefaultDevice()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int AndroidEnumerator::count()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
int AndroidEnumerator::idAt(int index)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string AndroidEnumerator::nameAt(int index)
|
||||
{
|
||||
return "Audio";
|
||||
}
|
||||
|
||||
void AndroidEnumerator::open(int direction)
|
||||
{}
|
||||
|
||||
void AndroidEnumerator::close()
|
||||
{}
|
||||
|
||||
// --------------- Input implementation ----------------
|
||||
AndroidInputDevice::AndroidInputDevice(int devId)
|
||||
{}
|
||||
|
||||
AndroidInputDevice::~AndroidInputDevice()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
bool AndroidInputDevice::open()
|
||||
{
|
||||
if (active())
|
||||
return true;
|
||||
|
||||
oboe::AudioStreamBuilder builder;
|
||||
builder.setDirection(oboe::Direction::Input);
|
||||
builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
|
||||
builder.setSharingMode(oboe::SharingMode::Exclusive);
|
||||
builder.setFormat(oboe::AudioFormat::I16);
|
||||
builder.setChannelCount(oboe::ChannelCount::Mono);
|
||||
builder.setCallback(this);
|
||||
oboe::Result rescode = builder.openStream(&mRecordingStream);
|
||||
if (rescode != oboe::Result::OK)
|
||||
return false;
|
||||
|
||||
mDeviceRate = mRecordingStream->getSampleRate();
|
||||
ICELogInfo(<< "Input Opened with rate " << mDeviceRate);
|
||||
mActive = true;
|
||||
|
||||
rescode = mRecordingStream->requestStart();
|
||||
if (rescode != oboe::Result::OK)
|
||||
{
|
||||
close();
|
||||
mActive = false;
|
||||
}
|
||||
return mActive;
|
||||
}
|
||||
|
||||
void AndroidInputDevice::close()
|
||||
{
|
||||
// There is no check for active() value because close() can be called to cleanup after bad open() call.
|
||||
if (mRecordingStream != nullptr)
|
||||
{
|
||||
mRecordingStream->close();
|
||||
delete mRecordingStream; mRecordingStream = nullptr;
|
||||
}
|
||||
mActive = false;
|
||||
}
|
||||
|
||||
oboe::DataCallbackResult
|
||||
AndroidInputDevice::onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames)
|
||||
{
|
||||
std::unique_lock<std::mutex> l(mMutex);
|
||||
|
||||
// Send data to AudioPair
|
||||
if (mConnection)
|
||||
mConnection->onMicData(getFormat(), audioData, numFrames);
|
||||
|
||||
return oboe::DataCallbackResult::Continue;
|
||||
}
|
||||
|
||||
Format AndroidInputDevice::getFormat()
|
||||
{
|
||||
return Format(mDeviceRate, 1);
|
||||
}
|
||||
|
||||
bool AndroidInputDevice::active() const
|
||||
{
|
||||
return mActive;
|
||||
}
|
||||
|
||||
bool AndroidInputDevice::fakeMode()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void AndroidInputDevice::setFakeMode(bool fakemode)
|
||||
{}
|
||||
|
||||
int AndroidInputDevice::readBuffer(void* buffer)
|
||||
{
|
||||
throw std::runtime_error("AndroidInputDevice::readBuffer() is not implemented.");
|
||||
}
|
||||
|
||||
// ------------ AndroidOutputDevice -----------------
|
||||
AndroidOutputDevice::AndroidOutputDevice(int devId)
|
||||
{
|
||||
ICELogDebug(<< "Creating AndroidOutputDevice. This is: " << StringHelper::toHex(this));
|
||||
}
|
||||
AndroidOutputDevice::~AndroidOutputDevice()
|
||||
{
|
||||
ICELogDebug(<< "Deleting AndroidOutputDevice.");
|
||||
close();
|
||||
}
|
||||
|
||||
bool AndroidOutputDevice::open()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(mMutex);
|
||||
|
||||
if (mActive)
|
||||
return true;
|
||||
|
||||
mRequestedFrames = 0;
|
||||
mStartTime = 0.0;
|
||||
mEndTime = 0.0;
|
||||
|
||||
oboe::AudioStreamBuilder builder;
|
||||
builder.setDirection(oboe::Direction::Output);
|
||||
builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
|
||||
builder.setSharingMode(oboe::SharingMode::Exclusive);
|
||||
builder.setFormat(oboe::AudioFormat::I16);
|
||||
builder.setChannelCount(oboe::ChannelCount::Mono);
|
||||
// builder.setDataCallback(this);
|
||||
builder.setCallback(this);
|
||||
//builder.setErrorCallback(this)
|
||||
|
||||
oboe::Result rescode = builder.openStream(&mPlayingStream);
|
||||
if (rescode != oboe::Result::OK)
|
||||
return false;
|
||||
|
||||
mDeviceRate = mPlayingStream->getSampleRate();
|
||||
ICELogInfo(<< "Input Opened with rate " << mDeviceRate);
|
||||
mActive = true;
|
||||
|
||||
rescode = mPlayingStream->requestStart();
|
||||
if (rescode != oboe::Result::OK)
|
||||
{
|
||||
close();
|
||||
mActive = false;
|
||||
}
|
||||
return mActive;
|
||||
}
|
||||
|
||||
void AndroidOutputDevice::close()
|
||||
{
|
||||
std::unique_lock<std::mutex> l(mMutex);
|
||||
if (!mActive)
|
||||
return;
|
||||
|
||||
if (mPlayingStream != nullptr)
|
||||
{
|
||||
mPlayingStream->close();
|
||||
delete mPlayingStream; mPlayingStream = nullptr;
|
||||
}
|
||||
mEndTime = now_ms();
|
||||
mActive = false;
|
||||
|
||||
ICELogInfo(<< "For time " << mEndTime - mStartTime << " ms was requested "
|
||||
<< float(mRequestedFrames) / getFormat().mRate * 1000 << " ms");
|
||||
}
|
||||
|
||||
Format AndroidOutputDevice::getFormat()
|
||||
{
|
||||
return {mDeviceRate, 1};
|
||||
}
|
||||
|
||||
bool AndroidOutputDevice::fakeMode()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void AndroidOutputDevice::setFakeMode(bool /*fakemode*/)
|
||||
{
|
||||
}
|
||||
|
||||
oboe::DataCallbackResult AndroidOutputDevice::onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames)
|
||||
{
|
||||
if (mInShutdown)
|
||||
return oboe::DataCallbackResult::Stop;
|
||||
|
||||
if (mStartTime == 0.0)
|
||||
mStartTime = now_ms();
|
||||
|
||||
// Ask producer about data
|
||||
memset(audioData, 0, numFrames * 2);
|
||||
if (mConnection)
|
||||
{
|
||||
Format f = getFormat();
|
||||
if (f.mRate != 0)
|
||||
mConnection->onSpkData(f, audioData, numFrames * 2);
|
||||
}
|
||||
mRequestedFrames += numFrames;
|
||||
|
||||
return oboe::DataCallbackResult::Continue;
|
||||
}
|
||||
|
||||
// TODO - special case https://github.com/google/oboe/blob/master/docs/notes/disconnect.md
|
||||
void AndroidOutputDevice::onErrorAfterClose(oboe::AudioStream *stream, oboe::Result result) {
|
||||
if (result == oboe::Result::ErrorDisconnected) {
|
||||
// LOGI("Restarting AudioStream after disconnect");
|
||||
// soundEngine.restart(); // please check oboe samples for soundEngine.restart(); call
|
||||
}
|
||||
}
|
||||
#endif // TARGET_ANDROID
|
||||
109
src/engine/audio/Audio_AndroidOboe.h
Normal file
109
src/engine/audio/Audio_AndroidOboe.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/* Copyright(C) 2007-2017 VoIP objects (voipobjects.com)
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
|
||||
#ifndef __AUDIO_ANDROID_OBOE_H
|
||||
#define __AUDIO_ANDROID_OBOE_H
|
||||
|
||||
#ifdef TARGET_ANDROID
|
||||
|
||||
#include "Audio_Interface.h"
|
||||
#include "Audio_Helper.h"
|
||||
#include "Audio_Resampler.h"
|
||||
#include "Audio_DataWindow.h"
|
||||
#include "../helper/HL_Pointer.h"
|
||||
#include "../helper/HL_ByteBuffer.h"
|
||||
#include "../helper/HL_Exception.h"
|
||||
#include "../helper/HL_Statistics.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "oboe/Oboe.h"
|
||||
|
||||
namespace Audio
|
||||
{
|
||||
class AndroidEnumerator: public Enumerator
|
||||
{
|
||||
public:
|
||||
AndroidEnumerator();
|
||||
~AndroidEnumerator();
|
||||
|
||||
void open(int direction);
|
||||
void close();
|
||||
|
||||
int count();
|
||||
std::string nameAt(int index);
|
||||
int idAt(int index);
|
||||
int indexOfDefaultDevice();
|
||||
|
||||
protected:
|
||||
};
|
||||
|
||||
class AndroidInputDevice: public InputDevice, public oboe::AudioStreamCallback
|
||||
{
|
||||
public:
|
||||
AndroidInputDevice(int devId);
|
||||
~AndroidInputDevice();
|
||||
|
||||
bool open();
|
||||
void close();
|
||||
Format getFormat();
|
||||
|
||||
bool fakeMode();
|
||||
void setFakeMode(bool fakemode);
|
||||
int readBuffer(void* buffer);
|
||||
bool active() const;
|
||||
|
||||
oboe::DataCallbackResult
|
||||
onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames);
|
||||
|
||||
|
||||
protected:
|
||||
bool mActive = false;
|
||||
oboe::AudioStream* mRecordingStream = nullptr;
|
||||
PResampler mResampler;
|
||||
DataWindow mDeviceRateCache, mSdkRateCache;
|
||||
int mDeviceRate; // Actual rate of opened recorder
|
||||
int mBufferSize; // Size of buffer used for recording (at native sample rate)
|
||||
DataWindow mRecorderBuffer;
|
||||
std::condition_variable mDataCondVar;
|
||||
int mRecorderBufferIndex;
|
||||
std::mutex mMutex;
|
||||
};
|
||||
|
||||
class AndroidOutputDevice: public OutputDevice, public oboe::AudioStreamCallback
|
||||
{
|
||||
public:
|
||||
AndroidOutputDevice(int devId);
|
||||
~AndroidOutputDevice();
|
||||
|
||||
bool open();
|
||||
void close();
|
||||
Format getFormat();
|
||||
|
||||
bool fakeMode();
|
||||
void setFakeMode(bool fakemode);
|
||||
|
||||
oboe::DataCallbackResult onAudioReady(oboe::AudioStream *audioStream, void *audioData, int32_t numFrames);
|
||||
void onErrorAfterClose(oboe::AudioStream *stream, oboe::Result result);
|
||||
|
||||
protected:
|
||||
std::mutex mMutex;
|
||||
int mDeviceRate = 0;
|
||||
oboe::AudioStream* mPlayingStream = nullptr;
|
||||
DataWindow mPlayBuffer;
|
||||
int mBufferIndex = 0, mBufferSize = 0;
|
||||
bool mInShutdown = false;
|
||||
bool mActive = false;
|
||||
|
||||
// Statistics
|
||||
float mRequestedFrames = 0.0, mStartTime = 0.0, mEndTime = 0.0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif // TARGET_ANDROID
|
||||
|
||||
#endif // __AUDIO_ANDROID_H
|
||||
@@ -56,17 +56,25 @@ void DataWindow::add(const void* data, int length)
|
||||
|
||||
if (length > mCapacity)
|
||||
{
|
||||
// Use latest bytes from data buffer in this case.
|
||||
data = (char*)data + length - mCapacity;
|
||||
length = mCapacity;
|
||||
}
|
||||
|
||||
// Check how much free space we have
|
||||
int avail = mCapacity - mFilled;
|
||||
|
||||
|
||||
if (avail < length)
|
||||
{
|
||||
memmove(mData, mData + length - avail, mFilled - (length - avail));
|
||||
mFilled -= length - avail;
|
||||
// Find the portion of data to move & save
|
||||
int delta = length - avail;
|
||||
|
||||
// Move the data
|
||||
if (mFilled - delta > 0)
|
||||
memmove(mData, mData + delta, mFilled - delta);
|
||||
mFilled -= delta;
|
||||
}
|
||||
|
||||
memcpy(mData + mFilled, data, length);
|
||||
mFilled += length;
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ void DevicePair::onSpkData(const Format& f, void* buffer, int length)
|
||||
|
||||
// Resample these 10 milliseconds it to native format
|
||||
size_t wasProcessed = 0;
|
||||
size_t wasProduced = mSpkResampler.resample(AUDIO_SAMPLERATE, mOutput10msBuffer.data(), mOutput10msBuffer.capacity(), wasProcessed, f.mRate,
|
||||
size_t wasProduced = mSpkResampler.resample(nativeFormat.mRate, mOutput10msBuffer.data(), mOutput10msBuffer.capacity(), wasProcessed, f.mRate,
|
||||
mOutputNativeData.mutableData() + mOutputNativeData.filled(), mOutputNativeData.capacity() - mOutputNativeData.filled());
|
||||
mOutputNativeData.setFilled(mOutputNativeData.filled() + wasProduced);
|
||||
#ifdef CONSOLE_LOGGING
|
||||
@@ -206,7 +206,7 @@ void DevicePair::onSpkData(const Format& f, void* buffer, int length)
|
||||
}
|
||||
}
|
||||
|
||||
assert(mOutputNativeData.filled() >= length);
|
||||
// assert(mOutputNativeData.filled() >= length);
|
||||
#ifdef DUMP_NATIVEOUTPUT
|
||||
if (mNativeOutputDump)
|
||||
mNativeOutputDump->write(mOutputNativeData.data(), length);
|
||||
|
||||
@@ -146,7 +146,8 @@ OsEngine* OsEngine::instance()
|
||||
#endif
|
||||
|
||||
#ifdef TARGET_ANDROID
|
||||
return &OpenSLEngine::instance();
|
||||
return nullptr; // As we use Oboe library for now
|
||||
//return &OpenSLEngine::instance();
|
||||
#endif
|
||||
|
||||
return nullptr;
|
||||
|
||||
@@ -216,48 +216,52 @@ void Mixer::mix()
|
||||
channelList[activeCounter++] = &mChannelList[i];
|
||||
|
||||
// No active channels - nothing to mix - exit
|
||||
if (!activeCounter)
|
||||
if (!activeCounter)
|
||||
{
|
||||
//ICELogDebug(<< "No active channel");
|
||||
// ICELogDebug(<< "No active channel");
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimized versions for 1& 2 active channels
|
||||
if (activeCounter == 1)
|
||||
{
|
||||
// Copy much samples as we have
|
||||
{
|
||||
// Copy much samples as we have
|
||||
Stream& audio = *channelList[0];
|
||||
mOutput.add(audio.data().data(), audio.data().filled());
|
||||
audio.data().erase(audio.data().filled());
|
||||
|
||||
// Copy the decoded data
|
||||
mOutput.add(audio.data().data(), audio.data().filled());
|
||||
|
||||
// Erase copied audio samples
|
||||
audio.data().erase(audio.data().filled());
|
||||
//ICELogSpecial(<<"Length of mixer stream " << audio.data().filled());
|
||||
}
|
||||
}
|
||||
else
|
||||
if (activeCounter == 2)
|
||||
{
|
||||
if (activeCounter == 2)
|
||||
{
|
||||
Stream& audio1 = *channelList[0];
|
||||
Stream& audio2 = *channelList[1];
|
||||
int filled1 = audio1.data().filled() / 2, filled2 = audio2.data().filled() / 2;
|
||||
Stream& audio2 = *channelList[1];
|
||||
int filled1 = audio1.data().filled() / 2, filled2 = audio2.data().filled() / 2;
|
||||
int available = filled1 > filled2 ? filled1 : filled2;
|
||||
|
||||
// Find how much samples can be mixed
|
||||
int filled = mOutput.filled() / 2;
|
||||
// Find how much samples can be mixed
|
||||
int filled = mOutput.filled() / 2;
|
||||
|
||||
int maxsize = mOutput.capacity() / 2;
|
||||
if (maxsize - filled < available)
|
||||
available = maxsize - filled;
|
||||
if (maxsize - filled < available)
|
||||
available = maxsize - filled;
|
||||
|
||||
short sample = 0;
|
||||
for (int i=0; i<available; i++)
|
||||
{
|
||||
short sample = 0;
|
||||
for (int i=0; i<available; i++)
|
||||
{
|
||||
short sample1 = filled1 > i ? audio1.data().shortAt(i) : 0;
|
||||
short sample2 = filled2 > i ? audio2.data().shortAt(i) : 0;
|
||||
sample = (abs(sample1) > abs(sample2)) ? sample1 : sample2;
|
||||
|
||||
mOutput.add(sample);
|
||||
}
|
||||
audio1.data().erase(available*2);
|
||||
audio2.data().erase(available*2);
|
||||
}
|
||||
audio1.data().erase(available*2);
|
||||
audio2.data().erase(available*2);
|
||||
}
|
||||
else
|
||||
{
|
||||
do
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
/* Copyright(C) 2007-2014 VoIP objects (voipobjects.com)
|
||||
/* Copyright(C) 2007-2021 VoIP objects (voipobjects.com)
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "Audio_Player.h"
|
||||
|
||||
#include "../helper/HL_Log.h"
|
||||
|
||||
#define LOG_SUBSYSTEM "Player"
|
||||
|
||||
using namespace Audio;
|
||||
// -------------- Player -----------
|
||||
Player::Player()
|
||||
:mDelegate(NULL), mPlayedTime(0)
|
||||
:mDelegate(nullptr), mPlayedTime(0)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -47,7 +51,7 @@ void Player::onMicData(const Format& f, const void* buffer, int length)
|
||||
void Player::onSpkData(const Format& f, void* buffer, int length)
|
||||
{
|
||||
Lock l(mGuard);
|
||||
|
||||
|
||||
// Fill buffer by zero if player owns dedicated device
|
||||
if (mOutput)
|
||||
memset(buffer, 0, length);
|
||||
@@ -99,7 +103,7 @@ void Player::onFilePlayed()
|
||||
void Player::obtain(int usage)
|
||||
{
|
||||
Lock l(mGuard);
|
||||
UsageMap::iterator usageIter = mUsage.find(usage);
|
||||
auto usageIter = mUsage.find(usage);
|
||||
if (usageIter == mUsage.end())
|
||||
mUsage[usage] = 1;
|
||||
else
|
||||
@@ -132,7 +136,7 @@ int Player::releasePlayed()
|
||||
{
|
||||
Lock l(mGuard);
|
||||
int result = mFinishedUsages.size();
|
||||
while (mFinishedUsages.size())
|
||||
while (!mFinishedUsages.empty())
|
||||
{
|
||||
release(mFinishedUsages.front());
|
||||
mFinishedUsages.erase(mFinishedUsages.begin());
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* Copyright(C) 2007-2014 VoIP objects (voipobjects.com)
|
||||
/* Copyright(C) 2007-2021 VoIP objects (voipobjects.com)
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "../helper/HL_Log.h"
|
||||
#include "../helper/HL_Sync.h"
|
||||
#include "../helper/HL_Statistics.h"
|
||||
#include "Audio_Interface.h"
|
||||
#include <deque>
|
||||
#include <map>
|
||||
@@ -48,15 +49,18 @@ namespace Audio
|
||||
void onMicData(const Format& f, const void* buffer, int length);
|
||||
void onSpkData(const Format& f, void* buffer, int length);
|
||||
void onFilePlayed();
|
||||
void scheduleRelease();
|
||||
void obtain(int usageId);
|
||||
|
||||
public:
|
||||
Player();
|
||||
~Player();
|
||||
|
||||
void setDelegate(EndOfAudioDelegate* d);
|
||||
EndOfAudioDelegate* getDelegate() const;
|
||||
|
||||
void setOutput(POutputDevice output);
|
||||
POutputDevice getOutput() const;
|
||||
|
||||
void add(int usageId, PWavFileReader file, bool loop, int timelength);
|
||||
void release(int usageId);
|
||||
void clear();
|
||||
|
||||
Reference in New Issue
Block a user