/* 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 . */ #import #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