# QOA 練習 on WSLg (棄坑) > 會有大量使用ChatGpt 幫忙寫Code 的部分 靈感 https://github.com/marty1885/magic-qoa/tree/master https://www.facebook.com/groups/1686432621633958/posts/3783463605264172 ## 可以使用的音樂來源 https://yunosuke-official.bandcamp.com/ ## PluseAudio Play Audio(8/17/2024) ### 目標 能夠使用Pulse Audio 播放1 Khz Sine Wave ### Bash ```bash sudo apt-get install libpulse-dev ``` ### VScode ``` > CMake:Quick Start ``` ### SourceCode ```cmake cmake_minimum_required(VERSION 3.5.0) project(01_PulseAudioPlayAudio VERSION 0.1.0 LANGUAGES C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED True) find_package(PkgConfig REQUIRED) pkg_check_modules(PULSE REQUIRED libpulse) include_directories(${PULSE_INCLUDE_DIRS}) link_directories(${PULSE_LIBRARY_DIRS}) add_executable(01_PulseAudioPlayAudio main.c) target_link_libraries(01_PulseAudioPlayAudio ${PULSE_LIBRARIES}) target_link_libraries(01_PulseAudioPlayAudio m) target_link_libraries(01_PulseAudioPlayAudio pulse-simple) include(CTest) enable_testing() ``` ```cpp #include <stdio.h> #include <stdlib.h> #include <math.h> #include <pulse/simple.h> #include <pulse/error.h> #define SAMPLE_RATE 48000 #define CHANNELS 2 #define DURATION 5 // 正弦波播放時長(秒) #define FREQ 1000 // 正弦波頻率(Hz) int main(int, char**){ pa_simple *s = NULL; pa_sample_spec ss = { .format = PA_SAMPLE_S16LE, .rate = SAMPLE_RATE, .channels = CHANNELS }; int error; s = pa_simple_new(NULL, "Sine Wave Generator", PA_STREAM_PLAYBACK, NULL, "playback", &ss, NULL, NULL, &error); if (!s) { fprintf(stderr, "Failed to create PulseAudio connection: %s\n", pa_strerror(error)); return 1; } size_t buffer_size = SAMPLE_RATE * CHANNELS * sizeof(short) / 10; // 0.1秒的緩衝區 short *buffer = (short *)malloc(buffer_size); if (!buffer) { perror("Failed to allocate buffer"); pa_simple_free(s); return 1; } size_t num_samples = buffer_size / sizeof(short); double amplitude = 30000; // 16-bit 音頻範圍內的幅度 double phase = 0; double phase_increment = 2 * M_PI * FREQ / SAMPLE_RATE; for (int i = 0; i < DURATION * SAMPLE_RATE; i++) { short sample = (short)(amplitude * sin(phase)); for (int ch = 0; ch < CHANNELS; ch++) { buffer[i % num_samples + ch * (num_samples / CHANNELS)] = sample; } phase += phase_increment; if (phase >= 2 * M_PI) { phase -= 2 * M_PI; } if ((i % num_samples) == (num_samples - 1)) { if (pa_simple_write(s, buffer, buffer_size, &error) < 0) { fprintf(stderr, "Failed to write to PulseAudio: %s\n", pa_strerror(error)); free(buffer); pa_simple_free(s); return 1; } } } if (pa_simple_drain(s, &error) < 0) { fprintf(stderr, "Failed to drain PulseAudio: %s\n", pa_strerror(error)); } free(buffer); pa_simple_free(s); return 0; } ``` ## Simple libwebsockets server(8/17/2024) > https://github.com/warmcat/libwebsockets/tree/main/minimal-examples-lowlevel/http-server/minimal-http-server ### 檔案資料夾結構 ``` . ├── mount-origin │ └── index.html └── your_http_server.c ``` ### Html ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Minimal HTTP Server</title> </head> <body> <h1>Hello, World!</h1> <p>This is a minimal HTTP server using libwebsockets.</p> </body> </html> ``` ### 執行的時候需要注意資料夾結構 你需要找到正確的mount-origin的相對位置 ```bash . out/build/GCC 12.3.0 x86_64-linux-gnu/02_WebsocketPlayAudio ``` ## Simple libwebsockers Play Audio (Fail at Web browser)(8/17/2024) ## PlusAudio Play Wav Fie(8/17/2024) ### SourceCode ```cpp #include <stdio.h> #include <stdlib.h> #include <math.h> #include <pulse/simple.h> #include <pulse/error.h> #include <pulse/def.h> #include <sndfile.h> #define BUFFER_SIZE 1024 int main(int, char**){ // 打開 WAV 檔案 SNDFILE *sf; SF_INFO sfinfo; sf = sf_open("audio.wav", SFM_READ, &sfinfo); if (!sf) { fprintf(stderr, "Failed to open WAV file: %s\n", sf_strerror(NULL)); return 1; } pa_simple *s = NULL; pa_sample_spec ss = { .format = PA_SAMPLE_S16LE, .rate = sfinfo.samplerate, .channels = sfinfo.channels }; int error; s = pa_simple_new(NULL, "WAV Player", PA_STREAM_PLAYBACK, NULL, "playback", &ss, NULL, NULL, &error); if (!s) { fprintf(stderr, "Failed to create PulseAudio connection: %s\n", pa_strerror(error)); return 1; } // 播放 WAV 檔案 size_t buffer_size = BUFFER_SIZE * sfinfo.channels * sizeof(short); short *buffer = (short *)malloc(buffer_size); if (!buffer) { perror("Failed to allocate buffer"); pa_simple_free(s); sf_close(sf); return 1; } sf_count_t read_count; while ((read_count = sf_read_short(sf, buffer, BUFFER_SIZE * sfinfo.channels)) > 0) { if (pa_simple_write(s, buffer, read_count * sizeof(short), &error) < 0) { fprintf(stderr, "Failed to write to PulseAudio: %s\n", pa_strerror(error)); free(buffer); pa_simple_free(s); sf_close(sf); return 1; } } if (pa_simple_drain(s, &error) < 0) { fprintf(stderr, "Failed to drain PulseAudio: %s\n", pa_strerror(error)); } free(buffer); pa_simple_free(s); sf_close(sf); return 0; } ``` ### CMAKE ```cmake cmake_minimum_required(VERSION 3.5.0) project(01_PulseAudioPlayAudio VERSION 0.1.0 LANGUAGES C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED True) find_package(PkgConfig REQUIRED) pkg_check_modules(PULSE REQUIRED libpulse) include_directories(${SNDFILE_INCLUDE_DIRS}) link_directories(${SNDFILE_LIBRARY_DIRS}) add_executable(01_PulseAudioPlayAudio main.c) target_link_libraries(01_PulseAudioPlayAudio ${PULSE_LIBRARIES}) target_link_libraries(01_PulseAudioPlayAudio m) target_link_libraries(01_PulseAudioPlayAudio pulse-simple) target_link_libraries(01_PulseAudioPlayAudio sndfile) include(CTest) enable_testing() ``` ## PulseAudio Play Wav Through Socket(8/17/2024) 因為看懂程式碼之後發現送到Pulse Audio 的資料不能包含Header 需要先使用sndfile 把header 移除 ### Source Code #### Server ```cpp #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <pulse/simple.h> #include <pulse/error.h> #include <pulse/def.h> #include <sys/socket.h> #define PORT 1234 #define BUFFER_SIZE 1024 int main() { int server_fd, client_fd; struct sockaddr_in server_addr, client_addr; socklen_t client_len = sizeof(client_addr); char buffer[BUFFER_SIZE]; ssize_t bytes_received; // PulseAudio 設定 pa_sample_spec ss = { .format = PA_SAMPLE_S16LE, .rate = 48000, // 假設音頻的採樣率為 48kHz .channels = 2 // 假設音頻為單聲道 }; int error; pa_simple *s = pa_simple_new(NULL, "WAV Streamer", PA_STREAM_PLAYBACK, NULL, "playback", &ss, NULL, NULL, &error); if (!s) { fprintf(stderr, "Failed to create PulseAudio connection: %s\n", pa_strerror(error)); return 1; } // 創建 socket server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd < 0) { perror("socket"); pa_simple_free(s); exit(EXIT_FAILURE); } // 設定 server 地址 memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); // 綁定 socket if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("bind"); close(server_fd); pa_simple_free(s); exit(EXIT_FAILURE); } // 監聽 client 連接 if (listen(server_fd, 1) < 0) { perror("listen"); close(server_fd); pa_simple_free(s); exit(EXIT_FAILURE); } printf("Waiting for a connection...\n"); // 接受 client 連接 client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len); if (client_fd < 0) { perror("accept"); close(server_fd); pa_simple_free(s); exit(EXIT_FAILURE); } printf("Client connected.\n"); // 實時接收並播放音頻數據 while ((bytes_received = recv(client_fd, buffer, sizeof(buffer), 0)) > 0) { // 確認收到的數據符合 PulseAudio 設定 if (bytes_received % (ss.channels * sizeof(short)) != 0) { fprintf(stderr, "Received data size mismatch. Expected multiple of %zu bytes.\n", ss.channels * sizeof(short)); break; } if (pa_simple_write(s, buffer, (size_t)bytes_received, &error) < 0) { fprintf(stderr, "Failed to write to PulseAudio: %s\n", pa_strerror(error)); break; } } if (bytes_received < 0) { perror("recv"); } // 關閉 socket 和 PulseAudio close(client_fd); close(server_fd); if (pa_simple_drain(s, &error) < 0) { fprintf(stderr, "Failed to drain PulseAudio: %s\n", pa_strerror(error)); } pa_simple_free(s); return 0; } ``` #### Client ```cpp #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sndfile.h> #define SERVER_ADDR "127.0.0.1" #define PORT 1234 #define BUFFER_SIZE 1024 int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "Usage: %s <wav-file>\n", argv[0]); return 1; } // 打開 WAV 檔案 SNDFILE *sf; SF_INFO sfinfo; sf = sf_open(argv[1], SFM_READ, &sfinfo); if (!sf) { fprintf(stderr, "Failed to open WAV file: %s\n", sf_strerror(NULL)); return 1; } // 確認音頻格式 if (sfinfo.format != (SF_FORMAT_WAV | SF_FORMAT_PCM_16)) { fprintf(stderr, "Unsupported audio format. Only 16-bit PCM WAV files are supported.\n"); sf_close(sf); return 1; } // 創建 socket int client_fd = socket(AF_INET, SOCK_STREAM, 0); if (client_fd < 0) { perror("socket"); sf_close(sf); return 1; } // 設定 server 地址 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr(SERVER_ADDR); server_addr.sin_port = htons(PORT); // 連接到 server if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("connect"); close(client_fd); sf_close(sf); return 1; } // 讀取並發送音頻數據 char buffer[BUFFER_SIZE]; sf_count_t read_count; while ((read_count = sf_read_raw(sf, buffer, sizeof(buffer))) > 0) { ssize_t bytes_sent = send(client_fd, buffer, read_count, 0); if (bytes_sent < 0) { perror("send"); break; } } if (read_count < 0) { perror("sf_read_raw"); } // 關閉 socket 和檔案 close(client_fd); sf_close(sf); return 0; } ``` ### CMAKE ```camke cmake_minimum_required(VERSION 3.5.0) project(server VERSION 0.1.0 LANGUAGES C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED True) find_package(PkgConfig REQUIRED) pkg_check_modules(PULSE REQUIRED libpulse) include_directories(${SNDFILE_INCLUDE_DIRS}) link_directories(${SNDFILE_LIBRARY_DIRS}) add_executable(server server.c) add_executable(client client.c) target_link_libraries(server ${PULSE_LIBRARIES}) target_link_libraries(server pulse-simple) target_link_libraries(client sndfile) include(CTest) enable_testing() ``` ## PulseAudio Play Audio Frome Mic(8/18/2024) ### 基本使用PulseAudio 進行環境驗證 > windows 是設定chaneel 1,16bit 48000Khz ```bash pactl list short sources # 列出音頻輸入設備 pactl list short sinks # 列出音頻輸出設備 pactl load-module module-loopback source=RDPSource sink=RDPSink ``` ### CMAKE ```cmake cmake_minimum_required(VERSION 3.5.0) project(server VERSION 0.1.0 LANGUAGES C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED True) find_package(PkgConfig REQUIRED) pkg_check_modules(PULSE REQUIRED libpulse) include_directories(${SNDFILE_INCLUDE_DIRS}) link_directories(${SNDFILE_LIBRARY_DIRS}) add_executable(client client.c) target_link_libraries(client ${PULSE_LIBRARIES}) target_link_libraries(client pulse-simple) include(CTest) enable_testing() ``` ### Source Code ```cpp #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pulse/simple.h> #include <pulse/error.h> #include <signal.h> #define BUFFER_SIZE 1024 volatile sig_atomic_t running = 1; // 用於控制迴圈是否繼續的全域變數 // 信號處理函式 void handle_sigint(int sig) { (void)sig; // 防止未使用參數的警告 running = 0; // 設置 running 為 0,告訴迴圈應該結束 printf("\n收到 Ctrl+C,程式結束。\n"); } int main() { signal(SIGINT, handle_sigint); pa_simple *record_stream = NULL; pa_simple *playback_stream = NULL; int error; pa_sample_spec record_ss = { .format = PA_SAMPLE_S16LE, // 16-bit PCM, little-endian .rate = 48000, // 麥克風的採樣率 .channels = 1 // 麥克風是單聲道 }; pa_sample_spec playback_ss = { .format = PA_SAMPLE_S16LE, // 16-bit PCM, little-endian .rate = 48000, // 喇叭的採樣率 .channels = 2 // 喇叭是立體聲 }; // 創建錄音 stream (從默認麥克風錄音) record_stream = pa_simple_new(NULL, "Mic-to-Speaker", PA_STREAM_RECORD, NULL, "record", &record_ss, NULL, NULL, &error); if (!record_stream) { fprintf(stderr, "Failed to create PulseAudio record stream: %s\n", pa_strerror(error)); return 1; } // 創建播放 stream (播放錄音內容) playback_stream = pa_simple_new(NULL, "Mic-to-Speaker", PA_STREAM_PLAYBACK, NULL, "playback", &playback_ss, NULL, NULL, &error); if (!playback_stream) { fprintf(stderr, "Failed to create PulseAudio playback stream: %s\n", pa_strerror(error)); pa_simple_free(record_stream); return 1; } // 從麥克風錄音並播放 short buffer[BUFFER_SIZE]; short playback_buffer[BUFFER_SIZE * 2]; // 2 channels, so double the size memset(buffer, 0, sizeof(buffer)); memset(playback_buffer, 0, sizeof(playback_buffer)); do { if (pa_simple_read(record_stream, buffer, sizeof(buffer), &error) < 0) { fprintf(stderr, "Failed to read from PulseAudio: %s\n", pa_strerror(error)); break; } // 將單聲道複製到立體聲 for (int i = 0; i < sizeof(buffer) / sizeof(short); i++) { playback_buffer[i * 2] = buffer[i]; // 左聲道 playback_buffer[i * 2 + 1] = buffer[i]; // 右聲道 } // 播放音頻 if (pa_simple_write(playback_stream, playback_buffer, sizeof(playback_buffer), &error) < 0) { fprintf(stderr, "Failed to play audio: %s\n", pa_strerror(error)); break; } } while (running); // 清空並釋放 PulseAudio stream pa_simple_drain(playback_stream, &error); pa_simple_free(record_stream); pa_simple_free(playback_stream); return 0; } ``` ## PulseAudio Play Audio Frome Mic Through Socket(8/19/2024) ### CMAKE ```cmake cmake_minimum_required(VERSION 3.5.0) project(server VERSION 0.1.0 LANGUAGES C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED True) find_package(PkgConfig REQUIRED) pkg_check_modules(PULSE REQUIRED libpulse) include_directories(${SNDFILE_INCLUDE_DIRS}) link_directories(${SNDFILE_LIBRARY_DIRS}) add_executable(server server.c) add_executable(client client.c) target_link_libraries(server ${PULSE_LIBRARIES}) target_link_libraries(server pulse-simple) target_link_libraries(client ${PULSE_LIBRARIES}) target_link_libraries(client pulse-simple) include(CTest) enable_testing() ``` ### Source Code >Server 的可以沿用上面的Server (PulseAudio Play Wav Through Socket) ```cpp #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pulse/simple.h> #include <pulse/error.h> #include <signal.h> #include <arpa/inet.h> #include <sys/socket.h> #include <unistd.h> #define SERVER_ADDR "127.0.0.1" #define PORT 1234 #define BUFFER_SIZE 1024 volatile sig_atomic_t running = 1; // 用於控制迴圈是否繼續的全域變數 // 信號處理函式 void handle_sigint(int sig) { (void)sig; // 防止未使用參數的警告 running = 0; // 設置 running 為 0,告訴迴圈應該結束 printf("\n收到 Ctrl+C,程式結束。\n"); } int main() { signal(SIGINT, handle_sigint); // 創建 socket int client_fd = socket(AF_INET, SOCK_STREAM, 0); if (client_fd < 0) { perror("socket"); return 1; } // 設定 server 地址 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr(SERVER_ADDR); server_addr.sin_port = htons(PORT); // 連接到 server if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("connect"); close(client_fd); return 1; } pa_simple *record_stream = NULL; int error; pa_sample_spec record_ss = { .format = PA_SAMPLE_S16LE, // 16-bit PCM, little-endian .rate = 48000, // 麥克風的採樣率 .channels = 1 // 麥克風是單聲道 }; // 創建錄音 stream (從默認麥克風錄音) record_stream = pa_simple_new(NULL, "Mic-to-Speaker", PA_STREAM_RECORD, NULL, "record", &record_ss, NULL, NULL, &error); if (!record_stream) { fprintf(stderr, "Failed to create PulseAudio record stream: %s\n", pa_strerror(error)); return 1; } // 從麥克風錄音並播放 short buffer[BUFFER_SIZE]; short playback_buffer[BUFFER_SIZE * 2]; // 2 channels, so double the size memset(buffer, 0, sizeof(buffer)); memset(playback_buffer, 0, sizeof(playback_buffer)); do { if (pa_simple_read(record_stream, buffer, sizeof(buffer), &error) < 0) { fprintf(stderr, "Failed to read from PulseAudio: %s\n", pa_strerror(error)); break; } // 將單聲道複製到立體聲 for (int i = 0; i < sizeof(buffer) / sizeof(short); i++) { playback_buffer[i * 2] = buffer[i]; // 左聲道 playback_buffer[i * 2 + 1] = buffer[i]; // 右聲道 } ssize_t bytes_sent = send(client_fd, playback_buffer, sizeof(playback_buffer), 0); if (bytes_sent < 0) { perror("send"); break; } } while (running); // 清空並釋放 PulseAudio stream pa_simple_free(record_stream); // 關閉 socket close(client_fd); return 0; } ``` ## PulseAudio Record From to QOA File(8/19/2024) ### CMAKE ```cmake cmake_minimum_required(VERSION 3.5.0) project(server VERSION 0.1.0 LANGUAGES C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED True) find_package(PkgConfig REQUIRED) pkg_check_modules(PULSE REQUIRED libpulse) include_directories(${SNDFILE_INCLUDE_DIRS}) link_directories(${SNDFILE_LIBRARY_DIRS}) add_executable(server server.c) add_executable(client client.c) target_link_libraries(server ${PULSE_LIBRARIES}) target_link_libraries(server pulse-simple) target_link_libraries(client ${PULSE_LIBRARIES}) target_link_libraries(client pulse-simple) include(CTest) enable_testing() ``` ### Library > https://github.com/marty1885/magic-qoa/tree/master ### Source Code ```cpp #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pulse/simple.h> #include <pulse/error.h> #include <signal.h> #include <arpa/inet.h> #include <sys/socket.h> #include <unistd.h> #define QOA_IMPLEMENTATION #define QOA_RECORD_TOTAL_ERROR #include "qoa.h" #define SERVER_ADDR "127.0.0.1" #define PORT 1234 #define BUFFER_SIZE 1024000 //5 secs volatile sig_atomic_t running = 1; // 用於控制迴圈是否繼續的全域變數 // 信號處理函式 void handle_sigint(int sig) { (void)sig; // 防止未使用參數的警告 running = 0; // 設置 running 為 0,告訴迴圈應該結束 printf("\n收到 Ctrl+C,程式結束。\n"); } int main() { signal(SIGINT, handle_sigint); // 創建 socket int client_fd = socket(AF_INET, SOCK_STREAM, 0); if (client_fd < 0) { perror("socket"); return 1; } // 設定 server 地址 // struct sockaddr_in server_addr; // memset(&server_addr, 0, sizeof(server_addr)); // server_addr.sin_family = AF_INET; // server_addr.sin_addr.s_addr = inet_addr(SERVER_ADDR); // server_addr.sin_port = htons(PORT); // 連接到 server // if (connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { // perror("connect"); // close(client_fd); // return 1; // } pa_simple *record_stream = NULL; int error; pa_sample_spec record_ss = { .format = PA_SAMPLE_S16LE, // 16-bit PCM, little-endian .rate = 48000, // 麥克風的採樣率 .channels = 1 // 麥克風是單聲道 }; // 創建錄音 stream (從默認麥克風錄音) record_stream = pa_simple_new(NULL, "Mic-to-Speaker", PA_STREAM_RECORD, NULL, "record", &record_ss, NULL, NULL, &error); if (!record_stream) { fprintf(stderr, "Failed to create PulseAudio record stream: %s\n", pa_strerror(error)); return 1; } // 從麥克風錄音並播放 short buffer[BUFFER_SIZE]; short playback_buffer[BUFFER_SIZE * 2]; // 2 channels, so double the size memset(buffer, 0, sizeof(buffer)); memset(playback_buffer, 0, sizeof(playback_buffer)); do { if (pa_simple_read(record_stream, buffer, sizeof(buffer), &error) < 0) { fprintf(stderr, "Failed to read from PulseAudio: %s\n", pa_strerror(error)); break; } // 將單聲道複製到立體聲 for (int i = 0; i < sizeof(buffer) / sizeof(short); i++) { playback_buffer[i * 2] = buffer[i]; // 左聲道 playback_buffer[i * 2 + 1] = buffer[i]; // 右聲道 } // ssize_t bytes_sent = send(client_fd, playback_buffer, sizeof(playback_buffer), 0); // if (bytes_sent < 0) { // perror("send"); // break; // } } while (running); //Save the QOA File int bytes_written = 0; qoa_desc desc; desc.channels = 2; desc.samplerate = 48000; desc.samples = (sizeof(buffer) / sizeof(short)) / (desc.channels * (16/8)); bytes_written = qoa_write("sample.qoa", playback_buffer, &desc); // 清空並釋放 PulseAudio stream pa_simple_free(record_stream); // 關閉 socket close(client_fd); return 0; } ``` ## PulseAudio Play QOA Audio Through Socket(8/19/2024) ### Bash ```bash cat sample.qoa | nc -q 1 localhost 1234 ``` ### CMAKE ```cmake cmake_minimum_required(VERSION 3.5.0) project(server VERSION 0.1.0 LANGUAGES C) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED True) find_package(PkgConfig REQUIRED) pkg_check_modules(PULSE REQUIRED libpulse) include_directories(${SNDFILE_INCLUDE_DIRS}) link_directories(${SNDFILE_LIBRARY_DIRS}) add_executable(server server.c) add_executable(client client.c) target_link_libraries(server ${PULSE_LIBRARIES}) target_link_libraries(server pulse-simple) target_link_libraries(client ${PULSE_LIBRARIES}) target_link_libraries(client pulse-simple) include(CTest) enable_testing() ``` ### Source Code ```cpp #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <pulse/simple.h> #include <pulse/error.h> #include <pulse/def.h> #include <sys/socket.h> #define QOA_IMPLEMENTATION #define QOA_RECORD_TOTAL_ERROR #include "qoa.h" #define PORT 1234 #define BUFFER_SIZE 1024000 int main() { int server_fd, client_fd; struct sockaddr_in server_addr, client_addr; socklen_t client_len = sizeof(client_addr); char buffer[BUFFER_SIZE]; ssize_t bytes_received; // PulseAudio 設定 pa_sample_spec ss = { .format = PA_SAMPLE_S16LE, .rate = 48000, // 假設音頻的採樣率為 48kHz .channels = 2 // 假設音頻為雙聲道 }; int error; pa_simple *s = pa_simple_new(NULL, "WAV Streamer", PA_STREAM_PLAYBACK, NULL, "playback", &ss, NULL, NULL, &error); if (!s) { fprintf(stderr, "Failed to create PulseAudio connection: %s\n", pa_strerror(error)); return 1; } // 創建 socket server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd < 0) { perror("socket"); pa_simple_free(s); exit(EXIT_FAILURE); } // 設定 server 地址 memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(PORT); // 綁定 socket if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("bind"); close(server_fd); pa_simple_free(s); exit(EXIT_FAILURE); } // 監聽 client 連接 if (listen(server_fd, 1) < 0) { perror("listen"); close(server_fd); pa_simple_free(s); exit(EXIT_FAILURE); } printf("Waiting for a connection...\n"); // 接受 client 連接 client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len); if (client_fd < 0) { perror("accept"); close(server_fd); pa_simple_free(s); exit(EXIT_FAILURE); } printf("Client connected.\n"); short *sample_data; bytes_received = 0; char *buffer_pointer = NULL; buffer_pointer = buffer; // 實時接收並播放音頻數據 while ((bytes_received = recv(client_fd, buffer_pointer, 1024, 0)) > 0) { buffer_pointer += bytes_received; } qoa_desc desc; sample_data = qoa_decode(buffer, sizeof(buffer), &desc); fprintf(stderr, "desc.samples: %d\n", desc.samples); fprintf(stderr, "real data: %ld\n", desc.samples * desc.channels * sizeof(short)); if (pa_simple_write(s, (void*)sample_data, desc.samples * desc.channels * sizeof(short), &error) < 0) { fprintf(stderr, "Failed to write to PulseAudio: %s\n", pa_strerror(error)); } if (bytes_received < 0) { perror("recv"); } // 關閉 socket 和 PulseAudio close(client_fd); close(server_fd); if (pa_simple_drain(s, &error) < 0) { fprintf(stderr, "Failed to drain PulseAudio: %s\n", pa_strerror(error)); } pa_simple_free(s); return 0; } ``` ## PulseAudio Play Audio Frome Mic Through Socket With QOA En/Decoder ###