diff -r 000000000000 -r 1918ee327afb src/3rdparty/phonon/qt7/quicktimeaudioplayer.mm --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/3rdparty/phonon/qt7/quicktimeaudioplayer.mm Mon Jan 11 14:00:40 2010 +0000 @@ -0,0 +1,491 @@ +/* This file is part of the KDE project. + + Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). + + This library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2.1 or 3 of the License. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with this library. If not, see . +*/ + +#include "quicktimeaudioplayer.h" +#include "quicktimevideoplayer.h" +#include "audiograph.h" +#include "medianodeevent.h" +#include "medianode.h" + +QT_BEGIN_NAMESPACE + +namespace Phonon +{ +namespace QT7 +{ + +QuickTimeAudioPlayer::QuickTimeAudioPlayer() : AudioNode(0, 1) +{ + m_state = NoMedia; + m_videoPlayer = 0; + m_audioChannelLayout = 0; + m_sliceList = 0; + m_sliceCount = 30; + m_maxExtractionPacketCount = 4096; + m_audioExtractionComplete = false; + m_audioEnabled = true; + m_samplesRemaining = -1; + m_startTime = 0; + m_sampleTimeStamp = 0; + m_audioUnitIsReset = true; + +#ifdef QUICKTIME_C_API_AVAILABLE + m_audioExtractionRef = 0; +#endif +} + +QuickTimeAudioPlayer::~QuickTimeAudioPlayer() +{ + unsetVideoPlayer(); +} + +void QuickTimeAudioPlayer::unsetVideoPlayer() +{ + if (m_audioUnit){ + OSStatus err = AudioUnitReset(m_audioUnit, kAudioUnitScope_Global, 0); + BACKEND_ASSERT2(err == noErr, "Could not reset audio player unit when unsetting movie", FATAL_ERROR) + } + +#ifdef QUICKTIME_C_API_AVAILABLE + if (m_audioExtractionRef && m_videoPlayer && m_videoPlayer->hasMovie()) + MovieAudioExtractionEnd(m_audioExtractionRef); + m_audioExtractionRef = 0; +#endif + + if (m_audioChannelLayout){ + free(m_audioChannelLayout); + m_audioChannelLayout = 0; + } + + if (m_sliceList){ + for (int i=0; ihasMovie()){ + m_videoPlayer = videoPlayer; + initSoundExtraction(); + allocateSoundSlices(); + m_state = Paused; + seek(0); + } +} + +QuickTimeVideoPlayer *QuickTimeAudioPlayer::videoPlayer() +{ + return m_videoPlayer; +} + +void QuickTimeAudioPlayer::scheduleAudioToGraph() +{ + if (!m_videoPlayer || !m_audioEnabled || m_audioExtractionComplete || m_state != Playing) + return; + + // Schedule audio slices, and detect if everything went OK. + // If not, flag the need for another audio system, but let + // the end app know about it: + gClearError(); + scheduleSoundSlices(); + if (gGetErrorType() != NO_ERROR){ + gClearError(); + if (m_audioGraph) + m_audioGraph->setStatusCannotPlay(); + } +} + +void QuickTimeAudioPlayer::flush() +{ + // Empty scheduled audio data, so playback + // will stop. Call seek to refill data again. + if (m_audioUnit){ + m_startTime = currentTime(); + OSStatus err = AudioUnitReset(m_audioUnit, kAudioUnitScope_Global, 0); + BACKEND_ASSERT2(err == noErr, "Could not reset audio player unit on pause", FATAL_ERROR) + m_audioUnitIsReset = true; + } +} + +void QuickTimeAudioPlayer::pause() +{ + m_state = Paused; + flush(); +} + +void QuickTimeAudioPlayer::play() +{ + m_state = Playing; + if (!m_audioEnabled) + return; + if (m_audioUnitIsReset) + seek(m_startTime); + else + scheduleAudioToGraph(); +} + +bool QuickTimeAudioPlayer::isPlaying() +{ + return m_videoPlayer && m_state == Playing; +} + +void QuickTimeAudioPlayer::seek(quint64 milliseconds) +{ + if (!m_videoPlayer || !m_videoPlayer->hasMovie()) + return; + if (milliseconds > m_videoPlayer->duration()) + milliseconds = m_videoPlayer->duration(); + if (!m_audioUnitIsReset && milliseconds == currentTime()) + return; + + m_startTime = milliseconds; + + // Since the graph may be running (advancing time), there is + // no point in seeking if were not going to play immidiatly: + if (m_state != Playing) + return; + if (!m_audioUnit) + return; + if (!m_audioEnabled || !m_videoPlayer->isSeekable()) + return; + + // Reset (and stop playing): + OSStatus err; + if (!m_audioUnitIsReset){ + err = AudioUnitReset(m_audioUnit, kAudioUnitScope_Global, 0); + BACKEND_ASSERT2(err == noErr, "Could not reset audio player unit before seek", FATAL_ERROR) + } + m_sampleTimeStamp = 0; + for (int i = 0; i < m_sliceCount; i++) + m_sliceList[i].mFlags = kScheduledAudioSliceFlag_Complete; + + // Start to play again immidiatly: + AudioTimeStamp timeStamp; + memset(&timeStamp, 0, sizeof(timeStamp)); + timeStamp.mFlags = kAudioTimeStampSampleTimeValid; + timeStamp.mSampleTime = -1; + err = AudioUnitSetProperty(m_audioUnit, + kAudioUnitProperty_ScheduleStartTimeStamp, kAudioUnitScope_Global, + 0, &timeStamp, sizeof(timeStamp)); + BACKEND_ASSERT2(err == noErr, "Could not set schedule start time stamp on audio player unit", FATAL_ERROR) + + // Seek back to 'now' in the movie: + TimeRecord timeRec; + timeRec.scale = m_videoPlayer->timeScale(); + timeRec.base = 0; + timeRec.value.hi = 0; + timeRec.value.lo = (milliseconds / 1000.0f) * timeRec.scale; + +#ifdef QUICKTIME_C_API_AVAILABLE + err = MovieAudioExtractionSetProperty(m_audioExtractionRef, + kQTPropertyClass_MovieAudioExtraction_Movie, + kQTMovieAudioExtractionMoviePropertyID_CurrentTime, + sizeof(TimeRecord), &timeRec); + BACKEND_ASSERT2(err == noErr, "Could not set current time on audio player unit", FATAL_ERROR) +#endif + + float durationLeftSec = float(m_videoPlayer->duration() - milliseconds) / 1000.0f; + m_samplesRemaining = (durationLeftSec > 0) ? (durationLeftSec * m_audioStreamDescription.mSampleRate) : -1; + m_audioExtractionComplete = false; + m_audioUnitIsReset = false; + scheduleAudioToGraph(); + +} + +quint64 QuickTimeAudioPlayer::currentTime() +{ + if (!m_audioUnit){ + if (m_videoPlayer) + return m_videoPlayer->currentTime(); + else + return m_startTime; + } + + Float64 currentUnitTime = getTimeInSamples(kAudioUnitProperty_CurrentPlayTime); + if (currentUnitTime == -1) + currentUnitTime = 0; + + quint64 cTime = quint64(m_startTime + + float(currentUnitTime / float(m_audioStreamDescription.mSampleRate)) * 1000.0f); + return (m_videoPlayer && cTime > m_videoPlayer->duration()) ? m_videoPlayer->duration() : cTime; +} + +QString QuickTimeAudioPlayer::currentTimeString() +{ + return QuickTimeVideoPlayer::timeToString(currentTime()); +} + +bool QuickTimeAudioPlayer::hasAudio() +{ + if (!m_videoPlayer) + return false; + + return m_videoPlayer->hasAudio(); +} + +bool QuickTimeAudioPlayer::soundPlayerIsAwailable() +{ + QuickTimeAudioPlayer player; + ComponentDescription d = player.getAudioNodeDescription(); + return FindNextComponent(0, &d); +} + +ComponentDescription QuickTimeAudioPlayer::getAudioNodeDescription() const +{ + ComponentDescription description; + description.componentType = kAudioUnitType_Generator; + description.componentSubType = kAudioUnitSubType_ScheduledSoundPlayer; + description.componentManufacturer = kAudioUnitManufacturer_Apple; + description.componentFlags = 0; + description.componentFlagsMask = 0; + return description; +} + +void QuickTimeAudioPlayer::initializeAudioUnit() +{ +} + +bool QuickTimeAudioPlayer::fillInStreamSpecification(AudioConnection *connection, ConnectionSide side) +{ + if (!m_videoPlayer){ + if (side == Source) + DEBUG_AUDIO_STREAM("QuickTimeAudioPlayer" << int(this) << "is source, but has no movie to use for stream spec fill.") + return true; + } + + if (side == Source){ + DEBUG_AUDIO_STREAM("QuickTimeAudioPlayer" << int(this) << "is source, and fills in stream spec from movie.") + connection->m_sourceStreamDescription = m_audioStreamDescription; + connection->m_sourceChannelLayout = (AudioChannelLayout *) malloc(m_audioChannelLayoutSize); + memcpy(connection->m_sourceChannelLayout, m_audioChannelLayout, m_audioChannelLayoutSize); + connection->m_sourceChannelLayoutSize = m_audioChannelLayoutSize; + connection->m_hasSourceSpecification = true; + } + return true; +} + +long QuickTimeAudioPlayer::regularTaskFrequency(){ + if (!m_audioEnabled || !m_audioUnit || (m_audioGraph && m_audioGraph->graphCannotPlay())) + return INT_MAX; + + // Calculate how much audio in + // milliseconds our slices can hold: + int packetNeedPerSecond = m_audioStreamDescription.mSampleRate / m_maxExtractionPacketCount; + long bufferTimeLengthSec = float(m_sliceCount) / float(packetNeedPerSecond); + // Make sure we also get some time to fill the + // buffer, so divide the time by two: + return (bufferTimeLengthSec * (1000 / 2)); +} + +void QuickTimeAudioPlayer::initSoundExtraction() +{ +#ifdef QUICKTIME_C_API_AVAILABLE + + // Initilize the extraction: + OSStatus err = noErr; + err = MovieAudioExtractionBegin([m_videoPlayer->qtMovie() quickTimeMovie], 0, &m_audioExtractionRef); + BACKEND_ASSERT2(err == noErr, "Could not start audio extraction on audio player unit", FATAL_ERROR) + m_discrete = false; +#if 0 + // Extract all channels as descrete: + err = MovieAudioExtractionSetProperty(audioExtractionRef, + kQTPropertyClass_MovieAudioExtraction_Movie, + kQTMovieAudioExtractionMoviePropertyID_AllChannelsDiscrete, + sizeof (discrete), + &discrete); + BACKEND_ASSERT2(err == noErr, "Could not set channels discrete on audio player unit", FATAL_ERROR) +#endif + + // Get the size of the audio channel layout (may include offset): + err = MovieAudioExtractionGetPropertyInfo(m_audioExtractionRef, + kQTPropertyClass_MovieAudioExtraction_Audio, + kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout, + 0, &m_audioChannelLayoutSize, 0); + BACKEND_ASSERT2(err == noErr, "Could not get channel layout size from audio extraction", FATAL_ERROR) + + // Allocate memory for the layout + m_audioChannelLayout = (AudioChannelLayout *) calloc(1, m_audioChannelLayoutSize); + BACKEND_ASSERT2(m_audioChannelLayout, "Could not allocate memory for channel layout on audio player unit", FATAL_ERROR) + + // Get the layout: + err = MovieAudioExtractionGetProperty(m_audioExtractionRef, + kQTPropertyClass_MovieAudioExtraction_Audio, + kQTMovieAudioExtractionAudioPropertyID_AudioChannelLayout, + m_audioChannelLayoutSize, m_audioChannelLayout, 0); + BACKEND_ASSERT2(err == noErr, "Could not get channel layout from audio extraction", FATAL_ERROR) + + // Get audio stream description: + err = MovieAudioExtractionGetProperty(m_audioExtractionRef, + kQTPropertyClass_MovieAudioExtraction_Audio, + kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription, + sizeof(m_audioStreamDescription), &m_audioStreamDescription, 0); + BACKEND_ASSERT2(err == noErr, "Could not get audio stream description from audio extraction", FATAL_ERROR) + +#endif // QUICKTIME_C_API_AVAILABLE +} + +void QuickTimeAudioPlayer::allocateSoundSlices() +{ +#ifdef QUICKTIME_C_API_AVAILABLE + + // m_sliceList will contain a specified number of ScheduledAudioSlice-s that each can + // carry audio from extraction, and be scheduled for playback at an audio unit. + // Each ScheduledAudioSlice will contain several audio buffers, one for each sound channel. + // Each buffer will carry (at most) a specified number of sound packets, and each packet can + // contain one or more frames. + + // Create a list of ScheduledAudioSlices: + m_sliceList = (ScheduledAudioSlice *) calloc(m_sliceCount, sizeof(ScheduledAudioSlice)); + BACKEND_ASSERT2(m_sliceList, "Could not allocate memory for audio slices", FATAL_ERROR) + bzero(m_sliceList, m_sliceCount * sizeof(ScheduledAudioSlice)); + + // Calculate the size of the different structures needed: + int packetsBufferSize = m_maxExtractionPacketCount * m_audioStreamDescription.mBytesPerPacket; + int channels = m_audioStreamDescription.mChannelsPerFrame; + int audioBufferListSize = int(sizeof(AudioBufferList) + (channels-1) * sizeof(AudioBuffer)); + int mallocSize = audioBufferListSize + (packetsBufferSize * m_audioStreamDescription.mChannelsPerFrame); + + // Round off to Altivec sizes: + packetsBufferSize = int(((packetsBufferSize + 15) / 16) * 16); + audioBufferListSize = int(((audioBufferListSize + 15) / 16) * 16); + + for (int sliceIndex = 0; sliceIndex < m_sliceCount; ++sliceIndex){ + // Create the memory chunk for this audio slice: + AudioBufferList *audioBufferList = (AudioBufferList*) calloc(1, mallocSize); + BACKEND_ASSERT2(audioBufferList, "Could not allocate memory for audio buffer list", FATAL_ERROR) + + // The AudioBufferList contains an AudioBuffer for each channel in the audio stream: + audioBufferList->mNumberBuffers = m_audioStreamDescription.mChannelsPerFrame; + for (uint i = 0; i < audioBufferList->mNumberBuffers; ++i){ + audioBufferList->mBuffers[i].mNumberChannels = 1; + audioBufferList->mBuffers[i].mData = (char *) audioBufferList + audioBufferListSize + (i * packetsBufferSize); + audioBufferList->mBuffers[i].mDataByteSize = packetsBufferSize; + } + + m_sliceList[sliceIndex].mBufferList = audioBufferList; + m_sliceList[sliceIndex].mNumberFrames = m_maxExtractionPacketCount; + m_sliceList[sliceIndex].mTimeStamp.mFlags = kAudioTimeStampSampleTimeValid; + m_sliceList[sliceIndex].mCompletionProcUserData = 0; + m_sliceList[sliceIndex].mCompletionProc = 0; + m_sliceList[sliceIndex].mFlags = kScheduledAudioSliceFlag_Complete; + m_sliceList[sliceIndex].mReserved = 0; + } + +#endif // QUICKTIME_C_API_AVAILABLE +} + +void QuickTimeAudioPlayer::scheduleSoundSlices() +{ +#ifdef QUICKTIME_C_API_AVAILABLE + + PhononAutoReleasePool pool; + // For each completed (or never used) slice, fill and schedule it. + for (int sliceIndex = 0; sliceIndex < m_sliceCount; ++sliceIndex){ + if (m_sliceList[sliceIndex].mFlags & kScheduledAudioSliceFlag_Complete){ + if (m_samplesRemaining == 0) + m_audioExtractionComplete = true; + + if (!m_audioExtractionComplete){ + // Determine how many samples to read: + int samplesCount = m_samplesRemaining; + if ((samplesCount > m_maxExtractionPacketCount) || (samplesCount == -1)) + samplesCount = m_maxExtractionPacketCount; + m_sliceList[sliceIndex].mTimeStamp.mSampleTime = m_sampleTimeStamp; + + // Reset buffer sizes: + int byteSize = samplesCount * m_audioStreamDescription.mBytesPerPacket; + for (uint i = 0; i < m_sliceList[sliceIndex].mBufferList->mNumberBuffers; ++i) + m_sliceList[sliceIndex].mBufferList->mBuffers[i].mDataByteSize = byteSize; + + // Do the extraction: + UInt32 flags = 0; + UInt32 samplesRead = samplesCount; + OSStatus err = MovieAudioExtractionFillBuffer( + m_audioExtractionRef, &samplesRead, m_sliceList[sliceIndex].mBufferList, &flags); + BACKEND_ASSERT2(err == noErr, "Could not fill audio buffers from audio extraction", FATAL_ERROR) + m_audioExtractionComplete = (flags & kQTMovieAudioExtractionComplete); + + // Play the slice: + if (samplesRead != 0 && m_audioUnit != 0){ + m_sliceList[sliceIndex].mNumberFrames = samplesRead; + err = AudioUnitSetProperty(m_audioUnit, + kAudioUnitProperty_ScheduleAudioSlice, kAudioUnitScope_Global, + 0, &m_sliceList[sliceIndex], sizeof(ScheduledAudioSlice)); + BACKEND_ASSERT2(err == noErr, "Could not schedule audio buffers on audio unit", FATAL_ERROR) + } else + m_sliceList[sliceIndex].mFlags = kScheduledAudioSliceFlag_Complete; + + // Move the window: + m_sampleTimeStamp += samplesRead; + if (m_samplesRemaining != -1) + m_samplesRemaining -= samplesRead; + } + } + } + +#endif // QUICKTIME_C_API_AVAILABLE +} + +void QuickTimeAudioPlayer::mediaNodeEvent(const MediaNodeEvent *event) +{ + switch (event->type()){ + case MediaNodeEvent::AudioGraphAboutToBeDeleted: + case MediaNodeEvent::AboutToRestartAudioStream: + case MediaNodeEvent::StartConnectionChange: + m_startTime = currentTime(); + break; + case MediaNodeEvent::AudioGraphInitialized: + case MediaNodeEvent::RestartAudioStreamRequest: + case MediaNodeEvent::EndConnectionChange: + if (m_state == Playing) + seek(m_startTime); + break; + default: + break; + } +} + +}} + +QT_END_NAMESPACE +