/*
==============================================================================
  UDP -> HLS  v14.3  —  AES-128 + MP3->AAC  PRODUCTION STABLE
==============================================================================
  Fixes vs v14.2:
    - Removed per-packet mutex on video path (was locking 3000x/sec)
    - Audio thread uses a dedicated pipe fd per segment, not shared seg_fd
    - Clean separation: UDP thread owns seg_fd exclusively
      Audio thread gets its own write fd (dup of seg_fd) per segment
    - No mutex needed during normal operation
    - Mutex only used at segment boundary to hand off new fd to audio thread

  Architecture:
    UDP thread:
      recv() -> parse video/PAT/PMT -> write video directly to seg_fd
             -> reassemble audio PES -> push to audio queue

    Audio thread:
      pop PES from queue -> decode MP3 -> encode AAC
      -> write AAC TS packets to audio_fd (its own dup of current seg_fd)

    Segment boundary:
      UDP thread: flush+close seg_fd, open new seg_fd
      Lock: atomically give audio thread new audio_fd (dup of new seg_fd)
      Lock is held <1 microsecond, only at segment boundaries (~every 2s)

  BUILD:
    ln -sf /usr/lib/x86_64-linux-gnu/libavutil.so.58    /usr/lib/x86_64-linux-gnu/libavutil.so
    ln -sf /usr/lib/x86_64-linux-gnu/libavcodec.so.60   /usr/lib/x86_64-linux-gnu/libavcodec.so
    ln -sf /usr/lib/x86_64-linux-gnu/libavformat.so.60  /usr/lib/x86_64-linux-gnu/libavformat.so
    ln -sf /usr/lib/x86_64-linux-gnu/libswresample.so.4 /usr/lib/x86_64-linux-gnu/libswresample.so
    ln -sf /usr/lib/x86_64-linux-gnu/libssl.so.3        /usr/lib/x86_64-linux-gnu/libssl.so
    ln -sf /usr/lib/x86_64-linux-gnu/libcrypto.so.3     /usr/lib/x86_64-linux-gnu/libcrypto.so
    apt-get install -y libavcodec-dev libavutil-dev libswresample-dev libssl-dev
    gcc -O2 -pthread -o udp_hls udp_hls.c \
        -lssl -lcrypto -lavcodec -lavformat -lavutil -lswresample -lm
==============================================================================
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>
#include <ifaddrs.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <math.h>
#include <openssl/aes.h>
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libavutil/channel_layout.h>
#include <libavutil/samplefmt.h>
#include <libswresample/swresample.h>

/* -- tunables -- */
#define SEGMENT_SECS      2
#define MAX_SEGMENTS      6
#define MAX_CHANNELS      64
#define WRITE_BATCH       128
#define RCVBUF            (16<<20)
#define DUR_SLOTS         64
#define AAC_BITRATE       128000
#define AUDIO_QUEUE_SIZE  128
#define AUDIO_PES_BUF     131072

/* -- constants -- */
#define TS_SZ             188
#define AES_BLOCK         16
#define UDP_MAX           65536
#define PCR_HZ            90000ULL
#define PCR_WRAP          0x200000000ULL
#define PCR_DISC          (10*90000ULL)
#define ADTS_HDR_SIZE     7

/* ----------------------------------------------------------
   AES-128-CBC — two independent instances (video + audio)
   ---------------------------------------------------------- */
typedef struct {
    AES_KEY enc;
    uint8_t iv_orig[AES_BLOCK];
    uint8_t iv_work[AES_BLOCK];
    uint8_t plain[WRITE_BATCH * TS_SZ + AES_BLOCK * 2];
    int     plain_n;
} AesCbc;

static void aes_init(AesCbc *a, const uint8_t *key16, const uint8_t *iv16)
{
    AES_set_encrypt_key(key16, 128, &a->enc);
    memcpy(a->iv_orig, iv16, AES_BLOCK);
    memcpy(a->iv_work, iv16, AES_BLOCK);
    a->plain_n = 0;
}
static void aes_reset(AesCbc *a)
{
    memcpy(a->iv_work, a->iv_orig, AES_BLOCK);
    a->plain_n = 0;
}
static int aes_flush(AesCbc *a, int fd, int final)
{
    int n = a->plain_n;
    if (final) {
        int pad = AES_BLOCK - (n % AES_BLOCK);
        if (!pad) pad = AES_BLOCK;
        memset(a->plain + n, pad, (size_t)pad);
        n += pad;
    } else n = (n / AES_BLOCK) * AES_BLOCK;
    if (n <= 0) return 0;
    uint8_t out[sizeof(a->plain)];
    AES_cbc_encrypt(a->plain, out, (size_t)n, &a->enc, a->iv_work, AES_ENCRYPT);
    ssize_t r = write(fd, out, (size_t)n);
    if (r < 0) { perror("aes write"); return -1; }
    int rem = final ? 0 : (a->plain_n - n);
    if (rem > 0) memmove(a->plain, a->plain + n, (size_t)rem);
    a->plain_n = rem;
    return 0;
}
static void aes_push(AesCbc *a, int fd, const uint8_t *pkt)
{
    memcpy(a->plain + a->plain_n, pkt, TS_SZ);
    a->plain_n += TS_SZ;
    if (a->plain_n >= WRITE_BATCH * TS_SZ) aes_flush(a, fd, 0);
}

/* plain write helpers */
static void plain_push(uint8_t *wb, int *wb_n, int fd, const uint8_t *pkt)
{
    memcpy(wb + (*wb_n) * TS_SZ, pkt, TS_SZ);
    if (++(*wb_n) >= WRITE_BATCH) {
        ssize_t r = write(fd, wb, (size_t)(*wb_n) * TS_SZ); (void)r;
        *wb_n = 0;
    }
}
static void plain_flush(uint8_t *wb, int *wb_n, int fd)
{
    if (!*wb_n || fd < 0) return;
    ssize_t r = write(fd, wb, (size_t)(*wb_n) * TS_SZ); (void)r;
    *wb_n = 0;
}

/* ----------------------------------------------------------
   PES QUEUE
   ---------------------------------------------------------- */
