Merge lp:~mixxxdevelopers/mixxx/features_m4a_win7 into lp:mixxx/1.10

Proposed by William Good
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
Reviewer Review Type Date Requested Status
William Good Disapprove
Sean M. Pappalardo Needs Resubmitting
Review via email: mp+67111@code.launchpad.net

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.

To post a comment you must log in.
Revision history for this message
William Good (bkgood) wrote :

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

Revision history for this message
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

Revision history for this message
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.

Revision history for this message
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 soundsourcemediafoundation.dll). IMO the plugin API needs to be modified such that it frees its own memory (and stupid stuff like this is why I don't really want to deal with plugins).

Revision history for this message
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.

Revision history for this message
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!

Revision history for this message
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-using binary without cost 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.

Revision history for this message
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-using binary without cost
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://code.launchpad.net/~mixxxdevelopers/mixxx/features_m4a_win7/+merge/67111
> Your team Mixxx Development Team is requested to review the proposed merge
of lp:~mixxxdevelopers/mixxx/features_m4a_win7 into lp:mixxx/1.10.

Revision history for this message
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://msdn.microsoft.com/en-us/library/windows/desktop/dd757927(v=vs.85).aspx. It would have the same minimum requirements as the AAC/m4a functionality because all systems having the necessary SourceReader interface also have the AAC decoder (and vice versa). I don't think the amount of work would be overwhelming but as we don't even have a wishlist bug for wma support I don't know that it's worth the time.

> 1) Can we legally distribute the MediaFoundation-using binary without cost or restriction?

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.

Revision history for this message
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.

review: Needs Resubmitting
Revision history for this message
William Good (bkgood) wrote :
review: Disapprove

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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+ &timestamp, // [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;

Subscribers

People subscribed via source and target branches

to all changes: