root/src/apps/mediaplayer/media_node_framework/NodeManager.cpp
/*
 * Copyright (c) 2000-2008, Ingo Weinhold <ingo_weinhold@gmx.de>,
 * Copyright (c) 2000-2008, Stephan Aßmus <superstippi@gmx.de>,
 * All Rights Reserved. Distributed under the terms of the MIT license.
 */


//! This class controls our media nodes and general playback


#include "NodeManager.h"

#include <stdio.h>
#include <string.h>

#include <MediaRoster.h>
#include <scheduler.h>
#include <TimeSource.h>

#include "AudioProducer.h"
#include "AudioSupplier.h"
#include "VideoConsumer.h"
#include "VideoProducer.h"
#include "VideoSupplier.h"


// debugging
//#define TRACE_NODE_MANAGER
#ifdef TRACE_NODE_MANAGER
#       define TRACE(x...)      printf(x)
#       define ERROR(x...)      fprintf(stderr, x)
#else
#       define TRACE(x...)
#       define ERROR(x...)      fprintf(stderr, x)
#endif

#define print_error(str, status) printf(str ", error: %s\n", strerror(status))


NodeManager::Connection::Connection()
        :
        connected(false)
{
}


// #pragma mark -


NodeManager::NodeManager()
        :
        PlaybackManager(),
        fMediaRoster(NULL),
        fAudioProducer(NULL),
        fVideoConsumer(NULL),
        fVideoProducer(NULL),
        fTimeSource(media_node::null),
        fAudioConnection(),
        fVideoConnection(),
        fPerformanceTimeBase(0),
        fStatus(B_NO_INIT),
        fVideoTarget(NULL),
        fAudioSupplier(NULL),
        fVideoSupplier(NULL),
        fVideoBounds(0, 0, -1, -1),
        fPeakListener(NULL)
{
}


NodeManager::~NodeManager()
{
        _StopNodes();
        _TearDownNodes();
}


status_t
NodeManager::Init(BRect videoBounds, float videoFrameRate,
        color_space preferredVideoFormat, float audioFrameRate,
        uint32 audioChannels, int32 loopingMode, bool loopingEnabled,
        float speed, uint32 enabledNodes, bool useOverlays)
{
        // init base class
        PlaybackManager::Init(videoFrameRate, true, loopingMode, loopingEnabled,
                speed);

        // get some objects from a derived class
        if (fVideoTarget == NULL)
                fVideoTarget = CreateVideoTarget();

        if (fVideoSupplier == NULL)
                fVideoSupplier = CreateVideoSupplier();

        if (fAudioSupplier == NULL)
                fAudioSupplier = CreateAudioSupplier();

        return FormatChanged(videoBounds, videoFrameRate, preferredVideoFormat,
                audioFrameRate, audioChannels, enabledNodes, useOverlays, true);
}


status_t
NodeManager::InitCheck()
{
        return fStatus;
}


void
NodeManager::SetPlayMode(int32 mode, bool continuePlaying)
{
        if (fVideoConsumer != NULL && fMediaRoster != NULL) {
                BMediaNode::run_mode runMode = mode > 0 ?
                        BMediaNode::B_DROP_DATA : BMediaNode::B_OFFLINE;
                status_t ret = fMediaRoster->SetRunModeNode(fVideoConnection.consumer,
                        runMode);
                if (ret != B_OK) {
                        printf("NodeManager::SetPlayMode(%" B_PRId32 "), setting run mode "
                                "failed: %s\n", mode, strerror(ret));
                }
        }

        PlaybackManager::SetPlayMode(mode, continuePlaying);
}


status_t
NodeManager::CleanupNodes()
{
        _StopNodes();
        return _TearDownNodes(false);
}


