/*! @file AudioUnitSDK/AUBuffer.cpp @copyright © 2000-2021 Apple Inc. All rights reserved. */ #include #include namespace ausdk { inline void ThrowBadAlloc() { AUSDK_LogError("AUBuffer throwing bad_alloc"); throw std::bad_alloc(); } // x: number to be rounded; y: the power of 2 to which to round constexpr uint32_t RoundUpToMultipleOfPowerOf2(uint32_t x, uint32_t y) noexcept { const auto mask = y - 1; #if DEBUG assert((mask & y) == 0u); // verifies that y is a power of 2 NOLINT #endif return (x + mask) & ~mask; } // a * b + c static UInt32 SafeMultiplyAddUInt32(UInt32 a, UInt32 b, UInt32 c) { if (a == 0 || b == 0) { return c; // prevent zero divide } if (a > (0xFFFFFFFF - c) / b) { // NOLINT magic ThrowBadAlloc(); } return a * b + c; } AllocatedBuffer* BufferAllocator::Allocate( UInt32 numberBuffers, UInt32 maxBytesPerBuffer, UInt32 /*reservedFlags*/) { constexpr size_t kAlignment = 16; constexpr size_t kMaxBufferListSize = 65536; // Check for a reasonable number of buffers (obviate a more complicated check with offsetof). if (numberBuffers > kMaxBufferListSize / sizeof(AudioBuffer)) { throw std::out_of_range("AudioBuffers::Allocate: Too many buffers"); } maxBytesPerBuffer = RoundUpToMultipleOfPowerOf2(maxBytesPerBuffer, kAlignment); const auto bufferDataSize = SafeMultiplyAddUInt32(numberBuffers, maxBytesPerBuffer, 0); void* bufferData = nullptr; if (bufferDataSize > 0) { bufferData = malloc(bufferDataSize); // don't use calloc(); it might not actually touch the memory and cause a VM fault later memset(bufferData, 0, bufferDataSize); } const auto implSize = static_cast( offsetof(AllocatedBuffer, mAudioBufferList.mBuffers[std::max(UInt32(1), numberBuffers)])); auto* const implMem = malloc(implSize); auto* const allocatedBuffer = new (implMem) AllocatedBuffer{ .mMaximumNumberBuffers = numberBuffers, .mMaximumBytesPerBuffer = maxBytesPerBuffer, .mHeaderSize = implSize, .mBufferDataSize = bufferDataSize, .mBufferData = bufferData }; allocatedBuffer->mAudioBufferList.mNumberBuffers = numberBuffers; return allocatedBuffer; } void BufferAllocator::Deallocate(AllocatedBuffer* allocatedBuffer) { if (allocatedBuffer->mBufferData != nullptr) { free(allocatedBuffer->mBufferData); } allocatedBuffer->~AllocatedBuffer(); free(allocatedBuffer); } AudioBufferList& AllocatedBuffer::Prepare(UInt32 channelsPerBuffer, UInt32 bytesPerBuffer) { if (mAudioBufferList.mNumberBuffers > mMaximumNumberBuffers) { throw std::out_of_range("AllocatedBuffer::Prepare(): too many buffers"); } if (bytesPerBuffer > mMaximumBytesPerBuffer) { throw std::out_of_range("AllocatedBuffer::Prepare(): insufficient capacity"); } auto* ptr = static_cast(mBufferData); auto* const ptrend = ptr + mBufferDataSize; for (UInt32 bufIdx = 0, nBufs = mAudioBufferList.mNumberBuffers; bufIdx < nBufs; ++bufIdx) { auto& buf = mAudioBufferList.mBuffers[bufIdx]; // NOLINT buf.mNumberChannels = channelsPerBuffer; buf.mDataByteSize = bytesPerBuffer; buf.mData = ptr; ptr += mMaximumBytesPerBuffer; // NOLINT ptr math } if (ptr > ptrend) { throw std::out_of_range("AllocatedBuffer::Prepare(): insufficient capacity"); } return mAudioBufferList; } AudioBufferList& AllocatedBuffer::PrepareNull(UInt32 channelsPerBuffer, UInt32 bytesPerBuffer) { if (mAudioBufferList.mNumberBuffers > mMaximumNumberBuffers) { throw std::out_of_range("AllocatedBuffer::PrepareNull(): too many buffers"); } for (UInt32 bufIdx = 0, nBufs = mAudioBufferList.mNumberBuffers; bufIdx < nBufs; ++bufIdx) { auto& buf = mAudioBufferList.mBuffers[bufIdx]; // NOLINT buf.mNumberChannels = channelsPerBuffer; buf.mDataByteSize = bytesPerBuffer; buf.mData = nullptr; } return mAudioBufferList; } AudioBufferList& AUBufferList::PrepareBuffer( const AudioStreamBasicDescription& format, UInt32 nFrames) { ausdk::ThrowExceptionIf(nFrames > mAllocatedFrames, kAudioUnitErr_TooManyFramesToProcess); UInt32 nStreams = 0; UInt32 channelsPerStream = 0; if (ASBD::IsInterleaved(format)) { nStreams = 1; channelsPerStream = format.mChannelsPerFrame; } else { nStreams = format.mChannelsPerFrame; channelsPerStream = 1; } ausdk::ThrowExceptionIf(nStreams > mAllocatedStreams, kAudioUnitErr_FormatNotSupported); auto& abl = mBuffers->Prepare(channelsPerStream, nFrames * format.mBytesPerFrame); mPtrState = EPtrState::ToMyMemory; return abl; } AudioBufferList& AUBufferList::PrepareNullBuffer( const AudioStreamBasicDescription& format, UInt32 nFrames) { UInt32 nStreams = 0; UInt32 channelsPerStream = 0; if (ASBD::IsInterleaved(format)) { nStreams = 1; channelsPerStream = format.mChannelsPerFrame; } else { nStreams = format.mChannelsPerFrame; channelsPerStream = 1; } ausdk::ThrowExceptionIf(nStreams > mAllocatedStreams, kAudioUnitErr_FormatNotSupported); auto& abl = mBuffers->PrepareNull(channelsPerStream, nFrames * format.mBytesPerFrame); mPtrState = EPtrState::ToExternalMemory; return abl; } void AUBufferList::Allocate(const AudioStreamBasicDescription& format, UInt32 nFrames) { auto& alloc = BufferAllocator::instance(); if (mBuffers != nullptr) { alloc.Deallocate(mBuffers); } const uint32_t nstreams = ASBD::IsInterleaved(format) ? 1 : format.mChannelsPerFrame; mBuffers = alloc.Allocate(nstreams, nFrames * format.mBytesPerFrame, 0u); mAllocatedFrames = nFrames; mAllocatedStreams = nstreams; mPtrState = EPtrState::Invalid; } void AUBufferList::Deallocate() { if (mBuffers != nullptr) { BufferAllocator::instance().Deallocate(mBuffers); mBuffers = nullptr; } mAllocatedFrames = 0; mAllocatedStreams = 0; mPtrState = EPtrState::Invalid; } } // namespace ausdk