root/src/add-ons/media/media-add-ons/AbstractFileInterfaceNode.cpp
// AbstractFileInterfaceNode.cpp
//
// Andrew Bachmann, 2002
//
// The AbstractFileInterfaceNode class implements
// the common functionality between MediaReader
// and MediaWriter.
#include "AbstractFileInterfaceNode.h"
#include "debug.h"

#include <Buffer.h>
#include <Controllable.h>
#include <Entry.h>
#include <Errors.h>
#include <File.h>
#include <FileInterface.h>
#include <MediaDefs.h>
#include <MediaEventLooper.h>
#include <MediaNode.h>
#include <TimeSource.h>
#include <ParameterWeb.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>


AbstractFileInterfaceNode::~AbstractFileInterfaceNode(void)
{
        CALLED();

        // Stop the BMediaEventLooper thread
        Quit();
        if (fCurrentFile != 0)
                delete fCurrentFile;
}


AbstractFileInterfaceNode::AbstractFileInterfaceNode(
                                size_t defaultChunkSize,
                                float defaultBitRate,
                                const flavor_info * info,
                                BMessage * config,
                                BMediaAddOn * addOn)
        : BMediaNode("AbstractFileInterfaceNode"),
          BFileInterface(),
          BControllable(),
          BMediaEventLooper()
{
        CALLED();

        // keep our creator around for AddOn calls later
        fAddOn = addOn;
        // null some fields
        fCurrentFile = 0;
        f_current_mime_type[0] = '\0';

        // initialize the parameters
        if (defaultChunkSize <= 0) {
                fInitCheckStatus = B_BAD_VALUE;
                return;
        }

        fDefaultChunkSizeParam = defaultChunkSize;
        fDefaultChunkSizeParamChangeTime = 0;
        if (defaultBitRate <= 0) {
                fInitCheckStatus = B_BAD_VALUE;
                return;
        }

        fDefaultBitRateParam = defaultBitRate;
        fDefaultBitRateParamChangeTime = 0;
        // initialize parameter compute fields
        fLeastRecentlyUpdatedParameter = DEFAULT_BIT_RATE_PARAM;
        fLastUpdatedParameter = DEFAULT_BUFFER_PERIOD_PARAM;    
        // From the chunk size and bit rate we compute the buffer period.
        int64 value = int64(8000000/1024*fDefaultChunkSizeParam/fDefaultBitRateParam);
        if ((value <= 0) || (value > INT_MAX)) {
                fInitCheckStatus = B_BAD_VALUE;
                return;
        }
        fDefaultBufferPeriodParam = int32(value);
        fDefaultBufferPeriodParamChangeTime = 0;
        
        fInitCheckStatus = B_OK;
}


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


status_t AbstractFileInterfaceNode::GetConfigurationFor(
                                BMessage * into_message)
{
        CALLED();
        return B_OK;
}


// -------------------------------------------------------- //
// implementation of BMediaNode
// -------------------------------------------------------- //
BMediaAddOn * AbstractFileInterfaceNode::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 = 0;
        }
        return fAddOn;
}


void AbstractFileInterfaceNode::Start(
                                bigtime_t performance_time)
{
        PRINT("AbstractFileInterfaceNode::Start(pt=%lld)\n",performance_time);
        BMediaEventLooper::Start(performance_time);
}


void AbstractFileInterfaceNode::Stop(
                                bigtime_t performance_time,
                                bool immediate)
{
        PRINT("AbstractFileInterfaceNode::Stop(pt=%lld, im=%d)\n",
                  performance_time, immediate);
        BMediaEventLooper::Stop(performance_time,immediate);
}


void AbstractFileInterfaceNode::Seek(
                                bigtime_t media_time,
                                bigtime_t performance_time)
{
        PRINT("AbstractFileInterfaceNode::Seek(mt=%lld,pt=%lld)\n",
                  media_time,performance_time);
        BMediaEventLooper::Seek(media_time,performance_time);
}


void AbstractFileInterfaceNode::SetRunMode(
                                run_mode mode)
{
        PRINT("AbstractFileInterfaceNode::SetRunMode(%i)\n",mode);
        BMediaEventLooper::SetRunMode(mode);
}


void AbstractFileInterfaceNode::TimeWarp(
                                bigtime_t at_real_time,
                                bigtime_t to_performance_time)
{
        PRINT("AbstractFileInterfaceNode::TimeWarp(rt=%lld,pt=%lld)\n",
                  at_real_time,to_performance_time);
        BMediaEventLooper::TimeWarp(at_real_time,to_performance_time);
}


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