status_t
NodeManager::FormatChanged(BRect videoBounds, float videoFrameRate,
        color_space preferredVideoFormat, float audioFrameRate,
        uint32 audioChannels, uint32 enabledNodes, bool useOverlays, bool force)
{
        TRACE("NodeManager::FormatChanged()\n");

        if (!force && videoBounds == VideoBounds()
                && videoFrameRate == FramesPerSecond()) {
                TRACE("   -> reusing existing nodes\n");
                // TODO: if enabledNodes would indicate that audio or video
                // is no longer needed, or, worse yet, suddenly needed when
                // it wasn't before, then we should not return here!
                PlaybackManager::Init(videoFrameRate, false, LoopMode(),
                        IsLoopingEnabled(), Speed(), MODE_PLAYING_PAUSED_FORWARD,
                        CurrentFrame());
                return B_OK;
        }

        _StopNodes();
        _TearDownNodes();

        PlaybackManager::Init(videoFrameRate, true, LoopMode(), IsLoopingEnabled(),
                Speed(), MODE_PLAYING_PAUSED_FORWARD, CurrentFrame());

        SetVideoBounds(videoBounds);

        status_t ret = _SetUpNodes(preferredVideoFormat, enabledNodes,
                useOverlays, audioFrameRate, audioChannels);
        if (ret == B_OK)
                _StartNodes();
        else
                fprintf(stderr, "unable to setup nodes: %s\n", strerror(ret));

        return ret;
}


bigtime_t
NodeManager::RealTimeForTime(bigtime_t time) const
{
        bigtime_t result = 0;
        if (fVideoProducer) {
                result = fVideoProducer->TimeSource()->RealTimeFor(
                        fPerformanceTimeBase + time, 0);
        } else if (fAudioProducer) {
                result = fAudioProducer->TimeSource()->RealTimeFor(
                        fPerformanceTimeBase + time, 0);
        }
//printf("NodeManager::RealTimeForTime(%lld) -> %lld\n", time, result);
        return result;
}


bigtime_t
NodeManager::TimeForRealTime(bigtime_t time) const
{
        bigtime_t result = 0;
        if (fVideoProducer) {
                result = fVideoProducer->TimeSource()->PerformanceTimeFor(time)
                        - fPerformanceTimeBase;
        } else if (fAudioProducer) {
                result = fAudioProducer->TimeSource()->PerformanceTimeFor(time)
                        - fPerformanceTimeBase;
        }
        return result;
}


void
NodeManager::SetCurrentAudioTime(bigtime_t time)
{
//printf("NodeManager::SetCurrentAudioTime(%lld)\n", time);
        PlaybackManager::SetCurrentAudioTime(time);
        if (!fVideoProducer) {
                // running without video, update video time as well
                PlaybackManager::SetCurrentVideoTime(time);
        }
}


void
NodeManager::SetVideoBounds(BRect bounds)
{
        if (bounds != fVideoBounds) {
                fVideoBounds = bounds;
                NotifyVideoBoundsChanged(fVideoBounds);
        }
}


BRect
NodeManager::VideoBounds() const
{
        return fVideoBounds;
}


void
NodeManager::SetVideoTarget(VideoTarget* videoTarget)
{
        if (videoTarget != fVideoTarget) {
                fVideoTarget = videoTarget;
                if (fVideoConsumer)
                        fVideoConsumer->SetTarget(fVideoTarget);
        }
}


VideoTarget*
NodeManager::GetVideoTarget() const
{
        return fVideoTarget;
}


void
NodeManager::SetVolume(float percent)
{
        // TODO: would be nice to set the volume on the system mixer input of
        // our audio node...
}


void
NodeManager::SetPeakListener(BHandler* handler)
{
        fPeakListener = handler;
        if (fAudioProducer)
                fAudioProducer->SetPeakListener(fPeakListener);
}


// #pragma mark -


