Skip to content

Commit

Permalink
RingBuffer: Make the buffer size dynamic.
Browse files Browse the repository at this point in the history
- convert the buffer size to a member variable that can be adjusted mid-
- add buffer sizing factors that increase the size of the buffer for
networked playback, matroska streams and streams with an unknown video
bitrate.
- set the matroska and unknown bitrate flags when scanning the streams
in AvFormatDecoder.

RingBuffer behaviour is unchanged for playback of local files and for
RingBuffers operating on backends; the default 4Mb is ample for fast,
local disk access.

For networked playback, the 4Mb is doubled and doubled again for both
matroska files and files of an unknown bitrate - and hence the maximum
networked buffer size (in the frontend) is 32Mb. This is sufficient to
playback the now infamous galactica clip over an 18Mps wifi connection
with minimal pauses (and there are a couple of additional tweaks that
will remove those pauses altogether).

The specific triggers and multipliers used are a little contrived to fix
the worst case scenarios we have now (in many cases the buffer size will
be much larger than needed - improvements are more than welcome) but the
increase in buffer size for networked playback is probably long overdue.
  • Loading branch information
Mark Kendall committed Jun 5, 2011
1 parent 491cc4a commit bff8594
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 23 deletions.
11 changes: 11 additions & 0 deletions mythtv/libs/libmythtv/avformatdecoder.cpp
Expand Up @@ -1693,6 +1693,7 @@ void AvFormatDecoder::ScanDSMCCStreams(void)

int AvFormatDecoder::ScanStreams(bool novideo)
{
bool unknownbitrate = false;
int scanerror = 0;
bitrate = 0;
fps = 0;
Expand Down Expand Up @@ -1745,6 +1746,9 @@ int AvFormatDecoder::ScanStreams(bool novideo)

codec_is_mpeg = CODEC_IS_FFMPEG_MPEG(enc->codec_id);

if (enc->bit_rate == 0)
unknownbitrate = true;

// HACK -- begin
// ffmpeg is unable to compute H.264 bitrates in mpegts?
if (CODEC_IS_H264(enc->codec_id) && enc->bit_rate == 0)
Expand Down Expand Up @@ -2082,6 +2086,13 @@ int AvFormatDecoder::ScanStreams(bool novideo)
ringBuffer->UpdateRawBitrate(bitrate);
}

// update RingBuffer buffer size
if (ringBuffer)
{
ringBuffer->SetBufferSizeFactors(unknownbitrate,
QString(ic->iformat->name).contains("matroska"));
}

PostProcessTracks();

