diff --git a/aplay/Makefile.am b/aplay/Makefile.am
index ef70bcb..59052d9 100644
--- a/aplay/Makefile.am
+++ b/aplay/Makefile.am
@@ -7,7 +7,8 @@ bin_PROGRAMS = aplay
 man_MANS = aplay.1 arecord.1
 noinst_HEADERS = \
 	formats.h \
-	container.h
+	container.h \
+	aligner.h
 
 aplay_SOURCES = \
 	formats.h \
@@ -17,6 +18,8 @@ aplay_SOURCES = \
 	container-riff-wave.c \
 	container-au.c \
 	container-voc.c
+	aligner.h \
+	aligner.c
 
 EXTRA_DIST = aplay.1 arecord.1
 EXTRA_CLEAN = arecord
diff --git a/aplay/aligner.c b/aplay/aligner.c
new file mode 100644
index 0000000..db59fc8
--- /dev/null
+++ b/aplay/aligner.c
@@ -0,0 +1,104 @@
+/*
+ * aligner.c - a muxer/demuxer between data frams and file containers.
+ *
+ * Copyright (c) 2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#include "aligner.h"
+
+#include <gettext.h>
+
+static const char *const aligner_type_labels[] = {
+	[ALIGNER_TYPE_MUXER] = "muxer",
+	[ALIGNER_TYPE_DEMUXER] = "demuxer",
+};
+
+static const char *const aligner_target_labels[] = {
+	[ALIGNER_TARGET_COUNT] = "",
+};
+
+int aligner_context_init(struct aligner_context *aligner,
+			enum aligner_type type, unsigned int cntr_count,
+			unsigned int verbose)
+{
+	const struct aligner_data *data = NULL;
+
+	aligner->ops = &data->ops;
+	aligner->type = type;
+
+	aligner->private_data = malloc(data->private_size);
+	if (aligner->private_data == NULL)
+		return -ENOMEM;
+	memset(aligner->private_data, 0, data->private_size);
+
+	aligner->cntr_count = cntr_count;
+	aligner->verbose = verbose;
+
+	return 0;
+}
+
+int aligner_context_pre_process(struct aligner_context *aligner,
+				snd_pcm_access_t access,
+				snd_pcm_format_t format,
+				unsigned int samples_per_frame,
+				unsigned int frames_per_buffer,
+				struct container_context *cntrs)
+{
+	int err;
+
+	/*
+	 * The purpose of multiple target is to mux/demux each channels to/from
+	 * containers.
+	 */
+	if (aligner->target == ALIGNER_TARGET_MULTIPLE &&
+	    samples_per_frame != aligner->cntr_count)
+		return -EINVAL;
+
+	aligner->access = access;
+	aligner->bytes_per_sample = snd_pcm_format_physical_width(format) / 8;
+	aligner->samples_per_frame = samples_per_frame;
+	aligner->frames_per_buffer = frames_per_buffer;
+
+	err = aligner->ops->pre_process(aligner, cntrs, aligner->cntr_count);
+	if (err < 0)
+		return err;
+
+	if (aligner->verbose > 0) {
+		printf(_("Aligner: %s\n"),
+		       aligner_type_labels[aligner->type]);
+		printf(_("  target: %s\n"),
+		       aligner_target_labels[aligner->target]);
+		printf(_("  access: %s\n"),
+		       snd_pcm_access_name(aligner->access));
+		printf(_("  bytes/sample: %u\n"), aligner->bytes_per_sample);
+		printf(_("  samples/frame: %u\n"), aligner->samples_per_frame);
+		printf(_("  frames/buffer: %lu\n"), aligner->frames_per_buffer);
+		printf("\n");
+	}
+
+	return 0;
+}
+
+int aligner_context_process_frames(struct aligner_context *aligner,
+				   void *frame_buffer,
+				   unsigned int *frame_count,
+				   struct container_context *cntrs)
+{
+	return aligner->ops->process_frames(aligner, frame_buffer, frame_count,
+					    cntrs, aligner->cntr_count);
+}
+
+void aligner_context_post_process(struct aligner_context *aligner)
+{
+	if (aligner->ops && aligner->ops->post_process)
+		aligner->ops->post_process(aligner);
+}
+
+void aligner_context_destroy(struct aligner_context *aligner)
+{
+	if (aligner->private_data)
+		free(aligner->private_data);
+	aligner->private_data = NULL;
+}
diff --git a/aplay/aligner.h b/aplay/aligner.h
new file mode 100644
index 0000000..441dda4
--- /dev/null
+++ b/aplay/aligner.h
@@ -0,0 +1,80 @@
+/*
+ * aligner.h - a header muxer/demuxer between data frames and file containers.
+ *
+ * Copyright (c) 2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
+ *
+ * Licensed under the terms of the GNU General Public License, version 2.
+ */
+
+#ifndef __ALSA_UTILS_APLAY_ALIGNER__H_
+#define __ALSA_UTILS_APLAY_ALIGNER__H_
+
+#include "container.h"
+
+#include <string.h>
+
+enum aligner_type {
+	ALIGNER_TYPE_MUXER = 0,
+	ALIGNER_TYPE_DEMUXER,
+	ALIGNER_TYPE_COUNT,
+};
+
+enum aligner_target {
+	ALIGNER_TARGET_COUNT,
+};
+
+struct aligner_ops;
+
+struct aligner_context {
+	enum aligner_type type;
+	enum aligner_target target;
+	const struct aligner_ops *ops;
+	unsigned int private_size;
+
+	void *private_data;
+	unsigned int cntr_count;
+
+	/* A part of parameters of PCM substream. */
+	snd_pcm_access_t access;
+	unsigned int bytes_per_sample;
+	unsigned int samples_per_frame;
+	snd_pcm_uframes_t frames_per_buffer;
+
+	unsigned int verbose;
+};
+
+int aligner_context_init(struct aligner_context *aligner,
+			enum aligner_type type, unsigned int cntr_count,
+			unsigned int verbose);
+int aligner_context_pre_process(struct aligner_context *aligner,
+				snd_pcm_access_t access,
+				snd_pcm_format_t format,
+				unsigned int samples_per_frame,
+				unsigned int frames_per_buffer,
+				struct container_context *cntrs);
+int aligner_context_process_frames(struct aligner_context *aligner,
+				   void *frame_buffer,
+				   unsigned int *frame_count,
+				   struct container_context *cntrs);
+void aligner_context_post_process(struct aligner_context *aligner);
+void aligner_context_destroy(struct aligner_context *aligner);
+
+/* For internal use in 'aligner' module. */
+
+struct aligner_ops {
+	int (*pre_process)(struct aligner_context *aligner,
+			   struct container_context *cntrs,
+			   unsigned int cntr_count);
+	int (*process_frames)(struct aligner_context *aligner,
+			      void *frame_buffer, unsigned int *frame_count,
+			      struct container_context *cntrs,
+			      unsigned int cntr_count);
+	void (*post_process)(struct aligner_context *aligner);
+};
+
+struct aligner_data {
+	struct aligner_ops ops;
+	unsigned int private_size;
+};
+
+#endif
