module Codec.FFmpeg.Encode where
import Codec.FFmpeg.Common
import Codec.FFmpeg.Enums
import Codec.FFmpeg.Scaler
import Codec.FFmpeg.Types
import Codec.Picture
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.Utils
import Foreign.Ptr
import Foreign.Storable
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 ()
data EncodingParams = EncodingParams { epWidth :: CInt
, epHeight :: CInt
, epFps :: Int
, epCodec :: AVCodecID
, epPreset :: String }
defaultParams :: CInt -> CInt -> EncodingParams
defaultParams w h = EncodingParams w h 30 avCodecIdH264 "medium"
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
outputFlags <- getOutputFormat oc >>= getFormatFlags
when (outputFlags .&. avfmtGlobalheader /= clearBit (bit 0) 0) $
getCodecFlags ctx >>= setCodecFlags ctx . (.|. codecFlagGlobalHeader)
rOpen <- open_codec ctx cod nullPtr
when (rOpen < 0) (throwError $ strMsg "Couldn't open codec")
return (st, ctx)
initTempFrame :: EncodingParams -> AVPixelFormat -> IO AVFrame
initTempFrame ep fmt = do
yuv <- frame_alloc_check
setPixelFormat yuv fmt
setWidth yuv (epWidth ep)
setHeight yuv (epHeight ep)
setPts yuv 0
frame_get_buffer_check yuv 32
return yuv
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
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")
avio_close_check :: AVFormatContext -> IO ()
avio_close_check oc = do r <- getIOContext oc >>= avio_close
when (r /= 0) (errMsg "Error closing IO")
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
write_header_check :: AVFormatContext -> IO ()
write_header_check oc = do r <- avformat_write_header oc nullPtr
when (r < 0) (errMsg "Error writing header")
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_trailer_check :: AVFormatContext -> IO ()
write_trailer_check oc = do r <- av_write_trailer oc
when (r /= 0) (errMsg "Error writing trailer")
frameWriter :: EncodingParams -> FilePath -> IO (Maybe (Vector CUChar) -> IO ())
frameWriter ep fname = do
oc <- allocOutputContext fname
(st,ctx) <- initStream ep oc
dstFmt <- getPixelFormat ctx
dstFrame <- initTempFrame ep dstFmt
sws <- swsInit (ImageInfo (epWidth ep) (epHeight ep) avPixFmtRgb24)
(ImageInfo (epWidth ep) (epHeight ep) dstFmt)
swsBilinear
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
mkImage :: Vector CUChar -> Image PixelRGB8
mkImage = let [w,h] = map fromIntegral [epWidth ep, epHeight ep]
in Image w h . V.unsafeCast
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 dstFrame av_frame_free
avio_close_check oc
avformat_free_context oc
go (Just pixels) = do
resetPacket
_ <- swsScale sws (mkImage pixels) dstFrame
getPts dstFrame >>= setPts dstFrame . (+ frameTime)
encode_video_check ctx pkt (Just dstFrame) >>= flip when writePacket
return go