Forum: PC-Programmierung Frequenzanalyse mit ALSA und FFTW (FFT) unter Linux


von Markus (Gast)


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

von Daniel V. (volte)


Lesenswert?

Markus schrieb:
> Hallo,
>
> ich habe als Schulprojekt ??????????????

macht man das jetz schon in der Mittelschule? na wow

von Markus (Gast)


Lesenswert?

Neee,

aber in der Techniker-Schule ;-))

Gruß
Markus

von zwieblum (Gast)


Lesenswert?

FFTW3 liefert auch RFFTW, das macht alles mit Real, ohne Komplexanteil.
Schau mal da, da ist ein Codebeispiel 
http://jgt.akpeters.com/papers/Stam01/ :-)

von Markus (Gast)


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

von zwieblum (Gast)


Lesenswert?

Du liegst richtig. Kannst aber auch float nehmen, wenn ich mich nicht 
irre, aber das steht im Manual.

von zwieblum (Gast)


Lesenswert?

Nimm aber in-place-conversion. Den Teil mit Plan erzeugen hast du auch 
schon gefunden, denk' ich mal.

von Markus (Gast)


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

von zwieblum (Gast)


Lesenswert?

Erst mal C lernen wäre gut: 
http://www.amazon.de/Programming-Language-Prentice-Hall-Software/dp/0131103628

Und dann das Manual von FFTW reinziehen - oder den Beispielcode vom rfft 
Demo ...

von Markus (Gast)


Lesenswert?

Also,

mir würde mit der Umwandlung von "snd_pcm_readi(handle, buffer, frames)"
nach "double" schon geholfen :-)

Gruß
Markus

von Rolf Magnus (Gast)


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.

von Markus (Gast)


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

von Rolf Magnus (Gast)


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.

von Markus (Gast)


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

von Markus (Gast)


Lesenswert?

Habs selbst gefunden , eine Periode hat ja dann nur ein 16er Frame.

von Markus (Gast)


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

von Rolf Magnus (Gast)


Lesenswert?

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

Ja.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.