Started a new thread to discuss the android port with Oboe I'm working on.
Whilst I pretty much the Obo implementation working in terms of being able to open both the speaker device plus the recording devices on the android device, I'm struggling a little in understanding the method of transferring the data back and forther between the main application.
I've obviously modified the android/sound.cpp/h files to use the Oboe libraries and I see that CSound is a derirvative of CSoundBase which has a protected vector (vecsTmpAudioSndCrdStereo) for the transferring of data between the sound buffers and the main program, what I can't figure out is the other CSound implementations for mac, linux and windows appear to use that same vector for both incoming sound (from device to be sent up to jamulus server) and outgoing sound (mixed audio data to be delivered to audio device speakers/headphones). The way oboe works is with a single callback method for low latency audio delivery this is called for both incoming and outgoing audio streams and in the callback is a buffer which can be cast to whatever structure you need, mostly it's just an array. But if I was to copy this data (for incoming) onto the CSoundBase->vecsTmpAudioSndCrdStereo vector surely this would overrite any outgoing data that jamulus client had placed on this vector for delivery to speakers? What is the method of protecting this structure so that data is not overwritten? I had a late night working on this so probably my brain is not working well enough this morning!
Thanks
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
If you just have one callback function which delivers you with the sound card input audio and expects you to write the sound card output signal its easy: You simply take the data from the vecsTmpAudioSndCrdStereo and write that in the sound card output signal vector of your callback and then you use the sound card input audio signal and write that in the vecsTmpAudioSndCrdStereo. That way you make sure that nothing is overwritten.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Thanks, I'm now getting some data back and sending it up to the server, but the playback stream is sounding odd: sounds like a static in a bathroom. I think it could be due to type casting on the playback callback. Here's a very simiplified versoin of the code i'm using:
The numFrames is being set to 256, channelCount is 2.
The code looks ok to me.
"sounds like a static in a bathroom" could also be caused by too small network jitter buffers or other network problem. 256 samples is quite large. Is it possible to go down to 128?
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
That got set from the client, when i choose middle option (128 samples) CSound.Init(preferredBufferMono) is called, but I guess because it's stereo it creates a buffer that is 256 samples in size. I've tried reducing it down to 64 (which makes it 128) but that makes it sound worse. I'm running it within the android emulator on my PC, so perhaps it could be something to do with that. I'm about to compile one of the samples Oboe provides to see if streaming a wav file from my filesystem provides the same effect... will keep trying.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
hi Volker. Still struggling with getting this sound quality to sound right in Oboe. I've taken a video of my phone connected to a jamulus room where music is being played so you can hear. I didn't have headphones plugged in so it does cause a feedback loop fairly quickly, but hopefully initially you can hear what it sounds like.
/******************************************************************************\ * Copyright (c) 2004-2020 * * Author(s): * 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., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *\******************************************************************************/
#include"sound.h"
#include"androiddebug.cpp"/* Implementation *************************************************************/CSound::CSound(void(*fpNewProcessCallback)(CVector<short>&psData, void*arg),
void*arg,
constintiCtrlMIDIChannel,
constboolbNoAutoJackConnect) :
CSoundBase("OpenSL", true, fpNewProcessCallback, arg, iCtrlMIDIChannel, bNoAutoJackConnect)
{
//createCallback();qInfo()<<"Exeucting CSound::CSound";pSound=this;
#ifdefANDROIDDEBUGqInstallMessageHandler(myMessageHandler);
#endif
}
voidCSound::setupRecordingStreamParams(oboe::AudioStreamBuilder*builder, oboe::ManagedStream*mOutStream)
{
//checkwhatproperiteswehaveontheplayBackstreamfirst;builder->setDirection(oboe::Direction::Input);returnsetupCommonStreamParams(builder);
}
voidCSound::setupPlaybackStreamParams(oboe::AudioStreamBuilder*builder)
{
builder->setDirection(oboe::Direction::Output);returnsetupCommonStreamParams(builder);
}
voidCSound::setupCommonStreamParams(oboe::AudioStreamBuilder*builder)
{
//WerequestEXCLUSIVEmodesincethiswillgiveusthelowestpossible//latency.
//IfEXCLUSIVEmodeisn't available the builder will fall back to SHARED//mode.
builder->setCallback(this)->setFormat(oboe::AudioFormat::Float)->setSharingMode(oboe::SharingMode::Shared)->setFramesPerCallback(pSound->iOpenSLBufferSizeStereo)->setChannelCount(oboe::ChannelCount::Stereo)->setSampleRate(48000)->setSampleRateConversionQuality(oboe::SampleRateConversionQuality::Medium)->setPerformanceMode(oboe::PerformanceMode::None);return;
}
voidCSound::openStreams()
{
//CreatecallbackqDebug()<<"Setting up streams";mCallback=this;oboe::AudioStreamBuilderinBuilder, outBuilder;setupPlaybackStreamParams(&outBuilder);oboe::Resultresult=outBuilder.openManagedStream(mPlayStream);if(result!=oboe::Result::OK) {
return;
}
//TODOFigureoutlowlatencywarnIfNotLowLatency(mPlayStream, "PlayStream");setupRecordingStreamParams(&inBuilder, &mPlayStream);result=inBuilder.openManagedStream(mRecordingStream);if(result!=oboe::Result::OK) {
qInfo()<<"Error, stream closing";closeStream(mPlayStream);return;
}
warnIfNotLowLatency(mRecordingStream, "RecordStream");qDebug()<<"Recording/Playback streams opened";
}
voidCSound::warnIfNotLowLatency(oboe::ManagedStream&stream, QStringstreamName) {
if(stream->getPerformanceMode()!=oboe::PerformanceMode::LowLatency) {
QStringlatencyMode=(stream->getPerformanceMode()==oboe::PerformanceMode::None ? "None" : "Power Saving");qInfo()<<"Stream '"<<streamName<<"' is not lowLatency:"<<latencyMode;//throwCGenErr(tr("Stream is NOT low latency."//"Check your requested format, sample rate and channel count."));
}
}
voidCSound::closeStream(oboe::ManagedStream&stream)
{
if(stream) {
oboe::ResultrequestStopRes=stream->requestStop();qDebug()<<"Requeststop on stream, res:";oboe::Resultresult=stream->close();if(result!=oboe::Result::OK) {
throwCGenErr(tr("Error closing stream: $s",
oboe::convertToText(result)));
}
stream.reset();
}
}
voidCSound::closeStreams()
{
//cleanupcloseStream(mRecordingStream);closeStream(mPlayStream);
}
voidCSound::Start()
{
qInfo()<<"CSound::Start - entering";openStreams();//callbaseclassCSoundBase::Start();qInfo()<<"CSound::Start - exiting";//finallystartthestreamsmRecordingStream->requestStart();mPlayStream->requestStart();
}
voidCSound::Stop()
{
closeStreams();//callbaseclassCSoundBase::Stop();
}
intCSound::Init(constintiNewPrefMonoBufferSize)
{
qDebug()<<"Executing CSound::Init, buffersize:"<<iNewPrefMonoBufferSize;//storebuffersizeiOpenSLBufferSizeMono=iNewPrefMonoBufferSize;//initbaseclassCSoundBase::Init(iOpenSLBufferSizeMono);//setinternalbuffersizevalueandcalculatestereobuffersizeiOpenSLBufferSizeStereo=2*iOpenSLBufferSizeMono;//creatememoryforintermediateaudiobufferqDebug()<<"Executing CSound::Init, setting buffer size initial to:"<<iOpenSLBufferSizeStereo;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//audiointerfacenumberofchannelsis1andthesamplerate//is16kHz->justcopysamplesandperformnofilteringasa//firstsimplesolution//48kHz/16kHz=factor3(notethatthebuffersizemonomight//bedivisiblebythree, thereforewewillgetalotofdropouts)iModifiedInBufSize=iOpenSLBufferSizeMono/3;vecsTmpAudioInSndCrd.Init(iModifiedInBufSize);qInfo()<<"Exiting CSound::Init exiting, have set stereo buffer size to:"<<iOpenSLBufferSizeStereo;returniOpenSLBufferSizeMono;
}
//Thisisthemaincallbackmethodforwhenanaudiostreamisreadytopublishdatatoanoutputstream//orhasreceiveddataonaninputstream. Aspermanualmuchbeverycarefulnottodoanythinginthisbackthat//cancausedelayssuchassleeping, fileprocessing, allocatememory, etcoboe::DataCallbackResultCSound::onAudioReady(oboe::AudioStream*oboeStream, void*audioData, int32_tnumFrames)
{
//qDebug("CSound::onAudioReady:BEGIN");//onlyprocessifwearerunningif(!pSound->bRun)
{
returnoboe::DataCallbackResult::Continue;
}
//NeedtomodifythesizeofthebufferbasedonthenumFramesrequestedinthiscallback.
//Buffersizecanchangeregularlybyandroiddevicesint&iBufferSizeMono=pSound->iOpenSLBufferSizeMono;//performtheprocessingforinputandoutputQMutexLockerlocker(&pSound->Mutex);locker.mutex();//Thiscanbecalledfrombothinputandoutputatdifferenttimesif(oboeStream==pSound->mPlayStream.get()&&audioData)
{
float*floatData=static_cast<float*>(audioData);//Zeroouttheincomingcontainerarraymemset(audioData, 0, sizeof(float)*numFrames*oboeStream->getChannelCount());//Onlycopydataifwehavedatatocopy, otherwisefillwithsilenceif(!pSound->vecsTmpAudioSndCrdStereo.empty())
{
for(intfrmNum=0; frmNum < numFrames; ++frmNum)
{
for(intchannelNum=0; channelNum < oboeStream->getChannelCount(); channelNum++)
{
//copysamplereceivedfromserverintooutputbufferfloatData[frmNum*oboeStream->getChannelCount()+channelNum] =(float)pSound->vecsTmpAudioSndCrdStereo [frmNum*oboeStream->getChannelCount()+channelNum] /32768/* Convert from 16bit signed to 24bit (float) */;
}
}
}
else
{
//primeoutputstreambufferwithsilencememset(static_cast<float*>(audioData)+numFrames*oboeStream->getChannelCount(), 0,
(numFrames)*oboeStream->getBytesPerFrame());
}
}
elseif(oboeStream==pSound->mRecordingStream.get()&&audioData)
{
//Firstthingsfirst, weneedtodiscardtheinputqueuealittlefor500msorsoif(pSound->mCountCallbacksToDrain>0)
{
//discardtheinputbufferint32_tnumBytes=numFrames*oboeStream->getBytesPerFrame();memset(audioData, 0/* value */, numBytes);pSound->mCountCallbacksToDrain--;
}
//We're good to start recording now//Takethedatafromtherecordingdeviceouputbufferandmove//ittothevectorreadytosenduptotheserverfloat*floatData=static_cast<float*>(audioData);//Copyrecordingdatatointernalvectorfor(intfrmNum=0; frmNum < numFrames; ++frmNum)
{
for(intchannelNum=0; channelNum < oboeStream->getChannelCount(); channelNum++)
{
pSound->vecsTmpAudioSndCrdStereo [frmNum*oboeStream->getChannelCount()+channelNum] =(short)floatData[frmNum*oboeStream->getChannelCount()+channelNum] *_MAXSHORT;
}
}
//Tellparentclassthatwe've put some data ready to send to the serverpSound->ProcessCallback(pSound->vecsTmpAudioSndCrdStereo);
}
locker.unlock();returnoboe::DataCallbackResult::Continue;
}
//TODObetterhandlingofstreamclosingerrorsvoidCSound::onErrorAfterClose(oboe::AudioStream*oboeStream, oboe::Resultresult)
{
qDebug()<<"CSound::onErrorAfterClose";
}
//TODObetterhandlingofstreamclosingerrorsvoidCSound::onErrorBeforeClose(oboe::AudioStream*oboeStream, oboe::Resultresult)
{
qDebug()<<"CSound::onErrorBeforeClose";
}
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I would be good if you would apply all the code you have so far to the Jamulus Git repo and create a Pull Request. I saw in the "Network" view on Github that you have created a fork quite a while ago but that seems not to be very up to date. If we have your code in the official Jamulus repo, maybe some other developers can give us some help. What do you think?
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Hi. I've committed all the code I've done so far to my forked branch: https://github.com/sthenos/jamulus. Still wasn't able to get the audio sounding clean and free of distortion. I had to add some additional code to Jamulus.pro, main.cpp (to add the permission for recording which now on android requires explicit request in code, not just in the android manifest), global.h (types for android for int64_t, etc are different from win32).
So I should do a new pull request from the main repo and then add the code in there and commit it back?
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
It is hard to say what goes wrong. Maybe the buffer is too short which leads to the strange audio artifacts. You could try to hard code a large size for a test:
Thanks a lot for the audio implementation on Android. I succeeded in compiling an deploying to my phone after installing Oboe from https://github.com/google/oboe.git. Sound doesn't really work yet for me (it doesn't pick up anything from the microphone and I hear only noise from other clients), I guess that's the same behaviour described above.
To make it look nicer, I have scaled the icon to the required resolutions, put it in the place Android expects and referenced it in the AndoirdManifest.xml. Shall I make a pull request for that?
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Yes please (if it does not cause a lot of work for you). That way I can easily see what you have changed and if I want these changes to apply to the official sources.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
BTW: Have you seen in the Git repo that I have merged the changes from Simon already. He has used Oboe in a Git submodule. So if you write that you had to install oboe, I guess you do not use the latest git code, right?
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
There don't appear to be any references to an Android client for jamulus since May - is anyone still working on this? I'm happily using Jamulus every week with friends, but they all have PCs or notebooks. I have another band that mostly has people who only have phones or tablets. I can't find ANY app for them - and the work on a Jamulus client port to Android using the Oboe library looks the most likely to produce something.
Can anyone please comment on progress? Is there anything I might do to help? Thanks.
Last edit: Andrew S 2020-10-16
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
The current status is:
- Audio in and out seems to work in the limited testing (a few different terminals, three people).
- Having tested enough to tell if audio quality is good or bad: You can hear other people talking and they can hear you without apparent distortions.
- GUI is exactly the same as the desktop version, as rendered by QT for Android. This might eventually require changes.
Building will required
- init the oboe git submodule.
- build the project for the android target in Qt Creator.
Last edit: Julian Santander 2020-10-18
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Thanks Julian - I've never tried compliling for an Android target before so I'll have to make the time to get my environment set up. If there's a test apk about I'd be more than happy to test it with actual musicians, rather than just speech.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
I was going to ask that too. I assume any effective Android implementation of Jamulus implies wired Ethernet unless perhaps 5G is implied (which it might?)
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
There are adapters of RJ-45 to USB. Then you need a mobile terminal with support for USB OTG (which basically means that the USB port can be configured to be "host") and typically an adaptor from USB-C to USB-A.
Having said that, the testing I'm aware has only used WiFi. In my experience, provided you have good WiFi signal (close to the AP) the result is acceptable. Network delay have a lot of variability and that's the main problem.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Started a new thread to discuss the android port with Oboe I'm working on.
Whilst I pretty much the Obo implementation working in terms of being able to open both the speaker device plus the recording devices on the android device, I'm struggling a little in understanding the method of transferring the data back and forther between the main application.
I've obviously modified the android/sound.cpp/h files to use the Oboe libraries and I see that CSound is a derirvative of CSoundBase which has a protected vector (vecsTmpAudioSndCrdStereo) for the transferring of data between the sound buffers and the main program, what I can't figure out is the other CSound implementations for mac, linux and windows appear to use that same vector for both incoming sound (from device to be sent up to jamulus server) and outgoing sound (mixed audio data to be delivered to audio device speakers/headphones). The way oboe works is with a single callback method for low latency audio delivery this is called for both incoming and outgoing audio streams and in the callback is a buffer which can be cast to whatever structure you need, mostly it's just an array. But if I was to copy this data (for incoming) onto the CSoundBase->vecsTmpAudioSndCrdStereo vector surely this would overrite any outgoing data that jamulus client had placed on this vector for delivery to speakers? What is the method of protecting this structure so that data is not overwritten? I had a late night working on this so probably my brain is not working well enough this morning!
Thanks
If you just have one callback function which delivers you with the sound card input audio and expects you to write the sound card output signal its easy: You simply take the data from the vecsTmpAudioSndCrdStereo and write that in the sound card output signal vector of your callback and then you use the sound card input audio signal and write that in the vecsTmpAudioSndCrdStereo. That way you make sure that nothing is overwritten.
Thanks, I'm now getting some data back and sending it up to the server, but the playback stream is sounding odd: sounds like a static in a bathroom. I think it could be due to type casting on the playback callback. Here's a very simiplified versoin of the code i'm using:
The numFrames is being set to 256, channelCount is 2.
The code looks ok to me.
"sounds like a static in a bathroom" could also be caused by too small network jitter buffers or other network problem. 256 samples is quite large. Is it possible to go down to 128?
That got set from the client, when i choose middle option (128 samples) CSound.Init(preferredBufferMono) is called, but I guess because it's stereo it creates a buffer that is 256 samples in size. I've tried reducing it down to 64 (which makes it 128) but that makes it sound worse. I'm running it within the android emulator on my PC, so perhaps it could be something to do with that. I'm about to compile one of the samples Oboe provides to see if streaming a wav file from my filesystem provides the same effect... will keep trying.
hi Volker. Still struggling with getting this sound quality to sound right in Oboe. I've taken a video of my phone connected to a jamulus room where music is being played so you can hear. I didn't have headphones plugged in so it does cause a feedback loop fairly quickly, but hopefully initially you can hear what it sounds like.
https://drive.google.com/drive/folders/1nmZcpgsN6bRP-KFdf5nJ_HH6nnmaFxEv?usp=sharing
Here's the code I'm using for sound.cpp
I would be good if you would apply all the code you have so far to the Jamulus Git repo and create a Pull Request. I saw in the "Network" view on Github that you have created a fork quite a while ago but that seems not to be very up to date. If we have your code in the official Jamulus repo, maybe some other developers can give us some help. What do you think?
Hi. I've committed all the code I've done so far to my forked branch: https://github.com/sthenos/jamulus. Still wasn't able to get the audio sounding clean and free of distortion. I had to add some additional code to Jamulus.pro, main.cpp (to add the permission for recording which now on android requires explicit request in code, not just in the android manifest), global.h (types for android for int64_t, etc are different from win32).
So I should do a new pull request from the main repo and then add the code in there and commit it back?
Sorry, I understand what you mean. I've initiated pull request from main repo and merged all my changes in. Hopefully this works.
It is hard to say what goes wrong. Maybe the buffer is too short which leads to the strange audio artifacts. You could try to hard code a large size for a test:
Instead of:
try out:
@simon good job! Do you by any chance have a way to share the resulting apk for others to beta test/try?
(and of course thx to @volker for jamulus :-)
Thanks a lot for the audio implementation on Android. I succeeded in compiling an deploying to my phone after installing Oboe from https://github.com/google/oboe.git. Sound doesn't really work yet for me (it doesn't pick up anything from the microphone and I hear only noise from other clients), I guess that's the same behaviour described above.
To make it look nicer, I have scaled the icon to the required resolutions, put it in the place Android expects and referenced it in the AndoirdManifest.xml. Shall I make a pull request for that?
Yes please (if it does not cause a lot of work for you). That way I can easily see what you have changed and if I want these changes to apply to the official sources.
BTW: Have you seen in the Git repo that I have merged the changes from Simon already. He has used Oboe in a Git submodule. So if you write that you had to install oboe, I guess you do not use the latest git code, right?
I have made a pull request.
The libs/oboe directory is still empty in my clone even though git pull tells me I'm up to date.
Make sure you do a git update/pull submodules and it should pull the oboe libraries in
Thanks for the hint, I was not aware of the submodule. After running
git submodule init
andgit submodule update
libs/oboe is filled.There don't appear to be any references to an Android client for jamulus since May - is anyone still working on this? I'm happily using Jamulus every week with friends, but they all have PCs or notebooks. I have another band that mostly has people who only have phones or tablets. I can't find ANY app for them - and the work on a Jamulus client port to Android using the Oboe library looks the most likely to produce something.
Can anyone please comment on progress? Is there anything I might do to help? Thanks.
Last edit: Andrew S 2020-10-16
Hi Andrew - there's been some activity on this ticket recently:
https://github.com/corrados/jamulus/issues/83
The current status is:
- Audio in and out seems to work in the limited testing (a few different terminals, three people).
- Having tested enough to tell if audio quality is good or bad: You can hear other people talking and they can hear you without apparent distortions.
- GUI is exactly the same as the desktop version, as rendered by QT for Android. This might eventually require changes.
Building will required
- init the oboe git submodule.
- build the project for the android target in Qt Creator.
Last edit: Julian Santander 2020-10-18
Thanks Julian - I've never tried compliling for an Android target before so I'll have to make the time to get my environment set up. If there's a test apk about I'd be more than happy to test it with actual musicians, rather than just speech.
Is there a solution for getting wired internet into an Android device ?
I was going to ask that too. I assume any effective Android implementation of Jamulus implies wired Ethernet unless perhaps 5G is implied (which it might?)
There are adapters of RJ-45 to USB. Then you need a mobile terminal with support for USB OTG (which basically means that the USB port can be configured to be "host") and typically an adaptor from USB-C to USB-A.
Having said that, the testing I'm aware has only used WiFi. In my experience, provided you have good WiFi signal (close to the AP) the result is acceptable. Network delay have a lot of variability and that's the main problem.
my experiences with WiFi is the opposite: numerous cases of bad sound/connections has been solved by switching to a non-radio based communication.