From 87706eec7df87230511df403355fdb94f022f739 Mon Sep 17 00:00:00 2001 From: nia Date: Wed, 15 Apr 2020 16:39:06 +0000 Subject: [PATCH] ossaudio: If the user's channel count is rejected, use the hardware count --- lib/libossaudio/ossaudio.c | 63 +++++++++++++++++++++++++++++----- sys/compat/ossaudio/ossaudio.c | 61 ++++++++++++++++++++++++++++---- 2 files changed, 109 insertions(+), 15 deletions(-) diff --git a/lib/libossaudio/ossaudio.c b/lib/libossaudio/ossaudio.c index d14e9af038b2..9bc4497eb7ea 100644 --- a/lib/libossaudio/ossaudio.c +++ b/lib/libossaudio/ossaudio.c @@ -1,4 +1,4 @@ -/* $NetBSD: ossaudio.c,v 1.40 2020/04/15 15:25:33 nia Exp $ */ +/* $NetBSD: ossaudio.c,v 1.41 2020/04/15 16:39:06 nia Exp $ */ /*- * Copyright (c) 1997 The NetBSD Foundation, Inc. @@ -27,7 +27,7 @@ */ #include -__RCSID("$NetBSD: ossaudio.c,v 1.40 2020/04/15 15:25:33 nia Exp $"); +__RCSID("$NetBSD: ossaudio.c,v 1.41 2020/04/15 16:39:06 nia Exp $"); /* * This is an OSS (Linux) sound API emulator. @@ -63,6 +63,7 @@ __RCSID("$NetBSD: ossaudio.c,v 1.40 2020/04/15 15:25:33 nia Exp $"); static struct audiodevinfo *getdevinfo(int); +static void setchannels(int, int, int); static void setblocksize(int, struct audio_info *); static int audio_ioctl(int, unsigned long, void *); @@ -350,12 +351,10 @@ audio_ioctl(int fd, unsigned long com, void *argp) INTARG = idat; break; case SNDCTL_DSP_CHANNELS: - AUDIO_INITINFO(&tmpinfo); - tmpinfo.play.channels = INTARG; - (void) ioctl(fd, AUDIO_SETINFO, &tmpinfo); - AUDIO_INITINFO(&tmpinfo); - tmpinfo.record.channels = INTARG; - (void) ioctl(fd, AUDIO_SETINFO, &tmpinfo); + retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo); + if (retval < 0) + return retval; + setchannels(fd, tmpinfo.mode, INTARG); /* FALLTHRU */ case SOUND_PCM_READ_CHANNELS: retval = ioctl(fd, AUDIO_GETBUFINFO, &tmpinfo); @@ -1048,6 +1047,54 @@ mixer_ioctl(int fd, unsigned long com, void *argp) return 0; } +/* + * When AUDIO_SETINFO fails to set a channel count, the application's chosen + * number is out of range of what the kernel allows. + * + * When this happens, we use the current hardware settings. This is just in + * case an application is abusing SNDCTL_DSP_CHANNELS - OSSv4 always sets and + * returns a reasonable value, even if it wasn't what the user requested. + * + * XXX: If a device is opened for both playback and recording, and supports + * fewer channels for recording than playback, applications that do both will + * behave very strangely. OSS doesn't allow for reporting separate channel + * counts for recording and playback. This could be worked around by always + * mixing recorded data up to the same number of channels as is being used + * for playback. + */ +static void +setchannels(int fd, int mode, int nchannels) +{ + struct audio_info tmpinfo, hwfmt; + + if (ioctl(fd, AUDIO_GETFORMAT, &hwfmt) < 0) { + errno = 0; + hwfmt.record.channels = hwfmt.play.channels = 2; + } + + if (mode & AUMODE_PLAY) { + AUDIO_INITINFO(&tmpinfo); + tmpinfo.play.channels = nchannels; + if (ioctl(fd, AUDIO_SETINFO, &tmpinfo) < 0) { + errno = 0; + AUDIO_INITINFO(&tmpinfo); + tmpinfo.play.channels = hwfmt.play.channels; + (void)ioctl(fd, AUDIO_SETINFO, &tmpinfo); + } + } + + if (mode & AUMODE_RECORD) { + AUDIO_INITINFO(&tmpinfo); + tmpinfo.record.channels = nchannels; + if (ioctl(fd, AUDIO_SETINFO, &tmpinfo) < 0) { + errno = 0; + AUDIO_INITINFO(&tmpinfo); + tmpinfo.record.channels = hwfmt.record.channels; + (void)ioctl(fd, AUDIO_SETINFO, &tmpinfo); + } + } +} + /* * Check that the blocksize is a power of 2 as OSS wants. * If not, set it to be. diff --git a/sys/compat/ossaudio/ossaudio.c b/sys/compat/ossaudio/ossaudio.c index effe949a46d0..2e4610f91a1c 100644 --- a/sys/compat/ossaudio/ossaudio.c +++ b/sys/compat/ossaudio/ossaudio.c @@ -1,4 +1,4 @@ -/* $NetBSD: ossaudio.c,v 1.80 2020/04/15 15:25:33 nia Exp $ */ +/* $NetBSD: ossaudio.c,v 1.81 2020/04/15 16:39:06 nia Exp $ */ /*- * Copyright (c) 1997, 2008 The NetBSD Foundation, Inc. @@ -27,7 +27,7 @@ */ #include -__KERNEL_RCSID(0, "$NetBSD: ossaudio.c,v 1.80 2020/04/15 15:25:33 nia Exp $"); +__KERNEL_RCSID(0, "$NetBSD: ossaudio.c,v 1.81 2020/04/15 16:39:06 nia Exp $"); #include #include @@ -68,6 +68,7 @@ static int opaque_to_enum(struct audiodevinfo *di, audio_mixer_name_t *label, in static int enum_to_ord(struct audiodevinfo *di, int enm); static int enum_to_mask(struct audiodevinfo *di, int enm); +static void setchannels(file_t *, int, int); static void setblocksize(file_t *, struct audio_info *); #ifdef AUDIO_DEBUG @@ -487,15 +488,13 @@ oss_ioctl_audio(struct lwp *l, const struct oss_sys_ioctl_args *uap, register_t __func__, error)); goto out; } - tmpinfo.play.channels = - tmpinfo.record.channels = idat; - DPRINTF(("%s: SNDCTL_DSP_CHANNELS > %d\n", __func__, idat)); - error = ioctlf(fp, AUDIO_SETINFO, &tmpinfo); + error = ioctlf(fp, AUDIO_GETBUFINFO, &tmpinfo); if (error) { - DPRINTF(("%s: AUDIO_SETINFO %d\n", + DPRINTF(("%s: AUDIO_GETBUFINFO %d\n", __func__, error)); goto out; } + setchannels(fp, tmpinfo.mode, idat); /* FALLTHROUGH */ case OSS_SOUND_PCM_READ_CHANNELS: error = ioctlf(fp, AUDIO_GETBUFINFO, &tmpinfo); @@ -1531,6 +1530,54 @@ oss_ioctl_sequencer(struct lwp *l, const struct oss_sys_ioctl_args *uap, registe return error; } +/* + * When AUDIO_SETINFO fails to set a channel count, the application's chosen + * number is out of range of what the kernel allows. + * + * When this happens, we use the current hardware settings. This is just in + * case an application is abusing SNDCTL_DSP_CHANNELS - OSSv4 always sets and + * returns a reasonable value, even if it wasn't what the user requested. + * + * XXX: If a device is opened for both playback and recording, and supports + * fewer channels for recording than playback, applications that do both will + * behave very strangely. OSS doesn't allow for reporting separate channel + * counts for recording and playback. This could be worked around by always + * mixing recorded data up to the same number of channels as is being used + * for playback. + */ +static void +setchannels(file_t *fp, int mode, int nchannels) +{ + struct audio_info tmpinfo, hwfmt; + int (*ioctlf)(file_t *, u_long, void *); + + ioctlf = fp->f_ops->fo_ioctl; + + if (ioctlf(fp, AUDIO_GETFORMAT, &hwfmt) < 0) { + hwfmt.record.channels = hwfmt.play.channels = 2; + } + + if (mode & AUMODE_PLAY) { + AUDIO_INITINFO(&tmpinfo); + tmpinfo.play.channels = nchannels; + if (ioctlf(fp, AUDIO_SETINFO, &tmpinfo) < 0) { + AUDIO_INITINFO(&tmpinfo); + tmpinfo.play.channels = hwfmt.play.channels; + (void)ioctlf(fp, AUDIO_SETINFO, &tmpinfo); + } + } + + if (mode & AUMODE_RECORD) { + AUDIO_INITINFO(&tmpinfo); + tmpinfo.record.channels = nchannels; + if (ioctlf(fp, AUDIO_SETINFO, &tmpinfo) < 0) { + AUDIO_INITINFO(&tmpinfo); + tmpinfo.record.channels = hwfmt.record.channels; + (void)ioctlf(fp, AUDIO_SETINFO, &tmpinfo); + } + } +} + /* * Check that the blocksize is a power of 2 as OSS wants. * If not, set it to be.