diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+# 0.13.0
+* Support webcams on Linux
+
 # 0.12.2.2
 * Fix a video playback framerate issue in the `vplay` demo (@gelisam)
 
diff --git a/demo/Main.hs b/demo/Main.hs
--- a/demo/Main.hs
+++ b/demo/Main.hs
@@ -5,6 +5,7 @@
 import qualified Data.Time.Clock as C
 import qualified Data.Vector.Storable as V
 import System.Environment
+import qualified System.Info as Info
 import Control.Monad (unless)
 
 -- The example used in the README
@@ -65,11 +66,20 @@
                  unless (realToFrac (C.diffUTCTime now start) >= time) go
      go
 
-testCamera :: IO ()
-testCamera =
+testCamera :: String -> IO ()
+testCamera cameraDevice =
   do initFFmpeg -- Defaults to quiet (minimal) logging
      -- setLogLevel avLogInfo -- Restore standard ffmpeg logging
-     (getFrame, cleanup) <- imageReader (Camera "0:0" defaultCameraConfig)
+     putStrLn "Opening camera..."
+     (getFrame, cleanup) <- imageReader $
+       case Info.os of
+         "linux" ->
+           let cfg = CameraConfig (Just 30) Nothing (Just "mjpeg")
+                                  -- (Just "v4l2")
+           in Camera ("/dev/video" <> cameraDevice) cfg
+         "darwin" -> Camera "0:0" defaultCameraConfig
+         _ -> error "Unsure how to identify a default camera input"
+
      frame1 <- getFrame
      case frame1 of
        img@(Just (Image w h _)) ->
@@ -86,15 +96,23 @@
 main = do args <- getArgs
           case args of
             [] -> testEncode
+            ["cam", n] -> testCamera n
             [s]
-              | s `elem` ["--help", "-help", "-h"] -> error usage
-              | s == "cam" -> testCamera
+              | s `elem` ["--help", "-help", "-h"] -> putStrLn usage
+              | s == "cam" -> testCamera "0"
             [vidFile] -> testDecode vidFile
             _ -> error usage
   where usage =
