packages feed

opus-0.3.0.0: test/Spec.hs

module Main(main) where

import           Codec.Audio.Opus.Encoder
import           Codec.Audio.Opus.Decoder
import           Lens.Micro
import           Control.Exception
import           Control.Monad (guard, forM_)
import qualified Data.ByteString          as B
import           Data.Bits
import           Data.List
import           Data.Word (Word8)
import           System.Directory (doesDirectoryExist)
import           Test.Hspec
import qualified OpusCompare as Opus

cfgs :: [EncoderConfig]
cfgs = [mkEncoderConfig sr s c | sr <- srs, s <- ss, c <- cs]
  where
    srs = [opusSR48k, opusSR24k, opusSR16k, opusSR12k, opusSR8k ]
    ss = [True, False]
    cs = [app_voip, app_audio, app_lowdelay]

seqWithCfgs :: Monad m => (EncoderConfig -> m a) -> m ()
seqWithCfgs a = sequence_ (a <$> cfgs)

testEncoderCreate :: HasEncoderConfig cfg => cfg -> SpecWith ()
testEncoderCreate cfg =
  let n = mconcat [ "create valid ", show $ cfg ^. encoderConfig, " encoder"]
  in it n $
     opusEncoderCreate cfg >>= (`shouldSatisfy` const True)


onlyIfTestVectorsExist :: IO () -> IO ()
onlyIfTestVectorsExist action = do
  exists <- doesDirectoryExist "opus_newvectors"
  if exists then action else fail "opus_newvectors directory not found"

decodeFile :: DecoderConfig -> B.ByteString -> IO B.ByteString
decodeFile decoderCfg bytes = do
  decoder <- opusDecoderCreate decoderCfg
  loop decoder bytes
  where

    -- | Convert four unsigned bytes to a 32-bit integer. fromIntegral is
    -- applied to each byte before shifting to not lose any bits.
    charToInt :: [Word8] -> Int
    charToInt (b1:b2:b3:b4:[]) = (fromIntegral b1) `shiftL` 24 .|. (fromIntegral b2) `shiftL` 16 .|. (fromIntegral b3) `shiftL` 8 .|. (fromIntegral b4)
    charToInt _ = error "wrong length to convert to int"

    maxPacket, maxFrameSize :: Int
    maxPacket = 1500
    maxFrameSize = 48000 * 2

    -- | A simplified port of the official opus_demo.c file's decoding loop
    loop decoder bytes
      | B.length bytes < 8 = pure mempty
      | otherwise = do
        -- lines 649 to 672 in opus_demo.c
        let inputLen = charToInt $ B.unpack $ B.take 4 bytes
        guard $ inputLen <= maxPacket && inputLen >= 0 -- invalid payload length

        let inputEncFinalRange = charToInt $ B.unpack $ B.take 4 $ B.drop 4 bytes
        let (inputData, remaining) = B.splitAt inputLen $ B.drop 8 bytes
        guard $ inputLen == B.length inputData -- ran out of input, expecting inputLen but got B.length bytes

        guard $ inputLen /= 0 -- lost packets are not supported for now in this test

        -- line 783
        let outputSamples = maxFrameSize
        decoded <- opusDecode decoder (mkDecoderStreamConfig decoderCfg outputSamples 0) inputData
        -- recursively continue with the rest
        (decoded <>) <$> loop decoder remaining

main :: IO ()
main = hspec $ do
  describe "opusEncoderCreate" $
    seqWithCfgs testEncoderCreate
  around_ onlyIfTestVectorsExist $ do
    -- These tests require the opus test vectors, downloaded from the official
    -- opus website.
    describe "opus mono test vectors" $
      forM_ ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] $ \file -> do
        it ("mono testvector " <> file) $ do
          let decoderCfg = mkDecoderConfig opusSR48k False
          B.readFile ("opus_newvectors/testvector" <> file <> ".bit") >>= decodeFile decoderCfg >>= B.writeFile "tmp.out"
          Opus.compareFiles Opus.Mono opusSR48k ("opus_newvectors/testvector" <> file <> "m.dec") "tmp.out" >>= shouldBe True

    describe "opus stereo test vectors" $
      forM_ ["01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"] $ \file -> do
        it ("stereo testvector " <> file) $ do
          let decoderCfg = mkDecoderConfig opusSR48k True
          B.readFile ("opus_newvectors/testvector" <> file <> ".bit") >>= decodeFile decoderCfg >>= B.writeFile "tmp.out"
          Opus.compareFiles Opus.Stereo opusSR48k ("opus_newvectors/testvector" <> file <> ".dec") "tmp.out" >>= shouldBe True


{-

-}
{-
  let testEncoderEncodeWith = testEncoderEncode <$> srs <*> ss <*> cs
  describe "opusEncode" $ do
r <-
      sequence_ $ testEncoderEncodeWith <*> pure "empty input" <*> pure mempty >>= (`shouldSatisfy` const True)
    return ()
-}