status_t
NodeManager::_SetUpNodes(color_space preferredVideoFormat, uint32 enabledNodes,
        bool useOverlays, float audioFrameRate, uint32 audioChannels)
{
        TRACE("NodeManager::_SetUpNodes()\n");

        // find the media roster
        fStatus = B_OK;
        fMediaRoster = BMediaRoster::Roster(&fStatus);
        if (fStatus != B_OK) {
                print_error("Can't find the media roster", fStatus);
                fMediaRoster = NULL;
                return fStatus;
        }

        // find the time source
        fStatus = fMediaRoster->GetTimeSource(&fTimeSource);
        if (fStatus != B_OK) {
                print_error("Can't get a time source", fStatus);
                return fStatus;
        }

        // setup the video nodes
        if (enabledNodes != AUDIO_ONLY) {
                fStatus = _SetUpVideoNodes(preferredVideoFormat, useOverlays);
                if (fStatus != B_OK) {
                        print_error("Error setting up video nodes", fStatus);
                        return fStatus;
                }
        } else
                printf("running without video node\n");

        // setup the audio nodes
        if (enabledNodes != VIDEO_ONLY) {
                fStatus = _SetUpAudioNodes(audioFrameRate, audioChannels);
                if (fStatus != B_OK) {
                        print_error("Error setting up audio nodes", fStatus);
                        return fStatus;
                }
                fNoAudio = false;
        } else {
                fNoAudio = true;
                printf("running without audio node\n");
        }

        return fStatus;
}


status_t
NodeManager::_SetUpVideoNodes(color_space preferredVideoFormat,
        bool useOverlays)
{
        // create the video producer node
        fVideoProducer = new VideoProducer(NULL, "MediaPlayer video out", 0,
                this, fVideoSupplier);

        // register the producer node
        fStatus = fMediaRoster->RegisterNode(fVideoProducer);
        if (fStatus != B_OK) {
                print_error("Can't register the video producer", fStatus);
                return fStatus;
        }

        // make sure the Media Roster knows that we're using the node
//      fMediaRoster->GetNodeFor(fVideoProducer->Node().node,
//              &fVideoConnection.producer);
        fVideoConnection.producer = fVideoProducer->Node();

        // create the video consumer node
        fVideoConsumer = new VideoConsumer("MediaPlayer video in", NULL, 0, this,
                fVideoTarget);

        // register the consumer node
        fStatus = fMediaRoster->RegisterNode(fVideoConsumer);
        if (fStatus != B_OK) {
                print_error("Can't register the video consumer", fStatus);
                return fStatus;
        }

        // make sure the Media Roster knows that we're using the node
//      fMediaRoster->GetNodeFor(fVideoConsumer->Node().node,
//              &fVideoConnection.consumer);
        fVideoConnection.consumer = fVideoConsumer->Node();

        // find free producer output
        media_input videoInput;
        media_output videoOutput;
        int32 count = 1;
        fStatus = fMediaRoster->GetFreeOutputsFor(fVideoConnection.producer,
                &videoOutput, 1, &count, B_MEDIA_RAW_VIDEO);
        if (fStatus != B_OK || count < 1) {
                fStatus = B_RESOURCE_UNAVAILABLE;
                print_error("Can't find an available video stream", fStatus);
                return fStatus;
        }

        // find free consumer input
        count = 1;
        fStatus = fMediaRoster->GetFreeInputsFor(fVideoConnection.consumer,
                &videoInput, 1, &count, B_MEDIA_RAW_VIDEO);
        if (fStatus != B_OK || count < 1) {
                fStatus = B_RESOURCE_UNAVAILABLE;
                print_error("Can't find an available connection to the video window",
                        fStatus);
                return fStatus;
        }

        // connect the nodes
        media_format format;
        format.type = B_MEDIA_RAW_VIDEO;
        media_raw_video_format videoFormat = {
                FramesPerSecond(), 1, 0,
                (uint32)fVideoBounds.IntegerWidth(),
                B_VIDEO_TOP_LEFT_RIGHT, 1, 1,
                {
                        preferredVideoFormat,
                        (uint32)(fVideoBounds.IntegerWidth() + 1),
                        (uint32)(fVideoBounds.IntegerHeight() + 1),
                        0, 0, 0
                }
        };
        format.u.raw_video = videoFormat;

        // connect video producer to consumer (hopefully using overlays)
        fVideoConsumer->SetTryOverlay(useOverlays);
        fStatus = fMediaRoster->Connect(videoOutput.source, videoInput.destination,
                &format, &videoOutput, &videoInput);

        if (fStatus != B_OK) {
                print_error("Can't connect the video source to the video window... "
                        "trying without overlays", fStatus);

                uint32 flags = 0;
                bool supported = bitmaps_support_space(
                        format.u.raw_video.display.format, &flags);
                if (!supported || (flags & B_VIEWS_SUPPORT_DRAW_BITMAP) == 0) {
                        // cannot create bitmaps with such a color space
                        // or BViews don't support drawing it, fallback to B_RGB32
                        format.u.raw_video.display.format = B_RGB32;
                        printf("NodeManager::_SetupVideoNodes() - falling back to "
                                "B_RGB32\n");
                }

                fVideoConsumer->SetTryOverlay(false);
                // connect video producer to consumer (not using overlays and using
                // a colorspace that BViews support drawing)
                fStatus = fMediaRoster->Connect(videoOutput.source,
                        videoInput.destination, &format, &videoOutput, &videoInput);
        }
        // bail if second attempt failed too
        if (fStatus != B_OK) {
                print_error("Can't connect the video source to the video window",
                        fStatus);
                return fStatus;
        }

        // the inputs and outputs might have been reassigned during the
        // nodes' negotiation of the Connect().  That's why we wait until
        // after Connect() finishes to save their contents.
        fVideoConnection.format = format;
        fVideoConnection.source = videoOutput.source;
        fVideoConnection.destination = videoInput.destination;
        fVideoConnection.connected = true;

        // set time sources
        fStatus = fMediaRoster->SetTimeSourceFor(fVideoConnection.producer.node,
                fTimeSource.node);
        if (fStatus != B_OK) {
                print_error("Can't set the timesource for the video source", fStatus);
                return fStatus;
        }

        fStatus = fMediaRoster->SetTimeSourceFor(fVideoConsumer->ID(),
                fTimeSource.node);
        if (fStatus != B_OK) {
                print_error("Can't set the timesource for the video window", fStatus);
                return fStatus;
        }

        return fStatus;
}


