root/src/add-ons/media/media-add-ons/esound_sink/ESDSinkNode.cpp
/*
 * ESounD media addon for BeOS
 *
 * Copyright (c) 2006 François Revol (revol@free.fr)
 * 
 * Based on Multi Audio addon for Haiku,
 * Copyright (c) 2002, 2003 Jerome Duval (jerome.duval@free.fr)
 *
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *
 * - 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,
 *   this list of conditions and the following disclaimer in the documentation 
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */
//#define DEBUG 4
#include <MediaDefs.h>
#include <MediaNode.h>
#include <MediaAddOn.h>
#include <BufferConsumer.h>
#include <FileInterface.h>
#include <Controllable.h>
#include <MediaEventLooper.h>
#include <File.h>
#include <Errors.h>
#include <Entry.h>
#include <BufferGroup.h>
#include <TimeSource.h>
#include <Buffer.h>
#include <ParameterWeb.h>
#include <MediaRoster.h>
#include <limits.h>
#include <MediaDefs.h>
#include <Message.h>

#include "ESDSinkNode.h"
#include "ESDEndpoint.h"
#ifdef DEBUG
  #define PRINTING
#endif
#include "debug.h"
#include <Debug.h>

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


// -------------------------------------------------------- //
// ctor/dtor
// -------------------------------------------------------- //

ESDSinkNode::~ESDSinkNode(void)
{
        CALLED();
        fAddOn->GetConfigurationFor(this, NULL);
                
        BMediaEventLooper::Quit();
                
        fWeb = NULL;
        delete fDevice;
}

ESDSinkNode::ESDSinkNode(BMediaAddOn *addon, char* name, BMessage * config)
        : BMediaNode(name),
          BBufferConsumer(B_MEDIA_RAW_AUDIO),
#if ENABLE_INPUT
          BBufferProducer(B_MEDIA_RAW_AUDIO),
#endif
#ifdef ENABLE_TS
          BTimeSource(),
#endif
          BMediaEventLooper(),
          fThread(-1),
          fDevice(NULL),
          fTimeSourceStarted(false),
          fWeb(NULL),
          fConfig(*config)
{
        CALLED();
        fInitCheckStatus = B_NO_INIT;
        
        fAddOn = addon;
        fId = 0;
        
        AddNodeKind( B_PHYSICAL_OUTPUT );
#if ENABLE_INPUT
        AddNodeKind( B_PHYSICAL_INPUT );
#endif
                
        // initialize our preferred format object
        memset(&fPreferredFormat, 0, sizeof(fPreferredFormat)); // set everything to wildcard first
        fPreferredFormat.type = B_MEDIA_RAW_AUDIO;
#if ESD_FMT == 8
        fPreferredFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_UCHAR;
#else
        fPreferredFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_SHORT;
#endif
        fPreferredFormat.u.raw_audio.valid_bits = 0;
        fPreferredFormat.u.raw_audio.channel_count = 2;
        fPreferredFormat.u.raw_audio.frame_rate = ESD_DEFAULT_RATE;
        fPreferredFormat.u.raw_audio.byte_order = B_MEDIA_HOST_ENDIAN;
        
        // we'll use the consumer's preferred buffer size, if any
        fPreferredFormat.u.raw_audio.buffer_size = ESD_MAX_BUF / 4
/*                                              * (fPreferredFormat.u.raw_audio.format & media_raw_audio_format::B_AUDIO_SIZE_MASK)
                                                * fPreferredFormat.u.raw_audio.channel_count*/;
        
        if(config) {
                //PRINT_OBJECT(*config);
                config->FindString("hostname", &fHostname);
        }
        if (fHostname.Length() < 1)
                fHostname = "172.20.109.151";//"192.168.0.2";
        fPort = ESD_DEFAULT_PORT;
        fEnabled = false;
        
        fDevice = new ESDEndpoint();
        /*
        if (fDevice) {
                if (fDevice->Connect(fHostname.String()) >= 0) {
                        fDevice->SetCommand();
                        fDevice->SetFormat(ESD_FMT, 2);
                        //fDevice->GetServerInfo();
                        fInitCheckStatus = fDevice->SendDefaultCommand();
                }
        }
        */
        if (!fDevice)
                return;
        fInitCheckStatus = B_OK;
}

status_t ESDSinkNode::InitCheck(void) const
{
        CALLED();
        return fInitCheckStatus;
}


// -------------------------------------------------------- //
// implementation of BMediaNode
// -------------------------------------------------------- //

BMediaAddOn * ESDSinkNode::AddOn(
                                int32 * internal_id) const
{
        CALLED();
        // BeBook says this only gets called if we were in an add-on.
        if (fAddOn != 0) {
                // If we get a null pointer then we just won't write.
                if (internal_id != 0) {
                        *internal_id = fId;
                }
        }
        return fAddOn;
}

void ESDSinkNode::Preroll(void)
{
        CALLED();
        // XXX:Performance opportunity
        BMediaNode::Preroll();
}

status_t ESDSinkNode::HandleMessage(
                                int32 message,
                                const void * data,
                                size_t size)
{
        CALLED();
        return B_ERROR;
}

void ESDSinkNode::NodeRegistered(void)
{
        CALLED();
        
        if (fInitCheckStatus != B_OK) {
                ReportError(B_NODE_IN_DISTRESS);
                return;
        }
        
//      media_input *input = new media_input;

        fInput.format = fPreferredFormat;
        fInput.destination.port = ControlPort();
        fInput.destination.id = 0;
        fInput.node = Node();
        sprintf(fInput.name, "output %ld", fInput.destination.id);
                
        fOutput.format = fPreferredFormat;
        fOutput.destination = media_destination::null;
        fOutput.source.port = ControlPort();
        fOutput.source.id = 0;
        fOutput.node = Node();
        sprintf(fOutput.name, "input %ld", fOutput.source.id);
                
        // Set up our parameter web
        fWeb = MakeParameterWeb();
        SetParameterWeb(fWeb);
        
        /* apply configuration */
#ifdef PRINTING
        bigtime_t start = system_time();
#endif
                
        int32 index = 0;
        int32 parameterID = 0;
        const void *data;
        ssize_t size;
        while(fConfig.FindInt32("parameterID", index, &parameterID) == B_OK) {
                if(fConfig.FindData("parameterData", B_RAW_TYPE, index, &data, &size) == B_OK)
                        SetParameterValue(parameterID, TimeSource()->Now(), data, size);
                index++;
        }
        
#ifdef PRINTING
        PRINT(("apply configuration in : %lld\n", system_time() - start));
#endif

        SetPriority(B_REAL_TIME_PRIORITY);
        Run();
}

status_t ESDSinkNode::RequestCompleted(const media_request_info &info)
{
        CALLED();
        return B_OK;
}

void ESDSinkNode::SetTimeSource(BTimeSource *timeSource)
{
        CALLED();
}

// -------------------------------------------------------- //
// implemention of BBufferConsumer
// -------------------------------------------------------- //

// Check to make sure the format is okay, then remove
// any wildcards corresponding to our requirements.
status_t ESDSinkNode::AcceptFormat(
                                const media_destination & dest,
                                media_format * format)
{
        status_t err;
        CALLED();
        
        if(fInput.destination != dest) {
                fprintf(stderr,"<- B_MEDIA_BAD_DESTINATION");
                return B_MEDIA_BAD_DESTINATION; // we only have one input so that better be it
        }
        
/*      media_format * myFormat = GetFormat();
        fprintf(stderr,"proposed format: ");
        print_media_format(format);
        fprintf(stderr,"\n");
        fprintf(stderr,"my format: ");
        print_media_format(myFormat);
        fprintf(stderr,"\n");*/
        // Be's format_is_compatible doesn't work.
//      if (!format_is_compatible(*format,*myFormat)) {
        
        if ( format->type != B_MEDIA_RAW_AUDIO ) {
                fprintf(stderr,"<- B_MEDIA_BAD_FORMAT\n");
                return B_MEDIA_BAD_FORMAT;
        }       
        
        /*if(format->u.raw_audio.format == media_raw_audio_format::B_AUDIO_FLOAT
                && channel->fPreferredFormat.u.raw_audio.format == media_raw_audio_format::B_AUDIO_SHORT)
                format->u.raw_audio.format = media_raw_audio_format::B_AUDIO_FLOAT;
        else*/ 
        format->u.raw_audio.format = fPreferredFormat.u.raw_audio.format;
        format->u.raw_audio.valid_bits = fPreferredFormat.u.raw_audio.valid_bits;
        
        format->u.raw_audio.frame_rate    = fPreferredFormat.u.raw_audio.frame_rate;
        format->u.raw_audio.channel_count = fPreferredFormat.u.raw_audio.channel_count;
        format->u.raw_audio.byte_order    = B_MEDIA_HOST_ENDIAN;
        format->u.raw_audio.buffer_size   = ESD_MAX_BUF / 4
/*                                              * (format->u.raw_audio.format & media_raw_audio_format::B_AUDIO_SIZE_MASK)
                                                * format->u.raw_audio.channel_count*/;
        
        
        /*media_format myFormat;
        GetFormat(&myFormat);
        if (!format_is_acceptible(*format,myFormat)) {
                fprintf(stderr,"<- B_MEDIA_BAD_FORMAT\n");
                return B_MEDIA_BAD_FORMAT;
        }*/
        //AddRequirements(format);
        
        // start connecting here
        err = fDevice->Connect(fHostname.String(), fPort);
        
        return B_OK;    
}

status_t ESDSinkNode::GetNextInput(
                                int32 * cookie,
                                media_input * out_input)
{
        CALLED();
        
        if ((*cookie < 1) && (*cookie >= 0)) {
                *out_input = fInput;
                *cookie += 1;
                PRINT(("input.format : %lu\n", fInput.format.u.raw_audio.format));
                return B_OK;
        } else
                return B_BAD_INDEX;
}

void ESDSinkNode::DisposeInputCookie(
                                int32 cookie)
{
        CALLED();
        // nothing to do since our cookies are just integers
}

void ESDSinkNode::BufferReceived(
                                BBuffer * buffer)
{
        CALLED();
        switch (buffer->Header()->type) {
                /*case B_MEDIA_PARAMETERS:
                        {
                        status_t status = ApplyParameterData(buffer->Data(),buffer->SizeUsed());
                        if (status != B_OK) {
                                fprintf(stderr,"ApplyParameterData in ESDSinkNode::BufferReceived failed\n");
                        }                       
                        buffer->Recycle();
                        }
                        break;*/
                case B_MEDIA_RAW_AUDIO:
#if 0
                        if (buffer->Flags() & BBuffer::B_SMALL_BUFFER) {
                                fprintf(stderr,"NOT IMPLEMENTED: B_SMALL_BUFFER in ESDSinkNode::BufferReceived\n");
                                // XXX: implement this part
                                buffer->Recycle();                      
                        } else {
                                media_timed_event event(buffer->Header()->start_time, BTimedEventQueue::B_HANDLE_BUFFER,
                                                                                buffer, BTimedEventQueue::B_RECYCLE_BUFFER);
                                status_t status = EventQueue()->AddEvent(event);
                                if (status != B_OK) {
                                        fprintf(stderr,"EventQueue()->AddEvent(event) in ESDSinkNode::BufferReceived failed\n");
                                        buffer->Recycle();
                                }
                        }
#endif
                        if (fDevice->CanSend()) {
                                
                                fDevice->Write(buffer->Data(), buffer->SizeUsed());
                                
                        }
                        buffer->Recycle();
                        break;
                default: 
                        fprintf(stderr,"unexpected buffer type in ESDSinkNode::BufferReceived\n");
                        buffer->Recycle();
                        break;
        }
}

void ESDSinkNode::ProducerDataStatus(
                                const media_destination & for_whom,
                                int32 status,
                                bigtime_t at_performance_time)
{
        CALLED();
        
        if(fInput.destination != for_whom) {
                fprintf(stderr,"invalid destination received in ESDSinkNode::ProducerDataStatus\n");
                return;
        }
        
        media_timed_event event(at_performance_time, BTimedEventQueue::B_DATA_STATUS,
                        &fInput, BTimedEventQueue::B_NO_CLEANUP, status, 0, NULL);
        EventQueue()->AddEvent(event);  
}

status_t ESDSinkNode::GetLatencyFor(
                                const media_destination & for_whom,
                                bigtime_t * out_latency,
                                media_node_id * out_timesource)
{
        CALLED();
        if ((out_latency == 0) || (out_timesource == 0)) {
                fprintf(stderr,"<- B_BAD_VALUE\n");
                return B_BAD_VALUE;
        }
        
        if(fInput.destination != for_whom) {
                fprintf(stderr,"<- B_MEDIA_BAD_DESTINATION\n");
                return B_MEDIA_BAD_DESTINATION;
        }
        
        bigtime_t intl = EventLatency();
        bigtime_t netl = 0LL;
        if (fDevice)
                netl = fDevice->Latency();
        // I don't want to swap
        if (netl > 500000)
                netl = 500000;
        *out_latency = intl + netl;
        fprintf(stderr, "int latency %lld, net latency %lld, total latency %lld\n", intl, netl, *out_latency);
        *out_timesource = TimeSource()->ID();
        return B_OK;
}

status_t ESDSinkNode::Connected(
                                const media_source & producer,  /* here's a good place to request buffer group usage */
                                const media_destination & where,
                                const media_format & with_format,
                                media_input * out_input)
{
        status_t err;
        CALLED();
        
        if(fInput.destination != where) {
                fprintf(stderr,"<- B_MEDIA_BAD_DESTINATION\n");
                return B_MEDIA_BAD_DESTINATION;
        }
        
        // 
        if (fDevice) {
                err = fDevice->WaitForConnect();
                if (err < B_OK)
                        return err;
                fDevice->SetCommand();
                //fDevice->GetServerInfo();
                fDevice->SetFormat(ESD_FMT, 2);
                err = fDevice->SendDefaultCommand();
                if (err < B_OK)
                        return err;
        }
        // use one buffer length latency
        fInternalLatency = with_format.u.raw_audio.buffer_size * 10000 / 2
                        / ( (with_format.u.raw_audio.format & media_raw_audio_format::B_AUDIO_SIZE_MASK)
                                * with_format.u.raw_audio.channel_count) 
                        / ((int32)(with_format.u.raw_audio.frame_rate / 100));
                        
        PRINT(("  internal latency = %lld\n",fInternalLatency));
        
        SetEventLatency(fInternalLatency);

        // record the agreed upon values
        fInput.source = producer;
        fInput.format = with_format;
        *out_input = fInput;
                
        return B_OK;
}

void ESDSinkNode::Disconnected(
                                const media_source & producer,
                                const media_destination & where)
{
        CALLED();

        if(fInput.destination != where) {
                fprintf(stderr,"<- B_MEDIA_BAD_DESTINATION\n");
                return;
        }
        if (fInput.source != producer) {
                fprintf(stderr,"<- B_MEDIA_BAD_SOURCE\n");
                return;
        }
                
        fInput.source = media_source::null;
        fInput.format = fPreferredFormat;
        //GetFormat(&channel->fInput.format);
        if (fDevice)
                fDevice->Disconnect();
}

        /* The notification comes from the upstream producer, so he's already cool with */
        /* the format; you should not ask him about it in here. */
status_t ESDSinkNode::FormatChanged(
                                const media_source & producer,
                                const media_destination & consumer, 
                                int32 change_tag,
                                const media_format & format)
{
        CALLED();

        if(fInput.destination != consumer) {
                fprintf(stderr,"<- B_MEDIA_BAD_DESTINATION\n");
                return B_MEDIA_BAD_DESTINATION;
        }
        if (fInput.source != producer) {
                return B_MEDIA_BAD_SOURCE;
        }
                
        return B_ERROR;
}

        /* Given a performance time of some previous buffer, retrieve the remembered tag */
        /* of the closest (previous or exact) performance time. Set *out_flags to 0; the */
        /* idea being that flags can be added later, and the understood flags returned in */
        /* *out_flags. */
