packages feed

ffmpeg-light-0.14.0: src/Codec/FFmpeg/Common.hsc

{-# LANGUAGE FlexibleContexts         #-}
{-# LANGUAGE ForeignFunctionInterface #-}
module Codec.FFmpeg.Common where
import           Codec.FFmpeg.Enums
import           Codec.FFmpeg.Types
import           Control.Exception
import           Control.Monad             (when)
import           Control.Monad.Error.Class
import           Control.Monad.IO.Class
import           Control.Monad.Trans.Maybe
import           Foreign.C.String
import           Foreign.C.Types
import           Foreign.Marshal.Alloc     (allocaBytes, free)
import           Foreign.Marshal.Array     (advancePtr, mallocArray)
import           Foreign.Ptr
import           Foreign.Storable

foreign import ccall "avcodec_open2"
  open_codec :: AVCodecContext -> AVCodec -> Ptr AVDictionary -> IO CInt

foreign import ccall "av_frame_alloc"
  av_frame_alloc :: IO AVFrame

foreign import ccall "av_frame_get_buffer"
  av_frame_get_buffer :: AVFrame -> CInt -> IO CInt

foreign import ccall "av_frame_free"
  av_frame_free :: Ptr AVFrame -> IO ()

foreign import ccall "avcodec_close"
  codec_close :: AVCodecContext -> IO CInt

foreign import ccall "av_init_packet"
  init_packet :: AVPacket -> IO ()

foreign import ccall "av_packet_alloc"
  av_packet_alloc :: IO AVPacket

foreign import ccall "av_free_packet"
  free_packet :: AVPacket -> IO ()

foreign import ccall "av_malloc"
  av_malloc :: CSize -> IO (Ptr ())

foreign import ccall "av_free"
  av_free :: Ptr () -> IO ()

foreign import ccall "sws_getCachedContext"
  sws_getCachedContext :: SwsContext
                       -> CInt -> CInt -> AVPixelFormat
                       -> CInt -> CInt -> AVPixelFormat
                       -> SwsAlgorithm -> Ptr () -> Ptr () -> Ptr CDouble
                       -> IO SwsContext

foreign import ccall "sws_scale"
  sws_scale :: SwsContext
            -> Ptr (Ptr CUChar) -> Ptr CInt -> CInt -> CInt
            -> Ptr (Ptr CUChar) -> Ptr CInt -> IO CInt

foreign import ccall "av_get_channel_layout_nb_channels"
  av_get_channel_layout_nb_channels :: CULong -> IO CInt

foreign import ccall "swr_alloc"
  swr_alloc :: IO SwrContext

foreign import ccall "swr_init"
  swr_init :: SwrContext -> IO CInt

foreign import ccall "av_opt_set_int"
  av_opt_set_int :: Ptr () -> CString -> CLong -> CInt -> IO CInt

foreign import ccall "av_opt_get_int"
  av_opt_get_int :: Ptr () -> CString -> CInt -> Ptr CULong -> IO CInt

foreign import ccall "av_opt_set_sample_fmt"
  av_opt_set_sample_fmt :: Ptr () -> CString -> AVSampleFormat -> CInt -> IO CInt

foreign import ccall "av_opt_get_sample_fmt"
  av_opt_get_sample_fmt :: Ptr () -> CString -> CInt -> Ptr AVSampleFormat -> IO CInt

foreign import ccall "avcodec_send_frame"
  avcodec_send_frame :: AVCodecContext -> AVFrame -> IO CInt

foreign import ccall "avcodec_send_packet"
  avcodec_send_packet :: AVCodecContext -> AVPacket -> IO CInt

foreign import ccall "avcodec_receive_frame"
  avcodec_receive_frame :: AVCodecContext -> AVFrame -> IO CInt

foreign import ccall "avcodec_receive_packet"
  avcodec_receive_packet :: AVCodecContext -> AVPacket -> IO CInt

foreign import ccall "av_get_channel_name"
  av_get_channel_name :: CULong -> IO CString

foreign import ccall "av_get_channel_description"
  av_get_channel_description :: CULong -> IO CString

-- Return size of buffer for image.
foreign import ccall "av_image_get_buffer_size"
  av_image_get_buffer_size
    -- Pixel format.
    :: AVPixelFormat
    -- Width.
    -> CInt
    -- Height.
    -> CInt
    -- Line size alignment.
    -> CInt
    -- Size of buffer.
    -> IO CInt

-- Copy image to buffer.
foreign import ccall "av_image_copy_to_buffer"
  av_image_copy_to_buffer
    -- Destination buffer.
    :: Ptr CUChar
    -- Destination buffer size.
    -> CInt
    -- Source image data.
    -> Ptr (Ptr CUChar)
    -- Source image line size.
    -> Ptr CInt
    -- Source image pixel format.
    -> AVPixelFormat
    -- Source image width.
    -> CInt
    -- Source image height.
    -> CInt
    -- Source image line size alignment.
    -> CInt
    -- Number of bytes written to destination.
    -> IO CInt


-- * Utility functions

-- | Catch an IOException from an IO action and re-throw it in a
-- wrapping monad transformer.
wrapIOError :: (MonadIO m, MonadError String m) => IO a -> m a
wrapIOError io = liftIO (catchError (fmap Right io) (return . Left . show))
                 >>= either throwError return

newtype FFmpegException = FFmpegException String deriving Show

instance Exception FFmpegException

runWithError :: String -> IO CInt -> IO CInt
runWithError msg toRun = do
  r <- toRun
  when (r < 0) $ do
    let len = 100 -- I have no idea how long this string should be so this is a guess
    errCStr <- mallocArray len
    av_strerror r errCStr (fromIntegral len)
    errStr <- peekCString errCStr
    free errCStr
    avError $ msg ++ " : " ++ errStr
  return r

avError :: String -> IO a
avError msg = throwIO $ FFmpegException $ msg

-- * Wrappers that may throw 'IOException's.

-- | Allocate an 'AVFrame' and set its fields to default values.
frame_alloc_check :: IO AVFrame
frame_alloc_check = do r <- av_frame_alloc
                       when (getPtr r == nullPtr)
                            (error "Couldn't allocate frame")
                       return r

-- | Allocate new buffer(s) for audio or video data with the required
-- alignment. Note, for video frames, pixel format, @width@, and
-- @height@ must be set before calling this function. For audio
-- frames, sample @format@, @nb_samples@, and @channel_layout@ must be
-- set.
frame_get_buffer_check :: AVFrame -> CInt -> IO ()
frame_get_buffer_check f x = do r <- av_frame_get_buffer f x
                                when (r /= 0)
                                     (error "Failed to allocate buffers")

-- | Bytes-per-pixel for an 'AVPixelFormat'
avPixelStride :: AVPixelFormat -> Maybe Int
avPixelStride fmt
  | fmt == avPixFmtGray8  = Just 1
  | fmt == avPixFmtRgb24  = Just 3
  | fmt == avPixFmtRgba   = Just 4
  | fmt == avPixFmtRgb8   = Just 1
  | fmt == avPixFmtPal8   = Just 1
  | otherwise = Nothing

-- | Return line size alignment.
lineSizeAlign :: CInt -> CInt
lineSizeAlign lineSize
  -- Alignment for 512 bit register.
  | lineSize `mod` 64 == 0 = 64
  -- Alignment for 256 bit register.
  | lineSize `mod` 32 == 0 = 32
  -- Alignment for 128 bit register.
  | lineSize `mod` 16 == 0 = 16
  -- Alignment for 64 bit register.
  | lineSize `mod` 8  == 0 = 8
  -- Alignment for 32 bit register.
  | lineSize `mod` 4  == 0 = 4
  -- Alignment for 16 bit register.
  | lineSize `mod` 2  == 0 = 2
  -- Alignment for 8 bit register.
  | otherwise = 1

-- | Retun 'AVFrame's line size.
frameLineSize :: AVFrame -> IO (Maybe CInt)
frameLineSize frame = do
  w   <- getWidth frame
  fmt <- getPixelFormat frame
  return $
    (*w) . fromIntegral <$> avPixelStride fmt

-- | Transformer version of 'frameLineSize'.
frameLineSizeT :: AVFrame -> MaybeT IO CInt
frameLineSizeT = MaybeT . frameLineSize

-- Return 'AVFrame's alignment.
frameAlign :: AVFrame -> IO (Maybe CInt)
frameAlign = fmap (fmap lineSizeAlign) . frameLineSize

-- Transformer version of 'frameAlign'.
frameAlignT :: AVFrame -> MaybeT IO CInt
frameAlignT = MaybeT . frameAlign


-- * Wrappers for copying 'AVFrame's image to buffer.

-- | Return size of buffer for 'AVFrame's image.
frameBufferSize :: AVFrame -> IO (Maybe CInt)
frameBufferSize frame =
  runMaybeT $ do
    a <- frameAlignT frame
    MaybeT $ do
      fmt <- getPixelFormat frame
      w   <- getWidth frame
      h   <- getHeight frame
      Just <$> av_image_get_buffer_size fmt w h a

-- | Transformer version of 'frameBufferSize'.
frameBufferSizeT :: AVFrame -> MaybeT IO CInt
frameBufferSizeT = MaybeT . frameBufferSize

-- | Copy 'AVFrame's image to buffer.
-- It is assumed that size of buffer is equal to
--
-- > bufSize <- fromJust <$> frameBufferSize frame.
frameCopyToBuffer :: AVFrame -> Ptr CUChar -> IO (Maybe CInt)
frameCopyToBuffer frame buffer =
  runMaybeT $ do

    a <- frameAlignT frame
    s <- frameBufferSizeT frame

    MaybeT $ do

      let imageData = hasData frame
          lineSize  = hasLineSize frame

      fmt <- getPixelFormat frame
      w   <- getWidth frame
      h   <- getHeight frame

      Just <$>
        av_image_copy_to_buffer
          buffer
          s
          (castPtr imageData)
          lineSize
          fmt
          w
          h
          a

-- | Transformer version of 'frameCopyToBuffer'.
frameCopyToBufferT :: AVFrame -> Ptr CUChar -> MaybeT IO CInt
frameCopyToBufferT frame = MaybeT . frameCopyToBuffer frame

-- * FFmpeg Errors

foreign import ccall "av_strerror"
  av_strerror :: CInt -> Ptr CChar -> CSize -> IO CInt

stringError :: CInt -> IO String
stringError err =
  allocaBytes len $ \block -> do
    let buf = castPtr block
    _ <- av_strerror err buf (fromIntegral len)
    peekCString buf
  where
    len = 1000

-- | Walk a C array placing the values into a Haskell list.
-- Stop incrementing the pointer when the function returns True
walkPtrs :: Storable a
         => Ptr a -- ^ Ptr to the beginning of an array
         -> (Ptr a -> IO Bool) -- ^ Function to specify when we should terminate
         -> IO [a]
walkPtrs ptr isDone = do
  d <- isDone ptr
  if d
    then return []
    else do
      v <- peek ptr
      rest <- walkPtrs (advancePtr ptr 1) isDone
      return $ v : rest

listSupportedSampleFormats :: AVCodec -> IO [AVSampleFormat]
listSupportedSampleFormats codec = do
  fmts <- getSampleFormats codec
  walkPtrs fmts (\ptr ->
                    if ptr == nullPtr
                      then return True
                      else do
                        v <- peek ptr
                        return $ getSampleFormatInt v == -1
                )

listSupportedChannelLayouts :: AVCodec -> IO [CULong]
listSupportedChannelLayouts codec = do
  chanPtr <- getChannelLayouts codec
  walkPtrs chanPtr (\ptr ->
                    if ptr == nullPtr
                      then return True
                      else do
                        v <- peek ptr
                        return $ v == 0
                   )

listSupportedSampleRates :: AVCodec -> IO [CInt]
listSupportedSampleRates codec = do
  srPtr <- getSupportedSampleRates codec
  walkPtrs srPtr (\ptr ->
                    if ptr == nullPtr
                      then return True
                      else do
                        v <- peek ptr
                        return $ v == 0
                 )