typedef struct { uint8_t *data; int len; } PesItem;
typedef struct {
    PesItem         items[AUDIO_QUEUE_SIZE];
    int             head, tail, count;
    pthread_mutex_t mu;
    pthread_cond_t  cond;
    int             done;
} PesQueue;

static void q_init(PesQueue *q)
{
    memset(q, 0, sizeof(*q));
    pthread_mutex_init(&q->mu, NULL);
    pthread_cond_init(&q->cond, NULL);
}
static void q_push(PesQueue *q, const uint8_t *d, int n)
{
    uint8_t *cp = malloc((size_t)n);
    if (!cp) return;
    memcpy(cp, d, (size_t)n);
    pthread_mutex_lock(&q->mu);
    if (q->count < AUDIO_QUEUE_SIZE) {
        q->items[q->tail].data = cp;
        q->items[q->tail].len  = n;
        q->tail = (q->tail + 1) % AUDIO_QUEUE_SIZE;
        q->count++;
        pthread_cond_signal(&q->cond);
    } else { free(cp); }   /* drop — prevents backpressure on video */
    pthread_mutex_unlock(&q->mu);
}
static int q_pop(PesQueue *q, uint8_t **d, int *n)
{
    pthread_mutex_lock(&q->mu);
    while (!q->count && !q->done)
        pthread_cond_wait(&q->cond, &q->mu);
    if (!q->count) { pthread_mutex_unlock(&q->mu); return 0; }
    *d = q->items[q->head].data;
    *n = q->items[q->head].len;
    q->head = (q->head + 1) % AUDIO_QUEUE_SIZE;
    q->count--;
    pthread_mutex_unlock(&q->mu);
    return 1;
}
static void q_done(PesQueue *q)
{
    pthread_mutex_lock(&q->mu);
    q->done = 1;
    pthread_cond_broadcast(&q->cond);
    pthread_mutex_unlock(&q->mu);
}

/* ----------------------------------------------------------
   AUDIO FD HANDOFF
   The audio thread needs to know which file to write to.
   At segment boundary the UDP thread atomically updates audio_fd.
   The audio thread reads audio_fd under a tiny spinlock.
   ---------------------------------------------------------- */
typedef struct {
    pthread_mutex_t mu;
    int             fd;      /* current segment fd for audio writes      */
    int             enc_on;  /* AES encryption active?                   */
    AesCbc          aes;     /* audio thread's own AES state             */
    uint8_t         wb[WRITE_BATCH * TS_SZ];
    int             wb_n;
} AudioFd;

static void afd_init(AudioFd *af)
{
    memset(af, 0, sizeof(*af));
    pthread_mutex_init(&af->mu, NULL);
    af->fd = -1;
}

/* Called by UDP thread at each segment boundary */
static void afd_set(AudioFd *af, int new_fd, int enc_on,
                    const uint8_t *key16, const uint8_t *iv16)
{
    pthread_mutex_lock(&af->mu);
    /* flush + close old audio fd */
    if (af->fd >= 0) {
        if (af->enc_on) aes_flush(&af->aes, af->fd, 1);
        else            plain_flush(af->wb, &af->wb_n, af->fd);
        close(af->fd);
    }
    af->fd     = new_fd;
    af->enc_on = enc_on;
    if (enc_on && key16 && iv16) aes_init(&af->aes, key16, iv16);
    else if (enc_on)              aes_reset(&af->aes);
    af->wb_n = 0;
    pthread_mutex_unlock(&af->mu);
}

/* Called by audio thread to write one TS packet */
static void afd_write(AudioFd *af, const uint8_t *pkt)
{
    pthread_mutex_lock(&af->mu);
    if (af->fd < 0) { pthread_mutex_unlock(&af->mu); return; }
    if (af->enc_on) aes_push(&af->aes, af->fd, pkt);
    else            plain_push(af->wb, &af->wb_n, af->fd, pkt);
    pthread_mutex_unlock(&af->mu);
}

/* ----------------------------------------------------------
   TYPES
   ---------------------------------------------------------- */
typedef struct {
    char mcast[64]; int port;
    char dir[256]; char name[64]; char iface[64];
    char keyfile[256]; char keyuri[512]; char iv_hex[33];
    uint64_t start_seq;
} ChCfg;

typedef struct {
    ChCfg cfg; int fd;
    int rtp_ok, rtp_off;

    uint8_t  pat[TS_SZ], pmt[TS_SZ];
    int      pat_ok, pmt_ok;
    uint16_t pmt_pid, vid_pid, aud_pid, pcr_pid;

    /* video segment — UDP thread only, no lock needed */
    int      seg_fd;
    uint64_t seg_seq;
    int      aes_ready;
    uint8_t  key16[16];
    uint8_t  iv16[16];
    AesCbc   aes_vid;                  /* video AES state                  */
    uint8_t  wb[WRITE_BATCH * TS_SZ];
    int      wb_n;

    /* audio fd — shared between UDP and audio thread */
    AudioFd  afd;

    /* audio transcoder + queue */
    PesQueue aq;
    pthread_t audio_tid;
    int       audio_started;

    /* MP3 decoder */
    AVCodecContext *dec_ctx;
    AVPacket       *dec_pkt;
    AVFrame        *dec_frame;
    SwrContext     *swr;

    /* AAC encoder */
    AVCodecContext *enc_ctx;
    AVPacket       *enc_pkt;
    AVFrame        *enc_frame;
    int             enc_frame_size;

    /* PCM ring buffer */
    float  *pcm_l, *pcm_r;
    int     pcm_n, pcm_cap;

    /* TS output for audio */
    uint8_t  aud_cc;
    int64_t  aud_pts;
    uint16_t aud_out_pid;

    /* PES reassembly (UDP thread) */
    uint8_t  pes_buf[AUDIO_PES_BUF];
    int      pes_len, pes_started;

    /* PCR */
    int      pcr_ready;
    uint64_t pcr_seg_start, pcr_last;
    struct timespec wall_seg_start; int wall_seeded;

    double   dur[DUR_SLOTS];
    uint64_t dgrams, pkts, written, segs, timeouts;
} Ch;

