加快阅读.wav并在Haskell中分析?

时间:2014-06-20 03:21:53

标签: haskell audio wav

我正在尝试读取.wav文件并最终可能会使用数据,但我被卡住了。只需读入文件,将其存储在结构中,并将其写入另一个文件需要很长时间。任何增加的处理都需要更长的时间。

我发布的代码相当简单。我必须遗漏一些东西,使程序比必要的更复杂或多余。

   
import qualified Data.Char as DC
import qualified Data.Word as DW
import qualified Data.Int as DI

import qualified Data.Binary.Get as BG
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Lazy.Internal as BLI

import qualified System.Environment as SE
import qualified System.IO as SIO

main = do
    (fstfilename:sndfilename:_) <- SE.getArgs
    fstfile <- SIO.openFile fstfilename SIO.ReadMode
    input <- BL.hGetContents fstfile

    raw_wav <- return $ BG.runGet parseWav input

    sndfile <- SIO.openFile sndfilename SIO.WriteMode
    SIO.hPutStr sndfile (show (wavData raw_wav))

data Sample = OneChannel {mono :: Integer} |
              TwoChannel {leftChannel :: Integer,
                         rightChannel :: Integer}

instance Show Sample where
    show (OneChannel m) = show m ++ " " 
    show (TwoChannel l r) = show l ++ "-" ++ show r ++ " "

data RaWavFile = RaWavFile {numChannels :: Integer,
                        sampleRate :: Integer,
                        bitsPerSample :: Integer,
                        wavData :: [Sample]}
                        deriving (Show)

parseWav :: BG.Get RaWavFile
parseWav = do
        BG.skip 22
        num_channels <- BG.getWord16le 
        sample_rate <- BG.getWord32le
        BG.skip 6
        bits_per_sample <- BG.getWord16le

        rem <- BG.getRemainingLazyByteString
        wav_data <- return $ BL.drop 8 (BL.dropWhile 
                            ((/=) (fromIntegral (DC.ord 'd') :: DW.Word8)) rem)

        nc <- return $ toInteger num_channels
        sr <- return $ toInteger sample_rate
        bps <- return $ toInteger bits_per_sample
        return $ RaWavFile nc sr bps (orgSamples nc bps wav_data)

--          numChannels bitpersample   wavData  
orgSamples :: Integer -> Integer -> BL.ByteString -> [Sample]
orgSamples nc bps BLI.Empty = [] 
orgSamples nc bps bs 
        | nc == 1 = (OneChannel (rle fb)):(orgSamples nc bps rst)
        | nc == 2 = (TwoChannel (rle fb) (rle sb)):(orgSamples nc bps rsst)
        | otherwise = error "Number of channels not 1 or 2"
            where nb = fromIntegral (bps `div` 8) :: DI.Int64
                  (fb, rst) = BL.splitAt nb bs
                  (sb, rsst) = BL.splitAt nb rst 
                  rle = toInteger . BG.runGet BG.getWord16le

1 个答案:

答案 0 :(得分:4)

为什么它很慢。

  1. 您正在使用Integer存储单个样本。 Integer是一种用于存储任意精度整数的特殊类型。因此,每次读/写这些值都会产生大量开销。不惜一切代价避免。我建议使用特定大小的类型,例如Int8 / Int16。您可能还应该对这些类型进行参数化。

  2. 您将样本存储为通道类型的标记并集。对于每个样本。这是一个很大的开销。你真的希望在文件中间改变频道数量吗?可能不是。

  3. 您正在使用列表来存储样本,这在您基本上是在讨论连续的字节流时会引入很多开销。

  4. 如何加快速度

    1. 通过样本的位深度参数化您的类型。我建议直接使用Int8 / Int16,因为8和16位轨道是最常用的两种格式。您可能希望坚持使用它们进行学习项目。

      import Data.Int
      
    2. 使用Unboxed Vectors存储您的数据。这避免了(懒惰)列表和thunk的大量开销,并且将显着减少启动时的内存消耗。

      import Data.Vector.Unboxed as V
      
    3. 不存储曲目数。 length $ tracks $ wavFile会在您需要时随时取回。在代码中消除Integer的所有用法(除非你真的真的需要存储大于2 ^ 64的数字)

      data RaWavFile b = RaWavFile {
          sampleRate :: Int,
          tracks     :: [Vector b] }
          deriving (Show)
      
    4. 使用类型来指导您。 binary对返回类型是多态的。只需询问它所需的类型,它将解析正确的字节数而无需您的干预。

      parseWav :: BL.ByteString -> BG.Get (RaWavFile b)
      wav <- parseWav input :: BG.Get (RaWavFile Int16)
      
    5. 您应该只使用BG.runGet 一次,以针对字节字符串运行解析器。