Bug 436663. Work around ATSUI crasher caused by long Hebrew sequence. r=roc, sr=vlad
[wine-gecko.git] / media / liboggplay_audio / sydney_audio_mac.c
blob0e3fcb6ebb3882fe96704292e391f7699b959c22
1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
14 * The Initial Developer of the Original Code is
15 * CSIRO
16 * Portions created by the Initial Developer are Copyright (C) 2007
17 * the Initial Developer. All Rights Reserved.
19 * Contributor(s): Michael Martin
21 * Alternatively, the contents of this file may be used under the terms of
22 * either the GNU General Public License Version 2 or later (the "GPL"), or
23 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
24 * in which case the provisions of the GPL or the LGPL are applicable instead
25 * of those above. If you wish to allow use of your version of this file only
26 * under the terms of either the GPL or the LGPL, and not to allow others to
27 * use your version of this file under the terms of the MPL, indicate your
28 * decision by deleting the provisions above and replace them with the notice
29 * and other provisions required by the GPL or the LGPL. If you do not delete
30 * the provisions above, a recipient may use your version of this file under
31 * the terms of any one of the MPL, the GPL or the LGPL.
33 * ***** END LICENSE BLOCK ***** *
36 #include <AudioUnit/AudioUnit.h>
37 #include "sydney_audio.h"
40 * The Mac's audio interface is based on a "pull" I/O model, which means you
41 * can't just provide a data buffer and tell the audio device to play; you must
42 * register a callback and provide data as the device asks for it. To support
43 * sydney audio's "write-to-play" style interface, we have to buffer up the
44 * data as it arrives and feed it to the callback as required.
46 * This is handled by a simple linked list of buffers; data is always written
47 * to the tail and read from the head. Each buffer tracks the start and end
48 * positions of its contained data. Buffers are allocated when the tail buffer
49 * fills, and freed when the head buffer empties. There is always at least one
50 * buffer allocated.
52 * s e s e s e + data read
53 * +++##### -> ######## -> ####---- # data written
54 * ^ ^ - empty
55 * bl_head bl_tail
58 typedef struct sa_buf sa_buf;
59 struct sa_buf {
60 unsigned int size;
61 unsigned int start;
62 unsigned int end;
63 sa_buf * next;
64 unsigned char data[0];
67 struct sa_stream {
68 AudioUnit output_unit;
69 pthread_mutex_t mutex;
70 bool playing;
71 int64_t bytes_played;
73 /* audio format info */
74 unsigned int rate;
75 unsigned int n_channels;
76 unsigned int bytes_per_ch;
78 /* buffer list */
79 sa_buf * bl_head;
80 sa_buf * bl_tail;
81 int n_bufs;
86 * Use a default buffer size with enough room for one second of audio,
87 * assuming stereo data at 44.1kHz with 32 bits per channel, and impose
88 * a generous limit on the number of buffers.
90 #define BUF_SIZE (2 * 44100 * 4)
91 #define BUF_LIMIT 5
93 #if BUF_LIMIT < 2
94 #error BUF_LIMIT must be at least 2!
95 #endif
98 static OSStatus audio_callback(void *arg, AudioUnitRenderActionFlags *action_flags,
99 const AudioTimeStamp *time_stamp, UInt32 bus_num, UInt32 n_frames, AudioBufferList *data);
101 static sa_buf *new_buffer(void);
105 * -----------------------------------------------------------------------------
106 * Startup and shutdown functions
107 * -----------------------------------------------------------------------------
111 sa_stream_create_pcm(
112 sa_stream_t ** _s,
113 const char * client_name,
114 sa_mode_t mode,
115 sa_pcm_format_t format,
116 unsigned int rate,
117 unsigned int n_channels
121 * Make sure we return a NULL stream pointer on failure.
123 if (_s == NULL) {
124 return SA_ERROR_INVALID;
126 *_s = NULL;
128 if (mode != SA_MODE_WRONLY) {
129 return SA_ERROR_NOT_SUPPORTED;
131 if (format != SA_PCM_FORMAT_S16_LE) {
132 return SA_ERROR_NOT_SUPPORTED;
136 * Allocate the instance and required resources.
138 sa_stream_t * s;
139 if ((s = malloc(sizeof(sa_stream_t))) == NULL) {
140 return SA_ERROR_OOM;
142 if ((s->bl_head = new_buffer()) == NULL) {
143 free(s);
144 return SA_ERROR_OOM;
146 if (pthread_mutex_init(&s->mutex, NULL) != 0) {
147 free(s->bl_head);
148 free(s);
149 return SA_ERROR_SYSTEM;
152 s->output_unit = NULL;
153 s->playing = FALSE;
154 s->bytes_played = 0;
155 s->rate = rate;
156 s->n_channels = n_channels;
157 s->bytes_per_ch = 2;
158 s->bl_tail = s->bl_head;
159 s->n_bufs = 1;
161 *_s = s;
162 return SA_SUCCESS;
167 sa_stream_open(sa_stream_t *s) {
169 if (s == NULL) {
170 return SA_ERROR_NO_INIT;
172 if (s->output_unit != NULL) {
173 return SA_ERROR_INVALID;
177 * Open the default audio output unit.
179 ComponentDescription desc;
180 desc.componentType = kAudioUnitType_Output;
181 desc.componentSubType = kAudioUnitSubType_DefaultOutput;
182 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
183 desc.componentFlags = 0;
184 desc.componentFlagsMask = 0;
186 Component comp = FindNextComponent(NULL, &desc);
187 if (comp == NULL) {
188 return SA_ERROR_NO_DEVICE;
191 if (OpenAComponent(comp, &s->output_unit) != noErr) {
192 return SA_ERROR_NO_DEVICE;
196 * Set up the render callback used to feed audio data into the output unit.
198 AURenderCallbackStruct input;
199 input.inputProc = audio_callback;
200 input.inputProcRefCon = s;
201 if (AudioUnitSetProperty(s->output_unit, kAudioUnitProperty_SetRenderCallback,
202 kAudioUnitScope_Input, 0, &input, sizeof(input)) != 0) {
203 return SA_ERROR_SYSTEM;
207 * Set up the format description for our audio data. Apple uses the
208 * following terminology:
210 * sample = a single data value for one channel
211 * frame = a set of samples that includes one sample for each channel
212 * packet = the smallest indivisible block of audio data; for uncompressed
213 * audio (which is what we have), this is one frame
214 * rate = the number of complete frames per second
216 * Note that this definition of frame differs from, well, pretty much everyone
217 * else's. See this really long link for more info:
219 * http://developer.apple.com/documentation/MusicAudio/Reference/CoreAudioDataTypesRef/Reference/reference.html#//apple_ref/c/tdef/AudioStreamBasicDescription
221 AudioStreamBasicDescription fmt;
222 fmt.mFormatID = kAudioFormatLinearPCM;
223 fmt.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger |
224 #ifdef __BIG_ENDIAN__
225 kLinearPCMFormatFlagIsBigEndian |
226 #endif
227 kLinearPCMFormatFlagIsPacked;
228 fmt.mSampleRate = s->rate;
229 fmt.mChannelsPerFrame = s->n_channels;
230 fmt.mBitsPerChannel = s->bytes_per_ch * 8;
231 fmt.mFramesPerPacket = 1; /* uncompressed audio */
232 fmt.mBytesPerFrame = fmt.mChannelsPerFrame * fmt.mBitsPerChannel / 8;
233 fmt.mBytesPerPacket = fmt.mBytesPerFrame * fmt.mFramesPerPacket;
236 * We're feeding data in to the output bus of the audio system, so we set
237 * the format description on the input scope of the device, using the very
238 * obvious element value of 0 to indicate the output bus.
240 * http://developer.apple.com/technotes/tn2002/tn2091.html
242 if (AudioUnitSetProperty(s->output_unit, kAudioUnitProperty_StreamFormat,
243 kAudioUnitScope_Input, 0, &fmt, sizeof(AudioStreamBasicDescription)) != 0) {
244 return SA_ERROR_NOT_SUPPORTED;
247 if (AudioUnitInitialize(s->output_unit) != 0) {
248 return SA_ERROR_SYSTEM;
251 return SA_SUCCESS;
256 sa_stream_destroy(sa_stream_t *s) {
258 if (s == NULL) {
259 return SA_SUCCESS;
262 pthread_mutex_lock(&s->mutex);
265 * Shut down the audio output device.
267 int result = SA_SUCCESS;
268 if (s->output_unit != NULL) {
269 if (s->playing && AudioOutputUnitStop(s->output_unit) != 0) {
270 result = SA_ERROR_SYSTEM;
272 if (AudioUnitUninitialize(s->output_unit) != 0) {
273 result = SA_ERROR_SYSTEM;
275 if (CloseComponent(s->output_unit) != noErr) {
276 result = SA_ERROR_SYSTEM;
280 pthread_mutex_unlock(&s->mutex);
283 * Release resources.
285 if (pthread_mutex_destroy(&s->mutex) != 0) {
286 result = SA_ERROR_SYSTEM;
288 while (s->bl_head != NULL) {
289 sa_buf * next = s->bl_head->next;
290 free(s->bl_head);
291 s->bl_head = next;
293 free(s);
295 return result;
301 * -----------------------------------------------------------------------------
302 * Data read and write functions
303 * -----------------------------------------------------------------------------
307 sa_stream_write(sa_stream_t *s, const void *data, size_t nbytes) {
309 if (s == NULL || s->output_unit == NULL) {
310 return SA_ERROR_NO_INIT;
312 if (nbytes == 0) {
313 return SA_SUCCESS;
316 pthread_mutex_lock(&s->mutex);
319 * Append the new data to the end of our buffer list.
321 int result = SA_SUCCESS;
322 while (1) {
323 unsigned int avail = s->bl_tail->size - s->bl_tail->end;
325 if (nbytes <= avail) {
328 * The new data will fit into the current tail buffer, so
329 * just copy it in and we're done.
331 memcpy(s->bl_tail->data + s->bl_tail->end, data, nbytes);
332 s->bl_tail->end += nbytes;
333 break;
335 } else {
338 * Copy what we can into the tail and allocate a new buffer
339 * for the rest.
341 memcpy(s->bl_tail->data + s->bl_tail->end, data, avail);
342 s->bl_tail->end += avail;
343 data = ((unsigned char *)data) + avail;
344 nbytes -= avail;
347 * If we still have data left to copy but we've hit the limit of
348 * allowable buffer allocations, we need to spin for a bit to allow
349 * the audio callback function to slurp some more data up.
351 if (nbytes > 0 && s->n_bufs == BUF_LIMIT) {
352 #ifdef TIMING_TRACE
353 printf("#"); /* too much audio data */
354 #endif
355 if (!s->playing) {
357 * We haven't even started playing yet! That means the
358 * BUF_SIZE/BUF_LIMIT values are too low... Not much we can
359 * do here; spinning won't help because the audio callback
360 * hasn't been enabled yet. Oh well, error time.
362 printf("Too much audio data received before audio device enabled!\n");
363 result = SA_ERROR_SYSTEM;
364 break;
366 while (s->n_bufs == BUF_LIMIT) {
367 pthread_mutex_unlock(&s->mutex);
368 struct timespec ts = {0, 1000000};
369 nanosleep(&ts, NULL);
370 pthread_mutex_lock(&s->mutex);
375 * Allocate a new tail buffer, and go 'round again to fill it up.
377 if ((s->bl_tail->next = new_buffer()) == NULL) {
378 result = SA_ERROR_OOM;
379 break;
381 s->n_bufs++;
382 s->bl_tail = s->bl_tail->next;
384 } /* if (nbytes <= avail), else */
386 } /* while (1) */
388 pthread_mutex_unlock(&s->mutex);
391 * Once we have our first block of audio data, enable the audio callback
392 * function. This doesn't need to be protected by the mutex, because
393 * s->playing is not used in the audio callback thread, and it's probably
394 * better not to be inside the lock when we enable the audio callback.
396 if (!s->playing) {
397 s->playing = TRUE;
398 if (AudioOutputUnitStart(s->output_unit) != 0) {
399 result = SA_ERROR_SYSTEM;
403 return result;
407 static OSStatus
408 audio_callback(
409 void * arg,
410 AudioUnitRenderActionFlags * action_flags,
411 const AudioTimeStamp * time_stamp,
412 UInt32 bus_num,
413 UInt32 n_frames,
414 AudioBufferList * data
417 #ifdef TIMING_TRACE
418 printf("."); /* audio read 'tick' */
419 #endif
422 * We're dealing with interleaved data, so the system should only
423 * have provided one buffer to be filled.
425 assert(data->mNumberBuffers == 1);
427 sa_stream_t * s = arg;
429 pthread_mutex_lock(&s->mutex);
431 unsigned char * dst = data->mBuffers[0].mData;
432 unsigned int bytes_per_frame = s->n_channels * s->bytes_per_ch;
433 unsigned int bytes_to_copy = n_frames * bytes_per_frame;
436 * Keep track of the number of bytes we've consumed so far. mSampleTime
437 * is actually the number of *frames* that have been consumed by the
438 * audio output unit so far. I don't know why it's a float.
440 assert(time_stamp->mFlags & kAudioTimeStampSampleTimeValid);
441 s->bytes_played = (int64_t)time_stamp->mSampleTime * bytes_per_frame;
444 * Consume data from the start of the buffer list.
446 while (1) {
447 assert(s->bl_head->start <= s->bl_head->end);
448 unsigned int avail = s->bl_head->end - s->bl_head->start;
450 if (avail >= bytes_to_copy) {
453 * We have all we need in the head buffer, so just grab it and go.
455 memcpy(dst, s->bl_head->data + s->bl_head->start, bytes_to_copy);
456 s->bl_head->start += bytes_to_copy;
457 break;
459 } else {
462 * Copy what we can from the head and move on to the next buffer.
464 memcpy(dst, s->bl_head->data + s->bl_head->start, avail);
465 s->bl_head->start += avail;
466 dst += avail;
467 bytes_to_copy -= avail;
470 * We want to free the now-empty buffer, but not if it's also the
471 * current tail. If it is the tail, we don't have enough data to fill
472 * the destination buffer, so we'll just zero it out and give up.
474 sa_buf * next = s->bl_head->next;
475 if (next == NULL) {
476 #ifdef TIMING_TRACE
477 printf("!"); /* not enough audio data */
478 #endif
479 memset(dst, 0, bytes_to_copy);
480 break;
482 free(s->bl_head);
483 s->bl_head = next;
484 s->n_bufs--;
486 } /* if (avail >= bytes_to_copy), else */
488 } /* while (1) */
490 pthread_mutex_unlock(&s->mutex);
491 return noErr;
497 * -----------------------------------------------------------------------------
498 * General query and support functions
499 * -----------------------------------------------------------------------------
503 sa_stream_get_write_size(sa_stream_t *s, size_t *size) {
505 if (s == NULL || s->output_unit == NULL) {
506 return SA_ERROR_NO_INIT;
509 pthread_mutex_lock(&s->mutex);
512 * Sum up the used portions of our buffers and subtract that from
513 * the pre-defined max allowed allocation.
515 sa_buf * b;
516 size_t used = 0;
517 for (b = s->bl_head; b != NULL; b = b->next) {
518 used += b->end - b->start;
520 *size = BUF_SIZE * BUF_LIMIT - used;
522 pthread_mutex_unlock(&s->mutex);
523 return SA_SUCCESS;
528 sa_stream_get_position(sa_stream_t *s, sa_position_t position, int64_t *pos) {
530 if (s == NULL || s->output_unit == NULL) {
531 return SA_ERROR_NO_INIT;
533 if (position != SA_POSITION_WRITE_SOFTWARE) {
534 return SA_ERROR_NOT_SUPPORTED;
537 pthread_mutex_lock(&s->mutex);
538 *pos = s->bytes_played;
539 pthread_mutex_unlock(&s->mutex);
540 return SA_SUCCESS;
545 sa_stream_pause(sa_stream_t *s) {
547 if (s == NULL || s->output_unit == NULL) {
548 return SA_ERROR_NO_INIT;
551 pthread_mutex_lock(&s->mutex);
552 AudioOutputUnitStop(s->output_unit);
553 pthread_mutex_unlock(&s->mutex);
554 return SA_SUCCESS;
559 sa_stream_resume(sa_stream_t *s) {
561 if (s == NULL || s->output_unit == NULL) {
562 return SA_ERROR_NO_INIT;
565 pthread_mutex_lock(&s->mutex);
568 * The audio device resets its mSampleTime counter after pausing,
569 * so we need to clear our tracking value to keep that in sync.
571 s->bytes_played = 0;
572 AudioOutputUnitStart(s->output_unit);
574 pthread_mutex_unlock(&s->mutex);
575 return SA_SUCCESS;
579 static sa_buf *
580 new_buffer(void) {
581 sa_buf * b = malloc(sizeof(sa_buf) + BUF_SIZE);
582 if (b != NULL) {
583 b->size = BUF_SIZE;
584 b->start = 0;
585 b->end = 0;
586 b->next = NULL;
588 return b;
594 * -----------------------------------------------------------------------------
595 * Extension functions
596 * -----------------------------------------------------------------------------
600 sa_stream_set_volume_abs(sa_stream_t *s, float vol) {
602 if (s == NULL || s->output_unit == NULL) {
603 return SA_ERROR_NO_INIT;
606 pthread_mutex_lock(&s->mutex);
607 AudioUnitSetParameter(s->output_unit, kHALOutputParam_Volume,
608 kAudioUnitParameterFlag_Output, 0, vol, 0);
609 pthread_mutex_unlock(&s->mutex);
610 return SA_SUCCESS;
615 sa_stream_get_volume_abs(sa_stream_t *s, float *vol) {
617 if (s == NULL || s->output_unit == NULL) {
618 return SA_ERROR_NO_INIT;
621 pthread_mutex_lock(&s->mutex);
622 Float32 local_vol = 0;
623 AudioUnitGetParameter(s->output_unit, kHALOutputParam_Volume,
624 kAudioUnitParameterFlag_Output, 0, &local_vol);
625 *vol = local_vol;
626 pthread_mutex_unlock(&s->mutex);
627 return SA_SUCCESS;
633 * -----------------------------------------------------------------------------
634 * Unsupported functions
635 * -----------------------------------------------------------------------------
637 #define UNSUPPORTED(func) func { return SA_ERROR_NOT_SUPPORTED; }
639 UNSUPPORTED(int sa_stream_create_opaque(sa_stream_t **s, const char *client_name, sa_mode_t mode, const char *codec))
640 UNSUPPORTED(int sa_stream_set_write_lower_watermark(sa_stream_t *s, size_t size))
641 UNSUPPORTED(int sa_stream_set_read_lower_watermark(sa_stream_t *s, size_t size))
642 UNSUPPORTED(int sa_stream_set_write_upper_watermark(sa_stream_t *s, size_t size))
643 UNSUPPORTED(int sa_stream_set_read_upper_watermark(sa_stream_t *s, size_t size))
644 UNSUPPORTED(int sa_stream_set_channel_map(sa_stream_t *s, const sa_channel_t map[], unsigned int n))
645 UNSUPPORTED(int sa_stream_set_xrun_mode(sa_stream_t *s, sa_xrun_mode_t mode))
646 UNSUPPORTED(int sa_stream_set_non_interleaved(sa_stream_t *s, int enable))
647 UNSUPPORTED(int sa_stream_set_dynamic_rate(sa_stream_t *s, int enable))
648 UNSUPPORTED(int sa_stream_set_driver(sa_stream_t *s, const char *driver))
649 UNSUPPORTED(int sa_stream_start_thread(sa_stream_t *s, sa_event_callback_t callback))
650 UNSUPPORTED(int sa_stream_stop_thread(sa_stream_t *s))
651 UNSUPPORTED(int sa_stream_change_device(sa_stream_t *s, const char *device_name))
652 UNSUPPORTED(int sa_stream_change_read_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
653 UNSUPPORTED(int sa_stream_change_write_volume(sa_stream_t *s, const int32_t vol[], unsigned int n))
654 UNSUPPORTED(int sa_stream_change_rate(sa_stream_t *s, unsigned int rate))
655 UNSUPPORTED(int sa_stream_change_meta_data(sa_stream_t *s, const char *name, const void *data, size_t size))
656 UNSUPPORTED(int sa_stream_change_user_data(sa_stream_t *s, const void *value))
657 UNSUPPORTED(int sa_stream_set_adjust_rate(sa_stream_t *s, sa_adjust_t direction))
658 UNSUPPORTED(int sa_stream_set_adjust_nchannels(sa_stream_t *s, sa_adjust_t direction))
659 UNSUPPORTED(int sa_stream_set_adjust_pcm_format(sa_stream_t *s, sa_adjust_t direction))
660 UNSUPPORTED(int sa_stream_set_adjust_watermarks(sa_stream_t *s, sa_adjust_t direction))
661 UNSUPPORTED(int sa_stream_get_mode(sa_stream_t *s, sa_mode_t *access_mode))
662 UNSUPPORTED(int sa_stream_get_codec(sa_stream_t *s, char *codec, size_t *size))
663 UNSUPPORTED(int sa_stream_get_pcm_format(sa_stream_t *s, sa_pcm_format_t *format))
664 UNSUPPORTED(int sa_stream_get_rate(sa_stream_t *s, unsigned int *rate))
665 UNSUPPORTED(int sa_stream_get_nchannels(sa_stream_t *s, int *nchannels))
666 UNSUPPORTED(int sa_stream_get_user_data(sa_stream_t *s, void **value))
667 UNSUPPORTED(int sa_stream_get_write_lower_watermark(sa_stream_t *s, size_t *size))
668 UNSUPPORTED(int sa_stream_get_read_lower_watermark(sa_stream_t *s, size_t *size))
669 UNSUPPORTED(int sa_stream_get_write_upper_watermark(sa_stream_t *s, size_t *size))
670 UNSUPPORTED(int sa_stream_get_read_upper_watermark(sa_stream_t *s, size_t *size))
671 UNSUPPORTED(int sa_stream_get_channel_map(sa_stream_t *s, sa_channel_t map[], unsigned int *n))
672 UNSUPPORTED(int sa_stream_get_xrun_mode(sa_stream_t *s, sa_xrun_mode_t *mode))
673 UNSUPPORTED(int sa_stream_get_non_interleaved(sa_stream_t *s, int *enabled))
674 UNSUPPORTED(int sa_stream_get_dynamic_rate(sa_stream_t *s, int *enabled))
675 UNSUPPORTED(int sa_stream_get_driver(sa_stream_t *s, char *driver_name, size_t *size))
676 UNSUPPORTED(int sa_stream_get_device(sa_stream_t *s, char *device_name, size_t *size))
677 UNSUPPORTED(int sa_stream_get_read_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
678 UNSUPPORTED(int sa_stream_get_write_volume(sa_stream_t *s, int32_t vol[], unsigned int *n))
679 UNSUPPORTED(int sa_stream_get_meta_data(sa_stream_t *s, const char *name, void*data, size_t *size))
680 UNSUPPORTED(int sa_stream_get_adjust_rate(sa_stream_t *s, sa_adjust_t *direction))
681 UNSUPPORTED(int sa_stream_get_adjust_nchannels(sa_stream_t *s, sa_adjust_t *direction))
682 UNSUPPORTED(int sa_stream_get_adjust_pcm_format(sa_stream_t *s, sa_adjust_t *direction))
683 UNSUPPORTED(int sa_stream_get_adjust_watermarks(sa_stream_t *s, sa_adjust_t *direction))
684 UNSUPPORTED(int sa_stream_get_state(sa_stream_t *s, sa_state_t *state))
685 UNSUPPORTED(int sa_stream_get_event_error(sa_stream_t *s, sa_error_t *error))
686 UNSUPPORTED(int sa_stream_get_event_notify(sa_stream_t *s, sa_notify_t *notify))
687 UNSUPPORTED(int sa_stream_read(sa_stream_t *s, void *data, size_t nbytes))
688 UNSUPPORTED(int sa_stream_read_ni(sa_stream_t *s, unsigned int channel, void *data, size_t nbytes))
689 UNSUPPORTED(int sa_stream_write_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes))
690 UNSUPPORTED(int sa_stream_pwrite(sa_stream_t *s, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence))
691 UNSUPPORTED(int sa_stream_pwrite_ni(sa_stream_t *s, unsigned int channel, const void *data, size_t nbytes, int64_t offset, sa_seek_t whence))
692 UNSUPPORTED(int sa_stream_get_read_size(sa_stream_t *s, size_t *size))
693 UNSUPPORTED(int sa_stream_drain(sa_stream_t *s))
695 const char *sa_strerror(int code) { return NULL; }