static volatile sig_atomic_t g_stop = 0;
static Ch  g_ch[MAX_CHANNELS];
static int g_nch = 0;
static void on_sig(int s) { (void)s; g_stop = 1; }

/* -- TS helpers -- */
static inline uint16_t ts_pid(const uint8_t *p)
    { return (uint16_t)(((p[1]&0x1F)<<8)|p[2]); }
static inline int ts_pusi(const uint8_t *p) { return (p[1]>>6)&1; }
static inline int ts_afc (const uint8_t *p) { return (p[3]>>4)&3; }
static inline int ts_poff(const uint8_t *p)
{
    int a=ts_afc(p),o;
    if(a==1)o=4; else if(a==3)o=5+(int)p[4]; else return -1;
    return o<TS_SZ?o:-1;
}
static int pcr_get(const uint8_t *p, uint64_t *v)
{
    int a=ts_afc(p);
    if(a!=2&&a!=3) return 0;
    if(p[4]<7||!(p[5]&0x10)) return 0;
    *v=((uint64_t)p[6]<<25)|((uint64_t)p[7]<<17)|
       ((uint64_t)p[8]<<9)|((uint64_t)p[9]<<1)|((p[10]>>7)&1ULL);
    return 1;
}
static inline double pcr_el(uint64_t a,uint64_t b)
{ uint64_t d=(b>=a)?(b-a):(PCR_WRAP-a+b); return(double)d/90000.0; }
static inline double wall_el(const struct timespec *a,const struct timespec *b)
{ return(double)(b->tv_sec-a->tv_sec)+(double)(b->tv_nsec-a->tv_nsec)/1e9; }

static uint16_t pat_parse(const uint8_t *p)
{
    int o=ts_poff(p); if(o<0||o>=TS_SZ) return 0;
    int b=o+1+p[o];
    if(b+8>=TS_SZ||p[b]!=0x00) return 0;
    int end=b+3+(((p[b+1]&0x0F)<<8)|p[b+2])-4;
    for(int q=b+8;q+3<=end&&q+3<TS_SZ;q+=4){
        uint16_t pn=(uint16_t)((p[q]<<8)|p[q+1]);
        uint16_t pp=(uint16_t)(((p[q+2]&0x1F)<<8)|p[q+3]);
        if(pn) return pp;
    }
    return 0;
}
static int is_video(uint8_t t)
{return t==0x01||t==0x02||t==0x10||t==0x1B||t==0x24||t==0x27||t==0x42;}
static int is_audio(uint8_t t)
{return t==0x03||t==0x04||t==0x0F||t==0x11||t==0x81||t==0x82;}
static int pmt_parse(const uint8_t *p,uint16_t *pcr,uint16_t *vid,uint16_t *aud)
{
    int o=ts_poff(p); if(o<0||o+1>=TS_SZ) return 0;
    int b=o+1+p[o];
    if(b+12>=TS_SZ||p[b]!=0x02) return 0;
    *pcr=(uint16_t)(((p[b+8]&0x1F)<<8)|p[b+9]);
    int pil=((p[b+10]&0x0F)<<8)|p[b+11];
    int sec=((p[b+1]&0x0F)<<8)|p[b+2];
    int es=b+12+pil, ee=b+3+sec-4; *vid=*aud=0;
    while(es+4<TS_SZ&&es+4<=ee){
        uint8_t st=p[es];
        uint16_t sp=(uint16_t)(((p[es+1]&0x1F)<<8)|p[es+2]);
        int el=((p[es+3]&0x0F)<<8)|p[es+4];
        if(!*vid&&is_video(st))*vid=sp;
        if(!*aud&&is_audio(st))*aud=sp;
        es+=5+el;
    }
    return (*pcr>0);
}
static void pmt_patch_aac(uint8_t *pm, uint16_t aud_pid)
{
    int o=ts_poff(pm); if(o<0||o+1>=TS_SZ) return;
    int b=o+1+pm[o];
    if(b+12>=TS_SZ||pm[b]!=0x02) return;
    int pil=((pm[b+10]&0x0F)<<8)|pm[b+11];
    int sec=((pm[b+1]&0x0F)<<8)|pm[b+2];
    int es=b+12+pil, ee=b+3+sec-4;
    while(es+4<TS_SZ&&es+4<=ee){
        uint16_t sp=(uint16_t)(((pm[es+1]&0x1F)<<8)|pm[es+2]);
        int el=((pm[es+3]&0x0F)<<8)|pm[es+4];
        if(sp==aud_pid&&is_audio(pm[es])){pm[es]=0x0F;break;}
        es+=5+el;
    }
}
static int rtp_detect(const uint8_t *b,int n)
{
    if(n<13||(b[0]>>6)!=2) return 0;
    int h=12+(b[0]&0xF)*4;
    if((b[0]>>4)&1){if(n<=h+4)return 0;h+=4+((b[h+2]<<8)|b[h+3])*4;}
    return(h<n&&b[h]==0x47)?h:0;
}

/* -- write one TS packet to video segment (UDP thread only, no lock) -- */
static void vid_write(Ch *c, const uint8_t *p)
{
    if(c->seg_fd<0) return;
    if(c->aes_ready) aes_push(&c->aes_vid,c->seg_fd,p);
    else             plain_push(c->wb,&c->wb_n,c->seg_fd,p);
    c->written++;
}
static void vid_flush(Ch *c)
{
    if(c->seg_fd<0) return;
    if(c->aes_ready) aes_flush(&c->aes_vid,c->seg_fd,0);
    else             plain_flush(c->wb,&c->wb_n,c->seg_fd);
}

/* -- ADTS header -- */
static void adts_hdr(uint8_t *h, int aac_len, int si, int ch)
{
    int fl=aac_len+ADTS_HDR_SIZE;
    h[0]=0xFF;h[1]=0xF1;
    h[2]=(uint8_t)((si<<2)|((ch>>2)&1));
    h[3]=(uint8_t)(((ch&3)<<6)|((fl>>11)&3));
    h[4]=(uint8_t)((fl>>3)&0xFF);
    h[5]=(uint8_t)(((fl&7)<<5)|0x1F);
    h[6]=0xFC;
}
static int sr_idx(int sr)
{
    static const int r[]={96000,88200,64000,48000,44100,32000,
                           24000,22050,16000,12000,11025,8000,7350};
    for(int i=0;i<13;i++) if(r[i]==sr) return i;
    return 4;
}

