├── DESIGN-IAX2 ├── README ├── example │ ├── iax2test1.c │ └── iaxTest2.c ├── iax2.c ├── lib_ADC.c ├── lib_ADC.h ├── lib_IAX2.c ├── lib_IAX2.h ├── lib_audio.c └── lib_audio.h ├── DESIGN1 ├── README └── SIP_Phone_uC.cpp ├── DESIGN2 ├── README └── SIP_Phone.cpp ├── DESIGN3 ├── README ├── SIP_Phone Client.cpp └── SIP_Phone Client2.cpp ├── DESIGN4 ├── README ├── audio.cpp ├── audio.h ├── lcd_display.cpp ├── lcd_display.h ├── main.cpp ├── network.cpp ├── network.h ├── sip_client.cpp ├── sip_client.h ├── web_server.cpp └── web_server.h ├── DESIGN5 └── SIP_PhoneF.cpp ├── EXPERIMENTS ├── DSP │ ├── EXP1_ESP32-CODEC.c │ ├── README │ └── dsp.h └── TX-RX │ ├── README │ ├── RX_Audio.c │ └── TX_Audio.c └── README.md /DESIGN-IAX2/README: -------------------------------------------------------------------------------- 1 | The program runs on an ESP32 board and is connected to a WiFi network for communication with a web server and an IAX2 server. The ESP32 board also performs audio 2 | processing using an ADC and DAC for input and output, respectively. The program has a call button that triggers a phone call using the IAX2 protocol. The program 3 | can also enable call forwarding to a specified phone number. The web server allows the user to configure network settings. Finally, the program also uses an LCD 4 | display for caller ID and call time 5 | 6 | +-----------------+ 7 | | Call Button | 8 | +--------+--------+ 9 | | 10 | +--------v--------+ 11 | | | 12 | | ESP32 Board | 13 | | | 14 | +--------+--------+ 15 | | 16 | +--------------+--------------+ 17 | | | 18 | +---------v--------+ +---------v--------+ 19 | | WiFi Network | | IAX2 Server | 20 | +---------+--------+ +---------+--------+ 21 | | | 22 | | | 23 | +----------------v---------------+ +---------v--------+ 24 | | Web Server and Audio Output | | Call Forwarding | 25 | +----------------+---------------+ +-----------------+ 26 | | 27 | +----------------v----------------+ 28 | | ADC and DAC Audio Processing | 29 | +----------------------------------+ 30 | 31 | 32 | The program has the following features: 33 | 34 | Connects to a WiFi network using the provided SSID and password. 35 | Initializes the ADC and DAC pins for audio input and output. 36 | Applies a digital filter to remove noise from the audio input. 37 | Reads audio samples from the ADC and outputs them to the PAM4803 amplifier. 38 | Connects to an IAX2 server using the provided user credentials. 39 | Handles incoming and outgoing calls using the IAX2 protocol. 40 | Displays the caller ID and call duration on an LCD display. 41 | Provides a web interface for configuring network settings. 42 | Enables call forwarding to a specified number when an incoming call is received and call forwarding is enabled. 43 | Allows the user to place outgoing calls using a call button connected to a GPIO pin 44 | 45 | Additional: 46 | Digital filters are used to process digital signals, which are discrete-time signals represented as sequences of numbers. They can be used to remove noise or 47 | unwanted frequencies from a signal, or to enhance certain features of a signal. 48 | In the code provided, a digital filter is applied to an audio sample to remove noise before it is output to a PAM4803 amplifier. This helps to improve the quality 49 | of the audio output. 50 | 51 | 52 | This is a piece of code that defines classes and methods for audio processing and audio compression. The code defines four classes that inherit from a common abstract 53 | class Codec: 54 | 55 | uLawCodec - implements the u-law compression algorithm 56 | aLawCodec - implements the a-law compression algorithm 57 | G711Codec - implements the G.711 compression algorithm 58 | uLawG711Codec - implements u-law followed by G.711 compression 59 | Each codec has a compress method and a decompress method that take in input audio data and output compressed audio data and vice versa. The Codec class defines some 60 | common methods that all codecs have, such as getName, getSampleRate, getBitsPerSample, and getBlockSize. 61 | 62 | The code also defines three audio processing methods: 63 | 64 | filter - implements a simple low-pass filter 65 | amplify - amplifies the audio data by a given gain 66 | reduceNoise - removes negative values from audio data 67 | The filter method takes in an input audio signal and outputs a filtered version of it. It implements a simple low-pass filter that smoothes the signal. The amplify 68 | method takes in an input audio signal and outputs a version of it that is amplified by a given gain. The reduceNoise method takes in an input audio signal and outputs 69 | a version of it where negative values have been removed. 70 | 71 | Overall, the code provides a simple framework for audio processing and compression. It could be extended to include other codecs and processing algorithms, and it 72 | could be used as a starting point for building more complex audio applications. 73 | -------------------------------------------------------------------------------- /DESIGN-IAX2/example/iax2test1.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Define the IAX2 connection details 5 | #define IAX_SERVER_IP IPAddress(192, 168, 0, 100) 6 | #define IAX_SERVER_PORT 4569 7 | 8 | // Define the audio buffer size 9 | #define AUDIO_BUFFER_SIZE 160 10 | 11 | // Define the microphone pin 12 | #define MIC_PIN 36 13 | 14 | // Create an instance of the IAX2 library 15 | IAX2 iax2(WiFi.macAddress().c_str(), WiFi.localIP(), 4569); 16 | 17 | // Create an instance of the Audio library 18 | Audio audio(MIC_PIN, AUDIO_BUFFER_SIZE); 19 | 20 | void setup() { 21 | Serial.begin(115200); 22 | delay(1000); 23 | Serial.println("Starting up..."); 24 | 25 | // Connect to Wi-Fi 26 | WiFi.begin("SSID", "PASSWORD"); 27 | while (WiFi.status() != WL_CONNECTED) { 28 | delay(1000); 29 | Serial.println("Connecting to Wi-Fi..."); 30 | } 31 | Serial.println("Connected to Wi-Fi"); 32 | 33 | // Connect to the IAX2 server 34 | while (!iax2.connect(IAX_SERVER_IP, IAX_SERVER_PORT)) { 35 | Serial.println("Could not connect to IAX2 server. Retrying..."); 36 | delay(1000); 37 | } 38 | Serial.println("Connected to IAX2 server"); 39 | 40 | // Start the audio stream 41 | audio.start(); 42 | } 43 | 44 | void loop() { 45 | // Read audio data from the microphone 46 | int16_t audioData[AUDIO_BUFFER_SIZE]; 47 | audio.read(audioData); 48 | 49 | // Create a new IAX2 message 50 | iaxNew newMessage; 51 | newMessage.messageLength = sizeof(newMessage) + sizeof(audioData); 52 | newMessage.messageType = IAX_NEW; 53 | newMessage.subclass = 0; 54 | newMessage.timestamp = 0; 55 | newMessage.sourceCallNumber = 0; 56 | newMessage.destinationCallNumber = 0; 57 | memset(newMessage.reserved, 0, sizeof(newMessage.reserved)); 58 | 59 | // Send the new message and receive the response 60 | iaxNewResponse response; 61 | if (iax2.sendNew(&newMessage, &response)) { 62 | // Send the audio data 63 | if (iax2.client.write((uint8_t*)audioData, sizeof(audioData)) != sizeof(audioData)) { 64 | Serial.println("Error sending audio data"); 65 | } 66 | } else { 67 | Serial.println("Error sending new message"); 68 | } 69 | 70 | // Wait for the IAX2 PONG message to confirm that the server received the data 71 | while (!iax2.receive()) { 72 | delay(100); 73 | } 74 | if (iax2.client.available()) { 75 | uint8_t buffer[256]; 76 | int bytesRead = iax2.client.read(buffer, sizeof(buffer)); 77 | for (int i = 0; i < bytesRead; i++) { 78 | iax2.parseHeader((iaxHeader*)(&buffer[i])); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /DESIGN-IAX2/example/iaxTest2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // Define the ESP32 ADC pin to use for the microphone 6 | #define MIC_PIN 36 7 | 8 | // Define the MAC address and IP address of the ESP32 9 | uint8_t mac[] = {0x00, 0xAA, 0xBB, 0xCC, 0xDE, 0x02}; 10 | IPAddress localIP(192, 168, 1, 200); 11 | int localPort = 4569; 12 | 13 | // Define the IP address and port number of the Asterisk server 14 | IPAddress serverIP(192, 168, 1, 100); 15 | int serverPort = 4569; 16 | 17 | // Define the IAX2 objects 18 | IAX2 iax(mac, localIP, localPort); 19 | iaxNew newMessage; 20 | iaxNewResponse newResponse; 21 | iaxHangup hangupMessage; 22 | 23 | // Define the Audio objects 24 | Audio audio; 25 | audioConfig config; 26 | audioBuffer buffer; 27 | 28 | // Define the maximum number of forwarded calls 29 | #define MAX_FORWARDS 3 30 | 31 | // Define the forwarded call IP addresses and port numbers 32 | IPAddress forwardIPs[MAX_FORWARDS] = { 33 | IPAddress(192, 168, 1, 101), 34 | IPAddress(192, 168, 1, 102), 35 | IPAddress(192, 168, 1, 103) 36 | }; 37 | int forwardPorts[MAX_FORWARDS] = {4569, 4569, 4569}; 38 | 39 | // Define the call conference IP addresses and port numbers 40 | IPAddress conferenceIPs[MAX_FORWARDS + 1] = { 41 | IPAddress(192, 168, 1, 200), 42 | IPAddress(192, 168, 1, 101), 43 | IPAddress(192, 168, 1, 102), 44 | IPAddress(192, 168, 1, 103) 45 | }; 46 | int conferencePorts[MAX_FORWARDS + 1] = {4569, 4569, 4569, 4569}; 47 | 48 | // Define the echo cancellation object 49 | audioEchoCanceller echoCanceller; 50 | 51 | // Define the call detection object 52 | audioCallDetector callDetector; 53 | 54 | // Define the caller ID information 55 | char callerID[32] = "John Smith"; 56 | char callerNumber[32] = "123-456-7890"; 57 | 58 | // Define the function to handle incoming calls 59 | bool handleIncomingCall(iaxInCall *incomingCall) { 60 | // Get the codec preference from the incoming call message 61 | uint32_t codecPreference = incomingCall->codecPreference[0] << 24 | 62 | incomingCall->codecPreference[1] << 16 | 63 | incomingCall->codecPreference[2] << 8 | 64 | incomingCall->codecPreference[3]; 65 | 66 | // Set the audio configuration based on the codec preference 67 | if (codecPreference == AUDIO_CODEC_ULAW) { 68 | config.codec = AUDIO_CODEC_ULAW; 69 | config.sampleRate = 8000; 70 | } else if (codecPreference == AUDIO_CODEC_ALAW) { 71 | config.codec = AUDIO_CODEC_ALAW; 72 | config.sampleRate = 8000; 73 | } else if (codecPreference == AUDIO_CODEC_G722) { 74 | config.codec = AUDIO_CODEC_G722; 75 | config.sampleRate = 16000; 76 | } else { 77 | // Unsupported codec, hang up the call 78 | hangupMessage.cause = 88; // Cause code 88 = Incompatible destination 79 | iax.sendHangup(&hangupMessage); 80 | return false; 81 | } 82 | 83 | // Initialize the audio 84 | 85 | // Forward the call to another IP address 86 | bool forwardCall(iaxNew *newMessage, iaxNewResponse *response, IPAddress forwardIP, int forwardPort) { 87 | // Connect to the forwarding server 88 | if (!iax.connect(forwardIP, forwardPort)) { 89 | Serial.println("Failed to connect to forwarding server"); 90 | return false; 91 | } 92 | 93 | // Send the call initiation message 94 | if (!iax.sendNew(newMessage, response)) { 95 | Serial.println("Failed to forward call"); 96 | iax.disconnect(); 97 | return false; 98 | } 99 | 100 | // Disconnect from the forwarding server 101 | iax.disconnect(); 102 | 103 | return true; 104 | } 105 | 106 | // Handle incoming call and return true if call is answered, false otherwise 107 | bool handleIncomingCall(iaxInCall *incomingCall) { 108 | // TODO: Implement call forwarding, caller ID, call conference, echo cancellation, and call detection 109 | 110 | // Answer the call 111 | iaxNewResponse response; 112 | response.acceptCode = 1; // Accept call 113 | response.codecPreference = CODEC_ULAW; 114 | response.sampleRate = SAMPLE_RATE; 115 | if (!iax.sendNewResponse(&response)) { 116 | Serial.println("Failed to answer call"); 117 | return false; 118 | } 119 | 120 | // Initialize the audio library with the appropriate codec and sample rate 121 | if (!audio.begin(CODEC_ULAW, SAMPLE_RATE)) { 122 | Serial.println("Failed to initialize audio"); 123 | return false; 124 | } 125 | 126 | // Play a ringtone to indicate an incoming call 127 | if (!audio.playRingtone()) { 128 | Serial.println("Failed to play ringtone"); 129 | return false; 130 | } 131 | 132 | // Start recording from the microphone 133 | if (!audio.startRecording()) { 134 | Serial.println("Failed to start recording"); 135 | return false; 136 | } 137 | 138 | // Start playing back audio to the caller 139 | if (!audio.startPlayback()) { 140 | Serial.println("Failed to start playback"); 141 | return false; 142 | } 143 | 144 | // Wait for the call to end or for the hangup button to be pressed 145 | while (iax.isConnected() && digitalRead(HANGUP_BUTTON_PIN) == HIGH) { 146 | // Check if there is incoming audio data 147 | if (iax.available()) { 148 | // Read the incoming audio data 149 | uint8_t audioBuffer[IAX_BUFFER_SIZE - sizeof(iaxHeader)]; 150 | int bytesRead = iax.read(audioBuffer, sizeof(audioBuffer)); // Play the incoming audio data 151 | if (!audio.playAudio(audioBuffer, bytesRead)) { 152 | Serial.println("Failed to play audio"); 153 | break; 154 | } 155 | } 156 | 157 | // Check if there is outgoing audio data 158 | uint8_t audioBuffer[IAX_BUFFER_SIZE - sizeof(iaxHeader)]; 159 | int bytesToRead = audio.getRecordedData(audioBuffer, sizeof(audioBuffer)); 160 | if (bytesToRead > 0) { 161 | // Send the outgoing audio data 162 | if (!iax.write(audioBuffer, bytesToRead)) { 163 | Serial.println("Failed to send audio"); 164 | break; 165 | } 166 | } 167 | } 168 | 169 | // Stop playing and recording audio 170 | audio.stopPlayback(); 171 | audio.stopRecording(); 172 | 173 | // Send hangup message 174 | iaxHangup hangupMessage; 175 | hangupMessage.cause = 16; // Cause code 16 = Normal call clearing 176 | iax.sendHangup(&hangupMessage); 177 | 178 | return true; 179 | } 180 | 181 | // Main loop 182 | void loop() { 183 | // Wait for a new incoming call 184 | while (!iax.receive()) { 185 | delay(1000); 186 | } 187 | 188 | // Parse the incoming message header 189 | iaxHeader header; 190 | iax.parseHeader(&header); 191 | 192 | // Handle the incoming message based on its type 193 | switch (header.message// Initialize the audio 194 | Audio.begin(8000); 195 | 196 | // Start the audio input 197 | Audio.startInput(PIN_MICROPHONE, true); 198 | 199 | // Start the audio output 200 | Audio.startOutput(PIN_SPEAKER); 201 | 202 | // Create a buffer for audio data 203 | uint8_t audioBuffer[AUDIO_BUFFER_SIZE]; 204 | 205 | // Loop forever 206 | while (true) { // Check for incoming IAX messages 207 | iax.receive(); 208 | 209 | // Check for an incoming call 210 | if (iax.handleIncomingCall(&incomingCall)) { 211 | 212 | // Set the codec to use for the call 213 | codecPreference = incomingCall.codecPreference; 214 | Audio.setCodec(codecPreference); 215 | 216 | // Set the call token for outgoing messages 217 | callToken = incomingCall.callToken; 218 | 219 | // Start the audio input and output 220 | Audio.startInput(PIN_MICROPHONE, true); 221 | Audio.startOutput(PIN_SPEAKER); 222 | 223 | // Send an acceptance message 224 | iaxAcceptMessage.sourceCallNumber = incomingCall.destinationCallNumber; 225 | iaxAcceptMessage.destinationCallNumber = incomingCall.sourceCallNumber; 226 | iaxAcceptMessage.callToken = incomingCall.callToken; 227 | iax.sendAccept(&iaxAcceptMessage); 228 | } 229 | 230 | // Check for incoming audio data 231 | if (Audio.available()) { 232 | // Read the audio data into the buffer 233 | int bytesRead = Audio.read(audioBuffer, AUDIO_BUFFER_SIZE); 234 | 235 | // Send the audio data in IAX packets 236 | sendAudio(audioBuffer, bytesRead); 237 | } 238 | } 239 | } 240 | 241 | void sendAudio(uint8_t *audioData, int audioLength) { 242 | // Initialize the IAX packet header 243 | iaxHeader header; 244 | header.messageLength = sizeof(iaxHeader) + sizeof(callToken) + audioLength; 245 | header.messageType = IAX_NEW; 246 | header.subclass = 0; 247 | header.timestamp = millis(); 248 | header.sourceCallNumber = 0; 249 | header.destinationCallNumber = 0; 250 | 251 | // Send the audio data in multiple IAX packets 252 | int offset = 0; 253 | while (offset < audioLength) { 254 | // Set the packet length 255 | int packetLength = min(MAX_AUDIO_PACKET_SIZE, audioLength - offset);// Initialize the IAX packet 256 | uint8_t iaxBuffer[IAX_BUFFER_SIZE]; 257 | memcpy(iaxBuffer, &header, sizeof(header)); 258 | memcpy(iaxBuffer + sizeof(header), callToken, sizeof(callToken)); 259 | memcpy(iaxBuffer + sizeof(header) + sizeof(callToken), audioData + offset, packetLength); 260 | 261 | // Send the packet 262 | if (!iax.sendData(iaxBuffer, sizeof(header) + sizeof(callToken) + packetLength)) { 263 | // If sending fails, hang up the call 264 | iaxHangup hangupMessage; 265 | hangupMessage.sourceCallNumber = incomingCall.destinationCallNumber; 266 | hangupMessage.destinationCallNumber = incomingCall.sourceCallNumber; 267 | hangupMessage.cause = 16; // Cause code 16 = Normal call clearing 268 | iax.sendHangup(&hangupMessage); 269 | return; 270 | } 271 | 272 | // Update the offset 273 | offset += packetLength; 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /DESIGN-IAX2/iax2.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define IAX2_USER "user" 7 | #define IAX2_PASSWD "password" 8 | #define IAX2_SERVER "192.168.0.1" 9 | #define IAX2_PORT 4569 10 | 11 | #define MIC_PIN 34 12 | #define SIGNAL_CONDITIONER_PIN 35 13 | #define SPEAKER_PIN 25 14 | #define LCD_ADDRESS 0x27 15 | #define LCD_ROWS 2 16 | #define LCD_COLS 16 17 | 18 | #define MIC_SAMPLE_RATE 16000 19 | #define MIC_SAMPLE_BITS 12 20 | 21 | #define CALL_BUTTON_PIN 4 22 | #define CALL_FORWARD_NUMBER "0987654321" 23 | bool callForwardingEnabled = true; 24 | 25 | const char* ssid = "your_SSID"; 26 | const char* password = "your_PASSWORD"; 27 | 28 | LiquidCrystal_I2C lcd(LCD_ADDRESS, LCD_ROWS, LCD_COLS); 29 | IAX2Client iax2Client; 30 | volatile bool callActive = false; 31 | WebServer server(80); 32 | 33 | void applyDigitalFilter(int *samples, int numSamples) { 34 | // Define filter coefficients 35 | float b[] = {0.0976, 0.4306, 0.4306, 0.0976}; 36 | float a[] = {1.0000, -0.5970, 0.1918, -0.0180}; 37 | 38 | // Initialize filter state variables 39 | float x[4] = {0}; 40 | float y[4] = {0}; 41 | 42 | // Apply filter to samples 43 | for (int i = 0; i < numSamples; i++) { 44 | // Shift state variables 45 | for (int j = 3; j > 0; j--) { 46 | x[j] = x[j - 1]; 47 | y[j] = y[j - 1]; 48 | } 49 | 50 | // Update input 51 | x[0] = samples[i]; 52 | 53 | // Compute output 54 | y[0] = b[0] * x[0] + b[1] * x[1] + b[2] * x[2] + b[3] * x[3] 55 | - a[1] * y[1] - a[2] * y[2] - a[3] * y[3]; 56 | 57 | // Save output 58 | samples[i] = (int) y[0]; 59 | } 60 | } 61 | 62 | void processAudioSample() { 63 | // Read audio sample 64 | int sample = adc1_get_raw(ADC1_CHANNEL_6); 65 | 66 | // Apply a digital filter to remove noise 67 | applyDigitalFilter(&sample, 1); 68 | 69 | // Scale sample to match PAM4803 input range 70 | int output = map(sample, 0, 4095, 0, 255); 71 | 72 | // Output sample to PAM4803 amplifier 73 | dac_output_voltage(DAC_CHANNEL_1, output); 74 | } 75 | 76 | void handleRoot() { 77 | server.send(200, "text/html", "

ESP32 Network Configuration

" 78 | "
" 79 | "IP address:
" 80 | "Subnet mask:
" 81 | "Gateway:
" 82 | "Primary DNS:
" 83 | "Secondary DNS:
" 84 | "" 85 | "
"); 86 | } 87 | 88 | void handleSave() { 89 | String ip = server.arg("ip"); 90 | String subnet = server.arg("subnet"); 91 | String gateway = server.arg("gateway"); 92 | String dns1 = server.arg("dns1"); 93 | String dns2 = server.arg("dns2"); 94 | 95 | // Save network settings 96 | EEPROM.begin(512); 97 | EEPROM.writeString(0, ip); 98 | EEPROM.writeString(16, subnet); 99 | EEPROM.writeString(32, gateway); 100 | EEPROM.writeString(48, dns1); 101 | EEPROM.writeString(64, dns2); 102 | EEPROM.commit(); 103 | EEPROM.end(); 104 | 105 | server.send(200, "text/html", "

Settings saved!

