Forum: Digitale Signalverarbeitung / DSP / Machine Learning Konvertierung Audiosample 24->16 Bit, DC-Offset Problem


von Daniel P. (zwirbeljupp)


Lesenswert?

Hallo zusammen,
ich möchte die 24Bit Audiosamples, die ich von einem digitalen Mikrophon 
(SPH0645LM4H) via I2S erhalte, in ein 16Bit Sample umwandeln. Zuvor muss 
ich jedoch einen vorhandenen DC-Offset entfernen, woran ich aktuell auf 
Grund akuter Verwirrung scheitere.

Das Mikrophon hat 18 Bit Auflösung und sendet diese MSB-first als 24 
Bit, 2's complement Sample Data. Die 6 niederwertigen Bits sind also 0.
Empfangen wird das Sample auf einem PIC32 und landet in einem 32 Bit 
int. Ein typisches Sample sieht dann in Hex- bzw. Binärdarstellung so 
aus:

00F91FC0 = 0000 0000 1111 1001 0001 1111 1100 0000

Beim Entfernen des DC-Offsets möchte ich zunächst mit 32-Bit Präzision 
rechnen, bevor ich in 16 Bit konvertiere. Und hier fängt die Verwirrung 
an:
1) wenn ich die 18 Datenbits des Audiosamples in 32 Bit umwandle, muss 
ich hier zunächst einen Typecast durchführen bzw. die 
Zweierkomplementdarstellung auf 32 Bit erweitern?
2) um das DC-Offset zu entfernen (nehmen wir mal an F91FC0 ist der 
Output des Mikrophons bei vollständiger Stille), kann ich das einfach 
per Subtraktion vom Sample entfernen?

Die Daten werden später als 16-Bit 2's complement in eine WAV-Datei 
geschrieben, es wäre also vorteilhaft das Sample gleich in dieser 
Darstellung zu bearbeiten, so dass ich später nur noch per 
Shift-Operation auf mein 16-Bit Sample komme.

Danke & Gruß
Daniel

:
von Oliver S. (oliverso)


Lesenswert?

Ja und ja.

Oliver

von Daniel P. (zwirbeljupp)


Lesenswert?

Oliver S. schrieb:
> Ja und ja.
>
> Oliver

Hmm, auch wenn die Antwort nur bedingt hilfreich war hat sie mich darin 
bestätigt folgenden Testcode zu schreiben. Das fehlende Stichwort war 
hier "sign extension", also bei der Erweiterung von 24-bit auf 32-bit 
die zusätzlichen Bits mit dem höchstwertige Bit des Samples aufzufüllen. 
Das weiß Oliver S. natürlich alles und setzt es auch bei allen anderen 
als Grundwissen voraus ;-)

Dieser Code löst mein Problem:
1
int sample;
2
signed short out;
3
signed int negative;
4
5
sample = SPI1BUF;   // get sample data from I2S buffer (outputs 32 bits data)
6
7
negative = (sample & (1 << 23)) != 0; // check if MSB is set 
8
                        
9
if (negative)
10
  sample = sample | ~((1 << 24) - 1);  // extend sign bit to bits 24 - 31
11
12
sample -= 0xFF91640;    // remove DC offset (in this case 0xFF91640)
13
                        
14
out = sample >> 6;  // remove lower 6 bits (unused) and cast to 16 bit signed int (note: the two most significant bits of the 18-bit sample data get lost here!)

Da diese Operation für jedes Sample in einem Interrupt durchgeführt 
wird, würde ich die gerne so effizient wie möglich durchführen. Gibt es 
da noch Optimierungspotenzial?

Danke & Gruß
Daniel

von pittiplatsch (Gast)


Lesenswert?

Streich von
> 00F91FC0 = 0000 0000 1111 1001 0001 1111 1100 0000

einfach die 1100 0000 weg. Macht F91F.

Dann braucht du nur ein 16 bit Offset wegzurechnen.
Wenn nach dem "truncate" ueberhaupt noch eins uebrigbleibt.

von pittiplatsch (Gast)


Lesenswert?

> Gibt es da noch Optimierungspotenzial?

Wenn es MIPS-PIC ist, dann schreibs halt in Assembler.
Portabilitaet kann man auch uebertreiben.

von Maxe (Gast)


Lesenswert?

Ich wuerde das sample zunaechst auf volle 32bit bringen, also 
'linksbuendig', damit auch das Vorzeichenbit stimmt. Entweder 
8-Linksshifts oder ueber Bytezugriffe kopieren, kommt darauf an, was die 
CPU unterstuetzt (kuemmert sich aber auch der Kompiler darum).