/* -- emit one AAC ADTS frame as TS packets (audio thread) -- */
static void emit_aac(Ch *c, const uint8_t *adts, int alen, int64_t pts)
{
    /* build PES */
    uint8_t pes[32]; int pi=0;
    int plen=3+5+alen;
    pes[pi++]=0x00;pes[pi++]=0x00;pes[pi++]=0x01;pes[pi++]=0xC0;
    pes[pi++]=(uint8_t)(plen>>8);pes[pi++]=(uint8_t)(plen&0xFF);
    pes[pi++]=0x80;pes[pi++]=0x80;pes[pi++]=0x05;
    uint64_t p=(uint64_t)pts;
    pes[pi++]=(uint8_t)(0x21|((p>>29)&0x0E));
    pes[pi++]=(uint8_t)((p>>22)&0xFF);
    pes[pi++]=(uint8_t)(0x01|((p>>14)&0xFE));
    pes[pi++]=(uint8_t)((p>>7)&0xFF);
    pes[pi++]=(uint8_t)(0x01|((p<<1)&0xFE));

    /* split into TS packets */
    const uint8_t *srcs[2]={pes,adts};
    int lens[2]={pi,alen};
    int si=0,so=0,first=1;
    while(si<2){
        if(so>=lens[si]){si++;so=0;continue;}
        uint8_t ts[TS_SZ]; memset(ts,0xFF,TS_SZ);
        ts[0]=0x47;
        ts[1]=(uint8_t)((first?0x40:0x00)|((c->aud_out_pid>>8)&0x1F));
        ts[2]=(uint8_t)(c->aud_out_pid&0xFF);
        ts[3]=(uint8_t)(0x10|(c->aud_cc&0x0F));
        c->aud_cc=(c->aud_cc+1)&0x0F; first=0;
        int sp=TS_SZ-4,dp=4;
        while(sp>0&&si<2){
            if(so>=lens[si]){si++;so=0;continue;}
            int cp=lens[si]-so; if(cp>sp)cp=sp;
            memcpy(ts+dp,srcs[si]+so,(size_t)cp);
            dp+=cp;sp-=cp;so+=cp;
        }
        afd_write(&c->afd,ts);   /* write via AudioFd — mutex only on contention */
    }
}

/* -- encode one 1024-sample frame (audio thread) -- */
static void encode_frame(Ch *c)
{
    av_frame_make_writable(c->enc_frame);
    c->enc_frame->nb_samples=c->enc_frame_size;
    c->enc_frame->pts       =c->aud_pts;
    memcpy(c->enc_frame->data[0],c->pcm_l,(size_t)c->enc_frame_size*sizeof(float));
    memcpy(c->enc_frame->data[1],c->pcm_r,(size_t)c->enc_frame_size*sizeof(float));
    int rem=c->pcm_n-c->enc_frame_size;
    if(rem>0){
        memmove(c->pcm_l,c->pcm_l+c->enc_frame_size,(size_t)rem*sizeof(float));
        memmove(c->pcm_r,c->pcm_r+c->enc_frame_size,(size_t)rem*sizeof(float));
    }
    c->pcm_n=rem<0?0:rem;

    avcodec_send_frame(c->enc_ctx,c->enc_frame);
    while(avcodec_receive_packet(c->enc_ctx,c->enc_pkt)==0){
        int si=sr_idx(c->enc_ctx->sample_rate);
        int ch=c->enc_ctx->ch_layout.nb_channels;
        uint8_t hdr[ADTS_HDR_SIZE];
        adts_hdr(hdr,c->enc_pkt->size,si,ch);
        int tot=ADTS_HDR_SIZE+c->enc_pkt->size;
        uint8_t *buf=(uint8_t*)malloc((size_t)tot);
        memcpy(buf,hdr,ADTS_HDR_SIZE);
        memcpy(buf+ADTS_HDR_SIZE,c->enc_pkt->data,(size_t)c->enc_pkt->size);
        emit_aac(c,buf,tot,c->enc_pkt->pts);
        free(buf);
        c->aud_pts+=c->enc_frame_size;
        av_packet_unref(c->enc_pkt);
    }
}

/* -- transcode one PES payload (audio thread) -- */
static void transcode_pes(Ch *c, const uint8_t *mp3, int mp3_len)
{
    c->dec_pkt->data=(uint8_t*)mp3;
    c->dec_pkt->size=mp3_len;
    if(avcodec_send_packet(c->dec_ctx,c->dec_pkt)<0) return;

    while(avcodec_receive_frame(c->dec_ctx,c->dec_frame)==0){
        AVFrame *in=c->dec_frame;
        if(!c->swr){
            AVChannelLayout ol=AV_CHANNEL_LAYOUT_STEREO;
            swr_alloc_set_opts2(&c->swr,
                &ol,           AV_SAMPLE_FMT_FLTP,44100,
                &in->ch_layout,(enum AVSampleFormat)in->format,
                               in->sample_rate,0,NULL);
            swr_init(c->swr);
        }
        int out_n=swr_get_out_samples(c->swr,in->nb_samples);
        if(c->pcm_n+out_n>c->pcm_cap){
            c->pcm_cap=c->pcm_n+out_n+c->enc_frame_size;
            c->pcm_l=(float*)av_realloc(c->pcm_l,(size_t)c->pcm_cap*sizeof(float));
            c->pcm_r=(float*)av_realloc(c->pcm_r,(size_t)c->pcm_cap*sizeof(float));
        }
        uint8_t *dst[2]={(uint8_t*)(c->pcm_l+c->pcm_n),
                         (uint8_t*)(c->pcm_r+c->pcm_n)};
        int got=swr_convert(c->swr,dst,out_n,
                            (const uint8_t**)in->data,in->nb_samples);
        if(got>0) c->pcm_n+=got;
        while(c->pcm_n>=c->enc_frame_size) encode_frame(c);
        av_frame_unref(c->dec_frame);
    }
}