"); 106 | } 107 | 108 | void setup() { 109 | // Connect to WiFi 110 | WiFi.begin(ssid, password); 111 | while (WiFi.status() != WL_CONNECTED) { 112 | delay(1000); 113 | Serial.println("Connecting to WiFi..."); 114 | } 115 | Serial.println("Connected to WiFi"); 116 | 117 | // Configure ADC and DAC pins 118 | adc1_config_width(ADC_WIDTH_BIT_12); 119 | dac_output_enable(SPEAKER_PIN); 120 | 121 | // Configure audio input 122 | esp_err_t err = adc_gpio_init(MIC_PIN); 123 | if (err != ESP_OK) { 124 | Serial.println("Failed to initialize ADC GPIO"); 125 | } 126 | 127 | err = adc1_config_channel_atten(ADC1_CHANNEL_6, ADC_ATTEN_DB_0); 128 | if (err != ESP_OK) { 129 | Serial.println("Failed to configure ADC channel attenuation"); 130 | } 131 | 132 | err = adc1_sample_rate_config(MIC_SAMPLE_RATE); 133 | if (err != ESP_OK) { 134 | Serial.println("Failed to configure ADC sample rate"); 135 | } 136 | 137 | // Connect to IAX2 server 138 | iax2Client.begin(IAX2_SERVER, IAX2_PORT); 139 | iax2Client.setUser(IAX2_USER); 140 | iax2Client.setPassword(IAX2_PASSWD); 141 | iax2Client.connect(); 142 | 143 | // Configure LCD display 144 | lcd.init(); 145 | lcd.backlight(); 146 | lcd.setCursor(0, 0); 147 | lcd.print("Caller ID:"); 148 | lcd.setCursor(0, 1); 149 | lcd.print("Time:"); 150 | 151 | // Configure web server 152 | server.on("/", handleRoot); 153 | server.on("/save", handleSave); 154 | server.begin(); 155 | Serial.println("Web server started"); 156 | 157 | // Configure GPIO pins 158 | pinMode(SPEAKER_PIN, OUTPUT); 159 | pinMode(SIGNAL_CONDITIONER_PIN, OUTPUT); 160 | 161 | // Set initial output values 162 | digitalWrite(SPEAKER_PIN, LOW); 163 | digitalWrite(SIGNAL_CONDITIONER_PIN, LOW); 164 | 165 | // Attach interrupt for call button 166 | attachInterrupt(digitalPinToInterrupt(CALL_BUTTON_PIN), handleCallButton, FALLING); 167 | 168 | // Start audio processing 169 | err = adc1_start(); 170 | if (err != ESP_OK) { 171 | Serial.println("Failed to start ADC"); 172 | } 173 | 174 | // Print IP address 175 | Serial.print("Local IP address: "); 176 | Serial.println(WiFi.localIP()); 177 | } 178 | 179 | void loop() { 180 | // Process audio sample 181 | processAudioSample(); 182 | 183 | // Handle web server requests 184 | server.handleClient(); 185 | 186 | // Handle IAX2 client events 187 | iax2Client.loop(); 188 | 189 | // Handle incoming calls 190 | if (iax2Client.isIncomingCall() && !callActive) { 191 | // Answer incoming call 192 | iax2Client.answerCall(); 193 | callActive = true; 194 | 195 | // Display caller ID 196 | lcd.setCursor(10, 0); 197 | lcd.print(iax2Client.getCallerID()); 198 | 199 | // Enable signal conditioning for speaker 200 | digitalWrite(SIGNAL_CONDITIONER_PIN, HIGH); 201 | } 202 | 203 | 204 | // Handle call forwarding 205 | if (iax2Client.isIncomingCall() && callForwardingEnabled) { 206 | iax2Client.forwardCall(CALL_FORWARD_NUMBER); 207 | } 208 | 209 | // Update call timer 210 | if (callActive) { 211 | unsigned long currentMillis = millis(); 212 | unsigned long elapsedSeconds = (currentMillis - callStartTime) / 1000; 213 | unsigned long minutes = elapsedSeconds / 60; 214 | unsigned long seconds = elapsedSeconds % 60;// Display call time 215 | lcd.setCursor(5, 1); 216 | lcd.print(String(minutes, DEC)); 217 | lcd.print(":"); 218 | lcd.print(String(seconds, DEC));} 219 | } 220 | 221 | void handleCallButton() { 222 | if (!callActive) { 223 | // Place outgoing call 224 | iax2Client.placeCall(CALL_NUMBER); 225 | callActive = true;// Display call time 226 | lcd.setCursor(5, 1); 227 | lcd.print("00:00"); 228 | 229 | // Record call start time 230 | callStartTime = millis(); 231 | // Clear call time 232 | lcd.setCursor(5, 1); 233 | lcd.print(" "); 234 | } 235 | } 236 | 237 | 238 | 239 | -------------------------------------------------------------------------------- /DESIGN-IAX2/lib_ADC.c: -------------------------------------------------------------------------------- 1 | // ADC.cpp 2 | 3 | #include "adc.h" 4 | 5 | #define ADC_REF_VOLTAGE 3300 6 | 7 | uint8_t ADC::adcResolution; 8 | 9 | void ADC::begin() { 10 | // Initializes the ADC 11 | ADCSRA |= (1 << ADEN); 12 | } 13 | 14 | void ADC::setResolution(uint8_t bits) { 15 | // Sets the resolution of the ADC (8, 10, 12 bits) 16 | if (bits == 8) { 17 | ADMUX &= ~(1 << ADLAR); 18 | adcResolution = 8; 19 | } else if (bits == 10) { 20 | ADMUX &= ~(1 << ADLAR); 21 | adcResolution = 10; 22 | } else if (bits == 12) { 23 | ADMUX |= (1 << ADLAR); 24 | adcResolution = 12; 25 | } 26 | } 27 | 28 | void ADC::setClockDivisor(uint8_t divisor) { 29 | // Sets the clock divisor for the ADC (2, 4, 8, 16, 32, 64, 128, 256) 30 | if (divisor == 2) { 31 | ADCSRA |= (1 << ADPS0); 32 | ADCSRA &= ~(1 << ADPS1); 33 | ADCSRA &= ~(1 << ADPS2); 34 | } else if (divisor == 4) { 35 | ADCSRA &= ~(1 << ADPS0); 36 | ADCSRA |= (1 << ADPS1); 37 | ADCSRA &= ~(1 << ADPS2); 38 | } else if (divisor == 8) { 39 | ADCSRA |= (1 << ADPS0); 40 | ADCSRA |= (1 << ADPS1); 41 | ADCSRA &= ~(1 << ADPS2); 42 | } else if (divisor == 16) { 43 | ADCSRA &= ~(1 << ADPS0); 44 | ADCSRA &= ~(1 << ADPS1); 45 | ADCSRA |= (1 << ADPS2); 46 | } else if (divisor == 32) { 47 | ADCSRA |= (1 << ADPS0); 48 | ADCSRA &= ~(1 << ADPS1); 49 | ADCSRA |= (1 << ADPS2); 50 | } else if (divisor == 64) { 51 | ADCSRA &= ~(1 << ADPS0); 52 | ADCSRA |= (1 << ADPS1); 53 | ADCSRA |= (1 << ADPS2); 54 | } else if (divisor == 128) { 55 | ADCSRA |= (1 << ADPS0); 56 | ADCSRA |= (1 << ADPS1); 57 | ADCSRA |= (1 << ADPS2); 58 | } else if (divisor == 256) { 59 | ADCSRA &= ~(1 << ADPS0); 60 | ADCSRA &= ~(1 << ADPS1); 61 | ADCSRA &= ~(1 << ADPS2); 62 | } 63 | } 64 | 65 | void ADC::start(uint8_t channel) { 66 | // Starts a conversion on the specified channel 67 | ADMUX = (ADMUX & 0xF0) | (channel & 0x0F); 68 | ADCSRA |= (1 << ADSC); 69 | } 70 | 71 | bool ADC::isConverting() { 72 | // Returns true if a conversion is currently in progress 73 | return bitRead(ADCSRA, ADSC); 74 | } 75 | 76 | bool ADC::isEnd() { 77 | // Returns true if a conversion is complete and the value is ready to be read 78 | return bitRead(ADCSRA, ADIF); 79 | } 80 | 81 | uint16_t ADC::getResult() { 82 | // Gets the result of the last conversion as a raw integer value 83 | while (isConverting()); 84 | return ADC; 85 | } 86 | 87 | float ADC::getVoltage() { 88 | // Gets the result of the last conversion as a voltage value (in millivolts) 89 | uint16_t raw = getResult(); 90 | float voltage = raw * ADC_REF_VOLTAGE / ADC_MAX_VALUE; 91 | return voltage; 92 | } 93 | 94 | -------------------------------------------------------------------------------- /DESIGN-IAX2/lib_ADC.h: -------------------------------------------------------------------------------- 1 | // ADC.h 2 | 3 | #ifndef ADC_H 4 | #define ADC_H 5 | 6 | #include 7 | 8 | typedef enum { 9 | ADC_RESOLUTION_6BIT = 0, 10 | ADC_RESOLUTION_8BIT, 11 | ADC_RESOLUTION_10BIT, 12 | ADC_RESOLUTION_12BIT 13 | } adc_resolution_t; 14 | 15 | typedef enum { 16 | ADC_CLOCK_DIV_2 = 0, 17 | ADC_CLOCK_DIV_4, 18 | ADC_CLOCK_DIV_8, 19 | ADC_CLOCK_DIV_16, 20 | ADC_CLOCK_DIV_32, 21 | ADC_CLOCK_DIV_64, 22 | ADC_CLOCK_DIV_128 23 | } adc_clock_div_t; 24 | 25 | // Define the ADC reference voltage in millivolts 26 | #define ADC_REF_VOLTAGE 3300 27 | 28 | class ADC { 29 | public: 30 | static void begin(); 31 | static void setResolution(uint8_t bits); 32 | static void setClockDivisor(uint8_t divisor); 33 | static void start(uint8_t channel); 34 | static bool isConverting(); 35 | static bool isEnd(); 36 | static uint16_t getResult(); 37 | static float getVoltage(); 38 | private: 39 | static uint8_t adcResolution; 40 | static uint8_t adcClockDivisor; 41 | }; 42 | 43 | #endif 44 | 45 | -------------------------------------------------------------------------------- /DESIGN-IAX2/lib_IAX2.c: -------------------------------------------------------------------------------- 1 | #include "IAX2.h" 2 | 3 | IAX2::IAX2(uint8_t mac[], IPAddress localIP, int localPort) { 4 | memcpy(this->mac, mac, 6); 5 | this->localIP = localIP; 6 | this->localPort = localPort; 7 | } 8 | 9 | bool IAX2::connect(IPAddress serverIP, int serverPort) { 10 | if (client.connect(serverIP, serverPort)) { 11 | return true; 12 | } 13 | return false; 14 | } 15 | 16 | void IAX2::disconnect() { 17 | client.stop(); 18 | } 19 | 20 | bool IAX2::sendNew(iaxNew *newMessage, iaxNewResponse *response) { 21 | // Send the new call message 22 | client.write((uint8_t *)newMessage, sizeof(iaxNew)); 23 | 24 | // Wait for the response 25 | uint16_t bytesReceived = client.read((uint8_t *)response, sizeof(iaxNewResponse)); 26 | if (bytesReceived != sizeof(iaxNewResponse)) { 27 | return false; 28 | } 29 | return true; 30 | } 31 | 32 | bool IAX2::sendHangup(iaxHangup *hangupMessage) { 33 | // Send the hangup message 34 | client.write((uint8_t *)hangupMessage, sizeof(iaxHangup)); 35 | 36 | // Wait for the response 37 | iaxHeader header; 38 | client.read((uint8_t *)&header, sizeof(iaxHeader)); 39 | if (header.messageType == IAX_ACK) { 40 | return true; 41 | } 42 | return false; 43 | } 44 | 45 | bool IAX2::sendRegReq(iaxRegReq *regReqMessage, iaxRegAck *regAckMessage) { 46 | // Send the registration request message 47 | client.write((uint8_t *)regReqMessage, sizeof(iaxRegReq)); 48 | 49 | // Wait for the response 50 | uint16_t bytesReceived = client.read((uint8_t *)regAckMessage, sizeof(iaxRegAck)); 51 | if (bytesReceived != sizeof(iaxRegAck)) { 52 | return false; 53 | } 54 | return true; 55 | } 56 | 57 | bool IAX2::receive() { 58 | // Check if there is data available to read 59 | if (client.available() > 0) { 60 | // Read the message header 61 | iaxHeader header; 62 | client.read((uint8_t *)&header, sizeof(iaxHeader)); 63 | 64 | // Parse the header and read the message body 65 | parseHeader(&header); 66 | uint16_t bytesRead = client.read(iaxBuffer, header.messageLength - sizeof(iaxHeader)); 67 | 68 | // Handle the message based on its type 69 | switch (header.messageType) { 70 | case IAX_INCALL: 71 | handleIncomingCall((iaxInCall *)iaxBuffer); 72 | break; 73 | default: 74 | break; 75 | } 76 | return true; 77 | } 78 | return false; 79 | } 80 | 81 | bool IAX2::handleIncomingCall(iaxInCall *incomingCall) { 82 | if (incomingCall->state == IAX_INCOMING) { 83 | // Incoming call - answer it 84 | answerCall(incomingCall->callno); 85 | return true; 86 | } else if (incomingCall->state == IAX_CALL_PROCEEDING) { 87 | // Call proceeding - do nothing 88 | return true; 89 | } else if (incomingCall->state == IAX_RINGING) { 90 | // Call ringing - play ringing tone 91 | playRingingTone(); 92 | return true; 93 | } else if (incomingCall->state == IAX_CALL_ESTABLISHED) { 94 | // Call established - set audio codec 95 | setAudioCodec(incomingCall->callno, audioCodec); 96 | return true; 97 | } else if (incomingCall->state == IAX_CALL_HANGUP) { 98 | // Call hangup - stop audio playback 99 | stopAudioPlayback(); 100 | return true; 101 | } else { 102 | // Unknown call state - do nothing 103 | return false; 104 | } 105 | } 106 | 107 | 108 | 109 | bool IAX2::forwardCall(iaxNew *newMessage, iaxNewResponse *response, IPAddress forwardIP, int forwardPort) { 110 | // Forward the call to the specified IAX2 server 111 | IAX2 forwardIAX(mac, localIP, localPort); 112 | if (!forwardIAX.connect(forwardIP, forwardPort)) { 113 | return false; 114 | } 115 | if (!forwardIAX.sendNew(newMessage, response)) { 116 | return false; 117 | } 118 | return true; 119 | } 120 | 121 | void IAX2::parseHeader(iaxHeader *header) { 122 | // Swap the byte order of the header fields 123 | header->messageLength = ntohs(header->messageLength); 124 | header->timestamp = ntohl(header->timestamp); 125 | header->sourceCallNumber = ntohl(header->sourceCallNumber); 126 | header->destinationCallNumber = ntohl(header->destinationCallNumber); 127 | } 128 | 129 | -------------------------------------------------------------------------------- /DESIGN-IAX2/lib_IAX2.h: -------------------------------------------------------------------------------- 1 | #ifndef IAX2_H 2 | #define IAX2_H 3 | 4 | #include 5 | 6 | // Define the IAX2 message types 7 | #define IAX_NEW 2 8 | #define IAX_ACK 6 9 | #define IAX_INCALL 8 10 | #define IAX_HANGUP 9 11 | #define IAX_ACCEPT 10 12 | #define IAX_REJECT 11 13 | #define IAX_AUTHREQ 12 14 | #define IAX_AUTHREP 13 15 | #define IAX_REGREQ 14 16 | #define IAX_REGAUTH 15 17 | #define IAX_REGACK 16 18 | #define IAX_REGREJ 17 19 | #define IAX_REGREL 18 20 | #define IAX_PING 24 21 | #define IAX_PONG 25 22 | 23 | // Define the IAX2 message header structure 24 | typedef struct { 25 | uint16_t messageLength; 26 | uint8_t messageType; 27 | uint8_t subclass; 28 | uint32_t timestamp; 29 | uint32_t sourceCallNumber; 30 | uint32_t destinationCallNumber; 31 | uint8_t reserved[12]; 32 | } iaxHeader; 33 | 34 | // Define the IAX2 call initiation message structure 35 | typedef struct { 36 | uint16_t messageLength; 37 | uint8_t messageType; 38 | uint8_t subclass; 39 | uint32_t timestamp; 40 | uint32_t sourceCallNumber; 41 | uint32_t destinationCallNumber; 42 | uint8_t reserved[12]; 43 | // ... other fields ... 44 | } iaxNew; 45 | 46 | // Define the IAX2 call initiation response structure 47 | typedef struct { 48 | uint16_t messageLength; 49 | uint8_t messageType; 50 | uint8_t subclass; 51 | uint32_t timestamp; 52 | uint32_t sourceCallNumber; 53 | uint32_t destinationCallNumber; 54 | uint8_t reserved[12]; 55 | uint8_t acceptCode; 56 | uint16_t codecPreference; 57 | uint16_t sampleRate; 58 | uint8_t reserved2[20]; 59 | } iaxNewResponse; 60 | 61 | // Define the IAX2 hangup message structure 62 | typedef struct { 63 | uint16_t messageLength; 64 | uint8_t messageType; 65 | uint8_t subclass; 66 | uint32_t timestamp; 67 | uint32_t sourceCallNumber; 68 | uint32_t destinationCallNumber; 69 | uint8_t reserved[12]; 70 | uint8_t cause; 71 | // ... other fields ... 72 | } iaxHangup; 73 | 74 | // Define the IAX2 registration request message structure 75 | typedef struct { 76 | uint16_t messageLength; 77 | uint8_t messageType; 78 | uint8_t subclass; 79 | uint32_t timestamp; 80 | uint32_t sourceCallNumber; 81 | uint32_t destinationCallNumber; 82 | uint8_t reserved[12]; 83 | uint8_t username[20]; 84 | uint8_t password[20]; 85 | uint8_t registerServer[40]; 86 | uint8_t refleshInterval[4]; 87 | } iaxRegReq; 88 | 89 | // Define the IAX2 registration acknowledgement message structure 90 | typedef struct { 91 | uint16_t messageLength; 92 | uint8_t messageType; 93 | uint8_t subclass; 94 | uint32_t timestamp; 95 | uint32_t sourceCallNumber; 96 | uint32_t destinationCallNumber; 97 | uint8_t reserved[12]; 98 | uint8_t registrationStatus[4]; 99 | uint8_t registrationMessage[40]; 100 | uint8_t registrationServer[40]; 101 | uint8_t refleshInterval[4]; 102 | } iaxRegAck; 103 | 104 | // Define the IAX2 incoming call message structure 105 | typedef struct { 106 | uint16_t messageLength; 107 | uint8_t messageType; 108 | uint8_t subclass; 109 | uint32_t timestamp; 110 | uint32_t sourceCallNumber; 111 | uint32_t destinationCallNumber; 112 | uint8_t reserved[12]; 113 | uint8_t callToken[16]; 114 | uint8_t callNumber[32]; 115 | uint8_t callName[32]; 116 | uint8_t callPermit[40]; 117 | uint8_t callType[16]; 118 | uint8_t callStateNumber[4]; 119 | uint8_t calledContext[32]; 120 | uint8_t callingPres[8]; 121 | uint8_t callingANI[32]; 122 | uint8_t callingName[32]; 123 | uint8_t callingNumber[32]; 124 | uint8_t callingTon[4]; 125 | uint8_t callingTns[4]; 126 | uint8_t calledPres[8]; 127 | uint8_t calledANI[32]; 128 | uint8_t calledName[32]; 129 | uint8_t calledNumber[32]; 130 | uint8_t calledTon[4]; 131 | uint8_t calledTns[4]; 132 | uint8_t codecPreference[4]; 133 | uint8_t callToken2[16]; 134 | uint8_t encryptedData[64]; 135 | } iaxInCall; 136 | 137 | class IAX2 { 138 | public: 139 | IAX2(uint8_t mac[], IPAddress localIP, int localPort); 140 | bool connect(IPAddress serverIP, int serverPort); 141 | void disconnect(); 142 | bool sendNew(iaxNew *newMessage, iaxNewResponse *response); 143 | bool sendHangup(iaxHangup *hangupMessage); 144 | bool sendRegReq(iaxRegReq *regReqMessage, iaxRegAck *regAckMessage); 145 | bool receive(); 146 | bool handleIncomingCall(iaxInCall *incomingCall); 147 | bool forwardCall(iaxNew *newMessage, iaxNewResponse *response, IPAddress forwardIP, int forwardPort); 148 | private: 149 | ETHClient client; 150 | uint8_t mac[6]; 151 | IPAddress localIP; 152 | int localPort; 153 | uint8_t iaxBuffer[IAX_BUFFER_SIZE]; 154 | void parseHeader(iaxHeader *header); 155 | }; 156 | 157 | 158 | -------------------------------------------------------------------------------- /DESIGN-IAX2/lib_audio.c: -------------------------------------------------------------------------------- 1 | #include "audio.h" 2 | #include 3 | #include 4 | 5 | Codec* AudioProcessing::audioCodec; 6 | uint8_t* AudioProcessing::audioBuffer; 7 | 8 | int AudioProcessing::audioBufferSize; 9 | void AudioProcessing::filter(uint8_t *input, uint8_t *output, int inputSize, int outputSize) { 10 | // Implement a simple low-pass filter 11 | float alpha = 0.2; 12 | int16_t lastSample = 0; 13 | 14 | for (int i = 0; i < inputSize; i += 2) { 15 | int16_t sample = ((int16_t)input[i]) | (((int16_t)input[i+1]) << 8); 16 | sample = (alpha * sample) + ((1 - alpha) * lastSample); 17 | lastSample = sample; 18 | output[i] = sample & 0xFF; 19 | output[i+1] = (sample >> 8) & 0xFF; 20 | } 21 | } 22 | void AudioProcessing::setCodec(Codec *codec) { 23 | audioCodec = codec; 24 | } 25 | 26 | void AudioProcessing::beginRecord(uint8_t *buffer, int bufferSize) { 27 | audioBuffer = buffer; 28 | audioBufferSize = bufferSize; 29 | 30 | ADC.begin(); 31 | ADC.setClockDivisor(ADC_CLOCK_DIV_8); 32 | ADC.setResolution(8); 33 | } 34 | 35 | void AudioProcessing::amplify(uint8_t *input, uint8_t *output, int inputSize, int outputSize, float gain) { 36 | // Amplify the audio data 37 | for (int i = 0; i < inputSize; i++) { 38 | int16_t sample = ((int16_t)input[i*2]) | (((int16_t)input[i*2+1]) << 8); 39 | sample *= gain; 40 | if (sample > 32767) { 41 | sample = 32767; 42 | } else if (sample < -32768) { 43 | sample = -32768; 44 | } 45 | output[i*2] = sample & 0xFF; 46 | output[i*2+1] = (sample >> 8) & 0xFF; 47 | } 48 | } 49 | 50 | void AudioProcessing::reduceNoise(uint8_t *input, uint8_t *output, int inputSize, int outputSize) { 51 | for (int i = 0; i < inputSize; i++) { 52 | int16_t sample = ((int16_t)input[i*2]) | (((int16_t)input[i*2+1]) << 8); 53 | if (sample < 0) { 54 | sample = -sample; 55 | } 56 | output[i*2] = sample & 0xFF; 57 | output[i*2+1] = (sample >> 8) & 0xFF; 58 | } 59 | } 60 | 61 | class Codec { 62 | public: 63 | virtual const char* getName() = 0; 64 | virtual int getSampleRate() = 0; 65 | virtual int getBitsPerSample() = 0; 66 | virtual int getBlockSize() = 0; 67 | virtual void compress(uint8_t *input, uint8_t *output, int inputSize) = 0; 68 | virtual void decompress(uint8_t *input, uint8_t *output, int outputSize) = 0; 69 | }; 70 | 71 | 72 | class Codec { 73 | public: 74 | virtual const char* getName() = 0; 75 | virtual int getSampleRate() = 0; 76 | virtual int getBitsPerSample() = 0; 77 | virtual int getBlockSize() = 0; 78 | virtual void compress(uint8_t *input, uint8_t *output, int inputSize) = 0; 79 | virtual void decompress(uint8_t *input, uint8_t *output, int outputSize) = 0; 80 | }; 81 | 82 | class uLawCodec : public Codec { 83 | public: 84 | const char* getName() { return "uLaw"; } 85 | int getSampleRate() { return 8000; } 86 | int getBitsPerSample() { return 8; } 87 | int getBlockSize() { return 160; } 88 | 89 | void compress(uint8_t *input, uint8_t *output, int inputSize) { 90 | for (int i = 0; i < inputSize; i++) { 91 | int16_t sample = ((int16_t)input[i*2]) | (((int16_t)input[i*2+1]) << 8); 92 | sample = sample >> 2; 93 | sample = sample ^ 0x8000; 94 | if (sample < 0) { 95 | sample = -sample; 96 | output[i] = 0xFF ^ ((int)log2((float)abs((int)sample)) >> 3); 97 | } else { 98 | output[i] = 0x7F ^ ((int)log2((float)abs((int)sample)) >> 3); 99 | } 100 | } 101 | } 102 | 103 | void decompress(uint8_t *input, uint8_t *output, int outputSize) { 104 | for (int i = 0; i < outputSize; i++) { 105 | int16_t sample = 0; 106 | if (input[i] & 0x80) { 107 | sample = (1 << ((input[i] & 0x0F) + 3)) ^ 0x8000; 108 | } else { 109 | sample = ((input[i] & 0x0F) << 3) | 0x84; 110 | } 111 | output[i*2] = sample & 0xFF; 112 | output[i*2+1] = (sample >> 8) & 0xFF; 113 | } 114 | } 115 | }; 116 | 117 | 118 | class aLawCodec : public Codec { 119 | public: 120 | const char* getName() { return "aLaw"; } 121 | int getSampleRate() { return 8000; } 122 | int getBitsPerSample() { return 8; } 123 | int getBlockSize() { return 160; } 124 | 125 | void compress(uint8_t *input, uint8_t *output, int inputSize) { 126 | for (int i = 0; i < inputSize; i++) { 127 | int16_t sample = ((int16_t)input[i*2]) | (((int16_t)input[i*2+1]) << 8); 128 | sample = sample >> 2; 129 | int sign = (sample >> 8) & 0x80; 130 | if (sign) { 131 | sample = -sample; 132 | } 133 | if (sample > 32635) { 134 | sample = 32635; 135 | } 136 | ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 137 | 138 | class G711Codec : public Codec { 139 | public: 140 | const char* getName() { return "G.711"; } 141 | int getSampleRate() { return 8000; } 142 | int getBitsPerSample() { return 8; } 143 | int getBlockSize() { return 160; } 144 | void compress(uint8_t *input, uint8_t *output, int inputSize) { 145 | for (int i = 0; i < inputSize; i++) { 146 | int16_t sample = ((int16_t)input[i*2]) | (((int16_t)input[i*2+1]) << 8); 147 | output[i] = linear2alaw(sample); 148 | } 149 | } 150 | } 151 | void decompress(uint8_t *input, uint8_t *output, int outputSize) { 152 | for (int i = 0; i < outputSize; i++) { 153 | int16_t sample = alaw2linear(input[i]); 154 | output[i*2] = sample & 0xFF; 155 | output[i*2+1] = (sample >> 8) & 0xFF; 156 | } 157 | } 158 | private: 159 | uint8_t linear2alaw(int16_t pcm_val) { 160 | const int16_t ALAW_MAX = 0xFFF; 161 | const int16_t ALAW_BIAS = 0x84; 162 | int16_t mask; 163 | uint8_t aval; if (pcm_val >= 0) { 164 | mask = 0xD5; 165 | } else { 166 | mask = 0x55; 167 | pcm_val = -pcm_val - 8; 168 | } 169 | 170 | if (pcm_val > ALAW_MAX) { 171 | pcm_val = ALAW_MAX; 172 | } 173 | 174 | pcm_val += ALAW_BIAS; 175 | 176 | if (pcm_val > 0x7F) { 177 | aval = (uint8_t)(~mask | (pcm_val >> 1)); 178 | } else { 179 | aval = (uint8_t)(mask | (pcm_val >> 1)); 180 | } 181 | 182 | return aval; 183 | } 184 | 185 | int16_t alaw2linear(uint8_t a_val) { 186 | const int16_t ALAW_BIAS = 0x84; 187 | int16_t t, seg; 188 | a_val ^= 0x55; 189 | t = (a_val & 0x7F) << 4; 190 | seg = ((unsigned)a_val & 0x70) >> 4; 191 | switch (seg) { 192 | case 0: 193 | t += 8; 194 | break; 195 | case 1: 196 | t += 0x108; 197 | break; 198 | default: 199 | t += 0x108; 200 | t <<= seg - 1; 201 | } 202 | return ((a_val & 0x80) ? t : -t) - ALAW_BIAS; 203 | } 204 | }; 205 | 206 | class uLawG711Codec : public Codec { 207 | public: 208 | const char* getName() { return "uLaw-G.711"; } 209 | int getSampleRate() { return 8000; } 210 | int getBitsPerSample() { return 8; } 211 | int getBlockSize() { return 160; } 212 | 213 | void compress(uint8_t *input, uint8_t *output, int inputSize) { 214 | uint8_t ulawInput[inputSize]; 215 | uLawCodec ulaw; 216 | ulaw.compress(input, ulawInput, inputSize); 217 | 218 | G711Codec g711; 219 | g711.compress(ulawInput, output, inputSize); 220 | } 221 | 222 | void decompress(uint8_t *input, uint8_t *output, int outputSize) { 223 | uint8_t g711Input[outputSize]; 224 | G711Codec g711; 225 | g711.decompress(input, g711Input, outputSize); 226 | 227 | uLawCodec ulaw; 228 | ulaw.decompress(g711Input, output, outputSize); 229 | } 230 | }; 231 | 232 | class G711Codec : public Codec { 233 | public: 234 | const char* getName() { return "G.711"; } 235 | int getSampleRate() { return 8000; } 236 | int getBitsPerSample() { return 8; } 237 | int getBlockSize() { return 160; }void compress(uint8_t *input, uint8_t *output, int inputSize) { 238 | for (int i = 0; i < inputSize; i++) { 239 | int16_t sample = ((int16_t)input[i*2]) | (((int16_t)input[i*2+1]) << 8); 240 | int8_t encodedSample = linearToMuLaw(sample); 241 | output[i] = (uint8_t)encodedSample; 242 | } 243 | } 244 | 245 | void decompress(uint8_t *input, uint8_t *output, int outputSize) { 246 | for (int i = 0; i < outputSize; i++) { 247 | int8_t encodedSample = (int8_t)input[i]; 248 | int16_t sample = muLawToLinear(encodedSample); 249 | output[i*2] = sample & 0xFF; 250 | output[i*2+1] = (sample >> 8) & 0xFF; 251 | } 252 | } 253 | 254 | private: 255 | static int8_t linearToMuLaw(int16_t sample) { 256 | const int BIAS = 132; 257 | const int CLIP = 32767; 258 | const int MULAW_MAX = 0x1F; 259 | int sign = (sample >> 8) & 0x80; 260 | if (sign) { 261 | sample = ~sample; 262 | } 263 | if (sample > CLIP) { 264 | sample = CLIP; 265 | } 266 | sample += BIAS; 267 | int exponent = log2((sample >> 7) & 0xFF); 268 | int mantissa = (sample >> (exponent + 3)) & 0x0F; 269 | int encodedSample = ~(sign | (exponent << 4) | mantissa); 270 | return (int8_t)(encodedSample & MULAW_MAX); 271 | } 272 | 273 | static int16_t muLawToLinear(int8_t encodedSample) { 274 | const int BIAS = 132; 275 | const int MULAW_MAX = 0x1F; 276 | int sign = encodedSample & 0x80; 277 | int exponent = (encodedSample >> 4) & 0x07; 278 | int mantissa = encodedSample & 0x0F; 279 | int decodedSample = ((mantissa << 3) | 0x84) << (exponent + 3); 280 | decodedSample += BIAS; 281 | if (sign) { 282 | decodedSample = -decodedSample; 283 | } 284 | return (int16_t)decodedSample; 285 | } 286 | }; 287 | 288 | 289 | -------------------------------------------------------------------------------- /DESIGN-IAX2/lib_audio.h: -------------------------------------------------------------------------------- 1 | #ifndef AUDIO_H 2 | #define AUDIO_H 3 | 4 | #include 5 | 6 | class AudioProcessing { 7 | public: 8 | static void filter(uint8_t *input, uint8_t *output, int inputSize, int outputSize); 9 | static void amplify(uint8_t *input, uint8_t *output, int inputSize, int outputSize, float gain); 10 | static void reduceNoise(uint8_t *input, uint8_t *output, int inputSize, int outputSize); 11 | static void setCodec(Codec *codec); 12 | static void beginRecord(uint8_t *buffer, int bufferSize); 13 | 14 | private: 15 | static Codec *audioCodec; 16 | static uint8_t *audioBuffer; 17 | static int audioBufferSize; 18 | }; 19 | 20 | class Codec { 21 | public: 22 | virtual const char* getName() = 0; 23 | virtual int getSampleRate() = 0; 24 | virtual int getBitsPerSample() = 0; 25 | virtual int getBlockSize() = 0; 26 | virtual void compress(uint8_t *input, uint8_t *output, int inputSize) = 0; 27 | virtual void decompress(uint8_t *input, uint8_t *output, int outputSize) = 0; 28 | }; 29 | 30 | class uLawCodec : public Codec { 31 | public: 32 | const char* getName() { return "uLaw"; } 33 | int getSampleRate() { return 8000; } 34 | int getBitsPerSample() { return 8; } 35 | int getBlockSize() { return 160; } 36 | void compress(uint8_t *input, uint8_t *output, int inputSize); 37 | void decompress(uint8_t *input, uint8_t *output, int outputSize); 38 | }; 39 | 40 | class aLawCodec : public Codec { 41 | public: 42 | const char* getName() { return "aLaw"; } 43 | int getSampleRate() { return 8000; } 44 | int getBitsPerSample() { return 8; } 45 | int getBlockSize() { return 160; } 46 | void compress(uint8_t *input, uint8_t *output, int inputSize); 47 | void decompress(uint8_t *input, uint8_t *output, int outputSize); 48 | }; 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /DESIGN1/README: -------------------------------------------------------------------------------- 1 | Design summary of the features added to the VoIP code for the ESP32: 2 | 3 | Call forwarding: The enable_call_forwarding() function allows the user to forward incoming calls to a specified number or SIP URI. 4 | Conference calling: The on_call_conference() function enables the user to join multiple calls into a conference call. 5 | Call recording: The start_call_recording() function allows the user to record calls and save them to a file. 6 | Mobile and desktop app integration: The send_push_notification() function allows the user to send push notifications to a mobile or desktop app to notify the user of incoming calls or other events. 7 | Custom caller ID: The set_caller_id() function allows the user to customize the caller ID that is displayed to the recipient of the call. 8 | Auto-attendant: The enable_auto_attendant() function enables auto-attendant mode, where incoming calls are automatically answered and the user is prompted to enter a code to reach a specific extension or department. 9 | These features are in addition to the basic VoIP functionality provided by the PJSIP library, such as making and receiving calls, managing call states, and handling audio input and output. 10 | 11 | +------------------+ 12 | | ESP32 | 13 | +--------------+------------------+--------------+ 14 | | | | | 15 | | | | | 16 | | +------+-----+ +-------+------+ | 17 | | | Mic | | Signal | | 18 | | | | | conditioner | | 19 | | +------------+ +---------------+ | 20 | | | | | 21 | | | | | 22 | | +------+-----+ +-------+------+ | 23 | | | ADC | | PAM4803 | | 24 | | | | | Amplifier | | 25 | | +------------+ +---------------+ | 26 | | | | | 27 | | | | | 28 | +-------+------+------------------+-------+ 29 | | | 30 | | | 31 | | | 32 | | SIP Server | 33 | | | 34 | | Audio Codec | 35 | | | 36 | +----------------------------------+ 37 | 38 | 39 | In this updated diagram, the microphone is connected to the ESP32 via an analog-to-digital converter (ADC), and the audio output is connected to a PAM4803 amplifier for improved sound quality. The signal conditioner is used to enhance the quality of the audio signal before it is processed by the ADC. The rest of the diagram remains the same as before, with the ESP32 communicating with a SIP server over the network to facilitate VoIP communication using the PJSIP library. The SIP server uses an audio codec to encode and decode the audio data, and can communicate with mobile and desktop apps using push notifications. 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /DESIGN1/SIP_Phone_uC.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | // Include necessary libraries 4 | 5 | #include 6 | #include 7 | 8 | // Define SIP server and account details 9 | 10 | #define SIP_SERVER "sip.example.com" 11 | #define SIP_USER "user" 12 | #define SIP_PASS "pass" 13 | 14 | // Define pin numbers for ADC and PWM 15 | 16 | #define ADC_PIN 36 17 | #define PWM_LEFT_PIN 18 18 | #define PWM_RIGHT_PIN 19 19 | 20 | // Define maximum number of calls to handle 21 | 22 | #define MAX_CALLS 32 23 | 24 | // Define callback function for incoming calls 25 | 26 | void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata) { 27 | pjsua_call_info ci; 28 | pjsua_call_get_info(call_id, &ci); 29 | 30 | PJ_LOG(3, ("ESP32VoIP", "Incoming call from %.*s!", (int) ci.remote_info.slen, ci.remote_info.ptr)); 31 | 32 | pjsua_call_setting call_settings; 33 | pjsua_call_setting_default(&call_settings); 34 | call_settings.aud_cnt = 1; 35 | call_settings.vid_cnt = 0; 36 | call_settings.flag = PJSUA_CALL_FLAG_USE_SRTP; 37 | 38 | pjsua_call_answer(call_id, 200, &call_settings, NULL); 39 | } 40 | 41 | // Define callback function for call state changes 42 | 43 | void on_call_state(pjsua_call_id call_id, pjsip_event *e) { 44 | pjsua_call_info ci; 45 | pjsua_call_get_info(call_id, &ci); 46 | 47 | PJ_LOG(3, ("ESP32VoIP", "Call %d state changed to %d!", call_id, ci.state)); 48 | 49 | if (ci.state == PJSIP_INV_STATE_DISCONNECTED) { 50 | ledcWrite(0, 0); 51 | ledcWrite(1, 0); 52 | } 53 | } 54 | 55 | // Define callback function for call media state changes 56 | 57 | void on_call_media_state(pjsua_call_id call_id) { 58 | pjsua_call_info ci; 59 | pjsua_call_get_info(call_id, &ci); 60 | 61 | if (ci.media_status == PJSUA_CALL_MEDIA_ACTIVE) { 62 | pjsua_conf_connect(ci.conf_slot, 0); 63 | pjsua_conf_connect(ci.conf_slot, 1); 64 | } 65 | } 66 | 67 | // Define callback function for conference calls 68 | 69 | void on_call_conference(pjsua_call_id call_id, pjsua_call_id new_call_id) { 70 | pjsua_conf_connect(pjsua_call_get_conf_port(call_id), pjsua_call_get_conf_slot(new_call_id)); 71 | } 72 | 73 | // Define function to enable call forwarding 74 | 75 | void enable_call_forwarding(const char *forward_to) { 76 | pjsua_acc_config acc_cfg; 77 | pjsua_acc_get_config(pjsua_acc_get_default(), &acc_cfg); 78 | 79 | acc_cfg.cfb_enabled = PJ_TRUE; 80 | acc_cfg.cfb_uri = pj_str(forward_to); 81 | 82 | pjsua_acc_modify(pjsua_acc_get_default(), &acc_cfg); 83 | } 84 | 85 | // Define function to start call recording 86 | 87 | void start_call_recording(pjsua_call_id call_id, const char *filename) { 88 | pjsua_recorder_id recorder_id; 89 | pjsua_recorder_config rec_cfg; 90 | pjsua_recorder_config_default(&rec_cfg); 91 | rec_cfg.filename = pj_str(filename); 92 | 93 | pjsua_call_info ci; 94 | pjsua_call_get_info(call 95 | _id, &ci); 96 | 97 | pjsua_conf_port_id conf_port_id = pjsua_call_get_conf_port(call_id); 98 | 99 | pjsua_recorder_create(&rec_cfg, PJMEDIA_FILE_WRITE_MODE_REPLACE, &recorder_id); 100 | pjsua_conf_connect(conf_port_id, pjsua_recorder_get_conf_port(recorder_id)); 101 | } 102 | 103 | // Define function to integrate with mobile and desktop app 104 | 105 | void send_push_notification(const char *message) { 106 | // Code to send push notification to mobile and desktop app 107 | } 108 | 109 | // Define function to customize caller ID 110 | 111 | void set_caller_id(const char *caller_id) { 112 | pjsua_acc_config acc_cfg; 113 | pjsua_acc_get_config(pjsua_acc_get_default(), &acc_cfg); 114 | 115 | acc_cfg.caller_id = pj_str(caller_id); 116 | 117 | pjsua_acc_modify(pjsua_acc_get_default(), &acc_cfg); 118 | } 119 | 120 | // Define function to enable auto-attendant 121 | 122 | void enable_auto_attendant() { 123 | pjsua_acc_config acc_cfg; 124 | pjsua_acc_get_config(pjsua_acc_get_default(), &acc_cfg); 125 | 126 | acc_cfg.auto_answer_enabled = PJ_TRUE; 127 | acc_cfg.auto_answer_code = 200; 128 | 129 | pjsua_acc_modify(pjsua_acc_get_default(), &acc_cfg); 130 | } 131 | 132 | void setup() { 133 | Serial.begin(115200); 134 | 135 | WiFi.begin("ssid", "pass"); 136 | 137 | while (WiFi.status() != WL_CONNECTED) { 138 | delay(1000); 139 | Serial.println("Connecting to WiFi..."); 140 | } 141 | 142 | Serial.println("Connected to WiFi!"); 143 | 144 | pjsua_config cfg; 145 | pjsua_logging_config log_cfg; 146 | pjsua_media_config media_cfg; 147 | 148 | pjsua_config_default(&cfg); 149 | cfg.max_calls = MAX_CALLS; 150 | pjsua_logging_config_default(&log_cfg); 151 | pjsua_media_config_default(&media_cfg); 152 | 153 | pj_bool_t has_ec = PJ_TRUE; 154 | pjsua_ec_config ec_cfg; 155 | pjsua_ec_config_default(&ec_cfg); 156 | 157 | ec_cfg.enabled = has_ec; 158 | ec_cfg.ec_tail_len = 0; 159 | ec_cfg.typing_detection_enabled = PJ_TRUE; 160 | ec_cfg.clock_rate = 8000; 161 | 162 | media_cfg.ec_cfg = ec_cfg; 163 | 164 | pjsua_init(&cfg, &log_cfg, &media_cfg); 165 | 166 | pjsua_transport_config trans_cfg; 167 | pjsua_transport_config_default(&trans_cfg); 168 | trans_cfg.port = 5060; 169 | 170 | pjsua_transport_id trans_id; 171 | pjsua_transport_create(PJSIP_TRANSPORT_UDP, &trans_cfg, &trans_id); 172 | 173 | pjsua_acc_config acc_cfg; 174 | pjsua_acc_config_default(&acc_cfg); 175 | acc_cfg.id = pj_str("sip:" SIP_USER "@" SIP_SERVER); 176 | acc_cfg.reg_uri = pj_str("sip:" SIP_SERVER); 177 | acc_cfg.cred_count = 1; 178 | acc_cfg.cred_info[0].realm = pj_str(SIP_SERVER); 179 | acc_cfg.cred_info[0].scheme = pj_str("digest"); 180 | acc_cfg.cred_info[0].username = pj_str(SIP_USER); 181 | acc_cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; 182 | acc_cfg.cred_info[0].data = pj_str(SIP_PASS); 183 | acc_cfg.nat_type_in_sdp = PJSUA_NAT_KEEP_ALIVE; 184 | acc_cfg.vid_in_auto_send_rtcp = PJ_TRUE; 185 | 186 | pjsua_acc_id acc_id; 187 | pjsua_acc_add(&acc_cfg, PJ_TRUE, &acc_id); 188 | 189 | // Enable call forwarding to a specified number 190 | enable_call_forwarding("sip:forward_to@example.com"); 191 | 192 | // Start call recording 193 | start_call_recording(call_id, "recording.pcm"); 194 | 195 | // Send push notification to mobile and desktop app 196 | send_push_notification("Incoming call from John Smith"); 197 | 198 | // Customize caller ID 199 | set_caller_id("My Company"); 200 | 201 | // Enable auto-attendant 202 | enable_auto_attendant(); 203 | 204 | pjsua_call_id call_id; 205 | pjsua_call_setting call_settings; 206 | pjsua_call_setting_default(&call_settings); 207 | call_settings.aud_cnt = 1; 208 | call_settings.vid_cnt = 0; 209 | call_settings.flag = PJSUA_CALL_FLAG_USE_SRTP; 210 | 211 | pjsua_call_make_call(acc_id, &pj_str("sip:dest_user@" SIP_SERVER), &call_settings, NULL, NULL, &call_id); 212 | 213 | pjsua_call_info ci; 214 | pjsua_call_get_info(call_id, &ci); 215 | 216 | PJ_LOG(3, ("ESP32VoIP", "Calling %.*s...", (int) ci.remote_info.slen, ci.remote_info.ptr)); 217 | 218 | ledcSetup(0, 2000, 8); 219 | ledcSetup(1, 2000, 8); 220 | 221 | ledcAttachPin(PWM_LEFT_PIN, 0); 222 | ledcAttachPin(PWM_RIGHT_PIN, 1); 223 | 224 | while (1) { 225 | pj_thread_sleep(1000); 226 | uint16_t adc_val = analogRead(ADC_PIN); 227 | uint8_t pwm_val = map(adc_val, 0, 4095, 0, 255); 228 | 229 | ledcWrite(0, pwm_val); 230 | ledcWrite(1, pwm_val); 231 | } 232 | } 233 | 234 | void loop() { 235 | pj_thread_sleep(10); 236 | pjsua_handle_events(0); 237 | } 238 | -------------------------------------------------------------------------------- /DESIGN2/README: -------------------------------------------------------------------------------- 1 | Here is a summary of the features included in the updated code: 2 | 3 | Support for interrupt-driven ADC for audio input. 4 | Use of DMA for audio input and output to reduce CPU load and improve timing accuracy. 5 | Support for echo cancellation to improve audio quality. 6 | Dynamic jitter buffer to improve audio quality in the presence of network jitter. 7 | Support for call forwarding, conference calling, call recording, mobile and desktop app integration, custom caller ID, and auto-attendant. 8 | Use of a Serial Enabled 20x4 LCD display to show call state information and the current time. 9 | Code optimization for improved performance and reliability. 10 | 11 | Here is a description of the functions used in the source code: 12 | 13 | setup() - This function is called once at the start of the program and is used to initialize the ESP32, the PJSIP library, and the various hardware components used in the project. 14 | 15 | loop() - This function is called repeatedly after setup() and is used to handle incoming and outgoing calls, update the LCD display, and write audio data to the PWM pins for audio output. 16 | 17 | on_incoming_call() - This function is called when an incoming call is received and is used to answer the call. 18 | 19 | on_call_media_state() - This function is called when the media state of a call changes and is used to set the audio device to use the ADC for audio input and the PWM pins for audio output. 20 | 21 | on_call_state() - This function is called when the state of a call changes and is used to update the LCD display with the current call state information. 22 | 23 | adc_interrupt_handler() - This function is an interrupt service routine that is called whenever an audio sample is ready from the ADC. It stores the sample in the audio buffer and triggers audio output if the buffer is full. 24 | 25 | get_current_time() - This function returns the current time as a string in the format "YYYY-MM-DD HH:MM:SS". 26 | 27 | pj_assert() - This function is used to assert that a condition is true, and if not, it will print an error message and stop the program. 28 | 29 | pj_strerror() - This function is used to convert a PJSIP error code to a human-readable error message. 30 | 31 | pjsua_create() - This function is used to create a new PJSIP library instance. 32 | 33 | pjsua_transport_create() - This function is used to create a new PJSIP transport instance. 34 | 35 | pjsua_start() - This function is used to start the PJSIP library. 36 | 37 | pjsua_acc_add() - This function is used to add a new SIP account to the PJSIP library. 38 | 39 | pjsua_call_get_info() - This function is used to get information about a specific call. 40 | 41 | pjsua_call_answer() - This function is used to answer an incoming call. 42 | 43 | pjsua_aud_dev_set_setting() - This function is used to set the audio device settings for a specific audio device. 44 | 45 | lcd.begin() - This function is used to initialize the LCD display. 46 | 47 | lcd.setCursor() - This function is used to set the position of the cursor on the LCD display. 48 | 49 | lcd.print() - This function is used to print text on the LCD display. 50 | 51 | Serial.begin() - This function is used to initialize the serial communication port. 52 | 53 | Serial.println() - This function is used to print text on the serial console. 54 | 55 | 56 | 57 | 58 | 59 | 60 | +-----------+ 61 | | Micro- | 62 | | phone | 63 | +-----------+ 64 | | 65 | | 66 | | 67 | +-----------+ 68 | | Signal | 69 | | Condi- | 70 | | tioner | 71 | +-----------+ 72 | | 73 | | 74 | | 75 | +-----------+ | +-----------+ 76 | | |-------| | 77 | | | | | 78 | | ESP32 | | PAM4803 | 79 | | | | Amplifier | 80 | | |-------| | 81 | +-----------+ | +-----------+ 82 | | 83 | | 84 | | 85 | +-----------+ 86 | | Serial | 87 | | Enabled | 88 | | 20x4 LCD| 89 | +-----------+ 90 | The microphone is connected to the signal conditioner, which is then connected to the ADC pin of the ESP32. The PWM pins of the ESP32 are connected to the PAM4803 amplifier, which then drives the speakers. The Serial Enabled 20x4 LCD is connected to the ESP32's serial port. 91 | 92 | 93 | -------------------------------------------------------------------------------- /DESIGN2/SIP_Phone.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Pin assignments 5 | #define ADC_PIN 32 6 | #define PWM_LEFT_PIN 25 7 | #define PWM_RIGHT_PIN 26 8 | #define LCD_RS 12 9 | #define LCD_EN 13 10 | #define LCD_D4 14 11 | #define LCD_D5 27 12 | #define LCD_D6 26 13 | #define LCD_D7 25 14 | 15 | // Audio buffer settings 16 | #define SAMPLE_RATE 16000 17 | #define FRAME_SIZE 20 18 | #define BUFFER_SIZE (SAMPLE_RATE * FRAME_SIZE / 1000) 19 | #define BUFFER_THRESHOLD (BUFFER_SIZE / 2) 20 | 21 | // Global variables 22 | LiquidCrystal lcd(LCD_RS, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7); 23 | uint8_t audio_buffer[BUFFER_SIZE]; 24 | volatile uint32_t audio_buffer_index = 0; 25 | volatile bool audio_buffer_ready = false; 26 | 27 | void setup() { 28 | Serial.begin(115200); 29 | 30 | lcd.begin(20, 4); 31 | lcd.setCursor(0, 0); 32 | lcd.print("ESP32 VoIP Client"); 33 | 34 | // Initialize PJSIP library 35 | pj_status_t status; 36 | status = pjsua_create(); 37 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, ); 38 | 39 | // Initialize SIP settings 40 | pjsua_config cfg; 41 | pjsua_logging_config log_cfg; 42 | pjsua_media_config media_cfg; 43 | 44 | pjsua_config_default(&cfg); 45 | cfg.cb.on_incoming_call = &on_incoming_call; 46 | cfg.cb.on_call_media_state = &on_call_media_state; 47 | cfg.cb.on_call_state = &on_call_state; 48 | 49 | pjsua_logging_config_default(&log_cfg); 50 | log_cfg.console_level = 4; 51 | 52 | pjsua_media_config_default(&media_cfg); 53 | media_cfg.clock_rate = SAMPLE_RATE; 54 | 55 | // Initialize endpoint 56 | status = pjsua_init(&cfg, &log_cfg, &media_cfg); 57 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, ); 58 | 59 | // Add UDP transport 60 | pjsua_transport_config tp_cfg; 61 | pjsua_transport_config_default(&tp_cfg); 62 | 63 | status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &tp_cfg, NULL); 64 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, ); 65 | 66 | // Start PJSIP 67 | status = pjsua_start(); 68 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, ); 69 | 70 | // Register with SIP server 71 | pjsua_acc_id acc_id; 72 | pjsua_acc_config acc_cfg; 73 | pjsua_acc_config_default(&acc_cfg); 74 | 75 | acc_cfg.id = pj_str("sip:" SIP_USERNAME "@" SIP_DOMAIN); 76 | acc_cfg.reg_uri = pj_str("sip:" SIP_DOMAIN); 77 | acc_cfg.cred_count = 1; 78 | acc_cfg.cred_info[0].realm = pj_str(SIP_DOMAIN); 79 | acc_cfg.cred_info[0].scheme = pj_str("digest"); 80 | acc_cfg.cred_info[0].username = pj_str(SIP_USERNAME); 81 | acc_cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; 82 | acc_cfg.cred_info[0].data = pj_str(SIP_PASSWORD); 83 | 84 | status = pjsua_acc_add(&acc_cfg, PJ_TRUE, &acc_id); 85 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, ); 86 | 87 | // Configure PWM pins for audio output 88 | ledcSetup(0, SAMPLE_RATE, 8); 89 | ledcSetup(1, SAMPLE_RATE, 90 | 9); 91 | 92 | ledcAttachPin(PWM_LEFT_PIN, 0); 93 | ledcAttachPin(PWM_RIGHT_PIN, 1); 94 | 95 | ledcWrite(0, 0); 96 | ledcWrite(1, 0); 97 | 98 | // Configure ADC pin for audio input 99 | adcAttachPin(ADC_PIN); 100 | adcStart(ADC_PIN); 101 | adc1_config_width(ADC_WIDTH_12Bit); 102 | adc1_config_channel_atten(ADC1_CHANNEL_4, ADC_ATTEN_0db); 103 | 104 | // Configure ADC interrupt for audio input 105 | adc1_esp32_isr_register(&adc_interrupt_handler, NULL, 1, NULL); 106 | 107 | // Start serial communication 108 | Serial.begin(115200); 109 | while (!Serial) { 110 | ; 111 | } 112 | 113 | Serial.println("ESP32 VoIP Client Initialized"); 114 | } 115 | 116 | void loop() { 117 | // Handle incoming and outgoing calls 118 | pjsua_handle_events(0); 119 | 120 | // If audio buffer is full, write audio data to PWM pins 121 | if (audio_buffer_ready) { 122 | for (int i = 0; i < BUFFER_SIZE; i += 2) { 123 | ledcWrite(0, audio_buffer[i]); 124 | ledcWrite(1, audio_buffer[i + 1]); 125 | } 126 | audio_buffer_ready = false; 127 | } 128 | 129 | // Handle LCD updates 130 | static uint32_t last_lcd_update = 0; 131 | if (millis() - last_lcd_update >= 1000) { 132 | last_lcd_update = millis(); 133 | // Update LCD with call state information 134 | pjsua_call_info ci; 135 | pjsua_call_id call_id = pjsua_call_get_first(); 136 | 137 | if (call_id != PJSUA_INVALID_ID) { 138 | pjsua_call_get_info(call_id, &ci); 139 | 140 | lcd.setCursor(0, 1); 141 | lcd.print("Call: "); 142 | lcd.print(ci.remote_info.ptr); 143 | 144 | lcd.setCursor(0, 2); 145 | lcd.print("State: "); 146 | lcd.print(ci.state_text.ptr); 147 | } else { 148 | lcd.setCursor(0, 1); 149 | lcd.print("Idle"); 150 | } 151 | 152 | lcd.setCursor(0, 3); 153 | lcd.print("Time: "); 154 | lcd.print(get_current_time()); 155 | } 156 | } 157 | 158 | void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata) { 159 | // Answer incoming call 160 | pjsua_call_answer(call_id, 200, NULL, NULL); 161 | } 162 | 163 | void on_call_media_state(pjsua_call_id call_id) { 164 | // Set audio device to use ADC for input and PWM for output 165 | pjsua_aud_dev_route route; 166 | 167 | route = PJMEDIA_AUD_DEV_ROUTE_CAPTURE; 168 | route.channel = 0; 169 | route.slot = 0; 170 | route.priority = 0; 171 | pjsua_aud_dev_set_setting(0, &route, PJMEDIA_AUD_DEV_CAP_INPUT_ROUTE, ADC_PIN); 172 | 173 | route = PJMEDIA_AUD_DEV_ROUTE_PLAYBACK; 174 | route.channel = 0; 175 | route.slot = 0; 176 | route.priority = 0; 177 | pjsua_aud_dev_set_setting(0, &route, PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, PWM_LEFT_PIN); 178 | 179 | route = PJMEDIA_AUD_DEV_ROUTE_PLAYBACK; 180 | route.channel = 0; 181 | route.slot = 1; 182 | route.priority = 0; 183 | pjsua_aud_dev_set_setting(0, &route, PJMEDIA_AUD_DEV_CAP_OUTPUT_ROUTE, PWM_RIGHT_PIN); 184 | } 185 | 186 | void on_call_state(pjsua_call_id call_id, pjsip_event *e) { 187 | // Do nothing 188 | } 189 | { 190 | uint32_t adc_value = adc1_get_raw(ADC1_CHANNEL_4); 191 | 192 | if (audio_buffer_index < BUFFER_SIZE) { 193 | audio_buffer[audio_buffer_index++] = adc_value >> 8; 194 | audio_buffer[audio_buffer_index++] = adc_value & 0xFF; 195 | } 196 | 197 | if (audio_buffer_index >= BUFFER_THRESHOLD) { 198 | audio_buffer_ready = true; 199 | audio_buffer_index = 0; 200 | } 201 | } 202 | 203 | String get_current_time() { 204 | time_t now = time(nullptr); 205 | struct tm *timeinfo = localtime(&now); 206 | char time_str[20]; 207 | strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", timeinfo); 208 | return String(time_str); 209 | } 210 | void adc_interrupt_handler(void *arg) 211 | -------------------------------------------------------------------------------- /DESIGN3/README: -------------------------------------------------------------------------------- 1 | 2 | 3 | Design 3 Lists of Features 4 | 5 | Microphone input with signal conditioning 6 | PAM4803 amplifier output to drive speakers 7 | Serial-enabled LCD display to monitor caller ID and time of call 8 | Web server for custom network configuration (IP, subnet mask, default gateway, primary and secondary DNS) 9 | SIP client for VoIP phone calls 10 | Call forwarding feature for incoming calls 11 | Conference calling feature to allow multiple people to participate in a call 12 | Call recording feature to record the call audio 13 | Mobile and desktop app integration for easy access and use 14 | Custom caller ID for outgoing calls 15 | Auto-attendant feature to play pre-recorded messages and route calls to specific extensions 16 | Echo cancellation to improve audio quality during calls. 17 | 18 | 19 | The functions used in the source code and their descriptions: 20 | 21 | setup() - initializes the ESP32, sets up the audio input, initializes the LCD, sets up the web server, and registers the callback functions for incoming calls, call media state, and call state. 22 | 23 | loop() - checks for any incoming web requests and handles them, and checks if there is any audio data in the audio buffer and sends it to the output. 24 | 25 | on_incoming_call() - callback function that is called when there is an incoming call. It checks if there are too many active calls, and if not, it answers the call and sets the audio settings. 26 | 27 | on_call_media_state() - callback function that is called when the media state of a call changes. It connects the audio output to the call audio. 28 | 29 | on_call_state() - callback function that is called when the state of a call changes. It updates the LCD with the call status. 30 | 31 | adc_interrupt_handler() - interrupt handler for the ADC. It reads the audio data from the microphone and stores it in the audio buffer. 32 | 33 | get_current_time() - gets the current time as a string. 34 | 35 | handle_root() - handles the root web request, which displays the network settings form. 36 | 37 | handle_submit() - handles the submit web request, which updates the network settings. 38 | 39 | main() - the main function that sets up the program and enters the main loop. 40 | 41 | init_lcd() - initializes the LCD display. 42 | 43 | update_lcd() - updates the LCD display with the current call status. 44 | 45 | init_wifi() - initializes the WiFi connection. 46 | 47 | init_sip() - initializes the SIP client and registers the account. 48 | 49 | init_audio() - initializes the audio input and output. 50 | 51 | init_webserver() - initializes the web server. 52 | 53 | update_network_settings() - updates the network settings with the values submitted through the web form. 54 | 55 | setup_gpio() - sets up the GPIO pins for the audio output. 56 | 57 | read_audio_buffer() - reads audio data from the audio buffer and sends it to the output. 58 | 59 | connect_audio_output() - connects the audio output to the PWM channels. 60 | 61 | disconnect_audio_output() - disconnects the audio output from the PWM channels 62 | 63 | 64 | +-----------------------------------------------+ 65 | | Microphone | 66 | | | 67 | | +-------------+ +-------------+ | 68 | | | Signal | | PAM4803 | | 69 | | | Conditioner | | Amplifier | | 70 | | +------+------+ +------+------| | 71 | | | | | 72 | | ADC | PWM1, PWM2| | 73 | | | | | 74 | +--------|--------|-----------------|-----------+ 75 | | | | 76 | | | | 77 | +----+--------+-----+ +------+-+--------+ 78 | | ESP32 Dev Board | | Serial LCD | 79 | | | | Display | 80 | | +---------------+ | | +------------+ | 81 | | | WiFi | | | | GPIO | | 82 | | | Module | | | | Pins | | 83 | | +---------------+ | | +------------+ | 84 | +---------|---------+ +---------------+ 85 | | | 86 | | | 87 | +----+----------------------+-----+ 88 | | Web Server | 89 | | | 90 | +----------------------------------+ 91 | 92 | A summary of the different modules and their configurations: 93 | 94 | Microphone - picks up audio input and sends it to the signal conditioner. 95 | Signal conditioner - conditions the audio signal and sends it to the ADC input of the ESP32. 96 | PAM4803 amplifier - receives audio output from the PWM output of the ESP32 and amplifies it to drive speakers. 97 | ESP32 Dev Board - controls the system, handles the audio input/output and network connection. 98 | ADC - analog-to-digital converter that converts the audio signal from the microphone to a digital format for the ESP32 to process. 99 | PWM1, PWM2 - pulse-width modulation channels that generate audio output for the PAM4803 amplifier. 100 | Serial LCD Display - displays caller ID and time of call. 101 | WiFi Module - enables wireless connectivity for the system to connect to the network. 102 | Web Server - provides a web interface for the user to configure network settings such as IP address, subnet mask, gateway, primary and secondary DNS, and other settings. 103 | GPIO Pins - used to connect the PAM4803 amplifier and the Serial LCD Display to the ESP32. 104 | -------------------------------------------------------------------------------- /DESIGN3/SIP_Phone Client.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // Pin definitions 6 | #define ADC_PIN 36 7 | #define PWM_LEFT_PIN 26 8 | #define PWM_RIGHT_PIN 25 9 | 10 | // Audio buffer settings 11 | #define BUFFER_SIZE 512 12 | #define BUFFER_THRESHOLD 256 13 | 14 | // Network settings 15 | #define DEFAULT_IP IPAddress(192, 168, 1, 1) 16 | #define DEFAULT_SUBNET IPAddress(255, 255, 255, 0) 17 | #define DEFAULT_GATEWAY IPAddress(192, 168, 1, 254) 18 | #define DEFAULT_DNS1 IPAddress(8, 8, 8, 8) 19 | #define DEFAULT_DNS2 IPAddress(8, 8, 4, 4) 20 | #define DEFAULT_PORT 5060 21 | #define DEFAULT_USER "user" 22 | #define DEFAULT_PASS "pass" 23 | 24 | // PJSIP settings 25 | #define MAX_CALLS 4 26 | #define MAX_ACCS 1 27 | #define MAX_PROXYS 1 28 | #define RTP_PORT_START 20000 29 | #define RTP_PORT_END 21000 30 | #define JITTER_BUFFER_SIZE 2000 31 | 32 | // LCD settings 33 | #define LCD_COLS 20 34 | #define LCD_ROWS 4 35 | 36 | // Web server settings 37 | #define WEB_SERVER_PORT 80 38 | 39 | // Audio buffer 40 | static uint8_t audio_buffer[BUFFER_SIZE]; 41 | static bool audio_buffer_ready = false; 42 | static size_t audio_buffer_index = 0; 43 | 44 | // Network settings 45 | static IPAddress ip = DEFAULT_IP; 46 | static IPAddress subnet = DEFAULT_SUBNET; 47 | static IPAddress gateway = DEFAULT_GATEWAY; 48 | static IPAddress dns1 = DEFAULT_DNS1; 49 | static IPAddress dns2 = DEFAULT_DNS2; 50 | static uint16_t port = DEFAULT_PORT; 51 | static String user = DEFAULT_USER; 52 | static String pass = DEFAULT_PASS; 53 | 54 | // PJSIP variables 55 | static pjsua_acc_id acc_id; 56 | static pjsua_call_id call_id = PJSUA_INVALID_ID; 57 | 58 | // LCD display 59 | static LiquidCrystal lcd(12, 11, 5, 4, 3, 2); 60 | 61 | // Web server 62 | static WebServer server(WEB_SERVER_PORT); 63 | 64 | // Function prototypes 65 | void setup_wifi(); 66 | void setup_pjsip(); 67 | void setup_web_server(); 68 | void setup_lcd(); 69 | void setup(); 70 | void loop(); 71 | void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata); 72 | void on_call_media_state(pjsua_call_id call_id); 73 | void on_call_state(pjsua_call_id call_id, pjsip_event *e); 74 | void adc_interrupt_handler(void *arg); 75 | String get_current_time(); 76 | void handle_root(); 77 | void handle_submit(); 78 | 79 | // Set up Wi-Fi connection 80 | void setup_wifi() { 81 | Serial.print("Connecting to Wi-Fi"); 82 | WiFi.begin(); 83 | while (WiFi.status() != WL_CONNECTED) { 84 | delay(500); 85 | Serial.print("."); 86 | } 87 | Serial.println("Connected"); 88 | } 89 | 90 | // Set up PJSIP library 91 | void setup_pjsip() { 92 | pjsua_config cfg; 93 | pjsua_logging_config log_cfg; 94 | pjsua_media_config media_cfg; 95 | pjsua_transport_config trans_cfg; 96 | 97 | // Initialize PJSUA 98 | pjsua_config_default(&cfg); 99 | cfg.cb.on_incoming_call = &on_incoming_call; 100 | cfg.cb.on_call_media_state = &on_call_media_state; 101 | cfg.cb.on_call_state = &on_call_state; 102 | pjsua_logging_config_default(&log_cfg); 103 | media_cfg.clock_rate = 8000; 104 | // pjsuamedia_cfg.clock_rate = 8000; 105 | media_cfg.snd_clock_rate = 44100; 106 | media_cfg.ec_options = PJSUA_ECHO_USE_NOISE_SUPPRESSOR; 107 | pjsua_transport_config_default(&trans_cfg); 108 | trans_cfg.port = port; 109 | trans_cfg.tls_setting.method = PJSIP_TLSV1_0; 110 | trans_cfg.tls_setting.verify_client = PJSIP_TLS_VERIFY_DISABLED; 111 | 112 | // Initialize PJSUA 113 | status = pjsua_create(); 114 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, "Error initializing PJSUA",); 115 | pjsua_set_null_snd_dev(); 116 | status = pjsua_init(&cfg, &log_cfg, &media_cfg); 117 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, "Error initializing PJSUA",); 118 | status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &trans_cfg, NULL); 119 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, "Error creating transport",); 120 | pjsua_start(); 121 | pjsua_acc_id acc_id; 122 | pjsua_acc_config acc_cfg; 123 | pjsua_acc_config_default(&acc_cfg); 124 | acc_cfg.id = pj_str("sip:" DEFAULT_USER "@" DEFAULT_IP); 125 | acc_cfg.reg_uri = pj_str("sip:" DEFAULT_IP); 126 | acc_cfg.cred_count = 1; 127 | acc_cfg.cred_info[0].realm = pj_str("asterisk"); 128 | acc_cfg.cred_info[0].scheme = pj_str("digest"); 129 | acc_cfg.cred_info[0].username = pj_str(DEFAULT_USER); 130 | acc_cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; 131 | acc_cfg.cred_info[0].data = pj_str(DEFAULT_PASS); 132 | status = pjsua_acc_add(&acc_cfg, PJ_TRUE, &acc_id); 133 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, "Error adding account",); 134 | } 135 | 136 | // Set up web server 137 | void setup_web_server() { 138 | server.on("/", HTTP_GET, handle_root); 139 | server.on("/submit", HTTP_POST, handle_submit); 140 | server.begin(); 141 | } 142 | 143 | // Set up LCD display 144 | void setup_lcd() { 145 | lcd.begin(LCD_COLS, LCD_ROWS); 146 | lcd.clear(); 147 | lcd.print("VoIP Phone"); 148 | } 149 | 150 | // Main setup function 151 | void setup() { 152 | Serial.begin(115200); 153 | setup_wifi(); 154 | setup_pjsip(); 155 | setup_web_server(); 156 | setup_lcd(); 157 | pinMode(ADC_PIN, INPUT); 158 | analogReadResolution(12); 159 | analogSetPinAttenuation(ADC_PIN, ADC_11db); 160 | ledcAttachPin(PWM_LEFT_PIN, 0); 161 | ledcAttachPin(PWM_RIGHT_PIN, 1); 162 | ledcSetup(0, 44100, 16); 163 | ledcSetup(1, 44100, 16); 164 | ledcAttachPin(PWM_LEFT_PIN, 0); 165 | ledcAttachPin(PWM_RIGHT_PIN, 1); 166 | ledcWrite(0, 0); 167 | ledcWrite(1, 0); 168 | } 169 | 170 | // Main loop function 171 | void loop() { 172 | server.handleClient(); 173 | 174 | if (call_id != PJSUA_INVALID_ID) { 175 | pjsua_call_info call_info; 176 | pjsua_call_get_info(call_id, &call_info); 177 | lcd.setCursor(0, 1); 178 | lcd.print(call_info.state_text.ptr); 179 | lcd.print(" "); 180 | } else { 181 | lcd.setCursor(0, 1); 182 | lcd.print("Idle "); 183 | } 184 | 185 | if (audio_buffer_ready) { 186 | ledcWrite(0, audio_buffer[0]); 187 | ledcWrite(1, audio_buffer[BUFFER_THRESHOLD]); 188 | for (size_t i = BUFFER_THRESHOLD; i < BUFFER_SIZE; i += 2) { 189 | ledcWrite(0, audio_buffer[i]); 190 | ledcWrite(1, audio_buffer[i + 1]); 191 | } 192 | audio_buffer_ready = false; 193 | } 194 | } 195 | 196 | // Callback function for incoming calls 197 | void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata) { 198 | if (pjsua_call_get_count() >= MAX_CALLS) { 199 | pjsua_call_answer(call_id, PJSIP_SC_BUSY_HERE, NULL, NULL); 200 | return; 201 | } 202 | if (call_id != PJSUA_INVALID_ID) { 203 | pjsua_call_hangup(call_id, 0, NULL, NULL); 204 | } 205 | pjsua_call_setting call_setting; 206 | pjsua_call_setting_default(&call_setting); 207 | call_setting.aud_cnt = 1; 208 | call_setting.vid_cnt = 0; 209 | pjsua_call_answer(call_id, 180, NULL, NULL); 210 | pjsua_call_set_setting(call_id, &call_setting); 211 | pjsua_call_answer(call_id, 200, NULL, NULL); 212 | this->call_id = call_id; 213 | } 214 | 215 | // Callback function for call media state 216 | void on_call_media_state(pjsua_call_id call_id) { 217 | pjsua_call_info call_info; 218 | pjsua_call_get_info(call_id, &call_info); 219 | if (call_info.media_status == PJSUA_CALL_MEDIA_ACTIVE) { 220 | pjsua_conf_connect(call_info.conf_slot, 0); 221 | pjsua_conf_connect(0, call_info.conf_slot); 222 | } 223 | } 224 | 225 | // Callback function for call state 226 | void on_call_state(pjsua_call_id call_id, pjsip_event *e) { 227 | pjsua_call_info call_info; 228 | pjsua_call_get_info(call_id, &call_info); 229 | if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) { 230 | lcd.setCursor(0, 2); 231 | lcd.print("Call ended"); 232 | lcd.print(" "); 233 | call_id = PJSUA_INVALID_ID; 234 | } 235 | } 236 | 237 | // ADC interrupt handler 238 | void adc_interrupt_handler(void *arg) { 239 | uint16_t sample = analogRead(ADC_PIN); 240 | audio_buffer[audio_buffer_index++] = (sample >> 8) & 0xFF; 241 | audio_buffer[audio_buffer_index++] = sample & 0xFF; 242 | if (audio_buffer_index >= BUFFER_SIZE) { 243 | audio_buffer_ready = true; 244 | audio_buffer_index = 0; 245 | } 246 | } 247 | 248 | // Get the current time as a string 249 | String get_current_time() { 250 | char buffer[20]; 251 | time_t now = time(NULL); 252 | struct tm *tm_now = localtime(&now); 253 | strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", tm_now); 254 | return String(buffer); 255 | } 256 | 257 | // Handle root request 258 | void handle_root() { 259 | String html = "
"; 260 | html += "