status_t ESDSinkNode::SeekTagRequested(
                                const media_destination & destination,
                                bigtime_t in_target_time,
                                uint32 in_flags, 
                                media_seek_tag * out_seek_tag,
                                bigtime_t * out_tagged_time,
                                uint32 * out_flags)
{
        CALLED();
        return BBufferConsumer::SeekTagRequested(destination,in_target_time,in_flags,
                                                                                        out_seek_tag,out_tagged_time,out_flags);
}

// -------------------------------------------------------- //
// implementation for BBufferProducer
// -------------------------------------------------------- //
#if 0
status_t 
ESDSinkNode::FormatSuggestionRequested(media_type type, int32 /*quality*/, media_format* format)
{
        // FormatSuggestionRequested() is not necessarily part of the format negotiation
        // process; it's simply an interrogation -- the caller wants to see what the node's
        // preferred data format is, given a suggestion by the caller.
        CALLED();

        if (!format)
        {
                fprintf(stderr, "\tERROR - NULL format pointer passed in!\n");
                return B_BAD_VALUE;
        }

        // this is the format we'll be returning (our preferred format)
        *format = fPreferredFormat;

        // a wildcard type is okay; we can specialize it
        if (type == B_MEDIA_UNKNOWN_TYPE) type = B_MEDIA_RAW_AUDIO;

        // we only support raw audio
        if (type != B_MEDIA_RAW_AUDIO) return B_MEDIA_BAD_FORMAT;
        else return B_OK;
}

status_t 
ESDSinkNode::FormatProposal(const media_source& output, media_format* format)
{
        // FormatProposal() is the first stage in the BMediaRoster::Connect() process.  We hand
        // out a suggested format, with wildcards for any variations we support.
        CALLED();
        node_output *channel = FindOutput(output);
        
        // is this a proposal for our select output?
        if (channel == NULL)
        {
                fprintf(stderr, "ESDSinkNode::FormatProposal returning B_MEDIA_BAD_SOURCE\n");
                return B_MEDIA_BAD_SOURCE;
        }

        // we only support floating-point raw audio, so we always return that, but we
        // supply an error code depending on whether we found the proposal acceptable.
        media_type requestedType = format->type;
        *format = channel->fPreferredFormat;
        if ((requestedType != B_MEDIA_UNKNOWN_TYPE) && (requestedType != B_MEDIA_RAW_AUDIO))
        {
                fprintf(stderr, "ESDSinkNode::FormatProposal returning B_MEDIA_BAD_FORMAT\n");
                return B_MEDIA_BAD_FORMAT;
        }
        else return B_OK;               // raw audio or wildcard type, either is okay by us
}

status_t 
ESDSinkNode::FormatChangeRequested(const media_source& source, const media_destination& destination, media_format* io_format, int32* _deprecated_)
{
        CALLED();

        // we don't support any other formats, so we just reject any format changes.
        return B_ERROR;
}

status_t 
ESDSinkNode::GetNextOutput(int32* cookie, media_output* out_output)
{
        CALLED();

        if ((*cookie < fOutputs.CountItems()) && (*cookie >= 0)) {
                node_output *channel = (node_output *)fOutputs.ItemAt(*cookie);
                *out_output = channel->fOutput;
                *cookie += 1;
                return B_OK;
        } else
                return B_BAD_INDEX;
}

status_t 
ESDSinkNode::DisposeOutputCookie(int32 cookie)
{
        CALLED();
        // do nothing because we don't use the cookie for anything special
        return B_OK;
}

status_t 
ESDSinkNode::SetBufferGroup(const media_source& for_source, BBufferGroup* newGroup)
{
        CALLED();

        node_output *channel = FindOutput(for_source);
        
        // is this our output?
        if (channel == NULL)
        {
                fprintf(stderr, "ESDSinkNode::SetBufferGroup returning B_MEDIA_BAD_SOURCE\n");
                return B_MEDIA_BAD_SOURCE;
        }

        // Are we being passed the buffer group we're already using?
        if (newGroup == channel->fBufferGroup) return B_OK;

        // Ahh, someone wants us to use a different buffer group.  At this point we delete
        // the one we are using and use the specified one instead.  If the specified group is
        // NULL, we need to recreate one ourselves, and use *that*.  Note that if we're
        // caching a BBuffer that we requested earlier, we have to Recycle() that buffer
        // *before* deleting the buffer group, otherwise we'll deadlock waiting for that
        // buffer to be recycled!
        delete channel->fBufferGroup;           // waits for all buffers to recycle
        if (newGroup != NULL)
        {
                // we were given a valid group; just use that one from now on
                channel->fBufferGroup = newGroup;
        }
        else
        {
                // we were passed a NULL group pointer; that means we construct
                // our own buffer group to use from now on
                size_t size = channel->fOutput.format.u.raw_audio.buffer_size;
                int32 count = int32(fLatency / BufferDuration() + 1 + 1);
                channel->fBufferGroup = new BBufferGroup(size, count);
        }

        return B_OK;
}

