Exemplo n.º 1
0
/******************************************************************************
 * Start the PulseAudio mainloop background thread.
 * This function returns as soon as the PulseAudio thread is started.
 * Initialization of PulseAudio is done in the background thread.
 * In order to learn when initialization is finished, you need to wait until
 * context_state_cb() is called, and check the PulseAudio context state.
 *****************************************************************************/
static mbx_error_code init_pulseaudio(_mbx_out out) {
    if ((out->pa_ml = pa_threaded_mainloop_new()) == NULL ) {
        mbx_log_error(MBX_LOG_AUDIO_OUTPUT, "Unable to allocate a PulseAudio threaded main "
            "loop object.");
        return MBX_PULSEAUDIO_ERROR;
    }
    out->pa_props = pa_proplist_new(); /* TODO: Set properties in proplist */
    pa_mainloop_api *pa_mlapi = pa_threaded_mainloop_get_api(out->pa_ml);
    out->pa_ctx = pa_context_new_with_proplist(pa_mlapi, NULL, out->pa_props);
    if ( out->pa_ctx == NULL ) {
        mbx_log_error(MBX_LOG_AUDIO_OUTPUT, "Unable to instantiate a pulseaudio connection "
            "context.");
        return MBX_PULSEAUDIO_ERROR;
    }
    pa_context_set_state_callback(out->pa_ctx, context_state_cb, out);
    if ( pa_context_connect(out->pa_ctx, NULL, 0, NULL) < 0 ) {
        mbx_log_error(MBX_LOG_AUDIO_OUTPUT, "Unable to connect to the pulseaudio server: %s",
            pa_msg(out));
        return MBX_PULSEAUDIO_ERROR;
    }
    if ( pa_threaded_mainloop_start(out->pa_ml) ) {
        mbx_log_error(MBX_LOG_AUDIO_OUTPUT, "Failed to start pulseaudio mainloop "
            "in background thread.");
        return MBX_PULSEAUDIO_ERROR;
    }
    return MBX_SUCCESS;
}
Exemplo n.º 2
0
/* This callback is called when the stream drain is complete, i.e. the stream
 * can be disconnected and we can start draining the pulseaudio context. */
static void stream_drain_complete_cb(pa_stream*s, int success, void *userdata){
    _mbx_out out = (_mbx_out) userdata;
    assert(out != NULL && out->stream == s);
    mbx_log_debug(MBX_LOG_AUDIO_OUTPUT, "Draining pulseaudio stream has completed.");
    if (!success) {
        mbx_log_error(MBX_LOG_AUDIO_OUTPUT, "Failed to complete pulseaudio stream drain: %s",
            pa_msg(out));
    }
    pa_stream_disconnect(s);
    pa_stream_unref(s);
    pa_operation *o;
    o = pa_context_drain(out->pa_ctx, context_drain_complete_cb, out);
    if ( o == NULL ) {
        mbx_log_error(MBX_LOG_AUDIO_OUTPUT, "Failed to start pulseaudio context drain: %s",
            pa_msg(out));
        /* In case of error, we do our best and continue manually */
        context_drain_complete_cb(out->pa_ctx, out);
    }
    pa_operation_unref(o);
}
Exemplo n.º 3
0
/* Shutdown the application. This is called from the pulseaudio mainloop
 * thread in stream_write_cb() */
static void do_shutdown(_mbx_out out) {
    mbx_log_debug(MBX_LOG_AUDIO_OUTPUT, "Shutting down.");
    unset_all_callbacks(out);
    /* stream_drain_complete_cb will be called when drain is done */
    pa_operation *o = pa_stream_drain(out->stream,stream_drain_complete_cb,out);
    if ( o == NULL ) {
        mbx_log_error(MBX_LOG_AUDIO_OUTPUT, "Failed to start pulseaudio stream drain: %s",
            pa_msg(out));
        /* In case of error, we do our best and continue manually */
        stream_drain_complete_cb(out->stream, 0, out);
    }
    pa_operation_unref(o);
}
Exemplo n.º 4
0
/******************************************************************************
 * This is called when the PulseAudio context state becomes PA_CONTEXT_READY.
 * We initialize a new PulseAudio stream, configure it with our callbacks,
 * and connect the new stream to the PulseAudio server.
 *****************************************************************************/
static void context_ready(_mbx_out out) {
    out->stream = pa_stream_new(out->pa_ctx, "playback", &out->sample_spec, NULL);
    if ( out->stream == NULL ) {
        mbx_log_error(MBX_LOG_AUDIO_OUTPUT, "Unable to create pulseaudio stream: %s", pa_msg(out));
        out->state = _MBX_OUT_PULSEAUDIO_ERROR;
        return;
    }
    /* will be called when pulseaudio requests audio data */
    pa_stream_set_write_callback(out->stream, stream_write_cb, out);
    /* will be called when an buffer underflow occurs */
    pa_stream_set_underflow_callback(out->stream, stream_underflow_cb, out);
    pa_buffer_attr bufattr = make_bufattr();
    int r = pa_stream_connect_playback(out->stream, out->dev_name, &bufattr,
        PA_STREAM_INTERPOLATE_TIMING |
        PA_STREAM_ADJUST_LATENCY |
        PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL);
    if (r < 0) {
        mbx_log_error(MBX_LOG_AUDIO_OUTPUT, "Failed to connect stream to pulseaudio sink: %s",
            pa_msg(out));
        out->state = _MBX_OUT_PULSEAUDIO_ERROR;
        return;
    }
    out->state = _MBX_OUT_READY;
}
Exemplo n.º 5
0
/******************************************************************************
 * stream_write_cb()
 * This will be called by PulseAudio when we need to provide audio data
 * for playback.
 *****************************************************************************/
