Entwicklung & Code
Symphonia 0.6: Rust-Audioframework wird multimedial
Mit Version 0.6 baut das Rust-Multimediaframework Symphonia seine Architektur grundlegend um. Laut Maintainer steckt in der neuen Version Entwicklungsarbeit aus zwei Jahren. Sie soll das bislang primär auf Audio ausgelegte Framework auf künftige Video- und Untertitel-Unterstützung vorbereiten. Video- oder Subtitle-Decoder bringt Symphonia 0.6 zwar noch nicht offiziell mit – über die experimentellen Feature Flags exp-video-codecs und exp-subtitle-codecs sind allerdings bereits erste Prototypen möglich, die internen APIs und Datentypen wurden entsprechend erweitert. Auf Entwickler, die das Framework nutzen, kommen dadurch zahlreiche Breaking Changes zu.
Weiterlesen nach der Anzeige
Symphonia ist ein in Rust entwickeltes Multimedia-Framework mit Fokus auf Audio-Decoding, Container-Parsing und Metadatenverarbeitung. Das Open-Source-Projekt unterstützt unter anderem MP3, AAC, FLAC, MP4, Ogg/Vorbis sowie Matroska/WebM und versteht sich als speichersichere Alternative zu klassischen Multimedia-Bibliotheken für das Rust-Ökosystem. Zum Einsatz kommt Symphonia etwa in Audio-Playern, Medienwerkzeugen oder Streaming-Pipelines. Laut der offiziellen Symphonia-Dokumentation liegt ein Schwerpunkt auf „Safe Rust“ und modularen Komponenten.
Neue Architektur trennt Tracks und Codecs
Im Zentrum der neuen Version steht der Wechsel von einer audiozentrierten Architektur hin zu einem allgemeineren Multimedia-Modell. Dazu hat das Projekt zentrale Datentypen für Zeitstempel, Dauer und Zeitbasis neu entworfen. Außerdem trennt Symphonia Track-Informationen künftig sauberer von Codec-Parametern: Timing-Daten liegen nicht länger in den Codec-Strukturen, sondern in separaten Track-Metadaten. Das soll die Verarbeitung mehrerer Streams vereinfachen – etwa Audio, Video und Untertitel in einem Matroska-Container.
Neu ist außerdem ein Prioritätssystem für Decoder und Format-Reader. Anwendungen können damit bevorzugte Implementierungen registrieren und Fallbacks definieren. Das dürfte vor allem für modulare Medien-Stacks interessant sein, etwa wenn Hardware-Decoder Vorrang vor generischen Software-Decodern erhalten sollen.
Weniger Kopien, mehr Metadatenformate
Ein weiterer Schwerpunkt der neuen Version liegt auf effizienteren Datenpfaden. Decoder müssen die Paketdaten nicht mehr zwangsläufig kopieren, bevor sie sie verarbeiten. In Kombination mit externen Demuxern lassen sich so zusätzliche Speicheroperationen vermeiden – ein Zero-Copy-ähnlicher Ansatz, wie er in Hochleistungs-Medienpipelines verbreitet ist.
Bei den Metadaten verarbeitet Symphonia nun zusätzlich ID3v1-, APEv1- und APEv2-Tags und unterstützt Kapitelinformationen für Matroska, ID3v2 und Vorbis Comments. Bekannte Tags interpretiert das Framework dabei stärker typisiert: Eine Tracknummer liest es nicht mehr nur als Zeichenkette, sondern direkt als numerischen Wert (u64). Von dieser Typisierung profitieren etwa Medienbibliotheken oder automatische Sortierfunktionen.
Weiterlesen nach der Anzeige
Für Matroska-Container ergänzt Symphonia 0.6 außerdem Support für Attachments und Metadaten. Attachments kommen vor allem bei Untertiteln zum Einsatz, etwa um Schriftarten direkt in MKV-Dateien einzubetten. Solche Dateien finden sich häufig bei Anime-Releases oder bei aufwendig formatierten ASS-Untertiteln.
Neu geschriebene Demuxer und SIMD ab Werk
Ferner listet das Projekt in den Release Notes zahlreiche Verbesserungen in den Bereichen Sicherheit und Stabilität auf. Den Matroska-Demuxer hat Maintainer Philip Deljanov vollständig neu geschrieben, Teile des MP4-Demuxers ebenfalls überarbeitet. Hinzu kommen zahlreiche Fehlerkorrekturen auf Basis von Fuzzing-Tests. Gerade Multimedia-Parser gelten traditionell als sicherheitskritisch, weil fehlerhafte Containerdateien Speicherfehler oder Abstürze auslösen können. Rust soll solche Risiken durch Speichersicherheit reduzieren.
Auch bei der Performance verspricht Symphonia Fortschritte. SSE-, AVX- und Neon-SIMD-Optimierungen sind nun standardmäßig aktiviert. SIMD (Single Instruction, Multiple Data) nutzt Vektorinstruktionen moderner CPUs, um mehrere Datenwerte parallel zu verarbeiten – ein typischer Ansatz bei Audio- und Video-Decodern. Laut Release Notes soll außerdem die Decoding-Leistung steigen und die Binärgröße sinken.
Genauere Formaterkennung und neue MSRV-Policy
Weitere Änderungen betreffen die Formaterkennung, die nun ein Punktesystem verwendet, um Fehlklassifizierungen zu vermeiden. Erkennt Symphonia ein Medienformat, reicht es die dabei gefundenen Metadaten künftig an den jeweiligen Format-Reader weiter. Auch die Implementierungen für AIFF und CAF haben die Entwickler überarbeitet.
Wegen der zahlreichen API-Änderungen verweist der Maintainer ausdrücklich auf den Guide für die Migration. Zudem führt das Projekt erstmals eine offizielle MSRV-Policy ein: Eine Anhebung der minimal unterstützten Rust-Version gilt künftig als Breaking Change. Eine vollständige Übersicht aller Neuerungen liefern die Release Notes zu Symphonia 0.6.0 auf GitHub.
Lesen Sie auch
(fo)
Entwicklung & Code
Neu in .NET 10.0 [23]: Neue LINQ-Operatoren LeftJoin() und RightJoin()
Wie in den letzten .NET-Versionen auch, liefert Microsoft in .NET 10.0 wieder neue Operatoren für Language Integrated Query (LINQ), die bestehende Konstrukte vereinfachen. Dieses Mal sind es mit LeftJoin() und RightJoin() zwei elementare Operatoren, zum einen aus der Mengenlehre und zum anderen für relationale Datenbanken. Mehr dazu steht im zugehörigen Issue auf GitHub.
Weiterlesen nach der Anzeige

