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
=== modified file 'mixxx/SConstruct'
--- mixxx/SConstruct 2011-06-13 01:37:30 +0000
+++ mixxx/SConstruct 2011-07-20 02:13:34 +0000
@@ -36,6 +36,7 @@
36 features.MIDIScript,36 features.MIDIScript,
37 features.Mad,37 features.Mad,
38 features.CoreAudio,38 features.CoreAudio,
39 features.MediaFoundation,
39 features.HSS1394,40 features.HSS1394,
40 features.VinylControl,41 features.VinylControl,
41 features.Shoutcast,42 features.Shoutcast,
4243
=== modified file 'mixxx/build/depends.py'
--- mixxx/build/depends.py 2011-07-19 18:12:33 +0000
+++ mixxx/build/depends.py 2011-07-20 02:13:34 +0000
@@ -97,7 +97,6 @@
97 def sources(self, build):97 def sources(self, build):
98 return ['soundsourceflac.cpp',]98 return ['soundsourceflac.cpp',]
9999
100
101class Qt(Dependence):100class Qt(Dependence):
102 DEFAULT_QTDIRS = {'linux': '/usr/share/qt4',101 DEFAULT_QTDIRS = {'linux': '/usr/share/qt4',
103 'bsd': '/usr/local/lib/qt4',102 'bsd': '/usr/local/lib/qt4',
@@ -739,7 +738,7 @@
739738
740 def depends(self, build):739 def depends(self, build):
741 return [SoundTouch, KissFFT, ReplayGain, PortAudio, PortMIDI, Qt,740 return [SoundTouch, KissFFT, ReplayGain, PortAudio, PortMIDI, Qt,
742 FidLib, SndFile, FLAC, OggVorbis, OpenGL, TagLib]741 FidLib, SndFile, FLAC, OggVorbis, OpenGL, TagLib,]
743742
744 def post_dependency_check_configure(self, build, conf):743 def post_dependency_check_configure(self, build, conf):
745 """Sets up additional things in the Environment that must happen744 """Sets up additional things in the Environment that must happen
746745
=== modified file 'mixxx/build/features.py'
--- mixxx/build/features.py 2011-06-12 20:55:47 +0000
+++ mixxx/build/features.py 2011-07-20 02:13:34 +0000
@@ -104,6 +104,40 @@
104 return ['soundsourcecoreaudio.cpp',104 return ['soundsourcecoreaudio.cpp',
105 '#lib/apple/CAStreamBasicDescription.h']105 '#lib/apple/CAStreamBasicDescription.h']
106106
107class MediaFoundation(Feature):
108 FLAG = 'mediafoundation'
109 def description(self):
110 return "Media Foundation AAC Decoder"
111 def enabled(self, build):
112 build.flags[self.FLAG] = util.get_flags(build.env, self.FLAG, 0)
113 if int(build.flags[self.FLAG]):
114 return True
115 return False
116 def add_options(self, build, vars):
117 vars.Add(self.FLAG, "Set to 1 to enable Media Foundation AAC decoder support (Windows Vista with KB2117917 or Windows 7 required)", 0)
118 def configure(self, build, conf):
119 if not self.enabled(build):
120 return
121 if not build.platform_is_windows:
122 raise Exception("Media Foundation is only supported on Windows!")
123 # need to look into this, SDK 6 might be ok?
124 try:
125 include_path = os.path.join(os.environ["MSSdk"], "Include")
126 except KeyError:
127 raise Exception("MSSdk environment variable not set, have you run setenv?")
128 build.env.Append(CPPPATH=[include_path])
129 if not conf.CheckLib('Ole32'):
130 raise Exception('Did not find Ole32.lib - exiting!')
131 if not conf.CheckLib(['Mfuuid']):
132 raise Exception('Did not find Mfuuid.lib - exiting!')
133 if not conf.CheckLib(['Mfplat']):
134 raise Exception('Did not find Mfplat.lib - exiting!')
135 if not conf.CheckLib(['Mfreadwrite']): #Only available on Windows 7 and up, or properly updated Vista
136 raise Exception('Did not find Mfreadwrite.lib - exiting!')
137 build.env.Append(CPPDEFINES='__MEDIAFOUNDATION__')
138 return
139 def sources(self, build):
140 return ['soundsourcemediafoundation.cpp']
107141
108class MIDIScript(Feature):142class MIDIScript(Feature):
109 def description(self):143 def description(self):
110144
=== added file 'mixxx/src/soundsourcemediafoundation.cpp'
--- mixxx/src/soundsourcemediafoundation.cpp 1970-01-01 00:00:00 +0000
+++ mixxx/src/soundsourcemediafoundation.cpp 2011-07-20 02:13:34 +0000
@@ -0,0 +1,551 @@
1/**
2 * \file soundsourcemediafoundation.cpp
3 * \author Bill Good <bkgood at gmail dot com>
4 * \author Albert Santoni <alberts at mixxx dot org>
5 * \date Jan 10, 2011
6 * \note This file uses COM interfaces defined in Windows 7 and later added to
7 * Vista and Server 2008 via the "Platform Update Supplement for Windows Vista
8 * and for Windows Server 2008" (http://support.microsoft.com/kb/2117917).
9 * Earlier versions of Vista (and possibly Server 2008) have some Media
10 * Foundation interfaces but not the required IMFSourceReader, and are missing
11 * the Microsoft-provided AAC decoder. XP does not include Media Foundation.
12 */
13
14/***************************************************************************
15 * *
16 * This program is free software; you can redistribute it and/or modify *
17 * it under the terms of the GNU General Public License as published by *
18 * the Free Software Foundation; either version 2 of the License, or *
19 * (at your option) any later version. *
20 * *
21 ***************************************************************************/
22
23#include <QtDebug>
24#include <taglib/mp4file.h>
25#include <windows.h>
26#include <mfapi.h>
27#include <mfidl.h>
28#include <mfreadwrite.h>
29#include <mferror.h>
30#include <propvarutil.h>
31
32#include "soundsourcemediafoundation.h"
33
34const int kBitsPerSample = 16;
35const int kNumChannels = 2;
36const int kSampleRate = 44100;
37const int kLeftoverSize = 4096; // in int16's, this seems to be the size MF AAC
38// decoder likes to give
39
40/** Microsoft examples use this snippet often. */
41template<class T> static void safeRelease(T **ppT)
42{
43 if (*ppT) {
44 (*ppT)->Release();
45 *ppT = NULL;
46 }
47}
48
49SoundSourceMediaFoundation::SoundSourceMediaFoundation(QString filename)
50 : SoundSource(filename)
51 , m_file(filename)
52 , m_pReader(NULL)
53 , m_pAudioType(NULL)
54 , m_wcFilename(NULL)
55 , m_nextFrame(0)
56 , m_leftoverBuffer(NULL)
57 , m_leftoverBufferSize(0)
58 , m_leftoverBufferLength(0)
59 , m_leftoverBufferPosition(0)
60 , m_mfDuration(0)
61 , m_dead(false)
62 , m_seeking(false)
63{
64 // these are always the same, might as well just stick them here
65 // -bkgood
66 m_iChannels = kNumChannels;
67 m_iSampleRate = kSampleRate;
68
69 // http://social.msdn.microsoft.com/Forums/en/netfxbcl/thread/35c6a451-3507-40c8-9d1c-8d4edde7c0cc
70 // gives maximum path + file length as 248 + 260, using that -bkgood
71 m_wcFilename = new wchar_t[248 + 260];
72}
73
74SoundSourceMediaFoundation::~SoundSourceMediaFoundation()
75{
76 delete [] m_wcFilename;
77 delete [] m_leftoverBuffer;
78
79 safeRelease(&m_pReader);
80 safeRelease(&m_pAudioType);
81 MFShutdown();
82 CoUninitialize();
83}
84
85int SoundSourceMediaFoundation::open()
86{
87 QString qurlStr(m_qFilename);
88 int wcFilenameLength(m_qFilename.toWCharArray(m_wcFilename));
89 // toWCharArray does not append a null terminator to the string!
90 m_wcFilename[wcFilenameLength] = '\0';
91
92 HRESULT hr(S_OK);
93 // Initialize the COM library.
94 hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
95 if (FAILED(hr)) {
96 qWarning() << "SSMF: failed to initialize COM";
97 return ERR;
98 }
99
100 // Initialize the Media Foundation platform.
101 hr = MFStartup(MF_VERSION);
102 if (FAILED(hr)) {
103 qWarning() << "SSMF: failed to initialize Media Foundation";
104 return ERR;
105 }
106
107 // Create the source reader to read the input file.
108 hr = MFCreateSourceReaderFromURL(m_wcFilename, NULL, &m_pReader);
109 if (FAILED(hr)) {
110 qWarning() << "SSMF: Error opening input file:" << m_qFilename;
111 return ERR;
112 }
113
114 hr = configureAudioStream();
115 if (FAILED(hr)) {
116 qWarning() << "SSMF: Error configuring audio stream.";
117 return ERR;
118 }
119
120 if (!readProperties()) {
121 qWarning() << "SSMF::readProperties failed";
122 return ERR;
123 }
124
125 //Seek to position 0, which forces us to skip over all the header frames.
126 //This makes sure we're ready to just let the Analyser rip and it'll
127 //get the number of samples it expects (ie. no header frames).
128 seek(0);
129
130 return OK;
131}
132
133long SoundSourceMediaFoundation::seek(long filepos)
134{
135 PROPVARIANT prop;
136 HRESULT hr(S_OK);
137 qint64 seekTarget(filepos / kNumChannels);
138 qint64 mfSeekTarget(mfFromFrame(seekTarget) - 1);
139 // minus 1 here seems to make our seeking work properly, otherwise we will
140 // (more often than not, maybe always) seek a bit too far (although not
141 // enough for our calculatedFrameFromMF <= nextFrame assertion in ::read).
142 // Has something to do with 100ns MF units being much smaller than most
143 // frame offsets (in seconds) -bkgood
144
145 if (m_dead) {
146 return filepos;
147 }
148
149 // this doesn't fail, see MS's implementation
150 hr = InitPropVariantFromInt64(mfSeekTarget < 0 ? 0 : mfSeekTarget, &prop);
151
152 // http://msdn.microsoft.com/en-us/library/dd374668(v=VS.85).aspx
153 hr = m_pReader->SetCurrentPosition(GUID_NULL, prop);
154 if (FAILED(hr)) {
155 // nothing we can do here as we can't fail (no facility to other than
156 // crashing mixxx)
157 qWarning() << "SSMF: failed to seek" << (
158 hr == MF_E_INVALIDREQUEST ? "Sample requests still pending" : "");
159 } else {
160 hr = m_pReader->Flush(MF_SOURCE_READER_FIRST_AUDIO_STREAM);
161 if (FAILED(hr)) {
162 qWarning() << "SSMF: failed to flush after seek";
163 }
164 }
165 PropVariantClear(&prop);
166
167 // record the next frame so that we can make sure we're there the next
168 // time we get a buffer from MFSourceReader
169 m_nextFrame = seekTarget;
170 m_seeking = true;
171
172 return filepos;
173}
174
175unsigned int SoundSourceMediaFoundation::read(unsigned long size,
176 const SAMPLE *destination)
177{
178 SAMPLE *destBuffer(const_cast<SAMPLE*>(destination));
179 size_t framesRequested(size / kNumChannels);
180 size_t framesNeeded(framesRequested);
181
182 // first, copy frames from leftover buffer IF the leftover buffer is at
183 // the correct frame
184 if (m_leftoverBufferLength > 0 && m_leftoverBufferPosition == m_nextFrame) {
185 copyFrames(destBuffer, &framesNeeded, m_leftoverBuffer,
186 m_leftoverBufferLength);
187 if (m_leftoverBufferLength > 0) {
188 Q_ASSERT(framesNeeded == 0); // make sure CopyFrames worked
189 m_leftoverBufferPosition += framesRequested;
190 }
191 } else {
192 // leftoverBuffer already empty or in the wrong position, clear it
193 m_leftoverBufferLength = 0;
194 }
195
196 while (!m_dead && framesNeeded > 0) {
197 HRESULT hr(S_OK);
198 DWORD dwFlags(0);
199 qint64 timestamp(0);
200 IMFSample *pSample(NULL);
201 bool error(false); // set to true to break after releasing
202
203 hr = m_pReader->ReadSample(
204 MF_SOURCE_READER_FIRST_AUDIO_STREAM, // [in] DWORD dwStreamIndex,
205 0, // [in] DWORD dwControlFlags,
206 NULL, // [out] DWORD *pdwActualStreamIndex,
207 &dwFlags, // [out] DWORD *pdwStreamFlags,
208 &timestamp, // [out] LONGLONG *pllTimestamp,
209 &pSample); // [out] IMFSample **ppSample
210 if (FAILED(hr)) break;
211 if (dwFlags & MF_SOURCE_READERF_ERROR) {
212 // our source reader is now dead, according to the docs
213 qWarning() << "SSMF: ReadSample set ERROR, SourceReader is now dead";
214 m_dead = true;
215 break;
216 } else if (dwFlags & MF_SOURCE_READERF_ENDOFSTREAM) {
217 qDebug() << "SSMF: End of input file.";
218 break;
219 } else if (dwFlags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED) {
220 qWarning() << "SSMF: Type change";
221 break;
222 } else if (pSample == NULL) {
223 // generally this will happen when dwFlags contains ENDOFSTREAM,
224 // so it'll be caught before now -bkgood
225 qWarning() << "SSMF: No sample";
226 continue;
227 } // we now own a ref to the instance at pSample
228
229 IMFMediaBuffer *pMBuffer(NULL);
230 // I know this does at least a memcopy and maybe a malloc, if we have
231 // xrun issues with this we might want to look into using
232 // IMFSample::GetBufferByIndex (although MS doesn't recommend this)
233 if (FAILED(hr = pSample->ConvertToContiguousBuffer(&pMBuffer))) {
234 error = true;
235 goto releaseSample;
236 }
237 qint16 *buffer(NULL);
238 size_t bufferLength(0);
239 hr = pMBuffer->Lock(reinterpret_cast<quint8**>(&buffer), NULL,
240 reinterpret_cast<DWORD*>(&bufferLength));
241 if (FAILED(hr)) {
242 error = true;
243 goto releaseMBuffer;
244 }
245 bufferLength /= (kBitsPerSample / 8 * kNumChannels); // now in frames
246
247 if (m_seeking) {
248 qint64 bufferPosition(frameFromMF(timestamp));
249 Q_ASSERT(m_nextFrame >= bufferPosition); // we can never go
250 // backwards here in ::read, so if the seek didn't manage to take
251 // us far back it's important to fail
252 if (m_nextFrame == bufferPosition) {
253 m_seeking = false;
254 } else if (m_nextFrame < bufferPosition + bufferLength) {
255 // nextFrame is in this buffer
256 buffer += (m_nextFrame - bufferPosition) * kNumChannels;
257 bufferLength -= m_nextFrame - bufferPosition;
258 m_seeking = false;
259 } else { // we need to keep going forward
260 goto releaseRawBuffer;
261 }
262 }
263
264 Q_ASSERT(bufferLength * kNumChannels <= m_leftoverBufferSize);
265 copyFrames(destBuffer + (size - framesNeeded * kNumChannels),
266 &framesNeeded, buffer, bufferLength);
267
268releaseRawBuffer:
269 hr = pMBuffer->Unlock();
270 // I'm ignoring this, MSDN for IMFMediaBuffer::Unlock stipulates
271 // nothing about the state of the instance if this fails so might as
272 // well just let it be released.
273 //if (FAILED(hr)) break;
274releaseMBuffer:
275 safeRelease(&pMBuffer);
276releaseSample:
277 safeRelease(&pSample);
278 if (error) break;
279 }
280
281 m_nextFrame += framesRequested - framesNeeded;
282 if (m_leftoverBufferLength > 0) {
283 Q_ASSERT(framesNeeded == 0); // make sure CopyFrames worked
284 m_leftoverBufferPosition = m_nextFrame;
285 }
286 return size - framesNeeded * kNumChannels;
287}
288
289inline unsigned long SoundSourceMediaFoundation::length()
290{
291 unsigned long len(secondsFromMF(m_mfDuration) * kSampleRate * kNumChannels);
292 return len % kNumChannels == 0 ? len : len + 1;
293}
294
295int SoundSourceMediaFoundation::parseHeader()
296{
297 setType("m4a");
298
299 TagLib::MP4::File f(getFilename().toLocal8Bit().constData());
300 bool result = processTaglibFile(f);
301 TagLib::MP4::Tag* tag = f.tag();
302
303 if (tag) {
304 processMP4Tag(tag);
305 }
306
307 if (result)
308 return OK;
309 return ERR;
310}
311
312
313// static
314QList<QString> SoundSourceMediaFoundation::supportedFileExtensions()
315{
316 QList<QString> list;
317 list.push_back("m4a");
318 list.push_back("mp4");
319 return list;
320}
321
322
323//-------------------------------------------------------------------
324// configureAudioStream
325//
326// Selects an audio stream from the source file, and configures the
327// stream to deliver decoded PCM audio.
328//-------------------------------------------------------------------
329
330/** Cobbled together from:
331 http://msdn.microsoft.com/en-us/library/dd757929(v=vs.85).aspx
332 and http://msdn.microsoft.com/en-us/library/dd317928(VS.85).aspx
333 -- Albert
334 If anything in here fails, just bail. I'm not going to decode HRESULTS.
335 -- Bill
336 */
337bool SoundSourceMediaFoundation::configureAudioStream()
338{
339 HRESULT hr(S_OK);
340
341 // deselect all streams, we only want the first
342 hr = m_pReader->SetStreamSelection(MF_SOURCE_READER_ALL_STREAMS, false);
343 if (FAILED(hr)) {
344 qWarning() << "SSMF: failed to deselect all streams";
345 return false;
346 }
347
348 hr = m_pReader->SetStreamSelection(MF_SOURCE_READER_FIRST_AUDIO_STREAM, true);
349 if (FAILED(hr)) {
350 qWarning() << "SSMF: failed to select first audio stream";
351 return false;
352 }
353
354 hr = MFCreateMediaType(&m_pAudioType);
355 if (FAILED(hr)) {
356 qWarning() << "SSMF: failed to create media type";
357 return false;
358 }
359
360 hr = m_pAudioType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
361 if (FAILED(hr)) {
362 qWarning() << "SSMF: failed to set major type";
363 return false;
364 }
365
366 hr = m_pAudioType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM);
367 if (FAILED(hr)) {
368 qWarning() << "SSMF: failed to set subtype";
369 return false;
370 }
371
372 hr = m_pAudioType->SetUINT32(MF_MT_ALL_SAMPLES_INDEPENDENT, true);
373 if (FAILED(hr)) {
374 qWarning() << "SSMF: failed to set samples independent";
375 return false;
376 }
377
378 hr = m_pAudioType->SetUINT32(MF_MT_FIXED_SIZE_SAMPLES, true);
379 if (FAILED(hr)) {
380 qWarning() << "SSMF: failed to set fixed size samples";
381 return false;
382 }
383
384 hr = m_pAudioType->SetUINT32(MF_MT_SAMPLE_SIZE, kLeftoverSize);
385 if (FAILED(hr)) {
386 qWarning() << "SSMF: failed to set sample size";
387 return false;
388 }
389
390 // MSDN for this attribute says that if bps is 8, samples are unsigned.
391 // Otherwise, they're signed (so they're signed for us as 16 bps). Why
392 // chose to hide this rather useful tidbit here is beyond me -bkgood
393 hr = m_pAudioType->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, kBitsPerSample);
394 if (FAILED(hr)) {
395 qWarning() << "SSMF: failed to set bits per sample";
396 return false;
397 }
398
399 hr = m_pAudioType->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT,
400 kNumChannels * (kBitsPerSample / 8));
401 if (FAILED(hr)) {
402 qWarning() << "SSMF: failed to set block alignment";
403 return false;
404 }
405
406 hr = m_pAudioType->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, kNumChannels);
407 if (FAILED(hr)) {
408 qWarning() << "SSMF: failed to set number of channels";
409 return false;
410 }
411
412 hr = m_pAudioType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, kSampleRate);
413 if (FAILED(hr)) {
414 qWarning() << "SSMF: failed to set sample rate";
415 return false;
416 }
417
418 // Set this type on the source reader. The source reader will
419 // load the necessary decoder.
420 hr = m_pReader->SetCurrentMediaType(
421 MF_SOURCE_READER_FIRST_AUDIO_STREAM,
422 NULL, m_pAudioType);
423
424 // the reader has the media type now, free our reference so we can use our
425 // pointer for other purposes. Do this before checking for failure so we
426 // don't dangle.
427 safeRelease(&m_pAudioType);
428 if (FAILED(hr)) {
429 qWarning() << "SSMF: failed to set media type";
430 return false;
431 }
432
433 // Get the complete uncompressed format.
434 hr = m_pReader->GetCurrentMediaType(
435 MF_SOURCE_READER_FIRST_AUDIO_STREAM,
436 &m_pAudioType);
437 if (FAILED(hr)) {
438 qWarning() << "SSMF: failed to retrieve completed media type";
439 return false;
440 }
441
442 // Ensure the stream is selected.
443 hr = m_pReader->SetStreamSelection(
444 MF_SOURCE_READER_FIRST_AUDIO_STREAM,
445 true);
446 if (FAILED(hr)) {
447 qWarning() << "SSMF: failed to select first audio stream (again)";
448 return false;
449 }
450
451 // this may not be safe on all platforms as m_leftoverBufferSize is a
452 // size_t and this function is writing a uint32. However, on 32-bit
453 // Windows 7, size_t is defined as uint which is 32-bits, so we're safe
454 // for all supported platforms -bkgood
455 hr = m_pAudioType->GetUINT32(MF_MT_SAMPLE_SIZE, &m_leftoverBufferSize);
456 if (FAILED(hr)) {
457 qWarning() << "SSMF: failed to get buffer size";
458 return false;
459 }
460 m_leftoverBufferSize /= 2; // convert size in bytes to size in int16s
461 m_leftoverBuffer = new qint16[m_leftoverBufferSize];
462
463 return true;
464}
465
466bool SoundSourceMediaFoundation::readProperties()
467{
468 PROPVARIANT prop;
469 HRESULT hr = S_OK;
470
471 //Get the duration, provided as a 64-bit integer of 100-nanosecond units
472 hr = m_pReader->GetPresentationAttribute(MF_SOURCE_READER_MEDIASOURCE,
473 MF_PD_DURATION, &prop);
474 if (FAILED(hr)) {
475 qWarning() << "SSMF: error getting duration";
476 return false;
477 }
478 // QuadPart isn't available on compilers that don't support _int64. Visual
479 // Studio 6.0 introduced the type in 1998, so I think we're safe here
480 // -bkgood
481 m_iDuration = secondsFromMF(prop.hVal.QuadPart);
482 m_mfDuration = prop.hVal.QuadPart;
483 qDebug() << "SSMF: Duration:" << m_iDuration;
484 PropVariantClear(&prop);
485
486 // presentation attribute MF_PD_AUDIO_ENCODING_BITRATE only exists for
487 // presentation descriptors, one of which MFSourceReader is not.
488 // Therefore, we calculate it ourselves.
489 m_iBitrate = kBitsPerSample * kSampleRate * kNumChannels;
490
491 return true;
492}
493
494/**
495 * Copies min(destFrames, srcFrames) frames to dest from src. Anything leftover
496 * is moved to the beginning of m_leftoverBuffer, so empty it first (possibly
497 * with this method). If src and dest overlap, I'll hurt you.
498 */
499void SoundSourceMediaFoundation::copyFrames(
500 qint16 *dest, size_t *destFrames, const qint16 *src, size_t srcFrames)
501{
502 if (srcFrames > *destFrames) {
503 int samplesToCopy(*destFrames * kNumChannels);
504 memcpy(dest, src, samplesToCopy * sizeof(*src));
505 srcFrames -= *destFrames;
506 memmove(m_leftoverBuffer,
507 src + samplesToCopy,
508 srcFrames * kNumChannels * sizeof(*src));
509 *destFrames = 0;
510 m_leftoverBufferLength = srcFrames;
511 } else {
512 int samplesToCopy(srcFrames * kNumChannels);
513 memcpy(dest, src, samplesToCopy * sizeof(*src));
514 *destFrames -= srcFrames;
515 if (src == m_leftoverBuffer) {
516 m_leftoverBufferLength = 0;
517 }
518 }
519}
520
521/**
522 * Convert a 100ns Media Foundation value to a number of seconds.
523 */
524inline qreal SoundSourceMediaFoundation::secondsFromMF(qint64 mf)
525{
526 return static_cast<qreal>(mf) / 1e7;
527}
528
529/**
530 * Convert a number of seconds to a 100ns Media Foundation value.
531 */
532inline qint64 SoundSourceMediaFoundation::mfFromSeconds(qreal sec)
533{
534 return sec * 1e7;
535}
536
537/**
538 * Convert a 100ns Media Foundation value to a frame offset.
539 */
540inline qint64 SoundSourceMediaFoundation::frameFromMF(qint64 mf)
541{
542 return static_cast<qreal>(mf) * kSampleRate / 1e7;
543}
544
545/**
546 * Convert a frame offset to a 100ns Media Foundation value.
547 */
548inline qint64 SoundSourceMediaFoundation::mfFromFrame(qint64 frame)
549{
550 return static_cast<qreal>(frame) / kSampleRate * 1e7;
551}
0\ No newline at end of file552\ No newline at end of file
1553
=== added file 'mixxx/src/soundsourcemediafoundation.h'
--- mixxx/src/soundsourcemediafoundation.h 1970-01-01 00:00:00 +0000
+++ mixxx/src/soundsourcemediafoundation.h 2011-07-20 02:13:34 +0000
@@ -0,0 +1,67 @@
1/**
2 * \file soundsourcemediafoundation.h
3 * \class SoundSourceMediaFoundation
4 * \brief Decodes MPEG4/AAC audio using the SourceReader interface of the
5 * Media Foundation framework included in Windows 7.
6 * \author Bill Good <bkgood at gmail dot com>
7 * \author Albert Santoni <alberts at mixxx dot org>
8 * \date Jan 10, 2011
9 */
10
11/***************************************************************************
12 * *
13 * This program is free software; you can redistribute it and/or modify *
14 * it under the terms of the GNU General Public License as published by *
15 * the Free Software Foundation; either version 2 of the License, or *
16 * (at your option) any later version. *
17 * *
18 ***************************************************************************/
19
20#ifndef SOUNDSOURCEMEDIAFOUNDATION_H
21#define SOUNDSOURCEMEDIAFOUNDATION_H
22
23#include <QFile>
24#include <QString>
25
26#include "defs.h"
27#include "soundsource.h"
28
29class IMFSourceReader;
30class IMFMediaType;
31class IMFMediaSource;
32
33class SoundSourceMediaFoundation : public Mixxx::SoundSource {
34public:
35 SoundSourceMediaFoundation(QString filename);
36 ~SoundSourceMediaFoundation();
37 int open();
38 long seek(long filepos);
39 unsigned read(unsigned long size, const SAMPLE *buffer);
40 inline long unsigned length();
41 int parseHeader();
42 static QList<QString> supportedFileExtensions();
43
44private:
45 bool configureAudioStream();
46 bool readProperties();
47 void copyFrames(qint16 *dest, size_t *destFrames, const qint16 *src,
48 size_t srcFrames);
49 static inline qreal secondsFromMF(qint64 mf);
50 static inline qint64 mfFromSeconds(qreal sec);
51 static inline qint64 frameFromMF(qint64 mf);
52 static inline qint64 mfFromFrame(qint64 frame);
53 QFile m_file;
54 IMFSourceReader *m_pReader;
55 IMFMediaType *m_pAudioType;
56 wchar_t *m_wcFilename;
57 int m_nextFrame;
58 qint16 *m_leftoverBuffer;
59 size_t m_leftoverBufferSize;
60 size_t m_leftoverBufferLength;
61 int m_leftoverBufferPosition;
62 qint64 m_mfDuration;
63 bool m_dead;
64 bool m_seeking;
65};
66
67#endif // ifndef SOUNDSOURCEMEDIAFOUNDATION_H
068
=== modified file 'mixxx/src/soundsourceproxy.cpp'
--- mixxx/src/soundsourceproxy.cpp 2011-03-27 00:50:47 +0000
+++ mixxx/src/soundsourceproxy.cpp 2011-07-20 02:13:34 +0000
@@ -25,6 +25,9 @@
25#ifdef __COREAUDIO__25#ifdef __COREAUDIO__
26#include "soundsourcecoreaudio.h"26#include "soundsourcecoreaudio.h"
27#endif27#endif
28#ifdef __MEDIAFOUNDATION__
29#include "soundsourcemediafoundation.h"
30#endif
28#ifdef __SNDFILE__31#ifdef __SNDFILE__
29#include "soundsourcesndfile.h"32#include "soundsourcesndfile.h"
30#endif33#endif
@@ -131,6 +134,10 @@
131 } else if (SoundSourceCoreAudio::supportedFileExtensions().contains(extension)) {134 } else if (SoundSourceCoreAudio::supportedFileExtensions().contains(extension)) {
132 return new SoundSourceCoreAudio(qFilename);135 return new SoundSourceCoreAudio(qFilename);
133#endif136#endif
137#ifdef __MEDIAFOUNDATION__
138 } else if (SoundSourceMediaFoundation::supportedFileExtensions().contains(extension)) {
139 return new SoundSourceMediaFoundation(qFilename);
140#endif
134 } else if (m_extensionsSupportedByPlugins.contains(extension)) {141 } else if (m_extensionsSupportedByPlugins.contains(extension)) {
135 getSoundSourceFunc getter = m_extensionsSupportedByPlugins.value(extension);142 getSoundSourceFunc getter = m_extensionsSupportedByPlugins.value(extension);
136 if (getter)143 if (getter)
@@ -333,6 +340,9 @@
333#ifdef __COREAUDIO__340#ifdef __COREAUDIO__
334 supportedFileExtensions.append(SoundSourceCoreAudio::supportedFileExtensions());341 supportedFileExtensions.append(SoundSourceCoreAudio::supportedFileExtensions());
335#endif342#endif
343#ifdef __MEDIAFOUNDATION__
344 supportedFileExtensions.append(SoundSourceMediaFoundation::supportedFileExtensions());
345#endif
336 supportedFileExtensions.append(m_extensionsSupportedByPlugins.keys());346 supportedFileExtensions.append(m_extensionsSupportedByPlugins.keys());
337347
338 return supportedFileExtensions;348 return supportedFileExtensions;

Subscribers

People subscribed via source and target branches

to all changes: