blob: 32fc4e2d9814cf3d83680570b44ca15c75322ba5 [file] [log] [blame]
/*
* GStreamer - SunAudio mixer interface element
* Copyright (C) 2005,2006,2008,2009 Sun Microsystems, Inc.,
* Brian Cameron <brian.cameron@sun.com>
* Copyright (C) 2008 Sun Microsystems, Inc.,
* Jan Schmidt <jan.schmidt@sun.com>
* Copyright (C) 2009 Sun Microsystems, Inc.,
* Garrett D'Amore <garrett.damore@sun.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/audio.h>
#include <sys/mixer.h>
#include <gst/gst-i18n-plugin.h>
#include "gstsunaudiomixerctrl.h"
#include "gstsunaudiomixertrack.h"
#include "gstsunaudiomixeroptions.h"
GST_DEBUG_CATEGORY_EXTERN (sunaudio_debug);
#define GST_CAT_DEFAULT sunaudio_debug
static gboolean
gst_sunaudiomixer_ctrl_open (GstSunAudioMixerCtrl * mixer)
{
int fd;
/* First try to open non-blocking */
fd = open (mixer->device, O_RDWR | O_NONBLOCK);
if (fd >= 0) {
close (fd);
fd = open (mixer->device, O_WRONLY);
}
if (fd == -1) {
GST_DEBUG_OBJECT (mixer,
"Failed to open mixer device %s, mixing disabled: %s", mixer->device,
strerror (errno));
return FALSE;
}
mixer->mixer_fd = fd;
/* Try to set the multiple open flag if we can, but ignore errors */
ioctl (mixer->mixer_fd, AUDIO_MIXER_MULTIPLE_OPEN);
GST_DEBUG_OBJECT (mixer, "Opened mixer device %s", mixer->device);
return TRUE;
}
void
gst_sunaudiomixer_ctrl_build_list (GstSunAudioMixerCtrl * mixer)
{
GstMixerTrack *track;
GstMixerOptions *options;
struct audio_info audioinfo;
/*
* Do not continue appending the same 3 static tracks onto the list
*/
if (mixer->tracklist == NULL) {
g_return_if_fail (mixer->mixer_fd != -1);
/* query available ports */
if (ioctl (mixer->mixer_fd, AUDIO_GETINFO, &audioinfo) < 0) {
g_warning ("Error getting audio device volume");
return;
}
/* Output & should be MASTER when it's the only one. */
track = gst_sunaudiomixer_track_new (GST_SUNAUDIO_TRACK_OUTPUT);
mixer->tracklist = g_list_append (mixer->tracklist, track);
/* Input */
track = gst_sunaudiomixer_track_new (GST_SUNAUDIO_TRACK_RECORD);
mixer->tracklist = g_list_append (mixer->tracklist, track);
/* Monitor */
track = gst_sunaudiomixer_track_new (GST_SUNAUDIO_TRACK_MONITOR);
mixer->tracklist = g_list_append (mixer->tracklist, track);
if (audioinfo.play.avail_ports & AUDIO_SPEAKER) {
track = gst_sunaudiomixer_track_new (GST_SUNAUDIO_TRACK_SPEAKER);
mixer->tracklist = g_list_append (mixer->tracklist, track);
}
if (audioinfo.play.avail_ports & AUDIO_HEADPHONE) {
track = gst_sunaudiomixer_track_new (GST_SUNAUDIO_TRACK_HP);
mixer->tracklist = g_list_append (mixer->tracklist, track);
}
if (audioinfo.play.avail_ports & AUDIO_LINE_OUT) {
track = gst_sunaudiomixer_track_new (GST_SUNAUDIO_TRACK_LINEOUT);
mixer->tracklist = g_list_append (mixer->tracklist, track);
}
if (audioinfo.play.avail_ports & AUDIO_SPDIF_OUT) {
track = gst_sunaudiomixer_track_new (GST_SUNAUDIO_TRACK_SPDIFOUT);
mixer->tracklist = g_list_append (mixer->tracklist, track);
}
if (audioinfo.play.avail_ports & AUDIO_AUX1_OUT) {
track = gst_sunaudiomixer_track_new (GST_SUNAUDIO_TRACK_AUX1OUT);
mixer->tracklist = g_list_append (mixer->tracklist, track);
}
if (audioinfo.play.avail_ports & AUDIO_AUX2_OUT) {
track = gst_sunaudiomixer_track_new (GST_SUNAUDIO_TRACK_AUX2OUT);
mixer->tracklist = g_list_append (mixer->tracklist, track);
}
if (audioinfo.record.avail_ports != AUDIO_NONE) {
options =
gst_sunaudiomixer_options_new (mixer, GST_SUNAUDIO_TRACK_RECSRC);
mixer->tracklist = g_list_append (mixer->tracklist, options);
}
}
}
GstSunAudioMixerCtrl *
gst_sunaudiomixer_ctrl_new (const char *device)
{
GstSunAudioMixerCtrl *ret = NULL;
g_return_val_if_fail (device != NULL, NULL);
ret = g_new0 (GstSunAudioMixerCtrl, 1);
ret->device = g_strdup (device);
ret->mixer_fd = -1;
ret->tracklist = NULL;
if (!gst_sunaudiomixer_ctrl_open (ret))
goto error;
return ret;
error:
if (ret)
gst_sunaudiomixer_ctrl_free (ret);
return NULL;
}
void
gst_sunaudiomixer_ctrl_free (GstSunAudioMixerCtrl * mixer)
{
g_return_if_fail (mixer != NULL);
if (mixer->device) {
g_free (mixer->device);
mixer->device = NULL;
}
if (mixer->tracklist) {
g_list_foreach (mixer->tracklist, (GFunc) g_object_unref, NULL);
g_list_free (mixer->tracklist);
mixer->tracklist = NULL;
}
if (mixer->mixer_fd != -1) {
close (mixer->mixer_fd);
mixer->mixer_fd = -1;
}
g_free (mixer);
}
GstMixerFlags
gst_sunaudiomixer_ctrl_get_mixer_flags (GstSunAudioMixerCtrl * mixer)
{
return GST_MIXER_FLAG_HAS_WHITELIST | GST_MIXER_FLAG_GROUPING;
}
const GList *
gst_sunaudiomixer_ctrl_list_tracks (GstSunAudioMixerCtrl * mixer)
{
gst_sunaudiomixer_ctrl_build_list (mixer);
return (const GList *) mixer->tracklist;
}
void
gst_sunaudiomixer_ctrl_get_volume (GstSunAudioMixerCtrl * mixer,
GstMixerTrack * track, gint * volumes)
{
gint gain, balance;
float ratio;
struct audio_info audioinfo;
GstSunAudioMixerTrack *sunaudiotrack;
g_return_if_fail (GST_IS_SUNAUDIO_MIXER_TRACK (track));
sunaudiotrack = GST_SUNAUDIO_MIXER_TRACK (track);
g_return_if_fail (mixer->mixer_fd != -1);
if (ioctl (mixer->mixer_fd, AUDIO_GETINFO, &audioinfo) < 0) {
g_warning ("Error getting audio device volume");
return;
}
balance = AUDIO_MID_BALANCE;
gain = 0;
switch (sunaudiotrack->track_num) {
case GST_SUNAUDIO_TRACK_OUTPUT:
gain = (int) audioinfo.play.gain;
balance = audioinfo.play.balance;
break;
case GST_SUNAUDIO_TRACK_RECORD:
gain = (int) audioinfo.record.gain;
balance = audioinfo.record.balance;
break;
case GST_SUNAUDIO_TRACK_MONITOR:
gain = (int) audioinfo.monitor_gain;
balance = audioinfo.record.balance;
break;
case GST_SUNAUDIO_TRACK_SPEAKER:
if (audioinfo.play.port & AUDIO_SPEAKER)
gain = AUDIO_MAX_GAIN;
break;
case GST_SUNAUDIO_TRACK_HP:
if (audioinfo.play.port & AUDIO_HEADPHONE)
gain = AUDIO_MAX_GAIN;
break;
case GST_SUNAUDIO_TRACK_LINEOUT:
if (audioinfo.play.port & AUDIO_LINE_OUT)
gain = AUDIO_MAX_GAIN;
break;
case GST_SUNAUDIO_TRACK_SPDIFOUT:
if (audioinfo.play.port & AUDIO_SPDIF_OUT)
gain = AUDIO_MAX_GAIN;
break;
case GST_SUNAUDIO_TRACK_AUX1OUT:
if (audioinfo.play.port & AUDIO_AUX1_OUT)
gain = AUDIO_MAX_GAIN;
break;
case GST_SUNAUDIO_TRACK_AUX2OUT:
if (audioinfo.play.port & AUDIO_AUX2_OUT)
gain = AUDIO_MAX_GAIN;
break;
default:
break;
}
switch (track->num_channels) {
case 2:
if (balance == AUDIO_MID_BALANCE) {
volumes[0] = gain;
volumes[1] = gain;
} else if (balance < AUDIO_MID_BALANCE) {
volumes[0] = gain;
ratio = 1 - (float) (AUDIO_MID_BALANCE - balance) /
(float) AUDIO_MID_BALANCE;
volumes[1] = (int) ((float) gain * ratio + 0.5);
} else {
volumes[1] = gain;
ratio = 1 - (float) (balance - AUDIO_MID_BALANCE) /
(float) AUDIO_MID_BALANCE;
volumes[0] = (int) ((float) gain * ratio + 0.5);
}
break;
case 1:
volumes[0] = gain;
break;
}
/* Likewise reset MUTE */
if ((sunaudiotrack->track_num == GST_SUNAUDIO_TRACK_OUTPUT
&& audioinfo.output_muted == 1)
|| (sunaudiotrack->track_num != GST_SUNAUDIO_TRACK_OUTPUT && gain == 0)) {
/*
* If MUTE is set, then gain is always 0, so don't bother
* resetting our internal value.
*/
track->flags |= GST_MIXER_TRACK_MUTE;
} else {
sunaudiotrack->gain = gain;
sunaudiotrack->balance = balance;
track->flags &= ~GST_MIXER_TRACK_MUTE;
}
}
void
gst_sunaudiomixer_ctrl_set_volume (GstSunAudioMixerCtrl * mixer,
GstMixerTrack * track, gint * volumes)
{
gint gain;
gint balance;
gint l_real_gain;
gint r_real_gain;
float ratio;
struct audio_info audioinfo;
GstSunAudioMixerTrack *sunaudiotrack = GST_SUNAUDIO_MIXER_TRACK (track);
l_real_gain = volumes[0];
r_real_gain = volumes[1];
if (l_real_gain == r_real_gain) {
gain = l_real_gain;
balance = AUDIO_MID_BALANCE;
} else if (l_real_gain < r_real_gain) {
gain = r_real_gain;
ratio = (float) l_real_gain / (float) r_real_gain;
balance =
AUDIO_RIGHT_BALANCE - (int) (ratio * (float) AUDIO_MID_BALANCE + 0.5);
} else {
gain = l_real_gain;
ratio = (float) r_real_gain / (float) l_real_gain;
balance =
AUDIO_LEFT_BALANCE + (int) (ratio * (float) AUDIO_MID_BALANCE + 0.5);
}
sunaudiotrack->gain = gain;
sunaudiotrack->balance = balance;
if (GST_MIXER_TRACK_HAS_FLAG (track, GST_MIXER_TRACK_MUTE)) {
if (sunaudiotrack->track_num == GST_SUNAUDIO_TRACK_OUTPUT) {
return;
} else if (gain == 0) {
return;
} else {
/*
* If the volume is set to a non-zero value for LINE_IN
* or MONITOR, then unset MUTE.
*/
track->flags &= ~GST_MIXER_TRACK_MUTE;
}
}
/* Set the volume */
AUDIO_INITINFO (&audioinfo);
switch (sunaudiotrack->track_num) {
case GST_SUNAUDIO_TRACK_OUTPUT:
audioinfo.play.gain = gain;
audioinfo.play.balance = balance;
break;
case GST_SUNAUDIO_TRACK_RECORD:
audioinfo.record.gain = gain;
audioinfo.record.balance = balance;
break;
case GST_SUNAUDIO_TRACK_MONITOR:
audioinfo.monitor_gain = gain;
audioinfo.record.balance = balance;
break;
default:
break;
}
g_return_if_fail (mixer->mixer_fd != -1);
if (ioctl (mixer->mixer_fd, AUDIO_SETINFO, &audioinfo) < 0) {
g_warning ("Error setting audio device volume");
return;
}
}
void
gst_sunaudiomixer_ctrl_set_mute (GstSunAudioMixerCtrl * mixer,
GstMixerTrack * track, gboolean mute)
{
struct audio_info audioinfo;
struct audio_info oldinfo;
GstSunAudioMixerTrack *sunaudiotrack = GST_SUNAUDIO_MIXER_TRACK (track);
gint volume, balance;
AUDIO_INITINFO (&audioinfo);
if (ioctl (mixer->mixer_fd, AUDIO_GETINFO, &oldinfo) < 0) {
g_warning ("Error getting audio device volume");
return;
}
if (mute) {
volume = 0;
track->flags |= GST_MIXER_TRACK_MUTE;
} else {
volume = sunaudiotrack->gain;
track->flags &= ~GST_MIXER_TRACK_MUTE;
}
balance = sunaudiotrack->balance;
switch (sunaudiotrack->track_num) {
case GST_SUNAUDIO_TRACK_OUTPUT:
if (mute)
audioinfo.output_muted = 1;
else
audioinfo.output_muted = 0;
audioinfo.play.gain = volume;
audioinfo.play.balance = balance;
break;
case GST_SUNAUDIO_TRACK_RECORD:
audioinfo.record.gain = volume;
audioinfo.record.balance = balance;
break;
case GST_SUNAUDIO_TRACK_MONITOR:
audioinfo.monitor_gain = volume;
audioinfo.record.balance = balance;
break;
case GST_SUNAUDIO_TRACK_SPEAKER:
if (mute) {
audioinfo.play.port = oldinfo.play.port & ~AUDIO_SPEAKER;
} else {
audioinfo.play.port = oldinfo.play.port | AUDIO_SPEAKER;
}
break;
case GST_SUNAUDIO_TRACK_HP:
if (mute) {
audioinfo.play.port = oldinfo.play.port & ~AUDIO_HEADPHONE;
} else {
audioinfo.play.port = oldinfo.play.port | AUDIO_HEADPHONE;
}
break;
case GST_SUNAUDIO_TRACK_LINEOUT:
if (mute) {
audioinfo.play.port = oldinfo.play.port & ~AUDIO_LINE_OUT;
} else {
audioinfo.play.port = oldinfo.play.port | AUDIO_LINE_OUT;
}
break;
case GST_SUNAUDIO_TRACK_SPDIFOUT:
if (mute) {
audioinfo.play.port = oldinfo.play.port & ~AUDIO_SPDIF_OUT;
} else {
audioinfo.play.port = oldinfo.play.port | AUDIO_SPDIF_OUT;
}
break;
case GST_SUNAUDIO_TRACK_AUX1OUT:
if (mute) {
audioinfo.play.port = oldinfo.play.port & ~AUDIO_AUX1_OUT;
} else {
audioinfo.play.port = oldinfo.play.port | AUDIO_AUX1_OUT;
}
break;
case GST_SUNAUDIO_TRACK_AUX2OUT:
if (mute) {
audioinfo.play.port = oldinfo.play.port & ~AUDIO_AUX2_OUT;
} else {
audioinfo.play.port = oldinfo.play.port | AUDIO_AUX2_OUT;
}
break;
default:
break;
}
if (audioinfo.play.port != ((unsigned) ~0)) {
/* mask off ports we can't modify. Hack for broken drivers where mod_ports == 0 */
if (oldinfo.play.mod_ports != 0) {
audioinfo.play.port &= oldinfo.play.mod_ports;
/* and add in any that are forced to be on */
audioinfo.play.port |= (oldinfo.play.port & ~oldinfo.play.mod_ports);
}
}
g_return_if_fail (mixer->mixer_fd != -1);
if (audioinfo.play.port != (guint) (-1) &&
audioinfo.play.port != oldinfo.play.port)
GST_LOG_OBJECT (mixer, "Changing play port mask to 0x%08x",
audioinfo.play.port);
if (ioctl (mixer->mixer_fd, AUDIO_SETINFO, &audioinfo) < 0) {
g_warning ("Error setting audio settings");
return;
}
}
void
gst_sunaudiomixer_ctrl_set_record (GstSunAudioMixerCtrl * mixer,
GstMixerTrack * track, gboolean record)
{
}
void
gst_sunaudiomixer_ctrl_set_option (GstSunAudioMixerCtrl * mixer,
GstMixerOptions * options, gchar * value)
{
struct audio_info audioinfo;
GstMixerTrack *track;
GstSunAudioMixerOptions *opts;
GQuark q;
int i;
g_return_if_fail (mixer != NULL);
g_return_if_fail (mixer->mixer_fd != -1);
g_return_if_fail (value != NULL);
g_return_if_fail (GST_IS_SUNAUDIO_MIXER_OPTIONS (options));
track = GST_MIXER_TRACK (options);
opts = GST_SUNAUDIO_MIXER_OPTIONS (options);
if (opts->track_num != GST_SUNAUDIO_TRACK_RECSRC) {
g_warning ("set_option not supported on track %s", track->label);
return;
}
q = g_quark_try_string (value);
if (q == 0) {
g_warning ("unknown option '%s'", value);
return;
}
for (i = 0; i < 8; i++) {
if (opts->names[i] == q) {
break;
}
}
if (((1 << (i)) & opts->avail) == 0) {
g_warning ("Record port %s not available", g_quark_to_string (q));
return;
}
AUDIO_INITINFO (&audioinfo);
audioinfo.record.port = (1 << (i));
if (ioctl (mixer->mixer_fd, AUDIO_SETINFO, &audioinfo) < 0) {
g_warning ("Error setting audio record port");
}
}
const gchar *
gst_sunaudiomixer_ctrl_get_option (GstSunAudioMixerCtrl * mixer,
GstMixerOptions * options)
{
GstMixerTrack *track;
GstSunAudioMixerOptions *opts;
struct audio_info audioinfo;
int i;
g_return_val_if_fail (mixer != NULL, NULL);
g_return_val_if_fail (mixer->fd != -1, NULL);
g_return_val_if_fail (GST_IS_SUNAUDIO_MIXER_OPTIONS (options), NULL);
track = GST_MIXER_TRACK (options);
opts = GST_SUNAUDIO_MIXER_OPTIONS (options);
g_return_val_if_fail (opts->track_num == GST_SUNAUDIO_TRACK_RECSRC, NULL);
if (ioctl (mixer->mixer_fd, AUDIO_GETINFO, &audioinfo) < 0) {
g_warning ("Error getting audio device settings");
return (NULL);
}
for (i = 0; i < 8; i++) {
if ((1 << i) == audioinfo.record.port) {
const gchar *s = g_quark_to_string (opts->names[i]);
GST_DEBUG_OBJECT (mixer, "Getting value for option %d: %s",
opts->track_num, s);
return (s);
}
}
GST_DEBUG_OBJECT (mixer, "Unable to get value for option %d",
opts->track_num);
g_warning ("Record port value %d seems illegal", audioinfo.record.port);
return (NULL);
}