root/src/add-ons/media/media-add-ons/opensound/OpenSoundAddOn.cpp
/*
 * OpenSound media addon for BeOS and Haiku
 *
 * Copyright (c) 2007, François Revol (revol@free.fr)
 * Distributed under the terms of the MIT License.
 * 
 * Based on MultiAudio media addon
 * Copyright (c) 2002, 2003 Jerome Duval (jerome.duval@free.fr)
 */
#include "OpenSoundAddOn.h"

#include <MediaDefs.h>
#include <MediaAddOn.h>
#include <Errors.h>
#include <Node.h>
#include <Mime.h>
#include <StorageDefs.h>
#include <Path.h>
#include <Directory.h>
#include <Entry.h>
#include <FindDirectory.h>
#include <Debug.h>
#include <errno.h>

#include "OpenSoundNode.h"
#include "OpenSoundDevice.h"
#include "OpenSoundDeviceEngine.h"

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

#include "debug.h"

#define MULTI_SAVE


// instantiation function
extern "C" _EXPORT BMediaAddOn * make_media_addon(image_id image) {
        CALLED();
        return new OpenSoundAddOn(image);
}

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

OpenSoundAddOn::~OpenSoundAddOn()
{
        CALLED();

        void *device = NULL;
        for (int32 i = 0; (device = fDevices.ItemAt(i)); i++)
                delete (OpenSoundDevice *)device;

        SaveSettings();
}

OpenSoundAddOn::OpenSoundAddOn(image_id image) :
        BMediaAddOn(image),
        fDevices()
{
        CALLED();
        fInitCheckStatus = B_NO_INIT;

        /* unix paths */
        if (RecursiveScan("/dev/oss/") != B_OK)
                return;
        /*
        if (RecursiveScan("/dev/audio/oss/") != B_OK)
                return;
        */
        
        LoadSettings();

        fInitCheckStatus = B_OK;
}

// -------------------------------------------------------- //
// BMediaAddOn impl
// -------------------------------------------------------- //

status_t OpenSoundAddOn::InitCheck(
        const char ** out_failure_text)
{
        CALLED();
        return B_OK;
}

int32 OpenSoundAddOn::CountFlavors()
{
        CALLED();
        PRINT(("%" B_PRId32 " flavours\n", fDevices.CountItems()));
        return fDevices.CountItems();
}

status_t OpenSoundAddOn::GetFlavorAt(
        int32 n,
        const flavor_info ** out_info)
{
        CALLED();
        if (n < 0 || n > fDevices.CountItems() - 1) {
                fprintf(stderr, "<- B_BAD_INDEX\n");
                return B_BAD_INDEX;
        }

        OpenSoundDevice *device = (OpenSoundDevice *) fDevices.ItemAt(n);

        flavor_info * infos = new flavor_info[1];
        OpenSoundNode::GetFlavor(&infos[0], n);
        infos[0].name = device->fCardInfo.longname;
        (*out_info) = infos;
        return B_OK;
}

BMediaNode * OpenSoundAddOn::InstantiateNodeFor(
        const flavor_info * info,
        BMessage * config,
        status_t * out_error)
{
        CALLED();
        
        OpenSoundDevice *device = (OpenSoundDevice*)fDevices.ItemAt(
                info->internal_id);
        if (device == NULL) {
                *out_error = B_ERROR;
                return NULL;
        }

#ifdef MULTI_SAVE
        if (fSettings.FindMessage(device->fCardInfo.longname, config) == B_OK) {
                fSettings.RemoveData(device->fCardInfo.longname);
        }
#endif

        OpenSoundNode * node =
                new OpenSoundNode(this,
                        device->fCardInfo.longname,
                        device,
                        info->internal_id,
                        config);
        if (node == 0) {
                *out_error = B_NO_MEMORY;
                fprintf(stderr, "<- B_NO_MEMORY\n");
        } else {
                *out_error = node->InitCheck();
        }
        return node;
}

