3

I have a media stream with associated named pipe to it. The program reads data from pipe and processes frames with processFrame.

module Main where
import Codec.FFmpeg
import Codec.FFmpeg.Decode
import Control.Monad.Except (runExceptT)

main :: IO ()
main = do
    initFFmpeg
    -- get IO reader
    eResult <- runExceptT $ frameReader avPixFmtRgb24 (File "pipe")
    case eResult of
        Left err -> do print "couldn't establish frameReader\n"
                       print err
        Right (getMaybeFrame, cleanup) -> do
            -- read and process frames until av_read_frame returns no frames
            loopWhileTrue (getAndProcessFrame getMaybeFrame)
            cleanup
          where
            loopWhileTrue f = do result <- f
                                 case result of
                                    True -> loopWhileTrue f
                                    False -> return ()
      where
        getAndProcessFrame gMF = do
            maybeFrame <- gMF
            case maybeFrame of
                Just frame -> do tc <- timeCondition
                                 if tc
                                 then do processFrame frame
                                 else return ()
                                 return True
                Nothing -> return False
        timeCondition = return True

processFrame :: AVFrame -> IO ()
processFrame frame = do isKeyFrame <- getKeyFrame frame
                        if isKeyFrame
                        then do pts <- getPts frame
                                print pts
                        else return ()

I need to perform processFrame only once in a minute, bypassing all other frames.

This code does not have a proper time condition, only a timeCondition stub. I planned to write a proper timeCondition function which returns True when a certain time has passed since last processFrame, otherwise False. But this looks too imperative and it's not clear how to use Monads/MVars for this. What's the right way of designing such a program in Haskell?

AleXoundOS
  • 341
  • 1
  • 4
  • 13
  • Either main loopWhileTrue a little more complex, keeping track of the last time a frame was processed, or pass an MVar to getAndProcessFrame then to `processFrame` that contains the last time. Check the current time against this last time at each frame and conditionally continue or exit early. – Thomas M. DuBuisson Jun 30 '16 at 23:23
  • 1
    The most obvious code structure improvement would be to make `loopWhileTrue` a top-level binding instead of burying it in a `case` expression inside a `do` block. Also, you can write it `loopWhileTrue f = do { result <- f; when result $ loopWhileTrue f }`, or even `loopWhileTrue f = fix $ \m -> do { result <- f; when result m }`. These will give type signatures `loopWhileTrue :: Monad m => m Bool -> m ()`. – dfeuer Jul 01 '16 at 01:09
  • Is there no way of sleeping the thread for a minute? (in `loopWhileTrue`) – Alec Jul 01 '16 at 01:17
  • 1
    @Alec It looks like OP wants to continue receiving frames while waiting (but discarding them unprocessed). – R B Jul 01 '16 at 01:59
  • 1
    @AleXoundOS Cleaning up your code is an excellent question for Code Review. – R B Jul 01 '16 at 02:14
  • 1
    @RowanBlush agreed! it's not very clear from the narrative whether the code currently works as intended though. or does it? – Mathieu Guindon Jul 01 '16 at 02:21

1 Answers1

3

Grab a timestamp when the processing step completes with getCurrentTime and thread it back through your loop, then timeCondition is just some math on time values.

import Data.Time.Clock

-- | Returns `True` when 60 seconds have passed from the given value.
-- Not respectful of leap seconds.
isAMinuteSince :: UTCTime -> IO Bool
isAMinuteSince timestamp = do
    now <- getCurrentTime
    return $ diffUTCTime now timestamp >= 60
R B
  • 1,109
  • 9
  • 13