Dann brauchst du nicht mehr auf das Vorzeichen testen. Offset verrechnen 
und dann einfach die hinteren beiden Byte 'wegschmeissen' und du hast 
den 16-Bit-Wert.

Das machts nicht unbedingt schneller, aber auf jeden Fall 
uebersichtlicher. Also kurz gesagt erstmal in eine CPU-gerechte 
Darstellung wandeln und dann damit rechnen.

von pittiplatsch (Gast)


Lesenswert?

Wenn das SPI-Interface auch "Audio Codec Serial Protocols"
unterstuetzt, kannst du das gleich linksbuendig in ein
32 bit Register schreiben.
Naeheres verraet das Datenblatt.

Wenn nicht: P.G.H.
Falschen Controller benutzt.

von Dergute W. (derguteweka)


Lesenswert?

Moin,

Daniel P. schrieb:
> 2) um das DC-Offset zu entfernen (nehmen wir mal an F91FC0 ist der
> Output des Mikrophons bei vollständiger Stille), kann ich das einfach
> per Subtraktion vom Sample entfernen?

Kannste natuerlich machen, aber wenn du das so machst, und im Datenblatt 
des Mikros steht nicht explizit drinnen: "Der Offset ist immer F91FC0", 
dann wird der auch irgendwann mal anders sein (Anderes Mikro, andere 
Temperatur, andere Schuhgroesse,...).

Da waer's wohl sinnvoller, einen Hochpass mit niedriger Grenzfrequenz 
einzubauen, der dann den Offset sicher entfernt, egal wie gross der 
grad' ist. So aehnlich wie ein Koppelkondensator in der Analogtechnik.

Gruss
WK

von Andi (Gast)


Lesenswert?

1
out = ((sample - DCOFFSET) << 8) >> 16;

DCOFFSET ist hier der vorhandene Offset als positive Zahl (24 Bits).

von i2s (Gast)


Lesenswert?

DC - Offset bei Audio entfernt man mit einem digitalen Hoghpass-Filter, 
der eine sehr sehr kleine Grenzfrequenz hat (unter 5 Hz).
Hat einer aber hier schon geschrieben. Der DC-Offset kann je nach 
Mikrofon schwanken.

Beitrag #6462055 wurde von einem Moderator gelöscht.
Beitrag #6517223 wurde von einem Moderator gelöscht.
von Daniel P. (zwirbeljupp)


Lesenswert?

Hallo zusammen,
ich würde gerne noch einmal den Vorschlag des digitalen High-Pass 
Filters aufgreifen, um das DC-Offset loszuwerden.
Verwendet wird ein PIC32MX150F128B, der mit 48 MHz getaktet ist. Es gibt 
also keine dedizierte DSP-Hardware. Da ich die Berechnung des Filters 
sowie die Konvertierung in 16-Bit Samples innerhalb eines Interrupts 
ausführen muss, sollte das ganze möglichst Effizient ablaufen.
Ein digitaler DC-Blocker kann ja bekanntlich über eine einfache 
Differenzengleichung implementiert werden:
1
  y = x - xm1 + R * ym1;
2
  xm1 = x;
3
  ym1 = y;

(z.B. https://ccrma.stanford.edu/~jos/fp/DC_Blocker.html)

Dabei sind:
1
x  - aktuelles Input-Sample 
2
y  - aktuelles Output-Sample 
3
xm1 – letztes Input-Sample 
4
ym1 – letztes Output-Sample 
5
R – Filterparameter (0.995 scheint hier ein üblicher Wert zu sein)

Mit der Information, dass vom Mikrophon nur 18 Nutzbits ausgegeben 
werden, würde ich natürlich versuchen die gesamte Berechnung in maximal 
32-Bit durchzuführen. Mein bisheriger Ansatz dazu sieht so aus:
1
int sample,y,ym1,xm1;
2
signed short out;
3
signed int negative;
4
5
sample >>= 6;  // ungenutze niederwertige Bits entfernen
6
negative = (sample & (1 << 17)) != 0;   // prüfen, ob höchstwertiges Bit gesetzt ist 
7
if (negative)  
8
      sample = sample | ~((1 << 18) - 1);  // “sign extension” für Bits 18 – 31
9
10
// mit ~0.995 multiplizieren (1019/1024 = 0.995117)
11
ym1 *= 1019;
12
ym1 >>= 10;
13
14
y = sample  xm1 + ym1;
15
xm1 = sample;
16
ym1 = y;
17
18
out = (signed short) y;  // cast zu 16-Bit integer
Was meint ihr, ist das ein sinnvoller Ansatz oder seht ihr noch 
Optimierungspotenzial?
Ich arbeite gerade noch an der Infrastruktur, um das ganze mit realen 
Daten zu testen/simulieren zu können, d.h. ich muss mich vorerst auf 
Trockenübungen beschränken. Feedback daher sehr willkommen.

Danke & Gruß
Daniel

von 900ss (900ss)


Lesenswert?

Daniel P. schrieb:
> Trockenübungen

Du kannst das doch simulieren. Füttere deine Funktion mit simulierten 
Daten und schaue ob die sich richtig verhält. Das kannst du gut auf dem 
PC machen.

: Bearbeitet durch User
von DH1AKF W. (wolfgang_kiefer) Benutzerseite


Lesenswert?

Daniel P. schrieb:
> // mit ~0.995 multiplizieren (1019/1024 = 0.995117)
> ym1 *= 1019;
> ym1 >>= 10;

Hallo Daniel,
die Multiplikation (Zeile 10..12) würde ich durch folgendes ersetzen:
ym1 -=ym1>>8;

Erklärung: 0,995 = 1 - 0,005 ~ 1 - 5/1000 ~ 1 - 4/1024 ~ 1 - 1/256 = 
0,9961

Mit dem Faktor 0,9961 machst Du auch nichts falsch, aber so geht es auch 
ohne die Multiplikation...

Beitrag #6576617 wurde vom Autor gelöscht.
von Detlef _. (detlef_a)


Lesenswert?

So gehts' auch ohne Multiplikation
x*(1019/1024) = x-x*5/1024 = x-(x*4+x)/1024 = x-((x<<2+x)>>10)

DC Blocker auch hier, Kap. 13.23
https://www.mikrocontroller.net/attachment/341426/Understanding_digital_signal_processing.pdf

Lass den Compiler die sign extension machen, der wird seinen 
Zielassembler schon kennen:
samplesignextended = ((int32_t)((uint32_t)(sample<<(31-23))))>>(31-17)

C rulez!
Cheers
Detlef

von Daniel P. (zwirbeljupp)


Angehängte Dateien:

Lesenswert?

Ich bin begeistert, vielen Dank für die Tips!

@Wolfgang, Detlef: Eure Vorschläge sind klasse, damit wird das noch ein 
ganzes Stück effizienter. Ich denke, ich werde die Variante ym1 
-=ym1>>8; weiter verfolgen, denn damit kann ich - wie weiter oben von 
Maxe bereits empfohlen - die Samples zunächst linksbündig ausrichten, 
ohne die Gefahr eines Überlaufs durch Multiplikation.

Ich habe mir ein kleine Programm geschrieben, das die Filter auf eine 
Wav-Datei anwendet und habe dazu Samples mit meiner Hardware 
aufgenommen. Test erfolgreich!
Das I²S Mikrophon (SPH0645LM4H) hat nach dem Powerup ein DC-Offset, 
welches sich über ca. 1-4 Sekunden auf sein Minimum einpendelt. Der 
high-pass Filter löst dieses Problem sehr elegant (siehe Anhang, 
oben=Originaldaten, unten=gefiltert).

Danke auch für die DSP-Lektüre, sowas habe ich schon länger gesucht!

Gruß
Daniel

von Michael W. (Gast)


Lesenswert?

i2s schrieb:
> Der DC-Offset kann je nach
> Mikrofon schwanken.

Woher kommt der überhaupt?

von Daniel P. (zwirbeljupp)


Lesenswert?

M. W. schrieb:
> Woher kommt der überhaupt?

Ich könnte mir vorstellen, dass alleine durch den Lötprozess bleibende 
mechanische Spannungen in das Sensorelement eingeprägt werden, die zu 
einem DC-Offset führen.
Sofern das Sensorelement nicht perfekt temperaturkompensiert ist, 
könnten auch thermische Einflüsse (ggf. sogar durch Eigenerwärmung) zu 
einem variablen DC-Offset führen.

von Michael W. (Gast)


Lesenswert?

Daniel P. schrieb:
> Ich könnte mir vorstellen, dass alleine durch den Lötprozess bleibende
> mechanische Spannungen in das Sensorelement eingeprägt werden, die zu
> einem DC-Offset führen.

D.h. eine einseitig verschobene "Membran" , die nach oben anders 
auslenkt, als nach unten? Sollte das nicht der Delta-Sigma-Wandler 
erledigen, weil er nur die Differenz bildet?

von Michael W. (Gast)


Lesenswert?

Gerade nochmal nachgedacht: In der Tat würde ein Sigma-Delta-Wandler 
einen Offset transportieren, sofern nicht von Haus aus eine DC-Sperre 
immplentiert ist. Das lässt sich aber beim Empfänger eigentlich recht 
einfach lösen, meine ich.

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.