* [PoC] ahwmon: alsa "hw" what-you-hear recorder
@ 2016-01-12 2:21 Sergey
2016-01-21 11:57 ` Takashi Iwai
0 siblings, 1 reply; 2+ messages in thread
From: Sergey @ 2016-01-12 2:21 UTC (permalink / raw)
To: alsa-devel
[-- Attachment #1: Type: text/plain, Size: 1257 bytes --]
Hello.
I needed a tool to test if anything loud is playing right now. So I wrote
"ahwmon" - alsa hw monitoring tool. It's based on dmix idea: it gets "hw"
shared playback buffer, but reads from the buffer instead of writing.
Code is mostly copied from alsa-lib, but it uses kernel /dev/snd/pcm*
files directly and doesn't depend on alsa-lib or dmix. May work for
other apps playing to "hw", including pulseaudio and jackd.
What do you think about it? Can something like this get into alsa-lib or
alsa-utils or alsa-plugins? It could be a capture side of "dmix" plugin,
which has no capture now. Maybe someone with strong dmix knowledge
can port it to dmix?
Build:
gcc -o ahwmon ahwmon.c
Usage:
./ahwmon [-v] [-v] <card_index> <device_index> [<subdevice_index>]
subdevice 0 is used by default
it outputs .wav stream to stdout
Use cases:
* Card VUmeter:
./ahwmon 0 0 | aplay -V stereo -D null
* Dump what you hear:
./ahwmon 0 0 > audiodump.wav
* Duplicate output of one card to another:
./ahwmon 0 0 | aplay -D plughw:1,0
* Stream what you hear to another pc:
./ahwmon 0 0 | nc 192.168.1.10 12345
Known limitations:
* supports S16_LE and S32_LE only.
* breaks clients attempting to close and reopen "hw"
Any ideas are welcome!
--
Sergey
[-- Attachment #2: ahwmon.c --]
[-- Type: application/octet-stream, Size: 5896 bytes --]
// based on [alsa-lib]/src/pcm/pcm_mmap.c and pcm_hw.c
// known issues: breaks clients attempting to close and reopen "hw"
// build: gcc -o ahwmon ahwmon.c
// usage: ./ahwmon 0 0 > audiodump.wav
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sound/asound.h>
#include <assert.h>
size_t page_align(size_t size)
{
size_t r;
long psz = sysconf(_SC_PAGE_SIZE);
assert(psz > 0);
r = size % psz;
if (r)
return size + psz - r;
return size;
}
void checkerrno(const char* msg, int ok)
{
if (!ok) {
int result = errno;
fprintf(stderr, "ERROR: %s: %s(%d)\n", msg, strerror(result), result);
exit(result);
}
}
int main(int argc, char *argv[])
{
int verbose = 0, raw = 0, printusage = 0, opt;
while ((opt = getopt(argc, argv, "vr")) != -1) {
switch (opt) {
case 'v': verbose++; break;
case 'r': raw++; break;
default: printusage++;
}
}
if (optind+2 > argc || printusage) {
fprintf(stderr, "usage: ahwmon [-v] [-v] cardnum devnum [subdevnum]\n");
fprintf(stderr, "example: ahwmon 0 0 > audiodump.wav\n");
return 1;
}
int card = atoi(argv[0+optind]);
int device = atoi(argv[1+optind]);
int subdevice = 2+optind<argc ? atoi(argv[2+optind]) : 0;
char buf[64];
int bits = 0, rate = 0, channels = 0, buffer_size = 0;
sprintf(buf, "/proc/asound/card%d/pcm%up/sub%u/hw_params", card, device, subdevice);
if (verbose>1) fprintf(stderr, "(debug: opening \"%s\")\n", buf);
FILE *hwf = fopen(buf, "r");
checkerrno("fopen(hw_params)", hwf != NULL);
while (fscanf(hwf, "%[^ :\n] : ", buf) > 0) {
if (verbose>1) fprintf(stderr, "(debug: parsing \"%s\")\n", buf);
if (!strcmp(buf, "access")) {
if (fscanf(hwf, "%s", buf) < 1 || strcmp(buf, "MMAP_INTERLEAVED")) {
fprintf(stderr, "ERROR: only MMAP_INTERLEAVED access is supported\n");
return 2;
}
}
else if (!strcmp(buf, "format")) {
if (fscanf(hwf, "%s", buf) == 1) {
if (!strcmp(buf, "S16_LE")) bits = 16;
else if (!strcmp(buf, "S32_LE")) bits = 32;
else {
fprintf(stderr, "ERROR: unknown format '%s'\n", buf);
return 2;
}
}
}
else if (!strcmp(buf, "rate"))
fscanf(hwf, "%d", &rate);
else if (!strcmp(buf, "channels"))
fscanf(hwf, "%d", &channels);
else if (!strcmp(buf, "buffer_size"))
fscanf(hwf, "%d", &buffer_size);
fscanf(hwf, "%*[^\n\r]"); fscanf(hwf, "%*[\n\r]"); // skip everything up to end of line
}
checkerrno("fclose(hw_params)", fclose(hwf) == 0);
if (!bits && !rate && !channels && !buffer_size) {
fprintf(stderr, "ERROR: can't find hw_params of existing stream. Nobody's playing?\n");
return 3;
}
if (!bits || !rate || !channels || !buffer_size) {
fprintf(stderr, "ERROR: can't find playback rate (%d), bits (%d), channels (%d) or buffer_size (%d)\n", rate, bits, channels, buffer_size);
return 2;
}
sprintf(buf, "/dev/snd/controlC%d", card);
int ctlfd = open(buf, O_RDWR);
checkerrno("open(ctl)", ctlfd != -1);
checkerrno("ioctl(subdev)", ioctl(ctlfd, SNDRV_CTL_IOCTL_PCM_PREFER_SUBDEVICE, &subdevice) == 0);
sprintf(buf, "/dev/snd/pcmC%dD%dp", card, device);
int pcmfd = open(buf, O_RDWR|O_APPEND|O_NONBLOCK);
checkerrno("open(pcm)", pcmfd != -1);
checkerrno("close(ctl)", close(ctlfd) == 0);
if (verbose) {
fprintf(stderr, "Recording from card %d device %u subdevice %u\n",
card, device, subdevice);
fprintf(stderr, "%s %d Hz %d-bit %dch stream (buffer_size=%d)\n",
(raw ? "Raw" : "Wav"), rate, bits, channels, buffer_size);
}
void *ptr = mmap(NULL, page_align(sizeof(struct snd_pcm_mmap_status)),
PROT_READ, MAP_FILE|MAP_SHARED,
pcmfd, SNDRV_PCM_MMAP_OFFSET_STATUS);
checkerrno("mmap(status)", ptr != MAP_FAILED);
// TODO: use SNDRV_PCM_IOCTL_SYNC_PTR if mmap fails
volatile struct snd_pcm_mmap_status *status = (struct snd_pcm_mmap_status*)ptr;
int framesize = channels*bits/8;
int byterate = framesize*rate;
ptr = mmap(NULL, page_align(buffer_size*framesize),
PROT_READ, MAP_FILE|MAP_SHARED,
pcmfd, SNDRV_PCM_MMAP_OFFSET_DATA);
checkerrno("mmap(data)", ptr != MAP_FAILED);
volatile char *bufptr = (char*)ptr;
if (!raw) {
fwrite("RIFF\x24\0\0\x80WAVEfmt \x10\0\0\0\x01\0", 22, 1, stdout);
#define OUTC(x) fputc(x, stdout)
OUTC(0xff&channels); OUTC(channels >> 8);
OUTC(0xff&rate); OUTC(0xff&(rate>>8)); OUTC(0xff&(rate>>16)); OUTC(0xff&(rate>>24));
OUTC(0xff&byterate); OUTC(0xff&(byterate>>8)); OUTC(0xff&(byterate>>16)); OUTC(0xff&(byterate>>24));
OUTC(0xff&framesize); OUTC(framesize >> 8);
OUTC(0xff&bits); OUTC(bits >> 8);
#undef OUTC
fwrite("data\0\0\0\x80", 8, 1, stdout);
}
snd_pcm_uframes_t recorded_ptr = -1;
while(status->hw_ptr && status->state == SNDRV_PCM_STATE_RUNNING) { // ??? always true?
snd_pcm_uframes_t next_ptr = status->hw_ptr + buffer_size/3;
if (recorded_ptr < next_ptr) {
int bufstart = recorded_ptr % buffer_size, bufend = next_ptr % buffer_size;
if (verbose>2)
fprintf(stderr, "state = %d, hw_ptr = %d, (%d-%d) \r",
(int)status->state, (int)status->hw_ptr, bufstart, bufend);
if (bufstart < bufend)
fwrite(bufptr + bufstart*framesize, (bufend-bufstart)*framesize, 1, stdout);
else if (bufstart > bufend) {
fwrite(bufptr + bufstart*framesize, (buffer_size-bufstart)*framesize, 1, stdout);
fwrite(bufptr, bufend*framesize, 1, stdout);
}
}
recorded_ptr = next_ptr;
usleep(1000000LL*(buffer_size/8)/rate);
}
checkerrno("close(pcm)", close(pcmfd) == 0);
return 0;
}
[-- Attachment #3: Type: text/plain, Size: 0 bytes --]
^ permalink raw reply [flat|nested] 2+ messages in thread* Re: [PoC] ahwmon: alsa "hw" what-you-hear recorder
2016-01-12 2:21 [PoC] ahwmon: alsa "hw" what-you-hear recorder Sergey
@ 2016-01-21 11:57 ` Takashi Iwai
0 siblings, 0 replies; 2+ messages in thread
From: Takashi Iwai @ 2016-01-21 11:57 UTC (permalink / raw)
To: Sergey; +Cc: alsa-devel
On Tue, 12 Jan 2016 03:21:16 +0100,
Sergey wrote:
>
> Hello.
>
> I needed a tool to test if anything loud is playing right now. So I wrote
> "ahwmon" - alsa hw monitoring tool. It's based on dmix idea: it gets "hw"
> shared playback buffer, but reads from the buffer instead of writing.
>
> Code is mostly copied from alsa-lib, but it uses kernel /dev/snd/pcm*
> files directly and doesn't depend on alsa-lib or dmix. May work for
> other apps playing to "hw", including pulseaudio and jackd.
>
> What do you think about it? Can something like this get into alsa-lib or
> alsa-utils or alsa-plugins? It could be a capture side of "dmix" plugin,
> which has no capture now. Maybe someone with strong dmix knowledge
> can port it to dmix?
This is an interesting idea. I like the simplicity of your method,
despite the known limitations. I suppose it can be well implemented
as a normal alsa-lib plugin?
thanks,
Takashi
>
> Build:
> gcc -o ahwmon ahwmon.c
>
> Usage:
> ./ahwmon [-v] [-v] <card_index> <device_index> [<subdevice_index>]
> subdevice 0 is used by default
> it outputs .wav stream to stdout
>
> Use cases:
> * Card VUmeter:
> ./ahwmon 0 0 | aplay -V stereo -D null
> * Dump what you hear:
> ./ahwmon 0 0 > audiodump.wav
> * Duplicate output of one card to another:
> ./ahwmon 0 0 | aplay -D plughw:1,0
> * Stream what you hear to another pc:
> ./ahwmon 0 0 | nc 192.168.1.10 12345
>
> Known limitations:
> * supports S16_LE and S32_LE only.
> * breaks clients attempting to close and reopen "hw"
>
> Any ideas are welcome!
> --
> Sergey
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2016-01-21 11:57 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2016-01-12 2:21 [PoC] ahwmon: alsa "hw" what-you-hear recorder Sergey
2016-01-21 11:57 ` Takashi Iwai
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).