The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

624 lines
22KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #if JUCE_USE_FLAC
  20. }
  21. #if defined _WIN32 && !defined __CYGWIN__
  22. #include <io.h>
  23. #else
  24. #include <unistd.h>
  25. #endif
  26. #if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__
  27. #include <sys/types.h> /* for off_t */
  28. #endif
  29. #if HAVE_INTTYPES_H
  30. #define __STDC_FORMAT_MACROS
  31. #include <inttypes.h>
  32. #endif
  33. #if defined _MSC_VER || defined __MINGW32__ || defined __CYGWIN__ || defined __EMX__
  34. #include <io.h> /* for _setmode(), chmod() */
  35. #include <fcntl.h> /* for _O_BINARY */
  36. #else
  37. #include <unistd.h> /* for chown(), unlink() */
  38. #endif
  39. #if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__
  40. #if defined __BORLANDC__
  41. #include <utime.h> /* for utime() */
  42. #else
  43. #include <sys/utime.h> /* for utime() */
  44. #endif
  45. #else
  46. #include <sys/types.h> /* some flavors of BSD (like OS X) require this to get time_t */
  47. #include <utime.h> /* for utime() */
  48. #endif
  49. #if defined _MSC_VER
  50. #if _MSC_VER >= 1600
  51. #include <stdint.h>
  52. #else
  53. #include <limits.h>
  54. #endif
  55. #endif
  56. #ifdef _WIN32
  57. #include <stdio.h>
  58. #include <sys/stat.h>
  59. #include <stdarg.h>
  60. #include <windows.h>
  61. #endif
  62. #ifdef DEBUG
  63. #include <assert.h>
  64. #endif
  65. #include <stdlib.h>
  66. #include <stdio.h>
  67. namespace juce
  68. {
  69. namespace FlacNamespace
  70. {
  71. #if JUCE_INCLUDE_FLAC_CODE || ! defined (JUCE_INCLUDE_FLAC_CODE)
  72. #undef VERSION
  73. #define VERSION "1.3.1"
  74. #define FLAC__NO_DLL 1
  75. #if JUCE_MSVC
  76. #pragma warning (disable: 4267 4127 4244 4996 4100 4701 4702 4013 4133 4206 4312 4505 4365 4005 4334 181 111)
  77. #else
  78. #define HAVE_LROUND 1
  79. #endif
  80. #if JUCE_MAC
  81. #define FLAC__SYS_DARWIN 1
  82. #endif
  83. #ifndef SIZE_MAX
  84. #define SIZE_MAX 0xffffffff
  85. #endif
  86. #if JUCE_CLANG
  87. #pragma clang diagnostic push
  88. #pragma clang diagnostic ignored "-Wconversion"
  89. #pragma clang diagnostic ignored "-Wshadow"
  90. #pragma clang diagnostic ignored "-Wdeprecated-register"
  91. #endif
  92. #if JUCE_INTEL
  93. #if JUCE_32BIT
  94. #define FLAC__CPU_IA32 1
  95. #endif
  96. #if JUCE_64BIT
  97. #define FLAC__CPU_X86_64 1
  98. #endif
  99. #define FLAC__HAS_X86INTRIN 1
  100. #endif
  101. #undef __STDC_LIMIT_MACROS
  102. #define __STDC_LIMIT_MACROS 1
  103. #define flac_max jmax
  104. #define flac_min jmin
  105. #undef DEBUG // (some flac code dumps debug trace if the app defines this macro)
  106. #include "flac/all.h"
  107. #include "flac/libFLAC/bitmath.c"
  108. #include "flac/libFLAC/bitreader.c"
  109. #include "flac/libFLAC/bitwriter.c"
  110. #include "flac/libFLAC/cpu.c"
  111. #include "flac/libFLAC/crc.c"
  112. #include "flac/libFLAC/fixed.c"
  113. #include "flac/libFLAC/float.c"
  114. #include "flac/libFLAC/format.c"
  115. #include "flac/libFLAC/lpc_flac.c"
  116. #include "flac/libFLAC/md5.c"
  117. #include "flac/libFLAC/memory.c"
  118. #include "flac/libFLAC/stream_decoder.c"
  119. #include "flac/libFLAC/stream_encoder.c"
  120. #include "flac/libFLAC/stream_encoder_framing.c"
  121. #include "flac/libFLAC/window_flac.c"
  122. #undef VERSION
  123. #else
  124. #include <FLAC/all.h>
  125. #endif
  126. #if JUCE_CLANG
  127. #pragma clang diagnostic pop
  128. #endif
  129. }
  130. #undef max
  131. #undef min
  132. //==============================================================================
  133. static const char* const flacFormatName = "FLAC file";
  134. //==============================================================================
  135. class FlacReader : public AudioFormatReader
  136. {
  137. public:
  138. FlacReader (InputStream* const in)
  139. : AudioFormatReader (in, flacFormatName),
  140. reservoirStart (0),
  141. samplesInReservoir (0),
  142. scanningForLength (false)
  143. {
  144. using namespace FlacNamespace;
  145. lengthInSamples = 0;
  146. decoder = FLAC__stream_decoder_new();
  147. ok = FLAC__stream_decoder_init_stream (decoder,
  148. readCallback_, seekCallback_, tellCallback_, lengthCallback_,
  149. eofCallback_, writeCallback_, metadataCallback_, errorCallback_,
  150. this) == FLAC__STREAM_DECODER_INIT_STATUS_OK;
  151. if (ok)
  152. {
  153. FLAC__stream_decoder_process_until_end_of_metadata (decoder);
  154. if (lengthInSamples == 0 && sampleRate > 0)
  155. {
  156. // the length hasn't been stored in the metadata, so we'll need to
  157. // work it out the length the hard way, by scanning the whole file..
  158. scanningForLength = true;
  159. FLAC__stream_decoder_process_until_end_of_stream (decoder);
  160. scanningForLength = false;
  161. const int64 tempLength = lengthInSamples;
  162. FLAC__stream_decoder_reset (decoder);
  163. FLAC__stream_decoder_process_until_end_of_metadata (decoder);
  164. lengthInSamples = tempLength;
  165. }
  166. }
  167. }
  168. ~FlacReader()
  169. {
  170. FlacNamespace::FLAC__stream_decoder_delete (decoder);
  171. }
  172. void useMetadata (const FlacNamespace::FLAC__StreamMetadata_StreamInfo& info)
  173. {
  174. sampleRate = info.sample_rate;
  175. bitsPerSample = info.bits_per_sample;
  176. lengthInSamples = (unsigned int) info.total_samples;
  177. numChannels = info.channels;
  178. reservoir.setSize ((int) numChannels, 2 * (int) info.max_blocksize, false, false, true);
  179. }
  180. // returns the number of samples read
  181. bool readSamples (int** destSamples, int numDestChannels, int startOffsetInDestBuffer,
  182. int64 startSampleInFile, int numSamples) override
  183. {
  184. using namespace FlacNamespace;
  185. if (! ok)
  186. return false;
  187. while (numSamples > 0)
  188. {
  189. if (startSampleInFile >= reservoirStart
  190. && startSampleInFile < reservoirStart + samplesInReservoir)
  191. {
  192. const int num = (int) jmin ((int64) numSamples,
  193. reservoirStart + samplesInReservoir - startSampleInFile);
  194. jassert (num > 0);
  195. for (int i = jmin (numDestChannels, reservoir.getNumChannels()); --i >= 0;)
  196. if (destSamples[i] != nullptr)
  197. memcpy (destSamples[i] + startOffsetInDestBuffer,
  198. reservoir.getReadPointer (i, (int) (startSampleInFile - reservoirStart)),
  199. sizeof (int) * (size_t) num);
  200. startOffsetInDestBuffer += num;
  201. startSampleInFile += num;
  202. numSamples -= num;
  203. }
  204. else
  205. {
  206. if (startSampleInFile >= (int) lengthInSamples)
  207. {
  208. samplesInReservoir = 0;
  209. }
  210. else if (startSampleInFile < reservoirStart
  211. || startSampleInFile > reservoirStart + jmax (samplesInReservoir, 511))
  212. {
  213. // had some problems with flac crashing if the read pos is aligned more
  214. // accurately than this. Probably fixed in newer versions of the library, though.
  215. reservoirStart = (int) (startSampleInFile & ~511);
  216. samplesInReservoir = 0;
  217. FLAC__stream_decoder_seek_absolute (decoder, (FLAC__uint64) reservoirStart);
  218. }
  219. else
  220. {
  221. reservoirStart += samplesInReservoir;
  222. samplesInReservoir = 0;
  223. FLAC__stream_decoder_process_single (decoder);
  224. }
  225. if (samplesInReservoir == 0)
  226. break;
  227. }
  228. }
  229. if (numSamples > 0)
  230. {
  231. for (int i = numDestChannels; --i >= 0;)
  232. if (destSamples[i] != nullptr)
  233. zeromem (destSamples[i] + startOffsetInDestBuffer, sizeof (int) * (size_t) numSamples);
  234. }
  235. return true;
  236. }
  237. void useSamples (const FlacNamespace::FLAC__int32* const buffer[], int numSamples)
  238. {
  239. if (scanningForLength)
  240. {
  241. lengthInSamples += numSamples;
  242. }
  243. else
  244. {
  245. if (numSamples > reservoir.getNumSamples())
  246. reservoir.setSize ((int) numChannels, numSamples, false, false, true);
  247. const unsigned int bitsToShift = 32 - bitsPerSample;
  248. for (int i = 0; i < (int) numChannels; ++i)
  249. {
  250. const FlacNamespace::FLAC__int32* src = buffer[i];
  251. int n = i;
  252. while (src == 0 && n > 0)
  253. src = buffer [--n];
  254. if (src != nullptr)
  255. {
  256. int* const dest = reinterpret_cast<int*> (reservoir.getWritePointer(i));
  257. for (int j = 0; j < numSamples; ++j)
  258. dest[j] = src[j] << bitsToShift;
  259. }
  260. }
  261. samplesInReservoir = numSamples;
  262. }
  263. }
  264. //==============================================================================
  265. static FlacNamespace::FLAC__StreamDecoderReadStatus readCallback_ (const FlacNamespace::FLAC__StreamDecoder*, FlacNamespace::FLAC__byte buffer[], size_t* bytes, void* client_data)
  266. {
  267. using namespace FlacNamespace;
  268. *bytes = (size_t) static_cast<const FlacReader*> (client_data)->input->read (buffer, (int) *bytes);
  269. return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE;
  270. }
  271. static FlacNamespace::FLAC__StreamDecoderSeekStatus seekCallback_ (const FlacNamespace::FLAC__StreamDecoder*, FlacNamespace::FLAC__uint64 absolute_byte_offset, void* client_data)
  272. {
  273. using namespace FlacNamespace;
  274. static_cast<const FlacReader*> (client_data)->input->setPosition ((int) absolute_byte_offset);
  275. return FLAC__STREAM_DECODER_SEEK_STATUS_OK;
  276. }
  277. static FlacNamespace::FLAC__StreamDecoderTellStatus tellCallback_ (const FlacNamespace::FLAC__StreamDecoder*, FlacNamespace::FLAC__uint64* absolute_byte_offset, void* client_data)
  278. {
  279. using namespace FlacNamespace;
  280. *absolute_byte_offset = (uint64) static_cast<const FlacReader*> (client_data)->input->getPosition();
  281. return FLAC__STREAM_DECODER_TELL_STATUS_OK;
  282. }
  283. static FlacNamespace::FLAC__StreamDecoderLengthStatus lengthCallback_ (const FlacNamespace::FLAC__StreamDecoder*, FlacNamespace::FLAC__uint64* stream_length, void* client_data)
  284. {
  285. using namespace FlacNamespace;
  286. *stream_length = (uint64) static_cast<const FlacReader*> (client_data)->input->getTotalLength();
  287. return FLAC__STREAM_DECODER_LENGTH_STATUS_OK;
  288. }
  289. static FlacNamespace::FLAC__bool eofCallback_ (const FlacNamespace::FLAC__StreamDecoder*, void* client_data)
  290. {
  291. return static_cast<const FlacReader*> (client_data)->input->isExhausted();
  292. }
  293. static FlacNamespace::FLAC__StreamDecoderWriteStatus writeCallback_ (const FlacNamespace::FLAC__StreamDecoder*,
  294. const FlacNamespace::FLAC__Frame* frame,
  295. const FlacNamespace::FLAC__int32* const buffer[],
  296. void* client_data)
  297. {
  298. using namespace FlacNamespace;
  299. static_cast<FlacReader*> (client_data)->useSamples (buffer, (int) frame->header.blocksize);
  300. return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
  301. }
  302. static void metadataCallback_ (const FlacNamespace::FLAC__StreamDecoder*,
  303. const FlacNamespace::FLAC__StreamMetadata* metadata,
  304. void* client_data)
  305. {
  306. static_cast<FlacReader*> (client_data)->useMetadata (metadata->data.stream_info);
  307. }
  308. static void errorCallback_ (const FlacNamespace::FLAC__StreamDecoder*, FlacNamespace::FLAC__StreamDecoderErrorStatus, void*)
  309. {
  310. }
  311. private:
  312. FlacNamespace::FLAC__StreamDecoder* decoder;
  313. AudioSampleBuffer reservoir;
  314. int reservoirStart, samplesInReservoir;
  315. bool ok, scanningForLength;
  316. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacReader)
  317. };
  318. //==============================================================================
  319. class FlacWriter : public AudioFormatWriter
  320. {
  321. public:
  322. FlacWriter (OutputStream* const out, double rate, uint32 numChans, uint32 bits, int qualityOptionIndex)
  323. : AudioFormatWriter (out, flacFormatName, rate, numChans, bits),
  324. streamStartPos (output != nullptr ? jmax (output->getPosition(), 0ll) : 0ll)
  325. {
  326. using namespace FlacNamespace;
  327. encoder = FLAC__stream_encoder_new();
  328. if (qualityOptionIndex > 0)
  329. FLAC__stream_encoder_set_compression_level (encoder, (uint32) jmin (8, qualityOptionIndex));
  330. FLAC__stream_encoder_set_do_mid_side_stereo (encoder, numChannels == 2);
  331. FLAC__stream_encoder_set_loose_mid_side_stereo (encoder, numChannels == 2);
  332. FLAC__stream_encoder_set_channels (encoder, numChannels);
  333. FLAC__stream_encoder_set_bits_per_sample (encoder, jmin ((unsigned int) 24, bitsPerSample));
  334. FLAC__stream_encoder_set_sample_rate (encoder, (unsigned int) sampleRate);
  335. FLAC__stream_encoder_set_blocksize (encoder, 0);
  336. FLAC__stream_encoder_set_do_escape_coding (encoder, true);
  337. ok = FLAC__stream_encoder_init_stream (encoder,
  338. encodeWriteCallback, encodeSeekCallback,
  339. encodeTellCallback, encodeMetadataCallback,
  340. this) == FLAC__STREAM_ENCODER_INIT_STATUS_OK;
  341. }
  342. ~FlacWriter()
  343. {
  344. if (ok)
  345. {
  346. FlacNamespace::FLAC__stream_encoder_finish (encoder);
  347. output->flush();
  348. }
  349. else
  350. {
  351. output = nullptr; // to stop the base class deleting this, as it needs to be returned
  352. // to the caller of createWriter()
  353. }
  354. FlacNamespace::FLAC__stream_encoder_delete (encoder);
  355. }
  356. //==============================================================================
  357. bool write (const int** samplesToWrite, int numSamples) override
  358. {
  359. using namespace FlacNamespace;
  360. if (! ok)
  361. return false;
  362. HeapBlock<int*> channels;
  363. HeapBlock<int> temp;
  364. const int bitsToShift = 32 - (int) bitsPerSample;
  365. if (bitsToShift > 0)
  366. {
  367. temp.malloc (numChannels * (size_t) numSamples);
  368. channels.calloc (numChannels + 1);
  369. for (unsigned int i = 0; i < numChannels; ++i)
  370. {
  371. if (samplesToWrite[i] == nullptr)
  372. break;
  373. int* const destData = temp.getData() + i * (size_t) numSamples;
  374. channels[i] = destData;
  375. for (int j = 0; j < numSamples; ++j)
  376. destData[j] = (samplesToWrite[i][j] >> bitsToShift);
  377. }
  378. samplesToWrite = const_cast<const int**> (channels.getData());
  379. }
  380. return FLAC__stream_encoder_process (encoder, (const FLAC__int32**) samplesToWrite, (unsigned) numSamples) != 0;
  381. }
  382. bool writeData (const void* const data, const int size) const
  383. {
  384. return output->write (data, (size_t) size);
  385. }
  386. static void packUint32 (FlacNamespace::FLAC__uint32 val, FlacNamespace::FLAC__byte* b, const int bytes)
  387. {
  388. b += bytes;
  389. for (int i = 0; i < bytes; ++i)
  390. {
  391. *(--b) = (FlacNamespace::FLAC__byte) (val & 0xff);
  392. val >>= 8;
  393. }
  394. }
  395. void writeMetaData (const FlacNamespace::FLAC__StreamMetadata* metadata)
  396. {
  397. using namespace FlacNamespace;
  398. const FLAC__StreamMetadata_StreamInfo& info = metadata->data.stream_info;
  399. unsigned char buffer [FLAC__STREAM_METADATA_STREAMINFO_LENGTH];
  400. const unsigned int channelsMinus1 = info.channels - 1;
  401. const unsigned int bitsMinus1 = info.bits_per_sample - 1;
  402. packUint32 (info.min_blocksize, buffer, 2);
  403. packUint32 (info.max_blocksize, buffer + 2, 2);
  404. packUint32 (info.min_framesize, buffer + 4, 3);
  405. packUint32 (info.max_framesize, buffer + 7, 3);
  406. buffer[10] = (uint8) ((info.sample_rate >> 12) & 0xff);
  407. buffer[11] = (uint8) ((info.sample_rate >> 4) & 0xff);
  408. buffer[12] = (uint8) (((info.sample_rate & 0x0f) << 4) | (channelsMinus1 << 1) | (bitsMinus1 >> 4));
  409. buffer[13] = (FLAC__byte) (((bitsMinus1 & 0x0f) << 4) | (unsigned int) ((info.total_samples >> 32) & 0x0f));
  410. packUint32 ((FLAC__uint32) info.total_samples, buffer + 14, 4);
  411. memcpy (buffer + 18, info.md5sum, 16);
  412. const bool seekOk = output->setPosition (streamStartPos + 4);
  413. ignoreUnused (seekOk);
  414. // if this fails, you've given it an output stream that can't seek! It needs
  415. // to be able to seek back to write the header
  416. jassert (seekOk);
  417. output->writeIntBigEndian (FLAC__STREAM_METADATA_STREAMINFO_LENGTH);
  418. output->write (buffer, FLAC__STREAM_METADATA_STREAMINFO_LENGTH);
  419. }
  420. //==============================================================================
  421. static FlacNamespace::FLAC__StreamEncoderWriteStatus encodeWriteCallback (const FlacNamespace::FLAC__StreamEncoder*,
  422. const FlacNamespace::FLAC__byte buffer[],
  423. size_t bytes,
  424. unsigned int /*samples*/,
  425. unsigned int /*current_frame*/,
  426. void* client_data)
  427. {
  428. using namespace FlacNamespace;
  429. return static_cast<FlacWriter*> (client_data)->writeData (buffer, (int) bytes)
  430. ? FLAC__STREAM_ENCODER_WRITE_STATUS_OK
  431. : FLAC__STREAM_ENCODER_WRITE_STATUS_FATAL_ERROR;
  432. }
  433. static FlacNamespace::FLAC__StreamEncoderSeekStatus encodeSeekCallback (const FlacNamespace::FLAC__StreamEncoder*, FlacNamespace::FLAC__uint64, void*)
  434. {
  435. using namespace FlacNamespace;
  436. return FLAC__STREAM_ENCODER_SEEK_STATUS_UNSUPPORTED;
  437. }
  438. static FlacNamespace::FLAC__StreamEncoderTellStatus encodeTellCallback (const FlacNamespace::FLAC__StreamEncoder*, FlacNamespace::FLAC__uint64* absolute_byte_offset, void* client_data)
  439. {
  440. using namespace FlacNamespace;
  441. if (client_data == nullptr)
  442. return FLAC__STREAM_ENCODER_TELL_STATUS_UNSUPPORTED;
  443. *absolute_byte_offset = (FLAC__uint64) static_cast<FlacWriter*> (client_data)->output->getPosition();
  444. return FLAC__STREAM_ENCODER_TELL_STATUS_OK;
  445. }
  446. static void encodeMetadataCallback (const FlacNamespace::FLAC__StreamEncoder*, const FlacNamespace::FLAC__StreamMetadata* metadata, void* client_data)
  447. {
  448. static_cast<FlacWriter*> (client_data)->writeMetaData (metadata);
  449. }
  450. bool ok;
  451. private:
  452. FlacNamespace::FLAC__StreamEncoder* encoder;
  453. int64 streamStartPos;
  454. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacWriter)
  455. };
  456. //==============================================================================
  457. FlacAudioFormat::FlacAudioFormat()
  458. : AudioFormat (flacFormatName, ".flac")
  459. {
  460. }
  461. FlacAudioFormat::~FlacAudioFormat()
  462. {
  463. }
  464. Array<int> FlacAudioFormat::getPossibleSampleRates()
  465. {
  466. const int rates[] = { 8000, 11025, 12000, 16000, 22050, 32000, 44100, 48000,
  467. 88200, 96000, 176400, 192000, 352800, 384000 };
  468. return Array<int> (rates, numElementsInArray (rates));
  469. }
  470. Array<int> FlacAudioFormat::getPossibleBitDepths()
  471. {
  472. const int depths[] = { 16, 24 };
  473. return Array<int> (depths, numElementsInArray (depths));
  474. }
  475. bool FlacAudioFormat::canDoStereo() { return true; }
  476. bool FlacAudioFormat::canDoMono() { return true; }
  477. bool FlacAudioFormat::isCompressed() { return true; }
  478. AudioFormatReader* FlacAudioFormat::createReaderFor (InputStream* in, const bool deleteStreamIfOpeningFails)
  479. {
  480. ScopedPointer<FlacReader> r (new FlacReader (in));
  481. if (r->sampleRate > 0)
  482. return r.release();
  483. if (! deleteStreamIfOpeningFails)
  484. r->input = nullptr;
  485. return nullptr;
  486. }
  487. AudioFormatWriter* FlacAudioFormat::createWriterFor (OutputStream* out,
  488. double sampleRate,
  489. unsigned int numberOfChannels,
  490. int bitsPerSample,
  491. const StringPairArray& /*metadataValues*/,
  492. int qualityOptionIndex)
  493. {
  494. if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample))
  495. {
  496. ScopedPointer<FlacWriter> w (new FlacWriter (out, sampleRate, numberOfChannels,
  497. (uint32) bitsPerSample, qualityOptionIndex));
  498. if (w->ok)
  499. return w.release();
  500. }
  501. return nullptr;
  502. }
  503. StringArray FlacAudioFormat::getQualityOptions()
  504. {
  505. static const char* options[] = { "0 (Fastest)", "1", "2", "3", "4", "5 (Default)","6", "7", "8 (Highest quality)", 0 };
  506. return StringArray (options);
  507. }
  508. #endif