OpenShot Audio Library | OpenShotAudio 0.4.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
26namespace juce
27{
28
29namespace WindowsMediaCodec
30{
31
32class JuceIStream final : public ComBaseClassHelper<IStream>
33{
34public:
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
119private:
120 InputStream& source;
121
122 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceIStream)
123};
124
125//==============================================================================
126static const char* wmFormatName = "Windows Media";
127static const char* const extensions[] = { ".mp3", ".wmv", ".asf", ".wm", ".wma", nullptr };
128
129//==============================================================================
130class WMAudioReader final : public AudioFormatReader
131{
132public:
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
257private:
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//==============================================================================
316WindowsMediaAudioFormat::WindowsMediaAudioFormat()
317 : AudioFormat (TRANS (WindowsMediaCodec::wmFormatName),
318 StringArray (WindowsMediaCodec::extensions))
319{
320}
321
322WindowsMediaAudioFormat::~WindowsMediaAudioFormat() = default;
323
324Array<int> WindowsMediaAudioFormat::getPossibleSampleRates() { return {}; }
325Array<int> WindowsMediaAudioFormat::getPossibleBitDepths() { return {}; }
326
327bool WindowsMediaAudioFormat::canDoStereo() { return true; }
328bool WindowsMediaAudioFormat::canDoMono() { return true; }
329bool WindowsMediaAudioFormat::isCompressed() { return true; }
330
331//==============================================================================
332AudioFormatReader* 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
345AudioFormatWriter* 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)