Tipp-Upload: VB.NET 0238: Wave Splitter und Packetizer-Klasse
von BloodySword
Hinweis zum Tippvorschlag
Dieser Vorschlag wurde noch nicht auf Sinn und Inhalt überprüft und die Zip-Datei wurde noch nicht auf schädlichen Inhalt hin untersucht.
Bitte haben Sie ein wenig Geduld, bis die Freigabe erfolgt.
Über den Tipp
Dieser Tippvorschlag ist noch unbewertet.
Der Vorschlag ist in den folgenden Kategorien zu finden:
- Dateien und Laufwerke
- Multimedia
Dem Tippvorschlag wurden folgende Schlüsselwörter zugeordnet:
WAVE Parser Splitter Packetizer Writer Reader PCM Chunk RIFF
Der Vorschlag wurde erstellt am: 30.03.2008 13:10.
Die letzte Aktualisierung erfolgte am 30.03.2008 17:21.
Beschreibung
Hier stelle ich eine Klasse vor, die WAVE-Dateien blockweise lesen und schreiben kann.
Wenn man sich die Klasse anschaut, sollte sie sich selbst erklären.
Dieser Tipp beinhaltet ein Anwendungsbeispiel, welches aus einer WAVE-Datei eine RAW-PCM-Datei erstellt ohne jegliche header. Kein revolutuionäres Programm, aber besser als gar kein Anwendungsbeispiel ;o).
Wenn es Probleme mit einigen verkorksten WAVE-Files gibt, lasst es mich wissen.
Viel Spaß!
Update: Übersichtlicher gestaltet
Update: Abspiellänge kann nun wahlwaiese als Double oder als TimeSpan ausgegeben werden.
Update: Zeigt nun die Abspiellänge nach dem Klick auf "Start" im Fenster an.
Update: Bugfix in frmMain.vb: FileStream gab ein Byte zu viel aus. buffer.Length - 1 muss es lauten!
ACHTUNG: Projekt und Projektgruppe sind im Format VB.NET Express Edition 2008!
Schwierigkeitsgrad |
Verwendete API-Aufrufe: |
Download: |
' Dieser Source stammt von http://www.activevb.de ' und kann frei verwendet werden. Für eventuelle Schäden ' wird nicht gehaftet. ' Um Fehler oder Fragen zu klären, nutzen Sie bitte unser Forum. ' Ansonsten viel Spaß und Erfolg mit diesem Source! ' ' Beachten Sie, das vom Designer generierter Code hier ausgeblendet wird. ' In den Zip-Dateien ist er jedoch zu finden. ' --------- Anfang Projektgruppe WaveParserTest.sln --------- ' -------- Anfang Projektdatei WaveParserTest.vbproj -------- ' ----------------- Anfang Datei filename.vb ----------------- Public Module ManipulateFilenames Public Function GetPathFromFullPath _ (ByVal FileFullPath As String) As String Try Dim intPos As Integer intPos = FileFullPath.LastIndexOfAny("\") intPos += 1 Return FileFullPath.Substring(0, intPos) Catch ex As Exception Return "" End Try End Function Public Function GetNameFromFullPath _ (ByVal FileFullPath As String, Optional ByVal RemoveExt As Boolean = True) As String Dim intPos As Integer, ns As String Try intPos = FileFullPath.LastIndexOfAny("\") intPos += 1 ns = FileFullPath.Substring(intPos, (Len(FileFullPath) - intPos)) If RemoveExt Then intPos = ns.LastIndexOfAny(".") ns = Left(ns, intPos) End If Return ns Catch ex As Exception Return "" End Try End Function Public Function CheckPathBackSlash(ByVal sIn As String) As String If Right(sIn, 1) <> "\" Then Return sIn & "\" Else Return sIn End If End Function End Module ' ------------------ Ende Datei filename.vb ------------------ ' ----------------- Anfang Datei frmMain.vb ----------------- Option Strict On Imports WaveParserTest.Multimedia.Audio.Packetizers Public Class frmMain Dim CancelOp As Boolean Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles Button1.Click ofd.Filter = "WAVE-Datei|*.wav" If ofd.ShowDialog <> Windows.Forms.DialogResult.Cancel Then TextBox1.Text = ofd.FileName TextBox2.Text = CheckPathBackSlash(GetPathFromFullPath(ofd.FileName)) & _ GetNameFromFullPath(ofd.FileName) & ".raw" End If End Sub Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles Button2.Click sfd.Filter = "RAW-PCM|*.raw;*.bin" If sfd.ShowDialog <> Windows.Forms.DialogResult.Cancel Then TextBox2.Text = sfd.FileName End If End Sub Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) _ Handles Button3.Click Select Case Button3.Text Case "Start" If My.Computer.FileSystem.FileExists(TextBox1.Text) = False Then Exit Sub Button3.Text = "Abbrechen" CancelOp = False ' WAVE-FILE-READER mit 10 Zeilen Code (Ohne Kommentare)! ' Packetizer initialisieren Dim ws As New WAVE(TextBox1.Text, WAVE.WAVEMode.Splitter) ' Abspieldauer anzeigen lblDuration.Text = ws.PlaybackDuration.ToString ' Ausgabe initialisieren Dim ro As New System.IO.FileStream(TextBox2.Text, IO.FileMode.OpenOrCreate) ' Zwischenspeicher (Puffer) für eine Sekunde Audiomaterial Dim buffer() As Byte ' GUI vorbereiten ProgressBar1.Maximum = CType(ws.SplitterTotalLength \ 1000, Integer) ' Daten lesen und weiter verarbeiten Do ' Eine Sekunde lesen und weiter verwenden buffer = CType(ws.SplitterReadOneSecond, Byte()) ' In diesem Fall schreiben wir die rohen PCM-Daten in eine neue Datei ro.Write(buffer, 0, buffer.Length - 1) ' Bugfix: buffer.Length - 1 ' GUI aktualisieren ProgressBar1.Value = CType(ws.SplitterReadPosition \ 1000, Integer) ' Damit der User nicht glaubt, das Programm würde hängen: My.Application.DoEvents() Loop Until ws.SplitterReadPosition >= ws.SplitterTotalLength Or CancelOp ws.Dispose() ro.Dispose() Button3.Text = "Start" Case "Abbrechen" CancelOp = True Button3.Text = "Start" End Select End Sub End Class ' ------------------ Ende Datei frmMain.vb ------------------ ' -------------- Anfang Datei wave_splitter.vb -------------- ' WAVE-Class by BloodySword ' This Program applies to the terms of General Public License GPL. Namespace Multimedia.Audio.Packetizers Public Class WAVE #Region "Declarations" Implements IDisposable Dim FileName As String Private disposedValue As Boolean = False ' To detect redundant calls Structure WAVEFORMATEXTENSIBLE Dim wFormatTag As UInt16 Dim nChannels As UInt16 Dim nSamplesPerSec As UInt32 Dim nAvgBytesPerSeconds As UInt32 Dim nBlockAlign As UInt16 Dim nBitsPerSamples As UInt16 End Structure Enum WAVEMode As Byte Splitter = 1 Packetizer = 2 End Enum Enum PlayDurationReturnType As Byte TimeSpanObject = 0 Float32 = 1 End Enum Dim WAVEFile As System.IO.FileStream Dim BinRead As System.IO.BinaryReader Dim BinWrite As System.IO.BinaryWriter Dim hFMT As WAVEFORMATEXTENSIBLE Dim PCMOffset As UInt32 Dim PCMLength As UInt32 Dim SplitterInitialized As Boolean Dim PacketizerInitialized As Boolean Dim PacketizerCanFinish As Boolean Dim PacketizerWrittenBytes As UInt32 Dim FMTHeaderSet As Boolean Dim AlreadyClean As Boolean Dim lastsecond As UInt32 #End Region #Region "Properties" Property FMTHeader() As WAVEFORMATEXTENSIBLE Get If FMTHeaderSet Then Return hFMT Else Return Nothing End Get Set(ByVal value As WAVEFORMATEXTENSIBLE) hFMT = value FMTHeaderSet = True End Set End Property Property WaveFileName() As String Get Return FileName End Get Set(ByVal value As String) FileName = value End Set End Property ReadOnly Property PlaybackDuration(Optional ByVal ReturnFormat As _ PlayDurationReturnType = PlayDurationReturnType.TimeSpanObject) As Object Get Select Case ReturnFormat Case PlayDurationReturnType.Float32 Return PCMLength / hFMT.nAvgBytesPerSeconds Case Else Return TimeSpan.FromMilliseconds(PCMLength / hFMT.nAvgBytesPerSeconds _ * 1000) End Select End Get End Property #End Region #Region "Class Construtor" Public Sub New(Optional ByVal PathName As String = Nothing, Optional ByVal Mode As _ WAVEMode = WAVEMode.Splitter) If PathName <> Nothing Then FileName = PathName Select Case Mode Case WAVEMode.Splitter Dim Result As Long = InitSplitter() If Result <> 0 Then Select Case Result Case 1 Throw New System.Exception("This is not a WAVE-File " & _ "or the header is corrupted. To prevent errors, " & _ "this exception was thrown!") Case 2 Throw New System.Exception("The 'fmt ' header is too " & _ "small. Possibly another WAVE-Format? Skipped!") Case 3 Throw New System.Exception("Fatal Error. No FMT chunk found.") End Select End If Case WAVEMode.Packetizer If FMTHeaderSet Then InitPacketizer() Else Throw New _ System.Exception( "Fatal Error. No FMT chunk given.") : Exit Sub End Select End If End Sub #End Region #Region "Splitter / Reader" Function InitSplitter() As Long WAVEFile = New System.IO.FileStream(FileName, IO.FileMode.Open, _ IO.FileAccess.Read, IO.FileShare.Read) BinRead = New System.IO.BinaryReader(WAVEFile) ' Get the RIFF DWord Dim DummyDWORD As UInt32 DummyDWORD = BinRead.ReadUInt32() If DummyDWORD <> 1179011410 Then ' RIFF ' Seems to be corrupt. Skipping Return 1 ' Error 1 - Not a WAVE-File or corrpupt Exit Function End If ' The next DWord containts the FileSize. Could be used to validate the WAVE, but ' it is often ' Written wrong by some applications. So wie slip this and use other conditions ' for validating. WAVEFile.Position += 4 ' Skip DWord ' The next word has to be "WAVE". It indicates the RIFF-Type. DummyDWORD = BinRead.ReadUInt32() If DummyDWORD <> 1163280727 Then ' Oups this file is not a WAVE-File Return 1 ' Same problem, same exception! Exit Function End If ' Now let's scan through the file, till we have found the "fmt " header. ' If found, we will read this out. DummyDWORD = BinRead.ReadUInt32() If DummyDWORD = 544501094 Then ' Found it! GoTo fmt Else ' Seems to be another chunk. Let's get the size and overjump all chunks, ' till we have found The fmt header... DummyDWORD = BinRead.ReadUInt32() WAVEFile.Position += (DummyDWORD - 4) ' Overjump the chunk Dim Found As Boolean, loops As Long Do DummyDWORD = BinRead.ReadUInt32() If DummyDWORD = 544501094 Then Found = True Exit Do Else DummyDWORD = BinRead.ReadUInt32() WAVEFile.Position += (DummyDWORD - 4) ' Overjump the chunk If loops > 1024 Then Return 3 ' Fatal Error. No FMT chunk found, after 1024 cycles. Exit Function End If End If Loop Until Found ' Now we have found it. GoTo fmt End If fmt: DummyDWORD = BinRead.ReadUInt32() ' Get the header length If DummyDWORD < 16 Then ' The fmt header size is smaller than 16 byte. It is invalid. Fails Return 2 ' Invalid fmt header found. Skipping Exit Function End If ' Now we can read the header out. hFMT.wFormatTag = BinRead.ReadUInt16() hFMT.nChannels = BinRead.ReadUInt16() hFMT.nSamplesPerSec = BinRead.ReadUInt32() hFMT.nAvgBytesPerSeconds = BinRead.ReadUInt32() hFMT.nBlockAlign = BinRead.ReadUInt16() hFMT.nBitsPerSamples = BinRead.ReadUInt16() If DummyDWORD > 16 Then ' Correcting Position: WAVEFile.Position -= 16 WAVEFile.Position += DummyDWORD End If ' We must find the pcm data block, now. Searching for data-Chunk Dim FoundBegin As Boolean, dloops As Long Do DummyDWORD = BinRead.ReadUInt32() If DummyDWORD = 1635017060 Then FoundBegin = True Exit Do Else DummyDWORD = BinRead.ReadUInt32() WAVEFile.Position += (DummyDWORD - 4) ' Overjump the chunk If dloops > 1024 Then Return 3 ' Fatal Error. No FMT chunk found, after 1024 cycles. Exit Function End If End If Loop Until FoundBegin If FoundBegin Then PCMOffset = WAVEFile.Position + 4 PCMLength = BinRead.ReadUInt32() End If WAVEFile.Seek(PCMOffset, IO.SeekOrigin.Begin) FMTHeaderSet = True SplitterInitialized = True AlreadyClean = False Dim tmp As Double = ((PCMLength / hFMT.nAvgBytesPerSeconds) - (PCMLength \ _ hFMT.nAvgBytesPerSeconds)) lastsecond = Math.Round(tmp, 10) * hFMT.nAvgBytesPerSeconds ' We've finished it. ^^ End Function Function SplitterReadPCMSingleSample(ByVal Sample As UInt32, ByVal Channel As UInt16) _ As Object If Not SplitterInitialized Then Return False : Exit Function Dim pos As UInt32 = PCMOffset + Sample * hFMT.nChannels + (Channel * ( _ hFMT.nBitsPerSamples / 8)) WAVEFile.Seek(pos, IO.SeekOrigin.Begin) Return Nothing If hFMT.wFormatTag = 1 Then Select Case hFMT.nBitsPerSamples Case 8 Return BinRead.ReadByte() Case 16 Return BinRead.ReadInt16() Case 24, 32 Return BinRead.ReadInt32() End Select ElseIf hFMT.wFormatTag = &HFFFE Then If hFMT.nBitsPerSamples = 32 Then Return BinRead.ReadSingle() End If End If End Function Function SplitterReadOneSecond() As System.Array If Not SplitterInitialized Then Return Nothing : Exit Function Dim Buffer() As Byte Dim fr As UInt32 = WAVEFile.Position + hFMT.nAvgBytesPerSeconds If fr <= WAVEFile.Length Then ReDim Preserve Buffer(hFMT.nAvgBytesPerSeconds) Else ReDim Preserve Buffer(lastsecond) End If WAVEFile.Read(Buffer, 0, Buffer.Length - 1) Return Buffer End Function Function SplitterReadPCMSamples(ByVal NumSamples As Long) As System.Array If Not SplitterInitialized Then Return Nothing : Exit Function Dim Buffer() As Byte Dim BytePerSample = hFMT.nBlockAlign Dim FrameSizeBytes As Long = BytePerSample * NumSamples Dim fr As UInt32 = WAVEFile.Position + FrameSizeBytes Dim tmp As Double = ((PCMLength / FrameSizeBytes) - (PCMLength \ FrameSizeBytes)) Dim last As Long = Math.Round(tmp, 10) * FrameSizeBytes If fr <= WAVEFile.Length Then ReDim Preserve Buffer(FrameSizeBytes) Else ReDim Preserve Buffer(last) End If WAVEFile.Read(Buffer, 0, Buffer.Length - 1) Return Buffer End Function ReadOnly Property SplitterReadPosition() As UInt32 Get Return WAVEFile.Position End Get End Property ReadOnly Property SplitterDataLength() As UInt32 Get Return PCMLength End Get End Property ReadOnly Property SplitterDataOffset() As UInt32 Get Return PCMOffset End Get End Property ReadOnly Property SplitterTotalLength() As UInt32 Get Return PCMOffset + PCMLength End Get End Property #End Region #Region "Packetizer / Writer" Function InitPacketizer() As Boolean Try WAVEFile = New System.IO.FileStream(FileName, IO.FileMode.OpenOrCreate, _ IO.FileAccess.Write, IO.FileShare.Read) BinWrite = New System.IO.BinaryWriter(WAVEFile) Catch ex As Exception Return False Exit Function End Try ' First we write the RIFF header with empty filesize. BinWrite.Write(CType(1179011410, UInt32)) ' RIFF BinWrite.Write(CType(0, UInt32)) ' Chunksize 0 BinWrite.Write(CType(1163280727, UInt32)) ' WAVE BinWrite.Write(CType(544501094, UInt32)) ' fmt ' BinWrite.Write(CType(16, UInt32)) ' 'fmt ' Chunksize 16 ' Now we can write the FMT-Header you created BinWrite.Write(hFMT.wFormatTag) BinWrite.Write(hFMT.nChannels) BinWrite.Write(hFMT.nSamplesPerSec) BinWrite.Write(hFMT.nAvgBytesPerSeconds) BinWrite.Write(hFMT.nBlockAlign) BinWrite.Write(hFMT.nBitsPerSamples) BinWrite.Write(CType(1635017060, UInt32)) ' data' BinWrite.Write(CType(0, UInt32)) ' Length 0 till we are finished PacketizerInitialized = True AlreadyClean = False Return True End Function Function PacketizerWriteBuffer(ByVal Buffer() As Byte) As Boolean If Not PacketizerInitialized Then Return False : Exit Function WAVEFile.Write(Buffer, 0, Buffer.Length - 1) PacketizerWrittenBytes += (Buffer.Length - 1) If Not PacketizerCanFinish Then PacketizerCanFinish = True End Function Function PacketizerFinishWAVEFile() As Boolean If Not PacketizerCanFinish Then Return False : Exit Function WAVEFile.Seek(4, IO.SeekOrigin.Begin) BinWrite.Write(CType(PacketizerWrittenBytes + 44, UInt32)) WAVEFile.Seek(40, IO.SeekOrigin.Begin) BinWrite.Write(CType(PacketizerWrittenBytes, UInt32)) Return True Clear() AlreadyClean = True End Function ReadOnly Property PacketizerWritePosition() As UInt32 Get Return WAVEFile.Position End Get End Property #End Region #Region "CleanUp" Sub Clear() If AlreadyClean Then Exit Sub If SplitterInitialized = True Then BinRead.Close() BinRead = Nothing SplitterInitialized = False ElseIf PacketizerInitialized = True Then BinWrite.Close() BinWrite = Nothing PacketizerWrittenBytes = Nothing PacketizerCanFinish = False PacketizerInitialized = False End If Try WAVEFile.Dispose() WAVEFile = Nothing hFMT = Nothing FMTHeaderSet = False AlreadyClean = True Catch ex As Exception End Try End Sub Protected Overrides Sub Finalize() If AlreadyClean Then Exit Sub Clear() MyBase.Finalize() End Sub #Region " IDisposable Support " ' IDisposable Protected Overridable Sub Dispose(ByVal disposing As Boolean) If Not Me.disposedValue Then If disposing Then ' TODO: free other state (managed objects). End If ' TODO: free your own state (unmanaged objects). ' TODO: set large fields to null. Finalize() End If Me.disposedValue = True End Sub ' This code added by Visual Basic to correctly implement the disposable pattern. Public Sub Dispose() Implements IDisposable.Dispose ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As ' Boolean) above. Dispose(True) GC.SuppressFinalize(Me) End Sub #End Region #End Region End Class End Namespace ' --------------- Ende Datei wave_splitter.vb --------------- ' --------- Ende Projektdatei WaveParserTest.vbproj --------- ' ---------- Ende Projektgruppe WaveParserTest.sln ----------
Diskussion
Diese Funktion ermöglicht es, Fragen, die die Veröffentlichung des Tipps betreffen, zu klären, oder Anregungen und Verbesserungsvorschläge einzubringen. Nach der Veröffentlichung des Tipps werden diese Beiträge nicht weiter verlinkt. Allgemeine Fragen zum Inhalt sollten daher hier nicht geklärt werden.
Um eine Diskussion eröffnen zu können, müssen sie angemeldet sein.