mediabus-fdk-aac-0.4.0.0: src/Data/MediaBus/FdkAac/Encoder.hs
{-# LANGUAGE RankNTypes #-}
-- | This module defines a type-safe, high-level interface to encode linear
-- audio into AAC. This interface is still relatively low-level.
-- For an easy to use, high-level interface please consult
-- 'Data.MediaBus.FdkAac.Conduit.Encoder'.
module Data.MediaBus.FdkAac.Encoder
( aacFrameDuration
, aacFrameMediaData
, Aac
, aacHe48KhzStereo
, aacHe16KhzStereo
, aacHe48KhzMono
, aacHe16KhzMono
, aacLc48KhzStereo
, aacLc16KhzStereo
, aacLc48KhzMono
, aacLc16KhzMono
, aacEncoderConfig
, AacEncoderInfo(..)
, aacEncoderInfoId
, aacEncoderInfoDelay
, aacEncoderInfoFrameSize
, aacEncoderInfoAudioSpecificConfig
, type AacEncoderConfig
, type AacEncoderContext
, AacAot(..)
, AacAotProxy(..)
, type GetAacAot
, aacEncoderAllocate
, type AacEncT
, encodeLinearToAac
, flushAacEncoder
) where
import Conduit
import Control.DeepSeq
import Control.Lens
import Control.Monad.Logger
import Control.Monad.Trans.Reader
import Control.Monad.Trans.Resource
import Data.Kind
import Data.MediaBus
import Data.MediaBus.FdkAac.EncoderFdkWrapper
import Data.Proxy
import Data.String
import Data.Tagged
import qualified Data.Vector.Storable as V
import Data.Word
import GHC.Generics (Generic)
import GHC.TypeLits
import Text.Printf
-- * MediaBus Aac Encoder
-- | This is the media type for AAC encoded audio data.
data Aac (aot :: AacAot)
data instance Audio r c (Aac aot) = MkAacBuffer{aacFrameMediaData
:: !(V.Vector Word8),
aacFrameDuration :: !(Ticks64 r)}
deriving (Generic)
instance (KnownRate r, KnownChannelLayout c, Show (AacAotProxy aot)) =>
IsMedia (Audio r c (Aac aot))
instance (KnownRate r, KnownChannelLayout c, Show (AacAotProxy aot)) =>
HasMedia (Audio r c (Aac aot)) (Audio r c (Aac aot)) where
type MediaFrom (Audio r c (Aac aot)) = Audio r c (Aac aot)
type MediaTo (Audio r c (Aac aot)) = Audio r c (Aac aot)
media = iso id id
instance HasMediaBuffer (Audio r c (Aac aot)) (Audio r c (Aac aot)) where
type MediaBufferFrom (Audio r c (Aac aot)) = MediaBuffer Word8
type MediaBufferTo (Audio r c (Aac aot)) = MediaBuffer Word8
mediaBuffer =
lens
(MkMediaBuffer . aacFrameMediaData)
(\aacFrame -> \(MkMediaBuffer v) -> aacFrame {aacFrameMediaData = v})
instance (KnownRate r) =>
HasDuration (Audio r c (Aac aot)) where
getDuration MkAacBuffer {aacFrameDuration} =
from nominalDiffTime # aacFrameDuration
getDurationTicks MkAacBuffer {aacFrameDuration} =
convertTicks aacFrameDuration
instance NFData (Audio r c (Aac aot))
instance (KnownRate r, KnownChannelLayout c, Show (AacAotProxy aot)) =>
Show (Audio r c (Aac aot)) where
showsPrec d MkAacBuffer {aacFrameMediaData, aacFrameDuration} =
showParen
(d > 10)
(showString "aac frame: " .
showsPrec 11 (MkShowMedia :: MediaDescription (Audio r c (Aac aot))) .
showString ", samples: " .
shows aacFrameDuration .
showString ", data: " .
showsPrec 11 (V.length aacFrameMediaData) .
showString " bytes starting with: " .
showsPrec 11 (V.take 16 aacFrameMediaData))
instance (KnownRate r, KnownChannelLayout c, Show (AacAotProxy aot)) =>
Show (MediaDescription (Audio r c (Aac aot))) where
showsPrec d _ =
showParen
(d > 10)
(showsPrec 11 (MkAacAotProxy :: AacAotProxy aot) . showChar ' ' .
showsPrec 11 (MkRateProxy :: RateProxy r) .
showChar ' ' .
showsPrec 11 (MkChannelLayoutProxy :: ChannelLayoutProxy c))
-- ** Aac Configuration
-- | Create the 'Config' value needed to intialize the encoder from type-level
-- audio stream settings.
aacEncoderConfig
:: forall rate channels aot proxy.
(KnownRate rate, KnownChannelLayout channels, KnownNat (GetAacAot aot))
=> proxy (Audio rate channels (Aac aot)) -> AacEncoderConfig rate channels aot
aacEncoderConfig _pa =
let pr :: Proxy rate
pr = Proxy
pch :: Proxy channels
pch = Proxy
pco :: AacAotProxy aot
pco = MkAacAotProxy
in Tagged $
simpleConfig
(getAacAot pco)
(numberOfChannels pch)
(fromInteger (rateVal pr))
-- *** Pre-defined Aac media Proxies
-- | 48 kHz Stereo 'HighEfficiency'
aacHe48KhzStereo :: Proxy (Audio (Hz 48000) Stereo (Aac 'HighEfficiency))
aacHe48KhzStereo = Proxy
-- | 16 kHz Stereo 'HighEfficiency'
aacHe16KhzStereo :: Proxy (Audio (Hz 16000) Stereo (Aac 'HighEfficiency))
aacHe16KhzStereo = Proxy
-- | 48 kHz Mono 'HighEfficiency'
aacHe48KhzMono :: Proxy (Audio (Hz 48000) Mono (Aac 'HighEfficiency))
aacHe48KhzMono = Proxy
-- | 16 kHz Mono 'HighEfficiency'
aacHe16KhzMono :: Proxy (Audio (Hz 16000) Mono (Aac 'HighEfficiency))
aacHe16KhzMono = Proxy
-- | 48 kHz Stereo 'LowComplexity'
aacLc48KhzStereo :: Proxy (Audio (Hz 48000) Stereo (Aac 'LowComplexity))
aacLc48KhzStereo = Proxy
-- | 16 kHz Stereo 'LowComplexity'
aacLc16KhzStereo :: Proxy (Audio (Hz 16000) Stereo (Aac 'LowComplexity))
aacLc16KhzStereo = Proxy
-- | 48 kHz Mono 'LowComplexity'
aacLc48KhzMono :: Proxy (Audio (Hz 48000) Mono (Aac 'LowComplexity))
aacLc48KhzMono = Proxy
-- | 16 kHz Mono 'LowComplexity'
aacLc16KhzMono :: Proxy (Audio (Hz 16000) Mono (Aac 'LowComplexity))
aacLc16KhzMono = Proxy
-- | The encoding specific information, created __by the encoder__ during
-- initialization.
data AacEncoderInfo (rate :: Rate) (channels :: Type) (aot :: AacAot) = MkAacEncoderInfo
{ _aacEncoderInfoDelay :: !Word64
-- ^ Number of samples that the output will be delayed through the encoding
-- mechanism (e.g. aot delay lines)
, _aacEncoderInfoFrameSize :: !Word64
-- ^ Number of samples that are encoded into each frame.
, _aacEncoderInfoAudioSpecificConfig :: !(V.Vector Word8)
-- ^ The audio specific configuration /box/ as specified in ISO/IEC 14496-3
-- section 1.6, that the encoder generates.
, _aacEncoderInfoId :: String
-- ^ Identification string for one specific encoder, derived from the natice
-- C pointer in 'encoderHandle'.
} deriving (Generic)
instance NFData (AacEncoderInfo r c a)
instance ( KnownRate r
, KnownChannelLayout c
, Show (AacAotProxy aot)
, KnownNat (GetAacAot aot)
) =>
Show (AacEncoderInfo r c aot) where
showsPrec d MkAacEncoderInfo { _aacEncoderInfoDelay
, _aacEncoderInfoFrameSize
, _aacEncoderInfoAudioSpecificConfig
, _aacEncoderInfoId
} =
showParen
(d > 10)
(showString _aacEncoderInfoId . showString " " .
showsPrec 11 (MkShowMedia :: MediaDescription (Audio r c (Aac aot))) .
showParen
True
(showString "encoding delay: " . showsPrec 11 _aacEncoderInfoDelay .
showString ", samples per frame: " .
showsPrec 11 _aacEncoderInfoFrameSize .
showString ", audio specific config: " .
showsPrec 11 _aacEncoderInfoAudioSpecificConfig))
-- | Tag a 'Config' with the compile time configuration parameters.
type AacEncoderConfig rate channels aot = Tagged '( rate, channels, aot) Config
-- | Tag an 'Encoder' with the compile time configuration parameters.
type AacEncoderContext rate channels aot = Tagged '( rate, channels, aot) Encoder
-- ** Audio Object Types
-- | Type level symbol to indicate that the AAC audio coding is used for the
-- respective media. This is a phantom type to index the 'Audio' data family.
data AacAot
= LowComplexity -- ^ corresponds to audio object type @2@
| HighEfficiency -- ^ corresponds to audio object type @5@
deriving (Show)
instance Show (AacAotProxy 'LowComplexity) where
showsPrec _ _ = showString "low-complexity"
instance Show (AacAotProxy 'HighEfficiency) where
showsPrec _ _ = showString "high-efficiency"
-- | Convert the 'AacAot' to an ISO/IEC 14496-3 Audio Object Type value.
type family GetAacAot (c :: AacAot) :: Nat where
GetAacAot 'LowComplexity = 2
GetAacAot 'HighEfficiency = 5
-- | Convert the 'AacAot' to an ISO/IEC 14496-3 Audio Object Type value.
getAacAot
:: forall aot c proxy.
(Integral aot, KnownNat (GetAacAot c))
=> proxy c -> aot
getAacAot _ =
let px :: Proxy (GetAacAot c)
px = Proxy
in fromInteger $ natVal px
-- | Proxy for showing an 'AacAot'
data AacAotProxy (aot :: AacAot) =
MkAacAotProxy
-- * Encoder Initialization
-- | Allocate a new encoder instance in a 'ResourceT' monad such that it will
-- automatically be released and the freed.
aacEncoderAllocate
:: ( KnownRate rate
, KnownChannelLayout channels
, Show (AacAotProxy aot)
, KnownNat (GetAacAot aot)
, MonadIO m
, MonadResource m
, MonadThrow m
, MonadLoggerIO m
)
=> AacEncoderConfig rate channels aot
-> m (AacEncoderContext rate channels aot, AacEncoderInfo rate channels aot)
aacEncoderAllocate (Tagged cfg) = do
let destroyAndLog logger enc =
runLoggingT
(do $logDebug (fromString (printf "%s destroying" (show enc)))
res <- liftIO (destroy enc)
case res of
AacEncOk ->
$logInfo (fromString (printf "%s destroyed" (show enc)))
other ->
$logError
(fromString
(printf
"%s failed to destroy: %s"
(show enc)
(show other))))
logger
logger <- askLoggerIO
(rkey, eres) <- allocate (create cfg) (Prelude.mapM_ (destroyAndLog logger))
case eres of
Left err -> do
$logError (fromString (printf "encoder allocation failed: %s" (show err)))
release rkey
throwM err
Right enc@MkEncoder {encoderDelay, frameSize, audioSpecificConfig} -> do
let i =
MkAacEncoderInfo
{ _aacEncoderInfoDelay = fromIntegral encoderDelay
, _aacEncoderInfoFrameSize = fromIntegral frameSize
, _aacEncoderInfoAudioSpecificConfig = audioSpecificConfig
, _aacEncoderInfoId = show enc
}
$logInfo (fromString (printf "allocated: %s" (show i)))
return (Tagged enc, i)
-- * Stateful Encoding
-- | Type alias for the internal ReaderT monad of the aac encoder.
type AacEncT rate channels aot m a = ReaderT Encoder m a
-- | Aac encoded 'Frame's
type AacFrame rate channels aot = Frame () () (Audio rate channels (Aac aot))
-- | Convert linear audio to AAC encoded audio with the given encoder settings.
encodeLinearToAac
:: ( KnownRate rate
, KnownChannelLayout channels
, KnownRate rate
, Show (AacAotProxy aot)
, CanBeSample (Pcm channels S16)
, MonadIO m
, MonadThrow m
, MonadLogger m
)
=> Frame someSeq someTicks (Audio rate channels (Raw S16))
-> AacEncT rate channels aot m [AacFrame rate channels aot]
encodeLinearToAac (MkFrame _ _ !aIn) =
let dIn = V.unsafeCast (aIn ^. mediaBuffer . mediaBufferVector)
in encodeAllFrames dIn []
-- | (Internal) Convert linear audio to AAC encoded audio with the given encoder settings,
-- from a raw sample vector.
-- If the input contains more than 'frameSize' samples per channel, recurse
-- until all input is consumed and return the generated frames.
encodeAllFrames
:: forall m channels rate aot.
( MonadLogger m
, MonadIO m
, MonadThrow m
, KnownChannelLayout channels
, KnownRate rate
, Show (AacAotProxy aot)
, V.Storable (Pcm channels S16)
)
=> V.Vector Word16
-> [AacFrame rate channels aot]
-> AacEncT rate channels aot m [AacFrame rate channels aot]
encodeAllFrames dIn acc
| V.length dIn <= 0 = return []
| otherwise = do
e <- ask
eres <- liftIO $ encode e dIn
case eres of
Left err -> do
$logError (fromString (printf "encoding failed: %s" (show err)))
throwM err
Right MkEncodeResultEof -> return (reverse acc)
Right MkEncodeResult {encodeResultLeftOverInput, encodeResultSamples} -> do
acc' <- appendNextFrame acc encodeResultSamples
case encodeResultLeftOverInput of
Nothing -> return (reverse acc')
Just rest -> encodeAllFrames rest acc'
-- | Flush any left over input and then also flush delay lines of the AAC encoder.
flushAacEncoder
:: ( MonadIO m
, MonadLogger m
, MonadThrow m
, KnownChannelLayout channels
, KnownRate r
, Show (AacAotProxy aot)
, V.Storable (Pcm channels S16)
)
=> AacEncT r channels aot m [AacFrame r channels aot]
flushAacEncoder = do
$logDebug "flushing"
flushLoop []
where
flushLoop acc = do
e <- ask
eres <- liftIO $ flush e
case eres of
Left err -> do
$logError (fromString (printf "flushing failed: %s" (show err)))
throwM err
Right MkEncodeResultEof -> do
$logInfo "flushed"
return (reverse acc)
Right MkEncodeResult {encodeResultSamples} -> do
acc' <- appendNextFrame acc encodeResultSamples
flushUntilEof acc'
-- | (Internal) Flush until the encoder returns the EOF return code.
flushUntilEof
:: (MonadThrow m, MonadLogger m, MonadIO m)
=> [AacFrame r channels aot]
-> AacEncT r channels aot m [AacFrame r channels aot]
flushUntilEof acc = do
e <- ask
eres <- liftIO $ flush e
case eres of
Left err -> do
$logError (fromString (printf "flushing failed: %s" (show err)))
throwM err
Right MkEncodeResultEof -> do
$logInfo "flushed"
return (reverse acc)
Right MkEncodeResult {encodeResultSamples} -> do
acc' <- appendNextFrame acc encodeResultSamples
flushUntilEof acc'
-- | (Internal) Create a new 'Frame' and increment the sequence number and
-- update the timestamp from 'AacEncSt'
appendNextFrame
:: Monad m
=> [AacFrame rate channels aot]
-> Maybe (V.Vector Word8)
-> AacEncT rate channels aot m [AacFrame rate channels aot]
appendNextFrame acc = maybe (return acc) (\es -> (: acc) <$> mkNextFrame es)
where
mkNextFrame es = do
len <- fromIntegral <$> asks frameSize
return (MkFrame () () (MkAacBuffer es len))
makeLenses ''AacEncoderInfo