Merge lp:~mixxxdevelopers/mixxx/features_m4a_win7_plugin into lp:mixxx/1.10
- features_m4a_win7_plugin
- Merge into release-1.10.x
Status: | Merged |
---|---|
Merged at revision: | 2881 |
Proposed branch: | lp:~mixxxdevelopers/mixxx/features_m4a_win7_plugin |
Merge into: | lp:mixxx/1.10 |
Prerequisite: | lp:~mixxxdevelopers/mixxx/fixes-plugins-mempassing |
Diff against target: |
800 lines (+725/-2) 7 files modified
mixxx/SConstruct (+2/-0) mixxx/build/depends.py (+1/-2) mixxx/build/features.py (+32/-0) mixxx/plugins/SConscript (+6/-0) mixxx/plugins/soundsourcemediafoundation/SConscript (+22/-0) mixxx/plugins/soundsourcemediafoundation/soundsourcemediafoundation.cpp (+551/-0) mixxx/plugins/soundsourcemediafoundation/soundsourcemediafoundation.h (+111/-0) |
To merge this branch: | bzr merge lp:~mixxxdevelopers/mixxx/features_m4a_win7_plugin |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
RJ Skerry-Ryan | Approve | ||
William Good | Needs Fixing | ||
Review via email: mp+78777@code.launchpad.net |
Commit message
Description of the change
Replacing https:/
I was initially against the plugin thing but with the fixes in lp:~mixxxdevelopers/mixxx/fixes-plugins-mempassing it should be stable for production.
Anyway, so this way we can package a plugin DLL with the normal installers, and if the requisite interfaces/
Proposed for 1.10 because it works, and m4a decoding was targeted for like 2 major versions ago so it'd be way cool to have in 1.10. It does feel a bit slower than most of our decoders (to me), but that's Media Foundation's fault, as far as I can tell. CachingReader really mitigates the issue for all intents and purposes, though.
- 2850. By William Good
-
Merged from lp:~mixxxdevelopers/mixxx/fixes-plugins-mempassing. Updated SoundSourceMedi
aFoundation to new plugin API.
RJ Skerry-Ryan (rryan) wrote : | # |
Hey Bill,
What's the status of the branch? Do you think it's stable enough to be in 1.10?
If so, I'd like to figure out how to get it packaged for the beta so we can work out the kinks. It only works for Vista onwards because of the WMF dependency right? We may need Sean's help updating the NSIS to only install the plugin on >=Vista.
RJ Skerry-Ryan (rryan) wrote : | # |
Also, just to make sure I'm right here -- is the mempassing branch now fully merged into this one or should I also review that branch?
William Good (bkgood) wrote : | # |
Yeah, I think it's good to go. We really could be sloppy with it and
install it anywhere as if Mixxx can't load the DLL (due to unresolved
symbols) it'll happily continue on. It's also possible that a user
could install Mixxx, and then install the necessary update that gives
Vista the AAC decoder, and they ought not have to reinstall Mixxx if
possible.
On Wed, Oct 19, 2011 at 11:57 AM, RJ Ryan <email address hidden> wrote:
> Hey Bill,
>
> What's the status of the branch? Do you think it's stable enough to be in 1.10?
>
> If so, I'd like to figure out how to get it packaged for the beta so we can work out the kinks. It only works for Vista onwards because of the WMF dependency right? We may need Sean's help updating the NSIS to only install the plugin on >=Vista.
> --
> https:/
> You proposed lp:~mixxxdevelopers/mixxx/features_m4a_win7_plugin for merging.
>
William Good (bkgood) wrote : | # |
mempassing hasn't been merged and needs review, it needs to be merged
before this branch is.
On Wed, Oct 19, 2011 at 11:58 AM, RJ Ryan <email address hidden> wrote:
> Also, just to make sure I'm right here -- is the mempassing branch now fully merged into this one or should I also review that branch?
> --
> https:/
> You proposed lp:~mixxxdevelopers/mixxx/features_m4a_win7_plugin for merging.
>
RJ Skerry-Ryan (rryan) wrote : | # |
In seek(): If the original setCurrentPosition fails, in other SoundSources I think we generally return the current position (i.e. the current location of the reader) to indicate that the seek failed.
Also, could you define a "const static bool sDebug" at the top of the soundsourcemedi
RJ Skerry-Ryan (rryan) wrote : | # |
It all looks pretty good to me. I didn't delve much into the API and its edge cases since I assume you and Albert both did that. In favor of getting quick feedback I'm merging this in before the 1.10 beta. We'll fix any problems that crop up on the fly. Thanks Bill!
Preview Diff
1 | === modified file 'mixxx/SConstruct' |
2 | --- mixxx/SConstruct 2011-10-07 14:33:18 +0000 |
3 | +++ mixxx/SConstruct 2011-10-11 02:00:31 +0000 |
4 | @@ -36,6 +36,7 @@ |
5 | features.MIDIScript, |
6 | features.Mad, |
7 | features.CoreAudio, |
8 | + features.MediaFoundation, |
9 | features.HSS1394, |
10 | features.VinylControl, |
11 | features.Shoutcast, |
12 | @@ -77,6 +78,7 @@ |
13 | # and link properly. This sucks but it's the best way I can find -- bkgood |
14 | VariantDir("plugins/soundsourcem4a", "src", duplicate=0) |
15 | VariantDir("plugins/soundsourcewv", "src", duplicate=0) |
16 | +VariantDir("plugins/soundsourcemediafoundation", "src", duplicate=0) |
17 | |
18 | # Build our soundsource plugins |
19 | soundsource_plugins = SConscript( |
20 | |
21 | === modified file 'mixxx/build/depends.py' |
22 | --- mixxx/build/depends.py 2011-10-07 06:02:31 +0000 |
23 | +++ mixxx/build/depends.py 2011-10-11 02:00:31 +0000 |
24 | @@ -97,7 +97,6 @@ |
25 | def sources(self, build): |
26 | return ['soundsourceflac.cpp',] |
27 | |
28 | - |
29 | class Qt(Dependence): |
30 | DEFAULT_QTDIRS = {'linux': '/usr/share/qt4', |
31 | 'bsd': '/usr/local/lib/qt4', |
32 | @@ -740,7 +739,7 @@ |
33 | |
34 | def depends(self, build): |
35 | return [SoundTouch, KissFFT, ReplayGain, PortAudio, PortMIDI, Qt, |
36 | - FidLib, SndFile, FLAC, OggVorbis, OpenGL, TagLib] |
37 | + FidLib, SndFile, FLAC, OggVorbis, OpenGL, TagLib,] |
38 | |
39 | def post_dependency_check_configure(self, build, conf): |
40 | """Sets up additional things in the Environment that must happen |
41 | |
42 | === modified file 'mixxx/build/features.py' |
43 | --- mixxx/build/features.py 2011-10-07 06:02:31 +0000 |
44 | +++ mixxx/build/features.py 2011-10-11 02:00:31 +0000 |
45 | @@ -104,6 +104,38 @@ |
46 | return ['soundsourcecoreaudio.cpp', |
47 | '#lib/apple/CAStreamBasicDescription.h'] |
48 | |
49 | +class MediaFoundation(Feature): |
50 | + FLAG = 'mediafoundation' |
51 | + def description(self): |
52 | + return "Media Foundation AAC Decoder Plugin" |
53 | + def enabled(self, build): |
54 | + build.flags[self.FLAG] = util.get_flags(build.env, self.FLAG, 0) |
55 | + if int(build.flags[self.FLAG]): |
56 | + return True |
57 | + return False |
58 | + def add_options(self, build, vars): |
59 | + vars.Add(self.FLAG, "Set to 1 to enable the Media Foundation AAC decoder plugin (Windows Vista with KB2117917 or Windows 7 required)", 0) |
60 | + def configure(self, build, conf): |
61 | + if not self.enabled(build): |
62 | + return |
63 | + if not build.platform_is_windows: |
64 | + raise Exception("Media Foundation is only supported on Windows!") |
65 | + # need to look into this, SDK 6 might be ok? |
66 | + try: |
67 | + include_path = os.path.join(os.environ["MSSdk"], "Include") |
68 | + except KeyError: |
69 | + raise Exception("MSSdk environment variable not set, have you run setenv?") |
70 | + build.env.Append(CPPPATH=[include_path]) |
71 | + if not conf.CheckLib('Ole32'): |
72 | + raise Exception('Did not find Ole32.lib - exiting!') |
73 | + if not conf.CheckLib(['Mfuuid']): |
74 | + raise Exception('Did not find Mfuuid.lib - exiting!') |
75 | + if not conf.CheckLib(['Mfplat']): |
76 | + raise Exception('Did not find Mfplat.lib - exiting!') |
77 | + if not conf.CheckLib(['Mfreadwrite']): #Only available on Windows 7 and up, or properly updated Vista |
78 | + raise Exception('Did not find Mfreadwrite.lib - exiting!') |
79 | + build.env.Append(CPPDEFINES='__MEDIAFOUNDATION__') |
80 | + return |
81 | |
82 | class MIDIScript(Feature): |
83 | def description(self): |
84 | |
85 | === modified file 'mixxx/plugins/SConscript' |
86 | --- mixxx/plugins/SConscript 2011-10-07 18:45:59 +0000 |
87 | +++ mixxx/plugins/SConscript 2011-10-11 02:00:31 +0000 |
88 | @@ -17,4 +17,10 @@ |
89 | duplicate=0, exports=['build']) |
90 | plugins.extend(soundsourcewv) |
91 | |
92 | +soundsourcemediafoundation = SConscript( |
93 | + File('soundsourcemediafoundation/SConscript'), |
94 | + build_dir=Dir(build.build_dir + "/mediafoundation"), |
95 | + duplicate=0, exports=['build']) |
96 | +plugins.extend(soundsourcemediafoundation) |
97 | + |
98 | Return("plugins") |
99 | |
100 | === added directory 'mixxx/plugins/soundsourcemediafoundation' |
101 | === added file 'mixxx/plugins/soundsourcemediafoundation/SConscript' |
102 | --- mixxx/plugins/soundsourcemediafoundation/SConscript 1970-01-01 00:00:00 +0000 |
103 | +++ mixxx/plugins/soundsourcemediafoundation/SConscript 2011-10-11 02:00:31 +0000 |
104 | @@ -0,0 +1,22 @@ |
105 | +#!/usr/bin/env python |
106 | +# -*- coding: utf-8 -*- |
107 | +import os |
108 | +import sys |
109 | +import SCons |
110 | +import shutil |
111 | + |
112 | +Import('build') |
113 | + |
114 | +if int(build.flags['mediafoundation']): |
115 | + env = build.env.Clone() |
116 | + conf = Configure(env) |
117 | + env = conf.Finish() |
118 | + env["LINKFLAGS"].remove("/entry:mainCRTStartup") |
119 | + env["LINKFLAGS"].remove("/subsystem:windows") |
120 | + ssmediafoundation_bin = env.SharedLibrary('soundsourcemediafoundation', |
121 | + ['soundsource.cpp', 'soundsourcemediafoundation.cpp'], |
122 | + LINKCOM = [env['LINKCOM'], |
123 | + 'mt.exe -nologo -manifest ${TARGET}.manifest -outputresource:$TARGET;1']) |
124 | + Return("ssmediafoundation_bin") |
125 | +else: |
126 | + Return("") |
127 | \ No newline at end of file |
128 | |
129 | === added file 'mixxx/plugins/soundsourcemediafoundation/soundsourcemediafoundation.cpp' |
130 | --- mixxx/plugins/soundsourcemediafoundation/soundsourcemediafoundation.cpp 1970-01-01 00:00:00 +0000 |
131 | +++ mixxx/plugins/soundsourcemediafoundation/soundsourcemediafoundation.cpp 2011-10-11 02:00:31 +0000 |
132 | @@ -0,0 +1,551 @@ |
133 | +/** |
134 | + * \file soundsourcemediafoundation.cpp |
135 | + * \author Bill Good <bkgood at gmail dot com> |
136 | + * \author Albert Santoni <alberts at mixxx dot org> |
137 | + * \date Jan 10, 2011 |
138 | + * \note This file uses COM interfaces defined in Windows 7 and later added to |
139 | + * Vista and Server 2008 via the "Platform Update Supplement for Windows Vista |
140 | + * and for Windows Server 2008" (http://support.microsoft.com/kb/2117917). |
141 | + * Earlier versions of Vista (and possibly Server 2008) have some Media |
142 | + * Foundation interfaces but not the required IMFSourceReader, and are missing |
143 | + * the Microsoft-provided AAC decoder. XP does not include Media Foundation. |
144 | + */ |
145 | + |
146 | +/*************************************************************************** |
147 | + * * |
148 | + * This program is free software; you can redistribute it and/or modify * |
149 | + * it under the terms of the GNU General Public License as published by * |
150 | + * the Free Software Foundation; either version 2 of the License, or * |
151 | + * (at your option) any later version. * |
152 | + * * |
153 | + ***************************************************************************/ |
154 | + |
155 | +#include <QtDebug> |
156 | +#include <taglib/mp4file.h> |
157 | +#include <windows.h> |
158 | +#include <mfapi.h> |
159 | +#include <mfidl.h> |
160 | +#include <mfreadwrite.h> |
161 | +#include <mferror.h> |
162 | +#include <propvarutil.h> |
163 | + |
164 | +#include "soundsourcemediafoundation.h" |
165 | + |
166 | +const int kBitsPerSample = 16; |
167 | +const int kNumChannels = 2; |
168 | +const int kSampleRate = 44100; |
169 | +const int kLeftoverSize = 4096; // in int16's, this seems to be the size MF AAC |
170 | +// decoder likes to give |
171 | + |
172 | +/** Microsoft examples use this snippet often. */ |
173 | +template<class T> static void safeRelease(T **ppT) |
174 | +{ |
175 | + if (*ppT) { |
176 | + (*ppT)->Release(); |
177 | + *ppT = NULL; |
178 | + } |
179 | +} |
180 | + |
181 | +SoundSourceMediaFoundation::SoundSourceMediaFoundation(QString filename) |
182 | + : SoundSource(filename) |
183 | + , m_file(filename) |
184 | + , m_pReader(NULL) |
185 | + , m_pAudioType(NULL) |
186 | + , m_wcFilename(NULL) |
187 | + , m_nextFrame(0) |
188 | + , m_leftoverBuffer(NULL) |
189 | + , m_leftoverBufferSize(0) |
190 | + , m_leftoverBufferLength(0) |
191 | + , m_leftoverBufferPosition(0) |
192 | + , m_mfDuration(0) |
193 | + , m_dead(false) |
194 | + , m_seeking(false) |
195 | +{ |
196 | + // these are always the same, might as well just stick them here |
197 | + // -bkgood |
198 | + m_iChannels = kNumChannels; |
199 | + m_iSampleRate = kSampleRate; |
200 | + |
201 | + // http://social.msdn.microsoft.com/Forums/en/netfxbcl/thread/35c6a451-3507-40c8-9d1c-8d4edde7c0cc |
202 | + // gives maximum path + file length as 248 + 260, using that -bkgood |
203 | + m_wcFilename = new wchar_t[248 + 260]; |
204 | +} |
205 | + |
206 | +SoundSourceMediaFoundation::~SoundSourceMediaFoundation() |
207 | +{ |
208 | + delete [] m_wcFilename; |
209 | + delete [] m_leftoverBuffer; |
210 | + |
211 | + safeRelease(&m_pReader); |
212 | + safeRelease(&m_pAudioType); |
213 | + MFShutdown(); |
214 | + CoUninitialize(); |
215 | +} |
216 | + |
217 | +int SoundSourceMediaFoundation::open() |
218 | +{ |
219 | + QString qurlStr(m_qFilename); |
220 | + int wcFilenameLength(m_qFilename.toWCharArray(m_wcFilename)); |
221 | + // toWCharArray does not append a null terminator to the string! |
222 | + m_wcFilename[wcFilenameLength] = '\0'; |
223 | + |
224 | + HRESULT hr(S_OK); |
225 | + // Initialize the COM library. |
226 | + hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); |
227 | + if (FAILED(hr)) { |
228 | + qWarning() << "SSMF: failed to initialize COM"; |
229 | + return ERR; |
230 | + } |
231 | + |
232 | + // Initialize the Media Foundation platform. |
233 | + hr = MFStartup(MF_VERSION); |
234 | + if (FAILED(hr)) { |
235 | + qWarning() << "SSMF: failed to initialize Media Foundation"; |
236 | + return ERR; |
237 | + } |
238 | + |
239 | + // Create the source reader to read the input file. |
240 | + hr = MFCreateSourceReaderFromURL(m_wcFilename, NULL, &m_pReader); |
241 | + if (FAILED(hr)) { |
242 | + qWarning() << "SSMF: Error opening input file:" << m_qFilename; |
243 | + return ERR; |
244 | + } |
245 | + |
246 | + hr = configureAudioStream(); |
247 | + if (FAILED(hr)) { |
248 | + qWarning() << "SSMF: Error configuring audio stream."; |
249 | + return ERR; |
250 | + } |
251 | + |
252 | + if (!readProperties()) { |
253 | + qWarning() << "SSMF::readProperties failed"; |
254 | + return ERR; |
255 | + } |
256 | + |
257 | + //Seek to position 0, which forces us to skip over all the header frames. |
258 | + //This makes sure we're ready to just let the Analyser rip and it'll |
259 | + //get the number of samples it expects (ie. no header frames). |
260 | + seek(0); |
261 | + |
262 | + return OK; |
263 | +} |
264 | + |
265 | +long SoundSourceMediaFoundation::seek(long filepos) |
266 | +{ |
267 | + PROPVARIANT prop; |
268 | + HRESULT hr(S_OK); |
269 | + qint64 seekTarget(filepos / kNumChannels); |
270 | + qint64 mfSeekTarget(mfFromFrame(seekTarget) - 1); |
271 | + // minus 1 here seems to make our seeking work properly, otherwise we will |
272 | + // (more often than not, maybe always) seek a bit too far (although not |
273 | + // enough for our calculatedFrameFromMF <= nextFrame assertion in ::read). |
274 | + // Has something to do with 100ns MF units being much smaller than most |
275 | + // frame offsets (in seconds) -bkgood |
276 | + |
277 | + if (m_dead) { |
278 | + return filepos; |
279 | + } |
280 | + |
281 | + // this doesn't fail, see MS's implementation |
282 | + hr = InitPropVariantFromInt64(mfSeekTarget < 0 ? 0 : mfSeekTarget, &prop); |
283 | + |
284 | + // http://msdn.microsoft.com/en-us/library/dd374668(v=VS.85).aspx |
285 | + hr = m_pReader->SetCurrentPosition(GUID_NULL, prop); |
286 | + if (FAILED(hr)) { |
287 | + // nothing we can do here as we can't fail (no facility to other than |
288 | + // crashing mixxx) |
289 | + qWarning() << "SSMF: failed to seek" << ( |
290 | + hr == MF_E_INVALIDREQUEST ? "Sample requests still pending" : ""); |
291 | + } else { |
292 | + hr = m_pReader->Flush(MF_SOURCE_READER_FIRST_AUDIO_STREAM); |
293 | + if (FAILED(hr)) { |
294 | + qWarning() << "SSMF: failed to flush after seek"; |
295 | + } |
296 | + } |
297 | + PropVariantClear(&prop); |
298 | + |
299 | + // record the next frame so that we can make sure we're there the next |
300 | + // time we get a buffer from MFSourceReader |
301 | + m_nextFrame = seekTarget; |
302 | + m_seeking = true; |
303 | + |
304 | + return filepos; |
305 | +} |
306 | + |
307 | +unsigned int SoundSourceMediaFoundation::read(unsigned long size, |
308 | + const SAMPLE *destination) |
309 | +{ |
310 | + SAMPLE *destBuffer(const_cast<SAMPLE*>(destination)); |
311 | + size_t framesRequested(size / kNumChannels); |
312 | + size_t framesNeeded(framesRequested); |
313 | + |
314 | + // first, copy frames from leftover buffer IF the leftover buffer is at |
315 | + // the correct frame |
316 | + if (m_leftoverBufferLength > 0 && m_leftoverBufferPosition == m_nextFrame) { |
317 | + copyFrames(destBuffer, &framesNeeded, m_leftoverBuffer, |
318 | + m_leftoverBufferLength); |
319 | + if (m_leftoverBufferLength > 0) { |
320 | + Q_ASSERT(framesNeeded == 0); // make sure CopyFrames worked |
321 | + m_leftoverBufferPosition += framesRequested; |
322 | + } |
323 | + } else { |
324 | + // leftoverBuffer already empty or in the wrong position, clear it |
325 | + m_leftoverBufferLength = 0; |
326 | + } |
327 | + |
328 | + while (!m_dead && framesNeeded > 0) { |
329 | + HRESULT hr(S_OK); |
330 | + DWORD dwFlags(0); |
331 | + qint64 timestamp(0); |
332 | + IMFSample *pSample(NULL); |
333 | + bool error(false); // set to true to break after releasing |
334 | + |
335 | + hr = m_pReader->ReadSample( |
336 | + MF_SOURCE_READER_FIRST_AUDIO_STREAM, // [in] DWORD dwStreamIndex, |
337 | + 0, // [in] DWORD dwControlFlags, |
338 | + NULL, // [out] DWORD *pdwActualStreamIndex, |
339 | + &dwFlags, // [out] DWORD *pdwStreamFlags, |
340 | + ×tamp, // [out] LONGLONG *pllTimestamp, |
341 | + &pSample); // [out] IMFSample **ppSample |
342 | + if (FAILED(hr)) break; |
343 | + if (dwFlags & MF_SOURCE_READERF_ERROR) { |
344 | + // our source reader is now dead, according to the docs |
345 | + qWarning() << "SSMF: ReadSample set ERROR, SourceReader is now dead"; |
346 | + m_dead = true; |
347 | + break; |
348 | + } else if (dwFlags & MF_SOURCE_READERF_ENDOFSTREAM) { |
349 | + qDebug() << "SSMF: End of input file."; |
350 | + break; |
351 | + } else if (dwFlags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED) { |
352 | + qWarning() << "SSMF: Type change"; |
353 | + break; |
354 | + } else if (pSample == NULL) { |
355 | + // generally this will happen when dwFlags contains ENDOFSTREAM, |
356 | + // so it'll be caught before now -bkgood |
357 | + qWarning() << "SSMF: No sample"; |
358 | + continue; |
359 | + } // we now own a ref to the instance at pSample |
360 | + |
361 | + IMFMediaBuffer *pMBuffer(NULL); |
362 | + // I know this does at least a memcopy and maybe a malloc, if we have |
363 | + // xrun issues with this we might want to look into using |
364 | + // IMFSample::GetBufferByIndex (although MS doesn't recommend this) |
365 | + if (FAILED(hr = pSample->ConvertToContiguousBuffer(&pMBuffer))) { |
366 | + error = true; |
367 | + goto releaseSample; |
368 | + } |
369 | + qint16 *buffer(NULL); |
370 | + size_t bufferLength(0); |
371 | + hr = pMBuffer->Lock(reinterpret_cast<quint8**>(&buffer), NULL, |
372 | + reinterpret_cast<DWORD*>(&bufferLength)); |
373 | + if (FAILED(hr)) { |
374 | + error = true; |
375 | + goto releaseMBuffer; |
376 | + } |
377 | + bufferLength /= (kBitsPerSample / 8 * kNumChannels); // now in frames |
378 | + |
379 | + if (m_seeking) { |
380 | + qint64 bufferPosition(frameFromMF(timestamp)); |
381 | + Q_ASSERT(m_nextFrame >= bufferPosition); // we can never go |
382 | + // backwards here in ::read, so if the seek didn't manage to take |
383 | + // us far back it's important to fail |
384 | + if (m_nextFrame == bufferPosition) { |
385 | + m_seeking = false; |
386 | + } else if (m_nextFrame < bufferPosition + bufferLength) { |
387 | + // nextFrame is in this buffer |
388 | + buffer += (m_nextFrame - bufferPosition) * kNumChannels; |
389 | + bufferLength -= m_nextFrame - bufferPosition; |
390 | + m_seeking = false; |
391 | + } else { // we need to keep going forward |
392 | + goto releaseRawBuffer; |
393 | + } |
394 | + } |
395 | + |
396 | + Q_ASSERT(bufferLength * kNumChannels <= m_leftoverBufferSize); |
397 | + copyFrames(destBuffer + (size - framesNeeded * kNumChannels), |
398 | + &framesNeeded, buffer, bufferLength); |
399 | + |
400 | +releaseRawBuffer: |
401 | + hr = pMBuffer->Unlock(); |
402 | + // I'm ignoring this, MSDN for IMFMediaBuffer::Unlock stipulates |
403 | + // nothing about the state of the instance if this fails so might as |
404 | + // well just let it be released. |
405 | + //if (FAILED(hr)) break; |
406 | +releaseMBuffer: |
407 | + safeRelease(&pMBuffer); |
408 | +releaseSample: |
409 | + safeRelease(&pSample); |
410 | + if (error) break; |
411 | + } |
412 | + |
413 | + m_nextFrame += framesRequested - framesNeeded; |
414 | + if (m_leftoverBufferLength > 0) { |
415 | + Q_ASSERT(framesNeeded == 0); // make sure CopyFrames worked |
416 | + m_leftoverBufferPosition = m_nextFrame; |
417 | + } |
418 | + return size - framesNeeded * kNumChannels; |
419 | +} |
420 | + |
421 | +inline unsigned long SoundSourceMediaFoundation::length() |
422 | +{ |
423 | + unsigned long len(secondsFromMF(m_mfDuration) * kSampleRate * kNumChannels); |
424 | + return len % kNumChannels == 0 ? len : len + 1; |
425 | +} |
426 | + |
427 | +int SoundSourceMediaFoundation::parseHeader() |
428 | +{ |
429 | + setType("m4a"); |
430 | + |
431 | + TagLib::MP4::File f(getFilename().toLocal8Bit().constData()); |
432 | + bool result = processTaglibFile(f); |
433 | + TagLib::MP4::Tag* tag = f.tag(); |
434 | + |
435 | + if (tag) { |
436 | + processMP4Tag(tag); |
437 | + } |
438 | + |
439 | + if (result) |
440 | + return OK; |
441 | + return ERR; |
442 | +} |
443 | + |
444 | + |
445 | +// static |
446 | +QList<QString> SoundSourceMediaFoundation::supportedFileExtensions() |
447 | +{ |
448 | + QList<QString> list; |
449 | + list.push_back("m4a"); |
450 | + list.push_back("mp4"); |
451 | + return list; |
452 | +} |
453 | + |
454 | + |
455 | +//------------------------------------------------------------------- |
456 | +// configureAudioStream |
457 | +// |
458 | +// Selects an audio stream from the source file, and configures the |
459 | +// stream to deliver decoded PCM audio. |
460 | +//------------------------------------------------------------------- |
461 | + |
462 | +/** Cobbled together from: |
463 | + http://msdn.microsoft.com/en-us/library/dd757929(v=vs.85).aspx |
464 | + and http://msdn.microsoft.com/en-us/library/dd317928(VS.85).aspx |
465 | + -- Albert |
466 | + If anything in here fails, just bail. I'm not going to decode HRESULTS. |
467 | + -- Bill |
468 | + */ |
469 | +bool SoundSourceMediaFoundation::configureAudioStream() |
470 | +{ |
471 | + HRESULT hr(S_OK); |
472 | + |
473 | + // deselect all streams, we only want the first |
474 | + hr = m_pReader->SetStreamSelection(MF_SOURCE_READER_ALL_STREAMS, false); |
475 | + if (FAILED(hr)) { |
476 | + qWarning() << "SSMF: failed to deselect all streams"; |
477 | + return false; |
478 | + } |
479 | + |
480 | + hr = m_pReader->SetStreamSelection(MF_SOURCE_READER_FIRST_AUDIO_STREAM, true); |
481 | + if (FAILED(hr)) { |
482 | + qWarning() << "SSMF: failed to select first audio stream"; |
483 | + return false; |
484 | + } |
485 | + |
486 | + hr = MFCreateMediaType(&m_pAudioType); |
487 | + if (FAILED(hr)) { |
488 | + qWarning() << "SSMF: failed to create media type"; |
489 | + return false; |
490 | + } |
491 | + |
492 | + hr = m_pAudioType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); |
493 | + if (FAILED(hr)) { |
494 | + qWarning() << "SSMF: failed to set major type"; |
495 | + return false; |
496 | + } |
497 | + |
498 | + hr = m_pAudioType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM); |
499 | + if (FAILED(hr)) { |
500 | + qWarning() << "SSMF: failed to set subtype"; |
501 | + return false; |
502 | + } |
503 | + |
504 | + hr = m_pAudioType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, true); |
505 | + if (FAILED(hr)) { |
506 | + qWarning() << "SSMF: failed to set samples independent"; |
507 | + return false; |
508 | + } |
509 | + |
510 | + hr = m_pAudioType->SetUINT32(MF_MT_FIXED_SIZE_SAMPLES, true); |
511 | + if (FAILED(hr)) { |
512 | + qWarning() << "SSMF: failed to set fixed size samples"; |
513 | + return false; |
514 | + } |
515 | + |
516 | + hr = m_pAudioType->SetUINT32(MF_MT_SAMPLE_SIZE, kLeftoverSize); |
517 | + if (FAILED(hr)) { |
518 | + qWarning() << "SSMF: failed to set sample size"; |
519 | + return false; |
520 | + } |
521 | + |
522 | + // MSDN for this attribute says that if bps is 8, samples are unsigned. |
523 | + // Otherwise, they're signed (so they're signed for us as 16 bps). Why |
524 | + // chose to hide this rather useful tidbit here is beyond me -bkgood |
525 | + hr = m_pAudioType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, kBitsPerSample); |
526 | + if (FAILED(hr)) { |
527 | + qWarning() << "SSMF: failed to set bits per sample"; |
528 | + return false; |
529 | + } |
530 | + |
531 | + hr = m_pAudioType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, |
532 | + kNumChannels * (kBitsPerSample / 8)); |
533 | + if (FAILED(hr)) { |
534 | + qWarning() << "SSMF: failed to set block alignment"; |
535 | + return false; |
536 | + } |
537 | + |
538 | + hr = m_pAudioType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, kNumChannels); |
539 | + if (FAILED(hr)) { |
540 | + qWarning() << "SSMF: failed to set number of channels"; |
541 | + return false; |
542 | + } |
543 | + |
544 | + hr = m_pAudioType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, kSampleRate); |
545 | + if (FAILED(hr)) { |
546 | + qWarning() << "SSMF: failed to set sample rate"; |
547 | + return false; |
548 | + } |
549 | + |
550 | + // Set this type on the source reader. The source reader will |
551 | + // load the necessary decoder. |
552 | + hr = m_pReader->SetCurrentMediaType( |
553 | + MF_SOURCE_READER_FIRST_AUDIO_STREAM, |
554 | + NULL, m_pAudioType); |
555 | + |
556 | + // the reader has the media type now, free our reference so we can use our |
557 | + // pointer for other purposes. Do this before checking for failure so we |
558 | + // don't dangle. |
559 | + safeRelease(&m_pAudioType); |
560 | + if (FAILED(hr)) { |
561 | + qWarning() << "SSMF: failed to set media type"; |
562 | + return false; |
563 | + } |
564 | + |
565 | + // Get the complete uncompressed format. |
566 | + hr = m_pReader->GetCurrentMediaType( |
567 | + MF_SOURCE_READER_FIRST_AUDIO_STREAM, |
568 | + &m_pAudioType); |
569 | + if (FAILED(hr)) { |
570 | + qWarning() << "SSMF: failed to retrieve completed media type"; |
571 | + return false; |
572 | + } |
573 | + |
574 | + // Ensure the stream is selected. |
575 | + hr = m_pReader->SetStreamSelection( |
576 | + MF_SOURCE_READER_FIRST_AUDIO_STREAM, |
577 | + true); |
578 | + if (FAILED(hr)) { |
579 | + qWarning() << "SSMF: failed to select first audio stream (again)"; |
580 | + return false; |
581 | + } |
582 | + |
583 | + // this may not be safe on all platforms as m_leftoverBufferSize is a |
584 | + // size_t and this function is writing a uint32. However, on 32-bit |
585 | + // Windows 7, size_t is defined as uint which is 32-bits, so we're safe |
586 | + // for all supported platforms -bkgood |
587 | + hr = m_pAudioType->GetUINT32(MF_MT_SAMPLE_SIZE, &m_leftoverBufferSize); |
588 | + if (FAILED(hr)) { |
589 | + qWarning() << "SSMF: failed to get buffer size"; |
590 | + return false; |
591 | + } |
592 | + m_leftoverBufferSize /= 2; // convert size in bytes to size in int16s |
593 | + m_leftoverBuffer = new qint16[m_leftoverBufferSize]; |
594 | + |
595 | + return true; |
596 | +} |
597 | + |
598 | +bool SoundSourceMediaFoundation::readProperties() |
599 | +{ |
600 | + PROPVARIANT prop; |
601 | + HRESULT hr = S_OK; |
602 | + |
603 | + //Get the duration, provided as a 64-bit integer of 100-nanosecond units |
604 | + hr = m_pReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, |
605 | + MF_PD_DURATION, &prop); |
606 | + if (FAILED(hr)) { |
607 | + qWarning() << "SSMF: error getting duration"; |
608 | + return false; |
609 | + } |
610 | + // QuadPart isn't available on compilers that don't support _int64. Visual |
611 | + // Studio 6.0 introduced the type in 1998, so I think we're safe here |
612 | + // -bkgood |
613 | + m_iDuration = secondsFromMF(prop.hVal.QuadPart); |
614 | + m_mfDuration = prop.hVal.QuadPart; |
615 | + qDebug() << "SSMF: Duration:" << m_iDuration; |
616 | + PropVariantClear(&prop); |
617 | + |
618 | + // presentation attribute MF_PD_AUDIO_ENCODING_BITRATE only exists for |
619 | + // presentation descriptors, one of which MFSourceReader is not. |
620 | + // Therefore, we calculate it ourselves. |
621 | + m_iBitrate = kBitsPerSample * kSampleRate * kNumChannels; |
622 | + |
623 | + return true; |
624 | +} |
625 | + |
626 | +/** |
627 | + * Copies min(destFrames, srcFrames) frames to dest from src. Anything leftover |
628 | + * is moved to the beginning of m_leftoverBuffer, so empty it first (possibly |
629 | + * with this method). If src and dest overlap, I'll hurt you. |
630 | + */ |
631 | +void SoundSourceMediaFoundation::copyFrames( |
632 | + qint16 *dest, size_t *destFrames, const qint16 *src, size_t srcFrames) |
633 | +{ |
634 | + if (srcFrames > *destFrames) { |
635 | + int samplesToCopy(*destFrames * kNumChannels); |
636 | + memcpy(dest, src, samplesToCopy * sizeof(*src)); |
637 | + srcFrames -= *destFrames; |
638 | + memmove(m_leftoverBuffer, |
639 | + src + samplesToCopy, |
640 | + srcFrames * kNumChannels * sizeof(*src)); |
641 | + *destFrames = 0; |
642 | + m_leftoverBufferLength = srcFrames; |
643 | + } else { |
644 | + int samplesToCopy(srcFrames * kNumChannels); |
645 | + memcpy(dest, src, samplesToCopy * sizeof(*src)); |
646 | + *destFrames -= srcFrames; |
647 | + if (src == m_leftoverBuffer) { |
648 | + m_leftoverBufferLength = 0; |
649 | + } |
650 | + } |
651 | +} |
652 | + |
653 | +/** |
654 | + * Convert a 100ns Media Foundation value to a number of seconds. |
655 | + */ |
656 | +inline qreal SoundSourceMediaFoundation::secondsFromMF(qint64 mf) |
657 | +{ |
658 | + return static_cast<qreal>(mf) / 1e7; |
659 | +} |
660 | + |
661 | +/** |
662 | + * Convert a number of seconds to a 100ns Media Foundation value. |
663 | + */ |
664 | +inline qint64 SoundSourceMediaFoundation::mfFromSeconds(qreal sec) |
665 | +{ |
666 | + return sec * 1e7; |
667 | +} |
668 | + |
669 | +/** |
670 | + * Convert a 100ns Media Foundation value to a frame offset. |
671 | + */ |
672 | +inline qint64 SoundSourceMediaFoundation::frameFromMF(qint64 mf) |
673 | +{ |
674 | + return static_cast<qreal>(mf) * kSampleRate / 1e7; |
675 | +} |
676 | + |
677 | +/** |
678 | + * Convert a frame offset to a 100ns Media Foundation value. |
679 | + */ |
680 | +inline qint64 SoundSourceMediaFoundation::mfFromFrame(qint64 frame) |
681 | +{ |
682 | + return static_cast<qreal>(frame) / kSampleRate * 1e7; |
683 | +} |
684 | \ No newline at end of file |
685 | |
686 | === added file 'mixxx/plugins/soundsourcemediafoundation/soundsourcemediafoundation.h' |
687 | --- mixxx/plugins/soundsourcemediafoundation/soundsourcemediafoundation.h 1970-01-01 00:00:00 +0000 |
688 | +++ mixxx/plugins/soundsourcemediafoundation/soundsourcemediafoundation.h 2011-10-11 02:00:31 +0000 |
689 | @@ -0,0 +1,111 @@ |
690 | +/** |
691 | + * \file soundsourcemediafoundation.h |
692 | + * \class SoundSourceMediaFoundation |
693 | + * \brief Decodes MPEG4/AAC audio using the SourceReader interface of the |
694 | + * Media Foundation framework included in Windows 7. |
695 | + * \author Bill Good <bkgood at gmail dot com> |
696 | + * \author Albert Santoni <alberts at mixxx dot org> |
697 | + * \date Jan 10, 2011 |
698 | + */ |
699 | + |
700 | +/*************************************************************************** |
701 | + * * |
702 | + * This program is free software; you can redistribute it and/or modify * |
703 | + * it under the terms of the GNU General Public License as published by * |
704 | + * the Free Software Foundation; either version 2 of the License, or * |
705 | + * (at your option) any later version. * |
706 | + * * |
707 | + ***************************************************************************/ |
708 | + |
709 | +#ifndef SOUNDSOURCEMEDIAFOUNDATION_H |
710 | +#define SOUNDSOURCEMEDIAFOUNDATION_H |
711 | + |
712 | +#include <QFile> |
713 | +#include <QString> |
714 | + |
715 | +#include "defs.h" |
716 | +#include "defs_version.h" |
717 | +#include "soundsource.h" |
718 | + |
719 | +#ifdef Q_WS_WIN |
720 | +#define MY_EXPORT __declspec(dllexport) |
721 | +#else |
722 | +#define MY_EXPORT |
723 | +#endif |
724 | + |
725 | +class IMFSourceReader; |
726 | +class IMFMediaType; |
727 | +class IMFMediaSource; |
728 | + |
729 | +class SoundSourceMediaFoundation : public Mixxx::SoundSource { |
730 | +public: |
731 | + SoundSourceMediaFoundation(QString filename); |
732 | + ~SoundSourceMediaFoundation(); |
733 | + int open(); |
734 | + long seek(long filepos); |
735 | + unsigned read(unsigned long size, const SAMPLE *buffer); |
736 | + inline long unsigned length(); |
737 | + int parseHeader(); |
738 | + static QList<QString> supportedFileExtensions(); |
739 | + |
740 | +private: |
741 | + bool configureAudioStream(); |
742 | + bool readProperties(); |
743 | + void copyFrames(qint16 *dest, size_t *destFrames, const qint16 *src, |
744 | + size_t srcFrames); |
745 | + static inline qreal secondsFromMF(qint64 mf); |
746 | + static inline qint64 mfFromSeconds(qreal sec); |
747 | + static inline qint64 frameFromMF(qint64 mf); |
748 | + static inline qint64 mfFromFrame(qint64 frame); |
749 | + QFile m_file; |
750 | + IMFSourceReader *m_pReader; |
751 | + IMFMediaType *m_pAudioType; |
752 | + wchar_t *m_wcFilename; |
753 | + int m_nextFrame; |
754 | + qint16 *m_leftoverBuffer; |
755 | + size_t m_leftoverBufferSize; |
756 | + size_t m_leftoverBufferLength; |
757 | + int m_leftoverBufferPosition; |
758 | + qint64 m_mfDuration; |
759 | + bool m_dead; |
760 | + bool m_seeking; |
761 | +}; |
762 | + |
763 | +extern "C" MY_EXPORT const char* getMixxxVersion() |
764 | +{ |
765 | + return VERSION; |
766 | +} |
767 | + |
768 | +extern "C" MY_EXPORT int getSoundSourceAPIVersion() |
769 | +{ |
770 | + return MIXXX_SOUNDSOURCE_API_VERSION; |
771 | +} |
772 | + |
773 | +extern "C" MY_EXPORT Mixxx::SoundSource* getSoundSource(QString filename) |
774 | +{ |
775 | + return new SoundSourceMediaFoundation(filename); |
776 | +} |
777 | + |
778 | +extern "C" MY_EXPORT char** supportedFileExtensions() |
779 | +{ |
780 | + QList<QString> exts = SoundSourceMediaFoundation::supportedFileExtensions(); |
781 | + //Convert to C string array. |
782 | + char** c_exts = (char**)malloc((exts.count() + 1) * sizeof(char*)); |
783 | + for (int i = 0; i < exts.count(); i++) |
784 | + { |
785 | + QByteArray qba = exts[i].toUtf8(); |
786 | + c_exts[i] = strdup(qba.constData()); |
787 | + qDebug() << c_exts[i]; |
788 | + } |
789 | + c_exts[exts.count()] = NULL; //NULL terminate the list |
790 | + |
791 | + return c_exts; |
792 | +} |
793 | + |
794 | +extern "C" MY_EXPORT void freeFileExtensions(char **exts) |
795 | +{ |
796 | + for (int i(0); exts[i]; ++i) free(exts[i]); |
797 | + free(exts); |
798 | +} |
799 | + |
800 | +#endif // ifndef SOUNDSOURCEMEDIAFOUNDATION_H |
I screwed up a merge somewhere on this, I'll sort it out later today.