root/src/add-ons/kernel/drivers/audio/null/null_multi.c
/*
 * Copyright 2007-2009, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Bek, host.haiku@gmx.de
 */
#include "driver.h"


// Convenience function to determine the byte count
// of a sample for a given format.
// Note: Currently null_audio only supports 16 bit,
// but that is supposed to change later
int32
format_to_sample_size(uint32 format)
{
        switch(format) {
                case B_FMT_8BIT_S:
                        return 1;
                case B_FMT_16BIT:
                        return 2;

                case B_FMT_18BIT:
                case B_FMT_24BIT:
                case B_FMT_32BIT:
                case B_FMT_FLOAT:
                        return 4;

                default:
                        return 0;
        }
}


multi_channel_info channel_descriptions[] = {
        {  0, B_MULTI_OUTPUT_CHANNEL,   B_CHANNEL_LEFT | B_CHANNEL_STEREO_BUS, 0 },
        {  1, B_MULTI_OUTPUT_CHANNEL,   B_CHANNEL_RIGHT | B_CHANNEL_STEREO_BUS, 0 },
        {  2, B_MULTI_INPUT_CHANNEL,    B_CHANNEL_LEFT | B_CHANNEL_STEREO_BUS, 0 },
        {  3, B_MULTI_INPUT_CHANNEL,    B_CHANNEL_RIGHT | B_CHANNEL_STEREO_BUS, 0 },
        {  4, B_MULTI_OUTPUT_BUS,               B_CHANNEL_LEFT | B_CHANNEL_STEREO_BUS,  B_CHANNEL_MINI_JACK_STEREO },
        {  5, B_MULTI_OUTPUT_BUS,               B_CHANNEL_RIGHT | B_CHANNEL_STEREO_BUS, B_CHANNEL_MINI_JACK_STEREO },
        {  6, B_MULTI_INPUT_BUS,                B_CHANNEL_LEFT | B_CHANNEL_STEREO_BUS,  B_CHANNEL_MINI_JACK_STEREO },
        {  7, B_MULTI_INPUT_BUS,                B_CHANNEL_RIGHT | B_CHANNEL_STEREO_BUS, B_CHANNEL_MINI_JACK_STEREO },
};


static status_t
get_description(void* cookie, multi_description* data)
{
        multi_description description;

        dprintf("null_audio: %s\n", __func__ );

        if (user_memcpy(&description, data, sizeof(multi_description)) != B_OK) {
                return B_BAD_ADDRESS;
        }

        description.interface_version = B_CURRENT_INTERFACE_VERSION;
        description.interface_minimum = B_CURRENT_INTERFACE_VERSION;

        strcpy(description.friendly_name,"Virtual audio (null_audio)");
        strcpy(description.vendor_info,"Host/Haiku");

        description.output_channel_count = 2;
        description.input_channel_count = 2;
        description.output_bus_channel_count = 2;
        description.input_bus_channel_count = 2;
        description.aux_bus_channel_count = 0;

        description.output_rates = B_SR_44100;
        description.input_rates = B_SR_44100;

        description.max_cvsr_rate = 0;
        description.min_cvsr_rate = 0;

        description.output_formats = B_FMT_16BIT;
        description.input_formats = B_FMT_16BIT;
        description.lock_sources = B_MULTI_LOCK_INTERNAL;
        description.timecode_sources = 0;
        description.interface_flags = B_MULTI_INTERFACE_PLAYBACK | B_MULTI_INTERFACE_RECORD;
        description.start_latency = 30000;

        strcpy(description.control_panel,"");

        if (user_memcpy(data, &description, sizeof(multi_description)) != B_OK)
                return B_BAD_ADDRESS;

        if ((size_t)description.request_channel_count >= B_COUNT_OF(channel_descriptions)) {
                if (user_memcpy(data->channels,
                                        &channel_descriptions, sizeof(channel_descriptions)) != B_OK)
                        return B_BAD_ADDRESS;
        }

        return B_OK;
}


static status_t
get_enabled_channels(void* cookie, multi_channel_enable* data)
{
        dprintf("null_audio: %s\n", __func__ );
        // By default we say, that all channels are enabled
        // and that this cannot be changed
        B_SET_CHANNEL(data->enable_bits, 0, true);
        B_SET_CHANNEL(data->enable_bits, 1, true);
        B_SET_CHANNEL(data->enable_bits, 2, true);
        B_SET_CHANNEL(data->enable_bits, 3, true);
        return B_OK;
}


static status_t
set_global_format(device_t* device, multi_format_info* data)
{
        // The media kit asks us to set our streams
        // according to its settings
        dprintf("null_audio: %s\n", __func__ );
        device->playback_stream.format = data->output.format;
        device->playback_stream.rate = data->output.rate;

        device->record_stream.format = data->input.format;
        device->record_stream.rate = data->input.rate;

        return B_OK;
}


