/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** Permute is a host tool to randomly permute an audio file.
* It takes as input an ordinary .wav file and produces as output a
* permuted .wav file and .map which can be given the seek torture test
* located in seekTorture.c. A build prerequisite is libsndfile;
* see installation instructions at http://www.mega-nerd.com/libsndfile/
* The format of the .map file is a sequence of lines, each of which is:
* seek_position_in_ms duration_in_ms
*/
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sndfile.h>
/** Global variables */
// command line options
// mean length of each segment of the permutation, in seconds
static double meanSegmentLengthSeconds = 5.0;
// minimum length of each segment of the permutation, in seconds
static double minSegmentLengthSeconds = 1.0;
/** Describes each contiguous segment generated */
typedef struct {
unsigned mFrameStart;
unsigned mFrameLength;
unsigned mPermutedStart;
} Segment;
/** Global state during the split phase */
typedef struct {
// derived from command line options combined with file properties
unsigned mMinSegmentLengthFrames;
//unsigned mMeanSegmentLengthFrames;
unsigned mSegmentMax; // maximum number of segments allowed
unsigned mSegmentCount; // number of segments generated so far
Segment *mSegmentArray; // storage for the segments [max]
} State;
/** Called by qsort as the comparison handler */
static int qsortCompare(const void *x, const void *y)
{
const Segment *x_ = (Segment *) x;
const Segment *y_ = (Segment *) y;
return x_->mFrameStart - y_->mFrameStart;
}
/** Split the specified range of frames, using the allowed budget of segments.
* Returns the actual number of segments consumed.
*/
static unsigned split(State *s, unsigned frameStart, unsigned frameLength, unsigned segmentBudget)
{
if (frameLength <= 0)
return 0;
assert(segmentBudget > 0);
if ((frameLength <= s->mMinSegmentLengthFrames*2) || (segmentBudget <= 1)) {
assert(s->mSegmentCount < s->mSegmentMax);
Segment *seg = &s->mSegmentArray[s->mSegmentCount++];
seg->mFrameStart = frameStart;
seg->mFrameLength = frameLength;
seg->mPermutedStart = ~0;
return 1;
}
// slop is how much wiggle room we have to play with
unsigned slop = frameLength - s->mMinSegmentLengthFrames*2;
assert(slop > 0);
// choose a random cut point within the slop region
unsigned r = rand() & 0x7FFFFFFF;
unsigned cut = r % slop;
unsigned leftStart = frameStart;
unsigned leftLength = s->mMinSegmentLengthFrames + cut;
unsigned rightStart = frameStart + leftLength;
unsigned rightLength = s->mMinSegmentLengthFrames + (slop - cut);
assert(leftLength + rightLength == frameLength);
// process the two sides in random order
assert(segmentBudget >= 2);
unsigned used;
if (leftLength <= rightLength) {
used = split(s, leftStart, leftLength, segmentBudget / 2);
used += split(s, rightStart, rightLength, segmentBudget - used);
} else {
used = split(s, rightStart, rightLength, segmentBudget / 2);
used += split(s, leftStart, leftLength, segmentBudget - used);
}
assert(used >= 2);
assert(used <= segmentBudget);
return used;
}
/** Permute the specified input file */
void permute(char *path_in)
{
// Open the file using libsndfile
SNDFILE *sf_in;
SF_INFO sfinfo_in;
sfinfo_in.format = 0;
sf_in = sf_open(path_in, SFM_READ, &sfinfo_in);
if (NULL == sf_in) {
perror(path_in);
return;
}
// Check if it is a supported file format: must be WAV
unsigned type = sfinfo_in.format & SF_FORMAT_TYPEMASK;
switch (type) {
case SF_FORMAT_WAV:
break;
default:
fprintf(stderr, "%s: unsupported type 0x%X\n", path_in, type);
goto out;
}
#if 0
// Must be 16-bit signed or 8-bit unsigned PCM
unsigned subtype = sfinfo_in.format & SF_FORMAT_SUBMASK;
unsigned sampleSizeIn = 0;
switch (subtype) {
case SF_FORMAT_PCM_16:
sampleSizeIn = 2;
break;
case SF_FORMAT_PCM_U8:
sampleSizeIn = 1;
break;
default:
fprintf(stderr, "%s: unsupported subtype 0x%X\n", path_in, subtype);
goto out;
}
#endif
// always read shorts
unsigned sampleSizeRead = 2;
// Must be little-endian
unsigned endianness = sfinfo_in.format & SF_FORMAT_ENDMASK;
switch (endianness) {
case SF_ENDIAN_FILE:
case SF_ENDIAN_LITTLE:
break;
default:
fprintf(stderr, "%s: unsupported endianness 0x%X\n", path_in, endianness);
goto out;
}
// Must be a known sample rate
switch (sfinfo_in.samplerate) {
case 8000:
case 11025:
case 16000:
case 22050:
case 32000:
case 44100:
case 48000:
break;
default:
fprintf(stderr, "%s: unsupported sample rate %d\n", path_in, sfinfo_in.samplerate);
goto out;
}
// Must be either stereo or mono
unsigned frameSizeRead = 0;
switch (sfinfo_in.channels) {
case 1:
case 2:
frameSizeRead = sampleSizeRead * sfinfo_in.channels;
break;
default:
fprintf(stderr, "%s: unsupported channels %d\n", path_in, sfinfo_in.channels);
goto out;
}
// Duration must be known
switch (sfinfo_in.frames) {
case (sf_count_t) 0:
case (sf_count_t) ~0:
fprintf(stderr, "%s: unsupported frames %d\n", path_in, (int) sfinfo_in.frames);
goto out;
default:
break;
}
// Allocate space to hold the audio data, based on duration
double durationSeconds = (double) sfinfo_in.frames / (double) sfinfo_in.samplerate;
State s;
s.mMinSegmentLengthFrames = minSegmentLengthSeconds * sfinfo_in.samplerate;
if (s.mMinSegmentLengthFrames <= 0)
s.mMinSegmentLengthFrames = 1;
s.mSegmentMax = durationSeconds / meanSegmentLengthSeconds;
if (s.mSegmentMax <= 0)
s.mSegmentMax = 1;
s.mSegmentCount = 0;
s.mSegmentArray = (Segment *) malloc(sizeof(Segment) * s.mSegmentMax);
assert(s.mSegmentArray != NULL);
unsigned used;
used = split(&s, 0, sfinfo_in.frames, s.mSegmentMax);
assert(used <= s.mSegmentMax);
assert(used == s.mSegmentCount);
// now permute the segments randomly using a bad algorithm
unsigned i;
for (i = 0; i < used; ++i) {
unsigned r = rand() & 0x7FFFFFFF;
unsigned j = r % used;
if (j != i) {
Segment temp = s.mSegmentArray[i];
s.mSegmentArray[i] = s.mSegmentArray[j];
s.mSegmentArray[j] = temp;
}
}
// read the entire file into memory
void *ptr = malloc(sfinfo_in.frames * frameSizeRead);
assert(NULL != ptr);
sf_count_t count;
count = sf_readf_short(sf_in, ptr, sfinfo_in.frames);
if (count != sfinfo_in.frames) {
fprintf(stderr, "%s: expected to read %d frames but actually read %d frames\n", path_in,
(int) sfinfo_in.frames, (int) count);
goto out;
}
// Create a permuted output file
char *path_out = malloc(strlen(path_in) + 8);
assert(path_out != NULL);
strcpy(path_out, path_in);
strcat(path_out, ".wav");
SNDFILE *sf_out;
SF_INFO sfinfo_out;
memset(&sfinfo_out, 0, sizeof(SF_INFO));
sfinfo_out.samplerate = sfinfo_in.samplerate;
sfinfo_out.channels = sfinfo_in.channels;
sfinfo_out.format = sfinfo_in.format;
sf_out = sf_open(path_out, SFM_WRITE, &sfinfo_out);
if (sf_out == NULL) {
perror(path_out);
goto out;
}
unsigned permutedStart = 0;
for (i = 0; i < used; ++i) {
s.mSegmentArray[i].mPermutedStart = permutedStart;
count = sf_writef_short(sf_out, &((short *) ptr)[sfinfo_in.channels * s.mSegmentArray[i]
.mFrameStart], s.mSegmentArray[i].mFrameLength);
if (count != s.mSegmentArray[i].mFrameLength) {
fprintf(stderr, "%s: expected to write %d frames but actually wrote %d frames\n",
path_out, (int) s.mSegmentArray[i].mFrameLength, (int) count);
break;
}
permutedStart += s.mSegmentArray[i].mFrameLength;
}
assert(permutedStart == sfinfo_in.frames);
sf_close(sf_out);
// now create a seek map to let us play this back in a reasonable order
qsort((void *) s.mSegmentArray, used, sizeof(Segment), qsortCompare);
char *path_map = malloc(strlen(path_in) + 8);
assert(path_map != NULL);
strcpy(path_map, path_in);
strcat(path_map, ".map");
FILE *fp_map = fopen(path_map, "w");
if (fp_map == NULL) {
perror(path_map);
} else {
for (i = 0; i < used; ++i)
fprintf(fp_map, "%u %u\n", (unsigned) ((s.mSegmentArray[i].mPermutedStart * 1000.0) /
sfinfo_in.samplerate), (unsigned) ((s.mSegmentArray[i].mFrameLength * 1000.0) /
sfinfo_in.samplerate));
fclose(fp_map);
}
out:
// Close the input file
sf_close(sf_in);
}
// main program
int main(int argc, char **argv)
{
int i;
for (i = 1; i < argc; ++i) {
char *arg = argv[i];
// process command line options
if (!strncmp(arg, "-m", 2)) {
double mval = atof(&arg[2]);
if (mval >= 0.1 && mval <= 1000.0)
minSegmentLengthSeconds = mval;
else
fprintf(stderr, "%s: invalid value %s\n", argv[0], arg);
continue;
}
if (!strncmp(arg, "-s", 2)) {
double sval = atof(&arg[2]);
if (sval >= 0.1 && sval <= 1000.0)
meanSegmentLengthSeconds = sval;
else
fprintf(stderr, "%s: invalid value %s\n", argv[0], arg);
continue;
}
if (!strncmp(arg, "-r", 2)) {
srand(atoi(&arg[2]));
continue;
}
if (meanSegmentLengthSeconds < minSegmentLengthSeconds)
meanSegmentLengthSeconds = minSegmentLengthSeconds * 2.0;
// Permute the file
permute(arg);
}
return EXIT_SUCCESS;
}