Merge lp:~mixxxdevelopers/mixxx/fixes_soundcardSync into lp:mixxx/1.9

Proposed by Sean M. Pappalardo
Status: Merged
Approved by: Sean M. Pappalardo
Approved revision: 2553
Merged at revision: 2744
Proposed branch: lp:~mixxxdevelopers/mixxx/fixes_soundcardSync
Merge into: lp:mixxx/1.9
Diff against target: 276 lines (+91/-40)
4 files modified
mixxx/src/sounddeviceportaudio.cpp (+9/-3)
mixxx/src/soundmanager.cpp (+76/-34)
mixxx/src/soundmanager.h (+5/-2)
mixxx/src/soundmanagerconfig.h (+1/-1)
To merge this branch: bzr merge lp:~mixxxdevelopers/mixxx/fixes_soundcardSync
Reviewer Review Type Date Requested Status
Phillip Whelan code review Approve
Review via email: mp+42801@code.launchpad.net

Commit message

Merging fixes_soundcardSync which now causes Mixxx to lock to the clock of the master output card.

Description of the change

This branch re-works when Mixxx calculates new sound buffers and now only does it in sync with the sound card that is selected as the master output. (If no master out is selected, it uses one of the direct deck outputs.) This eliminates pops and stutters on the master/direct deck output at least.

If only one card is enabled, it functions as the master.

There is commented code in SoundManager::requestBuffer() that will print whenever a slave sound card drops or repeats a buffer which is very useful for testing.

To post a comment you must log in.
Revision history for this message
Phillip Whelan (pwhelan) wrote :

Resubmit this for trunk, since this did not make the feature freeze for 1.9.

review: Disapprove
2553. By Phillip Whelan

Merging from the 1.9 branch in preparation for merging into the 1.9 release.

Revision history for this message
Phillip Whelan (pwhelan) wrote :

I have reviewed the code and it looks all good; The code is now more elegant, is more flexible and creates no new problems.

I tested this on my desktop; an Ubuntu 10.10 running a vanilla kernel, and experienced no regressions on my Delta 1010LT multi-out card.

Reconsidering my earlier position, I think this should be merged for Mixxx 1.9.1; I would delay it until after the first release, but then get it out as soon as possible. This is only to not further delay the 1.9.0 release. The code is simple enough, and despite how critical it is, it is worth it since it will benefit a large number of novice users.

