Index: uspace/app/init/init.c
===================================================================
--- uspace/app/init/init.c	(revision ccdc63e559f32b50f2381110a99211f6ed0775f3)
+++ uspace/app/init/init.c	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
@@ -392,13 +392,22 @@
 		rc = console(HID_INPUT, HID_OUTPUT);
 		if (rc == EOK) {
-			getterm("term/vc0", "/app/bdsh", true);
-			getterm("term/vc1", "/app/bdsh", false);
-			getterm("term/vc2", "/app/bdsh", false);
-			getterm("term/vc3", "/app/bdsh", false);
-			getterm("term/vc4", "/app/bdsh", false);
-			getterm("term/vc5", "/app/bdsh", false);
+#ifndef CONFIG_VC_COUNT
+#define CONFIG_VC_COUNT 6
+#endif
+			switch((unsigned)CONFIG_VC_COUNT) {
+			default:
+			case 6:	getterm("term/vc5", "/app/bdsh", false);
+			case 5: getterm("term/vc4", "/app/bdsh", false);
+			case 4: getterm("term/vc3", "/app/bdsh", false);
+			case 3: getterm("term/vc2", "/app/bdsh", false);
+			case 2: getterm("term/vc1", "/app/bdsh", false);
+			case 1: getterm("term/vc0", "/app/bdsh", true);
+			}
+#ifdef CONFIG_KERNEL_LOG_VC_6
 			getterm("term/vc6", "/app/klog", false);
+#endif
 		}
 	}
+	srv_start("/srv/hound");
 	
 	return 0;
