// sb-send: Sonobus audio sender.
// enough ogg to contain our raw opus streams.
// used by sb-rec.c and opus2ogg.c.

#include "sb-send.h"

static void write_le32(unsigned char *p, uint32_t v) {
    p[0] = v;
    p[1] = v >> 8;
    p[2] = v >> 16;
    p[3] = v >> 24;
}

static void ogg_write_page(ogg_page *page, FILE *out) {
    fwrite(page->header, 1, page->header_len, out);
    fwrite(page->body, 1, page->body_len, out);
    fflush(out);
}

static void ogg_flush_pages(ogg_stream_state *os, FILE *out) {
    ogg_page page;
    while (ogg_stream_pageout_fill(os, &page, 65024) == 1) {
        ogg_write_page(&page, out);
    }
}

static void ogg_force_pages(ogg_stream_state *os, FILE *out) {
    ogg_page page;
    while (ogg_stream_flush(os, &page) == 1) {
        ogg_write_page(&page, out);
    }
}

static void write_opus_head(ogg_stream_state *os, int channels, FILE *out) {
    unsigned char head[21 + 255];
    memset(head, 0, sizeof(head));
    memcpy(head, "OpusHead", 8);
    head[8] = 1;
    head[9] = channels;
    head[10] = OGG_PRESKIP & 0xFF;
    head[11] = (OGG_PRESKIP >> 8) & 0xFF;
    write_le32(head + 12, OGG_SAMPLE_RATE);
    head[16] = 0;
    head[17] = 0;

    int pkt_len;
    if (channels == 1) {
        head[18] = 0;
        pkt_len = 19;
    } else {
        head[18] = 1;
        head[19] = channels;
        head[20] = 0;
        for (int i = 0; i < channels; i++) {
            head[21 + i] = i;
        }
        pkt_len = 21 + channels;
    }

    ogg_packet pkt;
    memset(&pkt, 0, sizeof(pkt));
    pkt.packet = head;
    pkt.bytes = pkt_len;
    pkt.b_o_s = 1;
    pkt.e_o_s = 0;
    pkt.granulepos = 0;
    pkt.packetno = 0;
    ogg_stream_packetin(os, &pkt);
    ogg_force_pages(os, out);
}

static void write_opus_tags(ogg_stream_state *os, const char *vendor, FILE *out) {
    int vlen = strlen(vendor);
    int tags_size = 8 + 4 + vlen + 4;

    unsigned char tags[64];
    memset(tags, 0, sizeof(tags));
    memcpy(tags, "OpusTags", 8);
    write_le32(tags + 8, vlen);
    memcpy(tags + 12, vendor, vlen);
    write_le32(tags + 12 + vlen, 0);

    ogg_packet pkt;
    memset(&pkt, 0, sizeof(pkt));
    pkt.packet = tags;
    pkt.bytes = tags_size;
    pkt.b_o_s = 0;
    pkt.e_o_s = 0;
    pkt.granulepos = 0;
    pkt.packetno = 1;
    ogg_stream_packetin(os, &pkt);
    ogg_force_pages(os, out);
}

void ogg_writer_init(OggWriter *w, int channels, const char *vendor, FILE *out) {
    memset(w, 0, sizeof(*w));
    if (channels < 1) {
        channels = 1;
    }
    if (channels > 2) {
        channels = 2;
    }
    srand((unsigned)time(NULL));
    ogg_stream_init(&w->os, (uint32_t)rand());
    w->channels = channels;
    w->granule = OGG_PRESKIP;
    w->packetno = 0;

    write_opus_head(&w->os, channels, out);
    write_opus_tags(&w->os, vendor, out);
    w->packetno = 2;
    w->started = 1;
}

void ogg_writer_packet(OggWriter *w, const unsigned char *data, int len, FILE *out) {
    if (len > 0) {
        int samples = opus_packet_get_nb_samples(data, len, OGG_SAMPLE_RATE);
        if (samples > 0) {
            w->granule += samples;
        }
    }

    if (len == 0) {
        return;
    }

    if (w->have_last) {
        ogg_packet opkt;
        memset(&opkt, 0, sizeof(opkt));
        opkt.packet = w->last_pkt;
        opkt.bytes = w->last_len;
        opkt.b_o_s = 0;
        opkt.e_o_s = 0;
        opkt.granulepos = w->last_granule;
        opkt.packetno = w->last_pktno;
        ogg_stream_packetin(&w->os, &opkt);
        ogg_flush_pages(&w->os, out);
    }

    if (len <= (int)sizeof(w->last_pkt)) {
        memcpy(w->last_pkt, data, len);
        w->last_len = len;
        w->last_granule = w->granule;
        w->last_pktno = w->packetno;
        w->have_last = 1;
    }
    w->packetno++;
}

void ogg_writer_finish(OggWriter *w, FILE *out) {
    if (!w->started) {
        return;
    }

    if (w->have_last) {
        ogg_packet opkt;
        memset(&opkt, 0, sizeof(opkt));
        opkt.packet = w->last_pkt;
        opkt.bytes = w->last_len;
        opkt.b_o_s = 0;
        opkt.e_o_s = 1;
        opkt.granulepos = w->last_granule;
        opkt.packetno = w->last_pktno;
        ogg_stream_packetin(&w->os, &opkt);
        ogg_flush_pages(&w->os, out);
    }

    ogg_force_pages(&w->os, out);
    ogg_stream_clear(&w->os);
}
// EOF