void AbstractFileInterfaceNode::SetTimeSource(
                                BTimeSource * time_source)
{
        CALLED();
        BMediaNode::SetTimeSource(time_source);
}


status_t AbstractFileInterfaceNode::HandleMessage(
                                int32 message,
                                const void * data,
                                size_t size)
{
        CALLED();

        status_t status = B_OK;
        switch (message) {
                // no special messages for now
                default:
                        status = BFileInterface::HandleMessage(message,data,size);
                        if (status == B_OK) {
                                break;
                        }
                        status = BControllable::HandleMessage(message, data, size);
                        if (status == B_OK)
                                break;
                        status = BMediaNode::HandleMessage(message,data,size);
                        if (status == B_OK) {
                                break;
                        }
                        BMediaNode::HandleBadMessage(message,data,size);
                        status = B_ERROR;
                        break;
        }
        return status;
}


status_t AbstractFileInterfaceNode::RequestCompleted(
                                const media_request_info & info)
{
        CALLED();
        return BMediaNode::RequestCompleted(info);
}


status_t AbstractFileInterfaceNode::DeleteHook(
                                BMediaNode * node)
{
        CALLED();
        return BMediaEventLooper::DeleteHook(node);
}


void AbstractFileInterfaceNode::NodeRegistered(void)
{
        CALLED();
        
        // set up our parameter web
        SetParameterWeb(MakeParameterWeb());
        
        // start the BMediaEventLooper thread
        SetPriority(B_REAL_TIME_PRIORITY);
        Run();
}


status_t AbstractFileInterfaceNode::GetNodeAttributes(
                                media_node_attribute * outAttributes,
                                size_t inMaxCount)
{
        CALLED();
        return BMediaNode::GetNodeAttributes(outAttributes,inMaxCount);
}


status_t AbstractFileInterfaceNode::AddTimer(
                                        bigtime_t at_performance_time,
                                        int32 cookie)
{
        CALLED();
        return BMediaEventLooper::AddTimer(at_performance_time,cookie);
}


// protected:
BParameterWeb * AbstractFileInterfaceNode::MakeParameterWeb(void)
{
        CALLED();
        
        BParameterWeb * web = new BParameterWeb();
        BParameterGroup * mainGroup = web->MakeGroup("AbstractFileInterfaceNode Parameters");

        // these three are related:
        // DEFAULT_CHUNK_SIZE_PARAM = 
        // DEFAULT_BIT_RATE_PARAM / 1024 * DEFAULT_BUFFER_PERIOD_PARAM * 1000
        BParameterGroup * chunkSizeGroup = mainGroup->MakeGroup("Chunk Size Group");
        BContinuousParameter * chunkSizeParameter
           = chunkSizeGroup->MakeContinuousParameter(
             DEFAULT_CHUNK_SIZE_PARAM, B_MEDIA_MULTISTREAM,
                 "Chunk Size", B_GAIN, "bytes", 1024, 32*1024, 512);
        chunkSizeParameter->SetResponse(BContinuousParameter::B_LINEAR,1,0);
        chunkSizeParameter->SetValue(&fDefaultChunkSizeParam,sizeof(fDefaultChunkSizeParam),0);
        
        BParameterGroup * bitRateGroup = mainGroup->MakeGroup("Bit Rate Group");
        BContinuousParameter * bitRateParameter
           = bitRateGroup->MakeContinuousParameter(
             DEFAULT_BIT_RATE_PARAM, B_MEDIA_MULTISTREAM,
             "Bit Rate", B_GAIN, "kbits/sec", 1, 320000, 1);
        bitRateParameter->SetResponse(BContinuousParameter::B_LINEAR,.001,0);
        bitRateParameter->SetValue(&fDefaultBitRateParam,sizeof(fDefaultBitRateParam),0);
        
        BParameterGroup * bufferPeriodGroup = mainGroup->MakeGroup("Buffer Period Group");
        BContinuousParameter * bufferPeriodParameter
           = bufferPeriodGroup->MakeContinuousParameter(
             DEFAULT_BUFFER_PERIOD_PARAM, B_MEDIA_MULTISTREAM,
             "Buffer Period", B_GAIN, "ms", 1, 10000, 1);
        bufferPeriodParameter->SetResponse(BContinuousParameter::B_LINEAR,1,0);
        bufferPeriodParameter->SetValue(&fDefaultBufferPeriodParam,sizeof(fDefaultBufferPeriodParam),0);
        
        return web;
}


