Die Community zu .NET und Classic VB.
Menü

Tipp-Upload: VB.NET 0238: Wave Splitter und Packetizer-Klasse

 von 

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.

Zurück zur Übersicht

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

Schwierigkeitsgrad 3

Verwendete API-Aufrufe:

Download:

Download des Beispielprojektes [16,64 KB]

' 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.