packages feed

opus-0.3.0.0: src/Codec/Audio/Opus/Decoder.hs

{-# LANGUAGE FlexibleContexts #-}
-- | This module contains the high-level API for decoding Opus audio.
module Codec.Audio.Opus.Decoder
  ( -- * Decoder
    Decoder, OpusException(..)
    -- ** create
  , withOpusDecoder, opusDecoderCreate, opusDecoderDestroy
    -- ** run
  , opusDecode, opusDecodeLazy
    -- * re-exports
  , module Codec.Audio.Opus.Types
  ) where

import           Codec.Audio.Opus.Internal.Opus
import           Codec.Audio.Opus.Types
import           Lens.Micro
import           Control.Monad.Catch
import           Control.Monad.IO.Class
import           Control.Monad.Trans.Resource
import           Data.ByteString                (ByteString)
import qualified Data.ByteString                as BS
import qualified Data.ByteString.Lazy           as BL
import           Foreign

-- | Decoder. Internally, it holds a pointer to the libopus decoder state and
-- a pointer to the (potential) last Opus error code.
newtype Decoder = Decoder (ForeignPtr DecoderT, ForeignPtr ErrorCode)
  deriving (Eq, Ord, Show)

-- | Allocates and initializes a decoder.
opusDecoderCreate :: (HasDecoderConfig cfg, MonadIO m) => cfg -> m Decoder
opusDecoderCreate cfg = liftIO $ do
  let cs = if isStereo then 2 else 1
      sr = cfg ^. (decoderConfig . samplingRate)
      isStereo = cfg ^. decoderIsStereo
  err <- mallocForeignPtr
  d <- withForeignPtr err (c_opus_decoder_create sr cs)
  d' <- newForeignPtr cp_opus_decoder_destroy d
  let enc = Decoder (d', err)
  opusLastError enc >>= maybe (pure enc) throwM

-- | Decode an Opus frame.
opusDecode
  :: (HasDecoderStreamConfig cfg, MonadIO m)
  => Decoder
  -- ^ 'Decoder' state
  -> cfg
  -- ^ The stream configuration that specifies the frame size, whether FEC is
  -- enabled, and the decoder configuration (sampling rate, channels).
  -> ByteString
  -- ^ Input signal (interleaved if 2 channels)
  -> m ByteString
opusDecode d cfg i =
  let fs = cfg ^. deStreamFrameSize
      fec = cfg ^. deStreamDecodeFec
      conf = cfg ^. deStreamDecoderConfig
      chans = if conf ^. decoderIsStereo then 2 else 1
      pcm_length = fs * chans
  in liftIO $
  BS.useAsCStringLen i $ \(i', ilen) ->
    allocaArray pcm_length $ \os ->
      runDecoderAction d $ \d' -> do
        r <- c_opus_decode d' i' (fromIntegral ilen) os
          (fromIntegral fs) (fromIntegral fec)
        let l = fromIntegral r
        if l < 0 then do
          let mbException = ErrorCode l ^? _ErrorCodeException 
          case mbException of
              Nothing -> throwM OpusInvalidPacket
              Just x  -> throwM x
        else do
          -- multiply by 2 because "os" is CShort i.e. Int16
          -- but CStringLen expects a CChar which is Int8
          BS.packCStringLen $ (castPtr os, (fromIntegral l) * 2 * chans)

-- | Decode an Opus frame, returning a lazy 'BL.ByteString'.
opusDecodeLazy :: (HasDecoderStreamConfig cfg, MonadIO m)
  => Decoder
  -- ^ 'Decoder' state
  -> cfg
  -- ^ The stream configuration that specifies the frame size, whether FEC is
  -- enabled, and the decoder configuration (sampling rate, channels).
  -> ByteString
  -- ^ Input signal (interleaved if 2 channels)
  -> m BL.ByteString
opusDecodeLazy d cfg = fmap BL.fromStrict . opusDecode d cfg

-- | For use with 'ResourceT' or any other monad that implements 'MonadResource'.
-- Safely allocate a 'Decoder' that will be freed upon exiting the monad, an
-- exception, or an explicit call to 'Control.Monad.Trans.Resource.release'.
withOpusDecoder :: (HasDecoderConfig cfg) => MonadResource m
  => cfg
  -> (Decoder -> IO ())
  -> m Decoder
withOpusDecoder cfg a =
  snd <$> allocate (opusDecoderCreate cfg) a

-- | Frees an 'Decoder'.
opusDecoderDestroy :: MonadIO m => Decoder -> m ()
opusDecoderDestroy (Decoder (d, err)) = liftIO $
  finalizeForeignPtr d >> finalizeForeignPtr err

-- | Get the last error from decoder.
opusLastError :: MonadIO m => Decoder -> m (Maybe OpusException)
opusLastError (Decoder (_, fp)) =
  liftIO $ (^? _ErrorCodeException) <$> withForeignPtr fp peek

-- | An 'DecoderAction' is an IO action that uses a 'DecoderT' for its operation.
type DecoderAction  a = Ptr DecoderT -> IO a

-- | Run a 'DecoderAction' using a 'Decoder', returning either 'OpusException'
-- for errors or the result of the action.
withDecoder' :: MonadIO m =>
  Decoder -> DecoderAction a -> m (Either OpusException a)
withDecoder' e@(Decoder (fp_a, _)) m = liftIO $
  withForeignPtr fp_a $ \a -> do
    r <- m a
    le <- opusLastError e
    pure $ maybe (Right r) Left le

-- | Run a 'DecoderAction'. Might throw an 'OpusException' if the action fails.
runDecoderAction :: (MonadIO m, MonadThrow m) =>
  Decoder -> DecoderAction a -> m a
runDecoderAction d m = withDecoder' d m >>= either throwM pure