// -------------------------------------------------------- //
// implementation of BFileInterface
// -------------------------------------------------------- //
status_t AbstractFileInterfaceNode::GetNextFileFormat(
                                int32 * cookie,
                                media_file_format * out_format)
{
        CALLED();

        // it's valid but they already got our 1 file format
        if (*cookie != 0) {
                PRINT("\t<- B_ERROR\n");
                return B_ERROR;
        }

        // so next time they won't get the same format again
        *cookie = 1;
        GetFileFormat(out_format);
        return B_OK;
}


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


status_t AbstractFileInterfaceNode::GetDuration(
                                bigtime_t * out_time)
{
        CALLED();
        if (out_time == 0) {
                PRINT("\t<- B_BAD_VALUE\n");
                return B_BAD_VALUE;
        }

        if (fCurrentFile == 0) {
                PRINT("\t<- B_NO_INIT\n");
                return B_NO_INIT;
        }       

        return fCurrentFile->GetSize(out_time);
}


status_t AbstractFileInterfaceNode::SniffRef(
                                const entry_ref & file,
                                char * out_mime_type,   /* 256 bytes */
                                float * out_quality)
{
        CALLED();
        return StaticSniffRef(file,out_mime_type,out_quality);
}


status_t AbstractFileInterfaceNode::SetRef(
                                const entry_ref & file,
                                uint32 openMode,
                                bool create,
                                bigtime_t * out_time)
{
        CALLED();

        status_t status;
        f_current_ref = file;
        if (fCurrentFile == 0) {
                fCurrentFile = new BFile(&f_current_ref,(openMode|(create?B_CREATE_FILE:0)));
                status = fCurrentFile->InitCheck();
        } else {
                status = fCurrentFile->SetTo(&f_current_ref,(openMode|(create?B_CREATE_FILE:0)));
        }

        if (status != B_OK) {
                PRINT("\t<- failed BFile initialization\n");
                return status;
        }

        // cache the mime type for later
        fCurrentFile->ReadAttr("BEOS:TYPE",0,0,f_current_mime_type,B_MIME_TYPE_LENGTH);
        // compute the duration and return any error
        return GetDuration(out_time);
}


status_t AbstractFileInterfaceNode::GetRef(
                                entry_ref * out_ref,
                                char * out_mime_type)
{
        CALLED();

        if (fCurrentFile == 0) {
                PRINT("\t<- B_NO_INIT\n");
                return B_NO_INIT; // the input_ref isn't valid yet either
        }

        *out_ref = f_current_ref;
        // they hopefully allocated enough space (no way to check :-/ )
        strcpy(out_mime_type,f_current_mime_type);
        return B_OK;
}


// provided for BAbstractFileInterfaceNodeAddOn
status_t AbstractFileInterfaceNode::StaticSniffRef(
                                const entry_ref & file,
                                char * out_mime_type,   /* 256 bytes */
                                float * out_quality)
{
        CALLED();

        BNode node(&file);
        status_t initCheck = node.InitCheck();
        if (initCheck != B_OK) {
                PRINT("\t<- failed BNode::InitCheck()\n");
                return initCheck;
        }

        // they hopefully allocated enough room
        node.ReadAttr("BEOS:TYPE",0,0,out_mime_type,B_MIME_TYPE_LENGTH);
        *out_quality = 1.0; // we handle all files perfectly!  we are so amazing!
        return B_OK;
}


// -------------------------------------------------------- //
// implementation for BControllable
// -------------------------------------------------------- //
const int32 AbstractFileInterfaceNode::DEFAULT_CHUNK_SIZE_PARAM = 1;
const int32 AbstractFileInterfaceNode::DEFAULT_BIT_RATE_PARAM = 2;
const int32 AbstractFileInterfaceNode::DEFAULT_BUFFER_PERIOD_PARAM = 3;


status_t AbstractFileInterfaceNode::GetParameterValue(
                                int32 id,
                                bigtime_t * last_change,
                                void * value,
                                size_t * ioSize)
{
        CALLED();

        switch (id) {
                case DEFAULT_CHUNK_SIZE_PARAM:
                        if (*ioSize < sizeof(size_t)) {
                                return B_ERROR; // not enough room
                        }
                        *last_change = fDefaultChunkSizeParamChangeTime;
                        *((size_t*)value) = fDefaultChunkSizeParam;
                        *ioSize = sizeof(size_t);
                        break;
                        
                case DEFAULT_BIT_RATE_PARAM:
                        if (*ioSize < sizeof(float)) {
                                return B_ERROR; // not enough room
                        }
                        *last_change = fDefaultBitRateParamChangeTime;
                        *((float*)value) = fDefaultBitRateParam;
                        *ioSize = sizeof(float);
                        break;
                
                case DEFAULT_BUFFER_PERIOD_PARAM:
                        if (*ioSize < sizeof(int32)) {
                                return B_ERROR; // not enough room
                        }
                        *last_change = fDefaultBufferPeriodParamChangeTime;
                        *((int32*)value) = fDefaultBufferPeriodParam;
                        *ioSize = sizeof(int32);
                        break;
                                
                default:
                        PRINT("AbstractFileInterfaceNode::GetParameterValue unknown id (%ld)\n",id);
                        return B_ERROR;
        }

        return B_OK;
}


