source: mainline/uspace/app/wavplay/dplay.c@ 64ce0c1

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 64ce0c1 was 33b8d024, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 8 years ago

Remove const qualifier from the argument of free() and realloc(),
as well as in numerous other variables that hold ownership of memory.

By convention, a pointer that holds ownership is _never_ qualified by const.
This is reflected in the standard type signature of free() and realloc().
Allowing const pointers to hold ownership may seem superficially convenient,
but is actually quite confusing to experienced C programmers.

  • 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
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/**
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
95 */
96static void device_event_callback(ipc_callid_t iid, ipc_call_t *icall, void* arg)
97{
98 async_answer_0(iid, EOK);
99 playback_t *pb = arg;
100 const size_t fragment_size = pb->buffer.size / DEFAULT_FRAGMENTS;
101 while (1) {
102 ipc_call_t call;
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);
109 break;
110 case PCM_EVENT_PLAYBACK_TERMINATED:
111 printf("Playback terminated\n");
112 fibril_mutex_lock(&pb->mutex);
113 pb->playing = false;
114 fibril_condvar_signal(&pb->cv);
115 async_answer_0(callid, EOK);
116 fibril_mutex_unlock(&pb->mutex);
117 return;
118 default:
119 printf("Unknown event %" PRIun ".\n", IPC_GET_IMETHOD(call));
120 async_answer_0(callid, ENOTSUP);
121 continue;
122
123 }
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);
128 if (bytes == 0) {
129 audio_pcm_last_playback_fragment(pb->device);
130 }
131 /* any constant is silence */
132 memset(pb->buffer.write_ptr + bytes, 0, fragment_size - bytes);
133 pb->buffer.write_ptr += fragment_size;
134
135 if (pb->buffer.write_ptr >= (pb->buffer.base + pb->buffer.size))
136 pb->buffer.write_ptr -= pb->buffer.size;
137 }
138}
139
140/**
141 * Start event based playback.
142 * @param pb Playback helper structure.
143 */
144static void play_fragment(playback_t *pb)
145{
146 assert(pb);
147 assert(pb->device);
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);
152 if (ret != EOK) {
153 printf("Failed to register event callback: %s.\n",
154 str_error(ret));
155 return;
156 }
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);
171 if (ret != EOK) {
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);
175 return;
176 }
177
178 for (pb->playing = true; pb->playing;
179 fibril_condvar_wait(&pb->cv, &pb->mutex));
180
181 fibril_mutex_unlock(&pb->mutex);
182 printf("\n");
183 audio_pcm_unregister_event_callback(pb->device);
184}
185
186/**
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.
191 */
192static size_t buffer_occupied(const playback_t *pb, size_t pos)
193{
194 assert(pb);
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;
199
200}
201
202/**
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.
207 */
208static size_t buffer_avail(const playback_t *pb, size_t pos)
209{
210 assert(pb);
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;
215}
216
217/**
218 * Size of the space between write pointer and the end of a cyclic buffer
219 * @param pb Playback helper structure.
220 */
221static size_t buffer_remain(const playback_t *pb)
222{
223 assert(pb);
224 return (pb->buffer.base + pb->buffer.size) - pb->buffer.write_ptr;
225}
226
227/**
228 * Move write pointer forward. Wrap around the end.
229 * @param pb Playback helper structure.
230 * @param bytes NUmber of bytes to advance.
231 */
232static void buffer_advance(playback_t *pb, size_t bytes)
233{
234 assert(pb);
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;
238}
239
240#define DPRINTF(f, ...) \
241 printf("%.2lu:%.6lu "f, time.tv_sec % 100, time.tv_usec, __VA_ARGS__)
242
243/**
244 * Start playback using buffer position api.
245 * @param pb Playback helper function.
246 */
247static void play(playback_t *pb)
248{
249 assert(pb);
250 assert(pb->device);
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;
256 size_t pos = 0;
257 struct timeval time = { 0 };
258 getuptime(&time);
259 do {
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);
269 available -= bytes;
270
271 /* continue if we wrapped around the end */
272 if (available) {
273 bytes = fread(pb->buffer.write_ptr,
274 sizeof(uint8_t), min(available, buffer_remain(pb)),
275 pb->source);
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);
280 available -= bytes;
281 }
282
283 if (!started) {
284 errno_t ret = audio_pcm_start_playback(pb->device,
285 pb->f.channels, pb->f.sampling_rate,
286 pb->f.sample_format);
287 if (ret != EOK) {
288 printf("Failed to start playback: %s\n",
289 str_error(ret));
290 return;
291 }
292 started = true;
293 ret = audio_pcm_get_buffer_pos(pb->device, &pos);
294 if (ret != EOK) {
295 printf("Failed to update position indicator "
296 "%s\n", str_error(ret));
297 }
298 }
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);
302
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);
308 if (real_delay)
309 async_usleep(real_delay);
310 /* update buffer position */
311 const errno_t ret = audio_pcm_get_buffer_pos(pb->device, &pos);
312 if (ret != EOK) {
313 printf("Failed to update position indicator %s\n",
314 str_error(ret));
315 }
316 getuptime(&time);
317
318 /* we did not use all the space we had,
319 * that is the end */
320 if (available)
321 break;
322
323 } while (1);
324 audio_pcm_stop_playback_immediate(pb->device);
325}
326
327/**
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.
332 */
333int dplay(const char *device, const char *file)
334{
335 errno_t ret = EOK;
336 audio_pcm_sess_t *session = NULL;
337 if (str_cmp(device, "default") == 0) {
338 session = audio_pcm_open_default();
339 } else {
340 session = audio_pcm_open(device);
341 }
342 if (!session) {
343 printf("Failed to connect to device %s.\n", device);
344 return 1;
345 }
346 printf("Playing on device: %s.\n", device);
347 sysarg_t val;
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);
351 ret = ENOTSUP;
352 goto close_session;
353 }
354
355 char* info = NULL;
356 ret = audio_pcm_get_info_str(session, &info);
357 if (ret != EOK) {
358 printf("Failed to get PCM info: %s.\n", str_error(ret));
359 goto close_session;
360 }
361 printf("Playing on %s.\n", info);
362 free(info);
363
364 playback_t pb;
365 playback_initialize(&pb, session);
366
367 ret = audio_pcm_get_buffer(pb.device, &pb.buffer.base, &pb.buffer.size);
368 if (ret != EOK) {
369 printf("Failed to get PCM buffer: %s.\n", str_error(ret));
370 goto close_session;
371 }
372 printf("Buffer: %p %zu.\n", pb.buffer.base, pb.buffer.size);
373
374 pb.source = fopen(file, "rb");
375 if (pb.source == NULL) {
376 ret = ENOENT;
377 printf("Failed to open file: %s.\n", file);
378 goto cleanup;
379 }
380
381 wave_header_t header;
382 fread(&header, sizeof(header), 1, pb.source);
383 const char *error;
384 ret = wav_parse_header(&header, NULL, NULL,
385 &pb.f.channels, &pb.f.sampling_rate, &pb.f.sample_format, &error);
386 if (ret != EOK) {
387 printf("Error parsing wav header: %s.\n", error);
388 goto cleanup;
389 }
390 ret = audio_pcm_query_cap(pb.device, AUDIO_CAP_BUFFER_POS, &val);
391 if (ret == EOK && val) {
392 play(&pb);
393 } else {
394 ret = audio_pcm_query_cap(pb.device, AUDIO_CAP_INTERRUPT, &val);
395 if (ret == EOK && val)
396 play_fragment(&pb);
397 else
398 printf("Neither playing method is supported");
399 }
400
401cleanup:
402 fclose(pb.source);
403 as_area_destroy(pb.buffer.base);
404 audio_pcm_release_buffer(pb.device);
405close_session:
406 audio_pcm_close(session);
407 return ret == EOK ? 0 : 1;
408}
409/**
410 * @}
411 */
Note: See TracBrowser for help on using the repository browser.