mediabus-fdk-aac (empty) → 0.3.0.0
raw patch · 10 files changed
+1504/−0 lines, 10 filesdep +basedep +bytestringdep +conduitsetup-changed
Dependencies added: base, bytestring, conduit, conduit-combinators, containers, criterion, deepseq, ghc-prim, inline-c, lens, mediabus, mediabus-fdk-aac, monad-logger, random, resourcet, spool, tagged, text, time, transformers, vector
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- bench/Encoder.hs +86/−0
- examples/Main.hs +30/−0
- mediabus-fdk-aac.cabal +112/−0
- src/Data/MediaBus/FdkAac.hs +9/−0
- src/Data/MediaBus/FdkAac/Conduit/Encoder.hs +114/−0
- src/Data/MediaBus/FdkAac/Encoder.hs +410/−0
- src/Data/MediaBus/FdkAac/EncoderFdkWrapper.c +200/−0
- src/Data/MediaBus/FdkAac/EncoderFdkWrapper.hs +511/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Sven Heyll and Lindenbaum GmbH (c) 2016, 2017++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Author name here nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ bench/Encoder.hs view
@@ -0,0 +1,86 @@+module Main+ ( main+ ) where++import Control.Monad+import Control.Monad.Logger+import Data.Conduit+import Data.MediaBus+import Data.MediaBus.FdkAac+import Data.Conduit.List+import Data.Time.Clock+import Data.Typeable+import Criterion.Main+import Control.Lens+import Data.Vector.Storable as V+import Data.Word+import GHC.TypeLits++main :: IO ()+main =+ defaultMain+ [bgroupFromTestDuration 1000 16000, bgroupFromTestDuration 1000 48000]++bgroupFromTestDuration :: NominalDiffTime -> Int -> Benchmark+bgroupFromTestDuration testdur samplesPerSecond =+ bgroup+ (show testdur Prelude.++ " ptime=20ms " Prelude.++ show samplesPerSecond Prelude.+++ " Hz") $+ let frameCount = ceiling (testdur / ptime)+ ptime = 20 / 1000+ in [ bgroup+ "AAC HE"+ [ bench "Stereo" $+ nfIO $+ encodeNFrames (aacEncoderConfig aacHe16KhzStereo) $+ testFrames testdur frameCount+ , bench "Mono" $+ nfIO $+ encodeNFrames (aacEncoderConfig aacHe16KhzMono) $+ testFrames testdur frameCount+ ]+ , bgroup+ "AAC LC"+ [ bench "Stereo" $+ nfIO $+ encodeNFrames (aacEncoderConfig aacLc16KhzStereo) $+ testFrames testdur frameCount+ , bench "Mono" $+ nfIO $+ encodeNFrames (aacEncoderConfig aacLc16KhzMono) $+ testFrames testdur frameCount+ ]+ ]++encodeNFrames+ :: forall r channels aot.+ ( CanBeSample (Pcm channels S16)+ , CanBeBlank (Pcm channels S16)+ , Show (Pcm channels S16)+ , KnownRate r+ , KnownChannelLayout channels+ , Show (AacAotProxy aot)+ , KnownNat (GetAacAot aot)+ , Typeable r+ )+ => AacEncoderConfig r channels aot+ -> [Stream SrcId32 SeqNum16 (Ticks r Word64) () (Audio r channels (Raw S16))]+ -> IO [Stream SrcId32 SeqNum16 (Ticks r Word64) (AacEncoderInfo r channels aot) (Audio r channels (Aac aot))]+encodeNFrames cfg pcms =+ runStdoutLoggingT $+ runConduitRes $ sourceList pcms .| encodeLinearToAacC cfg .| consume++testFrames+ :: (KnownRate r, IsPcmValue (Pcm channels S16))+ => NominalDiffTime+ -> Int+ -> [Stream SrcId32 SeqNum16 (Ticks r Word64) () (Audio r channels (Raw S16))]+testFrames frameDuration frames =+ [ MkStream+ (Next+ (MkFrame+ (fromIntegral x * (nominalDiffTime # frameDuration))+ (fromIntegral x)+ (blankFor frameDuration)))+ | x <- [0 .. frames]+ ]
+ examples/Main.hs view
@@ -0,0 +1,30 @@+module Main+ ( main+ ) where++import Control.Monad+import Control.Monad.Logger+import Data.Conduit+import Data.MediaBus+import Data.MediaBus.FdkAac++main :: IO ()+main = void encodeOneSeconfOfSilence++encodeOneSeconfOfSilence+ :: IO [Stream SrcId32+ SeqNum16+ (Ticks64 (Hz 48000))+ (AacEncoderInfo (Hz 48000) Stereo 'HighEfficiency)+ (Audio (Hz 48000) Stereo (Aac 'HighEfficiency))]+encodeOneSeconfOfSilence =+ runStdoutLoggingT $+ runConduitRes $+ yieldNextFrame (MkFrame 0 0 pcmAudioOneSecond)+ .| encodeLinearToAacC encoderConfig+ .| traceShowSink 1 "Example Trace"++ where+ pcmAudioOneSecond :: Audio (Hz 48000) Stereo (Raw S16)+ pcmAudioOneSecond = blankFor 1+ encoderConfig = aacEncoderConfig aacHe48KhzStereo
+ mediabus-fdk-aac.cabal view
@@ -0,0 +1,112 @@+name: mediabus-fdk-aac+version: 0.3.0.0+cabal-version: >=1.10+build-type: Simple+license: BSD3+license-file: LICENSE+copyright: 2016, 2017 Sven Heyll, Lindenbaum GmbH+maintainer: sven.heyll@lindenbaum.eu+homepage: https://github.com/lindenbaum/mediabus-fdk-aac+synopsis: Mediabus plugin for the Frauenhofer ISO-14496-3 AAC FDK+description:+ Please see README.md+category: Sound+author: Sven Heyll++library+ exposed-modules:+ Data.MediaBus.FdkAac.Encoder+ Data.MediaBus.FdkAac.Conduit.Encoder+ Data.MediaBus.FdkAac+ build-depends:+ base >=4.9 && <5,+ bytestring >=0.10.8.1 && <0.11,+ conduit >=1.2.9 && <1.3,+ conduit-combinators >=1.1.0 && <1.2,+ containers >=0.5.7.1 && <0.6,+ deepseq >=1.4.2.0 && <1.5,+ inline-c >=0.5.6.1 && <0.6,+ lens >=4.15.1 && <4.16,+ mediabus >=0.3.2.0 && <0.4,+ monad-logger >=0.3.20.2 && <0.4,+ random ==1.1.*,+ resourcet >=1.1.9 && <1.2,+ spool ==0.1.*,+ tagged >=0.8.5 && <0.9,+ text >=1.2.2.1 && <1.3,+ time >=1.6.0.1 && <1.7,+ transformers >=0.5.2.0 && <0.6,+ vector >=0.11.0.0 && <0.12+ cc-options: -std=c99 -Wall -O2+ pkgconfig-depends: fdk-aac >=0.1.4+ c-sources:+ src/Data/MediaBus/FdkAac/EncoderFdkWrapper.c+ default-language: Haskell2010+ default-extensions: BangPatterns ConstraintKinds CPP DataKinds+ DefaultSignatures DeriveDataTypeable DeriveFunctor DeriveGeneric+ DuplicateRecordFields FlexibleInstances FlexibleContexts+ FunctionalDependencies GADTs GeneralizedNewtypeDeriving+ KindSignatures MultiParamTypeClasses NamedFieldPuns+ OverloadedStrings QuasiQuotes RecordWildCards RankNTypes+ ScopedTypeVariables StandaloneDeriving TemplateHaskell+ TupleSections TypeApplications TypeFamilies TypeInType+ TypeOperators TypeSynonymInstances UnicodeSyntax+ extra-libraries:+ m+ hs-source-dirs: src+ other-modules:+ Data.MediaBus.FdkAac.EncoderFdkWrapper+ ghc-options: -O2 -Wall -funbox-strict-fields -fno-warn-unused-do-bind++test-suite examples+ type: exitcode-stdio-1.0+ main-is: Main.hs+ build-depends:+ base >=4.9.1.0 && <4.10,+ deepseq >=1.4.2.0 && <1.5,+ ghc-prim >=0.5.0.0 && <0.6,+ lens >=4.15.1 && <4.16,+ monad-logger >=0.3.20.2 && <0.4,+ mediabus-fdk-aac >=0.3.0.0 && <0.4,+ mediabus >=0.3.2.0 && <0.4,+ conduit >=1.2.9 && <1.3,+ vector >=0.11.0.0 && <0.12+ default-language: Haskell2010+ default-extensions: BangPatterns ConstraintKinds CPP DataKinds+ DefaultSignatures DeriveDataTypeable DeriveFunctor DeriveGeneric+ DuplicateRecordFields FlexibleInstances FlexibleContexts+ FunctionalDependencies GADTs GeneralizedNewtypeDeriving+ KindSignatures MultiParamTypeClasses NamedFieldPuns+ OverloadedStrings QuasiQuotes RecordWildCards RankNTypes+ ScopedTypeVariables StandaloneDeriving TemplateHaskell+ TupleSections TypeApplications TypeFamilies TypeInType+ TypeOperators TypeSynonymInstances UnicodeSyntax+ hs-source-dirs: examples+ ghc-options: -O2 -Wall -funbox-strict-fields -fno-warn-unused-do-bind++benchmark encoder-benchmark+ type: exitcode-stdio-1.0+ main-is: Encoder.hs+ build-depends:+ base >=4.9.1.0 && <4.10,+ deepseq >=1.4.2.0 && <1.5,+ ghc-prim >=0.5.0.0 && <0.6,+ lens >=4.15.1 && <4.16,+ monad-logger >=0.3.20.2 && <0.4,+ criterion >=1.1.4.0 && <1.2,+ mediabus-fdk-aac >=0.3.0.0 && <0.4,+ mediabus >=0.3.2.0 && <0.4,+ conduit >=1.2.9 && <1.3,+ vector >=0.11.0.0 && <0.12+ default-language: Haskell2010+ default-extensions: BangPatterns ConstraintKinds CPP DataKinds+ DefaultSignatures DeriveDataTypeable DeriveFunctor DeriveGeneric+ DuplicateRecordFields FlexibleInstances FlexibleContexts+ FunctionalDependencies GADTs GeneralizedNewtypeDeriving+ KindSignatures MultiParamTypeClasses NamedFieldPuns+ OverloadedStrings QuasiQuotes RecordWildCards RankNTypes+ ScopedTypeVariables StandaloneDeriving TemplateHaskell+ TupleSections TypeApplications TypeFamilies TypeInType+ TypeOperators TypeSynonymInstances UnicodeSyntax+ hs-source-dirs: bench+ ghc-options: -O2 -Wall -funbox-strict-fields -fno-warn-unused-do-bind
+ src/Data/MediaBus/FdkAac.hs view
@@ -0,0 +1,9 @@+-- | A MediaBus adapter for the Frauenhofer AAC encoder/decoder.+-- This module reexports the high-level encoding and decoding functions.+module Data.MediaBus.FdkAac+ ( module Data.MediaBus.FdkAac.Encoder+ , module Data.MediaBus.FdkAac.Conduit.Encoder+ ) where++import Data.MediaBus.FdkAac.Conduit.Encoder+import Data.MediaBus.FdkAac.Encoder
+ src/Data/MediaBus/FdkAac/Conduit/Encoder.hs view
@@ -0,0 +1,114 @@+{-# LANGUAGE RankNTypes #-}++-- | This module defines a conduit to encode linear audio into AAC.+module Data.MediaBus.FdkAac.Conduit.Encoder+ ( encodeLinearToAacC+ ) where++import Conduit+import Control.Lens+import Control.Monad+import Control.Monad.Logger+import Control.Monad.Trans.State.Strict as State+import Data.MediaBus+import Data.MediaBus.FdkAac.Encoder+import Data.String+import Data.Tagged+import Data.Typeable+import GHC.TypeLits+++-- * Conduit Based 'Stream' Encoding+-- | A conduit that receives signed 16 bit, mono or stereo, 'Raw' 'Audio' and+-- yields AAC encoded frames. The timestamps of the input frames will be+-- regarded such that gaps in the timestamps are reflected in the generated+-- frames. The sequence numbering will be+encodeLinearToAacC+ :: forall channels r aot i s t m.+ ( MonadLoggerIO m+ , MonadThrow m+ , MonadResource m+ , KnownChannelLayout channels+ , KnownRate r+ , Typeable r+ , Show (AacAotProxy aot)+ , KnownNat (GetAacAot aot)+ , Show i+ , Show t+ , Typeable t+ , LocalOrd t+ , Show s+ , Typeable s+ , Num t+ , Num s+ , CanBeSample (Pcm channels S16)+ , Show (Pcm channels S16)+ , Integral t+ )+ => AacEncoderConfig r channels aot+ -> Conduit (Stream i s (Ticks r t) () (Audio r channels (Raw S16))) m (Stream i s (Ticks r t) (AacEncoderInfo r channels aot) (Audio r channels (Aac aot)))+encodeLinearToAacC aacCfg = do+ (Tagged enc, info) <- lift $ aacEncoderAllocate aacCfg+ (_res, _st, ()) <-+ prefixLogsC (show enc ++ " - ") $+ runRWSC+ enc+ (MkAacEncSt @r @channels @aot 0)+ (evalStateC+ (initialReframerState :: ReframerSt s (Ticks r t))+ (encodeThenFlush info))+ return ()+ where+ encodeThenFlush info = do+ awaitForever go+ aacs <- lift $ lift flushAacEncoder+ Prelude.mapM_ yieldAacFrame aacs+ where+ go (MkStream (Next f)) = do+ let reframeFrame = f & framePayload %~ getDurationTicks+ $logDebug (fromString ("in: " ++ show f))+ pushRes <- lift $ pushFrame reframeFrame+ case pushRes of+ Nothing -> return ()+ Just er -> do+ $logInfo+ (fromString+ ("frame timing problem: " ++ show er ++ ", frame: " +++ show reframeFrame))+ lift $ State.get >>= $logInfoSH+ aacs <- lift $ lift flushAacEncoder+ Prelude.mapM_ yieldAacFrame aacs+ lift $+ pushStartFrame+ (reframeFrame ^. frameTimestamp + reframeFrame ^. framePayload)+ aacs <-+ lift $ lift $+ encodeLinearToAac (view framePayload f)+ Prelude.mapM_ yieldAacFrame aacs+ go start@(MkStream (Start (MkFrameCtx fi ft fs _fp))) = do+ lift $ pushStartFrame ft+ lift $ $logDebug (fromString ("in: " ++ show start))+ let outStart = MkFrameCtx fi ft fs info+ lift $ $logDebug (fromString ("out: " ++ show outStart))+ yieldStartFrameCtx outStart+ yieldAacFrame fc =+ let wantDur = getDurationTicks fc+ in when (wantDur > 0) $ do+ frm <- lift $ generateFrame wantDur+ when (frm ^. framePayload < wantDur) $ do+ $logInfo+ (fromString+ ("encoder generated more output than input, input: " +++ show (frm ^. framePayload) +++ " < output: " +++ show wantDur))+ availDur <- lift nextFrameAvailableDuration+ $logInfo+ (fromString+ ("samples enqueuend in the encoder delay lines: " +++ show availDur))+ rfst <- lift $ State.get+ $logInfoSH rfst+ let outFrm = frm & framePayload .~ fc+ $logDebug (fromString ("out: " ++ show outFrm))+ yieldNextFrame outFrm
+ src/Data/MediaBus/FdkAac/Encoder.hs view
@@ -0,0 +1,410 @@+{-# 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+ ( aacFrameSize+ , 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+ , AacEncSt(..)+ , numberOfDelayedSamples+ , encodeLinearToAac+ , flushAacEncoder+ ) where++import Conduit+import Control.DeepSeq+import Control.Lens+import Control.Monad.Logger+import Control.Monad.Trans.RWS.Strict+import Control.Monad.Trans.Resource+import Data.Kind+import Data.Maybe+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) = MkAacFrame{aacFrameMediaData+ :: !(V.Vector Word8),+ aacFrameSize :: !Word64}+ 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 MkAacFrame {aacFrameSize} =+ from nominalDiffTime # (MkTicks aacFrameSize :: Ticks r Word64)+ getDurationTicks MkAacFrame {aacFrameSize} =+ convertTicks (MkTicks aacFrameSize :: Ticks r Word64)++instance NFData (Audio r c (Aac aot))++instance (KnownRate r, KnownChannelLayout c, Show (AacAotProxy aot)) =>+ Show (Audio r c (Aac aot)) where+ showsPrec d MkAacFrame {aacFrameMediaData, aacFrameSize} =+ showParen+ (d > 10)+ (showString "aac frame: " .+ showsPrec 11 (MkShowMedia :: MediaDescription (Audio r c (Aac aot))) .+ showString ", samples: " .+ shows aacFrameSize .+ 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 RWS monad of the aac encoder.+type AacEncT rate channels aot m a = RWST Encoder () (AacEncSt rate channels aot) m a++-- | The internal state for 'encodeLinearToAac'+newtype AacEncSt (rate :: Rate) channels (aot :: AacAot) = MkAacEncSt+ { _numberOfDelayedSamples :: Word64+ -- ^ The number of input samples currently buffered in the delay lines of the encoder.+ } deriving (Show)++-- | An internal 'Iso' for the 'AacEncSt'+numberOfDelayedSamples :: Iso' (AacEncSt (rate :: Rate) channels (aot :: AacAot)) Word64+numberOfDelayedSamples = iso _numberOfDelayedSamples MkAacEncSt++-- | 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+ )+ => Audio rate channels (Raw S16)+ -> AacEncT rate channels aot m [Audio rate channels (Aac aot)]+encodeLinearToAac !aIn =+ let dIn = V.unsafeCast (aIn ^. mediaBuffer . mediaBufferVector)+ in encodeSampleVector dIn []++-- | Convert linear audio to AAC encoded audio with the given encoder settings,+-- from a raw sample vector.+-- If the sample vector contains more that the encoder can swallow at a time,+-- the encoder is repeatedly called until all input is consumed.+encodeSampleVector+ :: 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+ -> [Audio rate channels (Aac aot)]+ -> AacEncT rate channels aot m [Audio rate channels (Aac aot)]+encodeSampleVector 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 fr -> returnAccOrEncodeAgain acc fr++-- | 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 [Audio r channels (Aac aot)]+flushAacEncoder = do+ d <- use numberOfDelayedSamples+ e <- ask+ $logDebug (fromString (printf "flushing %d samples" d))+ eres <- liftIO $ flush e+ case eres of+ Left err -> do+ $logError (fromString (printf "flushing failed: %s" (show err)))+ throwM err+ Right fr -> do+ $logInfo (fromString (printf "flushed frame: %s" (show fr)))+ returnAccOrEncodeAgain [] fr++-- | Internal function+returnAccOrEncodeAgain+ :: ( MonadLogger m+ , MonadIO m+ , MonadThrow m+ , KnownChannelLayout channels+ , KnownRate rate+ , Show (AacAotProxy aot)+ , V.Storable (Pcm channels S16)+ )+ => [Audio rate channels (Aac aot)]+ -> EncodeResult+ -> AacEncT rate channels aot m [Audio rate channels (Aac aot)]+returnAccOrEncodeAgain acc MkEncodeResult { encodeResultLeftOverInput+ , encodeResultSamples+ , encodeResultConsumedFrames+ } = do+ numberOfDelayedSamples += encodeResultConsumedFrames+ delayed <- use numberOfDelayedSamples+ let acc' = maybe acc (\es -> MkAacFrame es encoded : acc) encodeResultSamples+ encoded =+ if isJust encodeResultSamples+ then delayed+ else 0+ numberOfDelayedSamples -= encoded+ case encodeResultLeftOverInput of+ Nothing -> return (reverse acc')+ Just rest -> encodeSampleVector rest acc'++makeLenses ''AacEncoderInfo
+ src/Data/MediaBus/FdkAac/EncoderFdkWrapper.c view
@@ -0,0 +1,200 @@++#include <stdio.h>++#include <stdint.h>++#include "fdk-aac/aacenc_lib.h"++uintptr_t inline_c_Data_MediaBus_FdkAac_EncoderFdkWrapper_0_e88399b3eaf706673efe095f84ff92194fa12ff2(int * aacEncoderCfgErrorP_inline_c_0, unsigned configChannelMode_inline_c_1, unsigned configModules_inline_c_2, unsigned configChannels_inline_c_3, int * aacEncoderCfgErrorP_inline_c_4, unsigned configAot_inline_c_5, int * aacEncoderCfgErrorP_inline_c_6, unsigned configSbrMode_inline_c_7, int * aacEncoderCfgErrorP_inline_c_8, unsigned configSampleRate_inline_c_9, int * aacEncoderCfgErrorP_inline_c_10, int * aacEncoderCfgErrorP_inline_c_11, unsigned configBitRateMode_inline_c_12, int * aacEncoderCfgErrorP_inline_c_13, unsigned configBitRate_inline_c_14, int * aacEncoderCfgErrorP_inline_c_15, unsigned configBandwidth_inline_c_16, unsigned configBandwidth_inline_c_17, int * aacEncoderCfgErrorP_inline_c_18, int * aacEncoderCfgErrorP_inline_c_19, unsigned configSignallingMode_inline_c_20, int * aacEncoderCfgErrorP_inline_c_21, unsigned configAfterburner_inline_c_22, int * aacEncoderCfgErrorP_inline_c_23, int * aacEncoderCfgErrorP_inline_c_24, int * aacEncoderCfgErrorP_inline_c_25, unsigned confBufMaxLen_inline_c_26, unsigned char * confBufP_inline_c_27, unsigned * confBufSizeP_inline_c_28, unsigned * encDelayP_inline_c_29, unsigned * frameSizeP_inline_c_30, int * errorCodeP_inline_c_31) {++ AACENC_ERROR e = AACENC_OK;+ HANDLE_AACENCODER phAacEncoder;+ AACENC_InfoStruct pInfo;+ CHANNEL_MODE channelMode;+ *(aacEncoderCfgErrorP_inline_c_0) = 13;++ switch (configChannelMode_inline_c_1) {+ case 1: channelMode = MODE_1; break;+ case 2: channelMode = MODE_2; break;+ case 3: channelMode = MODE_1_2; break;+ case 4: channelMode = MODE_1_2_1; break;+ case 5: channelMode = MODE_1_2_2; break;+ case 6: channelMode = MODE_1_2_2_1; break;+ default: channelMode = 0; break;+ }++ e = aacEncOpen(&phAacEncoder, configModules_inline_c_2, configChannels_inline_c_3);+ if (e != AACENC_OK) {+ *(aacEncoderCfgErrorP_inline_c_4) = 0;+ goto e0;+ }+ e = aacEncoder_SetParam(phAacEncoder, AACENC_AOT, (const UINT) configAot_inline_c_5);+ if (e != AACENC_OK) {+ *(aacEncoderCfgErrorP_inline_c_6) = 1;+ goto e1;+ }+ e = aacEncoder_SetParam(phAacEncoder, AACENC_SBR_MODE, (const UINT) configSbrMode_inline_c_7);+ if (e != AACENC_OK) {+ *(aacEncoderCfgErrorP_inline_c_8) = 2;+ goto e1;+ }+ e = aacEncoder_SetParam(phAacEncoder, AACENC_SAMPLERATE, (const UINT) configSampleRate_inline_c_9);+ if (e != AACENC_OK) {+ *(aacEncoderCfgErrorP_inline_c_10) = 3;+ goto e1;+ }+ e = aacEncoder_SetParam(phAacEncoder, AACENC_CHANNELMODE, channelMode);+ if (e != AACENC_OK) {+ *(aacEncoderCfgErrorP_inline_c_11) = 4;+ goto e1;+ }+ e = aacEncoder_SetParam(phAacEncoder, AACENC_BITRATEMODE, (const UINT) configBitRateMode_inline_c_12);+ if (e != AACENC_OK) {+ *(aacEncoderCfgErrorP_inline_c_13) = 5;+ goto e1;+ }+ e = aacEncoder_SetParam(phAacEncoder, AACENC_BITRATE, (const UINT) configBitRate_inline_c_14);+ if (e != AACENC_OK) {+ *(aacEncoderCfgErrorP_inline_c_15) = 6;+ goto e1;+ }+ if ((const UINT) configBandwidth_inline_c_16 != 0) {+ e = aacEncoder_SetParam(phAacEncoder, AACENC_BANDWIDTH, (const UINT) configBandwidth_inline_c_17);+ if (e != AACENC_OK) {+ *(aacEncoderCfgErrorP_inline_c_18) = 7;+ goto e1;+ }+ }+ e = aacEncoder_SetParam(phAacEncoder, AACENC_TRANSMUX, TT_MP4_RAW); // TODO extract from TRANSPORT_TYPE in FDK_audio.h+ if (e != AACENC_OK) {+ *(aacEncoderCfgErrorP_inline_c_19) = 8;+ goto e1;+ }+ e = aacEncoder_SetParam(phAacEncoder, AACENC_SIGNALING_MODE, (const UINT) configSignallingMode_inline_c_20);+ if (e != AACENC_OK) {+ *(aacEncoderCfgErrorP_inline_c_21) = 9;+ goto e1;+ }+ e = aacEncoder_SetParam(phAacEncoder, AACENC_AFTERBURNER, (const UINT) configAfterburner_inline_c_22);+ if (e != AACENC_OK) {+ *(aacEncoderCfgErrorP_inline_c_23) = 10;+ goto e1;+ }+ e = aacEncEncode(phAacEncoder, NULL, NULL, NULL, NULL);+ if (e != AACENC_OK) {+ *(aacEncoderCfgErrorP_inline_c_24) = 11;+ goto e1;+ }+ e = aacEncInfo(phAacEncoder, &pInfo);+ if (e != AACENC_OK) {+ *(aacEncoderCfgErrorP_inline_c_25) = 12;+ goto e1;+ }++ for (unsigned int i = 0; i < pInfo.confSize && i < confBufMaxLen_inline_c_26; ++i) {+ *(confBufP_inline_c_27 + i) = pInfo.confBuf[i];+ }+ *(confBufSizeP_inline_c_28) = pInfo.confSize;+ *(encDelayP_inline_c_29) = pInfo.encoderDelay;+ *(frameSizeP_inline_c_30) = pInfo.frameLength;+ return ((uintptr_t) phAacEncoder);++ e1:+ if (AACENC_OK != aacEncClose(&phAacEncoder)) {+ printf("Failed to free the AAC Encoder.\n");+ }++ e0:+ *(errorCodeP_inline_c_31) = e;+ return (uintptr_t)NULL;+ +}+++int inline_c_Data_MediaBus_FdkAac_EncoderFdkWrapper_1_922d89f2c2b9a2bb7dbc4bbc946440f71a320f2a(uintptr_t encoderHandle_inline_c_0, long vec_inline_c_1, short * vec_inline_c_2, long vec_inline_c_3, long unsafeOutBuffer_inline_c_4, unsigned char * unsafeOutBuffer_inline_c_5, int * numOutBytesP_inline_c_6, int * numInSamplesP_inline_c_7, int * numAncBytesP_inline_c_8) {++ AACENC_ERROR e;+ HANDLE_AACENCODER phAacEncoder = (HANDLE_AACENCODER) encoderHandle_inline_c_0;++ + AACENC_BufDesc inBuffDesc;+ INT inBuffIds[1] = {IN_AUDIO_DATA};+ INT inBuffSizes[1] = {vec_inline_c_1 * 2};+ INT inBuffElSizes[1] = {2};+ void* inBuffBuffers[1] = {vec_inline_c_2};+ inBuffDesc.numBufs = 1;+ inBuffDesc.bufs = inBuffBuffers;+ inBuffDesc.bufferIdentifiers = inBuffIds;+ inBuffDesc.bufSizes = inBuffSizes;+ inBuffDesc.bufElSizes = inBuffElSizes;+ AACENC_InArgs inArgs =+ { .numInSamples = vec_inline_c_3+ , .numAncBytes = 0 };++ + AACENC_BufDesc outBuffDesc;+ INT outBuffIds[1] = {OUT_BITSTREAM_DATA};+ INT outBuffSizes[1] = {unsafeOutBuffer_inline_c_4};+ INT outBuffElSizes[1] = {1};+ void* outBuffBuffers[1] = {unsafeOutBuffer_inline_c_5};+ outBuffDesc.numBufs = 1;+ outBuffDesc.bufs = outBuffBuffers;+ outBuffDesc.bufferIdentifiers = outBuffIds;+ outBuffDesc.bufSizes = outBuffSizes;+ outBuffDesc.bufElSizes = outBuffElSizes;+ AACENC_OutArgs outArgs;++ e = aacEncEncode (phAacEncoder, &inBuffDesc, &outBuffDesc,+ &inArgs, &outArgs);+ *(numOutBytesP_inline_c_6) = outArgs.numOutBytes;+ *(numInSamplesP_inline_c_7) = outArgs.numInSamples;+ *(numAncBytesP_inline_c_8) = outArgs.numAncBytes;++ return e;+ +}+++int inline_c_Data_MediaBus_FdkAac_EncoderFdkWrapper_2_666791a4914d28907cccec9952c8fb7e36c63ac9(uintptr_t encoderHandle_inline_c_0, long unsafeOutBuffer_inline_c_1, unsigned char * unsafeOutBuffer_inline_c_2, int * numOutBytesP_inline_c_3, int * numInSamplesP_inline_c_4, int * numAncBytesP_inline_c_5) {++ AACENC_ERROR e;+ HANDLE_AACENCODER phAacEncoder = (HANDLE_AACENCODER) encoderHandle_inline_c_0;++ + AACENC_BufDesc inBuffDesc;+ inBuffDesc.numBufs = 0;++ AACENC_InArgs inArgs =+ { .numInSamples = -1+ , .numAncBytes = 0 };++ AACENC_BufDesc outBuffDesc;+ INT outBuffIds[1] = {OUT_BITSTREAM_DATA};+ INT outBuffSizes[1] = {unsafeOutBuffer_inline_c_1};+ INT outBuffElSizes[1] = {1};+ void* outBuffBuffers[1] = {unsafeOutBuffer_inline_c_2};+ outBuffDesc.numBufs = 1;+ outBuffDesc.bufs = outBuffBuffers;+ outBuffDesc.bufferIdentifiers = outBuffIds;+ outBuffDesc.bufSizes = outBuffSizes;+ outBuffDesc.bufElSizes = outBuffElSizes;+ AACENC_OutArgs outArgs;++ e = aacEncEncode (phAacEncoder, &inBuffDesc, &outBuffDesc,+ &inArgs, &outArgs);+ *(numOutBytesP_inline_c_3) = outArgs.numOutBytes;+ *(numInSamplesP_inline_c_4) = outArgs.numInSamples;+ *(numAncBytesP_inline_c_5) = outArgs.numAncBytes;++ return e;+ +}+++int inline_c_Data_MediaBus_FdkAac_EncoderFdkWrapper_3_8fd9c8ff1f367d8600d391d8958211c9959d567d(uintptr_t encoderHandle_inline_c_0) {++ HANDLE_AACENCODER phAacEncoder = (HANDLE_AACENCODER) encoderHandle_inline_c_0;+ return aacEncClose(&phAacEncoder);+ +}+
+ src/Data/MediaBus/FdkAac/EncoderFdkWrapper.hs view
@@ -0,0 +1,511 @@+-- | An internal module that tightly wraps around the Frauenhofer Development+-- Toolkit for AAC audio.+module Data.MediaBus.FdkAac.EncoderFdkWrapper+ ( AacEncErrorCode(..)+ , toAacEncErrorCode+ , simpleConfig+ , Config(..)+ , CreateFailure(..)+ , CreateFailedAt(..)+ , Encoder(..)+ , create+ , EncodeResult(..)+ , EncodeFailure(..)+ , encode+ , flush+ , destroy+ ) where++import Control.Exception+import Data.Coerce+import Data.Int+import Data.Monoid+import qualified Data.Vector.Storable as V+import qualified Data.Vector.Storable.Mutable as VM+import Data.Word+import Foreign.C.Types+import Foreign.ForeignPtr (mallocForeignPtrBytes, withForeignPtr)+import qualified Language.C.Inline as C+import Text.Printf++C.context (C.baseCtx <> C.vecCtx)++C.include "<stdio.h>"++C.include "<stdint.h>"++C.include "fdk-aac/aacenc_lib.h"++-- | Call into the native code to initialize an encoder context, if everything+-- works out great, an 'Encoder' is returned. Use 'destroy' to release the+-- resources associated with an 'Encoder'.+create :: Config -> IO (Either CreateFailure Encoder)+create config@(MkConfig { configModules+ , configChannels+ , configAot+ , configSampleRate+ , configBitRate+ , configBitRateMode+ , configBandwidth+ , configSbrMode+ , configSignallingMode+ , configChannelMode+ , configAfterburner+ }) = do+ let confBufMaxLen = 255 :: CUInt+ confBufC <- mallocForeignPtrBytes (fromIntegral confBufMaxLen)+ ((encDelayC, confBufSizeC, frameSizeC, aacEncoderCfgErrorC, errorCodeC), hPtr) <-+ withForeignPtr confBufC $ \confBufP ->+ C.withPtrs $ \(encDelayP, confBufSizeP, frameSizeP, aacEncoderCfgErrorP, errorCodeP) ->+ [C.block| uintptr_t {+ AACENC_ERROR e = AACENC_OK;+ HANDLE_AACENCODER phAacEncoder;+ AACENC_InfoStruct pInfo;+ CHANNEL_MODE channelMode;+ *($(int* aacEncoderCfgErrorP)) = 13;++ switch ($(unsigned int configChannelMode)) {+ case 1: channelMode = MODE_1; break;+ case 2: channelMode = MODE_2; break;+ case 3: channelMode = MODE_1_2; break;+ case 4: channelMode = MODE_1_2_1; break;+ case 5: channelMode = MODE_1_2_2; break;+ case 6: channelMode = MODE_1_2_2_1; break;+ default: channelMode = 0; break;+ }++ e = aacEncOpen(&phAacEncoder, $(unsigned int configModules), $(unsigned int configChannels));+ if (e != AACENC_OK) {+ *($(int* aacEncoderCfgErrorP)) = 0;+ goto e0;+ }+ e = aacEncoder_SetParam(phAacEncoder, AACENC_AOT, (const UINT) $(unsigned int configAot));+ if (e != AACENC_OK) {+ *($(int* aacEncoderCfgErrorP)) = 1;+ goto e1;+ }+ e = aacEncoder_SetParam(phAacEncoder, AACENC_SBR_MODE, (const UINT) $(unsigned int configSbrMode));+ if (e != AACENC_OK) {+ *($(int* aacEncoderCfgErrorP)) = 2;+ goto e1;+ }+ e = aacEncoder_SetParam(phAacEncoder, AACENC_SAMPLERATE, (const UINT) $(unsigned int configSampleRate));+ if (e != AACENC_OK) {+ *($(int* aacEncoderCfgErrorP)) = 3;+ goto e1;+ }+ e = aacEncoder_SetParam(phAacEncoder, AACENC_CHANNELMODE, channelMode);+ if (e != AACENC_OK) {+ *($(int* aacEncoderCfgErrorP)) = 4;+ goto e1;+ }+ e = aacEncoder_SetParam(phAacEncoder, AACENC_BITRATEMODE, (const UINT) $(unsigned int configBitRateMode));+ if (e != AACENC_OK) {+ *($(int* aacEncoderCfgErrorP)) = 5;+ goto e1;+ }+ e = aacEncoder_SetParam(phAacEncoder, AACENC_BITRATE, (const UINT) $(unsigned int configBitRate));+ if (e != AACENC_OK) {+ *($(int* aacEncoderCfgErrorP)) = 6;+ goto e1;+ }+ if ((const UINT) $(unsigned int configBandwidth) != 0) {+ e = aacEncoder_SetParam(phAacEncoder, AACENC_BANDWIDTH, (const UINT) $(unsigned int configBandwidth));+ if (e != AACENC_OK) {+ *($(int* aacEncoderCfgErrorP)) = 7;+ goto e1;+ }+ }+ e = aacEncoder_SetParam(phAacEncoder, AACENC_TRANSMUX, TT_MP4_RAW); // TODO extract from TRANSPORT_TYPE in FDK_audio.h+ if (e != AACENC_OK) {+ *($(int* aacEncoderCfgErrorP)) = 8;+ goto e1;+ }+ e = aacEncoder_SetParam(phAacEncoder, AACENC_SIGNALING_MODE, (const UINT) $(unsigned int configSignallingMode));+ if (e != AACENC_OK) {+ *($(int* aacEncoderCfgErrorP)) = 9;+ goto e1;+ }+ e = aacEncoder_SetParam(phAacEncoder, AACENC_AFTERBURNER, (const UINT) $(unsigned int configAfterburner));+ if (e != AACENC_OK) {+ *($(int* aacEncoderCfgErrorP)) = 10;+ goto e1;+ }+ e = aacEncEncode(phAacEncoder, NULL, NULL, NULL, NULL);+ if (e != AACENC_OK) {+ *($(int* aacEncoderCfgErrorP)) = 11;+ goto e1;+ }+ e = aacEncInfo(phAacEncoder, &pInfo);+ if (e != AACENC_OK) {+ *($(int* aacEncoderCfgErrorP)) = 12;+ goto e1;+ }++ for (unsigned int i = 0; i < pInfo.confSize && i < $(unsigned int confBufMaxLen); ++i) {+ *($(unsigned char* confBufP) + i) = pInfo.confBuf[i];+ }+ *($(unsigned int* confBufSizeP)) = pInfo.confSize;+ *($(unsigned int* encDelayP)) = pInfo.encoderDelay;+ *($(unsigned int* frameSizeP)) = pInfo.frameLength;+ return ((uintptr_t) phAacEncoder);++ e1:+ if (AACENC_OK != aacEncClose(&phAacEncoder)) {+ printf("Failed to free the AAC Encoder.\n");+ }++ e0:+ *($(int* errorCodeP)) = e;+ return (uintptr_t)NULL;+ } |]+ if hPtr == 0+ then return+ (Left+ MkCreateFailure+ { createFailureErrorCode = toAacEncErrorCode errorCodeC+ , createFailureAt = toEnum (fromIntegral aacEncoderCfgErrorC)+ , createFailureInputConfig = config+ })+ else do+ let ascVM = VM.unsafeFromForeignPtr0 confBufC (fromIntegral confBufSizeC)+ asc <- V.freeze ascVM+ let outSize = fromIntegral (768 * configChannels)+ outV <- VM.new outSize+ return $+ Right $+ MkEncoder+ { encoderHandle = hPtr+ , channelCount = configChannels+ , encoderDelay = fromIntegral encDelayC+ , frameSize = fromIntegral frameSizeC+ , unsafeOutBuffer = outV+ , audioSpecificConfig = coerce asc+ }++-- | A subset of the possible encoder configuration parameters+data Config = MkConfig+ { configModules :: !CUInt+ , configChannels :: !CUInt+ , configAot :: !CUInt+ , configSampleRate :: !CUInt+ , configBitRate :: !CUInt+ , configBitRateMode :: !CUInt+ , configBandwidth :: !CUInt -- ^ The audio frequency bandwidth to be considered+ -- when compressing audio, if @0@ the setting is+ -- not applied.+ , configSbrMode :: !CUInt+ , configSignallingMode :: !CUInt+ , configChannelMode :: !CUInt+ , configAfterburner :: !CUInt+ } deriving (Eq, Show)++-- | Generate a 'Config' from three simple parameters+simpleConfig :: Word8 -> Int -> Word32 -> Config+simpleConfig !aot !channels !sampleRate =+ let !configModules = 0x17+ !configChannels = fromIntegral channels+ !highEfficiency = configAot == 5+ !configAot = fromIntegral aot+ !configSampleRate = fromIntegral sampleRate+ !configBitRate =+ configChannelMode *+ round+ (fromIntegral configSampleRate *+ ((if highEfficiency+ then 0.625+ else 1.5) :: Double --+ ))+ !configBitRateMode = 0+ !configBandwidth = 0+ !configSbrMode = fromIntegral (fromEnum highEfficiency)+ !configSignallingMode =+ if highEfficiency+ then 2+ else 0+ !configChannelMode = fromIntegral channels+ !configAfterburner = 1+ in MkConfig+ { configModules+ , configChannels+ , configAot+ , configSampleRate+ , configBitRate+ , configBitRateMode+ , configBandwidth+ , configSbrMode+ , configSignallingMode+ , configChannelMode+ , configAfterburner+ }++-- | Description of the context in which the 'create' function failed.+data CreateFailure = MkCreateFailure+ { createFailureErrorCode :: AacEncErrorCode+ , createFailureAt :: CreateFailedAt+ , createFailureInputConfig :: Config+ } deriving (Show, Eq)++instance Exception CreateFailure++-- | This sum type narrows down the specific step that failed,+-- in the @inline-c@ wrapper code in the 'create' function.+data CreateFailedAt+ = AacEncOpen+ | AacEncSetAot+ | AacEncSetSbrMode+ | AacEncSetSampleRate+ | AacEncSetChannelMode+ | AacEncSetBitRateMode+ | AacEncSetBitRate+ | AacEncSetBandwidth+ | AacEncSetTransMux+ | AacEncSetSignalingMode+ | AacEncSetAfterburner+ | AacEncApplyConfig+ | AacEncReadInfo+ | AacEncUnknownError+ deriving (Eq, Show, Enum)++-- | Handle for a specific encoder, can be created with 'aacEncoderNew'.+data Encoder = MkEncoder+ { encoderHandle :: !CUIntPtr+ , unsafeOutBuffer :: !(VM.IOVector CUChar)+ , channelCount :: !CUInt+ , encoderDelay :: !Word32+ , frameSize :: !Word32+ , audioSpecificConfig :: !(V.Vector Word8)+ }++-- | This instance only shows the 'encoderHandle' as hex string.+instance Show Encoder where+ showsPrec d MkEncoder {encoderHandle} =+ showParen (d > 10) $+ showString (printf "fdk-aac-enc: %016X" (toInteger encoderHandle))++-- | Encode Samples.+encode :: Encoder -> V.Vector Word16 -> IO (Either EncodeFailure EncodeResult)+encode enc@MkEncoder {encoderHandle, unsafeOutBuffer} !vecW16 = do+ let vec = coerce vecW16+ toEncodeResult vec enc =<<+ (C.withPtrs $ \(numOutBytesP, numInSamplesP, numAncBytesP) ->+ [C.block| int {+ AACENC_ERROR e;+ HANDLE_AACENCODER phAacEncoder = (HANDLE_AACENCODER) $(uintptr_t encoderHandle);++ /* Input buffer */+ AACENC_BufDesc inBuffDesc;+ INT inBuffIds[1] = {IN_AUDIO_DATA};+ INT inBuffSizes[1] = {$vec-len:vec * 2};+ INT inBuffElSizes[1] = {2};+ void* inBuffBuffers[1] = {$vec-ptr:(short *vec)};+ inBuffDesc.numBufs = 1;+ inBuffDesc.bufs = inBuffBuffers;+ inBuffDesc.bufferIdentifiers = inBuffIds;+ inBuffDesc.bufSizes = inBuffSizes;+ inBuffDesc.bufElSizes = inBuffElSizes;+ AACENC_InArgs inArgs =+ { .numInSamples = $vec-len:vec+ , .numAncBytes = 0 };++ /* Ouput buffer */+ AACENC_BufDesc outBuffDesc;+ INT outBuffIds[1] = {OUT_BITSTREAM_DATA};+ INT outBuffSizes[1] = {$vec-len:unsafeOutBuffer};+ INT outBuffElSizes[1] = {1};+ void* outBuffBuffers[1] = {$vec-ptr:(unsigned char *unsafeOutBuffer)};+ outBuffDesc.numBufs = 1;+ outBuffDesc.bufs = outBuffBuffers;+ outBuffDesc.bufferIdentifiers = outBuffIds;+ outBuffDesc.bufSizes = outBuffSizes;+ outBuffDesc.bufElSizes = outBuffElSizes;+ AACENC_OutArgs outArgs;++ e = aacEncEncode (phAacEncoder, &inBuffDesc, &outBuffDesc,+ &inArgs, &outArgs);+ *($(int* numOutBytesP)) = outArgs.numOutBytes;+ *($(int* numInSamplesP)) = outArgs.numInSamples;+ *($(int* numAncBytesP)) = outArgs.numAncBytes;++ return e;+ }|])++-- | Encode the contents of the delay lines of the encoder.+flush :: Encoder -> IO (Either EncodeFailure EncodeResult)+flush enc@(MkEncoder {encoderHandle, unsafeOutBuffer}) = do+ toEncodeResult mempty enc =<<+ (C.withPtrs $ \(numOutBytesP, numInSamplesP, numAncBytesP) ->+ [C.block| int {+ AACENC_ERROR e;+ HANDLE_AACENCODER phAacEncoder = (HANDLE_AACENCODER) $(uintptr_t encoderHandle);++ /* Input buffer */+ AACENC_BufDesc inBuffDesc;+ inBuffDesc.numBufs = 0;++ AACENC_InArgs inArgs =+ { .numInSamples = -1+ , .numAncBytes = 0 };++ AACENC_BufDesc outBuffDesc;+ INT outBuffIds[1] = {OUT_BITSTREAM_DATA};+ INT outBuffSizes[1] = {$vec-len:unsafeOutBuffer};+ INT outBuffElSizes[1] = {1};+ void* outBuffBuffers[1] = {$vec-ptr:(unsigned char *unsafeOutBuffer)};+ outBuffDesc.numBufs = 1;+ outBuffDesc.bufs = outBuffBuffers;+ outBuffDesc.bufferIdentifiers = outBuffIds;+ outBuffDesc.bufSizes = outBuffSizes;+ outBuffDesc.bufElSizes = outBuffElSizes;+ AACENC_OutArgs outArgs;++ e = aacEncEncode (phAacEncoder, &inBuffDesc, &outBuffDesc,+ &inArgs, &outArgs);+ *($(int* numOutBytesP)) = outArgs.numOutBytes;+ *($(int* numInSamplesP)) = outArgs.numInSamples;+ *($(int* numAncBytesP)) = outArgs.numAncBytes;++ return e;+ } |])++-- | Internal function+toEncodeResult+ :: V.Vector C.CShort+ -> Encoder+ -> ((C.CInt, C.CInt, C.CInt), CInt)+ -> IO (Either EncodeFailure EncodeResult)+toEncodeResult vec MkEncoder {unsafeOutBuffer, channelCount} ((numOutBytes, numInSamples, numAncBytes), retCode) =+ let retCode' = toAacEncErrorCode retCode+ in+ if retCode' /= AacEncOk && retCode' /= AacEncEncodeEof + then return $+ Left+ MkEncodeFailure+ { encodeFailureCode = toAacEncErrorCode retCode+ , encodeFailureNumOutBytes = fromIntegral numOutBytes+ , encodeFailureNumInSamples = fromIntegral numInSamples+ , encodeFailureNumAncBytes = fromIntegral numAncBytes+ }+ else do+ let !numInSamplesI = fromIntegral numInSamples+ !consumedFrames =+ fromIntegral numInSamplesI `div` fromIntegral channelCount+ !leftOverInput =+ if numInSamplesI >= V.length vec+ then Nothing+ else let !inSliceLen = V.length vec - numInSamplesI+ !inSlice = V.slice numInSamplesI inSliceLen vec+ in Just inSlice+ !numOutBytesI = fromIntegral numOutBytes+ !encodedOutput <-+ if numOutBytesI == 0+ then return Nothing+ else do+ !outTooLarge <- V.freeze unsafeOutBuffer+ return $ Just $ V.force $ V.slice 0 numOutBytesI outTooLarge+ return $+ Right+ MkEncodeResult+ { encodeResultConsumedFrames = consumedFrames+ , encodeResultLeftOverInput = coerce leftOverInput+ , encodeResultSamples = coerce encodedOutput+ }++-- | Result of 'encode'+data EncodeResult = MkEncodeResult+ { encodeResultConsumedFrames :: !Word64 -- ^ Number of samples from the input+ -- that were processed by the encoder+ , encodeResultLeftOverInput :: !(Maybe (V.Vector Word16)) -- ^ The unprocessed+ -- rest of the input.+ -- Only if the input+ -- is larger than the+ -- encoders frame+ -- length a left over+ -- is returned.+ , encodeResultSamples :: !(Maybe (V.Vector Word8)) -- ^ Encoded output. If less+ -- than the frame length+ -- samples have been+ -- encoded, then the result+ -- will be 'Nothing'+ -- otherwise it is the+ -- encoded AAC content+ -- representing the encded+ -- samples delayed by+ -- 'frameLength'.+ }++instance Show EncodeResult where+ showsPrec d MkEncodeResult { encodeResultConsumedFrames+ , encodeResultLeftOverInput+ , encodeResultSamples+ } =+ showParen (d > 10) $+ showString "encode result: consumed input samples: " .+ shows encodeResultConsumedFrames .+ showString ", left over input values: " .+ (maybe (showString "n/a") (shows . V.length) encodeResultLeftOverInput) .+ showString ", encoded output bytes: " .+ (maybe (showString "n/a") (shows . V.length) encodeResultSamples)++-- | Information about encoder when an 'encode' failure occured.+data EncodeFailure = MkEncodeFailure+ { encodeFailureCode :: !AacEncErrorCode+ , encodeFailureNumOutBytes :: !Word64+ , encodeFailureNumInSamples :: !Int16+ , encodeFailureNumAncBytes :: !Word64+ } deriving (Eq)++instance Exception EncodeFailure++instance Show EncodeFailure where+ show MkEncodeFailure {..} =+ printf+ "(FDK AAC encode error: %s, numOutBytes: %d, numInSamples: %d, numAncBytes: %d)"+ (show encodeFailureCode)+ encodeFailureNumOutBytes+ encodeFailureNumInSamples+ encodeFailureNumAncBytes++-- | Close an FDK-AAC encoder.+destroy :: Encoder -> IO AacEncErrorCode+destroy MkEncoder {encoderHandle} = do+ toAacEncErrorCode . fromIntegral <$>+ [C.block| int {+ HANDLE_AACENCODER phAacEncoder = (HANDLE_AACENCODER) $(uintptr_t encoderHandle);+ return aacEncClose(&phAacEncoder);+ } |]++-- | Error codes from the C-library, translated to this sum type+data AacEncErrorCode+ = AacEncOk+ | AacEncInvalidHandle+ | AacEncMemoryError+ | AacEncUnsupportedParameter+ | AacEncInvalidConfig+ | AacEncInitError+ | AacEncInitAacError+ | AacEncInitSbrError+ | AacEncInitTpError+ | AacEncInitMetaError+ | AacEncEncoderError+ | AacEncEncodeEof+ | AacEncErrorOther CInt+ deriving (Eq, Show)++-- | Create an 'AacEncErrorCode' from the @int@ returned by the C-code.+toAacEncErrorCode :: CInt -> AacEncErrorCode+toAacEncErrorCode e =+ case e of+ 0x00 -> AacEncOk+ 0x20 -> AacEncInvalidHandle+ 0x21 -> AacEncMemoryError+ 0x22 -> AacEncUnsupportedParameter+ 0x23 -> AacEncInvalidConfig+ 0x40 -> AacEncInitError+ 0x41 -> AacEncInitAacError+ 0x42 -> AacEncInitSbrError+ 0x43 -> AacEncInitTpError+ 0x44 -> AacEncInitMetaError+ 0x60 -> AacEncEncoderError+ 0x80 -> AacEncEncodeEof+ othr -> AacEncErrorOther othr