mikrocontroller.net

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


Autor: Markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Daniel V. (volte)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Markus schrieb:
> Hallo,
>
> ich habe als Schulprojekt ??????????????

macht man das jetz schon in der Mittelschule? na wow

Autor: Markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Neee,

aber in der Techniker-Schule ;-))

Gruß
Markus

Autor: zwieblum (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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/ :-)

Autor: Markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: zwieblum (Gast)
Datum:

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

Autor: zwieblum (Gast)
Datum:

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

Autor: Markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: zwieblum (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Erst mal C lernen wäre gut: 
http://www.amazon.de/Programming-Language-Prentice...

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

Autor: Markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Also,

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

Gruß
Markus

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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).

Autor: Markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Habs selbst gefunden , eine Periode hat ja dann nur ein 16er Frame.

Autor: Markus (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Rolf Magnus (Gast)
Datum:

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

Ja.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.