Dr. Holger Schwichtenberg ist technischer Leiter des Expertennetzwerks www.IT-Visions.de, das mit 53 renommierten Experten zahlreiche mittlere und große Unternehmen durch Beratungen und Schulungen sowie bei der Softwareentwicklung unterstützt. Durch seine Auftritte auf zahlreichen nationalen und internationalen Fachkonferenzen sowie mehr als 90 Fachbücher und mehr als 1500 Fachartikel gehört Holger Schwichtenberg zu den bekanntesten Experten für .NET und Webtechniken in Deutschland.
Tatsächlich waren diese Operationen bisher in LINQ bereits möglich, allerdings nur umständlich über eine Gruppierung mit GroupJoin() und SelectMany() sowie DefaultIfEmpty(). Die neuen Methoden LeftJoin() und RightJoin() vereinfachen den Einsatz, wie folgender Code am Beispiel eines Join zwischen den Klassen Company und Website zeigt:
public void LeftRightJoin()
{
CUI.Demo();
Company[] companies =
[
new Company{ ID = 1, Name = "www.IT-Visions.de" },
new Company{ ID = 2, Name = "Software & Support" },
new Company{ ID = 3, Name = "Heise Gruppe GmbH & Co. KG" },
new Company{ ID = 4, Name = "Startup i.Gr." } // hat noch keine Website
];
Website[] websites =
[
new Website{ CompanyID = 1, URL = "www.IT-Visions.de" },
new Website{ CompanyID = 1, URL = "www.dotnet10.de" },
new Website{ CompanyID = 2, URL = "www.entwickler.de" },
new Website{ URL = "www.Microsoft.com" }, // Diese kleine ;-) Firma ist noch nicht angelegt...
new Website{ CompanyID = 3, URL = "www.heise.de" },
];
// ### LeftJOIN ALT ############################################################################
CUI.H2("--- Alle Firmen mit ggf. vorhandenen Websites via LeftJoin ALT (GroupJoin+SelectMany) seit .NET Framework 3.5 ---");
var AllCompaniesWithWebsitesSetOld = companies
.GroupJoin(websites,
c => c.ID,
w => w.CompanyID,
(c, websites) => new { Company = c, Websites = websites })
.SelectMany(
x => x.Websites.DefaultIfEmpty(), // Falls keine Website existiert, wird `null` verwendet
(c, w) => new WebsiteWithCompany
{
Name = c.Company.Name,
URL = w.URL, // Falls `w` null ist, bleibt URL null
City = c.Company.City
});
foreach (var item in AllCompaniesWithWebsitesSetOld)
{
Console.WriteLine((item.Name != null ? item.Name + " " + item.City : "- keine Firma -").Trim() + " -> " + (item.URL ?? "- keine URL -"));
}
// ### LeftJOIN NEU ############################################################################
CUI.H2("--- Alle Firmen mit ggf. vorhandenen Websites via LeftJoin NEU ab .NET 10.0 ---");
var AllCompaniesWithWebsitesSet = companies.LeftJoin(websites,
e => e.ID,
e => e.CompanyID,
(c, w) => new WebsiteWithCompany { Name = c.Name, City = c.City, URL = w.URL }
);
foreach (var item in AllCompaniesWithWebsitesSet)
{
Console.WriteLine((item.Name != null ? item.Name + " " + item.City : "- keine Firma -").Trim() + " -> " + (item.URL ?? "- keine URL -"));
}
// ### RightJoin ALT ############################################################################
CUI.H2("--- Alle Websites mit ggf. vorhandenen Firmen via RightJoin ALT (GroupJoin+SelectMany) seit .NET Framework 3.5 ---");
var WebsiteWithCompanySetOLD = websites
.GroupJoin(companies,
w => w.CompanyID,
c => c.ID,
(w, companies) => new { Website = w, Companies = companies })
.SelectMany(
x => x.Companies.DefaultIfEmpty(), // Falls kein Unternehmen existiert -> null
(w, c) => new WebsiteWithCompany
{
Name = c.Name, // Falls `c` null ist, bleibt `Name` null
City = c.City, // Falls `c` null ist, bleibt `City` null
URL = w.Website.URL
});
foreach (var item in WebsiteWithCompanySetOLD)
{
Console.WriteLine((item.Name != null ? item.Name + " " + item.City : "- keine Firma -").Trim() + " -> " + (item.URL ?? "- keine URL -"));
}
// ### RightJoin NEU ############################################################################
CUI.H2("--- Alle Websites mit ggf. vorhandenen Firmen via RightJoin NEU ab .NET 10.0 ---");
var WebsiteWithCompanySet = companies.RightJoin(websites,
e => e.ID,
e => e.CompanyID,
(c, w) => new WebsiteWithCompany { Name = c.Name, City = c.City, URL = w.URL }
);
foreach (var item in WebsiteWithCompanySet)
{
Console.WriteLine((item.Name != null ? item.Name + " " + item.City : "- keine Firma -").Trim() + " -> " + (item.URL ?? "- keine URL -"));
}
// ### Zum Vergleich: Inner Join, den es seit .NET Framework 3.5 gibt
CUI.H2("--- Alle Firmen, die Websites haben, via InnerJoin seit .NET Framework 3.5 ---");
var CompaniesWithWebsitesSet = companies.Join(websites,
c => c.ID,
w => w.CompanyID,
(c, w) => new WebsiteWithCompany
{
Name = c.Name,
URL = w.URL,
City = c.City
});
foreach (var item in CompaniesWithWebsitesSet)
{
Console.WriteLine((item.Name != null ? item.Name + " " + item.City : "- keine Firma -").Trim() + " -> " + (item.URL ?? "- keine URL -"));
}
}

