From 4ae9412a3aea75990008c3a32080c54533fdb389 Mon Sep 17 00:00:00 2001 From: Max Horn Date: Mon, 4 Jan 2010 22:48:28 +0000 Subject: Make some improvements for Audio::Timestamp. * Add convertToFramerate() method * Add framerate() method * Add operator == and != * Improve frameDiff() to work for two timestamps with distinct framerates * Improve Doxygen comments svn-id: r46994 --- sound/timestamp.cpp | 107 ++++++++++++++++++++++++++++++++++++++----------- sound/timestamp.h | 72 ++++++++++++++++++++++++++------- test/sound/timestamp.h | 30 +++++++++++++- 3 files changed, 170 insertions(+), 39 deletions(-) diff --git a/sound/timestamp.cpp b/sound/timestamp.cpp index c85ec47ae8..a517462bb4 100644 --- a/sound/timestamp.cpp +++ b/sound/timestamp.cpp @@ -27,51 +27,110 @@ namespace Audio { -Timestamp::Timestamp(uint32 m, int frameRate) : - _msecs(m), _frameRate(frameRate), _frameOffset(0) { - assert(_frameRate > 0); +static uint gcd(uint a, uint b) { + while (a > 0) { + int tmp = a; + a = b % a; + b = tmp; + } + return b; +} + +Timestamp::Timestamp(uint32 m, int framerate) : + _msecs(m), _framerate(framerate), _numberOfFrames(0) { + assert(_framerate > 0); +} + + +Timestamp Timestamp::convertToFramerate(int newFramerate) const { + Timestamp ts(*this); + + if (ts._framerate != newFramerate) { + ts._framerate = newFramerate; + + const uint g = gcd(_framerate, ts._framerate); + const uint p = _framerate / g; + const uint q = ts._framerate / g; + + // Convert the frame offset to the new framerate. + // We round to the nearest (as opposed to always + // rounding down), to minimize rounding errors during + // round trip conversions. + ts._numberOfFrames = (ts._numberOfFrames * q + p/2) / p; + + ts._msecs += (ts._numberOfFrames / ts._framerate) * 1000; + ts._numberOfFrames %= ts._framerate; + } + + return ts; +} + +bool Timestamp::operator==(const Timestamp &ts) const { + // TODO: Alternatively, we could define equality to mean that + // two timestamps describe the exacts same moment in time. + return (_msecs == ts._msecs) && + (_numberOfFrames == ts._numberOfFrames) && + (_framerate == ts._framerate); +} + +bool Timestamp::operator!=(const Timestamp &ts) const { + return !(*this == ts); } Timestamp Timestamp::addFrames(int frames) const { - Timestamp timestamp(*this); - timestamp._frameOffset += frames; + Timestamp ts(*this); + ts._numberOfFrames += frames; - if (timestamp._frameOffset < 0) { - int secsub = 1 + (-timestamp._frameOffset / timestamp._frameRate); + if (ts._numberOfFrames < 0) { + int secsub = 1 + (-ts._numberOfFrames / ts._framerate); - timestamp._frameOffset += timestamp._frameRate * secsub; - timestamp._msecs -= secsub * 1000; + ts._numberOfFrames += ts._framerate * secsub; + ts._msecs -= secsub * 1000; } - timestamp._msecs += (timestamp._frameOffset / timestamp._frameRate) * 1000; - timestamp._frameOffset %= timestamp._frameRate; + ts._msecs += (ts._numberOfFrames / ts._framerate) * 1000; + ts._numberOfFrames %= ts._framerate; - return timestamp; + return ts; } Timestamp Timestamp::addMsecs(int ms) const { - Timestamp timestamp(*this); - timestamp._msecs += ms; - return timestamp; + Timestamp ts(*this); + ts._msecs += ms; + return ts; } -int Timestamp::frameDiff(const Timestamp &b) const { - assert(_frameRate == b._frameRate); +int Timestamp::frameDiff(const Timestamp &ts) const { + + int delta = 0; + if (_msecs != ts._msecs) + delta = (long(_msecs) - long(ts._msecs)) * _framerate / 1000; + + delta += _numberOfFrames; - int msecdelta = 0; - if (_msecs != b._msecs) - msecdelta = (long(_msecs) - long(b._msecs)) * _frameRate / 1000; + if (_framerate == ts._framerate) { + delta -= ts._numberOfFrames; + } else { + // We need to multiply by the quotient of the two framerates. + // We cancel the GCD in this fraction to reduce the risk of + // overflows. + const uint g = gcd(_framerate, ts._framerate); + const uint p = _framerate / g; + const uint q = ts._framerate / g; + + delta -= (ts._numberOfFrames * p + q/2) / q; + } - return msecdelta + _frameOffset - b._frameOffset; + return delta; } -int Timestamp::msecsDiff(const Timestamp &b) const { - return long(msecs()) - long(b.msecs()); +int Timestamp::msecsDiff(const Timestamp &ts) const { + return long(msecs()) - long(ts.msecs()); } uint32 Timestamp::msecs() const { - return _msecs + _frameOffset * 1000L / _frameRate; + return _msecs + _numberOfFrames * 1000L / _framerate; } diff --git a/sound/timestamp.h b/sound/timestamp.h index 1a30b4353e..218198dcf7 100644 --- a/sound/timestamp.h +++ b/sound/timestamp.h @@ -38,33 +38,77 @@ namespace Audio { */ class Timestamp { protected: - uint32 _msecs; - int _frameRate; - int _frameOffset; - /* Total time: msecs + frame_offset/frame_rate */ + /** + * The millisecond part of this timestamp. + * The total time represented by this timestamp is + * computed as follows: + * _msecs + 1000 * _numberOfFrames / _framerate + */ + uint _msecs; + + /** + * The number of frames which together with _msecs encodes + * the timestamp. The total number of frames represented + * by this timestamp is computed as follows: + * _numberOfFrames + _msecs * _framerate / 1000 + */ + int _numberOfFrames; + + /** The framerate, i.e. the number of frames per second. */ + int _framerate; public: /** * Set up a timestamp with a given time and framerate. - * @param msecs staring time in milliseconds - * @param frameRate number of frames per second (must be > 0) + * @param msecs starting time in milliseconds + * @param framerate number of frames per second (must be > 0) */ - Timestamp(uint32 msecs, int frameRate); + Timestamp(uint32 msecs, int framerate); + + /** + * Return a timestamp which represents as closely as possible + * the point in time describes by this timestamp, but with + * a different framerate. + */ + Timestamp convertToFramerate(int newFramerate) const; + + bool operator==(const Timestamp &ts) const; + bool operator!=(const Timestamp &ts) const; +// bool operator<(const Timestamp &ts) const; +// bool operator<=(const Timestamp &ts) const; +// bool operator>(const Timestamp &ts) const; +// bool operator>=(const Timestamp &ts) const; - /** Adds a number of frames to a timestamp. */ + /** + * Returns a new timestamp, which corresponds to the time encoded + * by this timestamp with the given number of frames added. + */ Timestamp addFrames(int frames) const; - /** Adds a number of milliseconds to a timestamp. */ + /** + * Returns a new timestamp, which corresponds to the time encoded + * by this timestamp with the given number of milliseconds added. + */ Timestamp addMsecs(int ms) const; - /** Computes the difference (# of frames) between this timestamp and b. */ - int frameDiff(const Timestamp &b) const; + /** + * Computes the number of frames between this timestamp and ts. + * The frames are with respect to the framerate used by this + * Timestamp (which may differ from the framerate used by ts). + */ + int frameDiff(const Timestamp &ts) const; - /** Computes the difference (# of milliseconds) between this timestamp and b. */ - int msecsDiff(const Timestamp &b) const; + /** Computes the number off milliseconds between this timestamp and ts. */ + int msecsDiff(const Timestamp &ts) const; - /** Determines the time in milliseconds described by this timestamp. */ + /** + * Determines the time in milliseconds described by this timestamp, + * rounded down. + */ uint32 msecs() const; + + /** Return the framerate used by this timestamp. */ + int getFramerate() const { return _framerate; } }; diff --git a/test/sound/timestamp.h b/test/sound/timestamp.h index 89fc851a80..dccf67399c 100644 --- a/test/sound/timestamp.h +++ b/test/sound/timestamp.h @@ -47,7 +47,6 @@ class TimestampTestSuite : public CxxTest::TestSuite TS_ASSERT_EQUALS(a.addFrames(-60).msecs(), (uint32)(1234 - 1000 * 60/60)); } - void test_more_add_diff() { const Audio::Timestamp c(10002, 1000); @@ -56,4 +55,33 @@ class TimestampTestSuite : public CxxTest::TestSuite TS_ASSERT_EQUALS(v, i); } } + + + void test_diff_with_conversion() { + const Audio::Timestamp a = Audio::Timestamp(10, 1000).addFrames(20); + const Audio::Timestamp b = Audio::Timestamp(10, 1000/5).addFrames(20/5); + const Audio::Timestamp c = Audio::Timestamp(10, 1000*2).addFrames(20*2); + + TS_ASSERT_EQUALS(a.frameDiff(a), 0); + TS_ASSERT_EQUALS(a.frameDiff(b), 0); + TS_ASSERT_EQUALS(a.frameDiff(c), 0); + + TS_ASSERT_EQUALS(b.frameDiff(a), 0); + TS_ASSERT_EQUALS(b.frameDiff(b), 0); + TS_ASSERT_EQUALS(b.frameDiff(c), 0); + + TS_ASSERT_EQUALS(c.frameDiff(a), 0); + TS_ASSERT_EQUALS(c.frameDiff(b), 0); + TS_ASSERT_EQUALS(c.frameDiff(c), 0); + } + + + void test_convert() { + const Audio::Timestamp a = Audio::Timestamp(10, 1000).addFrames(20); + const Audio::Timestamp b = Audio::Timestamp(10, 1000/5).addFrames(20/5); + const Audio::Timestamp c = Audio::Timestamp(10, 1000*2).addFrames(20*2); + + TS_ASSERT_EQUALS(a.convertToFramerate(1000/5), b); + TS_ASSERT_EQUALS(a.convertToFramerate(1000*2), c); + } }; -- cgit v1.2.3