Skip to content

Commit

Permalink
Sampling rate synchronization - generic function
Browse files Browse the repository at this point in the history
Make sampling rate synchronization more generic. It can be later
used by other bluez-alsa components.
  • Loading branch information
arkq committed Apr 9, 2017
1 parent b3e8ef3 commit cf7aee5
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 113 deletions.
2 changes: 2 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ AC_CHECK_LIB([pthread], [pthread_create],
[], [AC_MSG_ERROR([pthread library not found])])
AC_CHECK_HEADERS([ortp/rtp.h],
[], [AC_MSG_ERROR([ortp/rtp.h header not found])])
AC_SEARCH_LIBS([clock_gettime], [rt],
[], [AC_MSG_ERROR([unable to find clock_gettime() function])])
AC_SEARCH_LIBS([pow], [m],
[], [AC_MSG_ERROR([unable to find pow() function])])

Expand Down
119 changes: 22 additions & 97 deletions src/io.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,6 @@
#include "shared/rt.h"


struct io_sync {

/* reference time point */
struct timespec ts0;
/* transfered frames since ts0 */
uint32_t frames;

/* time-stamp from the previous sync */
struct timespec ts;
/* delay in 1/10 of millisecond since ts */
uint16_t delay;

/* used sampling frequency */
uint16_t sampling;

};

/**
* Wrapper for release callback, which can be used by pthread cleanup. */
static void io_thread_release(struct ba_transport *t) {
Expand Down Expand Up @@ -247,53 +230,6 @@ static ssize_t io_thread_write_at_response(int fd, const char *msg) {
return io_thread_write_rfcomm(fd, buffer);
}

/**
* Synchronize thread timing with the audio sampling frequency.
*
* Time synchronization relies on the frame counter being linear. This counter
* should be initialized upon transfer start and resume. With the size of this
* counter being 32 bits, it is possible to track up to ~24 hours of playback,
* with the sampling rate of 48 kHz. If this is insufficient, one can switch
* to 64 bits, which would be sufficient to play for 12 million years. */
static int io_thread_time_sync(struct io_sync *io_sync, uint32_t frames) {

const uint16_t sampling = io_sync->sampling;
struct timespec ts_audio;
struct timespec ts_clock;
struct timespec ts;

/* calculate the playback duration of given frames */
unsigned int sec = frames / sampling;
unsigned int res = frames % sampling;
int duration = 1000000 * sec + 1000000 / sampling * res;

io_sync->frames += frames;
frames = io_sync->frames;

/* keep transfer 10ms ahead */
unsigned int overframes = sampling / 100;
frames = frames > overframes ? frames - overframes : 0;

ts_audio.tv_sec = frames / sampling;
ts_audio.tv_nsec = 1000000000 / sampling * (frames % sampling);

gettimestamp(&ts_clock);

/* Calculate delay since the last sync. Note, that we are not taking whole
* seconds into account, because if the delay is greater than one second,
* we are screwed anyway... */
difftimespec(&io_sync->ts, &ts_clock, &ts);
io_sync->delay = ts.tv_nsec / 100000;

/* maintain constant bit rate */
difftimespec(&io_sync->ts0, &ts_clock, &ts_clock);
if (difftimespec(&ts_clock, &ts_audio, &ts) > 0)
nanosleep(&ts, NULL);

gettimestamp(&io_sync->ts);
return duration;
}

void *io_thread_a2dp_sink_sbc(void *arg) {
struct ba_transport *t = (struct ba_transport *)arg;

Expand Down Expand Up @@ -464,6 +400,7 @@ void *io_thread_a2dp_source_sbc(void *arg) {

const size_t sbc_codesize = sbc_get_codesize(&sbc);
const size_t sbc_frame_len = sbc_get_frame_length(&sbc);
const unsigned int sbc_frame_duration = sbc_get_frame_duration(&sbc);
const unsigned int channels = transport_get_channels(t);

/* Writing MTU should be big enough to contain RTP header, SBC payload
Expand Down Expand Up @@ -511,15 +448,12 @@ void *io_thread_a2dp_source_sbc(void *arg) {
int16_t *in_buffer_head = in_buffer;
size_t in_samples = in_buffer_size / sizeof(int16_t);

struct asrsync asrs = { .frames = 0 };
struct pollfd pfds[] = {
{ t->event_fd, POLLIN, 0 },
{ -1, POLLIN, 0 },
};

struct io_sync io_sync = {
.sampling = transport_get_sampling(t),
};

debug("Starting IO loop: %s",
bluetooth_profile_to_string(t->profile, t->codec));
for (;;) {
Expand All @@ -539,7 +473,7 @@ void *io_thread_a2dp_source_sbc(void *arg) {
/* dispatch incoming event */
eventfd_t event;
eventfd_read(pfds[0].fd, &event);
io_sync.frames = 0;
asrs.frames = 0;
continue;
}

Expand All @@ -556,10 +490,8 @@ void *io_thread_a2dp_source_sbc(void *arg) {
* there might be no data for a long time - until client starts playback.
* In order to correctly calculate time drift, the zero time point has to
* be obtained after the stream has started. */
if (io_sync.frames == 0) {
gettimestamp(&io_sync.ts);
io_sync.ts0 = io_sync.ts;
}
if (asrs.frames == 0)
asrsync_init(asrs, transport_get_sampling(t));

if (!config.a2dp_volume)
/* scale volume or mute audio signal */
Expand Down Expand Up @@ -616,8 +548,9 @@ void *io_thread_a2dp_source_sbc(void *arg) {

/* keep data transfer at a constant bit rate, also
* get a timestamp for the next RTP frame */
timestamp += io_thread_time_sync(&io_sync, pcm_frames);
t->delay = io_sync.delay;
asrsync_sync(&asrs, pcm_frames);
timestamp += sbc_frame_duration;
t->delay = asrs.ts_busy.tv_nsec / 100000;

}

Expand Down Expand Up @@ -950,15 +883,12 @@ void *io_thread_a2dp_source_aac(void *arg) {
size_t in_samples = in_buffer_size / in_buffer_element_size;
in_buffer_head = in_buffer;

struct asrsync asrs = { .frames = 0 };
struct pollfd pfds[] = {
{ t->event_fd, POLLIN, 0 },
{ -1, POLLIN, 0 },
};

struct io_sync io_sync = {
.sampling = samplerate,
};

debug("Starting IO loop: %s",
bluetooth_profile_to_string(t->profile, t->codec));
for (;;) {
Expand All @@ -978,7 +908,7 @@ void *io_thread_a2dp_source_aac(void *arg) {
/* dispatch incoming event */
eventfd_t event;
eventfd_read(pfds[0].fd, &event);
io_sync.frames = 0;
asrs.frames = 0;
continue;
}

Expand All @@ -991,10 +921,8 @@ void *io_thread_a2dp_source_aac(void *arg) {

pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

if (io_sync.frames == 0) {
gettimestamp(&io_sync.ts);
io_sync.ts0 = io_sync.ts;
}
if (asrs.frames == 0)
asrsync_init(asrs, samplerate);

if (!config.a2dp_volume)
/* scale volume or mute audio signal */
Expand Down Expand Up @@ -1064,8 +992,10 @@ void *io_thread_a2dp_source_aac(void *arg) {

/* keep data transfer at a constant bit rate, also
* get a timestamp for the next RTP frame */
timestamp += io_thread_time_sync(&io_sync, out_args.numInSamples / channels);
t->delay = io_sync.delay;
unsigned int frames = out_args.numInSamples / channels;
asrsync_sync(&asrs, frames);
timestamp += frames * 10000 / samplerate;
t->delay = asrs.ts_busy.tv_nsec / 100000;

}

Expand Down Expand Up @@ -1244,16 +1174,13 @@ void *io_thread_sco(void *arg) {
goto fail;
}

struct asrsync asrs = { .frames = 0 };
struct pollfd pfds[] = {
{ t->event_fd, POLLIN, 0 },
{ -1, POLLIN, 0 },
{ -1, POLLIN, 0 },
};

struct io_sync io_sync = {
.sampling = transport_get_sampling(t),
};

debug("Starting IO loop: %s",
bluetooth_profile_to_string(t->profile, t->codec));
for (;;) {
Expand Down Expand Up @@ -1282,18 +1209,16 @@ void *io_thread_sco(void *arg) {
* transfered even though we are not reading from it! */
if (t->sco.spk_pcm.fd == -1 && t->sco.mic_pcm.fd == -1) {
transport_release_bt_sco(t);
io_sync.frames = 0;
asrs.frames = 0;
}
else
transport_acquire_bt_sco(t);

continue;
}

if (io_sync.frames == 0) {
gettimestamp(&io_sync.ts);
io_sync.ts0 = io_sync.ts;
}
if (asrs.frames == 0)
asrsync_init(asrs, transport_get_sampling(t));

if (pfds[1].revents & POLLIN) {

Expand Down Expand Up @@ -1322,8 +1247,8 @@ void *io_thread_sco(void *arg) {
}

/* keep data transfer at a constant bit rate */
io_thread_time_sync(&io_sync, 48 / 2);
t->delay = io_sync.delay;
asrsync_sync(&asrs, 48 / 2);
t->delay = asrs.ts_busy.tv_nsec / 100000;

}

Expand Down
43 changes: 43 additions & 0 deletions src/shared/rt.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,49 @@
#include <stdlib.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 = 1000000000 / rate * (frames % rate);

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

/* maintain constant rate */
difftimespec(&asrs->ts0, &ts, &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.
*
Expand Down
39 changes: 39 additions & 0 deletions src/shared/rt.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,47 @@
#ifndef BLUEALSA_SHARED_RT_H_
#define BLUEALSA_SHARED_RT_H_

#include <stdint.h>
#include <time.h>

/**
* Structure used for time synchronization.
*
* With the size of the frame counter being 32 bits, it is possible to track
* up to ~24 hours, with the sampling rate of 48 kHz. If it is insufficient,
* one can switch to 64 bits, which would suffice for 12 million years. */
struct asrsync {

/* used sampling rate */
unsigned int rate;
/* reference time point */
struct timespec ts0;

/* time-stamp from the previous sync */
struct timespec ts;
/* transfered frames since ts0 */
uint32_t frames;

/* time spent outside of the sync function */
struct timespec ts_busy;
/* If the asrsync_sync() returns a positive value, then this variable
* contains an amount of time used for synchronization. Otherwise, it
* contains an overdue time - synchronization was not possible due to
* too much time spent outside of the sync function. */
struct timespec ts_idle;

};

/**
* Start (initialize) time synchronization.
*
* @param asrs Time synchronization structure.
* @param sr Synchronization sampling rate. */
#define asrsync_init(asrs, sr) do { asrs.rate = sr; asrs.frames = 0; \
gettimestamp(&asrs.ts0); asrs.ts = asrs.ts0; } while(0)

int asrsync_sync(struct asrsync *asr, unsigned int frames);

/**
* Get system monotonic time-stamp.
*
Expand Down
Loading

0 comments on commit cf7aee5

Please sign in to comment.