* 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"
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__ );
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)
{
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__ );
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__ );
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;
data->flags = 0;
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;
}
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)
{
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;
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;
}