static void stream_write_cb(pa_stream *s, size_t n_requested_bytes,
        void *userdata) {
    _mbx_out out = (_mbx_out ) userdata;
    sample_t *data_to_write = NULL;
    size_t n_bytes_written = 0;
    assert ( out != NULL && out->stream == s);
    if ( out->trigger_shutdown ) {
        do_shutdown(out);
        return;
    }
    while ( n_bytes_written < n_requested_bytes ) {
        int r;
        size_t n_bytes_to_write = n_requested_bytes - n_bytes_written;
        r = pa_stream_begin_write(s, (void**)&data_to_write, &n_bytes_to_write);
        assert(n_bytes_to_write % (2*sizeof(sample_t)) == 0);
        if ( r < 0 ) {
            mbx_log_error(MBX_LOG_AUDIO_OUTPUT, "Prepare writing data to the pulseaudio server"
                " failed: %s", pa_msg(out));
            pa_stream_cancel_write(s);
            return;
        }
        if ( n_bytes_to_write > 0 ) {
            int i;
            assert ( n_bytes_to_write / sizeof(sample_t) < 44100L*2*8 );
            sample_t left[44100L*2*8];
            sample_t right[44100L*2*8];
            out->cb(left, right, n_bytes_to_write / (2*sizeof(sample_t)), out->output_cb_userdata);
            for ( i=0; i<n_bytes_to_write/(2*sizeof(sample_t)); i++ ) {
                data_to_write[2*i] = left[i];
                data_to_write[2*i+1] = right[i];
            }
            pa_stream_write(s, data_to_write, n_bytes_to_write, NULL, 0,
                PA_SEEK_RELATIVE);
            n_bytes_written += n_bytes_to_write;
        }
    }
}
Exemplo n.º 6
0
/* The implementation of create_output_device_name_list() is based on the
 * example code in
 * http://www.ypass.net/blog/2009/10/
 *    pulseaudio-an-async-example-to-get-device-lists/
 */
mbx_error_code mbx_create_output_device_name_list(char ***dev_names, size_t *n_devs)
{
    pa_mainloop *pa_ml = pa_mainloop_new();
    pa_mainloop_api *pa_mlapi = pa_mainloop_get_api(pa_ml);
    pa_context *pa_ctx = pa_context_new(pa_mlapi, "music box (listing output "
        "devices)");
    pa_operation *pa_op;
    pa_context_state_t pa_context_state = PA_CONTEXT_UNCONNECTED;
    int do_iterate = 1;
    int error = 0;

    pa_context_connect(pa_ctx, NULL, 0, NULL);
    /* The state callback will update the state when we are connected to the
     * PulseAudio server, or if an error occurs. */
    pa_context_set_state_callback(pa_ctx, pa_context_state_cb,
        &pa_context_state);

    while ( do_iterate ) {
        switch ( pa_context_state ) {
            case PA_CONTEXT_UNCONNECTED:
            case PA_CONTEXT_CONNECTING:
            case PA_CONTEXT_AUTHORIZING:
            case PA_CONTEXT_SETTING_NAME:
                pa_mainloop_iterate(pa_ml, 1, NULL); // we must wait.
                break;
            case PA_CONTEXT_READY:
                do_iterate = 0;
                break;
            case PA_CONTEXT_FAILED:
                mbx_log_error(MBX_LOG_AUDIO_OUTPUT, "Connection to PulseAudio server failed: "
                    "%s", pa_strerror(pa_context_errno(pa_ctx)));
                error = 1;
                break;
            case PA_CONTEXT_TERMINATED:
                mbx_log_error(MBX_LOG_AUDIO_OUTPUT, "Connection to PulseAudio server "
                    "terminated unexpectedly.");
                error = 1;
                break;
            default:
                mbx_log_error(MBX_LOG_AUDIO_OUTPUT, "The PulseAudio context has an unexpected "
                    "state: %d", pa_context_state);
                error = 1;
                break;
        }
        if ( error ) {
            do_iterate = 0;
        }
    }
    if ( ! error ) {
        struct list_of_strings result = { NULL, 0, 0 };
        pa_op = pa_context_get_sink_info_list(pa_ctx, pa_sinklist_cb, &result);
        while ( pa_operation_get_state(pa_op) == PA_OPERATION_RUNNING ) {
            pa_mainloop_iterate(pa_ml, 1, NULL); // wait.
        }
        pa_operation_unref(pa_op);
        *dev_names = result.strings;
        *n_devs = result.n_strings;
    }
    pa_context_disconnect(pa_ctx);
    pa_context_unref(pa_ctx);
    pa_mainloop_free(pa_ml);
    return error ? MBX_PULSEAUDIO_ERROR : MBX_SUCCESS;
}