[haiku-development] null_audio

  • From: Bek <HOST.HAIKU@xxxxxx>
  • To: haiku-development@xxxxxxxxxxxxx
  • Date: Fri, 14 Sep 2007 00:47:18 +0200

Hi all,

I have seen on the task tracker(http://dev.haiku-os.org/ticket/1189), 
that Haiku misses a null_audio driver, which simulates a real soundcard.

Therefore I wanted to take care of this and attached a first draft. If
it is ok with you, I have set the friendly name to "Virtual Audio",
which is more user-understandable in my opinion.

In addition to this, I tried to add some comments and arrange it as a 
sample driver, as I recognized that most of drivers have redundant code.
So null_audio should be designed as a startup point to develop for real
hardware without the need of setting the environment up. It is based on 
the sb16 and hda drivers, so the authors will find a lot of their stuff
inside here.

But unfortunately I recognized a number of issues myself with that
driver
and/or my setup in general. When I try to play a mp3 file without the 
null_audio by calling playfile in the terminal, I get a lot of too big
latency errors. The same appears on MediaPlayer sometimes. My guess is
that trying to do this in an emulator will not work out at all. But with
the driver installed it is even more worse. The amount of timeout
heavily
increases. What confuses me is the point (in case I understood the
concept
correctly), that an application "connects" to the mixer, which send the
data to the driver. So the mixer should in theory be responsible for the
timeouts, right? In that case it has nothing to do with the driver. But
I am not happy with the theory that the emulator is just too slow for
that
as other operating systems manage to play sound. Did anyone manage to
get
sound with any of the existing drivers inside an emulator and can tell
me
about its results?

Furthermore two things I have recognized in the meantime:
1. When the buffer_exchange returns B_ERROR, the system hangs. That is
due to the fact, that in MultiAudioNode.cpp in line 1462, it tries to
do the buffer exchange, but does not check for sanity. As there is no
sleep or wait, it directly tries to do a buffer exchange again, which 
obviously kills the system.
2. B_MULTI_BUFFER_FORCE_STOP is never send to the driver. Is that
intended?
Currently I clean up in the null_audio_close hook. But startup is done
within the multi_audio section, which is confusing I guess?

What needs to be done for the final version is creating buffers, which
form a fine sinus-wave, so that the output is useful for testing
purposes.

Btw. having a virtual audio driver fixes
http://dev.haiku-os.org/ticket/1450
or at least resolves the issue. My guess is, that the MediaKit somewhere
does not check for a present soundcard and due to that misbehaves. I
did not find the time to investigate on this yet, but maybe this helps
the maintainer already.

Finally I was wondering, if the MediaKit has some routine to check
whether
a "real" soundcard is present and uses this one instead of the
null_audio.
If not, one might be confused, that he has to switch to his soundcard in
the Media Preferences. So I suppose to add some code for this then.

Best Regards,

Bek, HOST team

/*
 * Copyright 2007 Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Bek, host.haiku@xxxxxx
 */
#include "driver.h"

const char** publish_devices(void); /* Just to silence compiler */

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)
{
        dprintf("null_audio: %s\n", __func__);
        static const char* published_paths[] = {
                MULTI_AUDIO_DEV_PATH "/null/0",
                NULL
        };

        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)
{
        dprintf("null_audio: %s\n" , __func__ );
        device_t* device = (device_t*) cookie;
        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;
}
/*
 * 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 */

SubDir HAIKU_TOP src add-ons kernel drivers audio null_audio ;

SetSubDirSupportedPlatformsBeOSCompatible ;

UsePrivateHeaders media ;

KernelAddon null_audio :
        driver.c
        null_hardware.c
        null_multi_audio.c
;

Package haiku-null_audio-cvs :
        null_audio :
        boot home config add-ons kernel drivers bin ;
PackageDriverSymLink haiku-null_audio-cvs : audio multi null_audio ;
/*
 * Copyright 2007 Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Bek, host.haiku@xxxxxx
 */
#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;
}

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;
}
/*
 * Copyright 2007 Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Bek, host.haiku@xxxxxx
 */
#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:
                case B_FMT_16BIT:
                        return 2;

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

                case B_FMT_FLOAT:
                        return 8;
                        
                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)
{
        dprintf("null_audio: %s\n" , __func__ );
        int32 parent;

        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)
{
        dprintf("null_audio: %s\n" , __func__ );
        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;
 
        /* 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;
}

Other related posts: