root/src/kits/media/MediaEventLooper.cpp
/*
 * Copyright (c) 2015 Dario Casalinuovo <b.vitruvio@gmail.com>
 * Copyright (c) 2002, 2003 Marcus Overhagen <Marcus@Overhagen.de>
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files or portions
 * thereof (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge,
 * publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so, subject
 * to the following conditions:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 *  * Redistributions in binary form must reproduce the above copyright notice
 *    in the  binary, as well as this list of conditions and the following
 *    disclaimer in the documentation and/or other materials provided with
 *    the distribution.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

#include <MediaEventLooper.h>
#include <TimeSource.h>
#include <scheduler.h>
#include <Buffer.h>
#include <ServerInterface.h>
#include "MediaDebug.h"

/*************************************************************
 * protected BMediaEventLooper
 *************************************************************/

/* virtual */
BMediaEventLooper::~BMediaEventLooper()
{
        CALLED();

        // don't call Quit(); here, except if the user was stupid
        if (fControlThread != -1) {
                printf("You MUST call BMediaEventLooper::Quit() in your destructor!\n");
                Quit();
        }
}

/* explicit */
BMediaEventLooper::BMediaEventLooper(uint32 apiVersion) :
        BMediaNode("called by BMediaEventLooper"),
        fControlThread(-1),
        fCurrentPriority(B_URGENT_PRIORITY),
        fSetPriority(B_URGENT_PRIORITY),
        fRunState(B_UNREGISTERED),
        fEventLatency(0),
        fSchedulingLatency(0),
        fBufferDuration(0),
        fOfflineTime(0),
        fApiVersion(apiVersion)
{
        CALLED();
        fEventQueue.SetCleanupHook(BMediaEventLooper::_CleanUpEntry, this);
        fRealTimeQueue.SetCleanupHook(BMediaEventLooper::_CleanUpEntry, this);
}

/* virtual */ void
BMediaEventLooper::NodeRegistered()
{
        CALLED();
        // Calling Run() should be done by the derived class,
        // at least that's how it is documented by the BeBook.
        // It appears that BeOS R5 called it here. Calling Run()
        // twice doesn't hurt, and some nodes need it to be called here.
        Run();
}


/* virtual */ void
BMediaEventLooper::Start(bigtime_t performance_time)
{
        CALLED();
        // This hook function is called when a node is started
        // by a call to the BMediaRoster. The specified
        // performanceTime, the time at which the node
        // should start running, may be in the future.
        fEventQueue.AddEvent(media_timed_event(performance_time, BTimedEventQueue::B_START));
}


/* virtual */ void
BMediaEventLooper::Stop(bigtime_t performance_time,
                                                bool immediate)
{
        CALLED();
        // This hook function is called when a node is stopped
        // by a call to the BMediaRoster. The specified performanceTime,
        // the time at which the node should stop, may be in the future.
        // If immediate is true, your node should ignore the performanceTime
        // value and synchronously stop performance. When Stop() returns,
        // you're promising not to write into any BBuffers you may have
        // received from your downstream consumers, and you promise not
        // to send any more buffers until Start() is called again.

        if (immediate) {
                // always be sure to add to the front of the queue so we can make sure it is
                // handled before any buffers are sent!
                performance_time = 0;
        }
        fEventQueue.AddEvent(media_timed_event(performance_time, BTimedEventQueue::B_STOP));
}


/* virtual */ void
BMediaEventLooper::Seek(bigtime_t media_time,
                                                bigtime_t performance_time)
{
        CALLED();
        // This hook function is called when a node is asked to seek to
        // the specified mediaTime by a call to the BMediaRoster.
        // The specified performanceTime, the time at which the node
        // should begin the seek operation, may be in the future.
        fEventQueue.AddEvent(media_timed_event(performance_time, BTimedEventQueue::B_SEEK, NULL,
                BTimedEventQueue::B_NO_CLEANUP, 0, media_time, NULL));
}