void AbstractFileInterfaceNode::SetParameterValue(
                                int32 id,
                                bigtime_t when,
                                const void * value,
                                size_t size)
{
        PRINT("AbstractFileInterfaceNode::SetParameterValue(id=%ld,when=%lld,size=%ld)\n",id,when,int32(size));

        switch (id) {
                case DEFAULT_CHUNK_SIZE_PARAM:
                case DEFAULT_BIT_RATE_PARAM:
                case DEFAULT_BUFFER_PERIOD_PARAM:
                        {
                                media_timed_event event(when, BTimedEventQueue::B_PARAMETER,
                                                                                NULL, BTimedEventQueue::B_NO_CLEANUP,
                                                                                size, id, (char*) value, size);
                                EventQueue()->AddEvent(event);
                        }
                        break;
                        
                default:
                        PRINT("AbstractFileInterfaceNode::SetParameterValue unknown id (%ld)\n",id);
                        break;
        }
}                       


// the default implementation should call the add-on main()
status_t AbstractFileInterfaceNode::StartControlPanel(
                                BMessenger * out_messenger)
{
        CALLED();
        return BControllable::StartControlPanel(out_messenger);
}


// -------------------------------------------------------- //
// implementation for BMediaEventLooper
// -------------------------------------------------------- //
void AbstractFileInterfaceNode::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:
                        PRINT("  unknown event type: %ld\n",event->type);
                        break;
        }
}


/* override to clean up custom events you have added to your queue */
void AbstractFileInterfaceNode::CleanUpEvent(
                                const media_timed_event *event)
{
        BMediaEventLooper::CleanUpEvent(event);
}


/* called from Offline mode to determine the current time of the node */
/* update your internal information whenever it changes */
bigtime_t AbstractFileInterfaceNode::OfflineTime()
{
        CALLED();
        return BMediaEventLooper::OfflineTime();
        // XXX: do something else?
        //      if (inputFile == 0) {
        //              return 0;
        //      } else {
        //              return inputFile->Position();
        //      }
}


/* override only if you know what you are doing! */
/* otherwise much badness could occur */
/* the actual control loop function: */
/*      waits for messages, Pops events off the queue and calls DispatchEvent */
void AbstractFileInterfaceNode::ControlLoop() {
        BMediaEventLooper::ControlLoop();
}


// protected:
status_t AbstractFileInterfaceNode::HandleStart(
                                                const media_timed_event *event,
                                                bigtime_t lateness,
                                                bool realTimeEvent)
{
        CALLED();

        if (RunState() != B_STARTED) {
                // XXX: Either use the following line or the lines that are not commented.
                // There doesn't seem to be a practical difference that i can tell.
                //              HandleBuffer(event,lateness,realTimeEvent);
                media_timed_event firstBufferEvent(event->event_time, BTimedEventQueue::B_HANDLE_BUFFER);
                HandleEvent(&firstBufferEvent, 0, false);
                EventQueue()->AddEvent(firstBufferEvent);
        }
        return B_OK;
}


status_t AbstractFileInterfaceNode::HandleSeek(
                                                const media_timed_event *event,
                                                bigtime_t lateness,
                                                bool realTimeEvent)
{
        PRINT("AbstractFileInterfaceNode::HandleSeek(t=%lld,d=%ld,bd=%lld)\n",event->event_time,event->data,event->bigdata);

        if (fCurrentFile != 0)
                return fCurrentFile->Seek(event->bigdata,SEEK_SET);
        else
                return B_ERROR;
}
                                                

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


status_t AbstractFileInterfaceNode::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);
        return B_OK;
}