status_t
NodeManager::_SetUpAudioNodes(float audioFrameRate, uint32 audioChannels)
{
        fAudioProducer = new AudioProducer("MediaPlayer audio out", fAudioSupplier);
        fAudioProducer->SetPeakListener(fPeakListener);
        fStatus = fMediaRoster->RegisterNode(fAudioProducer);
        if (fStatus != B_OK) {
                print_error("unable to register audio producer node!\n", fStatus);
                return fStatus;
        }
        // make sure the Media Roster knows that we're using the node
//      fMediaRoster->GetNodeFor(fAudioProducer->Node().node,
//                                                       &fAudioConnection.producer);
        fAudioConnection.producer = fAudioProducer->Node();

        // connect to the mixer
        fStatus = fMediaRoster->GetAudioMixer(&fAudioConnection.consumer);
        if (fStatus != B_OK) {
                print_error("unable to get the system mixer", fStatus);
                return fStatus;
        }

        fMediaRoster->SetTimeSourceFor(fAudioConnection.producer.node,
                fTimeSource.node);

        // got the nodes; now we find the endpoints of the connection
        media_input mixerInput;
        media_output soundOutput;
        int32 count = 1;
        fStatus = fMediaRoster->GetFreeOutputsFor(fAudioConnection.producer,
                &soundOutput, 1, &count);
        if (fStatus != B_OK) {
                print_error("unable to get a free output from the producer node",
                        fStatus);
                return fStatus;
        }
        count = 1;
        fStatus = fMediaRoster->GetFreeInputsFor(fAudioConnection.consumer,
                &mixerInput, 1, &count);
        if (fStatus != B_OK) {
                print_error("unable to get a free input to the mixer", fStatus);
                return fStatus;
        }

        // got the endpoints; now we connect it!
        media_format audioFormat;
        audioFormat.type = B_MEDIA_RAW_AUDIO;
        audioFormat.u.raw_audio = media_raw_audio_format::wildcard;
        audioFormat.u.raw_audio.frame_rate = audioFrameRate;
        audioFormat.u.raw_audio.channel_count = audioChannels;
        fStatus = fMediaRoster->Connect(soundOutput.source, mixerInput.destination,
                &audioFormat, &soundOutput, &mixerInput);
        if (fStatus != B_OK) {
                print_error("unable to connect audio nodes", fStatus);
                return fStatus;
        }

        // the inputs and outputs might have been reassigned during the
        // nodes' negotiation of the Connect().  That's why we wait until
        // after Connect() finishes to save their contents.
        fAudioConnection.format = audioFormat;
        fAudioConnection.source = soundOutput.source;
        fAudioConnection.destination = mixerInput.destination;
        fAudioConnection.connected = true;

        // Set an appropriate run mode for the producer
        fMediaRoster->SetRunModeNode(fAudioConnection.producer,
                BMediaNode::B_INCREASE_LATENCY);

        return fStatus;
}


