3 months ago

So in this tutorial I will show you how to record audio output to a .wav file. This doesn't record phone calls on some iOS versions unfortunately, but you can record pretty much everything else.

This isn't a fully Lua tutorial, there is some Objective-C but I have pre-written all the Objective-C code so you only need to copy paste it. See the Objective-C compiling tutorial for how to compile it.

Initial setup

Heres the Objective-C code:

#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>

float *shit_audio = NULL;
size_t shit_siz = 0;
static const size_t shit_cap = 44100 * 2 * 60 * 10; // 10 min max

void shit_init()
    shit_audio = malloc(sizeof(float) * shit_cap);

void shit_record(float **samples, int num_frames, int num_channels, float sampleRate)
    if(shit_audio == NULL) {

    for(int i = 0; i < num_frames; i++) {
        if(shit_siz >= shit_cap) {
        for(int c = 0; c < 2; c++) {
            int x = c;
            if(num_channels == 1) {
                x = 0;
            shit_audio[shit_siz + c] = samples[x][i];
        shit_siz += 2;

void shit_write(const char *path)
    dispatch_async(dispatch_queue_create("eqe.save.dat.shit", NULL), ^{
        NSURL *fileURL = [NSURL fileURLWithPath:[NSString stringWithUTF8String:path]];

        // prepare the format
        AudioStreamBasicDescription outputFormat = {0};
        outputFormat.mSampleRate = 44100;
        outputFormat.mFormatID = kAudioFormatLinearPCM;
        outputFormat.mBitsPerChannel = 32;
        outputFormat.mFormatFlags = kAudioFormatFlagIsFloat | kAudioFormatFlagIsPacked;
        outputFormat.mFramesPerPacket = 1;
        outputFormat.mChannelsPerFrame = 2;
        outputFormat.mBytesPerFrame = 8;
        outputFormat.mBytesPerPacket = 8;

        // set up the file
        AudioFileID audioFile;
        OSStatus audioErr = noErr;
        AudioFileCreateWithURL((__bridge CFURLRef)fileURL,

        // start writing samples
        UInt32 bytesToWrite = 4;
        for(int i = 0; i < shit_siz; i++) {
                i * 4,
        shit_siz = 0;

Save that as record.m somewhere on your phone, and then compile it using these commands (remember to install the iOS toolchain and SDK first, see this tutorial if you haven't done that):

clang record.m -isysroot PATH_TO_IPHONE_SDK -framework Foundation -framework AudioToolbox -dynamiclib -arch arm64 -arch armv7 -o record.dylib
ldid -S record.dylib

Replace PATH_TO_IPHONE_SDK with wherever your iPhone SDK is. It's probably /var/iPhoneOS9.3.sdk if you followed the Objective-C tutorial l linked above.

After running those 2 commands, you will now have a file record.dylib in the same folder as record.m. Keep note of the full path.

Then make a .lua file in /var/tweak/com.r333d.eqe/lua/autorun/raw/, the name can be anything as long as its .lua at the end. And put this code in:

local lib_path = '/var/root/tmp/record.dylib'
local output_path = '/var/mobile/recorded_audio.wav'

local ffi = require 'ffi'
local C = ffi.C
float *shit_audio;
size_t shit_siz;

void shit_init();
void shit_record(float **samples, int num_frames, int num_channels, float sampleRate);
void shit_write(const char *path);
local lib = ffi.load(lib_path)

function start_recording()
    if lib.shit_audio == ffi.NULL then
    raw.c = lib.shit_record

function stop_recording(path)
    local seconds = tonumber(lib.shit_siz / 44100 / 2)

    raw.c = nil
    lib.shit_write(path or output_path)

    return 'it will take about '..(seconds/2)..' seconds to save.'

You should edit the first two lines on the above script, to specify where record.dylib is and also where you want the .wav file to be saved.

Then, run killall mediaserverd.

At this point your phone is setup to record audio.

Actually recording the audio

When you want to start recording run eqe raw 'start_recording()' and when you want to stop run eqe raw 'stop_recording()'. Keep in mind when you run stop_recording, it will take a while to save. For a rough estimate, if it was around 2 minutes to record then it'll take 1 minute to save. If you want to cancel, then run killall mediaserverd.


As you can tell this code can be improved by quite a bit. If you wanted to get rid of that delay at the end you would need to incorporate a ring buffer, but that will make the code a lot more complicated. This way it stays relatively simple.

1 month ago