├── .gitignore ├── .gitmodules ├── ffmpeg_test.lua └── init.lua /.gitignore: -------------------------------------------------------------------------------- 1 | ffmpeg_defs.h 2 | samples.raw 3 | *.dll 4 | include/ 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ffi_util"] 2 | path = ffi_util 3 | url = git://github.com/daurnimator/lua-ffi-util.git 4 | -------------------------------------------------------------------------------- /ffmpeg_test.lua: -------------------------------------------------------------------------------- 1 | -- A test for ffi based ffmpeg bindings 2 | -- Based on https://github.com/mkottman/ffi_fun/blob/master/ffmpeg_audio.lua 3 | 4 | local FILENAME = assert ( arg[1] , "No input file" ) 5 | 6 | package.path = "./?/init.lua;" .. package.path 7 | package.loaded [ "ffmpeg" ] = dofile ( "init.lua" ) 8 | local ffmpeg = require"ffmpeg" 9 | local avutil = ffmpeg.avutil 10 | local avAssert = ffmpeg.avAssert 11 | local avcodec = ffmpeg.avcodec 12 | local avformat = ffmpeg.avformat 13 | 14 | local ffi = require"ffi" 15 | 16 | local SECTION = print 17 | 18 | SECTION "Opening file" 19 | 20 | local formatctx = ffmpeg.openfile ( FILENAME ) 21 | local audioctx = assert ( ffmpeg.findaudiostreams ( formatctx ) [ 1 ] , "No Audio Stream Found" ) 22 | 23 | print ( "Bitrate:", tonumber(audioctx.bit_rate)) 24 | print ( "Channels:", tonumber(audioctx.channels)) 25 | print ( "Sample rate:", tonumber(audioctx.sample_rate)) 26 | print ( "Sample type:", ({[0]="u8", "s16", "s32", "flt", "dbl"}) [ audioctx.sample_fmt ] ) 27 | 28 | 29 | SECTION "Decoding" 30 | 31 | local all_samples = {} 32 | local total_samples = 0 33 | 34 | local buffsize = 192000--ffmpeg.AVCODEC_MAX_AUDIO_FRAME_SIZE 35 | local frame_size = ffi.new ( "int[1]" ) 36 | 37 | local output_type = ffmpeg.format_to_type [ audioctx.sample_fmt ] 38 | local output_buff = ffi.new ( output_type .. "[?]" , buffsize ) 39 | 40 | for packet in ffmpeg.read_frames ( formatctx ) do 41 | frame_size[0] = buffsize 42 | avAssert ( avcodec.avcodec_decode_audio3 ( audioctx , output_buff , frame_size , packet ) ) 43 | local size = tonumber ( frame_size[0] ) / ffi.sizeof ( output_type ) -- frame_size is in bytes 44 | 45 | local frame = ffi.new ( "int16_t[?]" , size ) 46 | ffi.copy ( frame , output_buff , size*2 ) 47 | all_samples[#all_samples + 1] = frame 48 | total_samples = total_samples + size 49 | end 50 | 51 | 52 | SECTION "Merging samples" 53 | 54 | local samples = ffi.new ( "int16_t[?]" , total_samples ) 55 | local offset = 0 56 | for _ , s in ipairs ( all_samples ) do 57 | local size = ffi.sizeof ( s ) 58 | ffi.copy ( samples + offset , s , size ) 59 | offset = offset + size/2 60 | end 61 | 62 | local outfilename = "samples.raw" 63 | SECTION "Generating: " .. outfilename 64 | 65 | local out = assert ( io.open ( outfilename , 'wb' ) ) 66 | local size = ffi.sizeof ( samples ) 67 | out:write ( ffi.string ( samples , size ) ) 68 | out:close ( ) 69 | -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | -- FFI binding to FFmpeg 2 | 3 | local rel_dir = assert ( debug.getinfo ( 1 , "S" ).source:match ( [=[^@(.-[/\]?)[^/\]*$]=] ) , "Current directory unknown" ) .. "./" 4 | 5 | local assert , error = assert , error 6 | local setmetatable = setmetatable 7 | local tonumber , tostring = tonumber , tostring 8 | local tblinsert = table.insert 9 | 10 | local ffi = require"ffi" 11 | local ffi_util = require"ffi_util" 12 | local ffi_add_include_dir = ffi_util.ffi_add_include_dir 13 | local ffi_defs = ffi_util.ffi_defs 14 | 15 | ffi_add_include_dir ( rel_dir .. "include" ) 16 | 17 | ffi_defs ( [[ffmpeg_funcs.h]] , [[ffmpeg_defs.h]] , { 18 | [[libavutil/avstring.h]] ; 19 | [[libavcodec/avcodec.h]] ; 20 | [[libavformat/avformat.h]] ; 21 | } ) 22 | 23 | local avutil , avcodec , avformat 24 | assert ( jit , "jit table unavailable" ) 25 | if jit.os == "Windows" then -- Windows binaries from http://ffmpeg.zeranoe.com/builds/ 26 | avutil = ffi.load ( rel_dir .. [[avutil-51]] ) 27 | avcodec = ffi.load ( rel_dir .. [[avcodec-53]] ) 28 | avformat = ffi.load ( rel_dir .. [[avformat-53]] ) 29 | elseif jit.os == "Linux" or jit.os == "OSX" or jit.os == "POSIX" or jit.os == "BSD" then 30 | avutil = ffi.load ( [[libavutil]] ) 31 | avcodec = ffi.load ( [[libavcodec]] ) 32 | avformat = ffi.load ( [[libavformat]] ) 33 | else 34 | error ( "Unknown platform" ) 35 | end 36 | 37 | local ffmpeg = { 38 | avutil = avutil ; 39 | avcodec = avcodec ; 40 | avformat = avformat ; 41 | 42 | AV_TIME_BASE = 1000000 ; 43 | } 44 | 45 | ffmpeg.format_to_type = { 46 | --[avutil.AV_SAMPLE_FMT_NONE] = nil ; 47 | [avutil.AV_SAMPLE_FMT_U8] = "uint8_t" ; 48 | [avutil.AV_SAMPLE_FMT_S16] = "int16_t" ; 49 | [avutil.AV_SAMPLE_FMT_S32] = "int32_t" ; 50 | [avutil.AV_SAMPLE_FMT_FLT] = "float" ; 51 | [avutil.AV_SAMPLE_FMT_DBL] = "double" ; 52 | --[avutil.AV_SAMPLE_FMT_NB] = "" ; 53 | } 54 | 55 | 56 | local function avAssert ( err ) 57 | if err < 0 then 58 | local errbuff = ffi.new ( "uint8_t[256]" ) 59 | local ret = avutil.av_strerror ( err , errbuff , 256 ) 60 | if ret ~= -1 then 61 | error ( ffi.string ( errbuff ) , 2 ) 62 | else 63 | error ( "Unknown AV error: " .. tostring ( ret ) , 2 ) 64 | end 65 | end 66 | return err 67 | end 68 | ffmpeg.avAssert = avAssert 69 | 70 | function ffmpeg.openfile ( file ) 71 | assert ( file , "No input file" ) 72 | local formatContext_p = ffi.new ( "AVFormatContext*[1]" ) 73 | avAssert ( avformat.avformat_open_input ( formatContext_p , file , nil , nil ) ) 74 | local formatContext = ffi.gc ( formatContext_p[0] , avformat.av_close_input_stream ) 75 | return formatContext 76 | end 77 | 78 | function ffmpeg.findaudiostreams ( formatContext ) 79 | avAssert( avformat.av_find_stream_info ( formatContext ) ) 80 | 81 | local audiostreams = { } 82 | local nStreams = tonumber ( formatContext.nb_streams ) 83 | for i=0 , nStreams-1 do 84 | local ctx = formatContext.streams [ i ].codec 85 | if ctx.codec_type == avutil.AVMEDIA_TYPE_AUDIO then 86 | local codec = assert ( avcodec.avcodec_find_decoder ( ctx.codec_id ) , "Unsupported codec" ) 87 | avAssert ( avcodec.avcodec_open ( ctx , codec ) ) 88 | tblinsert ( audiostreams , ctx ) 89 | end 90 | end 91 | 92 | return audiostreams 93 | end 94 | 95 | local packet = ffi.new ( "AVPacket" ) 96 | function ffmpeg.read_frames ( formatctx ) 97 | return function ( formatctx , packet ) 98 | if tonumber( avformat.url_feof ( formatctx.pb ) ) == 0 then 99 | avAssert ( avformat.av_read_frame ( formatctx , packet ) ) 100 | return packet 101 | else 102 | return nil 103 | end 104 | end , formatctx , packet 105 | end 106 | 107 | avcodec.avcodec_init() 108 | avcodec.avcodec_register_all() 109 | avformat.av_register_all() 110 | 111 | return ffmpeg 112 | --------------------------------------------------------------------------------