/* virtual */ void
BMediaEventLooper::TimeWarp(bigtime_t at_real_time,
                                                        bigtime_t to_performance_time)
{
        CALLED();
        // This hook function is called when the time source to which the
        // node is slaved is repositioned (via a seek operation) such that
        // there will be a sudden jump in the performance time progression
        // as seen by the node. The to_performance_time argument indicates
        // the new performance time; the change should occur at the real
        // time specified by the at_real_time argument.

        // place in the realtime queue
        fRealTimeQueue.AddEvent(media_timed_event(at_real_time, BTimedEventQueue::B_WARP,
                NULL, BTimedEventQueue::B_NO_CLEANUP, 0, to_performance_time, NULL));

        // BeBook: Your implementation of TimeWarp() should call through to BMediaNode::TimeWarp()
        // BeBook: as well as all other inherited forms of TimeWarp()
        // XXX should we do this here?
        BMediaNode::TimeWarp(at_real_time, to_performance_time);
}


/* virtual */ status_t
BMediaEventLooper::AddTimer(bigtime_t at_performance_time,
                                                        int32 cookie)
{
        CALLED();

        media_timed_event event(at_performance_time,
                BTimedEventQueue::B_TIMER, NULL,
                BTimedEventQueue::B_EXPIRE_TIMER);
        event.data = cookie;
        return EventQueue()->AddEvent(event);
}


/* virtual */ void
BMediaEventLooper::SetRunMode(run_mode mode)
{
        CALLED();
        // The SetRunMode() hook function is called when someone requests that your node's run mode be changed.

        // bump or reduce priority when switching from/to offline run mode
        int32 priority;
        priority = (mode == B_OFFLINE) ? min_c(B_NORMAL_PRIORITY, fSetPriority) : fSetPriority;
        if (priority != fCurrentPriority) {
                fCurrentPriority = priority;
                if (fControlThread > 0) {
                        set_thread_priority(fControlThread, fCurrentPriority);
                        fSchedulingLatency = estimate_max_scheduling_latency(fControlThread);
                        printf("BMediaEventLooper: SchedulingLatency is %" B_PRId64 "\n",
                                fSchedulingLatency);
                }
        }

        BMediaNode::SetRunMode(mode);
}


/* virtual */ void
BMediaEventLooper::CleanUpEvent(const media_timed_event *event)
{
        CALLED();
        // Implement this function to clean up after custom events you've created
        // and added to your queue. It's called when a custom event is removed from
        // the queue, to let you handle any special tidying-up that the event might require.
}


/* virtual */ bigtime_t
BMediaEventLooper::OfflineTime()
{
        CALLED();
        return fOfflineTime;
}


/* virtual */ void
BMediaEventLooper::ControlLoop()
{
        CALLED();

        status_t err = B_OK;
        bigtime_t waitUntil = B_INFINITE_TIMEOUT;
        bool hasRealtime = false;
        bool hasEvent = false;

        // While there are no events or it is not time for the earliest event,
        // process messages using WaitForMessages. Whenever this funtion times out,
        // we need to handle the next event

        fSchedulingLatency = estimate_max_scheduling_latency(fControlThread);
        while (RunState() != B_QUITTING) {
                if (err == B_TIMED_OUT
                                || err == B_WOULD_BLOCK) {
                        // NOTE: The reference for doing the lateness calculus this way can
                        // be found in the BeBook article "A BMediaEventLooper Example".
                        // The value which we are going to calculate, is referred there as
                        // 'lateness'.
                        media_timed_event event;
                        if (hasEvent)
                                err = fEventQueue.RemoveFirstEvent(&event);
                        else if (hasRealtime)
                                err = fRealTimeQueue.RemoveFirstEvent(&event);

                        if (err == B_OK) {
                                // The general idea of lateness is to allow
                                // the client code to detect when the buffer
                                // is handled late or early.
                                bigtime_t lateness = TimeSource()->RealTime() - waitUntil;

                                DispatchEvent(&event, lateness, hasRealtime);
                        }
                } else if (err != B_OK)
                        return;

                // BMediaEventLooper compensates your performance time by adding
                // the event latency (see SetEventLatency()) and the scheduling
                // latency (or, for real-time events, only the scheduling latency).

                hasRealtime = fRealTimeQueue.HasEvents();
                hasEvent = fEventQueue.HasEvents();

                if (hasEvent) {
                        waitUntil = TimeSource()->RealTimeFor(
                                fEventQueue.FirstEventTime(),
                                fEventLatency + fSchedulingLatency);
                } else if (!hasRealtime)
                        waitUntil = B_INFINITE_TIMEOUT;

                if (hasRealtime) {
                        bigtime_t realtimeWait = fRealTimeQueue.FirstEventTime()
                                - fSchedulingLatency;

                        if (!hasEvent || realtimeWait <= waitUntil) {
                                waitUntil = realtimeWait;
                                hasEvent = false;
                        } else
                                hasRealtime = false;
                }

                if (waitUntil != B_INFINITE_TIMEOUT
                                && TimeSource()->RealTime() >= waitUntil) {
                        err = WaitForMessage(0);
                } else
                        err = WaitForMessage(waitUntil);
        }
}


