void Tool::error(const char *format, ...) { char buf[4096]; va_list va; va_start(va, format); vsnprintf(buf, 4096, format, va); va_end(va); throw ToolException(buf); }
void Tool::parseOutputArguments() { if (_arguments.empty()) return; if (_arguments.front() == "-o" || _arguments.front() == "--output") { // It's an -o argument _arguments.pop_front(); if (_arguments.empty()) throw ToolException("Could not parse arguments: Expected path after '-o' or '--output'."); _outputPath = _arguments.front(); _arguments.pop_front(); } }
void CompressTouche::execute() { Common::Filename inpath(_inputPaths[0].path); Common::Filename &outpath = _outputPath; if (outpath.getFullName().empty()) { switch(_format) { case AUDIO_MP3: outpath.setFullName(OUTPUT_MP3); break; case AUDIO_VORBIS: outpath.setFullName(OUTPUT_OGG); break; case AUDIO_FLAC: outpath.setFullName(OUTPUT_FLA); break; default: throw ToolException("Unknown audio format"); break; } } compress_sound_data(&inpath, &outpath); }
void CompressSword2::execute() { int j; uint32 indexSize; uint32 totalSize; uint32 length; Common::Filename inpath(_inputPaths[0].path); Common::Filename &outpath = _outputPath; if (outpath.empty()) // Extensions change between the in/out files, so we can use the same directory outpath = inpath; switch (_format) { case AUDIO_MP3: _audioOutputFilename = TEMP_MP3; outpath.setExtension(".cl3"); break; case AUDIO_VORBIS: _audioOutputFilename = TEMP_OGG; outpath.setExtension(".clg"); break; case AUDIO_FLAC: _audioOutputFilename = TEMP_FLAC; outpath.setExtension(".clf"); break; default: throw ToolException("Unknown audio format"); break; } _input.open(inpath, "rb"); indexSize = _input.readUint32LE(); totalSize = 12 * (indexSize + 1); if (_input.readUint32BE() != 0xfff0fff0) { error("This doesn't look like a cluster file"); } _output_idx.open(TEMP_IDX, "wb"); _output_snd.open(TEMP_DAT, "wb"); _output_idx.writeUint32LE(indexSize); _output_idx.writeUint32BE(0xfff0fff0); _output_idx.writeUint32BE(0xfff0fff0); for (int i = 0; i < (int)indexSize; i++) { // Update progress, this loop is where most of the time is spent updateProgress(i, indexSize); uint32 pos; uint32 enc_length; _input.seek(8 * (i + 1), SEEK_SET); pos = _input.readUint32LE(); length = _input.readUint32LE(); if (pos != 0 && length != 0) { uint16 prev; Common::File f(TEMP_WAV, "wb"); /* * The number of decodeable 16-bit samples is one less * than the length of the resource. */ length--; /* * Back when this tool was written, encodeAudio() had * no way of encoding 16-bit data, so it was simpler to * output a WAV file. */ f.writeUint32BE(0x52494646); /* "RIFF" */ f.writeUint32LE(2 * length + 36); f.writeUint32BE(0x57415645); /* "WAVE" */ f.writeUint32BE(0x666d7420); /* "fmt " */ f.writeUint32LE(16); f.writeUint16LE(1); /* PCM */ f.writeUint16LE(1); /* mono */ f.writeUint32LE(22050); /* sample rate */ f.writeUint32LE(44100); /* bytes per second */ f.writeUint16LE(2); /* basic block size */ f.writeUint16LE(16); /* sample width */ f.writeUint32BE(0x64617461); /* "data" */ f.writeUint32LE(2 * length); _input.seek(pos, SEEK_SET); /* * The first sample is stored uncompressed. Subsequent * samples are stored as some sort of 8-bit delta. */ prev = _input.readUint16LE(); f.writeUint16LE(prev); for (j = 1; j < (int)length; j++) { byte data; uint16 out; data = _input.readByte(); if (GetCompressedSign(data)) out = prev - (GetCompressedAmplitude(data) << GetCompressedShift(data)); else out = prev + (GetCompressedAmplitude(data) << GetCompressedShift(data)); f.writeUint16LE(out); prev = out; } f.close(); encodeAudio(TEMP_WAV, false, -1, tempEncoded, _format); enc_length = append_to_file(_output_snd, tempEncoded); _output_idx.writeUint32LE(totalSize); _output_idx.writeUint32LE(length); _output_idx.writeUint32LE(enc_length); totalSize = totalSize + enc_length; } else { _output_idx.writeUint32LE(0); _output_idx.writeUint32LE(0); _output_idx.writeUint32LE(0); } } _output_snd.close(); _output_idx.close(); Common::File output(outpath, "wb"); append_to_file(output, TEMP_IDX); append_to_file(output, TEMP_DAT); output.close(); Common::removeFile(TEMP_DAT); Common::removeFile(TEMP_IDX); Common::removeFile(TEMP_MP3); Common::removeFile(TEMP_OGG); Common::removeFile(TEMP_FLAC); Common::removeFile(TEMP_WAV); }
void CompressionTool::encodeRaw(const char *rawData, int length, int samplerate, const char *outname, AudioFormat compmode) { print(" - len=%ld, ch=%d, rate=%d, %dbits", length, (rawAudioType.isStereo ? 2 : 1), samplerate, rawAudioType.bitsPerSample); #ifdef USE_VORBIS if (compmode == AUDIO_VORBIS) { char outputString[256] = ""; int numChannels = (rawAudioType.isStereo ? 2 : 1); int totalSamples = length / ((rawAudioType.bitsPerSample / 8) * numChannels); int samplesLeft = totalSamples; int eos = 0; int totalBytes = 0; vorbis_info vi; vorbis_comment vc; vorbis_dsp_state vd; vorbis_block vb; ogg_stream_state os; ogg_page og; ogg_packet op; ogg_packet header; ogg_packet header_comm; ogg_packet header_code; Common::File outputOgg(outname, "wb"); vorbis_info_init(&vi); if (oggparms.nominalBitr > 0) { int result = 0; /* Input is in kbps, function takes bps */ result = vorbis_encode_setup_managed(&vi, numChannels, samplerate, (oggparms.maxBitr > 0 ? 1000 * oggparms.maxBitr : -1), (1000 * oggparms.nominalBitr), (oggparms.minBitr > 0 ? 1000 * oggparms.minBitr : -1)); if (result == OV_EFAULT) { vorbis_info_clear(&vi); error("Error: Internal Logic Fault"); } else if ((result == OV_EINVAL) || (result == OV_EIMPL)) { vorbis_info_clear(&vi); error("Error: Invalid bitrate parameters"); } if (!oggparms.silent) { sprintf(outputString, "Encoding to\n \"%s\"\nat average bitrate %i kbps (", outname, oggparms.nominalBitr); if (oggparms.minBitr > 0) { sprintf(outputString + strlen(outputString), "min %i kbps, ", oggparms.minBitr); } else { sprintf(outputString + strlen(outputString), "no min, "); } if (oggparms.maxBitr > 0) { sprintf(outputString + strlen(outputString), "max %i kbps),\nusing full bitrate management engine\nSet optional hard quality restrictions\n", oggparms.maxBitr); } else { sprintf(outputString + strlen(outputString), "no max),\nusing full bitrate management engine\nSet optional hard quality restrictions\n"); } } } else { int result = 0; /* Quality input is -1 - 10, function takes -0.1 through 1.0 */ result = vorbis_encode_setup_vbr(&vi, numChannels, samplerate, oggparms.quality * 0.1f); if (result == OV_EFAULT) { vorbis_info_clear(&vi); error("Internal Logic Fault"); } else if ((result == OV_EINVAL) || (result == OV_EIMPL)) { vorbis_info_clear(&vi); error("Invalid bitrate parameters"); } if (!oggparms.silent) { sprintf(outputString, "Encoding to\n \"%s\"\nat quality %2.2f", outname, oggparms.quality); } if ((oggparms.minBitr > 0) || (oggparms.maxBitr > 0)) { struct ovectl_ratemanage_arg extraParam; vorbis_encode_ctl(&vi, OV_ECTL_RATEMANAGE_GET, &extraParam); extraParam.bitrate_hard_min = (oggparms.minBitr > 0 ? (1000 * oggparms.minBitr) : -1); extraParam.bitrate_hard_max = (oggparms.maxBitr > 0 ? (1000 * oggparms.maxBitr) : -1); extraParam.management_active = 1; vorbis_encode_ctl(&vi, OV_ECTL_RATEMANAGE_SET, &extraParam); if (!oggparms.silent) { sprintf(outputString + strlen(outputString), " using constrained VBR ("); if (oggparms.minBitr != -1) { sprintf(outputString + strlen(outputString), "min %i kbps, ", oggparms.minBitr); } else { sprintf(outputString + strlen(outputString), "no min, "); } if (oggparms.maxBitr != -1) { sprintf(outputString + strlen(outputString), "max %i kbps)\nSet optional hard quality restrictions\n", oggparms.maxBitr); } else { sprintf(outputString + strlen(outputString), "no max)\nSet optional hard quality restrictions\n"); } } } else { sprintf(outputString + strlen(outputString), "\n"); } } puts(outputString); vorbis_encode_setup_init(&vi); vorbis_comment_init(&vc); vorbis_analysis_init(&vd, &vi); vorbis_block_init(&vd, &vb); ogg_stream_init(&os, 0); vorbis_analysis_headerout(&vd, &vc, &header, &header_comm, &header_code); ogg_stream_packetin(&os, &header); ogg_stream_packetin(&os, &header_comm); ogg_stream_packetin(&os, &header_code); while (!eos) { int result = ogg_stream_flush(&os,&og); if (result == 0) { break; } outputOgg.write(og.header, og.header_len); outputOgg.write(og.body, og.body_len); } while (!eos) { int numSamples = ((samplesLeft < 2048) ? samplesLeft : 2048); float **buffer = vorbis_analysis_buffer(&vd, numSamples); /* We must tell the encoder that we have reached the end of the stream */ if (numSamples == 0) { vorbis_analysis_wrote(&vd, 0); } else { /* Adapted from oggenc 1.1.1 */ if (rawAudioType.bitsPerSample == 8) { const byte *rawDataUnsigned = (const byte *)rawData; for (int i = 0; i < numSamples; i++) { for (int j = 0; j < numChannels; j++) { buffer[j][i] = ((int)(rawDataUnsigned[i * numChannels + j]) - 128) / 128.0f; } } } else if (rawAudioType.bitsPerSample == 16) { if (rawAudioType.isLittleEndian) { for (int i = 0; i < numSamples; i++) { for (int j = 0; j < numChannels; j++) { buffer[j][i] = ((rawData[(i * 2 * numChannels) + (2 * j) + 1] << 8) | (rawData[(i * 2 * numChannels) + (2 * j)] & 0xff)) / 32768.0f; } } } else { for (int i = 0; i < numSamples; i++) { for (int j = 0; j < numChannels; j++) { buffer[j][i] = ((rawData[(i * 2 * numChannels) + (2 * j)] << 8) | (rawData[(i * 2 * numChannels) + (2 * j) + 1] & 0xff)) / 32768.0f; } } } } vorbis_analysis_wrote(&vd, numSamples); } while (vorbis_analysis_blockout(&vd, &vb) == 1) { vorbis_analysis(&vb, NULL); vorbis_bitrate_addblock(&vb); while (vorbis_bitrate_flushpacket(&vd, &op)) { ogg_stream_packetin(&os, &op); while (!eos) { int result = ogg_stream_pageout(&os, &og); if (result == 0) { break; } totalBytes += outputOgg.write(og.header, og.header_len); totalBytes += outputOgg.write(og.body, og.body_len); if (ogg_page_eos(&og)) { eos = 1; } } } } rawData += 2048 * (rawAudioType.bitsPerSample / 8) * numChannels; samplesLeft -= 2048; } ogg_stream_clear(&os); vorbis_block_clear(&vb); vorbis_dsp_clear(&vd); vorbis_info_clear(&vi); if (!oggparms.silent) { print("\nDone encoding file \"%s\"", outname); print("\n\tFile length: %dm %ds", (int)(totalSamples / samplerate / 60), (totalSamples / samplerate % 60)); print("\tAverage bitrate: %.1f kb/s\n", (8.0 * (double)totalBytes / 1000.0) / ((double)totalSamples / (double)samplerate)); } } #endif #ifdef USE_FLAC if (compmode == AUDIO_FLAC) { int i; int numChannels = (rawAudioType.isStereo ? 2 : 1); int samplesPerChannel = length / ((rawAudioType.bitsPerSample / 8) * numChannels); FLAC__StreamEncoder *encoder; FLAC__StreamEncoderInitStatus initStatus; FLAC__int32 *flacData; flacData = (FLAC__int32 *)malloc(samplesPerChannel * numChannels * sizeof(FLAC__int32)); if (rawAudioType.bitsPerSample == 8) { for (i = 0; i < samplesPerChannel * numChannels; i++) { FLAC__uint8 *rawDataUnsigned; rawDataUnsigned = (FLAC__uint8 *)rawData; flacData[i] = (FLAC__int32)rawDataUnsigned[i] - 0x80; } } else if (rawAudioType.bitsPerSample == 16) { /* The rawData pointer is an 8-bit char so we must create a new pointer to access 16-bit samples */ FLAC__int16 *rawData16; rawData16 = (FLAC__int16 *)rawData; for (i = 0; i < samplesPerChannel * numChannels; i++) { flacData[i] = (FLAC__int32)rawData16[i]; } } if (!flacparms.silent) { print("Encoding to\n \"%s\"\nat compression level %d using blocksize %d\n", outname, flacparms.compressionLevel, flacparms.blocksize); } encoder = FLAC__stream_encoder_new(); FLAC__stream_encoder_set_bits_per_sample(encoder, rawAudioType.bitsPerSample); FLAC__stream_encoder_set_blocksize(encoder, flacparms.blocksize); FLAC__stream_encoder_set_channels(encoder, numChannels); FLAC__stream_encoder_set_compression_level(encoder, flacparms.compressionLevel); FLAC__stream_encoder_set_sample_rate(encoder, samplerate); FLAC__stream_encoder_set_streamable_subset(encoder, false); FLAC__stream_encoder_set_total_samples_estimate(encoder, samplesPerChannel); FLAC__stream_encoder_set_verify(encoder, flacparms.verify); initStatus = FLAC__stream_encoder_init_file(encoder, outname, NULL, NULL); if (initStatus != FLAC__STREAM_ENCODER_INIT_STATUS_OK) { char buf[2048]; sprintf(buf, "Error in FLAC encoder. (check the parameters)\nExact error was:%s", FLAC__StreamEncoderInitStatusString[initStatus]); free(flacData); throw ToolException(buf); } else { FLAC__stream_encoder_process_interleaved(encoder, flacData, samplesPerChannel); } FLAC__stream_encoder_finish(encoder); FLAC__stream_encoder_delete(encoder); free(flacData); if (!flacparms.silent) { print("\nDone encoding file \"%s\"", outname); print("\n\tFile length: %dm %ds\n", (int)(samplesPerChannel / samplerate / 60), (samplesPerChannel / samplerate % 60)); } } #endif }
void CompressionTool::encodeAudio(const char *inname, bool rawInput, int rawSamplerate, const char *outname, AudioFormat compmode) { bool err = false; char fbuf[2048]; char *tmp = fbuf; if (compmode == AUDIO_MP3) { tmp += sprintf(tmp, "%s -t ", lameparms.lamePath.c_str()); if (rawInput) { tmp += sprintf(tmp, "-r "); tmp += sprintf(tmp, "--bitwidth %d ", rawAudioType.bitsPerSample); if (rawAudioType.isLittleEndian) { tmp += sprintf(tmp, "--little-endian "); } else { tmp += sprintf(tmp, "--big-endian "); } tmp += sprintf(tmp, (rawAudioType.isStereo ? "-m j " : "-m m ")); tmp += sprintf(tmp, "-s %d ", rawSamplerate); } if (lameparms.type == CBR) tmp += sprintf(tmp, "--cbr -b %d ", lameparms.targetBitr); else { if (lameparms.type == ABR) tmp += sprintf(tmp, "--abr %d ", lameparms.targetBitr); else tmp += sprintf(tmp, "--vbr-new -V %d ", lameparms.vbrqual); if (lameparms.minBitr != -1) tmp += sprintf(tmp, "-b %d ", lameparms.minBitr); if (lameparms.maxBitr != -1) tmp += sprintf(tmp, "-B %d ", lameparms.maxBitr); } /* Explicitly specify a target sample rate, to work around a bug (?) * in newer lame versions (>= 3.95) which causes it to malfunction * for odd sample rates when in VBR mode. See also bug #934026. * We essentially duplicate the old behaviour of lame (found in e.g. * version 3.93.1): we round the input sample rate up to the next * higher valid MP3 sample rate, with a margin of 3%. */ if (rawSamplerate != -1) { tmp += sprintf(tmp, "--resample %d ", map2MP3Frequency(97 * rawSamplerate / 100)); } if (lameparms.silent) { tmp += sprintf(tmp, " --silent "); } tmp += sprintf(tmp, "-q %d ", lameparms.algqual); tmp += sprintf(tmp, "\"%s\" \"%s\" ", inname, outname); err = spawnSubprocess(fbuf) != 0; if (err) { char buf[2048]; sprintf(buf, "Error in MP3 encoder.(check parameters) \nMP3 Encoder Commandline:%s\n", fbuf); throw ToolException(buf, err); } else { return; } } #ifndef USE_VORBIS if (compmode == AUDIO_VORBIS) { tmp += sprintf(tmp, "oggenc "); if (rawInput) { tmp += sprintf(tmp, "--raw "); tmp += sprintf(tmp, "--raw-chan=%d ", (rawAudioType.isStereo ? 2 : 1)); tmp += sprintf(tmp, "--raw-bits=%d ", rawAudioType.bitsPerSample); tmp += sprintf(tmp, "--raw-rate=%d ", rawSamplerate); tmp += sprintf(tmp, "--raw-endianness=%d ", (rawAudioType.isLittleEndian ? 0 : 1)); } if (oggparms.nominalBitr != -1) { tmp += sprintf(tmp, "--bitrate=%d ", oggparms.nominalBitr); } else { tmp += sprintf(tmp, "--quality=%f ", oggparms.quality); } if (oggparms.minBitr != -1) { tmp += sprintf(tmp, "--min-bitrate=%d ", oggparms.minBitr); } if (oggparms.maxBitr != -1) { tmp += sprintf(tmp, "--max-bitrate=%d ", oggparms.maxBitr); } if (oggparms.silent) { tmp += sprintf(tmp, "--quiet "); } tmp += sprintf(tmp, "--output=\"%s\" ", outname); tmp += sprintf(tmp, "\"%s\" ", inname); err = spawnSubprocess(fbuf) != 0; if (err) { char buf[2048]; sprintf(buf, "Error in Vorbis encoder. (check parameters)\nVorbis Encoder Commandline:%s\n", fbuf); throw ToolException(buf, err); } else { return; } } #endif #ifndef USE_FLAC if (compmode == AUDIO_FLAC) { /* --lax is needed to allow 11kHz, we dont need place for meta-tags, and no seektable */ /* -f is reqired to force override of unremoved temp file. See bug #1294648 */ tmp += sprintf(tmp, "flac -f --lax --no-padding --no-seektable --no-ogg "); if (rawInput) { tmp += sprintf(tmp, "--force-raw-format "); tmp += sprintf(tmp, "--sign=%s ", ((rawAudioType.bitsPerSample == 8) ? "unsigned" : "signed")); tmp += sprintf(tmp, "--channels=%d ", (rawAudioType.isStereo ? 2 : 1)); tmp += sprintf(tmp, "--bps=%d ", rawAudioType.bitsPerSample); tmp += sprintf(tmp, "--sample-rate=%d ", rawSamplerate); tmp += sprintf(tmp, "--endian=%s ", (rawAudioType.isLittleEndian ? "little" : "big")); } if (flacparms.silent) { tmp += sprintf(tmp, "--silent "); } if (flacparms.verify) { tmp += sprintf(tmp, "--verify "); } tmp += sprintf(tmp, "--compression-level-%d ", flacparms.compressionLevel); tmp += sprintf(tmp, "-b %d ", flacparms.blocksize); tmp += sprintf(tmp, "-o \"%s\" ", outname); tmp += sprintf(tmp, "\"%s\" ", inname); err = spawnSubprocess(fbuf) != 0; if (err) { char buf[2048]; sprintf(buf, "Error in FLAC encoder. (check parameters)\nFLAC Encoder Commandline:%s\n", fbuf); throw ToolException(buf, err); } else { return; } } #endif if (rawInput) { long length; char *rawData; Common::File inputRaw(inname, "rb"); length = inputRaw.size(); rawData = (char *)malloc(length); inputRaw.read_throwsOnError(rawData, length); encodeRaw(rawData, length, rawSamplerate, outname, compmode); free(rawData); } else { int fmtHeaderSize, length, numChannels, sampleRate, bitsPerSample; char *wavData; Common::File inputWav(inname, "rb"); /* Standard PCM fmt header is 16 bits, but at least Simon 1 and 2 use 18 bits */ inputWav.seek(16, SEEK_SET); fmtHeaderSize = inputWav.readUint32LE(); inputWav.seek(22, SEEK_SET); numChannels = inputWav.readUint16LE(); sampleRate = inputWav.readUint32LE(); inputWav.seek(34, SEEK_SET); bitsPerSample = inputWav.readUint16LE(); /* The size of the raw audio is after the RIFF chunk (12 bytes), fmt chunk (8 + fmtHeaderSize bytes), and data chunk id (4 bytes) */ inputWav.seek(24 + fmtHeaderSize, SEEK_SET); length = inputWav.readUint32LE(); wavData = (char *)malloc(length); inputWav.read_throwsOnError(wavData, length); setRawAudioType(true, numChannels == 2, (uint8)bitsPerSample); encodeRaw(wavData, length, sampleRate, outname, compmode); free(wavData); } }
void CompressionExample::execute() { // By now, all arguments have been parsed, all members setup and all input paths setup // If this was a compression tool _format would contain the selected audio format // Note that we almost need no error handling, since exceptions will be thrown if we do // something bad, and all parameters have already been setup // This 'tool' doesn't takes two input files // It writes the of each file 100 times into X files, where X is the input file size. // _inputPaths[0].path is always valid and contains the correct path, same for 1 Common::Filename inpath1(_inputPaths[0].path); Common::Filename inpath2(_inputPaths[1].path); // We always need to setup default output path, since there is no obligation to specify it // If you don't do this, the OS will usually default to the working directory, if we output a file // it will fail most likely if (_outputPath.empty()) _outputPath = "output/"; // The File class is very similar to the FILE struct, look in util.h for details File in1(inpath1, "r"); File in2(inpath2, "r"); // Read the complete contents of the files (if they don't contain NUL ofcourse) std::string text1 = in1.readString(); std::string text2 = in2.readString(); // Start the 'extraction' size_t total_files = in1.size() + in2.size(); // There has to be some roof on this if (total_files > 1000) throw ToolException("Input files are too large!"); for (size_t i = 0; i < total_files; ++i) { // This updates any progress bar, if there is any // if you don't support progress bars, you should use notifyProgress instead // to make sure progress can be aborted (print calls notifyProgress internally) updateProgress(i, total_files); // Convert i to string std::ostringstream outname; outname << i; // Open a file for writing if (_outputFiles) { File out(_outputPath.getPath() + outname.str() + ".exo", "w"); // What we actually do, output some text alot for (size_t j = 0; j < 100; ++j) { if (i < in1.size()) out.write(text1.c_str(), 1, text1.size()); else out.write(text2.c_str(), 1, text2.size()); } } else { // Do nothing for awhile for (int i = 0; i < 1000000; ++i) { int *n = new int; delete n; } } // Print can also throw an AbortException // Do NOT use printf or anything else that outputs to standard output, as it will not display in the GUI. print("Outputted file %d of %d", i, total_files); } // We indicate success by not throwing any exceptions during the execution print("Extraction finished without errors!"); }