Author: stippi Date: 2010-09-15 20:15:20 +0200 (Wed, 15 Sep 2010) New Revision: 38662 Changeset: http://dev.haiku-os.org/changeset/38662 Modified: haiku/trunk/src/apps/mediaplayer/media_node_framework/video/VideoProducer.cpp haiku/trunk/src/apps/mediaplayer/media_node_framework/video/VideoProducer.h haiku/trunk/src/apps/mediaplayer/supplier/ProxyVideoSupplier.cpp haiku/trunk/src/apps/mediaplayer/supplier/ProxyVideoSupplier.h Log: * 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. Modified: haiku/trunk/src/apps/mediaplayer/media_node_framework/video/VideoProducer.cpp =================================================================== --- haiku/trunk/src/apps/mediaplayer/media_node_framework/video/VideoProducer.cpp 2010-09-15 17:49:47 UTC (rev 38661) +++ haiku/trunk/src/apps/mediaplayer/media_node_framework/video/VideoProducer.cpp 2010-09-15 18:15:20 UTC (rev 38662) @@ -51,6 +51,10 @@ fUsedBufferGroup(NULL), fThread(-1), fFrameSync(-1), + fFrame(0), + fFrameBase(0), + fPerformanceTimeBase(0), + fBufferLatency(0), fRunning(false), fConnected(false), fEnabled(false), @@ -422,9 +426,6 @@ } -#define NODE_LATENCY 20000 - - void VideoProducer::Connect(status_t error, const media_source& source, const media_destination& destination, const media_format& format, @@ -454,29 +455,25 @@ fOutput.destination = destination; 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 + (bigtime_t)((fFrame - fFrameBase) - * 1000000 / fOutput.format.u.raw_video.field_rate); + * 1000000LL / fConnectedFormat.field_rate); fFrameBase = fFrame; + fBufferDuration = 1000000LL / fConnectedFormat.field_rate; } - fConnectedFormat = format.u.raw_video; if (fConnectedFormat.display.bytes_per_row == 0) { ERROR("Connect() - connected format still has BPR wildcard!\n"); fConnectedFormat.display.bytes_per_row = 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 - if (!fUsedBufferGroup) { + if (fUsedBufferGroup == NULL) { fBufferGroup = new BBufferGroup(fConnectedFormat.display.bytes_per_row * fConnectedFormat.display.line_count, BUFFER_COUNT); status_t err = fBufferGroup->InitCheck(); @@ -489,6 +486,20 @@ 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; fEnabled = true; @@ -665,8 +676,6 @@ VideoProducer::_FrameGeneratorThread() { bool forceSendingBuffer = true; - bigtime_t lastFrameSentAt = 0; - int64 lastPlaylistFrame = 0; int32 droppedFrames = 0; const int32 kMaxDroppedFrames = 15; bool running = true; @@ -680,7 +689,6 @@ bigtime_t nextPerformanceTime = 0; bigtime_t waitUntil = 0; bigtime_t nextWaitUntil = 0; - bigtime_t maxRenderTime = 0; int32 playingDirection = 0; int32 playingMode = 0; int64 playlistFrame = 0; @@ -691,14 +699,11 @@ // get the times for the current and the next frame performanceTime = fManager->TimeForFrame(fFrame); nextPerformanceTime = fManager->TimeForFrame(fFrame + 1); - maxRenderTime = min_c(bigtime_t(40000), - max_c(fSupplier->ProcessingLatency(), maxRenderTime)); playingMode = fManager->PlayModeAtFrame(fFrame); - waitUntil = TimeSource()->RealTimeFor(fPerformanceTimeBase - + performanceTime, 0) - maxRenderTime; + + performanceTime, fBufferLatency); nextWaitUntil = TimeSource()->RealTimeFor(fPerformanceTimeBase - + nextPerformanceTime, 0) - maxRenderTime; + + nextPerformanceTime, fBufferLatency); // get playing direction and playlist frame for the current // frame bool newPlayingState; @@ -707,10 +712,6 @@ TRACE("_FrameGeneratorThread: performance time: %Ld, " "playlist frame: %lld\n", performanceTime, playlistFrame); forceSendingBuffer |= newPlayingState; - if (lastPlaylistFrame != playlistFrame) { - forceSendingBuffer = true; - lastPlaylistFrame = playlistFrame; - } fManager->SetCurrentVideoTime(nextPerformanceTime); fManager->Unlock(); break; @@ -748,7 +749,7 @@ if (ignoreEvent || !fRunning || !fEnabled) { TRACE("_FrameGeneratorThread: ignore event\n"); // nothing to do - } else if (nextWaitUntil < system_time() + } else if (nextWaitUntil < system_time() - fBufferLatency && droppedFrames < kMaxDroppedFrames) { // Drop frame if it's at least a frame late. printf("VideoProducer: dropped frame (%Ld)\n", fFrame); @@ -765,78 +766,72 @@ TRACE("_FrameGeneratorThread: produce frame\n"); BAutolock _(fLock); // Fetch a buffer from the buffer group + fUsedBufferGroup->WaitForBuffers(); BBuffer* buffer = fUsedBufferGroup->RequestBuffer( fConnectedFormat.display.bytes_per_row * fConnectedFormat.display.line_count, 0LL); - if (buffer != NULL) { - // 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; -// 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 (buffer == NULL) { + // Wait until a buffer becomes available again + TRACE("_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) { - // 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"); -// ERROR("_FrameGeneratorThread: no buffer!\n"); } + // 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 fFrame++; droppedFrames = 0; Modified: haiku/trunk/src/apps/mediaplayer/media_node_framework/video/VideoProducer.h =================================================================== --- haiku/trunk/src/apps/mediaplayer/media_node_framework/video/VideoProducer.h 2010-09-15 17:49:47 UTC (rev 38661) +++ haiku/trunk/src/apps/mediaplayer/media_node_framework/video/VideoProducer.h 2010-09-15 18:15:20 UTC (rev 38662) @@ -132,6 +132,8 @@ int64 fFrame; int64 fFrameBase; bigtime_t fPerformanceTimeBase; + bigtime_t fBufferDuration; + bigtime_t fBufferLatency; media_output fOutput; media_raw_video_format fConnectedFormat; bool fRunning; Modified: haiku/trunk/src/apps/mediaplayer/supplier/ProxyVideoSupplier.cpp =================================================================== --- haiku/trunk/src/apps/mediaplayer/supplier/ProxyVideoSupplier.cpp 2010-09-15 17:49:47 UTC (rev 38661) +++ haiku/trunk/src/apps/mediaplayer/supplier/ProxyVideoSupplier.cpp 2010-09-15 18:15:20 UTC (rev 38662) @@ -18,18 +18,13 @@ ProxyVideoSupplier::ProxyVideoSupplier() : fSupplierLock("video supplier lock"), - fSupplier(NULL), - fCachedFrame(NULL), - fCachedFrameSize(0), - fCachedFrameValid(false), - fUseFrameCaching(true) + fSupplier(NULL) { } ProxyVideoSupplier::~ProxyVideoSupplier() { - free(fCachedFrame); } @@ -44,35 +39,12 @@ if (fSupplier == NULL) 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 (fCachedFrameValid) { - memcpy(buffer, fCachedFrame, fCachedFrameSize); - 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 = true; + return B_OK; } + wasCached = false; status_t ret = B_OK; bigtime_t performanceTime = 0; if (fSupplier->CurrentFrame() != startFrame) { @@ -97,11 +69,6 @@ ret = fSupplier->ReadFrame(buffer, &performanceTime, format, wasCached); - if (fUseFrameCaching && ret == B_OK) { - memcpy(fCachedFrame, buffer, fCachedFrameSize); - fCachedFrameValid = true; - } - fProcessingLatency = system_time() - now; return ret; Modified: haiku/trunk/src/apps/mediaplayer/supplier/ProxyVideoSupplier.h =================================================================== --- haiku/trunk/src/apps/mediaplayer/supplier/ProxyVideoSupplier.h 2010-09-15 17:49:47 UTC (rev 38661) +++ haiku/trunk/src/apps/mediaplayer/supplier/ProxyVideoSupplier.h 2010-09-15 18:15:20 UTC (rev 38662) @@ -30,11 +30,6 @@ BLocker fSupplierLock; VideoTrackSupplier* fSupplier; - - void* fCachedFrame; - size_t fCachedFrameSize; - bool fCachedFrameValid; - bool fUseFrameCaching; }; #endif // PROXY_VIDEO_SUPPLIER_H