Exemplo n.º 1
0
void AudioOutputPulseAudio::SetVolumeChannel(int channel, int volume)
{
    QString fn_log_tag = "SetVolumeChannel, ";

    if (channel < 0 || channel > PULSE_MAX_CHANNELS || volume < 0)
    {
        VBERROR(fn_log_tag + QString("bad volume params, channel %1, volume %2")
                .arg(channel).arg(volume));
        return;
    }

    volume_control.values[channel] =
        (float)volume / 100.0f * (float)PA_VOLUME_NORM;

    volume = min(100, volume);
    volume = max(0, volume);

    uint32_t sink_index = pa_stream_get_device_index(pstream);
    pa_threaded_mainloop_lock(mainloop);
    pa_operation *op =
        pa_context_set_sink_volume_by_index(pcontext, sink_index,
                                            &volume_control,
                                            OpCompletionCallback, this);
    pa_threaded_mainloop_unlock(mainloop);
    if (op)
        pa_operation_unref(op);
    else
        VBERROR(fn_log_tag +
                QString("set sink volume operation failed, sink %1, error %2 ")
                .arg(sink_index).arg(pa_strerror(pa_context_errno(pcontext))));
}
void AudioOutputPulseAudio::WriteAudio(uchar *aubuf, int size)
{
    QString fn_log_tag = "WriteAudio, ";
    pa_stream_state_t sstate = pa_stream_get_state(pstream);

    VBAUDIOTS(fn_log_tag + QString("writing %1 bytes").arg(size));

    /* NB This "if" check can be replaced with PA_STREAM_IS_GOOD() in
       PulseAudio API from 0.9.11. As 0.9.10 is still widely used
       we use the more verbose version for now */

    if (sstate == PA_STREAM_CREATING || sstate == PA_STREAM_READY)
    {
        int write_status = PA_ERR_INVALID;
        size_t to_write = size;
        unsigned char *buf_ptr = aubuf;

        pa_threaded_mainloop_lock(mainloop);
        while (to_write > 0)
        {
            write_status = 0;
            size_t writable = pa_stream_writable_size(pstream);
            if (writable > 0)
            {
                size_t write = min(to_write, writable);
                write_status = pa_stream_write(pstream, buf_ptr, write,
                                               NULL, 0, PA_SEEK_RELATIVE);

                if (0 != write_status)
                    break;

                buf_ptr += write;
                to_write -= write;
            }
            else
            {
                pa_threaded_mainloop_wait(mainloop);
            }
        }
        pa_threaded_mainloop_unlock(mainloop);

        if (to_write > 0)
        {
            if (write_status != 0)
                VBERROR(fn_log_tag + QString("stream write failed: %1")
                                     .arg(write_status == PA_ERR_BADSTATE
                                                ? "PA_ERR_BADSTATE"
                                                : "PA_ERR_INVALID"));

            VBERROR(fn_log_tag + QString("short write, %1 of %2")
                                 .arg(size - to_write).arg(size));
        }
    }
    else
        VBERROR(fn_log_tag + QString("stream state not good: %1")
                             .arg(sstate,0,16));
}
Exemplo n.º 3
0
char *AudioOutputPulseAudio::ChooseHost(void)
{
    QString fn_log_tag = "ChooseHost, ";
    char *pulse_host = NULL;
    char *device = strdup(main_device.toAscii().constData());
    const char *host;

    for (host=device; host && *host != ':' && *host != 0; host++);

    if (host && *host != 0)
        host++;

    if ( !(!host || *host == 0 || strcmp(host,"default") == 0))
    {
        if ((pulse_host = new char[strlen(host)]))
            strcpy(pulse_host, host);
        else
            VBERROR(fn_log_tag +
                    QString("allocation of pulse host '%1' char[%2] failed")
                    .arg(host).arg(strlen(host) + 1));
    }

    if (!pulse_host && strcmp(host,"default") != 0)
    {
        char *env_pulse_host = getenv("PULSE_SERVER");
        if (env_pulse_host && (*env_pulse_host != '\0'))
        {
            int host_len = strlen(env_pulse_host) + 1;

            if ((pulse_host = new char[host_len]))
                strcpy(pulse_host, env_pulse_host);
            else
            {
                VBERROR(fn_log_tag +
                        QString("allocation of pulse host '%1' char[%2] failed")
                        .arg(env_pulse_host).arg(host_len));
            }
        }
    }

    VBAUDIO(fn_log_tag + QString("chosen PulseAudio server: %1")
            .arg((pulse_host != NULL) ? pulse_host : "default"));

    free(device);

    return pulse_host;
}
Exemplo n.º 4
0
void AudioOutputPulseAudio::FlushStream(const char *caller)
{
    QString fn_log_tag = QString("FlushStream (%1), ").arg(caller);
    pa_threaded_mainloop_lock(mainloop);
    pa_operation *op = pa_stream_flush(pstream, NULL, this);
    pa_threaded_mainloop_unlock(mainloop);
    if (op)
        pa_operation_unref(op);
    else
        VBERROR(fn_log_tag + "stream flush operation failed ");
}
Exemplo n.º 5
0
void AudioOutputPulseAudio::OpCompletionCallback(
    pa_context *c, int ok, void *arg)
{
    QString fn_log_tag = "OpCompletionCallback, ";
    AudioOutputPulseAudio *audoutP = static_cast<AudioOutputPulseAudio*>(arg);
    if (!ok)
    {
        VBERROR(fn_log_tag + QString("bummer, an operation failed: %1")
                .arg(pa_strerror(pa_context_errno(c))));
    }
    pa_threaded_mainloop_signal(audoutP->mainloop, 0);
}
Exemplo n.º 6
0
void AudioOutputPulseAudio::Drain(void)
{
    AudioOutputBase::Drain();
    pa_threaded_mainloop_lock(mainloop);
    pa_operation *op = pa_stream_drain(pstream, NULL, this);
    pa_threaded_mainloop_unlock(mainloop);

    if (op)
        pa_operation_unref(op);
    else
        VBERROR("Drain, stream drain failed");
}
Exemplo n.º 7
0
void AudioOutputPulseAudio::BufferFlowCallback(pa_stream *s, void *tag)
{
    VBERROR(QString("stream buffer %1 flow").arg((char*)tag));
}
Exemplo n.º 8
0
AudioOutputSettings* AudioOutputPulseAudio::GetOutputSettings()
{
    AudioFormat fmt;
    m_aosettings = new AudioOutputSettings();
    QString fn_log_tag = "OpenDevice, ";

    /* Start the mainloop and connect a context so we can retrieve the
       parameters of the default sink */
    mainloop = pa_threaded_mainloop_new();
    if (!mainloop)
    {
        VBERROR(fn_log_tag + "Failed to get new threaded mainloop");
        delete m_aosettings;
        return NULL;
    }

    pa_threaded_mainloop_start(mainloop);
    pa_threaded_mainloop_lock(mainloop);

    if (!ContextConnect())
    {
        pa_threaded_mainloop_unlock(mainloop);
        pa_threaded_mainloop_stop(mainloop);
        delete m_aosettings;
        return NULL;
    }

    /* Get the samplerate and channel count of the default sink, supported rate
       and channels are added in SinkInfoCallback */
    /* We should in theory be able to feed pulse any samplerate but allowing it
       to resample results in weird behaviour (odd channel maps, static) post
       pause / reset */
    pa_operation *op = pa_context_get_sink_info_by_index(pcontext, 0,
                       SinkInfoCallback,
                       this);
    if (op)
    {
        pa_operation_unref(op);
        pa_threaded_mainloop_wait(mainloop);
    }
    else
        VBERROR("Failed to determine default sink samplerate");

    pa_threaded_mainloop_unlock(mainloop);

    // All formats except S24 (pulse wants S24LSB)
    while ((fmt = m_aosettings->GetNextFormat()))
    {
        if (fmt == FORMAT_S24
// define from PA 0.9.15 only
#ifndef PA_MAJOR
                || fmt == FORMAT_S24LSB
#endif
           )
            continue;
        m_aosettings->AddSupportedFormat(fmt);
    }

    pa_context_disconnect(pcontext);
    pa_context_unref(pcontext);
    pcontext = NULL;
    pa_threaded_mainloop_stop(mainloop);
    mainloop = NULL;

    return m_aosettings;
}
Exemplo n.º 9
0
bool AudioOutputPulseAudio::ConnectPlaybackStream(void)
{
    QString fn_log_tag = "ConnectPlaybackStream, ";
    pstream = pa_stream_new(pcontext, "MythTV playback", &sample_spec,
                            &channel_map);
    if (!pstream)
    {
        VBERROR(fn_log_tag + QString("failed to create new playback stream"));
        return false;
    }
    pa_stream_set_state_callback(pstream, StreamStateCallback, this);
    pa_stream_set_write_callback(pstream, WriteCallback, this);
    pa_stream_set_overflow_callback(pstream, BufferFlowCallback, (char*)"over");
    pa_stream_set_underflow_callback(pstream, BufferFlowCallback,
                                     (char*)"under");
    if (set_initial_vol)
    {
        int volume = gCoreContext->GetNumSetting("MasterMixerVolume", 80);
        pa_cvolume_set(&volume_control, channels,
                       (float)volume * (float)PA_VOLUME_NORM / 100.0f);
    }
    else
        pa_cvolume_reset(&volume_control, channels);

    fragment_size = (samplerate * 25 * output_bytes_per_frame) / 1000;

    buffer_settings.maxlength = (uint32_t)-1;
    buffer_settings.tlength = fragment_size * 4;
    buffer_settings.prebuf = (uint32_t)-1;
    buffer_settings.minreq = (uint32_t)-1;

    int flags = PA_STREAM_INTERPOLATE_TIMING
                | PA_STREAM_ADJUST_LATENCY
                | PA_STREAM_AUTO_TIMING_UPDATE
                | PA_STREAM_NO_REMIX_CHANNELS;

    pa_stream_connect_playback(pstream, NULL, &buffer_settings,
                               (pa_stream_flags_t)flags, &volume_control, NULL);

    pa_context_state_t cstate;
    pa_stream_state_t sstate;
    bool connected = false, failed = false;

    while (!(connected || failed))
    {
        switch (cstate = pa_context_get_state(pcontext))
        {
        case PA_CONTEXT_FAILED:
        case PA_CONTEXT_TERMINATED:
            VERBOSE(VB_IMPORTANT, LOC_ERR + fn_log_tag +
                    QString("context is stuffed, %1")
                    .arg(pa_strerror(pa_context_errno(
                                         pcontext))));
            failed = true;
            break;
        default:
            switch (sstate = pa_stream_get_state(pstream))
            {
            case PA_STREAM_READY:
                connected = true;
                break;
            case PA_STREAM_FAILED:
            case PA_STREAM_TERMINATED:
                VBERROR(fn_log_tag +
                        QString("stream failed or was terminated, "
                                "context state %1, stream state %2")
                        .arg(cstate).arg(sstate));
                failed = true;
                break;
            default:
                pa_threaded_mainloop_wait(mainloop);
                break;
            }
        }
    }

    const pa_buffer_attr *buf_attr = pa_stream_get_buffer_attr(pstream);
    fragment_size = buf_attr->tlength >> 2;
    soundcard_buffer_size = buf_attr->maxlength;

    VBAUDIO(fn_log_tag + QString("fragment size %1, soundcard buffer size %2")
            .arg(fragment_size).arg(soundcard_buffer_size));

    return (connected && !failed);
}
Exemplo n.º 10
0
bool AudioOutputPulseAudio::ContextConnect(void)
{
    QString fn_log_tag = "ContextConnect, ";
    if (pcontext)
    {
        VBERROR(fn_log_tag + "context appears to exist, but shouldn't (yet)");
        pa_context_unref(pcontext);
        pcontext = NULL;
        return false;
    }
    pcontext = pa_context_new(pa_threaded_mainloop_get_api(mainloop), "MythTV");
    if (!pcontext)
    {
        VBERROR(fn_log_tag + "failed to acquire new context");
        return false;
    }
    pa_context_set_state_callback(pcontext, ContextStateCallback, this);

    char *pulse_host = ChooseHost();
    int chk = pa_context_connect(
                  pcontext, pulse_host, (pa_context_flags_t)0, NULL);

    delete(pulse_host);

    if (chk < 0)
    {
        VBERROR(fn_log_tag + QString("context connect failed: %1")
                .arg(pa_strerror(pa_context_errno(pcontext))));
        return false;
    }
    bool connected = false;
    pa_context_state_t state = pa_context_get_state(pcontext);
    for (; !connected; state = pa_context_get_state(pcontext))
    {
        switch(state)
        {
        case PA_CONTEXT_READY:
            VBAUDIO(fn_log_tag +"context connection ready");
            connected = true;
            continue;

        case PA_CONTEXT_FAILED:
        case PA_CONTEXT_TERMINATED:
            VBERROR(fn_log_tag +
                    QString("context connection failed or terminated: %1")
                    .arg(pa_strerror(pa_context_errno(pcontext))));
            return false;

        default:
            VBAUDIO(fn_log_tag + "waiting for context connection ready");
            pa_threaded_mainloop_wait(mainloop);
            break;
        }
    }

    pa_operation *op =
        pa_context_get_server_info(pcontext, ServerInfoCallback, this);

    if (op)
        pa_operation_unref(op);
    else
        VBERROR(fn_log_tag + "failed to get PulseAudio server info");

    return true;
}
Exemplo n.º 11
0
bool AudioOutputPulseAudio::OpenDevice()
{
    QString fn_log_tag = "OpenDevice, ";
    if (channels > PULSE_MAX_CHANNELS )
    {
        VBERROR(fn_log_tag + QString("audio channel limit %1, but %2 requested")
                .arg(PULSE_MAX_CHANNELS).arg(channels));
        return false;
    }

    sample_spec.rate = samplerate;
    sample_spec.channels = volume_control.channels = channels;
    switch (output_format)
    {
    case FORMAT_U8:
        sample_spec.format = PA_SAMPLE_U8;
        break;
    case FORMAT_S16:
        sample_spec.format = PA_SAMPLE_S16NE;
        break;
// define from PA 0.9.15 only
#ifdef PA_MAJOR
    case FORMAT_S24LSB:
        sample_spec.format = PA_SAMPLE_S24_32NE;
        break;
#endif
    case FORMAT_S32:
        sample_spec.format = PA_SAMPLE_S32NE;
        break;
    case FORMAT_FLT:
        sample_spec.format = PA_SAMPLE_FLOAT32NE;
        break;
        break;
    default:
        VBERROR(fn_log_tag + QString("unsupported sample format %1")
                .arg(output_format));
        return false;
    }

    if (!pa_sample_spec_valid(&sample_spec))
    {
        VBERROR(fn_log_tag + "invalid sample spec");
        return false;
    }
    else
    {
        char spec[PA_SAMPLE_SPEC_SNPRINT_MAX];
        pa_sample_spec_snprint(spec, sizeof(spec), &sample_spec);
        VBAUDIO(fn_log_tag + QString("using sample spec %1").arg(spec));
    }

    pa_channel_map *pmap = NULL;

    if(!(pmap = pa_channel_map_init_auto(&channel_map, channels,
                                         PA_CHANNEL_MAP_WAVEEX)) < 0)
    {
        VBERROR(fn_log_tag + "failed to init channel map");
        return false;
    }

    channel_map = *pmap;

    mainloop = pa_threaded_mainloop_new();
    if (!mainloop)
    {
        VBERROR(fn_log_tag + "failed to get new threaded mainloop");
        return false;
    }

    pa_threaded_mainloop_start(mainloop);
    pa_threaded_mainloop_lock(mainloop);

    if (!ContextConnect())
    {
        pa_threaded_mainloop_unlock(mainloop);
        pa_threaded_mainloop_stop(mainloop);
        return false;
    }

    if (!ConnectPlaybackStream())
    {
        pa_threaded_mainloop_unlock(mainloop);
        pa_threaded_mainloop_stop(mainloop);
        return false;
    }

    pa_threaded_mainloop_unlock(mainloop);
    return true;
}
Exemplo n.º 12
0
void AudioOutputPulseAudio::WriteAudio(uchar *aubuf, int size)
{
    QString fn_log_tag = "WriteAudio, ";
    pa_stream_state_t sstate = pa_stream_get_state(pstream);

    // Do not write anything to pulse server if we are in pause mode
    if (IsPaused())
        return;

    VBAUDIOTS(fn_log_tag + QString("writing %1 bytes").arg(size));

    /* NB This "if" check can be replaced with PA_STREAM_IS_GOOD() in
       PulseAudio API from 0.9.11. As 0.9.10 is still widely used
       we use the more verbose version for now */

    if (sstate == PA_STREAM_CREATING || sstate == PA_STREAM_READY)
    {
        int write_status = PA_ERR_INVALID;
        size_t write;
        size_t writable;
        size_t to_write = size;
        unsigned char *buf_ptr = aubuf;
        pa_context_state_t cstate;

        pa_threaded_mainloop_lock(mainloop);
        while (to_write > 0)
        {
            write_status = 0;
            writable = pa_stream_writable_size(pstream);
            if (writable > 0)
            {
                write = min(to_write, writable);
                write_status = pa_stream_write(pstream, buf_ptr, write,
                                               NULL, 0, PA_SEEK_RELATIVE);
                if (!write_status)
                {
                    buf_ptr += write;
                    to_write -= write;
                }
                else
                    break;
            }
            else if (writable < 0)
                break;
            else // writable == 0
                pa_threaded_mainloop_wait(mainloop);
        }
        pa_threaded_mainloop_unlock(mainloop);

        if (to_write > 0)
        {
            if (writable < 0)
            {
                cstate = pa_context_get_state(pcontext);
                sstate = pa_stream_get_state(pstream);
                VBERROR(fn_log_tag +
                        QString("stream unfit for writing (writable < 0), "
                                "context state: %1, stream state: %2")
                        .arg(cstate,0,16).arg(sstate,0,16));
            }

            if (write_status != 0)
                VBERROR(fn_log_tag + QString("stream write failed: %1")
                                     .arg(write_status == PA_ERR_BADSTATE
                                                ? "PA_ERR_BADSTATE"
                                                : "PA_ERR_INVALID"));

            VBERROR(fn_log_tag + QString("short write, %1 of %2")
                                 .arg(size - to_write).arg(size));
        }
    }
    else
        VBERROR(fn_log_tag + QString("stream state not good: %1")
                             .arg(sstate,0,16));
}