└── ffmpeg_audio.lua /ffmpeg_audio.lua: -------------------------------------------------------------------------------- 1 | -- How to decode an audio file with Lua, using LuaJIT FFI and ffmpeg... 2 | -- Michal Kottman, 2011 3 | 4 | local FILENAME = arg[1] or 'song.mp3' 5 | local SECTION = print 6 | 7 | SECTION "Initializing the FFI library" 8 | 9 | local ffi = require 'ffi' 10 | local C = ffi.C 11 | 12 | --[[ 13 | To recreate ffmpeg.h, create a file tmp.h with the following content 14 | (or more, or less, depending on what you want): 15 | 16 | #include "config.h" 17 | #include "libavutil/avstring.h" 18 | #include "libavutil/pixdesc.h" 19 | #include "libavformat/avformat.h" 20 | #include "libavdevinputContexte/avdevinputContexte.h" 21 | #include "libswscale/swscale.h" 22 | #include "libavcodec/audioconvert.h" 23 | #include "libavcodec/colorspace.h" 24 | #include "libavcodec/opt.h" 25 | #include "libavcodec/avfft.h" 26 | #include "libavfilter/avfilter.h" 27 | #include "libavfilter/avfiltergraph.h" 28 | #include "libavfilter/graphparser.h" 29 | 30 | Then run gcc -E -I $PATH_TO_FFMPEG_SRC tmp.h > ffmpeg.h 31 | ]] 32 | 33 | local avcodec = ffi.load('avcodec-52') 34 | local avformat = ffi.load('avformat-52') 35 | local avutil = ffi.load('avutil-50') 36 | local header = assert(io.open('ffmpeg.h')):read('*a') 37 | ffi.cdef(header) 38 | 39 | function avAssert(err) 40 | if err < 0 then 41 | local errbuf = ffi.new("uint8_t[256]") 42 | local ret = avutil.av_strerror(err, errbuf, 256) 43 | if ret ~= -1 then 44 | error(ffi.string(errbuf), 2) 45 | else 46 | error('Unknown AV error: '..tostring(ret), 2) 47 | end 48 | end 49 | return err 50 | end 51 | 52 | SECTION "Initializing the avcodec and avformat libraries" 53 | 54 | avcodec.avcodec_init() 55 | avcodec.avcodec_register_all() 56 | avformat.av_register_all() 57 | 58 | SECTION "Opening file" 59 | 60 | local pinputContext = ffi.new("AVFormatContext*[1]") 61 | avAssert(avformat.av_open_input_file(pinputContext, FILENAME, nil, 0, nil)) 62 | local inputContext = pinputContext[0] 63 | 64 | avAssert(avformat.av_find_stream_info(inputContext)) 65 | 66 | SECTION "Finding audio stream" 67 | 68 | local audioCtx 69 | local nStreams = tonumber(inputContext.nb_streams) 70 | for i=1,nStreams do 71 | local stream = inputContext.streams[i-1] 72 | local ctx = stream.codec 73 | if ctx.codec_type == C.AVMEDIA_TYPE_AUDIO then 74 | local codec = avcodec.avcodec_find_decoder(ctx.codec_id) 75 | avAssert(avcodec.avcodec_open(ctx, codec)) 76 | audioCtx = ctx 77 | end 78 | end 79 | if not audioCtx then error('Unable to find audio stream') end 80 | 81 | print("Bitrate:", tonumber(audioCtx.bit_rate)) 82 | print("Channels:", tonumber(audioCtx.channels)) 83 | print("Sample rate:", tonumber(audioCtx.sample_rate)) 84 | print("Sample type:", ({[0]="u8", "s16", "s32", "flt", "dbl"})[audioCtx.sample_fmt]) 85 | 86 | SECTION "Decoding" 87 | 88 | local AVCODEC_MAX_AUDIO_FRAME_SIZE = 192000 89 | 90 | local packet = ffi.new("AVPacket") 91 | local temp_frame = ffi.new("int16_t[?]", AVCODEC_MAX_AUDIO_FRAME_SIZE) 92 | local frame_size = ffi.new("int[1]") 93 | 94 | local all_samples = {} 95 | local total_samples = 0 96 | 97 | while tonumber(avformat.url_feof(inputContext.pb)) == 0 do 98 | local ret = avAssert(avformat.av_read_frame(inputContext, packet)) 99 | 100 | frame_size[0] = AVCODEC_MAX_AUDIO_FRAME_SIZE 101 | local n = avcodec.avcodec_decode_audio3(audioCtx, temp_frame, frame_size, packet) 102 | if n == -1 then break 103 | elseif n < 0 then avAssert(n) end 104 | 105 | local size = tonumber(frame_size[0])/2 -- frame_size is in bytes 106 | local frame = ffi.new("int16_t[?]", size) 107 | ffi.copy(frame, temp_frame, size*2) 108 | all_samples[#all_samples + 1] = frame 109 | total_samples = total_samples + size 110 | end 111 | 112 | SECTION "Merging samples" 113 | 114 | local samples = ffi.new("int16_t[?]", total_samples) 115 | local offset = 0 116 | for _,s in ipairs(all_samples) do 117 | local size = ffi.sizeof(s) 118 | ffi.copy(samples + offset, s, size) 119 | offset = offset + size/2 120 | end 121 | 122 | SECTION "Processing" 123 | 124 | -- The `samples` array is now ready for some processing! :) 125 | 126 | -- ... like writing it raw to a file 127 | 128 | local out = assert(io.open('samples.raw', 'wb')) 129 | local size = ffi.sizeof(samples) 130 | out:write(ffi.string(samples, size)) 131 | out:close() 132 | 133 | -- Now you can open it in any audio processing program to see that it works. 134 | -- In Audacity: Project -> Import Raw Data (and fill out according to info) 135 | --------------------------------------------------------------------------------