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