/* -- audio thread -- */
static void *audio_thread(void *arg)
{
    Ch *c=(Ch*)arg;
    uint8_t *data; int len;
    while(q_pop(&c->aq,&data,&len)){
        transcode_pes(c,data,len);
        free(data);
    }
    /* flush remaining PCM */
    if(c->pcm_n>0){
        int pad=c->enc_frame_size-c->pcm_n;
        if(pad>0){
            memset(c->pcm_l+c->pcm_n,0,(size_t)pad*sizeof(float));
            memset(c->pcm_r+c->pcm_n,0,(size_t)pad*sizeof(float));
            c->pcm_n=c->enc_frame_size;
        }
        while(c->pcm_n>=c->enc_frame_size) encode_frame(c);
    }
    return NULL;
}

/* -- init audio -- */
static int audio_init(Ch *c, uint16_t pid)
{
    c->aud_out_pid=pid;
    c->aud_cc=0; c->aud_pts=0;

    const AVCodec *dec=avcodec_find_decoder(AV_CODEC_ID_MP3);
    if(!dec){fprintf(stderr,"[aac] MP3 decoder not found\n");return -1;}
    c->dec_ctx=avcodec_alloc_context3(dec);
    if(avcodec_open2(c->dec_ctx,dec,NULL)<0) return -1;
    c->dec_pkt=av_packet_alloc();
    c->dec_frame=av_frame_alloc();

    const AVCodec *enc=avcodec_find_encoder(AV_CODEC_ID_AAC);
    if(!enc){fprintf(stderr,"[aac] AAC encoder not found\n");return -1;}
    c->enc_ctx=avcodec_alloc_context3(enc);
    c->enc_ctx->bit_rate   =AAC_BITRATE;
    c->enc_ctx->sample_rate=44100;
    c->enc_ctx->sample_fmt =AV_SAMPLE_FMT_FLTP;
    av_channel_layout_default(&c->enc_ctx->ch_layout,2);
    if(avcodec_open2(c->enc_ctx,enc,NULL)<0) return -1;
    c->enc_pkt=av_packet_alloc();
    c->enc_frame_size=c->enc_ctx->frame_size;

    c->enc_frame=av_frame_alloc();
    c->enc_frame->nb_samples =c->enc_frame_size;
    c->enc_frame->format     =AV_SAMPLE_FMT_FLTP;
    c->enc_frame->sample_rate=44100;
    av_channel_layout_default(&c->enc_frame->ch_layout,2);
    av_frame_get_buffer(c->enc_frame,0);

    c->pcm_cap=c->enc_frame_size*16;
    c->pcm_l=(float*)av_malloc((size_t)c->pcm_cap*sizeof(float));
    c->pcm_r=(float*)av_malloc((size_t)c->pcm_cap*sizeof(float));
    c->pcm_n=0;

    afd_init(&c->afd);
    q_init(&c->aq);
    pthread_create(&c->audio_tid,NULL,audio_thread,c);
    c->audio_started=1;

    printf("[aac] pid=0x%04X frame=%d %dkbps async\n",
           pid,c->enc_frame_size,AAC_BITRATE/1000);
    return 0;
}
static void audio_free(Ch *c)
{
    if(!c->audio_started) return;
    q_done(&c->aq);
    pthread_join(c->audio_tid,NULL);
    c->audio_started=0;

    /* close audio fd */
    pthread_mutex_lock(&c->afd.mu);
    if(c->afd.fd>=0){
        if(c->afd.enc_on) aes_flush(&c->afd.aes,c->afd.fd,1);
        else              plain_flush(c->afd.wb,&c->afd.wb_n,c->afd.fd);
        close(c->afd.fd); c->afd.fd=-1;
    }
    pthread_mutex_unlock(&c->afd.mu);

    avcodec_free_context(&c->dec_ctx);
    avcodec_free_context(&c->enc_ctx);
    av_packet_free(&c->dec_pkt);
    av_packet_free(&c->enc_pkt);
    av_frame_free(&c->dec_frame);
    av_frame_free(&c->enc_frame);
    if(c->swr) swr_free(&c->swr);
    av_free(c->pcm_l); av_free(c->pcm_r);
}

/* -- playlist -- */
static void write_m3u8(Ch *c)
{
    char tmp[512],fin[512];
    snprintf(fin,512,"%s/%s.m3u8",    c->cfg.dir,c->cfg.name);
    snprintf(tmp,512,"%s/%s.m3u8.tmp",c->cfg.dir,c->cfg.name);
    FILE *f=fopen(tmp,"w"); if(!f) return;
    uint64_t total =c->seg_seq-c->cfg.start_seq;
    uint64_t window=(total>(uint64_t)MAX_SEGMENTS)?(uint64_t)MAX_SEGMENTS:total;
    uint64_t seq_old=c->seg_seq-window;
    double max_dur=(double)SEGMENT_SECS;
    for(uint64_t s=seq_old;s<c->seg_seq;s++){
        double d=c->dur[s%DUR_SLOTS]; if(d>max_dur) max_dur=d;
    }
    fprintf(f,"#EXTM3U\n#EXT-X-VERSION:3\n"
              "#EXT-X-TARGETDURATION:%d\n"
              "#EXT-X-MEDIA-SEQUENCE:%llu\n",
              (int)max_dur+1,(unsigned long long)seq_old);
    if(c->cfg.keyuri[0])
        fprintf(f,"#EXT-X-KEY:METHOD=AES-128,URI=\"%s\",IV=0x%s\n",
                c->cfg.keyuri,c->cfg.iv_hex);
    for(uint64_t s=seq_old;s<c->seg_seq;s++){
        double dur=c->dur[s%DUR_SLOTS];
        if(dur<=0.0) dur=(double)SEGMENT_SECS;
        fprintf(f,"#EXTINF:%f,\nindex%llu.ts\n",dur,(unsigned long long)s);
    }
    fflush(f); fclose(f); rename(tmp,fin);
}

