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