Streaming Audio On-Demand

Not really a question, merely a report on an experiment.

I’ve been experimenting with feeding audio to Asterisk via FFmpeg, as an example of how to use AGI-in-AMI. The idea is that you can have an input audio file in just any format you like, and FFmpeg will convert it to something acceptable to Asterisk on-the-fly. The basic setup looks like this:

    pipe_seq += 1
    outpipe_base = os.path.join(tempdir, "%0.6d" % pipe_seq)
      # This is the name of the media file that I pass to Asterisk
      # (no extension allowed).
    outpipe_full = outpipe_base + ".gsm"
      # This is the actual name of the file, and hopefully Asterisk
      # will use this name.
    os.mkfifo(outpipe_full, mode = 0o600)
    child = subprocess.Popen \
      (
        args =
            (
                "ffmpeg", "-i", audio_file,
                "-loglevel", "quiet",
                  # quiet messages, unfortunately quiets error reports as well
                "-f", "gsm", "-acodec", "gsm", "-ar", "8000",
                "-af", ",".join(["lowpass=f=3000"] * 16),
                "-y", outpipe_full,
            ),
        stdin = subprocess.DEVNULL,
        stdout = subprocess.DEVNULL,
      )

(I tried various filter options available in my FFmpeg build, but the one above–applying a simple 3dB filter 16 times–seemed the simplest way to get decent results.)

The example dialplan context is

    [audio-player]
    exten => _X.,1,Verbose(entering audio player)
    exten => _X.,n,AGI(agi:async,audio-player)
    exten => _X.,n,Verbose(exiting audio player)
    exten => _X.,n,Hangup()

And my AMI script listens for the corresponding AsyncAGIStart events for doing its stuff. After the above setup, I tell Asterisk to stream the generated audio via an AGI-in-AMI command:

    await mgr.send_request \
      (
        action = "AGI",
        parms =
            {
                "Channel" : evt["Channel"],
                "Command" : "STREAM FILE \"%s\" \"\"" % outpipe_base,
            }
      )

Then when the script sees the AGIExecEnd event for the “STREAM FILE” command, it knows to shut things down and issue an ASYNCAGI BREAK command to resume dialplan execution.

What I found was, Asterisk registers a few complaints about being asked to read from a pipe, e.g.:

    WARNING[682534][C-00000024]: file.c:611 filehelper: File /tmp/aster-audio-player7x653mxe/000003.gsm detected to have zero size.
        --  Playing '/tmp/aster-audio-player7x653mxe/000003.gsm' (escape_digits=) (sample_offset 0) (language 'en')
    WARNING[682534][C-00000024]: format_gsm.c:105 gsm_seek: Unable to determine current position in g719 filestream 0x7fe9e4010cb0: Illegal seek
    WARNING[682534][C-00000024]: format_gsm.c:167 gsm_tell: Unable to determine offset for gsm filestream 0x7fe9e4010cb0: Illegal seek
    WARNING[682534][C-00000024]: format_gsm.c:105 gsm_seek: Unable to determine current position in g719 filestream 0x7fe9e4010cb0: Illegal seek

but otherwise it seems to work. Naturally I avoid specifying a nonzero initial offset…

Another thing is, during testing, if my FFmpeg child process crashes before producing any output, then the Asterisk channel gets stuck and cannot be hung up. I think this is because it is blocked trying to open the FIFO for reading until somebody opens it for writing. (I can do e.g. “echo >fifo-name” from a command prompt to unblock it, assuming fifo-name is still accessible.)

Anyway, that’s what I have found so far. Feel free to offer comments, up to and including “this is a really, really bad idea”. :wink:

If you are interested, the full script is here.

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