/* -- segment open -- */
static void seg_open(Ch *c, const uint8_t *first_pkt,
                     double elapsed, uint64_t cut_pcr)
{
    /* close video side of old segment */
    if(c->seg_fd>=0){
        if(c->aes_ready) aes_flush(&c->aes_vid,c->seg_fd,0);
        else             plain_flush(c->wb,&c->wb_n,c->seg_fd);
        /* don't close yet — audio thread may still write to its dup */
        /* close via afd_set below */
        close(c->seg_fd); c->seg_fd=-1;
        uint64_t closed=c->seg_seq-1;
        c->dur[closed%DUR_SLOTS]=elapsed;
        c->segs++;
        printf("[%s] seg index%llu.ts  %.3fs\n",
               c->cfg.name,(unsigned long long)closed,elapsed);
    }
    /* delete oldest outside window */
    if(c->seg_seq>=c->cfg.start_seq+(uint64_t)MAX_SEGMENTS){
        char old[512];
        snprintf(old,512,"%s/index%llu.ts",c->cfg.dir,
                 (unsigned long long)(c->seg_seq-MAX_SEGMENTS));
        unlink(old);
    }
    /* open new segment */
    char path[512];
    snprintf(path,512,"%s/index%llu.ts",c->cfg.dir,
             (unsigned long long)c->seg_seq);
    c->seg_fd=open(path,O_WRONLY|O_CREAT|O_TRUNC,0644);
    if(c->seg_fd<0){perror(path);return;}

    if(c->aes_ready) aes_reset(&c->aes_vid);
    c->wb_n=0;
    c->seg_seq++;
    if(c->pcr_ready) c->pcr_seg_start=cut_pcr;
    c->wall_seeded=0;

    /* write PAT + patched PMT to video fd */
    if(c->pat_ok) vid_write(c,c->pat);
    if(c->pmt_ok){
        uint8_t pm[TS_SZ]; memcpy(pm,c->pmt,TS_SZ);
        if(c->audio_started) pmt_patch_aac(pm,c->aud_pid);
        vid_write(c,pm);
    }
    if(first_pkt) vid_write(c,first_pkt);

    /* hand audio thread a dup of the new fd with its own AES state */
    if(c->audio_started){
        int adup=dup(c->seg_fd);
        afd_set(&c->afd, adup, c->aes_ready,
                c->aes_ready?c->key16:NULL,
                c->aes_ready?c->iv16:NULL);
    }

    write_m3u8(c);
}

/* -- NIC -- */
static void auto_iface(char *out,size_t len)
{
    struct ifaddrs *l=NULL; getifaddrs(&l);
    for(struct ifaddrs *i=l;i;i=i->ifa_next){
        if(!i->ifa_addr||i->ifa_addr->sa_family!=AF_INET) continue;
        struct sockaddr_in *sa=(struct sockaddr_in*)i->ifa_addr;
        if((ntohl(sa->sin_addr.s_addr)>>24)==127) continue;
        inet_ntop(AF_INET,&sa->sin_addr,out,(socklen_t)len);
        printf("[iface] auto: %s (%s)\n",i->ifa_name,out);
        break;
    }
    if(l) freeifaddrs(l);
}

/* -- socket -- */
static int sock_open(Ch *c)
{
    uint32_t grp=inet_addr(c->cfg.mcast);
    int is_mc=((ntohl(grp)>>28)==0xE);
    char iface[64]={0};
    if(c->cfg.iface[0]) strncpy(iface,c->cfg.iface,63);
    else if(is_mc) auto_iface(iface,sizeof(iface));
    int fd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
    if(fd<0){perror("socket");return -1;}
    int one=1,zero=0;
    setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&one,sizeof(one));
    setsockopt(fd,SOL_SOCKET,SO_REUSEPORT,&one,sizeof(one));
    int rb=RCVBUF; setsockopt(fd,SOL_SOCKET,SO_RCVBUF,&rb,sizeof(rb));
    struct timeval tv={2,0};
    setsockopt(fd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv));
    struct sockaddr_in sa={0};
    sa.sin_family=AF_INET; sa.sin_port=htons((uint16_t)c->cfg.port);
    if(is_mc){
        sa.sin_addr.s_addr=grp;
        if(bind(fd,(struct sockaddr*)&sa,sizeof(sa))<0){
            sa.sin_addr.s_addr=htonl(INADDR_ANY);
            bind(fd,(struct sockaddr*)&sa,sizeof(sa));
        }
        printf("[%s] bound %s:%d\n",c->cfg.name,c->cfg.mcast,c->cfg.port);
        struct ip_mreq mr={0};
        mr.imr_multiaddr.s_addr=grp;
        mr.imr_interface.s_addr=iface[0]?inet_addr(iface):htonl(INADDR_ANY);
        if(setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mr,sizeof(mr))<0){
            perror("IP_ADD_MEMBERSHIP");close(fd);return -1;}
        setsockopt(fd,IPPROTO_IP,IP_MULTICAST_ALL,&zero,sizeof(zero));
        printf("[%s] joined %s on %s\n",c->cfg.name,c->cfg.mcast,
               iface[0]?iface:"INADDR_ANY");
    } else {
        sa.sin_addr.s_addr=htonl(INADDR_ANY);
        bind(fd,(struct sockaddr*)&sa,sizeof(sa));
    }
    return fd;
}
static void mkdirp(const char *p)
{
    char t[512]; snprintf(t,512,"%s",p);
    for(char *s=t+1;*s;s++) if(*s=='/'){*s=0;mkdir(t,0755);*s='/';}
    mkdir(t,0755);
}
static int parse_iv(const char *hex,uint8_t *out16)
{
    if(strlen(hex)!=32) return 0;
    for(int i=0;i<16;i++){
        unsigned v=0;
        if(sscanf(hex+i*2,"%02x",&v)!=1) return 0;
        out16[i]=(uint8_t)v;
    }
    return 1;
}