// Select a new track at the next opportunity.
Expand Down
6 changes: 3 additions & 3 deletions mythtv/libs/libmythtv/fileringbuffer.cpp
Expand Up @@ -561,7 +561,7 @@ long long FileRingBuffer::Seek(long long pos, int whence, bool has_lock)
{
int min_safety = max(fill_min, readblocksize);
int free = ((rbwpos >= rbrpos) ?
rbrpos + kBufferSize : rbrpos) - rbwpos;
rbrpos + bufferSize : rbrpos) - rbwpos;
int internal_backbuf =
(rbwpos >= rbrpos) ? rbrpos : rbrpos - rbwpos;
internal_backbuf = min(internal_backbuf, free - min_safety);
Expand All @@ -572,7 +572,7 @@ long long FileRingBuffer::Seek(long long pos, int whence, bool has_lock)
if (internal_backbuf >= sba)
{
rbrpos = (rbrpos>=sba) ? rbrpos - sba :
kBufferSize + rbrpos - sba;
bufferSize + rbrpos - sba;
used_opt = true;
VERBOSE(VB_FILE, LOC +
QString("Seek(): OPT1 rbrpos: %1 rbwpos: %2"
Expand All @@ -583,7 +583,7 @@ long long FileRingBuffer::Seek(long long pos, int whence, bool has_lock)
}
else if ((new_pos >= readpos) && (new_pos <= internalreadpos))
{
rbrpos = (rbrpos + (new_pos - readpos)) % kBufferSize;
rbrpos = (rbrpos + (new_pos - readpos)) % bufferSize;
used_opt = true;
VERBOSE(VB_FILE, LOC +
QString("Seek(): OPT2 rbrpos: %1 sba: %2")
Expand Down
99 changes: 83 additions & 16 deletions mythtv/libs/libmythtv/ringbuffer.cpp
Expand Up @@ -28,7 +28,11 @@
#include "util.h"

// about one second at 35mbit
const uint RingBuffer::kBufferSize = 4 * 1024 * 1024;
#define BUFFER_SIZE_MINIMUM 4 * 1024 * 1024
#define BUFFER_FACTOR_NETWORK 2
#define BUFFER_FACTOR_BITRATE 2
#define BUFFER_FACTOR_MATROSKA 2

const int RingBuffer::kDefaultOpenTimeout = 2000; // ms
const int RingBuffer::kLiveTVOpenTimeout = 10000;

Expand Down Expand Up @@ -175,6 +179,8 @@ RingBuffer::RingBuffer(void) :
filename(), subtitlefilename(),
tfw(NULL), fd2(-1),
writemode(false), remotefile(NULL),
bufferSize(BUFFER_SIZE_MINIMUM),
fileismatroska(false), unknownbitrate(false),
startreadahead(false), readAheadBuffer(NULL),
readaheadrunning(false), reallyrunning(false),
request_pause(false), paused(false),
Expand Down Expand Up @@ -304,6 +310,20 @@ void RingBuffer::UpdatePlaySpeed(float play_speed)
rwlock.unlock();
}

/** \fn RingBuffer::SetBufferSizeFactors(bool, bool)
* \brief Tells RingBuffer that the raw bitrate may be innacurate and the
* underlying container is matroska, both of which may require a larger
* buffer size.
*/
void RingBuffer::SetBufferSizeFactors(bool estbitrate, bool matroska)
{
rwlock.lockForWrite();
unknownbitrate = estbitrate;
fileismatroska = matroska;
rwlock.unlock();
CreateReadAheadBuffer();
}

/** \fn RingBuffer::CalcReadAheadThresh(void)
* \brief Calculates fill_min, fill_threshold, and readblocksize
* from the estimated effective bitrate of the stream.
Expand All @@ -319,7 +339,7 @@ void RingBuffer::CalcReadAheadThresh(void)
readblocksize = max(readblocksize, CHUNK);

// loop without sleeping if the buffered data is less than this
fill_threshold = 3 * kBufferSize / 4;
fill_threshold = 3 * bufferSize / 8;

const uint KB32 = 32*1024;
const uint KB64 = 64*1024;
Expand Down Expand Up @@ -384,7 +404,7 @@ int RingBuffer::ReadBufFree(void) const
{
rbrlock.lockForRead();
rbwlock.lockForRead();
int ret = ((rbwpos >= rbrpos) ? rbrpos + kBufferSize : rbrpos) - rbwpos - 1;
int ret = ((rbwpos >= rbrpos) ? rbrpos + bufferSize : rbrpos) - rbwpos - 1;
rbwlock.unlock();
rbrlock.unlock();
return ret;
Expand All @@ -396,7 +416,7 @@ int RingBuffer::ReadBufAvail(void) const
{
rbrlock.lockForRead();
rbwlock.lockForRead();
int ret = (rbwpos >= rbrpos) ? rbwpos - rbrpos : kBufferSize - rbrpos + rbwpos;
int ret = (rbwpos >= rbrpos) ? rbwpos - rbrpos : bufferSize - rbrpos + rbwpos;
rbwlock.unlock();
rbrlock.unlock();
return ret;
Expand Down Expand Up @@ -623,6 +643,53 @@ bool RingBuffer::PauseAndWait(void)
return request_pause || paused;
}

void RingBuffer::CreateReadAheadBuffer(void)
{
rwlock.lockForWrite();
poslock.lockForWrite();

uint oldsize = bufferSize;
uint newsize = BUFFER_SIZE_MINIMUM;
if (remotefile)
{
newsize *= BUFFER_FACTOR_NETWORK;
if (fileismatroska)
newsize *= BUFFER_FACTOR_MATROSKA;
if (unknownbitrate)
newsize *= BUFFER_FACTOR_BITRATE;
}

// N.B. Don't try and make it smaller - bad things happen...
if (readAheadBuffer && oldsize >= newsize)
{
poslock.unlock();
rwlock.unlock();
return;
}

bufferSize = newsize;
if (readAheadBuffer)
{
char* newbuffer = new char[bufferSize + 1024];
memcpy(newbuffer, readAheadBuffer + rbwpos, oldsize - rbwpos);
memcpy(newbuffer + (oldsize - rbwpos), readAheadBuffer, rbwpos);
delete [] readAheadBuffer;
readAheadBuffer = newbuffer;
rbrpos = rbrpos + oldsize - rbwpos;
rbwpos = oldsize;
}
else
{
readAheadBuffer = new char[bufferSize + 1024];
}
CalcReadAheadThresh();
poslock.unlock();
rwlock.unlock();

VERBOSE(VB_FILE, LOC + QString("Created readAheadBuffer: %1Mb")
.arg(newsize >> 20));
}

void RingBuffer::run(void)
{
// These variables are used to adjust the read block size
Expand All @@ -632,10 +699,10 @@ void RingBuffer::run(void)

gettimeofday(&lastread, NULL); // this is just to keep gcc happy

CreateReadAheadBuffer();
rwlock.lockForWrite();
poslock.lockForWrite();
request_pause = false;
readAheadBuffer = new char[kBufferSize + 1024];
ResetReadAhead(0);
readaheadrunning = true;
reallyrunning = true;
Expand Down Expand Up @@ -697,7 +764,7 @@ void RingBuffer::run(void)
(now.tv_usec - lastread.tv_usec) / 1000;
readtimeavg = (readtimeavg * 9 + readinterval) / 10;

if (readtimeavg < 150 && (uint)readblocksize < (kBufferSize>>2))
if (readtimeavg < 150 && (uint)readblocksize < (bufferSize>>2))
{
int old_block_size = readblocksize;
readblocksize = 3 * readblocksize / 2;
Expand Down Expand Up @@ -726,9 +793,9 @@ void RingBuffer::run(void)
lastread = now;

rbwlock.lockForRead();
if (rbwpos + totfree > kBufferSize)
if (rbwpos + totfree > bufferSize)
{
totfree = kBufferSize - rbwpos;
totfree = bufferSize - rbwpos;
VERBOSE(VB_FILE|VB_EXTRA, LOC +
"Shrinking read, near end of buffer");
}
Expand Down Expand Up @@ -769,15 +836,15 @@ void RingBuffer::run(void)
poslock.lockForWrite();
rbwlock.lockForWrite();
internalreadpos += read_return;
rbwpos = (rbwpos + read_return) % kBufferSize;
rbwpos = (rbwpos + read_return) % bufferSize;
VERBOSE(VB_FILE|VB_EXTRA,
LOC + QString("rbwpos += %1K requested %2K in read")
.arg(read_return/1024,3).arg(totfree/1024,3));
rbwlock.unlock();
poslock.unlock();
}

int used = kBufferSize - ReadBufFree();
int used = bufferSize - ReadBufFree();

if ((0 == read_return) || (numfailures > 5) ||
(readsallowed != (used >= fill_min || ateof ||
Expand Down Expand Up @@ -816,7 +883,7 @@ void RingBuffer::run(void)

rwlock.unlock();
rwlock.lockForRead();
used = kBufferSize - ReadBufFree();
used = bufferSize - ReadBufFree();
}

VERBOSE(VB_FILE|VB_EXTRA, LOC + "@ end of read ahead loop");
Expand Down Expand Up @@ -1148,9 +1215,9 @@ int RingBuffer::ReadPriv(void *buf, int count, bool peek)

VERBOSE(VB_FILE|VB_EXTRA, LOC + loc_desc + " -- copying data");

if (rbrpos + count > (int) kBufferSize)
if (rbrpos + count > (int) bufferSize)
{
int firstsize = kBufferSize - rbrpos;
int firstsize = bufferSize - rbrpos;
int secondsize = count - firstsize;

memcpy(buf, readAheadBuffer + rbrpos, firstsize);
Expand All @@ -1165,7 +1232,7 @@ int RingBuffer::ReadPriv(void *buf, int count, bool peek)

if (!peek)
{
rbrpos = (rbrpos + count) % kBufferSize;
rbrpos = (rbrpos + count) % bufferSize;
generalWait.wakeAll();
}
rbrlock.unlock();
Expand Down Expand Up @@ -1240,8 +1307,8 @@ QString RingBuffer::GetStorageRate(void)

QString RingBuffer::GetAvailableBuffer(void)
{
int avail = (rbwpos >= rbrpos) ? rbwpos - rbrpos : kBufferSize - rbrpos + rbwpos;
return QString("%1%").arg((int)(((float)avail / (float)kBufferSize) * 100.0));
int avail = (rbwpos >= rbrpos) ? rbwpos - rbrpos : bufferSize - rbrpos + rbwpos;
return QString("%1%").arg((int)(((float)avail / (float)bufferSize) * 100.0));
}

uint64_t RingBuffer::UpdateDecoderRate(uint64_t latest)
Expand Down
9 changes: 5 additions & 4 deletions mythtv/libs/libmythtv/ringbuffer.h
Expand Up @@ -47,6 +47,7 @@ class MTV_PUBLIC RingBuffer : protected QThread
void UpdateRawBitrate(uint rawbitrate);
void UpdatePlaySpeed(float playspeed);
void EnableBitrateMonitor(bool enable) { bitrateMonitorEnabled = enable; }
void SetBufferSizeFactors(bool estbitrate, bool matroska);

// Gets
QString GetSafeFilename(void) { return safefilename; }
Expand Down Expand Up @@ -141,6 +142,7 @@ class MTV_PUBLIC RingBuffer : protected QThread
RingBuffer();

void run(void); // QThread
void CreateReadAheadBuffer(void);
void CalcReadAheadThresh(void);
bool PauseAndWait(void);
virtual int safe_read(void *data, uint sz) = 0;
Expand Down Expand Up @@ -187,6 +189,9 @@ class MTV_PUBLIC RingBuffer : protected QThread

RemoteFile *remotefile; // protected by rwlock

uint bufferSize; // protected by rwlock
bool fileismatroska; // protected by rwlock
bool unknownbitrate; // protected by rwlock
bool startreadahead; // protected by rwlock
char *readAheadBuffer; // protected by rwlock
bool readaheadrunning; // protected by rwlock
Expand Down Expand Up @@ -233,10 +238,6 @@ class MTV_PUBLIC RingBuffer : protected QThread
static QMutex subExtLock;
static QStringList subExt;
static QStringList subExtNoCheck;

// constants
public:
static const uint kBufferSize;
};

#endif // _RINGBUFFER_H_

0 comments on commit bff8594

Please sign in to comment.