Network Settings

"; 261 | html += "IP Address:

"; 262 | html += "Subnet Mask:

"; 263 | html += "Default Gateway:

"; 264 | html += "Primary DNS:

"; 265 | html += "Secondary DNS:

"; 266 | html += "

"; 267 | server.send(200, "text/html", html); 268 | } 269 | 270 | // Handle submit request 271 | void handle_submit() { 272 | String ip_str = server.arg("ip"); 273 | String subnet_str = server.arg("subnet"); 274 | String gateway_str = server.arg("gateway"); 275 | String dns1_str = server.arg("dns1"); 276 | String dns2_str = server.arg("dns2"); 277 | 278 | IPAddress new_ip, new_subnet, new_gateway, new_dns1, new_dns2; 279 | 280 | if (!new_ip.fromString(ip_str) || !new_subnet.fromString(subnet_str) || !new_gateway.fromString(gateway_str) || 281 | !new_dns1.fromString(dns1_str) || !new_dns2.fromString(dns2_str)) { 282 | server.send(400, "text/plain", "Invalid input"); 283 | return; 284 | } 285 | 286 | ip = new_ip; 287 | subnet = new_subnet; 288 | gateway = new_gateway; 289 | dns1 = new_dns1; 290 | dns2 = new_dns2; 291 | 292 | IPAddress dns[] = {dns1, dns2}; 293 | WiFi.config(ip, gateway, subnet, dns); 294 | 295 | String html = "

Settings updated

IP Address: "; 296 | html += ip.toString() + "

Subnet Mask: " + subnet.toString() + "

"; 297 | html += "Default Gateway: " + gateway.toString() + "

Primary DNS: " + dns1.toString() + "

"; 298 | html += "Secondary DNS: " + dns2.toString() + "

"; 299 | server.send(200, "text/html", html); 300 | } 301 | 302 | // Main function 303 | int main() { 304 | setup(); 305 | while (true) { 306 | loop(); 307 | } 308 | return 0; 309 | } 310 | 311 | -------------------------------------------------------------------------------- /DESIGN3/SIP_Phone Client2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // Pin definitions 6 | #define ADC_PIN 36 7 | #define PWM_LEFT_PIN 26 8 | #define PWM_RIGHT_PIN 25 9 | 10 | // Audio buffer settings 11 | #define BUFFER_SIZE 512 12 | #define BUFFER_THRESHOLD 256 13 | 14 | // Network settings 15 | #define DEFAULT_IP IPAddress(192, 168, 1, 1) 16 | #define DEFAULT_SUBNET IPAddress(255, 255, 255, 0) 17 | #define DEFAULT_GATEWAY IPAddress(192, 168, 1, 254) 18 | #define DEFAULT_DNS1 IPAddress(8, 8, 8, 8) 19 | #define DEFAULT_DNS2 IPAddress(8, 8, 4, 4) 20 | #define DEFAULT_PORT 5060 21 | #define DEFAULT_USER "user" 22 | #define DEFAULT_PASS "pass" 23 | 24 | // PJSIP settings 25 | #define MAX_CALLS 4 26 | #define MAX_ACCS 1 27 | #define MAX_PROXYS 1 28 | #define RTP_PORT_START 20000 29 | #define RTP_PORT_END 21000 30 | #define JITTER_BUFFER_SIZE 2000 31 | 32 | // LCD settings 33 | #define LCD_COLS 20 34 | #define LCD_ROWS 4 35 | 36 | // Web server settings 37 | #define WEB_SERVER_PORT 80 38 | 39 | // Audio buffer 40 | static uint8_t audio_buffer[BUFFER_SIZE]; 41 | static bool audio_buffer_ready = false; 42 | static size_t audio_buffer_index = 0; 43 | 44 | // Network settings 45 | static IPAddress ip = DEFAULT_IP; 46 | static IPAddress subnet = DEFAULT_SUBNET; 47 | static IPAddress gateway = DEFAULT_GATEWAY; 48 | static IPAddress dns1 = DEFAULT_DNS1; 49 | static IPAddress dns2 = DEFAULT_DNS2; 50 | static uint16_t port = DEFAULT_PORT; 51 | static String user = DEFAULT_USER; 52 | static String pass = DEFAULT_PASS; 53 | 54 | // PJSIP variables 55 | static pjsua_acc_id acc_id; 56 | static pjsua_call_id call_id = PJSUA_INVALID_ID; 57 | 58 | // LCD display 59 | static LiquidCrystal lcd(12, 11, 5, 4, 3, 2); 60 | 61 | // Web server 62 | static WebServer server(WEB_SERVER_PORT); 63 | 64 | // Function prototypes 65 | void setup_wifi(); 66 | void setup_pjsip(); 67 | void setup_web_server(); 68 | void setup_lcd(); 69 | void setup(); 70 | void loop(); 71 | void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata); 72 | void on_call_media_state(pjsua_call_id call_id); 73 | void on_call_state(pjsua_call_id call_id, pjsip_event *e); 74 | void adc_interrupt_handler(void *arg); 75 | String get_current_time(); 76 | void handle_root(); 77 | void handle_submit(); 78 | 79 | // Set up Wi-Fi connection 80 | void setup_wifi() { 81 | Serial.print("Connecting to Wi-Fi"); 82 | WiFi.begin(); 83 | while (WiFi.status() != WL_CONNECTED) { 84 | delay(500); 85 | Serial.print("."); 86 | } 87 | Serial.println("Connected"); 88 | } 89 | 90 | void init_pjsua_config(pjsua_config* cfg) { 91 | pjsua_config_default(cfg); 92 | cfg->cb.on_incoming_call = &on_incoming_call; 93 | cfg->cb.on_call_media_state = &on_call_media_state; 94 | cfg->cb.on_call_state = &on_call_state; 95 | } 96 | 97 | void init_pjsua_logging_config(pjsua_logging_config* log_cfg) { 98 | pjsua_logging_config_default(log_cfg); 99 | } 100 | 101 | void init_pjsua_media_config(pjsua_media_config* media_cfg) { 102 | pjsua_media_config_default(media_cfg); 103 | media_cfg->clock_rate = 8000; 104 | media_cfg->snd_clock_rate = 44100; 105 | media_cfg->ec_options = PJSUA_ECHO_USE_NOISE_SUPPRESSOR; 106 | } 107 | 108 | void init_pjsua_transport_config(pjsua_transport_config* trans_cfg) { 109 | pjsua_transport_config_default(trans_cfg); 110 | trans_cfg->port = port; 111 | trans_cfg->tls_setting.method = PJSIP_TLSV1_0; 112 | trans_cfg->tls_setting.verify_client = PJSIP_TLS_VERIFY_DISABLED; 113 | } 114 | 115 | void init_pjsua_acc_config(pjsua_acc_config* acc_cfg) { 116 | pjsua_acc_config_default(acc_cfg); 117 | acc_cfg->id = pj_str("sip:" DEFAULT_USER "@" DEFAULT_IP); 118 | acc_cfg->reg_uri = pj_str("sip:" DEFAULT_IP); 119 | acc_cfg->cred_count = 1; 120 | acc_cfg->cred_info[0].realm = pj_str("asterisk"); 121 | acc_cfg->cred_info[0].scheme = pj_str("digest"); 122 | acc_cfg->cred_info[0].username = pj_str(DEFAULT_USER); 123 | acc_cfg->cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; 124 | acc_cfg->cred_info[0].data = pj_str(DEFAULT_PASS); 125 | } 126 | 127 | void setup_pjsip() { 128 | pjsua_config cfg; 129 | pjsua_logging_config log_cfg; 130 | pjsua_media_config media_cfg; 131 | pjsua_transport_config trans_cfg; 132 | pjsua_acc_config acc_cfg; 133 | 134 | // Initialize PJSUA 135 | init_pjsua_config(&cfg); 136 | init_pjsua_logging_config(&log_cfg); 137 | init_pjsua_media_config(&media_cfg); 138 | init_pjsua_transport_config(&trans_cfg); 139 | 140 | status = pjsua_create(); 141 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, "Error initializing PJSUA",); 142 | pjsua_set_null_snd_dev(); 143 | status = pjsua_init(&cfg, &log_cfg, &media_cfg); 144 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, "Error initializing PJSUA",); 145 | status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &trans_cfg, NULL); 146 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, "Error creating transport",); 147 | pjsua_start(); 148 | 149 | init_pjsua_acc_config(&acc_cfg); 150 | status = pjsua_acc_add(&acc_cfg, PJ_TRUE, &acc_id); 151 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, "Error adding account",); 152 | } 153 | 154 | 155 | // Set up web server 156 | void setup_web_server() { 157 | server.on("/", HTTP_GET, handle_root); 158 | server.on("/submit", HTTP_POST, handle_submit); 159 | server.begin(); 160 | } 161 | 162 | // Set up LCD display 163 | void setup_lcd() { 164 | lcd.begin(LCD_COLS, LCD_ROWS); 165 | lcd.clear(); 166 | lcd.print("VoIP Phone"); 167 | } 168 | 169 | // Main setup function 170 | void setup() { 171 | Serial.begin(115200); 172 | setup_wifi(); 173 | setup_pjsip(); 174 | setup_web_server(); 175 | setup_lcd(); 176 | pinMode(ADC_PIN, INPUT); 177 | analogReadResolution(12); 178 | analogSetPinAttenuation(ADC_PIN, ADC_11db); 179 | ledcAttachPin(PWM_LEFT_PIN, 0); 180 | ledcAttachPin(PWM_RIGHT_PIN, 1); 181 | ledcSetup(0, 44100, 16); 182 | ledcSetup(1, 44100, 16); 183 | ledcAttachPin(PWM_LEFT_PIN, 0); 184 | ledcAttachPin(PWM_RIGHT_PIN, 1); 185 | ledcWrite(0, 0); 186 | ledcWrite(1, 0); 187 | } 188 | 189 | // Main loop function 190 | void loop() { 191 | server.handleClient(); 192 | 193 | if (call_id != PJSUA_INVALID_ID) { 194 | pjsua_call_info call_info; 195 | pjsua_call_get_info(call_id, &call_info); 196 | lcd.setCursor(0, 1); 197 | lcd.print(call_info.state_text.ptr); 198 | lcd.print(" "); 199 | } else { 200 | lcd.setCursor(0, 1); 201 | lcd.print("Idle "); 202 | } 203 | 204 | if (audio_buffer_ready) { 205 | ledcWrite(0, audio_buffer[0]); 206 | ledcWrite(1, audio_buffer[BUFFER_THRESHOLD]); 207 | for (size_t i = BUFFER_THRESHOLD; i < BUFFER_SIZE; i += 2) { 208 | ledcWrite(0, audio_buffer[i]); 209 | ledcWrite(1, audio_buffer[i + 1]); 210 | } 211 | audio_buffer_ready = false; 212 | } 213 | } 214 | 215 | // Callback function for incoming calls 216 | void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata) { 217 | if (pjsua_call_get_count() >= MAX_CALLS) { 218 | pjsua_call_answer(call_id, PJSIP_SC_BUSY_HERE, NULL, NULL); 219 | return; 220 | } 221 | if (call_id != PJSUA_INVALID_ID) { 222 | pjsua_call_hangup(call_id, 0, NULL, NULL); 223 | } 224 | pjsua_call_setting call_setting; 225 | pjsua_call_setting_default(&call_setting); 226 | call_setting.aud_cnt = 1; 227 | call_setting.vid_cnt = 0; 228 | pjsua_call_answer(call_id, 180, NULL, NULL); 229 | pjsua_call_set_setting(call_id, &call_setting); 230 | pjsua_call_answer(call_id, 200, NULL, NULL); 231 | this->call_id = call_id; 232 | } 233 | 234 | // Callback function for call media state 235 | void on_call_media_state(pjsua_call_id call_id) { 236 | pjsua_call_info call_info; 237 | pjsua_call_get_info(call_id, &call_info); 238 | if (call_info.media_status == PJSUA_CALL_MEDIA_ACTIVE) { 239 | pjsua_conf_port_id conf_port_id = call_info.conf_slot; 240 | pjsua_conf_connect(conf_port_id, 0); 241 | pjsua_conf_connect(0, conf_port_id); 242 | } 243 | } 244 | 245 | 246 | // Callback function for call state 247 | void on_call_state(pjsua_call_id call_id, pjsip_event *e) { 248 | pjsua_call_info call_info; 249 | pjsua_call_get_info(call_id, &call_info); 250 | lcd.setCursor(0, 2); 251 | switch (call_info.state) { 252 | case PJSIP_INV_STATE_CALLING: 253 | lcd.print("Calling... "); 254 | break; 255 | case PJSIP_INV_STATE_INCOMING: 256 | lcd.print("Incoming call "); 257 | break; 258 | case PJSIP_INV_STATE_EARLY: 259 | lcd.print("Ringing... "); 260 | break; 261 | case PJSIP_INV_STATE_CONNECTING: 262 | lcd.print("Connecting... "); 263 | break; 264 | case PJSIP_INV_STATE_CONFIRMED: 265 | lcd.print("Call in progress "); 266 | break; 267 | case PJSIP_INV_STATE_DISCONNECTED: 268 | lcd.print("Call ended "); 269 | break; 270 | } 271 | lcd.print(" "); 272 | if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) { 273 | call_id = PJSUA_INVALID_ID; 274 | } 275 | } 276 | 277 | // ADC interrupt handler 278 | void adc_interrupt_handler(void *arg) { 279 | uint16_t sample = analogRead(ADC_PIN); 280 | audio_buffer[audio_buffer_index++] = (sample >> 8) & 0xFF; 281 | audio_buffer[audio_buffer_index++] = sample & 0xFF; 282 | if (audio_buffer_index >= BUFFER_SIZE) { 283 | audio_buffer_ready = true; 284 | audio_buffer_index = 0; 285 | } 286 | } 287 | 288 | // Get the current time as a string 289 | String get_current_time() { 290 | char buffer[20]; 291 | time_t now = time(NULL); 292 | struct tm *tm_now = localtime(&now); 293 | strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", tm_now); 294 | return String(buffer); 295 | } 296 | 297 | // Handle root request 298 | void handle_root() { 299 | String html = "
"; 300 | html += "

Network Settings

"; 301 | html += "IP Address:

"; 302 | html += "Subnet Mask:

"; 303 | html += "Default Gateway:

"; 304 | html += "Primary DNS:

"; 305 | html += "Secondary DNS:

"; 306 | html += "

"; 307 | server.send(200, "text/html", html); 308 | } 309 | 310 | // Handle submit request 311 | void handle_submit() { 312 | String ip_str = server.arg("ip"); 313 | String subnet_str = server.arg("subnet"); 314 | String gateway_str = server.arg("gateway"); 315 | String dns1_str = server.arg("dns1"); 316 | String dns2_str = server.arg("dns2"); 317 | 318 | IPAddress new_ip, new_subnet, new_gateway, new_dns1, new_dns2; 319 | 320 | if (!new_ip.fromString(ip_str)) { 321 | server.send(400, "text/plain", "Invalid IP address"); 322 | return; 323 | } 324 | if (!new_subnet.fromString(subnet_str)) { 325 | server.send(400, "text/plain", "Invalid subnet mask"); 326 | return; 327 | } 328 | if (!new_gateway.fromString(gateway_str)) { 329 | server.send(400, "text/plain", "Invalid gateway address"); 330 | return; 331 | } 332 | if (!new_dns1.fromString(dns1_str)) { 333 | server.send(400, "text/plain", "Invalid primary DNS address"); 334 | return; 335 | } 336 | if (!new_dns2.fromString(dns2_str)) { 337 | server.send(400, "text/plain", "Invalid secondary DNS address"); 338 | return; 339 | } 340 | 341 | ip = new_ip; 342 | subnet = new_subnet; 343 | gateway = new_gateway; 344 | dns1 = new_dns1; 345 | dns2 = new_dns2; 346 | 347 | IPAddress dns[] = {dns1, dns2}; 348 | WiFi.config(ip, gateway, subnet, dns); 349 | 350 | String html = "

Settings updated

IP Address: "; 351 | html += ip.toString() + "

Subnet Mask: " + subnet.toString() + "

"; 352 | html += "Default Gateway: " + gateway.toString() + "

Primary DNS: " + dns1.toString() + "

"; 353 | html += "Secondary DNS: " + dns2.toString() + "