status_t AbstractFileInterfaceNode::HandleParameter(
                                const media_timed_event *event,
                                bigtime_t lateness,
                                bool realTimeEvent)
{
        CALLED();

        status_t status = B_OK;

        bool chunkSizeUpdated = false, bitRateUpdated = false, bufferPeriodUpdated = false;

        size_t dataSize = size_t(event->data);
        int64 param = int64(event->bigdata);

        switch (param) {
                case DEFAULT_CHUNK_SIZE_PARAM:
                        PRINT("(DEFAULT_CHUNK_SIZE_PARAM,size=%ld",dataSize);
                        if (dataSize < sizeof(size_t)) {
                                PRINT("\ลง<- B_BAD_VALUE: %lld\n",param);
                                status = B_BAD_VALUE;
                        } else {
                                size_t newDefaultChunkSize = *((size_t*)event->user_data);
                                PRINT(",%ld)\n", newDefaultChunkSize);
                                // ignore non positive chunk sizes
                                // XXX: we may decide later that a 0 chunk size means ship the whole file in one chunk (!)
                                if ((newDefaultChunkSize > 0) && (newDefaultChunkSize != fDefaultChunkSizeParam)) {
                                        PRINT("\tgot a new chunk size, old chunk size was %ld\n",fDefaultChunkSizeParam);
                                        fDefaultChunkSizeParam = newDefaultChunkSize;
                                        fDefaultChunkSizeParamChangeTime = TimeSource()->Now();
                                        chunkSizeUpdated = true;
                                        if (fLeastRecentlyUpdatedParameter == DEFAULT_CHUNK_SIZE_PARAM) {
                                                // Okay we were the least recently updated parameter,
                                                // but we just got an update so we are no longer that.
                                                // Let's figure out who the new least recently updated
                                                // parameter is.  We are going to prefer to compute the
                                                // bit rate since you usually don't want to muck with
                                                // the buffer period.  However, if you just set the bitrate
                                                // then we are stuck with making the buffer period the new
                                                // parameter to be computed.
                                                if (fLastUpdatedParameter == DEFAULT_BIT_RATE_PARAM)
                                                        fLeastRecentlyUpdatedParameter = DEFAULT_BUFFER_PERIOD_PARAM;
                                                else
                                                        fLeastRecentlyUpdatedParameter = DEFAULT_BIT_RATE_PARAM;
                                        }
                                        // we just got an update, so we are the new lastUpdatedParameter
                                        fLastUpdatedParameter = DEFAULT_CHUNK_SIZE_PARAM;
                                        // now we have to compute the new value for the leastRecentlyUpdatedParameter
                                        // we use the chunk size change time to preserve "simultaneity" information
                                        if (fLeastRecentlyUpdatedParameter == DEFAULT_BUFFER_PERIOD_PARAM) {
                                                int64 value = int64(8000000/1024*fDefaultChunkSizeParam/fDefaultBitRateParam);
                                                if (value > INT_MAX) {
                                                        // clamp to INT_MAX
                                                        fDefaultBufferPeriodParam = INT_MAX;
                                                        // recompute chunk size
                                                        fDefaultChunkSizeParam = size_t(1024/8000000*fDefaultBitRateParam*fDefaultBufferPeriodParam);
                                                } else {
                                                        fDefaultBufferPeriodParam = MAX(1,value);
                                                }                                                       
                                                fDefaultBufferPeriodParamChangeTime = fDefaultChunkSizeParamChangeTime;
                                                bufferPeriodUpdated = true;
                                        } else { // must have been bit rate
                                                fDefaultBitRateParam = MAX(0.001,8000000/1024*fDefaultChunkSizeParam/fDefaultBufferPeriodParam);
                                                fDefaultBitRateParamChangeTime = fDefaultChunkSizeParamChangeTime;
                                                bitRateUpdated = true;
                                        }
                                }
                        }
                        break;          
                case DEFAULT_BIT_RATE_PARAM:
                        PRINT("(DEFAULT_BIT_RATE_PARAM,size=%ld",dataSize);
                        if (dataSize < sizeof(float)) {
                                PRINT("\t<- B_BAD_VALUE:lld\n",param);
                                status = B_BAD_VALUE;
                        } else {
                                float newDefaultBitRate = *((float*)event->user_data);
                                PRINT(",%f)\n",newDefaultBitRate);
                                // ignore non positive bitrates
                                if ((newDefaultBitRate > 0) && (newDefaultBitRate != fDefaultBitRateParam)) {
                                        PRINT("\tgot a new bit rate, old bit rate was %ld\n",fDefaultBitRateParam);
                                        fDefaultBitRateParam = newDefaultBitRate;
                                        fDefaultBitRateParamChangeTime = TimeSource()->Now();
                                        bitRateUpdated = true;
                                        if (fLeastRecentlyUpdatedParameter == DEFAULT_BIT_RATE_PARAM) {
                                                // Okay we were the least recently updated parameter,
                                                // but we just got an update so we are no longer that.
                                                // Let's figure out who the new least recently updated
                                                // parameter is.  We are going to prefer to compute the
                                                // chunk size since you usually don't want to muck with
                                                // the buffer period.  However, if you just set the chunk size
                                                // then we are stuck with making the buffer period the new
                                                // parameter to be computed.
                                                if (fLastUpdatedParameter == DEFAULT_CHUNK_SIZE_PARAM) {
                                                        fLeastRecentlyUpdatedParameter = DEFAULT_BUFFER_PERIOD_PARAM;
                                                } else {
                                                        fLeastRecentlyUpdatedParameter = DEFAULT_CHUNK_SIZE_PARAM;
                                                }
                                        }
                                        // we just got an update, so we are the new lastUpdatedParameter
                                        fLastUpdatedParameter = DEFAULT_BIT_RATE_PARAM;
                                        // now we have to compute the new value for the leastRecentlyUpdatedParameter
                                        // we use the bit rate change time to preserve "simultaneity" information
                                        if (fLeastRecentlyUpdatedParameter == DEFAULT_BUFFER_PERIOD_PARAM) {
                                                int64 value = 
                                                                int64(8000000/1024*fDefaultChunkSizeParam/fDefaultBitRateParam);
                                                if (value > INT_MAX) {
                                                        // clamp to INT_MAX
                                                        fDefaultBufferPeriodParam = INT_MAX;
                                                        // recompute bit rate
                                                        fDefaultBitRateParam = MAX(0.001,
                                                                                8000000/1024*fDefaultChunkSizeParam/fDefaultBufferPeriodParam);
                                                } else {
                                                        fDefaultBufferPeriodParam = MAX(1,int32(value));
                                                }
                                                fDefaultBufferPeriodParamChangeTime = fDefaultBitRateParamChangeTime;
                                                bufferPeriodUpdated = true;
                                        } else { // must have been chunk size
                                                int64 value = 
                                                                int64(1024/8000000*fDefaultBitRateParam*fDefaultBufferPeriodParam);
                                                if (value > INT_MAX) {
                                                        // clamp to INT_MAX
                                                        fDefaultChunkSizeParam = INT_MAX;
                                                        // recompute bit rate
                                                        fDefaultBitRateParam = 
                                                                        MAX(0.001,8000000/1024*fDefaultChunkSizeParam/fDefaultBufferPeriodParam);
                                                } else {
                                                        fDefaultChunkSizeParam = MAX(1,int32(value));
                                                }
                                                fDefaultChunkSizeParamChangeTime = fDefaultBitRateParamChangeTime;
                                                chunkSizeUpdated = true;
                                        }
                                }
                        }
                        break;          
                case DEFAULT_BUFFER_PERIOD_PARAM:
                        PRINT("(DEFAULT_BUFFER_PERIOD_PARAM,size=%ld",dataSize);
                        if (dataSize < sizeof(int32)) {
                                PRINT("\t<- B_BAD_VALUE:%ld\n",param);
                                status = B_BAD_VALUE;
                        } else {
                                int32 newBufferPeriod = *((int32*)event->user_data);
                                PRINT(",%ld)\n",newBufferPeriod);
                                // ignore non positive buffer period
                                if ((newBufferPeriod > 0) && (newBufferPeriod != fDefaultBufferPeriodParam)) {
                                        PRINT("\tgot a new buffer period, old buffer period was %ld\n",
                                                  fDefaultBufferPeriodParam);
                                        fDefaultBufferPeriodParam = newBufferPeriod;
                                        fDefaultBufferPeriodParamChangeTime = TimeSource()->Now();
                                        bufferPeriodUpdated = true;
                                        if (fLastUpdatedParameter == DEFAULT_BUFFER_PERIOD_PARAM) {
                                                // prefer to update bit rate, unless you just set it
                                                if (fLastUpdatedParameter == DEFAULT_BIT_RATE_PARAM) {
                                                        fLeastRecentlyUpdatedParameter = DEFAULT_CHUNK_SIZE_PARAM;
                                                } else {
                                                        fLeastRecentlyUpdatedParameter = DEFAULT_BIT_RATE_PARAM;
                                                }
                                        }
                                        // we just got an update, so we are the new lastUpdatedParameter
                                        fLastUpdatedParameter = DEFAULT_BUFFER_PERIOD_PARAM;
                                        // now we have to compute the new value for the leastRecentlyUpdatedParameter
                                        // we use the buffer period change time to preserve "simultaneity" information
                                        if (fLeastRecentlyUpdatedParameter == DEFAULT_BIT_RATE_PARAM) {
                                                fDefaultBitRateParam = 
                                                                MAX(0.001,8000000/1024*fDefaultChunkSizeParam/fDefaultBufferPeriodParam);
                                                fDefaultBitRateParamChangeTime = fDefaultBufferPeriodParamChangeTime;
                                                bitRateUpdated = true;
                                        } else { // must have been chunk size
                                                int64 value = int64(1024/8000000*fDefaultBitRateParam*fDefaultBufferPeriodParam);
                                                if (value > INT_MAX) {
                                                        // clamp to INT_MAX
                                                        fDefaultChunkSizeParam = INT_MAX;
                                                        // recompute buffer period
                                                        fDefaultBufferPeriodParam = 
                                                                        size_t(8000000/1024*fDefaultChunkSizeParam/fDefaultBitRateParam);
                                                } else {
                                                        fDefaultChunkSizeParam = MAX(1,int32(value));
                                                }
                                                fDefaultChunkSizeParamChangeTime = fDefaultBufferPeriodParamChangeTime;
                                                chunkSizeUpdated = true;
                                        }                                       
                                }
                        }
                        break;  
                default:
                        PRINT("AbstractFileInterfaceNode::HandleParameter called with unknown param id (%ld)\n",param);
                        status = B_ERROR;
        }
        // send updates out for all the parameters that changed
        // in every case this should be two updates. (if I have not made an error :-) )
        if (chunkSizeUpdated) {
                PRINT("\tchunk size parameter updated\n");
                BroadcastNewParameterValue(fDefaultChunkSizeParamChangeTime,
                                                                   DEFAULT_CHUNK_SIZE_PARAM,
                                                                   &fDefaultChunkSizeParam,
                                                                   sizeof(fDefaultChunkSizeParam));
        }
        if (bitRateUpdated) {
                PRINT("\tbit rate parameter updated\n");
                BroadcastNewParameterValue(fDefaultBitRateParamChangeTime,
                                                                   DEFAULT_BIT_RATE_PARAM,
                                                                   &fDefaultBitRateParam,
                                                                   sizeof(fDefaultBitRateParam));
        }
        if (bufferPeriodUpdated) {
                PRINT("\tbuffer period parameter updated\n");
                BroadcastNewParameterValue(fDefaultBufferPeriodParamChangeTime,
                                                                   DEFAULT_BUFFER_PERIOD_PARAM,
                                                                   &fDefaultBufferPeriodParam,
                                                                   sizeof(fDefaultBufferPeriodParam));
        }
        return status;
}


