Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 27 additions & 17 deletions src/Codec/Audio/Opus/Decoder.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{-# LANGUAGE FlexibleContexts #-}

-- | This module contains the high-level API for decoding Opus audio.
module Codec.Audio.Opus.Decoder
( -- * Decoder
Decoder, OpusException(..)
Expand All @@ -22,11 +22,12 @@ import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
import Foreign

-- | Decoder State
-- | 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 state.
-- | 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
Expand All @@ -38,14 +39,16 @@ opusDecoderCreate cfg = liftIO $ do
let enc = Decoder (d', err)
opusLastError enc >>= maybe (pure enc) throwM



-- | Decode an Opus frame.
opusDecode
:: (HasDecoderStreamConfig cfg, MonadIO m)
=> Decoder -- ^ 'Decoder' state
-> cfg -- ^ max data bytes
-> ByteString -- ^ input signal (interleaved if 2 channels)
=> 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
Expand All @@ -70,36 +73,43 @@ opusDecode d cfg i =
-- 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
=> Decoder
-- ^ 'Decoder' state
-> cfg
-> ByteString -- ^ input signal (interleaved if 2 channels)
-- ^ 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'. Is normaly called automaticly
-- when 'Decoder' gets out of scope
-- | Frees an 'Decoder'.
opusDecoderDestroy :: MonadIO m => Decoder -> m ()
opusDecoderDestroy (Decoder (d, err)) = liftIO $
finalizeForeignPtr d >> finalizeForeignPtr err


-- | get last error from decoder
-- | 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 an 'DecoderAction'.
-- | 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 $
Expand All @@ -108,7 +118,7 @@ withDecoder' e@(Decoder (fp_a, _)) m = liftIO $
le <- opusLastError e
pure $ maybe (Right r) Left le

-- | Run an 'DecoderAction'. Might throw an 'OpusException'
-- | 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
6 changes: 6 additions & 0 deletions src/Codec/Audio/Opus/Decoder/Conduit.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
-- | Conduit interface for decoding audio data with Opus.
module Codec.Audio.Opus.Decoder.Conduit
( decoderC, decoderLazyC
, decoderSink
Expand All @@ -11,21 +12,26 @@ import qualified Data.ByteString.Lazy as BL
import Data.Conduit.Combinators
import Prelude (($))

-- | Decode audio data with Opus.
decoderC :: (HasDecoderStreamConfig cfg, MonadResource m) =>
cfg -> ConduitT ByteString ByteString m ()
decoderC cfg = withDecoder (cfg ^. deStreamDecoderConfig) $
\d -> mapM (opusDecode d cfg)

-- | Decode lazy bytestring audio data with Opus.
decoderLazyC :: (HasDecoderStreamConfig cfg, MonadResource m) =>
cfg -> ConduitT ByteString BL.ByteString m ()
decoderLazyC cfg = withDecoder (cfg ^. deStreamDecoderConfig) $
\d -> mapM (opusDecodeLazy d cfg)

-- | A sink to decode audio data with Opus and return a lazy bytestring of the
-- whole stream.
decoderSink :: (HasDecoderStreamConfig cfg, MonadResource m) =>
cfg -> ConduitT ByteString o m BL.ByteString
decoderSink cfg = withDecoder (cfg ^. deStreamDecoderConfig) $
\d -> foldMapM (opusDecodeLazy d cfg)

-- | Run a conduit that uses a decoder with the given configuration.
withDecoder :: (HasDecoderConfig cfg, MonadResource m) =>
cfg -> (Decoder -> ConduitT i o m r) -> ConduitT i o m r
withDecoder cfg = bracketP (opusDecoderCreate cfg) opusDecoderDestroy
43 changes: 28 additions & 15 deletions src/Codec/Audio/Opus/Encoder.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{-# LANGUAGE FlexibleContexts #-}

-- | This module contains the high-level API for encoding Opus audio.
module Codec.Audio.Opus.Encoder
( -- * Encoder
Encoder, OpusException(..)
Expand All @@ -22,11 +22,12 @@ import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
import Foreign

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

-- | allocates and initializes an encoder state.
-- | Allocates and initializes an encoder.
opusEncoderCreate :: (HasEncoderConfig cfg, MonadIO m) => cfg -> m Encoder
opusEncoderCreate cfg = liftIO $ do
let cs = if isStereo then 2 else 1
Expand All @@ -39,14 +40,16 @@ opusEncoderCreate cfg = liftIO $ do
let enc = Encoder (e', err)
opusLastError enc >>= maybe (pure enc) throwM



-- | Encode an Opus frame.
opusEncode
:: (HasStreamConfig cfg, MonadIO m)
=> Encoder -- ^ 'Encoder' state
-> cfg -- ^ max data bytes
-> ByteString -- ^ input signal (interleaved if 2 channels)
=> Encoder
-- ^ 'Encoder' state
-> cfg
-- ^ The stream configuration that specifies the frame size, the output size,
-- and the encoder configuration (sampling rate, channels, coding mode).
-> ByteString
-- ^ Input signal (interleaved if 2 channels)
-> m ByteString
opusEncode e cfg i =
let fs = cfg ^. streamFrameSize
Expand All @@ -62,13 +65,22 @@ opusEncode e cfg i =
if l < 0 then throwM OpusInvalidPacket else
BS.packCStringLen ol

-- | Encode an Opus frame. Returns a lazy 'BL.ByteString'.
opusEncodeLazy :: (HasStreamConfig cfg, MonadIO m)
=> Encoder -- ^ 'Encoder' state
=> Encoder
-- ^ 'Encoder' state
-> cfg
-> ByteString -- ^ input signal (interleaved if 2 channels)
-- ^ The stream configuration that specifies the frame size, the output size,
-- and the encoder configuration (sampling rate, channels, coding mode).
-> ByteString
-- ^ Input signal (interleaved if 2 channels)
-> m BL.ByteString
opusEncodeLazy e cfg = fmap BL.fromStrict . opusEncode e cfg


-- | For use with 'ResourceT' or any other monad that implements 'MonadResource'.
-- Safely allocate an 'Encoder' that will be freed upon exiting the monad, an
-- exception, or an explicit call to 'Control.Monad.Trans.Resource.release'.
withOpusEncoder :: (HasEncoderConfig cfg) => MonadResource m
=> cfg
-> (Encoder -> IO ())
Expand All @@ -77,21 +89,22 @@ withOpusEncoder cfg a =
snd <$> allocate (opusEncoderCreate cfg) a


-- | Frees an 'Encoder'. Is normaly called automaticly
-- when 'Encoder' gets out of scope
-- | Frees an 'Encoder'.
opusEncoderDestroy :: MonadIO m => Encoder -> m ()
opusEncoderDestroy (Encoder (e, err)) = liftIO $
finalizeForeignPtr e >> finalizeForeignPtr err


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

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

-- | Run an 'EncoderAction'.
-- | Run an 'EncoderAction' using an 'Encoder', returning either 'OpusException'
-- for errors or the result of the action.
withEncoder' :: MonadIO m =>
Encoder -> EncoderAction a -> m (Either OpusException a)
withEncoder' e@(Encoder (fp_a, _)) m = liftIO $
Expand All @@ -100,7 +113,7 @@ withEncoder' e@(Encoder (fp_a, _)) m = liftIO $
le <- opusLastError e
pure $ maybe (Right r) Left le

-- | Run an 'EncoderAction'. Might throw an 'OpusException'
-- | Run an 'EncoderAction'. Might throw an 'OpusException' if the action fails.
runEncoderAction :: (MonadIO m, MonadThrow m) =>
Encoder -> EncoderAction a -> m a
runEncoderAction e m = withEncoder' e m >>= either throwM pure
6 changes: 6 additions & 0 deletions src/Codec/Audio/Opus/Encoder/Conduit.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
-- | Conduit interface for encoding audio data with Opus.
module Codec.Audio.Opus.Encoder.Conduit
( encoderC, encoderLazyC
, encoderSink
Expand All @@ -11,21 +12,26 @@ import qualified Data.ByteString.Lazy as BL
import Data.Conduit.Combinators
import Prelude (($))

-- | Encode audio data with Opus.
encoderC :: (HasStreamConfig cfg, MonadResource m) =>
cfg -> ConduitT ByteString ByteString m ()
encoderC cfg = withEncoder (cfg ^. streamConfig) $
\e -> mapM (opusEncode e cfg)

-- | Encode lazy bytestring audio data with Opus.
encoderLazyC :: (HasStreamConfig cfg, MonadResource m) =>
cfg -> ConduitT ByteString BL.ByteString m ()
encoderLazyC cfg = withEncoder (cfg ^. streamConfig) $
\e -> mapM (opusEncodeLazy e cfg)

-- | A sink to encode audio data with Opus and return a lazy bytestring of the
-- whole stream.
encoderSink :: (HasStreamConfig cfg, MonadResource m) =>
cfg -> ConduitT ByteString o m BL.ByteString
encoderSink cfg = withEncoder (cfg ^. streamConfig) $
\e -> foldMapM (opusEncodeLazy e cfg)

-- | Run a conduit that uses an encoder with the given configuration.
withEncoder :: (HasEncoderConfig cfg, MonadResource m) =>
cfg -> (Encoder -> ConduitT i o m r) -> ConduitT i o m r
withEncoder cfg = bracketP (opusEncoderCreate cfg) opusEncoderDestroy
Loading
Loading