status_t 
ESDSinkNode::PrepareToConnect(const media_source& what, const media_destination& where, media_format* format, media_source* out_source, char* out_name)
{
        // PrepareToConnect() is the second stage of format negotiations that happens
        // inside BMediaRoster::Connect().  At this point, the consumer's AcceptFormat()
        // method has been called, and that node has potentially changed the proposed
        // format.  It may also have left wildcards in the format.  PrepareToConnect()
        // *must* fully specialize the format before returning!
        CALLED();

        node_output *channel = FindOutput(what);
        
        // is this our output?
        if (channel == NULL)
        {
                fprintf(stderr, "ESDSinkNode::PrepareToConnect returning B_MEDIA_BAD_SOURCE\n");
                return B_MEDIA_BAD_SOURCE;
        }

        // are we already connected?
        if (channel->fOutput.destination != media_destination::null)
                return B_MEDIA_ALREADY_CONNECTED;

        // the format may not yet be fully specialized (the consumer might have
        // passed back some wildcards).  Finish specializing it now, and return an
        // error if we don't support the requested format.
        if (format->type != B_MEDIA_RAW_AUDIO)
        {
                fprintf(stderr, "\tnon-raw-audio format?!\n");
                return B_MEDIA_BAD_FORMAT;
        }
        
         // !!! validate all other fields except for buffer_size here, because the consumer might have
        // supplied different values from AcceptFormat()?

        // check the buffer size, which may still be wildcarded
        if (format->u.raw_audio.buffer_size == media_raw_audio_format::wildcard.buffer_size)
        {
                format->u.raw_audio.buffer_size = 2048;         // pick something comfortable to suggest
                fprintf(stderr, "\tno buffer size provided, suggesting %lu\n", format->u.raw_audio.buffer_size);
        }
        else
        {
                fprintf(stderr, "\tconsumer suggested buffer_size %lu\n", format->u.raw_audio.buffer_size);
        }

        // Now reserve the connection, and return information about it
        channel->fOutput.destination = where;
        channel->fOutput.format = *format;
        *out_source = channel->fOutput.source;
        strncpy(out_name, channel->fOutput.name, B_MEDIA_NAME_LENGTH);
        return B_OK;
}

void 
ESDSinkNode::Connect(status_t error, const media_source& source, const media_destination& destination, const media_format& format, char* io_name)
{
        CALLED();
        
        node_output *channel = FindOutput(source);
        
        // is this our output?
        if (channel == NULL)
        {
                fprintf(stderr, "ESDSinkNode::Connect returning (cause : B_MEDIA_BAD_SOURCE)\n");
                return;
        }
        
        // If something earlier failed, Connect() might still be called, but with a non-zero
        // error code.  When that happens we simply unreserve the connection and do
        // nothing else.
        if (error)
        {
                channel->fOutput.destination = media_destination::null;
                channel->fOutput.format = channel->fPreferredFormat;
                return;
        }

        // Okay, the connection has been confirmed.  Record the destination and format
        // that we agreed on, and report our connection name again.
        channel->fOutput.destination = destination;
        channel->fOutput.format = format;
        strncpy(io_name, channel->fOutput.name, B_MEDIA_NAME_LENGTH);

        // reset our buffer duration, etc. to avoid later calculations
        bigtime_t duration = channel->fOutput.format.u.raw_audio.buffer_size * 10000
                        / ( (channel->fOutput.format.u.raw_audio.format & media_raw_audio_format::B_AUDIO_SIZE_MASK)
                                * channel->fOutput.format.u.raw_audio.channel_count) 
                        / ((int32)(channel->fOutput.format.u.raw_audio.frame_rate / 100));
        
        SetBufferDuration(duration);
        
        // Now that we're connected, we can determine our downstream latency.
        // Do so, then make sure we get our events early enough.
        media_node_id id;
        FindLatencyFor(channel->fOutput.destination, &fLatency, &id);
        PRINT(("\tdownstream latency = %lld\n", fLatency));

        fInternalLatency = BufferDuration();
        PRINT(("\tbuffer-filling took %lld usec on this machine\n", fInternalLatency));
        //SetEventLatency(fLatency + fInternalLatency);

        // Set up the buffer group for our connection, as long as nobody handed us a
        // buffer group (via SetBufferGroup()) prior to this.  That can happen, for example,
        // if the consumer calls SetOutputBuffersFor() on us from within its Connected()
        // method.
        if (!channel->fBufferGroup) 
                AllocateBuffers(*channel);
                
        // we are sure the thread is started
        StartThread();
}

void 
ESDSinkNode::Disconnect(const media_source& what, const media_destination& where)
{
        CALLED();
        
        node_output *channel = FindOutput(what);
        
        // is this our output?
        if (channel == NULL)
        {
                fprintf(stderr, "ESDSinkNode::Disconnect() returning (cause : B_MEDIA_BAD_SOURCE)\n");
                return;
        }

        // Make sure that our connection is the one being disconnected
        if ((where == channel->fOutput.destination) && (what == channel->fOutput.source))
        {
                channel->fOutput.destination = media_destination::null;
                channel->fOutput.format = channel->fPreferredFormat;
                delete channel->fBufferGroup;
                channel->fBufferGroup = NULL;
        }
        else
        {
                fprintf(stderr, "\tDisconnect() called with wrong source/destination (%ld/%ld), ours is (%ld/%ld)\n",
                        what.id, where.id, channel->fOutput.source.id, channel->fOutput.destination.id);
        }
}

