Created
February 27, 2022 00:32
-
-
Save MCJack123/86472ceab601ddfc4b4cee96c0f97490 to your computer and use it in GitHub Desktop.
Microphone to WebSocket loopback server (requires Poco and PortAudio)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Microphone to WebSocket loopback server | |
* Requires Poco and PortAudio | |
* Compile with g++ -o micserver micserver.cpp -lPocoFoundation -lPocoNet -lPocoUtil -lportaudio | |
* | |
* MIT License | |
* | |
* Copyright (c) 2022 JackMacWindows | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all | |
* copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
* SOFTWARE. | |
*/ | |
#include <Poco/Net/NetException.h> | |
#include <Poco/Net/HTTPRequest.h> | |
#include <Poco/Net/HTTPRequestHandler.h> | |
#include <Poco/Net/HTTPResponse.h> | |
#include <Poco/Net/HTTPServer.h> | |
#include <Poco/Net/HTTPServerRequest.h> | |
#include <Poco/Net/HTTPServerResponse.h> | |
#include <Poco/Net/WebSocket.h> | |
#include <portaudio.h> | |
#include <csignal> | |
#include <iostream> | |
#include <mutex> | |
#include <condition_variable> | |
using namespace Poco::Net; | |
static bool running = true; | |
static uint8_t buf[32768]; | |
std::mutex lock; | |
std::condition_variable notify; | |
class WebSocketServer: public HTTPRequestHandler { | |
public: | |
void handleRequest(HTTPServerRequest &request, HTTPServerResponse &response) override { | |
try { | |
WebSocket ws(request, response); | |
while (running) { | |
std::unique_lock<std::mutex> l(lock); | |
notify.wait(l); | |
ws.sendFrame(buf, 32768, WebSocket::FRAME_BINARY); | |
} | |
try {ws.shutdown();} catch (...) {} | |
} catch (Poco::Exception &e) { | |
std::cerr << "WebSocket exception: " << e.displayText() << "\n"; | |
} catch (std::exception &e) { | |
std::cerr << "WebSocket exception: " << e.what() << "\n"; | |
} | |
} | |
class Factory: public HTTPRequestHandlerFactory { | |
public: | |
HTTPRequestHandler* createRequestHandler(const HTTPServerRequest&) override { | |
return new WebSocketServer; | |
} | |
}; | |
}; | |
static int recordCallback(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void *userData) { | |
memcpy(buf, inputBuffer, framesPerBuffer); | |
notify.notify_all(); | |
return paContinue; | |
} | |
static void sighandler(int sig) { | |
running = false; | |
} | |
int main(int argc, const char * argv[]) { | |
if (argc < 2) { | |
std::cerr << "Usage: " << argv[0] << " <port> [device name]\n"; | |
return 1; | |
} | |
int port = std::stoi(argv[1]); | |
std::string device; | |
if (argc > 2) device = argv[2]; | |
std::cout << "Initializing...\n"; | |
PaError err = Pa_Initialize(); | |
if (err) { | |
std::cerr << "Error while initializing: " << Pa_GetErrorText(err) << "\n"; | |
return err; | |
} | |
PaStream * stream; | |
PaStreamParameters inputParameters; | |
if (!device.empty()) { | |
inputParameters.device = paNoDevice; | |
for (PaDeviceIndex i = 0; i < Pa_GetDeviceCount(); i++) { | |
const PaDeviceInfo * info = Pa_GetDeviceInfo(i); | |
if (info->name == device && info->maxInputChannels > 0) { | |
inputParameters.device = i; | |
break; | |
} | |
} | |
} else inputParameters.device = Pa_GetDefaultInputDevice(); | |
if (inputParameters.device == paNoDevice) { | |
std::cerr << "Error: No input device found\n"; | |
Pa_Terminate(); | |
return 2; | |
} | |
inputParameters.channelCount = 1; | |
inputParameters.sampleFormat = paUInt8; | |
inputParameters.suggestedLatency = Pa_GetDeviceInfo(inputParameters.device)->defaultLowInputLatency; | |
inputParameters.hostApiSpecificStreamInfo = NULL; | |
std::cout << "Opening...\n"; | |
err = Pa_OpenStream(&stream, &inputParameters, NULL, 48000, 32768, paNoFlag, recordCallback, NULL); | |
if (err) { | |
std::cerr << "Error while opening stream: " << Pa_GetErrorText(err) << "\n"; | |
Pa_Terminate(); | |
return err; | |
} | |
std::cout << "Starting stream...\n"; | |
err = Pa_StartStream(stream); | |
if (err) { | |
std::cerr << "Error while starting stream: " << Pa_GetErrorText(err) << "\n"; | |
Pa_Terminate(); | |
return err; | |
} | |
std::cout << "Starting server...\n"; | |
HTTPServer srv(new WebSocketServer::Factory, port); | |
srv.start(); | |
signal(SIGINT, sighandler); | |
std::cout << "Now listening on port " << port << " with device '" << Pa_GetDeviceInfo(inputParameters.device)->name << "'\n"; | |
while (running && Pa_IsStreamActive(stream) == 1) Pa_Sleep(1000); | |
srv.stopAll(); | |
Pa_StopStream(stream); | |
Pa_CloseStream(stream); | |
Pa_Terminate(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment