void JackAudioSystem::destroyOutput() { AudioOutputPtr ao = g.ao; JackAudioOutput * const jao = dynamic_cast<JackAudioOutput *>(ao.get()); if (jao) { jao->qmMutex.lock(); } delete [] output_buffer; output_buffer = NULL; for (unsigned int i = 0; i < iOutPorts; ++i) { if (out_ports[i] != NULL) { int err = jack_port_unregister(client, out_ports[i]); if (err != 0) { qWarning("JackAudioSystem: unable to unregister out port - jack_port_unregister() returned %i", err); } out_ports[i] = NULL; } } bOutputIsGood = false; if (jao) { jao->qmMutex.unlock(); } }
void JackAudioSystem::initializeOutput() { QMutexLocker lock(&qmWait); if (!jasys->bJackIsGood) { return; } AudioOutputPtr ao = g.ao; JackAudioOutput * const jao = dynamic_cast<JackAudioOutput *>(ao.get()); allocOutputBuffer(iBufferSize); if (jao) { jao->qmMutex.lock(); } for (unsigned int i = 0; i < iOutPorts; ++i) { char name[10]; snprintf(name, 10, "output_%d", i + 1); out_ports[i] = jack_port_register(client, name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); if (out_ports[i] == NULL) { qWarning("JackAudioSystem: unable to register 'output' port"); break; } } bOutputIsGood = true; if (jao) { jao->qmMutex.unlock(); } }
void JackAudioSystem::allocOutputBuffer(jack_nframes_t frames) { iBufferSize = frames; AudioOutputPtr ao = g.ao; JackAudioOutput * const jao = dynamic_cast<JackAudioOutput *>(ao.get()); if (jao) { jao->qmMutex.lock(); } if (output_buffer) { delete [] output_buffer; output_buffer = NULL; } output_buffer = new jack_default_audio_sample_t[frames * iOutPorts]; if (output_buffer == NULL) { bJackIsGood = false; } if (jao) { jao->qmMutex.unlock(); } }
void PulseAudioSystem::write_callback(pa_stream *s, size_t bytes, void *userdata) { PulseAudioSystem *pas = reinterpret_cast<PulseAudioSystem *>(userdata); Q_ASSERT(s == pas->pasOutput); AudioOutputPtr ao = g.ao; PulseAudioOutput *pao = dynamic_cast<PulseAudioOutput *>(ao.get()); unsigned char buffer[bytes]; if (! pao) { // Transitioning, but most likely transitions back, so just zero. memset(buffer, 0, bytes); pa_stream_write(s, buffer, bytes, NULL, 0, PA_SEEK_RELATIVE); pas->wakeup(); return; } const pa_sample_spec *pss = pa_stream_get_sample_spec(s); const pa_channel_map *pcm = pa_stream_get_channel_map(pas->pasOutput); if (!pa_sample_spec_equal(pss, &pao->pss) || !pa_channel_map_equal(pcm, &pao->pcm)) { pao->pss = *pss; pao->pcm = *pcm; if (pss->format == PA_SAMPLE_FLOAT32NE) pao->eSampleFormat = PulseAudioOutput::SampleFloat; else pao->eSampleFormat = PulseAudioOutput::SampleShort; pao->iMixerFreq = pss->rate; pao->iChannels = pss->channels; unsigned int chanmasks[pss->channels]; for (int i=0;i<pss->channels;++i) { unsigned int cm = 0; switch (pcm->map[i]) { case PA_CHANNEL_POSITION_LEFT: cm = SPEAKER_FRONT_LEFT; break; case PA_CHANNEL_POSITION_RIGHT: cm = SPEAKER_FRONT_RIGHT; break; case PA_CHANNEL_POSITION_CENTER: cm = SPEAKER_FRONT_CENTER; break; case PA_CHANNEL_POSITION_REAR_LEFT: cm = SPEAKER_BACK_LEFT; break; case PA_CHANNEL_POSITION_REAR_RIGHT: cm = SPEAKER_BACK_RIGHT; break; case PA_CHANNEL_POSITION_REAR_CENTER: cm = SPEAKER_BACK_CENTER; break; case PA_CHANNEL_POSITION_LFE: cm = SPEAKER_LOW_FREQUENCY; break; case PA_CHANNEL_POSITION_SIDE_LEFT: cm = SPEAKER_SIDE_LEFT; break; case PA_CHANNEL_POSITION_SIDE_RIGHT: cm = SPEAKER_SIDE_RIGHT; break; case PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER: cm = SPEAKER_FRONT_LEFT_OF_CENTER; break; case PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER: cm = SPEAKER_FRONT_RIGHT_OF_CENTER; break; default: cm = 0; break; } chanmasks[i] = cm; } pao->initializeMixer(chanmasks); } const unsigned int iSampleSize = pao->iSampleSize; const unsigned int samples = static_cast<unsigned int>(bytes) / iSampleSize; bool oldAttenuation = pas->bAttenuating; // do we have some mixed output? if (pao->mix(buffer, samples)) { // attenuate if instructed to or it's in settings pas->bAttenuating = (g.bAttenuateOthers || g.s.bAttenuateOthers); } else { memset(buffer, 0, bytes); // attenuate if intructed to (self-activated) pas->bAttenuating = g.bAttenuateOthers; } // if the attenuation state has changed if (oldAttenuation != pas->bAttenuating) { pas->setVolumes(); } pa_stream_write(s, buffer, iSampleSize * samples, NULL, 0, PA_SEEK_RELATIVE); }
void PulseAudioSystem::eventCallback(pa_mainloop_api *api, pa_defer_event *) { api->defer_enable(pade, false); if (! bSourceDone || ! bSinkDone || ! bServerDone) return; AudioInputPtr ai = g.ai; AudioOutputPtr ao = g.ao; AudioInput *raw_ai = ai.get(); AudioOutput *raw_ao = ao.get(); PulseAudioInput *pai = dynamic_cast<PulseAudioInput *>(raw_ai); PulseAudioOutput *pao = dynamic_cast<PulseAudioOutput *>(raw_ao); if (raw_ao) { QString odev = outputDevice(); pa_stream_state ost = pasOutput ? pa_stream_get_state(pasOutput) : PA_STREAM_TERMINATED; bool do_stop = false; bool do_start = false; if (! pao && (ost == PA_STREAM_READY)) { do_stop = true; } else if (pao) { switch (ost) { case PA_STREAM_TERMINATED: { if (pasOutput) pa_stream_unref(pasOutput); pa_sample_spec pss = qhSpecMap.value(odev); pa_channel_map pcm = qhChanMap.value(odev); if ((pss.format != PA_SAMPLE_FLOAT32NE) && (pss.format != PA_SAMPLE_S16NE)) pss.format = PA_SAMPLE_FLOAT32NE; if (pss.rate == 0) pss.rate = SAMPLE_RATE; if ((pss.channels == 0) || (! g.s.doPositionalAudio())) pss.channels = 1; pasOutput = pa_stream_new(pacContext, mumble_sink_input, &pss, (pss.channels == 1) ? NULL : &pcm); pa_stream_set_state_callback(pasOutput, stream_callback, this); pa_stream_set_write_callback(pasOutput, write_callback, this); } case PA_STREAM_UNCONNECTED: do_start = true; break; case PA_STREAM_READY: { if (g.s.iOutputDelay != iDelayCache) { do_stop = true; } else if (g.s.doPositionalAudio() != bPositionalCache) { do_stop = true; } else if (odev != qsOutputCache) { do_stop = true; } break; } default: break; } } if (do_stop) { qWarning("PulseAudio: Stopping output"); pa_stream_disconnect(pasOutput); iSinkId = -1; } else if (do_start) { qWarning("PulseAudio: Starting output: %s", qPrintable(odev)); pa_buffer_attr buff; const pa_sample_spec *pss = pa_stream_get_sample_spec(pasOutput); const size_t sampleSize = (pss->format == PA_SAMPLE_FLOAT32NE) ? sizeof(float) : sizeof(short); const unsigned int iBlockLen = ((pao->iFrameSize * pss->rate) / SAMPLE_RATE) * pss->channels * static_cast<unsigned int>(sampleSize); buff.tlength = iBlockLen * (g.s.iOutputDelay+1); buff.minreq = iBlockLen; buff.maxlength = -1; buff.prebuf = -1; buff.fragsize = iBlockLen; iDelayCache = g.s.iOutputDelay; bPositionalCache = g.s.doPositionalAudio(); qsOutputCache = odev; pa_stream_connect_playback(pasOutput, qPrintable(odev), &buff, PA_STREAM_ADJUST_LATENCY, NULL, NULL); pa_context_get_sink_info_by_name(pacContext, qPrintable(odev), sink_info_callback, this); } } if (raw_ai) { QString idev = inputDevice(); pa_stream_state ist = pasInput ? pa_stream_get_state(pasInput) : PA_STREAM_TERMINATED; bool do_stop = false; bool do_start = false; if (! pai && (ist == PA_STREAM_READY)) { do_stop = true; } else if (pai) { switch (ist) { case PA_STREAM_TERMINATED: { if (pasInput) pa_stream_unref(pasInput); pa_sample_spec pss = qhSpecMap.value(idev); if ((pss.format != PA_SAMPLE_FLOAT32NE) && (pss.format != PA_SAMPLE_S16NE)) pss.format = PA_SAMPLE_FLOAT32NE; if (pss.rate == 0) pss.rate = SAMPLE_RATE; pss.channels = 1; pasInput = pa_stream_new(pacContext, "Microphone", &pss, NULL); pa_stream_set_state_callback(pasInput, stream_callback, this); pa_stream_set_read_callback(pasInput, read_callback, this); } case PA_STREAM_UNCONNECTED: do_start = true; break; case PA_STREAM_READY: { if (idev != qsInputCache) { do_stop = true; } break; } default: break; } } if (do_stop) { qWarning("PulseAudio: Stopping input"); pa_stream_disconnect(pasInput); } else if (do_start) { qWarning("PulseAudio: Starting input %s",qPrintable(idev)); pa_buffer_attr buff; const pa_sample_spec *pss = pa_stream_get_sample_spec(pasInput); const size_t sampleSize = (pss->format == PA_SAMPLE_FLOAT32NE) ? sizeof(float) : sizeof(short); const unsigned int iBlockLen = ((pai->iFrameSize * pss->rate) / SAMPLE_RATE) * pss->channels * static_cast<unsigned int>(sampleSize); buff.tlength = iBlockLen; buff.minreq = iBlockLen; buff.maxlength = -1; buff.prebuf = -1; buff.fragsize = iBlockLen; qsInputCache = idev; pa_stream_connect_record(pasInput, qPrintable(idev), &buff, PA_STREAM_ADJUST_LATENCY); } } if (raw_ai) { QString odev = outputDevice(); QString edev = qhEchoMap.value(odev); pa_stream_state est = pasSpeaker ? pa_stream_get_state(pasSpeaker) : PA_STREAM_TERMINATED; bool do_stop = false; bool do_start = false; if ((! pai || ! g.s.doEcho()) && (est == PA_STREAM_READY)) { do_stop = true; } else if (pai && g.s.doEcho()) { switch (est) { case PA_STREAM_TERMINATED: { if (pasSpeaker) pa_stream_unref(pasSpeaker); pa_sample_spec pss = qhSpecMap.value(edev); pa_channel_map pcm = qhChanMap.value(edev); if ((pss.format != PA_SAMPLE_FLOAT32NE) && (pss.format != PA_SAMPLE_S16NE)) pss.format = PA_SAMPLE_FLOAT32NE; if (pss.rate == 0) pss.rate = SAMPLE_RATE; if ((pss.channels == 0) || (! g.s.bEchoMulti)) pss.channels = 1; pasSpeaker = pa_stream_new(pacContext, mumble_echo, &pss, (pss.channels == 1) ? NULL : &pcm); pa_stream_set_state_callback(pasSpeaker, stream_callback, this); pa_stream_set_read_callback(pasSpeaker, read_callback, this); } case PA_STREAM_UNCONNECTED: do_start = true; break; case PA_STREAM_READY: { if (g.s.bEchoMulti != bEchoMultiCache) { do_stop = true; } else if (edev != qsEchoCache) { do_stop = true; } break; } default: break; } } if (do_stop) { qWarning("PulseAudio: Stopping echo"); pa_stream_disconnect(pasSpeaker); } else if (do_start) { qWarning("PulseAudio: Starting echo: %s", qPrintable(edev)); pa_buffer_attr buff; const pa_sample_spec *pss = pa_stream_get_sample_spec(pasSpeaker); const size_t sampleSize = (pss->format == PA_SAMPLE_FLOAT32NE) ? sizeof(float) : sizeof(short); const unsigned int iBlockLen = ((pai->iFrameSize * pss->rate) / SAMPLE_RATE) * pss->channels * static_cast<unsigned int>(sampleSize); buff.tlength = iBlockLen; buff.minreq = iBlockLen; buff.maxlength = -1; buff.prebuf = -1; buff.fragsize = iBlockLen; bEchoMultiCache = g.s.bEchoMulti; qsEchoCache = edev; pa_stream_connect_record(pasSpeaker, qPrintable(edev), &buff, PA_STREAM_ADJUST_LATENCY); } } }
int JackAudioSystem::process_callback(jack_nframes_t nframes, void *arg) { JackAudioSystem * const jas = static_cast<JackAudioSystem*>(arg); if (jas && jas->bJackIsGood) { AudioInputPtr ai = g.ai; AudioOutputPtr ao = g.ao; JackAudioInput * const jai = dynamic_cast<JackAudioInput *>(ai.get()); JackAudioOutput * const jao = dynamic_cast<JackAudioOutput *>(ao.get()); if (jai && jai->isRunning() && jai->iMicChannels > 0 && !jai->isFinished()) { QMutexLocker(&jai->qmMutex); void *input = jack_port_get_buffer(jas->in_port, nframes); if (input != NULL) { jai->addMic(input, nframes); } } if (jao && jao->isRunning() && jao->iChannels > 0 && !jao->isFinished()) { QMutexLocker(&jao->qmMutex); jack_default_audio_sample_t *port_buffers[JACK_MAX_OUTPUT_PORTS]; for (unsigned int i = 0; i < jao->iChannels; ++i) { port_buffers[i] = (jack_default_audio_sample_t*)jack_port_get_buffer(jas->out_ports[i], nframes); if (port_buffers[i] == NULL) { return 1; } } jack_default_audio_sample_t * const buffer = jas->output_buffer; memset(buffer, 0, sizeof(jack_default_audio_sample_t) * nframes * jao->iChannels); jao->mix(buffer, nframes); if (jao->iChannels == 1) { memcpy(port_buffers[0], buffer, sizeof(jack_default_audio_sample_t) * nframes); } else { // de-interleave channels for (unsigned int i = 0; i < nframes * jao->iChannels; ++i) { port_buffers[i % jao->iChannels][i / jao->iChannels] = buffer[i]; } } } } return 0; }