331 lines
10 KiB
C
331 lines
10 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <alsa/asoundlib.h>
|
|
#include <alsa/control.h>
|
|
#include <alsa/pcm.h>
|
|
#include <curl/curl.h>
|
|
|
|
// NOTE: find this via `arecord -l`
|
|
#define ALSA_MIC_HARDWARE_DEVICE "hw:3"
|
|
|
|
void print_pcm_state(snd_pcm_t *pcm_handle) {
|
|
snd_pcm_state_t state = snd_pcm_state(pcm_handle);
|
|
const char *state_name = snd_pcm_state_name(state);
|
|
printf("State of pcm handle: %s\n", state_name);
|
|
}
|
|
|
|
struct file_data {
|
|
FILE *file;
|
|
size_t size;
|
|
};
|
|
|
|
struct response_data {
|
|
char *data;
|
|
size_t size;
|
|
};
|
|
|
|
size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
struct file_data *data = (struct file_data *)userdata;
|
|
size_t bytes_to_read = size * nmemb;
|
|
return fread(ptr, 1, bytes_to_read, data->file);
|
|
}
|
|
|
|
// Returns string that caller must free
|
|
char* find_transcript(char* json_response)
|
|
{
|
|
printf("Parsing response: %s \n", json_response);
|
|
char* start = strstr(json_response, "\"transcript\":\"");
|
|
if (!start) return NULL;
|
|
|
|
start += 14; // Skip past "transcript":"
|
|
char* end = strchr(start, '"'); // Find closing quote
|
|
// Copy substring between start and end
|
|
size_t len = end - start;
|
|
char* result = malloc(len + 1);
|
|
strncpy(result, start, len);
|
|
result[len] = '\0';
|
|
return result;
|
|
}
|
|
|
|
size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) {
|
|
size_t total_size = size * nmemb;
|
|
struct response_data *response = (struct response_data *)userp;
|
|
|
|
// Reallocate buffer to fit new data
|
|
response->data = realloc(response->data, response->size + total_size + 1);
|
|
if (response->data == NULL) {
|
|
return 0; // Error
|
|
}
|
|
|
|
// Copy new data to buffer
|
|
memcpy(&response->data[response->size], contents, total_size);
|
|
response->size += total_size;
|
|
response->data[response->size] = '\0'; // Null terminate
|
|
|
|
printf("read an extra %d bytes of data\n", (int)total_size);
|
|
|
|
return total_size;
|
|
}
|
|
|
|
// Returns transcript of audio
|
|
char* transcribe(FILE* audio_file) {
|
|
CURL *curl;
|
|
CURLcode res;
|
|
struct curl_slist *headers = NULL;
|
|
|
|
// Initialize response buffer
|
|
struct response_data response = {0};
|
|
response.data = malloc(1);
|
|
response.size = 0;
|
|
|
|
// Get file size
|
|
fseek(audio_file, 0, SEEK_END);
|
|
long file_size = ftell(audio_file);
|
|
fseek(audio_file, 0, SEEK_SET);
|
|
|
|
struct file_data data;
|
|
data.file = audio_file;
|
|
data.size = file_size;
|
|
|
|
curl = curl_easy_init();
|
|
if (curl) {
|
|
// Set headers for Deepgram API
|
|
headers = curl_slist_append(headers, "Authorization: Token YOUR_DEEPGRAM_API_KEY");
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, "https://api.deepgram.com/v1/listen?encoding=linear16&sample_rate=48000&channels=1");
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
|
curl_easy_setopt(curl, CURLOPT_POST, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback);
|
|
curl_easy_setopt(curl, CURLOPT_READDATA, &data);
|
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, file_size);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
|
|
|
res = curl_easy_perform(curl);
|
|
if (res != CURLE_OK) {
|
|
fprintf(stderr, "curl failed: %s\n", curl_easy_strerror(res));
|
|
free(response.data);
|
|
curl_slist_free_all(headers);
|
|
curl_easy_cleanup(curl);
|
|
return NULL;
|
|
}
|
|
|
|
printf("done performing curl\n");
|
|
|
|
curl_slist_free_all(headers);
|
|
curl_easy_cleanup(curl);
|
|
}
|
|
|
|
// Parse transcript from complete response
|
|
char* transcript = find_transcript(response.data);
|
|
free(response.data);
|
|
|
|
return transcript;
|
|
}
|
|
|
|
char* find_ai_response(char* json_response) {
|
|
printf("Parsing response: %s \n", json_response);
|
|
char* start = strstr(json_response, "\"text\":\"");
|
|
if (!start) return NULL;
|
|
|
|
start += 8; // Skip past "text":"
|
|
char* end = strchr(start, '"');
|
|
if (!end) return NULL;
|
|
|
|
size_t len = end - start;
|
|
char* result = malloc(len + 1);
|
|
strncpy(result, start, len);
|
|
result[len] = '\0';
|
|
return result;
|
|
}
|
|
|
|
char *ask_ai(char *question) {
|
|
CURL *curl;
|
|
CURLcode res;
|
|
struct curl_slist *headers = NULL;
|
|
|
|
// Initialize response buffer
|
|
struct response_data response = {0};
|
|
response.data = malloc(1);
|
|
response.size = 0;
|
|
|
|
// Create JSON payload
|
|
char json_payload[2048];
|
|
snprintf(json_payload, sizeof(json_payload),
|
|
"{"
|
|
"\"model\":\"claude-3-5-sonnet-20241022\","
|
|
"\"max_tokens\":1024,"
|
|
"\"messages\":[{\"role\":\"user\",\"content\":\"%s\"}]"
|
|
"}", question);
|
|
|
|
curl = curl_easy_init();
|
|
if (curl) {
|
|
// Set headers for Anthropic API
|
|
headers = curl_slist_append(headers, "content-Type: application/json");
|
|
headers = curl_slist_append(headers, "anthropic-version: 2023-06-01");
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, "https://api.anthropic.com/v1/messages");
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
|
curl_easy_setopt(curl, CURLOPT_POST, 1L);
|
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_payload);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
|
|
|
res = curl_easy_perform(curl);
|
|
if (res != CURLE_OK) {
|
|
fprintf(stderr, "curl failed: %s\n", curl_easy_strerror(res));
|
|
free(response.data);
|
|
curl_slist_free_all(headers);
|
|
curl_easy_cleanup(curl);
|
|
return NULL;
|
|
}
|
|
|
|
curl_slist_free_all(headers);
|
|
curl_easy_cleanup(curl);
|
|
}
|
|
|
|
// Parse AI response from complete response
|
|
char* answer = find_ai_response(response.data);
|
|
free(response.data);
|
|
|
|
return answer;
|
|
}
|
|
|
|
int main() {
|
|
snd_pcm_t *pcm_handle = NULL;
|
|
|
|
int err;
|
|
err = snd_pcm_open(&pcm_handle, ALSA_MIC_HARDWARE_DEVICE, SND_PCM_STREAM_CAPTURE, 0);
|
|
if (err < 0) {
|
|
printf("unable to open pcm device");
|
|
return -1;
|
|
}
|
|
print_pcm_state(pcm_handle);
|
|
|
|
const char *name = snd_pcm_name(pcm_handle);
|
|
printf("found this device: %s\n", name);
|
|
|
|
snd_pcm_info_t *pcm_info;
|
|
snd_pcm_info_malloc(&pcm_info);
|
|
err = snd_pcm_info(pcm_handle, pcm_info);
|
|
if (err < 0) {
|
|
snd_pcm_close(pcm_handle);
|
|
printf("unable to get info of pcm device");
|
|
return -1;
|
|
}
|
|
name = snd_pcm_info_get_name(pcm_info);
|
|
if (err < 0) {
|
|
printf("unable to get card info");
|
|
snd_pcm_close(pcm_handle);
|
|
snd_pcm_info_free(pcm_info);
|
|
return -1;
|
|
}
|
|
printf("got pcm name: %s \n", name);
|
|
|
|
print_pcm_state(pcm_handle);
|
|
snd_pcm_info_free(pcm_info);
|
|
|
|
snd_pcm_hw_params_t *params;
|
|
err = snd_pcm_hw_params_malloc(¶ms);
|
|
if (err < 0) {
|
|
fprintf(stderr, "unable to mallow hw params");
|
|
return -1;
|
|
}
|
|
err = snd_pcm_hw_params_any(pcm_handle, params);
|
|
if (err < 0) {
|
|
fprintf(stderr, "unable to find ranges for hw device");
|
|
snd_pcm_close(pcm_handle);
|
|
snd_pcm_hw_params_free(params);
|
|
return -1;
|
|
}
|
|
|
|
uint period_size = 1024;
|
|
uint buffer_size = 4096;
|
|
uint channel_count = 1;
|
|
uint bytes_per_sample = 2; // FORMAT_S16_LE
|
|
uint samples_per_second = 48000;
|
|
snd_pcm_hw_params_set_access(pcm_handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);
|
|
snd_pcm_hw_params_set_channels(pcm_handle, params, channel_count);
|
|
snd_pcm_hw_params_set_buffer_size(pcm_handle, params, buffer_size);
|
|
snd_pcm_hw_params_set_format(pcm_handle, params, SND_PCM_FORMAT_S16_LE);
|
|
snd_pcm_hw_params_set_rate(pcm_handle, params, samples_per_second, 0);
|
|
snd_pcm_hw_params_set_periods(pcm_handle, params, period_size, 0);
|
|
|
|
err = snd_pcm_hw_params(pcm_handle, params);
|
|
if (err < 0) {
|
|
fprintf(stderr, "unable to prepare pcm\n");
|
|
snd_pcm_close(pcm_handle);
|
|
snd_pcm_hw_params_free(params);
|
|
return -1;
|
|
}
|
|
|
|
print_pcm_state(pcm_handle);
|
|
|
|
err = snd_pcm_start(pcm_handle);
|
|
if (err < 0) {
|
|
fprintf(stderr, "unable to start pcm");
|
|
snd_pcm_close(pcm_handle);
|
|
snd_pcm_hw_params_free(params);
|
|
return -1;
|
|
}
|
|
|
|
print_pcm_state(pcm_handle);
|
|
|
|
const uint seconds_to_capture = 5;
|
|
const uint periods_to_read = (samples_per_second * seconds_to_capture) / period_size;
|
|
char *buf = malloc(period_size * channel_count * bytes_per_sample);
|
|
uint periods_read = 0;
|
|
/* Read one period at a time from the hardware buffer
|
|
* We set the hardware buffer to read 4096 frames.
|
|
* The period size is 1024 frames.
|
|
* Each period is 2048 BYTES because each frame is 2 bytes.
|
|
* Each frame is 2 bytes because of 1 channel, and 16 bit format
|
|
*/
|
|
FILE *file = fopen("output.pcm", "wb");
|
|
if (file == NULL) {
|
|
perror("Error opening file");
|
|
return 1;
|
|
}
|
|
printf("======READING FRAMES for 5 seconds========\n");
|
|
while (snd_pcm_readi(pcm_handle, (void *)buf, period_size) > 0 && periods_read < periods_to_read) {
|
|
// Because each frame is 2 bytes (format/bit rate), cast to int16
|
|
// int16_t *samples = (int16_t *)buf;
|
|
// for (int i = 0; i < period_size; i++) {
|
|
// printf("%d ", samples[i]);
|
|
// }
|
|
|
|
fwrite(buf, sizeof(int16_t), period_size, file);
|
|
periods_read++;
|
|
}
|
|
printf("done capturing audio\n");
|
|
|
|
fclose(file);
|
|
|
|
// Reopen file for reading and transcribe
|
|
file = fopen("output.pcm", "rb");
|
|
if (file != NULL) {
|
|
char* transcript = transcribe(file);
|
|
if (transcript) {
|
|
printf("Transcript: %s\n", transcript);
|
|
|
|
char *ai_answer = ask_ai(transcript);
|
|
if (ai_answer) {
|
|
printf("AI answer: %s\n", ai_answer);
|
|
free(ai_answer);
|
|
}
|
|
free(transcript);
|
|
} else {
|
|
fprintf(stderr, "unable to get transcript");
|
|
}
|
|
fclose(file);
|
|
} else {
|
|
fprintf(stderr, "unable to open file for reading\n");
|
|
}
|
|
|
|
free((void *)buf);
|
|
snd_pcm_close(pcm_handle);
|
|
snd_pcm_hw_params_free(params);
|
|
|
|
return 0;
|
|
}
|