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; }