status_t
NodeManager::_TearDownNodes(bool disconnect)
{
        TRACE("NodeManager::_TearDownNodes()\n");
        status_t err = B_OK;
        fMediaRoster = BMediaRoster::Roster(&err);
        if (err != B_OK) {
                fprintf(stderr, "NodeManager::_TearDownNodes() - error getting media "
                        "roster: %s\n", strerror(err));
                fMediaRoster = NULL;
        }

        if (fVideoConsumer && fVideoProducer && fVideoConnection.connected) {
                // disconnect
                if (fMediaRoster) {
                TRACE("  disconnecting video...\n");
                        err = fMediaRoster->Disconnect(fVideoConnection.producer.node,
                                fVideoConnection.source, fVideoConnection.consumer.node,
                                fVideoConnection.destination);
                        if (err < B_OK)
                                print_error("unable to disconnect video nodes", err);
                } else {
                        fprintf(stderr, "NodeManager::_TearDownNodes() - cannot "
                                "disconnect video nodes, no media server!\n");
                }
                fVideoConnection.connected = false;
        }
        if (fVideoProducer) {
                TRACE("  releasing video producer...\n");
                fVideoProducer->Release();
                fVideoProducer = NULL;
        }
        if (fVideoConsumer) {
                TRACE("  releasing video consumer...\n");
                fVideoConsumer->Release();
                fVideoConsumer = NULL;
        }
        if (fAudioProducer) {
                disconnect = fAudioConnection.connected;
                // Ordinarily we'd stop *all* of the nodes in the chain at this point.
                // However, one of the nodes is the System Mixer, and stopping the
                // Mixer is a  Bad Idea (tm). So, we just disconnect from it, and
                // release our references to the nodes that we're using.  We *are*
                // supposed to do that even for global nodes like the Mixer.
                if (fMediaRoster != NULL && disconnect) {
                        TRACE("  disconnecting audio...\n");
                        err = fMediaRoster->Disconnect(fAudioConnection.producer.node,
                                fAudioConnection.source, fAudioConnection.consumer.node,
                                fAudioConnection.destination);
                        if (err < B_OK) {
                                print_error("unable to disconnect audio nodes", err);
                                disconnect = false;
                        }
                } else {
                        fprintf(stderr, "NodeManager::_TearDownNodes() - cannot "
                                "disconnect audio nodes, no media server!\n");
                }

                TRACE("  releasing audio producer...\n");
                fAudioProducer->Release();
                fAudioProducer = NULL;
                fAudioConnection.connected = false;

                if (fMediaRoster != NULL && disconnect) {
                        TRACE("  releasing audio consumer...\n");
                        fMediaRoster->ReleaseNode(fAudioConnection.consumer);
                } else {
                        fprintf(stderr, "NodeManager::_TearDownNodes() - cannot release "
                                "audio consumer (system mixer)!\n");
                }
        }

        TRACE("NodeManager::_TearDownNodes() done\n");
        return err;
}