// -------------------------------------------------------- //
// AbstractFileInterfaceNode specific functions
// -------------------------------------------------------- //
void AbstractFileInterfaceNode::GetFlavor(flavor_info * info, int32 id)
{
        CALLED();

        if (info == 0)
                return;

        info->name = strdup("AbstractFileInterfaceNode");
        info->info = strdup("A AbstractFileInterfaceNode node handles a file.");
        info->kinds = B_FILE_INTERFACE | B_CONTROLLABLE;
        info->flavor_flags = B_FLAVOR_IS_LOCAL;
        info->possible_count = INT_MAX;
        info->in_format_count = 0; // no inputs
        info->in_formats = 0;
        info->out_format_count = 0; // no outputs
        info->out_formats = 0;
        info->internal_id = id;
        return;
}


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

        if (outFormat == 0)
                return;

        outFormat->type = B_MEDIA_MULTISTREAM;
        outFormat->require_flags = B_MEDIA_MAUI_UNDEFINED_FLAGS;
        outFormat->deny_flags = B_MEDIA_MAUI_UNDEFINED_FLAGS;   
        outFormat->u.multistream = media_multistream_format::wildcard;
}


void AbstractFileInterfaceNode::GetFileFormat(media_file_format * outFileFormat)
{
        CALLED();

        if (outFileFormat == 0)
                return;

        outFileFormat->capabilities =
                          media_file_format::B_PERFECTLY_SEEKABLE
                        | media_file_format::B_IMPERFECTLY_SEEKABLE
                        | media_file_format::B_KNOWS_ANYTHING;
        /* I don't know what to initialize this to. (or if I should) */
        // format.id =
        outFileFormat->family = B_ANY_FORMAT_FAMILY;
        outFileFormat->version = 100;
        // see media_file_format in <MediaDefs.h> for limits
        strncpy(outFileFormat->mime_type,"",63);
        outFileFormat->mime_type[63]='\0';
        strncpy(outFileFormat->pretty_name,"any media file format",63);
        outFileFormat->pretty_name[63]='\0';
        strncpy(outFileFormat->short_name,"any",31);
        outFileFormat->short_name[31]='\0';
        strncpy(outFileFormat->file_extension,"",7);
        outFileFormat->file_extension[7]='\0';  
}


