/* 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 "VideoProducer.h" #include #include #include #include #include #include #include "NodeManager.h" #include "VideoSupplier.h" // debugging //#define TRACE_VIDEO_PRODUCER #ifdef TRACE_VIDEO_PRODUCER # define TRACE(x...) printf("VideoProducer::"); printf(x) # define FUNCTION(x...) TRACE(x) # define ERROR(x...) fprintf(stderr, "VideoProducer::"); fprintf(stderr, x) #else # define TRACE(x...) # define FUNCTION(x...) # define ERROR(x...) fprintf(stderr, "VideoProducer::"); fprintf(stderr, x) #endif #define BUFFER_COUNT 3 #define TOUCH(x) ((void)(x)) VideoProducer::VideoProducer(BMediaAddOn* addon, const char* name, int32 internalId, NodeManager* manager, VideoSupplier* supplier) : BMediaNode(name), BMediaEventLooper(), BBufferProducer(B_MEDIA_RAW_VIDEO), fInitStatus(B_NO_INIT), fInternalID(internalId), fAddOn(addon), fBufferGroup(NULL), fUsedBufferGroup(NULL), fThread(-1), fFrameSync(-1), fRunning(false), fConnected(false), fEnabled(false), fManager(manager), fSupplier(supplier) { fOutput.destination = media_destination::null; fInitStatus = B_OK; } VideoProducer::~VideoProducer() { if (fInitStatus == B_OK) { // Clean up after ourselves, in case the application didn't make us // do so. if (fConnected) Disconnect(fOutput.source, fOutput.destination); if (fRunning) _HandleStop(); } Quit(); } port_id VideoProducer::ControlPort() const { return BMediaNode::ControlPort(); } BMediaAddOn* VideoProducer::AddOn(int32* _internalId) const { if (_internalId) *_internalId = fInternalID; return fAddOn; } status_t VideoProducer::HandleMessage(int32 message, const void* data, size_t size) { return B_ERROR; } void VideoProducer::SetTimeSource(BTimeSource* timeSource) { // Tell frame generation thread to recalculate delay value release_sem(fFrameSync); } status_t VideoProducer::RequestCompleted(const media_request_info& info) { return BMediaNode::RequestCompleted(info); } void VideoProducer::NodeRegistered() { if (fInitStatus != B_OK) { ReportError(B_NODE_IN_DISTRESS); return; } fOutput.node = Node(); fOutput.source.port = ControlPort(); fOutput.source.id = 0; fOutput.destination = media_destination::null; strcpy(fOutput.name, Name()); // fill with wild cards at this point in time fOutput.format.type = B_MEDIA_RAW_VIDEO; fOutput.format.u.raw_video = media_raw_video_format::wildcard; fOutput.format.u.raw_video.interlace = 1; fOutput.format.u.raw_video.display.format = B_NO_COLOR_SPACE; fOutput.format.u.raw_video.display.bytes_per_row = 0; fOutput.format.u.raw_video.display.line_width = 0; fOutput.format.u.raw_video.display.line_count = 0; // start the BMediaEventLooper control loop running Run(); } void VideoProducer::Start(bigtime_t performanceTime) { // notify the manager in case we were started from the outside world // fManager->StartPlaying(); BMediaEventLooper::Start(performanceTime); } void VideoProducer::Stop(bigtime_t performanceTime, bool immediate) { // notify the manager in case we were stopped from the outside world // fManager->StopPlaying(); BMediaEventLooper::Stop(performanceTime, immediate); } void VideoProducer::Seek(bigtime_t media_time, bigtime_t performanceTime) { BMediaEventLooper::Seek(media_time, performanceTime); } void VideoProducer::TimeWarp(bigtime_t at_real_time, bigtime_t to_performance_time) { BMediaEventLooper::TimeWarp(at_real_time, to_performance_time); } status_t VideoProducer::AddTimer(bigtime_t at_performance_time, int32 cookie) { return BMediaEventLooper::AddTimer(at_performance_time, cookie); } void VideoProducer::SetRunMode(run_mode mode) { printf("VideoProducer::SetRunMode(%d)\n", mode); TRACE("SetRunMode(%d)\n", mode); BMediaEventLooper::SetRunMode(mode); } void VideoProducer::HandleEvent(const media_timed_event* event, bigtime_t lateness, bool realTimeEvent) { TOUCH(lateness); TOUCH(realTimeEvent); switch(event->type) { case BTimedEventQueue::B_START: _HandleStart(event->event_time); break; case BTimedEventQueue::B_STOP: _HandleStop(); break; case BTimedEventQueue::B_WARP: _HandleTimeWarp(event->bigdata); break; case BTimedEventQueue::B_SEEK: _HandleSeek(event->bigdata); break; case BTimedEventQueue::B_HANDLE_BUFFER: case BTimedEventQueue::B_DATA_STATUS: case BTimedEventQueue::B_PARAMETER: default: TRACE("HandleEvent: Unhandled event -- %lx\n", event->type); break; } } void VideoProducer::CleanUpEvent(const media_timed_event *event) { BMediaEventLooper::CleanUpEvent(event); } bigtime_t VideoProducer::OfflineTime() { return BMediaEventLooper::OfflineTime(); } void VideoProducer::ControlLoop() { BMediaEventLooper::ControlLoop(); } status_t VideoProducer::DeleteHook(BMediaNode* node) { return BMediaEventLooper::DeleteHook(node); } status_t VideoProducer::FormatSuggestionRequested(media_type type, int32 quality, media_format* _format) { FUNCTION("FormatSuggestionRequested\n"); if (type != B_MEDIA_ENCODED_VIDEO) return B_MEDIA_BAD_FORMAT; TOUCH(quality); *_format = fOutput.format; return B_OK; } status_t VideoProducer::FormatProposal(const media_source& output, media_format* format) { #ifdef TRACE_VIDEO_PRODUCER char string[256]; string_for_format(*format, string, 256); FUNCTION("FormatProposal(%s)\n", string); #endif if (!format) return B_BAD_VALUE; if (output != fOutput.source) return B_MEDIA_BAD_SOURCE; status_t ret = format_is_compatible(*format, fOutput.format) ? B_OK : B_MEDIA_BAD_FORMAT; if (ret != B_OK) ERROR("FormatProposal() error\n"); // change any wild cards to specific values return ret; } status_t VideoProducer::FormatChangeRequested(const media_source& source, const media_destination& destination, media_format* ioFormat, int32 *_deprecated_) { TOUCH(destination); TOUCH(ioFormat); TOUCH(_deprecated_); if (source != fOutput.source) return B_MEDIA_BAD_SOURCE; return B_ERROR; } status_t VideoProducer::GetNextOutput(int32* cookie, media_output* outOutput) { if (!outOutput) return B_BAD_VALUE; if ((*cookie) != 0) return B_BAD_INDEX; *outOutput = fOutput; (*cookie)++; return B_OK; } status_t VideoProducer::DisposeOutputCookie(int32 cookie) { TOUCH(cookie); return B_OK; } status_t VideoProducer::SetBufferGroup(const media_source& forSource, BBufferGroup *group) { if (forSource != fOutput.source) return B_MEDIA_BAD_SOURCE; TRACE("VideoProducer::SetBufferGroup() - using buffer group of " "consumer.\n"); fUsedBufferGroup = group; return B_OK; } status_t VideoProducer::VideoClippingChanged(const media_source& forSource, int16 numShorts, int16* clipData, const media_video_display_info& display, int32* _deprecated_) { TOUCH(forSource); TOUCH(numShorts); TOUCH(clipData); TOUCH(display); TOUCH(_deprecated_); return B_ERROR; } status_t VideoProducer::GetLatency(bigtime_t* _latency) { if (!_latency) return B_BAD_VALUE; *_latency = EventLatency() + SchedulingLatency(); return B_OK; } status_t VideoProducer::PrepareToConnect(const media_source& source, const media_destination& destination, media_format* format, media_source* outSource, char* outName) { FUNCTION("PrepareToConnect() %ldx%ld\n", format->u.raw_video.display.line_width, format->u.raw_video.display.line_count); if (fConnected) { ERROR("PrepareToConnect() - already connected!\n"); return B_MEDIA_ALREADY_CONNECTED; } if (source != fOutput.source) return B_MEDIA_BAD_SOURCE; if (fOutput.destination != media_destination::null) { ERROR("PrepareToConnect() - destination != null.\n"); return B_MEDIA_ALREADY_CONNECTED; } // The format parameter comes in with the suggested format, and may be // specialized as desired by the node if (!format_is_compatible(*format, fOutput.format)) { ERROR("PrepareToConnect() - incompatible format.\n"); *format = fOutput.format; return B_MEDIA_BAD_FORMAT; } if (format->u.raw_video.display.line_width == 0) format->u.raw_video.display.line_width = 384; if (format->u.raw_video.display.line_count == 0) format->u.raw_video.display.line_count = 288; if (format->u.raw_video.field_rate == 0) format->u.raw_video.field_rate = 25.0; if (format->u.raw_video.display.bytes_per_row == 0) format->u.raw_video.display.bytes_per_row = format->u.raw_video.display.line_width * 4; *outSource = fOutput.source; strcpy(outName, fOutput.name); return B_OK; } #define NODE_LATENCY 20000 void VideoProducer::Connect(status_t error, const media_source& source, const media_destination& destination, const media_format& format, char* _name) { FUNCTION("Connect() %ldx%ld\n", format.u.raw_video.display.line_width, format.u.raw_video.display.line_count); if (fConnected) { ERROR("Connect() - already connected.\n"); return; } if (source != fOutput.source) { ERROR("Connect() - wrong source.\n"); return; } if (error < B_OK) { ERROR("Connect() - consumer error: %s\n", strerror(error)); return; } if (!const_cast(&format)->Matches(&fOutput.format)) { ERROR("Connect() - format mismatch.\n"); return; } fOutput.destination = destination; strcpy(_name, fOutput.name); if (fOutput.format.u.raw_video.field_rate != 0.0f) { fPerformanceTimeBase = fPerformanceTimeBase + (bigtime_t)((fFrame - fFrameBase) * 1000000 / fOutput.format.u.raw_video.field_rate); fFrameBase = fFrame; } 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) { fBufferGroup = new BBufferGroup(fConnectedFormat.display.bytes_per_row * fConnectedFormat.display.line_count, BUFFER_COUNT); status_t err = fBufferGroup->InitCheck(); if (err < B_OK) { delete fBufferGroup; fBufferGroup = NULL; ERROR("Connect() - buffer group error: %s\n", strerror(err)); return; } fUsedBufferGroup = fBufferGroup; } fConnected = true; fEnabled = true; // Tell frame generation thread to recalculate delay value release_sem(fFrameSync); } void VideoProducer::Disconnect(const media_source& source, const media_destination& destination) { FUNCTION("Disconnect()\n"); if (!fConnected) { ERROR("Disconnect() - Not connected\n"); return; } if ((source != fOutput.source) || (destination != fOutput.destination)) { ERROR("Disconnect() - Bad source and/or destination\n"); return; } fEnabled = false; fOutput.destination = media_destination::null; if (fLock.Lock()) { // Always delete the buffer group, even if it is not ours. // (See BeBook::SetBufferGroup()). delete fUsedBufferGroup; fUsedBufferGroup = NULL; fBufferGroup = NULL; fLock.Unlock(); } fConnected = false; TRACE("Disconnect() done\n"); } void VideoProducer::LateNoticeReceived(const media_source &source, bigtime_t how_much, bigtime_t performanceTime) { TOUCH(source); TOUCH(how_much); TOUCH(performanceTime); TRACE("Late!!!\n"); } void VideoProducer::EnableOutput(const media_source& source, bool enabled, int32* _deprecated_) { TOUCH(_deprecated_); if (source != fOutput.source) return; fEnabled = enabled; } status_t VideoProducer::SetPlayRate(int32 numer, int32 denom) { TOUCH(numer); TOUCH(denom); return B_ERROR; } void VideoProducer::AdditionalBufferRequested(const media_source& source, media_buffer_id prevBuffer, bigtime_t prevTime, const media_seek_tag* prevTag) { TOUCH(source); TOUCH(prevBuffer); TOUCH(prevTime); TOUCH(prevTag); } void VideoProducer::LatencyChanged(const media_source& source, const media_destination& destination, bigtime_t newLatency, uint32 flags) { TOUCH(source); TOUCH(destination); TOUCH(newLatency); TOUCH(flags); TRACE("Latency changed!\n"); } // #pragma mark - void VideoProducer::_HandleStart(bigtime_t performanceTime) { // Start producing frames, even if the output hasn't been connected yet. TRACE("_HandleStart(%Ld)\n", performanceTime); if (fRunning) { TRACE("_HandleStart: Node already started\n"); return; } fFrame = 0; fFrameBase = 0; fPerformanceTimeBase = performanceTime; fFrameSync = create_sem(0, "frame synchronization"); if (fFrameSync < B_OK) return; fThread = spawn_thread(_FrameGeneratorThreadEntry, "frame generator", B_NORMAL_PRIORITY, this); if (fThread < B_OK) { delete_sem(fFrameSync); return; } resume_thread(fThread); fRunning = true; return; } void VideoProducer::_HandleStop() { TRACE("_HandleStop()\n"); if (!fRunning) { TRACE("_HandleStop: Node isn't running\n"); return; } delete_sem(fFrameSync); wait_for_thread(fThread, &fThread); fRunning = false; } void VideoProducer::_HandleTimeWarp(bigtime_t performanceTime) { fPerformanceTimeBase = performanceTime; fFrameBase = fFrame; // Tell frame generation thread to recalculate delay value release_sem(fFrameSync); } void VideoProducer::_HandleSeek(bigtime_t performanceTime) { fPerformanceTimeBase = performanceTime; fFrameBase = fFrame; // Tell frame generation thread to recalculate delay value release_sem(fFrameSync); } int32 VideoProducer::_FrameGeneratorThreadEntry(void* data) { return ((VideoProducer*)data)->_FrameGeneratorThread(); } int32 VideoProducer::_FrameGeneratorThread() { bool forceSendingBuffer = true; bigtime_t lastFrameSentAt = 0; int64 lastPlaylistFrame = 0; bool running = true; while (running) { TRACE("_FrameGeneratorThread: loop: %Ld\n", fFrame); // lock the node manager status_t err = fManager->LockWithTimeout(10000); bool ignoreEvent = false; // Data to be retrieved from the node manager. bigtime_t performanceTime = 0; 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; switch (err) { case B_OK: { TRACE("_FrameGeneratorThread: node manager successfully " "locked\n"); // get the times for the current and the next frame performanceTime = fManager->TimeForFrame(fFrame); nextPerformanceTime = fManager->TimeForFrame(fFrame + 1); maxRenderTime = min_c(bigtime_t(33334 * 0.9), max_c(fSupplier->ProcessingLatency(), maxRenderTime)); playingMode = fManager->PlayModeAtFrame(fFrame); waitUntil = TimeSource()->RealTimeFor(fPerformanceTimeBase + performanceTime, 0) - maxRenderTime; nextWaitUntil = TimeSource()->RealTimeFor(fPerformanceTimeBase + nextPerformanceTime, 0) - maxRenderTime; // get playing direction and playlist frame for the current // frame bool newPlayingState; playlistFrame = fManager->PlaylistFrameAtFrame(fFrame, playingDirection, newPlayingState); 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; } case B_TIMED_OUT: TRACE("_FrameGeneratorThread: Couldn't lock the node " "manager.\n"); ignoreEvent = true; waitUntil = system_time() - 1; break; default: ERROR("_FrameGeneratorThread: Couldn't lock the node manager. " "Terminating video producer frame generator thread.\n"); TRACE("_FrameGeneratorThread: frame generator thread done.\n"); // do not access any member variables, since this could // also mean the Node has been deleted return B_OK; } TRACE("_FrameGeneratorThread: waiting (%Ld)...\n", waitUntil); // wait until... err = acquire_sem_etc(fFrameSync, 1, B_ABSOLUTE_TIMEOUT, waitUntil); // The only acceptable responses are B_OK and B_TIMED_OUT. Everything // else means the thread should quit. Deleting the semaphore, as in // VideoProducer::_HandleStop(), will trigger this behavior. switch (err) { case B_OK: TRACE("_FrameGeneratorThread: going back to sleep.\n"); break; case B_TIMED_OUT: TRACE("_FrameGeneratorThread: timed out => event\n"); // Catch the cases in which the node manager could not be // locked and we therefore have no valid data to work with, // or the producer is not running or enabled. if (ignoreEvent || !fRunning || !fEnabled) { TRACE("_FrameGeneratorThread: ignore event\n"); // nothing to do } else if (nextWaitUntil < system_time()) { // Drop frame if it's at least a frame late. printf("VideoProducer: dropped frame (%Ld)\n", fFrame); if (fManager->LockWithTimeout(10000) == B_OK) { fManager->FrameDropped(); fManager->Unlock(); } // next frame fFrame++; } else if (playingDirection != 0 || forceSendingBuffer) { // Send buffers only, if playing, the node is running and // the output has been enabled TRACE("_FrameGeneratorThread: produce frame\n"); BAutolock _(fLock); // Fetch a buffer from the buffer group BBuffer *buffer = fUsedBufferGroup->RequestBuffer( fConnectedFormat.display.bytes_per_row * fConnectedFormat.display.line_count, 0LL); if (buffer) { // 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) { 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 media_format mf; mf.type = B_MEDIA_RAW_VIDEO; mf.u.raw_video = fConnectedFormat; TRACE("_FrameGeneratorThread: frame: %Ld, " "playlistFrame: %Ld\n", fFrame, playlistFrame); bool forceOrWasCached = forceSendingBuffer; err = fSupplier->FillBuffer(playlistFrame, buffer->Data(), &mf, 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.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"); } // next frame fFrame++; } else { TRACE("_FrameGeneratorThread: not playing\n"); // next frame fFrame++; } break; default: TRACE("_FrameGeneratorThread: Couldn't acquire semaphore. " "Error: %s\n", strerror(err)); running = false; break; } } TRACE("_FrameGeneratorThread: frame generator thread done.\n"); return B_OK; }