source: trunk/src/3rdparty/phonon/qt7/quicktimeaudioplayer.mm

Last change on this file was 846, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 17.1 KB
Line 
1/* This file is part of the KDE project.
2
3 Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4
5 This library is free software: you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation, either version 2.1 or 3 of the License.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public License
15 along with this library. If not, see <http://www.gnu.org/licenses/>.
16*/
17
18#import <QTKit/QTMovie.h>
19
20#include "quicktimeaudioplayer.h"
21#include "quicktimevideoplayer.h"
22#include "audiograph.h"
23#include "medianodeevent.h"
24#include "medianode.h"
25
26QT_BEGIN_NAMESPACE
27
28namespace Phonon
29{
30namespace QT7
31{
32
33QuickTimeAudioPlayer::QuickTimeAudioPlayer() : AudioNode(0, 1)
34{
35 m_state = NoMedia;
36 m_videoPlayer = 0;
37 m_audioChannelLayout = 0;
38 m_sliceList = 0;
39 m_sliceCount = 30;
40 m_maxExtractionPacketCount = 4096;
41 m_audioExtractionComplete = false;
42 m_audioEnabled = true;
43 m_samplesRemaining = -1;
44 m_startTime = 0;
45 m_sampleTimeStamp = 0;
46 m_audioUnitIsReset = true;
47
48#ifdef QUICKTIME_C_API_AVAILABLE
49 m_audioExtractionRef = 0;
50#endif
51}
52
53QuickTimeAudioPlayer::~QuickTimeAudioPlayer()
54{
55 unsetVideoPlayer();
56}
57
58void QuickTimeAudioPlayer::unsetVideoPlayer()
59{
60 if (m_audioUnit){
61 OSStatus err = AudioUnitReset(m_audioUnit, kAudioUnitScope_Global, 0);
62 BACKEND_ASSERT2(err == noErr, "Could not reset audio player unit when unsetting movie", FATAL_ERROR)
63 }
64
65#ifdef QUICKTIME_C_API_AVAILABLE
66 if (m_audioExtractionRef && m_videoPlayer && m_videoPlayer->hasMovie())
67 MovieAudioExtractionEnd(m_audioExtractionRef);
68 m_audioExtractionRef = 0;
69#endif
70
71 if (m_audioChannelLayout){
72 free(m_audioChannelLayout);
73 m_audioChannelLayout = 0;
74 }
75
76 if (m_sliceList){
77 for (int i=0; i<m_sliceCount; i++)
78 free(m_sliceList[i].mBufferList);
79 free(m_sliceList);
80 m_sliceList = 0;
81 }
82
83 m_videoPlayer = 0;
84 m_audioExtractionComplete = false;
85 m_samplesRemaining = -1;
86 m_sampleTimeStamp = 0;
87 m_state = NoMedia;
88}
89
90void QuickTimeAudioPlayer::enableAudio(bool enable)
91{
92 // Remember to seek after enabling audio.
93 if (enable == m_audioEnabled)
94 return;
95
96 m_audioEnabled = enable;
97 if (!enable)
98 flush();
99}
100
101bool QuickTimeAudioPlayer::audioEnabled()
102{
103 return m_audioEnabled;
104}
105
106void QuickTimeAudioPlayer::setVideoPlayer(QuickTimeVideoPlayer *videoPlayer)
107{
108 unsetVideoPlayer();
109 if (videoPlayer && videoPlayer->hasMovie()){
110 m_videoPlayer = videoPlayer;
111 initSoundExtraction();
112 allocateSoundSlices();
113 m_state = Paused;
114 seek(0);
115 }
116}
117
118QuickTimeVideoPlayer *QuickTimeAudioPlayer::videoPlayer()
119{
120 return m_videoPlayer;
121}
122
123void QuickTimeAudioPlayer::scheduleAudioToGraph()
124{
125 if (!m_videoPlayer || !m_audioEnabled || m_audioExtractionComplete || m_state != Playing)
126 return;
127
128 // Schedule audio slices, and detect if everything went OK.
129 // If not, flag the need for another audio system, but let
130 // the end app know about it:
131 gClearError();
132 scheduleSoundSlices();
133 if (gGetErrorType() != NO_ERROR){
134 gClearError();
135 if (m_audioGraph)
136 m_audioGraph->setStatusCannotPlay();
137 }
138}
139
140void QuickTimeAudioPlayer::flush()
141{
142 // Empty scheduled audio data, so playback
143 // will stop. Call seek to refill data again.
144 if (m_audioUnit){
145 m_startTime = currentTime();
146 OSStatus err = AudioUnitReset(m_audioUnit, kAudioUnitScope_Global, 0);
147 BACKEND_ASSERT2(err == noErr, "Could not reset audio player unit on pause", FATAL_ERROR)
148 m_audioUnitIsReset = true;
149 }
150}
151
152void QuickTimeAudioPlayer::pause()
153{
154 m_state = Paused;
155 flush();
156}
157
158void QuickTimeAudioPlayer::play()
159{
160 m_state = Playing;
161 if (!m_audioEnabled)
162 return;
163 if (m_audioUnitIsReset)
164 seek(m_startTime);
165 else
166 scheduleAudioToGraph();
167}
168
169bool QuickTimeAudioPlayer::isPlaying()
170{
171 return m_videoPlayer && m_state == Playing;
172}
173
174void QuickTimeAudioPlayer::seek(quint64 milliseconds)
175{
176 if (!m_videoPlayer || !m_videoPlayer->hasMovie())
177 return;
178 if (milliseconds > m_videoPlayer->duration())
179 milliseconds = m_videoPlayer->duration();
180 if (!m_audioUnitIsReset && milliseconds == currentTime())
181 return;
182
183 m_startTime = milliseconds;
184
185 // Since the graph may be running (advancing time), there is
186 // no point in seeking if were not going to play immidiatly:
187 if (m_state != Playing)
188 return;
189 if (!m_audioUnit)
190 return;
191 if (!m_audioEnabled || !m_videoPlayer->isSeekable())
192 return;
193
194 // Reset (and stop playing):
195 OSStatus err;
196 if (!m_audioUnitIsReset){
197 err = AudioUnitReset(m_audioUnit, kAudioUnitScope_Global, 0);
198 BACKEND_ASSERT2(err == noErr, "Could not reset audio player unit before seek", FATAL_ERROR)
199 }
200 m_sampleTimeStamp = 0;
201 for (int i = 0; i < m_sliceCount; i++)
202 m_sliceList[i].mFlags = kScheduledAudioSliceFlag_Complete;
203
204 // Start to play again immidiatly:
205 AudioTimeStamp timeStamp;
206 memset(&timeStamp, 0, sizeof(timeStamp));
207 timeStamp.mFlags = kAudioTimeStampSampleTimeValid;
208 timeStamp.mSampleTime = -1;
209 err = AudioUnitSetProperty(m_audioUnit,
210 kAudioUnitProperty_ScheduleStartTimeStamp, kAudioUnitScope_Global,
211 0, &timeStamp, sizeof(timeStamp));
212 BACKEND_ASSERT2(err == noErr, "Could not set schedule start time stamp on audio player unit", FATAL_ERROR)
213
214 // Seek back to 'now' in the movie:
215 TimeRecord timeRec;
216 timeRec.scale = m_videoPlayer->timeScale();
217 timeRec.base = 0;
218 timeRec.value.hi = 0;
219 timeRec.value.lo = (milliseconds / 1000.0f) * timeRec.scale;
220
221#ifdef QUICKTIME_C_API_AVAILABLE
222 err = MovieAudioExtractionSetProperty(m_audioExtractionRef,
223 kQTPropertyClass_MovieAudioExtraction_Movie,
224 kQTMovieAudioExtractionMoviePropertyID_CurrentTime,
225 sizeof(TimeRecord), &timeRec);
226 BACKEND_ASSERT2(err == noErr, "Could not set current time on audio player unit", FATAL_ERROR)
227#endif
228
229 float durationLeftSec = float(m_videoPlayer->duration() - milliseconds) / 1000.0f;
230 m_samplesRemaining = (durationLeftSec > 0) ? (durationLeftSec * m_audioStreamDescription.mSampleRate) : -1;
231 m_audioExtractionComplete = false;
232 m_audioUnitIsReset = false;
233 scheduleAudioToGraph();
234
235}
236
237quint64 QuickTimeAudioPlayer::currentTime()
238{
239 if (!m_audioUnit){
240 if (m_videoPlayer)
241 return m_videoPlayer->currentTime();
242 else
243 return m_startTime;
244 }
245
246 Float64 currentUnitTime = getTimeInSamples(kAudioUnitProperty_CurrentPlayTime);
247 if (currentUnitTime == -1)
248 currentUnitTime = 0;
249
250 quint64 cTime = quint64(m_startTime +
251 float(currentUnitTime / float(m_audioStreamDescription.mSampleRate)) * 1000.0f);
252 return (m_videoPlayer && cTime > m_videoPlayer->duration()) ? m_videoPlayer->duration() : cTime;
253}
254
255QString QuickTimeAudioPlayer::currentTimeString()
256{
257 return QuickTimeVideoPlayer::timeToString(currentTime());
258}
259
260bool QuickTimeAudioPlayer::hasAudio()
261{
262 if (!m_videoPlayer)
263 return false;
264
265 return m_videoPlayer->hasAudio();
266}
267
268bool QuickTimeAudioPlayer::soundPlayerIsAwailable()
269{
270 QuickTimeAudioPlayer player;
271 ComponentDescription d = player.getAudioNodeDescription();
272 return FindNextComponent(0, &d);
273}
274
275ComponentDescription QuickTimeAudioPlayer::getAudioNodeDescription() const
276{
277 ComponentDescription description;
278 description.componentType = kAudioUnitType_Generator;
279 description.componentSubType = kAudioUnitSubType_ScheduledSoundPlayer;
280 description.componentManufacturer = kAudioUnitManufacturer_Apple;
281 description.componentFlags = 0;
282 description.componentFlagsMask = 0;
283 return description;
284}
285
286void QuickTimeAudioPlayer::initializeAudioUnit()
287{
288}
289
290bool QuickTimeAudioPlayer::fillInStreamSpecification(AudioConnection *connection, ConnectionSide side)
291{
292 if (!m_videoPlayer){
293 if (side == Source)
294 DEBUG_AUDIO_STREAM("QuickTimeAudioPlayer" << int(this) << "is source, but has no movie to use for stream spec fill.")
295 return true;
296 }
297
298 if (side == Source){
299 DEBUG_AUDIO_STREAM("QuickTimeAudioPlayer" << int(this) << "is source, and fills in stream spec from movie.")
300 connection->m_sourceStreamDescription = m_audioStreamDescription;
301 connection->m_sourceChannelLayout = (AudioChannelLayout *) malloc(m_audioChannelLayoutSize);
302 memcpy(connection->m_sourceChannelLayout, m_audioChannelLayout, m_audioChannelLayoutSize);
303 connection->m_sourceChannelLayoutSize = m_audioChannelLayoutSize;
304 connection->m_hasSourceSpecification = true;
305 }
306 return true;
307}
308
309long QuickTimeAudioPlayer::regularTaskFrequency(){
310 if (!m_audioEnabled || !m_audioUnit || (m_audioGraph && m_audioGraph->graphCannotPlay()))
311 return INT_MAX;
312
313 // Calculate how much audio in
314 // milliseconds our slices can hold:
315 int packetNeedPerSecond = m_audioStreamDescription.mSampleRate / m_maxExtractionPacketCount;
316 long bufferTimeLengthSec = float(m_sliceCount) / float(packetNeedPerSecond);
317 // Make sure we also get some time to fill the
318 // buffer, so divide the time by two:
319 return (bufferTimeLengthSec * (1000 / 2));
320}
321
322void QuickTimeAudioPlayer::initSoundExtraction()
323{
324#ifdef QUICKTIME_C_API_AVAILABLE
325
326 // Initilize the extraction:
327 OSStatus err = noErr;
328 err = MovieAudioExtractionBegin([m_videoPlayer->qtMovie() quickTimeMovie], 0, &m_audioExtractionRef);
329 BACKEND_ASSERT2(err == noErr, "Could not start audio extraction on audio player unit", FATAL_ERROR)
330 m_discrete = false;
331#if 0
332 // Extract all channels as descrete:
333 err = MovieAudioExtractionSetProperty(audioExtractionRef,
334 kQTPropertyClass_MovieAudioExtraction_Movie,
335 kQTMovieAudioExtractionMoviePropertyID_AllChannelsDiscrete,
336 sizeof (discrete),
337 &discrete);
338 BACKEND_ASSERT2(err == noErr, "Could not set channels discrete on audio player unit", FATAL_ERROR)
339#endif
340
341 // Get the size of the audio channel layout (may include offset):
342 err = MovieAudioExtractionGetPropertyInfo(m_audioExtractionRef,
343 kQTPropertyClass_MovieAudioExtraction_Audio,
344 kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout,
345 0, &m_audioChannelLayoutSize, 0);
346 BACKEND_ASSERT2(err == noErr, "Could not get channel layout size from audio extraction", FATAL_ERROR)
347
348 // Allocate memory for the layout
349 m_audioChannelLayout = (AudioChannelLayout *) calloc(1, m_audioChannelLayoutSize);
350 BACKEND_ASSERT2(m_audioChannelLayout, "Could not allocate memory for channel layout on audio player unit", FATAL_ERROR)
351
352 // Get the layout:
353 err = MovieAudioExtractionGetProperty(m_audioExtractionRef,
354 kQTPropertyClass_MovieAudioExtraction_Audio,
355 kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout,
356 m_audioChannelLayoutSize, m_audioChannelLayout, 0);
357 BACKEND_ASSERT2(err == noErr, "Could not get channel layout from audio extraction", FATAL_ERROR)
358
359 // Get audio stream description:
360 err = MovieAudioExtractionGetProperty(m_audioExtractionRef,
361 kQTPropertyClass_MovieAudioExtraction_Audio,
362 kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription,
363 sizeof(m_audioStreamDescription), &m_audioStreamDescription, 0);
364 BACKEND_ASSERT2(err == noErr, "Could not get audio stream description from audio extraction", FATAL_ERROR)
365
366#endif // QUICKTIME_C_API_AVAILABLE
367}
368
369void QuickTimeAudioPlayer::allocateSoundSlices()
370{
371#ifdef QUICKTIME_C_API_AVAILABLE
372
373 // m_sliceList will contain a specified number of ScheduledAudioSlice-s that each can
374 // carry audio from extraction, and be scheduled for playback at an audio unit.
375 // Each ScheduledAudioSlice will contain several audio buffers, one for each sound channel.
376 // Each buffer will carry (at most) a specified number of sound packets, and each packet can
377 // contain one or more frames.
378
379 // Create a list of ScheduledAudioSlices:
380 m_sliceList = (ScheduledAudioSlice *) calloc(m_sliceCount, sizeof(ScheduledAudioSlice));
381 BACKEND_ASSERT2(m_sliceList, "Could not allocate memory for audio slices", FATAL_ERROR)
382 bzero(m_sliceList, m_sliceCount * sizeof(ScheduledAudioSlice));
383
384 // Calculate the size of the different structures needed:
385 int packetsBufferSize = m_maxExtractionPacketCount * m_audioStreamDescription.mBytesPerPacket;
386 int channels = m_audioStreamDescription.mChannelsPerFrame;
387 int audioBufferListSize = int(sizeof(AudioBufferList) + (channels-1) * sizeof(AudioBuffer));
388 int mallocSize = audioBufferListSize + (packetsBufferSize * m_audioStreamDescription.mChannelsPerFrame);
389
390 // Round off to Altivec sizes:
391 packetsBufferSize = int(((packetsBufferSize + 15) / 16) * 16);
392 audioBufferListSize = int(((audioBufferListSize + 15) / 16) * 16);
393
394 for (int sliceIndex = 0; sliceIndex < m_sliceCount; ++sliceIndex){
395 // Create the memory chunk for this audio slice:
396 AudioBufferList *audioBufferList = (AudioBufferList*) calloc(1, mallocSize);
397 BACKEND_ASSERT2(audioBufferList, "Could not allocate memory for audio buffer list", FATAL_ERROR)
398
399 // The AudioBufferList contains an AudioBuffer for each channel in the audio stream:
400 audioBufferList->mNumberBuffers = m_audioStreamDescription.mChannelsPerFrame;
401 for (uint i = 0; i < audioBufferList->mNumberBuffers; ++i){
402 audioBufferList->mBuffers[i].mNumberChannels = 1;
403 audioBufferList->mBuffers[i].mData = (char *) audioBufferList + audioBufferListSize + (i * packetsBufferSize);
404 audioBufferList->mBuffers[i].mDataByteSize = packetsBufferSize;
405 }
406
407 m_sliceList[sliceIndex].mBufferList = audioBufferList;
408 m_sliceList[sliceIndex].mNumberFrames = m_maxExtractionPacketCount;
409 m_sliceList[sliceIndex].mTimeStamp.mFlags = kAudioTimeStampSampleTimeValid;
410 m_sliceList[sliceIndex].mCompletionProcUserData = 0;
411 m_sliceList[sliceIndex].mCompletionProc = 0;
412 m_sliceList[sliceIndex].mFlags = kScheduledAudioSliceFlag_Complete;
413 m_sliceList[sliceIndex].mReserved = 0;
414 }
415
416#endif // QUICKTIME_C_API_AVAILABLE
417}
418
419void QuickTimeAudioPlayer::scheduleSoundSlices()
420{
421#ifdef QUICKTIME_C_API_AVAILABLE
422
423 PhononAutoReleasePool pool;
424 // For each completed (or never used) slice, fill and schedule it.
425 for (int sliceIndex = 0; sliceIndex < m_sliceCount; ++sliceIndex){
426 if (m_sliceList[sliceIndex].mFlags & kScheduledAudioSliceFlag_Complete){
427 if (m_samplesRemaining == 0)
428 m_audioExtractionComplete = true;
429
430 if (!m_audioExtractionComplete){
431 // Determine how many samples to read:
432 int samplesCount = m_samplesRemaining;
433 if ((samplesCount > m_maxExtractionPacketCount) || (samplesCount == -1))
434 samplesCount = m_maxExtractionPacketCount;
435 m_sliceList[sliceIndex].mTimeStamp.mSampleTime = m_sampleTimeStamp;
436
437 // Reset buffer sizes:
438 int byteSize = samplesCount * m_audioStreamDescription.mBytesPerPacket;
439 for (uint i = 0; i < m_sliceList[sliceIndex].mBufferList->mNumberBuffers; ++i)
440 m_sliceList[sliceIndex].mBufferList->mBuffers[i].mDataByteSize = byteSize;
441
442 // Do the extraction:
443 UInt32 flags = 0;
444 UInt32 samplesRead = samplesCount;
445 OSStatus err = MovieAudioExtractionFillBuffer(
446 m_audioExtractionRef, &samplesRead, m_sliceList[sliceIndex].mBufferList, &flags);
447 BACKEND_ASSERT2(err == noErr, "Could not fill audio buffers from audio extraction", FATAL_ERROR)
448 m_audioExtractionComplete = (flags & kQTMovieAudioExtractionComplete);
449
450 // Play the slice:
451 if (samplesRead != 0 && m_audioUnit != 0){
452 m_sliceList[sliceIndex].mNumberFrames = samplesRead;
453 err = AudioUnitSetProperty(m_audioUnit,
454 kAudioUnitProperty_ScheduleAudioSlice, kAudioUnitScope_Global,
455 0, &m_sliceList[sliceIndex], sizeof(ScheduledAudioSlice));
456 BACKEND_ASSERT2(err == noErr, "Could not schedule audio buffers on audio unit", FATAL_ERROR)
457 } else
458 m_sliceList[sliceIndex].mFlags = kScheduledAudioSliceFlag_Complete;
459
460 // Move the window:
461 m_sampleTimeStamp += samplesRead;
462 if (m_samplesRemaining != -1)
463 m_samplesRemaining -= samplesRead;
464 }
465 }
466 }
467
468#endif // QUICKTIME_C_API_AVAILABLE
469}
470
471void QuickTimeAudioPlayer::mediaNodeEvent(const MediaNodeEvent *event)
472{
473 switch (event->type()){
474 case MediaNodeEvent::AudioGraphAboutToBeDeleted:
475 case MediaNodeEvent::AboutToRestartAudioStream:
476 case MediaNodeEvent::StartConnectionChange:
477 m_startTime = currentTime();
478 break;
479 case MediaNodeEvent::AudioGraphInitialized:
480 case MediaNodeEvent::RestartAudioStreamRequest:
481 case MediaNodeEvent::EndConnectionChange:
482 if (m_state == Playing)
483 seek(m_startTime);
484 break;
485 default:
486 break;
487 }
488}
489
490}}
491
492QT_END_NAMESPACE
493
Note: See TracBrowser for help on using the repository browser.