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
:
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
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.
> Gibt es da noch Optimierungspotenzial?
Wenn es MIPS-PIC ist, dann schreibs halt in Assembler.
Portabilitaet kann man auch uebertreiben.
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.
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.
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
1 | out = ((sample - DCOFFSET) << 8) >> 16; |
DCOFFSET ist hier der vorhandene Offset als positive Zahl (24 Bits).
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.
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
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
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.
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
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
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.
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?
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.