Merge lp:~mixxxdevelopers/mixxx/features_m4a_win7 into lp:mixxx/1.10
- features_m4a_win7
- Merge into release-1.10.x
Status: | Merged |
---|---|
Merged at revision: | 2881 |
Proposed branch: | lp:~mixxxdevelopers/mixxx/features_m4a_win7 |
Merge into: | lp:mixxx/1.10 |
Diff against target: |
741 lines (+664/-2) 6 files modified
mixxx/SConstruct (+1/-0) mixxx/build/depends.py (+1/-2) mixxx/build/features.py (+34/-0) mixxx/src/soundsourcemediafoundation.cpp (+551/-0) mixxx/src/soundsourcemediafoundation.h (+67/-0) mixxx/src/soundsourceproxy.cpp (+10/-0) |
To merge this branch: | bzr merge lp:~mixxxdevelopers/mixxx/features_m4a_win7 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Good | Disapprove | ||
Sean M. Pappalardo | Needs Resubmitting | ||
Review via email: mp+67111@code.launchpad.net |
Commit message
Description of the change
Implements MP4/AAC decoding using Media Foundation with MS-provided AAC decoder in Windows 7+, and Vista/Server 2008 with KB2117917.
RJ mentioned merging this into release-1.10 so I'm proposing the merge to that branch. The decoder has tested stable for me, although it doesn't seem particularly fast, unfortunately (this seems to be a deficiency of Media Foundation -- although if anyone spots performance issues in the soundsource, I'd be interested). Build with mediafoundation=1 to enable. Windows SDK 7 is required, although it's installable on Vista.
William Good (bkgood) wrote : | # |
Sean M. Pappalardo (pegasus-renegadetech) wrote : | # |
Let me first formally say here: thank you for your work on this, Bill!
Yeah, I saw the update sizes after I said that. We can (and should) provide a pop-up box with links to the updates when ppl click on the M4A build/plugin on the downloads page (like we did for PortAudio on Ubuntu 8.04.)
Also, I strongly recommend making this a plugin rather than a separate build for supportability and less confusion. That way, no matter where people get Mixxx, they can just download the plugin (and the OS update if needed) and they're good to go. And we can even ship the plugin with Mixxx (if it's legal to do so and it will just silently not load or do nothing on a system that doesn't have the OS support.)
- 2846. By William Good
-
Addressed some issues brought up in review; lengthened filename buffer to maximum allowed by Windows (was probably too short previously).
- 2847. By William Good
-
Use Windows SDK path from environment.
- 2848. By William Good
-
Clean up description for build a bit
William Good (bkgood) wrote : | # |
I've pushed a branch (lp:~mixxxdevelopers/mixxx/features_m4a_win7_plugin) with the necessary changes to make this a plugin if that's really want we want to do, and fixed that fixed-path in the build script.
William Good (bkgood) wrote : | # |
And for my last bit of spam for the moment, soundsource plugins allocate a array of cstrings which are then filled and returned to the caller (SoundSourceProxy), which then owns the memory and must free it; this is how plugins communicate the file name extensions of the respective file types they support. I tried using the plugin I built with my branch with the 1.9 distribution (figured I could post a link to it to mixxx-devel for people to try out) and it's failing because of this malloc/free across dll boundaries (different msvcrt versions linked between mixxx.exe and soundsourcemedi
Sean M. Pappalardo (pegasus-renegadetech) wrote : | # |
Yick...well IIRC the whole plugin system was knocked out very quickly by Albert in an effort to get M4A support to users as quickly as possible, so there's probably room for improvement. Mind filing a bug for that?
In the meantime, I can build the plugin for 1.9 since I did the main Mixxx binary. Just let me know.
William Good (bkgood) wrote : | # |
Hey Sean- yeah, if you wouldn't mind building the plugin to match the 1.9 msvcrt links, I'd appreciate it. I'm thinking we're still a bit out from 1.10 so maybe we could push this on 1.9 (forum and ml I guess) users and see if there are any bugs in the code needing quashed before it gets put in a release.
Thanks!
Sean M. Pappalardo (pegasus-renegadetech) wrote : | # |
Hrm, Mfreadwrite.lib is not in the Windows 6.0 (Vista) SDK (the one I use for 1.9.x) so I can't build the plugin for 1.9. I guess this will have to wait until 1.10.
Bill: Doesn't Media Foundation allow us to use WMA and other kinds of file types in addition to M4A? If so, how much work would be involved in adding all of them that we don't currently support? What about just WMA/ASF?
We still need an official decision on whether or not to do this as a plugin. So the questions are:
1) Can we legally distribute the MediaFoundation
2) Will a Mixxx binary that has it built-in (as opposed to a plugin) still run fine (just without M4A/WMA support) on systems without the required system libraries, such as Windows 2000 & XP?
If the answer to both is yes, and RJ has no issues, I'd say let's go ahead and merge this for 1.10.
William Good (bkgood) wrote : | # |
Note to self : look into dynamic load of the mf DLL. I'll reply in full when
I'm not on my phone.
On Oct 4, 2011 8:57 AM, "Sean M. Pappalardo" <email address hidden> wrote:
> Hrm, Mfreadwrite.lib is not in the Windows 6.0 (Vista) SDK (the one I use
for 1.9.x) so I can't build the plugin for 1.9. I guess this will have to
wait until 1.10.
>
> Bill: Doesn't Media Foundation allow us to use WMA and other kinds of file
types in addition to M4A? If so, how much work would be involved in adding
all of them that we don't currently support? What about just WMA/ASF?
>
> We still need an official decision on whether or not to do this as a
plugin. So the questions are:
> 1) Can we legally distribute the MediaFoundation
or restriction?
> 2) Will a Mixxx binary that has it built-in (as opposed to a plugin) still
run fine (just without M4A/WMA support) on systems without the required
system libraries, such as Windows 2000 & XP?
>
> If the answer to both is yes, and RJ has no issues, I'd say let's go ahead
and merge this for 1.10.
> --
>
https:/
> Your team Mixxx Development Team is requested to review the proposed merge
of lp:~mixxxdevelopers/mixxx/features_m4a_win7 into lp:mixxx/1.10.
William Good (bkgood) wrote : | # |
> Bill: Doesn't Media Foundation allow us to use WMA and other kinds of file types in addition to M4A? If so, how much work would be involved in adding all of them that we don't currently support? What about just WMA/ASF?
Media Foundation supports: http://
> 1) Can we legally distribute the MediaFoundation
Yes, it's the same situation as the Core Audio SoundSource. We're just calling an OS interface. They've paid for the AAC codec.
> 2) Will a Mixxx binary that has it built-in (as opposed to a plugin) still run fine (just without M4A/WMA support) on systems without the required system libraries, such as Windows 2000 & XP?
In short, no. Vista/2008 without the "Platform Update Supplement" and earlier versions of Windows will fail at run-time linking because they're missing either Media Foundation itself or the SourceReader interface. If a system were to exist with the SourceReader interface but not have the AAC decoder, Mixxx would run and simply fail to decode AAC, although I'm not aware of any situations in which this can occur.
Sean M. Pappalardo (pegasus-renegadetech) wrote : | # |
Then we have to do this as a plugin since we support Windows 2000 and up. (MANY of our users are on XP.) So maybe void this merge request and submit one on the plugin branch.
Preview Diff
1 | === modified file 'mixxx/SConstruct' |
2 | --- mixxx/SConstruct 2011-06-13 01:37:30 +0000 |
3 | +++ mixxx/SConstruct 2011-07-20 02:13:34 +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 | |
13 | === modified file 'mixxx/build/depends.py' |
14 | --- mixxx/build/depends.py 2011-07-19 18:12:33 +0000 |
15 | +++ mixxx/build/depends.py 2011-07-20 02:13:34 +0000 |
16 | @@ -97,7 +97,6 @@ |
17 | def sources(self, build): |
18 | return ['soundsourceflac.cpp',] |
19 | |
20 | - |
21 | class Qt(Dependence): |
22 | DEFAULT_QTDIRS = {'linux': '/usr/share/qt4', |
23 | 'bsd': '/usr/local/lib/qt4', |
24 | @@ -739,7 +738,7 @@ |
25 | |
26 | def depends(self, build): |
27 | return [SoundTouch, KissFFT, ReplayGain, PortAudio, PortMIDI, Qt, |
28 | - FidLib, SndFile, FLAC, OggVorbis, OpenGL, TagLib] |
29 | + FidLib, SndFile, FLAC, OggVorbis, OpenGL, TagLib,] |
30 | |
31 | def post_dependency_check_configure(self, build, conf): |
32 | """Sets up additional things in the Environment that must happen |
33 | |
34 | === modified file 'mixxx/build/features.py' |
35 | --- mixxx/build/features.py 2011-06-12 20:55:47 +0000 |
36 | +++ mixxx/build/features.py 2011-07-20 02:13:34 +0000 |
37 | @@ -104,6 +104,40 @@ |
38 | return ['soundsourcecoreaudio.cpp', |
39 | '#lib/apple/CAStreamBasicDescription.h'] |
40 | |
41 | +class MediaFoundation(Feature): |
42 | + FLAG = 'mediafoundation' |
43 | + def description(self): |
44 | + return "Media Foundation AAC Decoder" |
45 | + def enabled(self, build): |
46 | + build.flags[self.FLAG] = util.get_flags(build.env, self.FLAG, 0) |
47 | + if int(build.flags[self.FLAG]): |
48 | + return True |
49 | + return False |
50 | + def add_options(self, build, vars): |
51 | + vars.Add(self.FLAG, "Set to 1 to enable Media Foundation AAC decoder support (Windows Vista with KB2117917 or Windows 7 required)", 0) |
52 | + def configure(self, build, conf): |
53 | + if not self.enabled(build): |
54 | + return |
55 | + if not build.platform_is_windows: |
56 | + raise Exception("Media Foundation is only supported on Windows!") |
57 | + # need to look into this, SDK 6 might be ok? |
58 | + try: |
59 | + include_path = os.path.join(os.environ["MSSdk"], "Include") |
60 | + except KeyError: |
61 | + raise Exception("MSSdk environment variable not set, have you run setenv?") |
62 | + build.env.Append(CPPPATH=[include_path]) |
63 | + if not conf.CheckLib('Ole32'): |
64 | + raise Exception('Did not find Ole32.lib - exiting!') |
65 | + if not conf.CheckLib(['Mfuuid']): |
66 | + raise Exception('Did not find Mfuuid.lib - exiting!') |
67 | + if not conf.CheckLib(['Mfplat']): |
68 | + raise Exception('Did not find Mfplat.lib - exiting!') |
69 | + if not conf.CheckLib(['Mfreadwrite']): #Only available on Windows 7 and up, or properly updated Vista |
70 | + raise Exception('Did not find Mfreadwrite.lib - exiting!') |
71 | + build.env.Append(CPPDEFINES='__MEDIAFOUNDATION__') |
72 | + return |
73 | + def sources(self, build): |
74 | + return ['soundsourcemediafoundation.cpp'] |
75 | |
76 | class MIDIScript(Feature): |
77 | def description(self): |
78 | |
79 | === added file 'mixxx/src/soundsourcemediafoundation.cpp' |
80 | --- mixxx/src/soundsourcemediafoundation.cpp 1970-01-01 00:00:00 +0000 |
81 | +++ mixxx/src/soundsourcemediafoundation.cpp 2011-07-20 02:13:34 +0000 |
82 | @@ -0,0 +1,551 @@ |
83 | +/** |
84 | + * \file soundsourcemediafoundation.cpp |
85 | + * \author Bill Good <bkgood at gmail dot com> |
86 | + * \author Albert Santoni <alberts at mixxx dot org> |
87 | + * \date Jan 10, 2011 |
88 | + * \note This file uses COM interfaces defined in Windows 7 and later added to |
89 | + * Vista and Server 2008 via the "Platform Update Supplement for Windows Vista |
90 | + * and for Windows Server 2008" (http://support.microsoft.com/kb/2117917). |
91 | + * Earlier versions of Vista (and possibly Server 2008) have some Media |
92 | + * Foundation interfaces but not the required IMFSourceReader, and are missing |
93 | + * the Microsoft-provided AAC decoder. XP does not include Media Foundation. |
94 | + */ |
95 | + |
96 | +/*************************************************************************** |
97 | + * * |
98 | + * This program is free software; you can redistribute it and/or modify * |
99 | + * it under the terms of the GNU General Public License as published by * |
100 | + * the Free Software Foundation; either version 2 of the License, or * |
101 | + * (at your option) any later version. * |
102 | + * * |
103 | + ***************************************************************************/ |
104 | + |
105 | +#include <QtDebug> |
106 | +#include <taglib/mp4file.h> |
107 | +#include <windows.h> |
108 | +#include <mfapi.h> |
109 | +#include <mfidl.h> |
110 | +#include <mfreadwrite.h> |
111 | +#include <mferror.h> |
112 | +#include <propvarutil.h> |
113 | + |
114 | +#include "soundsourcemediafoundation.h" |
115 | + |
116 | +const int kBitsPerSample = 16; |
117 | +const int kNumChannels = 2; |
118 | +const int kSampleRate = 44100; |
119 | +const int kLeftoverSize = 4096; // in int16's, this seems to be the size MF AAC |
120 | +// decoder likes to give |
121 | + |
122 | +/** Microsoft examples use this snippet often. */ |
123 | +template<class T> static void safeRelease(T **ppT) |
124 | +{ |
125 | + if (*ppT) { |
126 | + (*ppT)->Release(); |
127 | + *ppT = NULL; |
128 | + } |
129 | +} |
130 | + |
131 | +SoundSourceMediaFoundation::SoundSourceMediaFoundation(QString filename) |
132 | + : SoundSource(filename) |
133 | + , m_file(filename) |
134 | + , m_pReader(NULL) |
135 | + , m_pAudioType(NULL) |
136 | + , m_wcFilename(NULL) |
137 | + , m_nextFrame(0) |
138 | + , m_leftoverBuffer(NULL) |
139 | + , m_leftoverBufferSize(0) |
140 | + , m_leftoverBufferLength(0) |
141 | + , m_leftoverBufferPosition(0) |
142 | + , m_mfDuration(0) |
143 | + , m_dead(false) |
144 | + , m_seeking(false) |
145 | +{ |
146 | + // these are always the same, might as well just stick them here |
147 | + // -bkgood |
148 | + m_iChannels = kNumChannels; |
149 | + m_iSampleRate = kSampleRate; |
150 | + |
151 | + // http://social.msdn.microsoft.com/Forums/en/netfxbcl/thread/35c6a451-3507-40c8-9d1c-8d4edde7c0cc |
152 | + // gives maximum path + file length as 248 + 260, using that -bkgood |
153 | + m_wcFilename = new wchar_t[248 + 260]; |
154 | +} |
155 | + |
156 | +SoundSourceMediaFoundation::~SoundSourceMediaFoundation() |
157 | +{ |
158 | + delete [] m_wcFilename; |
159 | + delete [] m_leftoverBuffer; |
160 | + |
161 | + safeRelease(&m_pReader); |
162 | + safeRelease(&m_pAudioType); |
163 | + MFShutdown(); |
164 | + CoUninitialize(); |
165 | +} |
166 | + |
167 | +int SoundSourceMediaFoundation::open() |
168 | +{ |
169 | + QString qurlStr(m_qFilename); |
170 | + int wcFilenameLength(m_qFilename.toWCharArray(m_wcFilename)); |
171 | + // toWCharArray does not append a null terminator to the string! |
172 | + m_wcFilename[wcFilenameLength] = '\0'; |
173 | + |
174 | + HRESULT hr(S_OK); |
175 | + // Initialize the COM library. |
176 | + hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE); |
177 | + if (FAILED(hr)) { |
178 | + qWarning() << "SSMF: failed to initialize COM"; |
179 | + return ERR; |
180 | + } |
181 | + |
182 | + // Initialize the Media Foundation platform. |
183 | + hr = MFStartup(MF_VERSION); |
184 | + if (FAILED(hr)) { |
185 | + qWarning() << "SSMF: failed to initialize Media Foundation"; |
186 | + return ERR; |
187 | + } |
188 | + |
189 | + // Create the source reader to read the input file. |
190 | + hr = MFCreateSourceReaderFromURL(m_wcFilename, NULL, &m_pReader); |
191 | + if (FAILED(hr)) { |
192 | + qWarning() << "SSMF: Error opening input file:" << m_qFilename; |
193 | + return ERR; |
194 | + } |
195 | + |
196 | + hr = configureAudioStream(); |
197 | + if (FAILED(hr)) { |
198 | + qWarning() << "SSMF: Error configuring audio stream."; |
199 | + return ERR; |
200 | + } |
201 | + |
202 | + if (!readProperties()) { |
203 | + qWarning() << "SSMF::readProperties failed"; |
204 | + return ERR; |
205 | + } |
206 | + |
207 | + //Seek to position 0, which forces us to skip over all the header frames. |
208 | + //This makes sure we're ready to just let the Analyser rip and it'll |
209 | + //get the number of samples it expects (ie. no header frames). |
210 | + seek(0); |
211 | + |
212 | + return OK; |
213 | +} |
214 | + |
215 | +long SoundSourceMediaFoundation::seek(long filepos) |
216 | +{ |
217 | + PROPVARIANT prop; |
218 | + HRESULT hr(S_OK); |
219 | + qint64 seekTarget(filepos / kNumChannels); |
220 | + qint64 mfSeekTarget(mfFromFrame(seekTarget) - 1); |
221 | + // minus 1 here seems to make our seeking work properly, otherwise we will |
222 | + // (more often than not, maybe always) seek a bit too far (although not |
223 | + // enough for our calculatedFrameFromMF <= nextFrame assertion in ::read). |
224 | + // Has something to do with 100ns MF units being much smaller than most |
225 | + // frame offsets (in seconds) -bkgood |
226 | + |
227 | + if (m_dead) { |
228 | + return filepos; |
229 | + } |
230 | + |
231 | + // this doesn't fail, see MS's implementation |
232 | + hr = InitPropVariantFromInt64(mfSeekTarget < 0 ? 0 : mfSeekTarget, &prop); |
233 | + |
234 | + // http://msdn.microsoft.com/en-us/library/dd374668(v=VS.85).aspx |
235 | + hr = m_pReader->SetCurrentPosition(GUID_NULL, prop); |
236 | + if (FAILED(hr)) { |
237 | + // nothing we can do here as we can't fail (no facility to other than |
238 | + // crashing mixxx) |
239 | + qWarning() << "SSMF: failed to seek" << ( |
240 | + hr == MF_E_INVALIDREQUEST ? "Sample requests still pending" : ""); |
241 | + } else { |
242 | + hr = m_pReader->Flush(MF_SOURCE_READER_FIRST_AUDIO_STREAM); |
243 | + if (FAILED(hr)) { |
244 | + qWarning() << "SSMF: failed to flush after seek"; |
245 | + } |
246 | + } |
247 | + PropVariantClear(&prop); |
248 | + |
249 | + // record the next frame so that we can make sure we're there the next |
250 | + // time we get a buffer from MFSourceReader |
251 | + m_nextFrame = seekTarget; |
252 | + m_seeking = true; |
253 | + |
254 | + return filepos; |
255 | +} |
256 | + |
257 | +unsigned int SoundSourceMediaFoundation::read(unsigned long size, |
258 | + const SAMPLE *destination) |
259 | +{ |
260 | + SAMPLE *destBuffer(const_cast<SAMPLE*>(destination)); |
261 | + size_t framesRequested(size / kNumChannels); |
262 | + size_t framesNeeded(framesRequested); |
263 | + |
264 | + // first, copy frames from leftover buffer IF the leftover buffer is at |
265 | + // the correct frame |
266 | + if (m_leftoverBufferLength > 0 && m_leftoverBufferPosition == m_nextFrame) { |
267 | + copyFrames(destBuffer, &framesNeeded, m_leftoverBuffer, |
268 | + m_leftoverBufferLength); |
269 | + if (m_leftoverBufferLength > 0) { |
270 | + Q_ASSERT(framesNeeded == 0); // make sure CopyFrames worked |
271 | + m_leftoverBufferPosition += framesRequested; |
272 | + } |
273 | + } else { |
274 | + // leftoverBuffer already empty or in the wrong position, clear it |
275 | + m_leftoverBufferLength = 0; |
276 | + } |
277 | + |
278 | + while (!m_dead && framesNeeded > 0) { |
279 | + HRESULT hr(S_OK); |
280 | + DWORD dwFlags(0); |
281 | + qint64 timestamp(0); |
282 | + IMFSample *pSample(NULL); |
283 | + bool error(false); // set to true to break after releasing |
284 | + |
285 | + hr = m_pReader->ReadSample( |
286 | + MF_SOURCE_READER_FIRST_AUDIO_STREAM, // [in] DWORD dwStreamIndex, |
287 | + 0, // [in] DWORD dwControlFlags, |
288 | + NULL, // [out] DWORD *pdwActualStreamIndex, |
289 | + &dwFlags, // [out] DWORD *pdwStreamFlags, |
290 | + ×tamp, // [out] LONGLONG *pllTimestamp, |
291 | + &pSample); // [out] IMFSample **ppSample |
292 | + if (FAILED(hr)) break; |
293 | + if (dwFlags & MF_SOURCE_READERF_ERROR) { |
294 | + // our source reader is now dead, according to the docs |
295 | + qWarning() << "SSMF: ReadSample set ERROR, SourceReader is now dead"; |
296 | + m_dead = true; |
297 | + break; |
298 | + } else if (dwFlags & MF_SOURCE_READERF_ENDOFSTREAM) { |
299 | + qDebug() << "SSMF: End of input file."; |
300 | + break; |
301 | + } else if (dwFlags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED) { |
302 | + qWarning() << "SSMF: Type change"; |
303 | + break; |
304 | + } else if (pSample == NULL) { |
305 | + // generally this will happen when dwFlags contains ENDOFSTREAM, |
306 | + // so it'll be caught before now -bkgood |
307 | + qWarning() << "SSMF: No sample"; |
308 | + continue; |
309 | + } // we now own a ref to the instance at pSample |
310 | + |
311 | + IMFMediaBuffer *pMBuffer(NULL); |
312 | + // I know this does at least a memcopy and maybe a malloc, if we have |
313 | + // xrun issues with this we might want to look into using |
314 | + // IMFSample::GetBufferByIndex (although MS doesn't recommend this) |
315 | + if (FAILED(hr = pSample->ConvertToContiguousBuffer(&pMBuffer))) { |
316 | + error = true; |
317 | + goto releaseSample; |
318 | + } |
319 | + qint16 *buffer(NULL); |
320 | + size_t bufferLength(0); |
321 | + hr = pMBuffer->Lock(reinterpret_cast<quint8**>(&buffer), NULL, |
322 | + reinterpret_cast<DWORD*>(&bufferLength)); |
323 | + if (FAILED(hr)) { |
324 | + error = true; |
325 | + goto releaseMBuffer; |
326 | + } |
327 | + bufferLength /= (kBitsPerSample / 8 * kNumChannels); // now in frames |
328 | + |
329 | + if (m_seeking) { |
330 | + qint64 bufferPosition(frameFromMF(timestamp)); |
331 | + Q_ASSERT(m_nextFrame >= bufferPosition); // we can never go |
332 | + // backwards here in ::read, so if the seek didn't manage to take |
333 | + // us far back it's important to fail |
334 | + if (m_nextFrame == bufferPosition) { |
335 | + m_seeking = false; |
336 | + } else if (m_nextFrame < bufferPosition + bufferLength) { |
337 | + // nextFrame is in this buffer |
338 | + buffer += (m_nextFrame - bufferPosition) * kNumChannels; |
339 | + bufferLength -= m_nextFrame - bufferPosition; |
340 | + m_seeking = false; |
341 | + } else { // we need to keep going forward |
342 | + goto releaseRawBuffer; |
343 | + } |
344 | + } |
345 | + |
346 | + Q_ASSERT(bufferLength * kNumChannels <= m_leftoverBufferSize); |
347 | + copyFrames(destBuffer + (size - framesNeeded * kNumChannels), |
348 | + &framesNeeded, buffer, bufferLength); |
349 | + |
350 | +releaseRawBuffer: |
351 | + hr = pMBuffer->Unlock(); |
352 | + // I'm ignoring this, MSDN for IMFMediaBuffer::Unlock stipulates |
353 | + // nothing about the state of the instance if this fails so might as |
354 | + // well just let it be released. |
355 | + //if (FAILED(hr)) break; |
356 | +releaseMBuffer: |
357 | + safeRelease(&pMBuffer); |
358 | +releaseSample: |
359 | + safeRelease(&pSample); |
360 | + if (error) break; |
361 | + } |
362 | + |
363 | + m_nextFrame += framesRequested - framesNeeded; |
364 | + if (m_leftoverBufferLength > 0) { |
365 | + Q_ASSERT(framesNeeded == 0); // make sure CopyFrames worked |
366 | + m_leftoverBufferPosition = m_nextFrame; |
367 | + } |
368 | + return size - framesNeeded * kNumChannels; |
369 | +} |
370 | + |
371 | +inline unsigned long SoundSourceMediaFoundation::length() |
372 | +{ |
373 | + unsigned long len(secondsFromMF(m_mfDuration) * kSampleRate * kNumChannels); |
374 | + return len % kNumChannels == 0 ? len : len + 1; |
375 | +} |
376 | + |
377 | +int SoundSourceMediaFoundation::parseHeader() |
378 | +{ |
379 | + setType("m4a"); |
380 | + |
381 | + TagLib::MP4::File f(getFilename().toLocal8Bit().constData()); |
382 | + bool result = processTaglibFile(f); |
383 | + TagLib::MP4::Tag* tag = f.tag(); |
384 | + |
385 | + if (tag) { |
386 | + processMP4Tag(tag); |
387 | + } |
388 | + |
389 | + if (result) |
390 | + return OK; |
391 | + return ERR; |
392 | +} |
393 | + |
394 | + |
395 | +// static |
396 | +QList<QString> SoundSourceMediaFoundation::supportedFileExtensions() |
397 | +{ |
398 | + QList<QString> list; |
399 | + list.push_back("m4a"); |
400 | + list.push_back("mp4"); |
401 | + return list; |
402 | +} |
403 | + |
404 | + |
405 | +//------------------------------------------------------------------- |
406 | +// configureAudioStream |
407 | +// |
408 | +// Selects an audio stream from the source file, and configures the |
409 | +// stream to deliver decoded PCM audio. |
410 | +//------------------------------------------------------------------- |
411 | + |
412 | +/** Cobbled together from: |
413 | + http://msdn.microsoft.com/en-us/library/dd757929(v=vs.85).aspx |
414 | + and http://msdn.microsoft.com/en-us/library/dd317928(VS.85).aspx |
415 | + -- Albert |
416 | + If anything in here fails, just bail. I'm not going to decode HRESULTS. |
417 | + -- Bill |
418 | + */ |
419 | +bool SoundSourceMediaFoundation::configureAudioStream() |
420 | +{ |
421 | + HRESULT hr(S_OK); |
422 | + |
423 | + // deselect all streams, we only want the first |
424 | + hr = m_pReader->SetStreamSelection(MF_SOURCE_READER_ALL_STREAMS, false); |
425 | + if (FAILED(hr)) { |
426 | + qWarning() << "SSMF: failed to deselect all streams"; |
427 | + return false; |
428 | + } |
429 | + |
430 | + hr = m_pReader->SetStreamSelection(MF_SOURCE_READER_FIRST_AUDIO_STREAM, true); |
431 | + if (FAILED(hr)) { |
432 | + qWarning() << "SSMF: failed to select first audio stream"; |
433 | + return false; |
434 | + } |
435 | + |
436 | + hr = MFCreateMediaType(&m_pAudioType); |
437 | + if (FAILED(hr)) { |
438 | + qWarning() << "SSMF: failed to create media type"; |
439 | + return false; |
440 | + } |
441 | + |
442 | + hr = m_pAudioType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); |
443 | + if (FAILED(hr)) { |
444 | + qWarning() << "SSMF: failed to set major type"; |
445 | + return false; |
446 | + } |
447 | + |
448 | + hr = m_pAudioType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM); |
449 | + if (FAILED(hr)) { |
450 | + qWarning() << "SSMF: failed to set subtype"; |
451 | + return false; |
452 | + } |
453 | + |
454 | + hr = m_pAudioType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, true); |
455 | + if (FAILED(hr)) { |
456 | + qWarning() << "SSMF: failed to set samples independent"; |
457 | + return false; |
458 | + } |
459 | + |
460 | + hr = m_pAudioType->SetUINT32(MF_MT_FIXED_SIZE_SAMPLES, true); |
461 | + if (FAILED(hr)) { |
462 | + qWarning() << "SSMF: failed to set fixed size samples"; |
463 | + return false; |
464 | + } |
465 | + |
466 | + hr = m_pAudioType->SetUINT32(MF_MT_SAMPLE_SIZE, kLeftoverSize); |
467 | + if (FAILED(hr)) { |
468 | + qWarning() << "SSMF: failed to set sample size"; |
469 | + return false; |
470 | + } |
471 | + |
472 | + // MSDN for this attribute says that if bps is 8, samples are unsigned. |
473 | + // Otherwise, they're signed (so they're signed for us as 16 bps). Why |
474 | + // chose to hide this rather useful tidbit here is beyond me -bkgood |
475 | + hr = m_pAudioType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, kBitsPerSample); |
476 | + if (FAILED(hr)) { |
477 | + qWarning() << "SSMF: failed to set bits per sample"; |
478 | + return false; |
479 | + } |
480 | + |
481 | + hr = m_pAudioType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, |
482 | + kNumChannels * (kBitsPerSample / 8)); |
483 | + if (FAILED(hr)) { |
484 | + qWarning() << "SSMF: failed to set block alignment"; |
485 | + return false; |
486 | + } |
487 | + |
488 | + hr = m_pAudioType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, kNumChannels); |
489 | + if (FAILED(hr)) { |
490 | + qWarning() << "SSMF: failed to set number of channels"; |
491 | + return false; |
492 | + } |
493 | + |
494 | + hr = m_pAudioType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, kSampleRate); |
495 | + if (FAILED(hr)) { |
496 | + qWarning() << "SSMF: failed to set sample rate"; |
497 | + return false; |
498 | + } |
499 | + |
500 | + // Set this type on the source reader. The source reader will |
501 | + // load the necessary decoder. |
502 | + hr = m_pReader->SetCurrentMediaType( |
503 | + MF_SOURCE_READER_FIRST_AUDIO_STREAM, |
504 | + NULL, m_pAudioType); |
505 | + |
506 | + // the reader has the media type now, free our reference so we can use our |
507 | + // pointer for other purposes. Do this before checking for failure so we |
508 | + // don't dangle. |
509 | + safeRelease(&m_pAudioType); |
510 | + if (FAILED(hr)) { |
511 | + qWarning() << "SSMF: failed to set media type"; |
512 | + return false; |
513 | + } |
514 | + |
515 | + // Get the complete uncompressed format. |
516 | + hr = m_pReader->GetCurrentMediaType( |
517 | + MF_SOURCE_READER_FIRST_AUDIO_STREAM, |
518 | + &m_pAudioType); |
519 | + if (FAILED(hr)) { |
520 | + qWarning() << "SSMF: failed to retrieve completed media type"; |
521 | + return false; |
522 | + } |
523 | + |
524 | + // Ensure the stream is selected. |
525 | + hr = m_pReader->SetStreamSelection( |
526 | + MF_SOURCE_READER_FIRST_AUDIO_STREAM, |
527 | + true); |
528 | + if (FAILED(hr)) { |
529 | + qWarning() << "SSMF: failed to select first audio stream (again)"; |
530 | + return false; |
531 | + } |
532 | + |
533 | + // this may not be safe on all platforms as m_leftoverBufferSize is a |
534 | + // size_t and this function is writing a uint32. However, on 32-bit |
535 | + // Windows 7, size_t is defined as uint which is 32-bits, so we're safe |
536 | + // for all supported platforms -bkgood |
537 | + hr = m_pAudioType->GetUINT32(MF_MT_SAMPLE_SIZE, &m_leftoverBufferSize); |
538 | + if (FAILED(hr)) { |
539 | + qWarning() << "SSMF: failed to get buffer size"; |
540 | + return false; |
541 | + } |
542 | + m_leftoverBufferSize /= 2; // convert size in bytes to size in int16s |
543 | + m_leftoverBuffer = new qint16[m_leftoverBufferSize]; |
544 | + |
545 | + return true; |
546 | +} |
547 | + |
548 | +bool SoundSourceMediaFoundation::readProperties() |
549 | +{ |
550 | + PROPVARIANT prop; |
551 | + HRESULT hr = S_OK; |
552 | + |
553 | + //Get the duration, provided as a 64-bit integer of 100-nanosecond units |
554 | + hr = m_pReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE, |
555 | + MF_PD_DURATION, &prop); |
556 | + if (FAILED(hr)) { |
557 | + qWarning() << "SSMF: error getting duration"; |
558 | + return false; |
559 | + } |
560 | + // QuadPart isn't available on compilers that don't support _int64. Visual |
561 | + // Studio 6.0 introduced the type in 1998, so I think we're safe here |
562 | + // -bkgood |
563 | + m_iDuration = secondsFromMF(prop.hVal.QuadPart); |
564 | + m_mfDuration = prop.hVal.QuadPart; |
565 | + qDebug() << "SSMF: Duration:" << m_iDuration; |
566 | + PropVariantClear(&prop); |
567 | + |
568 | + // presentation attribute MF_PD_AUDIO_ENCODING_BITRATE only exists for |
569 | + // presentation descriptors, one of which MFSourceReader is not. |
570 | + // Therefore, we calculate it ourselves. |
571 | + m_iBitrate = kBitsPerSample * kSampleRate * kNumChannels; |
572 | + |
573 | + return true; |
574 | +} |
575 | + |
576 | +/** |
577 | + * Copies min(destFrames, srcFrames) frames to dest from src. Anything leftover |
578 | + * is moved to the beginning of m_leftoverBuffer, so empty it first (possibly |
579 | + * with this method). If src and dest overlap, I'll hurt you. |
580 | + */ |
581 | +void SoundSourceMediaFoundation::copyFrames( |
582 | + qint16 *dest, size_t *destFrames, const qint16 *src, size_t srcFrames) |
583 | +{ |
584 | + if (srcFrames > *destFrames) { |
585 | + int samplesToCopy(*destFrames * kNumChannels); |
586 | + memcpy(dest, src, samplesToCopy * sizeof(*src)); |
587 | + srcFrames -= *destFrames; |
588 | + memmove(m_leftoverBuffer, |
589 | + src + samplesToCopy, |
590 | + srcFrames * kNumChannels * sizeof(*src)); |
591 | + *destFrames = 0; |
592 | + m_leftoverBufferLength = srcFrames; |
593 | + } else { |
594 | + int samplesToCopy(srcFrames * kNumChannels); |
595 | + memcpy(dest, src, samplesToCopy * sizeof(*src)); |
596 | + *destFrames -= srcFrames; |
597 | + if (src == m_leftoverBuffer) { |
598 | + m_leftoverBufferLength = 0; |
599 | + } |
600 | + } |
601 | +} |
602 | + |
603 | +/** |
604 | + * Convert a 100ns Media Foundation value to a number of seconds. |
605 | + */ |
606 | +inline qreal SoundSourceMediaFoundation::secondsFromMF(qint64 mf) |
607 | +{ |
608 | + return static_cast<qreal>(mf) / 1e7; |
609 | +} |
610 | + |
611 | +/** |
612 | + * Convert a number of seconds to a 100ns Media Foundation value. |
613 | + */ |
614 | +inline qint64 SoundSourceMediaFoundation::mfFromSeconds(qreal sec) |
615 | +{ |
616 | + return sec * 1e7; |
617 | +} |
618 | + |
619 | +/** |
620 | + * Convert a 100ns Media Foundation value to a frame offset. |
621 | + */ |
622 | +inline qint64 SoundSourceMediaFoundation::frameFromMF(qint64 mf) |
623 | +{ |
624 | + return static_cast<qreal>(mf) * kSampleRate / 1e7; |
625 | +} |
626 | + |
627 | +/** |
628 | + * Convert a frame offset to a 100ns Media Foundation value. |
629 | + */ |
630 | +inline qint64 SoundSourceMediaFoundation::mfFromFrame(qint64 frame) |
631 | +{ |
632 | + return static_cast<qreal>(frame) / kSampleRate * 1e7; |
633 | +} |
634 | \ No newline at end of file |
635 | |
636 | === added file 'mixxx/src/soundsourcemediafoundation.h' |
637 | --- mixxx/src/soundsourcemediafoundation.h 1970-01-01 00:00:00 +0000 |
638 | +++ mixxx/src/soundsourcemediafoundation.h 2011-07-20 02:13:34 +0000 |
639 | @@ -0,0 +1,67 @@ |
640 | +/** |
641 | + * \file soundsourcemediafoundation.h |
642 | + * \class SoundSourceMediaFoundation |
643 | + * \brief Decodes MPEG4/AAC audio using the SourceReader interface of the |
644 | + * Media Foundation framework included in Windows 7. |
645 | + * \author Bill Good <bkgood at gmail dot com> |
646 | + * \author Albert Santoni <alberts at mixxx dot org> |
647 | + * \date Jan 10, 2011 |
648 | + */ |
649 | + |
650 | +/*************************************************************************** |
651 | + * * |
652 | + * This program is free software; you can redistribute it and/or modify * |
653 | + * it under the terms of the GNU General Public License as published by * |
654 | + * the Free Software Foundation; either version 2 of the License, or * |
655 | + * (at your option) any later version. * |
656 | + * * |
657 | + ***************************************************************************/ |
658 | + |
659 | +#ifndef SOUNDSOURCEMEDIAFOUNDATION_H |
660 | +#define SOUNDSOURCEMEDIAFOUNDATION_H |
661 | + |
662 | +#include <QFile> |
663 | +#include <QString> |
664 | + |
665 | +#include "defs.h" |
666 | +#include "soundsource.h" |
667 | + |
668 | +class IMFSourceReader; |
669 | +class IMFMediaType; |
670 | +class IMFMediaSource; |
671 | + |
672 | +class SoundSourceMediaFoundation : public Mixxx::SoundSource { |
673 | +public: |
674 | + SoundSourceMediaFoundation(QString filename); |
675 | + ~SoundSourceMediaFoundation(); |
676 | + int open(); |
677 | + long seek(long filepos); |
678 | + unsigned read(unsigned long size, const SAMPLE *buffer); |
679 | + inline long unsigned length(); |
680 | + int parseHeader(); |
681 | + static QList<QString> supportedFileExtensions(); |
682 | + |
683 | +private: |
684 | + bool configureAudioStream(); |
685 | + bool readProperties(); |
686 | + void copyFrames(qint16 *dest, size_t *destFrames, const qint16 *src, |
687 | + size_t srcFrames); |
688 | + static inline qreal secondsFromMF(qint64 mf); |
689 | + static inline qint64 mfFromSeconds(qreal sec); |
690 | + static inline qint64 frameFromMF(qint64 mf); |
691 | + static inline qint64 mfFromFrame(qint64 frame); |
692 | + QFile m_file; |
693 | + IMFSourceReader *m_pReader; |
694 | + IMFMediaType *m_pAudioType; |
695 | + wchar_t *m_wcFilename; |
696 | + int m_nextFrame; |
697 | + qint16 *m_leftoverBuffer; |
698 | + size_t m_leftoverBufferSize; |
699 | + size_t m_leftoverBufferLength; |
700 | + int m_leftoverBufferPosition; |
701 | + qint64 m_mfDuration; |
702 | + bool m_dead; |
703 | + bool m_seeking; |
704 | +}; |
705 | + |
706 | +#endif // ifndef SOUNDSOURCEMEDIAFOUNDATION_H |
707 | |
708 | === modified file 'mixxx/src/soundsourceproxy.cpp' |
709 | --- mixxx/src/soundsourceproxy.cpp 2011-03-27 00:50:47 +0000 |
710 | +++ mixxx/src/soundsourceproxy.cpp 2011-07-20 02:13:34 +0000 |
711 | @@ -25,6 +25,9 @@ |
712 | #ifdef __COREAUDIO__ |
713 | #include "soundsourcecoreaudio.h" |
714 | #endif |
715 | +#ifdef __MEDIAFOUNDATION__ |
716 | +#include "soundsourcemediafoundation.h" |
717 | +#endif |
718 | #ifdef __SNDFILE__ |
719 | #include "soundsourcesndfile.h" |
720 | #endif |
721 | @@ -131,6 +134,10 @@ |
722 | } else if (SoundSourceCoreAudio::supportedFileExtensions().contains(extension)) { |
723 | return new SoundSourceCoreAudio(qFilename); |
724 | #endif |
725 | +#ifdef __MEDIAFOUNDATION__ |
726 | + } else if (SoundSourceMediaFoundation::supportedFileExtensions().contains(extension)) { |
727 | + return new SoundSourceMediaFoundation(qFilename); |
728 | +#endif |
729 | } else if (m_extensionsSupportedByPlugins.contains(extension)) { |
730 | getSoundSourceFunc getter = m_extensionsSupportedByPlugins.value(extension); |
731 | if (getter) |
732 | @@ -333,6 +340,9 @@ |
733 | #ifdef __COREAUDIO__ |
734 | supportedFileExtensions.append(SoundSourceCoreAudio::supportedFileExtensions()); |
735 | #endif |
736 | +#ifdef __MEDIAFOUNDATION__ |
737 | + supportedFileExtensions.append(SoundSourceMediaFoundation::supportedFileExtensions()); |
738 | +#endif |
739 | supportedFileExtensions.append(m_extensionsSupportedByPlugins.keys()); |
740 | |
741 | return supportedFileExtensions; |
I missed this last night so I'll reply here:
[00:42] <Pegasus_RPG> just for my own knowledge, what would it take to make it a plugin?
Not much, it's a few build system changes and adding a handful of plugin metadata-providing statics.
[00:48] <Pegasus_RPG> (we could even pack in the Vista platform upgrade and auto-install that if needed)
It's a 5MB update for i386 and 12MB for x86-64, quite large.
Also, reminder to myself:
[00:32] <Pegasus_RPG> ok. Also, can you change the path to the SDK to use a variable? A bunch get set in the build env.
[00:33] <Pegasus_RPG> I'm not sure what they are though
[00:33] <Pegasus_RPG> check setenv.bat
[00:33] <Pegasus_RPG> something like %SDKPATH%
[00:38] <bkgood> Pegasus_RPG: %MSSdk% seems to point in the right direction, is there a more standard one?
[00:38] <bkgood> (that's in my SetEnv.cmd file)
[00:38] <Pegasus_RPG> I don't know. That sounds good