/* * Copyright (C) 2017 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #if PLATFORM(MAC) #include "Test.h" #include #include using namespace WebCore; namespace TestWebKitAPI { class CARingBufferTest : public testing::Test { public: virtual void SetUp() { WTF::initializeMainThread(); m_ringBuffer = std::make_unique(); } // CAAudioStreamDescription(double sampleRate, UInt32 numChannels, PCMFormat format, bool isInterleaved, size_t capacity) void setup(double sampleRate, UInt32 numChannels, CAAudioStreamDescription::PCMFormat format, bool isInterleaved, size_t capacity) { m_description = CAAudioStreamDescription(sampleRate, numChannels, format, isInterleaved); m_capacity = capacity; size_t listSize = offsetof(AudioBufferList, mBuffers) + (sizeof(AudioBuffer) * std::max(1, m_description.numberOfChannelStreams())); m_bufferList = std::unique_ptr(static_cast(::operator new (listSize))); m_ringBuffer->allocate(m_description, capacity); } void setListDataBuffer(uint8_t* bufferData, size_t sampleCount) { size_t bufferCount = m_description.numberOfChannelStreams(); size_t channelCount = m_description.numberOfInterleavedChannels(); size_t bytesPerChannel = sampleCount * m_description.bytesPerFrame(); m_bufferList->mNumberBuffers = bufferCount; for (unsigned i = 0; i < bufferCount; ++i) { m_bufferList->mBuffers[i].mNumberChannels = channelCount; m_bufferList->mBuffers[i].mDataByteSize = bytesPerChannel; m_bufferList->mBuffers[i].mData = bufferData; if (bufferData) bufferData = bufferData + bytesPerChannel; } } const CAAudioStreamDescription& description() const { return m_description; } AudioBufferList& bufferList() const { return *m_bufferList.get(); } CARingBuffer& ringBuffer() const { return *m_ringBuffer.get(); } size_t capacity() const { return m_capacity; } private: size_t audioBufferListSizeForStream(const CAAudioStreamDescription& format) { return offsetof(AudioBufferList, mBuffers) + (sizeof(AudioBuffer) * std::max(1, format.numberOfChannelStreams())); } void configureBufferListForStream(AudioBufferList& bufferList, const CAAudioStreamDescription& format, uint8_t* bufferData, size_t sampleCount) { size_t bufferCount = format.numberOfChannelStreams(); size_t channelCount = format.numberOfInterleavedChannels(); size_t bytesPerChannel = sampleCount * format.bytesPerFrame(); bufferList.mNumberBuffers = bufferCount; for (unsigned i = 0; i < bufferCount; ++i) { bufferList.mBuffers[i].mNumberChannels = channelCount; bufferList.mBuffers[i].mDataByteSize = bytesPerChannel; bufferList.mBuffers[i].mData = bufferData; if (bufferData) bufferData = bufferData + bytesPerChannel; } } std::unique_ptr m_bufferList; std::unique_ptr m_ringBuffer; CAAudioStreamDescription m_description = { }; size_t m_capacity = { 0 }; }; TEST_F(CARingBufferTest, Basics) { const int capacity = 32; setup(44100, 1, CAAudioStreamDescription::PCMFormat::Float32, true, capacity); float sourceBuffer[capacity]; for (int i = 0; i < capacity; i++) sourceBuffer[i] = i + 0.5; setListDataBuffer(reinterpret_cast(sourceBuffer), capacity); // Fill the first half of the buffer ... int sampleCount = capacity / 2; CARingBuffer::Error err = ringBuffer().store(&bufferList(), sampleCount, 0); EXPECT_EQ(err, CARingBuffer::Error::Ok); uint64_t startTime; uint64_t endTime; ringBuffer().getCurrentFrameBounds(startTime, endTime); EXPECT_EQ(0, (int)startTime); EXPECT_EQ((int)sampleCount, (int)endTime); float scratchBuffer[capacity]; setListDataBuffer(reinterpret_cast(scratchBuffer), capacity); err = ringBuffer().fetch(&bufferList(), sampleCount, 0); EXPECT_EQ(err, CARingBuffer::Error::Ok); EXPECT_TRUE(!memcmp(sourceBuffer, scratchBuffer, sampleCount * description().sampleWordSize())); // ... and the second half. err = ringBuffer().store(&bufferList(), capacity / 2, capacity / 2); EXPECT_EQ(err, CARingBuffer::Error::Ok); ringBuffer().getCurrentFrameBounds(startTime, endTime); EXPECT_EQ(0, (int)startTime); EXPECT_EQ(capacity, (int)endTime); memset(scratchBuffer, 0, sampleCount * description().sampleWordSize()); err = ringBuffer().fetch(&bufferList(), sampleCount, 0); EXPECT_EQ(err, CARingBuffer::Error::Ok); EXPECT_TRUE(!memcmp(sourceBuffer, scratchBuffer, sampleCount * description().sampleWordSize())); // Force the buffer to wrap around err = ringBuffer().store(&bufferList(), capacity, capacity - 1); EXPECT_EQ(err, CARingBuffer::Error::Ok); ringBuffer().getCurrentFrameBounds(startTime, endTime); EXPECT_EQ((int)capacity - 1, (int)startTime); EXPECT_EQ(capacity - 1 + capacity, (int)endTime); // Make sure it returns an error when asked to store too much ... err = ringBuffer().store(&bufferList(), capacity * 3, capacity / 2); EXPECT_EQ(err, CARingBuffer::Error::TooMuch); // ... and doesn't modify the buffer ringBuffer().getCurrentFrameBounds(startTime, endTime); EXPECT_EQ((int)capacity - 1, (int)startTime); EXPECT_EQ(capacity - 1 + capacity, (int)endTime); ringBuffer().flush(); ringBuffer().getCurrentFrameBounds(startTime, endTime); EXPECT_EQ(0, (int)startTime); EXPECT_EQ(0, (int)endTime); } template class MixingTest { public: static void run(CARingBufferTest& test) { const int sampleCount = 64; CAAudioStreamDescription::PCMFormat format; if (std::is_same::value) format = CAAudioStreamDescription::PCMFormat::Float32; else if (std::is_same::value) format = CAAudioStreamDescription::PCMFormat::Float64; else if (std::is_same::value) format = CAAudioStreamDescription::PCMFormat::Int32; else if (std::is_same::value) format = CAAudioStreamDescription::PCMFormat::Int16; else ASSERT_NOT_REACHED(); test.setup(44100, 1, format, true, sampleCount); type referenceBuffer[sampleCount]; type sourceBuffer[sampleCount]; type readBuffer[sampleCount]; for (int i = 0; i < sampleCount; i++) { sourceBuffer[i] = i * 0.5; referenceBuffer[i] = sourceBuffer[i]; } test.setListDataBuffer(reinterpret_cast(sourceBuffer), sampleCount); CARingBuffer::Error err = test.ringBuffer().store(&test.bufferList(), sampleCount, 0); EXPECT_EQ(err, CARingBuffer::Error::Ok); memset(readBuffer, 0, sampleCount * test.description().sampleWordSize()); test.setListDataBuffer(reinterpret_cast(readBuffer), sampleCount); err = test.ringBuffer().fetch(&test.bufferList(), sampleCount, 0, CARingBuffer::FetchMode::Mix); EXPECT_EQ(err, CARingBuffer::Error::Ok); for (int i = 0; i < sampleCount; i++) EXPECT_EQ(readBuffer[i], referenceBuffer[i]) << "Ring buffer value differs at index " << i; err = test.ringBuffer().fetch(&test.bufferList(), sampleCount, 0, CARingBuffer::FetchMode::Mix); EXPECT_EQ(err, CARingBuffer::Error::Ok); err = test.ringBuffer().fetch(&test.bufferList(), sampleCount, 0, CARingBuffer::FetchMode::Mix); EXPECT_EQ(err, CARingBuffer::Error::Ok); err = test.ringBuffer().fetch(&test.bufferList(), sampleCount, 0, CARingBuffer::FetchMode::Mix); EXPECT_EQ(err, CARingBuffer::Error::Ok); for (int i = 0; i < sampleCount; i++) referenceBuffer[i] += sourceBuffer[i] * 3; for (int i = 0; i < sampleCount; i++) EXPECT_EQ(readBuffer[i], referenceBuffer[i]) << "Ring buffer value differs at index " << i; } }; TEST_F(CARingBufferTest, FloatMixing) { MixingTest::run(*this); } TEST_F(CARingBufferTest, DoubleMixing) { MixingTest::run(*this); } TEST_F(CARingBufferTest, Int32Mixing) { MixingTest::run(*this); } TEST_F(CARingBufferTest, Int16Mixing) { MixingTest::run(*this); } } #endif