status_t
OpenSoundAddOn::GetConfigurationFor(BMediaNode * your_node, BMessage * into_message)
{
        CALLED();
#ifdef MULTI_SAVE
        {
                into_message = new BMessage();
                OpenSoundNode * node = dynamic_cast<OpenSoundNode*>(your_node);
                if (node == 0) {
                        fprintf(stderr, "<- B_BAD_TYPE\n");
                        return B_BAD_TYPE;
                }
                if (node->GetConfigurationFor(into_message) == B_OK) {
                        fSettings.AddMessage(your_node->Name(), into_message);
                }
                return B_OK;
        }
#endif
        // currently never called by the media kit. Seems it is not implemented.
        OpenSoundNode * node = dynamic_cast<OpenSoundNode*>(your_node);
        if (node == 0) {
                fprintf(stderr, "<- B_BAD_TYPE\n");
                return B_BAD_TYPE;
        }
        return node->GetConfigurationFor(into_message);
}


bool OpenSoundAddOn::WantsAutoStart()
{
        CALLED();
        return false;
}

status_t OpenSoundAddOn::AutoStart(
        int in_count,
        BMediaNode ** out_node,
        int32 * out_internal_id,
        bool * out_has_more)
{
        CALLED();
        return B_OK;
}

status_t
OpenSoundAddOn::RecursiveScan(const char* rootPath, BEntry *rootEntry)
{
        status_t err;
        int mixer;
        oss_sysinfo sysinfo;
        oss_card_info cardinfo;
        int card, i, j;
        BList devs;
        
        CALLED();

        // make sure directories are scanned in this order
        BDirectory("/dev/audio/hmulti");
        BDirectory("/dev/audio/old");
        // OSS last, to give precedence to native drivers.
        // If other addons are loaded first it's ok as well.
        // Also, we must open it to make sure oss_loader is here,
        // else we don't get /dev/sndstat since we don't have a symlink in dev/.
        BDirectory("/dev/oss");

        mixer = open(OSS_MIXER_DEV, O_RDWR);
        if (mixer < 0) {
                // try to rescan
                // only works in BeOS
                BFile fDevFS("/dev/.", B_WRITE_ONLY);
                const char *drv = "oss_loader";
                fDevFS.Write(drv, strlen(drv));
                mixer = open(OSS_MIXER_DEV, O_RDWR);
                if (mixer < 0) {
                        err = errno;
                        goto err0;
                }
        }

        if (ioctl(mixer, SNDCTL_SYSINFO, &sysinfo) < 0) {
                err = errno;
                goto err1;
        }
        
        PRINT(("OSS: %s %s (0x%08X)\n", sysinfo.product, sysinfo.version, sysinfo.versionnum));
        PRINT(("OSS: %d audio cards, %d audio devs, %d audio engines, %d midi, %d mixers\n", sysinfo.numcards, sysinfo.numaudios, sysinfo.numaudioengines, sysinfo.nummidis, sysinfo.nummixers));

        /* construct an empty SoundDevice per card */
        
        for (card = 0; card < sysinfo.numcards; card++) {
                cardinfo.card = card;
                if (ioctl(mixer, SNDCTL_CARDINFO, &cardinfo) < 0) {
                        err = errno;
                        goto err1;
                }
                OpenSoundDevice *device = new OpenSoundDevice(&cardinfo);
                if (device)
                        devs.AddItem(device);
                else {
                        err = ENOMEM;
                        goto err1;
                }
        }
        
        /* Add its audio engines to it */
        
        for (i = 0; i < sysinfo.numaudioengines; i++) {
                oss_audioinfo audioinfo;
                audioinfo.dev = i;
                if (ioctl(mixer, SNDCTL_ENGINEINFO, &audioinfo, sizeof(oss_audioinfo)) < 0) {
                        err = errno;
                        goto err1;
                }
                PRINT(("OSS: engine[%d]: card=%d, port=%d, legacy=%d, next_play=%d, next_rec=%d\n", i, audioinfo.card_number, audioinfo.port_number, audioinfo.legacy_device, audioinfo.next_play_engine, audioinfo.next_rec_engine));
                OpenSoundDevice *device = (OpenSoundDevice *)(devs.ItemAt(audioinfo.card_number));
                if (device)
                        device->AddEngine(&audioinfo);
        }
        
        /* Add its mixers to it */
        
        for (i = 0; i < sysinfo.nummixers; i++) {
                oss_mixerinfo mixerinfo;
                mixerinfo.dev = i;
                if (ioctl(mixer, SNDCTL_MIXERINFO, &mixerinfo) < 0) {
                        err = errno;
                        goto err1;
                }
                PRINT(("OSS: mixer[%d]: card=%d\n", i, mixerinfo.card_number));
                OpenSoundDevice *device = (OpenSoundDevice *)(devs.ItemAt(mixerinfo.card_number));
                if (device)
                        device->AddMixer(&mixerinfo);
        }
        
        /* resolve engine chains of shadow engines */
        
        for (card = 0; card < sysinfo.numcards; card++) {
                OpenSoundDevice *device = (OpenSoundDevice *)(devs.ItemAt(card));
                if (!device)
                        continue;
                for (i = 0; i < device->CountEngines(); i++) {
                        OpenSoundDeviceEngine *engine = device->EngineAt(i);
                        if (engine) {
                                if (engine->Info()->next_play_engine) {
                                        for (j = 0; j < device->CountEngines(); j++) {
                                                OpenSoundDeviceEngine *next = device->EngineAt(j);
                                                if (!next || (engine == next))
                                                        continue;
                                                if (next->Info()->dev == engine->Info()->next_play_engine) {
                                                        PRINT(("OSS: engine[%d].next_play = engine[%d]\n", i, j));
                                                        engine->fNextPlay = next;
                                                        break;
                                                }
                                        }
                                }
                                if (engine->Info()->next_rec_engine) {
                                        for (j = 0; j < device->CountEngines(); j++) {
                                                OpenSoundDeviceEngine *next = device->EngineAt(j);
                                                if (!next || (engine == next))
                                                        continue;
                                                if (next->Info()->dev == engine->Info()->next_rec_engine) {
                                                        PRINT(("OSS: engine[%d].next_rec = engine[%d]\n", i, j));
                                                        engine->fNextRec = next;
                                                        break;
                                                }
                                        }
                                }
                        }
                }
        }
        
        /* copy correctly initialized devs to fDevices */
        
        for (card = 0; card < sysinfo.numcards; card++) {
                OpenSoundDevice *device = (OpenSoundDevice *)(devs.ItemAt(card));
                if (device) {
                        if (card == 0) { /* skip the "oss0" pseudo card device */
                                delete device;
                                continue;
                        }
                        if ((device->InitDriver() == B_OK) && (device->InitCheck() == B_OK))
                                fDevices.AddItem(device);
                        else
                                delete device;
                }
        }
        if (fDevices.CountItems())
                err = B_OK;
        else
                err = ENOENT;
err1:
        close(mixer);
err0:
        return err;

#if MA
        BDirectory root;
        if (rootEntry != NULL)
                root.SetTo(rootEntry);
        else if (rootPath != NULL) {
                root.SetTo(rootPath);
        } else {
                PRINT(("Error in OpenSoundAddOn::RecursiveScan null params\n"));
                return B_ERROR;
        }

        BEntry entry;

        while (root.GetNextEntry(&entry) > B_ERROR) {

                if (entry.IsDirectory()) {
                        BPath path;
                        entry.GetPath(&path);
                        OpenSoundDevice *device = new OpenSoundDevice(path.Path() + strlen(rootPath), path.Path());
                        if (device) {
                                if (device->InitCheck() == B_OK)
                                        fDevices.AddItem(device);
                                else
                                        delete device;
                        }
                }
        }

        return B_OK;
#endif
}


void
OpenSoundAddOn::SaveSettings(void)
{
        CALLED();
        BPath path;
        if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
                path.Append(SETTINGS_FILE);
                BFile file(path.Path(), B_READ_WRITE | B_CREATE_FILE | B_ERASE_FILE);
                if (file.InitCheck() == B_OK)
                        fSettings.Flatten(&file);
        }
}


void
OpenSoundAddOn::LoadSettings(void)
{
        CALLED();
        fSettings.MakeEmpty();

        BPath path;
        if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
                path.Append(SETTINGS_FILE);
                BFile file(path.Path(), B_READ_ONLY);
                if ((file.InitCheck() == B_OK) && (fSettings.Unflatten(&file) == B_OK))
                {
                        PRINT_OBJECT(fSettings);
                } else {
                        PRINT(("Error unflattening settings file %s\n", path.Path()));
                }
        }
}


void
OpenSoundAddOn::RegisterMediaFormats(void)
{
        CALLED();
        // register non-raw audio formats to the Media Kit
#ifdef ENABLE_NON_RAW_SUPPORT
        OpenSoundDevice::register_media_formats();
#endif

        
}