2 * Copyright (c) 2012 Jan Vesely
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 * @file PCM playback audio devices
38 #include <str_error.h>
40 #include <audio_pcm_iface.h>
41 #include <fibril_synch.h>
42 #include <pcm/format.h>
53 #define DEFAULT_FRAGMENTS 2
55 /** Playback helper structure */
64 volatile bool playing
;
67 audio_pcm_sess_t
*device
;
71 * Initialize playback helper structure.
72 * @param pb Pointer to helper structure to initialize
73 * @param sess Pointer to audio device IPC session
76 static void playback_initialize(playback_t
*pb
, audio_pcm_sess_t
*sess
)
80 pb
->buffer
.base
= NULL
;
82 pb
->buffer
.write_ptr
= NULL
;
86 fibril_mutex_initialize(&pb
->mutex
);
87 fibril_condvar_initialize(&pb
->cv
);
91 * Fragment playback callback function.
92 * @param iid IPC call id.
93 * @param icall Pointer to the call structure
94 * @param arg Argument, pointer to the playback helper function
96 static void device_event_callback(ipc_callid_t iid
, ipc_call_t
*icall
, void* arg
)
98 async_answer_0(iid
, EOK
);
100 const size_t fragment_size
= pb
->buffer
.size
/ DEFAULT_FRAGMENTS
;
103 ipc_callid_t callid
= async_get_call(&call
);
104 switch(IPC_GET_IMETHOD(call
)) {
105 case PCM_EVENT_PLAYBACK_STARTED
:
106 case PCM_EVENT_FRAMES_PLAYED
:
107 printf("%" PRIun
" frames: ", IPC_GET_ARG1(call
));
108 async_answer_0(callid
, EOK
);
110 case PCM_EVENT_PLAYBACK_TERMINATED
:
111 printf("Playback terminated\n");
112 fibril_mutex_lock(&pb
->mutex
);
114 fibril_condvar_signal(&pb
->cv
);
115 async_answer_0(callid
, EOK
);
116 fibril_mutex_unlock(&pb
->mutex
);
119 printf("Unknown event %" PRIun
".\n", IPC_GET_IMETHOD(call
));
120 async_answer_0(callid
, ENOTSUP
);
124 const size_t bytes
= fread(pb
->buffer
.write_ptr
,
125 sizeof(uint8_t), fragment_size
, pb
->source
);
126 printf("Copied from position %p size %zu/%zu\n",
127 pb
->buffer
.write_ptr
, bytes
, fragment_size
);
129 audio_pcm_last_playback_fragment(pb
->device
);
131 /* any constant is silence */
132 memset(pb
->buffer
.write_ptr
+ bytes
, 0, fragment_size
- bytes
);
133 pb
->buffer
.write_ptr
+= fragment_size
;
135 if (pb
->buffer
.write_ptr
>= (pb
->buffer
.base
+ pb
->buffer
.size
))
136 pb
->buffer
.write_ptr
-= pb
->buffer
.size
;
141 * Start event based playback.
142 * @param pb Playback helper structure.
144 static void play_fragment(playback_t
*pb
)
148 const size_t fragment_size
= pb
->buffer
.size
/ DEFAULT_FRAGMENTS
;
149 printf("Registering event callback\n");
150 errno_t ret
= audio_pcm_register_event_callback(pb
->device
,
151 device_event_callback
, pb
);
153 printf("Failed to register event callback: %s.\n",
157 printf("Playing: %dHz, %s, %d channel(s).\n", pb
->f
.sampling_rate
,
158 pcm_sample_format_str(pb
->f
.sample_format
), pb
->f
.channels
);
159 const size_t bytes
= fread(pb
->buffer
.base
, sizeof(uint8_t),
160 fragment_size
, pb
->source
);
161 if (bytes
!= fragment_size
)
162 memset(pb
->buffer
.base
+ bytes
, 0, fragment_size
- bytes
);
163 printf("Initial: Copied from position %p size %zu/%zu\n",
164 pb
->buffer
.base
, bytes
, fragment_size
);
165 pb
->buffer
.write_ptr
= pb
->buffer
.base
+ fragment_size
;
166 fibril_mutex_lock(&pb
->mutex
);
167 const unsigned frames
=
168 pcm_format_size_to_frames(fragment_size
, &pb
->f
);
169 ret
= audio_pcm_start_playback_fragment(pb
->device
, frames
,
170 pb
->f
.channels
, pb
->f
.sampling_rate
, pb
->f
.sample_format
);
172 fibril_mutex_unlock(&pb
->mutex
);
173 printf("Failed to start playback: %s.\n", str_error(ret
));
174 audio_pcm_unregister_event_callback(pb
->device
);
178 for (pb
->playing
= true; pb
->playing
;
179 fibril_condvar_wait(&pb
->cv
, &pb
->mutex
));
181 fibril_mutex_unlock(&pb
->mutex
);
183 audio_pcm_unregister_event_callback(pb
->device
);
187 * Count occupied space in a cyclic buffer.
188 * @param pb Playback helper structure.
189 * @param pos read pointer position.
190 * @return Occupied space size.
192 static size_t buffer_occupied(const playback_t
*pb
, size_t pos
)
195 void *read_ptr
= pb
->buffer
.base
+ pos
;
196 if (read_ptr
> pb
->buffer
.write_ptr
)
197 return pb
->buffer
.write_ptr
+ pb
->buffer
.size
- read_ptr
;
198 return pb
->buffer
.write_ptr
- read_ptr
;
203 * Count available space in a cyclic buffer.
204 * @param pb Playback helper structure.
205 * @param pos read pointer position.
206 * @return Free space size.
208 static size_t buffer_avail(const playback_t
*pb
, size_t pos
)
211 void *read_ptr
= pb
->buffer
.base
+ pos
;
212 if (read_ptr
<= pb
->buffer
.write_ptr
)
213 return read_ptr
+ pb
->buffer
.size
- pb
->buffer
.write_ptr
- 1;
214 return (read_ptr
- pb
->buffer
.write_ptr
) - 1;
218 * Size of the space between write pointer and the end of a cyclic buffer
219 * @param pb Playback helper structure.
221 static size_t buffer_remain(const playback_t
*pb
)
224 return (pb
->buffer
.base
+ pb
->buffer
.size
) - pb
->buffer
.write_ptr
;
228 * Move write pointer forward. Wrap around the end.
229 * @param pb Playback helper structure.
230 * @param bytes NUmber of bytes to advance.
232 static void buffer_advance(playback_t
*pb
, size_t bytes
)
235 pb
->buffer
.write_ptr
+= bytes
;
236 while (pb
->buffer
.write_ptr
>= (pb
->buffer
.base
+ pb
->buffer
.size
))
237 pb
->buffer
.write_ptr
-= pb
->buffer
.size
;
240 #define DPRINTF(f, ...) \
241 printf("%.2lu:%.6lu "f, time.tv_sec % 100, time.tv_usec, __VA_ARGS__)
244 * Start playback using buffer position api.
245 * @param pb Playback helper function.
247 static void play(playback_t
*pb
)
251 pb
->buffer
.write_ptr
= pb
->buffer
.base
;
252 printf("Playing: %dHz, %s, %d channel(s).\n", pb
->f
.sampling_rate
,
253 pcm_sample_format_str(pb
->f
.sample_format
), pb
->f
.channels
);
254 useconds_t work_time
= 50000; /* 50 ms */
255 bool started
= false;
257 struct timeval time
= { 0 };
260 size_t available
= buffer_avail(pb
, pos
);
261 /* Writing might need wrap around the end,
262 * read directly to device buffer */
263 size_t bytes
= fread(pb
->buffer
.write_ptr
, sizeof(uint8_t),
264 min(available
, buffer_remain(pb
)), pb
->source
);
265 buffer_advance(pb
, bytes
);
266 DPRINTF("POS %zu: %zu bytes free in buffer, read %zu, wp %zu\n",
267 pos
, available
, bytes
,
268 pb
->buffer
.write_ptr
- pb
->buffer
.base
);
271 /* continue if we wrapped around the end */
273 bytes
= fread(pb
->buffer
.write_ptr
,
274 sizeof(uint8_t), min(available
, buffer_remain(pb
)),
276 buffer_advance(pb
, bytes
);
277 DPRINTF("POS %zu: %zu bytes still free in buffer, "
278 "read %zu, wp %zu\n", pos
, available
, bytes
,
279 pb
->buffer
.write_ptr
- pb
->buffer
.base
);
284 errno_t ret
= audio_pcm_start_playback(pb
->device
,
285 pb
->f
.channels
, pb
->f
.sampling_rate
,
286 pb
->f
.sample_format
);
288 printf("Failed to start playback: %s\n",
293 ret
= audio_pcm_get_buffer_pos(pb
->device
, &pos
);
295 printf("Failed to update position indicator "
296 "%s\n", str_error(ret
));
299 const size_t to_play
= buffer_occupied(pb
, pos
);
300 const useconds_t usecs
=
301 pcm_format_size_to_usec(to_play
, &pb
->f
);
303 /* Compute delay time */
304 const useconds_t real_delay
= (usecs
> work_time
)
305 ? usecs
- work_time
: 0;
306 DPRINTF("POS %zu: %u usecs (%u) to play %zu bytes.\n",
307 pos
, usecs
, real_delay
, to_play
);
309 async_usleep(real_delay
);
310 /* update buffer position */
311 const errno_t ret
= audio_pcm_get_buffer_pos(pb
->device
, &pos
);
313 printf("Failed to update position indicator %s\n",
318 /* we did not use all the space we had,
324 audio_pcm_stop_playback_immediate(pb
->device
);
328 * Play audio file usign direct device access.
329 * @param device The device.
330 * @param file The file.
331 * @return 0 on success, non-zero on failure.
333 int dplay(const char *device
, const char *file
)
336 audio_pcm_sess_t
*session
= NULL
;
337 if (str_cmp(device
, "default") == 0) {
338 session
= audio_pcm_open_default();
340 session
= audio_pcm_open(device
);
343 printf("Failed to connect to device %s.\n", device
);
346 printf("Playing on device: %s.\n", device
);
348 ret
= audio_pcm_query_cap(session
, AUDIO_CAP_PLAYBACK
, &val
);
349 if (ret
!= EOK
|| !val
) {
350 printf("Device %s does not support playback\n", device
);
356 ret
= audio_pcm_get_info_str(session
, &info
);
358 printf("Failed to get PCM info: %s.\n", str_error(ret
));
361 printf("Playing on %s.\n", info
);
365 playback_initialize(&pb
, session
);
367 ret
= audio_pcm_get_buffer(pb
.device
, &pb
.buffer
.base
, &pb
.buffer
.size
);
369 printf("Failed to get PCM buffer: %s.\n", str_error(ret
));
372 printf("Buffer: %p %zu.\n", pb
.buffer
.base
, pb
.buffer
.size
);
374 pb
.source
= fopen(file
, "rb");
375 if (pb
.source
== NULL
) {
377 printf("Failed to open file: %s.\n", file
);
381 wave_header_t header
;
382 fread(&header
, sizeof(header
), 1, pb
.source
);
384 ret
= wav_parse_header(&header
, NULL
, NULL
,
385 &pb
.f
.channels
, &pb
.f
.sampling_rate
, &pb
.f
.sample_format
, &error
);
387 printf("Error parsing wav header: %s.\n", error
);
390 ret
= audio_pcm_query_cap(pb
.device
, AUDIO_CAP_BUFFER_POS
, &val
);
391 if (ret
== EOK
&& val
) {
394 ret
= audio_pcm_query_cap(pb
.device
, AUDIO_CAP_INTERRUPT
, &val
);
395 if (ret
== EOK
&& val
)
398 printf("Neither playing method is supported");
403 as_area_destroy(pb
.buffer
.base
);
404 audio_pcm_release_buffer(pb
.device
);
406 audio_pcm_close(session
);
407 return ret
== EOK
? 0 : 1;