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

Proposed by William Good
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
Reviewer Review Type Date Requested Status
RJ Skerry-Ryan Approve
William Good Needs Fixing
Review via email: mp+78777@code.launchpad.net

Description of the change

Replacing https://code.launchpad.net/~mixxxdevelopers/mixxx/features_m4a_win7/+merge/67111 because the only way to have this functionality built in to Mixxx would be to either a) distribute multiple executables which is a PITA or b) load the necessary COM interfaces dynamically which would be somewhere between a huge PITA and impossible.

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/libraries aren't available at runtime, the plugin DLL will simply fail to load and nothing will crash and burn. This plugin is 110% legal to distribute because the AAC patent applies to distributing decoders, not code calling someone else's decoder (like how our binaries with CoreAudio-based m4a decoding are legal to distribute, except on Windows).

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.

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

I screwed up a merge somewhere on this, I'll sort it out later today.

review: Needs Fixing
2850. By William Good

Merged from lp:~mixxxdevelopers/mixxx/fixes-plugins-mempassing. Updated SoundSourceMediaFoundation to new plugin API.

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

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

Revision history for this message
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://code.launchpad.net/~mixxxdevelopers/mixxx/features_m4a_win7_plugin/+merge/78777
> You proposed lp:~mixxxdevelopers/mixxx/features_m4a_win7_plugin for merging.
>

Revision history for this message
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://code.launchpad.net/~mixxxdevelopers/mixxx/features_m4a_win7_plugin/+merge/78777
> You proposed lp:~mixxxdevelopers/mixxx/features_m4a_win7_plugin for merging.
>

Revision history for this message
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 soundsourcemediafoundation.cpp and then guard all qDebug()'s with that? It could be useful to be able to turn all of those debugs on with a single flag if something needs debugging in the future.

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

review: Approve

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-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+ &timestamp, // [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

Subscribers

People subscribed via source and target branches

to all changes: