/*
 * BlueALSA - rt.h
 * Copyright (c) 2016-2021 Arkadiusz Bokowy
 *
 * This file is a part of bluez-alsa.
 *
 * This project is licensed under the terms of the MIT license.
 *
 */

#include "shared/rt.h"

#include <stdlib.h>
#include <sys/time.h>

/**
 * Synchronize time with the sampling rate.
 *
 * Notes:
 * 1. Time synchronization relies on the frame counter being linear.
 * 2. In order to prevent frame counter overflow (for more information see
 *   the asrsync structure definition), this counter should be initialized
 *   (zeroed) upon every transfer stop.
 *
 * @param asrs Pointer to the time synchronization structure.
 * @param frames Number of frames since the last call to this function.
 * @return This function returns a positive value or zero respectively for
 *   the case, when the synchronization was required or when blocking was
 *   not necessary. If an error has occurred, -1 is returned and errno is
 *   set to indicate the error. */
int asrsync_sync(struct asrsync *asrs, unsigned int frames) {

	const unsigned int rate = asrs->rate;
	struct timespec ts_rate;
	struct timespec ts;
	int rv = 0;

	asrs->frames += frames;
	frames = asrs->frames;

	ts_rate.tv_sec = frames / rate;
	ts_rate.tv_nsec = 1000000000L / rate * (frames % rate);

	gettimestamp(&ts);
	/* calculate delay since the last sync */
	timespecsub(&ts, &asrs->ts, &asrs->ts_busy);

	/* maintain constant rate */
	timespecsub(&ts, &asrs->ts0, &ts);
	if (difftimespec(&ts, &ts_rate, &asrs->ts_idle) > 0) {
		nanosleep(&asrs->ts_idle, NULL);
		rv = 1;
	}

	gettimestamp(&asrs->ts);
	return rv;
}

/**
 * Calculate time difference for two time points.
 *
 * @param ts1 Address to the timespec structure providing t1 time point.
 * @param ts2 Address to the timespec structure providing t2 time point.
 * @param ts Address to the timespec structure where the absolute time
 *   difference will be stored.
 * @return This function returns an integer less than, equal to, or greater
 *   than zero, if t2 time point is found to be, respectively, less than,
 *   equal to, or greater than the t1 time point.*/
int difftimespec(
		const struct timespec *ts1,
		const struct timespec *ts2,
		struct timespec *ts) {

	const struct timespec _ts1 = *ts1;
	const struct timespec _ts2 = *ts2;

	if (_ts1.tv_sec == _ts2.tv_sec) {
		ts->tv_sec = 0;
		ts->tv_nsec = labs(_ts2.tv_nsec - _ts1.tv_nsec);
		return _ts2.tv_nsec - _ts1.tv_nsec;
	}

	if (_ts1.tv_sec < _ts2.tv_sec) {
		timespecsub(&_ts2, &_ts1, ts);
		return 1;
	}

	timespecsub(&_ts1, &_ts2, ts);
	return -1;
}
