/*
 * Reveive UDP packets (possible joining multicast group) and write them to matroska file (to stdout)
 * License=MIT; Vitaly "_Vi" Shukela; 2012.
 *
 * i586-mingw32msvc-gcc  udp2mkv.c -O2 -lws2_32 -o udp2mkv.exe
 * gcc -O2 udp2mkv.c -o udp2mkv
 */


#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#include <string.h>

#ifndef WIN32
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <endian.h>
#else
    #include <winsock2.h>

    // Remove these things if they conflict with already defined ones
    #if __BYTE_ORDER == __LITTLE_ENDIAN
        // http://www.linux.org.ru/forum/development/4087771
        uint16_t htobe16(uint16_t x) {
            union { uint16_t u16; uint8_t v[2]; } ret;
            ret.v[0] = (uint8_t)(x >> 8);
            ret.v[1] = (uint8_t) x;
            return ret.u16;
        }

        uint32_t htobe32(uint32_t x) {
            union { uint32_t u32; uint8_t v[4]; } ret;
            ret.v[0] = (uint8_t)(x >> 24);
            ret.v[1] = (uint8_t)(x >> 16);
            ret.v[2] = (uint8_t)(x >> 8);
            ret.v[3] = (uint8_t) x;
            return ret.u32;
        }

        uint64_t htobe64(uint64_t x) {
            union { uint64_t u64; uint8_t v[8]; } ret;
            ret.v[0] = (uint8_t)(x >> 56);
            ret.v[1] = (uint8_t)(x >> 48);
            ret.v[2] = (uint8_t)(x >> 40);
            ret.v[3] = (uint8_t)(x >> 32);
            ret.v[4] = (uint8_t)(x >> 24);
            ret.v[5] = (uint8_t)(x >> 16);
            ret.v[6] = (uint8_t)(x >> 8);
            ret.v[7] = (uint8_t) x;
            return ret.u64;
        }
    #else
        #define htobe16(x) (x)
        #define htobe32(x) (x)
        #define htobe64(x) (x)
    #endif

    // Remove these things if they conflict with already defined ones
    #define IP_ADD_MEMBERSHIP 12
    struct ip_mreq  {
        struct in_addr imr_multiaddr;   /* IP multicast address of group */
        struct in_addr imr_interface;   /* local IP address of interface */
    };

#endif


char buf[240*1024];

int udp_listen(const char* host, int port) {
    int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (udp_socket == -1) {
        perror("socket");
        return -1;
    }
    
    struct sockaddr_in sa = {AF_INET, htons(port), {inet_addr(host)}};

    if(bind(udp_socket, (struct sockaddr*)&sa, sizeof sa)==-1) {
        perror("bind");
        close(udp_socket);
        return -1;
    }
    
    int rxsockbufsz = 240 * 1024;
    if (setsockopt (udp_socket, SOL_SOCKET, SO_RCVBUF,
                &rxsockbufsz, sizeof (rxsockbufsz))) {
        perror("setsockopt SO_RCVBUF");
    }

    if ((ntohl (sa.sin_addr.s_addr) >> 28) == 0xe) {
        struct ip_mreq mcast = {{sa.sin_addr.s_addr}, {0}};
        if (setsockopt (udp_socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mcast, sizeof (mcast))) {
            perror("setsockopt IP_ADD_MEMBERSHIP");
        }
    }

    return udp_socket;

}

