| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- #pragma once
- #include "esp_err.h"
- #include <stdbool.h>
- #include <stddef.h>
- #include <stdint.h>
- /**
- * Audio receiver for AirPlay RTP streams
- * Handles UDP packet reception and audio decoding
- */
- // Audio format info from ANNOUNCE SDP
- typedef struct {
- char codec[32]; // "AppleLossless", "AAC", etc.
- int sample_rate; // 44100, 48000, etc.
- int channels; // 1 or 2
- int bits_per_sample; // 16, 24
- int frame_size; // Samples per frame (ALAC: 352)
- // ALAC-specific config (from fmtp line)
- uint32_t max_samples_per_frame;
- uint8_t sample_size;
- uint8_t rice_history_mult;
- uint8_t rice_initial_history;
- uint8_t rice_limit;
- uint8_t num_channels;
- uint16_t max_run;
- uint32_t max_coded_frame_size;
- uint32_t avg_bit_rate;
- uint32_t sample_rate_config;
- } audio_format_t;
- // Audio encryption types
- typedef enum {
- AUDIO_ENCRYPT_NONE = 0,
- AUDIO_ENCRYPT_AES_CBC,
- AUDIO_ENCRYPT_CHACHA20_POLY1305
- } audio_encrypt_type_t;
- // Audio encryption configuration
- typedef struct {
- audio_encrypt_type_t type;
- uint8_t key[32]; // AES-128 uses 16, ChaCha20 uses 32
- uint8_t iv[16]; // AES-CBC IV
- size_t key_len;
- } audio_encrypt_t;
- // Audio buffer statistics
- typedef struct {
- uint32_t packets_received;
- uint32_t packets_decoded;
- uint32_t packets_dropped;
- uint32_t decrypt_errors;
- uint32_t buffer_underruns;
- uint32_t buffer_overruns;
- uint32_t late_frames;
- uint16_t last_seq;
- uint32_t last_timestamp;
- } audio_stats_t;
- /**
- * Initialize audio receiver
- */
- esp_err_t audio_receiver_init(void);
- /**
- * Set audio format from ANNOUNCE SDP
- */
- void audio_receiver_set_format(const audio_format_t *format);
- /**
- * Set encryption parameters for RTP decryption
- */
- void audio_receiver_set_encryption(const audio_encrypt_t *encrypt);
- /**
- * Start receiving audio on specified port
- */
- esp_err_t audio_receiver_start(uint16_t data_port, uint16_t control_port);
- /**
- * Start the active stream type using the provided ports.
- */
- esp_err_t audio_receiver_start_stream(uint16_t data_port, uint16_t control_port,
- uint16_t tcp_port);
- /**
- * Stop receiving audio
- */
- void audio_receiver_stop(void);
- /**
- * Get audio statistics
- */
- void audio_receiver_get_stats(audio_stats_t *stats);
- /**
- * Read decoded PCM samples from buffer
- * @param buffer Output buffer for PCM samples (interleaved stereo, 16-bit)
- * @param samples Maximum number of samples to read (per channel)
- * @return Number of samples actually read
- */
- size_t audio_receiver_read(int16_t *buffer, size_t samples);
- /**
- * Check if audio data is available
- */
- bool audio_receiver_has_data(void);
- /**
- * Flush audio buffer (full stop path — used by TEARDOWN and stop).
- */
- void audio_receiver_flush(void);
- /**
- * Flush audio buffer for a mid-stream seek (FLUSH / immediate FLUSHBUFFERED).
- * Identical to audio_receiver_flush() but also sets timing.post_flush so
- * that audio_timing_read plays frames immediately after the seek instead of
- * silencing them during the phone's pre-buffer window (which can be several
- * seconds with AirPlay 2 buffered streams). Mirrors shairport-sync's
- * first_packet_timestamp==0 behaviour: post-flush frames play unconditionally
- * until the anchor reports on-time (early_us < TIMING_THRESHOLD_US).
- */
- void audio_receiver_seek_flush(void);
- /**
- * Arm a deferred flush for AirPlay 2 FLUSHBUFFERED with flushFromSeq.
- *
- * Instead of discarding the buffer immediately, audio_timing_read will
- * continue playing normally until it encounters a frame whose rtp_timestamp
- * >= flush_until_ts, at which point it bulk-flushes the remainder and sets
- * post_flush so the next track starts without delay.
- *
- * @param flush_until_ts RTP timestamp boundary from flushUntilTS plist key.
- */
- void audio_receiver_set_deferred_flush(uint32_t flush_until_ts);
- /**
- * Pause playback while preserving the timing anchor.
- * Flushes the audio buffer and resets playback-start state, but does NOT
- * call audio_timing_reset() so the anchor remains valid. The pause start
- * time is recorded so that audio_receiver_set_playing(true) can compensate
- * for the pause duration on resume.
- */
- void audio_receiver_pause(void);
- /**
- * Set advertised/target output latency in microseconds.
- */
- void audio_receiver_set_output_latency_us(uint32_t latency_us);
- /**
- * Get current output latency in microseconds (buffer latency only).
- */
- uint32_t audio_receiver_get_output_latency_us(void);
- /**
- * Get hardware output latency in microseconds (I2S DMA pipeline delay).
- */
- uint32_t audio_receiver_get_hardware_latency_us(void);
- /**
- * Get total advertised latency in microseconds. Includes the jitter-buffer
- * target depth, hardware DMA delay, and fixed decrypt/decode/network
- * pipeline constant. Report this in outputLatencyMicros so the phone
- * schedules sends to match our actual end-to-end depth.
- */
- uint32_t audio_receiver_get_advertised_latency_us(void);
- /**
- * Provide anchor timing information from SETRATEANCHORTIME.
- * @param clock_id PTP clock ID (networkTimeTimelineID)
- * @param network_time_ns Anchor time in nanoseconds (PTP timeline)
- * @param rtp_time RTP timestamp for the anchor
- */
- void audio_receiver_set_anchor_time(uint64_t clock_id, uint64_t network_time_ns,
- uint32_t rtp_time);
- /**
- * Enable or pause playout scheduling.
- */
- void audio_receiver_set_playing(bool playing);
- /**
- * Check if playback is currently active (not paused).
- */
- bool audio_receiver_is_playing(void);
- /**
- * Reset timing anchor (call when PTP clock changes, e.g., SETPEERS)
- */
- void audio_receiver_reset_timing(void);
- /**
- * Set the client's control address for NACK retransmission requests.
- * @param client_ip Client IP in network byte order
- * @param client_control_port Client's control port (host byte order)
- */
- void audio_receiver_set_client_control(uint32_t client_ip,
- uint16_t client_control_port);
- /**
- * Stream types for AirPlay 2
- */
- typedef enum {
- AUDIO_STREAM_NONE = 0,
- AUDIO_STREAM_REALTIME = 96, // UDP, ALAC
- AUDIO_STREAM_BUFFERED = 103 // TCP, AAC-ELD
- } audio_stream_type_t;
- /**
- * Start buffered audio receiver (type=103) on TCP port
- * @param tcp_port Port to listen on for TCP connections
- * @return ESP_OK on success
- */
- esp_err_t audio_receiver_start_buffered(uint16_t tcp_port);
- /**
- * Get the active stream port (data or buffered).
- */
- uint16_t audio_receiver_get_stream_port(void);
- /**
- * Get the TCP port for buffered audio (after start_buffered)
- */
- uint16_t audio_receiver_get_buffered_port(void);
- /**
- * Stop only the buffered receiver but keep playing buffered data
- * Used when TEARDOWN with streams array is received (sender done sending)
- */
- void audio_receiver_stop_buffered_only(void);
- /**
- * Set the stream type (realtime vs buffered)
- */
- void audio_receiver_set_stream_type(audio_stream_type_t type);
|