/* * vim: softtabstop=4 shiftwidth=4 cindent foldmethod=marker expandtab * * $LastChangedDate$ * $Revision$ * $LastChangedBy$ * $URL$ * * Copyright 2009-2011 Eric Connell * * This file is part of Mangler. * * Mangler is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Mangler 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Mangler. If not, see . */ #include "mangler.h" #include "mangleraudio.h" #include "mangler-sounds.h" #include #include "channeltree.h" #include "manglersettings.h" ManglerAudio::ManglerAudio(int type, uint32_t rate, uint8_t channels, uint32_t pcm_framesize, uint8_t buffer, bool check_loggedin) {/*{{{*/ this->type = type; this->rate = rate; this->channels = channels; this->pcm_framesize = pcm_framesize; this->buffer = buffer; this->check_loggedin = check_loggedin; outputStreamOpen = false; inputStreamOpen = false; backend = NULL; if (type < AUDIO_INPUT || !rate) { return; } if (type >= AUDIO_OUTPUT) { if (!open()) { return; } outputStreamOpen = true; stop_output = false; pcm_queue = g_async_queue_new(); Glib::Thread::create(sigc::mem_fun(*this, &ManglerAudio::output), false); } else { if (!pcm_framesize) { fprintf(stderr, "pcm frame size not specified on input stream open; unsupported codec?\n"); return; } if (!open()) { return; } inputStreamOpen = true; stop_input = false; Glib::Thread::create(sigc::mem_fun(*this, &ManglerAudio::input), false); } }/*}}}*/ ManglerAudio::~ManglerAudio() {/*{{{*/ if (backend) { delete backend; } }/*}}}*/ bool ManglerAudio::switchBackend(Glib::ustring audioSubsystem) {/*{{{*/ ManglerBackend *backend; if (!(backend = ManglerBackend::getBackend(audioSubsystem, rate, channels, pcm_framesize))) { return false; } if (this->backend) { delete this->backend; } this->backend = backend; return true; }/*}}}*/ bool ManglerAudio::open(void) {/*{{{*/ Glib::ustring direction; switch (type) { case AUDIO_INPUT: direction = "Input"; break; default: case AUDIO_OUTPUT: direction = "Output"; break; case AUDIO_NOTIFY: direction = "Notification"; break; } Glib::ustring device = Mangler::config[direction + "DeviceName"].toUString(); if (device == "Default") { device = ""; } else if (device == "Custom") { device = Mangler::config[direction + "DeviceCustomName"].toUString(); } close(); if (!backend && !switchBackend(Mangler::config["AudioSubsystem"].toUString())) { return false; } if (!backend->open(type, device, rate, channels)) { return false; } return true; }/*}}}*/ void ManglerAudio::close(bool drain) {/*{{{*/ if (backend) { backend->close(drain); } }/*}}}*/ void ManglerAudio::queue(uint32_t length, uint8_t *sample) {/*{{{*/ if (!outputStreamOpen) { return; } g_async_queue_push(pcm_queue, new ManglerPCM(length, sample)); }/*}}}*/ void ManglerAudio::finish(bool drop) {/*{{{*/ if (outputStreamOpen) { stop_output = drop; g_async_queue_push(pcm_queue, new ManglerPCM(0, NULL)); } if (inputStreamOpen) { stop_input = true; } }/*}}}*/ void ManglerAudio::output(void) {/*{{{*/ ManglerPCM *queuedpcm = NULL; if (!pcm_queue) { return; } g_async_queue_ref(pcm_queue); for (;;) { if (stop_output || (check_loggedin && !v3_is_loggedin())) { close(); break; } if (buffer) { buffer--; ManglerPCM *bufferpcm = (ManglerPCM *)g_async_queue_pop(pcm_queue); if (bufferpcm && bufferpcm->length) { uint32_t prelen = (queuedpcm) ? queuedpcm->length : 0; uint32_t buflen = prelen + bufferpcm->length; uint8_t *bufpcm = (uint8_t *)malloc(buflen); memcpy(bufpcm + prelen, bufferpcm->sample, bufferpcm->length); if (queuedpcm) { memcpy(bufpcm, queuedpcm->sample, queuedpcm->length); delete queuedpcm; } delete bufferpcm; queuedpcm = new ManglerPCM(buflen, bufpcm); free(bufpcm); continue; } else { buffer = 0; finish(); } } if (!queuedpcm) { queuedpcm = (ManglerPCM *)g_async_queue_pop(pcm_queue); } // finish() queues a 0 length packet to notify us that we're done if (!queuedpcm->length) { close(true); break; } if (mangler->muteSound) { delete queuedpcm; queuedpcm = NULL; continue; } if (Mangler::config["AudioSubsystem"].toLower() != backend->getAudioSubsystem()) { if (!switchBackend(Mangler::config["AudioSubsystem"].toLower()) || !open()) { break; } } if (!backend->write(queuedpcm->sample, queuedpcm->length, channels)) { close(); break; } delete queuedpcm; queuedpcm = NULL; } outputStreamOpen = false; close(); while (queuedpcm || (queuedpcm = (ManglerPCM *)g_async_queue_try_pop(pcm_queue))) { delete queuedpcm; queuedpcm = NULL; } g_async_queue_unref(pcm_queue); delete this; return; }/*}}}*/ void ManglerAudio::input(void) {/*{{{*/ uint8_t *buf = NULL; struct timeval start, vastart, now, diff; int ctr; bool drop, xmit = false; float seconds = 0; register float pcmpeak = 0; float midpeak = 0; float vasilencedur, vasilenceelapsed; uint8_t vapercent; for (;;) { if (stop_input) { break; } gettimeofday(&start, NULL); ctr = 0; seconds = 0; pcmpeak = 0; midpeak = 0; // need to send ~0.115 seconds of audio for each packet for (;;) { /* if (seconds >= 0.115) { //fprintf(stderr, "0.115 seconds of real time has elapsed\n"); break; } */ if (pcm_framesize * ctr > rate * sizeof(int16_t) * 0.115) { //fprintf(stderr, "we have 0.115 seconds of audio in %d iterations\n", ctr); break; } if ((pcm_framesize*(ctr+1)) > 16384) { fprintf(stderr, "audio frames are greater than buffer size. dropping audio frames after %f seconds\n", seconds); drop = true; break; } drop = false; //fprintf(stderr, "reallocating %d bytes of memory\n", pcm_framesize*(ctr+1)); buf = (uint8_t *)realloc(buf, pcm_framesize*(ctr+1)); //fprintf(stderr, "reading %d bytes of memory to %lu\n", pcm_framesize, buf+(pcm_framesize*ctr)); if (Mangler::config["AudioSubsystem"].toLower() != backend->getAudioSubsystem()) { if (!switchBackend(Mangler::config["AudioSubsystem"].toLower()) || !open()) { stop_input = true; break; } } if (!backend->read(buf+(pcm_framesize*ctr))) { close(); stop_input = true; break; } gettimeofday(&now, NULL); timeval_subtract(&diff, &now, &start); seconds = (float)diff.tv_sec + ((float)diff.tv_usec / 1000000.0); //fprintf(stderr, "iteration after %f seconds with %d bytes\n", seconds, pcm_framesize*ctr); for (int16_t *pcmptr = (int16_t *)(buf+(pcm_framesize*ctr)); pcmptr < (int16_t *)(buf+(pcm_framesize*(ctr+1))); pcmptr++) { pcmpeak = abs(*pcmptr) > pcmpeak ? abs(*pcmptr) : pcmpeak; } if (seconds >= 0.115 / 2 && !midpeak && pcmpeak) { midpeak = log10(((pcmpeak / 0x7fff) * 9) + 1); midpeak = (midpeak > 1) ? 1 : midpeak; gdk_threads_enter(); mangler->inputvumeter->set_fraction(midpeak); gdk_threads_leave(); } ctr++; } if (!stop_input) { pcmpeak = log10(((pcmpeak / 0x7fff) * 9) + 1); pcmpeak = (pcmpeak > 1) ? 1 : pcmpeak; gdk_threads_enter(); if (Mangler::config["VoiceActivationEnabled"].toBool() && !mangler->isTransmittingKey && !mangler->isTransmittingMouse && !mangler->isTransmittingButton) { vasilencedur = Mangler::config["VoiceActivationSilenceDuration"].toInt(); vapercent = Mangler::config["VoiceActivationSensitivity"].toUInt(); if (pcmpeak * 100 >= vapercent) { gettimeofday(&vastart, NULL); if (!xmit) { xmit = true; mangler->audioControl->playNotification("talkstart"); mangler->statusIcon->set(mangler->icons["tray_icon_green"]); v3_start_audio(V3_AUDIO_SENDTYPE_U2CCUR); } mangler->channelTree->setUserIcon(v3_get_user_id(), "green", true); } else if (xmit) { gettimeofday(&now, NULL); timeval_subtract(&diff, &now, &vastart); vasilenceelapsed = (float)diff.tv_sec + ((float)diff.tv_usec / 1000000.0); if (vasilenceelapsed * 1000 >= vasilencedur) { xmit = false; v3_stop_audio(); mangler->channelTree->setUserIcon(v3_get_user_id(), "orange", true); mangler->statusIcon->set(mangler->icons["tray_icon_yellow"]); mangler->audioControl->playNotification("talkend"); } else { mangler->channelTree->setUserIcon(v3_get_user_id(), "yellow", true); } } } else { if (!xmit) { xmit = true; mangler->audioControl->playNotification("talkstart"); v3_start_audio(V3_AUDIO_SENDTYPE_U2CCUR); } mangler->channelTree->setUserIcon(v3_get_user_id(), "green", true); mangler->statusIcon->set(mangler->icons["tray_icon_green"]); } mangler->inputvumeter->set_fraction(pcmpeak); gdk_threads_leave(); if (!drop && xmit) { //fprintf(stderr, "sending %d bytes of audio\n", pcm_framesize * ctr); // TODO: hard coding user to channel for now, need to implement U2U uint32_t ret; if ((ret = v3_send_audio(V3_AUDIO_SENDTYPE_U2CCUR, rate, buf, pcm_framesize * ctr, false)) != rate) { if (!(rate = ret) || !open()) { stop_input = true; } } } } free(buf); buf = NULL; } inputStreamOpen = false; close(); gdk_threads_enter(); if (xmit) { xmit = false; v3_stop_audio(); mangler->audioControl->playNotification("talkend"); } if (v3_is_loggedin()) { mangler->channelTree->setUserIcon(v3_get_user_id(), "red"); mangler->statusIcon->set(mangler->icons["tray_icon_red"]); } mangler->inputvumeter->set_fraction(0); if (mangler->inputAudio == this) { mangler->inputAudio = NULL; } gdk_threads_leave(); delete this; return; }/*}}}*/ void ManglerAudio::getDeviceList(Glib::ustring audioSubsystem) {/*{{{*/ outputDevices.clear(); inputDevices.clear(); ManglerBackend::getDeviceList(audioSubsystem, inputDevices, outputDevices); }/*}}}*/ void ManglerAudio::playNotification(Glib::ustring name) {/*{{{*/ if (mangler->muteSound) { return; } if ((name == "talkstart" || name == "talkend") && !Mangler::config["NotificationTransmitStartStop"].toBool()) { return; } if ((name == "channelenter" || name == "channelleave") && !Mangler::config["NotificationChannelEnterLeave"].toBool()) { return; } if ((name == "login" || name == "logout") && !Mangler::config["NotificationLoginLogout"].toBool()) { return; } if (sounds.empty()) { sounds["talkstart"] = new ManglerPCM(sizeof(sound_talkstart), sound_talkstart); sounds["talkend"] = new ManglerPCM(sizeof(sound_talkend), sound_talkend); sounds["channelenter"] = new ManglerPCM(sizeof(sound_channelenter), sound_channelenter); sounds["channelleave"] = new ManglerPCM(sizeof(sound_channelleave), sound_channelleave); sounds["login"] = new ManglerPCM(sizeof(sound_login), sound_login); sounds["logout"] = new ManglerPCM(sizeof(sound_logout), sound_logout); } ManglerAudio *notify = new ManglerAudio(AUDIO_NOTIFY, 44100, 1, 0, 0, false); notify->queue(sounds[name]->length, sounds[name]->sample); notify->finish(); }/*}}}*/ #ifdef HAVE_ESPEAK int espeak_synth_cb(short *wav, int numsamples, espeak_EVENT *events) {/*{{{*/ ManglerAudio *tts = (ManglerAudio *)events->user_data; if (!numsamples) { if (tts) { tts->finish(); events->user_data = NULL; } return 1; } tts->queue(numsamples * sizeof(short), (uint8_t *)wav); return 0; }/*}}}*/ #endif void ManglerAudio::playText(Glib::ustring text) {/*{{{*/ if (!text.length() || !Mangler::config["NotificationTextToSpeech"].toBool()) { return; } #ifdef HAVE_ESPEAK espeak_SetSynthCallback(espeak_synth_cb); ManglerAudio *tts = new ManglerAudio(AUDIO_NOTIFY, mangler->espeakRate, 1, 0, 0, false); if (espeak_Synth(text.c_str(), text.length() + 1, 0, POS_CHARACTER, 0, espeakCHARS_AUTO, NULL, tts) != EE_OK) { tts->finish(); fprintf(stderr, "espeak: synth error\n"); return; } #endif }/*}}}*/ int timeval_subtract(struct timeval *result, struct timeval *x, struct timeval *y) {/*{{{*/ /* Perform the carry for the later subtraction by updating y. */ if (x->tv_usec < y->tv_usec) { int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1; y->tv_usec -= 1000000 * nsec; y->tv_sec += nsec; } if (x->tv_usec - y->tv_usec > 1000000) { int nsec = (x->tv_usec - y->tv_usec) / 1000000; y->tv_usec += 1000000 * nsec; y->tv_sec -= nsec; } /* Compute the time remaining to wait. tv_usec is certainly positive. */ result->tv_sec = x->tv_sec - y->tv_sec; result->tv_usec = x->tv_usec - y->tv_usec; /* Return 1 if result is negative. */ return x->tv_sec < y->tv_sec; }/*}}}*/