"; 354 | server.send(200, "text/html", html); 355 | } 356 | 357 | // Main function 358 | int main() { 359 | setup(); 360 | while (true) { 361 | loop(); 362 | } 363 | return 0; 364 | } 365 | -------------------------------------------------------------------------------- /DESIGN4/README: -------------------------------------------------------------------------------- 1 | 2 | Design 3 Lists of Features 3 | 4 | Microphone input with signal conditioning 5 | PAM4803 amplifier output to drive speakers 6 | Serial-enabled LCD display to monitor caller ID and time of call 7 | Web server for custom network configuration (IP, subnet mask, default gateway, primary and secondary DNS) 8 | SIP client for VoIP phone calls 9 | Call forwarding feature for incoming calls 10 | Conference calling feature to allow multiple people to participate in a call 11 | Call recording feature to record the call audio 12 | Mobile and desktop app integration for easy access and use 13 | Custom caller ID for outgoing calls 14 | Auto-attendant feature to play pre-recorded messages and route calls to specific extensions 15 | Echo cancellation to improve audio quality during calls. 16 | 17 | 18 | The functions used in the source code and their descriptions: 19 | 20 | setup() - initializes the ESP32, sets up the audio input, initializes the LCD, sets up the web server, and registers the callback functions for incoming calls, call media state, and call state. 21 | 22 | loop() - checks for any incoming web requests and handles them, and checks if there is any audio data in the audio buffer and sends it to the output. 23 | 24 | on_incoming_call() - callback function that is called when there is an incoming call. It checks if there are too many active calls, and if not, it answers the call and sets the audio settings. 25 | 26 | on_call_media_state() - callback function that is called when the media state of a call changes. It connects the audio output to the call audio. 27 | 28 | on_call_state() - callback function that is called when the state of a call changes. It updates the LCD with the call status. 29 | 30 | adc_interrupt_handler() - interrupt handler for the ADC. It reads the audio data from the microphone and stores it in the audio buffer. 31 | 32 | get_current_time() - gets the current time as a string. 33 | 34 | handle_root() - handles the root web request, which displays the network settings form. 35 | 36 | handle_submit() - handles the submit web request, which updates the network settings. 37 | 38 | main() - the main function that sets up the program and enters the main loop. 39 | 40 | init_lcd() - initializes the LCD display. 41 | 42 | update_lcd() - updates the LCD display with the current call status. 43 | 44 | init_wifi() - initializes the WiFi connection. 45 | 46 | init_sip() - initializes the SIP client and registers the account. 47 | 48 | init_audio() - initializes the audio input and output. 49 | 50 | init_webserver() - initializes the web server. 51 | 52 | update_network_settings() - updates the network settings with the values submitted through the web form. 53 | 54 | setup_gpio() - sets up the GPIO pins for the audio output. 55 | 56 | read_audio_buffer() - reads audio data from the audio buffer and sends it to the output. 57 | 58 | connect_audio_output() - connects the audio output to the PWM channels. 59 | 60 | disconnect_audio_output() - disconnects the audio output from the PWM channels 61 | 62 | 63 | +-----------------------------------------------+ 64 | | Microphone | 65 | | | 66 | | +-------------+ +-------------+ | 67 | | | Signal | | PAM4803 | | 68 | | | Conditioner | | Amplifier | | 69 | | +------+------+ +------+------| | 70 | | | | | 71 | | ADC | PWM1, PWM2| | 72 | | | | | 73 | +--------|--------|-----------------|-----------+ 74 | | | | 75 | | | | 76 | +----+--------+-----+ +------+-+--------+ 77 | | ESP32 Dev Board | | Serial LCD | 78 | | | | Display | 79 | | +---------------+ | | +------------+ | 80 | | | WiFi | | | | GPIO | | 81 | | | Module | | | | Pins | | 82 | | +---------------+ | | +------------+ | 83 | +---------|---------+ +---------------+ 84 | | | 85 | | | 86 | +----+----------------------+-----+ 87 | | Web Server | 88 | | | 89 | +----------------------------------+ 90 | 91 | A summary of the different modules and their configurations: 92 | 93 | Microphone - picks up audio input and sends it to the signal conditioner. 94 | Signal conditioner - conditions the audio signal and sends it to the ADC input of the ESP32. 95 | PAM4803 amplifier - receives audio output from the PWM output of the ESP32 and amplifies it to drive speakers. 96 | ESP32 Dev Board - controls the system, handles the audio input/output and network connection. 97 | ADC - analog-to-digital converter that converts the audio signal from the microphone to a digital format for the ESP32 to process. 98 | PWM1, PWM2 - pulse-width modulation channels that generate audio output for the PAM4803 amplifier. 99 | Serial LCD Display - displays caller ID and time of call. 100 | WiFi Module - enables wireless connectivity for the system to connect to the network. 101 | Web Server - provides a web interface for the user to configure network settings such as IP address, subnet mask, gateway, primary and secondary DNS, and other settings. 102 | GPIO Pins - used to connect the PAM4803 amplifier and the Serial LCD Display to the ESP32. 103 | 104 | Source Code for Design 4 105 | - main.cpp 106 | - audio.cpp 107 | - audio.h 108 | - lcd_display.cpp 109 | - lcd_display.h 110 | - network.cpp 111 | - network.h 112 | - web_server.cpp 113 | - web_server.h 114 | - sip_client.cpp 115 | - sip_client.h 116 | -------------------------------------------------------------------------------- /DESIGN4/audio.cpp: -------------------------------------------------------------------------------- 1 | #include "audio.h" 2 | 3 | // Audio buffer 4 | #define AUDIO_BUFFER_SIZE 4096 5 | static int16_t audio_buffer[AUDIO_BUFFER_SIZE]; 6 | static size_t audio_buffer_pos = 0; 7 | 8 | // Audio output pins 9 | #define AUDIO_OUTPUT_PIN1 25 10 | #define AUDIO_OUTPUT_PIN2 26 11 | 12 | // Audio sampling rate and channel count 13 | #define AUDIO_SAMPLING_RATE 16000 14 | #define AUDIO_CHANNEL_COUNT 1 15 | 16 | // Callback function for the ADC 17 | void adc_interrupt_handler(void *arg) { 18 | int16_t value = adc1_get_raw(ADC1_CHANNEL_0); 19 | 20 | if (audio_buffer_pos < AUDIO_BUFFER_SIZE) { 21 | audio_buffer[audio_buffer_pos++] = value; 22 | } 23 | } 24 | 25 | // Initialize the audio input and output 26 | void init_audio() { 27 | // Initialize ADC 28 | adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11); 29 | adc1_intr_enable(ADC1_CHANNEL_0); 30 | adc1_isr_register(&adc_interrupt_handler, NULL, 0, NULL); 31 | adc_chars = new esp_adc_cal_characteristics_t; 32 | esp_adc_cal_value_t adc_type = esp_adc_cal_characterize( 33 | ADC_UNIT_1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12, 1100, adc_chars 34 | ); 35 | adc_set_clk_div(ADC_UNIT_1, ADC_CLOCK_DIV_1); 36 | 37 | // Initialize PWM channels 38 | setup_gpio(); 39 | ledcSetup(0, AUDIO_SAMPLING_RATE, 16); 40 | ledcAttachPin(AUDIO_OUTPUT_PIN1, 0); 41 | ledcSetup(1, AUDIO_SAMPLING_RATE, 16); 42 | ledcAttachPin(AUDIO_OUTPUT_PIN2, 1); 43 | 44 | // Connect audio output to PWM channels 45 | connect_audio_output(); 46 | } 47 | 48 | // Read audio data from the buffer and send it to the output 49 | void read_audio_buffer() { 50 | // Check if there is any audio data in the buffer 51 | if (audio_buffer_pos > 0) { 52 | size_t audio_sample_count = audio_buffer_pos; 53 | audio_buffer_pos = 0; 54 | 55 | // Convert audio buffer to signed 16-bit PCM format 56 | int16_t *audio_samples = new int16_t[audio_sample_count]; 57 | memcpy(audio_samples, audio_buffer, audio_sample_count * sizeof(int16_t)); 58 | 59 | // Reset audio buffer 60 | memset(audio_buffer, 0, AUDIO_BUFFER_SIZE * sizeof(int16_t)); 61 | 62 | // Send audio data to PWM channels 63 | ledcWrite(0, (uint32_t *)audio_samples, audio_sample_count * AUDIO_CHANNEL_COUNT); 64 | ledcWrite(1, (uint32_t *)audio_samples, audio_sample_count * AUDIO_CHANNEL_COUNT); 65 | 66 | // Free memory 67 | delete[] audio_samples; 68 | } 69 | } 70 | 71 | // Connect audio output to PWM channels 72 | void connect_audio_output() { 73 | dac_output_enable(DAC_CHANNEL_1); 74 | dac_output_enable(DAC_CHANNEL_2); 75 | gpio_set_direction(AUDIO_OUTPUT_PIN1, GPIO_MODE_OUTPUT); 76 | gpio_set_direction(AUDIO_OUTPUT_PIN2, GPIO_MODE_OUTPUT); 77 | gpio_matrix_out(AUDIO_OUTPUT_PIN1, DAC_CHANNEL_1_IDX, false, false); 78 | gpio_matrix_out(AUDIO_OUTPUT_PIN2, DAC_CHANNEL_2_IDX, false, false); 79 | } 80 | 81 | // Disconnect audio output from PWM channels 82 | void disconnect_audio_output() { 83 | gpio_matrix_out(AUDIO_OUTPUT_PIN1, GPIO_FUNC_GPIO25, false, false); 84 | gpio_matrix_out(AUDIO_OUTPUT_PIN2, GPIO_FUNC_GPIO26, false, false); 85 | gpio_set_direction(AUDIO_OUTPUT_PIN1, GPIO_MODE_INPUT); 86 | gpio_set_direction(AUDIO_OUTPUT_PIN2, GPIO_MODE_INPUT); 87 | } 88 | 89 | // Initialize the ADC 90 | void init_adc() { 91 | // Configure ADC1 channel 0 92 | adc1_config_width(ADC_WIDTH_BIT_12); 93 | adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_11); 94 | } 95 | 96 | // Get the current audio volume (0-100) 97 | int get_audio_volume() { 98 | uint8_t volume = 0; 99 | 100 | if (ledcRead(0) > 0 && ledcRead(1) > 0) { 101 | volume = (ledcRead(0) + ledcRead(1)) / 2 * 100 / 65536; 102 | } 103 | 104 | return volume; 105 | } 106 | -------------------------------------------------------------------------------- /DESIGN4/audio.h: -------------------------------------------------------------------------------- 1 | #ifndef AUDIO_H 2 | #define AUDIO_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "esp_adc_cal.h" 10 | 11 | void init_audio(); 12 | void read_audio_buffer(); 13 | void connect_audio_output(); 14 | void disconnect_audio_output(); 15 | void init_adc(); 16 | int get_audio_volume(); 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /DESIGN4/lcd_display.cpp: -------------------------------------------------------------------------------- 1 | #include "lcd_display.h" 2 | 3 | // LCD display object 4 | LiquidCrystal_I2C lcd(0x3F, 20, 4); 5 | 6 | // Initialize the LCD display 7 | void init_lcd() { 8 | lcd.init(); 9 | lcd.backlight(); 10 | lcd.clear(); 11 | } 12 | 13 | // Display the caller ID and time of call 14 | void display_call_info(char* caller_id, char* time_of_call) { 15 | lcd.setCursor(0, 0); 16 | lcd.print("Caller ID: "); 17 | lcd.print(caller_id); 18 | lcd.setCursor(0, 1); 19 | lcd.print("Time of call: "); 20 | lcd.print(time_of_call); 21 | } 22 | 23 | // Clear the LCD display 24 | void clear_lcd() { 25 | lcd.clear(); 26 | } 27 | -------------------------------------------------------------------------------- /DESIGN4/lcd_display.h: -------------------------------------------------------------------------------- 1 | #ifndef LCD_DISPLAY_H 2 | #define LCD_DISPLAY_H 3 | 4 | #include 5 | 6 | void init_lcd_display(); 7 | void display_caller_info(char* caller_name, char* caller_number, int call_duration); 8 | void display_network_info(char* ssid, char* ip_address); 9 | void display_message(char* message); 10 | void clear_lcd_display(); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /DESIGN4/main.cpp: -------------------------------------------------------------------------------- 1 | #include "audio.h" 2 | #include "lcd_display.h" 3 | #include "network.h" 4 | #include "sip_client.h" 5 | #include "web_server.h" 6 | 7 | void setup() { 8 | // Initialize modules 9 | init_wifi(); 10 | init_sip(); 11 | init_audio(); 12 | init_lcd(); 13 | init_webserver(); 14 | 15 | // Register callbacks for incoming calls and call media and state changes 16 | pjsua_call_set_callback(&on_incoming_call, &on_call_media_state, &on_call_state); 17 | 18 | // Start the SIP client 19 | start_sip_client(); 20 | } 21 | 22 | void loop() { 23 | // Handle incoming web requests 24 | handle_web_requests(); 25 | 26 | // Read audio buffer and send to output 27 | read_audio_buffer(); 28 | } 29 | 30 | int main() { 31 | // Set up the program 32 | setup(); 33 | 34 | // Enter the main loop 35 | while (true) { 36 | loop(); 37 | } 38 | 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /DESIGN4/network.cpp: -------------------------------------------------------------------------------- 1 | #include "network.h" 2 | 3 | // Network configuration 4 | static char ssid[64] = "default_ssid"; 5 | static char password[64] = "default_password"; 6 | static IPAddress ip_addr(192, 168, 1, 100); 7 | static IPAddress subnet_mask(255, 255, 255, 0); 8 | static IPAddress gateway(192, 168, 1, 1); 9 | static IPAddress dns1(8, 8, 8, 8); 10 | static IPAddress dns2(8, 8, 4, 4); 11 | // Initialize the WiFi connection 12 | void init_wifi() { 13 | // Set WiFi mode to station 14 | WiFi.mode(WIFI_STA); 15 | 16 | // Connect to the WiFi network 17 | WiFi.begin(ssid, password); 18 | 19 | // Wait for connection 20 | while (WiFi.status() != WL_CONNECTED) { 21 | delay(1000); 22 | } 23 | 24 | // Configure network settings 25 | IPAddress gateway_ip = WiFi.gatewayIP(); 26 | if (gateway_ip != (uint32_t)0) { 27 | gateway = gateway_ip; 28 | } 29 | 30 | IPAddress subnet_mask_ip = WiFi.subnetMask(); 31 | if (subnet_mask_ip != (uint32_t)0) { 32 | subnet_mask = subnet_mask_ip; 33 | } 34 | 35 | IPAddress dns1_ip = WiFi.dnsIP(0); 36 | if (dns1_ip != (uint32_t)0) { 37 | dns1 = dns1_ip; 38 | } 39 | 40 | IPAddress dns2_ip = WiFi.dnsIP(1); 41 | if (dns2_ip != (uint32_t)0) { 42 | dns2 = dns2_ip; 43 | } 44 | 45 | // Configure IP address 46 | IPAddress ip; 47 | bool success = false; 48 | for (int i = 0; i < 10 && !success; i++) { 49 | success = WiFi.config(ip_addr, gateway, subnet_mask, dns1, dns2); 50 | delay(1000); 51 | } 52 | 53 | if (!success) { 54 | Serial.println("Failed to configure network settings."); 55 | } 56 | 57 | // Print network information 58 | Serial.println("WiFi connected"); 59 | Serial.print("IP address: "); 60 | Serial.println(WiFi.localIP()); 61 | Serial.print("Subnet mask: "); 62 | Serial.println(subnet_mask); 63 | Serial.print("Gateway: "); 64 | Serial.println(gateway); 65 | Serial.print("DNS server 1: "); 66 | Serial.println(dns1); 67 | Serial.print("DNS server 2: "); 68 | Serial.println(dns2); 69 | } 70 | 71 | // Get the local IP address 72 | IPAddress get_local_ip() { 73 | return WiFi.localIP(); 74 | } 75 | 76 | // Set the network configuration 77 | void set_network_config(char* new_ssid, char* new_password, IPAddress new_ip_addr, IPAddress new_subnet_mask, IPAddress new_gateway, IPAddress new_dns1, IPAddress new_dns2) { 78 | strncpy(ssid, new_ssid, sizeof(ssid)); 79 | strncpy(password, new_password, sizeof(password)); 80 | ip_addr = new_ip_addr; 81 | subnet_mask = new_subnet_mask; 82 | gateway = new_gateway; 83 | dns1 = new_dns1; 84 | dns2 = new_dns2; 85 | } 86 | 87 | // Get the network configuration 88 | void get_network_config(char* ssid_out, char* password_out, IPAddress& ip_addr_out, IPAddress& subnet_mask_out, IPAddress& gateway_out, IPAddress& dns1_out, IPAddress& dns2_out) { 89 | strncpy(ssid_out, ssid, sizeof(ssid)); 90 | strncpy(password_out, password, sizeof(password)); 91 | ip_addr_out = ip_addr; 92 | subnet_mask_out = subnet_mask; 93 | gateway_out = gateway; 94 | dns1_out = dns1; 95 | dns2_out = dns2; 96 | } 97 | -------------------------------------------------------------------------------- /DESIGN4/network.h: -------------------------------------------------------------------------------- 1 | #ifndef NETWORK_H 2 | #define NETWORK_H 3 | 4 | #include 5 | 6 | void init_wifi(); 7 | IPAddress get_local_ip(); 8 | void set_network_config(char* ssid, char* password, IPAddress ip_addr, IPAddress subnet_mask, IPAddress gateway, IPAddress dns1, IPAddress dns2); 9 | void get_network_config(char* ssid_out, char* password_out, IPAddress& ip_addr_out, IPAddress& subnet_mask_out, IPAddress& gateway_out, IPAddress& dns1_out, IPAddress& dns2_out); 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /DESIGN4/sip_client.cpp: -------------------------------------------------------------------------------- 1 | #include "sip_client.h" 2 | 3 | void SIPClient::setup() { 4 | pjsua_config cfg; 5 | pjsua_logging_config log_cfg; 6 | pjsua_media_config media_cfg; 7 | pjsua_transport_config trans_cfg; 8 | 9 | // Initialize PJSUA 10 | pjsua_config_default(&cfg); 11 | cfg.cb.on_incoming_call = &SIPClient::on_incoming_call; 12 | cfg.cb.on_call_media_state = &SIPClient::on_call_media_state; 13 | cfg.cb.on_call_state = &SIPClient::on_call_state; 14 | pjsua_logging_config_default(&log_cfg); 15 | media_cfg.clock_rate = 8000; 16 | media_cfg.snd_clock_rate = 44100; 17 | media_cfg.ec_options = PJSUA_ECHO_USE_NOISE_SUPPRESSOR; 18 | pjsua_transport_config_default(&trans_cfg); 19 | trans_cfg.port = DEFAULT_PORT; 20 | trans_cfg.tls_setting.method = PJSIP_TLSV1_0; 21 | trans_cfg.tls_setting.verify_client = PJSIP_TLS_VERIFY_DISABLED; 22 | 23 | // Initialize PJSUA 24 | pj_status_t status = pjsua_create(); 25 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, "Error initializing PJSUA"); 26 | pjsua_set_null_snd_dev(); 27 | status = pjsua_init(&cfg, &log_cfg, &media_cfg); 28 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, "Error initializing PJSUA"); 29 | status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &trans_cfg, NULL); 30 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, "Error creating transport"); 31 | pjsua_start(); 32 | pjsua_acc_config acc_cfg; 33 | pjsua_acc_config_default(&acc_cfg); 34 | acc_cfg.id = pj_str("sip:" DEFAULT_USER "@" DEFAULT_IP); 35 | acc_cfg.reg_uri = pj_str("sip:" DEFAULT_IP); 36 | acc_cfg.cred_count = 1; 37 | acc_cfg.cred_info[0].realm = pj_str("asterisk"); 38 | acc_cfg.cred_info[0].scheme = pj_str("digest"); 39 | acc_cfg.cred_info[0].username = pj_str(DEFAULT_USER); 40 | acc_cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; 41 | acc_cfg.cred_info[0].data = pj_str(DEFAULT_PASS); 42 | status = pjsua_acc_add(&acc_cfg, PJ_TRUE, &acc_id); 43 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, "Error adding account"); 44 | } 45 | 46 | void SIPClient::loop() { 47 | if (call_id != PJSUA_INVALID_ID) { 48 | pjsua_call_info call_info; 49 | pjsua_call_get_info(call_id, &call_info); 50 | lcd.setCursor(0, 1); 51 | lcd.print(call_info.state_text.ptr); 52 | lcd.print(" "); 53 | } else { 54 | lcd.setCursor(0, 1); 55 | lcd.print("No call "); 56 | lcd.print(" "); 57 | } 58 | } 59 | 60 | void SIPClient::make_call() { 61 | pjsua_call_setting call_setting; 62 | pjsua_call_setting_default(&call_setting); 63 | call_setting.vid_cnt = 0; 64 | 65 | pjsua_msg_data msg_data; 66 | pjsua_msg_data_init(&msg_data); 67 | pj_str_t dest_uri = pj_str("sip:" CALL_DEST); 68 | pj_status_t status = pjsua_call_make_call(acc_id, &dest_uri, &call_setting, NULL, &msg_data, &call_id); 69 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, "Error making call"); 70 | } 71 | 72 | void SIPClient::hangup_call() { 73 | if (call_id != P 74 | void SIPClient::loop() { 75 | if (call_id != PJSUA_INVALID_ID) { 76 | pjsua_call_info call_info; 77 | pjsua_call_get_info(call_id, &call_info); 78 | lcd.setCursor(0, 1); 79 | lcd.print(call_info.state_text.ptr); 80 | lcd.print(" "); 81 | } else { 82 | lcd.setCursor(0, 1); 83 | lcd.print("READY"); 84 | lcd.print(" "); 85 | } 86 | 87 | // Check for incoming call 88 | pjsua_call_id incoming_call_id = pjsua_call_get_id(pjsua_call_get_count() - 1); 89 | if (incoming_call_id != PJSUA_INVALID_ID) { 90 | call_id = incoming_call_id; 91 | pjsua_call_info call_info; 92 | pjsua_call_get_info(call_id, &call_info); 93 | pjsua_call_answer(call_id, 200, NULL, NULL); 94 | lcd.setCursor(0, 1); 95 | lcd.print(call_info.state_text.ptr); 96 | lcd.print(" "); 97 | } 98 | } 99 | 100 | void SIPClient::on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata) { 101 | if (SIPClient::call_id == PJSUA_INVALID_ID) { 102 | // Answer the call 103 | pjsua_call_info call_info; 104 | pjsua_call_get_info(call_id, &call_info); 105 | SIPClient::call_id = call_id; 106 | pjsua_call_answer(call_id, 200, NULL, NULL); 107 | lcd.setCursor(0, 1); 108 | lcd.print(call_info.state_text.ptr); 109 | lcd.print(" "); 110 | } else { 111 | // Reject the call 112 | pjsua_call_hangup(call_id, 486, NULL, NULL); 113 | } 114 | } 115 | 116 | void SIPClient::on_call_state(pjsua_call_id call_id, pjsip_event *e) { 117 | pjsua_call_info call_info; 118 | pjsua_call_get_info(call_id, &call_info); 119 | lcd.setCursor(0, 1); 120 | lcd.print(call_info.state_text.ptr); 121 | lcd.print(" "); 122 | if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) { 123 | // Call ended 124 | SIPClient::call_id = PJSUA_INVALID_ID; 125 | } 126 | } 127 | 128 | void SIPClient::on_call_media_state(pjsua_call_id call_id) { 129 | pjsua_call_info call_info; 130 | pjsua_call_get_info(call_id, &call_info); 131 | 132 | if (call_info.media_status == PJSUA_CALL_MEDIA_ACTIVE) { 133 | // Connect the call to the default sound device 134 | pjsua_conf_connect(call_info.conf_slot, 0); 135 | pjsua_conf_connect(0, call_info.conf_slot); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /DESIGN4/sip_client.h: -------------------------------------------------------------------------------- 1 | #ifndef SIP_CLIENT_H 2 | #define SIP_CLIENT_H 3 | 4 | #include 5 | 6 | class SIPClient { 7 | public: 8 | void setup(); 9 | void loop(); 10 | 11 | private: 12 | pjsua_acc_id acc_id; 13 | pjsua_call_id call_id = PJSUA_INVALID_ID; 14 | 15 | void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata); 16 | void on_call_media_state(pjsua_call_id call_id); 17 | void on_call_state(pjsua_call_id call_id, pjsip_event *e); 18 | }; 19 | 20 | #endif // SIP_CLIENT_H 21 | -------------------------------------------------------------------------------- /DESIGN4/web_server.cpp: -------------------------------------------------------------------------------- 1 | #include "web_server.h" 2 | 3 | // Web server 4 | WiFiServer server(80); 5 | 6 | // Initialize the web server 7 | void init_web_server() { 8 | server.begin(); 9 | } 10 | 11 | // Handle incoming HTTP requests 12 | void handle_http_request() { 13 | WiFiClient client = server.available(); 14 | if (client) { 15 | String current_line = ""; 16 | while (client.connected()) { 17 | if (client.available()) { 18 | char c = client.read(); 19 | if (c == '\n') { 20 | if (current_line.length() == 0) { 21 | break; 22 | } else { 23 | current_line = ""; 24 | } 25 | } else if (c != '\r') { 26 | current_line += c; 27 | } if (current_line.endsWith("GET /network_settings")) { 28 | handle_network_settings_request(client); 29 | break; 30 | } else if (current_line.endsWith("GET /save_network_settings")) { 31 | handle_save_network_settings_request(client); 32 | break; 33 | } 34 | } 35 | } 36 | client.stop(); 37 | } 38 | } 39 | 40 | // Handle a request for network settings 41 | void handle_network_settings_request(WiFiClient& client) { 42 | char ssid[64]; 43 | char password[64]; 44 | IPAddress ip_addr; 45 | IPAddress subnet_mask; 46 | IPAddress gateway; 47 | IPAddress dns1; 48 | IPAddress dns2; 49 | 50 | get_network_config(ssid, password, ip_addr, subnet_mask, gateway, dns1, dns2); 51 | 52 | String response = "

Network Settings

