Unable to properly inject audio via ChanSpy

I am trying to inject audio into a local channel using the ChanSpy application, as opposed to using ConfBridge, for a number of reasons. I’ve not been able to get it working properly. What seems to be happening is that ChanSpy is only active when far end audio is detected on the channel. When there is audio from the called party, suddenly ChanSpy will work and I will hear the audio file, and as soon as far end audio goes silent again, so does ChanSpy. I’m trying to use ChanSpy to inject audio into the channel, with no regard to whether there is audio present there already or not.

I’ve tested this extensively for 6 hours on and off at this point, and this much is clear:

  • Changing the ChanSpy configuration does not seem to help. I have used the qw, qW, qWB, qB, qo, and even without the q, etc. options. No ChanSpy options make the application work properly
  • The audio is playing... "somewhere". Not in the desired channel, except, of course, only during times when there is far end audio, and then I can hear it. The audio plays and loops as intended which I can see in the CLI, and during times when it is audible, it is nice and loud, and as soon as far end audio drops again, audio disappears.
  • Calling party audio presence makes no difference. Only called party audio presence does.
  • ChanSpy in general seems to work. I use ChanSpy for more "normal" applications and when I test trying to barge into a call, that seems to work.
  • It does not have to do with what is being injected (that is, the properties of the audio file).
  • Whether I connect ChanSpy to a local dialplan context or directly to a Playback using a call file, the result is the same.
  • Adding an additional Local dial with the /n modifier does not help. (Usually, doing this tends to fix weird issues like this one)
  • The actual channel name is something like Local/....;2. I tried spying on Local/....;1 instead (with everything else the same), and then there was never any audio audible at all.
  • For testing purposes, even manually changing the spied on channel, for instance from the actual Local channel to the SIP peer that's way up the channel stack that spawned it, does not fix the issue.
  • I tested it the other way, on incoming calls instead of outgoing calls, and the same issue persists.

It seems like something is broken somewhere, but I’ve been unable to find a workaround so far. Is this in any way expected behavior of the ChanSpy application? Is there anything I might change in my approach to make it work for my application? It’s unclear to me if this is a bug or deliberate behavior that could be removed by modified app_chanspy.c or something of the like. Is there some property of ChanSpy that makes it inactive if it doesn’t detect certain audio properties on the spied on channel? I looked at the source code but nothing stood out to me. I am using Asterisk 13.38.

There are multiple Stack Overflow questions asking how to do precisely this type of thing (inject audio into a channel), and multiple accepted answers recommend ChanSpy with qw or similar options, so I’m not alone in this approach and thus puzzled why it seems to be failing me.

More background: In theory, I could use a Confbridge, but if I did that, I would need to write all of the channel variables into global variables, then write them back into the call in the new outgoing leg, and then I would need to bring the actual outbound call into the bridge with the G option, fork the call upon answer, drop the called party into the bridge directly, kick the calling party out of the bridge, Answer() the call in the dialplan, and then drop the party back into the bridge (and even then, this only works because I have modified app_confbridge.c to not automatically supervise, or otherwise this would, in fact, not work at all since it would be impossible to provide accurate answer supervision). On top of that, when all this is done, it’s a confbridge, so the audio will never be as good as if the legs were directly connected to each other. Additionally, I have done similar things in the past, and it’s a high load and thus a very easy way to crash Asterisk and the server to the point where it requires a hard reboot. Since I’m only trying to put audio into the channel in one direction, ChanSpy seems like a far more sane, resource-friendly, and practical option (if I can get it to work).

ChanSpy fundamentally requires media flow to operate. The API it is based on is from a time before Asterisk had any timing mechanism, so it relied on the media flow to be able to inject itself. There is an open issue[1] to look at changing this but noone has worked on it as of yet.

[1] issues.asterisk.org/jira/browse/ASTERISK-24397

Thank you for the information! I was beginning to think I was going insane, trying over and over to get that working. This is unfortunate to hear but now it makes sense.

Realistically, do you think that issue will be worked on or addressed in the future? I would love to help but unfortunately I’m not that great with the codebase. I’ve only made very minor tweaks to Asterisk before, like removing Confbridge automatic supervision.

If not, is there any alternative you would suggest, or am I more or less screwed unless this gets fixed? Maybe I can try to look into the issue then.

I’m wondering if you have tried:
It is ‘no’ by default but, if you currently have it set to ‘yes’, try disabling it.

Precisely what configuration is this pertinent to? I can’t recall seeing that anywhere.

I just greped my directory and I see that in some PJSIP conf files, but I don’t use PJSIP and I’d need something that is channel independent - e.g. SIP, IAX2, Local, etc.

If there are other workarounds though, I am eager and desperate for anything at this point. Thank you!

I don’t see it getting attention anytime soon. A flow of media is generally constant, so the impact to the vast majority of the user base is non-existent. I don’t have a suggestion on injecting media.

The issue is not a simple one, it would require a fundamental rewrite of the underlying API that ChanSpy uses.

Is there a difference here by what you mean by “flow of media” and audio present in the call by any chance?

I’m just wondering if there might be any other way around it, like some hack that forces a constant flow of media. At least one part of the channel will always be a local channel - I don’t know if that makes any difference here. Or is there no way to control this?

One idea that comes to mind is instead of doing a direct IAX2 call, perhaps nest in yet another local channel and somehow guarantee a constant media flow. Except, when I tried doing that, the result was ChanSpy failed to work at all rather than only when there was audio.

If there isn’t a way to do that, do you think I would be better off:

  • trying to use Audiohooks to replicate ChanSpy but without this bug
  • putting a Bug Bounty on this issue to get it fixed? This is for non-commercial/non-profit hobbyist projects, though, so this might not be any more practical.