status_t
NodeManager::_StartNodes()
{
        status_t status = B_NO_INIT;
        if (!fMediaRoster)
                return status;

        bigtime_t latency = 0;
        bigtime_t initLatency = 0;
        if (fVideoProducer && fVideoConsumer) {
                // figure out what recording delay to use
                status = fMediaRoster->GetLatencyFor(fVideoConnection.producer,
                        &latency);
                if (status < B_OK) {
                        print_error("error getting latency for video producer",
                                status);
                } else
                        TRACE("video latency: %lld\n", latency);
                status = fMediaRoster->SetProducerRunModeDelay(
                        fVideoConnection.producer, latency);
                if (status < B_OK) {
                        print_error("error settings run mode delay for video producer",
                                status);
                }

                // start the nodes
                status = fMediaRoster->GetInitialLatencyFor(
                        fVideoConnection.producer, &initLatency);
                if (status < B_OK) {
                        print_error("error getting initial latency for video producer",
                                status);
                }
        }
        initLatency += estimate_max_scheduling_latency();

        if (fAudioProducer) {
                // TODO: was this supposed to be added to initLatency?!?
                bigtime_t audioLatency = 0;
                status = fMediaRoster->GetLatencyFor(fAudioConnection.producer,
                        &audioLatency);
                TRACE("audio latency: %lld\n", audioLatency);
        }

        BTimeSource* timeSource;
        if (fVideoProducer) {
                timeSource = fMediaRoster->MakeTimeSourceFor(
                        fVideoConnection.producer);
        } else {
                timeSource = fMediaRoster->MakeTimeSourceFor(
                        fAudioConnection.producer);
        }
        bool running = timeSource->IsRunning();

        // workaround for people without sound cards
        // because the system time source won't be running
        bigtime_t real = BTimeSource::RealTime();
        if (!running) {
                status = fMediaRoster->StartTimeSource(fTimeSource, real);
                if (status != B_OK) {
                        timeSource->Release();
                        print_error("cannot start time source!", status);
                        return status;
                }
                status = fMediaRoster->SeekTimeSource(fTimeSource, 0, real);
                if (status != B_OK) {
                        timeSource->Release();
                        print_error("cannot seek time source!", status);
                        return status;
                }
        }

        bigtime_t perf = timeSource->PerformanceTimeFor(real + latency
                + initLatency);

        timeSource->Release();

        // start the nodes
        if (fVideoProducer && fVideoConsumer) {
                status = fMediaRoster->StartNode(fVideoConnection.consumer, perf);
                if (status != B_OK) {
                        print_error("Can't start the video consumer", status);
                        return status;
                }
                status = fMediaRoster->StartNode(fVideoConnection.producer, perf);
                if (status != B_OK) {
                        print_error("Can't start the video producer", status);
                        return status;
                }
        }

        if (fAudioProducer) {
                status = fMediaRoster->StartNode(fAudioConnection.producer, perf);
                if (status != B_OK) {
                        print_error("Can't start the audio producer", status);
                        return status;
                }
        }

        fPerformanceTimeBase = perf;

        return status;
}


void
NodeManager::_StopNodes()
{
        TRACE("NodeManager::_StopNodes()\n");
        fMediaRoster = BMediaRoster::Roster();
        if (fMediaRoster != NULL) {
                // begin mucking with the media roster
                if (fVideoProducer != NULL) {
                        TRACE("  stopping video producer...\n");
                        fMediaRoster->StopNode(fVideoConnection.producer, 0, true);
                }
                if (fAudioProducer != NULL) {
                        TRACE("  stopping audio producer...\n");
                        fMediaRoster->StopNode(fAudioConnection.producer, 0, true);
                                // synchronous stop
                }
                if (fVideoConsumer != NULL) {
                        TRACE("  stopping video consumer...\n");
                        fMediaRoster->StopNode(fVideoConnection.consumer, 0, true);
                }
                TRACE("  all nodes stopped\n");
        }
        TRACE("NodeManager::_StopNodes() done\n");
}