Linux pipe audio file to microphone input

cheerupcharlie picture cheerupcharlie · Apr 18, 2017 · Viewed 18.1k times · Source

I'm looking for a way to feed audio data from a file into the microphone so when 3rd party applications (such as arecord or Chromium's "search by voice" feature) use the microphone for audio input, they receive the audio data from the file instead.

Here's my scenario: An application I wrote records audio data from the microphone (using ALSA) and saves it to a file (audioFile0.raw). At some unknown point in time in the future, some unknown 3rd party application (as in, something I did not develop so I have no development control over, such as the Chromium web browser's "search by voice" feature) will use the microphone to obtain audio data. I would like the audio data that the 3rd party application is gathering to come from audioFile.raw rather than the actual microphone itself.

I was thinking if it were possible to change the default audio input device to an audio file, or maybe a named pipe and do something like cat audioFile0.raw > mypipe (since I don't know when another application will try to read from the microphone). Perhaps there is a more simple way of doing this?

I hope I provided enough detail and clarity. Please let me know if something is unclear.


EDIT: So I figured out how to make a virtual microphone by creating the following .asoundrc file in my home directory:

pcm.!virtmic {
    type file
    slave.pcm "hw:0,0"
    file /dev/null
    infile "/home/charles/audioFiles/audioFile0.raw"
}

pcm.!default {
    type hw
    card 0
}

ctl.!default {
    type hw
    card 0
}

I then call arecord test.raw -c 1 -f S16_LE -r 16000 -t raw -D virtmic from the command line and I'm able to record the audio data that's in audioFile0.raw to test.raw.

My goal now is to replace the default device with my virtual microphone so any application accessing the microphone will read the audio data in audioFile0.raw instead of the actual microphone itself. So I edited my .asoundrc file to appear as follows:

pcm.!virtmic {
    type file
    slave.pcm "hw:0,0"
    file /dev/null
    infile "/home/charles/audioFiles/audioFile0.raw"
}

pcm.!default {
    type asym
    playback.pcm {
        type hw
        card 0
    }
    capture.pcm {
       type plug
       slave.pcm "virtmic"
    }
}

ctl.!default {
    type hw
    card 0
}

Then I called arecord test.raw -c 1 -f S16_LE -r 16000 -t raw from the command line. I then played back test.raw but it seemed to be recording from the microphone itself and not audioFile0.raw.

What am I doing wrong? How exactly do I change the default capture device so it will read the data from audioFile0.raw rather than the input from the microphone?


EDIT 2: Okay, so I was on the right track. I'm using the same .asoundrc file in my home directory that I showed earlier where I changed the default device to be the virtmic. I needed to change the file /usr/share/alsa/alsa.conf.d/pulse.conf so it looks like this:

# PulseAudio alsa plugin configuration file to set the pulseaudio plugin as
# default output for applications using alsa when pulseaudio is running.
hook_func.pulse_load_if_running {
    lib "libasound_module_conf_pulse.so"
    func "conf_pulse_hook_load_if_running"
}

@hooks [
    {
        func pulse_load_if_running
        files [
#           "/usr/share/alsa/pulse-alsa.conf"
            "/home/charles/.asoundrc"
        ]
        errors false
    }
]

The only thing I did was comment out the line "/usr/share/alsa/pulse-alsa.conf" and replaced it with "/home/charles/.asoundrc" so the pulseaudio plugin isn't the default for applications using ALSA, but rather use my virtual mic as the default. This may not be the best solution, but it works.

This worked when I did arecord test.raw -t raw -c 1 -f S16_LE -r 16000. It got the data from audiofile0.raw instead of the microphone! I used the command lsof /dev/snd/* to see what exactly was accessing the audio device while the arecord command was running. The output was as follows:

COMMAND    PID    USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
pulseaudi 2044 charles   22u   CHR  116,6      0t0 8977 /dev/snd/controlC0
pulseaudi 2044 charles   29u   CHR  116,6      0t0 8977 /dev/snd/controlC0
arecord   4051 charles  mem    CHR  116,5          8976 /dev/snd/pcmC0D0c
arecord   4051 charles    4u   CHR  116,5      0t0 8976 /dev/snd/pcmC0D0c

I then tried using Chromium browser's "search by voice" feature and saw that I couldn't get it to record from audioFile0.raw. I then used lsof /dev/snd/* to see what exactly was accessing the audio device.

COMMAND    PID    USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
pulseaudi 2044 charles  mem    CHR  116,5          8976 /dev/snd/pcmC0D0c
pulseaudi 2044 charles   22u   CHR  116,6      0t0 8977 /dev/snd/controlC0
pulseaudi 2044 charles   29u   CHR  116,6      0t0 8977 /dev/snd/controlC0
pulseaudi 2044 charles   30r   CHR 116,33      0t0 7992 /dev/snd/timer
pulseaudi 2044 charles   31u   CHR  116,5      0t0 8976 /dev/snd/pcmC0D0c

I see that they all have the same PID, 2044. They are all using the pulseaudio daemon, not going through ALSA.

My Question: How do I get pulseaudio to use my virtual microphone by default so all applications that go through pulseaudio for audio input will instead get the audio data from my file rather than the microphone?

Answer

cheerupcharlie picture cheerupcharlie · Apr 22, 2017

After many painstaking hours, I finally got something acceptable working. I undid everything I did with ALSA (since the default is for ALSA to use PulseAudio instead, which I initially overrode). I created a simple bash script install_virtmic.sh to create a "virtual microphone" for PulseAudio to use as well as PulseAudio clients:

#!/bin/bash

# This script will create a virtual microphone for PulseAudio to use and set it as the default device.

# Load the "module-pipe-source" module to read audio data from a FIFO special file.
echo "Creating virtual microphone."
pactl load-module module-pipe-source source_name=virtmic file=/home/charles/audioFiles/virtmic format=s16le rate=16000 channels=1

# Set the virtmic as the default source device.
echo "Set the virtual microphone as the default device."
pactl set-default-source virtmic

# Create a file that will set the default source device to virtmic for all 
PulseAudio client applications.
echo "default-source = virtmic" > /home/charles/.config/pulse/client.conf

# Write the audio file to the named pipe virtmic. This will block until the named pipe is read.
echo "Writing audio file to virtual microphone."
while true; do
    cat audioFile0.raw > /home/charles/audioFiles/virtmic
done

A quick script uninstall_virtmic.sh for undoing everything done by the install script:

#!/bin/bash

# Uninstall the virtual microphone.

pactl unload-module module-pipe-source
rm /home/charles/.config/pulse/client.conf

I then fired up Chromium and clicked the microphone to use its voice search feature and it worked! I also tried with arecord test.raw -t raw -f S16_LE -c 1 -r 16000 and it worked too! It isn't perfect, because I keep writing to the named pipe virtmic in an infinite loop in the script (which quickly made my test.raw file insanely large), but it will do for now.

Please feel free to let me know if anyone out there finds a better solution!