packages feed

ffmpeg-light (empty) → 0.1.0.0

raw patch · 12 files changed

+1133/−0 lines, 12 filesdep +JuicyPixelsdep +basedep +mtlsetup-changed

Dependencies added: JuicyPixels, base, mtl, transformers, vector

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2014, Anthony Cowley++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 Anthony Cowley 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
+ ffmpeg-light.cabal view
@@ -0,0 +1,51 @@+name:                ffmpeg-light+version:             0.1.0.0+synopsis:            Minimal bindings to the FFmpeg library.++description:         Stream frames from an encoded video, or stream frames to+                     a video output file. To read the first frame from+                     an @h264@-encoded file into a JuicyPixels +                     @Maybe DynamicImage@,+                     .+                     > import Codec.FFmpeg+                     > import Codec.Picture+                     > import Control.Applicative+                     > import Data.Traversable+                     >+                     > go :: IO (Maybe DynamicImage)+                     > go = do (getFrame, cleanup) <- frameReader "myVideo.mov"+                     >         (getFrame >>= traverse juicyRGB) <* cleanup+                     .+                     Tested on OS X 10.9.2 with <http://www.ffmpeg.org FFmpeg> 2.2.1+                     installed via <http://brew.sh homebrew>.++license:             BSD3+license-file:        LICENSE+author:              Anthony Cowley+maintainer:          acowley@gmail.com+copyright:           Copyright (C) 2014 Anthony Cowley+category:            Codec+build-type:          Simple+extra-source-files:  src/hscMacros.h+cabal-version:       >=1.10++library+  exposed-modules:     Codec.FFmpeg,+                       Codec.FFmpeg.Common,+                       Codec.FFmpeg.Decode,+                       Codec.FFmpeg.Encode,+                       Codec.FFmpeg.Enums,+                       Codec.FFmpeg.Juicy,+                       Codec.FFmpeg.Types,+                       Codec.FFmpeg.Internal.Debug+  build-tools:         hsc2hs+  build-depends:       base >=4.6 && < 5, +                       vector >= 0.10.9 && < 0.11,+                       transformers >= 0.3 && < 0.4,+                       mtl >= 2.1.3 && < 2.2,+                       JuicyPixels >= 3.1 && < 3.2+  pkgconfig-depends:   libavutil, libavformat, libavcodec, libswscale+  hs-source-dirs:      src+  include-dirs:        src+  default-language:    Haskell2010+  ghc-options:         -Wall
+ src/Codec/FFmpeg.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE ForeignFunctionInterface, FlexibleContexts #-}+-- | Interface to initialize FFmpeg, decode video files, encode video+-- files, and convert decoded image frames to JuicyPixels images.+module Codec.FFmpeg (-- * Initialization+                     initFFmpeg, +                     -- * Decoding+                     frameReader, frameReaderTime,+                     -- * Encoding+                     EncodingParams(..), defaultParams, frameWriter,+                     -- * JuicyPixels interop+                     juicyRGB, saveJuicyRGB,+                     -- * Types and Enums+                     module Codec.FFmpeg.Types, +                     module Codec.FFmpeg.Enums+                     )where+import Codec.FFmpeg.Decode+import Codec.FFmpeg.Encode+import Codec.FFmpeg.Enums+import Codec.FFmpeg.Juicy+import Codec.FFmpeg.Types++foreign import ccall "av_register_all" av_register_all :: IO ()++-- foreign import ccall "avcodec_register_all" avcodec_register_all :: IO ()++-- | Initialize FFmpeg by registering all known codecs. This /must/+-- be called before using other FFmpeg functions.+initFFmpeg :: IO ()+initFFmpeg = av_register_all+
+ src/Codec/FFmpeg/Common.hsc view
@@ -0,0 +1,75 @@+{-# LANGUAGE FlexibleContexts, ForeignFunctionInterface #-}+module Codec.FFmpeg.Common where+import Codec.FFmpeg.Enums+import Codec.FFmpeg.Types+import Control.Monad (when)+import Control.Monad.Error.Class+import Control.Monad.IO.Class+import Foreign.C.Types+import Foreign.Ptr++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_free_packet"+  free_packet :: AVPacket -> IO ()++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++-- * Utility functions++-- | Catch an IOException from an IO action and re-throw it in a+-- wrapping monad transformer.+wrapIOError :: (MonadIO m, Error e, MonadError e m) => IO a -> m a+wrapIOError = liftIO . flip catchError (errMsg . show)++-- | Throw an error with a 'String' message.+errMsg :: (Error e, MonadError e m) => String -> m a+errMsg = throwError . strMsg++-- * 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)+                            (errMsg "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)+                                     (errMsg "Failed to allocate buffers")
+ src/Codec/FFmpeg/Decode.hs view
@@ -0,0 +1,225 @@+{-# LANGUAGE ForeignFunctionInterface, FlexibleContexts #-}+-- | Video decoding API. Includes FFI declarations for the underlying+-- FFmpeg functions, wrappers for these functions that wrap error+-- condition checking, and high level Haskellized interfaces.+module Codec.FFmpeg.Decode where+import Codec.FFmpeg.Common+import Codec.FFmpeg.Enums+import Codec.FFmpeg.Types+import Control.Applicative+import Control.Monad (when)+import Control.Monad.Error.Class+import Control.Monad.IO.Class+import Foreign.C.String+import Foreign.C.Types+import Foreign.Marshal.Alloc (alloca, free, mallocBytes)+import Foreign.Marshal.Array (advancePtr)+import Foreign.Marshal.Utils (with)+import Foreign.Ptr+import Foreign.Storable++-- * FFI Declarations++foreign import ccall "avformat_open_input" +  avformat_open_input :: Ptr AVFormatContext -> CString -> Ptr ()+                      -> Ptr (Ptr ()) -> IO CInt++foreign import ccall "avformat_find_stream_info" +  avformat_find_stream_info :: AVFormatContext -> Ptr () -> IO CInt++foreign import ccall "av_find_best_stream"+  av_find_best_stream :: AVFormatContext -> AVMediaType -> CInt -> CInt+                      -> Ptr AVCodec -> CInt -> IO CInt++foreign import ccall "avcodec_find_decoder"+   avcodec_find_decoder :: AVCodecID -> IO AVCodec++foreign import ccall "avcodec_find_decoder_by_name"+  avcodec_find_decoder_by_name :: CString -> IO AVCodec++foreign import ccall "avcodec_get_frame_defaults"+  avcodec_get_frame_defaults :: AVFrame -> IO ()++foreign import ccall "avpicture_get_size"+  avpicture_get_size :: AVPixelFormat -> CInt -> CInt -> IO CInt++foreign import ccall "av_malloc"+  av_malloc :: CSize -> IO (Ptr ())++foreign import ccall "av_read_frame"+  av_read_frame :: AVFormatContext -> AVPacket -> IO CInt++foreign import ccall "avcodec_decode_video2"+  decode_video :: AVCodecContext -> AVFrame -> Ptr CInt -> AVPacket+               -> IO CInt+foreign import ccall "avformat_close_input"+  close_input :: Ptr AVFormatContext -> IO ()++-- * FFmpeg Decoding Interface++-- | Open an input media file.+openInput :: (MonadIO m, Error e, MonadError e m) => String -> m AVFormatContext+openInput filename = +  wrapIOError . alloca $ \ctx ->+    withCString filename $ \cstr ->+      do r <- avformat_open_input ctx cstr nullPtr nullPtr+         when (r /= 0) (errMsg "Error opening file")+         peek ctx++-- | @AVFrame@ is a superset of @AVPicture@, so we can upcast an+-- 'AVFrame' to an 'AVPicture'.+frameAsPicture :: AVFrame -> AVPicture+frameAsPicture = AVPicture . getPtr++-- | Find a codec given by name.+findDecoder :: (MonadIO m, Error e, MonadError e m) => String -> m AVCodec+findDecoder name = +  do r <- liftIO $ withCString name avcodec_find_decoder_by_name+     when (getPtr r == nullPtr)+          (errMsg $ "Unsupported codec: " ++ show name)+     return r++-- | Read packets of a media file to get stream information. This is+-- useful for file formats with no headers such as MPEG.+checkStreams :: (MonadIO m, Error e, MonadError e m) => AVFormatContext -> m ()+checkStreams ctx = +  do r <- liftIO $ avformat_find_stream_info ctx nullPtr+     when (r < 0) (errMsg "Couldn't find stream information")++-- | Searches for a video stream in an 'AVFormatContext'. If one is+-- found, returns the index of the stream in the container, and its+-- associated 'AVCodecContext' and 'AVCodec'.+findVideoStream :: (MonadIO m, Error e, MonadError e m)+                => AVFormatContext -> m (CInt, AVCodecContext, AVCodec)+findVideoStream fmt = do+  wrapIOError . alloca $ \codec -> do+      poke codec (AVCodec nullPtr)+      i <- av_find_best_stream fmt avmediaTypeVideo (-1) (-1) codec 0+      when (i < 0) (errMsg "Couldn't find a video stream")+      cod <- peek codec+      streams <- getStreams fmt+      vidStream <- peek (advancePtr streams (fromIntegral i))+      ctx <- getCodecContext vidStream+      return (i, ctx, cod)++-- | Find a registered decoder with a codec ID matching that found in+-- the given 'AVCodecContext'.+getDecoder :: (MonadIO m, Error e, MonadError e m)+           => AVCodecContext -> m AVCodec+getDecoder ctx = do p <- liftIO $ getCodecID ctx >>= avcodec_find_decoder+                    when (getPtr p == nullPtr) (errMsg "Unsupported codec")+                    return p++-- | Initialize the given 'AVCodecContext' to use the given+-- 'AVCodec'. **NOTE**: This function is not thread safe!+openCodec :: (MonadIO m, Error e, MonadError e m)+          => AVCodecContext -> AVCodec -> m AVDictionary+openCodec ctx cod = +  wrapIOError . alloca $ \dict -> do+    poke dict (AVDictionary nullPtr)+    r <- open_codec ctx cod dict+    when (r < 0) (errMsg "Couldn't open decoder")+    peek dict++-- | Return the next frame of a stream.+read_frame_check :: AVFormatContext -> AVPacket -> IO ()+read_frame_check ctx pkt = do r <- av_read_frame ctx pkt+                              when (r < 0) (errMsg "Frame read failed")++-- | Read RGB frames from a video stream.+frameReader :: (MonadIO m, Error e, MonadError e m)+            => FilePath -> m (IO (Maybe AVFrame), IO ())+frameReader fileName =+  do inputContext <- openInput fileName+     checkStreams inputContext+     (vidStreamIndex, ctx, cod) <- findVideoStream inputContext+     _ <- openCodec ctx cod+     prepareReader inputContext vidStreamIndex ctx++-- | Read time stamped RGB frames from a video stream. Time is given+-- in seconds from the start of the stream.+frameReaderTime :: (MonadIO m, Error e, MonadError e m)+                => FilePath -> m (IO (Maybe (AVFrame, Double)), IO ())+frameReaderTime fileName =+  do inputContext <- openInput fileName+     checkStreams inputContext+     (vidStreamIndex, ctx, cod) <- findVideoStream inputContext+     _ <- openCodec ctx cod+     (reader, cleanup) <- prepareReader inputContext vidStreamIndex ctx+     AVRational num den <- liftIO $ getTimeBase ctx+     let (numl, dend) = (fromIntegral num, fromIntegral den)+         frameTime' frame = +           do n <- getPts frame+              return $ fromIntegral (n * numl) / dend+         readTS = do frame <- reader+                     case frame of+                       Nothing -> return Nothing+                       Just f -> do t <- frameTime' f+                                    return $ Just (f, t)+     return (readTS, cleanup)++-- | Construct an action that gets the next available frame, and an+-- action to release all resources associated with this video stream.+prepareReader :: (MonadIO m, Error e, MonadError e m)+              => AVFormatContext -> CInt -> AVCodecContext+              -> m (IO (Maybe AVFrame), IO ())+prepareReader fmtCtx vidStream codCtx =+  wrapIOError $+  do fRaw <- frame_alloc_check+     fRgb <- frame_alloc_check++     w <- getWidth codCtx+     h <- getHeight codCtx+     fmt <- getPixelFormat codCtx++     setWidth fRgb w+     setHeight fRgb h+     setPixelFormat fRgb avPixFmtRgb24++     frame_get_buffer_check fRgb 32++     sws <- sws_getCachedContext (SwsContext nullPtr) +              w h fmt+              w h avPixFmtRgb24+              swsBilinear+              nullPtr nullPtr nullPtr++     pkt <- AVPacket <$> mallocBytes packetSize+     let cleanup = do with fRgb av_frame_free+                      with fRaw av_frame_free+                      _ <- codec_close codCtx+                      with fmtCtx close_input+                      free (getPtr pkt)+         getFrame = do+           read_frame_check fmtCtx pkt+           whichStream <- getStreamIndex pkt+           if whichStream == vidStream+           then do+             fin <- alloca $ \finished -> do+                      _ <- decode_video codCtx fRaw finished pkt+                      peek finished+             if fin > 0+             then do+               -- Some streaming codecs require a final flush with+               -- an empty packet+               -- fin' <- alloca $ \fin2 -> do+               --           free_packet pkt+               --           (#poke AVPacket, data) pkt nullPtr+               --           (#poke AVPacket, size) pkt (0::CInt)+               --           decode_video codCtx fRaw fin2 pkt+               --           peek fin2+               let frameData = castPtr $ hasData fRaw+                   lnSize = hasLineSize fRaw+                   rgbData = castPtr $ hasData fRgb+                   rgbLnSize = hasLineSize fRgb++               _ <- sws_scale sws frameData lnSize 0 h rgbData rgbLnSize++               -- Copy the raw frame's timestamp to the RGB frame+               getPktPts fRaw >>= setPts fRgb++               free_packet pkt+               return $ Just fRgb+             else free_packet pkt >> getFrame+           else free_packet pkt >> getFrame+     return (getFrame `catchError` const (return Nothing), cleanup)
+ src/Codec/FFmpeg/Encode.hs view
@@ -0,0 +1,260 @@+{-# LANGUAGE ForeignFunctionInterface #-}+-- | Video encoding API. Includes FFI declarations for the underlying+-- FFmpeg functions, wrappers for these functions that wrap error+-- condition checking, and high level Haskellized interfaces.+module Codec.FFmpeg.Encode where+import Codec.FFmpeg.Common+import Codec.FFmpeg.Enums+import Codec.FFmpeg.Types+import Control.Applicative+import Control.Monad (when)+import Control.Monad.Error.Class+import Data.Bits+import Data.Maybe (fromMaybe)+import Data.Vector.Storable (Vector)+import qualified Data.Vector.Storable as V+import Foreign.C.String+import Foreign.C.Types+import Foreign.Marshal.Alloc+import Foreign.Marshal.Array+import Foreign.Marshal.Utils++import Foreign.Ptr+import Foreign.Storable++-- Based on the FFmpeg muxing example+-- http://www.ffmpeg.org/doxygen/2.1/doc_2examples_2muxing_8c-example.html++-- * FFI Declarations++foreign import ccall "avcodec_find_encoder"+  avcodec_find_encoder :: AVCodecID -> IO AVCodec++foreign import ccall "avcodec_find_encoder_by_name"+  avcodec_find_encoder_by_name :: CString -> IO AVCodec++foreign import ccall "av_opt_set"+  av_opt_set :: Ptr () -> CString -> CString -> CInt -> IO CInt++foreign import ccall "avcodec_encode_video2"+  avcodec_encode_video2 :: AVCodecContext -> AVPacket -> AVFrame -> Ptr CInt+                        -> IO CInt++foreign import ccall "av_image_alloc"+  av_image_alloc :: Ptr (Ptr CUChar) -> Ptr CInt -> CInt -> CInt+                 -> AVPixelFormat -> CInt -> IO CInt++foreign import ccall "av_freep"+  av_freep :: Ptr (Ptr a) -> IO ()++foreign import ccall "av_guess_format"+  guess_format :: CString -> CString -> CString -> IO AVOutputFormat++foreign import ccall "avformat_alloc_output_context2"+  avformat_alloc_output_context :: Ptr AVFormatContext -> AVOutputFormat+                                -> CString -> CString -> IO CInt++foreign import ccall "avformat_new_stream"+  avformat_new_stream :: AVFormatContext -> AVCodec -> IO AVStream++foreign import ccall "av_write_frame"+  av_write_frame :: AVFormatContext -> AVPacket -> IO CInt++foreign import ccall "av_interleaved_write_frame"+  av_interleaved_write_frame :: AVFormatContext -> AVPacket -> IO CInt++foreign import ccall "avformat_write_header"+  avformat_write_header :: AVFormatContext -> Ptr AVDictionary -> IO CInt++foreign import ccall "av_write_trailer"+  av_write_trailer :: AVFormatContext -> IO CInt++foreign import ccall "avio_open"+  avio_open :: Ptr AVIOContext -> CString -> AVIOFlag -> IO CInt++foreign import ccall "avio_close"+  avio_close :: AVIOContext -> IO CInt++foreign import ccall "avformat_free_context"+  avformat_free_context :: AVFormatContext -> IO ()++-- * FFmpeg Encoding Interface++-- | Minimal parameters describing the desired video output.+data EncodingParams = EncodingParams { epWidth  :: CInt+                                     , epHeight :: CInt+                                     , epFps    :: Int+                                     , epCodec  :: AVCodecID+                                     , epPreset :: String }++-- | Use default parameters for a video of the given width and+-- height. This produces an h264 encoded stream.+defaultParams :: CInt -> CInt -> EncodingParams+defaultParams w h = EncodingParams w h 30 avCodecIdH264 "medium"++-- | Find and initialize the requested encoder, and add a video stream+-- to the output container.+initStream :: EncodingParams -> AVFormatContext -> IO (AVStream, AVCodecContext)+initStream ep _+  | (epWidth ep `rem` 2, epHeight ep `rem` 2) /= (0,0) =+    throwError $ strMsg "Video dimensions must be multiples of two"+initStream ep oc = do+  cod <- avcodec_find_encoder (epCodec ep)+  when (getPtr cod == nullPtr)+       (throwError $ strMsg "Couldn't find H264 encoder")++  st <- avformat_new_stream oc cod+  getNumStreams oc >>= setId st . subtract 1++  ctx <- getCodecContext st+  setWidth ctx (epWidth ep)+  setHeight ctx (epHeight ep)+  let framePeriod = AVRational 1 (fromIntegral $ epFps ep)+  setTimeBase ctx framePeriod+  setPixelFormat ctx avPixFmtYuv420p+  +  -- Some formats want stream headers to be separate+  outputFlags <- getOutputFormat oc >>= getFormatFlags+  when (outputFlags .&. avfmtGlobalheader /= clearBit (bit 0) 0) $+    getCodecFlags ctx >>= setCodecFlags ctx . (.|. codecFlagGlobalHeader)++  -- _ <- withCString "vprofile" $ \kStr ->+  --        withCString (preset ep) $ \vStr ->+  --          av_opt_set ((#ptr AVCodecContext, priv_data) (getPtr ctx))+  --                     kStr vStr 0+  -- _ <- withCString "preset" $ \kStr ->+  --        withCString "medium" $ \vStr ->+  --          av_opt_set ((#ptr AVCodecContext, priv_data) (getPtr ctx))+  --                     kStr vStr 0++  rOpen <- open_codec ctx cod nullPtr+  when (rOpen < 0) (throwError $ strMsg "Couldn't open codec")+  return (st, ctx)++-- | Initialize a temporary YUV frame of the same resolution as the+-- output video stream. We well convert RGB frames using this frame as+-- a destination before encoding the video frame.+initTempYuv :: EncodingParams -> IO AVFrame+initTempYuv ep = do+  yuv <- frame_alloc_check+  setPixelFormat yuv avPixFmtYuv420p+  setWidth yuv (epWidth ep)+  setHeight yuv (epHeight ep)+  setPts yuv 0+  frame_get_buffer_check yuv 32+  return yuv++-- | Allocate an output context inferring the codec from the given+-- file name.+allocOutputContext :: FilePath -> IO AVFormatContext+allocOutputContext fname = do+  oc <- alloca $ \ocTmp ->+          withCString fname $ \fname' -> do+            r <- avformat_alloc_output_context ocTmp (AVOutputFormat nullPtr)+                                               nullPtr fname'+            when (r < 0)+                 (throwError $ strMsg "Couldn't allocate output format context")+            peek ocTmp+  when (getPtr oc == nullPtr)+       (throwError $ strMsg "Couldn't allocate output AVFormatContext")+  return oc++-- | Open the given file for writing.+avio_open_check :: AVFormatContext -> String -> IO ()+avio_open_check oc fname =+  do r <- withCString fname $ \cstr ->+            avio_open (hasIOContext oc) cstr avioFlagWrite+     when (r < 0) (errMsg "Error opening IO for writing")++-- | Close an open IO context.+avio_close_check :: AVFormatContext -> IO ()+avio_close_check oc = do r <- getIOContext oc >>= avio_close+                         when (r /= 0) (errMsg "Error closing IO")++-- | Returns 'True' if the 'AVPacket' was updated with new output+-- data; 'False' otherwise.+encode_video_check :: AVCodecContext -> AVPacket -> Maybe AVFrame -> IO Bool+encode_video_check ctx pkt frame =+  alloca $ \gotOutput -> do+    r <- avcodec_encode_video2 ctx pkt frame' gotOutput+    when (r < 0) (errMsg "Error encoding frame")+    (> 0) <$> peek gotOutput+  where frame' = fromMaybe (AVFrame nullPtr) frame++-- | Allocate the stream private data and write the stream header to+-- an output media file.+write_header_check :: AVFormatContext -> IO ()+write_header_check oc = do r <- avformat_write_header oc nullPtr+                           when (r < 0) (errMsg "Error writing header")++-- | Write a packet to an output media file.+write_frame_check :: AVFormatContext -> AVPacket -> IO ()+write_frame_check oc pkt = do r <- av_write_frame oc pkt+                              when (r < 0) (errMsg "Error writing frame")++-- | Write the stream trailer to an output media file and free the+-- private data. May only be called after a successful call to+-- 'write_header_check'.+write_trailer_check :: AVFormatContext -> IO ()+write_trailer_check oc = do r <- av_write_trailer oc+                            when (r /= 0) (errMsg "Error writing trailer")++-- | Open a target file for writing a video stream. The function+-- returned may be used to write RGB images of the resolution given by+-- the provided 'EncodingParams'. The function will convert the+-- supplied RGB frame to YUV (specifically, @yuv420p@) before encoding+-- the image to the video stream. If this function is applied to+-- 'Nothing', then the output stream is closed. Note that 'Nothing'+-- /must/ be provided to properly terminate video encoding.+frameWriter :: EncodingParams -> FilePath -> IO (Maybe (Vector CUChar) -> IO ())+frameWriter ep fname = do+  oc <- allocOutputContext fname+  (st,ctx) <- initStream ep oc++  yuv <- initTempYuv ep++  sws <- sws_getCachedContext (SwsContext nullPtr)+           (epWidth ep) (epHeight ep) avPixFmtRgb24+           (epWidth ep) (epHeight ep) avPixFmtYuv420p+           swsBilinear nullPtr nullPtr nullPtr++  pkt <- AVPacket <$> mallocBytes packetSize++  stIndex <- getStreamIndex st+  avio_open_check oc fname+  write_header_check oc++  tb <- getTimeBase st+  codecTB <- getCodecContext st >>= getTimeBase+  let frameTime = av_rescale_q 1 codecTB tb++  let resetPacket = do init_packet pkt+                       setData pkt nullPtr+                       setSize pkt 0+      writePacket = do setStreamIndex pkt stIndex+                       write_frame_check oc pkt+      go Nothing = do+        resetPacket+        goOn <- encode_video_check ctx pkt Nothing+        if goOn+        then writePacket >> go Nothing+        else do write_trailer_check oc+                _ <- codec_close ctx+                with yuv av_frame_free+                avio_close_check oc+                avformat_free_context oc++      go (Just pixels) = do+        resetPacket+        let yuvData = castPtr $ hasData yuv+            yuvStride = hasLineSize yuv++        _ <- V.unsafeWith pixels $ \pixelPtr ->+               withArray (pixelPtr : replicate 7 nullPtr) $ \srcArray ->+                 withArray (epWidth ep * 3 : replicate 7 0) $ \strides ->+                   sws_scale sws srcArray strides 0 (epHeight ep) +                             yuvData yuvStride++        getPts yuv >>= setPts yuv . (+ frameTime)+        encode_video_check ctx pkt (Just yuv) >>= flip when writePacket+  return go
+ src/Codec/FFmpeg/Enums.hsc view
@@ -0,0 +1,192 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+module Codec.FFmpeg.Enums where+import Data.Bits (Bits)+import Foreign.C.Types+import Foreign.Storable (Storable)++#include <libavcodec/avcodec.h>+#include <libavformat/avformat.h>+#include <libavutil/avutil.h>+#include <libswscale/swscale.h>++newtype AVMediaType = AVMediaType CInt deriving (Eq, Storable)+#enum AVMediaType,AVMediaType \+ , AVMEDIA_TYPE_VIDEO\+ , AVMEDIA_TYPE_AUDIO\+ , AVMEDIA_TYPE_DATA\+ , AVMEDIA_TYPE_SUBTITLE\+ , AVMEDIA_TYPE_ATTACHMENT\+ , AVMEDIA_TYPE_NB++newtype AVPixelFormat = AVPixelFormat CInt deriving (Eq, Storable)+#enum AVPixelFormat,AVPixelFormat \+ , AV_PIX_FMT_RGB24\+ , AV_PIX_FMT_Y400A\+ , AV_PIX_FMT_RGB32\+ , AV_PIX_FMT_RGB32_1\+ , AV_PIX_FMT_BGR32\+ , AV_PIX_FMT_BGR32_1\+ , AV_PIX_FMT_GRAY8\+ , AV_PIX_FMT_GRAY16\+ , AV_PIX_FMT_GRAY8A\+ , AV_PIX_FMT_RGB565\+ , AV_PIX_FMT_RGB555\+ , AV_PIX_FMT_YUV420P\+ , AV_PIX_FMT_YUV420P9\+ , AV_PIX_FMT_YUV420P10\+ , AV_PIX_FMT_YUV420P12\+ , AV_PIX_FMT_YUV422P12\+ , AV_PIX_FMT_YUV444P12\+ , AV_PIX_FMT_YUV420P14\+ , AV_PIX_FMT_YUV422P14\+ , AV_PIX_FMT_YUV444P14\+ , AV_PIX_FMT_YUV420P16\+ , AV_PIX_FMT_YUV422P16\+ , AV_PIX_FMT_YUV444P16\+ , AV_PIX_FMT_RGBA64\+ , AV_PIX_FMT_BGRA64++instance Show AVPixelFormat where+  show x+    | x == avPixFmtRgb24 = "AV_PIX_FMT_RGB24"+    | x == avPixFmtYuv420p = "AV_PIX_FMT_Y420P"+    | x == avPixFmtYuv422p12 = "AV_PIX_FMTYUV422P12"+    | x == avPixFmtYuv420p14 = "AV_PIX_FMTYUV422P12"+    | otherwise = let AVPixelFormat y = x+                  in "Other pixel format: "++show y++newtype AVCodecID = AVCodecID CInt deriving (Eq, Show, Storable)+#enum AVCodecID,AVCodecID \+ , AV_CODEC_ID_H264\+ , AV_CODEC_ID_THEORA\+ , AV_CODEC_ID_MPEG4\+ , AV_CODEC_ID_MPEG2VIDEO\+ , AV_CODEC_ID_GIF\+ , AV_CODEC_ID_AAC\+ , AV_CODEC_ID_MP3\+ , AV_CODEC_ID_DTS++newtype SwsAlgorithm = SwsAlgorithm CUInt deriving (Eq, Show, Storable)+#enum SwsAlgorithm,SwsAlgorithm \+ , SWS_FAST_BILINEAR\+ , SWS_BILINEAR\+ , SWS_BICUBIC\+ , SWS_X\+ , SWS_POINT\+ , SWS_AREA\+ , SWS_BICUBLIN\+ , SWS_GAUSS\+ , SWS_SINC\+ , SWS_LANCZOS\+ , SWS_SPLINE++newtype FFProfile = FFProfile CInt deriving (Eq, Storable)+#enum FFProfile, FFProfile \+ , FF_PROFILE_AAC_MAIN\+ , FF_PROFILE_AAC_LOW\+ , FF_PROFILE_AAC_SSR\+ , FF_PROFILE_AAC_LTP\+ , FF_PROFILE_AAC_HE\+ , FF_PROFILE_AAC_HE_V2\+ , FF_PROFILE_AAC_LD\+ , FF_PROFILE_AAC_ELD\+ , FF_PROFILE_MPEG2_AAC_LOW\+ , FF_PROFILE_MPEG2_AAC_HE\+ , FF_PROFILE_DTS\+ , FF_PROFILE_DTS_ES\+ , FF_PROFILE_DTS_96_24\+ , FF_PROFILE_DTS_HD_HRA\+ , FF_PROFILE_DTS_HD_MA\+ , FF_PROFILE_MPEG2_422\+ , FF_PROFILE_MPEG2_HIGH\+ , FF_PROFILE_MPEG2_SS\+ , FF_PROFILE_MPEG2_SNR_SCALABLE\+ , FF_PROFILE_MPEG2_MAIN\+ , FF_PROFILE_MPEG2_SIMPLE\+ , FF_PROFILE_H264_CONSTRAINED\+ , FF_PROFILE_H264_INTRA\+ , FF_PROFILE_H264_BASELINE\+ , FF_PROFILE_H264_CONSTRAINED_BASELINE\+ , FF_PROFILE_H264_MAIN\+ , FF_PROFILE_H264_EXTENDED\+ , FF_PROFILE_H264_HIGH\+ , FF_PROFILE_H264_HIGH_10\+ , FF_PROFILE_H264_HIGH_10_INTRA\+ , FF_PROFILE_H264_HIGH_422\+ , FF_PROFILE_H264_HIGH_422_INTRA\+ , FF_PROFILE_H264_HIGH_444\+ , FF_PROFILE_H264_HIGH_444_PREDICTIVE\+ , FF_PROFILE_H264_HIGH_444_INTRA\+ , FF_PROFILE_H264_CAVLC_444\+ , FF_PROFILE_VC1_SIMPLE\+ , FF_PROFILE_VC1_MAIN\+ , FF_PROFILE_VC1_COMPLEX\+ , FF_PROFILE_VC1_ADVANCED\+ , FF_PROFILE_MPEG4_SIMPLE\+ , FF_PROFILE_MPEG4_SIMPLE_SCALABLE\+ , FF_PROFILE_MPEG4_CORE\+ , FF_PROFILE_MPEG4_MAIN\+ , FF_PROFILE_MPEG4_N_BIT\+ , FF_PROFILE_MPEG4_SCALABLE_TEXTURE\+ , FF_PROFILE_MPEG4_SIMPLE_FACE_ANIMATION\+ , FF_PROFILE_MPEG4_BASIC_ANIMATED_TEXTURE\+ , FF_PROFILE_MPEG4_HYBRID\+ , FF_PROFILE_MPEG4_ADVANCED_REAL_TIME\+ , FF_PROFILE_MPEG4_CORE_SCALABLE\+ , FF_PROFILE_MPEG4_ADVANCED_CODING\+ , FF_PROFILE_MPEG4_ADVANCED_CORE\+ , FF_PROFILE_MPEG4_ADVANCED_SCALABLE_TEXTURE\+ , FF_PROFILE_MPEG4_SIMPLE_STUDIO\+ , FF_PROFILE_MPEG4_ADVANCED_SIMPLE++newtype AVIOFlag = AVIOFlag CInt deriving (Eq, Storable)+#enum AVIOFlag, AVIOFlag \+ , AVIO_FLAG_READ\+ , AVIO_FLAG_WRITE\+ , AVIO_FLAG_READ_WRITE\+ , AVIO_FLAG_NONBLOCK\+ , AVIO_FLAG_DIRECT++newtype AVRoundMode = AVRoundMode CInt deriving (Eq, Storable)+#enum AVRoundMode, AVRoundMode \+ , AV_ROUND_NEAR_INF\+ , AV_ROUND_PASS_MINMAX++newtype CodecFlag = CodecFlag CInt deriving (Eq, Bits, Storable)+#enum CodecFlag, CodecFlag \+ , CODEC_FLAG_UNALIGNED\+ , CODEC_FLAG_QSCALE\+ , CODEC_FLAG_4MV\+ , CODEC_FLAG_OUTPUT_CORRUPT\+ , CODEC_FLAG_QPEL\+ , CODEC_FLAG_GMC\+ , CODEC_FLAG_MV0\+ , CODEC_FLAG_INPUT_PRESERVED\+ , CODEC_FLAG_PASS1\+ , CODEC_FLAG_PASS2\+ , CODEC_FLAG_GRAY\+ , CODEC_FLAG_EMU_EDGE\+ , CODEC_FLAG_PSNR\+ , CODEC_FLAG_TRUNCATED\+ , CODEC_FLAG_NORMALIZE_AQP\+ , CODEC_FLAG_INTERLACED_DCT\+ , CODEC_FLAG_LOW_DELAY\+ , CODEC_FLAG_GLOBAL_HEADER\+ , CODEC_FLAG_BITEXACT\+ , CODEC_FLAG_AC_PRED\+ , CODEC_FLAG_LOOP_FILTER\+ , CODEC_FLAG_INTERLACED_ME\+ , CODEC_FLAG_CLOSED_GOP++newtype FormatFlag = FormatFlag CInt deriving (Eq, Bits, Storable)+#enum FormatFlag, FormatFlag \+ , AVFMT_NOFILE\+ , AVFMT_NEEDNUMBER\+ , AVFMT_RAWPICTURE\+ , AVFMT_GLOBALHEADER\+ , AVFMT_NOTIMESTAMPS\+ , AVFMT_VARIABLE_FPS\+ , AVFMT_NODIMENSIONS\+ , AVFMT_NOSTREAMS\+ , AVFMT_ALLOW_FLUSH\+ , AVFMT_TS_NONSTRICT
+ src/Codec/FFmpeg/Internal/Debug.hsc view
@@ -0,0 +1,41 @@+-- | Helpers for dumping information about codecs to stdout.+module Codec.FFmpeg.Internal.Debug where+import Codec.FFmpeg.Types+import Foreign.C.String+import Foreign.C.Types+import Foreign.Storable++#include <libavcodec/avcodec.h>+#include <libavformat/avformat.h>++-- | FFmpeg's built-in format debug utlity.+foreign import ccall "av_dump_format"+  av_dump_format :: AVFormatContext -> CInt -> CString -> CInt -> IO ()++-- | Print the short name, long name, and ID of a codec.+debugCodec :: AVCodec -> IO ()+debugCodec cod = do+  longName <- getLongName cod >>= peekCString+  shortName <- getName cod >>= peekCString+  cid <- getCodecID cod+  putStrLn $ "Codec short_name = " ++ show shortName+  putStrLn $ "Codec long_name = " ++ show longName+  putStrLn $ "Codec ID = " ++ show cid++-- | Print various codec settings.+debugCodecContext :: AVCodecContext -> IO()+debugCodecContext (AVCodecContext p) = do+  putStrLn "*** AVCodecContext dump:"+  (#peek AVCodecContext, profile) p >>= si "profile"+  (#peek AVCodecContext, flags) p >>= si "flags"+  (#peek AVCodecContext, flags2) p >>= si "flags2"+  (#peek AVCodecContext, gop_size) p >>= si "gop_size"+  (#peek AVCodecContext, bit_rate) p >>= si "bit_rate"+  (#peek AVCodecContext, max_b_frames) p >>= si "max_b_frames"+  (#peek AVCodecContext, b_frame_strategy) p >>= si "b_frame_strategy"+  (#peek AVCodecContext, qmin) p >>= si "qmin"+  (#peek AVCodecContext, qmax) p >>= si "qmax"+  (#peek AVCodecContext, me_cmp) p >>= si "me_cmp"+  (#peek AVCodecContext, me_range) p >>= si "me_range"+  putStrLn ""+  where si msg = putStrLn . ((msg++" = ")++) . show :: CInt -> IO ()
+ src/Codec/FFmpeg/Juicy.hs view
@@ -0,0 +1,37 @@+-- | Convert between FFmpeg frames and JuicyPixels images.+module Codec.FFmpeg.Juicy where+import Codec.Picture+import Codec.FFmpeg.Enums+import Codec.FFmpeg.Types+import Control.Applicative+import Control.Arrow ((&&&))+import Control.Monad (when, (>=>))+import qualified Data.Vector.Storable as V+import qualified Data.Vector.Storable.Mutable as VM+import Foreign.C.Types+import Foreign.Marshal.Array (advancePtr, copyArray)+import Foreign.Ptr (castPtr, Ptr)++-- | Convert an RGB24 'AVFrame' to a 'DynamicImage'.+juicyRGB :: AVFrame -> IO DynamicImage+juicyRGB frame = do+  fmt <- getPixelFormat frame+  when (fmt /= avPixFmtRgb24)+       (putStrLn "Not RGB24?!")+  w <- fromIntegral <$> getWidth frame+  h <- fromIntegral <$> getHeight frame+  pixels <- castPtr <$> getData frame :: IO (Ptr CUChar)+  srcStride <- fromIntegral <$> getLineSize frame+  let dstStride = w * 3+  v <- VM.new (w*h*3)+  VM.unsafeWith v $ \vptr ->+    mapM_ (\(i,o) -> copyArray (advancePtr vptr o)+                               (advancePtr pixels i)+                               (w * 3))+          (map ((srcStride *) &&& (dstStride*)) [0 .. h - 1])+  v' <- V.unsafeFreeze v+  return $ ImageRGB8 (Image w h (V.unsafeCast v'))++-- | Save an RGB24 'AVFrame' to a PNG file on disk.+saveJuicyRGB :: FilePath -> AVFrame -> IO ()+saveJuicyRGB name = juicyRGB >=> savePngImage name 
+ src/Codec/FFmpeg/Types.hsc view
@@ -0,0 +1,145 @@+{-# LANGUAGE ForeignFunctionInterface, FlexibleInstances,+             GeneralizedNewtypeDeriving #-}+module Codec.FFmpeg.Types where+import Codec.FFmpeg.Enums+import Control.Applicative+import Foreign.C.String (CString)+import Foreign.C.Types+import Foreign.Ptr+import Foreign.Storable++#include <libavcodec/avcodec.h>+#include <libavformat/avformat.h>+#include <libavutil/avutil.h>+#include <libswscale/swscale.h>+#include "hscMacros.h"++class HasPtr a where+  getPtr :: a -> Ptr ()++instance HasPtr (Ptr ()) where getPtr = id++newtype AVFormatContext = AVFormatContext (Ptr ()) deriving (Storable, HasPtr)+#mkField NumStreams, CInt+#mkField Streams, (Ptr AVStream)+#mkField OutputFormat, AVOutputFormat+#mkField IOContext, AVIOContext++#hasField AVFormatContext, NumStreams, nb_streams+#hasField AVFormatContext, Streams, streams+#hasField AVFormatContext, OutputFormat, oformat+#hasField AVFormatContext, IOContext, pb++newtype AVCodecContext = AVCodecContext (Ptr ()) deriving (Storable, HasPtr)++#mkField BitRate, CInt+#hasField AVCodecContext, BitRate, bit_rate++#mkField Width, CInt+#mkField Height, CInt+#mkField TimeBase, AVRational+#mkField GopSize, CInt+#mkField PixelFormat, AVPixelFormat+#mkField CodecFlags, CodecFlag+#mkField CodecID, AVCodecID++#hasField AVCodecContext, Width, width+#hasField AVCodecContext, Height, height+#hasField AVCodecContext, TimeBase, time_base+#hasField AVCodecContext, GopSize, gop_size+#hasField AVCodecContext, PixelFormat, pix_fmt+#hasField AVCodecContext, CodecFlags, flags+#hasField AVCodecContext, CodecID, codec_id++newtype AVStream = AVStream (Ptr ()) deriving (Storable, HasPtr)++#mkField Id, CInt+#mkField CodecContext, AVCodecContext+#mkField StreamIndex, CInt++#hasField AVStream, Id, id+#hasField AVStream, TimeBase, time_base+#hasField AVStream, CodecContext, codec+#hasField AVStream, StreamIndex, index++newtype AVCodec = AVCodec (Ptr ()) deriving (Storable, HasPtr)+#mkField LongName, CString+#mkField Name, CString++#hasField AVCodec, LongName, long_name+#hasField AVCodec, Name, name+#hasField AVCodec, CodecID, id++newtype AVDictionary = AVDictionary (Ptr ()) deriving (Storable, HasPtr)+newtype AVFrame = AVFrame (Ptr ()) deriving (Storable, HasPtr)+#mkField Pts, CLong+#mkField PktPts, CLong+#mkField LineSize, CInt++#hasField AVFrame, PixelFormat, format+#hasField AVFrame, Width, width+#hasField AVFrame, Height, height+#hasField AVFrame, LineSize, linesize+#hasField AVFrame, Pts, pts+#hasField AVFrame, PktPts, pkt_pts+#hasField AVFrame, Data, data++newtype AVPicture = AVPicture (Ptr ()) deriving (Storable, HasPtr)+newtype SwsContext = SwsContext (Ptr ()) deriving (Storable, HasPtr)+newtype AVOutputFormat = AVOutputFormat (Ptr ()) deriving (Storable, HasPtr)+#mkField FormatFlags, FormatFlag+#hasField AVOutputFormat, FormatFlags, flags++newtype AVIOContext = AVIOContext (Ptr ()) deriving (Storable, HasPtr)++newtype AVPacket = AVPacket (Ptr ()) deriving (Storable, HasPtr)+#mkField Data, (Ptr ())+#hasField AVPacket, Data, data+#mkField Size, CInt+#hasField AVPacket, Size, size++#hasField AVPacket, StreamIndex, stream_index++-- | @sizeof@ the 'AVPacket' structure in bytes.+packetSize :: Int+packetSize = #size AVPacket++-- * Types with Haskell equivalents++data AVRational = AVRational { numerator   :: CInt+                             , denomenator :: CInt } deriving Show++instance Storable AVRational where+  sizeOf _ = #size AVRational+  alignment _ = #size AVRational+  peek ptr = AVRational <$> (#peek AVRational, num) ptr+                        <*> (#peek AVRational, den) ptr+  poke ptr (AVRational n d) = do (#poke AVRational, num) ptr n+                                 (#poke AVRational, den) ptr d++foreign import ccall "av_rescale_rnd"+  av_rescale_rnd :: CLong -> CLong -> CLong -> AVRoundMode -> CLong++-- | Convert an 'AVRational' to a 'Double'+av_q2d :: AVRational -> CDouble+av_q2d r = fromIntegral (numerator r) / fromIntegral (denomenator r)++-- | Rescale an integer from one time base to another.+av_rescale_q :: CLong -> AVRational -> AVRational -> CLong+av_rescale_q a bq cq = av_rescale_rnd a b c avRoundNearInf+  where b = fromIntegral (numerator bq) * fromIntegral (denomenator cq)+        c = fromIntegral (numerator cq) * fromIntegral (denomenator bq)++data AVFrac = AVFrac { fracVal :: CLong+                     , fracNum :: CLong+                     , fracDen :: CLong } deriving Show++instance Storable AVFrac where+  sizeOf _ = #size AVFrac+  alignment _ = #size AVFrac+  peek ptr = AVFrac <$> (#peek AVFrac, val) ptr+                    <*> (#peek AVFrac, num) ptr+                    <*> (#peek AVFrac, den) ptr+  poke ptr (AVFrac v n d) = do (#poke AVFrac, val) ptr v+                               (#poke AVFrac, num) ptr n+                               (#poke AVFrac, den) ptr d
+ src/hscMacros.h view
@@ -0,0 +1,45 @@+#include <stdio.h>++/*+Creates a class for a field with a particular Haskell type.+Example: #mkField Width, CInt+Generates:+class HasWidth t where+  getWidth :: t -> IO CInt+  setWidth :: t -> CInt -> IO ()+  hasWidth :: t -> Ptr CInt+*/++#define hsc_mkField(name, hType) \+  printf("class Has%s t where\n", #name);\+  printf("  get%s :: t -> IO %s\n", #name, #hType);\+  printf("  set%s :: t -> %s -> IO ()\n", #name, #hType);\+  printf("  has%s :: t -> Ptr %s\n", #name, #hType);++/*+Creates an instance of settable field class. The assumption is that+the first argument, the type name, is the name of bot the C and+Haskell types involved. This makes use of the HasPtr class whose+single method, getPtr, with type "a -> Ptr ()", unwraps a raw Ptr from+a value (typically a newtype around a Ptr).++Example: #hasField AVCodecContext, BitRate, bit_rate+Generates:+instance HasBitRate AVCodecContext where+  getBitRate = (#peek AVCodecContext, bit_rate) . getPtr+  setBitRate = (#poke AVCodecContext, bit_rate) . getPtr+  hasBitRate = (#ptr AVCodecContext, bit_rate) . getPtr+*/++#define hsc_hasField(type, hName, cName) \+   printf("instance Has%s %s where\n", #hName, #type);\+   printf("  get%s = ", #hName);\+   hsc_peek(type, cName)\+   printf(" . getPtr\n");\+   printf("  set%s = ", #hName);\+   hsc_poke(type, cName)\+   printf(" . getPtr\n");\+   printf("  has%s = ", #hName);\+   hsc_ptr(type, cName)\+   printf(" . getPtr\n");+