/*
 * Test playback on very low periods
 * This program is intended to work with direct hardware device,
 * i.e. not dmix or some wrapper layer.
 *
 * compile: gcc alsa_period_test.c -o test -lasound
 * license: Simplified BSD
 */
#include <stdio.h>
#include <alsa/asoundlib.h>

#define TEST_PERIOD_MAX 2048
#define TEST_PERIOD_STEP 128

static void check_ret(int r, const char *name)
{
	if (r < 0)
		fprintf(stderr, "%s: %d: %s\n", name, r, strerror(-r));
}

static int try_params_period(snd_pcm_t *handle, unsigned int period_size)
{
	snd_pcm_hw_params_t *hwparams = NULL;
	snd_pcm_uframes_t period = period_size;
	unsigned int rate = 8000;
	int r;

	snd_pcm_hw_params_alloca(&hwparams);

	r = snd_pcm_hw_params_any(handle, hwparams);
	check_ret(r, "snd_pcm_hw_params_any");

	r = snd_pcm_hw_params_set_access(handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED);
	check_ret(r, "snd_pcm_hw_params_set_access");

	r = snd_pcm_hw_params_set_format(handle, hwparams, SND_PCM_FORMAT_S16_LE);
	check_ret(r, "snd_pcm_hw_params_set_format");

	r = snd_pcm_hw_params_set_channels(handle, hwparams, 2);
	check_ret(r, "snd_pcm_hw_params_set_channels");

	r = snd_pcm_hw_params_set_rate_near(handle, hwparams, &rate, 0);
	check_ret(r, "snd_pcm_hw_params_set_rate_near");

	r = snd_pcm_hw_params_set_periods(handle, hwparams, 11, 0);
	check_ret(r, "snd_pcm_hw_params_set_periods");

	r = snd_pcm_hw_params_set_period_size_near(handle, hwparams, &period, NULL);
	if (r < 0)
		return r;

	return snd_pcm_hw_params(handle, hwparams);
}

int main(int argc, char *argv[])
{
	snd_pcm_t *handle = NULL;
	snd_pcm_hw_params_t *hwparams = NULL;
	unsigned int period_size = 0;
	snd_pcm_uframes_t period_got = 0;
	short buf[TEST_PERIOD_MAX * 2];
	const char *device;
	unsigned int i;
	int r;

	device = argv[1] ? argv[1] : "hw:0";

	r = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0);
	check_ret(r, "snd_pcm_open");
	if (r < 0)
		return 1;

	period_size = TEST_PERIOD_STEP;
	for (; period_size < TEST_PERIOD_MAX; period_size += TEST_PERIOD_STEP) {
		r = try_params_period(handle, period_size);
		if (r >= 0)
			break;
	}

	if (r < 0) {
		fprintf(stderr, "could not set any period, last ret %d\n", r);
		snd_pcm_close(handle);
		return 1;
	}

	snd_pcm_hw_params_alloca(&hwparams);

	r = snd_pcm_hw_params_current(handle, hwparams);
	check_ret(r, "snd_pcm_hw_params_current");

	snd_pcm_hw_params_get_period_size(hwparams, &period_got, NULL);

	printf("period size: tried %u, set %d\n", period_size, (int)period_got);
	period_size = (unsigned int)period_got;

	for (i = 0; i < period_size * 2; i++)
		buf[i] = rand();

	for (i = 0; i < 64; i++) {
		r = snd_pcm_writei(handle, buf, period_size);
		check_ret(r, "snd_pcm_writei");
		if (r < 0)
			snd_pcm_recover(handle, period_size, 0);
	}

	snd_pcm_close(handle);
	return 0;
}