void 
ESDSinkNode::LateNoticeReceived(const media_source& what, bigtime_t how_much, bigtime_t performance_time)
{
        CALLED();
        
        node_output *channel = FindOutput(what);
        
        // is this our output?
        if (channel == NULL)
        {
                return;
        }

        // If we're late, we need to catch up.  Respond in a manner appropriate to our
        // current run mode.
        if (RunMode() == B_RECORDING)
        {
                // A hardware capture node can't adjust; it simply emits buffers at
                // appropriate points.  We (partially) simulate this by not adjusting
                // our behavior upon receiving late notices -- after all, the hardware
                // can't choose to capture "sooner"....
        }
        else if (RunMode() == B_INCREASE_LATENCY)
        {
                // We're late, and our run mode dictates that we try to produce buffers
                // earlier in order to catch up.  This argues that the downstream nodes are
                // not properly reporting their latency, but there's not much we can do about
                // that at the moment, so we try to start producing buffers earlier to
                // compensate.
                fInternalLatency += how_much;
                SetEventLatency(fLatency + fInternalLatency);

                fprintf(stderr, "\tincreasing latency to %lld\n", fLatency + fInternalLatency);
        }
        else
        {
                // The other run modes dictate various strategies for sacrificing data quality
                // in the interests of timely data delivery.  The way *we* do this is to skip
                // a buffer, which catches us up in time by one buffer duration.
                /*size_t nSamples = fOutput.format.u.raw_audio.buffer_size / sizeof(float);
                mSamplesSent += nSamples;*/

                fprintf(stderr, "\tskipping a buffer to try to catch up\n");
        }
}

void 
ESDSinkNode::EnableOutput(const media_source& what, bool enabled, int32* _deprecated_)
{
        CALLED();

        // If I had more than one output, I'd have to walk my list of output records to see
        // which one matched the given source, and then enable/disable that one.  But this
        // node only has one output, so I just make sure the given source matches, then set
        // the enable state accordingly.
        node_output *channel = FindOutput(what);
        
        if (channel != NULL)
        {
                channel->fOutputEnabled = enabled;
        }
}

void 
ESDSinkNode::AdditionalBufferRequested(const media_source& source, media_buffer_id prev_buffer, bigtime_t prev_time, const media_seek_tag* prev_tag)
{
        CALLED();
        // we don't support offline mode
        return;
}
#endif

// -------------------------------------------------------- //
// implementation for BMediaEventLooper
// -------------------------------------------------------- //

void ESDSinkNode::HandleEvent(
                                const media_timed_event *event,
                                bigtime_t lateness,
                                bool realTimeEvent)
{
        CALLED();
        switch (event->type) {
                case BTimedEventQueue::B_START:
                        HandleStart(event,lateness,realTimeEvent);
                        break;
                case BTimedEventQueue::B_SEEK:
                        HandleSeek(event,lateness,realTimeEvent);
                        break;
                case BTimedEventQueue::B_WARP:
                        HandleWarp(event,lateness,realTimeEvent);
                        break;
                case BTimedEventQueue::B_STOP:
                        HandleStop(event,lateness,realTimeEvent);
                        break;
                case BTimedEventQueue::B_HANDLE_BUFFER:
                        if (RunState() == BMediaEventLooper::B_STARTED) {
                                HandleBuffer(event,lateness,realTimeEvent);
                        }
                        break;
                case BTimedEventQueue::B_DATA_STATUS:
                        HandleDataStatus(event,lateness,realTimeEvent);
                        break;
                case BTimedEventQueue::B_PARAMETER:
                        HandleParameter(event,lateness,realTimeEvent);
                        break;
                default:
                        fprintf(stderr,"  unknown event type: %li\n",event->type);
                        break;
        }
}

// protected:

// how should we handle late buffers?  drop them?
// notify the producer?
status_t ESDSinkNode::HandleBuffer(
                                const media_timed_event *event,
                                bigtime_t lateness,
                                bool realTimeEvent)
{
        CALLED();
        BBuffer * buffer = const_cast<BBuffer*>((BBuffer*)event->pointer);
        if (buffer == 0) {
                fprintf(stderr,"<- B_BAD_VALUE\n");
                return B_BAD_VALUE;
        }
        
        if(fInput.destination.id != buffer->Header()->destination) {
                fprintf(stderr,"<- B_MEDIA_BAD_DESTINATION\n");
                return B_MEDIA_BAD_DESTINATION;
        }
        
        media_header* hdr = buffer->Header();
        bigtime_t now = TimeSource()->Now();
        bigtime_t perf_time = hdr->start_time;
        
        // the how_early calculate here doesn't include scheduling latency because
        // we've already been scheduled to handle the buffer
        bigtime_t how_early = perf_time - EventLatency() - now;
        
        // if the buffer is late, we ignore it and report the fact to the producer
        // who sent it to us
        if ((RunMode() != B_OFFLINE) &&                         // lateness doesn't matter in offline mode...
                (RunMode() != B_RECORDING) &&           // ...or in recording mode
                (how_early < 0LL))
        {
                //mLateBuffers++;
                NotifyLateProducer(fInput.source, -how_early, perf_time);
                fprintf(stderr,"        <- LATE BUFFER : %lli\n", how_early);
                buffer->Recycle();
        } else {
                if (fDevice->CanSend())
                        fDevice->Write(buffer->Data(), buffer->SizeUsed());
        }
        return B_OK;
}

status_t ESDSinkNode::HandleDataStatus(
                                                const media_timed_event *event,
                                                bigtime_t lateness,
                                                bool realTimeEvent)
{
        CALLED();
        PRINT(("ESDSinkNode::HandleDataStatus status:%li, lateness:%lli\n", event->data, lateness));
        switch(event->data) {
                case B_DATA_NOT_AVAILABLE:
                        break;
                case B_DATA_AVAILABLE:
                        break;
                case B_PRODUCER_STOPPED:
                        break;
                default:
                        break;
        }
        return B_OK;
}

