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