Linux pipe audio file to microphone input
Asked Answered
O

2

28

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?

Ovate answered 18/4, 2017 at 19:43 Comment(3)
@CL. Correct me if I'm wrong, but in that case it appears that the recording application is known (arecord test.raw -r 8000 -t raw). In my case, I have no control over which application will be accessing the microphone. I want any application that will be reading from the microphone to read from my file instead.Ovate
Please note that that arecord call does not specify a device name and uses the default device. (And it is always possible for a program to explicitly use a different device name.)Zymogenesis
Hi @CL., I clarified my question a bit. Now I'm needing to change the default device itself.Ovate
O
34

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!

Ovate answered 21/4, 2017 at 23:59 Comment(7)
In my case streamed output gets distorted, I seems data goes very fast, any help ???Slily
fiddling with Alexa - I just added sleep 1; after the cat in while because it couldnt keep up with all the data. With that it works perfectly thanks.Aguiar
You're a boss !!! Saved me hours of poking around with alsa/pulseaudio. Just a little tip, instead of cat I use ffmpeg like so : ffmpeg -re -i input.mp3 -f s16le -ar 16000 -ac 1 - > /home/spiq/virtmicJordanna
Thanks @sebpiq, that's a pretty crucial detail that makes everything work smoothly!Dorita
It seems like the pulseaudio module-pipe-source ignores the rate parameter when it comes to how fast it reads data from the pipe and just reads as fast as it can. cat will write as fast as it can, so what you end up with is a 16 kHz source that is running about 1000x faster than it should be. It's necessary to pace the writes to pipe with something like ffmpeg.Ambrosius
I use RPI as Headset for my phone and I tried your steps to create virtual microphone but I can't record audio in my phone from the piped on RPi. What's wrong with that?Foolhardy
Worked great. Couple of tweaks for my use case which other people may find helpful. Add a loopback so I you can hear the music. ``` # Add loopback pactl load-module module-loopback latency_msec=1 # Remove loopback pactl unload-module module-loopback ``` Move the uninstall script into an exit trap, so you just have to Ctrl+C and it will restore the normal audio settings. clean_up () { ARG=$? pactl unload-module module-pipe-source pactl unload-module module-loopback rm /home/ajanuary/.config/pulse/client.conf exit $ARG } trap clean_up EXIT Intersection
B
4
  1. Make sure you have PulseAudio Volume Control (pavucontrol) installed. if not, you can install it simply typing in your terminal: sudo apt install pavucontrol.

Recording Tab

  1. Open PulseAudio Volume Control (pavucontrol) and go to "Recording" tab. When you don't have any devices recording your microphone, this panel should look empty. As soon as any application start recording or start Skype call, this panel should have at least one entry.

Recording Tab - Active

  1. Selecting "Built-in Audio Analog Stereo" is the default behavior (redirect your voice to microphone output). You should also see "Monitor of Built-in Audio Analog Stereo". when you select this option, the output any application that plays audio (e.g VLC) should be redirected to the microphone instead of your voice.

  2. Make sure to change it back to "Built-in Audio Analog Stereo" after the audio is finished playing for everything to go back to normal.

Blouse answered 10/11, 2020 at 15:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.