status_t ESDSinkNode::HandleStart(
                                                const media_timed_event *event,
                                                bigtime_t lateness,
                                                bool realTimeEvent)
{
        CALLED();
        if (RunState() != B_STARTED) {
        
        }
        return B_OK;
}

status_t ESDSinkNode::HandleSeek(
                                                const media_timed_event *event,
                                                bigtime_t lateness,
                                                bool realTimeEvent)
{
        CALLED();
        PRINT(("ESDSinkNode::HandleSeek(t=%lld,d=%li,bd=%lld)\n",event->event_time,event->data,event->bigdata));
        return B_OK;
}
                                                
status_t ESDSinkNode::HandleWarp(
                                                const media_timed_event *event,
                                                bigtime_t lateness,
                                                bool realTimeEvent)
{
        CALLED();
        return B_OK;
}

status_t ESDSinkNode::HandleStop(
                                                const media_timed_event *event,
                                                bigtime_t lateness,
                                                bool realTimeEvent)
{
        CALLED();
        // flush the queue so downstreamers don't get any more
        EventQueue()->FlushEvents(0, BTimedEventQueue::B_ALWAYS, true, BTimedEventQueue::B_HANDLE_BUFFER);
        
        //StopThread();
        return B_OK;
}

status_t ESDSinkNode::HandleParameter(
                                const media_timed_event *event,
                                bigtime_t lateness,
                                bool realTimeEvent)
{
        CALLED();
        return B_OK;
}

// -------------------------------------------------------- //
// implemention of BTimeSource
// -------------------------------------------------------- //
#ifdef ENABLE_TS

void
ESDSinkNode::SetRunMode(run_mode mode)
{
        CALLED();
        PRINT(("ESDSinkNode::SetRunMode mode:%i\n", mode));
        //BTimeSource::SetRunMode(mode);
}

status_t
ESDSinkNode::TimeSourceOp(const time_source_op_info &op, void *_reserved)
{
        CALLED();
        switch(op.op) {
                case B_TIMESOURCE_START:
                        PRINT(("TimeSourceOp op B_TIMESOURCE_START\n"));
                        if (RunState() != BMediaEventLooper::B_STARTED) {
                                fTimeSourceStarted = true;
                        
                                media_timed_event startEvent(0, BTimedEventQueue::B_START);
                                EventQueue()->AddEvent(startEvent);
                        }       
                        break;
                case B_TIMESOURCE_STOP:
                        PRINT(("TimeSourceOp op B_TIMESOURCE_STOP\n"));
                        if (RunState() == BMediaEventLooper::B_STARTED) {
                                media_timed_event stopEvent(0, BTimedEventQueue::B_STOP);
                                EventQueue()->AddEvent(stopEvent);
                                fTimeSourceStarted = false;
                                PublishTime(0, 0, 1.0f);
                        }
                        break;
                case B_TIMESOURCE_STOP_IMMEDIATELY:
                        PRINT(("TimeSourceOp op B_TIMESOURCE_STOP_IMMEDIATELY\n"));
                        if (RunState() == BMediaEventLooper::B_STARTED) {
                                media_timed_event stopEvent(0, BTimedEventQueue::B_STOP);
                                EventQueue()->AddEvent(stopEvent);
                                fTimeSourceStarted = false;
                                PublishTime(0, 0, 1.0f);
                        }
                        break;
                case B_TIMESOURCE_SEEK:
                        PRINT(("TimeSourceOp op B_TIMESOURCE_SEEK\n"));
                        BroadcastTimeWarp(op.real_time, op.performance_time);
                        break;
                default:
                        break;
        }
        return B_OK;
}
#endif

// -------------------------------------------------------- //
// implemention of BControllable
// -------------------------------------------------------- //

status_t 
ESDSinkNode::GetParameterValue(int32 id, bigtime_t* last_change, void* value, size_t* ioSize)
{
        CALLED();
        if (!fDevice)
                return B_ERROR;
        //PRINT(("id : %i\n", id));
        switch (id) {
                case PARAM_ENABLED:
                        if (*ioSize < sizeof(bool))
                                return B_NO_MEMORY;
                        *(bool *)value = fEnabled;
                        *ioSize = sizeof(bool);
                        return B_OK;
                case PARAM_HOST:
                {
                        BString s = fDevice->Host();
                        *ioSize = MIN(*ioSize, s.Length());
                        memcpy(value, s.String(), *ioSize);
                        return B_OK;
                }
                case PARAM_PORT:
                {
                        BString s;
                        s << fDevice->Port();
                        *ioSize = MIN(*ioSize, s.Length());
                        memcpy(value, s.String(), *ioSize);
                        return B_OK;
                }
                default:
                        break;
        }
#if 0
        BParameter *parameter = NULL;
        for(int32 i=0; i<fWeb->CountParameters(); i++) {
                parameter = fWeb->ParameterAt(i);
                if(parameter->ID() == id)
                        break;
        }
#endif
        
return EINVAL;
}