// protected:
// Here we make some guesses based on the file's mime type.
// We don't have enough information to add any other requirements.
// This function doesn't complain if you have already decided you want
// the stream to be considered a different one. (so you can say that you
// want to read that mpeg file as avi if you are so nutty.)
//
status_t AbstractFileInterfaceNode::AddRequirements(media_format * format)
{
        if (strcmp("video/x-msvideo",f_current_mime_type) == 0) {
                if (format->u.multistream.format == media_multistream_format::wildcard.format) {
                        format->u.multistream.format = media_multistream_format::B_AVI;
                }
        } else
        if (strcmp("video/mpeg",f_current_mime_type) == 0) {
                if (format->u.multistream.format == media_multistream_format::wildcard.format) {
                        format->u.multistream.format = media_multistream_format::B_MPEG1;
                }
        } else
        if (strcmp("video/quicktime",f_current_mime_type) == 0) {
                if (format->u.multistream.format == media_multistream_format::wildcard.format) {
                        format->u.multistream.format = media_multistream_format::B_QUICKTIME;
                }
        } else
        if (strcmp("audio/x-mpeg",f_current_mime_type) == 0) {
                if (format->u.multistream.format == media_multistream_format::wildcard.format) {
                        format->u.multistream.format = media_multistream_format::B_MPEG1;
                }
        }
        return B_OK;
}


