Frequenzanalyse mit ALSA und FFTW (FFT) unter Linux

Gast #1547557
Lesenswert?

Hallo,

ich habe als Schulprojekt die Aufgabe eine Frequenzanalyse in "Echtzeit" 
am Eingang der Soundkarte zu machen. Für das Projekt soll ich unter 
Linux die ALSA Lib. und die FFTW3 Lib. nehmen. Ich hab auch ein HowTo 
für den Soundkartenzugriff gefunden, nur leider hab ich jetzt keine 
Ahnung wie ich den "char * buffer" an die FFTW übergeben kann und wie 
ich diesen char* buffer in einen Realteil wandle....

Hier das HowTo mit "Read Interleaved" snd_pcm_readi()

/*

This example reads from the default PCM device
and writes to standard output for 5 seconds of data.

*/

/* Use the newer ALSA API */
#define ALSA_PCM_NEW_HW_PARAMS_API

#include <alsa/asoundlib.h>

int main() {
  long loops;
  int rc;
  int size;
  snd_pcm_t *handle;
  snd_pcm_hw_params_t *params;
  unsigned int val;
  int dir;
  snd_pcm_uframes_t frames;
  char *buffer;

  /* Open PCM device for recording (capture). */
  rc = snd_pcm_open(&handle, "default",
                    SND_PCM_STREAM_CAPTURE, 0);
  if (rc < 0) {
    fprintf(stderr,
            "unable to open pcm device: %s\n",
            snd_strerror(rc));
    exit(1);
  }

  /* Allocate a hardware parameters object. */
  snd_pcm_hw_params_alloca(&params);

  /* Fill it in with default values. */
  snd_pcm_hw_params_any(handle, params);

  /* Set the desired hardware parameters. */

  /* Interleaved mode */
  snd_pcm_hw_params_set_access(handle, params,
                      SND_PCM_ACCESS_RW_INTERLEAVED);

  /* Signed 16-bit little-endian format */
  snd_pcm_hw_params_set_format(handle, params,
                              SND_PCM_FORMAT_S16_LE);

  /* Two channels (stereo) */
  snd_pcm_hw_params_set_channels(handle, params, 2);

  /* 44100 bits/second sampling rate (CD quality) */
  val = 44100;
  snd_pcm_hw_params_set_rate_near(handle, params,
                                  &val, &dir);

  /* Set period size to 32 frames. */
  frames = 32;
  snd_pcm_hw_params_set_period_size_near(handle,
                              params, &frames, &dir);

  /* Write the parameters to the driver */
  rc = snd_pcm_hw_params(handle, params);
  if (rc < 0) {
    fprintf(stderr,
            "unable to set hw parameters: %s\n",
            snd_strerror(rc));
    exit(1);
  }

  /* Use a buffer large enough to hold one period */
  snd_pcm_hw_params_get_period_size(params,
                                      &frames, &dir);
  size = frames * 4; /* 2 bytes/sample, 2 channels */
  buffer = (char *) malloc(size);

  /* We want to loop for 5 seconds */
  snd_pcm_hw_params_get_period_time(params,
                                         &val, &dir);
  loops = 5000000 / val;

  while (loops > 0) {
    loops--;
    rc = snd_pcm_readi(handle, buffer, frames); //wie an FFT???
    if (rc == -EPIPE) {
      /* EPIPE means overrun */
      fprintf(stderr, "overrun occurred\n");
      snd_pcm_prepare(handle);
    } else if (rc < 0) {
      fprintf(stderr,
              "error from read: %s\n",
              snd_strerror(rc));
    } else if (rc != (int)frames) {
      fprintf(stderr, "short read, read %d frames\n", rc);
    }
    //rc = write(1, buffer, size); //kann man bei mir weglassen
    //if (rc != size)
    //  fprintf(stderr,
    //          "short write: wrote %d bytes\n", rc);
  }

  snd_pcm_drain(handle);
  snd_pcm_close(handle);
  free(buffer);

  return 0;
}


Gruß und Danke
Markus
Gast #1547945
Lesenswert?

Hi,

das mit der rfftw hab ich schon im Manual gefunden ,nur wie übergebe ich
die Daten aus der "While-Schleife" an die FFT.

rc = snd_pcm_readi(handle, buffer, frames);

Ich muss doch den Inhalt jedes einzelnen Samples in eine double Zahl 
umwandeln um dann ein Array mit N=16384 Werten zu füllen.... Oder liege 
ich da falsch?

Gruß
Markus
Gast #1548081
Lesenswert?

Leider nein,

das mit dem Plan hab ich noch nicht verstanden, genauso wenig wie das 
mit
der Wandlung von char* auf double genau funktioniert. Ich müsste ja bei 
einem char Zeiger (4Byte denk ich) irgendwie jeweils 16bit pro Sample in 
double wandeln...

