Change the way Timestamp stores its data.
Instead of storing milliseconds and frames (which causes rounding errors, and causes ambiguity in how a given time is stored), we now do things differently: We store a number of seconds, and frames. To make sure that we can still handle milliseconds accurately, though, we change the framerate to the least common multiple of the original framerate and 1000. So 60 becomes 6000, and 44100 becomes 441000. There are no visible changes for client code, except for the increased accuracy. svn-id: r47070
This commit is contained in:
parent
69566f6bf1
commit
5b635fd610
3 changed files with 81 additions and 27 deletions
|
@ -36,17 +36,23 @@ static uint gcd(uint a, uint b) {
|
|||
return b;
|
||||
}
|
||||
|
||||
Timestamp::Timestamp(uint32 m, int framerate) :
|
||||
_msecs(m), _framerate(framerate), _numberOfFrames(0) {
|
||||
assert(_framerate > 0);
|
||||
Timestamp::Timestamp(uint32 ms, int framerate) {
|
||||
assert(framerate > 0);
|
||||
|
||||
_secs = ms / 1000;
|
||||
_framerateFactor = 1000 / gcd(1000, framerate);
|
||||
_framerate = framerate * _framerateFactor;
|
||||
|
||||
_numberOfFrames = (ms % 1000) * _framerate / 1000;
|
||||
}
|
||||
|
||||
|
||||
Timestamp Timestamp::convertToFramerate(int newFramerate) const {
|
||||
Timestamp ts(*this);
|
||||
|
||||
if (ts._framerate != newFramerate) {
|
||||
ts._framerate = newFramerate;
|
||||
if (ts.getFramerate() != newFramerate) {
|
||||
ts._framerateFactor = 1000 / gcd(1000, newFramerate);
|
||||
ts._framerate = newFramerate * ts._framerateFactor;
|
||||
|
||||
const uint g = gcd(_framerate, ts._framerate);
|
||||
const uint p = _framerate / g;
|
||||
|
@ -58,7 +64,7 @@ Timestamp Timestamp::convertToFramerate(int newFramerate) const {
|
|||
// round trip conversions.
|
||||
ts._numberOfFrames = (ts._numberOfFrames * q + p/2) / p;
|
||||
|
||||
ts._msecs += (ts._numberOfFrames / ts._framerate) * 1000;
|
||||
ts._secs += (ts._numberOfFrames / ts._framerate);
|
||||
ts._numberOfFrames %= ts._framerate;
|
||||
}
|
||||
|
||||
|
@ -66,8 +72,8 @@ Timestamp Timestamp::convertToFramerate(int newFramerate) const {
|
|||
}
|
||||
|
||||
bool Timestamp::operator==(const Timestamp &ts) const {
|
||||
// TODO: Improve this
|
||||
return (ts.msecs() == msecs()) && !frameDiff(ts);
|
||||
return (ts._secs == _secs) &&
|
||||
(ts._numberOfFrames * _framerate == _numberOfFrames * ts._framerate);
|
||||
}
|
||||
|
||||
bool Timestamp::operator!=(const Timestamp &ts) const {
|
||||
|
@ -77,16 +83,19 @@ bool Timestamp::operator!=(const Timestamp &ts) const {
|
|||
|
||||
Timestamp Timestamp::addFrames(int frames) const {
|
||||
Timestamp ts(*this);
|
||||
ts._numberOfFrames += frames;
|
||||
|
||||
// The frames are given in the original framerate, so we have to
|
||||
// adjust by _framerateFactor accordingly.
|
||||
ts._numberOfFrames += frames * _framerateFactor;
|
||||
|
||||
if (ts._numberOfFrames < 0) {
|
||||
int secsub = 1 + (-ts._numberOfFrames / ts._framerate);
|
||||
|
||||
ts._numberOfFrames += ts._framerate * secsub;
|
||||
ts._msecs -= secsub * 1000;
|
||||
ts._secs -= secsub;
|
||||
}
|
||||
|
||||
ts._msecs += (ts._numberOfFrames / ts._framerate) * 1000;
|
||||
ts._secs += (ts._numberOfFrames / ts._framerate);
|
||||
ts._numberOfFrames %= ts._framerate;
|
||||
|
||||
return ts;
|
||||
|
@ -94,15 +103,16 @@ Timestamp Timestamp::addFrames(int frames) const {
|
|||
|
||||
Timestamp Timestamp::addMsecs(int ms) const {
|
||||
Timestamp ts(*this);
|
||||
ts._msecs += ms;
|
||||
return ts;
|
||||
ts._secs += ms / 1000;
|
||||
// Add the remaining frames. Note that _framerate is always divisible by 1000.
|
||||
return ts.addFrames((ms % 1000) * (_framerate / 1000));
|
||||
}
|
||||
|
||||
int Timestamp::frameDiff(const Timestamp &ts) const {
|
||||
|
||||
int delta = 0;
|
||||
if (_msecs != ts._msecs)
|
||||
delta = (long(_msecs) - long(ts._msecs)) * _framerate / 1000;
|
||||
if (_secs != ts._secs)
|
||||
delta = (long(_secs) - long(ts._secs)) * _framerate;
|
||||
|
||||
delta += _numberOfFrames;
|
||||
|
||||
|
@ -119,7 +129,7 @@ int Timestamp::frameDiff(const Timestamp &ts) const {
|
|||
delta -= (ts._numberOfFrames * p + q/2) / q;
|
||||
}
|
||||
|
||||
return delta;
|
||||
return delta / _framerateFactor;
|
||||
}
|
||||
|
||||
int Timestamp::msecsDiff(const Timestamp &ts) const {
|
||||
|
@ -127,7 +137,7 @@ int Timestamp::msecsDiff(const Timestamp &ts) const {
|
|||
}
|
||||
|
||||
uint32 Timestamp::msecs() const {
|
||||
return _msecs + _numberOfFrames * 1000L / _framerate;
|
||||
return _secs * 1000 + _numberOfFrames / (_framerate / 1000);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -39,24 +39,36 @@ namespace Audio {
|
|||
class Timestamp {
|
||||
protected:
|
||||
/**
|
||||
* The millisecond part of this timestamp.
|
||||
* The total time represented by this timestamp is
|
||||
* The seconds part of this timestamp.
|
||||
* The total time in seconds represented by this timestamp can be
|
||||
* computed as follows:
|
||||
* _msecs + 1000 * _numberOfFrames / _framerate
|
||||
* _secs + (double)_numberOfFrames / _framerate
|
||||
*/
|
||||
uint _msecs;
|
||||
uint _secs;
|
||||
|
||||
/**
|
||||
* The number of frames which together with _msecs encodes
|
||||
* The number of frames which together with _secs encodes
|
||||
* the timestamp. The total number of frames represented
|
||||
* by this timestamp is computed as follows:
|
||||
* _numberOfFrames + _msecs * _framerate / 1000
|
||||
* _numberOfFrames + _secs * _framerate
|
||||
*/
|
||||
int _numberOfFrames;
|
||||
|
||||
/** The framerate, i.e. the number of frames per second. */
|
||||
/**
|
||||
* The internal framerate, i.e. the number of frames per second.
|
||||
* This is computed as the least common multiple of the framerate
|
||||
* specified by the client code, and 1000.
|
||||
* This way, we ensure that we can store both frames and
|
||||
* milliseconds without any rounding losses.
|
||||
*/
|
||||
int _framerate;
|
||||
|
||||
/**
|
||||
* Factor by which the original framerate specified by the client
|
||||
* code was multipled to obtain the internal _framerate value.
|
||||
*/
|
||||
int _framerateFactor;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Set up a timestamp with a given time and framerate.
|
||||
|
@ -72,7 +84,13 @@ public:
|
|||
*/
|
||||
Timestamp convertToFramerate(int newFramerate) const;
|
||||
|
||||
/**
|
||||
* Check whether to timestamps describe the exact same moment
|
||||
* in time. This means that two timestamps can compare
|
||||
* as equal even if they use different framerates.
|
||||
*/
|
||||
bool operator==(const Timestamp &ts) const;
|
||||
|
||||
bool operator!=(const Timestamp &ts) const;
|
||||
// bool operator<(const Timestamp &ts) const;
|
||||
// bool operator<=(const Timestamp &ts) const;
|
||||
|
@ -82,14 +100,16 @@ public:
|
|||
/**
|
||||
* Returns a new timestamp, which corresponds to the time encoded
|
||||
* by this timestamp with the given number of frames added.
|
||||
* @param frames number of frames to add
|
||||
*/
|
||||
Timestamp addFrames(int frames) const;
|
||||
|
||||
/**
|
||||
* Returns a new timestamp, which corresponds to the time encoded
|
||||
* by this timestamp with the given number of milliseconds added.
|
||||
* @param msecs number of milliseconds to add
|
||||
*/
|
||||
Timestamp addMsecs(int ms) const;
|
||||
Timestamp addMsecs(int msecs) const;
|
||||
|
||||
/**
|
||||
* Computes the number of frames between this timestamp and ts.
|
||||
|
@ -108,7 +128,7 @@ public:
|
|||
uint32 msecs() const;
|
||||
|
||||
/** Return the framerate used by this timestamp. */
|
||||
int getFramerate() const { return _framerate; }
|
||||
int getFramerate() const { return _framerate / _framerateFactor; }
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -87,8 +87,32 @@ class TimestampTestSuite : public CxxTest::TestSuite
|
|||
|
||||
void test_equals() {
|
||||
const Audio::Timestamp a = Audio::Timestamp(500, 1000);
|
||||
const Audio::Timestamp b = Audio::Timestamp(0, 1000).addFrames(500);
|
||||
Audio::Timestamp b = Audio::Timestamp(0, 1000);
|
||||
Audio::Timestamp c = Audio::Timestamp(0, 100);
|
||||
|
||||
TS_ASSERT(a != b);
|
||||
TS_ASSERT(a != c);
|
||||
TS_ASSERT(b == c);
|
||||
|
||||
b = b.addFrames(500);
|
||||
c = c.addFrames(50);
|
||||
|
||||
TS_ASSERT(a == b);
|
||||
TS_ASSERT(a == c);
|
||||
TS_ASSERT(b == c);
|
||||
}
|
||||
|
||||
|
||||
void test_framerate() {
|
||||
const Audio::Timestamp a = Audio::Timestamp(500, 1000);
|
||||
const Audio::Timestamp b = Audio::Timestamp(500, 67);
|
||||
const Audio::Timestamp c = Audio::Timestamp(500, 100);
|
||||
const Audio::Timestamp d = Audio::Timestamp(500, 44100);
|
||||
|
||||
|
||||
TS_ASSERT_EQUALS(a.getFramerate(), 1000);
|
||||
TS_ASSERT_EQUALS(b.getFramerate(), 67);
|
||||
TS_ASSERT_EQUALS(c.getFramerate(), 100);
|
||||
TS_ASSERT_EQUALS(d.getFramerate(), 44100);
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue