aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorKa Ho Ng <[email protected]>2020-11-26 17:40:11 +0800
committerPaul Adenot <[email protected]>2020-12-01 14:05:01 +0000
commit5c2cf26778af7f2c857870f6bb2c35755f3c1d54 (patch)
treee1f327c730757563998bd5013b8826e4c8e62878 /src
parentdf5fe422b77a58fd8f7e0b3953e83807ae04c060 (diff)
downloadcubeb-5c2cf26778af7f2c857870f6bb2c35755f3c1d54.tar.gz
cubeb-5c2cf26778af7f2c857870f6bb2c35755f3c1d54.zip
Rework the buffering logic
Reduce the code complexity of non-blocking IO all together. Besides, we should let recording direction driving the playback direction if we are working with duplex stream. - Fix misuse of SNDCTL_DSP_GETOSPACE ioctl on record.fd as well - Add more comments regarding buffer initialization - Address comments about which direction driving another direction
Diffstat (limited to 'src')
-rw-r--r--src/cubeb_oss.c230
1 files changed, 118 insertions, 112 deletions
diff --git a/src/cubeb_oss.c b/src/cubeb_oss.c
index 3348cdc..32a4bf6 100644
--- a/src/cubeb_oss.c
+++ b/src/cubeb_oss.c
@@ -741,6 +741,46 @@ oss_linear16_set_vol(int16_t * buf, unsigned sample_count, float vol)
}
}
+static int
+oss_get_rec_frames(cubeb_stream * s, unsigned int nframes)
+{
+ size_t rem = nframes * s->record.frame_size;
+ size_t read_ofs = 0;
+ while (rem > 0) {
+ ssize_t n;
+ if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs, rem)) < 0) {
+ if (errno == EINTR)
+ continue;
+ return CUBEB_ERROR;
+ }
+ read_ofs += n;
+ rem -= n;
+ }
+ return 0;
+}
+
+
+static int
+oss_put_play_frames(cubeb_stream * s, unsigned int nframes)
+{
+ size_t rem = nframes * s->play.frame_size;
+ size_t write_ofs = 0;
+ while (rem > 0) {
+ ssize_t n;
+ if ((n = write(s->play.fd, (uint8_t *)s->play.buf + write_ofs, rem)) < 0) {
+ if (errno == EINTR)
+ continue;
+ return CUBEB_ERROR;
+ }
+ pthread_mutex_lock(&s->mtx);
+ s->frames_written += n / s->play.frame_size;
+ pthread_mutex_unlock(&s->mtx);
+ write_ofs += n;
+ rem -= n;
+ }
+ return 0;
+}
+
/* 1 - Stopped by cubeb_stream_stop, otherwise 0 */
static int
oss_audio_loop(cubeb_stream * s, cubeb_state *new_state)
@@ -748,18 +788,10 @@ oss_audio_loop(cubeb_stream * s, cubeb_state *new_state)
cubeb_state state = CUBEB_STATE_STOPPED;
int trig = 0;
int drain = 0;
- struct pollfd pfds[2];
- unsigned int ppending, rpending;
-
- pfds[0].fd = s->play.fd;
- pfds[0].events = POLLOUT;
- pfds[1].fd = s->record.fd;
- pfds[1].events = POLLIN;
-
- ppending = 0;
- rpending = s->bufframes;
+ const bool play_on = s->play.fd != -1, record_on = s->record.fd != -1;
+ long nfr = s->bufframes;
- if (s->record.fd != -1) {
+ if (record_on) {
if (ioctl(s->record.fd, SNDCTL_DSP_SETTRIGGER, &trig)) {
LOG("Error %d occured when setting trigger on record fd", errno);
state = CUBEB_STATE_ERROR;
@@ -771,43 +803,35 @@ oss_audio_loop(cubeb_stream * s, cubeb_state *new_state)
state = CUBEB_STATE_ERROR;
goto breakdown;
}
+
memset(s->record.buf, 0, s->bufframes * s->record.frame_size);
}
- while (1) {
- long nfr = 0;
+ if (!play_on && !record_on) {
+ /*
+ * Stop here if the stream is not play & record stream,
+ * play-only stream or record-only stream
+ */
+
+ goto breakdown;
+ }
+ while (1) {
pthread_mutex_lock(&s->mtx);
if (!s->running || s->destroying) {
pthread_mutex_unlock(&s->mtx);
break;
}
pthread_mutex_unlock(&s->mtx);
- if (s->play.fd == -1 && s->record.fd == -1) {
- /*
- * Stop here if the stream is not play & record stream,
- * play-only stream or record-only stream
- */
- goto breakdown;
- }
-
- while ((s->bufframes - ppending) >= s->nfr && rpending >= s->nfr) {
- long n = ((s->bufframes - ppending) < rpending) ? s->bufframes - ppending : rpending;
- char *rptr = NULL, *pptr = NULL;
- if (s->record.fd != -1)
- rptr = (char *)s->record.buf;
- if (s->play.fd != -1)
- pptr = (char *)s->play.buf + ppending * s->play.frame_size;
- if (s->record.fd != -1 && s->record.floating) {
- oss_linear32_to_float(s->record.buf, s->record.info.channels * n);
- }
- nfr = s->data_cb(s, s->user_ptr, rptr, pptr, n);
- if (nfr == CUBEB_ERROR) {
+ long got = 0;
+ if (nfr > 0) {
+ got = s->data_cb(s, s->user_ptr, s->record.buf, s->play.buf, nfr);
+ if (got == CUBEB_ERROR) {
state = CUBEB_STATE_ERROR;
goto breakdown;
}
- if (pptr) {
+ if (play_on) {
float vol;
pthread_mutex_lock(&s->mtx);
@@ -815,25 +839,15 @@ oss_audio_loop(cubeb_stream * s, cubeb_state *new_state)
pthread_mutex_unlock(&s->mtx);
if (s->play.floating) {
- oss_float_to_linear32(pptr, s->play.info.channels * nfr, vol);
+ oss_float_to_linear32(s->play.buf, s->play.info.channels * got, vol);
} else {
- oss_linear16_set_vol((int16_t *)pptr, s->play.info.channels * nfr, vol);
+ oss_linear16_set_vol((int16_t *)s->play.buf,
+ s->play.info.channels * got, vol);
}
}
- if (pptr) {
- ppending += nfr;
- assert(ppending <= s->bufframes);
- }
- if (rptr) {
- assert(rpending >= nfr);
- rpending -= nfr;
- memmove(rptr, rptr + nfr * s->record.frame_size,
- (s->bufframes - nfr) * s->record.frame_size);
- }
- if (nfr < n) {
+ if (got < nfr) {
if (s->play.fd != -1) {
drain = 1;
- break;
} else {
/*
* This is a record-only stream and number of frames
@@ -845,74 +859,48 @@ oss_audio_loop(cubeb_stream * s, cubeb_state *new_state)
goto breakdown;
}
}
+ nfr = 0;
}
- ssize_t n, frames;
- int nfds;
-
- pfds[0].revents = 0;
- pfds[1].revents = 0;
-
- nfds = poll(pfds, 2, 1000);
- if (nfds == -1) {
- if (errno == EINTR)
- continue;
- LOG("Error %d occured when polling playback and record fd", errno);
- state = CUBEB_STATE_ERROR;
- goto breakdown;
- } else if (nfds == 0)
- continue;
-
- if ((pfds[0].revents & (POLLERR | POLLHUP)) ||
- (pfds[1].revents & (POLLERR | POLLHUP))) {
- LOG("Error occured on playback, record fds");
- state = CUBEB_STATE_ERROR;
- goto breakdown;
- }
-
- if (pfds[0].revents) {
- while (ppending > 0) {
- size_t bytes = ppending * s->play.frame_size;
- if ((n = write(s->play.fd, (uint8_t *)s->play.buf, bytes)) < 0) {
- if (errno == EINTR)
- continue;
- if (errno == EAGAIN) {
- if (drain)
- continue;
- break;
- }
+ if (got > 0) {
+ if (play_on && oss_put_play_frames(s, got) < 0) {
state = CUBEB_STATE_ERROR;
goto breakdown;
- }
- frames = n / s->play.frame_size;
- pthread_mutex_lock(&s->mtx);
- s->frames_written += frames;
- pthread_mutex_unlock(&s->mtx);
- ppending -= frames;
- memmove(s->play.buf, (uint8_t *)s->play.buf + n,
- (s->bufframes - frames) * s->play.frame_size);
- }
- }
- if (pfds[1].revents) {
- while (s->bufframes - rpending > 0) {
- size_t bytes = (s->bufframes - rpending) * s->record.frame_size;
- size_t read_ofs = rpending * s->record.frame_size;
- if ((n = read(s->record.fd, (uint8_t *)s->record.buf + read_ofs, bytes)) < 0) {
- if (errno == EINTR)
- continue;
- if (errno == EAGAIN)
- break;
- state = CUBEB_STATE_ERROR;
- goto breakdown;
- }
- frames = n / s->record.frame_size;
- rpending += frames;
}
}
if (drain) {
state = CUBEB_STATE_DRAINED;
goto breakdown;
}
+
+ audio_buf_info bi;
+ if (play_on) {
+ if (ioctl(s->play.fd, SNDCTL_DSP_GETOSPACE, &bi)) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+ /*
+ * In duplex mode, playback direction drives recording direction to
+ * prevent building up latencies.
+ */
+ nfr = bi.fragsize * bi.fragments / s->play.frame_size;
+ if (nfr > s->bufframes) {
+ nfr = s->bufframes;
+ }
+ }
+
+ if (record_on) {
+ if (nfr == 0) {
+ nfr = s->nfr;
+ }
+ if (oss_get_rec_frames(s, nfr) == CUBEB_ERROR) {
+ state = CUBEB_STATE_ERROR;
+ goto breakdown;
+ }
+ if (s->record.floating) {
+ oss_linear32_to_float(s->record.buf, s->record.info.channels * nfr);
+ }
+ }
}
return 1;
@@ -1035,7 +1023,7 @@ oss_stream_init(cubeb * context,
goto error;
}
if (s->record.fd == -1) {
- if ((s->record.fd = open(s->record.name, O_RDONLY | O_NONBLOCK)) == -1) {
+ if ((s->record.fd = open(s->record.name, O_RDONLY)) == -1) {
LOG("Audio device \"%s\" could not be opened as read-only",
s->record.name);
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
@@ -1066,7 +1054,7 @@ oss_stream_init(cubeb * context,
goto error;
}
if (s->play.fd == -1) {
- if ((s->play.fd = open(s->play.name, O_WRONLY | O_NONBLOCK)) == -1) {
+ if ((s->play.fd = open(s->play.name, O_WRONLY)) == -1) {
LOG("Audio device \"%s\" could not be opened as write-only",
s->play.name);
ret = CUBEB_ERROR_DEVICE_UNAVAILABLE;
@@ -1082,22 +1070,40 @@ oss_stream_init(cubeb * context,
s->play.frame_size = s->play.info.channels * (s->play.info.precision / 8);
playnfr = (1 << oss_calc_frag_shift(s->nfr, s->play.frame_size)) / s->play.frame_size;
}
- /* Use the largest nframes among playing and recording streams */
+ /*
+ * Use the largest nframes among playing and recording streams to set OSS buffer size.
+ * After that, use the smallest allocated nframes among both direction to allocate our
+ * temporary buffers.
+ */
s->nfr = (playnfr > recnfr) ? playnfr : recnfr;
s->nfrags = OSS_NFRAGS;
- s->bufframes = s->nfr * s->nfrags;
if (s->play.fd != -1) {
int frag = oss_get_frag_params(oss_calc_frag_shift(s->nfr, s->play.frame_size));
- if (ioctl(s->record.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
- LOG("Failed to set record fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
+ if (ioctl(s->play.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
+ LOG("Failed to set play fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
frag);
+ audio_buf_info bi;
+ if (ioctl(s->play.fd, SNDCTL_DSP_GETOSPACE, &bi))
+ LOG("Failed to get play fd's buffer info.");
+ else {
+ if (bi.fragsize / s->play.frame_size < s->nfr)
+ s->nfr = bi.fragsize / s->play.frame_size;
+ }
}
if (s->record.fd != -1) {
int frag = oss_get_frag_params(oss_calc_frag_shift(s->nfr, s->record.frame_size));
if (ioctl(s->record.fd, SNDCTL_DSP_SETFRAGMENT, &frag))
LOG("Failed to set record fd with SNDCTL_DSP_SETFRAGMENT. frag: 0x%x",
frag);
+ audio_buf_info bi;
+ if (ioctl(s->record.fd, SNDCTL_DSP_GETISPACE, &bi))
+ LOG("Failed to get record fd's buffer info.");
+ else {
+ if (bi.fragsize / s->record.frame_size < s->nfr)
+ s->nfr = bi.fragsize / s->record.frame_size;
+ }
}
+ s->bufframes = s->nfr * s->nfrags;
s->context = context;
s->volume = 1.0;
s->state_cb = state_callback;