【问题标题】:Bad Format Using hsndfile (libsndfile)使用 hsndfile (libsndfile) 的错误格式
【发布时间】:2011-08-06 12:13:10
【问题描述】:

我正在尝试使用 hsndfile(libsndfile 的 Haskell 绑定)来生成一个 .wav 文件,但我又遇到了一个无法克服的难题。以下代码引发错误“格式错误”。 (如 openWavHandle 中所写)。我已经尝试了我认为存在的所有字节顺序与 HeaderFormatWav 和 SampleFormatPcm16 的组合,但无济于事。有谁知道如何解决这个问题?

import qualified Sound.File.Sndfile as Snd
import qualified Graphics.UI.SDL.Mixer.Channels as SDLC
import qualified Graphics.UI.SDL.Mixer.General as SDLG
import qualified Graphics.UI.SDL.Mixer.Samples as SDLS

import Control.Applicative
import Foreign.Marshal.Array
import Data.List.Split (splitOn)
import Data.Word (Word16)
import System.IO (hGetContents, Handle, openFile, IOMode(..))

a4 :: Double
a4 = 440.0

frameRate :: Int
frameRate = 16000

noteLength :: Double
noteLength = 5.0

volume = maxBound `div` 2 :: Word16

noteToFreq :: (String, Int) -> Double
noteToFreq (note, octave) =
    if octave >= -1 && octave < 10 && n /= 12.0
    then a4 * 2 ** ((o - 4.0) + ((n - 9.0) / 12.0))
    else undefined
    where o = fromIntegral octave :: Double
          n = case note of
                "B#" -> 0.0
                "C"  -> 0.0
                "C#" -> 1.0
                "Db" -> 1.0
                "D"  -> 2.0
                "D#" -> 3.0
                "Eb" -> 3.0
                "E"  -> 4.0
                "Fb" -> 4.0
                "E#" -> 5.0
                "F"  -> 5.0
                "F#" -> 6.0
                "Gb" -> 6.0
                "G"  -> 7.0
                "G#" -> 8.0
                "Ab" -> 8.0
                "A"  -> 9.0
                "A#" -> 10.0
                "Bb" -> 10.0
                "B"  -> 11.0
                "Cb" -> 11.0
                _    -> 12.0

notesToFreqs :: [(String, Int)] -> [Double]
notesToFreqs = map noteToFreq 

noteToSample :: Double -> [Word16]
noteToSample freq =
    take (round $ noteLength * fromIntegral frameRate) $
    map ((round . (* fromIntegral volume)) . sin) 
    [0.0, (freq * 2 * pi / fromIntegral frameRate)..]

notesToSamples :: [Double] -> [Word16]
notesToSamples = concatMap noteToSample 

getFileName :: IO FilePath
getFileName = putStr "Enter the name of the file: " >> getLine

openMFile :: FilePath -> IO Handle
openMFile fileName = openFile fileName ReadMode

getNotesAndOctaves :: IO String
getNotesAndOctaves = getFileName >>= openMFile >>= hGetContents 

noteValuePairs :: String -> [(String, Int)]
noteValuePairs = pair . splitOn " "
    where pair (x:y:ys) = (x, read y) : pair ys
          pair []       = []

getWavSamples :: IO [Word16]
getWavSamples = (notesToSamples . notesToFreqs . noteValuePairs) <$>
                getNotesAndOctaves 

extendNotes :: [Word16] -> [Word16]
extendNotes = concatMap (replicate 1000)

format :: Snd.Format
format = Snd.Format Snd.HeaderFormatWav Snd.SampleFormatPcm16 Snd.EndianBig

openWavHandle :: [Word16] -> IO Snd.Handle
openWavHandle frames =
    let info = Snd.Info (length frames) frameRate 1 format 1 False
    in if Snd.checkFormat info
       then Snd.openFile "temp.wav" Snd.WriteMode info
       else error "Bad format."

writeWav :: [Word16] -> IO Snd.Count
writeWav frames = openWavHandle frames >>= \h ->
                  newArray frames >>= \ptr ->
                  Snd.hPutBuf h ptr (length frames) >>= \c ->
                  return c

makeWavFile :: IO ()
makeWavFile = getWavSamples >>= \s ->
              writeWav s >>= \c ->
              putStrLn $ "Frames written: " ++ show c

【问题讨论】:

  • 我没有使用 hsndfile - 但 Sound.File.Sndfile.Buffer 的文档似乎表明样本数据应该是浮点值 [-1.0, 1.0]。您正在尝试编写 Word16 的。
  • 我修改它以使用双打,但这并没有改变任何东西。 :/ 错误在于format
  • 我很确定 WAV 是小端 - format 声称它是大端。
  • @Stephen 改变了,仍然没有骰子。 :( 不过,我得到它抛出一个错误!Exception {errorString = "Error : major format is 0."} 我不是 100% 确定它是什么。
  • 我将其更改为 little-endian,并从 ghci 运行 *Main&gt; writeWav [1,2,3,4,3,2,1,0]。这返回“8”,并且文件“temp.wav”已正确创建。有关“主要格式”的错误意味着可能未正确生成波头。是否创建了任何输出?

标签: haskell wav libsndfile


【解决方案1】:

安德鲁,

我是 libsndfile 的主要作者,也是一个 Haskell 黑客。我已经看过了,就我而言,下面的最小示例代码应该可以工作。

import qualified Sound.File.Sndfile as Snd
import Control.Applicative
import Foreign.Marshal.Array
import Data.Word (Word16)
import System.IO (hGetContents, Handle, openFile, IOMode(..))

format :: Snd.Format
format = Snd.Format Snd.HeaderFormatWav Snd.SampleFormatPcm16 Snd.EndianFile

openWavHandle :: [Word16] -> IO Snd.Handle
openWavHandle frames =
    let info = Snd.Info (length frames) 441000 1 format 1 False
    in Snd.openFile "temp.wav" Snd.WriteMode info

writeWav :: [Word16] -> IO Snd.Count
writeWav frames = openWavHandle frames >>= \h ->
              newArray frames >>= \ptr ->
              Snd.hPutBuf h ptr (length frames) >>= \c ->
              return c

makeWavFile :: IO ()
makeWavFile = writeWav [1..256] >>= \c ->
          putStrLn $ "Frames written: " ++ show c


main :: IO ()
main = makeWavFile

事实上它并没有暗示 hsndfile 有问题。为了证明这一点,我破解了 libsndfile 的 C 源代码以打印出 SF_INFO 结构(hsndfile 调用 Info)的值,然后执行以下操作:

samplerate : 1
channels   : 65538
format     : 0x1

这显然是错误的。

我查看了 hsndfile 的 Interface.hsc 代码。 format 字段的值实际上以 channels 字段结尾,而 channels 字段以 samplerate 字段结尾。

我弄乱了这段代码,但我没有得到任何结果。我会 ping 上游 hsndfile 维护者。

【讨论】:

【解决方案2】:

感谢 Erik,此错误已在 version 0.5.1 on Hackage 中修复。

由于 Linux 上的 sndfile.h 中缺少包含,Haskell 绑定生成器无法确定样本计数 sf_count_t 的大小应该是 64 位,因此 Info 结构在转换为它的 C 表示形式。

请将此问题的后续跟进发送至hsndfile tracker

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-02
    • 2014-01-26
    相关资源
    最近更新 更多