/* -- packet processor (UDP thread — zero blocking) -- */
static void pkt_process(Ch *c, const uint8_t *p, const struct timespec *now)
{
    if(p[0]!=0x47) return;
    uint16_t pid=ts_pid(p); c->pkts++;
    if(pid==0x1FFF) return;

    if(pid==0x0000){
        if(!c->pat_ok){
            uint16_t pp=pat_parse(p);
            if(pp){c->pmt_pid=pp;c->pat_ok=1;
                   printf("[%s] PAT pmt=0x%04X\n",c->cfg.name,pp);}
        }
        if(c->pat_ok) memcpy(c->pat,p,TS_SZ);
    }
    if(c->pat_ok&&pid==c->pmt_pid){
        uint16_t vp=0,ap=0,cp=0;
        if(pmt_parse(p,&cp,&vp,&ap)&&cp){
            if(!c->pmt_ok||cp!=c->pcr_pid||vp!=c->vid_pid||ap!=c->aud_pid){
                c->pcr_pid=cp;c->vid_pid=vp;c->aud_pid=ap;c->pmt_ok=1;
                printf("[%s] PMT pcr=0x%04X vid=0x%04X aud=0x%04X\n",
                       c->cfg.name,cp,vp,ap);
                if(!c->audio_started&&ap) audio_init(c,ap);
            }
        }
        memcpy(c->pmt,p,TS_SZ);
    }
    if(c->pmt_ok&&pid==c->pcr_pid){
        uint64_t pcr=0;
        if(pcr_get(p,&pcr)){
            if(c->pcr_ready){
                uint64_t fwd=(pcr>=c->pcr_last)?(pcr-c->pcr_last)
                            :(PCR_WRAP-c->pcr_last+pcr);
                if(fwd>PCR_DISC&&fwd<PCR_WRAP/2){
                    printf("[%s] PCR disc %.1fs\n",c->cfg.name,(double)fwd/PCR_HZ);
                    c->pcr_seg_start=pcr;
                }
            }
            if(!c->pcr_ready){c->pcr_seg_start=pcr;c->pcr_ready=1;
                printf("[%s] PCR seeded\n",c->cfg.name);}
            c->pcr_last=pcr;
        }
    }

    /* CUT: PUSI on video + elapsed >= SEGMENT_SECS */
    if(c->pmt_ok&&c->vid_pid&&pid==c->vid_pid&&ts_pusi(p)){
        double elapsed=0.0; int ready=0;
        if(c->pcr_ready){elapsed=pcr_el(c->pcr_seg_start,c->pcr_last);ready=1;}
        else if(c->wall_seeded){elapsed=wall_el(&c->wall_seg_start,now);ready=1;}
        if(ready&&elapsed>=(double)SEGMENT_SECS){
            seg_open(c,p,elapsed,c->pcr_last); return;
        }
        if(!c->pcr_ready&&!c->wall_seeded){c->wall_seg_start=*now;c->wall_seeded=1;}
    }

    /* audio PES reassembly */
    if(c->pmt_ok&&c->aud_pid&&pid==c->aud_pid){
        int o=ts_poff(p);
        if(ts_pusi(p)){
            if(c->pes_started&&c->pes_len>0){
                q_push(&c->aq,c->pes_buf,c->pes_len);
                c->pes_len=0;
            }
            /* fixed PES skip: 9 bytes fixed + p[o+8] optional */
            if(o>=0&&o+8<TS_SZ){
                int start=o+9+(int)p[o+8];
                if(start<TS_SZ){
                    int bytes=TS_SZ-start;
                    if(bytes>0&&c->pes_len+bytes<AUDIO_PES_BUF){
                        memcpy(c->pes_buf+c->pes_len,p+start,(size_t)bytes);
                        c->pes_len+=bytes;
                    }
                }
            }
            c->pes_started=1;
        } else if(c->pes_started&&o>=0){
            int bytes=TS_SZ-o;
            if(bytes>0&&c->pes_len+bytes<AUDIO_PES_BUF){
                memcpy(c->pes_buf+c->pes_len,p+o,(size_t)bytes);
                c->pes_len+=bytes;
            }
        }
        return; /* suppress original MP3 */
    }

    /* video and other PIDs — write directly, no lock */
    vid_write(c,p);
}

/* -- channel thread -- */
static void *ch_run(void *arg)
{
    Ch *c=(Ch*)arg;
    printf("\n[%s] udp://%s:%d -> %s/%s.m3u8\n",
           c->cfg.name,c->cfg.mcast,c->cfg.port,c->cfg.dir,c->cfg.name);
    mkdirp(c->cfg.dir);
    memset(c->dur,0,sizeof(c->dur));
    c->seg_fd=-1;c->rtp_ok=0;c->rtp_off=0;
    c->pcr_ready=0;c->wall_seeded=0;
    c->seg_seq=c->cfg.start_seq;c->wb_n=0;c->aes_ready=0;
    c->pes_len=0;c->pes_started=0;
    c->audio_started=0;

    /* load AES key */
    if(c->cfg.keyfile[0]){
        FILE *kf=fopen(c->cfg.keyfile,"rb");
        if(kf){
            size_t n=fread(c->key16,1,16,kf); fclose(kf);
            if(n==16&&(!c->cfg.iv_hex[0]||parse_iv(c->cfg.iv_hex,c->iv16))){
                aes_init(&c->aes_vid,c->key16,c->iv16);
                c->aes_ready=1;
                printf("[%s] AES-128-CBC ready\n",c->cfg.name);
            }
        }
    }

    c->fd=sock_open(c);
    if(c->fd<0){fprintf(stderr,"[%s] socket failed\n",c->cfg.name);return NULL;}

    /* open first segment */
    char path[512];
    snprintf(path,512,"%s/index%llu.ts",c->cfg.dir,(unsigned long long)c->seg_seq);
    c->seg_fd=open(path,O_WRONLY|O_CREAT|O_TRUNC,0644);
    if(c->seg_fd>=0){
        if(c->aes_ready) aes_reset(&c->aes_vid);
        c->seg_seq++;
    }
    write_m3u8(c);

    static __thread uint8_t buf[UDP_MAX];
    struct timespec stall; clock_gettime(CLOCK_MONOTONIC,&stall);
    int warned=0;
    printf("[%s] receiving...\n",c->cfg.name); fflush(stdout);

    while(!g_stop){
        ssize_t n=recv(c->fd,buf,sizeof(buf),0);
        if(n<=0){
            if(errno==EAGAIN||errno==EWOULDBLOCK){
                c->timeouts++;
                struct timespec now; clock_gettime(CLOCK_MONOTONIC,&now);
                if(!warned&&wall_el(&stall,&now)>=5.0){
                    warned=1;fprintf(stderr,"[%s] no data 5s\n",c->cfg.name);}
                continue;
            }
            if(errno==EINTR) continue;
            if(g_stop) break;
            perror("recv"); break;
        }
        clock_gettime(CLOCK_MONOTONIC,&stall); warned=0;
        c->dgrams++;
        if(c->dgrams==1) printf("[%s] first: len=%zd [0]=0x%02X\n",
                                c->cfg.name,n,buf[0]);
        if(!c->rtp_ok){
            c->rtp_ok=1;
            if(buf[0]!=0x47){
                int o=rtp_detect(buf,(int)n);
                if(o>0){c->rtp_off=o;printf("[%s] RTP +%d\n",c->cfg.name,o);}
                else fprintf(stderr,"[%s] WARN 0x%02X\n",c->cfg.name,buf[0]);
            } else printf("[%s] plain TS\n",c->cfg.name);
        }
        struct timespec now; clock_gettime(CLOCK_MONOTONIC,&now);
        const uint8_t *ts=buf+c->rtp_off;
        int np=(int)(n-c->rtp_off)/TS_SZ;
        for(int i=0;i<np;i++,ts+=TS_SZ)
            if(ts[0]==0x47) pkt_process(c,ts,&now);
    }

    /* flush last audio PES */
    if(c->pes_started&&c->pes_len>0)
        q_push(&c->aq,c->pes_buf,c->pes_len);

    audio_free(c);

    /* close final video segment */
    if(c->seg_fd>=0){
        double dur=c->pcr_ready?pcr_el(c->pcr_seg_start,c->pcr_last):0.0;
        if(c->aes_ready) aes_flush(&c->aes_vid,c->seg_fd,1);
        else             plain_flush(c->wb,&c->wb_n,c->seg_fd);
        close(c->seg_fd); c->seg_fd=-1;
        printf("[%s] final index%llu.ts %.3fs\n",
               c->cfg.name,(unsigned long long)(c->seg_seq-1),dur);
    }
    close(c->fd);
    printf("[%s] done segs=%llu pkts=%llu\n",
           c->cfg.name,(unsigned long long)c->segs,
           (unsigned long long)c->pkts);
    return NULL;
}

