blob: 81d7ba291ae9f4ab7cf2e5f9aad62c9424353672 [file] [log] [blame]
/*
* 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 <WebCore/CARingBuffer.h>
#include <wtf/MainThread.h>
using namespace WebCore;
namespace TestWebKitAPI {
class CARingBufferTest : public testing::Test {
public:
virtual void SetUp()
{
WTF::initializeMainThread();
m_ringBuffer = makeUniqueWithoutFastMallocCheck<CARingBuffer>();
}
// 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<uint32_t>(1, m_description.numberOfChannelStreams()));
m_bufferList = std::unique_ptr<AudioBufferList>(static_cast<AudioBufferList*>(::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<uint32_t>(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<AudioBufferList> m_bufferList;
std::unique_ptr<CARingBuffer> 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<uint8_t*>(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<uint8_t*>(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 <typename type>
class MixingTest {
public:
static void run(CARingBufferTest& test)
{
const int sampleCount = 64;
CAAudioStreamDescription::PCMFormat format;
if (std::is_same<type, float>::value)
format = CAAudioStreamDescription::PCMFormat::Float32;
else if (std::is_same<type, double>::value)
format = CAAudioStreamDescription::PCMFormat::Float64;
else if (std::is_same<type, int32_t>::value)
format = CAAudioStreamDescription::PCMFormat::Int32;
else if (std::is_same<type, int16_t>::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<uint8_t*>(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<uint8_t*>(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<float>::run(*this);
}
TEST_F(CARingBufferTest, DoubleMixing)
{
MixingTest<double>::run(*this);
}
TEST_F(CARingBufferTest, Int32Mixing)
{
MixingTest<int32_t>::run(*this);
}
TEST_F(CARingBufferTest, Int16Mixing)
{
MixingTest<int16_t>::run(*this);
}
}
#endif