source: mainline/uspace/app/wavplay/dplay.c@ 984a9ba

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 984a9ba was 984a9ba, checked in by Martin Decky <martin@…>, 7 years ago

do not expose the call capability handler from the async framework

Keep the call capability handler encapsulated within the async framework
and do not expose it explicitly via its API. Use the pointer to
ipc_call_t as the sole object identifying an IPC call in the code that
uses the async framework.

This plugs a major leak in the abstraction and also simplifies both the
async framework (slightly) and all IPC servers.

  • Property mode set to 100644
File size: 11.7 KB
Line 
1/*
2 * Copyright (c) 2012 Jan Vesely
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
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.
16 *
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.
27 */
28
29/** @addtogroup dplay
30 * @{
31 */
32/**
33 * @file PCM playback audio devices
34 */
35
36#include <assert.h>
37#include <errno.h>
38#include <str_error.h>
39#include <str.h>
40#include <audio_pcm_iface.h>
41#include <fibril_synch.h>
42#include <pcm/format.h>
43#include <as.h>
44#include <sys/time.h>
45#include <inttypes.h>
46#include <stdbool.h>
47#include <stdio.h>
48#include <macros.h>
49
50#include "wave.h"
51#include "dplay.h"
52
53#define DEFAULT_FRAGMENTS 2
54
55/** Playback helper structure */
56typedef struct {
57 struct {
58 void *base;
59 size_t size;
60 void *write_ptr;
61 } buffer;
62 pcm_format_t f;
63 FILE *source;
64 volatile bool playing;
65 fibril_mutex_t mutex;
66 fibril_condvar_t cv;
67 audio_pcm_sess_t *device;
68} playback_t;
69
70/**
71 * Initialize playback helper structure.
72 * @param pb Pointer to helper structure to initialize
73 * @param sess Pointer to audio device IPC session
74 * @return
75 */
76static void playback_initialize(playback_t *pb, audio_pcm_sess_t *sess)
77{
78 assert(sess);
79 assert(pb);
80 pb->buffer.base = NULL;
81 pb->buffer.size = 0;
82 pb->buffer.write_ptr = NULL;
83 pb->playing = false;
84 pb->source = NULL;
85 pb->device = sess;
86 fibril_mutex_initialize(&pb->mutex);
87 fibril_condvar_initialize(&pb->cv);
88}
89
90/** Fragment playback callback function.
91 *
92 * @param icall Pointer to the call structure
93 * @param arg Argument, pointer to the playback helper function
94 *
95 */
96static void device_event_callback(ipc_call_t *icall, void *arg)
97{
98 async_answer_0(icall, EOK);
99 playback_t *pb = arg;
100 const size_t fragment_size = pb->buffer.size / DEFAULT_FRAGMENTS;
101
102 while (true) {
103 ipc_call_t call;
104 async_get_call(&call);
105
106 switch (IPC_GET_IMETHOD(call)) {
107 case PCM_EVENT_PLAYBACK_STARTED:
108 case PCM_EVENT_FRAMES_PLAYED:
109 printf("%" PRIun " frames: ", IPC_GET_ARG1(call));
110 async_answer_0(&call, EOK);
111 break;
112 case PCM_EVENT_PLAYBACK_TERMINATED:
113 printf("Playback terminated\n");
114 fibril_mutex_lock(&pb->mutex);
115 pb->playing = false;
116 fibril_condvar_signal(&pb->cv);
117 async_answer_0(&call, EOK);
118 fibril_mutex_unlock(&pb->mutex);
119 return;
120 default:
121 printf("Unknown event %" PRIun ".\n", IPC_GET_IMETHOD(call));
122 async_answer_0(&call, ENOTSUP);
123 continue;
124
125 }
126 const size_t bytes = fread(pb->buffer.write_ptr,
127 sizeof(uint8_t), fragment_size, pb->source);
128 printf("Copied from position %p size %zu/%zu\n",
129 pb->buffer.write_ptr, bytes, fragment_size);
130 if (bytes == 0) {
131 audio_pcm_last_playback_fragment(pb->device);
132 }
133 /* any constant is silence */
134 memset(pb->buffer.write_ptr + bytes, 0, fragment_size - bytes);
135 pb->buffer.write_ptr += fragment_size;
136
137 if (pb->buffer.write_ptr >= (pb->buffer.base + pb->buffer.size))
138 pb->buffer.write_ptr -= pb->buffer.size;
139 }
140}
141
142/**
143 * Start event based playback.
144 * @param pb Playback helper structure.
145 */
146static void play_fragment(playback_t *pb)
147{
148 assert(pb);
149 assert(pb->device);
150 const size_t fragment_size = pb->buffer.size / DEFAULT_FRAGMENTS;
151 printf("Registering event callback\n");
152 errno_t ret = audio_pcm_register_event_callback(pb->device,
153 device_event_callback, pb);
154 if (ret != EOK) {
155 printf("Failed to register event callback: %s.\n",
156 str_error(ret));
157 return;
158 }
159 printf("Playing: %dHz, %s, %d channel(s).\n", pb->f.sampling_rate,
160 pcm_sample_format_str(pb->f.sample_format), pb->f.channels);
161 const size_t bytes = fread(pb->buffer.base, sizeof(uint8_t),
162 fragment_size, pb->source);
163 if (bytes != fragment_size)
164 memset(pb->buffer.base + bytes, 0, fragment_size - bytes);
165 printf("Initial: Copied from position %p size %zu/%zu\n",
166 pb->buffer.base, bytes, fragment_size);
167 pb->buffer.write_ptr = pb->buffer.base + fragment_size;
168 fibril_mutex_lock(&pb->mutex);
169 const unsigned frames =
170 pcm_format_size_to_frames(fragment_size, &pb->f);
171 ret = audio_pcm_start_playback_fragment(pb->device, frames,
172 pb->f.channels, pb->f.sampling_rate, pb->f.sample_format);
173 if (ret != EOK) {
174 fibril_mutex_unlock(&pb->mutex);
175 printf("Failed to start playback: %s.\n", str_error(ret));
176 audio_pcm_unregister_event_callback(pb->device);
177 return;
178 }
179
180 pb->playing = true;
181 while (pb->playing)
182 fibril_condvar_wait(&pb->cv, &pb->mutex);
183
184 fibril_mutex_unlock(&pb->mutex);
185 printf("\n");
186 audio_pcm_unregister_event_callback(pb->device);
187}
188
189/**
190 * Count occupied space in a cyclic buffer.
191 * @param pb Playback helper structure.
192 * @param pos read pointer position.
193 * @return Occupied space size.
194 */
195static size_t buffer_occupied(const playback_t *pb, size_t pos)
196{
197 assert(pb);
198 void *read_ptr = pb->buffer.base + pos;
199 if (read_ptr > pb->buffer.write_ptr)
200 return pb->buffer.write_ptr + pb->buffer.size - read_ptr;
201 return pb->buffer.write_ptr - read_ptr;
202
203}
204
205/**
206 * Count available space in a cyclic buffer.
207 * @param pb Playback helper structure.
208 * @param pos read pointer position.
209 * @return Free space size.
210 */
211static size_t buffer_avail(const playback_t *pb, size_t pos)
212{
213 assert(pb);
214 void *read_ptr = pb->buffer.base + pos;
215 if (read_ptr <= pb->buffer.write_ptr)
216 return read_ptr + pb->buffer.size - pb->buffer.write_ptr - 1;
217 return (read_ptr - pb->buffer.write_ptr) - 1;
218}
219
220/**
221 * Size of the space between write pointer and the end of a cyclic buffer
222 * @param pb Playback helper structure.
223 */
224static size_t buffer_remain(const playback_t *pb)
225{
226 assert(pb);
227 return (pb->buffer.base + pb->buffer.size) - pb->buffer.write_ptr;
228}
229
230/**
231 * Move write pointer forward. Wrap around the end.
232 * @param pb Playback helper structure.
233 * @param bytes NUmber of bytes to advance.
234 */
235static void buffer_advance(playback_t *pb, size_t bytes)
236{
237 assert(pb);
238 pb->buffer.write_ptr += bytes;
239 while (pb->buffer.write_ptr >= (pb->buffer.base + pb->buffer.size))
240 pb->buffer.write_ptr -= pb->buffer.size;
241}
242
243#define DPRINTF(f, ...) \
244 printf("%.2lu:%.6lu "f, time.tv_sec % 100, time.tv_usec, __VA_ARGS__)
245
246/**
247 * Start playback using buffer position api.
248 * @param pb Playback helper function.
249 */
250static void play(playback_t *pb)
251{
252 assert(pb);
253 assert(pb->device);
254 pb->buffer.write_ptr = pb->buffer.base;
255 printf("Playing: %dHz, %s, %d channel(s).\n", pb->f.sampling_rate,
256 pcm_sample_format_str(pb->f.sample_format), pb->f.channels);
257 useconds_t work_time = 50000; /* 50 ms */
258 bool started = false;
259 size_t pos = 0;
260 struct timeval time = { 0 };
261 getuptime(&time);
262 while (true) {
263 size_t available = buffer_avail(pb, pos);
264 /*
265 * Writing might need wrap around the end,
266 * read directly to device buffer
267 */
268 size_t bytes = fread(pb->buffer.write_ptr, sizeof(uint8_t),
269 min(available, buffer_remain(pb)), pb->source);
270 buffer_advance(pb, bytes);
271 DPRINTF("POS %zu: %zu bytes free in buffer, read %zu, wp %zu\n",
272 pos, available, bytes,
273 pb->buffer.write_ptr - pb->buffer.base);
274 available -= bytes;
275
276 /* continue if we wrapped around the end */
277 if (available) {
278 bytes = fread(pb->buffer.write_ptr,
279 sizeof(uint8_t), min(available, buffer_remain(pb)),
280 pb->source);
281 buffer_advance(pb, bytes);
282 DPRINTF("POS %zu: %zu bytes still free in buffer, "
283 "read %zu, wp %zu\n", pos, available, bytes,
284 pb->buffer.write_ptr - pb->buffer.base);
285 available -= bytes;
286 }
287
288 if (!started) {
289 errno_t ret = audio_pcm_start_playback(pb->device,
290 pb->f.channels, pb->f.sampling_rate,
291 pb->f.sample_format);
292 if (ret != EOK) {
293 printf("Failed to start playback: %s\n",
294 str_error(ret));
295 return;
296 }
297 started = true;
298 ret = audio_pcm_get_buffer_pos(pb->device, &pos);
299 if (ret != EOK) {
300 printf("Failed to update position indicator "
301 "%s\n", str_error(ret));
302 }
303 }
304 const size_t to_play = buffer_occupied(pb, pos);
305 const useconds_t usecs =
306 pcm_format_size_to_usec(to_play, &pb->f);
307
308 /* Compute delay time */
309 const useconds_t real_delay = (usecs > work_time) ?
310 usecs - work_time : 0;
311 DPRINTF("POS %zu: %u usecs (%u) to play %zu bytes.\n",
312 pos, usecs, real_delay, to_play);
313 if (real_delay)
314 async_usleep(real_delay);
315 /* update buffer position */
316 const errno_t ret = audio_pcm_get_buffer_pos(pb->device, &pos);
317 if (ret != EOK) {
318 printf("Failed to update position indicator %s\n",
319 str_error(ret));
320 }
321 getuptime(&time);
322
323 /*
324 * we did not use all the space we had,
325 * that is the end
326 */
327 if (available)
328 break;
329
330 }
331 audio_pcm_stop_playback_immediate(pb->device);
332}
333
334/**
335 * Play audio file usign direct device access.
336 * @param device The device.
337 * @param file The file.
338 * @return 0 on success, non-zero on failure.
339 */
340int dplay(const char *device, const char *file)
341{
342 errno_t ret = EOK;
343 audio_pcm_sess_t *session = NULL;
344 if (str_cmp(device, "default") == 0) {
345 session = audio_pcm_open_default();
346 } else {
347 session = audio_pcm_open(device);
348 }
349 if (!session) {
350 printf("Failed to connect to device %s.\n", device);
351 return 1;
352 }
353 printf("Playing on device: %s.\n", device);
354 sysarg_t val;
355 ret = audio_pcm_query_cap(session, AUDIO_CAP_PLAYBACK, &val);
356 if (ret != EOK || !val) {
357 printf("Device %s does not support playback\n", device);
358 ret = ENOTSUP;
359 goto close_session;
360 }
361
362 char *info = NULL;
363 ret = audio_pcm_get_info_str(session, &info);
364 if (ret != EOK) {
365 printf("Failed to get PCM info: %s.\n", str_error(ret));
366 goto close_session;
367 }
368 printf("Playing on %s.\n", info);
369 free(info);
370
371 playback_t pb;
372 playback_initialize(&pb, session);
373
374 ret = audio_pcm_get_buffer(pb.device, &pb.buffer.base, &pb.buffer.size);
375 if (ret != EOK) {
376 printf("Failed to get PCM buffer: %s.\n", str_error(ret));
377 goto close_session;
378 }
379 printf("Buffer: %p %zu.\n", pb.buffer.base, pb.buffer.size);
380
381 pb.source = fopen(file, "rb");
382 if (pb.source == NULL) {
383 ret = ENOENT;
384 printf("Failed to open file: %s.\n", file);
385 goto cleanup;
386 }
387
388 wave_header_t header;
389 fread(&header, sizeof(header), 1, pb.source);
390 const char *error;
391 ret = wav_parse_header(&header, NULL, NULL,
392 &pb.f.channels, &pb.f.sampling_rate, &pb.f.sample_format, &error);
393 if (ret != EOK) {
394 printf("Error parsing wav header: %s.\n", error);
395 goto cleanup;
396 }
397 ret = audio_pcm_query_cap(pb.device, AUDIO_CAP_BUFFER_POS, &val);
398 if (ret == EOK && val) {
399 play(&pb);
400 } else {
401 ret = audio_pcm_query_cap(pb.device, AUDIO_CAP_INTERRUPT, &val);
402 if (ret == EOK && val)
403 play_fragment(&pb);
404 else
405 printf("Neither playing method is supported");
406 }
407
408cleanup:
409 fclose(pb.source);
410 as_area_destroy(pb.buffer.base);
411 audio_pcm_release_buffer(pb.device);
412close_session:
413 audio_pcm_close(session);
414 return ret == EOK ? 0 : 1;
415}
416/**
417 * @}
418 */
Note: See TracBrowser for help on using the repository browser.