Index: uspace/app/mixerctl/Makefile
===================================================================
--- uspace/app/mixerctl/Makefile	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
+++ uspace/app/mixerctl/Makefile	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
@@ -0,0 +1,41 @@
+#
+# Copyright (c) 2011 Jan Vesely
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# - Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+# - The name of the author may not be used to endorse or promote products
+#   derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+USPACE_PREFIX = ../..
+BINARY = mixerctl
+
+LIBS = \
+	$(LIBDRV_PREFIX)/libdrv.a
+
+EXTRA_CFLAGS = \
+	-I$(LIBDRV_PREFIX)/include
+
+SOURCES = \
+	mixerctl.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/app/mixerctl/mixerctl.c
===================================================================
--- uspace/app/mixerctl/mixerctl.c	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
+++ uspace/app/mixerctl/mixerctl.c	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup mixerctl
+ * @{
+ */
+/** @file Mixer control for audio devices
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <str_error.h>
+#include <str.h>
+#include <devman.h>
+#include <audio_mixer_iface.h>
+#include <stdio.h>
+
+#define DEFAULT_DEVICE "/hw/pci0/00:01.0/sb16/control"
+
+/**
+ * Print volume levels on all channels on all control items.
+ * @param exch IPC exchange
+ */
+static void print_levels(async_exch_t *exch)
+{
+	const char* name = NULL;
+	unsigned count = 0;
+	int ret = audio_mixer_get_info(exch, &name, &count);
+	if (ret != EOK) {
+		printf("Failed to get mixer info: %s.\n", str_error(ret));
+		return;
+	}
+	printf("MIXER %s:\n\n", name);
+
+	for (unsigned i = 0; i < count; ++i) {
+		const char *name = NULL;
+		unsigned levels = 0, current = 0;
+		int ret =
+		    audio_mixer_get_item_info(exch, i, &name, &levels);
+		if (ret != EOK) {
+			printf("Failed to get item %u info: %s.\n",
+			    i, str_error(ret));
+			continue;
+		}
+		ret = audio_mixer_get_item_level(exch, i, &current);
+		if (ret != EOK) {
+			printf("Failed to get item %u info: %s.\n",
+			    i, str_error(ret));
+			continue;
+		}
+
+		printf("Control item %u `%s' : %u/%u.\n",
+		    i, name, current, levels - 1);
+		free(name);
+
+	}
+}
+
+static unsigned get_number(const char* str)
+{
+	uint16_t num;
+	str_uint16_t(str, NULL, 10, false, &num);
+	return num;
+}
+
+static void set_level(async_exch_t *exch, int argc, char *argv[])
+{
+	assert(exch);
+	if (argc != 4 && argc != 5) {
+		printf("%s [device] setlevel item value\n", argv[0]);
+		return;
+	}
+	unsigned params = argc == 5 ? 3 : 2;
+	const unsigned item = get_number(argv[params++]);
+	const unsigned value = get_number(argv[params]);
+	int ret = audio_mixer_set_item_level(exch, item, value);
+	if (ret != EOK) {
+		printf("Failed to set item level: %s.\n", str_error(ret));
+		return;
+	}
+	printf("Control item %u new level is %u.\n", item, value);
+}
+
+static void get_level(async_exch_t *exch, int argc, char *argv[])
+{
+	assert(exch);
+	if (argc != 3 && argc != 4) {
+		printf("%s [device] getlevel item \n", argv[0]);
+		return;
+	}
+	unsigned params = argc == 4 ? 3 : 2;
+	const unsigned item = get_number(argv[params++]);
+	unsigned value = 0;
+
+	int ret = audio_mixer_get_item_level(exch, item, &value);
+	if (ret != EOK) {
+		printf("Failed to get item level: %s.\n", str_error(ret));
+		return;
+	}
+	printf("Control item %u level: %u.\n", item, value);
+}
+
+int main(int argc, char *argv[])
+{
+	const char *device = DEFAULT_DEVICE;
+	void (*command)(async_exch_t *, int, char*[]) = NULL;
+
+	if (argc >= 2 && str_cmp(argv[1], "setlevel") == 0) {
+		command = set_level;
+		if (argc == 5)
+			device = argv[1];
+	}
+
+	if (argc >= 2 && str_cmp(argv[1], "getlevel") == 0) {
+		command = get_level;
+		if (argc == 4)
+			device = argv[1];
+	}
+
+	if ((argc == 2 && command == NULL))
+		device = argv[1];
+
+
+	devman_handle_t mixer_handle;
+	int ret = devman_fun_get_handle(device, &mixer_handle, 0);
+	if (ret != EOK) {
+		printf("Failed to get device(%s) handle: %s.\n",
+		    device, str_error(ret));
+		return 1;
+	}
+
+	async_sess_t *session = devman_device_connect(
+	    EXCHANGE_ATOMIC, mixer_handle, IPC_FLAG_BLOCKING);
+	if (!session) {
+		printf("Failed to connect to device.\n");
+		return 1;
+	}
+
+	async_exch_t *exch = async_exchange_begin(session);
+	if (!exch) {
+		printf("Failed to start session exchange.\n");
+		async_hangup(session);
+		return 1;
+	}
+
+	if (command) {
+		command(exch, argc, argv);
+	} else {
+		print_levels(exch);
+		printf("\n%s:\n", argv[0]);
+		printf("Use '%s getlevel idx' command to read individual "
+		    "settings\n", argv[0]);
+		printf("Use '%s setlevel idx' command to change "
+		    "settings\n", argv[0]);
+	}
+
+	async_exchange_end(exch);
+	async_hangup(session);
+	return 0;
+}
+
+/** @}
+ */
Index: uspace/app/wavplay/Makefile
===================================================================
--- uspace/app/wavplay/Makefile	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
+++ uspace/app/wavplay/Makefile	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
@@ -0,0 +1,49 @@
+#
+# Copyright (c) 2012 Jan Vesely
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# - Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+# - The name of the author may not be used to endorse or promote products
+#   derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+USPACE_PREFIX = ../..
+BINARY = wavplay
+
+LIBS = \
+	$(LIBHOUND_PREFIX)/libhound.a \
+	$(LIBDRV_PREFIX)/libdrv.a \
+	$(LIBPCM_PREFIX)/libpcm.a
+
+EXTRA_CFLAGS = \
+	-I$(LIBDRV_PREFIX)/include \
+	-I$(LIBHOUND_PREFIX)/include \
+	-I$(LIBPCM_PREFIX)/include
+
+SOURCES = \
+	dplay.c \
+	drec.c \
+	main.c \
+	wave.c
+
+include $(USPACE_PREFIX)/Makefile.common
+
Index: uspace/app/wavplay/dplay.c
===================================================================
--- uspace/app/wavplay/dplay.c	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
+++ uspace/app/wavplay/dplay.c	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
@@ -0,0 +1,402 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup dplay
+ * @{
+ */
+/**
+ * @file PCM playback audio devices
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <str_error.h>
+#include <str.h>
+#include <audio_pcm_iface.h>
+#include <fibril_synch.h>
+#include <pcm/format.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+
+#include <stdio.h>
+#include <macros.h>
+
+#include "wave.h"
+#include "dplay.h"
+
+#define DEFAULT_FRAGMENTS 2
+
+/** Playback helper structure */
+typedef struct {
+	struct {
+		void *base;
+		size_t size;
+		void* write_ptr;
+	} buffer;
+	pcm_format_t f;
+	FILE* source;
+	volatile bool playing;
+	fibril_mutex_t mutex;
+	fibril_condvar_t cv;
+	audio_pcm_sess_t *device;
+} playback_t;
+
+/**
+ * Initialize playback helper structure.
+ * @param pb Pointer to helper structure to initialize
+ * @param sess Pointer to audio device IPC session
+ * @return
+ */
+static void playback_initialize(playback_t *pb, audio_pcm_sess_t *sess)
+{
+	assert(sess);
+	assert(pb);
+	pb->buffer.base = NULL;
+	pb->buffer.size = 0;
+	pb->buffer.write_ptr = NULL;
+	pb->playing = false;
+	pb->source = NULL;
+	pb->device = sess;
+	fibril_mutex_initialize(&pb->mutex);
+	fibril_condvar_initialize(&pb->cv);
+}
+
+/**
+ * Fragment playback callback function.
+ * @param iid IPC call id.
+ * @param icall Pointer to the call structure
+ * @param arg Argument, pointer to the playback helper function
+ */
+static void device_event_callback(ipc_callid_t iid, ipc_call_t *icall, void* arg)
+{
+	async_answer_0(iid, EOK);
+	playback_t *pb = arg;
+	const size_t fragment_size = pb->buffer.size / DEFAULT_FRAGMENTS;
+	while (1) {
+		ipc_call_t call;
+		ipc_callid_t callid = async_get_call(&call);
+		switch(IPC_GET_IMETHOD(call)) {
+		case PCM_EVENT_PLAYBACK_STARTED:
+		case PCM_EVENT_FRAMES_PLAYED:
+			printf("%u frames: ", IPC_GET_ARG1(call));
+			async_answer_0(callid, EOK);
+			break;
+		case PCM_EVENT_PLAYBACK_TERMINATED:
+			printf("Playback terminated\n");
+			fibril_mutex_lock(&pb->mutex);
+			pb->playing = false;
+			fibril_condvar_signal(&pb->cv);
+			async_answer_0(callid, EOK);
+			fibril_mutex_unlock(&pb->mutex);
+			return;
+		default:
+			printf("Unknown event %d.\n", IPC_GET_IMETHOD(call));
+			async_answer_0(callid, ENOTSUP);
+			continue;
+
+		}
+		const size_t bytes = fread(pb->buffer.write_ptr,
+		   sizeof(uint8_t), fragment_size, pb->source);
+		printf("Copied from position %p size %zu/%zu\n",
+		    pb->buffer.write_ptr, bytes, fragment_size);
+		if (bytes == 0) {
+			audio_pcm_last_playback_fragment(pb->device);
+		}
+		/* any constant is silence */
+		memset(pb->buffer.write_ptr + bytes, 0, fragment_size - bytes);
+		pb->buffer.write_ptr += fragment_size;
+
+		if (pb->buffer.write_ptr >= (pb->buffer.base + pb->buffer.size))
+			pb->buffer.write_ptr -= pb->buffer.size;
+	}
+}
+
+/**
+ * Start event based playback.
+ * @param pb Playback helper structure.
+ */
+static void play_fragment(playback_t *pb)
+{
+	assert(pb);
+	assert(pb->device);
+	const size_t fragment_size = pb->buffer.size / DEFAULT_FRAGMENTS;
+	printf("Registering event callback\n");
+	int ret = audio_pcm_register_event_callback(pb->device,
+	    device_event_callback, pb);
+	if (ret != EOK) {
+		printf("Failed to register event callback.\n");
+		return;
+	}
+	printf("Playing: %dHz, %s, %d channel(s).\n", pb->f.sampling_rate,
+	    pcm_sample_format_str(pb->f.sample_format), pb->f.channels);
+	const size_t bytes = fread(pb->buffer.base, sizeof(uint8_t),
+	    fragment_size, pb->source);
+	if (bytes != fragment_size)
+		memset(pb->buffer.base + bytes, 0, fragment_size - bytes);
+	printf("Initial: Copied from position %p size %zu/%zu\n",
+	    pb->buffer.base, bytes, fragment_size);
+	pb->buffer.write_ptr = pb->buffer.base + fragment_size;
+	fibril_mutex_lock(&pb->mutex);
+	const unsigned frames =
+	    pcm_format_size_to_frames(fragment_size, &pb->f);
+	ret = audio_pcm_start_playback_fragment(pb->device, frames,
+	    pb->f.channels, pb->f.sampling_rate, pb->f.sample_format);
+	if (ret != EOK) {
+		fibril_mutex_unlock(&pb->mutex);
+		printf("Failed to start playback: %s.\n", str_error(ret));
+		audio_pcm_unregister_event_callback(pb->device);
+		return;
+	}
+
+	for (pb->playing = true; pb->playing;
+	    fibril_condvar_wait(&pb->cv, &pb->mutex));
+
+	fibril_mutex_unlock(&pb->mutex);
+	printf("\n");
+	audio_pcm_unregister_event_callback(pb->device);
+}
+
+/**
+ * Count occupied space in a cyclic buffer.
+ * @param pb Playback helper structure.
+ * @param pos read pointer position.
+ * @return Occupied space size.
+ */
+static size_t buffer_occupied(const playback_t *pb, size_t pos)
+{
+	assert(pb);
+	void *read_ptr = pb->buffer.base + pos;
+	if (read_ptr > pb->buffer.write_ptr)
+		return pb->buffer.write_ptr + pb->buffer.size - read_ptr;
+	return pb->buffer.write_ptr - read_ptr;
+
+}
+
+/**
+ * Count available space in a cyclic buffer.
+ * @param pb Playback helper structure.
+ * @param pos read pointer position.
+ * @return Free space size.
+ */
+static size_t buffer_avail(const playback_t *pb, size_t pos)
+{
+	assert(pb);
+	void *read_ptr = pb->buffer.base + pos;
+	if (read_ptr <= pb->buffer.write_ptr)
+		return read_ptr + pb->buffer.size - pb->buffer.write_ptr - 1;
+	return (read_ptr - pb->buffer.write_ptr) - 1;
+}
+
+/**
+ * Size of the space between write pointer and the end of a cyclic buffer
+ * @param pb Playback helper structure.
+ */
+static size_t buffer_remain(const playback_t *pb)
+{
+	assert(pb);
+	return (pb->buffer.base + pb->buffer.size) - pb->buffer.write_ptr;
+}
+
+/**
+ * Move write pointer forward. Wrap around the end.
+ * @param pb Playback helper structure.
+ * @param bytes NUmber of bytes to advance.
+ */
+static void buffer_advance(playback_t *pb, size_t bytes)
+{
+	assert(pb);
+	pb->buffer.write_ptr += bytes;
+	while (pb->buffer.write_ptr >= (pb->buffer.base + pb->buffer.size))
+		pb->buffer.write_ptr -= pb->buffer.size;
+}
+
+#define DPRINTF(f, ...) \
+	printf("%.2lu:%.6lu   "f, time.tv_sec % 100, time.tv_usec, __VA_ARGS__)
+
+/**
+ * Start playback using buffer position api.
+ * @param pb Playback helper function.
+ */
+static void play(playback_t *pb)
+{
+	assert(pb);
+	assert(pb->device);
+	pb->buffer.write_ptr = pb->buffer.base;
+	printf("Playing: %dHz, %s, %d channel(s).\n", pb->f.sampling_rate,
+	    pcm_sample_format_str(pb->f.sample_format), pb->f.channels);
+	useconds_t work_time = 20000; /* 20 ms */
+	bool started = false;
+	size_t pos = 0;
+	struct timeval time = { 0 };
+	getuptime(&time);
+	do {
+		size_t available = buffer_avail(pb, pos);
+		/* Writing might need wrap around the end,
+		 * read directly to device buffer */
+		size_t bytes = fread(pb->buffer.write_ptr, sizeof(uint8_t),
+		    min(available, buffer_remain(pb)), pb->source);
+		buffer_advance(pb, bytes);
+		DPRINTF("POS %zu: %zu bytes free in buffer, read %zu, wp %zu\n",
+		    pos, available, bytes,
+		    pb->buffer.write_ptr - pb->buffer.base);
+		available -= bytes;
+
+		/* continue if we wrapped around the end */
+		if (available) {
+			bytes = fread(pb->buffer.write_ptr,
+			    sizeof(uint8_t), min(available, buffer_remain(pb)),
+			    pb->source);
+			buffer_advance(pb, bytes);
+			DPRINTF("POS %zu: %zu bytes still free in buffer, "
+			    "read %zu, wp %zu\n", pos, available, bytes,
+			    pb->buffer.write_ptr - pb->buffer.base);
+			available -= bytes;
+		}
+
+		if (!started) {
+			int ret = audio_pcm_start_playback(pb->device,
+			    pb->f.channels, pb->f.sampling_rate,
+			    pb->f.sample_format);
+			if (ret != EOK) {
+				printf("Failed to start playback\n");
+				return;
+			}
+			started = true;
+			ret = audio_pcm_get_buffer_pos(pb->device, &pos);
+			if (ret != EOK) {
+				printf("Failed to update position indicator\n");
+			}
+		}
+		const size_t to_play = buffer_occupied(pb, pos);
+		const useconds_t usecs =
+		    pcm_format_size_to_usec(to_play, &pb->f);
+
+		/* Compute delay time */
+		const useconds_t real_delay = (usecs > work_time)
+		    ? usecs - work_time : 0;
+		DPRINTF("POS %zu: %u usecs (%u) to play %zu bytes.\n",
+		    pos, usecs, real_delay, to_play);
+		if (real_delay)
+			async_usleep(real_delay);
+		/* update buffer position */
+		const int ret = audio_pcm_get_buffer_pos(pb->device, &pos);
+		if (ret != EOK) {
+			printf("Failed to update position indicator\n");
+		}
+		getuptime(&time);
+
+		/* we did not use all the space we had,
+		 * that is the end */
+		if (available)
+			break;
+
+	} while (1);
+	audio_pcm_stop_playback(pb->device);
+}
+
+/**
+ * Play audio file usign direct device access.
+ * @param device The device.
+ * @param file The file.
+ * @return Error code.
+ */
+int dplay(const char *device, const char *file)
+{
+	int ret = EOK;
+	audio_pcm_sess_t *session = NULL;
+	if (str_cmp(device, "default") == 0) {
+		session = audio_pcm_open_default();
+	} else {
+		session = audio_pcm_open(device);
+	}
+	if (!session) {
+		printf("Failed to connect to device %s.\n", device);
+		return 1;
+	}
+	printf("Playing on device: %s.\n", device);
+	if (audio_pcm_query_cap(session, AUDIO_CAP_PLAYBACK) <= 0) {
+		printf("Device %s does not support playback\n", device);
+		ret = ENOTSUP;
+		goto close_session;
+	}
+
+	const char* info = NULL;
+	ret = audio_pcm_get_info_str(session, &info);
+	if (ret != EOK) {
+		printf("Failed to get PCM info.\n");
+		goto close_session;
+	}
+	printf("Playing on %s.\n", info);
+	free(info);
+
+	playback_t pb;
+	playback_initialize(&pb, session);
+
+	ret = audio_pcm_get_buffer(pb.device, &pb.buffer.base, &pb.buffer.size);
+	if (ret != EOK) {
+		printf("Failed to get PCM buffer: %s.\n", str_error(ret));
+		goto close_session;
+	}
+	printf("Buffer: %p %zu.\n", pb.buffer.base, pb.buffer.size);
+
+	pb.source = fopen(file, "rb");
+	if (pb.source == NULL) {
+		ret = ENOENT;
+		printf("Failed to open file: %s.\n", file);
+		goto cleanup;
+	}
+
+	wave_header_t header;
+	fread(&header, sizeof(header), 1, pb.source);
+	const char *error;
+	ret = wav_parse_header(&header, NULL, NULL,
+	    &pb.f.channels, &pb.f.sampling_rate, &pb.f.sample_format, &error);
+	if (ret != EOK) {
+		printf("Error parsing wav header: %s.\n", error);
+		goto cleanup;
+	}
+	if (audio_pcm_query_cap(pb.device, AUDIO_CAP_BUFFER_POS) > 0) {
+		play(&pb);
+	} else {
+		if (audio_pcm_query_cap(pb.device, AUDIO_CAP_INTERRUPT) > 0)
+			play_fragment(&pb);
+		else
+			printf("Neither playing method is supported");
+	}
+
+cleanup:
+	fclose(pb.source);
+	munmap(pb.buffer.base, pb.buffer.size);
+	audio_pcm_release_buffer(pb.device);
+close_session:
+	audio_pcm_close(session);
+	return ret == EOK ? 0 : 1;
+}
+/**
+ * @}
+ */
Index: uspace/app/wavplay/dplay.h
===================================================================
--- uspace/app/wavplay/dplay.h	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
+++ uspace/app/wavplay/dplay.h	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/** @addtogroup wavplay
+ * @{
+ */
+/** @file
+ * @brief .wav file format.
+ */
+#ifndef DPLAY_H
+#define DPLAY_H
+
+int dplay(const char *device, const char *file);
+
+#endif
+/**
+ * @}
+ */
+
Index: uspace/app/wavplay/drec.c
===================================================================
--- uspace/app/wavplay/drec.c	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
+++ uspace/app/wavplay/drec.c	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2013 Jan Vesely
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup wavplay
+ * @{
+ */
+/**
+ * @file PCM playback audio devices
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <str_error.h>
+#include <audio_pcm_iface.h>
+#include <pcm/format.h>
+#include <stdio.h>
+#include <sys/mman.h>
+
+#include "wave.h"
+#include "drec.h"
+
+
+#define BUFFER_PARTS   2
+
+/** Recording format */
+static const pcm_format_t format = {
+	.sampling_rate = 44100,
+	.channels = 2,
+	.sample_format = PCM_SAMPLE_SINT16_LE,
+};
+
+/** Recording helper structure */
+typedef struct {
+	struct {
+		void *base;
+		size_t size;
+		unsigned id;
+		void* position;
+	} buffer;
+	FILE* file;
+	audio_pcm_sess_t *device;
+} record_t;
+
+/**
+ * Initialize recording helper structure.
+ * @param rec Recording structure.
+ * @param sess Session to IPC device.
+ */
+static void record_initialize(record_t *rec, audio_pcm_sess_t *sess)
+{
+	assert(sess);
+	assert(rec);
+	rec->buffer.base = NULL;
+	rec->buffer.size = 0;
+	rec->buffer.position = NULL;
+	rec->file = NULL;
+	rec->device = sess;
+}
+
+/**
+ * Recording callback. Writes recorded data.
+ * @param iid IPC call id.
+ * @param icall Poitner to IPC call structure.
+ * @param arg Argument. Poitner to recording helper structure.
+ */
+static void device_event_callback(ipc_callid_t iid, ipc_call_t *icall, void* arg)
+{
+	async_answer_0(iid, EOK);
+	record_t *rec = arg;
+	const size_t buffer_part = rec->buffer.size / BUFFER_PARTS;
+	bool record = true;
+	while (record) {
+		ipc_call_t call;
+		ipc_callid_t callid = async_get_call(&call);
+		switch(IPC_GET_IMETHOD(call)) {
+		case PCM_EVENT_CAPTURE_TERMINATED:
+			printf("Recording terminated\n");
+			record = false;
+		case PCM_EVENT_FRAMES_CAPTURED:
+			printf("%u frames\n", IPC_GET_ARG1(call));
+			async_answer_0(callid, EOK);
+			break;
+		default:
+			printf("Unknown event %d.\n", IPC_GET_IMETHOD(call));
+			async_answer_0(callid, ENOTSUP);
+			continue;
+
+		}
+
+		/* Write directly from device buffer to file */
+		const size_t bytes = fwrite(rec->buffer.position,
+		   sizeof(uint8_t), buffer_part, rec->file);
+		printf("%zu ", bytes);
+		rec->buffer.position += buffer_part;
+
+		if (rec->buffer.position >= (rec->buffer.base + rec->buffer.size))
+			rec->buffer.position = rec->buffer.base;
+		async_answer_0(callid, EOK);
+	}
+}
+
+/**
+ * Start fragment based recording.
+ * @param rec Recording helper structure.
+ * @param f PCM format
+ */
+static void record_fragment(record_t *rec, pcm_format_t f)
+{
+	assert(rec);
+	assert(rec->device);
+	int ret = audio_pcm_register_event_callback(rec->device,
+	    device_event_callback, rec);
+	if (ret != EOK) {
+		printf("Failed to register for events: %s.\n", str_error(ret));
+		return;
+	}
+	rec->buffer.position = rec->buffer.base;
+	printf("Recording: %dHz, %s, %d channel(s).\n", f.sampling_rate,
+	    pcm_sample_format_str(f.sample_format), f.channels);
+	const unsigned frames =
+		pcm_format_size_to_frames(rec->buffer.size / BUFFER_PARTS, &f);
+	ret = audio_pcm_start_capture_fragment(rec->device,
+	    frames, f.channels, f.sampling_rate, f.sample_format);
+	if (ret != EOK) {
+		printf("Failed to start recording: %s.\n", str_error(ret));
+		return;
+	}
+
+	getchar();
+	printf("\n");
+	audio_pcm_stop_capture(rec->device);
+}
+
+/**
+ * Record directly from a device to a file.
+ * @param device The device.
+ * @param file The file.
+ * @return Error code.
+ */
+int drecord(const char *device, const char *file)
+{
+	int ret = EOK;
+	audio_pcm_sess_t *session = NULL;
+	if (str_cmp(device, "default") == 0) {
+		session = audio_pcm_open_default();
+	} else {
+		session = audio_pcm_open(device);
+	}
+	if (!session) {
+		printf("Failed to connect to device %s.\n", device);
+		return 1;
+	}
+	printf("Recording on device: %s.\n", device);
+	if (audio_pcm_query_cap(session, AUDIO_CAP_CAPTURE) <= 0) {
+		printf("Device %s does not support recording\n", device);
+		ret = ENOTSUP;
+		goto close_session;
+	}
+
+	const char* info = NULL;
+	ret = audio_pcm_get_info_str(session, &info);
+	if (ret != EOK) {
+		printf("Failed to get PCM info.\n");
+		goto close_session;
+	}
+	printf("Capturing on %s.\n", info);
+	free(info);
+
+	record_t rec;
+	record_initialize(&rec, session);
+
+	ret = audio_pcm_get_buffer(rec.device, &rec.buffer.base,
+	    &rec.buffer.size);
+	if (ret != EOK) {
+		printf("Failed to get PCM buffer: %s.\n", str_error(ret));
+		goto close_session;
+	}
+	printf("Buffer: %p %zu.\n", rec.buffer.base, rec.buffer.size);
+
+	rec.file = fopen(file, "w");
+	if (rec.file == NULL) {
+		ret = ENOENT;
+		printf("Failed to open file: %s.\n", file);
+		goto cleanup;
+	}
+
+	wave_header_t header;
+	fseek(rec.file, sizeof(header), SEEK_SET);
+	const char *error;
+	if (ret != EOK) {
+		printf("Error parsing wav header: %s.\n", error);
+		goto cleanup;
+	}
+	if (audio_pcm_query_cap(rec.device, AUDIO_CAP_INTERRUPT) > 0)
+		record_fragment(&rec, format);
+	else
+		printf("Recording method is not supported");
+	//TODO consider buffer position interface
+
+	wav_init_header(&header, format, ftell(rec.file) - sizeof(header));
+	fseek(rec.file, 0, SEEK_SET);
+	fwrite(&header, sizeof(header), 1, rec.file);
+
+cleanup:
+	fclose(rec.file);
+	munmap(rec.buffer.base, rec.buffer.size);
+	audio_pcm_release_buffer(rec.device);
+close_session:
+	audio_pcm_close(session);
+	return ret == EOK ? 0 : 1;
+}
+/**
+ * @}
+ */
Index: uspace/app/wavplay/drec.h
===================================================================
--- uspace/app/wavplay/drec.h	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
+++ uspace/app/wavplay/drec.h	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2013 Jan Vesely
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/** @addtogroup wavplay
+ * @{
+ */
+/** @file
+ * @brief .wav file format.
+ */
+#ifndef DREC_H
+#define DREC_H
+
+int drecord(const char *device, const char *file);
+
+#endif
+/**
+ * @}
+ */
+
Index: uspace/app/wavplay/main.c
===================================================================
--- uspace/app/wavplay/main.c	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
+++ uspace/app/wavplay/main.c	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
@@ -0,0 +1,344 @@
+/*
+ * Copyright (c) 2012 Jan Vesely
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup wavplay
+ * @{
+ */
+/**
+ * @file PCM playback audio devices
+ */
+
+#include <assert.h>
+#include <errno.h>
+#include <fibril_synch.h>
+#include <malloc.h>
+#include <str_error.h>
+#include <stdio.h>
+#include <hound/client.h>
+#include <pcm/sample_format.h>
+#include <getopt.h>
+
+#include "dplay.h"
+#include "drec.h"
+#include "wave.h"
+
+#define READ_SIZE   (32 * 1024)
+#define STREAM_BUFFER_SIZE   (64 * 1024)
+
+/**
+ * Play audio file using a new stream on provided context.
+ * @param ctx Provided context.
+ * @param filename File to play.
+ * @return Error code.
+ */
+static int hplay_ctx(hound_context_t *ctx, const char *filename)
+{
+	printf("Hound context playback: %s\n", filename);
+	FILE *source = fopen(filename, "rb");
+	if (!source) {
+		printf("Failed to open file %s\n", filename);
+		return EINVAL;
+	}
+
+	/* Read and parse WAV header */
+	wave_header_t header;
+	size_t read = fread(&header, sizeof(header), 1, source);
+	if (read != 1) {
+		printf("Failed to read WAV header: %zu\n", read);
+		fclose(source);
+		return EIO;
+	}
+	pcm_format_t format;
+	const char *error;
+	int ret = wav_parse_header(&header, NULL, NULL, &format.channels,
+	    &format.sampling_rate, &format.sample_format, &error);
+	if (ret != EOK) {
+		printf("Error parsing wav header: %s.\n", error);
+		fclose(source);
+		return EINVAL;
+	}
+
+	/* Allocate buffer and create new context */
+	char * buffer = malloc(READ_SIZE);
+	if (!buffer) {
+		fclose(source);
+		return ENOMEM;
+	}
+	hound_stream_t *stream = hound_stream_create(ctx,
+	    HOUND_STREAM_DRAIN_ON_EXIT, format, STREAM_BUFFER_SIZE);
+
+	/* Read and play */
+	while ((read = fread(buffer, sizeof(char), READ_SIZE, source)) > 0) {
+		ret = hound_stream_write(stream, buffer, read);
+		if (ret != EOK) {
+			printf("Failed to write to hound stream: %s\n",
+			    str_error(ret));
+			break;
+		}
+	}
+
+	/* Cleanup */
+	free(buffer);
+	fclose(source);
+	return ret;
+}
+
+/**
+ * Play audio file via hound server.
+ * @param filename File to play.
+ * @return Error code
+ */
+static int hplay(const char *filename)
+{
+	printf("Hound playback: %s\n", filename);
+	FILE *source = fopen(filename, "rb");
+	if (!source) {
+		printf("Failed to open file %s\n", filename);
+		return EINVAL;
+	}
+
+	/* Read and parse WAV header */
+	wave_header_t header;
+	size_t read = fread(&header, sizeof(header), 1, source);
+	if (read != 1) {
+		printf("Failed to read WAV header: %zu\n", read);
+		fclose(source);
+		return EIO;
+	}
+	pcm_format_t format;
+	const char *error;
+	int ret = wav_parse_header(&header, NULL, NULL, &format.channels,
+	    &format.sampling_rate, &format.sample_format, &error);
+	if (ret != EOK) {
+		printf("Error parsing wav header: %s.\n", error);
+		fclose(source);
+		return EINVAL;
+	}
+
+	/* Connect new playback context */
+	hound_context_t *hound = hound_context_create_playback(filename,
+	    format, STREAM_BUFFER_SIZE);
+	if (!hound) {
+		printf("Failed to create HOUND context\n");
+		fclose(source);
+		return ENOMEM;
+	}
+
+	ret = hound_context_connect_target(hound, HOUND_DEFAULT_TARGET);
+	if (ret != EOK) {
+		printf("Failed to connect to default target: %s\n",
+		    str_error(ret));
+		hound_context_destroy(hound);
+		fclose(source);
+		return ret;
+	}
+
+	/* Read and play */
+	static char buffer[READ_SIZE];
+	while ((read = fread(buffer, sizeof(char), READ_SIZE, source)) > 0) {
+		ret = hound_write_main_stream(hound, buffer, read);
+		if (ret != EOK) {
+			printf("Failed to write to main context stream: %s\n",
+			    str_error(ret));
+			break;
+		}
+	}
+
+	/* Cleanup */
+	hound_context_destroy(hound);
+	fclose(source);
+	return ret;
+}
+
+/**
+ * Helper structure for playback in separate fibrils
+ */
+typedef struct {
+	hound_context_t *ctx;
+	atomic_t *count;
+	const char *file;
+} fib_play_t;
+
+/**
+ * Fibril playback wrapper.
+ * @param arg Argument, pointer to playback helper structure.
+ * @return Error code.
+ */
+static int play_wrapper(void *arg)
+{
+	assert(arg);
+	fib_play_t *p = arg;
+	const int ret = hplay_ctx(p->ctx, p->file);
+	atomic_dec(p->count);
+	free(arg);
+	return ret;
+}
+
+/**
+ * Array of supported commandline options
+ */
+static const struct option opts[] = {
+	{"device", required_argument, 0, 'd'},
+	{"parallel", no_argument, 0, 'p'},
+	{"record", no_argument, 0, 'r'},
+	{"help", no_argument, 0, 'h'},
+	{0, 0, 0, 0}
+};
+
+/**
+ * Print usage help.
+ * @param name Name of the program.
+ */
+static void print_help(const char* name)
+{
+	printf("Usage: %s [options] file [files...]\n", name);
+	printf("supported options:\n");
+	printf("\t -h, --help\t Print this help.\n");
+	printf("\t -r, --record\t Start recording instead of playback. "
+	    "(Not implemented)\n");
+	printf("\t -d, --device\t Use specified device instead of the sound "
+	    "service. Use location path or a special device `default'\n");
+	printf("\t -p, --parallel\t Play given files in parallel instead of "
+	    "sequentially (does not work with -d).\n");
+}
+
+int main(int argc, char *argv[])
+{
+	const char *device = "default";
+	int idx = 0;
+	bool direct = false, record = false, parallel = false;
+	optind = 0;
+	int ret = 0;
+
+	/* Parse command line options */
+	while (ret != -1) {
+		ret = getopt_long(argc, argv, "d:prh", opts, &idx);
+		switch (ret) {
+		case 'd':
+			direct = true;
+			device = optarg;
+			break;
+		case 'r':
+			record = true;
+			break;
+		case 'p':
+			parallel = true;
+			break;
+		case 'h':
+			print_help(*argv);
+			return 0;
+		};
+	}
+
+	if (parallel && direct) {
+		printf("Parallel playback is available only if using sound "
+		    "server (no -d)\n");
+		print_help(*argv);
+		return 1;
+	}
+
+	if (optind == argc) {
+		printf("Not enough arguments.\n");
+		print_help(*argv);
+		return 1;
+	}
+
+	/* Init parallel playback variables */
+	hound_context_t *hound_ctx = NULL;
+	atomic_t playcount;
+	atomic_set(&playcount, 0);
+
+	/* Init parallel playback context if necessary */
+	if (parallel) {
+		hound_ctx = hound_context_create_playback("wavplay",
+		    AUDIO_FORMAT_DEFAULT, STREAM_BUFFER_SIZE);
+		if (!hound_ctx) {
+			printf("Failed to create global hound context\n");
+			return 1;
+		}
+		const int ret = hound_context_connect_target(hound_ctx,
+		    HOUND_DEFAULT_TARGET);
+		if (ret != EOK) {
+			printf("Failed to connect hound context to default "
+			   "target.\n");
+			hound_context_destroy(hound_ctx);
+			return 1;
+		}
+	}
+
+	/* play or record all files */
+	for (int i = optind; i < argc; ++i) {
+		const char *file = argv[i];
+
+		printf("%s (%d/%d) %s\n", record ? "Recording" : "Playing",
+		    i - optind + 1, argc - optind, file);
+		if (record) {
+			if (direct) {
+				drecord(device, file);
+			} else {
+				printf("Indirect recording is not supported "
+				    "yet.\n");
+				break;
+			}
+		}
+
+		if (direct) {
+			dplay(device, file);
+		} else {
+			if (parallel) {
+				/* Start new fibril for parallel playback */
+				fib_play_t *data = malloc(sizeof(fib_play_t));
+				if (!data) {
+					printf("Playback of %s failed.\n",
+						file);
+					continue;
+				}
+				data->file = file;
+				data->count = &playcount;
+				data->ctx = hound_ctx;
+				fid_t fid = fibril_create(play_wrapper, data);
+				atomic_inc(&playcount);
+				fibril_add_ready(fid);
+			} else {
+				hplay(file);
+			}
+		}
+	}
+
+	/* Wait for all fibrils to finish */
+	while (atomic_get(&playcount) > 0)
+		async_usleep(1000000);
+
+	/* Destroy parallel playback context, if initialized */
+	if (hound_ctx)
+		hound_context_destroy(hound_ctx);
+	return 0;
+}
+/**
+ * @}
+ */
Index: uspace/app/wavplay/wave.c
===================================================================
--- uspace/app/wavplay/wave.c	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
+++ uspace/app/wavplay/wave.c	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/** @addtogroup dplay
+ * @{
+ */
+/**
+ * @file PCM playback audio devices
+ */
+
+#include <byteorder.h>
+#include <str.h>
+#include <errno.h>
+
+#include "wave.h"
+
+/**
+ * Parse wav header data.
+ * @param[in] hdata Header data to parse.
+ * @param[out] data Pointer to audio data.
+ * @param[out] data_size Size of the data after the header.
+ * @param[out] channels Number of channels in stored audio format.
+ * @param[out] sampling_rate Sampling rate of the store data.
+ * @param[out] format Sample format.
+ * @param[out] error String representatoin of error, if any.
+ * @return Error code.
+ *
+ * Does sanity checks and endian conversion.
+ */
+int wav_parse_header(const void *hdata, const void **data, size_t *data_size,
+    unsigned *channels, unsigned *sampling_rate, pcm_sample_format_t *format,
+    const char **error)
+{
+	if (!hdata) {
+		if (error)
+			*error = "no header";
+		return EINVAL;
+	}
+
+	const wave_header_t *header = hdata;
+	if (str_lcmp(header->chunk_id, CHUNK_ID, 4) != 0) {
+		if (error)
+			*error = "invalid chunk id";
+		return EINVAL;
+	}
+
+	if (str_lcmp(header->format, FORMAT_STR, 4) != 0) {
+		if (error)
+			*error = "invalid format string";
+		return EINVAL;
+	}
+
+	if (str_lcmp(header->subchunk1_id, SUBCHUNK1_ID, 4) != 0) {
+		if (error)
+			*error = "invalid subchunk1 id";
+		return EINVAL;
+	}
+
+	if (uint16_t_le2host(header->subchunk1_size) != PCM_SUBCHUNK1_SIZE) {
+		if (error)
+			*error = "invalid subchunk1 size";
+		return EINVAL;
+	}
+
+	if (uint16_t_le2host(header->audio_format) != FORMAT_LINEAR_PCM) {
+		if (error)
+			*error = "unknown format";
+		return ENOTSUP;
+	}
+
+	if (str_lcmp(header->subchunk2_id, SUBCHUNK2_ID, 4) != 0) {
+		if (error)
+			*error = "invalid subchunk2 id";
+		return EINVAL;
+	}
+
+
+	if (data)
+		*data = header->data;
+	if (data_size)
+		*data_size = uint32_t_le2host(header->subchunk2_size);
+
+	if (sampling_rate)
+		*sampling_rate = uint32_t_le2host(header->sampling_rate);
+	if (channels)
+		*channels = uint16_t_le2host(header->channels);
+	if (format) {
+		const unsigned size = uint32_t_le2host(header->sample_size);
+		switch (size) {
+		case 8: *format = PCM_SAMPLE_UINT8; break;
+		case 16: *format = PCM_SAMPLE_SINT16_LE; break;
+		case 24: *format = PCM_SAMPLE_SINT24_LE; break;
+		case 32: *format = PCM_SAMPLE_SINT32_LE; break;
+		default:
+			*error = "Unknown format";
+			return ENOTSUP;
+		}
+	}
+	if (error)
+		*error = "no error";
+
+	return EOK;
+}
+
+/**
+ * Initialize wave fromat ehader structure.
+ * @param header Structure to initialize.
+ * @param format Desired PCM format
+ * @param size Size of the stored data.
+ *
+ * Initializes format specific elements and covnerts endian
+ */
+void wav_init_header(wave_header_t *header, pcm_format_t format, size_t size)
+{
+	assert(header);
+#define COPY_STR(dst, src)   memcpy(dst, src, str_size(src))
+
+	COPY_STR(&header->chunk_id, CHUNK_ID);
+	COPY_STR(&header->format, FORMAT_STR);
+	COPY_STR(&header->subchunk1_id, SUBCHUNK1_ID);
+	header->subchunk1_size = host2uint16_t_le(PCM_SUBCHUNK1_SIZE);
+	header->audio_format = host2uint16_t_le(FORMAT_LINEAR_PCM);
+
+	COPY_STR(&header->subchunk2_id, SUBCHUNK2_ID);
+	header->subchunk2_size = host2uint32_t_le(size);
+	header->sampling_rate = host2uint32_t_le(format.sampling_rate);
+	header->channels = host2uint32_t_le(format.channels);
+	header->sample_size =
+	    host2uint32_t_le(pcm_sample_format_size(format.sample_format));
+}
+/**
+ * @}
+ */
Index: uspace/app/wavplay/wave.h
===================================================================
--- uspace/app/wavplay/wave.h	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
+++ uspace/app/wavplay/wave.h	(revision 537620a892e20fa4a4182e2f1218b44c043cc551)
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2011 Jan Vesely
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/** @addtogroup dplay
+ * @{
+ */
+/** @file
+ * @brief .wav file format.
+ */
+#ifndef WAVE_H
+#define WAVE_H
+
+#include <stdint.h>
+#include <pcm/format.h>
+#include <pcm/sample_format.h>
+
+/** Wave file header format.
+ *
+ * https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
+ * @note: 8-bit samples are stored as unsigned bytes,
+ * 16-bit samples are stored as signed integers.
+ * @note: The default byte ordering assumed for WAVE data files is
+ * little-endian. Files written using the big-endian byte ordering scheme have
+ * the identifier RIFX instead of RIFF.
+ */
+typedef struct wave_header {
+	/** Should be 'R', 'I', 'F', 'F'. */
+	char chunk_id[4];
+#define CHUNK_ID "RIFF"
+
+	/** Total size minus the first 8 bytes */
+	uint32_t chunk_size;
+	/** Should be 'W', 'A', 'V', 'E'. */
+	char format[4];
+#define FORMAT_STR "WAVE"
+
+	/** Should be 'f', 'm', 't', ' '. */
+	char subchunk1_id[4];
+#define SUBCHUNK1_ID "fmt "
+
+	/** Size of the ret of this subchunk. 16 for PCM file. */
+	uint32_t subchunk1_size;
+#define PCM_SUBCHUNK1_SIZE 16
+	/** Format. 1 for Linear PCM */
+	uint16_t audio_format;
+#define FORMAT_LINEAR_PCM 1
+	/** Number of channels. */
+	uint16_t channels;
+	/** Sampling rate. */
+	uint32_t sampling_rate;
+	/** Byte rate. */
+	uint32_t byte_rate;
+	/** Block align. Bytes in one block (samples for all channels). */
+	uint16_t block_align;
+	/** Bits per sample (one channel). */
+	uint16_t sample_size;
+
+	/** Should be 'd', 'a', 't', 'a'. */
+	char subchunk2_id[4];
+#define SUBCHUNK2_ID "data"
+	/** Audio data size. */
+	uint32_t subchunk2_size;
+	/** Audio data. */
+	uint8_t data[];
+
+} wave_header_t;
+
+int wav_parse_header(const void *, const void**, size_t *, unsigned *,
+    unsigned *, pcm_sample_format_t *, const char **);
+
+void wav_init_header(wave_header_t *, pcm_format_t , size_t);
+#endif
+/**
+ * @}
+ */
