added null_audio driver from Bek, HOST team. Thanks.

Applied some style fixes.
It's not recognized as a fallback driver. We might also consider a simple userland BufferConsumer/BufferProducer.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@22299 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Jérôme Duval 2007-09-24 23:09:42 +00:00
parent c32beda5c4
commit 57cba4584f
6 changed files with 680 additions and 0 deletions

View File

@ -6,6 +6,7 @@ SubInclude HAIKU_TOP src add-ons kernel drivers audio echo ;
SubInclude HAIKU_TOP src add-ons kernel drivers audio emuxki ;
SubInclude HAIKU_TOP src add-ons kernel drivers audio hda ;
SubInclude HAIKU_TOP src add-ons kernel drivers audio module_driver ;
SubInclude HAIKU_TOP src add-ons kernel drivers audio null ;
SubInclude HAIKU_TOP src add-ons kernel drivers audio sb16 ;
SubInclude HAIKU_TOP src add-ons kernel drivers audio sis7018 ;
SubInclude HAIKU_TOP src add-ons kernel drivers audio usb_audio ;

View File

@ -0,0 +1,12 @@
SubDir HAIKU_TOP src add-ons kernel drivers audio null ;
SetSubDirSupportedPlatformsBeOSCompatible ;
UsePrivateHeaders media ;
KernelAddon null_audio :
driver.c
null_hardware.c
null_multi.c
;

View File

@ -0,0 +1,132 @@
/*
* Copyright 2007 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Bek, host.haiku@gmx.de
*/
#include "driver.h"
int32 api_version = B_CUR_DRIVER_API_VERSION;
device_t device;
status_t
init_hardware(void)
{
dprintf("null_audio: %s\n", __func__);
return B_OK;
}
status_t
init_driver(void)
{
dprintf("null_audio: %s\n", __func__);
device.running = false;
return B_OK;
}
void
uninit_driver(void)
{
}
const char**
publish_devices(void)
{
static const char* published_paths[] = {
MULTI_AUDIO_DEV_PATH "/null/0",
NULL
};
dprintf("null_audio: %s\n", __func__);
return published_paths;
}
static status_t
null_audio_open (const char *name, uint32 flags, void** cookie)
{
dprintf("null_audio: %s\n" , __func__ );
*cookie = &device;
return B_OK;
}
static status_t
null_audio_read (void* cookie, off_t a, void* b, size_t* num_bytes)
{
dprintf("null_audio: %s\n" , __func__ );
// Audio drivers are not supposed to return anything
// inside here
*num_bytes = 0;
return B_IO_ERROR;
}
static status_t
null_audio_write (void* cookie, off_t a, const void* b, size_t* num_bytes)
{
dprintf("null_audio: %s\n" , __func__ );
// Audio drivers are not supposed to return anything
// inside here
*num_bytes = 0;
return B_IO_ERROR;
}
static status_t
null_audio_control (void* cookie, uint32 op, void* arg, size_t len)
{
//dprintf("null_audio: %s\n" , __func__ );
// In case we have a valid cookie, initialized
// the driver and hardware connection properly
// Simply pass through to the multi audio hooks
if (cookie)
return multi_audio_control(cookie, op, arg, len);
else
dprintf("null_audio: %s called without cookie\n" , __func__);
// Return error in case we have no valid setup
return B_BAD_VALUE;
}
static status_t
null_audio_close (void* cookie)
{
device_t* device = (device_t*) cookie;
dprintf("null_audio: %s\n" , __func__ );
if (device && device->running)
null_stop_hardware(device);
return B_OK;
}
static status_t
null_audio_free (void* cookie)
{
dprintf("null_audio: %s\n" , __func__ );
return B_OK;
}
device_hooks driver_hooks = {
null_audio_open,
null_audio_close,
null_audio_free,
null_audio_control,
null_audio_read,
null_audio_write
};
device_hooks*
find_device(const char* name)
{
return &driver_hooks;
}

View File

@ -0,0 +1,60 @@
/*
* Copyright 2007 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#ifndef NULL_AUDIO_DRIVER_H
#define NULL_AUDIO_DRIVER_H
#include <drivers/driver_settings.h>
#include <drivers/Drivers.h>
#include <drivers/KernelExport.h>
#include <hmulti_audio.h>
#include <string.h>
#include <stdlib.h>
#define FRAMES_PER_BUFFER 1024
#define MULTI_AUDIO_BASE_ID 1024
#define MULTI_AUDIO_DEV_PATH "audio/hmulti"
#define MULTI_AUDIO_MASTER_ID 0
#define STRMINBUF 2
#define STRMAXBUF 2
typedef struct {
spinlock lock;
int bits;
void* buffers[STRMAXBUF];
uint32 num_buffers;
uint32 num_channels;
uint32 format;
uint32 rate;
uint32 buffer_length;
sem_id buffer_ready_sem;
uint32 frames_count;
uint32 buffer_cycle;
bigtime_t real_time;
area_id buffer_area;
} device_stream_t;
typedef struct {
device_stream_t playback_stream;
device_stream_t record_stream;
thread_id interrupt_thread;
bool running;
} device_t;
extern device_hooks driver_hooks;
int32 format_to_sample_size(uint32 format);
status_t multi_audio_control(void* cookie, uint32 op, void* arg, size_t len);
status_t null_hw_create_virtual_buffers(device_stream_t* stream, const char* name);
status_t null_start_hardware(device_t* device);
void null_stop_hardware(device_t* device);
#endif /* NULL_AUDIO_DRIVER_H */

View File

@ -0,0 +1,123 @@
/*
* Copyright 2007 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Bek, host.haiku@gmx.de
*/
#include "driver.h"
status_t
null_hw_create_virtual_buffers(device_stream_t* stream, const char* name)
{
int i;
int buffer_size;
int area_size;
uint8* buffer;
status_t result;
physical_entry pe;
buffer_size = stream->num_channels
* format_to_sample_size(stream->format)
* stream->buffer_length;
buffer_size = (buffer_size + 127) & (~127);
area_size = buffer_size * stream->num_buffers;
area_size = (area_size + B_PAGE_SIZE - 1) & (~(B_PAGE_SIZE -1));
stream->buffer_area = create_area("null_audio_buffers", (void**)&buffer,
B_ANY_KERNEL_ADDRESS, area_size,
B_CONTIGUOUS, B_READ_AREA | B_WRITE_AREA);
if (stream->buffer_area < B_OK)
return stream->buffer_area;
// Get the correct address for setting up the buffers
// pointers being passed back to userland
result = get_memory_map(buffer, area_size, &pe, 1);
if (result != B_OK) {
delete_area(stream->buffer_area);
return result;
}
for (i=0; i < stream->num_buffers; i++) {
stream->buffers[i] = buffer + (i*buffer_size);
}
stream->buffer_ready_sem = create_sem(0, name);
return B_OK;
}
static int32
null_fake_interrupt(void* cookie)
{
// This thread is supposed to fake the interrupt
// handling done in communication with the
// hardware usually. What it does is nearly the
// same like all soundrivers, get the interrupt
// exchange the buffer pointer and update the
// time information. Instead of exiting, we wait
// until the next fake interrupt appears.
int sleepTime;
device_t* device = (device_t*) cookie;
int sampleRate;
switch (device->playback_stream.rate) {
case B_SR_48000:
sampleRate = 48000;
break;
case B_SR_44100:
default:
sampleRate = 44100;
break;
}
// The time between until we get a new valid buffer
// from our soundcard: buffer_length / samplerate
sleepTime = (device->playback_stream.buffer_length*1000) / sampleRate;
while (device->running) {
cpu_status status;
status = disable_interrupts();
acquire_spinlock(&device->playback_stream.lock);
device->playback_stream.real_time = system_time();
device->playback_stream.frames_count += device->playback_stream.buffer_length;
device->playback_stream.buffer_cycle = (device->playback_stream.buffer_cycle +1) % device->playback_stream.num_buffers;
release_spinlock(&device->playback_stream.lock);
// TODO: Create a simple sinus wave, so that recording from
// the virtual device actually returns something useful
acquire_spinlock(&device->record_stream.lock);
device->record_stream.real_time = device->playback_stream.real_time;
device->record_stream.frames_count += device->record_stream.buffer_length;
device->record_stream.buffer_cycle = (device->record_stream.buffer_cycle +1) % device->record_stream.num_buffers;
release_spinlock(&device->record_stream.lock);
restore_interrupts(status);
release_sem_etc(device->playback_stream.buffer_ready_sem, 1, B_DO_NOT_RESCHEDULE);
release_sem_etc(device->record_stream.buffer_ready_sem, 1, B_DO_NOT_RESCHEDULE);
snooze(sleepTime);
}
return B_OK;
}
status_t
null_start_hardware(device_t* device)
{
dprintf("null_audio: %s spawning fake interrupter\n", __func__);
device->running = true;
device->interrupt_thread = spawn_kernel_thread(null_fake_interrupt, "null_audio interrupter",
B_REAL_TIME_PRIORITY, (void*)device);
return resume_thread(device->interrupt_thread);
}
void
null_stop_hardware(device_t* device)
{
device->running = false;
}

