Konvertierung Audiosample 24->16 Bit, DC-Offset Problem

OP #6410511
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
OP #6410678
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
Gast #6410885
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.
Gast #6410930
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.
Gast #6410998
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.
#6411077
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
Gast #6411817
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.
OP #6573309
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
Persönliche Seite #6576600
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 von einem Moderator gelöscht.
#6577173
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
OP #6577590
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
Angehängte Dateien:
OP #6603926
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.
Gast #6604882
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?
Gast #6604883
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.

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