0b5ad60c26
The port to for this two targets has been entirely developped by Ilia Sergachev (alias Il or xzcc). His source can be found at https://bitbucket.org/isergachev/rockbox . The few necesary modifications for the DX90 port was done by headwhacker form head-fi.org. Unfortunately i could not try out the final state of the DX90 port. The port is hosted on android (without java) as standalone app. The official Firmware is required to run this port. Ilia did modify the source files for the "android" target in the rockbox source to make the DX port work. The work I did was to separate the code for DX50 (&DX90) from the android target. On this Target Ilia used source from tinyalsa from AOSP. I did not touch that part of the code because I do not understand it. What else I changed from Ilias sources besides the separation from the target "android": * removed a dirty hack to keep backlight off * changed value battery meter to voltage battery meter * made all plugins compile (named target as "standalone") and added keymaps * i added the graphics for the manual but did not do anything else for the manual yet * minor optimizations known bugs: * timers are slowed donw when playback is active (tinyalsa related?) * some minor bugs Things to do: * The main prolem will be how to install the app correctly. A guy called DOC2008 added a CWM (by androtab.info) to the official firmware and Ilia made a CWM installation script and a dualboot selector (rbutils/ibassoboot, build with ndk-build). We will have to find a way to install rockbox in a proper way without breaking any copyrights. Maybe ADB is an option but it is not enable with OF by default. Patching the OF is probably the way to go. * All the wiki and manual to build: needed: android ndk installed, android sdk installed with additional build-tools 19.1.0 installed ./tools/configure select iBasso DX50 or iBasso DX90 make -j apk the content of rockbox.zip/.rockbox needs to be copied to /system/rockbox/app_rockbox/rockbox/ (rockbox app not needed) the content of libs/armeabi to /system/rockbox/lib/ (rockbox app needed) The boot selector is needed as /system/bin/MangoPlayer and the iBasso app as /system/bin/MangoPlayer_original. There is also the "vold" file. The one from OF does not work with DX50 rockbox (DX90 works!?), the one from Ilia is necessary. Until we have found a proper way to install it, it can only be installed following the instructions of Ilia on his bitbucket page, using the CWM-OF and his installation script package. Change-Id: Ic4faaf84824c162aabcc08e492cee6e0068719d0 Reviewed-on: http://gerrit.rockbox.org/941 Tested: Chiwen Chang <rock1104.tw@yahoo.com.tw> Reviewed-by: Michael Giacomelli <giac2000@hotmail.com>
973 lines
26 KiB
C
973 lines
26 KiB
C
/* pcm.c
|
|
**
|
|
** Copyright 2011, The Android Open Source Project
|
|
**
|
|
** Redistribution and use in source and binary forms, with or without
|
|
** modification, are permitted provided that the following conditions are met:
|
|
** * Redistributions of source code must retain the above copyright
|
|
** notice, this list of conditions and the following disclaimer.
|
|
** * Redistributions in binary form must reproduce the above copyright
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
** documentation and/or other materials provided with the distribution.
|
|
** * Neither the name of The Android Open Source Project nor the names of
|
|
** its contributors may be used to endorse or promote products derived
|
|
** from this software without specific prior written permission.
|
|
**
|
|
** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
|
|
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
|
|
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
|
** DAMAGE.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <poll.h>
|
|
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/time.h>
|
|
#include <limits.h>
|
|
|
|
#include <linux/ioctl.h>
|
|
#define __force
|
|
#define __bitwise
|
|
#define __user
|
|
#include <tinyalsa/asound.h>
|
|
|
|
#include <tinyalsa/asoundlib.h>
|
|
|
|
#define PARAM_MAX SNDRV_PCM_HW_PARAM_LAST_INTERVAL
|
|
#define SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP (1<<2)
|
|
|
|
static inline int param_is_mask(int p)
|
|
{
|
|
return (p >= SNDRV_PCM_HW_PARAM_FIRST_MASK) &&
|
|
(p <= SNDRV_PCM_HW_PARAM_LAST_MASK);
|
|
}
|
|
|
|
static inline int param_is_interval(int p)
|
|
{
|
|
return (p >= SNDRV_PCM_HW_PARAM_FIRST_INTERVAL) &&
|
|
(p <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL);
|
|
}
|
|
|
|
static inline struct snd_interval *param_to_interval(struct snd_pcm_hw_params *p, int n)
|
|
{
|
|
return &(p->intervals[n - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL]);
|
|
}
|
|
|
|
static inline struct snd_mask *param_to_mask(struct snd_pcm_hw_params *p, int n)
|
|
{
|
|
return &(p->masks[n - SNDRV_PCM_HW_PARAM_FIRST_MASK]);
|
|
}
|
|
|
|
static void param_set_mask(struct snd_pcm_hw_params *p, int n, unsigned int bit)
|
|
{
|
|
if (bit >= SNDRV_MASK_MAX)
|
|
return;
|
|
if (param_is_mask(n)) {
|
|
struct snd_mask *m = param_to_mask(p, n);
|
|
m->bits[0] = 0;
|
|
m->bits[1] = 0;
|
|
m->bits[bit >> 5] |= (1 << (bit & 31));
|
|
}
|
|
}
|
|
|
|
static void param_set_min(struct snd_pcm_hw_params *p, int n, unsigned int val)
|
|
{
|
|
if (param_is_interval(n)) {
|
|
struct snd_interval *i = param_to_interval(p, n);
|
|
i->min = val;
|
|
}
|
|
}
|
|
|
|
static unsigned int param_get_min(struct snd_pcm_hw_params *p, int n)
|
|
{
|
|
if (param_is_interval(n)) {
|
|
struct snd_interval *i = param_to_interval(p, n);
|
|
return i->min;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int param_get_max(struct snd_pcm_hw_params *p, int n)
|
|
{
|
|
if (param_is_interval(n)) {
|
|
struct snd_interval *i = param_to_interval(p, n);
|
|
return i->max;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void param_set_int(struct snd_pcm_hw_params *p, int n, unsigned int val)
|
|
{
|
|
if (param_is_interval(n)) {
|
|
struct snd_interval *i = param_to_interval(p, n);
|
|
i->min = val;
|
|
i->max = val;
|
|
i->integer = 1;
|
|
}
|
|
}
|
|
|
|
static unsigned int param_get_int(struct snd_pcm_hw_params *p, int n)
|
|
{
|
|
if (param_is_interval(n)) {
|
|
struct snd_interval *i = param_to_interval(p, n);
|
|
if (i->integer)
|
|
return i->max;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void param_init(struct snd_pcm_hw_params *p)
|
|
{
|
|
int n;
|
|
|
|
memset(p, 0, sizeof(*p));
|
|
for (n = SNDRV_PCM_HW_PARAM_FIRST_MASK;
|
|
n <= SNDRV_PCM_HW_PARAM_LAST_MASK; n++) {
|
|
struct snd_mask *m = param_to_mask(p, n);
|
|
m->bits[0] = ~0;
|
|
m->bits[1] = ~0;
|
|
}
|
|
for (n = SNDRV_PCM_HW_PARAM_FIRST_INTERVAL;
|
|
n <= SNDRV_PCM_HW_PARAM_LAST_INTERVAL; n++) {
|
|
struct snd_interval *i = param_to_interval(p, n);
|
|
i->min = 0;
|
|
i->max = ~0;
|
|
}
|
|
p->rmask = ~0U;
|
|
p->cmask = 0;
|
|
p->info = ~0U;
|
|
}
|
|
|
|
#define PCM_ERROR_MAX 128
|
|
|
|
struct pcm {
|
|
int fd;
|
|
unsigned int flags;
|
|
int running:1;
|
|
int underruns;
|
|
unsigned int buffer_size;
|
|
unsigned int boundary;
|
|
char error[PCM_ERROR_MAX];
|
|
struct pcm_config config;
|
|
struct snd_pcm_mmap_status *mmap_status;
|
|
struct snd_pcm_mmap_control *mmap_control;
|
|
struct snd_pcm_sync_ptr *sync_ptr;
|
|
void *mmap_buffer;
|
|
unsigned int noirq_frames_per_msec;
|
|
};
|
|
|
|
unsigned int pcm_get_buffer_size(struct pcm *pcm)
|
|
{
|
|
return pcm->buffer_size;
|
|
}
|
|
|
|
const char* pcm_get_error(struct pcm *pcm)
|
|
{
|
|
return pcm->error;
|
|
}
|
|
|
|
static int oops(struct pcm *pcm, int e, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int sz;
|
|
|
|
va_start(ap, fmt);
|
|
vsnprintf(pcm->error, PCM_ERROR_MAX, fmt, ap);
|
|
va_end(ap);
|
|
sz = strlen(pcm->error);
|
|
|
|
if (errno)
|
|
snprintf(pcm->error + sz, PCM_ERROR_MAX - sz,
|
|
": %s", strerror(e));
|
|
return -1;
|
|
}
|
|
|
|
static unsigned int pcm_format_to_alsa(enum pcm_format format)
|
|
{
|
|
switch (format) {
|
|
case PCM_FORMAT_S32_LE:
|
|
return SNDRV_PCM_FORMAT_S32_LE;
|
|
case PCM_FORMAT_S8:
|
|
return SNDRV_PCM_FORMAT_S8;
|
|
case PCM_FORMAT_S24_LE:
|
|
return SNDRV_PCM_FORMAT_S24_LE;
|
|
default:
|
|
case PCM_FORMAT_S16_LE:
|
|
return SNDRV_PCM_FORMAT_S16_LE;
|
|
};
|
|
}
|
|
|
|
unsigned int pcm_format_to_bits(enum pcm_format format)
|
|
{
|
|
switch (format) {
|
|
case PCM_FORMAT_S32_LE:
|
|
case PCM_FORMAT_S24_LE:
|
|
return 32;
|
|
default:
|
|
case PCM_FORMAT_S16_LE:
|
|
return 16;
|
|
};
|
|
}
|
|
|
|
unsigned int pcm_bytes_to_frames(struct pcm *pcm, unsigned int bytes)
|
|
{
|
|
return bytes / (pcm->config.channels *
|
|
(pcm_format_to_bits(pcm->config.format) >> 3));
|
|
}
|
|
|
|
unsigned int pcm_frames_to_bytes(struct pcm *pcm, unsigned int frames)
|
|
{
|
|
return frames * pcm->config.channels *
|
|
(pcm_format_to_bits(pcm->config.format) >> 3);
|
|
}
|
|
|
|
static int pcm_sync_ptr(struct pcm *pcm, int flags) {
|
|
if (pcm->sync_ptr) {
|
|
pcm->sync_ptr->flags = flags;
|
|
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SYNC_PTR, pcm->sync_ptr) < 0)
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int pcm_hw_mmap_status(struct pcm *pcm) {
|
|
|
|
if (pcm->sync_ptr)
|
|
return 0;
|
|
|
|
int page_size = sysconf(_SC_PAGE_SIZE);
|
|
pcm->mmap_status = mmap(NULL, page_size, PROT_READ, MAP_FILE | MAP_SHARED,
|
|
pcm->fd, SNDRV_PCM_MMAP_OFFSET_STATUS);
|
|
if (pcm->mmap_status == MAP_FAILED)
|
|
pcm->mmap_status = NULL;
|
|
if (!pcm->mmap_status)
|
|
goto mmap_error;
|
|
|
|
pcm->mmap_control = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
|
|
MAP_FILE | MAP_SHARED, pcm->fd, SNDRV_PCM_MMAP_OFFSET_CONTROL);
|
|
if (pcm->mmap_control == MAP_FAILED)
|
|
pcm->mmap_control = NULL;
|
|
if (!pcm->mmap_control) {
|
|
munmap(pcm->mmap_status, page_size);
|
|
pcm->mmap_status = NULL;
|
|
goto mmap_error;
|
|
}
|
|
pcm->mmap_control->avail_min = 1;
|
|
|
|
return 0;
|
|
|
|
mmap_error:
|
|
|
|
pcm->sync_ptr = calloc(1, sizeof(*pcm->sync_ptr));
|
|
if (!pcm->sync_ptr)
|
|
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;
|
|
}
|
|
|
|
static void pcm_hw_munmap_status(struct pcm *pcm) {
|
|
if (pcm->sync_ptr) {
|
|
free(pcm->sync_ptr);
|
|
pcm->sync_ptr = NULL;
|
|
} else {
|
|
int page_size = sysconf(_SC_PAGE_SIZE);
|
|
if (pcm->mmap_status)
|
|
munmap(pcm->mmap_status, page_size);
|
|
if (pcm->mmap_control)
|
|
munmap(pcm->mmap_control, page_size);
|
|
}
|
|
pcm->mmap_status = NULL;
|
|
pcm->mmap_control = NULL;
|
|
}
|
|
|
|
static int pcm_areas_copy(struct pcm *pcm, unsigned int pcm_offset,
|
|
const char *src, 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 */
|
|
memcpy((char*)pcm->mmap_buffer + pcm_offset_bytes,
|
|
src + src_offset_bytes, size_bytes);
|
|
return 0;
|
|
}
|
|
|
|
static int pcm_mmap_write_areas(struct pcm *pcm, const char *src,
|
|
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, src, 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_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;
|
|
}
|
|
|
|
int pcm_write(struct pcm *pcm, const void *data, unsigned int count)
|
|
{
|
|
struct snd_xferi x;
|
|
|
|
if (pcm->flags & PCM_IN)
|
|
return -EINVAL;
|
|
|
|
x.buf = (void*)data;
|
|
x.frames = count / (pcm->config.channels *
|
|
pcm_format_to_bits(pcm->config.format) / 8);
|
|
|
|
for (;;) {
|
|
if (!pcm->running) {
|
|
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE))
|
|
return oops(pcm, errno, "cannot prepare channel");
|
|
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x))
|
|
return oops(pcm, errno, "cannot write initial data");
|
|
pcm->running = 1;
|
|
return 0;
|
|
}
|
|
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_WRITEI_FRAMES, &x)) {
|
|
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 0;
|
|
}
|
|
}
|
|
|
|
int pcm_read(struct pcm *pcm, void *data, unsigned int count)
|
|
{
|
|
struct snd_xferi x;
|
|
|
|
if (!(pcm->flags & PCM_IN))
|
|
return -EINVAL;
|
|
|
|
x.buf = data;
|
|
x.frames = count / (pcm->config.channels *
|
|
pcm_format_to_bits(pcm->config.format) / 8);
|
|
|
|
for (;;) {
|
|
if (!pcm->running) {
|
|
if (pcm_start(pcm) < 0) {
|
|
fprintf(stderr, "start error");
|
|
return -errno;
|
|
}
|
|
}
|
|
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_READI_FRAMES, &x)) {
|
|
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 0;
|
|
}
|
|
}
|
|
|
|
static struct pcm bad_pcm = {
|
|
.fd = -1,
|
|
};
|
|
|
|
struct pcm_params *pcm_params_get(unsigned int card, unsigned int device,
|
|
unsigned int flags)
|
|
{
|
|
struct snd_pcm_hw_params *params;
|
|
char fn[256];
|
|
int fd;
|
|
|
|
snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
|
|
flags & PCM_IN ? 'c' : 'p');
|
|
|
|
fd = open(fn, O_RDWR);
|
|
if (fd < 0) {
|
|
fprintf(stderr, "cannot open device '%s'\n", fn);
|
|
goto err_open;
|
|
}
|
|
|
|
params = calloc(1, sizeof(struct snd_pcm_hw_params));
|
|
if (!params)
|
|
goto err_calloc;
|
|
|
|
param_init(params);
|
|
if (ioctl(fd, SNDRV_PCM_IOCTL_HW_REFINE, params)) {
|
|
fprintf(stderr, "SNDRV_PCM_IOCTL_HW_REFINE error (%d)\n", errno);
|
|
goto err_hw_refine;
|
|
}
|
|
|
|
close(fd);
|
|
|
|
return (struct pcm_params *)params;
|
|
|
|
err_hw_refine:
|
|
free(params);
|
|
err_calloc:
|
|
close(fd);
|
|
err_open:
|
|
return NULL;
|
|
}
|
|
|
|
void pcm_params_free(struct pcm_params *pcm_params)
|
|
{
|
|
struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
|
|
|
|
if (params)
|
|
free(params);
|
|
}
|
|
|
|
static int pcm_param_to_alsa(enum pcm_param param)
|
|
{
|
|
switch (param) {
|
|
case PCM_PARAM_SAMPLE_BITS:
|
|
return SNDRV_PCM_HW_PARAM_SAMPLE_BITS;
|
|
break;
|
|
case PCM_PARAM_FRAME_BITS:
|
|
return SNDRV_PCM_HW_PARAM_FRAME_BITS;
|
|
break;
|
|
case PCM_PARAM_CHANNELS:
|
|
return SNDRV_PCM_HW_PARAM_CHANNELS;
|
|
break;
|
|
case PCM_PARAM_RATE:
|
|
return SNDRV_PCM_HW_PARAM_RATE;
|
|
break;
|
|
case PCM_PARAM_PERIOD_TIME:
|
|
return SNDRV_PCM_HW_PARAM_PERIOD_TIME;
|
|
break;
|
|
case PCM_PARAM_PERIOD_SIZE:
|
|
return SNDRV_PCM_HW_PARAM_PERIOD_SIZE;
|
|
break;
|
|
case PCM_PARAM_PERIOD_BYTES:
|
|
return SNDRV_PCM_HW_PARAM_PERIOD_BYTES;
|
|
break;
|
|
case PCM_PARAM_PERIODS:
|
|
return SNDRV_PCM_HW_PARAM_PERIODS;
|
|
break;
|
|
case PCM_PARAM_BUFFER_TIME:
|
|
return SNDRV_PCM_HW_PARAM_BUFFER_TIME;
|
|
break;
|
|
case PCM_PARAM_BUFFER_SIZE:
|
|
return SNDRV_PCM_HW_PARAM_BUFFER_SIZE;
|
|
break;
|
|
case PCM_PARAM_BUFFER_BYTES:
|
|
return SNDRV_PCM_HW_PARAM_BUFFER_BYTES;
|
|
break;
|
|
case PCM_PARAM_TICK_TIME:
|
|
return SNDRV_PCM_HW_PARAM_TICK_TIME;
|
|
break;
|
|
|
|
default:
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
unsigned int pcm_params_get_min(struct pcm_params *pcm_params,
|
|
enum pcm_param param)
|
|
{
|
|
struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
|
|
int p;
|
|
|
|
if (!params)
|
|
return 0;
|
|
|
|
p = pcm_param_to_alsa(param);
|
|
if (p < 0)
|
|
return 0;
|
|
|
|
return param_get_min(params, p);
|
|
}
|
|
|
|
unsigned int pcm_params_get_max(struct pcm_params *pcm_params,
|
|
enum pcm_param param)
|
|
{
|
|
struct snd_pcm_hw_params *params = (struct snd_pcm_hw_params *)pcm_params;
|
|
int p;
|
|
|
|
if (!params)
|
|
return 0;
|
|
|
|
p = pcm_param_to_alsa(param);
|
|
if (p < 0)
|
|
return 0;
|
|
|
|
return param_get_max(params, p);
|
|
}
|
|
|
|
int pcm_close(struct pcm *pcm)
|
|
{
|
|
if (pcm == &bad_pcm)
|
|
return 0;
|
|
|
|
pcm_hw_munmap_status(pcm);
|
|
|
|
if (pcm->flags & PCM_MMAP) {
|
|
pcm_stop(pcm);
|
|
munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
|
|
}
|
|
|
|
if (pcm->fd >= 0)
|
|
close(pcm->fd);
|
|
pcm->running = 0;
|
|
pcm->buffer_size = 0;
|
|
pcm->fd = -1;
|
|
free(pcm);
|
|
return 0;
|
|
}
|
|
|
|
struct pcm *pcm_open(unsigned int card, unsigned int device,
|
|
unsigned int flags, struct pcm_config *config)
|
|
{
|
|
struct pcm *pcm;
|
|
struct snd_pcm_info info;
|
|
struct snd_pcm_hw_params params;
|
|
struct snd_pcm_sw_params sparams;
|
|
char fn[256];
|
|
int rc;
|
|
|
|
pcm = calloc(1, sizeof(struct pcm));
|
|
if (!pcm || !config)
|
|
return &bad_pcm; /* TODO: could support default config here */
|
|
|
|
pcm->config = *config;
|
|
|
|
snprintf(fn, sizeof(fn), "/dev/snd/pcmC%uD%u%c", card, device,
|
|
flags & PCM_IN ? 'c' : 'p');
|
|
|
|
pcm->flags = flags;
|
|
pcm->fd = open(fn, O_RDWR);
|
|
if (pcm->fd < 0) {
|
|
oops(pcm, errno, "cannot open device '%s'", fn);
|
|
return pcm;
|
|
}
|
|
|
|
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_INFO, &info)) {
|
|
oops(pcm, errno, "cannot get info");
|
|
goto fail_close;
|
|
}
|
|
|
|
param_init(¶ms);
|
|
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT,
|
|
pcm_format_to_alsa(config->format));
|
|
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_SUBFORMAT,
|
|
SNDRV_PCM_SUBFORMAT_STD);
|
|
param_set_min(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE, config->period_size);
|
|
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
|
|
pcm_format_to_bits(config->format));
|
|
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_FRAME_BITS,
|
|
pcm_format_to_bits(config->format) * config->channels);
|
|
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS,
|
|
config->channels);
|
|
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS, config->period_count);
|
|
param_set_int(¶ms, SNDRV_PCM_HW_PARAM_RATE, config->rate);
|
|
|
|
if (flags & PCM_NOIRQ) {
|
|
|
|
if (!(flags & PCM_MMAP)) {
|
|
oops(pcm, -EINVAL, "noirq only currently supported with mmap().");
|
|
goto fail;
|
|
}
|
|
|
|
params.flags |= SNDRV_PCM_HW_PARAMS_NO_PERIOD_WAKEUP;
|
|
pcm->noirq_frames_per_msec = config->rate / 1000;
|
|
}
|
|
|
|
if (flags & PCM_MMAP)
|
|
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS,
|
|
SNDRV_PCM_ACCESS_MMAP_INTERLEAVED);
|
|
else
|
|
param_set_mask(¶ms, SNDRV_PCM_HW_PARAM_ACCESS,
|
|
SNDRV_PCM_ACCESS_RW_INTERLEAVED);
|
|
|
|
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_HW_PARAMS, ¶ms)) {
|
|
oops(pcm, errno, "cannot set hw params");
|
|
goto fail_close;
|
|
}
|
|
|
|
/* get our refined hw_params */
|
|
config->period_size = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIOD_SIZE);
|
|
config->period_count = param_get_int(¶ms, SNDRV_PCM_HW_PARAM_PERIODS);
|
|
pcm->buffer_size = config->period_count * config->period_size;
|
|
|
|
if (flags & PCM_MMAP) {
|
|
pcm->mmap_buffer = mmap(NULL, pcm_frames_to_bytes(pcm, pcm->buffer_size),
|
|
PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, pcm->fd, 0);
|
|
if (pcm->mmap_buffer == MAP_FAILED) {
|
|
oops(pcm, -errno, "failed to mmap buffer %d bytes\n",
|
|
pcm_frames_to_bytes(pcm, pcm->buffer_size));
|
|
goto fail_close;
|
|
}
|
|
}
|
|
|
|
|
|
memset(&sparams, 0, sizeof(sparams));
|
|
sparams.tstamp_mode = SNDRV_PCM_TSTAMP_ENABLE;
|
|
sparams.period_step = 1;
|
|
sparams.avail_min = 1;
|
|
|
|
if (!config->start_threshold) {
|
|
if (pcm->flags & PCM_IN)
|
|
pcm->config.start_threshold = sparams.start_threshold = 1;
|
|
else
|
|
pcm->config.start_threshold = sparams.start_threshold =
|
|
config->period_count * config->period_size / 2;
|
|
} else
|
|
sparams.start_threshold = config->start_threshold;
|
|
|
|
/* pick a high stop threshold - todo: does this need further tuning */
|
|
if (!config->stop_threshold) {
|
|
if (pcm->flags & PCM_IN)
|
|
pcm->config.stop_threshold = sparams.stop_threshold =
|
|
config->period_count * config->period_size * 10;
|
|
else
|
|
pcm->config.stop_threshold = sparams.stop_threshold =
|
|
config->period_count * config->period_size;
|
|
}
|
|
else
|
|
sparams.stop_threshold = config->stop_threshold;
|
|
|
|
sparams.xfer_align = config->period_size / 2; /* needed for old kernels */
|
|
sparams.silence_size = 0;
|
|
sparams.silence_threshold = config->silence_threshold;
|
|
pcm->boundary = sparams.boundary = pcm->buffer_size;
|
|
|
|
while (pcm->boundary * 2 <= INT_MAX - pcm->buffer_size)
|
|
pcm->boundary *= 2;
|
|
|
|
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_SW_PARAMS, &sparams)) {
|
|
oops(pcm, errno, "cannot set sw params");
|
|
goto fail;
|
|
}
|
|
|
|
rc = pcm_hw_mmap_status(pcm);
|
|
if (rc < 0) {
|
|
oops(pcm, rc, "mmap status failed");
|
|
goto fail;
|
|
}
|
|
|
|
pcm->underruns = 0;
|
|
return pcm;
|
|
|
|
fail:
|
|
if (flags & PCM_MMAP)
|
|
munmap(pcm->mmap_buffer, pcm_frames_to_bytes(pcm, pcm->buffer_size));
|
|
fail_close:
|
|
close(pcm->fd);
|
|
pcm->fd = -1;
|
|
return pcm;
|
|
}
|
|
|
|
int pcm_is_ready(struct pcm *pcm)
|
|
{
|
|
return pcm->fd >= 0;
|
|
}
|
|
|
|
int pcm_start(struct pcm *pcm)
|
|
{
|
|
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_PREPARE) < 0)
|
|
return oops(pcm, errno, "cannot prepare channel");
|
|
|
|
if (pcm->flags & PCM_MMAP)
|
|
pcm_sync_ptr(pcm, 0);
|
|
|
|
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_START) < 0)
|
|
return oops(pcm, errno, "cannot start channel");
|
|
|
|
pcm->running = 1;
|
|
return 0;
|
|
}
|
|
|
|
int pcm_stop(struct pcm *pcm)
|
|
{
|
|
if (ioctl(pcm->fd, SNDRV_PCM_IOCTL_DROP) < 0)
|
|
return oops(pcm, errno, "cannot stop channel");
|
|
|
|
pcm->running = 0;
|
|
return 0;
|
|
}
|
|
|
|
static inline int pcm_mmap_playback_avail(struct pcm *pcm)
|
|
{
|
|
int avail;
|
|
|
|
avail = pcm->mmap_status->hw_ptr + pcm->buffer_size - pcm->mmap_control->appl_ptr;
|
|
|
|
if (avail < 0)
|
|
avail += pcm->boundary;
|
|
else if (avail > (int)pcm->boundary)
|
|
avail -= pcm->boundary;
|
|
|
|
return avail;
|
|
}
|
|
|
|
static inline int pcm_mmap_capture_avail(struct pcm *pcm)
|
|
{
|
|
int avail = pcm->mmap_status->hw_ptr - pcm->mmap_control->appl_ptr;
|
|
if (avail < 0)
|
|
avail += pcm->boundary;
|
|
return avail;
|
|
}
|
|
|
|
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
|
|
return pcm_mmap_playback_avail(pcm);
|
|
}
|
|
|
|
static void pcm_mmap_appl_forward(struct pcm *pcm, int frames)
|
|
{
|
|
unsigned int appl_ptr = pcm->mmap_control->appl_ptr;
|
|
appl_ptr += frames;
|
|
|
|
/* check for boundary wrap */
|
|
if (appl_ptr > pcm->boundary)
|
|
appl_ptr -= pcm->boundary;
|
|
pcm->mmap_control->appl_ptr = appl_ptr;
|
|
}
|
|
|
|
int pcm_mmap_begin(struct pcm *pcm, void **areas, unsigned int *offset,
|
|
unsigned int *frames)
|
|
{
|
|
unsigned int continuous, copy_frames, avail;
|
|
|
|
/* return the mmap buffer */
|
|
*areas = pcm->mmap_buffer;
|
|
|
|
/* and the application offset in frames */
|
|
*offset = pcm->mmap_control->appl_ptr % pcm->buffer_size;
|
|
|
|
avail = pcm_mmap_avail(pcm);
|
|
if (avail > pcm->buffer_size)
|
|
avail = pcm->buffer_size;
|
|
continuous = pcm->buffer_size - *offset;
|
|
|
|
/* we can only copy frames if the are availabale and continuos */
|
|
copy_frames = *frames;
|
|
if (copy_frames > avail)
|
|
copy_frames = avail;
|
|
if (copy_frames > continuous)
|
|
copy_frames = continuous;
|
|
*frames = copy_frames;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int pcm_mmap_commit(struct pcm *pcm, unsigned int offset, unsigned int frames)
|
|
{
|
|
(void)offset;
|
|
/* update the application pointer in userspace and kernel */
|
|
pcm_mmap_appl_forward(pcm, frames);
|
|
pcm_sync_ptr(pcm, 0);
|
|
|
|
return frames;
|
|
}
|
|
|
|
int pcm_avail_update(struct pcm *pcm)
|
|
{
|
|
pcm_sync_ptr(pcm, 0);
|
|
return pcm_mmap_avail(pcm);
|
|
}
|
|
|
|
int pcm_state(struct pcm *pcm)
|
|
{
|
|
int err = pcm_sync_ptr(pcm, 0);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
return pcm->mmap_status->state;
|
|
}
|
|
|
|
int pcm_wait(struct pcm *pcm, int timeout)
|
|
{
|
|
struct pollfd pfd;
|
|
int err;
|
|
|
|
pfd.fd = pcm->fd;
|
|
pfd.events = POLLOUT | POLLERR | POLLNVAL;
|
|
|
|
do {
|
|
/* let's wait for avail or timeout */
|
|
err = poll(&pfd, 1, timeout);
|
|
if (err < 0)
|
|
return -errno;
|
|
|
|
/* timeout ? */
|
|
if (err == 0)
|
|
return 0;
|
|
|
|
/* have we been interrupted ? */
|
|
if (errno == -EINTR)
|
|
continue;
|
|
|
|
/* check for any errors */
|
|
if (pfd.revents & (POLLERR | POLLNVAL)) {
|
|
switch (pcm_state(pcm)) {
|
|
case PCM_STATE_XRUN:
|
|
return -EPIPE;
|
|
case PCM_STATE_SUSPENDED:
|
|
return -ESTRPIPE;
|
|
case PCM_STATE_DISCONNECTED:
|
|
return -ENODEV;
|
|
default:
|
|
return -EIO;
|
|
}
|
|
}
|
|
/* poll again if fd not ready for IO */
|
|
} while (!(pfd.revents & (POLLIN | POLLOUT)));
|
|
|
|
return 1;
|
|
}
|
|
|
|
int pcm_mmap_write(struct pcm *pcm, const void *buffer, unsigned int bytes)
|
|
{
|
|
int err = 0, frames, avail;
|
|
unsigned int offset = 0, count;
|
|
|
|
if (bytes == 0)
|
|
return 0;
|
|
|
|
count = pcm_bytes_to_frames(pcm, bytes);
|
|
|
|
while (count > 0) {
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
|
|
/* sleep until we have space to write new frames */
|
|
if (pcm->running &&
|
|
(unsigned int)avail < pcm->mmap_control->avail_min) {
|
|
int time = -1;
|
|
|
|
if (pcm->flags & PCM_NOIRQ)
|
|
time = (pcm->buffer_size - avail - pcm->mmap_control->avail_min)
|
|
/ pcm->noirq_frames_per_msec;
|
|
|
|
err = pcm_wait(pcm, time);
|
|
if (err < 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;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
frames = count;
|
|
if (frames > avail)
|
|
frames = avail;
|
|
|
|
if (!frames)
|
|
break;
|
|
|
|
/* copy frames from buffer */
|
|
frames = pcm_mmap_write_areas(pcm, 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;
|
|
}
|
|
|
|
offset += frames;
|
|
count -= frames;
|
|
}
|
|
|
|
return 0;
|
|
}
|