// We need some sort of bit rate and chunk size, so if the other guy
// didn't care, we'll use our own defaults.
status_t AbstractFileInterfaceNode::ResolveWildcards(media_format * format)
{
        CALLED();
        // There isn't an unknown format. hmph.
        //      if (format->u.multistream.format == media_multistream_format::wildcard.format) {
        //              format->u.multistream.format = media_multistream_format::B_UNKNOWN;
        //      }
        if (format->u.multistream.max_bit_rate == media_multistream_format::wildcard.max_bit_rate) {
                format->u.multistream.max_bit_rate = fDefaultBitRateParam;
        }
        if (format->u.multistream.max_chunk_size == media_multistream_format::wildcard.max_chunk_size) {
                format->u.multistream.max_chunk_size = fDefaultChunkSizeParam;
        }
        if (format->u.multistream.avg_bit_rate == media_multistream_format::wildcard.avg_bit_rate) {
                format->u.multistream.avg_bit_rate = fDefaultBitRateParam;
        }
        if (format->u.multistream.avg_chunk_size == media_multistream_format::wildcard.avg_chunk_size) {
                format->u.multistream.avg_chunk_size = fDefaultChunkSizeParam;
        }
        return B_OK;
}


// -------------------------------------------------------- //
// stuffing
// -------------------------------------------------------- //
status_t AbstractFileInterfaceNode::_Reserved_AbstractFileInterfaceNode_0(void *) { return B_ERROR; }
status_t AbstractFileInterfaceNode::_Reserved_AbstractFileInterfaceNode_1(void *) { return B_ERROR; }
status_t AbstractFileInterfaceNode::_Reserved_AbstractFileInterfaceNode_2(void *) { return B_ERROR; }
status_t AbstractFileInterfaceNode::_Reserved_AbstractFileInterfaceNode_3(void *) { return B_ERROR; }
status_t AbstractFileInterfaceNode::_Reserved_AbstractFileInterfaceNode_4(void *) { return B_ERROR; }
status_t AbstractFileInterfaceNode::_Reserved_AbstractFileInterfaceNode_5(void *) { return B_ERROR; }
status_t AbstractFileInterfaceNode::_Reserved_AbstractFileInterfaceNode_6(void *) { return B_ERROR; }
status_t AbstractFileInterfaceNode::_Reserved_AbstractFileInterfaceNode_7(void *) { return B_ERROR; }
status_t AbstractFileInterfaceNode::_Reserved_AbstractFileInterfaceNode_8(void *) { return B_ERROR; }
status_t AbstractFileInterfaceNode::_Reserved_AbstractFileInterfaceNode_9(void *) { return B_ERROR; }
status_t AbstractFileInterfaceNode::_Reserved_AbstractFileInterfaceNode_10(void *) { return B_ERROR; }
status_t AbstractFileInterfaceNode::_Reserved_AbstractFileInterfaceNode_11(void *) { return B_ERROR; }
status_t AbstractFileInterfaceNode::_Reserved_AbstractFileInterfaceNode_12(void *) { return B_ERROR; }
status_t AbstractFileInterfaceNode::_Reserved_AbstractFileInterfaceNode_13(void *) { return B_ERROR; }
status_t AbstractFileInterfaceNode::_Reserved_AbstractFileInterfaceNode_14(void *) { return B_ERROR; }
status_t AbstractFileInterfaceNode::_Reserved_AbstractFileInterfaceNode_15(void *) { return B_ERROR; }