AVAudioSession: Some Bluetooth devices are not working properly on my App - ios

I'm developing a swift audio/video and text chat iOS App using AVAudioSession.
Whenever I select to use some Bluetooth devices the sound played on the device is not the App audio stream. They play only the system sound sent by text chat library whenever messages are sent/received instead. It doesn't happen on all Bluetooth devices, on some of them everything works fine. On Builtin Mic and Speaker the App works fine too.
Here are the most important methods from my class to manage the device:
class MyAudioSession
{
private var mAudioSession: AVAudioSession;
init!()
{
self.mAudioSession = AVAudioSession.sharedInstance();
do {
try self.mAudioSession.setActive(false);
try self.mAudioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, withOptions: .AllowBluetooth);
try self.mAudioSession.setMode(AVAudioSessionModeVideoChat);
try self.mAudioSession.setActive(true);
}
catch {
return nil;
}
}
func switchToDevice(device: AVAudioSessionPortDescription!) -> Bool
{
var ret = false;
if (device != nil) {
do {
try self.mAudioSession.setPreferredInput(device);
ret = true;
}
catch {
self.logSwitch(device, error: error);
}
}
return ret;
}
}
I'd like to understand why my App is not working fine on just SOME Bluetooth devices. These same devices work properly on the other Apps on my Cel.
I did another test: I changed all of this for MPVolumeView, and exactly the same issue occurred, so the problem seems to be on audio player.
Could anybody give me a suggestion to fix this ?
Thx.

Jorg,
While this might not be the best answer I have been able to overcome the weird Bluetooth issues. My problem seems to be similar to yours as I too was using:
AVAudioSessionCategoryPlayAndRecord
This was causing issues for me on some Bluetooth devices (not all but some).
What I wound up doing was setting the Category to:
AVAudioSessionCategoryPlayback
Then when ever I needed to record I would switch the Category over to:
AVAudioSessionCategoryRecord
Then back to Playback after completing my recording.
This was the only way at this time I could get a consistent result from switching between the different outputs (Speaker, Headphones, Bluetooth).
Hope that helps some. Guessing this is a bug in the "AVAudioSessionCategoryPlayAndRecord"

Related

no sound when using pjsip

I've got a problem with pjsip. I'm trying to make an outgoing call with pjsua_call_make_call. It's working, but when I answer this call on a device, I can't hear any sound. However, I can see an icon on iPhone, indicating that a microphone is in use. Did anybody come across such issue?
I am having a similar issue. I place an outbound call and I can hear the audio on the device when I pick up the call, but can't hear any audio on the device using pjsip to make the call.
If your audio capture doesn't seem to be working make sure you have microphone permission, and you have to manually call pjsua_set_snd_dev(), when you connect. There's some other additional troubleshooting here https://trac.pjsip.org/repos/wiki/Getting-Started/iPhone#Commonproblems
I've no experience with iOS, but I suppose you should connect audio stream to some device in on_call_media_state callback (link). Look at minimal example from desktop app:
pjsua_call_info ci;
pjsua_call_get_info(call_id, &ci);
for (unsigned i = 0; i < ci.media_cnt; i++) {
if (ci.media[i].type == PJMEDIA_TYPE_AUDIO) {
if (ci.media[i].status == PJSUA_CALL_MEDIA_ACTIVE) {
pjsua_conf_connect(ci.conf_slot, 0);
pjsua_conf_connect(0, ci.conf_slot);
}
}
}
Edit:
iOS code for default audio stream in call:
var callinfo: pjsua_call_info = pjsua_call_info()
pjsua_call_get_info(call_id, &callinfo)
if(callinfo.media_status == PJSUA_CALL_MEDIA_ACTIVE) {
pjsua_conf_connect(callinfo.conf_slot, 0)
pjsua_conf_connect(0, callinfo.conf_slot)
}

AVAudioSession in bluetooth switches strangely when interrupting playback of other apps

I'm working on a VoIP app and i have handled the interruption of AVAudioSession in normal cases. But i find that in bluetooth mode, when i start a call, the switching process from PlayBack to PlayAndRecord will not complete instantly. There will be a short play in low quality and loud volume during the switching time. After that, the playback will not resume even though the call is over. This phenomenon appears randomly. Mostly at the beginning time, and then everything is OK. But after several times of calling, it happens again, which confuses me a lot.
I guessed at the beginning that it might be the problem of bluetooth category, so i added the options [.allowBluetooth, .allowBluetoothA2DP]. But it did not work. Here's my code.
// setting of AVAudioSession
public static let voipCall: AudioSessionScenario = {
if #available(iOS 10.0, *) {
return AudioSessionScenario("voip", category: .playAndRecord, mode: .voiceChat, options: [.allowBluetooth, .allowBluetoothA2DP])
} else {
return AudioSessionScenario("voip", category: .playAndRecord, mode: .voiceChat, options: [.allowBluetooth])
}
}()
// when user starts a call
AVAudioSession.queue.async {
AVAudioSession.entry(AudioSessionScenario.voipCall)
}
// the entry method does nothing but set the setting of sharedInstance
if #available(iOS 10.0, *) {
try session.setCategory(category, mode: scenario.mode, options: scenario.options)
} else {
AVAudioSession.sharedInstance().perform(NSSelectorFromString("setCategory:withOptions:error:"), with: category, with: scenario.options)
}
I expect that there is no strange play in the switching process and the playback will resume after the call. It works well in normal cases including speaker and wired headset. Bluetooth devices like Bose QC30 or Airpods have the unstable problem. Does anyone know the reason? Thx.

NativeScript, Play sound mixed in with other app playing in background

