/* * 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; }