mirror of https://github.com/jetkvm/kvm.git
211 lines
7.1 KiB
C
211 lines
7.1 KiB
C
/*
|
|
* JetKVM Audio IPC Protocol
|
|
*
|
|
* Wire protocol for Unix domain socket communication between main process
|
|
* and audio subprocesses. This protocol is 100% compatible with the Go
|
|
* implementation in internal/audio/ipc_*.go
|
|
*
|
|
* CRITICAL: All multi-byte integers use LITTLE-ENDIAN byte order.
|
|
*/
|
|
|
|
#ifndef JETKVM_IPC_PROTOCOL_H
|
|
#define JETKVM_IPC_PROTOCOL_H
|
|
|
|
#include <stdint.h>
|
|
#include <sys/types.h>
|
|
|
|
// ============================================================================
|
|
// PROTOCOL CONSTANTS
|
|
// ============================================================================
|
|
|
|
// Magic numbers (ASCII representation when read as little-endian)
|
|
#define IPC_MAGIC_OUTPUT 0x4A4B4F55 // "JKOU" - JetKVM Output (device → browser)
|
|
#define IPC_MAGIC_INPUT 0x4A4B4D49 // "JKMI" - JetKVM Microphone Input (browser → device)
|
|
|
|
// Message types (matches Go UnifiedMessageType enum)
|
|
#define IPC_MSG_TYPE_OPUS_FRAME 0 // Audio frame data (Opus encoded)
|
|
#define IPC_MSG_TYPE_CONFIG 1 // Basic audio config (12 bytes)
|
|
#define IPC_MSG_TYPE_OPUS_CONFIG 2 // Complete Opus config (36 bytes)
|
|
#define IPC_MSG_TYPE_STOP 3 // Shutdown signal
|
|
#define IPC_MSG_TYPE_HEARTBEAT 4 // Keep-alive ping
|
|
#define IPC_MSG_TYPE_ACK 5 // Acknowledgment
|
|
|
|
// Size constraints
|
|
#define IPC_HEADER_SIZE 17 // Fixed header size
|
|
#define IPC_MAX_FRAME_SIZE 4096 // Maximum payload size (matches Go Config.MaxFrameSize)
|
|
|
|
// Socket paths
|
|
#define IPC_SOCKET_OUTPUT "/var/run/audio_output.sock"
|
|
#define IPC_SOCKET_INPUT "/var/run/audio_input.sock"
|
|
|
|
// ============================================================================
|
|
// WIRE FORMAT STRUCTURES
|
|
// ============================================================================
|
|
|
|
/**
|
|
* IPC message header (17 bytes, little-endian)
|
|
*
|
|
* Byte layout:
|
|
* [0-3] magic uint32_t LE Magic number (0x4A4B4F55 or 0x4A4B4D49)
|
|
* [4] type uint8_t Message type (0-5)
|
|
* [5-8] length uint32_t LE Payload size in bytes
|
|
* [9-16] timestamp int64_t LE Unix nanoseconds (time.Now().UnixNano())
|
|
* [17+] data uint8_t[] Variable payload
|
|
*
|
|
* CRITICAL: Must use __attribute__((packed)) to prevent padding.
|
|
*/
|
|
typedef struct __attribute__((packed)) {
|
|
uint32_t magic; // Magic number (LE)
|
|
uint8_t type; // Message type
|
|
uint32_t length; // Payload length in bytes (LE)
|
|
int64_t timestamp; // Unix nanoseconds (LE)
|
|
} ipc_header_t;
|
|
|
|
/**
|
|
* Basic audio configuration (12 bytes)
|
|
* Message type: IPC_MSG_TYPE_CONFIG
|
|
*
|
|
* All fields are uint32_t little-endian.
|
|
*/
|
|
typedef struct __attribute__((packed)) {
|
|
uint32_t sample_rate; // Samples per second (e.g., 48000)
|
|
uint32_t channels; // Number of channels (e.g., 2 for stereo)
|
|
uint32_t frame_size; // Samples per frame (e.g., 960)
|
|
} ipc_config_t;
|
|
|
|
/**
|
|
* Complete Opus encoder/decoder configuration (36 bytes)
|
|
* Message type: IPC_MSG_TYPE_OPUS_CONFIG
|
|
*
|
|
* All fields are uint32_t little-endian.
|
|
* Note: Negative values (like signal_type=-1000) are stored as two's complement uint32.
|
|
*/
|
|
typedef struct __attribute__((packed)) {
|
|
uint32_t sample_rate; // Samples per second (48000)
|
|
uint32_t channels; // Number of channels (2)
|
|
uint32_t frame_size; // Samples per frame (960)
|
|
uint32_t bitrate; // Bits per second (96000)
|
|
uint32_t complexity; // Encoder complexity 0-10 (1=fast, 10=best quality)
|
|
uint32_t vbr; // Variable bitrate: 0=disabled, 1=enabled
|
|
uint32_t signal_type; // Signal type: -1000=auto, 3001=music, 3002=voice
|
|
uint32_t bandwidth; // Bandwidth: 1101=narrowband, 1102=mediumband, 1103=wideband
|
|
uint32_t dtx; // Discontinuous transmission: 0=disabled, 1=enabled
|
|
} ipc_opus_config_t;
|
|
|
|
/**
|
|
* Complete IPC message (header + payload)
|
|
*/
|
|
typedef struct {
|
|
ipc_header_t header;
|
|
uint8_t *data; // Dynamically allocated payload (NULL if length=0)
|
|
} ipc_message_t;
|
|
|
|
// ============================================================================
|
|
// FUNCTION DECLARATIONS
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Read a complete IPC message from socket.
|
|
*
|
|
* This function:
|
|
* 1. Reads exactly 17 bytes (header)
|
|
* 2. Validates magic number
|
|
* 3. Validates length <= IPC_MAX_FRAME_SIZE
|
|
* 4. Allocates and reads payload if length > 0
|
|
* 5. Stores result in msg->header and msg->data
|
|
*
|
|
* @param sock Socket file descriptor
|
|
* @param msg Output message (data will be malloc'd if length > 0)
|
|
* @param expected_magic Expected magic number (IPC_MAGIC_OUTPUT or IPC_MAGIC_INPUT)
|
|
* @return 0 on success, -1 on error
|
|
*
|
|
* CALLER MUST FREE msg->data if non-NULL!
|
|
*/
|
|
int ipc_read_message(int sock, ipc_message_t *msg, uint32_t expected_magic);
|
|
|
|
/**
|
|
* Write a complete IPC message to socket.
|
|
*
|
|
* This function writes header + payload atomically (if possible via writev).
|
|
* Sets timestamp to current time.
|
|
*
|
|
* @param sock Socket file descriptor
|
|
* @param magic Magic number (IPC_MAGIC_OUTPUT or IPC_MAGIC_INPUT)
|
|
* @param type Message type (IPC_MSG_TYPE_*)
|
|
* @param data Payload data (can be NULL if length=0)
|
|
* @param length Payload length in bytes
|
|
* @return 0 on success, -1 on error
|
|
*/
|
|
int ipc_write_message(int sock, uint32_t magic, uint8_t type,
|
|
const uint8_t *data, uint32_t length);
|
|
|
|
/**
|
|
* Parse Opus configuration from message data.
|
|
*
|
|
* @param data Payload data (must be exactly 36 bytes)
|
|
* @param length Payload length (must be 36)
|
|
* @param config Output Opus configuration
|
|
* @return 0 on success, -1 if length != 36
|
|
*/
|
|
int ipc_parse_opus_config(const uint8_t *data, uint32_t length, ipc_opus_config_t *config);
|
|
|
|
/**
|
|
* Parse basic audio configuration from message data.
|
|
*
|
|
* @param data Payload data (must be exactly 12 bytes)
|
|
* @param length Payload length (must be 12)
|
|
* @param config Output audio configuration
|
|
* @return 0 on success, -1 if length != 12
|
|
*/
|
|
int ipc_parse_config(const uint8_t *data, uint32_t length, ipc_config_t *config);
|
|
|
|
/**
|
|
* Free message resources.
|
|
*
|
|
* @param msg Message to free (frees msg->data if non-NULL)
|
|
*/
|
|
void ipc_free_message(ipc_message_t *msg);
|
|
|
|
/**
|
|
* Get current time in nanoseconds (Unix epoch).
|
|
*
|
|
* @return Time in nanoseconds (compatible with Go time.Now().UnixNano())
|
|
*/
|
|
int64_t ipc_get_time_ns(void);
|
|
|
|
/**
|
|
* Create Unix domain socket server.
|
|
*
|
|
* This function:
|
|
* 1. Creates socket with AF_UNIX, SOCK_STREAM
|
|
* 2. Removes existing socket file
|
|
* 3. Binds to specified path
|
|
* 4. Listens with backlog=1 (single client)
|
|
*
|
|
* @param socket_path Path to Unix socket (e.g., "/var/run/audio_output.sock")
|
|
* @return Socket fd on success, -1 on error
|
|
*/
|
|
int ipc_create_server(const char *socket_path);
|
|
|
|
/**
|
|
* Accept client connection with automatic retry.
|
|
*
|
|
* Blocks until client connects or error occurs.
|
|
*
|
|
* @param server_sock Server socket fd from ipc_create_server()
|
|
* @return Client socket fd on success, -1 on error
|
|
*/
|
|
int ipc_accept_client(int server_sock);
|
|
|
|
/**
|
|
* Helper: Read exactly N bytes from socket (loops until complete or error).
|
|
*
|
|
* @param sock Socket file descriptor
|
|
* @param buf Output buffer
|
|
* @param len Number of bytes to read
|
|
* @return 0 on success, -1 on error
|
|
*/
|
|
int ipc_read_full(int sock, void *buf, size_t len);
|
|
|
|
#endif // JETKVM_IPC_PROTOCOL_H
|