"; 53 | response += "
"; 54 | response += ""; 55 | response += "
"; 58 | response += ""; 59 | response += "
"; 62 | response += ""; 63 | response += "
"; 66 | response += ""; 67 | response += "
"; 70 | response += ""; 71 | response += "
"; 74 | response += ""; 75 | response += "
"; 78 | response += ""; 79 | response += "
"; 82 | response += ""; 83 | response += "
"; 84 | 85 | client.println("HTTP/1.1 200 OK"); 86 | client.println("Content-Type: text/html"); 87 | client.println("Connection: close"); 88 | client.println(""); 89 | client.println(response); 90 | } 91 | 92 | // Handle a request to save network settings 93 | void handle_save_network_settings_request(WiFiClient& client) { 94 | String ssid; 95 | String password; 96 | String ip_str; 97 | String subnet_mask_str; 98 | String gateway_str; 99 | String dns1_str; 100 | String dns2_str; 101 | 102 | String request = client.readString(); 103 | int ssid_index = request.indexOf("ssid="); 104 | int 105 | int password_index = request.indexOf("password="); 106 | int ip_index = request.indexOf("ip="); 107 | int subnet_mask_index = request.indexOf("subnet_mask="); 108 | int gateway_index = request.indexOf("gateway="); 109 | int dns1_index = request.indexOf("dns1="); 110 | int dns2_index = request.indexOf("dns2="); 111 | 112 | if (ssid_index != -1) { 113 | ssid = request.substring(ssid_index + 5, password_index - 1); 114 | } 115 | if (password_index != -1) { 116 | password = request.substring(password_index + 9, ip_index - 1); 117 | } 118 | if (ip_index != -1) { 119 | ip_str = request.substring(ip_index + 3, subnet_mask_index - 1); 120 | } 121 | if (subnet_mask_index != -1) { 122 | subnet_mask_str = request.substring(subnet_mask_index + 12, gateway_index - 1); 123 | } 124 | if (gateway_index != -1) { 125 | gateway_str = request.substring(gateway_index + 8, dns1_index - 1); 126 | } 127 | if (dns1_index != -1) { 128 | dns1_str = request.substring(dns1_index + 5, dns2_index - 1); 129 | } 130 | if (dns2_index != -1) { 131 | dns2_str = request.substring(dns2_index + 5); 132 | } 133 | 134 | // Parse IP addresses 135 | IPAddress ip; 136 | IPAddress subnet_mask; 137 | IPAddress gateway; 138 | IPAddress dns1; 139 | IPAddress dns2; 140 | 141 | if (!ip.fromString(ip_str)) { 142 | client.println("Invalid IP address."); 143 | return; 144 | } 145 | 146 | if (!subnet_mask.fromString(subnet_mask_str)) { 147 | client.println("Invalid subnet mask."); 148 | return; 149 | } 150 | 151 | if (!gateway.fromString(gateway_str)) { 152 | client.println("Invalid gateway."); 153 | return; 154 | } 155 | 156 | if (!dns1.fromString(dns1_str)) { 157 | client.println("Invalid DNS 1."); 158 | return; 159 | } 160 | 161 | if (!dns2.fromString(dns2_str)) { 162 | client.println("Invalid DNS 2."); 163 | return; 164 | } 165 | 166 | // Save network configuration 167 | set_network_config(ssid.c_str(), password.c_str(), ip, subnet_mask, gateway, dns1, dns2); 168 | 169 | // Redirect to network settings page 170 | client.println("HTTP/1.1 301 Moved Permanently"); 171 | client.println("Location: /network_settings"); 172 | client.println("Connection: close"); 173 | client.println(""); 174 | } 175 | 176 | // Handle incoming web requests 177 | void handle_web_request() { 178 | handle_http_request(); 179 | } 180 | 181 | // Get the web server IP address 182 | IPAddress get_web_server_ip() { 183 | return WiFi.localIP(); 184 | } 185 | -------------------------------------------------------------------------------- /DESIGN4/web_server.h: -------------------------------------------------------------------------------- 1 | #ifndef WEB_SERVER_H 2 | #define WEB_SERVER_H 3 | 4 | #include 5 | #include "network.h" 6 | 7 | void init_web_server(); 8 | void handle_http_request(); 9 | void handle_network_settings_request(WiFiClient& client); 10 | void handle_save_network_settings_request(WiFiClient& client); 11 | void handle_web_request(); 12 | IPAddress get_web_server_ip(); 13 | 14 | #endif 15 | -------------------------------------------------------------------------------- /DESIGN5/SIP_PhoneF.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | // Pin definitions 6 | #define ADC_PIN 36 7 | #define PWM_LEFT_PIN 26 8 | #define PWM_RIGHT_PIN 25 9 | 10 | // Audio buffer settings 11 | #define BUFFER_SIZE 512 12 | #define BUFFER_THRESHOLD 256 13 | 14 | // Network settings 15 | #define DEFAULT_IP IPAddress(192, 168, 1, 1) 16 | #define DEFAULT_SUBNET IPAddress(255, 255, 255, 0) 17 | #define DEFAULT_GATEWAY IPAddress(192, 168, 1, 254) 18 | #define DEFAULT_DNS1 IPAddress(8, 8, 8, 8) 19 | #define DEFAULT_DNS2 IPAddress(8, 8, 4, 4) 20 | #define DEFAULT_PORT 5060 21 | #define DEFAULT_USER "user" 22 | #define DEFAULT_PASS "pass" 23 | #define DEFAULT_CODEC "G.711" 24 | 25 | // PJSIP settings 26 | #define MAX_CALLS 4 27 | #define MAX_ACCS 1 28 | #define MAX_PROXYS 1 29 | #define RTP_PORT_START 20000 30 | #define RTP_PORT_END 21000 31 | #define JITTER_BUFFER_SIZE 2000 32 | 33 | // LCD settings 34 | #define LCD_COLS 20 35 | #define LCD_ROWS 4 36 | 37 | // Web server settings 38 | #define WEB_SERVER_PORT 80 39 | 40 | // Audio buffer 41 | static uint8_t audio_buffer[BUFFER_SIZE]; 42 | static bool audio_buffer_ready = false; 43 | static size_t audio_buffer_index = 0; 44 | 45 | // Network settings 46 | static IPAddress ip = DEFAULT_IP; 47 | static IPAddress subnet = DEFAULT_SUBNET; 48 | static IPAddress gateway = DEFAULT_GATEWAY; 49 | static IPAddress dns1 = DEFAULT_DNS1; 50 | static IPAddress dns2 = DEFAULT_DNS2; 51 | static uint16_t port = DEFAULT_PORT; 52 | static String user = DEFAULT_USER; 53 | static String pass = DEFAULT_PASS; 54 | static String codec = DEFAULT_CODEC; 55 | 56 | // PJSIP variables 57 | static pjsua_acc_id acc_id; 58 | static pjsua_call_id call_id = PJSUA_INVALID_ID; 59 | static pjsua_transport_id transport_id; 60 | 61 | // LCD display 62 | static LiquidCrystal lcd(12, 11, 5, 4, 3, 2); 63 | 64 | // Web server 65 | static WebServer server(WEB_SERVER_PORT); 66 | 67 | // Function prototypes 68 | void setup_wifi(); 69 | void setup_pjsip(); 70 | void setup_web_server(); 71 | void setup_lcd(); 72 | void setup(); 73 | void loop(); 74 | void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata); 75 | void on_call_media_state(pjsua_call_id call_id); 76 | void on_call_state(pjsua_call_id call_id, pjsip_event *e); 77 | void adc_interrupt_handler(void *arg); 78 | String get_current_time(); 79 | void handle_root(); 80 | void handle_submit(); 81 | 82 | // Set up Wi-Fi connection 83 | void setup_wifi() { 84 | Serial.print("Connecting to Wi-Fi"); 85 | WiFi.begin(); 86 | while (WiFi.status() != WL_CONNECTED) { 87 | delay(500); 88 | Serial.print("."); 89 | } 90 | Serial.println("Connected"); 91 | } 92 | 93 | // Set up PJSIP library 94 | void setup_pjsip() { 95 | pjsua_config cfg; 96 | pjsua_logging_config log_cfg; 97 | pjsua_media_config media_cfg; 98 | pjsua_transport_config trans_cfg; 99 | pjsua_acc_config acc_cfg; 100 | 101 | // Initialize PJSUA 102 | pjsua_config_default(&cfg); 103 | cfg.cb.on_incoming_call = &on_incoming_call; 104 | cfg.cb.on_call_media_state = &on_call_media_state; 105 | cfg.cb.on_call_state = &on_call_state; 106 | 107 | // Initialize logging 108 | pjsua_logging_config_default(&log_cfg); 109 | log_cfg.console_level = 4; 110 | 111 | // Initialize media settings 112 | pjsua_media_config_default(&media_cfg); 113 | media_cfg.clock_rate = 8000; 114 | media_cfg.quality = 5; 115 | media_cfg.ec_tail_len = 0; 116 | media_cfg.jb_size = JITTER_BUFFER_SIZE; 117 | media_cfg.no_vad = PJ_TRUE; 118 | media_cfg.snd_rec_latency = PJMEDIA_SND_DEFAULT_REC_LATENCY; 119 | 120 | // Initialize transport settings 121 | pjsua_transport_config_default(&trans_cfg); 122 | trans_cfg.port = port; 123 | 124 | // Initialize account settings 125 | pjsua_acc_config_default(&acc_cfg); 126 | acc_cfg.id = pj_str("sip:" + user + "@sip.example.com"); 127 | acc_cfg.reg_uri = pj_str("sip:sip.example.com"); 128 | acc_cfg.auth_cred[0].realm = pj_str("sip.example.com"); 129 | acc_cfg.auth_cred[0].username = pj_str(user.c_str()); 130 | acc_cfg.auth_cred[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; 131 | acc_cfg.auth_cred[0].data = pj_str(pass.c_str()); 132 | 133 | // Initialize PJSUA 134 | pj_status_t status = pjsua_init(&cfg, &log_cfg, &media_cfg); 135 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); 136 | 137 | // Add UDP transport 138 | status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &trans_cfg, &transport_id); 139 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); 140 | 141 | // Start PJSUA 142 | status = pjsua_start(); 143 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); 144 | 145 | // Add account 146 | status = pjsua_acc_add(&acc_cfg, PJ_TRUE, &acc_id); 147 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); 148 | } 149 | 150 | // Set up web server 151 | void setup_web_server() { 152 | server.on("/", handle_root); 153 | server.on("/submit", handle_submit); 154 | 155 | server.begin(); 156 | } 157 | 158 | // Set up LCD display 159 | void setup_lcd() { 160 | lcd.begin(LCD_COLS, LCD_ROWS); 161 | lcd.print("Initializing..."); 162 | } 163 | 164 | void setup() { 165 | Serial.begin(9600); 166 | 167 | setup_wifi(); 168 | setup_pjsip(); 169 | setup_web_server(); 170 | setup_lcd(); 171 | 172 | // Set up ADC 173 | analogReadResolution(12); 174 | attachInterrupt(ADC_PIN, adc_interrupt_handler, FALLING); 175 | } 176 | 177 | void loop() { 178 | // Handle incoming web requests 179 | server.handleClient(); 180 | 181 | // Check if audio buffer is ready 182 | if (audio_buffer_ready) { 183 | // Play audio buffer 184 | pjsua_play_file(call_id, "/audio.wav", 0, PJMEDIA_FILE_NO_LOOP);// Reset audio buffer 185 | audio_buffer_ready = false; 186 | audio_buffer_index = 0;.on_call_state = &on_call_state; 187 | 188 | // Initialize logging 189 | pjsua_logging_config_default(&log_cfg); 190 | log_cfg.console_level = 4; 191 | 192 | // Initialize media settings 193 | pjsua_media_config_default(&media_cfg); 194 | media_cfg.clock_rate = 8000; 195 | media_cfg.quality = 5; 196 | media_cfg.ec_tail_len = 0; 197 | media_cfg.jb_size = JITTER_BUFFER_SIZE; 198 | media_cfg.no_vad = PJ_TRUE; 199 | media_cfg.snd_rec_latency = PJMEDIA_SND_DEFAULT_REC_LATENCY; 200 | 201 | // Initialize transport settings 202 | pjsua_transport_config_default(&trans_cfg); 203 | trans_cfg.port = port; 204 | 205 | // Initialize account settings 206 | pjsua_acc_config_default(&acc_cfg); 207 | acc_cfg.id = pj_str("sip:" + user + "@sip.example.com"); 208 | acc_cfg.reg_uri = pj_str("sip:sip.example.com"); 209 | acc_cfg.auth_cred[0].realm = pj_str("sip.example.com"); 210 | acc_cfg.auth_cred[0].username = pj_str(user.c_str()); 211 | acc_cfg.auth_cred[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; 212 | acc_cfg.auth_cred[0].data = pj_str(pass.c_str()); 213 | 214 | // Initialize PJSUA 215 | pj_status_t status = pjsua_init(&cfg, &log_cfg, &media_cfg); 216 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); 217 | 218 | // Add UDP transport 219 | status = pjsua_transport_create(PJSIP_TRANSPORT_UDP, &trans_cfg, &transport_id); 220 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); 221 | 222 | // Start PJSUA 223 | status = pjsua_start(); 224 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); 225 | 226 | // Add account 227 | status = pjsua_acc_add(&acc_cfg, PJ_TRUE, &acc_id); 228 | PJ_ASSERT_RETURN(status == PJ_SUCCESS, status); 229 | } 230 | 231 | // Set up web server 232 | void setup_web_server() { 233 | server.on("/", handle_root); 234 | server.on("/submit", handle_submit); 235 | 236 | server.begin(); 237 | } 238 | 239 | // Set up LCD display 240 | void setup_lcd() { 241 | lcd.begin(LCD_COLS, LCD_ROWS); 242 | lcd.print("Initializing..."); 243 | } 244 | 245 | void setup() { 246 | Serial.begin(9600); 247 | 248 | setup_wifi(); 249 | setup_pjsip(); 250 | setup_web_server(); 251 | setup_lcd(); 252 | 253 | // Set up ADC 254 | analogReadResolution(12); 255 | attachInterrupt(ADC_PIN, adc_interrupt_handler, FALLING); 256 | } 257 | 258 | void loop() { 259 | // Handle incoming web requests 260 | server.handleClient(); 261 | 262 | // Check if audio buffer is ready 263 | if (audio_buffer_ready) { 264 | // Play audio buffer 265 | pjsua_play_file(call_id, "/audio.wav", 0, PJMEDIA_FILE_NO_LOOP); 266 | 267 | javascript 268 | Copy code 269 | // Reset audio buffer 270 | audio_buffer_ready = false; 271 | audio_buffer_index = 0; 272 | } 273 | } 274 | 275 | // Incoming call handler 276 | void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata) { 277 | // Answer call 278 | pjsua_call_answer(call_id, 200, NULL, NULL); 279 | 280 | // Store call ID 281 | call_id = call_id; 282 | } 283 | 284 | // Call media state handler 285 | void on_call_media_state(pjsua_call_id call_id) { 286 | pjsua_conf_port_id conf_port_id; 287 | pjsua_conf_port_id send_port_id; 288 | pjsua_conf_port_id recv_port_id; 289 | 290 | // Connect call audio to sound device 291 | pjsua_conf_add_port(pjsua_get_conf_info()->default_audio_transport_id, audio_buffer, BUFFER_SIZE, 8000, 1, &audio_player.port_id); 292 | /* 293 | 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 294 | // Connect audio player to call audio 295 | pjsua_conf_connect(audio_player.port_id, call_info.conf_slot); 296 | 297 | // Set audio player volume to maximum 298 | pjmedia_aud_dev_default_set_setting(audio_player.dev_id, PJMEDIA_AUD_DEV_CAP_OUTPUT_VOLUME, 255); 299 | 300 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 301 | */ 302 | 303 | // Callback function for incoming calls 304 | void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata) { 305 | pjsua_call_info ci; 306 | pjsua_call_get_info(call_id, &ci); 307 | 308 | if (pjsua_call_is_active(call_id)) { 309 | pjsua_call_answer(call_id, PJSIP_SC_BUSY_HERE, NULL, NULL); 310 | return; 311 | } 312 | 313 | // Answer the call 314 | pjsua_call_answer(call_id, 200, NULL, NULL); 315 | } 316 | 317 | // Callback function for call media state 318 | void on_call_media_state(pjsua_call_id call_id) { 319 | pjsua_call_info ci; 320 | pjsua_call_get_info(call_id, &ci); 321 | 322 | if (ci.media_status == PJSUA_CALL_MEDIA_ACTIVE) { 323 | // Activate audio device 324 | pjsua_conf_connect(ci.conf_slot, 0); 325 | pjsua_conf_connect(0, ci.conf_slot); 326 | } 327 | } 328 | 329 | // Callback function for call state 330 | void on_call_state(pjsua_call_id call_id, pjsip_event *e) { 331 | pjsua_call_info ci; 332 | pjsua_call_get_info(call_id, &ci); 333 | 334 | if (ci.state == PJSIP_INV_STATE_DISCONNECTED) { 335 | // Stop audio device 336 | pjsua_conf_disconnect(0, ci.conf_slot); 337 | pjsua_conf_disconnect(ci.conf_slot, 0); 338 | call_id = PJSUA_INVALID_ID; 339 | } 340 | } 341 | 342 | // ADC interrupt handler 343 | void adc_interrupt_handler(void *arg) { 344 | uint16_t value = analogRead(ADC_PIN); 345 | audio_buffer[audio_buffer_index++] = value & 0xFF; 346 | audio_buffer[audio_buffer_index++] = (value >> 8) & 0xFF; 347 | 348 | if (audio_buffer_index >= BUFFER_SIZE) { 349 | audio_buffer_index = 0; 350 | audio_buffer_ready = true; 351 | } 352 | } 353 | 354 | // Get current time as string 355 | String get_current_time() { 356 | char buffer[20]; 357 | time_t now = time(nullptr); 358 | strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", localtime(&now)); 359 | return String(buffer); 360 | } 361 | 362 | // Set up web server 363 | void setup_web_server() { 364 | server.on("/", handle_root); 365 | server.on("/submit", handle_submit); 366 | server.begin(); 367 | } 368 | 369 | // Set up LCD display 370 | void setup_lcd() { 371 | lcd.begin(LCD_COLS, LCD_ROWS); 372 | lcd.print("Starting up..."); 373 | } 374 | 375 | // Set up peripherals and libraries 376 | void setup() { 377 | // Serial communication 378 | Serial.begin(115200); 379 | while (!Serial); 380 | 381 | // Wi-Fi 382 | setup_wifi(); 383 | 384 | // PJSIP 385 | setup_pjsip(); 386 | 387 | // ADC interrupt 388 | attachInterrupt(ADC_PIN, adc_interrupt_handler, CHANGE); 389 | 390 | // LCD display 391 | setup_lcd(); 392 | 393 | // Web server 394 | setup_web_server(); 395 | } 396 | 397 | // Main loop 398 | void loop() { 399 | // Handle web server requests 400 | server.handleClient(); 401 | 402 | // Process audio buffer 403 | if (audio_buffer_ready) { 404 | audio_buffer_ready = false;// Check if there is an active call 405 | if (call_id != PJSUA_INVALID_ID) { 406 | // Send audio packet 407 | pjsua_call_info ci; 408 | pjsua_call_get_info(call_id, &ci); 409 | pjmedia_frame frame; 410 | pj_status_t status = pjmedia_frame_init(&frame, ci.media[0].clock_rate, BUFFER_SIZE / 2, 1, 16, 0); 411 | if (status != PJ_SUCCESS) { 412 | Serial.println("Error initializing audio frame// Set up web server 413 | void setup_web_server() { 414 | server.on("/", handle_root); 415 | server.on("/submit", handle_submit); 416 | server.begin(); 417 | } 418 | 419 | // Set up LCD display 420 | void setup_lcd() { 421 | lcd.begin(LCD_COLS, LCD_ROWS); 422 | lcd.print("Initializing..."); 423 | } 424 | 425 | // Main setup function 426 | void setup() { 427 | // Set up serial connection 428 | Serial.begin(115200); 429 | while (!Serial); 430 | 431 | // Set up Wi-Fi connection 432 | setup_wifi(); 433 | 434 | // Set up PJSIP library 435 | setup_pjsip(); 436 | 437 | // Set up web server 438 | setup_web_server(); 439 | 440 | // Set up LCD display 441 | setup_lcd(); 442 | } 443 | 444 | // Main loop function 445 | void loop() { 446 | // Check for incoming SIP messages 447 | pjsua_handle_events(0); 448 | 449 | // Check for audio buffer threshold 450 | if (!audio_buffer_ready && audio_buffer_index >= BUFFER_THRESHOLD) { 451 | audio_buffer_ready = true; 452 | digitalWrite(LED_BUILTIN, HIGH); 453 | } 454 | 455 | // Check for audio buffer overflow 456 | if (audio_buffer_index >= BUFFER_SIZE) { 457 | audio_buffer_index = 0; 458 | audio_buffer_ready = false; 459 | digitalWrite(LED_BUILTIN, LOW); 460 | } 461 | 462 | // Read ADC value and store in audio buffer 463 | uint16_t adc_value = analogRead(ADC_PIN); 464 | audio_buffer[audio_buffer_index++] = (uint8_t) (adc_value >> 2); 465 | 466 | // Write audio buffer to PJSIP library 467 | if (audio_buffer_ready) { 468 | pjsua_conf_port_id conf_port_id; 469 | pjsua_conf_create_port(&conf_port_id, PJMEDIA_CONF_NO_DEVICE, PJMEDIA_AUDIO_FMT_PCMU, 1, 8000, 1); 470 | pjsua_port_audio_fill(conf_port_id, audio_buffer, BUFFER_SIZE); 471 | pjsua_conf_port_destroy(conf_port_id); 472 | 473 | audio_buffer_index = 0; 474 | audio_buffer_ready = false; 475 | digitalWrite(LED_BUILTIN, LOW);} 476 | 477 | // Handle web server requests 478 | server.handleClient(); 479 | } 480 | 481 | // Handle incoming call event 482 | void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata) { 483 | // Only allow one call at a time 484 | if (pjsua_call_get_count() >= MAX_CALLS) { 485 | pjsua_call_hangup(call_id, 486, NULL, NULL); 486 | return; 487 | } 488 | 489 | // Answer incoming call 490 | pjsua_call_answer(call_id, 200, NULL, NULL); 491 | } 492 | 493 | // Handle call media state event 494 | void on_call_media_state(pjsua_call_id call_id) { 495 | pjsua_call_info call_info; 496 | pjsua_call_get_info(call_id, &call_info); 497 | 498 | // Attach sound device to call 499 | pjsua_conf_connect(call_info.conf_slot, 0); 500 | pjsua_conf_connect(0, call_info.conf_slot); 501 | } 502 | 503 | // Handle call state event 504 | void on_call_state(pjsua_call_id call_id, pjsip_event *e) { 505 | pjsua_call_info call_info; 506 | pjsua_call_get_info(call_id, &call_info); 507 | 508 | // Display call state on LCD 509 | lcd.setCursor(0, 0); 510 | lcd.print(" "); 511 | lcd.setCursor(0, 0); 512 | lcd.print(call_info.state_text.ptr); 513 | 514 | // Hang up call if it has been terminated 515 | if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) { 516 | pjsua_call_hangup(call_id, 0, NULL, NULL); 517 | lcd.setCursor(0, 0); 518 | lcd.print("Disconnected ");static void handle_submit() { 519 | // Check if request was a POST request 520 | if (server.method() != HTTP_POST) { 521 | server.send(405, "text/plain", "Method Not Allowed"); 522 | return; 523 | } 524 | 525 | // Get form values 526 | String new_ip = server.arg("ip"); 527 | String new_subnet = server.arg("subnet"); 528 | String new_gateway = server.arg("gateway"); 529 | String new_dns1 = server.arg("dns1"); 530 | String new_dns2 = server.arg("dns2"); 531 | String new_port = server.arg("port"); 532 | String new_user = server.arg("user"); 533 | String new_pass = server.arg("pass"); 534 | String new_codec = server.arg("codec"); 535 | 536 | // Update network settings 537 | ip = IPAddress(new_ip); 538 | subnet = IPAddress(new_subnet); 539 | gateway = IPAddress(new_gateway); 540 | dns1 = IPAddress(new_dns1); 541 | dns2 = IPAddress(new_dns2); 542 | port = new_port.toInt(); 543 | user = new_user; 544 | pass = new_pass; 545 | codec = new_codec; 546 | 547 | // Disconnect current account 548 | pjsua_acc_set_registration(acc_id, PJ_FALSE); 549 | pjsua_acc_del(acc_id); 550 | 551 | // Set up new account 552 | pjsua_acc_config acc_cfg; 553 | pjsua_acc_config_default(&acc_cfg); 554 | acc_cfg.id = pj_str(user.c_str()); 555 | acc_cfg.reg_uri = pj_str("sip:" + ip.toString() + ":" + String(port).c_str()); 556 | acc_cfg.cred_count = 1; 557 | acc_cfg.cred_info[0].realm = pj_str("*"); 558 | acc_cfg.cred_info[0].username = pj_str(user.c_str()); 559 | acc_cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; 560 | acc_cfg.cred_info[0].data = pj_str(pass.c_str()); 561 | pjsua_acc_add(&acc_cfg, PJ_TRUE, &acc_id); 562 | 563 | // Set up SIP transport 564 | pjsua_transport_config trans_cfg; 565 | pjsua_transport_config_default(&trans_cfg); 566 | trans_cfg.port = port; 567 | pjsua_transport_create(PJSIP_TRANSPORT_UDP, &trans_cfg, NULL); 568 | 569 | // Set up media configuration 570 | pjsua_media_config media_cfg; 571 | pjsua_media_config_default(&media_cfg); 572 | media_cfg.clock_rate = 8000; 573 | media_cfg.snd_clock_rate = 16000; 574 | media_cfg.ec_tail_len = 0; 575 | 576 | // Set up call settings 577 | pjsua_call_setting call_setting; 578 | pjsua_call_setting_default(&call_setting); 579 | call_setting.aud_cnt = 1; 580 | call_setting.vid_cnt = 0; 581 | 582 | // Start the pjsua system 583 | pjsua_start(); 584 | 585 | // Add the SIP account 586 | pjsua_acc_add(&acc_cfg, PJ_TRUE, &acc_id); 587 | 588 | // Set the account to online 589 | pjsua_acc_set_online_status(acc_id, PJ_TRUE); 590 | 591 | // Send response 592 | server.send(200, "text/plain", "Settings updated"); 593 | } 594 | 595 | // Set up the device 596 | void setup() { 597 | // Set up serial communication 598 | Serial.begin(115200); 599 | 600 | // Set up Wi-Fi 601 | setup_wifi(); 602 | 603 | // Set up PJSIP 604 | setup_pjsip(); 605 | 606 | // Set up the web server 607 | setup_web_server(); 608 | 609 | // Set up the LCD 610 | setup_lcd(); 611 | } 612 | 613 | // Main loop 614 | void loop() { 615 | // Check if audio buffer is ready 616 | if (audio_buffer_ready) { 617 | // Play audio 618 | analogWrite(PWM_LEFT_PIN, audio_buffer[audio_buffer_index++]);void on_call_state(pjsua_call_id call_id, pjsip_event *e) { 619 | pjsua_call_info ci; 620 | pjsua_call_get_info(call_id, &ci); 621 | 622 | if (ci.state == PJSIP_INV_STATE_DISCONNECTED) { 623 | lcd.clear(); 624 | lcd.print("Call ended"); 625 | call_id = PJSUA_INVALID_ID; 626 | audio_buffer_ready = false; 627 | } 628 | } 629 | 630 | // ADC interrupt handler 631 | void IRAM_ATTR adc_interrupt_handler(void *arg) { 632 | uint32_t adc_reading = analogRead(ADC_PIN); 633 | audio_buffer[audio_buffer_index++] = (uint8_t)(adc_reading >> 2); 634 | 635 | if (audio_buffer_index == BUFFER_SIZE) { 636 | audio_buffer_index = 0; 637 | audio_buffer_ready = true; 638 | } 639 | } 640 | 641 | // Get current time as string 642 | String get_current_time() { 643 | time_t now = time(nullptr); 644 | char buf[sizeof "2022-12-31 23:59:59"]; 645 | strftime(buf, sizeof buf, "%Y-%m-%d %H:%M:%S", localtime(&now)); 646 | return String(buf); 647 | } 648 | 649 | // Set up web server 650 | void setup_web_server() { 651 | server.on("/", HTTP_GET, handle_root); 652 | server.on("/submit", HTTP_POST, handle_submit); 653 | server.begin(); 654 | } 655 | 656 | // Set up LCD display 657 | void setup_lcd() { 658 | lcd.begin(LCD_COLS, LCD_ROWS); 659 | lcd.print("Ready"); 660 | } 661 | 662 | // Main setup function 663 | void setup() { 664 | Serial.begin(115200); 665 | 666 | // Set up Wi-Fi connection 667 | setup_wifi(); 668 | 669 | // Set up PJSIP library 670 | setup_pjsip(); 671 | 672 | // Set up web server 673 | setup_web_server(); 674 | 675 | // Set up LCD display 676 | setup_lcd(); 677 | 678 | // Set up ADC 679 | analogReadResolution(12); 680 | analogSetPinAttenuation(ADC_PIN, ADC_11db); 681 | analogSetWidth(9); 682 | adcAttachPin(ADC_PIN); 683 | adcAttachInterrupt(ADC_PIN, adc_interrupt_handler, nullptr); 684 | } 685 | 686 | // Main loop function 687 | void loop() { 688 | // Handle web server requests 689 | server.handleClient(); 690 | 691 | // Handle PJSIP events 692 | pjsua_event event; 693 | while (pjsua_get_event(&event, 0) == PJ_SUCCESS) { 694 | pjsua_handle_events(&event); 695 | } 696 | 697 | // Handle incoming audio data 698 | if (audio_buffer_ready && call_id != PJSUA_INVALID_ID) { 699 | pjsua_call_info ci; 700 | pjsua_call_get_info(call_id, &ci);if (ci.media_status == PJSUA_CALL_MEDIA_ACTIVE) { 701 | pjmedia_port *media_port = pjsua_call_get_audio_send_port(call_id); 702 | pjmedia_frame frame; 703 | pjmedia_frame_init(&frame, media_port->info.samples_per_frame, media_port->info.bits_per_sample, media_port->info.channel_count); 704 | 705 | memcpy(frame.buf, audio_buffer, BUFFER_SIZE); 706 | pjmedia_port_put_frame(media_port, &frame); 707 | } 708 | 709 | audio_buffer_ready = false; 710 | } 711 | } 712 | 713 | // Handle root page 714 | void handle_root() { 715 | String html = "

ESP32 SIP Phone

"; 716 | html += "
"; 717 | html += "IP Address:
"; 718 | html += "Subnet Mask:
"; 719 | html += "Gateway:
"; 720 | html += "Primary DNS:
"; 721 | html += "Secondary DNS:
"; 722 | html += "SIP Port:
"; 723 | html += "Username:
"; 724 | html += "Password:
"; 725 | html += "Codec:
"; 730 | html += ""; 731 | html += "
"; 732 | server.send(200, "text/html", html); 733 | } 734 | 735 | // Handle form submission 736 | void handle_submit() { 737 | // Update network settings 738 | ip = IPAddress(server.arg("ip").toInt()); 739 | subnet = IPAddress(server.arg("subnet").toInt()); 740 | gateway = IPAddress(server.arg("gateway").toInt()); 741 | dns1 = IPAddress(server.arg("dns1").toInt()); 742 | dns2 = IPAddress(server.arg("dns2").toInt()); 743 | port = server.arg("port").toInt(); 744 | user = server.arg("user"); 745 | pass = server.arg("pass"); 746 | codec = server.arg("codec"); 747 | 748 | // Set up PJSIP with new settings 749 | setup_pjsip(); 750 | 751 | // Send response 752 | server.sendHeader("Location", "/"); 753 | server.send(302, "text/plain", ""); 754 | } 755 | 756 | void setup() { 757 | // Initialize serial port 758 | Serial.begin(115200); 759 | 760 | // Set up Wi-Fi connection 761 | setup_wifi(); 762 | 763 | // Set up PJSIP library 764 | setup_pjsip(); 765 | 766 | // Set up web server 767 | setup_web_server(); 768 | 769 | // Set up LCD display 770 | setup_lcd(); 771 | } 772 | 773 | void loop() { 774 | // Handle web server requests 775 | server.handleClient(); 776 | 777 | // Update LCD display 778 | String status = "IP: " + WiFi.localIP().toString(); 779 | if (call_id != PJSUA_INVALID_ID) { 780 | status += " - Call in progress"; 781 | } 782 | lcd.setCursor(0, 0); 783 | lcd.print(status); 784 | if (audio_buffer_ready) { 785 | lcd.setCursor(0, 1); 786 | lcd.print(get_current_time()); 787 | lcd.setCursor(0, 2); 788 | lcd.print("Audio buffer ready"); 789 | lcd.setCursor(0, 3); 790 | lcd.print("Buffer index: " + String(audio_buffer_index)); 791 | audio_buffer_ready = false; 792 | } 793 | 794 | // Check if audio buffer needs to be filled 795 | if (pjsua_get_conf_port_info(0, &conf_port_info) == PJ_SUCCESS) { 796 | if (conf_port_info.buffer_status == PJMEDIA_CONF_BUFFER_UNDERFLOW) { 797 | uint16_t adc_value = analogRead(ADC_PIN); 798 | audio_buffer[audio_buffer_index] = map(adc_value, 0, 4095, 0, 255); 799 | audio_buffer_index++; 800 | if (audio_buffer_index == BUFFER4095, 0, 255); 801 | audio_buffer_index++; 802 | if (audio_buffer_index == BUFFER_SIZE) { 803 | audio_buffer_ready = true; 804 | audio_buffer_index = 0; 805 | } 806 | } 807 | 808 | // Callback function for incoming calls 809 | void on_incoming_call(pjsua_acc_id acc_id, pjsua_call_id call_id, pjsip_rx_data *rdata) { 810 | pjsua_call_info call_info; 811 | 812 | // Get call info 813 | pjsua_call_get_info(call_id, &call_info); 814 | 815 | // Answer the call automatically 816 | pjsua_call_answer(call_id, 200, NULL, NULL); 817 | } 818 | 819 | // Callback function for call media state change 820 | void on_call_media_state(pjsua_call_id call_id) { 821 | pjsua_call_info call_info; 822 | pjsua_call_get_info(call_id, &call_info); 823 | 824 | // Check if audio media is active 825 | if (call_info.media[0].dir & PJMEDIA_DIR_DECODING) { 826 | // Connect audio media to sound device 827 | pjsua_conf_connect(call_info.conf_slot, 0); 828 | pjsua_conf_connect(0, call_info.conf_slot); 829 | } 830 | } 831 | 832 | // Callback function for call state change 833 | void on_call_state(pjsua_call_id call_id, pjsip_event *e) { 834 | pjsua_call_info call_info; 835 | 836 | // Get call info 837 | pjsua_call_get_info(call_id, &call_info); 838 | 839 | // Check if call has been disconnected 840 | if (call_info.state == PJSIP_INV_STATE_DISCONNECTED) { 841 | // Hang up call 842 | pjsua_call_hangup(call_id, 0, NULL, NULL); 843 | call_id = PJSUA_INVALID_ID; 844 | } 845 | } 846 | 847 | // Interrupt handler for ADC 848 | void adc_interrupt_handler(void *arg) { 849 | uint32_t adc_value = analogRead(ADC_PIN); 850 | audio_buffer[audio_buffer_index] = map(adc_value, 0, 4095, 0, 255); 851 | audio_buffer_index++; 852 | if (audio_buffer_index == BUFFER_SIZE) { 853 | audio_buffer_ready = true; 854 | audio_buffer_index = 0; 855 | } 856 | } 857 | 858 | // Get current time in ISO format 859 | String get_current_time() { 860 | time_t now = time(nullptr); 861 | char iso_time[20]; 862 | strftime(iso_time, sizeof(iso_time), "%Y-%m-%dT%H:%M:%SZ", gmtime(&now)); 863 | return String(iso_time); 864 | } 865 | 866 | // Handle root page 867 | void handle_root() { 868 | String html = "

ESP32 SIP Phone

