Try   HackMD

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

sudo apt-get install libpulse-dev

VScode

> CMake:Quick Start

SourceCode

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()
#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

<!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的相對位置

. 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

#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_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

#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

#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

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

pactl list short sources   # 列出音頻輸入設備
pactl list short sinks     # 列出音頻輸出設備
pactl load-module module-loopback source=RDPSource sink=RDPSink

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

#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_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)

#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_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

#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

cat sample.qoa | nc -q 1 localhost 1234

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

#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