thread_id
BMediaEventLooper::ControlThread()
{
        CALLED();
        return fControlThread;
}

/*************************************************************
 * protected BMediaEventLooper
 *************************************************************/


BTimedEventQueue *
BMediaEventLooper::EventQueue()
{
        CALLED();
        return &fEventQueue;
}


BTimedEventQueue *
BMediaEventLooper::RealTimeQueue()
{
        CALLED();
        return &fRealTimeQueue;
}


int32
BMediaEventLooper::Priority() const
{
        CALLED();
        return fCurrentPriority;
}


int32
BMediaEventLooper::RunState() const
{
        PRINT(6, "CALLED BMediaEventLooper::RunState()\n");
        return fRunState;
}


bigtime_t
BMediaEventLooper::EventLatency() const
{
        CALLED();
        return fEventLatency;
}


bigtime_t
BMediaEventLooper::BufferDuration() const
{
        CALLED();
        return fBufferDuration;
}


bigtime_t
BMediaEventLooper::SchedulingLatency() const
{
        CALLED();
        return fSchedulingLatency;
}


status_t
BMediaEventLooper::SetPriority(int32 priority)
{
        CALLED();

        // clamp to a valid value
        if (priority < 5)
                priority = 5;

        if (priority > 120)
                priority = 120;

        fSetPriority = priority;
        fCurrentPriority = (RunMode() == B_OFFLINE) ? min_c(B_NORMAL_PRIORITY, fSetPriority) : fSetPriority;

        if (fControlThread > 0) {
                set_thread_priority(fControlThread, fCurrentPriority);
                fSchedulingLatency = estimate_max_scheduling_latency(fControlThread);
                printf("BMediaEventLooper: SchedulingLatency is %" B_PRId64 "\n",
                        fSchedulingLatency);
        }

        return B_OK;
}


void
BMediaEventLooper::SetRunState(run_state state)
{
        CALLED();

        // don't allow run state changes while quitting,
        // also needed for correct terminating of the ControlLoop()
        if (fRunState == B_QUITTING && state != B_TERMINATED)
                return;

        fRunState = state;
}


void
BMediaEventLooper::SetEventLatency(bigtime_t latency)
{
        CALLED();
        // clamp to a valid value
        if (latency < 0)
                latency = 0;

        fEventLatency = latency;
        write_port_etc(ControlPort(), GENERAL_PURPOSE_WAKEUP, 0, 0, B_TIMEOUT, 0);
}


void
BMediaEventLooper::SetBufferDuration(bigtime_t duration)
{
        CALLED();

        if (duration < 0)
                duration = 0;

        fBufferDuration = duration;
}


void
BMediaEventLooper::SetOfflineTime(bigtime_t offTime)
{
        CALLED();
        fOfflineTime = offTime;
}


