Clarify API/docs by introducing the audio "frame", and add helpers.
authorMichael Goddard <michael.goddard@nokia.com>
Tue, 10 Jul 2012 04:26:58 +0000 (14:26 +1000)
committerQt by Nokia <qt-info@nokia.com>
Wed, 11 Jul 2012 06:01:45 +0000 (08:01 +0200)
The frame is the interleaved set of one sample for each channel.  Add
some docs and some methods that make working with samples a bit more
convenient.

Adjusted QAudioBuffer to use these helper functions and terminology.

Change-Id: I96db48e659561972d6de2aa19893d29f9a828cd3
Reviewed-by: Dmytro Poplavskiy <dmytro.poplavskiy@nokia.com>
src/multimedia/audio/qaudiobuffer.cpp
src/multimedia/audio/qaudiobuffer.h
src/multimedia/audio/qaudiobuffer_p.h
src/multimedia/audio/qaudioformat.cpp
src/multimedia/audio/qaudioformat.h
tests/auto/unit/qaudiobuffer/tst_qaudiobuffer.cpp
tests/auto/unit/qaudioformat/tst_qaudioformat.cpp

index bde0450..299a610 100644 (file)
@@ -106,18 +106,18 @@ public:
 // Private class to go in .cpp file
 class QMemoryAudioBufferProvider : public QAbstractAudioBuffer {
 public:
-    QMemoryAudioBufferProvider(const void *data, int sampleCount, const QAudioFormat &format, qint64 startTime)
+    QMemoryAudioBufferProvider(const void *data, int frameCount, const QAudioFormat &format, qint64 startTime)
         : mStartTime(startTime)
-        , mSampleCount(sampleCount)
+        , mFrameCount(frameCount)
         , mFormat(format)
     {
-        int numBytes = (sampleCount * format.sampleSize()) / 8;
+        int numBytes = format.bytesForFrames(frameCount);
         if (numBytes > 0) {
             mBuffer = malloc(numBytes);
             if (!mBuffer) {
                 // OOM, if that's likely
                 mStartTime = -1;
-                mSampleCount = 0;
+                mFrameCount = 0;
                 mFormat = QAudioFormat();
             } else {
                 // Allocated, see if we have data to copy
@@ -149,19 +149,19 @@ public:
     void release() {delete this;}
     QAudioFormat format() const {return mFormat;}
     qint64 startTime() const {return mStartTime;}
-    int sampleCount() const {return mSampleCount;}
+    int frameCount() const {return mFrameCount;}
 
     void *constData() const {return mBuffer;}
 
     void *writableData() {return mBuffer;}
     QAbstractAudioBuffer *clone() const
     {
-        return new QMemoryAudioBufferProvider(mBuffer, mSampleCount, mFormat, mStartTime);
+        return new QMemoryAudioBufferProvider(mBuffer, mFrameCount, mFormat, mStartTime);
     }
 
     void *mBuffer;
     qint64 mStartTime;
-    int mSampleCount;
+    int mFrameCount;
     QAudioFormat mFormat;
 };
 
@@ -176,7 +176,7 @@ QAudioBufferPrivate *QAudioBufferPrivate::clone()
         QAbstractAudioBuffer *abuf = mProvider->clone();
 
         if (!abuf) {
-            abuf = new QMemoryAudioBufferProvider(mProvider->constData(), mProvider->sampleCount(), mProvider->format(), mProvider->startTime());
+            abuf = new QMemoryAudioBufferProvider(mProvider->constData(), mProvider->frameCount(), mProvider->format(), mProvider->startTime());
         }
 
         if (abuf) {
@@ -236,7 +236,7 @@ QAudioBuffer::QAudioBuffer(const QAudioBuffer &other)
     and sizes of the samples are interpreted from the \a data.
 
     If the supplied \a data is not an integer multiple of the
-    calculated sample size, the excess data will not be used.
+    calculated frame size, the excess data will not be used.
 
     This audio buffer will copy the contents of \a data.
 
@@ -247,25 +247,25 @@ QAudioBuffer::QAudioBuffer(const QAudioBuffer &other)
 QAudioBuffer::QAudioBuffer(const QByteArray &data, const QAudioFormat &format, qint64 startTime)
 {
     if (format.isValid()) {
-        int sampleCount = (data.size() * 8) / format.sampleSize(); // truncate
-        d = new QAudioBufferPrivate(new QMemoryAudioBufferProvider(data.constData(), sampleCount, format, startTime));
+        int frameCount = format.framesForBytes(data.size());
+        d = new QAudioBufferPrivate(new QMemoryAudioBufferProvider(data.constData(), frameCount, format, startTime));
     } else
         d = 0;
 }
 
 /*!
-    Creates a new audio buffer with space for \a numSamples samples of
-    the given \a format.  The samples will be initialized to the default
-    for the format.
+    Creates a new audio buffer with space for \a numFrames frames of
+    the given \a format.  The individual samples will be initialized
+    to the default for the format.
 
     \a startTime (in microseconds) indicates when this buffer
     starts in the stream.
     If this buffer is not part of a stream, set it to -1.
  */
-QAudioBuffer::QAudioBuffer(int numSamples, const QAudioFormat &format, qint64 startTime)
+QAudioBuffer::QAudioBuffer(int numFrames, const QAudioFormat &format, qint64 startTime)
 {
     if (format.isValid())
-        d = new QAudioBufferPrivate(new QMemoryAudioBufferProvider(0, numSamples, format, startTime));
+        d = new QAudioBufferPrivate(new QMemoryAudioBufferProvider(0, numFrames, format, startTime));
     else
         d = 0;
 }
@@ -292,13 +292,13 @@ QAudioBuffer::~QAudioBuffer()
 
 /*!
     Returns true if this is a valid buffer.  A valid buffer
-    has more than zero samples in it and a valid format.
+    has more than zero frames in it and a valid format.
  */
 bool QAudioBuffer::isValid() const
 {
     if (!d || !d->mProvider)
         return false;
-    return d->mProvider->format().isValid() && (d->mProvider->sampleCount() > 0);
+    return d->mProvider->format().isValid() && (d->mProvider->frameCount() > 0);
 }
 
 /*!
@@ -306,7 +306,7 @@ bool QAudioBuffer::isValid() const
 
     Several properties of this format influence how
     the \l duration() or \l byteCount() are calculated
-    from the \l sampleCount().
+    from the \l frameCount().
  */
 QAudioFormat QAudioBuffer::format() const
 {
@@ -316,6 +316,19 @@ QAudioFormat QAudioBuffer::format() const
 }
 
 /*!
+    Returns the number of complete audio frames in this buffer.
+
+    An audio frame is an interleaved set of one sample per channel
+    for the same instant in time.
+*/
+int QAudioBuffer::frameCount() const
+{
+    if (!isValid())
+        return 0;
+    return d->mProvider->frameCount();
+}
+
+/*!
     Returns the number of samples in this buffer.
 
     If the format of this buffer has multiple channels,
@@ -323,12 +336,15 @@ QAudioFormat QAudioBuffer::format() const
     that a stereo buffer with 1000 samples in total will
     have 500 left samples and 500 right samples (interleaved),
     and this function will return 1000.
- */
+
+    \sa frameCount()
+*/
 int QAudioBuffer::sampleCount() const
 {
     if (!isValid())
         return 0;
-    return d->mProvider->sampleCount();
+
+    return frameCount() * format().channelCount();
 }
 
 /*!
@@ -337,21 +353,17 @@ int QAudioBuffer::sampleCount() const
 int QAudioBuffer::byteCount() const
 {
     const QAudioFormat f(format());
-    return (f.sampleSize() * sampleCount()) / 8; // sampleSize is in bits
+    return format().bytesForFrames(frameCount());
 }
 
 /*!
     Returns the duration of audio in this buffer, in microseconds.
 
-    This depends on the /l format(), and the \l sampleCount().
+    This depends on the /l format(), and the \l frameCount().
 */
 qint64 QAudioBuffer::duration() const
 {
-    int divisor = format().sampleRate() * format().channelCount();
-    if (divisor > 0)
-        return (sampleCount() * 1000000LL) / divisor;
-    else
-        return 0;
+    return format().durationForFrames(frameCount());
 }
 
 /*!
@@ -471,7 +483,7 @@ void *QAudioBuffer::data()
     }
 
     // Wasn't writable, so turn it into a memory provider
-    QAbstractAudioBuffer *memBuffer = new QMemoryAudioBufferProvider(constData(), sampleCount(), format(), startTime());
+    QAbstractAudioBuffer *memBuffer = new QMemoryAudioBufferProvider(constData(), frameCount(), format(), startTime());
 
     if (memBuffer) {
         d->mProvider->release();
@@ -487,35 +499,35 @@ void *QAudioBuffer::data()
 // Template helper classes worth documenting
 
 /*!
-    \class QAudioBuffer::StereoSampleDefault
+    \class QAudioBuffer::StereoFrameDefault
     \internal
 
     Just a trait class for the default value.
 */
 
 /*!
-    \class QAudioBuffer::StereoSample
-    \brief The StereoSample class provides a simple wrapper for a stereo audio sample.
+    \class QAudioBuffer::StereoFrame
+    \brief The StereoFrame class provides a simple wrapper for a stereo audio frame.
     \inmodule QtMultimedia
     \ingroup multimedia
     \ingroup multimedia_audio
 
     This templatized structure lets you treat a block of individual samples as an
-    interleaved stereo stream.  This is most useful when used with the templatized
+    interleaved stereo stream frame.  This is most useful when used with the templatized
     \l {QAudioBuffer::data()}{data()} functions of QAudioBuffer.  Generally the data
     is accessed as a pointer, so no copying should occur.
 
     There are some predefined instantiations of this template for working with common
     stereo sample depths in a convenient way.
 
-    This structure has \e left and \e right members for accessing individual channel data.
+    This frame structure has \e left and \e right members for accessing individual channel data.
 
     For example:
     \code
     // Assuming 'buffer' is an unsigned 16 bit stereo buffer..
-    QAudioBuffer::S16U *sample = buffer->data<QAudioBuffer::S16U>();
-    for (int i=0; i < buffer->sampleCount() / 2; i++) {
-        qSwap(sample[i].left, sample[i].right);
+    QAudioBuffer::S16U *frames = buffer->data<QAudioBuffer::S16U>();
+    for (int i=0; i < buffer->frameCount(); i++) {
+        qSwap(frames[i].left, frames[i].right);
     }
     \endcode
 
@@ -523,77 +535,77 @@ void *QAudioBuffer::data()
 */
 
 /*!
-    \fn QAudioBuffer::StereoSample::StereoSample()
+    \fn QAudioBuffer::StereoFrame::StereoFrame()
 
-    Constructs a new sample with the "silent" value for this
+    Constructs a new frame with the "silent" value for this
     sample format (0 for signed formats and floats, 0x8* for unsigned formats).
 */
 
 /*!
-    \fn QAudioBuffer::StereoSample::StereoSample(T leftSample, T rightSample)
+    \fn QAudioBuffer::StereoFrame::StereoFrame(T leftSample, T rightSample)
 
-    Constructs a new sample with the supplied \a leftSample and \a rightSample values.
+    Constructs a new frame with the supplied \a leftSample and \a rightSample values.
 */
 
 /*!
-    \fn QAudioBuffer::StereoSample::operator=(const StereoSample &other)
+    \fn QAudioBuffer::StereoFrame::operator=(const StereoFrame &other)
 
-    Assigns \a other to this sample.
+    Assigns \a other to this frame.
  */
 
 
 /*!
-    \fn QAudioBuffer::StereoSample::average() const
+    \fn QAudioBuffer::StereoFrame::average() const
 
     Returns the arithmetic average of the left and right samples.
  */
 
-/*! \fn QAudioBuffer::StereoSample::clear()
+/*! \fn QAudioBuffer::StereoFrame::clear()
 
-    Sets the values of this sample to the "silent" value.
+    Sets the values of this frame to the "silent" value.
 */
 
 /*!
-    \variable QAudioBuffer::StereoSample::left
+    \variable QAudioBuffer::StereoFrame::left
     \brief the left sample
 */
 
 /*!
-    \variable QAudioBuffer::StereoSample::right
+    \variable QAudioBuffer::StereoFrame::right
     \brief the right sample
 */
 
 /*!
     \typedef QAudioBuffer::S8U
-    \relates QAudioBuffer::StereoSample
+    \relates QAudioBuffer::StereoFrame
 
     This is a predefined specialization for an unsigned stereo 8 bit sample.  Each
     channel is an \e {unsigned char}.
 */
 /*!
     \typedef QAudioBuffer::S8S
-    \relates QAudioBuffer::StereoSample
+    \relates QAudioBuffer::StereoFrame
 
     This is a predefined specialization for a signed stereo 8 bit sample.  Each
     channel is a \e {signed char}.
 */
 /*!
     \typedef QAudioBuffer::S16U
-    \relates QAudioBuffer::StereoSample
+    \relates QAudioBuffer::StereoFrame
 
     This is a predefined specialization for an unsigned stereo 16 bit sample.  Each
     channel is an \e {unsigned short}.
 */
 /*!
     \typedef QAudioBuffer::S16S
-    \relates QAudioBuffer::StereoSample
+    \relates QAudioBuffer::StereoFrame
 
     This is a predefined specialization for a signed stereo 16 bit sample.  Each
     channel is a \e {signed short}.
 */
 /*!
     \typedef QAudioBuffer::S32F
-    \relates QAudioBuffer::StereoSample
+    \relates QAudioBuffer::StereoFrame
 
     This is a predefined specialization for an 32 bit float sample.  Each
     channel is a \e float.
index 14718cb..ac16e21 100644 (file)
@@ -65,7 +65,7 @@ public:
     QAudioBuffer(QAbstractAudioBuffer *provider);
     QAudioBuffer(const QAudioBuffer &other);
     QAudioBuffer(const QByteArray &data, const QAudioFormat &format, qint64 startTime = -1);
-    QAudioBuffer(int numSamples, const QAudioFormat &format, qint64 startTime = -1); // Initialized to empty
+    QAudioBuffer(int numFrames, const QAudioFormat &format, qint64 startTime = -1); // Initialized to empty
 
     QAudioBuffer& operator=(const QAudioBuffer &other);
 
@@ -75,6 +75,7 @@ public:
 
     QAudioFormat format() const;
 
+    int frameCount() const;
     int sampleCount() const;
     int byteCount() const;
 
@@ -93,23 +94,23 @@ public:
     void *data(); // detaches
 
     // Structures for easier access to stereo data
-    template <typename T> struct StereoSampleDefault { enum { Default = 0 }; };
+    template <typename T> struct StereoFrameDefault { enum { Default = 0 }; };
 
-    template <typename T> struct StereoSample {
+    template <typename T> struct StereoFrame {
 
-        StereoSample()
-            : left(T(StereoSampleDefault<T>::Default))
-            , right(T(StereoSampleDefault<T>::Default))
+        StereoFrame()
+            : left(T(StereoFrameDefault<T>::Default))
+            , right(T(StereoFrameDefault<T>::Default))
         {
         }
 
-        StereoSample(T leftSample, T rightSample)
+        StereoFrame(T leftSample, T rightSample)
             : left(leftSample)
             , right(rightSample)
         {
         }
 
-        StereoSample& operator=(const StereoSample &other)
+        StereoFrame& operator=(const StereoFrame &other)
         {
             // Two straight assigns is probably
             // cheaper than a conditional check on
@@ -123,14 +124,14 @@ public:
         T right;
 
         T average() const {return (left + right) / 2;}
-        void clear() {left = right = T(StereoSampleDefault<T>::Default);}
+        void clear() {left = right = T(StereoFrameDefault<T>::Default);}
     };
 
-    typedef StereoSample<unsigned char> S8U;
-    typedef StereoSample<signed char> S8S;
-    typedef StereoSample<unsigned short> S16U;
-    typedef StereoSample<signed short> S16S;
-    typedef StereoSample<float> S32F;
+    typedef StereoFrame<unsigned char> S8U;
+    typedef StereoFrame<signed char> S8S;
+    typedef StereoFrame<unsigned short> S16U;
+    typedef StereoFrame<signed short> S16S;
+    typedef StereoFrame<float> S32F;
 
     template <typename T> const T* constData() const {
         return static_cast<const T*>(constData());
@@ -145,8 +146,8 @@ private:
     QAudioBufferPrivate *d;
 };
 
-template <> struct QAudioBuffer::StereoSampleDefault<unsigned char> { enum { Default = 128 }; };
-template <> struct QAudioBuffer::StereoSampleDefault<unsigned short> { enum { Default = 32768 }; };
+template <> struct QAudioBuffer::StereoFrameDefault<unsigned char> { enum { Default = 128 }; };
+template <> struct QAudioBuffer::StereoFrameDefault<unsigned short> { enum { Default = 32768 }; };
 
 
 QT_END_NAMESPACE
index 854ca23..5f6d316 100644 (file)
@@ -66,7 +66,7 @@ public:
     // Format related
     virtual QAudioFormat format() const = 0;
     virtual qint64 startTime() const = 0;
-    virtual int sampleCount() const = 0;
+    virtual int frameCount() const = 0;
 
     // R/O Data
     virtual void *constData() const = 0;
index c91620b..32e17a9 100644 (file)
@@ -146,7 +146,8 @@ public:
 
     This class is typically used in conjunction with QAudioInput or
     QAudioOutput to allow you to specify the parameters of the audio
-    stream being read or written.
+    stream being read or written, or with QAudioBuffer when dealing with
+    samples in memory.
 
     You can obtain audio formats compatible with the audio device used
     through functions in QAudioDeviceInfo. This class also lets you
@@ -154,6 +155,11 @@ public:
     the parameters yourself. See the \l QAudioDeviceInfo class
     description for details. You need to know the format of the audio
     streams you wish to play or record.
+
+    In the common case of interleaved linear PCM data, the codec will
+    be "audio/pcm", and the samples for all channels will be interleaved.
+    One sample for each channel for the same instant in time is referred
+    to as a frame in QtMultimedia (and other places).
 */
 
 /*!
@@ -282,6 +288,8 @@ void QAudioFormat::setSampleSize(int sampleSize)
 
 /*!
     Returns the current sample size value, in bits.
+
+    \sa bytesPerFrame()
 */
 int QAudioFormat::sampleSize() const
 {
@@ -345,6 +353,108 @@ QAudioFormat::SampleType QAudioFormat::sampleType() const
 }
 
 /*!
+    Returns the number of bytes required for this audio format for \a duration microseconds.
+
+    Returns 0 if this format is not valid.
+
+    Note that some rounding may occur if \a duration is not an exact fraction of the
+    sampleRate().
+
+    \sa durationForBytes()
+ */
+qint32 QAudioFormat::bytesForDuration(qint64 duration) const
+{
+    return bytesPerFrame() * framesForDuration(duration);
+}
+
+/*!
+    Returns the number of microseconds represented by \a bytes in this format.
+
+    Returns 0 if this format is not valid.
+
+    Note that some rounding may occur if \a bytes is not an exact multiple
+    of the number of bytes per frame.
+
+    \sa bytesForDuration()
+*/
+qint64 QAudioFormat::durationForBytes(qint32 bytes) const
+{
+    if (!isValid() || bytes <= 0)
+        return 0;
+
+    // We round the byte count to ensure whole frames
+    return qint64(1000000LL * (bytes / bytesPerFrame())) / sampleRate();
+}
+
+/*!
+    Returns the number of bytes required for \a frameCount frames of this format.
+
+    Returns 0 if this format is not valid.
+
+    \sa bytesForDuration()
+*/
+qint32 QAudioFormat::bytesForFrames(qint32 frameCount) const
+{
+    return frameCount * bytesPerFrame();
+}
+
+/*!
+    Returns the number of frames represented by \a byteCount in this format.
+
+    Note that some rounding may occur if \a byteCount is not an exact multiple
+    of the number of bytes per frame.
+
+    Each frame has one sample per channel.
+
+    \sa framesForDuration()
+*/
+qint32 QAudioFormat::framesForBytes(qint32 byteCount) const
+{
+    int size = bytesPerFrame();
+    if (size > 0)
+        return byteCount / size;
+    return 0;
+}
+
+/*!
+    Returns the number of frames required to represent \a duration microseconds in this format.
+
+    Note that some rounding may occur if \a duration is not an exact fraction of the
+    \l sampleRate().
+*/
+qint32 QAudioFormat::framesForDuration(qint64 duration) const
+{
+    if (!isValid())
+        return 0;
+
+    return qint32((duration * sampleRate()) / 1000000LL);
+}
+
+/*!
+    Return the number of microseconds represented by \a frameCount frames in this format.
+*/
+qint64 QAudioFormat::durationForFrames(qint32 frameCount) const
+{
+    if (!isValid() || frameCount <= 0)
+        return 0;
+
+    return (frameCount * 1000000LL) / sampleRate();
+}
+
+/*!
+    Returns the number of bytes required to represent one frame (a sample in each channel) in this format.
+
+    Returns 0 if this format is invalid.
+*/
+int QAudioFormat::bytesPerFrame() const
+{
+    if (!isValid())
+        return 0;
+
+    return (sampleSize() * channelCount()) / 8;
+}
+
+/*!
     \enum QAudioFormat::SampleType
 
     \value Unknown       Not Set
index 3bc5780..fccb206 100644 (file)
@@ -91,6 +91,18 @@ public:
     void setSampleType(QAudioFormat::SampleType sampleType);
     QAudioFormat::SampleType sampleType() const;
 
+    // Helper functions
+    qint32 bytesForDuration(qint64 duration) const;
+    qint64 durationForBytes(qint32 byteCount) const;
+
+    qint32 bytesForFrames(qint32 frameCount) const;
+    qint32 framesForBytes(qint32 byteCount) const;
+
+    qint32 framesForDuration(qint64 duration) const;
+    qint64 durationForFrames(qint32 frameCount) const;
+
+    int bytesPerFrame() const;
+
 private:
     QSharedDataPointer<QAudioFormatPrivate> d;
 };
index ff403b3..bf38d8b 100644 (file)
@@ -76,11 +76,11 @@ tst_QAudioBuffer::tst_QAudioBuffer()
     mFormat.setSampleSize(16);
     mFormat.setSampleType(QAudioFormat::UnSignedInt);
     mFormat.setSampleRate(10000);
-    mFormat.setCodec("audio/x-pcm"); // XXX this is not a good fit?
+    mFormat.setCodec("audio/pcm");
 
     QByteArray b(4000, 0x80);
     mNull = new QAudioBuffer;
-    mEmpty = new QAudioBuffer(1000, mFormat); // 1000 samples of 16 bits -> 2KB
+    mEmpty = new QAudioBuffer(500, mFormat); // 500 stereo frames of 16 bits -> 2KB
     mFromArray = new QAudioBuffer(b, mFormat);
 }
 
@@ -102,6 +102,7 @@ void tst_QAudioBuffer::ctors()
     QCOMPARE(mNull->duration(), 0LL);
     QCOMPARE(mNull->byteCount(), 0);
     QCOMPARE(mNull->sampleCount(), 0);
+    QCOMPARE(mNull->frameCount(), 0);
     QCOMPARE(mNull->startTime(), -1LL);
 
     // Empty buffer
@@ -110,6 +111,7 @@ void tst_QAudioBuffer::ctors()
     QVERIFY(mEmpty->data() != 0);
     QVERIFY(((const QAudioBuffer*)mEmpty)->data() != 0);
     QCOMPARE(mEmpty->sampleCount(), 1000);
+    QCOMPARE(mEmpty->frameCount(), 500);
     QCOMPARE(mEmpty->duration(), 50000LL);
     QCOMPARE(mEmpty->byteCount(), 2000);
     QCOMPARE(mEmpty->startTime(), -1LL);
@@ -123,6 +125,7 @@ void tst_QAudioBuffer::ctors()
     QCOMPARE(mFromArray->duration(), 100000LL);
     QCOMPARE(mFromArray->byteCount(), 4000);
     QCOMPARE(mFromArray->sampleCount(), 2000);
+    QCOMPARE(mFromArray->frameCount(), 1000);
     QCOMPARE(mFromArray->startTime(), -1LL);
 
 
@@ -135,6 +138,7 @@ void tst_QAudioBuffer::ctors()
     QCOMPARE(badFormat.duration(), 0LL);
     QCOMPARE(badFormat.byteCount(), 0);
     QCOMPARE(badFormat.sampleCount(), 0);
+    QCOMPARE(badFormat.frameCount(), 0);
     QCOMPARE(badFormat.startTime(), -1LL);
 
     QAudioBuffer badArray(QByteArray(), mFormat);
@@ -145,6 +149,7 @@ void tst_QAudioBuffer::ctors()
     QCOMPARE(badArray.duration(), 0LL);
     QCOMPARE(badArray.byteCount(), 0);
     QCOMPARE(badArray.sampleCount(), 0);
+    QCOMPARE(badArray.frameCount(), 0);
     QCOMPARE(badArray.startTime(), -1LL);
 
     QAudioBuffer badBoth = QAudioBuffer(QByteArray(), QAudioFormat());
@@ -155,12 +160,13 @@ void tst_QAudioBuffer::ctors()
     QCOMPARE(badBoth.duration(), 0LL);
     QCOMPARE(badBoth.byteCount(), 0);
     QCOMPARE(badBoth.sampleCount(), 0);
+    QCOMPARE(badBoth.frameCount(), 0);
     QCOMPARE(badBoth.startTime(), -1LL);
 }
 
 void tst_QAudioBuffer::assign()
 {
-    // Needs strong behaviour definition
+    // TODO Needs strong behaviour definition
 }
 
 void tst_QAudioBuffer::constData() const
@@ -245,7 +251,8 @@ void tst_QAudioBuffer::durations()
 {
     QFETCH(int, channelCount);
     QFETCH(int, sampleSize);
-    QFETCH(int, sampleCount);
+    QFETCH(int, frameCount);
+    int sampleCount = frameCount * channelCount;
     QFETCH(QAudioFormat::SampleType, sampleType);
     QFETCH(int, sampleRate);
     QFETCH(qint64, duration);
@@ -256,10 +263,11 @@ void tst_QAudioBuffer::durations()
     f.setSampleType(sampleType);
     f.setSampleSize(sampleSize);
     f.setSampleRate(sampleRate);
-    f.setCodec("audio/x-pcm"); // XXX this is not a good fit?
+    f.setCodec("audio/pcm");
 
-    QAudioBuffer b(sampleCount, f);
+    QAudioBuffer b(frameCount, f);
 
+    QCOMPARE(b.frameCount(), frameCount);
     QCOMPARE(b.sampleCount(), sampleCount);
     QCOMPARE(b.duration(), duration);
     QCOMPARE(b.byteCount(), byteCount);
@@ -269,7 +277,7 @@ void tst_QAudioBuffer::durations_data()
 {
     QTest::addColumn<int>("channelCount");
     QTest::addColumn<int>("sampleSize");
-    QTest::addColumn<int>("sampleCount");
+    QTest::addColumn<int>("frameCount");
     QTest::addColumn<QAudioFormat::SampleType>("sampleType");
     QTest::addColumn<int>("sampleRate");
     QTest::addColumn<qint64>("duration");
@@ -278,11 +286,11 @@ void tst_QAudioBuffer::durations_data()
     QTest::newRow("M8_2000_8K") << 1 << 8 << 2000 << QAudioFormat::UnSignedInt << 8000 << 250000LL << 2000;
     QTest::newRow("M8_1000_4K") << 1 << 8 << 1000 << QAudioFormat::UnSignedInt << 4000 << 250000LL << 1000;
 
-    QTest::newRow("S8_1000_8K") << 2 << 8 << 1000 << QAudioFormat::UnSignedInt << 8000 << 62500LL << 1000;
+    QTest::newRow("S8_1000_8K") << 2 << 8 << 500 << QAudioFormat::UnSignedInt << 8000 << 62500LL << 1000;
 
-    QTest::newRow("SF_1000_8K") << 2 << 32 << 1000 << QAudioFormat::Float << 8000 << 62500LL << 4000;
+    QTest::newRow("SF_1000_8K") << 2 << 32 << 500 << QAudioFormat::Float << 8000 << 62500LL << 4000;
 
-    QTest::newRow("4x128_1000_16K") << 4 << 128 << 1000 << QAudioFormat::SignedInt << 16000 << 15625LL << 16000;
+    QTest::newRow("4x128_1000_16K") << 4 << 128 << 250 << QAudioFormat::SignedInt << 16000 << 15625LL << 16000;
 }
 
 void tst_QAudioBuffer::stereoSample()
index b5bb1a8..7193cb2 100644 (file)
@@ -67,6 +67,9 @@ private slots:
     void checkSampleRate();
     void checkChannelCount();
 
+    void checkSizes();
+    void checkSizes_data();
+
     void debugOperator();
     void debugOperator_data();
 };
@@ -222,6 +225,97 @@ void tst_QAudioFormat::checkChannelCount()
     QVERIFY(audioFormat.channelCount() == 5);
 }
 
+void tst_QAudioFormat::checkSizes()
+{
+    QFETCH(QAudioFormat, format);
+    QFETCH(int, frameSize);
+    QFETCH(int, byteCount);
+    QFETCH(int, frameCount);
+    QFETCH(qint64, durationForByte);
+    QFETCH(int, byteForFrame);
+    QFETCH(qint64, durationForByteForDuration);
+    QFETCH(int, byteForDuration);
+    QFETCH(int, framesForDuration);
+
+    QCOMPARE(format.bytesPerFrame(), frameSize);
+
+    // Byte input
+    QCOMPARE(format.framesForBytes(byteCount), frameCount);
+    QCOMPARE(format.durationForBytes(byteCount), durationForByte);
+
+    // Framecount input
+    QCOMPARE(format.bytesForFrames(frameCount), byteForFrame);
+    QCOMPARE(format.durationForFrames(frameCount), durationForByte);
+
+    // Duration input
+    QCOMPARE(format.bytesForDuration(durationForByteForDuration), byteForDuration);
+    QCOMPARE(format.framesForDuration(durationForByte), frameCount);
+    QCOMPARE(format.framesForDuration(durationForByteForDuration), framesForDuration);
+}
+
+void tst_QAudioFormat::checkSizes_data()
+{
+    QTest::addColumn<QAudioFormat>("format");
+    QTest::addColumn<int>("frameSize");
+    QTest::addColumn<int>("byteCount");
+    QTest::addColumn<qint64>("durationForByte");
+    QTest::addColumn<int>("frameCount"); // output of sampleCountforByte, input for byteForFrame
+    QTest::addColumn<int>("byteForFrame");
+    QTest::addColumn<qint64>("durationForByteForDuration"); // input for byteForDuration
+    QTest::addColumn<int>("byteForDuration");
+    QTest::addColumn<int>("framesForDuration");
+
+    QAudioFormat f;
+    QTest::newRow("invalid") << f << 0 << 0 << 0LL << 0 << 0 << 0LL << 0 << 0;
+
+    f.setByteOrder(QAudioFormat::LittleEndian);
+    f.setChannelCount(1);
+    f.setSampleRate(8000);
+    f.setCodec("audio/pcm");
+    f.setSampleSize(8);
+    f.setSampleType(QAudioFormat::SignedInt);
+
+    qint64 qrtr = 250000LL;
+    qint64 half = 500000LL;
+    qint64 one = 1000000LL;
+    qint64 two = 2000000LL;
+
+    // No rounding errors with mono 8 bit
+    QTest::newRow("1ch_8b_8k_signed_4000") << f << 1 << 4000 << half << 4000 << 4000 << half << 4000 << 4000;
+    QTest::newRow("1ch_8b_8k_signed_8000") << f << 1 << 8000 << one << 8000 << 8000 << one << 8000 << 8000;
+    QTest::newRow("1ch_8b_8k_signed_16000") << f << 1 << 16000 << two << 16000 << 16000 << two << 16000 << 16000;
+
+    // Mono 16bit
+    f.setSampleSize(16);
+    QTest::newRow("1ch_16b_8k_signed_8000") << f << 2 << 8000 << half << 4000 << 8000 << half << 8000 << 4000;
+    QTest::newRow("1ch_16b_8k_signed_16000") << f << 2 << 16000 << one << 8000 << 16000 << one << 16000 << 8000;
+
+    // Rounding errors
+    QTest::newRow("1ch_16b_8k_signed_8001") << f << 2 << 8001 << half << 4000 << 8000 << half << 8000 << 4000;
+    QTest::newRow("1ch_16b_8k_signed_8000_duration1") << f << 2 << 8000 << half << 4000 << 8000 << half + 1 << 8000 << 4000;
+    QTest::newRow("1ch_16b_8k_signed_8000_duration2") << f << 2 << 8000 << half << 4000 << 8000 << half + 124 << 8000 << 4000;
+    QTest::newRow("1ch_16b_8k_signed_8000_duration3") << f << 2 << 8000 << half << 4000 << 8000 << half + 125 << 8002 << 4001;
+    QTest::newRow("1ch_16b_8k_signed_8000_duration4") << f << 2 << 8000 << half << 4000 << 8000 << half + 126 << 8002 << 4001;
+
+    // Stereo 16 bit
+    f.setChannelCount(2);
+    QTest::newRow("2ch_16b_8k_signed_8000") << f << 4 << 8000 << qrtr << 2000 << 8000 << qrtr << 8000 << 2000;
+    QTest::newRow("2ch_16b_8k_signed_16000") << f << 4 << 16000 << half << 4000 << 16000 << half << 16000 << 4000;
+
+    // More rounding errors
+    // First rounding bytes
+    QTest::newRow("2ch_16b_8k_signed_8001") << f << 4 << 8001 << qrtr << 2000 << 8000 << qrtr << 8000 << 2000;
+    QTest::newRow("2ch_16b_8k_signed_8002") << f << 4 << 8002 << qrtr << 2000 << 8000 << qrtr << 8000 << 2000;
+    QTest::newRow("2ch_16b_8k_signed_8003") << f << 4 << 8003 << qrtr << 2000 << 8000 << qrtr << 8000 << 2000;
+
+    // Then rounding duration
+    // 8khz = 125us per frame
+    QTest::newRow("2ch_16b_8k_signed_8000_duration1") << f << 4 << 8000 << qrtr << 2000 << 8000 << qrtr + 1 << 8000 << 2000;
+    QTest::newRow("2ch_16b_8k_signed_8000_duration2") << f << 4 << 8000 << qrtr << 2000 << 8000 << qrtr + 124 << 8000 << 2000;
+    QTest::newRow("2ch_16b_8k_signed_8000_duration3") << f << 4 << 8000 << qrtr << 2000 << 8000 << qrtr + 125 << 8004 << 2001;
+    QTest::newRow("2ch_16b_8k_signed_8000_duration4") << f << 4 << 8000 << qrtr << 2000 << 8000 << qrtr + 126 << 8004 << 2001;
+}
+
 void tst_QAudioFormat::debugOperator_data()
 {
     QTest::addColumn<QAudioFormat>("format");