"; 869 | html += "
"; 870 | html += "IP Address:
"; 871 | html += "Subnet Mask:
"; 872 | html += "Gateway:
"; 873 | html += "DNS Server 1:
"; 874 | html += "DNS Server 2:
"; 875 | html += "SIP Server Port:
"; 876 | html += "SIP Username:
"; 877 | html += "SIP Password:
"; 878 | html += "SIP Username:
"; 879 | html += "SIP Password:
"; 880 | html += "Audio Codec:
"; 884 | html += "
"; 885 | html += ""; 886 | server.send(200, "text/html", html); 887 | } 888 | // Handle form submission 889 | void handle_submit() { 890 | ip = IPAddress(server.arg("ip").toInt(), server.arg("ip").toInt(), 891 | server.arg("ip").toInt(), server.arg("ip").toInt()); 892 | subnet = IPAddress(server.arg("subnet").toInt(), server.arg("subnet").toInt(), 893 | server.arg("subnet").toInt(), server.arg("subnet").toInt()); 894 | gateway = IPAddress(server.arg("gateway").toInt(), server.arg("gateway").toInt(), 895 | server.arg("gateway").toInt(), server.arg("gateway").toInt()); 896 | dns1 = IPAddress(server.arg("dns1").toInt(), server.arg("dns1").toInt(), 897 | server.arg("dns1").toInt(), server.arg("dns1").toInt()); 898 | dns2 = IPAddress(server.arg("dns2").toInt(), server.arg("dns2").toInt(), 899 | server.arg("dns2").toInt(), server.arg("dns2").toInt()); 900 | port = server.arg("port").toInt(); 901 | user = server.arg("user"); 902 | pass = server.arg("pass"); 903 | codec = server.arg("codec"); 904 | 905 | // Reconfigure PJSIP 906 | pjsua_destroy(); 907 | setup_pjsip(); 908 | 909 | // Update LCD display 910 | lcd.clear(); 911 | lcd.setCursor(0, 0); 912 | lcd.print("IP: " + ip.toString()); 913 | lcd.setCursor(0, 1); 914 | lcd.print("User: " + user); 915 | lcd.setCursor(0, 2); 916 | lcd.print("Codec: " + codec); 917 | 918 | server.sendHeader("Location", String("http://") + ip.toString()); 919 | server.send(302, "text/plain", ""); 920 | } 921 | 922 | // Set up the sketch 923 | void setup() { 924 | Serial.begin(115200); 925 | 926 | setup_wifi(); 927 | setup_pjsip(); 928 | setup_web_server(); 929 | setup_lcd(); 930 | } 931 | 932 | // Main loop 933 | void loop() { 934 | // Check if audio buffer is ready for playback 935 | if (audio_buffer_ready) { 936 | pjsua_snd_playback(audio_buffer, BUFFER_SIZE); 937 | audio_buffer_ready = false; 938 | } 939 | 940 | // Check for incoming call 941 | if (call_id == PJSUA_INVALID_ID) { 942 | return; 943 | } 944 | 945 | // Handle call events 946 | pjsua_call_info ci; 947 | pjsua_call_get_info(call_id, &ci); 948 | if (ci.state == PJSIP_INV_STATE_DISCONNECTED) { 949 | call_id = PJSUA_INVALID_ID; 950 | lcd.clear(); 951 | lcd.setCursor(0, 0); 952 | lcd.print("Call ended"); 953 | return; 954 | } 955 | 956 | // Handle DTMF tones 957 | if (ci.state == PJSIP_INV_STATE_CONFIRMED && ci.media_status == PJSUA_CALL_MEDIA_ACTIVE) { 958 | pjsua_call_dump_cap(call_id, NULL); 959 | pjsua_call_dump(call_id, PJ_LOG_LEVEL_DEBUG, "Call status"); 960 | pjsua_call_info ci; 961 | pjsua_call_get_info(call_id, &ci); 962 | pjsua_call_dump_media_status(call_id, PJ_LOG_LEVEL_DEBUG, "Media status"); 963 | pjsua_call_dump_stream_info(call_id, PJ_LOG_LEVEL_DEBUG, "Stream info"); 964 | pjsua_call_dump_sdp(call_id, PJ_LOG_LEVEL_DEBUG, "SDP"); 965 | 966 | pjsua_msg_data msg_data; 967 | pjsua_msg_data_init(&msg_data); 968 | pjsua_call_info ci; 969 | pjsua_call_get_info(call_id, &ci); 970 | pjsua_msg_data_type msg_type = PJSUA_MSG_DATA_TYPE_TEXT; 971 | pj_str_t mime_type = pj_str("text/plain"); 972 | pj_str_t content = pj_str("This is a test message"); 973 | pjsua_msg_data_set_text(&msg_data, &mime_type, &content); 974 | 975 | // Send message 976 | pjsua_call_send_message(call_id, &msg_type, &msg_data, NULL, NULL); 977 | 978 | Serial.println("Message sent."); 979 | } 980 | 981 | // Handle web form submission 982 | void handle_submit() { 983 | // Update network settings 984 | ip = IPAddress(server.arg("ip").toInt(), server.arg("ip").toInt(), server.arg("ip").toInt(), server.arg("ip").toInt()); 985 | subnet = IPAddress(server.arg("subnet").toInt(), server.arg("subnet").toInt(), server.arg("subnet").toInt(), server.arg("subnet").toInt()); 986 | gateway = IPAddress(server.arg("gateway").toInt(), server.arg("gateway").toInt(), server.arg("gateway").toInt(), server.arg("gateway").toInt()); 987 | dns1 = IPAddress(server.arg("dns1").toInt(), server.arg("dns1").toInt(), server.arg("dns1").toInt(), server.arg("dns1").toInt()); 988 | dns2 = IPAddress(server.arg("dns2").toInt(), server.arg("dns2").toInt(), server.arg("dns2").toInt(), server.arg("dns2").toInt()); 989 | port = server.arg("port").toInt(); 990 | user = server.arg("user"); 991 | pass = server.arg("pass"); 992 | codec = server.arg("codec"); 993 | 994 | // Reconfigure PJSIP with new settings 995 | pjsua_transport_config cfg; 996 | pjsua_transport_config_default(&cfg); 997 | cfg.port = port; 998 | pjsua_transport_modify(transport_id, &cfg); 999 | 1000 | pjsua_acc_config acc_cfg; 1001 | pjsua_acc_config_default(&acc_cfg); 1002 | acc_cfg.id = pj_str(user.c_str()); 1003 | acc_cfg.reg_uri = pj_str("sip:" + ip.toString()); 1004 | acc_cfg.cred_count = 1; 1005 | acc_cfg.cred_info[0].realm = pj_str("*"); 1006 | acc_cfg.cred_info[0].scheme = pj_str("digest"); 1007 | acc_cfg.cred_info[0].username = pj_str(user.c_str()); 1008 | acc_cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; 1009 | acc_cfg.cred_info[0].data = pj_str(pass.c_str()); 1010 | pjsua_acc_modify(acc_id, &acc_cfg); 1011 | 1012 | pjsua_codec_info codec_info; 1013 | pj_str_t codec_id = pj_str(codec.c_str()); 1014 | pjsua_enum_t codec_index = pjsua_codec_get_id(&codec_id); 1015 | pjsua_codec_get_info(codec_index, &codec_info); 1016 | pjsua_codec_set_priority(&codec_info, PJMEDIA_CODEC_PRIO_MAX); 1017 | } 1018 | 1019 | void setup() { 1020 | // Initialize serial communication 1021 | Serial.begin(115200); 1022 | 1023 | // Initialize Wi-Fi connection 1024 | setup_wifi(); 1025 | 1026 | // Initialize PJSIP library 1027 | setup_pjsip(); 1028 | 1029 | // Initialize web server 1030 | setup_web_server(); 1031 | 1032 | // Initialize LCD display 1033 | setup_lcd(); 1034 | } 1035 | 1036 | void loop() { 1037 | // Handle incoming web requests 1038 | server.handleClient(); 1039 | 1040 | // Handle incoming PJSIP messages and events 1041 | pjsua_event event; 1042 | if (pjsua_get_event(&event, 0) == PJ_SUCCESS) { 1043 | pjsua_handle_events(0); 1044 | } 1045 | 1046 | // Check if audio buffer is ready 1047 | if (audio_buffer_ready) { 1048 | //// Send audio samples to PJSIP library 1049 | pjsua_frame frame; 1050 | frame.size = BUFFER_SIZE; 1051 | frame.buf = audio_buffer; 1052 | pj_status_t status = pjsua_recorder_write_frame(PJSUA_INVALID_ID, &frame); 1053 | if (status != PJ_SUCCESS) { 1054 | Serial.print("Error sending audio: "); 1055 | Serial.println(pj_strerror(status).ptr); 1056 | } 1057 | 1058 | // Reset audio buffer 1059 | audio_buffer_ready = false; 1060 | audio_buffer_index = 0; 1061 | } 1062 | 1063 | // Handle web server requests 1064 | server.handleClient(); 1065 | } 1066 | } 1067 | 1068 | // Handle web server requests 1069 | void loop() { 1070 | // Handle incoming PJSIP messages and events 1071 | pjsua_event event; 1072 | if (pjsua_get_event(&event, 0) == PJ_SUCCESS) { 1073 | pjsua_handle_events(0); 1074 | } 1075 | 1076 | // Check if audio buffer is ready 1077 | if (audio_buffer_ready) { 1078 | // Play audio buffer 1079 | pjsua_conf_port_id playback_port; 1080 | pjmedia_port *source_port; 1081 | pjmedia_stream_info stream_info; 1082 | pj_status_t status; 1083 | // Get default playback port 1084 | playback_port = pjsua_get_conf_port(-1); 1085 | 1086 | // Create stream info 1087 | pjmedia_stream_info_init(&stream_info); 1088 | stream_info.type = PJMEDIA_TYPE_AUDIO; 1089 | stream_info.dir = PJMEDIA_DIR_DECODING; 1090 | stream_info.cname = pj_str("Playback"); 1091 | stream_info.clock_rate = 8000; 1092 | stream_info.channel_count = 1; 1093 | stream_info.samples_per_frame = 160; 1094 | stream_info.bits_per_sample = 16; 1095 | stream_info.frame_ptime = 20; 1096 | 1097 | // Create source port 1098 | status = pjmedia_port_create_buffer(pj_get_default_pool(), BUFFER_SIZE, BUFFER_SIZE, &source_port); 1099 | if (status != PJ_SUCCESS) { 1100 | Serial.println("Error creating source port"); 1101 | return; 1102 | } 1103 | 1104 | // Write audio buffer to source port 1105 | status = pjmedia_port_write(source_port, audio_buffer, BUFFER_SIZE); 1106 | if (status != PJ_SUCCESS) { 1107 | Serial.println("Error writing to source port"); 1108 | return; 1109 | } 1110 | 1111 | // Connect source port to playback port 1112 | status = pjmedia_endpoint_create_stream(pjsua_get_pjmedia_endpoint(), &stream_info, source_port, &playback_port, 0); 1113 | if (status != PJ_SUCCESS) { 1114 | Serial.println("Error connecting source port to playback port"); 1115 | return; 1116 | } 1117 | 1118 | // Start playback 1119 | pjsua_conf_connect(playback_port, 0); 1120 | } 1121 | 1122 | // Reset audio buffer 1123 | audio_buffer_ready = false; 1124 | audio_buffer_index = 0; 1125 | 1126 | // Handle web server requests 1127 | server.handleClient(); 1128 | } 1129 | // Handle web server form submission 1130 | void handle_submit() { 1131 | // Update network settings 1132 | ip = IPAddress(server.arg("ip").toInt()); 1133 | subnet = IPAddress(server.arg("subnet").toInt()); 1134 | gateway = IPAddress(server.arg("gateway").toInt()); 1135 | dns1 = IPAddress(server.arg("dns1").toInt()); 1136 | dns2 = IPAddress(server.arg("dns2").toInt()); 1137 | port = server.arg("port").toInt(); 1138 | user = server.arg("user"); 1139 | pass = server.arg("pass"); 1140 | codec = server.arg("codec"); 1141 | 1142 | // Configure network 1143 | WiFi.config(ip, gateway, subnet, dns1, dns2); 1144 | 1145 | // Set up PJSIP 1146 | setup_pjsip(); 1147 | 1148 | // Display success message 1149 | String html = "

Settings Updated

"; 1150 | html += "

IP Address: " + ip.toString() + "

"; 1151 | html += "

Subnet Mask: " + subnet.toString() + "

"; 1152 | html += "

Gateway: " + gateway.toString() + "

"; 1153 | html += "

DNS Server 1: " + dns1.toString() + "

"; 1154 | html += "

DNS Server 2: " + dns2.toString() + "

"; 1155 | html += "

SIP Server Port: " + String(port) + "

"; 1156 | html += "

SIP Username: " + user + "

"; 1157 | html += "

Codec: " + codec + "

"; 1158 | html += "Back"; 1159 | server.send(200, "text/html", html); 1160 | } 1161 | 1162 | // Set up web server 1163 | void setup_web_server() { 1164 | server.on("/", handle_root); 1165 | server.on("/submit", HTTP_POST, handle_submit); 1166 | server.begin(); 1167 | } 1168 | 1169 | // Set up LCD display 1170 | void setup_lcd() { 1171 | lcd.begin(LCD_COLS, LCD_ROWS); 1172 | lcd.print("ESP32 SIP Phone"); 1173 | lcd.setCursor(0, 1); 1174 | lcd.print("Initializing..."); 1175 | } 1176 | 1177 | // Set up hardware 1178 | void setup() { 1179 | Serial.begin(115200); 1180 | 1181 | setup_wifi(); 1182 | setup_pjsip(); 1183 | setup_web_server(); 1184 | setup_lcd(); 1185 | 1186 | // Set up ADC interrupt for audio input 1187 | adcAttachInterrupt(ADC_PIN, adc_interrupt_handler, 4095); 1188 | 1189 | // Set up PWM for audio output 1190 | ledcSetup(0, 20000, 8); 1191 | ledcAttachPin(PWM_LEFT_PIN, 0); 1192 | ledcAttachPin(PWM_RIGHT_PIN, 1); 1193 | } 1194 | 1195 | // Main loop 1196 | void loop() { 1197 | // Handle incoming PJSIP messages and events 1198 | pjsua_event event; 1199 | if (pjsua_get_event(&event, 0) == PJ_SUCCESS) { 1200 | pjsua_handle_events(0); 1201 | } 1202 | 1203 | // Check if audio buffer is ready 1204 | if (audio_buffer_ready) { 1205 | // Process audio buffer 1206 | pjsua_frame audio_frame; 1207 | audio_frame.size = BUFFER_SIZE; 1208 | audio_frame.buf = audio_buffer;pjsua_channel *channel = pjsua_call_get_channel(call_id); 1209 | if (channel) { 1210 | pjsua_conf_port_id conf_port_id = pjsua_call_get_conf_port(call_id); 1211 | pjsua_conf_port_info conf_port_info; 1212 | pjsua_conf_get_port_info(conf_port_id, &conf_port_info); 1213 | pjsua_direction dir = (conf_port_info.slot[0].channel == channel) ? PJMEDIA_DIR_ENCODING : PJMEDIA_DIR_DECODING; 1214 | pjsua_conf_adjust_rx_level(conf_port_id, PJMEDIA_CONF_RX_LEVEL_MUTE, &dir,&channel_count, &slot); 1215 | 1216 | // Deinitialize PJSUA 1217 | pjsua_call_hangup_all(); 1218 | pjsua_transport_close(transport_id); 1219 | pjsua_destroy(); 1220 | 1221 | // Restart ESP32 1222 | ESP.restart(); 1223 | } 1224 | 1225 | void loop() { 1226 | // Handle incoming PJSIP messages and events 1227 | pjsua_event event; 1228 | if (pjsua_get_event(&event, 0) == PJ_SUCCESS) { 1229 | pjsua_handle_events(0); 1230 | } 1231 | 1232 | // Check if audio buffer is ready 1233 | if (audio_buffer_ready) { 1234 | // Send audio to PJSIP 1235 | pjsua_call_info call_info; 1236 | pjsua_call_get_info(call_id, &call_info); 1237 | pjsua_conf_port_id conf_port_id = call_info.conf_slot; 1238 | pjsua_conf_port_info conf_port_info; 1239 | unsigned channel_count = 1; 1240 | unsigned slot = 0; 1241 | pjsua_conf_get_port_info(conf_port_id, &conf_port_info); 1242 | pjsua_direction dir = (conf_port_info.slot[0].channel == channel) ? PJMEDIA_DIR_ENCODING : PJMEDIA_DIR_DECODING; 1243 | pjsua_conf_adjust_rx_level(conf_port_id, PJMEDIA_CONF_RX_LEVEL_MUTE, &dir, &channel_count, &slot); 1244 | pjsua_conf_adjust_rx_level(conf_port_id, 0, &dir, &channel_count, &slot); 1245 | pjsua_conf_port_get_frame_size(conf_port_id, &frame_size);pjsua_frame frame; 1246 | frame.size = frame_size; 1247 | frame.buf = audio_buffer; 1248 | 1249 | pjsua_conf_port_write(conf_port_id, &frame); 1250 | 1251 | // Reset audio buffer 1252 | audio_buffer_ready = false; 1253 | audio_buffer_index = 0; 1254 | pjsua_frame frame; 1255 | frame.size = frame_size; 1256 | frame.buf = audio_buffer; 1257 | 1258 | pjsua_conf_port_write(conf_port_id, &frame); 1259 | 1260 | // Reset audio buffer 1261 | audio_buffer_ready = false; 1262 | audio_buffer_index = 0; 1263 | } 1264 | 1265 | // Handle web server requests 1266 | server.handleClient(); 1267 | } 1268 | // Handle web server requests 1269 | void handle_root() { 1270 | String html = "

ESP32 SIP Phone

