/* Copyright (c) 1998-99, Be Incorporated, All Rights Reserved. * Distributed under the terms of the Be Sample Code license. * * Copyright (c) 2000-2008, Ingo Weinhold , * Copyright (c) 2000-2008, Stephan Aßmus , * All Rights Reserved. Distributed under the terms of the MIT license. */ #include "VideoConsumer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "ColorSpaceToString.h" #include "NodeManager.h" #include "VideoTarget.h" // debugging //#define TRACE_VIDEO_CONSUMER #ifdef TRACE_VIDEO_CONSUMER # define TRACE(x...) printf(x) # define PROGRESS(x...) printf(x) # define FUNCTION(x...) printf(x) # define LOOP(x...) printf(x) # define ERROR(x...) fprintf(stderr, x) #else # define TRACE(x...) # define PROGRESS(x...) # define FUNCTION(x...) # define LOOP(x...) # define ERROR(x...) fprintf(stderr, x) #endif #define M1 ((double)1000000.0) static const bigtime_t kMaxBufferLateness = 20000LL; VideoConsumer::VideoConsumer(const char* name, BMediaAddOn* addon, const uint32 internal_id, NodeManager* manager, VideoTarget* target) : BMediaNode(name), BMediaEventLooper(), BBufferConsumer(B_MEDIA_RAW_VIDEO), fInternalID(internal_id), fAddOn(addon), fConnectionActive(false), fMyLatency(3000), fOurBuffers(false), fBuffers(NULL), fManager(manager), fTargetLock(), fTarget(target), fLastBufferIndex(-1), fTryOverlay(true) { FUNCTION("VideoConsumer::VideoConsumer\n"); AddNodeKind(B_PHYSICAL_OUTPUT); SetEventLatency(0); for (uint32 i = 0; i < kBufferCount; i++) { fBitmap[i] = NULL; fBufferMap[i] = NULL; } SetPriority(B_DISPLAY_PRIORITY); } VideoConsumer::~VideoConsumer() { Quit(); DeleteBuffers(); } BMediaAddOn* VideoConsumer::AddOn(int32* cookie) const { FUNCTION("VideoConsumer::AddOn\n"); // do the right thing if we're ever used with an add-on *cookie = fInternalID; return fAddOn; } void VideoConsumer::NodeRegistered() { FUNCTION("VideoConsumer::NodeRegistered\n"); fIn.destination.port = ControlPort(); fIn.destination.id = 0; fIn.source = media_source::null; fIn.format.type = B_MEDIA_RAW_VIDEO; // wild cards yet fIn.format.u.raw_video = media_raw_video_format::wildcard; fIn.format.u.raw_video.interlace = 1; fIn.format.u.raw_video.display.format = B_NO_COLOR_SPACE; fIn.format.u.raw_video.display.bytes_per_row = 0; fIn.format.u.raw_video.display.line_width = 0; fIn.format.u.raw_video.display.line_count = 0; Run(); } status_t VideoConsumer::RequestCompleted(const media_request_info& info) { FUNCTION("VideoConsumer::RequestCompleted\n"); switch(info.what) { case media_request_info::B_SET_OUTPUT_BUFFERS_FOR: if (info.status != B_OK) ERROR("VideoConsumer::RequestCompleted: Not using our " "buffers!\n"); break; default: break; } return B_OK; } status_t VideoConsumer::HandleMessage(int32 message, const void* data, size_t size) { return B_OK; } void VideoConsumer::BufferReceived(BBuffer* buffer) { LOOP("VideoConsumer::Buffer #%" B_PRId32 " received\n", buffer->ID()); if (RunState() == B_STOPPED) { buffer->Recycle(); return; } media_timed_event event(buffer->Header()->start_time, BTimedEventQueue::B_HANDLE_BUFFER, buffer, BTimedEventQueue::B_RECYCLE_BUFFER); EventQueue()->AddEvent(event); } void VideoConsumer::ProducerDataStatus(const media_destination& forWhom, int32 status, bigtime_t atMediaTime) { FUNCTION("VideoConsumer::ProducerDataStatus\n"); if (forWhom != fIn.destination) return; } status_t VideoConsumer::CreateBuffers(const media_format& format) { FUNCTION("VideoConsumer::CreateBuffers\n"); // delete any old buffers DeleteBuffers(); status_t status = B_OK; // create a buffer group uint32 width = format.u.raw_video.display.line_width; uint32 height = format.u.raw_video.display.line_count; color_space colorSpace = format.u.raw_video.display.format; PROGRESS("VideoConsumer::CreateBuffers - Width = %" B_PRIu32 " - " "Height = %" B_PRIu32 " - Colorspace = %d\n", width, height, colorSpace); fBuffers = new BBufferGroup(); status = fBuffers->InitCheck(); if (B_OK != status) { ERROR("VideoConsumer::CreateBuffers - ERROR CREATING BUFFER GROUP\n"); return status; } // and attach the bitmaps to the buffer group BRect bounds(0, 0, width - 1, height - 1); for (uint32 i = 0; i < kBufferCount; i++) { // figure out the bitmap creation flags uint32 bitmapFlags = 0; if (fTryOverlay) { // try to use hardware overlay bitmapFlags |= B_BITMAP_WILL_OVERLAY; if (i == 0) bitmapFlags |= B_BITMAP_RESERVE_OVERLAY_CHANNEL; } else bitmapFlags = B_BITMAP_IS_LOCKED; fBitmap[i] = new BBitmap(bounds, bitmapFlags, colorSpace); status = fBitmap[i]->InitCheck(); if (status >= B_OK) { buffer_clone_info info; uint8* bits = (uint8*)fBitmap[i]->Bits(); info.area = area_for(bits); area_info bitmapAreaInfo; status = get_area_info(info.area, &bitmapAreaInfo); if (status != B_OK) { fprintf(stderr, "VideoConsumer::CreateBuffers() - " "get_area_info(): %s\n", strerror(status)); return status; } //printf("area info for bitmap %ld (%p):\n", i, fBitmap[i]->Bits()); //printf(" area: %ld\n", bitmapAreaInfo.area); //printf(" size: %ld\n", bitmapAreaInfo.size); //printf(" lock: %ld\n", bitmapAreaInfo.lock); //printf(" protection: %ld\n", bitmapAreaInfo.protection); //printf(" ram size: %ld\n", bitmapAreaInfo.ram_size); //printf(" copy_count: %ld\n", bitmapAreaInfo.copy_count); //printf(" out_count: %ld\n", bitmapAreaInfo.out_count); //printf(" address: %p\n", bitmapAreaInfo.address); info.offset = bits - (uint8*)bitmapAreaInfo.address; info.size = (size_t)fBitmap[i]->BitsLength(); info.flags = 0; info.buffer = 0; // the media buffer id BBuffer* buffer = NULL; if ((status = fBuffers->AddBuffer(info, &buffer)) != B_OK) { ERROR("VideoConsumer::CreateBuffers - ERROR ADDING BUFFER " "TO GROUP (%" B_PRId32 "): %s\n", i, strerror(status)); return status; } else { PROGRESS("VideoConsumer::CreateBuffers - SUCCESSFUL ADD " "BUFFER TO GROUP\n"); } fBufferMap[i] = buffer; } else { ERROR("VideoConsumer::CreateBuffers - ERROR CREATING VIDEO RING " "BUFFER (Index %" B_PRId32 " Width %" B_PRId32 " Height %" B_PRId32 " Colorspace %d: %s\n", i, width, height, colorSpace, strerror(status)); return status; } } FUNCTION("VideoConsumer::CreateBuffers - EXIT\n"); return status; } void VideoConsumer::DeleteBuffers() { FUNCTION("VideoConsumer::DeleteBuffers\n"); if (fBuffers) { fTargetLock.Lock(); if (fLastBufferIndex >= 0) { if (fTarget) fTarget->SetBitmap(NULL); fLastBufferIndex = -1; } fTargetLock.Unlock(); delete fBuffers; fBuffers = NULL; for (uint32 i = 0; i < kBufferCount; i++) { snooze(20000); delete fBitmap[i]; fBitmap[i] = NULL; } } FUNCTION("VideoConsumer::DeleteBuffers - EXIT\n"); } void VideoConsumer::SetTarget(VideoTarget* target) { fTargetLock.Lock(); if (fTarget) fTarget->SetBitmap(NULL); fTarget = target; if (fTarget && fLastBufferIndex >= 0) fTarget->SetBitmap(fBitmap[fLastBufferIndex]); fTargetLock.Unlock(); } void VideoConsumer::SetTryOverlay(bool tryOverlay) { fTryOverlay = tryOverlay; } status_t VideoConsumer::Connected(const media_source& producer, const media_destination& where, const media_format& format, media_input* outInput) { FUNCTION("VideoConsumer::Connected\n"); fIn.source = producer; fIn.format = format; fIn.node = Node(); sprintf(fIn.name, "Video Consumer"); *outInput = fIn; uint32 userData = 0; int32 changeTag = 1; status_t ret = CreateBuffers(format); if (ret == B_OK) { // TODO: With overlay bitmaps, there seems to be a problem with // mapping the BBitmap areas into the BBuffers. Until that is fixed, // don't enable a shared BBufferGroup. if (!fTryOverlay) { ret = SetOutputBuffersFor(producer, fIn.destination, fBuffers, &userData, &changeTag, true); if (ret != B_OK) ERROR("SetOutputBuffersFor() failed: %s\n", strerror(ret)); } fIn.format.u.raw_video.display.bytes_per_row = fBitmap[0]->BytesPerRow(); } else { ERROR("VideoConsumer::Connected - COULDN'T CREATE BUFFERS\n"); return ret; } *outInput = fIn; // bytes per row might have changed fConnectionActive = true; FUNCTION("VideoConsumer::Connected - EXIT\n"); return B_OK; } void VideoConsumer::Disconnected(const media_source& producer, const media_destination& where) { FUNCTION("VideoConsumer::Disconnected\n"); if (where != fIn.destination || producer != fIn.source) return; // reclaim our buffers int32 changeTag = 0; SetOutputBuffersFor(producer, fIn.destination, NULL, NULL, &changeTag, false); if (fOurBuffers) { status_t reclaimError = fBuffers->ReclaimAllBuffers(); if (reclaimError != B_OK) { fprintf(stderr, "VideoConsumer::Disconnected() - Failed to " "reclaim our buffers: %s\n", strerror(reclaimError)); } } // disconnect the connection fIn.source = media_source::null; fConnectionActive = false; // Unset the target's bitmap. Just to be safe -- since it is usually // done when the stop event arrives, but someone may disonnect // without stopping us before. _UnsetTargetBuffer(); } status_t VideoConsumer::AcceptFormat(const media_destination& dest, media_format* format) { FUNCTION("VideoConsumer::AcceptFormat\n"); if (dest != fIn.destination) { ERROR("VideoConsumer::AcceptFormat - BAD DESTINATION\n"); return B_MEDIA_BAD_DESTINATION; } if (format->type == B_MEDIA_NO_TYPE) format->type = B_MEDIA_RAW_VIDEO; if (format->type != B_MEDIA_RAW_VIDEO) { ERROR("VideoConsumer::AcceptFormat - BAD FORMAT\n"); return B_MEDIA_BAD_FORMAT; } if (format->u.raw_video.display.format != media_raw_video_format::wildcard.display.format) { uint32 flags = 0; bool supported = bitmaps_support_space( format->u.raw_video.display.format, &flags); #ifndef HAIKU_TARGET_PLATFORM_HAIKU // GRRR! BeOS implementation claims not // to support these formats, while they work just fine. switch (format->u.raw_video.display.format) { case B_YCbCr422: case B_YCbCr411: case B_YCbCr444: case B_YCbCr420: supported = true; break; default: break; } #endif if (!supported) { // cannot create bitmaps with such a color space ERROR("AcceptFormat - unsupported color space for BBitmaps " "(%s)!\n", color_space_to_string(format->u.raw_video.display.format)); return B_MEDIA_BAD_FORMAT; } if (!fTryOverlay && (flags & B_VIEWS_SUPPORT_DRAW_BITMAP) == 0) { // BViews do not support drawing such a bitmap ERROR("AcceptFormat - BViews cannot draw bitmaps in given " "colorspace (%s)!\n", color_space_to_string(format->u.raw_video.display.format)); return B_MEDIA_BAD_FORMAT; } } #ifdef TRACE_VIDEO_CONSUMER char string[256]; string[0] = 0; string_for_format(*format, string, 256); FUNCTION("VideoConsumer::AcceptFormat: %s\n", string); #endif return B_OK; } status_t VideoConsumer::GetNextInput(int32* cookie, media_input* outInput) { FUNCTION("VideoConsumer::GetNextInput\n"); // custom build a destination for this connection // put connection number in id if (*cookie < 1) { fIn.node = Node(); fIn.destination.id = *cookie; sprintf(fIn.name, "Video Consumer"); *outInput = fIn; (*cookie)++; return B_OK; } else { return B_MEDIA_BAD_DESTINATION; } } void VideoConsumer::DisposeInputCookie(int32 /*cookie*/) { } status_t VideoConsumer::GetLatencyFor(const media_destination& whom, bigtime_t* _latency, media_node_id* _timeSource) { FUNCTION("VideoConsumer::GetLatencyFor\n"); if (whom != fIn.destination) return B_MEDIA_BAD_DESTINATION; *_latency = fMyLatency; *_timeSource = TimeSource()->ID(); return B_OK; } status_t VideoConsumer::FormatChanged(const media_source& producer, const media_destination& consumer, int32 fromChangeCount, const media_format& format) { FUNCTION("VideoConsumer::FormatChanged\n"); if (consumer != fIn.destination) return B_MEDIA_BAD_DESTINATION; if (producer != fIn.source) return B_MEDIA_BAD_SOURCE; fIn.format = format; return CreateBuffers(format); } void VideoConsumer::HandleEvent(const media_timed_event* event, bigtime_t lateness, bool realTimeEvent) { LOOP("VideoConsumer::HandleEvent\n"); switch (event->type) { case BTimedEventQueue::B_START: PROGRESS("VideoConsumer::HandleEvent - START\n"); _SetPerformanceTimeBase(event->event_time); break; case BTimedEventQueue::B_WARP: case BTimedEventQueue::B_SEEK: PROGRESS("VideoConsumer::HandleEvent - WARP or SEEK\n"); _SetPerformanceTimeBase(event->bigdata); break; case BTimedEventQueue::B_STOP: PROGRESS("VideoConsumer::HandleEvent - STOP\n"); EventQueue()->FlushEvents(event->event_time, BTimedEventQueue::B_ALWAYS, true, BTimedEventQueue::B_HANDLE_BUFFER); // unset the target's bitmap _UnsetTargetBuffer(); break; case BTimedEventQueue::B_HANDLE_BUFFER: LOOP("VideoConsumer::HandleEvent - HANDLE BUFFER\n"); _HandleBuffer(static_cast(event->pointer)); break; default: ERROR("VideoConsumer::HandleEvent - BAD EVENT\n"); break; } } void VideoConsumer::_SetPerformanceTimeBase(bigtime_t performanceTime) { fPerformanceTimeBase = performanceTime; } void VideoConsumer::_HandleBuffer(BBuffer* buffer) { if (RunState() != B_STARTED || !fConnectionActive) { TRACE("RunState() != B_STARTED\n"); buffer->Recycle(); return; } // See if this is one of our BBitmap buffers uint32 index = 0; fOurBuffers = true; while (index < kBufferCount) { if (buffer->ID() == fBufferMap[index]->ID()) break; else index++; } if (index == kBufferCount) { // Buffers belong to consumer // NOTE: We maintain this in a member variable, since we still need // to recycle this buffer later on, in case it was the last buffer // received before shutting down. fOurBuffers = false; index = (fLastBufferIndex + 1) % kBufferCount; } bool recycle = true; bigtime_t now = TimeSource()->Now(); if (RunMode() == B_OFFLINE || now < buffer->Header()->start_time + kMaxBufferLateness) { // Only display the buffer if it's not too late, or if we are // in B_OFFLINE run-mode. if (!fOurBuffers) { memcpy(fBitmap[index]->Bits(), buffer->Data(), fBitmap[index]->BitsLength()); } bigtime_t tooEarly = buffer->Header()->start_time - now; if (tooEarly > 3000) snooze(tooEarly); fTargetLock.Lock(); if (fTarget) { fTarget->SetBitmap(fBitmap[index]); if (fOurBuffers) { // recycle the previous but not the current buffer if (fLastBufferIndex >= 0) fBufferMap[fLastBufferIndex]->Recycle(); recycle = false; } fLastBufferIndex = index; } fTargetLock.Unlock(); } else { // Drop the buffer if it's too late. if (fManager->LockWithTimeout(10000) == B_OK) { fManager->FrameDropped(); fManager->Unlock(); } PROGRESS("VideoConsumer::HandleEvent - DROPPED FRAME\n" " start_time: %" B_PRIdBIGTIME ", current: %" B_PRIdBIGTIME ", " "latency: %" B_PRIdBIGTIME "\n", buffer->Header()->start_time, TimeSource()->Now(), SchedulingLatency()); } if (recycle) buffer->Recycle(); } void VideoConsumer::_UnsetTargetBuffer() { fTargetLock.Lock(); if (fLastBufferIndex >= 0) { if (fTarget) fTarget->SetBitmap(NULL); if (fOurBuffers) fBufferMap[fLastBufferIndex]->Recycle(); fLastBufferIndex = -1; } fTargetLock.Unlock(); }