void write_matroska_header(FILE* f, unsigned char track_type, const char* codec_id) {
    fwrite("\x1a\x45\xdf\xa3\xa3\x42\x86\x81\x01\x42\xf7\x81\x01"
           "\x42\xf2\x81\x04\x42\xf3\x81\x08\x42\x82\x88matroska"
           "\x42\x87\x81\x02\x42\x85\x81\x02" // EBML header
           "\x18\x53\x80\x67\xff",  // Start of infinite segment
           0x2d, 1, f);
    fwrite("\x15\x49\xA9\x66" // Info
             "\x90" // info size
             "\x57\x41\x87udp2mkv" // writing application
             "\x2A\xD7\xB1\x82\x03\xE8" // timecode scale = 1000 nanoseconds
             , 21, 1, stdout);

    size_t codec_id_len = strlen(codec_id);
    unsigned long tracks_size   = codec_id_len + 22;
    unsigned long track_size   = codec_id_len + 16;
    unsigned long codecID_size = codec_id_len;

    tracks_size  = htobe32(tracks_size );
    track_size   = htobe32(track_size  );
    codecID_size = htobe32(codecID_size);

    fwrite("\x16\x54\xAE\x6B\x08",5,1,f);  // Tracks header
        fwrite(&tracks_size, 4, 1, f);
        fwrite("\xAE\x08", 2, 1, f); // TrackEntry header
            fwrite(&track_size, 4, 1, f);
            fwrite("\xD7\x81\x01",     3, 1, f);     // TrackNumber
            fwrite("\x73\xC5\x81\x01", 4, 1, f); // TrackUID
            fwrite("\x83\x81\x02",     3, 1, f); // Track type
            fwrite("\x86\x08",         2, 1, f); // CodecID header
                fwrite(&codecID_size,  4, 1, f);
                fwrite(codec_id, codec_id_len, 1, f);
    fflush(f);
}

// timecode is in microseconds
void write_matroska_frame(FILE* f, unsigned long long int timecode, const char* buffer, size_t length) {
    fwrite("\x1F\x43\xB6\x75\x08", 5, 1, f); // Cluster with part of size
    long int cluster_size = length + 20;
    cluster_size = htobe32(cluster_size);
    fwrite(&cluster_size, 4, 1, f);

    fwrite("\xE7\x88", 2, 1, f); // Timecode 
    timecode = htobe64(timecode);
    fwrite(&timecode, 8, 1, f);

    fwrite("\xA3\x08", 2, 1, f); // Simpleblock with part of size
    long int simpleblock_size = length + 4;
    simpleblock_size = htobe32(simpleblock_size);
    fwrite(&simpleblock_size, 4, 1, f);
    fwrite("\x81\x00\x00\x00", 4, 1, f);
    fwrite(buffer, length, 1, f);
    fflush(f);
}

int main(int argc, char* argv[]) {

    if(argc<3) {
        fprintf(stderr, 
                "udp2mkv v0.1 by Vitaly \"_Vi\" Shukela; License=MIT\n"
                "\n"
                "Usage: udp2mkv host port > file.mkv\n"
                "    Receive UDP packets on host:port\n"
                "    and store them to matroska file\n"
                "    (CodecID=A_MPEG/L3)\n"
                "    The file can be re-streamed using mkv2udp or HsMkv's ExampleUdpSend\n");
        return 2;
    }
    #ifdef WIN32
        WSADATA wsaData;
        int iResult = WSAStartup( MAKEWORD(2,2), &wsaData );
        if ( iResult != NO_ERROR ) {
            fprintf(stderr,"Error at WSAStartup()\n");
            exit(1);
        }
        _setmode(_fileno(stdout), _O_BINARY);
    #endif


    int udp_socket = udp_listen(argv[1], atoi(argv[2]));
    if (udp_socket == -1) {
        return 1;
    }

    const char *codecId = "A_MPEG/L3";
    unsigned char type=0x02;

    if(getenv("CODECID")) codecId = getenv("CODECID");
    if(getenv("TRACKTYPE")) type = atoi(getenv("TRACKTYPE"));

    write_matroska_header(stdout, type, codecId);


    for(;;) {
        struct timeval tv;
        int s = recv(udp_socket, buf, sizeof buf, 0);
        if(s==-1) {
            break;
        }
        gettimeofday(&tv, NULL);
        unsigned long long int timecode = tv.tv_usec + tv.tv_sec*1000000LL;

        write_matroska_frame(stdout, timecode, buf, s);
        
    }
    
    return 0;
}