static int conf_load(const char *path)
{
    FILE *f=fopen(path,"r"); if(!f){perror(path);return 0;}
    int n=0; char line[1024];
    while(fgets(line,sizeof(line),f)&&n<MAX_CHANNELS){
        char *nl=strchr(line,'\n');if(nl)*nl=0;
        char *hsh=strchr(line,'#');if(hsh)*hsh=0;
        int l=(int)strlen(line);
        while(l>0&&(line[l-1]==' '||line[l-1]=='\t'))line[--l]=0;
        if(!line[0]) continue;
        ChCfg *cfg=&g_ch[n].cfg; memset(cfg,0,sizeof(*cfg));
        unsigned long long seq=0;
        int r=sscanf(line,"%63s %d %255s %63s %63s %255s %511s %32s %llu",
                     cfg->mcast,&cfg->port,cfg->dir,cfg->name,cfg->iface,
                     cfg->keyfile,cfg->keyuri,cfg->iv_hex,&seq);
        if(r>=4){cfg->start_seq=(uint64_t)seq;n++;}
        else fprintf(stderr,"[conf] bad: %s\n",line);
    }
    fclose(f); return n;
}
static void *stats_run(void *a)
{
    (void)a;
    while(!g_stop){
        sleep(30); if(g_stop) break;
        printf("\n--- stats ----------------------------------------\n");
        for(int i=0;i<g_nch;i++){
            Ch *c=&g_ch[i];
            printf("  [%-10s] segs=%-5llu pkts=%-9llu timeouts=%llu\n",
                   c->cfg.name,(unsigned long long)c->segs,
                   (unsigned long long)c->pkts,(unsigned long long)c->timeouts);
        }
        printf("--------------------------------------------------\n\n");
    }
    return NULL;
}
int main(int argc,char *argv[])
{
    signal(SIGINT,on_sig); signal(SIGTERM,on_sig); signal(SIGPIPE,SIG_IGN);
    memset(g_ch,0,sizeof(g_ch));
    if(argc==2){
        g_nch=conf_load(argv[1]);
        if(!g_nch){fprintf(stderr,"no channels\n");return 1;}
    } else if(argc>=5){
        ChCfg *cfg=&g_ch[0].cfg; memset(cfg,0,sizeof(*cfg));
        strncpy(cfg->mcast,  argv[1],63); cfg->port=atoi(argv[2]);
        strncpy(cfg->dir,    argv[3],255); strncpy(cfg->name,argv[4],63);
        if(argc>5) strncpy(cfg->iface,  argv[5],63);
        if(argc>6) strncpy(cfg->keyfile,argv[6],255);
        if(argc>7) strncpy(cfg->keyuri, argv[7],511);
        if(argc>8) strncpy(cfg->iv_hex, argv[8],32);
        if(argc>9) cfg->start_seq=(uint64_t)atoll(argv[9]);
        g_nch=1;
    } else {
        fprintf(stderr,
            "UDP->HLS v14.3 (AES-128 + MP3->AAC lockfree)\n\n"
            "Usage:\n"
            "  %s channels.conf\n"
            "  %s <mcast> <port> <dir> <name> [iface] [keyfile] [keyuri] [iv] [seq]\n",
            argv[0],argv[0]);
        return 1;
    }
    printf("\n======================================================\n");
    printf("  UDP->HLS v14.3  AES-128  MP3->AAC  seg=%ds\n",SEGMENT_SECS);
    printf("  Video: no-lock direct write  Audio: async dup-fd\n");
    printf("======================================================\n");
    pthread_t tids[MAX_CHANNELS],stid;
    pthread_create(&stid,NULL,stats_run,NULL);
    for(int i=0;i<g_nch;i++)
        pthread_create(&tids[i],NULL,ch_run,&g_ch[i]);
    for(int i=0;i<g_nch;i++)
        pthread_join(tids[i],NULL);
    g_stop=1; pthread_join(stid,NULL);
    return 0;
}