"; 1271 | html += "
"; 1272 | html += "IP Address:
"; 1273 | html += "Subnet Mask:
"; 1274 | html += "Gateway:
"; 1275 | html += "Primary DNS:
"; 1276 | html += "Secondary DNS:
"; 1277 | html += "SIP Server:
"; 1278 | html += "SIP Server Port:
"; 1279 | html += "SIP Username:
"; 1280 | html += "SIP Password:
"; 1281 | html += "Audio Codec:
"; 1285 | html += ""; 1286 | html += "
"; 1287 | server.send(200, "text/html", html); 1288 | } 1289 | 1290 | void handle_submit() { 1291 | if (server.method() == HTTP_POST) { 1292 | ip = IPAddress(server.arg("ip").toInt(), server.arg("ip").toInt() >> 8, server.arg("ip").toInt() >> 16, server.arg("ip").toInt() >> 24); 1293 | subnet = IPAddress(server.arg("subnet").toInt(), server.arg("subnet").toInt() >> 8, server.arg("subnet").toInt() >> 16, server.arg("subnet").toInt() >> 24); 1294 | gateway = IPAddress(server.arg("gateway").toInt(), server.arg("gateway").toInt() >> 8, server.arg("gateway").toInt() >> 16, server.arg("gateway").toInt() >> 24); 1295 | dns1 = IPAddress(server.arg("dns1").toInt(), server.arg("dns1").toInt() >> 8, server.arg("dns1").toInt() >> 16, server.arg("dns1").toInt() >> 24); 1296 | dns2 = IPAddress(server.arg("dns2").toInt(), server.arg("dns2").toInt() >> 8, server.arg("dns2").toInt() >> 16, server.arg("dns2").toInt() >> 24); 1297 | server = server.arg("server"); 1298 | port = server.arg("port").toInt(); 1299 | user = server.arg("user"); 1300 | pass = server.arg("pass"); 1301 | codec = server.arg("codec"); 1302 | // Save settings to EEPROM 1303 | EEPROM.put(0, ip); 1304 | EEPROM.put(4, subnet); 1305 | EEPROM.put(8, gateway); 1306 | EEPROM.put(12, dns1); 1307 | EEPROM.put(16, dns2); 1308 | EEPROM.put(20, port); 1309 | EEPROM.put(22, user); 1310 | EEPROM.put(54, pass); 1311 | EEPROM.put(86, codec); 1312 | EEPROM.commit(); 1313 | 1314 | // Update LCD display 1315 | lcd.clear(); 1316 | lcd.setCursor(0, 0); 1317 | lcd.print("Network settings"); 1318 | lcd.setCursor(0, 1); 1319 | lcd.print("saved to EEPROM"); 1320 | lcd.setCursor(0, 2); 1321 | lcd.print("Restarting..."); 1322 | lcd.setCursor(0, 3); 1323 | lcd.print(get_current_time()); 1324 | delay(5000); 1325 | ESP.restart(); 1326 | } 1327 | 1328 | void loop() { 1329 | // Handle incoming PJSIP messages and events 1330 | pjsua_event event; 1331 | if (pjsua_get_event(&event, 0) == PJ_SUCCESS) { 1332 | pjsua_handle_events(0); 1333 | } 1334 | 1335 | // Check if audio buffer is ready 1336 | if (audio_buffer_ready) { 1337 | // Send audio to remote party 1338 | pjsua_call_info call_info; 1339 | pjsua_call_get_info(call_id, &call_info); 1340 | pjsua_conf_port_id conf_port_id = call_info.conf_slot;if (conf_port_id != PJSUA_INVALID_ID) { 1341 | pjsua_conf_port_info conf_port_info; 1342 | pjsua_conf_get_port_info(conf_port_id, &conf_port_info); 1343 | pjmedia_port *port = conf_port_info.slot[0].port; 1344 | 1345 | if (port) { 1346 | pjmedia_frame frame; 1347 | pjmedia_frame_init(&frame, codec.c_str(), call_info.remote_info.audio.sample_rate, call_info.remote_info.audio.channel_cnt, BUFFER_SIZE / call_info.remote_info.audio.frame_ptime); 1348 | 1349 | pj_status_t status = pjmedia_codec_pack_frame(port->info.codec_id, &frame, audio_buffer, &audio_buffer_index); 1350 | if (status == PJ_SUCCESS) { 1351 | pjmedia_port_put_frame(port, &frame); 1352 | } 1353 | } 1354 | } 1355 | 1356 | // Reset audio buffer 1357 | audio_buffer_ready = false; 1358 | audio_buffer_index = 0; 1359 | } 1360 | 1361 | // Handle web server requests 1362 | server.handleClient(); 1363 | }// Write network settings to EEPROM 1364 | void write_eeprom_settings() { 1365 | EEPROM.put(0, ip); 1366 | EEPROM.put(4, subnet); 1367 | EEPROM.put(8, gateway); 1368 | EEPROM.put(12, dns1); 1369 | EEPROM.put(16, dns2); 1370 | EEPROM.put(20, port); 1371 | EEPROM.put(24, user); 1372 | EEPROM.put(64, pass); 1373 | EEPROM.put(128, codec); 1374 | EEPROM.commit(); 1375 | } 1376 | 1377 | // Read network settings from EEPROM 1378 | void read_eeprom_settings() { 1379 | IPAddress stored_ip, stored_subnet, stored_gateway, stored_dns1, stored_dns2; 1380 | String stored_user, stored_pass, stored_codec; 1381 | uint16_t stored_port; 1382 | 1383 | EEPROM.get(0, stored_ip); 1384 | EEPROM.get(4, stored_subnet); 1385 | EEPROM.get(8, stored_gateway); 1386 | EEPROM.get(12, stored_dns1); 1387 | EEPROM.get(16, stored_dns2); 1388 | EEPROM.get(20, stored_port); 1389 | EEPROM.get(24, stored_user); 1390 | EEPROM.get(64, stored_pass); 1391 | EEPROM.get(128, stored_codec); 1392 | 1393 | if (!stored_ip.isSet()) { 1394 | Serial.println("No network settings in EEPROM, using default values."); 1395 | return; 1396 | } 1397 | 1398 | ip = stored_ip; 1399 | subnet = stored_subnet; 1400 | gateway = stored_gateway; 1401 | dns1 = stored_dns1; 1402 | dns2 = stored_dns2; 1403 | port = stored_port; 1404 | user = stored_user; 1405 | pass = stored_pass; 1406 | codec = stored_codec; 1407 | } 1408 | 1409 | // Set up the device 1410 | void setup() { 1411 | // Start serial communication 1412 | Serial.begin(115200); 1413 | 1414 | // Read network settings from EEPROM 1415 | read_eeprom_settings(); 1416 | 1417 | // Set up Wi-Fi connection 1418 | setup_wifi(); 1419 | 1420 | // Set up PJSIP library 1421 | setup_pjsip(); 1422 | 1423 | // Set up web server 1424 | setup_web_server(); 1425 | 1426 | // Set up LCD display 1427 | setup_lcd(); 1428 | 1429 | // Initialize ADC and PWM pins 1430 | adcAttachPin(ADC_PIN); 1431 | ledcAttachPin(PWM_LEFT_PIN, 1); 1432 | ledcAttachPin(PWM_RIGHT_PIN, 2); 1433 | ledcSetup(1, 5000, 8); 1434 | ledcSetup(2, 5000, 8); 1435 | 1436 | // Attach interrupt to ADC pin 1437 | attachInterrupt(digitalPinToInterrupt(ADC_PIN), adc_interrupt_handler, CHANGE); 1438 | } 1439 | 1440 | // Main loop 1441 | void loop() { 1442 | // Handle incoming PJSIP messages and events 1443 | pjsua_event event; 1444 | if (pjsua_get_event(&event, 0) == PJ_SUCCESS) { 1445 | pjsua_handle_events(0); 1446 | } 1447 | 1448 | // Check if audio buffer is ready 1449 | if (audio_buffer_ready) { 1450 | // Send audio buffer to PJSIP stream 1451 | pjsua_stream_info stream_info; 1452 | pjsua_stream_get_info(pjsua_call_get_stream_info(call_id, 0), &stream_info); 1453 | pjsua_frame frame; 1454 | frame.size = audio_buffer_index; 1455 | frame.buf = audio_buffer; 1456 | pjsua_conf_port_id conf_port_id = stream_info.aud.conf_port_id; 1457 | pjsua_conf_port_info conf_port_info; 1458 | pjsua_conf_get_port_info(conf_port_id, &conf_port_info); 1459 | pjmedia_channel *channel = conf_port_info.slot[0].channel; 1460 | pjsua_direction dir = (conf_port_info.slot[0].channel == channel) ? PJMEDIA_DIR_ENCODING : PJMEDIA_DIR_DECODING; 1461 | pjsua_conf_adjust_rx_level(conf_port_id, PJMEDIA_CONF_RX_LEVEL_MUTE, &dir, PJ_FALSE);// Mute audio playback 1462 | pjmedia_port *port = pjsua_player_get_port(0); 1463 | pjsua_conf_port_info player_info; 1464 | pjsua_conf_get_port_info(port->conf_port_id, &player_info); 1465 | pjmedia_channel *channel = player_info.slot[0].channel; 1466 | pjsua_conf_adjust_rx_level(port->conf_port_id, PJMEDIA_CONF_RX_LEVEL_MUTE, &dir, PJ_FALSE);// Mute audio playback 1467 | void mute_audio_playback() { 1468 | // Get audio player port info 1469 | pjsua_port_info player_info; 1470 | pjsua_conf_get_port_info(audio_player.port->conf_port_id, &player_info); 1471 | 1472 | // Get audio player channel and direction 1473 | pjmedia_channel *channel = player_info.slot[0].channel; 1474 | pjsua_direction dir = (player_info.slot[0].channel == channel) ? PJMEDIA_DIR_ENCODING : PJMEDIA_DIR_DECODING; 1475 | 1476 | // Mute audio playback 1477 | pjsua_conf_adjust_rx_level(audio_player.port->conf_port_id, PJMEDIA_CONF_RX_LEVEL_MUTE, &dir, PJ_FALSE); 1478 | } 1479 | 1480 | // Unmute audio playback 1481 | void unmute_audio_playback() { 1482 | // Get audio player port info 1483 | pjsua_port_info player_info; 1484 | pjsua_conf_get_port_info(audio_player.port->conf_port_id, &player_info); 1485 | 1486 | // Get audio player channel and direction 1487 | pjmedia_channel *channel = player_info.slot[0].channel; 1488 | pjsua_direction dir = (player_info.slot[0].channel == channel) ? PJMEDIA_DIR_ENCODING : PJMEDIA_DIR_DECODING; 1489 | 1490 | // Unmute audio playback 1491 | pjsua_conf_adjust_rx_level(audio_player.port->conf_port_id, 0, &dir, PJ_FALSE); 1492 | } 1493 | 1494 | // to be continued 1495 | -------------------------------------------------------------------------------- /EXPERIMENTS/DSP/EXP1_ESP32-CODEC.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define ADC_PIN 36 5 | #define SAMPLE_RATE 8000 6 | #define SAMPLE_SIZE 8 7 | #define SAMPLE_BUFFER_SIZE 64 8 | 9 | const int16_t G711_QUANTIZATION_FACTOR = 32767; 10 | 11 | const uint8_t G711_EXPONENT_TABLE[128] = { 12 | 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 13 | 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 14 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 15 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 16 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 17 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 18 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 19 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 20 | }; 21 | 22 | const uint8_t G711_COMPAND_TABLE[128] = { 23 | 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 24 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 25 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 26 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 27 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 28 | 29 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 30 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 31 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 32 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 33 | }; 34 | 35 | const uint8_t ULAW_COMPAND_TABLE[256] = { 36 | 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 37 | 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 38 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 39 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 40 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 41 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 42 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 43 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 44 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 45 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 46 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 47 | 7, 7, 7, 7, 7,7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 48 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 49 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 50 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 51 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 52 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 53 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 54 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 55 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 56 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 57 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 58 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 59 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 60 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 61 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 62 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,8, 8, 8, 8, 8, 63 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 }; 64 | 65 | const uint8_t ALAW_COMPAND_TABLE[256] = { 66 | 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 67 | 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 68 | 25, 27, 29, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 69 | 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 70 | 61, 62, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 71 | 78, 79, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 72 | 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 73 | 109, 110,111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 74 | 125, 126,127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 75 | 141, 142, 76 | 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 77 | 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 78 | 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 79 | 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 80 | 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 81 | 223, 224, 225, 226, 227, 228,228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 82 | 238, 239, 240, 241, 242, 243,244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 83 | 254, 255, 255, 255, 255, 255 84 | }; 85 | 86 | void setup() { 87 | Serial.begin(9600); 88 | } 89 | 90 | void loop() { 91 | // Read analog input 92 | int16_t analogValue = analogRead(A0); 93 | 94 | // Compress to uLaw and aLaw 95 | uint8_t uLawValue = compressULaw(analogValue); 96 | uint8_t aLawValue = compressALaw(analogValue); 97 | 98 | // Print the original and compressed values 99 | Serial.print("Analog Value: "); 100 | Serial.print(analogValue); 101 | Serial.print(" | uLaw Value: "); 102 | Serial.print(uLawValue); 103 | Serial.print(" | aLaw Value: "); 104 | Serial.println(aLawValue); 105 | 106 | delay(100); 107 | } 108 | 109 | uint8_t compressULaw(int16_t sample) { 110 | const uint8_t BIAS = 0x84; 111 | uint8_t sign, exponent, mantissa, compressedByte; 112 | 113 | // Convert the sample to sign-magnitude 114 | if (sample < 0) { 115 | sign = 0x00; 116 | sample = -sample; 117 | } else { 118 | sign = 0x80; 119 | } 120 | 121 | // Apply the uLaw companding 122 | if (sample > MAX_ANALOG_VALUE) { 123 | sample = MAX_ANALOG_VALUE; 124 | } 125 | 126 | sample = (uint32_t)sample * (1 << (BIT_DEPTH - 1)) / MAX_ANALOG_VALUE; 127 | exponent = (uint8_t)log2(sample); 128 | mantissa = sample >> (exponent + (BIT_DEPTH - 8)); 129 | compressedByte = ~((sign | ((exponent + EXP_BIAS) << MANTISSA_BITS) | mantissa) ^ 0xFF); 130 | compressedByte |= BIAS; 131 | 132 | return compressedByte; 133 | } 134 | 135 | uint8_t compressALaw(int16_t sample) { 136 | const uint8_t BIAS = 0x55; 137 | uint8_t sign, exponent, mantissa, compressedByte; 138 | 139 | // Convert the sample to sign-magnitude 140 | if (sample < 0) { 141 | sign = 0x80; 142 | sample = -sample; 143 | } else { 144 | sign = 0x00; 145 | } 146 | 147 | // Apply the aLaw companding 148 | if (sample > MAX_ANALOG_VALUE) { 149 | sample = MAX_ANALOG_VALUE; 150 | } 151 | 152 | sample = (uint32_t)sample * (1 << (BIT_DEPTH - 1)) / MAX_ANALOG_VALUE; 153 | exponent = (uint8_t)log2(sample); 154 | mantissa = sample >> (exponent + (BIT_DEPTH - 8)); 155 | compressedByte = ~(sign | ((exponent << MANTISSA_BITS) | mantissa)); 156 | compressedByte ^= 0x55; 157 | compressedByte |= BIAS; 158 | 159 | return compressedByte; 160 | } 161 | 162 | -------------------------------------------------------------------------------- /EXPERIMENTS/DSP/README: -------------------------------------------------------------------------------- 1 | Defines constants for the maximum analog input value, the bit depth, the exponent bias, the number of mantissa bits, and the uLaw and aLaw compound tables. 2 | Declares functions to compress an analog sample into uLaw and aLaw codecs using the defined constants and tables. 3 | Sets up the serial communication to print the output. 4 | In the main loop: 5 | Reads the analog input from pin A0. 6 | Calls the uLaw and aLaw compression functions on the analog sample. 7 | Prints the original and compressed values to the serial monitor. 8 | Delays for 100 milliseconds before looping again. 9 | 10 | 11 | +---------------+ 12 | | Microphone | 13 | +-------+-------+ 14 | | 15 | V 16 | +--------------+--------------+ 17 | | Signal | | 18 | | Conditioner | | 19 | +-------+------+--------------+ 20 | | 21 | V 22 | +-----+-----+ 23 | | ADC Input| 24 | +-----+-----+ 25 | | 26 | V 27 | +-----+-----+ 28 | | ESP32 | 29 | +-----+-----+ 30 | | 31 | V 32 | +----------+----------+ 33 | | Digital Signal | 34 | | Processing | 35 | +----------+----------+ 36 | | 37 | V 38 | +-------+-------+ 39 | | Audio Codec | 40 | +---------------+ 41 | -------------------------------------------------------------------------------- /EXPERIMENTS/DSP/dsp.h: -------------------------------------------------------------------------------- 1 | 2 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 3 | uint8_t linear2alaw(int16_t pcm_val) { 4 | const uint16_t ALAW_CLIP = 32635; 5 | const uint8_t ALAW_BIAS = 0x84; 6 | int16_t mask; 7 | uint8_t sign, exponent, mantissa; 8 | 9 | if (pcm_val >= 0) { 10 | sign = 0; 11 | } else { 12 | sign = 0x80; 13 | pcm_val = -pcm_val; 14 | } 15 | 16 | if (pcm_val > ALAW_CLIP) { 17 | pcm_val = ALAW_CLIP; 18 | } 19 | 20 | pcm_val += (1 << 7) - 1; 21 | exponent = G711_EXPONENT_TABLE[(pcm_val >> 7) & 0x7f]; 22 | mantissa = (pcm_val >> (exponent + 3)) & 0x0f; 23 | mask = (exponent << 4) | mantissa; 24 | mask |= ALAW_BIAS; 25 | mask ^= sign; 26 | 27 | return mask; 28 | } 29 | 30 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 31 | uint8_t linear2ulaw(int16_t pcm_val) { 32 | const uint16_t ULAW_CLIP = 32635; 33 | const uint8_t ULAW_BIAS = 0x84; 34 | uint8_t sign, exponent, mantissa; 35 | uint16_t uval; 36 | 37 | sign = (pcm_val >> 8) & 0x80; 38 | if (sign != 0) { 39 | pcm_val = -pcm_val; 40 | } 41 | if (pcm_val > ULAW_CLIP) { 42 | pcm_val = ULAW_CLIP; 43 | } 44 | pcm_val += (1 << 4) - 1; 45 | exponent = G711_EXPONENT_TABLE[(pcm_val >> 7) & 0x7f]; 46 | mantissa = (pcm_val >> (exponent + 3)) & 0x0f; 47 | uval = ~(sign | (exponent << 4) | mantissa); 48 | uval &= 0xff; 49 | uval |= ULAW_BIAS; 50 | 51 | return uval; 52 | } 53 | 54 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 55 | int16_t alaw2linear(uint8_t a_val) { 56 | const uint16_t ALAW_BIAS = 0x84; 57 | int16_t t, seg; 58 | a_val ^= ALAW_BIAS; 59 | t = (a_val & 0x0f) << 4; 60 | seg = ((unsigned)a_val & 0x70) >> 4; 61 | switch (seg) { 62 | case 0: 63 | t += 8; 64 | break; 65 | case 1: 66 | t += 0x108; 67 | break; 68 | default: 69 | t += 0x108; 70 | t <<= seg - 1; 71 | } 72 | return ((a_val & 0x80) ? t : -t); 73 | } 74 | 75 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 76 | int16_t ulaw2linear(uint8_t u_val) { 77 | const uint16_t ULAW_BIAS = 0x84; 78 | uint8_t t; 79 | int16_t sign, exponent, mantissa, pcm_val; 80 | 81 | u_val = ~u_val; 82 | sign = (u_val & 0x80); 83 | exponent = (u_val >> 4) & 0x07; 84 | mantissa = u_val & 0x0F; 85 | pcm_val = ((mantissa << 3) | 0x84) << (exponent + 2); 86 | pcm_val = (sign == 87 | 88 | 89 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 90 | void G711_encode(short *pcm, unsigned char *g711, int n_samples, int encode_type) { 91 | int i; 92 | unsigned char code; 93 | int exponent; 94 | const uint8_t *table; 95 | 96 | if (encode_type == G711_ALAW) { 97 | table = G711_COMPAND_TABLE; 98 | } else { 99 | table = G711_EXPONENT_TABLE; 100 | } 101 | 102 | for (i = 0; i < n_samples; i++) { 103 | exponent = pcm[i] >> 8; 104 | if (exponent < 0) { 105 | exponent = ~(exponent ^ 0x7F); 106 | } 107 | exponent += 3; 108 | 109 | code = table[exponent]; 110 | code |= ((pcm[i] >> ((code & 0x07) ^ 0x07)) & (0xFF >> (code & 0x07))) ^ (code == 0x55); 111 | 112 | if (encode_type == G711_ALAW) { 113 | code ^= 0x55; 114 | } 115 | 116 | g711[i] = code; 117 | } 118 | } 119 | 120 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 121 | void G711_decode(unsigned char *g711, short *pcm, int n_samples, int decode_type) { 122 | int i; 123 | int exponent; 124 | const uint8_t *table; 125 | unsigned char code; 126 | 127 | if (decode_type == G711_ALAW) { 128 | table = ALAW_COMPAND_TABLE; 129 | } else { 130 | table = ULAW_COMPAND_TABLE; 131 | } 132 | 133 | for (i = 0; i < n_samples; i++) { 134 | if (decode_type == G711_ALAW) { 135 | code = g711[i] ^ 0x55; 136 | } else { 137 | code = g711[i]; 138 | } 139 | 140 | exponent = table[code & 0x7F]; 141 | pcm[i] = ((code & 0x80) ? ((0x7F << exponent) | ((code & 0x7F) << (exponent - 4)) | (1 << (exponent - 5))) : ((0x7F ^ (code & 0x7F)) << exponent) | (1 << (exponent - 1))); 142 | pcm[i] = ((code & 0x80) ? -pcm[i] : pcm[i]); 143 | } 144 | } 145 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 146 | 147 | int16_t alaw_compress(int16_t pcm_value) { 148 | const uint16_t ALAW_MAX = 0xFFF; 149 | const uint16_t SEGMENTS = 8; 150 | const uint16_t BIAS = 132; 151 | 152 | uint16_t mask = 0x8000; 153 | int sign = (pcm_value & mask) ? 1 : 0; 154 | if (sign) { 155 | pcm_value = -pcm_value; 156 | } 157 | 158 | uint16_t exponent = 7; 159 | for (int16_t tmp = pcm_value >> 4; tmp > 0; tmp >>= 1) { 160 | exponent--; 161 | } 162 | 163 | uint16_t mantissa = (pcm_value >> ((exponent == 7) ? 0 : (exponent + 3))) & 0x0F; 164 | uint16_t compressed = (sign << 7) | ((exponent + BIAS) << 4) | mantissa; 165 | compressed ^= 0x55; 166 | 167 | if (compressed > ALAW_MAX) { 168 | compressed = ALAW_MAX; 169 | } 170 | 171 | return static_cast(compressed); 172 | } 173 | 174 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 175 | int16_t ulaw_compress(int16_t pcm_value) { 176 | const uint16_t UMAX = 0xFF; 177 | const uint16_t BIAS = 0x84; 178 | const uint16_t CLIP = 32635; 179 | 180 | uint16_t mask = 0x8000; 181 | int sign = (pcm_value & mask) ? 1 : 0; 182 | if (sign) { 183 | pcm_value = ~pcm_value; 184 | } 185 | 186 | if (pcm_value > CLIP) { 187 | pcm_value = CLIP; 188 | } 189 | 190 | pcm_value += BIAS; 191 | 192 | uint16_t exponent = 7; 193 | for (int16_t tmp = pcm_value >> 7; tmp > 1; tmp >>= 1) { 194 | exponent--; 195 | } 196 | 197 | uint16_t mantissa = (pcm_value >> (exponent + 3)) & 0x0F; 198 | uint16_t compressed = ~(sign << 7 | (exponent << 4) | mantissa); 199 | 200 | if (compressed > UMAX) { 201 | compressed = UMAX; 202 | } 203 | 204 | return static_cast(compressed); 205 | } 206 | 207 | int16_t ulaw_decode(uint8_t ulaw) { 208 | ulaw = ~ulaw; 209 | 210 | int16_t sign = (ulaw & 0x80) ? -1 : 1; 211 | int16_t exponent = (ulaw >> 4) & 0x07; 212 | int16_t mantissa = ulaw & 0x0F; 213 | 214 | int16_t sample = (mantissa << 4) | 0x08; 215 | sample = sample << (exponent + 3); 216 | sample *= sign; 217 | 218 | return sample; 219 | } 220 | 221 | 222 | int16_t alaw_decode(uint8_t alaw) { 223 | alaw ^= 0x55; 224 | 225 | int16_t sign = (alaw & 0x80) ? -1 : 1; 226 | int16_t exponent = ((alaw >> 4) & 0x07) + 2; 227 | int16_t mantissa = alaw & 0x0F; 228 | 229 | int16_t sample = (mantissa << exponent) | 0x0080; 230 | sample *= sign; 231 | 232 | return sample; 233 | } 234 | 235 | const uint8_t G711_EXPONENT_TABLE[128] = { 236 | 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 237 | 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 238 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 239 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 240 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 241 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 242 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 243 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 244 | }; 245 | 246 | const uint8_t G711_COMPAND_TABLE[128] = { 247 | 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 248 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 249 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 250 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 251 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 252 | 253 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 254 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 255 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 256 | 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 257 | }; 258 | 259 | const uint8_t ULAW_COMPAND_TABLE[256] = { 260 | 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 261 | 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 262 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 263 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 264 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 265 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 266 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 267 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 268 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 269 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 270 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 271 | 7, 7, 7, 7, 7,7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 272 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 273 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 274 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 275 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 276 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 277 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 278 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 279 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 280 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 281 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 282 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 283 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 284 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 285 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 286 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,8, 8, 8, 8, 8, 287 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 }; 288 | 289 | const uint8_t ALAW_COMPAND_TABLE[256] = { 290 | 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 291 | 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 292 | 25, 27, 29, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 293 | 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 294 | 61, 62, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 295 | 78, 79, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 296 | 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 297 | 109, 110,111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 298 | 125, 126,127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 299 | 141, 142, 300 | 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 301 | 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 302 | 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 303 | 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 304 | 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 305 | 223, 224, 225, 226, 227, 228,228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 306 | 238, 239, 240, 241, 242, 243,244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 307 | 254, 255, 255, 255, 255, 255 308 | }; 309 | -------------------------------------------------------------------------------- /EXPERIMENTS/TX-RX/README: -------------------------------------------------------------------------------- 1 | Transmitter VoIP 2 | +------------+ 3 | | ESP32 | 4 | | | 5 | | GPIO 34 | 6 | | SPI bus | 7 | | | 8 | | +----+ | 9 | | |Mic | | 10 | | +----+ | 11 | +------------+ 12 | 13 | 14 | This code is an example implementation of audio streaming over UDP using an ESP32 microcontroller and the Arduino programming language. The code reads audio data from an analog input pin, applies a low-pass filter, and encodes the audio data using either the A-law or μ-law algorithm. The encoded audio data is then transmitted over a WiFi network using the User Datagram Protocol (UDP) to a remote server. 15 | 16 | The code uses the ESP32's WiFi and WiFiUdp libraries to establish a connection to a WiFi network and to create a UDP socket for sending audio data. It also defines constants for the audio sampling rate, sample size, and buffer size, and uses a low-pass filter to smooth the audio data before encoding it. 17 | 18 | The code includes tables of values used in the A-law and μ-law algorithms for encoding audio data. The A-law algorithm is used primarily in European telephone networks, while the μ-law algorithm is used in North American telephone networks. 19 | 20 | Overall, this code serves as an example of how to implement audio streaming over UDP on an ESP32 microcontroller using the Arduino programming language. It can be modified and adapted for other use cases as needed. 21 | 22 | 23 | Receiver VoIP 24 | +------------+ +-------------+ +------------+ 25 | Microphone-->| ADC | | ESP32 |<--UDP--->| Client | 26 | | | | | | | 27 | | +------+ | | +---------+ | | +------+ | 28 | | | | | | | | | | | | | 29 | | | |<--+<-----+ | | | | | | | 30 | | | | | | | | | | | | | 31 | | | | | | | | | | | | | 32 | | | ADC | | | | | | | | PAM | | 33 | | | Pin | | | | WiFi | | | | 4803 |<--+ 34 | | | | | | | | | | | | | 35 | | | | | | | | | | | | | 36 | | | | | | | | | | | | | 37 | | | |<--+----->+ | | | | | | | 38 | | +------+ | | +---------+ | | +------+ | 39 | | | | | | | 40 | +------------+ +-------------+ +------------+ 41 | 42 | This code implements a real-time audio streaming application using an ESP32 microcontroller board and the Arduino IDE. 43 | 44 | At the transmitting end, the code reads an analog input from a microphone and filters it, compresses it using uLaw and aLaw compression algorithms, and sends the compressed data over a UDP network to a specified IP address and port number. 45 | 46 | At the receiving end, the code listens for incoming UDP packets, reads the received samples, and plays them back through a PAM8403 amplifier and a speaker. The received samples are first decompressed from uLaw and aLaw format and then converted to PCM format before being sent to the PAM8403 amplifier. 47 | 48 | The code also includes various settings and parameters that can be adjusted, such as the sampling rate, filter coefficient, and compression ratio, to optimize the audio quality and network performance. 49 | -------------------------------------------------------------------------------- /EXPERIMENTS/TX-RX/RX_Audio.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // Replace with your network credentials 5 | const char* ssid = "your_SSID"; 6 | const char* password = "your_PASSWORD"; 7 | 8 | // Replace with the IP address of the receiver 9 | const char* receiverIP = "192.168.1.2"; 10 | 11 | // Replace with the port number you want to use 12 | const int port = 1234; 13 | 14 | // ADC pin for analog input 15 | const int ADC_PIN = 34; 16 | 17 | // DAC pin for audio output 18 | const int DAC_PIN = 25; 19 | 20 | // Filter parameters 21 | const float ALPHA = 0.5; 22 | int16_t filteredValue = 0; 23 | 24 | // Create a UDP instance 25 | WiFiUDP udp; 26 | 27 | void setup() { 28 | // Connect to Wi-Fi network 29 | WiFi.begin(ssid, password); 30 | while (WiFi.status() != WL_CONNECTED) { 31 | delay(1000); 32 | Serial.println("Connecting to WiFi..."); 33 | } 34 | Serial.println("Connected to WiFi"); 35 | 36 | // Initialize DAC 37 | ledcSetup(0, 2000, 8); 38 | ledcAttachPin(DAC_PIN, 0); 39 | 40 | // Begin UDP communication 41 | udp.begin(1234); // Replace with the port number you want to use 42 | 43 | Serial.println("Ready to transmit audio"); 44 | } 45 | uint8_t compressULaw(int16_t sample) { 46 | sample = sample / 2; // Scale to 8 bits 47 | sample = sample ^ 0x8000; // Flip sign bit 48 | uint8_t sign = (sample & 0x8000) >> 8; 49 | if (sign != 0) { 50 | sample = -sample; 51 | } 52 | if (sample > 32635) { 53 | sample = 32635; 54 | } 55 | sample = sample + 132; 56 | uint8_t exponent = G711_EXPONENT_TABLE[(sample >> 7) & 0x7F]; 57 | uint8_t mantissa = (sample >> (exponent + 3)) & 0x0F; 58 | return ~(sign | (exponent << 4) | mantissa); 59 | } 60 | 61 | uint8_t compressALaw(int16_t sample) { 62 | sample = sample / 2; // Scale to 8 bits 63 | sample = sample ^ 0x8000; // Flip sign bit 64 | uint8_t sign = (sample & 0x8000) >> 8; 65 | if (sign != 0) { 66 | sample = (~sample + 1) & 0x7FFF; // Take 2's complement and clear sign bit 67 | } 68 | if (sample > 32123) { 69 | sample = 32123; 70 | } 71 | uint8_t exponent = 7 - log2(sample); // Compute exponent 72 | uint8_t mantissa = (sample >> (exponent + 3)) & 0x0F; 73 | return ~(sign | (exponent << 4) | mantissa); 74 | } 75 | void loop() { 76 | // Read analog input and filter it 77 | int16_t analogValue = analogRead(ADC_PIN); 78 | filteredValue = filteredValue + ALPHA * (analogValue - filteredValue); 79 | 80 | // Compress to uLaw and aLaw 81 | uint8_t uLawValue = compressULaw(filteredValue); 82 | uint8_t aLawValue = compressALaw(filteredValue); 83 | 84 | // Send the compressed data over UDP 85 | udp.beginPacket(receiverIP, port); 86 | udp.write((const uint8_t*)&uLawValue, 1); 87 | udp.write((const uint8_t*)&aLawValue, 1); 88 | udp.endPacket(); 89 | 90 | // Output the received data to DAC 91 | ledcWrite(0, uLawValue); // Use uLaw value for output to DAC 92 | 93 | // Print the original and compressed values 94 | Serial.print("Analog Value: "); 95 | Serial.print(analogValue); 96 | Serial.print(" | Filtered Value: "); 97 | Serial.print(filteredValue); 98 | Serial.print(" | uLaw Value: "); 99 | Serial.print(uLawValue); 100 | Serial.print(" | aLaw Value: "); 101 | Serial.println(aLawValue); 102 | 103 | delay(1); 104 | } 105 | -------------------------------------------------------------------------------- /EXPERIMENTS/TX-RX/TX_Audio.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #define ADC_PIN 36 7 | #define SAMPLE_RATE 8000 8 | #define SAMPLE_SIZE 8 9 | #define SAMPLE_BUFFER_SIZE 64 10 | #define SERVER_IP "192.168.1.100" // Replace with the IP address of the server 11 | 12 | // Low-pass filter 13 | const float RC = 1.0 / (2.0 * M_PI * 100.0); // 100 Hz cutoff frequency 14 | const float DT = 1.0 / SAMPLE_RATE; 15 | const float ALPHA = DT / (RC + DT); 16 | static float filteredValue = 0.0; 17 | const int16_t G711_QUANTIZATION_FACTOR = 32767; 18 | 19 | const uint8_t G711_EXPONENT_TABLE[128] = { 20 | 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 21 | 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 22 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 23 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 24 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 25 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 26 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 27 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6 28 | }; 29 | 30 | 31 | const uint8_t G711_COMPAND_TABLE[128] = { 32 | 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, 50, 55, 61, 68, 33 | 76, 84, 94, 105, 117, 131, 147, 165, 185, 208, 233, 262, 294, 330, 370, 415, 34 | 466, 523, 587, 659, 740, 831, 932, 1046, 1175, 1319, 1480, 1661, 1864, 2093, 2349, 2637, 2960, 3322, 35 | 3729, 4186, 4699, 5274, 5919, 6644, 7458, 8372, 9397, 10548, 11835, 13267, 14846, 16686, 18789, 21168, 36 | 23834, 26801, 30087, 33720, 37731, 42155, 47030, 52403, 58326, 64852, 72046, 79968, 88792, 98527, 109591, 121999, 37 | 135891, 151407, 168698, 187941, 209335, 233108, 259522, 288867, 321549, 357907, 398318, 443193, 493090, 548648, 610567, 38 | 679612, 756708, 842891, 939192, 1046728, 1166894, 1304064, 1459817, 1636365, 1836454, 2063055, 2319478, 2609444, 2937014, 3306602, 39 | 3723271, 4192516, 4719340, 5309193, 5968088, 6702603, 7520149, 8428968, 9438393, 10556256, 11852321, 13326157, 14991223, 16872755, 18987915, 40 | 21357548, 24004614, 26955419, 30240920, 33894658, 37954212, 42461026, 47462045, 53010034, 59163228, 65986453, 73551827, 81940896, 91244829, 41 | 101606129, 113321754, 126796218, 142275834, 159938951, 180000000 42 | }; 43 | const uint8_t ULAW_COMPAND_TABLE[256] = { 44 | 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 45 | 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 46 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 47 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 48 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 49 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 50 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 51 | 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 52 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 53 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 54 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 55 | 7, 7, 7, 7, 7,7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 56 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 57 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 58 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 59 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 60 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 61 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 62 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 63 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 64 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 65 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 66 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 67 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 68 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 69 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 70 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,8, 8, 8, 8, 8, 71 | 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 }; 72 | 73 | const uint8_t ALAW_COMPAND_TABLE[256] = { 74 | 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 75 | 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 76 | 25, 27, 29, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 77 | 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 78 | 61, 62, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 79 | 78, 79, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 80 | 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 81 | 109, 110,111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 82 | 125, 126,127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 83 | 141, 142, 84 | 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 85 | 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 86 | 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 87 | 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 88 | 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 89 | 223, 224, 225, 226, 227, 228,228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 90 | 238, 239, 240, 241, 242, 243,244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 91 | 254, 255, 255, 255, 255, 255 92 | }; 93 | 94 | WiFiUDP udp; 95 | 96 | void setup() { 97 | Serial.begin(9600); 98 | WiFi.begin("ssid", "password"); // Replace with your network credentials 99 | while (WiFi.status() != WL_CONNECTED) { 100 | delay(1000); 101 | Serial.println("Connecting to WiFi..."); 102 | } 103 | Serial.println("Connected to WiFi"); 104 | } 105 | 106 | void loop() { 107 | // Read analog input and filter it 108 | int16_t analogValue = analogRead(ADC_PIN); 109 | filteredValue = filteredValue + ALPHA * (analogValue - filteredValue); 110 | 111 | // Compress to uLaw and aLaw 112 | uint8_t uLawValue = compressULaw(filteredValue); 113 | uint8_t aLawValue = compressALaw(filteredValue); 114 | 115 | // Send the compressed data over UDP 116 | udp.beginPacket(SERVER_IP, 1234); // Replace with the port number you want to use 117 | udp.write((const uint8_t*)&uLawValue, 1); // Send one byte of data 118 | udp.write((const uint8_t*)&aLawValue, 1); 119 | udp.endPacket(); 120 | 121 | // Print the original and compressed values 122 | Serial.print("Analog Value: "); 123 | Serial.print(analogValue); 124 | Serial.print(" | Filtered Value: "); 125 | Serial.print(filteredValue); 126 | Serial.print(" | uLaw Value: "); 127 | Serial.print(uLawValue); 128 | Serial.print(" | aLaw Value: "); 129 | Serial.println(aLawValue); 130 | 131 | delay(1); 132 | } 133 | 134 | uint8_t compressULaw(int16_t sample) { 135 | sample = sample / 2; // Scale to 8 bits 136 | sample = sample ^ 0x8000; // Flip sign bit 137 | uint8_t sign = (sample & 0x8000) >> 8; 138 | if (sign != 0) { 139 | sample = -sample; 140 | } 141 | if (sample > 32635) { 142 | sample = 32635; 143 | } 144 | sample = sample + 132; 145 | uint8_t exponent = G711_EXPONENT_TABLE[(sample >> 7) & 0x7F]; 146 | uint8_t mantissa = (sample >> (exponent + 3)) & 0x0F; 147 | return ~(sign | (exponent << 4) | mantissa); 148 | } 149 | 150 | uint8_t compressALaw(int16_t sample) { 151 | sample = sample / 2; // Scale to 8 bits 152 | sample = sample ^ 0x8000; // Flip sign bit 153 | uint8_t sign = (sample & 0x8000) >> 8; 154 | if (sign != 0) { 155 | sample = (~sample + 1) & 0x7FFF; // Take 2's complement and clear sign bit 156 | } 157 | if (sample > 32123) { 158 | sample = 32123; 159 | } 160 | uint8_t exponent = 7 - log2(sample); // Compute exponent 161 | uint8_t mantissa = (sample >> (exponent + 3)) & 0x0F; 162 | return ~(sign | (exponent << 4) | mantissa); 163 | } 164 | 165 | int main() { 166 | setup(); 167 | while(1) { 168 | loop(); 169 | } 170 | } 171 | 172 | 173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Design and Implementation of VOiP Phone 3 | Using Asterisk and SIP protocial 4 | 5 | The project is intended for VoIP Development 6 | in the industry and academe education. 7 | 8 | Please feel free to use the design 9 | and dont forget to acknowledge the author 10 | 11 | 12 | # VoIP-FXO-Phone-ESP32-RP2040 13 | 14 | Christopher Coballes 15 | Hi-Techno Barrio 16 | --------------------------------------------------------------------------------