Index: uspace/app/drec/Makefile
===================================================================
--- uspace/app/drec/Makefile	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
+++ uspace/app/drec/Makefile	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
@@ -0,0 +1,42 @@
+#
+# 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 = drec
+
+LIBS = \
+	$(LIBDRV_PREFIX)/libdrv.a
+
+EXTRA_CFLAGS = \
+	-I$(LIBDRV_PREFIX)/include -I$(LIBPCM_PREFIX)/include
+
+SOURCES = \
+	drec.c \
+	wave.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/app/drec/drec.c
===================================================================
--- uspace/app/drec/drec.c	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
+++ uspace/app/drec/drec.c	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
@@ -0,0 +1,217 @@
+/*
+ * 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 <devman.h>
+#include <audio_pcm_iface.h>
+#include <fibril_synch.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+
+#include <stdio.h>
+#include <macros.h>
+#include <pcm/sample_format.h>
+
+#include "wave.h"
+
+#define DEFAULT_DEVICE "/hw/pci0/00:01.0/sb16/pcm"
+#define BUFFER_PARTS 2
+
+const unsigned sampling_rate = 44100, channels = 2, sample_size = 16;
+const pcm_sample_format_t format = PCM_SAMPLE_SINT16_LE;
+
+typedef struct {
+	struct {
+		void *base;
+		size_t size;
+		unsigned id;
+		void* position;
+	} buffer;
+	FILE* file;
+	audio_pcm_sess_t *device;
+} record_t;
+
+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;
+}
+
+
+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;
+	while (1) {
+		ipc_call_t call;
+		ipc_callid_t callid = async_get_call(&call);
+		switch(IPC_GET_IMETHOD(call)) {
+		case PCM_EVENT_FRAMES_RECORDED:
+			printf("%u frames\n", IPC_GET_ARG1(call));
+			async_answer_0(callid, EOK);
+			break;
+		case PCM_EVENT_RECORDING_TERMINATED:
+			printf("Recording terminated\n");
+			return;
+		default:
+			printf("Unknown event %d.\n", IPC_GET_IMETHOD(call));
+			async_answer_0(callid, ENOTSUP);
+			continue;
+
+		}
+		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);
+	}
+}
+
+
+static void record(record_t *rec, unsigned channels, unsigned sampling_rate,
+    pcm_sample_format_t format)
+{
+	assert(rec);
+	assert(rec->device);
+	rec->buffer.position = rec->buffer.base;
+	printf("Recording: %dHz, %s, %d channel(s).\n",
+	    sampling_rate, pcm_sample_format_str(format), channels);
+	const unsigned frames = rec->buffer.size /
+	    (BUFFER_PARTS * channels * pcm_sample_format_size(format));
+	int ret = audio_pcm_start_record(rec->device,
+	    frames, channels, sampling_rate, format);
+	if (ret != EOK) {
+		printf("Failed to start recording: %s.\n", str_error(ret));
+		return;
+	}
+
+	getchar();
+	printf("\n");
+	audio_pcm_stop_record(rec->device);
+}
+
+int main(int argc, char *argv[])
+{
+	const char *device = DEFAULT_DEVICE;
+	const char *file;
+	switch (argc) {
+	case 2:
+		file = argv[1];
+		break;
+	case 3:
+		device = argv[1];
+		file = argv[2];
+		break;
+	default:
+		printf("Usage: %s [device] file.\n", argv[0]);
+		return 1;
+	}
+
+
+	audio_pcm_sess_t *session = audio_pcm_open(device);
+	if (!session) {
+		printf("Failed to connect to device.\n");
+		return 1;
+	}
+
+	const char* info = NULL;
+	int 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);
+
+	record_t rec;
+	record_initialize(&rec, session);
+
+	ret = audio_pcm_get_buffer(rec.device, &rec.buffer.base,
+	    &rec.buffer.size, device_event_callback, &rec);
+	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);
+	uintptr_t ptr = 0;
+	as_get_physical_mapping(rec.buffer.base, &ptr);
+	printf("buffer mapped at %x.\n", ptr);
+
+	rec.file = fopen(file, "wb");
+	if (rec.file == NULL) {
+		ret = ENOENT;
+		printf("Failed to open %s.\n", file);
+		goto cleanup;
+	}
+	wave_header_t header = {
+		.chunk_id = CHUNK_ID,
+		.format = FORMAT_STR,
+		.subchunk1_id = SUBCHUNK1_ID,
+		.subchunk1_size = PCM_SUBCHUNK1_SIZE,
+		.audio_format = FORMAT_LINEAR_PCM,
+		.channels = channels,
+		.sampling_rate = sampling_rate,
+		.sample_size = sample_size,
+		.byte_rate = sampling_rate * (sample_size / 8) * channels,
+		.block_align = (sample_size / 8) * channels,
+		.subchunk2_id = SUBCHUNK2_ID,
+	};
+	fwrite(&header, sizeof(header), 1, rec.file);
+	record(&rec, channels, sampling_rate, format);
+	fclose(rec.file);
+
+cleanup:
+	munmap(rec.buffer.base, rec.buffer.size);
+	audio_pcm_release_buffer(rec.device);
+close_session:
+	async_hangup(session);
+	return ret == EOK ? 0 : 1;
+}
+/**
+ * @}
+ */
Index: uspace/app/drec/wave.c
===================================================================
--- uspace/app/drec/wave.c	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
+++ uspace/app/drec/wave.c	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
@@ -0,0 +1,117 @@
+/*
+ * 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"
+
+int wav_parse_header(void *file, const void **data, size_t *data_size,
+    unsigned *channels, unsigned *sampling_rate, pcm_sample_format_t *format,
+    const char **error)
+{
+	if (!file) {
+		if (error)
+			*error = "file not present";
+		return EINVAL;
+	}
+
+	const wave_header_t *header = file;
+	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;
+}
+/**
+ * @}
+ */
Index: uspace/app/drec/wave.h
===================================================================
--- uspace/app/drec/wave.h	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
+++ uspace/app/drec/wave.h	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
@@ -0,0 +1,97 @@
+/*
+ * 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/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(void *, const void**, size_t *, unsigned *, unsigned *,
+    pcm_sample_format_t *, const char **);
+
+#endif
+/**
+ * @}
+ */
Index: uspace/app/init/init.c
===================================================================
--- uspace/app/init/init.c	(revision e43553758fa0974fa6ac8d0710b1699049d36e17)
+++ uspace/app/init/init.c	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
@@ -339,11 +339,17 @@
 #endif
 	
