Linux - Without Hardware Soundcard, Capture Audio Playback, and Record It to File

Linux - Without hardware soundcard, capture audio playback, and record it to file

Thanks to both @mjy and @Matthias I have finally managed to figure out the minimal steps to take in order to make the recording work:

sudo apt-get install pulseaudio jackd2 alsa-utils dbus-x11

No need to play with snd-dummy, no need to create any additional config files... All these things only caused me to lose few hours :( After installing these packages on a clean Ubuntu server installation, I was able to run Python script and capture output audio to a file using PyAudio...

Record Sound on Ubuntu Docker Image

EDIT: after reading first comment to BMitch about not needing to play out to local computer

Im recommanding to use pulseaudio since its userspace code and no kernel modules are needed. It would seem unlikely to me that alsa would work for docker (though I could be wrong)

If I do this:

apt-get update
apt-get install pulseaudio socat
apt-get install alsa-utils
apt-get install ffmpeg

# Start the pulseaudio server
pulseaudio -D --exit-idle-time=-1

# Load the virtual sink and set it as default
pacmd load-module module-virtual-sink sink_name=v1
pacmd set-default-sink v1

# set the monitor of v1 sink to be the default source
pacmd set-default-source v1.monitor

# Start the ffmpeg capture to an audio file
ffmpeg -f pulse -i default out.mp3

then in a separate terminal

paplay /usr/share/sounds/alsa/Front_Center.wav

The audio of the WAV file is captured by ffmpeg and appears in the out.mp3

I dont really know what your X setup looks like but if you can get the audio going to pulseaudio, then ffmpeg will capture the audio without needing a real sound card

Original Answer: if you want the audio to get to the Mac OS sound card

I originally got to this page cos I was trying to get the playback of audio from within the VM to the Mac OS. But if you dont care about getting it out the VM, then the following is overly complicated. Im leaving it here though as this idea was why i ended up here

It is possible to use pulseaudio within the VM to play back a WAV file to the physical sound card. Im using docker 17.03.1-ce on Mac OS Sierra and have used brew to install sox. This setup also requires socat to be installed on the VM but someone with more pulseaduio knowledge (or if i get more time) should be able to remove this part i think

The strategy is to use paplay to play the wav file through pulseaudio and have pulseaudio send the audio out over the network on a port you published at start up of the VM. On the Mac-side, you'll connect to this published port and send the data through sox to the Mac.

1. MAC: pull and run image with published port

    docker pull ubuntu
docker run -it --rm -p 127.0.0.1:3000:3000 ubuntu

2. VM: update and install the require packages

    apt-get update
apt-get install pulseaudio socat
apt-get install alsa-utils

Im only installing alsa-utils for the wav file, so you could remove this

3. VM: start the pulseaudio server

    pulseaudio -D --exit-idle-time=-1

This tell the server to fork to the background and not to exit based on inactivity

4. VM: create a "sink" for pulseaudio

The sink is where pulseaudio will send the audio data in a specific format:

    pacmd load-module module-pipe-sink file=/dev/audio format=s16 rate=44100 channels=2

This will send the audio to the file /dev/audio in signed 16-bit 2-channel at 44100Hz.

5. VM: Attach file to the network

This file now needs to be "attached" to the network, socat is used to create a listening socket at the published address (NOTE: there is no authentication here) that is ready to send the audio when the Mac-side connects

    socat file:/dev/audio tcp-listen:3000

6. MAC: read data from network into sox

On the Mac-side, we now need to connect to this port and send the detail, via sox, to the audio driver:

    nc 127.0.0.1 3000 | sox -traw -r44100 -b16 -c2 -e signed-integer - -d

the mac has netcat (nc) by default so Im using this here to connect to the published port and then pipe the data to sox, with flags to sox to match the values set in the "load-module" statement above.

7. VM: Play!

Finally, using paplay from the pulseaudio package the WAV file can be played:

    paplay /usr/share/sounds/alsa/Front_Center.wav

8. MAC: Listen!
On the mac side you should now here the audio and be able to see the output from sox which includes a small level meter and this should move:

    $ nc 127.0.0.1 3000 | sox -traw -r44100 -b16 -c2 -e signed-integer - -d
-: (raw)

File Size: 0
Encoding: Signed PCM
Channels: 2 @ 16-bit
Samplerate: 44100Hz
Replaygain: off
Duration: unknown

In:0.00% 00:00:06.78 [00:00:00.00] Out:299k [ | ] Clip:0

Doing the reverse (ie sending the Mac audio to pulseaudio on the VM) should also be possible with a similar, though backwards, setup (module-pipe-source?)

I haven't tested this against the commands on the ffmpeg page as I have no X server.

Linux how to record sound in RAM buffer and playback audio with custom delay

Here is a simple C program that will maintain a circular buffer between pipe in and out. Use like in | buffer_program | out. Error checking omitted. Robustness not guaranteed. Gives the general idea.

Test script (but actually since its circular buffer the data your piping in needs to be such that its coherent taking just any chunk in the stream. Or just make the buffer bigger than the data):

cat some.wav | ./circular_buffer 100000 | (sleep 1 && aplay)

circular_buffer.c:

 /**
* This program simply maintains a circular buffer of a given size indefinitely.
*/
#include <stdio.h>
#include <stddef.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h> /* C99 only */
#include <sys/select.h>
#include <errno.h>
#include <fcntl.h>

int c_read(int fd, char * buf, unsigned int size, unsigned int * head_in, unsigned int * tail_in);
int c_write(int fd, char * buf, unsigned int size, unsigned int * head_in, unsigned int * tail_in);
bool empty_buf(unsigned int head, unsigned int tail);
bool setblock(int fd, bool block);
#define FD_SET_SET(set, fd, max) FD_SET(fd, &set); max = ((fd > max) ? fd : max);
#define FD_SET_UNSET(set, fd, max) FD_CLR(fd, &set); max = ((fd == max) ? max - 1 : max); //not ideal. Do while ISFDSET...

int main(int argc, char **argv)
{
char * buf;
unsigned int buf_size = 0;
unsigned int buf_head = 0;
unsigned int buf_tail = 0;

// Check args.
if(argc != 2) {
fprintf(stderr, "Usage: %s <buffer size in bytes>\n", __FILE__);
exit(EXIT_FAILURE);
}
sscanf(argv[1], "%d", &buf_size);
buf_size = ( buf_size < 2 ) ? 2 : buf_size;

// Note the usable buffer space is buf_size-1.
fprintf(stderr, "Allocating %d\n", buf_size);
buf = (char*)malloc(buf_size);

bool done_reading = false;
int maxfd = 0;
fd_set r_set, w_set, r_tempset, w_tempset;
setblock(STDIN_FILENO, false);
setblock(STDOUT_FILENO, false);
FD_ZERO(&r_set);
FD_ZERO(&w_set);
FD_ZERO(&r_tempset);
FD_ZERO(&w_tempset);
FD_SET_SET(r_tempset, STDIN_FILENO, maxfd);
FD_SET_SET(w_tempset, STDOUT_FILENO, maxfd);
r_set = r_tempset;
while(true) {
select((maxfd + 1), &r_set, &w_set, NULL, NULL);
if(FD_ISSET(STDIN_FILENO, &r_set)) {
int c = c_read(STDIN_FILENO, buf, buf_size, &buf_head, &buf_tail);
if(c == -1) { // EOF, disable select on the input.
fprintf(stderr, "No more bytes to read\n");
done_reading = true;
FD_ZERO(&r_set);
}
}
if(!done_reading) {
r_set = r_tempset;
}
if(FD_ISSET(STDOUT_FILENO, &w_set)) {
c_write(STDOUT_FILENO, buf, buf_size, &buf_head, &buf_tail);
}
if(!empty_buf(buf_head, buf_tail)) { // Enable select on write whenever there is bytes.
w_set = w_tempset;
}
else {
FD_ZERO(&w_set);
if(done_reading) { // Finish.
fprintf(stderr, "No more bytes to write\n");
break;
}
}
}
fflush(stderr);
return 0;
}

bool empty_buf(unsigned int head, unsigned int tail) {
return head == tail;
}

/**
* Keep reading until we can read no more. Keep on pushing the tail forward as we overflow.
* Expects fd to be non blocking.
* @returns number of byte read, 0 on non stopping error, or -1 on error or EOF.
*/
int c_read(int fd, char * buf, unsigned int size, unsigned int * head_in, unsigned int * tail_in) {
fprintf(stderr, "In c_read()\n");
unsigned int head = *head_in;
unsigned int tail = *tail_in;
bool more_bytes = true;
int n = 0;
int c = 0;

while(more_bytes) {
bool in_front = tail > head;
fprintf(stderr, "Read %d %d %d\n", size, head, tail);

n = read(fd, buf+head, size - head);
if(n == -1) {
more_bytes = false;
if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { // Not EOF but the read would block.
c = 0;
}
else {
c = -1;
}
}
else if(n == 0) { // EOF. No more bytes possible.
more_bytes = false;
c = -1;
}
else if(n != (size - head)) { // if not full read adjust pointers and break.
more_bytes = false;
c += n;
head = (head+n)%size;
if(in_front && (head >= tail || head == 0)) {
tail = (head+1)%size;
}
}
else {
c = 0;
head = 0;
tail = (tail == 0) ? 1 : tail;
}
}
*head_in = head;
*tail_in = tail;
return c;
}

/**
* Try flush the buffer to fd. fd should be non blocking.
*/
int c_write(int fd, char * buf, unsigned int size, unsigned int * head_in, unsigned int * tail_in) {
fprintf(stderr, "In c_write()\n");
unsigned int head = *head_in;
unsigned int tail = *tail_in;
int n = 0;
fprintf(stderr, "Write %d %d %d\n", size, head, tail);

if(tail < head) {
n = write(fd, buf+tail, head-tail);
tail += n;
}
else if(head < tail) {
n = write(fd, buf+tail, size-tail);
if(n == size-tail) {
n = write(fd, buf, head);
tail = n;
}
}
*head_in = head;
*tail_in = tail;
return n;
}

bool setblock(int fd, bool block)
{
int flags;
flags = fcntl(fd, F_GETFL);
if (block)
flags &= ~O_NONBLOCK;
else
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
return true;
}

How to play sound in C from scratch (Linux)

The Linux kernel native audio API is ALSA (Advanced Linux Sound Architecture).

Example of raw audio playback with ALSA:
https://gist.github.com/ghedo/963382/815c98d1ba0eda1b486eb9d80d9a91a81d995283

However, ALSA is a low-level API that is not recommended to be used directly by higher-level applications.

A modern system audio API for GNU/Linux would be either PulseAudio (the current default on Ubuntu), or the newer and arguably better PipeWire (the default on Fedora).

Example of raw audio playback with PipeWire that generates audio "from scratch":

https://docs.pipewire.org/page_tutorial4.html

How do you tell the computer to play a certain note at a certain tone/frequency?

Sound is a mechanical vibration that propagates through the air (or another medium). It can be represented digitally as a sequence of numerical values representing air pressure at a given sampling rate. To play a given tone/frequency, generate a sine wave of that frequency (at the playback sampling rate) and use the sound API of your choice to play it.

See the PipeWire tutorial above for an example generating a 440Hz tone.

About PulseAudio/PipeWire:

These libraries are typically part of the OS and exposed as system APIs (so they are not "external libraries" if that means some library to ship with your program or to ask users to install), and they should be used by applications to play audio.

Behind the scene, these libraries handle audio routing, mixing, echo-canceling, recording, and playback to the kernel through ALSA (or using Bluetooth, etc). Everything that users and developers expect from the system audio layer.

Until recently, PulseAudio was the de-facto universal desktop system audio API, and many apps still use the PulseAudio API to play audio on GNU/Linux.
PipeWire includes compatibility with PulseAudio, so that apps using the PulseAudio API will keep working in the foreseeable future.

Example of raw audio playback with PulseAudio:
https://freedesktop.org/software/pulseaudio/doxygen/pacat-simple_8c-example.html

How to record audio with ffmpeg on linux?

I realise this is a bit old. Just in case anyone else is looking:

ffmpeg -f alsa -ac 2 -i default -itsoffset 00:00:00.5 -f video4linux2 -s 320x240 -r 25 -i /dev/video0 out.mpg

This way it will use the default device to record from. You were also missing a -i before the video capture device - /dev/device0

If you want to get more specific you should take a look in /proc/asound.
Check the cards, devices, pcm files and the card subdirectories. You should be able to glean enough information there to be able to make an educated guess; e.g hw:1,0 or hw:2,0

The documentation may provide further clues:

  • http://www.alsa-project.org/main/index.php/DeviceNames

The same goes for the webcam - it may not be /dev/video0, perhaps you have an external webcam plugged in and its at /dev/video1 - Have a look in the /dev directory and see whats available



Related Topics



Leave a reply



Submit