static status_t
get_global_format(device_t* device, multi_format_info* data)
{
        dprintf("null_audio: %s\n", __func__ );
        // Zero latency is unlikely to happen, so we fake some
        // additional latency
        data->output_latency = 30;
        data->input_latency = 30;
        data->timecode_kind = 0;

        data->output.format = device->playback_stream.format;
        data->output.rate = device->playback_stream.rate;
        data->input.format = device->record_stream.format;
        data->input.rate = device->record_stream.rate;

        return B_OK;
}


static int32
create_group_control(multi_mix_control* multi, int32 idx, int32 parent, int32 string,
        const char* name)
{
        multi->id = MULTI_AUDIO_BASE_ID + idx;
        multi->parent = parent;
        multi->flags = B_MULTI_MIX_GROUP;
        multi->master = MULTI_AUDIO_MASTER_ID;
        multi->string = string;
        if (name)
                strcpy(multi->name, name);

        return multi->id;
}


static status_t
list_mix_controls(device_t* device, multi_mix_control_info * data)
{
        dprintf("null_audio: %s\n", __func__ );

        create_group_control(data->controls + 0, 0, 0, 0, "Record");
        create_group_control(data->controls + 1, 1, 0, 0, "Playback");
        data->control_count = 2;

        return B_OK;
}


static status_t
list_mix_connections(void* cookie, multi_mix_connection_info* connection_info)
{
        dprintf("null_audio: %s\n", __func__ );
        return B_ERROR;
}


static status_t
list_mix_channels(void* cookie, multi_mix_channel_info* channel_info)
{
        dprintf("null_audio: %s\n", __func__ );
        return B_ERROR;
}


static status_t
get_buffers(device_t* device, multi_buffer_list* data)
{
        uint32 playback_sample_size = format_to_sample_size(device->playback_stream.format);
        uint32 record_sample_size = format_to_sample_size(device->record_stream.format);
        int32 bidx;
        int32 cidx;
        status_t result;

        dprintf("null_audio: %s\n", __func__ );

        // Workaround for Haiku multi_audio API, since it prefers
        // to let the driver pick values, while the BeOS multi_audio
        // actually gives the user's defaults.
        if (data->request_playback_buffers > STRMAXBUF || data->request_playback_buffers < STRMINBUF)
                data->request_playback_buffers = STRMINBUF;

        if (data->request_record_buffers > STRMAXBUF || data->request_record_buffers < STRMINBUF)
                data->request_record_buffers = STRMINBUF;

        if (data->request_playback_buffer_size == 0)
                data->request_playback_buffer_size = FRAMES_PER_BUFFER;

        if (data->request_record_buffer_size == 0)
                data->request_record_buffer_size = FRAMES_PER_BUFFER;

        // ... from here on, we can assume again that
        // a reasonable request is being made

        data->flags = 0;

        // Copy the requested settings into the streams
        // and initialize the virtual buffers properly
        device->playback_stream.num_buffers = data->request_playback_buffers;
        device->playback_stream.num_channels = data->request_playback_channels;
        device->playback_stream.buffer_length = data->request_playback_buffer_size;
        result = null_hw_create_virtual_buffers(&device->playback_stream,
                                "null_audio_playback_sem");
        if (result != B_OK) {
                dprintf("null_audio %s: Error setting up playback buffers (%s)\n",
                                                                                                        __func__, strerror(result));
                return result;
        }

        device->record_stream.num_buffers = data->request_record_buffers;
        device->record_stream.num_channels = data->request_record_channels;
        device->record_stream.buffer_length = data->request_record_buffer_size;
        result = null_hw_create_virtual_buffers(&device->record_stream,
                                "null_audio_record_sem");
        if (result != B_OK) {
                dprintf("null_audio %s: Error setting up recording buffers (%s)\n",
                                                                                                        __func__, strerror(result));
                return result;
        }

        /* Setup data structure for multi_audio API... */
        data->return_playback_buffers = data->request_playback_buffers;
        data->return_playback_channels = data->request_playback_channels;
        data->return_playback_buffer_size = data->request_playback_buffer_size;

        for (bidx = 0; bidx < data->return_playback_buffers; bidx++) {
                for (cidx = 0; cidx < data->return_playback_channels; cidx++) {
                        data->playback_buffers[bidx][cidx].base
                                = (char*)device->playback_stream.buffers[bidx] + (playback_sample_size * cidx);
                        data->playback_buffers[bidx][cidx].stride
                                = playback_sample_size * data->return_playback_channels;
                }
        }

        data->return_record_buffers = data->request_record_buffers;
        data->return_record_channels = data->request_record_channels;
        data->return_record_buffer_size = data->request_record_buffer_size;

        for (bidx = 0; bidx < data->return_record_buffers; bidx++) {
                for (cidx = 0; cidx < data->return_record_channels; cidx++) {
                        data->record_buffers[bidx][cidx].base
                                = (char*)device->record_stream.buffers[bidx] + (record_sample_size * cidx);
                        data->record_buffers[bidx][cidx].stride
                                = record_sample_size * data->return_record_channels;
                }
        }

        return B_OK;
}


