aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTaylor Holberton <taylorcholberton@gmail.com>2019-01-13 13:16:52 -0500
committerTaylor Holberton <taylorcholberton@gmail.com>2019-01-13 13:16:52 -0500
commitf6a348d4e74e82591ca1c71a37823802dcabce39 (patch)
tree42a45c601ceb1fe074f9d18469361f7e948394b0
parent3e06adbab73369ece01493c5e1cb982d7677b307 (diff)
parentbc86b6b288bfaac9fc15392fcc43b8e5789a7f15 (diff)
Merge branch 'develop'
-rw-r--r--CMakeLists.txt1
-rw-r--r--Makefile1
-rw-r--r--include/tinyalsa/attributes.h39
-rw-r--r--include/tinyalsa/pcm.h33
-rw-r--r--src/pcm.c632
5 files changed, 373 insertions, 333 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4c78dbf..cb31c58 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.0.2)
project("TinyALSA" C)
set (HDRS
+ "include/tinyalsa/attributes.h"
"include/tinyalsa/version.h"
"include/tinyalsa/asoundlib.h"
"include/tinyalsa/pcm.h"
diff --git a/Makefile b/Makefile
index 7042a3c..e3ed0ef 100644
--- a/Makefile
+++ b/Makefile
@@ -24,6 +24,7 @@ clean:
.PHONY: install
install:
install -d $(DESTDIR)$(INCDIR)/
+ install include/tinyalsa/attributes.h $(DESTDIR)$(INCDIR)/
install include/tinyalsa/pcm.h $(DESTDIR)$(INCDIR)/
install include/tinyalsa/mixer.h $(DESTDIR)$(INCDIR)/
install include/tinyalsa/asoundlib.h $(DESTDIR)$(INCDIR)/
diff --git a/include/tinyalsa/attributes.h b/include/tinyalsa/attributes.h
new file mode 100644
index 0000000..e33f46a
--- /dev/null
+++ b/include/tinyalsa/attributes.h
@@ -0,0 +1,39 @@
+#ifndef TINYALSA_ATTRIBUTES_H
+#define TINYALSA_ATTRIBUTES_H
+
+/** @defgroup libtinyalsa-attributes
+ * @brief GCC attributes to issue diagnostics
+ * when the library is being used incorrectly.
+ * */
+
+#ifdef __GNUC__
+
+/** Issues a warning when a function is being
+ * used that is now deprecated.
+ * @ingroup libtinyalsa-attributes
+ * */
+#define TINYALSA_DEPRECATED __attribute__((deprecated))
+
+/** Issues a warning when a return code of
+ * a function is not checked.
+ * @ingroup libtinyalsa-attributes
+ * */
+#define TINYALSA_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
+
+#else /* __GNUC__ */
+
+/** This is just a placeholder for compilers
+ * that aren't GCC or Clang.
+ * @ingroup libtinyalsa-attributes
+ * */
+#define TINYALSA_DEPRECATED
+
+/** This is just a placeholder for compilers
+ * that aren't GCC or Clang.
+ * @ingroup libtinyalsa-attributes
+ * */
+#define TINYALSA_WARN_UNUSED_RESULT
+
+#endif /* __GNUC__ */
+
+#endif /* TINYALSA_ATTRIBUTES_H */
diff --git a/include/tinyalsa/pcm.h b/include/tinyalsa/pcm.h
index 9b22c55..36aaf85 100644
--- a/include/tinyalsa/pcm.h
+++ b/include/tinyalsa/pcm.h
@@ -35,13 +35,11 @@
#ifndef TINYALSA_PCM_H
#define TINYALSA_PCM_H
+#include <tinyalsa/attributes.h>
+
#include <sys/time.h>
#include <stddef.h>
-#if defined(__cplusplus)
-extern "C" {
-#endif
-
/** A flag that specifies that the PCM is an output.
* May not be bitwise AND'd with @ref PCM_IN.
* Used in @ref pcm_open.
@@ -97,6 +95,11 @@ extern "C" {
* */
#define PCM_NONBLOCK 0x00000010
+/** Means a PCM is prepared
+ * @ingroup libtinyalsa-pcm
+ */
+#define PCM_STATE_PREPARED 0x02
+
/** For inputs, this means the PCM is recording audio samples.
* For outputs, this means the PCM is playing audio samples.
* @ingroup libtinyalsa-pcm
@@ -123,6 +126,10 @@ extern "C" {
*/
#define PCM_STATE_DISCONNECTED 0x08
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
/** Audio sample format of a PCM.
* The first letter specifiers whether the sample is signed or unsigned.
* The letter 'S' means signed. The letter 'U' means unsigned.
@@ -279,23 +286,13 @@ int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail, struct timespec *ts
unsigned int pcm_get_subdevice(const struct pcm *pcm);
-int pcm_writei(struct pcm *pcm, const void *data, unsigned int frame_count);
-
-int pcm_readi(struct pcm *pcm, void *data, unsigned int frame_count);
-
-#ifdef __GNUC__
+int pcm_writei(struct pcm *pcm, const void *data, unsigned int frame_count) TINYALSA_WARN_UNUSED_RESULT;
-int pcm_write(struct pcm *pcm, const void *data, unsigned int count) __attribute((deprecated));
+int pcm_readi(struct pcm *pcm, void *data, unsigned int frame_count) TINYALSA_WARN_UNUSED_RESULT;
-int pcm_read(struct pcm *pcm, void *data, unsigned int count) __attribute((deprecated));
+int pcm_write(struct pcm *pcm, const void *data, unsigned int count) TINYALSA_DEPRECATED;
-#else
-
-int pcm_write(struct pcm *pcm, const void *data, unsigned int count);
-
-int pcm_read(struct pcm *pcm, void *data, unsigned int count);
-
-#endif
+int pcm_read(struct pcm *pcm, void *data, unsigned int count) TINYALSA_DEPRECATED;
int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count);
diff --git a/src/pcm.c b/src/pcm.c
index 96a5788..3a9deec 100644
--- a/src/pcm.c
+++ b/src/pcm.c
@@ -215,12 +215,8 @@ struct pcm {
int fd;
/** Flags that were passed to @ref pcm_open */
unsigned int flags;
- /** Whether the PCM is running or not */
- int running:1;
- /** Whether or not the PCM has been prepared */
- int prepared:1;
- /** The number of underruns that have occured */
- int underruns;
+ /** The number of (under/over)runs that have occured */
+ int xruns;
/** Size of the buffer */
unsigned int buffer_size;
/** The boundary for ring buffer pointers */
@@ -518,14 +514,23 @@ unsigned int pcm_frames_to_bytes(const struct pcm *pcm, unsigned int frames)
static int pcm_sync_ptr(struct pcm *pcm, int flags)
{
- if (pcm->sync_ptr) {
+ if (pcm->sync_ptr == NULL) {
+ /* status and control are mmaped */
+
+ if (flags & SNDRV_PCM_SYNC_PTR_HWSYNC) {
+ if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HWSYNC) == -1) {
+ oops(pcm, errno, "failed to sync hardware pointer");
+ return -1;
+ }
+ }
+ } else {
pcm->sync_ptr->flags = flags;
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SYNC_PTR, pcm->sync_ptr) < 0) {
oops(pcm, errno, "failed to sync mmap ptr");
return -1;
}
- return 0;
}
+
return 0;
}
@@ -551,7 +556,6 @@ static int pcm_hw_mmap_status(struct pcm *pcm)
pcm->mmap_status = NULL;
goto mmap_error;
}
- pcm->mmap_control->avail_min = 1;
return 0;
@@ -562,8 +566,6 @@ mmap_error:
return -ENOMEM;
pcm->mmap_status = &pcm->sync_ptr->s.status;
pcm->mmap_control = &pcm->sync_ptr->c.control;
- pcm->mmap_control->avail_min = 1;
- pcm_sync_ptr(pcm, 0);
return 0;
}
@@ -583,230 +585,6 @@ static void pcm_hw_munmap_status(struct pcm *pcm) {
pcm->mmap_control = NULL;
}
-static int pcm_areas_copy(struct pcm *pcm, unsigned int pcm_offset,
- char *buf, unsigned int src_offset,
- unsigned int frames)
-{
- int size_bytes = pcm_frames_to_bytes(pcm, frames);
- int pcm_offset_bytes = pcm_frames_to_bytes(pcm, pcm_offset);
- int src_offset_bytes = pcm_frames_to_bytes(pcm, src_offset);
-
- /* interleaved only atm */
- if (pcm->flags & PCM_IN)
- memcpy(buf + src_offset_bytes,
- (char*)pcm->mmap_buffer + pcm_offset_bytes,
- size_bytes);
- else
- memcpy((char*)pcm->mmap_buffer + pcm_offset_bytes,
- buf + src_offset_bytes,
- size_bytes);
- return 0;
-}
-
-static int pcm_mmap_transfer_areas(struct pcm *pcm, char *buf,
- unsigned int offset, unsigned int size)
-{
- void *pcm_areas;
- int commit;
- unsigned int pcm_offset, frames, count = 0;
-
- while (size > 0) {
- frames = size;
- pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames);
- pcm_areas_copy(pcm, pcm_offset, buf, offset, frames);
- commit = pcm_mmap_commit(pcm, pcm_offset, frames);
- if (commit < 0) {
- oops(pcm, commit, "failed to commit %d frames\n", frames);
- return commit;
- }
-
- offset += commit;
- count += commit;
- size -= commit;
- }
- return count;
-}
-
-/** Returns available frames in pcm buffer and corresponding time stamp.
- * The clock is CLOCK_MONOTONIC if flag @ref PCM_MONOTONIC was specified in @ref pcm_open,
- * otherwise the clock is CLOCK_REALTIME.
- * For an input stream, frames available are frames ready for the application to read.
- * For an output stream, frames available are the number of empty frames available for the application to write.
- * Only available for PCMs opened with the @ref PCM_MMAP flag.
- * @param pcm A PCM handle.
- * @param avail The number of available frames
- * @param tstamp The timestamp
- * @return On success, zero is returned; on failure, negative one.
- */
-int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail,
- struct timespec *tstamp)
-{
- int frames;
- int rc;
- snd_pcm_uframes_t hw_ptr;
-
- if (!pcm_is_ready(pcm))
- return -1;
-
- rc = pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL|SNDRV_PCM_SYNC_PTR_HWSYNC);
- if (rc < 0)
- return -1;
-
- if ((pcm->mmap_status->state != PCM_STATE_RUNNING) &&
- (pcm->mmap_status->state != PCM_STATE_DRAINING))
- return -1;
-
- *tstamp = pcm->mmap_status->tstamp;
- if (tstamp->tv_sec == 0 && tstamp->tv_nsec == 0)
- return -1;
-
- hw_ptr = pcm->mmap_status->hw_ptr;
- if (pcm->flags & PCM_IN)
- frames = hw_ptr - pcm->mmap_control->appl_ptr;
- else
- frames = hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr;
-
- if (frames < 0)
- return -1;
-
- *avail = (unsigned int)frames;
-
- return 0;
-}
-
-/** Writes audio samples to PCM.
- * If the PCM has not been started, it is started in this function.
- * This function is only valid for PCMs opened with the @ref PCM_OUT flag.
- * This function is not valid for PCMs opened with the @ref PCM_MMAP flag.
- * @param pcm A PCM handle.
- * @param data The audio sample array
- * @param frame_count The number of frames occupied by the sample array.
- * This value should not be greater than @ref TINYALSA_FRAMES_MAX
- * or INT_MAX.
- * @return On success, this function returns the number of frames written; otherwise, a negative number.
- * @ingroup libtinyalsa-pcm
- */
-int pcm_writei(struct pcm *pcm, const void *data, unsigned int frame_count)
-{
- struct snd_xferi x;
-
- if (pcm->flags & PCM_IN)
- return -EINVAL;
-#if UINT_MAX > TINYALSA_FRAMES_MAX
- if (frame_count > TINYALSA_FRAMES_MAX)
- return -EINVAL;
-#endif
- if (frame_count > INT_MAX)
- return -EINVAL;
-
- x.buf = (void*)data;
- x.frames = frame_count;
- x.result = 0;
- for (;;) {
- if (!pcm->running) {
- int prepare_error = pcm_prepare(pcm);
- if (prepare_error)
- return prepare_error;
- if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x))
- return oops(pcm, errno, "cannot write initial data");
- pcm->running = 1;
- return x.result;
- }
- if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {
- pcm->prepared = 0;
- pcm->running = 0;
- if (errno == EPIPE) {
- /* we failed to make our window -- try to restart if we are
- * allowed to do so. Otherwise, simply allow the EPIPE error to
- * propagate up to the app level */
- pcm->underruns++;
- if (pcm->flags & PCM_NORESTART)
- return -EPIPE;
- continue;
- }
- return oops(pcm, errno, "cannot write stream data");
- }
- return x.result;
- }
-}
-
-/** Reads audio samples from PCM.
- * If the PCM has not been started, it is started in this function.
- * This function is only valid for PCMs opened with the @ref PCM_IN flag.
- * This function is not valid for PCMs opened with the @ref PCM_MMAP flag.
- * @param pcm A PCM handle.
- * @param data The audio sample array
- * @param frame_count The number of frames occupied by the sample array.
- * This value should not be greater than @ref TINYALSA_FRAMES_MAX
- * or INT_MAX.
- * @return On success, this function returns the number of frames written; otherwise, a negative number.
- * @ingroup libtinyalsa-pcm
- */
-int pcm_readi(struct pcm *pcm, void *data, unsigned int frame_count)
-{
- struct snd_xferi x;
-
- if (!(pcm->flags & PCM_IN))
- return -EINVAL;
-#if UINT_MAX > TINYALSA_FRAMES_MAX
- if (frame_count > TINYALSA_FRAMES_MAX)
- return -EINVAL;
-#endif
- if (frame_count > INT_MAX)
- return -EINVAL;
-
- x.buf = data;
- x.frames = frame_count;
- x.result = 0;
- for (;;) {
- if ((!pcm->running) && (pcm_start(pcm) < 0))
- return -errno;
- else if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) {
- pcm->prepared = 0;
- pcm->running = 0;
- if (errno == EPIPE) {
- /* we failed to make our window -- try to restart */
- pcm->underruns++;
- continue;
- }
- return oops(pcm, errno, "cannot read stream data");
- }
- return x.result;
- }
-}
-
-/** Writes audio samples to PCM.
- * If the PCM has not been started, it is started in this function.
- * This function is only valid for PCMs opened with the @ref PCM_OUT flag.
- * This function is not valid for PCMs opened with the @ref PCM_MMAP flag.
- * @param pcm A PCM handle.
- * @param data The audio sample array
- * @param count The number of bytes occupied by the sample array.
- * @return On success, this function returns zero; otherwise, a negative number.
- * @deprecated
- * @ingroup libtinyalsa-pcm
- */
-int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
-{
- return pcm_writei(pcm, data, pcm_bytes_to_frames(pcm, count));
-}
-
-/** Reads audio samples from PCM.
- * If the PCM has not been started, it is started in this function.
- * This function is only valid for PCMs opened with the @ref PCM_IN flag.
- * This function is not valid for PCMs opened with the @ref PCM_MMAP flag.
- * @param pcm A PCM handle.
- * @param data The audio sample array
- * @param count The number of bytes occupied by the sample array.
- * @return On success, this function returns zero; otherwise, a negative number.
- * @deprecated
- * @ingroup libtinyalsa-pcm
- */
-int pcm_read(struct pcm *pcm, void *data, unsigned int count)
-{
- return pcm_readi(pcm, data, pcm_bytes_to_frames(pcm, count));
-}
-
static struct pcm bad_pcm = {
.fd = -1,
};
@@ -1017,8 +795,6 @@ int pcm_close(struct pcm *pcm)
if (pcm->fd >= 0)
close(pcm->fd);
- pcm->prepared = 0;
- pcm->running = 0;
pcm->buffer_size = 0;
pcm->fd = -1;
free(pcm);
@@ -1129,10 +905,15 @@ struct pcm *pcm_open(unsigned int card, unsigned int device,
}
#endif
- pcm->underruns = 0;
+ /* prepare here so the user does not need to do this later */
+ if (pcm_prepare(pcm))
+ goto fail;
+
+ pcm->xruns = 0;
return pcm;
fail:
+ pcm_hw_munmap_status(pcm);
if (flags & PCM_MMAP)
munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
fail_close:
@@ -1195,39 +976,31 @@ int pcm_unlink(struct pcm *pcm)
*/
int pcm_prepare(struct pcm *pcm)
{
- if (pcm->prepared)
- return 0;
-
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) < 0)
return oops(pcm, errno, "cannot prepare channel");
- pcm->prepared = 1;
+ /* get appl_ptr and avail_min from kernel */
+ pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL|SNDRV_PCM_SYNC_PTR_AVAIL_MIN);
+
return 0;
}
/** Starts a PCM.
- * If the PCM has not been prepared,
- * it is prepared in this function.
* @param pcm A PCM handle.
* @return On success, zero; on failure, a negative number.
* @ingroup libtinyalsa-pcm
*/
int pcm_start(struct pcm *pcm)
{
- int prepare_error = pcm_prepare(pcm);
- if (prepare_error)
- return prepare_error;
-
- /* if pcm is linked, it may be already started by other pcm */
- /* check pcm state is not in running state */
- pcm_sync_ptr(pcm, 0);
+ /* set appl_ptr and avail_min in kernel */
+ if (pcm_sync_ptr(pcm, 0) < 0)
+ return -1;
if (pcm->mmap_status->state != PCM_STATE_RUNNING) {
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START) < 0)
return oops(pcm, errno, "cannot start channel");
}
- pcm->running = 1;
return 0;
}
@@ -1241,8 +1014,6 @@ int pcm_stop(struct pcm *pcm)
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DROP) < 0)
return oops(pcm, errno, "cannot stop channel");
- pcm->prepared = 0;
- pcm->running = 0;
return 0;
}
@@ -1270,7 +1041,6 @@ static inline int pcm_mmap_capture_avail(struct pcm *pcm)
static inline int pcm_mmap_avail(struct pcm *pcm)
{
- pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_HWSYNC);
if (pcm->flags & PCM_IN)
return pcm_mmap_capture_avail(pcm);
else
@@ -1315,6 +1085,26 @@ int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset,
return 0;
}
+static int pcm_areas_copy(struct pcm *pcm, unsigned int pcm_offset,
+ char *buf, unsigned int src_offset,
+ unsigned int frames)
+{
+ int size_bytes = pcm_frames_to_bytes(pcm, frames);
+ int pcm_offset_bytes = pcm_frames_to_bytes(pcm, pcm_offset);
+ int src_offset_bytes = pcm_frames_to_bytes(pcm, src_offset);
+
+ /* interleaved only atm */
+ if (pcm->flags & PCM_IN)
+ memcpy(buf + src_offset_bytes,
+ (char*)pcm->mmap_buffer + pcm_offset_bytes,
+ size_bytes);
+ else
+ memcpy((char*)pcm->mmap_buffer + pcm_offset_bytes,
+ buf + src_offset_bytes,
+ size_bytes);
+ return 0;
+}
+
int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, unsigned int frames)
{
int ret;
@@ -1333,12 +1123,82 @@ int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, unsigned int frames)
return frames;
}
+static int pcm_mmap_transfer_areas(struct pcm *pcm, char *buf,
+ unsigned int offset, unsigned int size)
+{
+ void *pcm_areas;
+ int commit;
+ unsigned int pcm_offset, frames, count = 0;
+
+ while (pcm_mmap_avail(pcm) && size) {
+ frames = size;
+ pcm_mmap_begin(pcm, &pcm_areas, &pcm_offset, &frames);
+ pcm_areas_copy(pcm, pcm_offset, buf, offset, frames);
+ commit = pcm_mmap_commit(pcm, pcm_offset, frames);
+ if (commit < 0) {
+ oops(pcm, commit, "failed to commit %d frames\n", frames);
+ return commit;
+ }
+
+ offset += commit;
+ count += commit;
+ size -= commit;
+ }
+ return count;
+}
+
int pcm_avail_update(struct pcm *pcm)
{
- pcm_sync_ptr(pcm, 0);
+ pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_APPL|SNDRV_PCM_SYNC_PTR_AVAIL_MIN);
return pcm_mmap_avail(pcm);
}
+/** Returns available frames in pcm buffer and corresponding time stamp.
+ * The clock is CLOCK_MONOTONIC if flag @ref PCM_MONOTONIC was specified in @ref pcm_open,
+ * otherwise the clock is CLOCK_REALTIME.
+ * For an input stream, frames available are frames ready for the application to read.
+ * For an output stream, frames available are the number of empty frames available for the application to write.
+ * @param pcm A PCM handle.
+ * @param avail The number of available frames
+ * @param tstamp The timestamp
+ * @return On success, zero is returned; on failure, negative one.
+ */
+int pcm_get_htimestamp(struct pcm *pcm, unsigned int *avail,
+ struct timespec *tstamp)
+{
+ int checking;
+ int tmp;
+
+ if (!pcm_is_ready(pcm))
+ return -1;
+
+ checking = 0;
+
+again:
+
+ tmp = pcm_avail_update(pcm);
+ if (tmp < 0)
+ return tmp; /* error */
+
+ if (checking && (unsigned int) tmp == *avail)
+ return 0;
+
+ *avail = (unsigned int) tmp;
+ *tstamp = pcm->mmap_status->tstamp;
+
+ /*
+ * When status is mmaped, get avail again to ensure
+ * valid timestamp.
+ */
+ if (!pcm->sync_ptr) {
+ checking = 1;
+ goto again;
+ }
+
+ /* SYNC_PTR ioctl was used, no need to check avail */
+ return 0;
+}
+
int pcm_state(struct pcm *pcm)
{
int err = pcm_sync_ptr(pcm, 0);
@@ -1397,82 +1257,89 @@ int pcm_wait(struct pcm *pcm, int timeout)
return 1;
}
-int pcm_mmap_transfer(struct pcm *pcm, const void *buffer, unsigned int bytes)
+/*
+ * Transfer data to/from mmaped buffer. This imitates the
+ * behavior of read/write system calls.
+ *
+ * However, this doesn't seems to offer any advantage over
+ * the read/write syscalls. Should it be removed?
+ */
+int pcm_mmap_transfer(struct pcm *pcm, void *buffer, unsigned int frames)
{
- int err = 0, frames, avail;
- unsigned int offset = 0, count;
+ int is_playback;
- if (bytes == 0)
- return 0;
+ int state;
+ unsigned int avail;
+ unsigned int user_offset;
- count = pcm_bytes_to_frames(pcm, bytes);
+ int err;
+ int tmp;
- while (count > 0) {
+ is_playback = !(pcm->flags & PCM_IN);
- /* get the available space for writing new frames */
- avail = pcm_avail_update(pcm);
- if (avail < 0) {
- fprintf(stderr, "cannot determine available mmap frames");
- return err;
- }
+ if (frames == 0)
+ return 0;
- /* start the audio if we reach the threshold */
- if (!pcm->running &&
- (pcm->buffer_size - avail) >= pcm->config.start_threshold) {
- if (pcm_start(pcm) < 0) {
- fprintf(stderr, "start error: hw 0x%x app 0x%x avail 0x%x\n",
- (unsigned int)pcm->mmap_status->hw_ptr,
- (unsigned int)pcm->mmap_control->appl_ptr,
- avail);
- return -errno;
- }
+ /* update hardware pointer and get state */
+ err = pcm_sync_ptr(pcm, SNDRV_PCM_SYNC_PTR_HWSYNC |
+ SNDRV_PCM_SYNC_PTR_APPL |
+ SNDRV_PCM_SYNC_PTR_AVAIL_MIN);
+ if (err == -1)
+ return -1;
+ state = pcm->mmap_status->state;
+
+ /* start capture if frames >= threshold */
+ if (!is_playback && state == PCM_STATE_PREPARED) {
+ if (frames >= pcm->config.start_threshold) {
+ err = ioctl(pcm->fd, SNDRV_PCM_IOCTL_START);
+ if (err == -1)
+ return -1;
+ /* state = PCM_STATE_RUNNING */
+ } else {
+ /* nothing to do */
+ return 0;
}
+ }
- /* sleep until we have space to write new frames */
- if (pcm->running &&
- (unsigned int)avail < pcm->mmap_control->avail_min) {
- int time = -1;
+ avail = pcm_mmap_avail(pcm);
+ user_offset = 0;
- if (pcm->flags & PCM_NOIRQ)
- time = (pcm->buffer_size - avail - pcm->mmap_control->avail_min)
- / pcm->noirq_frames_per_msec;
+ while (frames) {
+ if (!avail) {
+ if (pcm->flags & PCM_NONBLOCK) {
+ errno = EAGAIN;
+ break;
+ }
- err = pcm_wait(pcm, time);
+ /* wait for interrupt */
+ err = pcm_wait(pcm, -1);
if (err < 0) {
- pcm->prepared = 0;
- pcm->running = 0;
- fprintf(stderr, "wait error: hw 0x%x app 0x%x avail 0x%x\n",
- (unsigned int)pcm->mmap_status->hw_ptr,
- (unsigned int)pcm->mmap_control->appl_ptr,
- avail);
- pcm->mmap_control->appl_ptr = 0;
- return err;
+ errno = -err;
+ break;
}
- continue;
- }
- frames = count;
- if (frames > avail)
- frames = avail;
+ /* get hardware pointer */
+ avail = pcm_avail_update(pcm);
+ }
- if (!frames)
+ tmp = pcm_mmap_transfer_areas(pcm, buffer, user_offset, frames);
+ if (tmp < 0)
break;
- /* copy frames from buffer */
- frames = pcm_mmap_transfer_areas(pcm, (void *)buffer, offset, frames);
- if (frames < 0) {
- fprintf(stderr, "write error: hw 0x%x app 0x%x avail 0x%x\n",
- (unsigned int)pcm->mmap_status->hw_ptr,
- (unsigned int)pcm->mmap_control->appl_ptr,
- avail);
- return frames;
- }
+ user_offset += tmp;
+ frames -= tmp;
+ avail -= tmp;
- offset += frames;
- count -= frames;
+ /* start playback if written >= start_threshold */
+ if (is_playback && state == PCM_STATE_PREPARED &&
+ pcm->buffer_size - avail >= pcm->config.start_threshold) {
+ err = ioctl(pcm->fd, SNDRV_PCM_IOCTL_START);
+ if (err == -1)
+ break;
+ }
}
- return 0;
+ return user_offset ? (int) user_offset : -1;
}
int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count)
@@ -1480,7 +1347,8 @@ int pcm_mmap_write(struct pcm *pcm, const void *data, unsigned int count)
if ((~pcm->flags) & (PCM_OUT | PCM_MMAP))
return -ENOSYS;
- return pcm_mmap_transfer(pcm, (void *)data, count);
+ return pcm_mmap_transfer(pcm, (void *)data,
+ pcm_bytes_to_frames(pcm, count));
}
int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count)
@@ -1488,7 +1356,141 @@ int pcm_mmap_read(struct pcm *pcm, void *data, unsigned int count)
if ((~pcm->flags) & (PCM_IN | PCM_MMAP))
return -ENOSYS;
- return pcm_mmap_transfer(pcm, data, count);
+ return pcm_mmap_transfer(pcm, data, pcm_bytes_to_frames(pcm, count));
+}
+
+static int pcm_rw_transfer(struct pcm *pcm, void *data, unsigned int frames)
+{
+ int is_playback;
+
+ struct snd_xferi transfer;
+ int res;
+
+ is_playback = !(pcm->flags & PCM_IN);
+
+ transfer.buf = data;
+ transfer.frames = frames;
+ transfer.result = 0;
+
+ res = ioctl(pcm->fd, is_playback
+ ? SNDRV_PCM_IOCTL_WRITEI_FRAMES
+ : SNDRV_PCM_IOCTL_READI_FRAMES, &transfer);
+
+ return res == 0 ? (int) transfer.result : -1;
+}
+
+static int pcm_generic_transfer(struct pcm *pcm, void *data,
+ unsigned int frames)
+{
+ int res;
+
+#if UINT_MAX > TINYALSA_FRAMES_MAX
+ if (frames > TINYALSA_FRAMES_MAX)
+ return -EINVAL;
+#endif
+ if (frames > INT_MAX)
+ return -EINVAL;
+
+again:
+
+ if (pcm->flags & PCM_MMAP)
+ res = pcm_mmap_transfer(pcm, data, frames);
+ else
+ res = pcm_rw_transfer(pcm, data, frames);
+
+ if (res < 0) {
+ switch (errno) {
+ case EPIPE:
+ pcm->xruns++;
+ /* fallthrough */
+ case ESTRPIPE:
+ /*
+ * Try to restart if we are allowed to do so.
+ * Otherwise, return error.
+ */
+ if (pcm->flags & PCM_NORESTART || pcm_prepare(pcm))
+ return -1;
+ goto again;
+ case EAGAIN:
+ if (pcm->flags & PCM_NONBLOCK)
+ return -1;
+ /* fallthrough */
+ default:
+ return oops(pcm, errno, "cannot read/write stream data");
+ }
+ }
+
+ return res;
+}
+
+/** Writes audio samples to PCM.
+ * If the PCM has not been started, it is started in this function.
+ * This function is only valid for PCMs opened with the @ref PCM_OUT flag.
+ * @param pcm A PCM handle.
+ * @param data The audio sample array
+ * @param frame_count The number of frames occupied by the sample array.
+ * This value should not be greater than @ref TINYALSA_FRAMES_MAX
+ * or INT_MAX.
+ * @return On success, this function returns the number of frames written; otherwise, a negative number.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_writei(struct pcm *pcm, const void *data, unsigned int frame_count)
+{
+ if (pcm->flags & PCM_IN)
+ return -EINVAL;
+
+ return pcm_generic_transfer(pcm, (void*) data, frame_count);
+}
+
+/** Reads audio samples from PCM.
+ * If the PCM has not been started, it is started in this function.
+ * This function is only valid for PCMs opened with the @ref PCM_IN flag.
+ * @param pcm A PCM handle.
+ * @param data The audio sample array
+ * @param frame_count The number of frames occupied by the sample array.
+ * This value should not be greater than @ref TINYALSA_FRAMES_MAX
+ * or INT_MAX.
+ * @return On success, this function returns the number of frames written; otherwise, a negative number.
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_readi(struct pcm *pcm, void *data, unsigned int frame_count)
+{
+ if (!(pcm->flags & PCM_IN))
+ return -EINVAL;
+
+ return pcm_generic_transfer(pcm, data, frame_count);
+}
+
+/** Writes audio samples to PCM.
+ * If the PCM has not been started, it is started in this function.
+ * This function is only valid for PCMs opened with the @ref PCM_OUT flag.
+ * This function is not valid for PCMs opened with the @ref PCM_MMAP flag.
+ * @param pcm A PCM handle.
+ * @param data The audio sample array
+ * @param count The number of bytes occupied by the sample array.
+ * @return On success, this function returns zero; otherwise, a negative number.
+ * @deprecated
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
+{
+ return pcm_writei(pcm, data, pcm_bytes_to_frames(pcm, count));
+}
+
+/** Reads audio samples from PCM.
+ * If the PCM has not been started, it is started in this function.
+ * This function is only valid for PCMs opened with the @ref PCM_IN flag.
+ * This function is not valid for PCMs opened with the @ref PCM_MMAP flag.
+ * @param pcm A PCM handle.
+ * @param data The audio sample array
+ * @param count The number of bytes occupied by the sample array.
+ * @return On success, this function returns zero; otherwise, a negative number.
+ * @deprecated
+ * @ingroup libtinyalsa-pcm
+ */
+int pcm_read(struct pcm *pcm, void *data, unsigned int count)
+{
+ return pcm_readi(pcm, data, pcm_bytes_to_frames(pcm, count));
}
/** Gets the delay of the PCM, in terms of frames.