【问题标题】:Convert wav audio format ByteString to Floats将 wav 音频格式 ByteString 转换为浮点数
【发布时间】:2017-01-11 20:08:30
【问题描述】:

我正在处理存储在 .wav 文件中的传感器数据。样本代表介于 -1 和 1 之间的浮点数。

我正在以ByteStrings 的形式从.wav 文件中读取样本,我需要一种方法将这个ByteString 转换为Float。所以我正在寻找具有以下签名的函数:

toFloat :: ByteString -> Float

例如。我正在使用一个包含 1 个通道、帧速率为 48kHz 且样本由 24 位组成的 .wav 文件。这意味着每个样本包含 3 个字节,我可以从 .wav 文件中读取它,如下所示: hGet h 3。 这里,h 是 .wav 文件的句柄。

如何将我从 hGet 获得的这个 ByteString 转换为 Float(介于 -1 和 1 之间)?

正如您在我的previous question 中看到的,我目前正在将ByteString 转换为Double,首先将其转换为Int32(基于Data.WAVE)。由于我的样本永远不会大于 32 位,因此我想使用 Floats 而不是 Doubles。我也在寻找一种更有效的方式来进行这种转换。

编辑 我目前正在将ByteString 首先转换为Int32,然后再转换为Double。这是由bsToDouble完成的:

convertNBytesLen :: [Word8] -> Int32
convertNBytesLen = foldr accum 0
  where accum bs a = 256 * a + fromIntegral bs


bsToDouble :: S.ByteString -> Int -> Double
bsToDouble bs n = if intV >= 0
                   then fromIntegral intV / 2147483647
                   else - (fromIntegral intV / (-2147483648))
  where intV = convertNBytesLen (S.unpack bs) `shift` (32 - 8 * n) 

ByteString 作为bsToDouble 的输入直接来自hGet h 3,整数是样本中的字节数(即 3)。

【问题讨论】:

  • 您想如何将这 24 位解码为 Float?您在上一个问题中提到了Data.Binary.IEEE754。在源代码中有一个解码 16 位浮点数的示例,我希望您的 24 位需要类似的东西,然后您可以使用 Data.Binary 来解码您的流。
  • 祝你好运,使用该库,它将样本读入嵌套链表。上次我需要这样的东西时,我开发了wave:hackage.haskell.org/package/wave。在不知道你不切实际的目标的情况下,很难建议什么是做你想做的事情的最佳方式。
  • @Mark 我不打算使用那个库。我编写了自己的库,使用Conduits 流式传输 .wav 文件的内容。我从库中使用的唯一部分是 ByteStringDouble 的转换。但我正在寻找更有效的方法。
  • @ryachza 我不知道如何将这 24 位解码为 Float。那是我的问题。我不理解 16 位的示例,不足以制作我自己的 24 位版本。
  • @Mark 为什么我的最终目标对于解决这个子问题很重要?我只需要一种方法来将我的ByteString(我从.wav 文件的数据块中读取3 个字节)转换为Float(介于-1 和1 之间)。例如。 hGet 给了我这个ByteString"\131\237\242"。它应该被转换为包含-0.10212671756744385 的浮点数

标签: haskell audio wav data-conversion


【解决方案1】:

这样的事情有帮助吗:

import Data.Int (Int32)
import Data.Bits ((.|.),(.&.),unsafeShiftL)
import Data.Word (Word32)
import Data.Binary
import qualified Data.ByteString as BS
import qualified Data.ByteString.Unsafe as BSU

int32_24be :: BS.ByteString -> Int32
int32_24be = \s ->
  let x =   unsafeShiftL (fromIntegral (BSU.unsafeIndex s 0)) 16
        .|. unsafeShiftL (fromIntegral (BSU.unsafeIndex s 1))  8
        .|.               fromIntegral (BSU.unsafeIndex s 2)
        :: Int32
      y = fromIntegral x :: Word32
  in fromIntegral (if x .&. 0x00800000 > 0 then y .|. 0xFF000000 else y .&. 0x00FFFFFF)

getFloat :: BS.ByteString -> Float
getFloat = (/ 2^^23) . fromIntegral . int32_24be

我的想法是 24 位值是整数,您希望将它们标准化为 -1 和 1 之间的浮点数(但不包括正 1)。如果是这种情况,我认为您可以使用 getFloatData.Binary.Get 一次解析 24 位流。

【讨论】:

  • 这绝对有帮助!虽然,它没有给我正确的结果。我一直在查看适用于我的 .wav 文件 (Data.WAVE) 的 WAVE 解析器的源代码,它在模块描述中说:This module currently supports reading and writing single-section little-endian PCM audio files containing up to 32-bit samples encoded according to the well-known WAVE sample encoding.'. So I think the problem is that your conversion uses big-endian. I think you're right about the values being 24 bit integers that have to be normalized. I will take a look at the Get` monad。谢谢
  • @ThomasVanhelden 是的,这绝对是一个反转原始编码的问题。如果它是 little-endian,我认为您只需要反转索引(2,1,0 而不是 0,1,2)。
  • 反转索引(0,1,2 到 2,1,0)就可以了。性能比我目前的实现要好。谢谢!
  • 顺便说一句,删除多余的 lambda 表达式似乎效果更好。
  • @ThomasVanhelden 您指的是\s -> 还是int32_24be s =?差异与内联有关,因为我相信 GHC 只会内联完全应用的功能。因此,性能可能会因优化和确切使用而异。
【解决方案2】:

我正在使用它来转换为Double,它似乎对浮点数也有帮助 - 它假设底层数字的二进制表示与内存中的表示相同。: https://hackage.haskell.org/package/reinterpret-cast

wordToFloat :: Word32 -> Float

然而,WAV 中的 24 位似乎与您的底层平台具有不同的内存特性 - 如果您找到正确的尾数/指数长度,应该很容易将其转换为正确的 32 位浮点数并使用它进行转换的函数。

【讨论】:

    猜你喜欢
    • 2011-09-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-06
    • 2019-01-06
    • 1970-01-01
    • 2011-03-12
    • 1970-01-01
    相关资源
    最近更新 更多