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.

354 lines
13KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. namespace WindowsMediaCodec
  21. {
  22. class JuceIStream final : public ComBaseClassHelper<IStream>
  23. {
  24. public:
  25. JuceIStream (InputStream& in) noexcept
  26. : ComBaseClassHelper (0), source (in)
  27. {
  28. }
  29. JUCE_COMRESULT Commit (DWORD) { return S_OK; }
  30. JUCE_COMRESULT Write (const void*, ULONG, ULONG*) { return E_NOTIMPL; }
  31. JUCE_COMRESULT Clone (IStream**) { return E_NOTIMPL; }
  32. JUCE_COMRESULT SetSize (ULARGE_INTEGER) { return E_NOTIMPL; }
  33. JUCE_COMRESULT Revert() { return E_NOTIMPL; }
  34. JUCE_COMRESULT LockRegion (ULARGE_INTEGER, ULARGE_INTEGER, DWORD) { return E_NOTIMPL; }
  35. JUCE_COMRESULT UnlockRegion (ULARGE_INTEGER, ULARGE_INTEGER, DWORD) { return E_NOTIMPL; }
  36. JUCE_COMRESULT Read (void* dest, ULONG numBytes, ULONG* bytesRead)
  37. {
  38. auto numRead = source.read (dest, (size_t) numBytes);
  39. if (bytesRead != nullptr)
  40. *bytesRead = (ULONG) numRead;
  41. return (numRead == (int) numBytes) ? S_OK : S_FALSE;
  42. }
  43. JUCE_COMRESULT Seek (LARGE_INTEGER position, DWORD origin, ULARGE_INTEGER* resultPosition)
  44. {
  45. auto newPos = (int64) position.QuadPart;
  46. if (origin == STREAM_SEEK_CUR)
  47. {
  48. newPos += source.getPosition();
  49. }
  50. else if (origin == STREAM_SEEK_END)
  51. {
  52. auto len = source.getTotalLength();
  53. if (len < 0)
  54. return E_NOTIMPL;
  55. newPos += len;
  56. }
  57. if (resultPosition != nullptr)
  58. resultPosition->QuadPart = (ULONGLONG) newPos;
  59. return source.setPosition (newPos) ? S_OK : E_NOTIMPL;
  60. }
  61. JUCE_COMRESULT CopyTo (IStream* destStream, ULARGE_INTEGER numBytesToDo,
  62. ULARGE_INTEGER* bytesRead, ULARGE_INTEGER* bytesWritten)
  63. {
  64. uint64 totalCopied = 0;
  65. auto numBytes = (int64) numBytesToDo.QuadPart;
  66. while (numBytes > 0 && ! source.isExhausted())
  67. {
  68. char buffer [1024];
  69. auto numToCopy = (int) jmin ((int64) sizeof (buffer), (int64) numBytes);
  70. auto numRead = source.read (buffer, numToCopy);
  71. if (numRead <= 0)
  72. break;
  73. destStream->Write (buffer, (ULONG) numRead, nullptr);
  74. totalCopied += (ULONG) numRead;
  75. }
  76. if (bytesRead != nullptr) bytesRead->QuadPart = totalCopied;
  77. if (bytesWritten != nullptr) bytesWritten->QuadPart = totalCopied;
  78. return S_OK;
  79. }
  80. JUCE_COMRESULT Stat (STATSTG* stat, DWORD)
  81. {
  82. if (stat == nullptr)
  83. return STG_E_INVALIDPOINTER;
  84. zerostruct (*stat);
  85. stat->type = STGTY_STREAM;
  86. stat->cbSize.QuadPart = (ULONGLONG) jmax ((int64) 0, source.getTotalLength());
  87. return S_OK;
  88. }
  89. private:
  90. InputStream& source;
  91. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceIStream)
  92. };
  93. //==============================================================================
  94. static const char* wmFormatName = "Windows Media";
  95. static const char* const extensions[] = { ".mp3", ".wmv", ".asf", ".wm", ".wma", nullptr };
  96. //==============================================================================
  97. class WMAudioReader final : public AudioFormatReader
  98. {
  99. public:
  100. WMAudioReader (InputStream* const input_)
  101. : AudioFormatReader (input_, TRANS (wmFormatName)),
  102. wmvCoreLib ("Wmvcore.dll")
  103. {
  104. JUCE_LOAD_WINAPI_FUNCTION (wmvCoreLib, WMCreateSyncReader, wmCreateSyncReader,
  105. HRESULT, (IUnknown*, DWORD, IWMSyncReader**))
  106. if (wmCreateSyncReader != nullptr)
  107. {
  108. checkCoInitialiseCalled();
  109. HRESULT hr = wmCreateSyncReader (nullptr, WMT_RIGHT_PLAYBACK, wmSyncReader.resetAndGetPointerAddress());
  110. if (SUCCEEDED (hr))
  111. hr = wmSyncReader->OpenStream (new JuceIStream (*input));
  112. if (SUCCEEDED (hr))
  113. {
  114. WORD streamNum = 1;
  115. hr = wmSyncReader->GetStreamNumberForOutput (0, &streamNum);
  116. hr = wmSyncReader->SetReadStreamSamples (streamNum, false);
  117. scanFileForDetails();
  118. }
  119. }
  120. }
  121. ~WMAudioReader() override
  122. {
  123. if (wmSyncReader != nullptr)
  124. wmSyncReader->Close();
  125. }
  126. bool readSamples (int* const* destSamples, int numDestChannels, int startOffsetInDestBuffer,
  127. int64 startSampleInFile, int numSamples) override
  128. {
  129. if (sampleRate <= 0)
  130. return false;
  131. checkCoInitialiseCalled();
  132. clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer,
  133. startSampleInFile, numSamples, lengthInSamples);
  134. const auto stride = (int) (numChannels * sizeof (int16));
  135. while (numSamples > 0)
  136. {
  137. if (! bufferedRange.contains (startSampleInFile))
  138. {
  139. const bool hasJumped = (startSampleInFile != bufferedRange.getEnd());
  140. if (hasJumped)
  141. wmSyncReader->SetRange ((QWORD) (startSampleInFile * 10000000 / (int64) sampleRate), 0);
  142. ComSmartPtr<INSSBuffer> sampleBuffer;
  143. QWORD sampleTime, duration;
  144. DWORD flags, outputNum;
  145. WORD streamNum;
  146. HRESULT hr = wmSyncReader->GetNextSample (1, sampleBuffer.resetAndGetPointerAddress(),
  147. &sampleTime, &duration, &flags, &outputNum, &streamNum);
  148. if (sampleBuffer != nullptr)
  149. {
  150. BYTE* rawData = nullptr;
  151. DWORD dataLength = 0;
  152. hr = sampleBuffer->GetBufferAndLength (&rawData, &dataLength);
  153. if (dataLength == 0)
  154. return false;
  155. if (hasJumped)
  156. bufferedRange.setStart ((int64) ((sampleTime * (QWORD) sampleRate) / 10000000));
  157. else
  158. bufferedRange.setStart (bufferedRange.getEnd()); // (because the positions returned often aren't contiguous)
  159. bufferedRange.setLength ((int64) dataLength / (int64) stride);
  160. buffer.ensureSize ((size_t) dataLength);
  161. memcpy (buffer.getData(), rawData, (size_t) dataLength);
  162. }
  163. else if (hr == NS_E_NO_MORE_SAMPLES)
  164. {
  165. bufferedRange.setStart (startSampleInFile);
  166. bufferedRange.setLength (256);
  167. buffer.ensureSize (256 * (size_t) stride);
  168. buffer.fillWith (0);
  169. }
  170. else
  171. {
  172. return false;
  173. }
  174. }
  175. auto offsetInBuffer = (int) (startSampleInFile - bufferedRange.getStart());
  176. auto* rawData = static_cast<const int16*> (addBytesToPointer (buffer.getData(), offsetInBuffer * stride));
  177. auto numToDo = jmin (numSamples, (int) (bufferedRange.getLength() - offsetInBuffer));
  178. for (int i = 0; i < numDestChannels; ++i)
  179. {
  180. JUCE_BEGIN_IGNORE_WARNINGS_MSVC (28182)
  181. jassert (destSamples[i] != nullptr);
  182. auto srcChan = jmin (i, (int) numChannels - 1);
  183. const int16* src = rawData + srcChan;
  184. int* const dst = destSamples[i] + startOffsetInDestBuffer;
  185. for (int j = 0; j < numToDo; ++j)
  186. {
  187. dst[j] = (int) (((uint32) *src) << 16);
  188. src += numChannels;
  189. }
  190. JUCE_END_IGNORE_WARNINGS_MSVC
  191. }
  192. startSampleInFile += numToDo;
  193. startOffsetInDestBuffer += numToDo;
  194. numSamples -= numToDo;
  195. }
  196. return true;
  197. }
  198. private:
  199. DynamicLibrary wmvCoreLib;
  200. ComSmartPtr<IWMSyncReader> wmSyncReader;
  201. MemoryBlock buffer;
  202. Range<int64> bufferedRange;
  203. void checkCoInitialiseCalled()
  204. {
  205. [[maybe_unused]] const auto result = CoInitialize (nullptr);
  206. }
  207. void scanFileForDetails()
  208. {
  209. if (auto wmHeaderInfo = wmSyncReader.getInterface<IWMHeaderInfo>())
  210. {
  211. QWORD lengthInNanoseconds = 0;
  212. WORD lengthOfLength = sizeof (lengthInNanoseconds);
  213. WORD streamNum = 0;
  214. WMT_ATTR_DATATYPE wmAttrDataType;
  215. wmHeaderInfo->GetAttributeByName (&streamNum, L"Duration", &wmAttrDataType,
  216. (BYTE*) &lengthInNanoseconds, &lengthOfLength);
  217. if (auto wmProfile = wmSyncReader.getInterface<IWMProfile>())
  218. {
  219. ComSmartPtr<IWMStreamConfig> wmStreamConfig;
  220. auto hr = wmProfile->GetStream (0, wmStreamConfig.resetAndGetPointerAddress());
  221. if (SUCCEEDED (hr))
  222. {
  223. if (auto wmMediaProperties = wmStreamConfig.getInterface<IWMMediaProps>())
  224. {
  225. DWORD sizeMediaType;
  226. hr = wmMediaProperties->GetMediaType (nullptr, &sizeMediaType);
  227. HeapBlock<WM_MEDIA_TYPE> mediaType;
  228. mediaType.malloc (sizeMediaType, 1);
  229. hr = wmMediaProperties->GetMediaType (mediaType, &sizeMediaType);
  230. if (mediaType->majortype == WMMEDIATYPE_Audio)
  231. {
  232. auto* inputFormat = reinterpret_cast<WAVEFORMATEX*> (mediaType->pbFormat);
  233. sampleRate = inputFormat->nSamplesPerSec;
  234. numChannels = inputFormat->nChannels;
  235. bitsPerSample = inputFormat->wBitsPerSample != 0 ? inputFormat->wBitsPerSample : 16;
  236. lengthInSamples = (lengthInNanoseconds * (QWORD) sampleRate) / 10000000;
  237. }
  238. }
  239. }
  240. }
  241. }
  242. }
  243. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WMAudioReader)
  244. };
  245. }
  246. //==============================================================================
  247. WindowsMediaAudioFormat::WindowsMediaAudioFormat()
  248. : AudioFormat (TRANS (WindowsMediaCodec::wmFormatName),
  249. StringArray (WindowsMediaCodec::extensions))
  250. {
  251. }
  252. WindowsMediaAudioFormat::~WindowsMediaAudioFormat() {}
  253. Array<int> WindowsMediaAudioFormat::getPossibleSampleRates() { return {}; }
  254. Array<int> WindowsMediaAudioFormat::getPossibleBitDepths() { return {}; }
  255. bool WindowsMediaAudioFormat::canDoStereo() { return true; }
  256. bool WindowsMediaAudioFormat::canDoMono() { return true; }
  257. bool WindowsMediaAudioFormat::isCompressed() { return true; }
  258. //==============================================================================
  259. AudioFormatReader* WindowsMediaAudioFormat::createReaderFor (InputStream* sourceStream, bool deleteStreamIfOpeningFails)
  260. {
  261. std::unique_ptr<WindowsMediaCodec::WMAudioReader> r (new WindowsMediaCodec::WMAudioReader (sourceStream));
  262. if (r->sampleRate > 0)
  263. return r.release();
  264. if (! deleteStreamIfOpeningFails)
  265. r->input = nullptr;
  266. return nullptr;
  267. }
  268. AudioFormatWriter* WindowsMediaAudioFormat::createWriterFor (OutputStream* /*streamToWriteTo*/, double /*sampleRateToUse*/,
  269. unsigned int /*numberOfChannels*/, int /*bitsPerSample*/,
  270. const StringPairArray& /*metadataValues*/, int /*qualityOptionIndex*/)
  271. {
  272. jassertfalse; // not yet implemented!
  273. return nullptr;
  274. }
  275. } // namespace juce