300 lines
11 KiB
C++
300 lines
11 KiB
C++
/******************************************************************************\
|
|
* Copyright (c) 2004-2020
|
|
*
|
|
* Author(s):
|
|
* Simon Tomlinson, Volker Fischer
|
|
*
|
|
******************************************************************************
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License as published by the Free Software
|
|
* Foundation; either version 2 of the License, or (at your option) any later
|
|
* version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*
|
|
\******************************************************************************/
|
|
|
|
#include "sound.h"
|
|
#include "androiddebug.cpp"
|
|
|
|
/* Implementation *************************************************************/
|
|
|
|
CSound::CSound ( void (*fpNewProcessCallback) ( CVector<short>& psData, void* arg ),
|
|
void* arg,
|
|
const int iCtrlMIDIChannel,
|
|
const bool ,
|
|
const QString& ) :
|
|
CSoundBase ( "OpenSL", true, fpNewProcessCallback, arg, iCtrlMIDIChannel )
|
|
|
|
{
|
|
pSound = this;
|
|
#ifdef ANDROIDDEBUG
|
|
qInstallMessageHandler(myMessageHandler);
|
|
#endif
|
|
}
|
|
|
|
void CSound::setupCommonStreamParams(oboe::AudioStreamBuilder *builder)
|
|
{
|
|
// We request EXCLUSIVE mode since this will give us the lowest possible
|
|
// latency. If EXCLUSIVE mode isn't available the builder will fall back to SHARED mode
|
|
builder->setCallback(this)
|
|
->setFormat(oboe::AudioFormat::Float)
|
|
->setSharingMode(oboe::SharingMode::Shared)
|
|
->setChannelCount(oboe::ChannelCount::Mono)
|
|
// ->setSampleRate(48000)
|
|
// ->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::Medium)
|
|
->setPerformanceMode(oboe::PerformanceMode::None);
|
|
return;
|
|
}
|
|
|
|
void CSound::openStreams()
|
|
{
|
|
// Create callback
|
|
mCallback = this;
|
|
|
|
//Setup output stream
|
|
oboe::AudioStreamBuilder inBuilder, outBuilder;
|
|
outBuilder.setDirection(oboe::Direction::Output);
|
|
setupCommonStreamParams(&outBuilder);
|
|
oboe::Result result = outBuilder.openManagedStream(mPlayStream);
|
|
if (result != oboe::Result::OK) {
|
|
return;
|
|
}
|
|
mPlayStream->setBufferSizeInFrames(pSound->iOpenSLBufferSizeStereo);
|
|
|
|
warnIfNotLowLatency(mPlayStream, "PlayStream");
|
|
printStreamDetails(mPlayStream);
|
|
|
|
//Setup input stream
|
|
inBuilder.setDirection(oboe::Direction::Input);
|
|
setupCommonStreamParams(&inBuilder);
|
|
result = inBuilder.openManagedStream(mRecordingStream);
|
|
if (result != oboe::Result::OK) {
|
|
closeStream(mPlayStream);
|
|
return;
|
|
}
|
|
mRecordingStream->setBufferSizeInFrames(pSound->iOpenSLBufferSizeStereo);
|
|
|
|
warnIfNotLowLatency(mRecordingStream, "RecordStream");
|
|
printStreamDetails(mRecordingStream);
|
|
}
|
|
|
|
void CSound::printStreamDetails(oboe::ManagedStream &stream)
|
|
{
|
|
|
|
QString sDirection = (stream->getDirection()==oboe::Direction::Input?"Input":"Output");
|
|
QString sFramesPerBurst = QString::number(stream->getFramesPerBurst());
|
|
QString sBufferSizeInFrames = QString::number(stream->getBufferSizeInFrames());
|
|
QString sBytesPerFrame = QString::number(stream->getBytesPerFrame());
|
|
QString sBytesPerSample = QString::number(stream->getBytesPerSample());
|
|
QString sBufferCapacityInFrames = QString::number(stream->getBufferCapacityInFrames());
|
|
QString sPerformanceMode = (stream->getPerformanceMode()==oboe::PerformanceMode::LowLatency?"LowLatency":"NotLowLatency");
|
|
QString sSharingMode = (stream->getSharingMode() == oboe::SharingMode::Exclusive?"Exclusive":"Shared");
|
|
QString sDeviceID = QString::number(stream->getDeviceId());
|
|
QString sSampleRate = QString::number(stream->getSampleRate());
|
|
QString sAudioFormat = (stream->getFormat()==oboe::AudioFormat::I16?"I16":"Float");
|
|
|
|
QString sFramesPerCallback = QString::number(stream->getFramesPerCallback());
|
|
//QString sSampleRateConversionQuality = (stream.getSampleRateConversionQuality()==oboe::SampleRateConversionQuality::
|
|
|
|
qInfo() << "Stream details: [sDirection: " << sDirection <<
|
|
", FramesPerBurst: " << sFramesPerBurst <<
|
|
", BufferSizeInFrames: " << sBufferSizeInFrames <<
|
|
", BytesPerFrame: " << sBytesPerFrame <<
|
|
", BytesPerSample: " << sBytesPerSample <<
|
|
", BufferCapacityInFrames: " << sBufferCapacityInFrames <<
|
|
", PerformanceMode: " << sPerformanceMode <<
|
|
", SharingMode: " << sSharingMode <<
|
|
", DeviceID: " << sDeviceID <<
|
|
", SampleRate: " << sSampleRate <<
|
|
", AudioFormat: " << sAudioFormat <<
|
|
", FramesPerCallback: " << sFramesPerCallback << "]";
|
|
|
|
}
|
|
|
|
void CSound::warnIfNotLowLatency(oboe::ManagedStream &stream, QString streamName) {
|
|
if (stream->getPerformanceMode() != oboe::PerformanceMode::LowLatency) {
|
|
QString latencyMode = (stream->getPerformanceMode()==oboe::PerformanceMode::None ? "None" : "Power Saving");
|
|
// throw CGenErr ( tr ( "Stream is NOT low latency."
|
|
// "Check your requested format, sample rate and channel count." ) );
|
|
}
|
|
}
|
|
|
|
void CSound::closeStream(oboe::ManagedStream &stream)
|
|
{
|
|
if (stream) {
|
|
oboe::Result requestStopRes = stream->requestStop();
|
|
oboe::Result result = stream->close();
|
|
if (result != oboe::Result::OK) {
|
|
throw CGenErr ( tr ( "Error closing stream: $s",
|
|
oboe::convertToText(result) ) );
|
|
}
|
|
stream.reset();
|
|
}
|
|
}
|
|
|
|
void CSound::closeStreams()
|
|
{
|
|
// clean up
|
|
closeStream(mRecordingStream);
|
|
closeStream(mPlayStream);
|
|
}
|
|
|
|
void CSound::Start()
|
|
{
|
|
openStreams();
|
|
|
|
// call base class
|
|
CSoundBase::Start();
|
|
|
|
// finally start the streams so the callback begins, start with inputstream first.
|
|
mRecordingStream->requestStart();
|
|
mPlayStream->requestStart();
|
|
|
|
}
|
|
|
|
void CSound::Stop()
|
|
{
|
|
closeStreams();
|
|
|
|
// call base class
|
|
CSoundBase::Stop();
|
|
}
|
|
|
|
int CSound::Init ( const int iNewPrefMonoBufferSize )
|
|
{
|
|
// store buffer size
|
|
iOpenSLBufferSizeMono = 512 ;
|
|
//iNewPrefMonoBufferSize;
|
|
|
|
// init base class
|
|
CSoundBase::Init ( iOpenSLBufferSizeMono );
|
|
|
|
// set internal buffer size value and calculate stereo buffer size
|
|
iOpenSLBufferSizeStereo = 2 * iOpenSLBufferSizeMono;
|
|
|
|
// create memory for intermediate audio buffer
|
|
vecsTmpAudioSndCrdStereo.Init ( iOpenSLBufferSizeStereo );
|
|
|
|
// TEST
|
|
#if ( SYSTEM_SAMPLE_RATE_HZ != 48000 )
|
|
# error "Only a system sample rate of 48 kHz is supported by this module"
|
|
#endif
|
|
// audio interface number of channels is 1 and the sample rate
|
|
// is 16 kHz -> just copy samples and perform no filtering as a
|
|
// first simple solution
|
|
// 48 kHz / 16 kHz = factor 3 (note that the buffer size mono might
|
|
// be divisible by three, therefore we will get a lot of drop outs)
|
|
iModifiedInBufSize = iOpenSLBufferSizeMono / 3;
|
|
vecsTmpAudioInSndCrd.Init ( iModifiedInBufSize );
|
|
|
|
return iOpenSLBufferSizeMono;
|
|
}
|
|
|
|
// This is the main callback method for when an audio stream is ready to publish data to an output stream
|
|
// or has received data on an input stream. As per manual much be very careful not to do anything in this back that
|
|
// can cause delays such as sleeping, file processing, allocate memory, etc
|
|
oboe::DataCallbackResult CSound::onAudioReady(oboe::AudioStream *oboeStream, void *audioData, int32_t numFrames)
|
|
{
|
|
// only process if we are running
|
|
if ( ! pSound->bRun )
|
|
{
|
|
return oboe::DataCallbackResult::Continue;
|
|
}
|
|
|
|
// Need to modify the size of the buffer based on the numFrames requested in this callback.
|
|
// Buffer size can change regularly by android devices
|
|
int& iBufferSizeMono = pSound->iOpenSLBufferSizeMono;
|
|
|
|
// perform the processing for input and output
|
|
// QMutexLocker locker ( &pSound->Mutex );
|
|
// locker.mutex();
|
|
|
|
//This can be called from both input and output at different times
|
|
if (oboeStream == pSound->mPlayStream.get() && audioData)
|
|
{
|
|
float *floatData = static_cast<float *>(audioData);
|
|
|
|
// Zero out the incoming container array
|
|
memset(audioData, 0, sizeof(float) * numFrames * oboeStream->getChannelCount());
|
|
|
|
// Only copy data if we have data to copy, otherwise fill with silence
|
|
if (!pSound->vecsTmpAudioSndCrdStereo.empty())
|
|
{
|
|
for (int frmNum = 0; frmNum < numFrames; ++frmNum)
|
|
{
|
|
for (int channelNum = 0; channelNum < oboeStream->getChannelCount(); channelNum++)
|
|
{
|
|
// copy sample received from server into output buffer
|
|
|
|
|
|
// convert to 32 bit
|
|
const int32_t iCurSam = static_cast<int32_t> (
|
|
pSound->vecsTmpAudioSndCrdStereo [frmNum * oboeStream->getChannelCount() + channelNum] );
|
|
floatData[frmNum * oboeStream->getChannelCount() + channelNum] = (float) iCurSam/ _MAXSHORT;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// prime output stream buffer with silence
|
|
memset(static_cast<float*>(audioData) + numFrames * oboeStream->getChannelCount(), 0,
|
|
(numFrames) * oboeStream->getBytesPerFrame());
|
|
}
|
|
}
|
|
else if (oboeStream == pSound->mRecordingStream.get() && audioData)
|
|
{
|
|
// First things first, we need to discard the input queue a little for 500ms or so
|
|
if (pSound->mCountCallbacksToDrain > 0)
|
|
{
|
|
// discard the input buffer
|
|
int32_t numBytes = numFrames * oboeStream->getBytesPerFrame();
|
|
memset(audioData, 0 /* value */, numBytes);
|
|
pSound->mCountCallbacksToDrain--;
|
|
}
|
|
|
|
// We're good to start recording now
|
|
// Take the data from the recording device ouput buffer and move
|
|
// it to the vector ready to send up to the server
|
|
|
|
float *floatData = static_cast<float *>(audioData);
|
|
|
|
// Copy recording data to internal vector
|
|
for (int frmNum = 0; frmNum < numFrames; ++frmNum)
|
|
{
|
|
for (int channelNum = 0; channelNum < oboeStream->getChannelCount(); channelNum++)
|
|
{
|
|
pSound->vecsTmpAudioSndCrdStereo [frmNum * oboeStream->getChannelCount() + channelNum] =
|
|
(short) floatData[frmNum * oboeStream->getChannelCount() + channelNum] * _MAXSHORT;
|
|
}
|
|
}
|
|
|
|
// Tell parent class that we've put some data ready to send to the server
|
|
pSound->ProcessCallback ( pSound->vecsTmpAudioSndCrdStereo );
|
|
}
|
|
// locker.unlock();
|
|
return oboe::DataCallbackResult::Continue;
|
|
}
|
|
|
|
//TODO better handling of stream closing errors
|
|
void CSound::onErrorAfterClose(oboe::AudioStream *oboeStream, oboe::Result result)
|
|
{
|
|
qDebug() << "CSound::onErrorAfterClose";
|
|
}
|
|
|
|
//TODO better handling of stream closing errors
|
|
void CSound::onErrorBeforeClose(oboe::AudioStream *oboeStream, oboe::Result result)
|
|
{
|
|
qDebug() << "CSound::onErrorBeforeClose";
|
|
}
|
|
|
|
|
|
|