3

I'm building an app that, in part, needs to resample any input PCM audio file that isn't 44100Hz to 44.1 (or at least make a best effort to do so).

To handle the resampling I'm using soxr. soxr has no dependencies and is lightweight, which is ideal in this case, but it offers no native file I/O. I have very limited experience with IO streams in C, so I'm hitting a wall. The app is being designed modularly, so I need the resample process to create an output file that can then be passed on to other processors, rather than simply dealing with the output stream directly.

In order to create that output file, I'm trying to take the data generated by the soxr resampling process, and pass it to libsndfile, which should be able to write the audio out to a file.

Below is an extremely verbose explanation of where I'm at, though I'm at a loss for why it's crashing. I suspect it has something to do with how buffers are being allocated and used. (Note: The input file is being read with sndfile prior to this code)

(Here's a single gist of the entire thing)

Basic resampler options

// Use "high quality" resampling
unsigned int q_recipe = SOXR_HQ;

// No 
unsigned long q_flags = 0;

// Create the q_spec
soxr_quality_spec_t q_spec = soxr_quality_spec(q_recipe, q_flags);

Map the sndfile format to a soxr format

soxr_datatype_t itype;

// Get the SFINFO format
int iformat = self.inputFileInfo.format;

// Set the soxr itype to the corresponding format
if ((iformat & SF_FORMAT_FLOAT) == SF_FORMAT_FLOAT) {
  itype = SOXR_FLOAT32_S;
} else if ((iformat & SF_FORMAT_DOUBLE) == SF_FORMAT_DOUBLE) {
  itype = SOXR_FLOAT64_S;
} else if ((iformat & SF_FORMAT_PCM_32) == SF_FORMAT_PCM_32) {
  itype = SOXR_INT32_S;
} else {
  itype = SOXR_INT16_S;
}

Setup soxr IO spec

// Always want the output to match the input
soxr_datatype_t otype = itype;

soxr_io_spec_t io_spec = soxr_io_spec(itype, otype);

Threading

// A single thread is fine
soxr_runtime_spec_t runtime_spec = soxr_runtime_spec(1);

Construct the resampler

soxr_error_t error;

// Input rate can be read from the SFINFO    
double const irate = self.inputFileInfo.samplerate;

// Output rate is defined elsewhere, but this generally = 44100
double const orate = self.task.resampler.immutableConfiguration.targetSampleRate;

// Channel count also comes from SFINFO
unsigned chans = self.inputFileInfo.channels;

// Put it all together
soxr_t soxr = soxr_create(irate, orate, chans, &error, &io_spec, &q_spec, &runtime_spec);

Read, resample & write

I'm not really confident in any of the following code, but I've triple checked the math and everything seems to meet the expectations of the libraries' APIs.

// Frames in sndfile are called Samples in soxr

// One frame is 1 item per channel
// ie frame_items = 1 item * channels
size_t const iframeitems = (1 * chans);

// item size is the data type size of the input type
//
size_t iitemsize;

if ((iformat & SF_FORMAT_FLOAT) == SF_FORMAT_FLOAT) {
  iitemsize = sizeof(Float32);
} else if ((iformat & SF_FORMAT_DOUBLE) == SF_FORMAT_DOUBLE) {
  iitemsize = sizeof(Float64);
} else if ((iformat & SF_FORMAT_PCM_32) == SF_FORMAT_PCM_32) {
  iitemsize = sizeof(int32_t);
} else {
  iitemsize = sizeof(int16_t);
}

// frame size is item size * items per frame (channels)
// eg for 2 channel 16 bit, frame size = 2 * 2
size_t const iframesize = (iframeitems * iitemsize);

// Number of frames to read (arbitrary)
sf_count_t const ireqframes = 1024;

// Size of the buffer is number of frames * size per frame
size_t const ibufsize = iframesize * ireqframes;

void *ibuf = malloc(ibufsize);

// Output
//////////////////////////////

// These match the input
size_t const oframeitems = iframeitems;
size_t const oitemsize = iitemsize;

// frame size is item size * items per frame (channels)
size_t const oframesize = (oframeitems * oitemsize);

// Number of frames expected after resampling
// eg
// orate = 44100
// irate = 48000
// ireqframe = 1024
// expect fewer frames (downsample)
// (44100 / 4800) * 1024 = 940.8
// Add 0.5 to deal with rounding?
sf_count_t const oexpframes = (ireqframes * (orate / irate)) + 0.5;

// Size of the buffer is number of frames * size per frame
size_t const obufsize = oframesize * oexpframes;

void *obuf = malloc(obufsize);

// Go
//////////////////////////////

size_t total_resample_output_frame_count = 0;
size_t need_input = 1;
sf_count_t num_frames_written = 0;

do {

  sf_count_t num_frames_read = 0;
  size_t actual_resample_output_samples = 0;

  // Read the input file based on its type
  // num_frames_read should be 1024
  if (otype == SOXR_INT16_S || otype == SOXR_INT32_S) {
    num_frames_read = sf_readf_int(self.inputFile, ibuf, ireqframes);
  } else if (otype == SOXR_FLOAT32_S) {
    num_frames_read = sf_readf_float(self.inputFile, ibuf, ireqframes);
  } else {
    num_frames_read = sf_readf_double(self.inputFile, ibuf, ireqframes);
  }

  // If there were no frames left to read we're done
  if (num_frames_read == 0) {
    // passing NULL input buffer to soxr_process indicates End-of-input
    ibuf = NULL;
    need_input = 0;
  }

  // Run the resampling on frames read from the input file
  error = soxr_process(soxr, ibuf, num_frames_read, NULL, obuf, oexpframes, &actual_resample_output_samples);

  total_resample_output_frame_count += actual_resample_output_samples;

  // Write the resulting data to output file
  // num_frames_written should = actual_resample_output_samples
  if (otype == SOXR_INT16_S || otype == SOXR_INT32_S) {
    num_frames_written = sf_writef_int(self.outputFile, obuf, actual_resample_output_samples);
  } else if (otype == SOXR_FLOAT32_S) {
    num_frames_written = sf_writef_float(self.outputFile, obuf, actual_resample_output_samples);
  } else {
    num_frames_written = sf_writef_double(self.outputFile, obuf, actual_resample_output_samples);
  }

} while (!error && need_input);

soxr_delete(soxr);
free(obuf), free(ibuf);

This gives and EXC_BAD_ACCESS on soxr_process. I have no idea what else to try at this point.

Farski
  • 1,670
  • 3
  • 15
  • 30
  • Is the error on the first call to `sox_process`, or later on? For that matter, do you ever get some frames read from `sf_readf_*`? The examples I'm seeing seem to be using buffer sizes rather than frame counts for the size inputs to `soxr_process`, but that shouldn't be causing catastrophic failure if you're doing it wrong, just skipped audio. – hcs Nov 08 '14 at 20:58
  • The error does happen the first time `soxc_process` is called. On the files I've tested this with, `num_frames_read = 1024` after `sf_readf_`; I'm not really sure how to check if the bits in `ibuf` are actual frames, but I suspect if the return count is the expected value it's reading correctly. I'll review the examples for soxr, but the docs say "Input buf. length (samples per channel).", which makes me think it's expecting frames. – Farski Nov 09 '14 at 22:10
  • Have you tried to debug with tools: http://stackoverflow.com/questions/19740200/how-to-debug-exc-bad-access-bug ? – pmod Nov 09 '14 at 22:27
  • @pmod NSZombies won't help much here since this isn't obj-c. Is there something else in that thread that I'm missing that may help me? – Farski Nov 09 '14 at 22:33
  • Your example on gist, isn't it obj-c? – pmod Nov 09 '14 at 22:37
  • You didn't show your sf_open_... part - that might be relevant here – pmod Nov 09 '14 at 22:41
  • This code is in an obj-c project, but nothing that seems to be causing problem here is an object, and NSZombies, afaik, only affects object memory. – Farski Nov 10 '14 at 18:16

1 Answers1

1

The _S in data types like SOXR_INT32_S mean that you're using split channels, and from the example 4-split-channels.c it seems that in that case you need to pass an array of pointers, one for each channel.

However, in the code above you just pass a single allocated block of memory so I'm guessing you're expecting interleaved channel data. Perhaps you can try changing the _S to _I.

brm
  • 3,706
  • 1
  • 14
  • 14
  • How would I know if the file data is split or interleaved? Is that something that libsndfile normalizes, or should I be detecting that at any point for other parts of the process? – Farski Nov 16 '14 at 01:30
  • Ok, I made that change and it doesn't crash anymore! The audio is very corrupted, but at least I can work with that. Thanks! – Farski Nov 16 '14 at 01:34