You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
343 lines
11 KiB
343 lines
11 KiB
/*
|
|
* 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;
|
|
}
|