-          unlines [ "Usage: demo [videoFile]"
+          unlines [ "Usage: demo [cam [N]|videoFile]"
                   , "  If no argument is given, a test video named "
                   , "  pulse.mov is generated."
+                  , ""
+                  , "  If the word 'cam' (without quotes) is given as an "
+                  , "  argument, an attached webcam is opened and used to "
+                  , "  record a 10s video saved to the file camera.mov. "
+                  , "  On Linux, the camera is found at /dev/videoN where N "
+                  , "  is an argument following the word 'cam'. If no value "
+                  , "  is given for N, a default of 0 is used."
                   , ""
                   , "  If a file name is given, then two frames are "
                   , "  extracted: the first frame, and the 301st."
diff --git a/demo/VPlay.hs b/demo/VPlay.hs
--- a/demo/VPlay.hs
+++ b/demo/VPlay.hs
@@ -266,13 +266,24 @@
       (vsIdx, ctx, _, vs, _) <- prepareVideoCodec inputContext
 
       -- Get frame size.
-      w <- liftIO $ getWidth ctx
-      h <- liftIO $ getHeight ctx
+      textureWidth <- liftIO $ getWidth ctx
+      textureHeight <- liftIO $ getHeight ctx
 
+      -- Compute window size. If the pixels aren't square, stretch the window,
+      -- SDL will automatically scale the texture to fit.
+      par <- liftIO $ guessAspectRatio ctx
+
+      let pixelAspectRatio :: Double
+          pixelAspectRatio = fromIntegral (numerator par) / fromIntegral (denomenator par)
+
+          windowWidth, windowHeight :: CInt
+          windowWidth = round (pixelAspectRatio * fromIntegral textureWidth)
+          windowHeight = textureHeight
+
       -- Create window, renderer and texture.
-      window   <- createWindow w h
+      window   <- createWindow windowWidth windowHeight
       renderer <- createRenderer window
-      texture  <- createTexture renderer w h
+      texture  <- createTexture renderer textureWidth textureHeight
 
       -- Create frame reader.
       let dstFmt = cfgFmtFFmpeg cfg
diff --git a/ffmpeg-light.cabal b/ffmpeg-light.cabal
--- a/ffmpeg-light.cabal
+++ b/ffmpeg-light.cabal
@@ -1,5 +1,5 @@
 name:                ffmpeg-light
-version:             0.12.2.2
+version:             0.13.0
 synopsis:            Minimal bindings to the FFmpeg library.
 
 description:         Stream frames from an encoded video, or stream frames to
@@ -15,7 +15,7 @@
                      > go = do (getFrame, cleanup) <- imageReader "myVideo.mov"
                      >         (fmap ImageRGB8 <$> getFrame) <* cleanup
                      .
-                     Tested with FFmpeg 3.1 - 3.4.2
+                     Tested with FFmpeg 3.1 - 4.3.1
 
 license:             BSD3
 license-file:        LICENSE
@@ -28,7 +28,7 @@
 build-type:          Simple
 extra-source-files:  src/hscMacros.h, src/nameCompat.h, CHANGELOG.md
 cabal-version:       >=1.10
-tested-with:         GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.2
+tested-with:         GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.2, GHC == 8.6.5, GHC == 8.8.4
 
 source-repository head
   type:     git
@@ -67,7 +67,7 @@
                        Codec.FFmpeg.Internal.Debug,
                        Codec.FFmpeg.Internal.Linear
   build-tools:         hsc2hs
-  build-depends:       base >=4.6 && < 4.12,
+  build-depends:       base >=4.6 && < 5,
                        either,
                        exceptions,
                        vector >= 0.10.9 && < 0.13,
diff --git a/src/Codec/FFmpeg/Decode.hs b/src/Codec/FFmpeg/Decode.hs
--- a/src/Codec/FFmpeg/Decode.hs
+++ b/src/Codec/FFmpeg/Decode.hs
@@ -22,7 +22,7 @@
 -- * FFI Declarations
 
 foreign import ccall "avformat_open_input"
-  avformat_open_input :: Ptr AVFormatContext -> CString -> Ptr ()
+  avformat_open_input :: Ptr AVFormatContext -> CString -> Ptr AVInputFormat
                       -> Ptr AVDictionary -> IO CInt
 
 foreign import ccall "avformat_find_stream_info"
@@ -56,6 +56,12 @@
 foreign import ccall "av_dict_set"
   av_dict_set :: Ptr AVDictionary -> CString -> CString -> CInt -> IO CInt
 
+foreign import ccall "av_find_input_format"
+  av_find_input_format :: CString -> IO (Ptr AVInputFormat)
+
+foreign import ccall "av_format_set_video_codec"
+  av_format_set_video_codec :: AVFormatContext -> AVCodec -> IO ()
+
 dictSet :: Ptr AVDictionary -> String -> String -> IO ()
 dictSet d k v = do
   r <- withCString k $ \k' -> withCString v $ \v' ->
@@ -74,9 +80,13 @@
       do avPtr <- mallocAVFormatContext
          setupCamera avPtr cam
          poke ctx avPtr
+         fmt <- case format cfg of
+                  Just "mjpeg" -> withCString "v4l2" av_find_input_format
+                  Just f -> withCString f av_find_input_format
+                  Nothing -> pure nullPtr
          r <- alloca $ \dict -> do
                 setConfig dict cfg
-                avformat_open_input ctx cstr nullPtr dict
+                avformat_open_input ctx cstr fmt dict
          when (r /= 0) $
            stringError r >>= \err ->
              fail ("ffmpeg failed opening file: " ++ err)
@@ -95,6 +105,10 @@
     setupCamera avfc c =
       do setCamera avfc
          setFilename avfc c
+         when (format cfg == Just "mjpeg") $ do
+           mjpeg <- avcodec_find_decoder avCodecIdMjpeg
+           setVideoCodecID avfc avCodecIdMjpeg
+           av_format_set_video_codec avfc mjpeg
 
 openInput :: (MonadIO m, MonadError String m) => InputSource -> m AVFormatContext
 openInput ipt =
diff --git a/src/Codec/FFmpeg/Enums.hsc b/src/Codec/FFmpeg/Enums.hsc
--- a/src/Codec/FFmpeg/Enums.hsc
+++ b/src/Codec/FFmpeg/Enums.hsc
@@ -10,6 +10,7 @@
 #include <libavutil/pixfmt.h>
 #include <libavutil/mathematics.h>
 #include <libswscale/swscale.h>
+#include <libswresample/swresample.h>
 #include "nameCompat.h"
 
 newtype AVMediaType = AVMediaType CInt deriving (Eq, Storable)
@@ -68,18 +69,266 @@
 
 newtype AVCodecID = AVCodecID CInt deriving (Eq, Show, Storable)
 #enum AVCodecID,AVCodecID \
+ , AV_CODEC_ID_NONE\
+ , AV_CODEC_ID_MPEG1VIDEO\
+ , AV_CODEC_ID_MPEG2VIDEO\
+ , AV_CODEC_ID_H261\
+ , AV_CODEC_ID_H263\
+ , AV_CODEC_ID_RV10\
+ , AV_CODEC_ID_RV20\
+ , AV_CODEC_ID_MJPEG\
+ , AV_CODEC_ID_MJPEGB\
+ , AV_CODEC_ID_LJPEG\
+ , AV_CODEC_ID_SP5X\
+ , AV_CODEC_ID_JPEGLS\
+ , AV_CODEC_ID_MPEG4\
+ , AV_CODEC_ID_RAWVIDEO\
+ , AV_CODEC_ID_MSMPEG4V1\
+ , AV_CODEC_ID_MSMPEG4V2\
+ , AV_CODEC_ID_MSMPEG4V3\
+ , AV_CODEC_ID_WMV1\
+ , AV_CODEC_ID_WMV2\
+ , AV_CODEC_ID_H263P\
+ , AV_CODEC_ID_H263I\
+ , AV_CODEC_ID_FLV1\
+ , AV_CODEC_ID_SVQ1\
+ , AV_CODEC_ID_SVQ3\
+ , AV_CODEC_ID_DVVIDEO\
+ , AV_CODEC_ID_HUFFYUV\
+ , AV_CODEC_ID_CYUV\
  , AV_CODEC_ID_H264\
- , AV_CODEC_ID_HEVC\
+ , AV_CODEC_ID_INDEO3\
+ , AV_CODEC_ID_VP3\
  , AV_CODEC_ID_THEORA\
+ , AV_CODEC_ID_ASV1\
+ , AV_CODEC_ID_ASV2\
+ , AV_CODEC_ID_FFV1\
+ , AV_CODEC_ID_4XM\
+ , AV_CODEC_ID_VCR1\
+ , AV_CODEC_ID_CLJR\
+ , AV_CODEC_ID_MDEC\
+ , AV_CODEC_ID_ROQ\
+ , AV_CODEC_ID_INTERPLAY_VIDEO\
+ , AV_CODEC_ID_XAN_WC3\
+ , AV_CODEC_ID_XAN_WC4\
+ , AV_CODEC_ID_RPZA\
+ , AV_CODEC_ID_CINEPAK\
+ , AV_CODEC_ID_WS_VQA\
+ , AV_CODEC_ID_MSRLE\
+ , AV_CODEC_ID_MSVIDEO1\
+ , AV_CODEC_ID_IDCIN\
+ , AV_CODEC_ID_8BPS\
+ , AV_CODEC_ID_SMC\
+ , AV_CODEC_ID_FLIC\
+ , AV_CODEC_ID_TRUEMOTION1\
+ , AV_CODEC_ID_VMDVIDEO\
+ , AV_CODEC_ID_MSZH\
+ , AV_CODEC_ID_ZLIB\
+ , AV_CODEC_ID_QTRLE\
+ , AV_CODEC_ID_TSCC\
+ , AV_CODEC_ID_ULTI\
+ , AV_CODEC_ID_QDRAW\
+ , AV_CODEC_ID_VIXL\
+ , AV_CODEC_ID_QPEG\
+ , AV_CODEC_ID_PNG\
+ , AV_CODEC_ID_PPM\
+ , AV_CODEC_ID_PBM\
+ , AV_CODEC_ID_PGM\
+ , AV_CODEC_ID_PGMYUV\
+ , AV_CODEC_ID_PAM\
+ , AV_CODEC_ID_FFVHUFF\
+ , AV_CODEC_ID_RV30\
+ , AV_CODEC_ID_RV40\
  , AV_CODEC_ID_VC1\
- , AV_CODEC_ID_MPEG4\
- , AV_CODEC_ID_MPEG2VIDEO\
- , AV_CODEC_ID_RAWVIDEO\
+ , AV_CODEC_ID_WMV3\
+ , AV_CODEC_ID_LOCO\
+ , AV_CODEC_ID_WNV1\
+ , AV_CODEC_ID_AASC\
+ , AV_CODEC_ID_INDEO2\
+ , AV_CODEC_ID_FRAPS\
+ , AV_CODEC_ID_TRUEMOTION2\
+ , AV_CODEC_ID_BMP\
+ , AV_CODEC_ID_CSCD\
+ , AV_CODEC_ID_MMVIDEO\
+ , AV_CODEC_ID_ZMBV\
+ , AV_CODEC_ID_AVS\
+ , AV_CODEC_ID_SMACKVIDEO\
+ , AV_CODEC_ID_NUV\
+ , AV_CODEC_ID_KMVC\
+ , AV_CODEC_ID_FLASHSV\
+ , AV_CODEC_ID_CAVS\
+ , AV_CODEC_ID_JPEG2000\
+ , AV_CODEC_ID_VMNC\
+ , AV_CODEC_ID_VP5\
+ , AV_CODEC_ID_VP6\
+ , AV_CODEC_ID_VP6F\
+ , AV_CODEC_ID_TARGA\
+ , AV_CODEC_ID_DSICINVIDEO\
+ , AV_CODEC_ID_TIERTEXSEQVIDEO\
+ , AV_CODEC_ID_TIFF\
  , AV_CODEC_ID_GIF\
- , AV_CODEC_ID_AAC\
+ , AV_CODEC_ID_DXA\
+ , AV_CODEC_ID_DNXHD\
+ , AV_CODEC_ID_THP\
+ , AV_CODEC_ID_SGI\
+ , AV_CODEC_ID_C93\
+ , AV_CODEC_ID_BETHSOFTVID\
+ , AV_CODEC_ID_PTX\
+ , AV_CODEC_ID_TXD\
+ , AV_CODEC_ID_VP6A\
+ , AV_CODEC_ID_AMV\
+ , AV_CODEC_ID_VB\
+ , AV_CODEC_ID_PCX\
+ , AV_CODEC_ID_SUNRAST\
+ , AV_CODEC_ID_INDEO4\
+ , AV_CODEC_ID_INDEO5\
+ , AV_CODEC_ID_MIMIC\
+ , AV_CODEC_ID_RL2\
+ , AV_CODEC_ID_ESCAPE124\
+ , AV_CODEC_ID_DIRAC\
+ , AV_CODEC_ID_BFI\
+ , AV_CODEC_ID_CMV\
+ , AV_CODEC_ID_MOTIONPIXELS\
+ , AV_CODEC_ID_TGV\
+ , AV_CODEC_ID_TGQ\
+ , AV_CODEC_ID_TQI\
+ , AV_CODEC_ID_AURA\
+ , AV_CODEC_ID_AURA2\
+ , AV_CODEC_ID_V210X\
+ , AV_CODEC_ID_TMV\
+ , AV_CODEC_ID_V210\
+ , AV_CODEC_ID_DPX\
+ , AV_CODEC_ID_MAD\
+ , AV_CODEC_ID_FRWU\
+ , AV_CODEC_ID_FLASHSV2\
+ , AV_CODEC_ID_CDGRAPHICS\
+ , AV_CODEC_ID_R210\
+ , AV_CODEC_ID_ANM\
+ , AV_CODEC_ID_BINKVIDEO\
+ , AV_CODEC_ID_IFF_ILBM\
+ , AV_CODEC_ID_KGV1\
+ , AV_CODEC_ID_YOP\
+ , AV_CODEC_ID_VP8\
+ , AV_CODEC_ID_PICTOR\
+ , AV_CODEC_ID_ANSI\
+ , AV_CODEC_ID_A64_MULTI\
+ , AV_CODEC_ID_A64_MULTI5\
+ , AV_CODEC_ID_R10K\
+ , AV_CODEC_ID_MXPEG\
+ , AV_CODEC_ID_LAGARITH\
+ , AV_CODEC_ID_PRORES\
+ , AV_CODEC_ID_JV\
+ , AV_CODEC_ID_DFA\
+ , AV_CODEC_ID_WMV3IMAGE\
+ , AV_CODEC_ID_VC1IMAGE\
+ , AV_CODEC_ID_UTVIDEO\
+ , AV_CODEC_ID_BMV_VIDEO\
+ , AV_CODEC_ID_VBLE\
+ , AV_CODEC_ID_DXTORY\
+ , AV_CODEC_ID_V410\
+ , AV_CODEC_ID_XWD\
+ , AV_CODEC_ID_CDXL\
+ , AV_CODEC_ID_XBM\
+ , AV_CODEC_ID_ZEROCODEC\
+ , AV_CODEC_ID_MSS1\
+ , AV_CODEC_ID_MSA1\
+ , AV_CODEC_ID_TSCC2\
+ , AV_CODEC_ID_MTS2\
+ , AV_CODEC_ID_CLLC\
+ , AV_CODEC_ID_MSS2\
+ , AV_CODEC_ID_VP9\
+ , AV_CODEC_ID_AIC\
+ , AV_CODEC_ID_ESCAPE130\
+ , AV_CODEC_ID_G2M\
+ , AV_CODEC_ID_WEBP\
+ , AV_CODEC_ID_HNM4_VIDEO\
+ , AV_CODEC_ID_HEVC\
+ , AV_CODEC_ID_MP2\
  , AV_CODEC_ID_MP3\
- , AV_CODEC_ID_DTS
+ , AV_CODEC_ID_AAC\
+ , AV_CODEC_ID_AC3\
+ , AV_CODEC_ID_DTS\
+ , AV_CODEC_ID_VORBIS\
+ , AV_CODEC_ID_DVAUDIO\
+ , AV_CODEC_ID_WMAV1\
+ , AV_CODEC_ID_WMAV2\
+ , AV_CODEC_ID_MACE3\
+ , AV_CODEC_ID_MACE6\
+ , AV_CODEC_ID_VMDAUDIO\
+ , AV_CODEC_ID_FLAC\
+ , AV_CODEC_ID_MP3ADU\
+ , AV_CODEC_ID_MP3ON4\
+ , AV_CODEC_ID_SHORTEN\
+ , AV_CODEC_ID_ALAC\
+ , AV_CODEC_ID_WESTWOOD_SND1\
+ , AV_CODEC_ID_GSM\
+ , AV_CODEC_ID_QDM2\
+ , AV_CODEC_ID_COOK\
+ , AV_CODEC_ID_TRUESPEECH\
+ , AV_CODEC_ID_TTA\
+ , AV_CODEC_ID_SMACKAUDIO\
+ , AV_CODEC_ID_QCELP\
+ , AV_CODEC_ID_WAVPACK\
+ , AV_CODEC_ID_DSICINAUDIO\
+ , AV_CODEC_ID_IMC\
+ , AV_CODEC_ID_MUSEPACK7\
+ , AV_CODEC_ID_MLP\
+ , AV_CODEC_ID_GSM_MS\
+ , AV_CODEC_ID_ATRAC3\
+ , AV_CODEC_ID_APE\
+ , AV_CODEC_ID_NELLYMOSER\
+ , AV_CODEC_ID_MUSEPACK8\
+ , AV_CODEC_ID_SPEEX\
+ , AV_CODEC_ID_WMAVOICE\
+ , AV_CODEC_ID_WMAPRO\
+ , AV_CODEC_ID_WMALOSSLESS\
+ , AV_CODEC_ID_ATRAC3P\
+ , AV_CODEC_ID_EAC3\
+ , AV_CODEC_ID_SIPR\
+ , AV_CODEC_ID_MP1\
+ , AV_CODEC_ID_TWINVQ\
+ , AV_CODEC_ID_TRUEHD\
+ , AV_CODEC_ID_MP4ALS\
+ , AV_CODEC_ID_ATRAC1\
+ , AV_CODEC_ID_BINKAUDIO_RDFT\
+ , AV_CODEC_ID_BINKAUDIO_DCT\
+ , AV_CODEC_ID_AAC_LATM\
+ , AV_CODEC_ID_QDMC\
+ , AV_CODEC_ID_CELT\
+ , AV_CODEC_ID_G723_1\
+ , AV_CODEC_ID_G729\
+ , AV_CODEC_ID_8SVX_EXP\
+ , AV_CODEC_ID_8SVX_FIB\
+ , AV_CODEC_ID_BMV_AUDIO\
+ , AV_CODEC_ID_RALF\
+ , AV_CODEC_ID_IAC\
+ , AV_CODEC_ID_ILBC\
+ , AV_CODEC_ID_OPUS\
+ , AV_CODEC_ID_COMFORT_NOISE\
+ , AV_CODEC_ID_TAK\
+ , AV_CODEC_ID_METASOUND\
+ , AV_CODEC_ID_PAF_AUDIO\
+ , AV_CODEC_ID_ON2AVC\
+ , AV_CODEC_ID_DSS_SP\
+ , AV_CODEC_ID_FFWAVESYNTH\
+ , AV_CODEC_ID_SONIC\
+ , AV_CODEC_ID_SONIC_LS\
+ , AV_CODEC_ID_EVRC\
+ , AV_CODEC_ID_SMV\
+ , AV_CODEC_ID_DSD_LSBF\
+ , AV_CODEC_ID_DSD_MSBF\
+ , AV_CODEC_ID_DSD_LSBF_PLANAR\
+ , AV_CODEC_ID_DSD_MSBF_PLANAR\
+ , AV_CODEC_ID_4GV
 
+
+ -- \
+ -- , AV_CODEC_ID_INTERPLAY_ACM\
+ -- , AV_CODEC_ID_XMA1\
+ -- , AV_CODEC_ID_XMA2\
+ -- , AV_CODEC_ID_DST
+
+
 newtype SwsAlgorithm = SwsAlgorithm CUInt deriving (Eq, Show, Storable)
 #enum SwsAlgorithm,SwsAlgorithm \
  , SWS_FAST_BILINEAR\
@@ -266,3 +515,21 @@
  , AV_LOG_DEBUG\
  , AV_LOG_TRACE\
  , AV_LOG_MAX_OFFSET
+
+newtype AVSampleFormat = AVSampleFormat CInt deriving (Eq, Bits, Storable)
+#enum AVSampleFormat, AVSampleFormat \
+ , AV_SAMPLE_FMT_NONE\
+ , AV_SAMPLE_FMT_U8\
+ , AV_SAMPLE_FMT_S16\
+ , AV_SAMPLE_FMT_S32\
+ , AV_SAMPLE_FMT_FLT\
+ , AV_SAMPLE_FMT_DBL\
+ , AV_SAMPLE_FMT_U8P\
+ , AV_SAMPLE_FMT_S16P\
+ , AV_SAMPLE_FMT_S32P\
+ , AV_SAMPLE_FMT_FLTP\
+ , AV_SAMPLE_FMT_DBLP\
+ , AV_SAMPLE_FMT_NB
+
+getSampleFormatInt :: AVSampleFormat -> CInt
+getSampleFormatInt (AVSampleFormat i) = i
diff --git a/src/Codec/FFmpeg/Types.hsc b/src/Codec/FFmpeg/Types.hsc
--- a/src/Codec/FFmpeg/Types.hsc
+++ b/src/Codec/FFmpeg/Types.hsc
@@ -3,6 +3,7 @@
 module Codec.FFmpeg.Types where
 import Codec.FFmpeg.Enums
 import Control.Monad (zipWithM_,when)
+import Data.Maybe (fromMaybe)
 import Foreign.C.String (CString)
 import Foreign.C.Types
 import Foreign.Ptr
@@ -32,6 +33,7 @@
 #hasField AVFormatContext, OutputFormat, oformat
 #hasField AVFormatContext, InputFormat, iformat
 #hasField AVFormatContext, IOContext, pb
+#hasField AVFormatContext, VideoCodecID, video_codec_id
 
 setFilename :: AVFormatContext -> String -> IO ()
 setFilename ctx fn =
@@ -77,6 +79,8 @@
 #mkField CodecFlags, CodecFlag
 #mkField CodecID, AVCodecID
 #mkField PrivData, (Ptr ())
+#mkField TicksPerFrame, CInt
+#mkField RawAspectRatio, AVRational
 
 #hasField AVCodecContext, Width, width
 #hasField AVCodecContext, Height, height
@@ -86,7 +90,26 @@
 #hasField AVCodecContext, CodecFlags, flags
 #hasField AVCodecContext, CodecID, codec_id
 #hasField AVCodecContext, PrivData, priv_data
+#hasField AVCodecContext, TicksPerFrame, ticks_per_frame
+#hasField AVCodecContext, RawAspectRatio, sample_aspect_ratio
 
+getFps :: (HasTimeBase a, HasTicksPerFrame a) => a -> IO CDouble
+getFps x = do
+  timeBase <- getTimeBase x
+  ticksPerFrame <- getTicksPerFrame x
+  pure (1.0 / av_q2d timeBase / fromIntegral ticksPerFrame)
+
+getAspectRatio :: HasRawAspectRatio a => a -> IO (Maybe AVRational)
+getAspectRatio = fmap nonZeroAVRational . getRawAspectRatio
+
+-- | When unspecified, the most likely pixel shape is a square
+guessAspectRatio :: HasRawAspectRatio a => a -> IO AVRational
+guessAspectRatio = fmap (fromMaybe (AVRational 1 1)) . getAspectRatio
+
+setAspectRatio :: HasRawAspectRatio a => a -> Maybe AVRational -> IO ()
+setAspectRatio x Nothing      = setRawAspectRatio x (AVRational 0 1)
+setAspectRatio x (Just ratio) = setRawAspectRatio x ratio
+
 newtype AVStream = AVStream (Ptr ()) deriving (Storable, HasPtr)
 
 #mkField Id, CInt
@@ -182,6 +205,11 @@
 data AVRational = AVRational { numerator   :: CInt
                              , denomenator :: CInt } deriving Show
 
+-- | FFmpeg often uses 0 to mean "unknown"; use 'Nothing' instead.
+nonZeroAVRational :: AVRational -> Maybe AVRational
+nonZeroAVRational (AVRational 0 _) = Nothing
+nonZeroAVRational ratio            = Just ratio
+
 instance Storable AVRational where
   sizeOf _ = #size AVRational
   alignment _ = #size AVRational
@@ -228,8 +256,10 @@
 data CameraConfig =
   CameraConfig { framerate  :: Maybe Int
                , resolution :: Maybe (Int,Int)
+               , format :: Maybe String
+               -- ^ Short name for a video format (e.g. @"v4l2"@)
                }
           deriving (Eq,Ord,Show,Read)
 
 defaultCameraConfig :: CameraConfig
-defaultCameraConfig = CameraConfig (Just 30) Nothing
+defaultCameraConfig = CameraConfig (Just 30) Nothing Nothing