void 
ESDSinkNode::SetParameterValue(int32 id, bigtime_t performance_time, const void* value, size_t size)
{
        CALLED();
        PRINT(("id : %li, performance_time : %lld, size : %li\n", id, performance_time, size));
        BParameter *parameter = NULL;
        for(int32 i=0; i<fWeb->CountParameters(); i++) {
                parameter = fWeb->ParameterAt(i);
                if(parameter->ID() == id)
                        break;
        }
        switch (id) {
                case PARAM_ENABLED:
                        if (size != sizeof(bool))
                                return;
                        fEnabled = *(bool *)value;
                        return;
                case PARAM_HOST:
                {
                        fprintf(stderr, "set HOST: %s\n", (const char *)value);
                        fHostname = (const char *)value;
#if 0
                        if (fDevice && fDevice->Connected()) {
                                if (fDevice->Connect(fHostname.String(), fPort) >= 0) {
                                        fDevice->SetCommand();
                                        fDevice->SetFormat(ESD_FMT, 2);
                                        //fDevice->GetServerInfo();
                                        fInitCheckStatus = fDevice->SendDefaultCommand();
                                }
                        }
#endif
                        return;
                }
                case PARAM_PORT:
                {
                        fprintf(stderr, "set PORT: %s\n", (const char *)value);
                        fPort = atoi((const char *)value);
#if 0
                        if (fDevice && fDevice->Connected()) {
                                if (fDevice->Connect(fHostname.String(), fPort) >= 0) {
                                        fDevice->SetCommand();
                                        fDevice->SetFormat(ESD_FMT, 2);
                                        //fDevice->GetServerInfo();
                                        fInitCheckStatus = fDevice->SendDefaultCommand();
                                }
                        }
#endif
                        return;
                }
                default:
                        break;
        }
}

BParameterWeb* 
ESDSinkNode::MakeParameterWeb()
{
        CALLED();
        BParameterWeb* web = new BParameterWeb;
        BParameterGroup *group = web->MakeGroup("Server");
        BParameter *p;
        // XXX: use B_MEDIA_UNKNOWN_TYPE or _NO_TYPE ?
        // keep in sync with enum { PARAM_* } !
        p = group->MakeDiscreteParameter(PARAM_ENABLED, B_MEDIA_RAW_AUDIO, "Enable", B_ENABLE);
#if defined(B_BEOS_VERSION_DANO) || defined(__HAIKU__)
        p = group->MakeTextParameter(PARAM_HOST, B_MEDIA_RAW_AUDIO, "Hostname", B_GENERIC, 128);
        p = group->MakeTextParameter(PARAM_PORT, B_MEDIA_RAW_AUDIO, "Port", B_GENERIC, 16);
#endif
        return web;
}

// -------------------------------------------------------- //
// ESDSinkNode specific functions
// -------------------------------------------------------- //

status_t
ESDSinkNode::GetConfigurationFor(BMessage * into_message)
{
        CALLED();
        
        BParameter *parameter = NULL;
        void *buffer;
        size_t size = 128;
        bigtime_t last_change;
        status_t err;
        
        if (!into_message)
                return B_BAD_VALUE;
        
        buffer = malloc(size);
        
        for(int32 i=0; i<fWeb->CountParameters(); i++) {
                parameter = fWeb->ParameterAt(i);
                if(parameter->Type() != BParameter::B_CONTINUOUS_PARAMETER
                        && parameter->Type() != BParameter::B_DISCRETE_PARAMETER)
                        continue;
                        
                PRINT(("getting parameter %li\n", parameter->ID()));
                size = 128;
                while((err = GetParameterValue(parameter->ID(), &last_change, buffer, &size))==B_NO_MEMORY) {
                        size += 128;
                        free(buffer);
                        buffer = malloc(size);
                }
                
                if(err == B_OK && size > 0) {
                        into_message->AddInt32("parameterID", parameter->ID());
                        into_message->AddData("parameterData", B_RAW_TYPE, buffer, size, false);
                } else {
                        PRINT(("parameter %li err : %s\n", parameter->ID(), strerror(err)));
                }
        }
        
        //PRINT_OBJECT(*into_message);
        
        return B_OK;
}

// static:

void ESDSinkNode::GetFlavor(flavor_info * outInfo, int32 id)
{
        CALLED();

        outInfo->flavor_flags = B_FLAVOR_IS_GLOBAL;
//      outInfo->possible_count = 0;    // any number
        outInfo->possible_count = 1;    // only 1
        outInfo->in_format_count = 0; // no inputs
        outInfo->in_formats = 0;
        outInfo->out_format_count = 0; // no outputs
        outInfo->out_formats = 0;
        outInfo->internal_id = id;
        
        outInfo->name = new char[256];
                strcpy(outInfo->name, "ESounD Out");
        outInfo->info = new char[256];
                strcpy(outInfo->info, "The ESounD Sink node outputs a network Enlightenment Sound Daemon.");
        outInfo->kinds = /*B_TIME_SOURCE | *//*B_CONTROLLABLE | */ 0;

#if ENABLE_INPUT
        outInfo->kinds |= B_BUFFER_PRODUCER | B_PHYSICAL_INPUT;
        outInfo->out_format_count = 1; // 1 output
        media_format * outformats = new media_format[outInfo->out_format_count];
        GetFormat(&outformats[0]);
        outInfo->out_formats = outformats;
#endif
        
        outInfo->kinds |= B_BUFFER_CONSUMER | B_PHYSICAL_OUTPUT;
        outInfo->in_format_count = 1; // 1 input
        media_format * informats = new media_format[outInfo->in_format_count];
        GetFormat(&informats[0]);
        outInfo->in_formats = informats;
}

void ESDSinkNode::GetFormat(media_format * outFormat)
{
        CALLED();

        outFormat->type = B_MEDIA_RAW_AUDIO;
        outFormat->require_flags = B_MEDIA_MAUI_UNDEFINED_FLAGS;
        outFormat->deny_flags = B_MEDIA_MAUI_UNDEFINED_FLAGS;   
        outFormat->u.raw_audio = media_raw_audio_format::wildcard;
}