From 57cba4584f43e0d59e8cdc4b074840e0f5eecb73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Duval?= Date: Mon, 24 Sep 2007 23:09:42 +0000 Subject: [PATCH] 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 --- src/add-ons/kernel/drivers/audio/Jamfile | 1 + src/add-ons/kernel/drivers/audio/null/Jamfile | 12 + .../kernel/drivers/audio/null/driver.c | 132 +++++++ .../kernel/drivers/audio/null/driver.h | 60 +++ .../kernel/drivers/audio/null/null_hardware.c | 123 ++++++ .../kernel/drivers/audio/null/null_multi.c | 352 ++++++++++++++++++ 6 files changed, 680 insertions(+) create mode 100644 src/add-ons/kernel/drivers/audio/null/Jamfile create mode 100644 src/add-ons/kernel/drivers/audio/null/driver.c create mode 100644 src/add-ons/kernel/drivers/audio/null/driver.h create mode 100644 src/add-ons/kernel/drivers/audio/null/null_hardware.c create mode 100644 src/add-ons/kernel/drivers/audio/null/null_multi.c diff --git a/src/add-ons/kernel/drivers/audio/Jamfile b/src/add-ons/kernel/drivers/audio/Jamfile index 7498609d1e..ad21de9075 100644 --- a/src/add-ons/kernel/drivers/audio/Jamfile +++ b/src/add-ons/kernel/drivers/audio/Jamfile @@ -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 ; diff --git a/src/add-ons/kernel/drivers/audio/null/Jamfile b/src/add-ons/kernel/drivers/audio/null/Jamfile new file mode 100644 index 0000000000..de60b785e0 --- /dev/null +++ b/src/add-ons/kernel/drivers/audio/null/Jamfile @@ -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 +; + diff --git a/src/add-ons/kernel/drivers/audio/null/driver.c b/src/add-ons/kernel/drivers/audio/null/driver.c new file mode 100644 index 0000000000..33a68fea31 --- /dev/null +++ b/src/add-ons/kernel/drivers/audio/null/driver.c @@ -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; +} + diff --git a/src/add-ons/kernel/drivers/audio/null/driver.h b/src/add-ons/kernel/drivers/audio/null/driver.h new file mode 100644 index 0000000000..d31b691055 --- /dev/null +++ b/src/add-ons/kernel/drivers/audio/null/driver.h @@ -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 +#include +#include + +#include +#include +#include + +#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 */ + diff --git a/src/add-ons/kernel/drivers/audio/null/null_hardware.c b/src/add-ons/kernel/drivers/audio/null/null_hardware.c new file mode 100644 index 0000000000..d621b1aa6b --- /dev/null +++ b/src/add-ons/kernel/drivers/audio/null/null_hardware.c @@ -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; +} + diff --git a/src/add-ons/kernel/drivers/audio/null/null_multi.c b/src/add-ons/kernel/drivers/audio/null/null_multi.c new file mode 100644 index 0000000000..8ac9148f37 --- /dev/null +++ b/src/add-ons/kernel/drivers/audio/null/null_multi.c @@ -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; +} +