static status_t
buffer_exchange(device_t* device, multi_buffer_info* info)
{
        //dprintf("null_audio: %s\n", __func__ );
        static int debug_buffers_exchanged = 0;
        cpu_status status;
        status_t result;

        multi_buffer_info buffer_info;
        if (user_memcpy(&buffer_info, info, sizeof(multi_buffer_info)) != B_OK)
                return B_BAD_ADDRESS;

        // On first call, we start our fake hardware.
        // Usually one would jump into his interrupt handler now
        if (!device->running)
                null_start_hardware(device);

        result = acquire_sem(device->playback_stream.buffer_ready_sem);
        if (result != B_OK) {
                dprintf("null_audio: %s, Could not get playback buffer\n", __func__);
                return result;
        }

        result = acquire_sem(device->record_stream.buffer_ready_sem);
        if (result != B_OK) {
                dprintf("null_audio: %s, Could not get record buffer\n", __func__);
                return result;
        }

        status = disable_interrupts();
        acquire_spinlock(&device->playback_stream.lock);

        buffer_info.playback_buffer_cycle = device->playback_stream.buffer_cycle;
        buffer_info.played_real_time = device->playback_stream.real_time;
        buffer_info.played_frames_count = device->playback_stream.frames_count;

        buffer_info.record_buffer_cycle = device->record_stream.buffer_cycle;
        buffer_info.recorded_real_time = device->record_stream.real_time;
        buffer_info.recorded_frames_count = device->record_stream.frames_count;

        release_spinlock(&device->playback_stream.lock);
        restore_interrupts(status);

        debug_buffers_exchanged++;
        if (((debug_buffers_exchanged % 5000) == 0) ) {
                dprintf("null_audio: %s: %d buffers processed\n",
                                __func__, debug_buffers_exchanged);
        }

        if (user_memcpy(info, &buffer_info, sizeof(multi_buffer_info)) != B_OK)
                return B_BAD_ADDRESS;

        return B_OK;
}


static status_t
buffer_force_stop(device_t* device)
{
        dprintf("null_audio: %s\n", __func__ );

        if (device == NULL)
                return B_ERROR;

        if (device->running)
                null_stop_hardware(device);

        delete_area(device->playback_stream.buffer_area);
        delete_area(device->record_stream.buffer_area);

        delete_sem(device->playback_stream.buffer_ready_sem);
        delete_sem(device->record_stream.buffer_ready_sem);

        return B_OK;
}


status_t
multi_audio_control(void* cookie, uint32 op, void* arg, size_t len)
{
        switch(op) {
                case B_MULTI_GET_DESCRIPTION:           return get_description(cookie, arg);
                case B_MULTI_GET_EVENT_INFO:            return B_ERROR;
                case B_MULTI_SET_EVENT_INFO:            return B_ERROR;
                case B_MULTI_GET_EVENT:                         return B_ERROR;
                case B_MULTI_GET_ENABLED_CHANNELS:      return get_enabled_channels(cookie, arg);
                case B_MULTI_SET_ENABLED_CHANNELS:      return B_OK;
                case B_MULTI_GET_GLOBAL_FORMAT:         return get_global_format(cookie, arg);
                case B_MULTI_SET_GLOBAL_FORMAT:         return set_global_format(cookie, arg);
                case B_MULTI_GET_CHANNEL_FORMATS:       return B_ERROR;
                case B_MULTI_SET_CHANNEL_FORMATS:       return B_ERROR;
                case B_MULTI_GET_MIX:                           return B_ERROR;
                case B_MULTI_SET_MIX:                           return B_ERROR;
                case B_MULTI_LIST_MIX_CHANNELS:         return list_mix_channels(cookie, arg);
                case B_MULTI_LIST_MIX_CONTROLS:         return list_mix_controls(cookie, arg);
                case B_MULTI_LIST_MIX_CONNECTIONS:      return list_mix_connections(cookie, arg);
                case B_MULTI_GET_BUFFERS:                       return get_buffers(cookie, arg);
                case B_MULTI_SET_BUFFERS:                       return B_ERROR;
                case B_MULTI_SET_START_TIME:            return B_ERROR;
                case B_MULTI_BUFFER_EXCHANGE:           return buffer_exchange(cookie, arg);
                case B_MULTI_BUFFER_FORCE_STOP:         return buffer_force_stop(cookie);
        }

        dprintf("null_audio: %s - unknown op\n", __func__);
        return B_BAD_VALUE;
}