Der Code erzeugt diese Ausgabe (Abb. 1).
Die neuen Operatoren sind laut Microsoft performanter als die bisherige Operatorkombination.

Performance von LeftJoin() im Vergleich zu der bisherigen Operatorkombination (Abb. 2)
(Bild: Microsoft)
(rme)
Entwicklung & Code
software-architektur.tv: John Romeros Prinzipien – mit Tom Asel
John Romero ist bekannt als eine der treibenden Kräfte hinter id Software. Mit einer Gruppe von unter zehn Leuten hat id die Spiele-Welt mit „Doom“ und „Quake“ revolutioniert, Shareware als Vertriebsmodell etabliert, Spiele-Engines zu einem eigenständigen Produkt gemacht und viele seiner Innovationen später als Open Source veröffentlicht. In seinen aktuellen Vorträgen hat er einige fundamentale Prinzipien für die Softwareentwicklung diskutiert.
Weiterlesen nach der Anzeige
In dieser Episode sprechen Tom Asel und Eberhard Wolff darüber, was Developer daraus für die Entwicklung von Informationssystemen im Allgemeinen lernen können.
Livestream am 15. Mai
Die Ausstrahlung findet live am Freitag, dem 15. Mai 2026, um 13 Uhr statt. Die Folge steht im Anschluss als Aufzeichnung bereit. Während des Livestreams können Interessierte Fragen via Twitch-Chat, YouTube-Chat oder anonym über das Formular auf der Videocast-Seite einbringen.
software-architektur.tv ist ein Videocast von Eberhard Wolff, iX-Blogger und bekannter Softwarearchitekt, der als Head of Architecture bei SWAGLab arbeitet. Zum Team gehören außerdem Lisa Maria Schäfer (Socreatory) und Ralf D. Müller (DB Systel). Seit Juni 2020 sind über 250 Folgen entstanden, die unterschiedliche Bereiche der Softwarearchitektur beleuchten – mal mit Gästen, mal Wolff, Schäfer oder Müller solo. Seit mittlerweile mehr als zwei Jahren berichtet heise developer über die Episoden.
(who)
Entwicklung & Code
Von undefiniert zu definiert: Die Verwendung von std::launder in C++
Im heutigen Beitrag knüpfe ich an die übergreifenden Themen der letzten beiden Monate an. Heute geht es darum, wann und wo du std::launder aus C++17 einsetzen musst und worin der Unterschied zu reinterpret_cast oder std::start_lifetime_as besteht.
Weiterlesen nach der Anzeige