-	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);
+	spawn("/srv/hound");
+	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
 	
 	return 0;
Index: uspace/app/mixerctl/Makefile
===================================================================
--- uspace/app/mixerctl/Makefile	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
+++ uspace/app/mixerctl/Makefile	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
@@ -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 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
+++ uspace/app/mixerctl/mixerctl.c	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
@@ -0,0 +1,199 @@
+/*
+ * 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"
+
+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", name);
+
+	for (unsigned i = 0; i < count; ++i) {
+		const char *name = NULL;
+		unsigned channels = 0;
+		const int ret =
+		    audio_mixer_get_item_info(exch, i, &name, &channels);
+		if (ret != EOK) {
+			printf("Failed to get item %u info: %s.\n",
+			    i, str_error(ret));
+			continue;
+		}
+		for (unsigned j = 0; j < channels; ++j) {
+			const char *chan = NULL;
+			int ret = audio_mixer_get_channel_info(
+			    exch, i, j, &chan, NULL);
+			if (ret != EOK) {
+				printf(
+				    "Failed to get channel %u-%u info: %s.\n",
+				    i, j, str_error(ret));
+			}
+			unsigned level = 0, max = 0;
+			ret = audio_mixer_channel_volume_get(
+			    exch, i, j, &level, &max);
+			if (ret != EOK) {
+				printf("Failed to get channel %u-%u volume:"
+				    " %s.\n", i, j, str_error(ret));
+			}
+			bool mute = false;
+			ret = audio_mixer_channel_mute_get(
+			    exch, i, j, &mute);
+			if (ret != EOK) {
+				printf("Failed to get channel %u-%u mute"
+				    " status: %s.\n", i, j, str_error(ret));
+			}
+
+			printf("\tChannel(%u/%u) %s %s volume: %u/%u%s.\n",
+			    i, j, name, chan, level, max, mute ? " (M)":"");
+			free(chan);
+		}
+		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_volume(async_exch_t *exch, int argc, char *argv[])
+{
+	assert(exch);
+	if (argc != 5 && argc != 6) {
+		printf("%s [device] setvolume item channel value\n", argv[0]);
+	}
+	unsigned params = argc == 6 ? 3 : 2;
+	const unsigned item = get_number(argv[params++]);
+	const unsigned channel = get_number(argv[params++]);
+	const unsigned value = get_number(argv[params]);
+	int ret = audio_mixer_channel_volume_set(exch, item, channel, value);
+	if (ret != EOK) {
+		printf("Failed to set mixer volume: %s.\n", str_error(ret));
+		return;
+	}
+	printf("Channel %u-%u volume set to %u.\n", item, channel, value);
+}
+/*----------------------------------------------------------------------------*/
+static void get_volume(async_exch_t *exch, int argc, char *argv[])
+{
+	assert(exch);
+	if (argc != 4 && argc != 5) {
+		printf("%s [device] getvolume item channel\n", argv[0]);
+	}
+	unsigned params = argc == 5 ? 3 : 2;
+	const unsigned item = get_number(argv[params++]);
+	const unsigned channel = get_number(argv[params++]);
+	unsigned value = 0, max = 0;
+
+	int ret = audio_mixer_channel_volume_get(
+	    exch, item, channel, &value, &max);
+	if (ret != EOK) {
+		printf("Failed to get mixer volume: %s.\n", str_error(ret));
+		return;
+	}
+	printf("Channel %u-%u volume: %u/%u.\n", item, channel, value, max);
+}
+/*----------------------------------------------------------------------------*/
+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], "setvolume") == 0) {
+		command = set_volume;
+		if (argc == 6)
+			device = argv[1];
+	}
+
+	if (argc >= 2 && str_cmp(argv[1], "getvolume") == 0) {
+		command = get_volume;
+		if (argc == 5)
+			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);
+	}
+
+	async_exchange_end(exch);
+	async_hangup(session);
+	return 0;
+}
+
+/** @}
+ */
Index: uspace/app/wavplay/Makefile
===================================================================
--- uspace/app/wavplay/Makefile	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
+++ uspace/app/wavplay/Makefile	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
@@ -0,0 +1,47 @@
+#
+# 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
+
+EXTRA_CFLAGS = \
+	-I$(LIBDRV_PREFIX)/include \
+	-I$(LIBHOUND_PREFIX)/include \
+	-I$(LIBPCM_PREFIX)/include
+
+SOURCES = \
+	dplay.c \
+	main.c \
+	wave.c
+
+include $(USPACE_PREFIX)/Makefile.common
+
Index: uspace/app/wavplay/dplay.c
===================================================================
--- uspace/app/wavplay/dplay.c	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
+++ uspace/app/wavplay/dplay.c	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
@@ -0,0 +1,221 @@
+/*
+ * 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 <devman.h>
+#include <audio_pcm_iface.h>
+#include <fibril_synch.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+
+#include <stdio.h>
+#include <macros.h>
+
+#include "wave.h"
+#include "dplay.h"
+
+#define DEFAULT_DEVICE "/hw/pci0/00:01.0/sb16/pcm"
+#define BUFFER_PARTS 2
+
+typedef struct {
+	struct {
+		void *base;
+		size_t size;
+		void* position;
+	} buffer;
+	FILE* source;
+	volatile bool playing;
+	fibril_mutex_t mutex;
+	fibril_condvar_t cv;
+	audio_pcm_sess_t *device;
+} playback_t;
+
+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.position = NULL;
+	pb->playing = false;
+	pb->source = NULL;
+	pb->device = sess;
+	fibril_mutex_initialize(&pb->mutex);
+	fibril_condvar_initialize(&pb->cv);
+}
+
+
+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 buffer_part = pb->buffer.size / BUFFER_PARTS;
+	while (1) {
+		ipc_call_t call;
+		ipc_callid_t callid = async_get_call(&call);
+		switch(IPC_GET_IMETHOD(call)) {
+		case PCM_EVENT_FRAMES_PLAYED:
+			printf("%u frames\n", 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.position, sizeof(uint8_t),
+		   buffer_part, pb->source);
+		if (bytes == 0) {
+			audio_pcm_stop_playback(pb->device);
+		}
+		bzero(pb->buffer.position + bytes, buffer_part - bytes);
+		pb->buffer.position += buffer_part;
+
+		if (pb->buffer.position >= (pb->buffer.base + pb->buffer.size))
+			pb->buffer.position = pb->buffer.base;
+	}
+}
+
+
+static void play(playback_t *pb, unsigned channels,  unsigned sampling_rate,
+    pcm_sample_format_t format)
+{
+	assert(pb);
+	assert(pb->device);
+	pb->buffer.position = pb->buffer.base;
+	printf("Playing: %dHz, %s, %d channel(s).\n",
+	    sampling_rate, pcm_sample_format_str(format), channels);
+	const size_t bytes = fread(pb->buffer.base, sizeof(uint8_t),
+	    pb->buffer.size, pb->source);
+	if (bytes != pb->buffer.size)
+		bzero(pb->buffer.base + bytes, pb->buffer.size - bytes);
+	printf("Buffer data ready.\n");
+	fibril_mutex_lock(&pb->mutex);
+	const unsigned frames = pb->buffer.size /
+	    (BUFFER_PARTS * channels * pcm_sample_format_size(format));
+	int ret = audio_pcm_start_playback(pb->device,
+	    frames, channels, sampling_rate, format);
+	if (ret != EOK) {
+		fibril_mutex_unlock(&pb->mutex);
+		printf("Failed to start playback: %s.\n", str_error(ret));
+		return;
+	}
+
+	for (pb->playing = true; pb->playing;
+	    fibril_condvar_wait(&pb->cv, &pb->mutex));
+
+	fibril_mutex_unlock(&pb->mutex);
+	printf("\n");
+}
+
+int dplay(const char *device, const char *file)
+{
+	if (str_cmp(device, "default") == 0)
+		device = DEFAULT_DEVICE;
+	audio_pcm_sess_t *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);
+
+	const char* info = NULL;
+	int 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, device_event_callback, &pb);
+	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);
+	uintptr_t ptr = 0;
+	as_get_physical_mapping(pb.buffer.base, &ptr);
+	printf("buffer mapped at %x.\n", ptr);
+
+	pb.source = fopen(file, "rb");
+	if (pb.source == NULL) {
+		ret = ENOENT;
+		printf("Failed to open %s.\n", file);
+		goto cleanup;
+	}
+	wave_header_t header;
+	fread(&header, sizeof(header), 1, pb.source);
+	unsigned rate, channels;
+	pcm_sample_format_t format;
+	const char *error;
+	ret = wav_parse_header(&header, NULL, NULL, &channels, &rate, &format,
+	    &error);
+	if (ret != EOK) {
+		printf("Error parsing wav header: %s.\n", error);
+		fclose(pb.source);
+		goto cleanup;
+	}
+
+	play(&pb, channels, rate, format);
+	fclose(pb.source);
+
+cleanup:
+	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 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
+++ uspace/app/wavplay/dplay.h	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
@@ -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/main.c
===================================================================
--- uspace/app/wavplay/main.c	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
+++ uspace/app/wavplay/main.c	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
@@ -0,0 +1,205 @@
+/*
+ * 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 <str_error.h>
+#include <stdio.h>
+#include <hound/client.h>
+#include <pcm/sample_format.h>
+#include <getopt.h>
+
+#include "dplay.h"
+#include "wave.h"
+
+#define NAME_MAX 32
+char name[NAME_MAX + 1];
+
+typedef struct {
+	FILE* source;
+	volatile bool playing;
+	fibril_mutex_t mutex;
+	fibril_condvar_t cv;
+	hound_sess_t *server;
+} playback_t;
+
+static void playback_initialize(playback_t *pb, hound_sess_t *sess)
+{
+	assert(pb);
+	pb->playing = false;
+	pb->source = NULL;
+	pb->server = sess;
+	fibril_mutex_initialize(&pb->mutex);
+	fibril_condvar_initialize(&pb->cv);
+}
+
+
+static void data_callback(void* arg, void *buffer, ssize_t size)
+{
+	playback_t *pb = arg;
+	assert(pb);
+
+	if (size > 0) {
+		const ssize_t bytes =
+		    fread(buffer, sizeof(uint8_t), size, pb->source);
+		printf("%zu bytes ready\n", bytes);
+		if (bytes < size) {
+			printf(" requested: %zd ready: %zd zero: %zd\n",
+				size, bytes, size - bytes);
+			bzero(buffer + bytes, size - bytes);
+		}
+		if (bytes == 0) {
+			pb->playing = false;
+			printf("The end, nothing more to play.\n");
+			fibril_condvar_signal(&pb->cv);
+		}
+	} else {
+		printf("Got error %s.\n", str_error(size));
+		pb->playing = false;
+		fibril_condvar_signal(&pb->cv);
+	}
+}
+
+static void play(playback_t *pb, unsigned channels, unsigned rate, pcm_sample_format_t format)
+{
+	assert(pb);
+	/* Create playback client */
+	int ret = hound_register_playback(pb->server, name, channels, rate,
+	    format, data_callback, pb);
+	if (ret != EOK) {
+		printf("Failed to register playback: %s\n", str_error(ret));
+		return;
+	}
+
+	/* Connect */
+	ret = hound_create_connection(pb->server, name, DEFAULT_SINK);
+	if (ret == EOK) {
+		fibril_mutex_lock(&pb->mutex);
+		for (pb->playing = true; pb->playing;
+		    fibril_condvar_wait(&pb->cv, &pb->mutex));
+		fibril_mutex_unlock(&pb->mutex);
+
+		hound_destroy_connection(pb->server, name, DEFAULT_SINK);
+	} else
+		printf("Failed to connect: %s\n", str_error(ret));
+
+	printf("Unregistering playback\n");
+	hound_unregister_playback(pb->server, name);
+}
+
+static const struct option opts[] = {
+	{"device", required_argument, 0, 'd'},
+	{"record", no_argument, 0, 'r'},
+	{0, 0, 0, 0}
+};
+
+
+int main(int argc, char *argv[])
+{
+	const char *device = "default";
+	int idx = 0;
+	bool direct = false, record = false;
+	optind = 0;
+	int ret = 0;
+	while (ret != -1) {
+		ret = getopt_long(argc, argv, "d:r", opts, &idx);
+		switch (ret) {
+		case 'd':
+			direct = true;
+			device = optarg;
+			break;
+		case 'r':
+			record = true;
+			break;
+		};
+	}
+
+	if (optind == argc) {
+		printf("Not enough arguments.\n");
+		return 1;
+	}
+	const char *file = argv[optind];
+
+	printf("%s %s\n", record ? "Recording" : "Playing", file);
+	if (record) {
+		printf("Recording is not supported yet.\n");
+		return 1;
+	}
+	if (direct)
+		return dplay(device, file);
+
+	task_id_t tid = task_get_id();
+	snprintf(name, NAME_MAX, "%s%" PRIu64 ":%s", argv[0], tid, file);
+
+	printf("Client name: %s\n", name);
+
+	hound_sess_t *sess = hound_get_session();
+	if (!sess) {
+		printf("Failed to connect to hound service\n");
+		return 1;
+	}
+
+	playback_t pb;
+	playback_initialize(&pb, sess);
+	pb.source = fopen(file, "rb");
+	if (pb.source == NULL) {
+		printf("Failed to open %s.\n", file);
+		hound_release_session(sess);
+		return 1;
+	}
+	wave_header_t header;
+	fread(&header, sizeof(header), 1, pb.source);
+	unsigned rate, channels;
+	pcm_sample_format_t format;
+	const char *error;
+	ret = wav_parse_header(&header, NULL, NULL, &channels, &rate,
+	    &format, &error);
+	if (ret != EOK) {
+		printf("Error parsing wav header: %s.\n", error);
+		fclose(pb.source);
+		hound_release_session(sess);
+		return 1;
+	}
+
+	play(&pb, channels, rate, format);
+
+	printf("Releasing session\n");
+	hound_release_session(sess);
+	return 0;
+}
+/**
+ * @}
+ */
Index: uspace/app/wavplay/wave.c
===================================================================
--- uspace/app/wavplay/wave.c	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
+++ uspace/app/wavplay/wave.c	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
@@ -0,0 +1,117 @@
+/*
+ * 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"
+
+int wav_parse_header(void *file, const void **data, size_t *data_size,
+    unsigned *channels, unsigned *sampling_rate, pcm_sample_format_t *format,
+    const char **error)
+{
+	if (!file) {
+		if (error)
+			*error = "file not present";
+		return EINVAL;
+	}
+
+	const wave_header_t *header = file;
+	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;
+}
+/**
+ * @}
+ */
Index: uspace/app/wavplay/wave.h
===================================================================
--- uspace/app/wavplay/wave.h	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
+++ uspace/app/wavplay/wave.h	(revision 1bebadeef8c0e20e4858c16afc1cb5411a16aeaa)
@@ -0,0 +1,97 @@
+/*
+ * 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/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(void *, const void**, size_t *, unsigned *, unsigned *,
+    pcm_sample_format_t *, const char **);
+
+#endif
+/**
+ * @}
+ */
