Linux pipe audio file to microphone input

26,524

Solution 1

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!

Solution 2

  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.

Share:
26,524

Related videos on Youtube

cheerupcharlie
Author by

cheerupcharlie

Software engineer focused on embedded systems.

Updated on November 11, 2020

Comments

  • cheerupcharlie
    cheerupcharlie over 3 years

    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?

    • cheerupcharlie
      cheerupcharlie about 7 years
      @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.
    • Wirsing
      Wirsing about 7 years
      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.)
    • cheerupcharlie
      cheerupcharlie about 7 years
      Hi @CL., I clarified my question a bit. Now I'm needing to change the default device itself.
  • humble_wolf
    humble_wolf about 6 years
    In my case streamed output gets distorted, I seems data goes very fast, any help ???
  • nayana
    nayana almost 6 years
    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.
  • sebpiq
    sebpiq over 5 years
    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/virtmic
  • UncleZeiv
    UncleZeiv over 5 years
    Thanks @sebpiq, that's a pretty crucial detail that makes everything work smoothly!
  • TrentP
    TrentP over 3 years
    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.
  • Mamdouh Saeed
    Mamdouh Saeed over 3 years
    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?