]> code.delx.au - pulseaudio/blobdiff - src/modules/module-solaris.c
Channel map argument support for solaris.
[pulseaudio] / src / modules / module-solaris.c
index 76f50935d26836296907cc9b56e903f8a487a77e..eaac9e6e4591f457a5be83cca512f338a4866bab 100644 (file)
 PA_MODULE_AUTHOR("Pierre Ossman")
 PA_MODULE_DESCRIPTION("Solaris Sink/Source")
 PA_MODULE_VERSION(PACKAGE_VERSION)
-PA_MODULE_USAGE("sink_name=<name for the sink> source_name=<name for the source> device=<OSS device> record=<enable source?> playback=<enable sink?> format=<sample format> channels=<number of channels> rate=<sample rate> buffer_size=<record buffer size>")
+PA_MODULE_USAGE(
+    "sink_name=<name for the sink> "
+    "source_name=<name for the source> "
+    "device=<OSS device> record=<enable source?> "
+    "playback=<enable sink?> "
+    "format=<sample format> "
+    "channels=<number of channels> "
+    "rate=<sample rate> "
+    "buffer_size=<record buffer size> "
+    "channel_map=<channel map>")
 
 struct userdata {
     pa_sink *sink;
     pa_source *source;
     pa_iochannel *io;
     pa_core *core;
+    pa_time_event *timer;
+    pa_usec_t poll_timeout;
     pa_signal_event *sig;
 
     pa_memchunk memchunk, silence;
 
-    uint32_t sample_size;
+    uint32_t frame_size;
     uint32_t buffer_size;
     unsigned int written_bytes, read_bytes;
 
@@ -86,6 +97,7 @@ static const char* const valid_modargs[] = {
     "format",
     "rate",
     "channels",
+    "channel_map",
     NULL
 };
 
@@ -111,7 +123,8 @@ static void do_write(struct userdata *u) {
     
     assert(u);
 
-    if (!u->sink || !pa_iochannel_is_writable(u->io))
+    /* We cannot check pa_iochannel_is_writable() because of our buffer hack */
+    if (!u->sink)
         return;
 
     update_usage(u);
@@ -124,16 +137,18 @@ static void do_write(struct userdata *u) {
      * by not filling it more than u->buffer_size.
      */
     len = u->buffer_size;
-    len -= u->written_bytes - (info.play.samples * u->sample_size);
+    len -= u->written_bytes - (info.play.samples * u->frame_size);
 
-    /*
-     * Do not fill more than half the buffer in one chunk since we only
-     * get notifications upon completion of entire chunks.
-     */
-    if (len > (u->buffer_size / 2))
-        len = u->buffer_size / 2;
+    /* The sample counter can sometimes go backwards :( */
+    if (len > u->buffer_size)
+        len = 0;
+
+    if (len == u->buffer_size)
+        pa_log_debug(__FILE__": Solaris buffer underflow!");
 
-    if (len < u->sample_size)
+    len -= len % u->frame_size;
+
+    if (len == 0)
         return;
 
     memchunk = &u->memchunk;
@@ -146,17 +161,20 @@ static void do_write(struct userdata *u) {
     assert(memchunk->memblock->data);
     assert(memchunk->length);
 
-    if (memchunk->length < len)
+    if (memchunk->length < len) {
         len = memchunk->length;
-    
+        len -= len % u->frame_size;
+        assert(len);
+    }
+
     if ((r = pa_iochannel_write(u->io, (uint8_t*) memchunk->memblock->data + memchunk->index, len)) < 0) {
         pa_log(__FILE__": write() failed: %s", strerror(errno));
         return;
     }
+
+    assert(r % u->frame_size == 0);
     
-    if (memchunk == &u->silence)
-        assert(r % u->sample_size == 0);
-    else {
+    if (memchunk != &u->silence) {
         u->memchunk.index += r;
         u->memchunk.length -= r;
         
@@ -167,14 +185,6 @@ static void do_write(struct userdata *u) {
     }
 
     u->written_bytes += r;
-
-    /*
-     * Write 0 bytes which will generate a SIGPOLL when "played".
-     */
-    if (write(u->fd, NULL, 0) < 0) {
-        pa_log(__FILE__": write() failed: %s", strerror(errno));
-        return;
-    }
 }
 
 static void do_read(struct userdata *u) {
@@ -217,10 +227,49 @@ static void io_callback(pa_iochannel *io, void*userdata) {
     do_read(u);
 }
 
-void sig_callback(pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata) {
+static void timer_cb(pa_mainloop_api*a, pa_time_event *e, const struct timeval *tv, void *userdata) {
     struct userdata *u = userdata;
+    struct timeval ntv;
+
     assert(u);
+
     do_write(u);
+
+    pa_gettimeofday(&ntv);
+    pa_timeval_add(&ntv, u->poll_timeout);
+
+    a->time_restart(e, &ntv);
+}
+
+static void sig_callback(pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata) {
+    struct userdata *u = userdata;
+    pa_cvolume old_vol;
+    
+    assert(u);
+
+    if (u->sink) {
+        assert(u->sink->get_hw_volume);
+        memcpy(&old_vol, &u->sink->hw_volume, sizeof(pa_cvolume));
+        if (u->sink->get_hw_volume(u->sink) < 0)
+            return;
+        if (memcmp(&old_vol, &u->sink->hw_volume, sizeof(pa_cvolume)) != 0) {
+            pa_subscription_post(u->sink->core,
+                PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE,
+                u->sink->index);
+        }
+    }
+
+    if (u->source) {
+        assert(u->source->get_hw_volume);
+        memcpy(&old_vol, &u->source->hw_volume, sizeof(pa_cvolume));
+        if (u->source->get_hw_volume(u->source) < 0)
+            return;
+        if (memcmp(&old_vol, &u->source->hw_volume, sizeof(pa_cvolume)) != 0) {
+            pa_subscription_post(u->source->core,
+                PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE,
+                u->source->index);
+        }
+    }
 }
 
 static pa_usec_t sink_get_latency_cb(pa_sink *s) {
@@ -234,7 +283,7 @@ static pa_usec_t sink_get_latency_cb(pa_sink *s) {
     assert(err >= 0);
 
     r += pa_bytes_to_usec(u->written_bytes, &s->sample_spec);
-    r -= pa_bytes_to_usec(info.play.samples * u->sample_size, &s->sample_spec);
+    r -= pa_bytes_to_usec(info.play.samples * u->frame_size, &s->sample_spec);
 
     if (u->memchunk.memblock)
         r += pa_bytes_to_usec(u->memchunk.length, &s->sample_spec);
@@ -252,7 +301,7 @@ static pa_usec_t source_get_latency_cb(pa_source *s) {
     err = ioctl(u->fd, AUDIO_GETINFO, &info);
     assert(err >= 0);
 
-    r += pa_bytes_to_usec(info.record.samples * u->sample_size, &s->sample_spec);
+    r += pa_bytes_to_usec(info.record.samples * u->frame_size, &s->sample_spec);
     r -= pa_bytes_to_usec(u->read_bytes, &s->sample_spec);
 
     return r;
@@ -292,6 +341,69 @@ static int sink_set_hw_volume_cb(pa_sink *s) {
     return 0;
 }
 
+static int sink_get_hw_mute_cb(pa_sink *s) {
+    struct userdata *u = s->userdata;
+    audio_info_t info;
+    int err;
+
+    err = ioctl(u->fd, AUDIO_GETINFO, &info);
+    assert(err >= 0);
+
+    s->hw_muted = !!info.output_muted;
+
+    return 0;
+}
+
+static int sink_set_hw_mute_cb(pa_sink *s) {
+    struct userdata *u = s->userdata;
+    audio_info_t info;
+
+    AUDIO_INITINFO(&info);
+
+    info.output_muted = !!s->hw_muted;
+
+    if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) {
+        pa_log(__FILE__": AUDIO_SETINFO: %s", strerror(errno));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int source_get_hw_volume_cb(pa_source *s) {
+    struct userdata *u = s->userdata;
+    audio_info_t info;
+    int err;
+
+    err = ioctl(u->fd, AUDIO_GETINFO, &info);
+    assert(err >= 0);
+
+    pa_cvolume_set(&s->hw_volume, s->hw_volume.channels,
+        info.record.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN);
+
+    return 0;
+}
+
+static int source_set_hw_volume_cb(pa_source *s) {
+    struct userdata *u = s->userdata;
+    audio_info_t info;
+
+    AUDIO_INITINFO(&info);
+
+    info.record.gain = pa_cvolume_avg(&s->hw_volume) * AUDIO_MAX_GAIN / PA_VOLUME_NORM;
+    assert(info.record.gain <= AUDIO_MAX_GAIN);
+
+    if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) {
+        if (errno == EINVAL)
+            pa_log(__FILE__": AUDIO_SETINFO: Unsupported volume.");
+        else
+            pa_log(__FILE__": AUDIO_SETINFO: %s", strerror(errno));
+        return -1;
+    }
+
+    return 0;
+}
+
 static int pa_solaris_auto_format(int fd, int mode, pa_sample_spec *ss) {
     audio_info_t info;
 
@@ -384,7 +496,9 @@ int pa__init(pa_core *c, pa_module*m) {
     int mode;
     int record = 1, playback = 1;
     pa_sample_spec ss;
+    pa_channel_map map;
     pa_modargs *ma = NULL;
+    struct timeval tv;
     assert(c && m);
 
     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
@@ -411,7 +525,7 @@ int pa__init(pa_core *c, pa_module*m) {
     }
 
     ss = c->default_sample_spec;
-    if (pa_modargs_get_sample_spec(ma, &ss) < 0) {
+    if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map) < 0) {
         pa_log(__FILE__": failed to parse sample specification");
         goto fail;
     }
@@ -432,21 +546,25 @@ int pa__init(pa_core *c, pa_module*m) {
     u->core = c;
 
     if (mode != O_WRONLY) {
-        u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, NULL);
+        u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map);
         assert(u->source);
         u->source->userdata = u;
         u->source->get_latency = source_get_latency_cb;
+        u->source->get_hw_volume = source_get_hw_volume_cb;
+        u->source->set_hw_volume = source_set_hw_volume_cb;
         pa_source_set_owner(u->source, m);
         u->source->description = pa_sprintf_malloc("Solaris PCM on '%s'", p);
     } else
         u->source = NULL;
 
     if (mode != O_RDONLY) {
-        u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL);
+        u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map);
         assert(u->sink);
         u->sink->get_latency = sink_get_latency_cb;
         u->sink->get_hw_volume = sink_get_hw_volume_cb;
         u->sink->set_hw_volume = sink_set_hw_volume_cb;
+        u->sink->get_hw_mute = sink_get_hw_mute_cb;
+        u->sink->set_hw_mute = sink_set_hw_mute_cb;
         u->sink->userdata = u;
         pa_sink_set_owner(u->sink, m);
         u->sink->description = pa_sprintf_malloc("Solaris PCM on '%s'", p);
@@ -462,7 +580,7 @@ int pa__init(pa_core *c, pa_module*m) {
 
     u->memchunk.memblock = NULL;
     u->memchunk.length = 0;
-    u->sample_size = pa_frame_size(&ss);
+    u->frame_size = pa_frame_size(&ss);
     u->buffer_size = buffer_size;
 
     u->silence.memblock = pa_memblock_new(u->silence.length = CHUNK_SIZE, u->core->memblock_stat);
@@ -476,6 +594,14 @@ int pa__init(pa_core *c, pa_module*m) {
     u->module = m;
     m->userdata = u;
 
+    u->poll_timeout = pa_bytes_to_usec(u->buffer_size / 10, &ss);
+
+    pa_gettimeofday(&tv);
+    pa_timeval_add(&tv, u->poll_timeout);
+
+    u->timer = c->mainloop->time_new(c->mainloop, &tv, timer_cb, u);
+    assert(u->timer);
+
     u->sig = pa_signal_new(SIGPOLL, sig_callback, u);
     assert(u->sig);
     ioctl(u->fd, I_SETSIG, S_MSG);
@@ -483,8 +609,12 @@ int pa__init(pa_core *c, pa_module*m) {
     pa_modargs_free(ma);
 
     /* Read mixer settings */
-    if (u->sink)
-        sink_get_hw_volume(u->sink);
+    if (u->source)
+        source_get_hw_volume_cb(u->source);
+    if (u->sink) {
+        sink_get_hw_volume_cb(u->sink);
+        sink_get_hw_mute_cb(u->sink);
+    }
 
     return 0;
 
@@ -505,6 +635,8 @@ void pa__done(pa_core *c, pa_module*m) {
     if (!(u = m->userdata))
         return;
 
+    if (u->timer)
+        c->mainloop->time_free(u->timer);
     ioctl(u->fd, I_SETSIG, 0);
     pa_signal_free(u->sig);