Andreas Fertig ist erfahrener C++-Trainer und Berater, der weltweit Präsenz- sowie Remote-Kurse anbietet. Er engagiert sich im C++-Standardisierungskomitee und spricht regelmäßig auf internationalen Konferenzen. Mit C++ Insights ( hat er ein international anerkanntes Tool entwickelt, das C++-Programmierenden hilft, C++ noch besser zu verstehen.
Die Bereiche, in denen man das heute Gelernte anwenden kann, sind vielfältig. Im Embedded-Bereich wird std::launder in der Regel verwendet, aber auch beim Schreiben von Bibliothekscode kommt „Laundering“ (zu Deutsch waschen und bügeln, gerne im Zusammenhang mit „Money“ als Geldwäsche) vor.
Wann es zu Problemen kommen kann
Ich verwende das Beispiel aus dem Paper P0532R0:
struct X {
const int n; // #A
double d;
};
X* p = new X{7, 8.8}; // #B
new(p) X{42, 9.9}; // #C
int i = p->n; // #D
auto d = p->d; // #E
Hier stehen mehrere Teile, die zusammenpassen müssen. Beachte, dass das struct X das Datenfeld n als const deklariert.
Als Nächstes wird mithilfe von new in #B ein Objekt erstellt und der resultierende Zeiger in p gespeichert. So weit, so gut.
Weiterlesen nach der Anzeige
Der interessante Teil beginnt als Nächstes in #C mit dem Platzierungs-new. Wer das noch nie gemacht hat, muss den eigenen C++-Code vielleicht nicht waschen und glatt bügeln.
Das Platzierungs-new selbst ist auch in Ordnung. Das Problem tritt später in #D und #E auf. Hier wird auf die Werte des Zeigers p zugegriffen. Aber der Compiler darf davon ausgehen, dass nach #B der Inhalt von p unverändert ist, da p selbst nie dazu verwendet wurde, den Inhalt des Objekts zu ändern. Noch schlimmer: Eines der Datenelemente von X ist const. Das ist eine Freikarte für den Optimierer, zu Recht anzunehmen, dass sich der Wert von n nach der Konstruktion nie ändert.
Aber genau das habe ich in #C getan. Ich ändere einen const-Wert! Die Werte von i und d sind unbekannt. Das ist undefiniertes Verhalten.
Dieses lässt sich leicht vermeiden, sogar ohne std::launder, indem man den Zeiger aktualisiert und dem Compiler mitteilt, dass sich die Werte hinter p geändert haben:
X* p = new X{7, 8.8}; // #B
p = new(p) X{42, 9.9}; // #C
int i = p->n; // #D
auto d = p->d; // #E
Die Frage ist also: Warum macht man das nicht einfach und vergisst std::launder? Nun, wann immer möglich, vergiss std::launder.
Leider gibt es Fälle, in denen das Aktualisieren des Zeigers wertvolle Ressourcen opfern würde.
Wenn es komplizierter wird
Angenommen, du implementierst einen benutzerdefinierten Allokator:
template
class Buffer {
alignas(ALIGNMENT) std::byte mBuffer[SIZE];
public:
template
T* Construct(Ts... vals)
{
new(mBuffer) T{std::forward(vals)...};
return reinterpret_cast(mBuffer);
}
template
[[nodiscard]] T* Get()
{
return reinterpret_cast(mBuffer);
}
};
Du siehst hier zwei Funktionen, die der Allokator bereitstellt: Construct und Get. Das Konzept von Buffer besteht darin, dass die gespeicherten Daten typunabhängig sind. Eine mögliche vereinfachte Verwendung könnte folgendermaßen aussehen:
struct Point { // #A
int x;
int y;
};
struct Point3D {
int x;
int y;
int z;
};
std::array, 2> storage{}; // #B
// #C
storage.at(0).Construct(2, 3);
storage.at(1).Construct(4, 5, 6);
// #D
storage.at(0).Get()->x = 7;
Die zwei Datentypen Point und Point3D in #A stehen als Beispiel für beliebige Typen. storage steht für den Stack-Speicher, der beliebige Daten speichern kann. Solange die Nutzer wissen, welcher Datentyp hinter einem Index steckt (und dieser Typ nicht zu groß ist), kann alles gespeichert werden.
Am ersten Element des Arrays #C konstruiere ich ein Point-Objekt, während ich am zweiten Element ein Point3D erstelle. Erst später im Programm #D werden die Objekte tatsächlich verwendet. Dennoch hat niemand aus gutem Grund einen Zeiger auf das frisch konstruierte Objekt gespeichert. Ein solcher Zeiger würde Speicherkapazität erfordern, und du weißt bereits, wohin dieser Zeiger zeigt.
Aus der Perspektive der abstrakten Maschine habe ich Löcher in das Typsystem gestochen. Der Compiler kann zu Recht davon ausgehen, dass sich die Daten hinter Get nicht ändern, es sei denn, er sieht etwa einen direkten Schreibzugriff #D. Ein solcher Zugriff bleibt vom Compiler unbemerkt (oder könnte es bleiben, da es sich um undefiniertes Verhalten handelt) im folgenden Code in #E, wenn ich an einer bestehenden Stelle ein neues Point-Objekt erstelle:
std::array, 2> storage{}; // #B
// #C
storage.at(0).Construct(2, 3);
// #D
storage.at(0).Get()->x = 7;
// #E
storage.at(0).Construct(8, 9);
Offensichtlich lässt sich das Problem hier am einfachsten vermeiden, indem man den von jedem Construct-Aufruf zurückgegebenen Zeiger speichert. Wenn das, wie hier, nicht machbar ist, besteht die richtige Vorgehensweise darin, den Zeiger mit std::launder zu „waschen“. Dieses spezielle Hilfsmittel fungiert als Devirtualisierungsbarriere, die Compiler-Optimierungen und -Annahmen verhindert.
std::launder zur Rettung
So lässt sich die Buffer-Implementierung auf sichere Weise aktualisieren. Zunächst zu Construct: Meine ursprüngliche Implementierung sah so aus:
template
T* Construct(Ts... vals)
{
new(mBuffer) T{std::forward(vals)...};
return reinterpret_cast(mBuffer);
}
Du kannst diesen Code auch ohne std::launder sicher machen, indem du den von new zurückgegebenen Zeiger zurückgibst:
template
T* Construct(Ts... vals)
{
return new(mBuffer) T{std::forward(vals)...};
}
Die zweite Funktion, die du korrigieren musst, ist Get, dieses Mal durch Hinzufügen von std::launder:
template
[[nodiscard]] T* Get()
{
return std::launder(reinterpret_cast(mBuffer));
}
Der Code geht nun davon aus, dass du Construct aufrufst, bevor du Get aufrufst. Dieser mBuffer enthält ein gültiges Objekt, das mit dem übereinstimmt, das mit Construct erstellt wurde.
Zusammenfassung
Mit std::launder kannst du einen Zeiger aktualisieren, der auf ein bereits vorhandenes Objekt an dieser Speicheradresse verweist. Die Lebensdauer des Objekts an dieser Stelle hat also bereits begonnen.
(rme)
-
Künstliche Intelligenzvor 3 Monaten
Top 10: Die beste kabellose Überwachungskamera im Test – Akku, WLAN, LTE & Solar
-
Social Mediavor 2 MonatenCommunity Management und Zielgruppen-Analyse: Die besten Insights aus Blog und Podcast
-
Entwicklung & Codevor 2 MonatenCommunity-Protest erfolgreich: Galera bleibt Open Source in MariaDB
-
Künstliche Intelligenzvor 2 MonatenBlade‑Battery 2.0 und Flash-Charger: BYD beschleunigt Laden weiter
-
Künstliche Intelligenzvor 2 Monaten
Top 10: Der beste Luftgütesensor im Test – CO₂, Schadstoffe & Schimmel im Blick
-
Social Mediavor 2 MonatenVon Kennzeichnung bis Plattformpflichten: Was die EU-Regeln für Influencer Marketing bedeuten – Katy Link im AllSocial Interview
-
Apps & Mobile Entwicklungvor 2 MonatenMähroboter ohne Begrenzungsdraht für Gärten mit bis zu 300 m²
-
Künstliche Intelligenzvor 2 MonateniPhone Fold Leak: Apple spart sich wohl iPad‑Multitasking
