| @@ -0,0 +1,349 @@ | ||
| /* | ||
| * Copyright (C) 2014 Maxim Noah Khailo | ||
| * | ||
| * This program 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 vedit_refsion 3 of the License, or | ||
| * (at your option) any later vedit_refsion. | ||
| * | ||
| * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. | ||
| * | ||
| * In addition, as a special exception, the copyright holders give | ||
| * permission to link the code of portions of this program with the | ||
| * Botan library under certain conditions as described in each | ||
| * individual source file, and distribute linked combinations | ||
| * including the two. | ||
| * | ||
| * You must obey the GNU General Public License in all respects for | ||
| * all of the code used other than Botan. If you modify file(s) with | ||
| * this exception, you may extend this exception to your version of the | ||
| * file(s), but you are not obligated to do so. If you do not wish to do | ||
| * so, delete this exception statement from your version. If you delete | ||
| * this exception statement from all source files in the program, then | ||
| * also delete it here. | ||
| */ | ||
| #include <QtWidgets> | ||
| #include "gui/qtw/audio.hpp" | ||
| #include "gui/qtw/frontend.hpp" | ||
| #include "gui/util.hpp" | ||
| #include "util/dbc.hpp" | ||
| #include "util/log.hpp" | ||
| #include <QAudioDeviceInfo> | ||
| namespace u = fire::util; | ||
| namespace fire | ||
| { | ||
| namespace gui | ||
| { | ||
| namespace qtw | ||
| { | ||
| namespace | ||
| { | ||
| const size_t FRAMES = u::FRAMES; //40ms of PCM frames. Opus can handles 2.5, 5, 10, 20, 40 or 60ms of audio per frame. | ||
| const size_t SAMPLE_RATE = u::SAMPLE_RATE; | ||
| const size_t SAMPLE_SIZE = 16; | ||
| const size_t MAX_SAMPLE_BYTES = SAMPLE_SIZE * FRAMES; | ||
| const size_t CHANNELS = u::CHANNELS; | ||
| const std::string Q_CODEC = "audio/pcm"; | ||
| const size_t MIN_BUF_SIZE = u::MIN_BUF_SIZE; | ||
| } | ||
| codec_type parse_codec(const std::string& codec) | ||
| { | ||
| if(codec == "pcm") return codec_type::pcm; | ||
| else if(codec == "opus") return codec_type::opus; | ||
| return codec_type::pcm; | ||
| } | ||
| microphone::microphone(api::backend* back, qt_frontend* front, api::ref_id id, const std::string& codec) : | ||
| _id{id}, _back{back}, _front{front} | ||
| { | ||
| REQUIRE(back); | ||
| REQUIRE(front); | ||
| INVARIANT(_back); | ||
| INVARIANT(_front); | ||
| _f.setSampleRate(SAMPLE_RATE); | ||
| _f.setChannelCount(CHANNELS); | ||
| _f.setSampleSize(SAMPLE_SIZE); | ||
| _f.setSampleType(QAudioFormat::SignedInt); | ||
| _f.setByteOrder(QAudioFormat::LittleEndian); | ||
| _f.setCodec(Q_CODEC.c_str()); | ||
| _t = parse_codec(codec); | ||
| _inf = QAudioDeviceInfo::defaultInputDevice(); | ||
| if (!_inf.isFormatSupported(_f)) | ||
| { | ||
| _f = _inf.nearestFormat(_f); | ||
| LOG << "format not supported, using nearest." << std::endl; | ||
| LOG << "sample rate: " << _f.sampleRate() << std::endl; | ||
| LOG << "sample size: " << _f.sampleSize() << std::endl; | ||
| LOG << "channels: " << _f.channelCount() << std::endl; | ||
| LOG << "codec: " << convert(_f.codec()) << std::endl; | ||
| } | ||
| LOG << "using mic device: " << convert(_inf.deviceName()) << std::endl; | ||
| _i = new QAudioInput{_inf, _f, _front->canvas}; | ||
| CHECK_GREATER_EQUAL(static_cast<size_t>(_f.sampleRate()), SAMPLE_RATE); | ||
| _skip = _f.sampleRate() / SAMPLE_RATE; | ||
| _channels = _f.channelCount(); | ||
| if(_t == codec_type::opus) _opus = std::make_shared<u::opus_encoder>(); | ||
| } | ||
| //simple low pass filter | ||
| void reduce_noise(u::bytes& s, size_t len) | ||
| { | ||
| REQUIRE_EQUAL(s.size() % 2, 0); | ||
| auto ss = reinterpret_cast<short*>(s.data()); | ||
| len /= 2; | ||
| for(size_t i = 1; i < len; i++) | ||
| ss[i] = (0.333 * ss[i]) + ((1 - 0.333) * ss[i-1]) + 0.5; | ||
| } | ||
| //decimate sound to SAMPLE_RATE, using averaging | ||
| void decimate(const u::bytes& s, u::bytes& d, size_t channels, size_t skip) | ||
| { | ||
| REQUIRE_FALSE(s.empty()); | ||
| REQUIRE_EQUAL(s.size() % 2, 0); | ||
| //get sizes | ||
| auto dz = (s.size() / skip); | ||
| auto nz = d.size() + dz; | ||
| //add padding | ||
| if(nz % 2 == 1) nz += 1; | ||
| CHECK_EQUAL(nz % 2, 0); | ||
| //resize dest | ||
| const auto odz = d.size(); | ||
| d.resize(nz); | ||
| //cast to short arrays | ||
| auto ss = reinterpret_cast<const short*>(s.data()); | ||
| const auto sz = s.size() / 2; | ||
| auto sd = reinterpret_cast<short*>(d.data()); | ||
| const auto sdz = nz / 2; | ||
| int accum = 0; | ||
| size_t c = 1; | ||
| size_t si = 0; | ||
| auto di = odz / 2; | ||
| for(;si < sz; si+=channels) | ||
| { | ||
| accum += static_cast<int>(ss[si]); | ||
| if(c == skip) | ||
| { | ||
| accum /= c; | ||
| sd[di] = accum; | ||
| di++; | ||
| accum = 0; | ||
| c = 1; | ||
| continue; | ||
| } | ||
| c++; | ||
| } | ||
| //repeat last value if we have padding | ||
| si = sz-1; | ||
| while(di < sdz) | ||
| { | ||
| sd[di] = ss[si]; | ||
| di++; | ||
| } | ||
| CHECK_EQUAL(di, sdz); | ||
| } | ||
| u::bytes microphone::encode(const u::bytes& b) | ||
| { | ||
| REQUIRE(_opus); | ||
| REQUIRE_FALSE(b.empty()); | ||
| return _opus->encode(b); | ||
| } | ||
| u::bytes microphone::read_data() | ||
| { | ||
| REQUIRE(_d); | ||
| if(!_d) return {}; | ||
| INVARIANT(_i); | ||
| auto len = _i->bytesReady(); | ||
| if(len <= 0) return {}; | ||
| if(static_cast<size_t>(len) > MAX_SAMPLE_BYTES) len = MAX_SAMPLE_BYTES; | ||
| u::bytes data; | ||
| data.resize(len); | ||
| auto l = _d->read(data.data(), len); | ||
| if(l <= 0) return {}; | ||
| data.resize(l); | ||
| //decimate and add to buffer | ||
| decimate(data, _buffer, _channels, _skip); | ||
| if(_buffer.size() < MIN_BUF_SIZE) return {}; | ||
| //once we have enough data, do noise reduction | ||
| reduce_noise(_buffer, MIN_BUF_SIZE); | ||
| //copy buffer to result | ||
| u::bytes r(_buffer.begin(), _buffer.begin() + MIN_BUF_SIZE); | ||
| //copy extra to front and resize. Probably a better idea to use a circular buf here. | ||
| auto final_buf_size = _buffer.size() - MIN_BUF_SIZE; | ||
| std::copy(_buffer.begin() + MIN_BUF_SIZE, _buffer.end(), _buffer.begin()); | ||
| _buffer.resize(final_buf_size); | ||
| return r; | ||
| } | ||
| codec_type microphone::codec() const | ||
| { | ||
| return _t; | ||
| } | ||
| bool microphone::recording() const | ||
| { | ||
| return _recording; | ||
| } | ||
| void microphone::stop() | ||
| { | ||
| INVARIANT(_i); | ||
| _recording = false; | ||
| } | ||
| void microphone::start() | ||
| { | ||
| INVARIANT(_i); | ||
| INVARIANT(_back); | ||
| if(!_d) | ||
| { | ||
| _d = _i->start(); | ||
| if(_d) _front->connect_sound(_id, _i, _d); | ||
| } | ||
| _recording = true; | ||
| } | ||
| void inflate(const u::bytes& s, u::bytes& d, size_t channels, size_t rep) | ||
| { | ||
| REQUIRE_EQUAL(s.size() % 2, 0); | ||
| rep*=channels; | ||
| d.resize(s.size() * rep); | ||
| auto ss = reinterpret_cast<const short*>(s.data()); | ||
| auto sz = s.size() / 2; | ||
| auto sd = reinterpret_cast<short*>(d.data()); | ||
| size_t di = 0; | ||
| for(size_t si = 0; si < sz; si++) | ||
| for(size_t p = 0; p < rep; p++, di++) | ||
| sd[di] = ss[si]; | ||
| } | ||
| speaker::speaker(api::backend* back, qt_frontend* front, const std::string& codec) : _back{back}, _front{front} | ||
| { | ||
| REQUIRE(back); | ||
| REQUIRE(front); | ||
| INVARIANT(_back); | ||
| INVARIANT(_front); | ||
| _f.setSampleRate(SAMPLE_RATE); | ||
| _f.setChannelCount(CHANNELS); | ||
| _f.setSampleSize(SAMPLE_SIZE); | ||
| _f.setSampleType(QAudioFormat::SignedInt); | ||
| _f.setByteOrder(QAudioFormat::LittleEndian); | ||
| _f.setCodec(Q_CODEC.c_str()); | ||
| _t = parse_codec(codec); | ||
| QAudioDeviceInfo i{QAudioDeviceInfo::defaultOutputDevice()}; | ||
| if (!i.isFormatSupported(_f)) _f = i.nearestFormat(_f); | ||
| LOG << "using speaker device: " << convert(i.deviceName()) << std::endl; | ||
| _o = new QAudioOutput{i, _f, _front}; | ||
| CHECK_GREATER_EQUAL(static_cast<size_t>(_f.sampleRate()), SAMPLE_RATE); | ||
| _rep = _f.sampleRate() / SAMPLE_RATE; | ||
| _channels = _f.channelCount(); | ||
| if(_t == codec_type::opus) _opus = std::make_shared<u::opus_decoder>(); | ||
| } | ||
| u::bytes speaker::decode(const u::bytes& b) | ||
| { | ||
| REQUIRE(_opus); | ||
| return _opus->decode(b); | ||
| } | ||
| codec_type speaker::codec() const | ||
| { | ||
| return _t; | ||
| } | ||
| void speaker::mute() | ||
| { | ||
| INVARIANT(_o); | ||
| _mute = true; | ||
| } | ||
| void speaker::unmute() | ||
| { | ||
| INVARIANT(_o); | ||
| _mute = false; | ||
| } | ||
| void speaker::play(const u::bytes& d) | ||
| { | ||
| INVARIANT(_o); | ||
| if(_mute) return; | ||
| if(d.empty()) return; | ||
| const u::bytes* data = &d; | ||
| u::bytes dec; | ||
| if(_t == codec_type::opus) | ||
| { | ||
| dec = decode(d); | ||
| data = &dec; | ||
| } | ||
| CHECK(data); | ||
| //repeat to match speaker sample rate | ||
| u::bytes r; | ||
| inflate(*data, r, _channels, _rep); | ||
| if(_d) | ||
| { | ||
| _d->write(r.data(), r.size()); | ||
| if(_o->state() == QAudio::SuspendedState) | ||
| { | ||
| _o->reset(); | ||
| _o->resume(); | ||
| } | ||
| } | ||
| else | ||
| { | ||
| _d = _o->start(); | ||
| _d->write(r.data(), r.size()); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
| @@ -0,0 +1,113 @@ | ||
| /* | ||
| * Copyright (C) 2014 Maxim Noah Khailo | ||
| * | ||
| * This program 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 vedit_refsion 3 of the License, or | ||
| * (at your option) any later vedit_refsion. | ||
| * | ||
| * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. | ||
| * | ||
| * In addition, as a special exception, the copyright holders give | ||
| * permission to link the code of portions of this program with the | ||
| * Botan library under certain conditions as described in each | ||
| * individual source file, and distribute linked combinations | ||
| * including the two. | ||
| * | ||
| * You must obey the GNU General Public License in all respects for | ||
| * all of the code used other than Botan. If you modify file(s) with | ||
| * this exception, you may extend this exception to your version of the | ||
| * file(s), but you are not obligated to do so. If you do not wish to do | ||
| * so, delete this exception statement from your version. If you delete | ||
| * this exception statement from all source files in the program, then | ||
| * also delete it here. | ||
| */ | ||
| #ifndef FIRESTR_GUI_QTW_AUDIO_H | ||
| #define FIRESTR_GUI_QTW_AUDIO_H | ||
| #include "gui/api/service.hpp" | ||
| #include "util/audio.hpp" | ||
| #include <QAudioFormat> | ||
| #include <QAudioInput> | ||
| #include <QAudioOutput> | ||
| #include <string> | ||
| #include <unordered_map> | ||
| #include <opus/opus.h> | ||
| namespace fire | ||
| { | ||
| namespace gui | ||
| { | ||
| namespace qtw | ||
| { | ||
| class qt_frontend; | ||
| enum codec_type { pcm, opus}; | ||
| class microphone | ||
| { | ||
| public: | ||
| microphone(api::backend*, qt_frontend*, api::ref_id id, const std::string& codec = "pcm"); | ||
| void stop(); | ||
| void start(); | ||
| bool recording() const; | ||
| codec_type codec() const; | ||
| util::bytes encode(const util::bytes&); | ||
| util::bytes read_data(); | ||
| private: | ||
| QAudioFormat _f; | ||
| QAudioDeviceInfo _inf; | ||
| QAudioInput* _i; | ||
| codec_type _t; | ||
| api::ref_id _id; | ||
| QIODevice* _d = nullptr; | ||
| bool _recording = false; | ||
| api::backend* _back; | ||
| qt_frontend* _front; | ||
| //opus specific members | ||
| util::opus_encoder_ptr _opus; | ||
| util::bytes _buffer; | ||
| size_t _skip = 0; | ||
| size_t _channels = 0; | ||
| }; | ||
| using microphone_ptr = std::shared_ptr<microphone>; | ||
| struct speaker | ||
| { | ||
| public: | ||
| speaker(api::backend*, qt_frontend*, const std::string& code = "pcm"); | ||
| void mute(); | ||
| void unmute(); | ||
| void play(const util::bytes&); | ||
| codec_type codec() const; | ||
| util::bytes decode(const util::bytes&); | ||
| private: | ||
| bool _mute = false; | ||
| QAudioFormat _f; | ||
| codec_type _t; | ||
| QAudioOutput* _o; | ||
| QIODevice* _d = nullptr; | ||
| api::backend* _back; | ||
| qt_frontend* _front; | ||
| //opus specific members | ||
| util::opus_decoder_ptr _opus; | ||
| size_t _rep = 0; | ||
| size_t _channels = 0; | ||
| }; | ||
| using speaker_ptr = std::shared_ptr<speaker>; | ||
| } | ||
| } | ||
| } | ||
| #endif |
| @@ -0,0 +1,240 @@ | ||
| /* | ||
| * Copyright (C) 2014 Maxim Noah Khailo | ||
| * | ||
| * This program 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 vedit_refsion 3 of the License, or | ||
| * (at your option) any later vedit_refsion. | ||
| * | ||
| * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. | ||
| * | ||
| * In addition, as a special exception, the copyright holders give | ||
| * permission to link the code of portions of this program with the | ||
| * Botan library under certain conditions as described in each | ||
| * individual source file, and distribute linked combinations | ||
| * including the two. | ||
| * | ||
| * You must obey the GNU General Public License in all respects for | ||
| * all of the code used other than Botan. If you modify file(s) with | ||
| * this exception, you may extend this exception to your version of the | ||
| * file(s), but you are not obligated to do so. If you do not wish to do | ||
| * so, delete this exception statement from your version. If you delete | ||
| * this exception statement from all source files in the program, then | ||
| * also delete it here. | ||
| */ | ||
| #ifndef FIRESTR_GUI_QTW_H | ||
| #define FIRESTR_GUI_QTW_H | ||
| #include "gui/app/app.hpp" | ||
| #include "gui/api/service.hpp" | ||
| #include "gui/qtw/audio.hpp" | ||
| #include "gui/list.hpp" | ||
| #include "conversation/conversation_service.hpp" | ||
| #include <QImage> | ||
| #include <QGraphicsView> | ||
| namespace fire | ||
| { | ||
| namespace gui | ||
| { | ||
| namespace qtw | ||
| { | ||
| using QImage_ptr = std::shared_ptr<QImage>; | ||
| using widget_map = std::unordered_map<api::ref_id, QWidget*>; | ||
| using image_map = std::unordered_map<api::ref_id, QImage_ptr>; | ||
| using layout_map = std::unordered_map<api::ref_id, QGridLayout*>; | ||
| using timer_map = std::unordered_map<api::ref_id, QTimer*>; | ||
| using callback_map = std::unordered_map<std::string, std::string>; | ||
| using pen_map = std::unordered_map<api::ref_id, QPen>; | ||
| using mic_map = std::unordered_map<api::ref_id, microphone_ptr>; | ||
| using spk_map = std::unordered_map<api::ref_id, speaker_ptr>; | ||
| class qt_frontend; | ||
| class draw_view : public QGraphicsView | ||
| { | ||
| Q_OBJECT | ||
| public: | ||
| draw_view(qt_frontend*, | ||
| api::backend*, | ||
| api::ref_id, | ||
| int width, | ||
| int height, | ||
| QWidget* parent = nullptr); | ||
| protected: | ||
| void mousePressEvent(QMouseEvent*); | ||
| void mouseReleaseEvent(QMouseEvent*); | ||
| void mouseMoveEvent(QMouseEvent*); | ||
| private: | ||
| api::ref_id _id; | ||
| qt_frontend* _front; | ||
| api::backend* _back; | ||
| int _button; | ||
| }; | ||
| class qt_frontend : public QObject, public api::frontend | ||
| { | ||
| Q_OBJECT | ||
| public: | ||
| qt_frontend(QWidget* c, QGridLayout* cl, list* output); | ||
| public: | ||
| void set_backend(api::backend*); | ||
| public: | ||
| //all widgets | ||
| virtual void place(api::ref_id, int r, int c); | ||
| virtual void place_across(api::ref_id id, int r, int c, int row_span, int col_span); | ||
| virtual void widget_enable(api::ref_id, bool); | ||
| virtual bool is_widget_enabled(api::ref_id); | ||
| //grid | ||
| virtual void add_grid(api::ref_id); | ||
| virtual void grid_place(api::ref_id grid_id, api::ref_id widget_id, int r, int c); | ||
| virtual void grid_place_across(api::ref_id grid_id, api::ref_id widget_id, int r, int c, int row_span, int col_span); | ||
| //button | ||
| virtual void add_button(api::ref_id, const std::string&); | ||
| virtual std::string button_get_text(api::ref_id); | ||
| virtual void button_set_text(api::ref_id, const std::string&); | ||
| virtual void button_set_image(api::ref_id id, api::ref_id image_id); | ||
| //label | ||
| virtual void add_label(api::ref_id, const std::string& text); | ||
| virtual std::string label_get_text(api::ref_id); | ||
| virtual void label_set_text(api::ref_id, const std::string& text); | ||
| //edit | ||
| virtual void add_edit(api::ref_id id, const std::string& text); | ||
| virtual std::string edit_get_text(api::ref_id); | ||
| virtual void edit_set_text(api::ref_id, const std::string& text); | ||
| //text edit | ||
| virtual void add_text_edit(api::ref_id id, const std::string& text); | ||
| virtual std::string text_edit_get_text(api::ref_id); | ||
| virtual void text_edit_set_text(api::ref_id, const std::string& text); | ||
| //list | ||
| virtual void add_list(api::ref_id id); | ||
| virtual void list_add(api::ref_id list_id, api::ref_id widget_id); | ||
| virtual void list_remove(api::ref_id list_id, api::ref_id widget_id); | ||
| virtual size_t list_size(api::ref_id); | ||
| virtual void list_clear(api::ref_id); | ||
| //pen | ||
| virtual void add_pen(api::ref_id id, const std::string& color, int width); | ||
| virtual void pen_set_width(api::ref_id id, int width); | ||
| //draw | ||
| virtual void add_draw(api::ref_id id, int width, int height); | ||
| virtual void draw_line(api::ref_id id, api::ref_id pen_id, double x1, double y1, double x2, double y2); | ||
| virtual void draw_circle(api::ref_id id, api::ref_id pen_id, double x, double y, double r); | ||
| virtual void draw_image(api::ref_id id, api::ref_id image_id, double x, double y, double w, double h); | ||
| virtual void draw_clear(api::ref_id id); | ||
| //timer | ||
| virtual void add_timer(api::ref_id id, int msec); | ||
| virtual bool timer_running(api::ref_id id); | ||
| virtual void timer_stop(api::ref_id id); | ||
| virtual void timer_start(api::ref_id id); | ||
| virtual void timer_set_interval(api::ref_id, int msec); | ||
| //image | ||
| virtual bool add_image(api::ref_id, const util::bytes& d); | ||
| virtual int image_width(api::ref_id); | ||
| virtual int image_height(api::ref_id); | ||
| //mic | ||
| virtual void add_mic(api::ref_id id, const std::string& codec); | ||
| virtual void mic_start(api::ref_id); | ||
| virtual void mic_stop(api::ref_id); | ||
| //speaker | ||
| virtual void add_speaker(api::ref_id, const std::string& codec); | ||
| virtual void speaker_mute(api::ref_id); | ||
| virtual void speaker_unmute(api::ref_id); | ||
| virtual void speaker_play(api::ref_id, const util::bytes&); | ||
| //file | ||
| virtual api::file_data open_file(); | ||
| virtual api::bin_file_data open_bin_file(); | ||
| virtual bool save_file(const std::string&, const std::string&); | ||
| virtual bool save_bin_file(const std::string&, const util::bytes&); | ||
| //debug | ||
| virtual void print(const std::string&); | ||
| //overall gui | ||
| virtual void height(int h); | ||
| virtual void grow(); | ||
| virtual bool visible(); | ||
| //errors | ||
| virtual void report_error(const std::string& e); | ||
| virtual void reset(); | ||
| public slots: | ||
| void button_clicked(int id); | ||
| void edit_edited(int id); | ||
| void edit_finished(int id); | ||
| void text_edit_edited(int id); | ||
| void timer_triggered(int id); | ||
| void got_sound(int id); | ||
| public: | ||
| void connect_sound(api::ref_id id, QAudioInput* i, QIODevice* d); | ||
| private: | ||
| friend class microphone; | ||
| friend class speaker; | ||
| private: | ||
| //all widgets referenced are stored here | ||
| layout_map layouts; | ||
| widget_map widgets; | ||
| image_map images; | ||
| timer_map timers; | ||
| pen_map pens; | ||
| mic_map mics; | ||
| spk_map spkrs; | ||
| list* output = nullptr; | ||
| QWidget* canvas = nullptr; | ||
| QGridLayout* layout = nullptr; | ||
| api::backend* back = nullptr; | ||
| }; | ||
| using qt_frontend_ptr = std::shared_ptr<qt_frontend>; | ||
| template<class W, class M> | ||
| W* get_widget(int id, M& map) | ||
| { | ||
| auto wp = map.find(id); | ||
| return wp != map.end() ? dynamic_cast<W*>(wp->second) : nullptr; | ||
| } | ||
| template<class W, class M> | ||
| W get_ptr_from_map(int id, M& map) | ||
| { | ||
| auto wp = map.find(id); | ||
| return wp != map.end() ? wp->second : nullptr; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| #endif | ||
| @@ -0,0 +1,160 @@ | ||
| /* | ||
| * Copyright (C) 2014 Maxim Noah Khailo | ||
| * | ||
| * This program 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 vedit_refsion 3 of the License, or | ||
| * (at your option) any later vedit_refsion. | ||
| * | ||
| * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. | ||
| * | ||
| * In addition, as a special exception, the copyright holders give | ||
| * permission to link the code of portions of this program with the | ||
| * Botan library under certain conditions as described in each | ||
| * individual source file, and distribute linked combinations | ||
| * including the two. | ||
| * | ||
| * You must obey the GNU General Public License in all respects for | ||
| * all of the code used other than Botan. If you modify file(s) with | ||
| * this exception, you may extend this exception to your version of the | ||
| * file(s), but you are not obligated to do so. If you do not wish to do | ||
| * so, delete this exception statement from your version. If you delete | ||
| * this exception statement from all source files in the program, then | ||
| * also delete it here. | ||
| */ | ||
| #ifndef FIRESTR_UTIL_AUDIO_H | ||
| #define FIRESTR_UTIL_AUDIO_H | ||
| #include "util/audio.hpp" | ||
| #include "util/dbc.hpp" | ||
| #include "util/log.hpp" | ||
| namespace u = fire::util; | ||
| namespace fire | ||
| { | ||
| namespace util | ||
| { | ||
| const size_t FRAMES = 480; //40ms of PCM frames. Opus can handles 2.5, 5, 10, 20, 40 or 60ms of audio per frame. | ||
| const size_t MAX_FRAMES = 2*FRAMES; | ||
| const size_t MAX_OPUS_DECODE_SIZE = MAX_FRAMES * sizeof(opus_int16); | ||
| const size_t SAMPLE_RATE = 12000; | ||
| const size_t CHANNELS = 1; | ||
| const size_t MIN_BUF_SIZE = FRAMES * sizeof(opus_int16); | ||
| void log_opus_error(int e) | ||
| { | ||
| switch(e) | ||
| { | ||
| case OPUS_ALLOC_FAIL: LOG << "BAD ALLOC" << std::endl; break; | ||
| case OPUS_BAD_ARG: LOG << "BAD ARG" << std::endl; break; | ||
| case OPUS_BUFFER_TOO_SMALL: LOG << "TOO SMALL" << std::endl; break; | ||
| case OPUS_INTERNAL_ERROR: LOG << "INTERNAL ERR" << std::endl; break; | ||
| case OPUS_INVALID_PACKET: LOG << "INVALID PACKET" << std::endl; break; | ||
| case OPUS_INVALID_STATE: LOG << "INVALID STATE" << std::endl; break; | ||
| case OPUS_OK: LOG << "OK" << std::endl; break; | ||
| default: LOG << e << std::endl; | ||
| } | ||
| } | ||
| opus_encoder::opus_encoder() | ||
| { | ||
| int err; | ||
| _opus = opus_encoder_create(SAMPLE_RATE,CHANNELS, OPUS_APPLICATION_VOIP, &err); | ||
| if(err != OPUS_OK) | ||
| { | ||
| LOG << "opus encoder create error: "; | ||
| log_opus_error(err); | ||
| } | ||
| opus_encoder_ctl(_opus, OPUS_SET_BITRATE(OPUS_AUTO)); | ||
| opus_encoder_ctl(_opus, OPUS_SET_VBR(1)); | ||
| opus_encoder_ctl(_opus, OPUS_SET_FORCE_CHANNELS(1)); //force mono | ||
| opus_encoder_ctl(_opus, OPUS_SET_PACKET_LOSS_PERC(2)); | ||
| ENSURE(_opus); | ||
| } | ||
| opus_encoder::~opus_encoder() | ||
| { | ||
| REQUIRE(_opus); | ||
| opus_encoder_destroy(_opus); | ||
| } | ||
| bytes opus_encoder::encode(const bytes& b) | ||
| { | ||
| REQUIRE(_opus); | ||
| REQUIRE_FALSE(b.empty()); | ||
| if(b.size() != MIN_BUF_SIZE) return {}; | ||
| u::bytes r; | ||
| r.resize(MIN_BUF_SIZE); | ||
| auto size = opus_encode(_opus, | ||
| reinterpret_cast<const opus_int16*>(b.data()), | ||
| FRAMES, | ||
| reinterpret_cast<unsigned char*>(r.data()), | ||
| r.size()); | ||
| if(size < 0) | ||
| { | ||
| LOG << "opus encode error: "; log_opus_error(size); | ||
| return {}; | ||
| } | ||
| r.resize(size); | ||
| return r; | ||
| } | ||
| opus_decoder::opus_decoder() | ||
| { | ||
| int err; | ||
| _opus = opus_decoder_create(SAMPLE_RATE, CHANNELS, &err); | ||
| if(err != OPUS_OK) | ||
| { | ||
| LOG << "opus decoder create error: "; log_opus_error(err); | ||
| } | ||
| ENSURE(_opus); | ||
| } | ||
| opus_decoder::~opus_decoder() | ||
| { | ||
| REQUIRE(_opus); | ||
| opus_decoder_destroy(_opus); | ||
| } | ||
| bytes opus_decoder::decode(const bytes& b) | ||
| { | ||
| REQUIRE(_opus); | ||
| u::bytes t; | ||
| t.resize(MAX_OPUS_DECODE_SIZE); | ||
| auto frames = opus_decode( | ||
| _opus, | ||
| reinterpret_cast<const unsigned char*>(b.data()), | ||
| b.size(), | ||
| reinterpret_cast<opus_int16*>(t.data()), | ||
| MAX_FRAMES, | ||
| 0); | ||
| if(frames < 0) | ||
| { | ||
| LOG << "opus error decoding: "; log_opus_error(frames); | ||
| return {}; | ||
| } | ||
| auto size = frames * sizeof(opus_int16); | ||
| t.resize(size); | ||
| return t; | ||
| } | ||
| } | ||
| } | ||
| #endif |
| @@ -0,0 +1,82 @@ | ||
| /* | ||
| * Copyright (C) 2014 Maxim Noah Khailo | ||
| * | ||
| * This program 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 vedit_refsion 3 of the License, or | ||
| * (at your option) any later vedit_refsion. | ||
| * | ||
| * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. | ||
| * | ||
| * In addition, as a special exception, the copyright holders give | ||
| * permission to link the code of portions of this program with the | ||
| * Botan library under certain conditions as described in each | ||
| * individual source file, and distribute linked combinations | ||
| * including the two. | ||
| * | ||
| * You must obey the GNU General Public License in all respects for | ||
| * all of the code used other than Botan. If you modify file(s) with | ||
| * this exception, you may extend this exception to your version of the | ||
| * file(s), but you are not obligated to do so. If you do not wish to do | ||
| * so, delete this exception statement from your version. If you delete | ||
| * this exception statement from all source files in the program, then | ||
| * also delete it here. | ||
| */ | ||
| #ifndef FIRESTR_APP_QTW_AUDIO_H | ||
| #define FIRESTR_APP_QTW_AUDIO_H | ||
| #include "util/bytes.hpp" | ||
| #include <string> | ||
| #include <unordered_map> | ||
| #include <opus/opus.h> | ||
| namespace fire | ||
| { | ||
| namespace util | ||
| { | ||
| class opus_encoder | ||
| { | ||
| public: | ||
| opus_encoder(); | ||
| ~opus_encoder(); | ||
| public: | ||
| bytes encode(const bytes&); | ||
| private: | ||
| OpusEncoder* _opus = nullptr; | ||
| }; | ||
| using opus_encoder_ptr = std::shared_ptr<opus_encoder>; | ||
| class opus_decoder | ||
| { | ||
| public: | ||
| opus_decoder(); | ||
| ~opus_decoder(); | ||
| public: | ||
| bytes decode(const bytes&); | ||
| private: | ||
| OpusDecoder* _opus = nullptr; | ||
| }; | ||
| using opus_decoder_ptr = std::shared_ptr<opus_decoder>; | ||
| extern const size_t FRAMES; //40ms of PCM frames. Opus can handles 2.5, 5, 10, 20, 40 or 60ms of audio per frame. | ||
| extern const size_t MAX_FRAMES; | ||
| extern const size_t MAX_OPUS_DECODE_SIZE; | ||
| extern const size_t SAMPLE_RATE; | ||
| extern const size_t CHANNELS; | ||
| extern const size_t MIN_BUF_SIZE; | ||
| } | ||
| } | ||
| #endif |