* Instead of using some bogus latency for the VideoProducer,

compute the latency from the buffer count. It should be
   the duration of in-flight buffers (i.e. number of buffers
   minus the one currently showing).
 * Compute the wakeUp time based on the buffer latency used
   above.
 * Make the "wasCached" mechanism work again, i.e. don't send
   the buffer to the consumer if the contents did not change.
   This removes the need to cache frames in the ProxyVideoSupplier
   and thus one more memcpy() (5ms on my Q6600 for full-HD content).
 * Remove the weird forceSendingBuffer override and the setting
   of the header starttime to 0. Now seeking clips works also
   when the playback is paused.

All in all, playback is more efficient now, and the chances of
dropping frames are much less. Something is still fishy, though,
since VLC, even though seemingly using slightly more CPU, drops
frames more seldomly than MediaPlayer. Audio/Video sync seems to
be better, though, since the VideoProducer is using a more accurate
latency now.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@38662 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Stephan Aßmus 2010-09-15 18:15:20 +00:00
parent 6eac6bdea9
commit f7eb8be930
4 changed files with 93 additions and 134 deletions

View File

@ -51,6 +51,10 @@ VideoProducer::VideoProducer(BMediaAddOn* addon, const char* name,
fUsedBufferGroup(NULL), fUsedBufferGroup(NULL),
fThread(-1), fThread(-1),
fFrameSync(-1), fFrameSync(-1),
fFrame(0),
fFrameBase(0),
fPerformanceTimeBase(0),
fBufferLatency(0),
fRunning(false), fRunning(false),
fConnected(false), fConnected(false),
fEnabled(false), fEnabled(false),
@ -422,9 +426,6 @@ VideoProducer::PrepareToConnect(const media_source& source,
} }
#define NODE_LATENCY 20000
void void
VideoProducer::Connect(status_t error, const media_source& source, VideoProducer::Connect(status_t error, const media_source& source,
const media_destination& destination, const media_format& format, const media_destination& destination, const media_format& format,
@ -454,29 +455,25 @@ VideoProducer::Connect(status_t error, const media_source& source,
fOutput.destination = destination; fOutput.destination = destination;
strcpy(_name, fOutput.name); strcpy(_name, fOutput.name);
fConnectedFormat = format.u.raw_video;
fBufferDuration = 20000;
if (fOutput.format.u.raw_video.field_rate != 0.0f) { if (fConnectedFormat.field_rate != 0.0f) {
fPerformanceTimeBase = fPerformanceTimeBase fPerformanceTimeBase = fPerformanceTimeBase
+ (bigtime_t)((fFrame - fFrameBase) + (bigtime_t)((fFrame - fFrameBase)
* 1000000 / fOutput.format.u.raw_video.field_rate); * 1000000LL / fConnectedFormat.field_rate);
fFrameBase = fFrame; fFrameBase = fFrame;
fBufferDuration = 1000000LL / fConnectedFormat.field_rate;
} }
fConnectedFormat = format.u.raw_video;
if (fConnectedFormat.display.bytes_per_row == 0) { if (fConnectedFormat.display.bytes_per_row == 0) {
ERROR("Connect() - connected format still has BPR wildcard!\n"); ERROR("Connect() - connected format still has BPR wildcard!\n");
fConnectedFormat.display.bytes_per_row fConnectedFormat.display.bytes_per_row
= 4 * fConnectedFormat.display.line_width; = 4 * fConnectedFormat.display.line_width;
} }
// get the latency
bigtime_t latency = 0;
media_node_id tsID = 0;
FindLatencyFor(fOutput.destination, &latency, &tsID);
SetEventLatency(latency + NODE_LATENCY);
// Create the buffer group // Create the buffer group
if (!fUsedBufferGroup) { if (fUsedBufferGroup == NULL) {
fBufferGroup = new BBufferGroup(fConnectedFormat.display.bytes_per_row fBufferGroup = new BBufferGroup(fConnectedFormat.display.bytes_per_row
* fConnectedFormat.display.line_count, BUFFER_COUNT); * fConnectedFormat.display.line_count, BUFFER_COUNT);
status_t err = fBufferGroup->InitCheck(); status_t err = fBufferGroup->InitCheck();
@ -489,6 +486,20 @@ VideoProducer::Connect(status_t error, const media_source& source,
fUsedBufferGroup = fBufferGroup; fUsedBufferGroup = fBufferGroup;
} }
// get the latency
fBufferLatency = (BUFFER_COUNT - 1) * fBufferDuration;
int32 bufferCount;
if (fUsedBufferGroup->CountBuffers(&bufferCount) == B_OK) {
// recompute the latency
fBufferLatency = (bufferCount - 1) * fBufferDuration;
}
bigtime_t latency = 0;
media_node_id tsID = 0;
FindLatencyFor(fOutput.destination, &latency, &tsID);
SetEventLatency(latency + fBufferLatency);
fConnected = true; fConnected = true;
fEnabled = true; fEnabled = true;
@ -665,8 +676,6 @@ int32
VideoProducer::_FrameGeneratorThread() VideoProducer::_FrameGeneratorThread()
{ {
bool forceSendingBuffer = true; bool forceSendingBuffer = true;
bigtime_t lastFrameSentAt = 0;
int64 lastPlaylistFrame = 0;
int32 droppedFrames = 0; int32 droppedFrames = 0;
const int32 kMaxDroppedFrames = 15; const int32 kMaxDroppedFrames = 15;
bool running = true; bool running = true;
@ -680,7 +689,6 @@ VideoProducer::_FrameGeneratorThread()
bigtime_t nextPerformanceTime = 0; bigtime_t nextPerformanceTime = 0;
bigtime_t waitUntil = 0; bigtime_t waitUntil = 0;
bigtime_t nextWaitUntil = 0; bigtime_t nextWaitUntil = 0;
bigtime_t maxRenderTime = 0;
int32 playingDirection = 0; int32 playingDirection = 0;
int32 playingMode = 0; int32 playingMode = 0;
int64 playlistFrame = 0; int64 playlistFrame = 0;
@ -691,14 +699,11 @@ VideoProducer::_FrameGeneratorThread()
// get the times for the current and the next frame // get the times for the current and the next frame
performanceTime = fManager->TimeForFrame(fFrame); performanceTime = fManager->TimeForFrame(fFrame);
nextPerformanceTime = fManager->TimeForFrame(fFrame + 1); nextPerformanceTime = fManager->TimeForFrame(fFrame + 1);
maxRenderTime = min_c(bigtime_t(40000),
max_c(fSupplier->ProcessingLatency(), maxRenderTime));
playingMode = fManager->PlayModeAtFrame(fFrame); playingMode = fManager->PlayModeAtFrame(fFrame);
waitUntil = TimeSource()->RealTimeFor(fPerformanceTimeBase waitUntil = TimeSource()->RealTimeFor(fPerformanceTimeBase
+ performanceTime, 0) - maxRenderTime; + performanceTime, fBufferLatency);
nextWaitUntil = TimeSource()->RealTimeFor(fPerformanceTimeBase nextWaitUntil = TimeSource()->RealTimeFor(fPerformanceTimeBase
+ nextPerformanceTime, 0) - maxRenderTime; + nextPerformanceTime, fBufferLatency);
// get playing direction and playlist frame for the current // get playing direction and playlist frame for the current
// frame // frame
bool newPlayingState; bool newPlayingState;
@ -707,10 +712,6 @@ VideoProducer::_FrameGeneratorThread()
TRACE("_FrameGeneratorThread: performance time: %Ld, " TRACE("_FrameGeneratorThread: performance time: %Ld, "
"playlist frame: %lld\n", performanceTime, playlistFrame); "playlist frame: %lld\n", performanceTime, playlistFrame);
forceSendingBuffer |= newPlayingState; forceSendingBuffer |= newPlayingState;
if (lastPlaylistFrame != playlistFrame) {
forceSendingBuffer = true;
lastPlaylistFrame = playlistFrame;
}
fManager->SetCurrentVideoTime(nextPerformanceTime); fManager->SetCurrentVideoTime(nextPerformanceTime);
fManager->Unlock(); fManager->Unlock();
break; break;
@ -748,7 +749,7 @@ VideoProducer::_FrameGeneratorThread()
if (ignoreEvent || !fRunning || !fEnabled) { if (ignoreEvent || !fRunning || !fEnabled) {
TRACE("_FrameGeneratorThread: ignore event\n"); TRACE("_FrameGeneratorThread: ignore event\n");
// nothing to do // nothing to do
} else if (nextWaitUntil < system_time() } else if (nextWaitUntil < system_time() - fBufferLatency
&& droppedFrames < kMaxDroppedFrames) { && droppedFrames < kMaxDroppedFrames) {
// Drop frame if it's at least a frame late. // Drop frame if it's at least a frame late.
printf("VideoProducer: dropped frame (%Ld)\n", fFrame); printf("VideoProducer: dropped frame (%Ld)\n", fFrame);
@ -765,78 +766,72 @@ VideoProducer::_FrameGeneratorThread()
TRACE("_FrameGeneratorThread: produce frame\n"); TRACE("_FrameGeneratorThread: produce frame\n");
BAutolock _(fLock); BAutolock _(fLock);
// Fetch a buffer from the buffer group // Fetch a buffer from the buffer group
fUsedBufferGroup->WaitForBuffers();
BBuffer* buffer = fUsedBufferGroup->RequestBuffer( BBuffer* buffer = fUsedBufferGroup->RequestBuffer(
fConnectedFormat.display.bytes_per_row fConnectedFormat.display.bytes_per_row
* fConnectedFormat.display.line_count, 0LL); * fConnectedFormat.display.line_count, 0LL);
if (buffer != NULL) { if (buffer == NULL) {
// Fill out the details about this buffer. // Wait until a buffer becomes available again
media_header* h = buffer->Header();
h->type = B_MEDIA_RAW_VIDEO;
h->time_source = TimeSource()->ID();
h->size_used = fConnectedFormat.display.bytes_per_row
* fConnectedFormat.display.line_count;
// For a buffer originating from a device, you might
// want to calculate this based on the
// PerformanceTimeFor the time your buffer arrived at
// the hardware (plus any applicable adjustments).
h->start_time = fPerformanceTimeBase + performanceTime;
// TODO: Fix the runmode stuff! Setting the consumer to B_OFFLINE does
// not do the trick. I made the VideoConsumer check the performance
// time of the buffer and if it is 0, it plays it regardless.
if (playingMode < 0 || droppedFrames >= kMaxDroppedFrames) {
h->start_time = 0;
}
h->file_pos = 0;
h->orig_size = 0;
h->data_offset = 0;
h->u.raw_video.field_gamma = 1.0;
h->u.raw_video.field_sequence = fFrame;
h->u.raw_video.field_number = 0;
h->u.raw_video.pulldown_number = 0;
h->u.raw_video.first_active_line = 1;
h->u.raw_video.line_count
= fConnectedFormat.display.line_count;
// Fill in a frame
TRACE("_FrameGeneratorThread: frame: %Ld, "
"playlistFrame: %Ld\n", fFrame, playlistFrame);
bool forceOrWasCached = forceSendingBuffer;
err = fSupplier->FillBuffer(playlistFrame,
buffer->Data(), fConnectedFormat,
forceOrWasCached);
// clean the buffer if something went wrong
if (err != B_OK) {
// TODO: should use "back value" according
// to color space!
memset(buffer->Data(), 0, h->size_used);
err = B_OK;
}
// Send the buffer on down to the consumer
if (SendBuffer(buffer, fOutput.source,
fOutput.destination) < B_OK) {
ERROR("_FrameGeneratorThread: Error "
"sending buffer\n");
// If there is a problem sending the buffer,
// or if we don't send the buffer because its
// contents are the same as the last one,
// return it to its buffer group.
buffer->Recycle();
// we tell the supplier to delete
// its caches if there was a problem sending
// the buffer
fSupplier->DeleteCaches();
}
// Only if everything went fine we clear the flag
// that forces us to send a buffer even if not
// playing.
if (err == B_OK) {
forceSendingBuffer = false;
lastFrameSentAt = performanceTime;
}
} else {
TRACE("_FrameGeneratorThread: no buffer!\n"); TRACE("_FrameGeneratorThread: no buffer!\n");
// ERROR("_FrameGeneratorThread: no buffer!\n"); // ERROR("_FrameGeneratorThread: no buffer!\n");
break;
} }
// Fill out the details about this buffer.
media_header* h = buffer->Header();
h->type = B_MEDIA_RAW_VIDEO;
h->time_source = TimeSource()->ID();
h->size_used = fConnectedFormat.display.bytes_per_row
* fConnectedFormat.display.line_count;
// For a buffer originating from a device, you might
// want to calculate this based on the
// PerformanceTimeFor the time your buffer arrived at
// the hardware (plus any applicable adjustments).
h->start_time = fPerformanceTimeBase + performanceTime;
h->file_pos = 0;
h->orig_size = 0;
h->data_offset = 0;
h->u.raw_video.field_gamma = 1.0;
h->u.raw_video.field_sequence = fFrame;
h->u.raw_video.field_number = 0;
h->u.raw_video.pulldown_number = 0;
h->u.raw_video.first_active_line = 1;
h->u.raw_video.line_count
= fConnectedFormat.display.line_count;
// Fill in a frame
TRACE("_FrameGeneratorThread: frame: %Ld, "
"playlistFrame: %Ld\n", fFrame, playlistFrame);
bool wasCached = false;
err = fSupplier->FillBuffer(playlistFrame,
buffer->Data(), fConnectedFormat, wasCached);
// clean the buffer if something went wrong
if (err != B_OK) {
// TODO: should use "back value" according
// to color space!
memset(buffer->Data(), 0, h->size_used);
err = B_OK;
}
// Send the buffer on down to the consumer
if (wasCached || (err = SendBuffer(buffer, fOutput.source,
fOutput.destination) != B_OK)) {
// If there is a problem sending the buffer,
// or if we don't send the buffer because its
// contents are the same as the last one,
// return it to its buffer group.
buffer->Recycle();
// we tell the supplier to delete
// its caches if there was a problem sending
// the buffer
if (err != B_OK) {
ERROR("_FrameGeneratorThread: Error "
"sending buffer\n");
fSupplier->DeleteCaches();
}
}
// Only if everything went fine we clear the flag
// that forces us to send a buffer even if not
// playing.
if (err == B_OK)
forceSendingBuffer = false;
// next frame // next frame
fFrame++; fFrame++;
droppedFrames = 0; droppedFrames = 0;

View File

@ -132,6 +132,8 @@ protected:
int64 fFrame; int64 fFrame;
int64 fFrameBase; int64 fFrameBase;
bigtime_t fPerformanceTimeBase; bigtime_t fPerformanceTimeBase;
bigtime_t fBufferDuration;
bigtime_t fBufferLatency;
media_output fOutput; media_output fOutput;
media_raw_video_format fConnectedFormat; media_raw_video_format fConnectedFormat;
bool fRunning; bool fRunning;

View File

@ -18,18 +18,13 @@
ProxyVideoSupplier::ProxyVideoSupplier() ProxyVideoSupplier::ProxyVideoSupplier()
: :
fSupplierLock("video supplier lock"), fSupplierLock("video supplier lock"),
fSupplier(NULL), fSupplier(NULL)
fCachedFrame(NULL),
fCachedFrameSize(0),
fCachedFrameValid(false),
fUseFrameCaching(true)
{ {
} }
ProxyVideoSupplier::~ProxyVideoSupplier() ProxyVideoSupplier::~ProxyVideoSupplier()
{ {
free(fCachedFrame);
} }
@ -44,35 +39,12 @@ ProxyVideoSupplier::FillBuffer(int64 startFrame, void* buffer,
if (fSupplier == NULL) if (fSupplier == NULL)
return B_NO_INIT; return B_NO_INIT;
if (fUseFrameCaching) {
size_t bufferSize = format.display.bytes_per_row
* format.display.line_count;
if (fCachedFrame == NULL || fCachedFrameSize != bufferSize) {
// realloc cached frame
fCachedFrameValid = false;
void* cachedFrame = realloc(fCachedFrame, bufferSize);
if (cachedFrame != NULL) {
fCachedFrame = cachedFrame,
fCachedFrameSize = bufferSize;
} else
fUseFrameCaching = false;
fCachedFrameValid = false;
}
}
if (fSupplier->CurrentFrame() == startFrame + 1) { if (fSupplier->CurrentFrame() == startFrame + 1) {
if (fCachedFrameValid) { wasCached = true;
memcpy(buffer, fCachedFrame, fCachedFrameSize); return B_OK;
wasCached = true;
return B_OK;
}
// TODO: The problem here is hidden in PlaybackManager::_PushState()
// not computing the correct current_frame for the new PlayingState.
printf("ProxyVideoSupplier::FillBuffer(%lld) - TODO: Avoid "
"asking for the same frame twice (%lld)!\n", startFrame,
fSupplier->CurrentFrame());
} }
wasCached = false;
status_t ret = B_OK; status_t ret = B_OK;
bigtime_t performanceTime = 0; bigtime_t performanceTime = 0;
if (fSupplier->CurrentFrame() != startFrame) { if (fSupplier->CurrentFrame() != startFrame) {
@ -97,11 +69,6 @@ ProxyVideoSupplier::FillBuffer(int64 startFrame, void* buffer,
ret = fSupplier->ReadFrame(buffer, &performanceTime, format, wasCached); ret = fSupplier->ReadFrame(buffer, &performanceTime, format, wasCached);
if (fUseFrameCaching && ret == B_OK) {
memcpy(fCachedFrame, buffer, fCachedFrameSize);
fCachedFrameValid = true;
}
fProcessingLatency = system_time() - now; fProcessingLatency = system_time() - now;
return ret; return ret;

View File

@ -30,11 +30,6 @@ private:
BLocker fSupplierLock; BLocker fSupplierLock;
VideoTrackSupplier* fSupplier; VideoTrackSupplier* fSupplier;
void* fCachedFrame;
size_t fCachedFrameSize;
bool fCachedFrameValid;
bool fUseFrameCaching;
}; };
#endif // PROXY_VIDEO_SUPPLIER_H #endif // PROXY_VIDEO_SUPPLIER_H