How do I make my Tags NativeScript app play sound mixed in with other app playing in background?
I have looked everywhere, please help!
My solution for this. Add this lines in app.js
var audioSession = AVAudioSession.sharedInstance();
try {
audioSession.setCategoryError(AVAudioSessionCategoryAmbient);
audioSession.setActiveError(true);
} catch(err) {
console.log("setting audioSession category failed");
}

AVAudioSession - How to switch between speaker and headphones output

I'm trying to mimic behaviour as in Phone app during calling. You can easily switch output sources from/to speaker or headphones.
I know I can force speaker as an output when headphones are connected by calling:
try! audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
try! audioSession.overrideOutputAudioPort(.speaker)
However, when I do that, I don't see any way to detect if headphones are still connected to the device.
I initially thought outputDataSources on AVAudioSession would return all posible outputs but it always returns nil.
Is there something I'm missing
You need to change the outputDataSources, as when you overrode it,
now it contains only the .Speaker option
in the Documentation you can find the solution to this,
If your app uses the playAndRecord category, calling this method with the AVAudioSession.PortOverride.speaker option causes audio to be routed to the built-in speaker and microphone regardless of other settings. This change remains in effect only until the current route changes or you call this method again with the AVAudioSession.PortOverride.none option.
Therefore the audio is routed to the built-in speaker, This change remains in effect only untill the current route changes or you call this method again with .noneOption.
it's not possible to forcefully direct sound to headphone unless an accessory is plugged to headphone jack (which activates a physical switch to direct voice to headphone).
So when you want to switch back to headphone, this should work.
And if there is no headphone connected will switch the output device to the small speaker output on the top of the device instead of the big speaker.
let session: AVAudioSession = AVAudioSession.sharedInstance()
do {
try session.setCategory(AVAudioSessionCategoryPlayAndRecord)
try session.overrideOutputAudioPort(AVAudioSession.PortOverride.none)
try session.setActive(true)
} catch {
print("Couldn't override output audio port")
}
Read about this AVAdioSession/OverrideOutputAudioPort Here.
You can check if headset connected adding this extension,
extension AVAudioSession {
static var isHeadphonesConnected: Bool {
return sharedInstance().isHeadphonesConnected
}
var isHeadphonesConnected: Bool {
return !currentRoute.outputs.filter { $0.isHeadphones }.isEmpty
}
}
extension AVAudioSessionPortDescription {
var isHeadphones: Bool {
return portType == AVAudioSessionPortHeadphones
}
}
And simply use this line of code
session.isHeadphonesConnected

Remote Audio not connecting: iOS, PJSIP 2.6, CallKit, PJSUA2

I am updating an existing iOS VOIP application to use CallKit with PJSIP 2.6 and PJSUA2.
After some effort, the CallKit implementation seems to be working as expected. Incoming calls can be accepted or declined, and if accepted, will be connected and controlled with an in-app active call view controller.
The audio, however, does not appear to be properly connected at the pjsip end. There is no audio coming in from, or going out to the remote caller. The microphone audio appears to be routed back to the iPhone speaker.
The SIP audio ports should be connecting in callback function onCallMediaState:
virtual void onCallMediaState(OnCallMediaStateParam &prm) {
CallInfo ci = getInfo();
AudioMedia* audio_media = 0;
for (unsigned i = 0; i < ci.media.size(); i++) {
if (ci.media[i].type==PJMEDIA_TYPE_AUDIO && ( ci.media[i].status == PJSUA_CALL_MEDIA_ACTIVE ||
ci.media[i].status ==PJSUA_CALL_MEDIA_REMOTE_HOLD)) {
try {
audio_media = static_cast<AudioMedia*>(getMedia(i));
if(audio_media != 0)
{
Endpoint::instance().audDevManager().getCaptureDevMedia().startTransmit(*audio_media);
audio_media->startTransmit(Endpoint::instance().audDevManager().getPlaybackDevMedia());
}
} catch (std::exception ex) {
continue;
}
}
}
}
As described in Ticket#1941 at:
https://trac.pjsip.org/repos/ticket/1941:
I set the audio devices using:
ep->audDevManager().setNullDev();
immediately after the initialization of the Endpoint class (ep->libInit(epConfig);), and then:
I attempt to set the devices using pjsua_set_snd_dev() in CXProvider’s didActivate function, like this:
-(void) setSipSoundDevices {
pj_status_t status;
int captDev, playDev;
pjsua_get_snd_dev(&captDev, &playDev);
Endpoint::instance().audDevManager().setPlaybackDev(playDev);
Endpoint::instance().audDevManager().setCaptureDev(captDev);
}
pjsua_get_snd_dev(&captDev, &playDev) returns -99, -99 and the audio does not connect.
My question is this. How can I properly hook up the remote audio sources or ports, on an incoming call using PJSIP 2.6 and CallKit?
Might 2.5.5 work better in this regard?
Any insights are appreciated.
By and by I got the incoming call audio working properly. The crux of the matter was that even though the documentation from both Apple and SIP say that the audio has to be handled on the iOS end, you still have to set the SIP audio devices in the SIP layer in the provider delegate 'didActivate' and 'didDeactivate' functions. Because I use the PJSUA C++ layer, I had to drill down through the objc-c++ bridging layer to provide this functionality. ie.
-(void) activateSipSoundDevices {
pj_status_t status = pjsua_set_snd_dev(0, 0);
}
-(void) deactivateSipSoundDevices {
pj_status_t status = pjsua_set_null_snd_dev();
}
When initializing the SIP Account, be sure to set the null sound devices like:
ep->audDevManager().setNullDev();
Hope this helps.

Resources