Gruß
Markus
Gast #1548446
Lesenswert?

> das mit dem Plan hab ich noch nicht verstanden, genauso wenig wie das
> mit der Wandlung von char* auf double genau funktioniert. Ich müsste ja
> bei einem char Zeiger (4Byte denk ich) irgendwie jeweils 16bit pro
> Sample in double wandeln...

Wie groß ein Zeiger auf char ist, ist für diese Wandlung aber mal sowas 
von überhaupt nicht relevant...
Du mußt nicht den Zeiger, sondern das, worauf er zeigt, in double 
wandeln. Allerdings zeigt er noch auf den falschen Typ. Um jetzt ein 
Array aus char, in dem aber 16-Bit-Werte stehen, als eben eins aus 
16-Bit-Werten interpretieren zu können, ist es am einfachsten, den 
Zeiger erstmal in einen passenden Typ zu wandeln:

int16_t* samples = (int16_t*)buffer;

Nun können wir da einfach in einer Schleife den Zielpuffer befüllen:

double* double_samples = malloc(sizeof(double) * sampleanzahl);
for (int i = 0; i < sampleanzahl; i++)
{
    double_samples[i] = samples[i] / 32768.0;
}

Ich nehme mal an, daß ein Wertebereich von +/- 1 erwartet wird, deshalb 
wird durch 32768.0 geteilt. Ich kenne FFTW nicht und weiß daher nicht, 
ob das so richtig ist.
Gast #1548501
Lesenswert?

Hallo,

und danke für die Antwort.
Meine Verständnissfrage mit dem Zeiger hat folgenden Hintergrund.

Wenn ich 5 Sek. Stereo aufnehme, dann sind das laut ls -l im RAW-Format 
882688 Bytes. Das sind in der While-Schleife 6896 Durchgänge mit einem 
Frame von 32 und 4Byte char* genau 882688 Bytes.
Jetzt müsste ja alle 2Byte(16bit) der linke und dann der rechte Kanal
im Speicher stehen....nur wie steht jetzt das Frame genau in Verbindung
mit dem Buffer?

Ich würde ja gerne immer nur ein Sample des linken Kanal mit 16bit aus 
der Schleife in das "double Array" schreiben.

Gruß
Markus
Gast #1548584
Lesenswert?

> Wenn ich 5 Sek. Stereo aufnehme, dann sind das laut ls -l im RAW-Format
> 882688 Bytes.

Das paßt von der Größe her.
882688  2 Kanäle  2 BytesProSample / 44100 SamplesProSekunde = 
5,00390022676 Sekunden

> Das sind in der While-Schleife 6896 Durchgänge mit einem Frame von 32
> und 4Byte char* genau 882688 Bytes.

Was hast du hier schon wieder mit der Größe eines char*? Die 4 Bytes 
kommen zustande, weil du 2 Kanäle mit jeweils 2 Bytes pro Sample hast.

> Jetzt müsste ja alle 2Byte(16bit) der linke und dann der rechte Kanal
> im Speicher stehen....nur wie steht jetzt das Frame genau in Verbindung
> mit dem Buffer?

Da du

> /* Interleaved mode */

eingestellt hast, kommt immer wechselweise jeweils ein Sample vom linken 
und eins vom rechten Kanal.

> Ich würde ja gerne immer nur ein Sample des linken Kanal mit 16bit aus
> der Schleife in das "double Array" schreiben.

Warum nimmst du es dann überhaupt in Stereo auf? In Mono ergibt sich die 
Frage gar nicht erst.
Gast #1548637
Lesenswert?

Wenn ich set_channel() auf 1 stelle und nach get_period_size() frames*2 
antsatt 4 nehme, kommt bei meiner While-Schleife  aber folgendes 
zustande.

Durchgänge:3444
frames:64 (warum?)
buffer:4 Byte

Ergebnis:3444*64*4=881664 Byte
Das würde wieder einer Größe von Stereo entsprechen??
Aufgenommen wird aber nur die Hälfte 440960 Bytes und ich weiß nicht wo 
plötzlich die 64 bei "frames" herkommt (es werden ja auch mit 
write(1,buffer,size)nur 64Byte geschrieben).
Gast #1548876
Lesenswert?

Ok erster Versuch,

ich habe folgendes in der While-Schleife ergänzt um zu sehen was im 
Speicher steht.

rc= snd_pcm_readi(handle,buffer,frames);

short *samples = (short*)buffer;
for (int i=0;i < 128;i++)
{
    cout << *samples << endl;
    samples++;
}


Es werden jetzt Werte zwischen -32768…+32767 ausgegeben.
Ist das bis dahin richtig?

Gruß
Markus

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren