module Data.ByteString.Mp4.AudioStreaming
( Segment(..)
, StreamingContext
, AacMp4TrackFragment(..)
, numberOfChannels
, buildAacMp4TrackFragment
, getStreamConfig, getStreamBaseTime, getStreamSequence
, addToBaseTime, getSegmentDuration
, streamInitINTERNAL_TESTING, streamInitUtc, streamNextSample, streamFlush
, module X)
where
import Data.Time.Clock
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
import Data.ByteString.IsoBaseFileFormat.Box
import Data.ByteString.IsoBaseFileFormat.Boxes
import Data.ByteString.IsoBaseFileFormat.Brands.Dash as X
import Data.ByteString.IsoBaseFileFormat.MediaFile as X
import Data.ByteString.IsoBaseFileFormat.ReExports as X
import Data.ByteString.IsoBaseFileFormat.Util.BoxFields as X
import Data.ByteString.IsoBaseFileFormat.Util.Time as X
import Data.ByteString.Mp4.Boxes.AudioSpecificConfig as X
import Data.ByteString.Mp4.Boxes.Mp4AudioSampleEntry as X
import Data.ByteString.Mp4.AacInitSegment as X
data StreamingContext =
StreamingContext { acConfig :: !AacInitSegment
, acSegmentDuration :: !Word64
, acSequence :: !Word32
, acBaseTime :: !Word64
, acSegments :: ![(Word32, BS.ByteString)]
}
data Segment =
Segment
{ segmentSequence :: !Word32
, segmentTime :: !Word64
, segmentData :: !BS.ByteString
}
data AacMp4TrackFragment =
AacMp4TrackFragment { fragmentSampleSequence :: !Word32
, fragmentBaseMediaDecodeTime :: !Word64
, fragmentSamples :: ![(Word32, BS.ByteString)]
}
numberOfChannels :: Num a => StreamingContext -> a
numberOfChannels = channelConfigToNumber . channelConfig . acConfig
addToBaseTime :: StreamingContext -> NominalDiffTime -> StreamingContext
addToBaseTime !sc !dt =
sc { acSequence = newSequence
, acBaseTime = diffTimeToTicks newBaseTime timeScale}
where
!timeScale = getAacMp4StreamConfigTimeScale (acConfig sc)
!newSequence = round (newBaseTime / segmentDuration)
where
!segmentDuration = ticksToDiffTime (acSegmentDuration sc) timeScale
!newBaseTime = dt + baseTime
where
!baseTime = ticksToDiffTime (acBaseTime sc) timeScale
getStreamConfig :: StreamingContext -> AacInitSegment
getStreamConfig StreamingContext{..} = acConfig
getStreamBaseTime :: StreamingContext -> Word64
getStreamBaseTime StreamingContext{..} = acBaseTime
getStreamSequence :: StreamingContext -> Word32
getStreamSequence StreamingContext{..} = acSequence
instance Show Segment where
show (Segment !s !t !d) =
printf "SEGMENT - seq: %14d - time: %14d - size: %14d" s t (BS.length d)
getSegmentDuration :: StreamingContext -> NominalDiffTime
getSegmentDuration !sc = segmentDuration
where
!segmentDuration = ticksToDiffTime (acSegmentDuration sc) timeScale
!timeScale = getAacMp4StreamConfigTimeScale (acConfig sc)
sampleCountDuration :: AacInitSegment -> Word32 -> Word64
sampleCountDuration (AacInitSegment _ _ _ _ _c) r =
fromIntegral r
getAacMp4StreamConfigTimeScale :: AacInitSegment -> TimeScale
getAacMp4StreamConfigTimeScale AacInitSegment{..} =
TimeScale (sampleRateToNumber sampleRate)
streamInitINTERNAL_TESTING
:: String
-> NominalDiffTime
-> Bool
-> SamplingFreqTable
-> ChannelConfigTable
-> IO (BinaryAacInitSegment, StreamingContext)
streamInitINTERNAL_TESTING !trackTitle !segmentDuration !sbr !rate !channels = do
t <- mp4CurrentTime
let !cfg = AacInitSegment t trackTitle sbr rate channels
!dur = diffTimeToTicks segmentDuration timeScale
!timeScale = getAacMp4StreamConfigTimeScale cfg
return ( buildAacInitSegment cfg
, StreamingContext cfg dur 0 0 [])
streamInitUtc
:: String
-> UTCTime
-> UTCTime
-> NominalDiffTime
-> Bool
-> SamplingFreqTable
-> ChannelConfigTable
-> (BinaryAacInitSegment, StreamingContext)
streamInitUtc !trackTitle !availabilityStartTime !refTime !segmentDuration !sbr !rate !channels =
let !cfg = AacInitSegment t0 trackTitle sbr rate channels
!t0 = utcToMp4 availabilityStartTime
!secondsSinceRef = diffUTCTime availabilityStartTime refTime
!dur = diffTimeToTicks segmentDuration timeScale
!timeScale = getAacMp4StreamConfigTimeScale cfg
in ( buildAacInitSegment cfg
, addToBaseTime (StreamingContext cfg dur 0 0 []) secondsSinceRef)
streamNextSample
:: Word32
-> BS.ByteString
-> StreamingContext
-> (Maybe Segment, StreamingContext)
streamNextSample !sampleCount !sample !ctx@StreamingContext{..} =
let !segments = (sampleCount, sample) : acSegments
!currentEndTime = sampleCountDuration acConfig (sum (fst <$> segments)) + acBaseTime
!nextBaseTime = (fromIntegral acSequence + 1) * acSegmentDuration64
!acSegmentDuration64 = fromIntegral acSegmentDuration
in
if currentEndTime >= nextBaseTime then
let !ctx' =
ctx { acSequence = fromIntegral (currentEndTime `div` acSegmentDuration64)
, acBaseTime = currentEndTime
, acSegments = []
}
!tf = buildAacMp4TrackFragment $
AacMp4TrackFragment acSequence acBaseTime (reverse segments)
in (Just (Segment acSequence acBaseTime (BL.toStrict (toLazyByteString tf))), ctx')
else
(Nothing, ctx{ acSegments = segments })
streamFlush
:: StreamingContext
-> (Maybe Segment, StreamingContext)
streamFlush !ctx@StreamingContext{..} =
let currentEndTime =
sampleCountDuration acConfig (sum (fst <$> acSegments)) + acBaseTime
in
if not (null acSegments) then
let !tf = buildAacMp4TrackFragment $
AacMp4TrackFragment acSequence acBaseTime (reverse acSegments)
!ctx' = ctx { acSequence = fromIntegral
(currentEndTime `div` fromIntegral acSegmentDuration)
, acBaseTime = currentEndTime
, acSegments = []}
in (Just (Segment acSequence acBaseTime (BL.toStrict (toLazyByteString tf))), ctx')
else (Nothing, ctx)
buildAacMp4TrackFragment
:: AacMp4TrackFragment -> Builder
buildAacMp4TrackFragment AacMp4TrackFragment{..} =
mediaBuilder dash
( styp
:. movieFragment
( mfhd
:| trackFragment
( tfhd
:. tfdt
:| trun))
:| mdat)
where
!styp = segmentTypeBox (SegmentType "msdh" 0 ["msdh","dash"])
!mfhd = movieFragmentHeader (MovieFragmentHeader (Scalar fragmentSampleSequence))
!tfhd = trackFragmentHeader def
!tfdt = trackFragBaseMediaDecodeTime (TSv1 fragmentBaseMediaDecodeTime)
!trun = trackRunIso5 mdatOffset fragmentSamples
where
!mdatOffset = movieFragmentStaticSize
+ movieFragmentHeaderStaticSize
+ trackFragmentStaticSize
+ trackFragmentHeaderStaticSize
+ trackFragBaseMediaDecodeTimeStaticSize64
!mdat = mediaData (MediaData (BS.concat (snd <$> fragmentSamples)))