alsa-devel.alsa-project.org archive mirror
 help / color / mirror / Atom feed
* [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

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).