void
BMediaEventLooper::Run()
{
        CALLED();

        if (fControlThread != -1)
                return; // thread already running

        // until now, the run state is B_UNREGISTERED, but we need to start in B_STOPPED state.
        SetRunState(B_STOPPED);

        char threadName[32];
        sprintf(threadName, "%.20s control", Name());
        fControlThread = spawn_thread(_ControlThreadStart, threadName, fCurrentPriority, this);
        resume_thread(fControlThread);

        // get latency information
        fSchedulingLatency = estimate_max_scheduling_latency(fControlThread);
}


void
BMediaEventLooper::Quit()
{
        CALLED();

        if (fRunState == B_TERMINATED)
                return;

        SetRunState(B_QUITTING);
        close_port(ControlPort());
        if (fControlThread != -1) {
                status_t err;
                wait_for_thread(fControlThread, &err);
                fControlThread = -1;
        }
        SetRunState(B_TERMINATED);
}


void
BMediaEventLooper::DispatchEvent(const media_timed_event *event,
                                                                 bigtime_t lateness,
                                                                 bool realTimeEvent)
{
        PRINT(6, "CALLED BMediaEventLooper::DispatchEvent()\n");

        HandleEvent(event, lateness, realTimeEvent);

        switch (event->type) {
                case BTimedEventQueue::B_START:
                        SetRunState(B_STARTED);
                        break;

                case BTimedEventQueue::B_STOP:
                        SetRunState(B_STOPPED);
                        break;

                case BTimedEventQueue::B_SEEK:
                        /* nothing */
                        break;

                case BTimedEventQueue::B_WARP:
                        /* nothing */
                        break;

                case BTimedEventQueue::B_TIMER:
                        TimerExpired(event->event_time, event->data);
                        break;

                default:
                        break;
        }

        _DispatchCleanUp(event);
}

/*************************************************************
 * private BMediaEventLooper
 *************************************************************/


/* static */ int32
BMediaEventLooper::_ControlThreadStart(void *arg)
{
        CALLED();
        ((BMediaEventLooper *)arg)->SetRunState(B_STOPPED);
        ((BMediaEventLooper *)arg)->ControlLoop();
        ((BMediaEventLooper *)arg)->SetRunState(B_QUITTING);
        return 0;
}


/* static */ void
BMediaEventLooper::_CleanUpEntry(const media_timed_event *event,
                                                                 void *context)
{
        PRINT(6, "CALLED BMediaEventLooper::_CleanUpEntry()\n");
        ((BMediaEventLooper *)context)->_DispatchCleanUp(event);
}


void
BMediaEventLooper::_DispatchCleanUp(const media_timed_event *event)
{
        PRINT(6, "CALLED BMediaEventLooper::_DispatchCleanUp()\n");

        // this function to clean up after custom events you've created
        if (event->cleanup >= BTimedEventQueue::B_USER_CLEANUP)
                CleanUpEvent(event);
}

/*
// unimplemented
BMediaEventLooper::BMediaEventLooper(const BMediaEventLooper &)
BMediaEventLooper &BMediaEventLooper::operator=(const BMediaEventLooper &)
*/

/*************************************************************
 * protected BMediaEventLooper
 *************************************************************/


status_t
BMediaEventLooper::DeleteHook(BMediaNode *node)
{
        CALLED();
        // this is the DeleteHook that gets called by the media server
        // before the media node is deleted
        Quit();
        return BMediaNode::DeleteHook(node);
}

/*************************************************************
 * private BMediaEventLooper
 *************************************************************/

status_t BMediaEventLooper::_Reserved_BMediaEventLooper_0(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_1(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_2(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_3(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_4(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_5(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_6(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_7(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_8(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_9(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_10(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_11(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_12(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_13(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_14(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_15(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_16(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_17(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_18(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_19(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_20(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_21(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_22(int32 arg,...) { return B_ERROR; }
status_t BMediaEventLooper::_Reserved_BMediaEventLooper_23(int32 arg,...) { return B_ERROR; }