review: Approve (code review)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'mixxx/src/sounddeviceportaudio.cpp'
2--- mixxx/src/sounddeviceportaudio.cpp 2010-12-01 06:29:29 +0000
3+++ mixxx/src/sounddeviceportaudio.cpp 2010-12-05 22:36:10 +0000
4@@ -102,12 +102,12 @@
5 if (m_dSampleRate <= 0) {
6 m_dSampleRate = 44100.0f;
7 }
8- qDebug() << "m_dSampleRate" << m_dSampleRate;
9+ qDebug() << "Requested sample rate:" << m_dSampleRate;
10
11 //Get latency in milleseconds
12 qDebug() << "framesPerBuffer:" << m_framesPerBuffer;
13 double latencyMSec = m_framesPerBuffer / m_dSampleRate * 1000;
14- qDebug() << "latency in milliseconds:" << latencyMSec;
15+ qDebug() << "Mixxx latency in milliseconds:" << latencyMSec;
16
17 qDebug() << "output channels:" << m_outputParams.channelCount << "| input channels:"
18 << m_inputParams.channelCount;
19@@ -210,6 +210,12 @@
20 }
21 else
22 qDebug() << "PortAudio: Started stream successfully";
23+
24+ // Get the actual details of the stream & update Mixxx's data
25+ const PaStreamInfo* streamDetails = Pa_GetStreamInfo(m_pStream);
26+ m_dSampleRate = streamDetails->sampleRate;
27+ latencyMSec = streamDetails->outputLatency*1000;
28+ qDebug() << "Actual sample rate: " << m_dSampleRate << "Hz, latency:" << latencyMSec << "ms";
29
30 //Update the samplerate and latency ControlObjects, which allow the waveform view to properly correct
31 //for the latency.
32@@ -334,7 +340,7 @@
33 {
34 assert(iFrameSize > 0);
35 QHash<AudioOutput, const CSAMPLE*> outputAudio
36- = m_pSoundManager->requestBuffer(m_audioOutputs, framesPerBuffer);
37+ = m_pSoundManager->requestBuffer(m_audioOutputs, framesPerBuffer, this, Pa_GetStreamTime(m_pStream));
38
39 // Reset sample for each open channel
40 memset(output, 0, framesPerBuffer * iFrameSize * sizeof(*output));
41
42=== modified file 'mixxx/src/soundmanager.cpp'
43--- mixxx/src/soundmanager.cpp 2010-12-01 06:29:29 +0000
44+++ mixxx/src/soundmanager.cpp 2010-12-05 22:36:10 +0000
45@@ -42,9 +42,7 @@
46 m_pConfig = pConfig;
47 m_pMaster = _master;
48
49- iNumDevicesOpenedForOutput = 0;
50- iNumDevicesOpenedForInput = 0;
51- iNumDevicesHaveRequestedBuffer = 0;
52+ clearOperativeVariables();
53
54 //These are ControlObjectThreadMains because all the code that
55 //uses them is called from the GUI thread (stuff like opening soundcards).
56@@ -100,6 +98,17 @@
57 }
58
59 /**
60+ * Clears all variables used in operation.
61+ * @note This is in a function because it's done in a few places
62+ */
63+void SoundManager::clearOperativeVariables()
64+{
65+ iNumDevicesOpenedForOutput = 0;
66+ iNumDevicesOpenedForInput = 0;
67+ m_pClkRefDevice = NULL;
68+}
69+
70+/**
71 * Returns a pointer to the EngineMaster instance this SoundManager
72 * is using.
73 * @note pointer is const at this point because this is only being inserted
74@@ -196,7 +205,7 @@
75 {
76 //qDebug() << "SoundManager::closeDevices()";
77 QListIterator<SoundDevice*> dev_it(m_devices);
78-
79+
80 //requestBufferMutex.lock(); //Ensures we don't kill a stream in the middle of a callback call.
81 //Note: if we're using Pa_StopStream() (like now), we don't need
82 // to lock. PortAudio stops the threads nicely.
83@@ -208,9 +217,7 @@
84 //requestBufferMutex.unlock();
85
86 //requestBufferMutex.lock();
87- iNumDevicesOpenedForOutput = 0;
88- iNumDevicesOpenedForInput = 0;
89- iNumDevicesHaveRequestedBuffer = 0;
90+ clearOperativeVariables();
91 //requestBufferMutex.unlock();
92
93 m_outputBuffers.clear(); // anti-cruft (safe because outputs only have
94@@ -349,7 +356,7 @@
95 {
96 qDebug() << "SoundManager::setupDevices()";
97 int err = 0;
98- iNumDevicesOpenedForOutput = iNumDevicesOpenedForInput = 0;
99+ clearOperativeVariables();
100 int devicesAttempted = 0;
101 int devicesOpened = 0;
102
103@@ -400,12 +407,19 @@
104 switch (out.getType()) {
105 case AudioPath::MASTER:
106 m_outputBuffers[out] = m_pMaster->getMasterBuffer();
107+ // Set the master output device's clock as the reference
108+ // to avoid any clock-related disturbances to the public address
109+ m_pClkRefDevice = device;
110 break;
111 case AudioPath::HEADPHONES:
112 m_outputBuffers[out] = m_pMaster->getHeadphoneBuffer();
113 break;
114 case AudioPath::DECK:
115 m_outputBuffers[out] = m_pMaster->getChannelBuffer(out.getIndex());
116+ // If a reference device has not yet been set (such as when the Master output
117+ // isn't being used with an external mixer,) use the first deck output
118+ // to avoid any clock-related disturbances on at least one public output
119+ if (!m_pClkRefDevice) m_pClkRefDevice = device;
120 break;
121 default:
122 break;
123@@ -427,9 +441,17 @@
124 }
125 }
126 }
127+
128+ if (!m_pClkRefDevice) {
129+ QList<SoundDevice*> outputDevices = getDeviceList(m_config.getAPI(), true, false);
130+ SoundDevice* device = outputDevices.first();
131+ qWarning() << "Output sound device clock reference not set! Using" << device->getDisplayName();
132+ m_pClkRefDevice = device;
133+ }
134+ else qDebug() << "Using" << m_pClkRefDevice->getDisplayName() << "as output sound device clock reference";
135
136- qDebug() << "iNumDevicesOpenedForOutput:" << iNumDevicesOpenedForOutput;
137- qDebug() << "iNumDevicesOpenedForInput:" << iNumDevicesOpenedForInput;
138+ qDebug() << iNumDevicesOpenedForOutput << "output sound devices opened";
139+ qDebug() << iNumDevicesOpenedForInput << "input sound devices opened";
140
141 // returns OK if we were able to open all the devices the user
142 // wanted
143@@ -483,33 +505,53 @@
144
145 //Requests a buffer in the proper format, if we're prepared to give one.
146 QHash<AudioOutput, const CSAMPLE*>
147-SoundManager::requestBuffer(QList<AudioOutput> outputs, unsigned long iFramesPerBuffer)
148+SoundManager::requestBuffer(QList<AudioOutput> outputs, unsigned long iFramesPerBuffer, SoundDevice* device, double streamTime)
149 {
150 Q_UNUSED(outputs); // unused, we just give the caller the full hash -bkgood
151 //qDebug() << "SoundManager::requestBuffer()";
152-
153- //qDebug() << "numOpenedDevices" << iNumOpenedDevices;
154- //qDebug() << "iNumDevicesHaveRequestedBuffer" << iNumDevicesHaveRequestedBuffer;
155-
156- //When the first device requests a buffer...
157- if (requestBufferMutex.tryLock())
158+
159+ /*
160+ // Display when sound cards drop or duplicate buffers (use for testing only)
161+ if (iNumDevicesOpenedForOutput>1) {
162+ // Running total of requested frames
163+ long currentFrameCount = 0;
164+ if (m_deviceFrameCount.contains(device)) currentFrameCount=m_deviceFrameCount.value(device);
165+ m_deviceFrameCount.insert(device, currentFrameCount+iFramesPerBuffer); // Overwrites existing value if already present
166+ // Get current time in milliseconds
167+// uint t = QDateTime::currentDateTime().toTime_t()*1000+QDateTime::currentDateTime().toString("zzz").toUint();
168+
169+ if (device != m_pClkRefDevice) { // If not the reference device,
170+ // Detect dropped frames/buffers
171+ long sdifference = m_deviceFrameCount.value(m_pClkRefDevice)-m_deviceFrameCount.value(device);
172+ QString message = "dropped";
173+ if (sdifference < 0) message = "duplicated";
174+ if (sdifference != 0) {
175+ m_deviceFrameCount.clear();
176+ message = QString("%1 %2 %3 frames (%4 buffers)")
177+ .arg(device->getDisplayName())
178+ .arg(message)
179+ .arg(fabs(sdifference))
180+ .arg(fabs(sdifference)/iFramesPerBuffer);
181+ qWarning() << message;
182+ }
183+ }
184+ }
185+ // End dropped/duped buffer display
186+ */
187+
188+ //When the clock reference device requests a buffer...
189+ if (device == m_pClkRefDevice && requestBufferMutex.tryLock())
190 {
191- if (iNumDevicesHaveRequestedBuffer == 0)
192- {
193- //First, sync control parameters with changes from GUI thread
194- sync();
195-
196- //Process a block of samples for output. iFramesPerBuffer is the
197- //number of samples for one channel, but the EngineObject
198- //architecture expects number of samples for two channels
199- //as input (buffer size) so...
200- m_pMaster->process(0, 0, iFramesPerBuffer*2);
201-
202- }
203- iNumDevicesHaveRequestedBuffer++;
204-
205- if (iNumDevicesHaveRequestedBuffer >= iNumDevicesOpenedForOutput)
206- iNumDevicesHaveRequestedBuffer = 0;
207+ // Only generate a new buffer for the clock reference card
208+// qDebug() << "New buffer for" << device->getDisplayName() << "of size" << iFramesPerBuffer;
209+ //First, sync control parameters with changes from GUI thread
210+ sync();
211+
212+ //Process a block of samples for output. iFramesPerBuffer is the
213+ //number of samples for one channel, but the EngineObject
214+ //architecture expects number of samples for two channels
215+ //as input (buffer size) so...
216+ m_pMaster->process(0, 0, iFramesPerBuffer*2);
217
218 requestBufferMutex.unlock();
219 }
220@@ -617,4 +659,4 @@
221 }
222 //TODO: Add pass-through option here (and push it into EngineMaster)...
223 // (or maybe save it, and then have requestBuffer() push it into EngineMaster)...
224-}
225+}
226\ No newline at end of file
227
228=== modified file 'mixxx/src/soundmanager.h'
229--- mixxx/src/soundmanager.h 2010-11-25 05:44:03 +0000
230+++ mixxx/src/soundmanager.h 2010-12-05 22:36:10 +0000
231@@ -60,7 +60,7 @@
232 int setConfig(SoundManagerConfig config);
233 void checkConfig();
234 QHash<AudioOutput, const CSAMPLE*>
235- requestBuffer(QList<AudioOutput> outputs, unsigned long iFramesPerBuffer);
236+ requestBuffer(QList<AudioOutput> outputs, unsigned long iFramesPerBuffer, SoundDevice*, double streamTime=0);
237 void pushBuffer(QList<AudioInput> inputs, short *inputBuffer,
238 unsigned long iFramesPerBuffer, unsigned int iFrameSize);
239 signals:
240@@ -68,6 +68,8 @@
241 public slots:
242 void sync();
243 private:
244+ void clearOperativeVariables();
245+
246 EngineMaster *m_pMaster;
247 ConfigObject<ConfigValue> *m_pConfig;
248 QList<SoundDevice*> m_devices;
249@@ -75,12 +77,13 @@
250 QString m_hostAPI;
251 QHash<AudioOutput, const CSAMPLE*> m_outputBuffers;
252 QHash<AudioInput, short*> m_inputBuffers; /** Audio received from input */
253+ QHash<SoundDevice*, long> m_deviceFrameCount; /** Sound card sync */
254+ SoundDevice* m_pClkRefDevice; /** Sound card sync */
255 #ifdef __VINYLCONTROL__
256 QList<VinylControlProxy*> m_VinylControl;
257 #endif
258 unsigned int iNumDevicesOpenedForOutput;
259 unsigned int iNumDevicesOpenedForInput;
260- unsigned int iNumDevicesHaveRequestedBuffer;
261 QMutex requestBufferMutex;
262 SoundManagerConfig m_config;
263 SoundDevice *m_pErrorDevice;
264
265=== modified file 'mixxx/src/soundmanagerconfig.h'
266--- mixxx/src/soundmanagerconfig.h 2010-09-27 06:39:39 +0000
267+++ mixxx/src/soundmanagerconfig.h 2010-12-05 22:36:10 +0000
268@@ -29,7 +29,7 @@
269
270 #define DEFAULT_API "None"
271
272-const unsigned int MAX_LATENCY = 6; // this represents latency values from 1 ms to about
273+const unsigned int MAX_LATENCY = 7; // this represents latency values from 1 ms to about
274 // 180 ms, should be sufficient -- bkgood
275 // 20100726 latencies ~80 and ~170 are utterly broken right now, at least on
276 // linux/ALSA (although I think this is a Mixxx bug somewhere around

Subscribers

People subscribed via source and target branches