long BufferedAudioStream::DataCallback(void* aBuffer, long aFrames) { MonitorAutoLock mon(mMonitor); uint32_t available = std::min(static_cast<uint32_t>(FramesToBytes(aFrames)), mBuffer.Length()); NS_ABORT_IF_FALSE(available % mBytesPerFrame == 0, "Must copy complete frames"); uint32_t underrunFrames = 0; uint32_t servicedFrames = 0; if (available) { AudioDataValue* output = reinterpret_cast<AudioDataValue*>(aBuffer); if (mInRate == mOutRate) { servicedFrames = GetUnprocessed(output, aFrames); } else { servicedFrames = GetTimeStretched(output, aFrames); } float scaled_volume = float(GetVolumeScale() * mVolume); ScaleAudioSamples(output, aFrames * mChannels, scaled_volume); NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Must copy complete frames"); // Notify any blocked Write() call that more space is available in mBuffer. mon.NotifyAll(); } underrunFrames = aFrames - servicedFrames; if (mState != DRAINING) { uint8_t* rpos = static_cast<uint8_t*>(aBuffer) + FramesToBytes(aFrames - underrunFrames); memset(rpos, 0, FramesToBytes(underrunFrames)); #ifdef PR_LOGGING if (underrunFrames) { PR_LOG(gAudioStreamLog, PR_LOG_WARNING, ("AudioStream %p lost %d frames", this, underrunFrames)); } #endif mLostFrames += underrunFrames; servicedFrames += underrunFrames; } WriteDumpFile(mDumpFile, this, aFrames, aBuffer); mAudioClock.UpdateWritePosition(servicedFrames); return servicedFrames; }
long AudioStream::DataCallback(void* aBuffer, long aFrames) { MonitorAutoLock mon(mMonitor); MOZ_ASSERT(mState != SHUTDOWN, "No data callback after shutdown"); uint32_t available = std::min(static_cast<uint32_t>(FramesToBytes(aFrames)), mBuffer.Length()); NS_ABORT_IF_FALSE(available % mBytesPerFrame == 0, "Must copy complete frames"); AudioDataValue* output = reinterpret_cast<AudioDataValue*>(aBuffer); uint32_t underrunFrames = 0; uint32_t servicedFrames = 0; int64_t insertTime; // NOTE: wasapi (others?) can call us back *after* stop()/Shutdown() (mState == SHUTDOWN) // Bug 996162 // callback tells us cubeb succeeded initializing if (mState == STARTED) { // For low-latency streams, we want to minimize any built-up data when // we start getting callbacks. // Simple version - contract on first callback only. if (mLatencyRequest == LowLatency) { #ifdef PR_LOGGING uint32_t old_len = mBuffer.Length(); #endif available = mBuffer.ContractTo(FramesToBytes(aFrames)); #ifdef PR_LOGGING TimeStamp now = TimeStamp::Now(); if (!mStartTime.IsNull()) { int64_t timeMs = (now - mStartTime).ToMilliseconds(); PR_LOG(gAudioStreamLog, PR_LOG_WARNING, ("Stream took %lldms to start after first Write() @ %u", timeMs, mOutRate)); } else { PR_LOG(gAudioStreamLog, PR_LOG_WARNING, ("Stream started before Write() @ %u", mOutRate)); } if (old_len != available) { // Note that we may have dropped samples in Write() as well! PR_LOG(gAudioStreamLog, PR_LOG_WARNING, ("AudioStream %p dropped %u + %u initial frames @ %u", this, mReadPoint, BytesToFrames(old_len - available), mOutRate)); mReadPoint += BytesToFrames(old_len - available); } #endif } mState = RUNNING; } if (available) { // When we are playing a low latency stream, and it is the first time we are // getting data from the buffer, we prefer to add the silence for an // underrun at the beginning of the buffer, so the first buffer is not cut // in half by the silence inserted to compensate for the underrun. if (mInRate == mOutRate) { if (mLatencyRequest == LowLatency && !mWritten) { servicedFrames = GetUnprocessedWithSilencePadding(output, aFrames, insertTime); } else { servicedFrames = GetUnprocessed(output, aFrames, insertTime); } } else { servicedFrames = GetTimeStretched(output, aFrames, insertTime); } float scaled_volume = float(GetVolumeScale() * mVolume); ScaleAudioSamples(output, aFrames * mOutChannels, scaled_volume); NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Must copy complete frames"); // Notify any blocked Write() call that more space is available in mBuffer. mon.NotifyAll(); } else { GetBufferInsertTime(insertTime); } underrunFrames = aFrames - servicedFrames; // Always send audible frames first, and silent frames later. // Otherwise it will break the assumption of FrameHistory. if (mState != DRAINING) { mAudioClock.UpdateFrameHistory(servicedFrames, underrunFrames); uint8_t* rpos = static_cast<uint8_t*>(aBuffer) + FramesToBytes(aFrames - underrunFrames); memset(rpos, 0, FramesToBytes(underrunFrames)); if (underrunFrames) { PR_LOG(gAudioStreamLog, PR_LOG_WARNING, ("AudioStream %p lost %d frames", this, underrunFrames)); } servicedFrames += underrunFrames; } else { mAudioClock.UpdateFrameHistory(servicedFrames, 0); } WriteDumpFile(mDumpFile, this, aFrames, aBuffer); // Don't log if we're not interested or if the stream is inactive if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG) && mState != SHUTDOWN && insertTime != INT64_MAX && servicedFrames > underrunFrames) { uint32_t latency = UINT32_MAX; if (cubeb_stream_get_latency(mCubebStream, &latency)) { NS_WARNING("Could not get latency from cubeb."); } TimeStamp now = TimeStamp::Now(); mLatencyLog->Log(AsyncLatencyLogger::AudioStream, reinterpret_cast<uint64_t>(this), insertTime, now); mLatencyLog->Log(AsyncLatencyLogger::Cubeb, reinterpret_cast<uint64_t>(mCubebStream.get()), (latency * 1000) / mOutRate, now); } return servicedFrames; }
long AudioStream::DataCallback(void* aBuffer, long aFrames) { MonitorAutoLock mon(mMonitor); uint32_t available = std::min(static_cast<uint32_t>(FramesToBytes(aFrames)), mBuffer.Length()); NS_ABORT_IF_FALSE(available % mBytesPerFrame == 0, "Must copy complete frames"); AudioDataValue* output = reinterpret_cast<AudioDataValue*>(aBuffer); uint32_t underrunFrames = 0; uint32_t servicedFrames = 0; int64_t insertTime; if (available) { // When we are playing a low latency stream, and it is the first time we are // getting data from the buffer, we prefer to add the silence for an // underrun at the beginning of the buffer, so the first buffer is not cut // in half by the silence inserted to compensate for the underrun. if (mInRate == mOutRate) { if (mLatencyRequest == LowLatency && !mWritten) { servicedFrames = GetUnprocessedWithSilencePadding(output, aFrames, insertTime); } else { servicedFrames = GetUnprocessed(output, aFrames, insertTime); } } else { servicedFrames = GetTimeStretched(output, aFrames, insertTime); } float scaled_volume = float(GetVolumeScale() * mVolume); ScaleAudioSamples(output, aFrames * mOutChannels, scaled_volume); NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Must copy complete frames"); // Notify any blocked Write() call that more space is available in mBuffer. mon.NotifyAll(); } else { GetBufferInsertTime(insertTime); } underrunFrames = aFrames - servicedFrames; if (mState != DRAINING) { uint8_t* rpos = static_cast<uint8_t*>(aBuffer) + FramesToBytes(aFrames - underrunFrames); memset(rpos, 0, FramesToBytes(underrunFrames)); if (underrunFrames) { PR_LOG(gAudioStreamLog, PR_LOG_WARNING, ("AudioStream %p lost %d frames", this, underrunFrames)); } mLostFrames += underrunFrames; servicedFrames += underrunFrames; } WriteDumpFile(mDumpFile, this, aFrames, aBuffer); // Don't log if we're not interested or if the stream is inactive if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG) && insertTime != INT64_MAX && servicedFrames > underrunFrames) { uint32_t latency = UINT32_MAX; if (cubeb_stream_get_latency(mCubebStream, &latency)) { NS_WARNING("Could not get latency from cubeb."); } TimeStamp now = TimeStamp::Now(); mLatencyLog->Log(AsyncLatencyLogger::AudioStream, reinterpret_cast<uint64_t>(this), insertTime, now); mLatencyLog->Log(AsyncLatencyLogger::Cubeb, reinterpret_cast<uint64_t>(mCubebStream.get()), (latency * 1000) / mOutRate, now); } mAudioClock.UpdateWritePosition(servicedFrames); return servicedFrames; }