This is all assuming ChanSpy is the only way to have multiple audio sources audible in a channel without using some kind of bridge like Confbridge (which I believe is the case here).

As you point out, it probably has a minimal impact to the majority of users who don’t use Asterisk for things that aren’t by the book as intended, but as I see it, it would unlock HUGE opportunities and it would really be a huge step forward in what is possible with the software, and I think a lot of people would really benefit from it.

Thank you!

By flow of media I mean a constant stream of audio frames which contain audio, that ChanSpy can inject itself into. There’s nothing inherently in things to force this really except when connected to a ConfBridge which provides a constant stream.

ChanSpy uses audiohooks, the issue is in audiohooks themselves. Writing something new using audiohooks won’t change this. Audiohooks has to be rearchitected.

Wait a minute, so if I ChanSpied a confbridge into a channel, would that work, or would I have to be ChanSpying something into a confbridge (the other way around) in order to guarantee a constant stream of audio frames?

The channel being spied upon is the one that has to receive a constant stream of audio frames with content in order to be injected into. If that channel is connected to a ConfBridge it would work.

Gotcha, thanks for all the clarifications! Of course, if I had a Confbridge in the first place, I wouldn’t need ChanSpy at all, that’s kind of what I was trying to avoid but now I understand what’s going on. Would you still say it’s more “efficient” though to use ChanSpy to bridge in audio than something like Confbridge. My experience has been the quality of ChanSpy tends to be better as well as the system load lighter, but I’m not sure if that’s right or why that would be. Is that only because ChanSpy is only active when there are audio frames?
Also, there’s no way to pass supervision through a bridge, so I’d need to dump the caller, Answer(), and re-enter the bridge, right?

I checked the asterisk-dev and asterisk-bug mailing lists but I don’t see any bug bounties recently. Are those still a thing really or are people not taking those anymore? Just wondering if anyone would be willing to fix this for one.

It depends on usage patterns. ChanSpy is likely cheaper for the specific case. I haven’t investigated, compared, or done anything in that area as noone has really brought it up or cared.

I don’t know what “pass supervision through a bridge” means. ConfBridge itself always answers the channel.

As for bounties, the process is documented on the wiki[1].

[1] https://wiki.asterisk.org/wiki/display/AST/Asterisk+Bug+Bounties

Sorry, I forgot to mention I patched app_confbridge so Confbridge does not answer on its own. This proved to be necessary for a number of things where answer supervision was undesired. Commenting out two lines in the source file fixed the issue, since to my knowledge there is no way to “noanswer” a bridge as there is with Playback. In this case, I wouldn’t want an outgoing call to be answered until the called party answers (if at all), so dropping someone into a bridge and having it answer would not work - but anyways, I have fixed that issue.

One major issue I have with bridges is that you can tell you’re in a bridge just by the way it sounds, and if I have all my calls go through a Confbridge, obviously dtmf passthrough and so on need to be enabled so it would be quite a CPU load and there will probably be a good amount of feedback as well. I know I could do that and it work “OK”.

I have looked that page but when I see the mailing list, I don’t really see any bounties being posted, and the few I do see seem to get repeatedly posted with no interest from devs, even at $2k, so it seems like that may no longer be feasible. Do you have any insight on this?

Not really, we just have the policy of how to provide bug bounties. What they’re for, who actually takes them up, etc, are all out of our hands and sometimes we have no visibility as things can be done off list.

Personally I suspect that the off-nominal use cases of companies and other things have diminished, as functionality has increased and we’ve given ways to do things in a more friendly fashion externally. Of course there are still unique use cases, such as yours.

OK, thanks for the information.

I’ve been looking at some of the source code more in detail. I looked at the audiohooks include but it seems that’s just an API that ChanSpy (and probably other apps) fulfill. Would it be chanspy.c that would need to be fixed or any/other files?

In particular, this seemed related to some of the things you brought up:

/* Note: it is very important that the ast_waitfor() be the first
	   condition in this expression, so that if we wait for some period
	   of time before receiving a frame from our spying channel, we check
	   for hangup on the spied-on channel _after_ knowing that a frame
	   has arrived, since the spied-on channel could have gone away while
	   we were waiting
	while (ast_waitfor(chan, -1) > -1 && csth.spy_audiohook.status == AST_AUDIOHOOK_STATUS_RUNNING) {
		if (!(f = ast_read(chan)) || ast_check_hangup(chan)) {
			running = -1;
			if (f) {

Two questions:

  1. It says this makes the code wait for a frame from the “spying channel”. Is that a typo? Should that be the “spyee channel” instead? (Thought the spying channel had nothing to do with waiting for frames)

  2. The second part of the comment makes it seem like it’s waiting for frames, just in case the spied on channel has gone away. And, that’s important too, particularly with Local channels since it could go away. At the same time, if I didn’t want it to wait, if this code were modified to remove the wait, would there be any other adverse/unintended complications of doing this?

Sorry if this is a dumb question but just looking at this part of the source (around ln 720) makes it seem like maybe there could be a simple way around it?

That’s not the code that would need to be changed. ChanSpy just feeds frames into the audiohooks API, which is then responsible for mixing it in[1]. It’s not involved in any way in the actual spying/whispering. This is called by the core when audio frames pass through a channel. Modifying the code I reference isn’t even really the problem, but fundamentally when/how it is called by the core[2]. The code is only called when media flows, so now you’d have to rearchitect/change the core channel mechanism such that it was called at a regular interval - while taking into account the fact that a frame may or may not be present.

[1] https://github.com/asterisk/asterisk/blob/master/main/audiohook.c#L933
[2] https://github.com/asterisk/asterisk/blob/master/main/channel.c#L5255

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.