View File

@ -0,0 +1,352 @@
/*
* Copyright 2007 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)
{
dprintf("null_audio: %s\n" , __func__ );
data->interface_version = B_CURRENT_INTERFACE_VERSION;
data->interface_minimum = B_CURRENT_INTERFACE_VERSION;
strcpy(data->friendly_name,"Virtual Audio (null_audio)");
strcpy(data->vendor_info,"Host/Haiku");
data->output_channel_count = 2;
data->input_channel_count = 2;
data->output_bus_channel_count = 2;
data->input_bus_channel_count = 2;
data->aux_bus_channel_count = 0;
if (data->request_channel_count >= (int)(sizeof(channel_descriptions) / sizeof(channel_descriptions[0]))) {
memcpy(data->channels,&channel_descriptions,sizeof(channel_descriptions));
}
data->output_rates = B_SR_44100;
data->input_rates = B_SR_44100;
data->max_cvsr_rate = 0;
data->min_cvsr_rate = 0;
data->output_formats = B_FMT_16BIT;
data->input_formats = B_FMT_16BIT;
data->lock_sources = B_MULTI_LOCK_INTERNAL;
data->timecode_sources = 0;
data->interface_flags = B_MULTI_INTERFACE_PLAYBACK | B_MULTI_INTERFACE_RECORD;
data->start_latency = 30000;
strcpy(data->control_panel,"");
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)
{
int32 parent;
dprintf("null_audio: %s\n" , __func__ );
parent = create_group_control(data->controls +0, 0, 0, 0, "Record");
parent = 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);
uint32 cidx, bidx;
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;
if ((result = null_hw_create_virtual_buffers(&device->playback_stream, "null_audio_playback_sem")) != 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;
if ((result = null_hw_create_virtual_buffers(&device->record_stream, "null_audio_record_sem")) != 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 = 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 = 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* buffer_info)
{
//dprintf("null_audio: %s\n" , __func__ );
static int debug_buffers_exchanged = 0;
cpu_status status;
status_t result;
// 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) ) { //&& debug_buffers_exchanged < 1111) {
dprintf("null_audio: %s: %d buffers processed\n", __func__, debug_buffers_exchanged);
}
return B_OK;
}
static status_t
buffer_force_stop(device_t* device)
{
dprintf("null_audio: %s\n" , __func__ );
if (device && 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;
}