/*---------------------------------------------------------------------------*/
/* video_channel.cpp                                                         */
/* copyright (c) innovaphone 2017                                            */
/*---------------------------------------------------------------------------*/

#include <platform.h>
#include "sdk/common/os/iomux.h"

#include "sdk/common/interface/time.h"
#include "sdk/common/interface/socket.h"
#include "sdk/common/interface/guuid.h"
#include "sdk/common/interface/media.h"

#include "video_endpoint.h"

#define FILE_DURATION     0
#define FILE_UTC_DURATION 1
#define BLOCK_DURATION 1
#define BLOCK_DURATION2 1
#define TRACK_TIMESCALE 1
#define RECORD_VIDEO 0

VideoEndpoint::VideoEndpoint(class IIoMux * const iomux, VideoCoder coder, class UMediaEndpoint * channel, class IInstanceLog * const log) :
    coder(coder),
    channel(channel),
    log(log)
{
    maxRtpVideoData = DEFAULT_MTU-ETH_HEADER_LEN-IP_HEADER_LEN-UDP_HEADER_LEN-RTP_HEADER_LEN-RTP_H264_HEADER_LEN-TURN_HEADER_LEN-TURN_PADDING_LEN-SRTP_PADDING_LEN-SRTP_AUTH_LEN;
    lTimestamp = 0;
    videoFrame = (char *)malloc(640*480*2);
    dropFrame = false;
    rtpMarker = false;
    vp8PicId = 0;
    av1PicId = 0;
    firstObu = true;
    firCommandSequence = 0;
    sps = pps = NULL;
    indexStart = 512;
    indexSeq = indexStart;
    indexHigher = indexStart;
    packetsRx = 0;

    oooTimer = new ITimer(iomux, this);
    memset(sequenceRx, 0x0, 2048);

    this->fullIntraFrameTimer = new ITimer(iomux, this);
    this->fullIntraFrameTimer->Start(5000);

    this->videoChunkTimer = new ITimer(iomux, this);
    videoChunkListSize = 0;

    firstTimestamp = 0;
    totalSize = 0;
    totalNumPacket = 0;
    totalNumLost = 0;
    totalNumOoO = 0;
    totalNumPacketLost = 0;
    totalNumPacketOoO = 0;
    gotKeyFrame = 0;
    isKeyFrame = false;
    keyFrameLen = 0;
    maxAppSharingData = 6000;
}

VideoEndpoint::~VideoEndpoint()
{
    if(totalSize) {
        int diff = (int)(lastTimestamp - firstTimestamp);
        debug->printf("VideoEndpoint(%p)::VideoEndpoint stats seq=%d ooo(%d)=%d lost(%d)=%d bitrate(%d,%d)=%d kbps", this, totalNumPacket, totalNumPacketOoO, totalNumOoO, totalNumPacketLost, totalNumLost, totalSize, diff, diff ? ((totalSize * 8) / diff) : 0);
    }
    if(videoFrame) free(videoFrame);
    if(sps) free(sps);
    if(pps) free(pps);

    while (!videoChunkList.empty()) delete videoChunkList.front();

    delete oooTimer;
    delete videoChunkTimer;
    delete fullIntraFrameTimer;
}

void VideoEndpoint::TimerOnTimeout(ITimer * timer)
{
    if (timer == videoChunkTimer) {
        //debug->printf("VideoEndpoint(%p)::TimerOnTimeout size=%d maxData=%d", this, videoChunkListSize, maxAppSharingData);

        int dataSent = 0;
        // 30 msec, 6000 Bytes -> we allowed 1.6 Mbps, 
        // Enough to avoid delaying video streams, Video -> 500 kbps, VideoAppSharing -> 1 Mbps
        while (!videoChunkList.empty() && dataSent < maxAppSharingData) {
            class VideoChunk* chunk = videoChunkList.pop_front();

            channel->RtpSend(chunk->buf, chunk->len, chunk->timestamp, 0, chunk->marker);

            dataSent += chunk->len;

            delete chunk;
            videoChunkListSize--;
        }
        videoChunkTimer->Start(30);
        return;
    }
    else if (timer == oooTimer) {
        if (!packetsRx) {
            oooTimer->Start(3000);
            return;
        }
        int i;
        for (i = indexStart + 1; i <= indexHigher; i++) {
            if (!sequenceRx[i]) break;
        }
        //debug->printf("VideoEndpoint(%p)::TimerOnTimeout oooTimer indexStart=%d -> %d break=%d packetsRx=%d", this, indexStart+1, indexHigher, i, packetsRx);
        // No lost, no ooo or packet lost
        if ((i-1) == indexHigher || i == (indexStart+1)) {
            if(i == (indexStart+1)) SendFullIntraRequest();
            packetsRx = 0;
            indexHigher = indexStart;
            indexSeq = indexStart;
            memset(sequenceRx, 0, 2048);
            oooTimer->Start(3000);
        }
        else {
            // Some packet could be lost after 3 seconds
            int slotTimer = 3000 / packetsRx;
            int numPacketsOk = i - (indexStart + 1);
            int movePackets = indexHigher - i + 1;
            //debug->printf("VideoEndpoint(%p)::TimerOnTimeout oooTimer slotTimer=%d numPacketsOk=%d indexSeq=%d indexHigher=%d movePackets=%d", this, slotTimer, numPacketsOk, indexSeq, indexHigher, movePackets);
            memmove(&sequenceRx[indexStart + 1], &sequenceRx[i], movePackets);
            //debug->printf("VideoEndpoint(%p)::TimerOnTimeout memmove indexStart+1=%d, i=%d, movePackets=%d", this, indexStart + 1, i, movePackets);
            memset(&sequenceRx[indexStart + 1 + movePackets], 0x0, 2048 - (indexStart + 1 + movePackets));
            //debug->printf("VideoEndpoint(%p)::TimerOnTimeout memset indexStart+1=%d, len=%d", this, indexStart + 1 + movePackets, 2048 - (indexStart + 1 + movePackets));
            packetsRx -= numPacketsOk;
            indexHigher -= numPacketsOk;
            indexSeq -= numPacketsOk;
            oooTimer->Start(slotTimer * numPacketsOk);
            //debug->printf("VideoEndpoint(%p)::TimerOnTimeout oooTimer slotTimer=%d numPacketsOk=%d indexSeq=%d indexHigher=%d", this, slotTimer, numPacketsOk, indexSeq, indexHigher);
        }
        return;
    }
    // Two VideoEndpoint instances are sendonly
    // First KeyFrame is received but somehow ignored by local WebRTC
    // Let's wait for at least three keyFrames to make sure that Video is played
    if (totalSize && gotKeyFrame < 4) {
        debug->printf("VideoEndpoint(%p)::TimerOnTimeout received gotKeyFrame=%d", this, gotKeyFrame);
        SendFullIntraRequest();
        fullIntraFrameTimer->Start(5000);
        // Reset OoO RingBuffer
        packetsRx = 0;
        indexHigher = indexStart;
        indexSeq = indexStart;
        memset(sequenceRx, 0, 2048);
        oooTimer->Cancel();
        oooTimer->Start(3000);
    }
}

// RTP is still packed into video frames to send a FullIntraRequest in case packets got lost
void VideoEndpoint::Recv(char * buf, int len, dword timestamp, short sequenceNumberDiff, bool marker)
{
    if (oooTimer->IsRunning() == false) oooTimer->Start(3000);

    packetsRx++;

    //debug->printf("VideoEndpoint(%p)::Recv indexSeq=%d destIndexSeq=%d sequenceNumberDiff=%d indexHigher=%d packetsRx=%d", this, indexSeq, indexSeq + sequenceNumberDiff + 1, sequenceNumberDiff, indexHigher, packetsRx);

    if (indexSeq + sequenceNumberDiff + 1 < 2047 && indexSeq + sequenceNumberDiff + 1 > 0) {
        indexSeq += sequenceNumberDiff + 1;        
        sequenceRx[indexSeq] = 1;
        if (indexSeq > indexHigher) indexHigher = indexSeq;
    }
    if(coder == VideoCoder::VP8) return RecvVp8(buf, len, timestamp, sequenceNumberDiff, marker);
    else if(coder == VideoCoder::H264) return RecvH264(buf, len, timestamp, sequenceNumberDiff, marker);
    else if(coder == VideoCoder::AV1) return RecvAV1(buf, len, timestamp, sequenceNumberDiff, marker);
    else if(coder == VideoCoder::VP9) return RecvVp8(buf, len, timestamp, sequenceNumberDiff, marker);
}

void VideoEndpoint::RecvH264(char * buf, int len, dword timestamp, short sequenceNumberDiff, bool marker)
{
    int nalUnitType = (buf[0] & 0x1F);

    log->Log(LOG_VIDEO, "VideoChannel(%p)::Recv h264 len=%d timestamp=%x!=%x sequenceNumberDiff=%d markers l=%d,a=%d dropFrame=%d nalUnitType=%d", this, len, timestamp, lTimestamp, sequenceNumberDiff, rtpMarker, marker, dropFrame, nalUnitType);

    if(lTimestamp != timestamp) {
        if(rtpMarker && !dropFrame) {
            // Old Implementation, video frames instead of RTP stream 
            //channel->RtpRecvResult(videoFrame, frameLength, lTimestamp);
            if (isKeyFrame) {
                if (gotKeyFrame < 4) debug->printf("VideoEndpoint(%p)::RecvH264 keyframe len=%d", this, keyFrameLen);
                gotKeyFrame++;
            }
        }
        //else SendFullIntraRequest();
        isKeyFrame = false;
        if(sequenceNumberDiff) dropFrame = true;
        else dropFrame = false;
        if ((nalUnitType == NAL_UNIT_IDR) || (nalUnitType == NAL_UNIT_SPS) || (nalUnitType == NAL_UNIT_PPS)) {
            log->Log(LOG_VIDEO, "VideoEndpoint(%p)::RecvH264 keyframe", this);
            isKeyFrame = true;
            keyFrameLen = 0;
        }
    }
    else if(sequenceNumberDiff) {
        dropFrame = true;
    }
    rtpMarker = marker;
    lTimestamp = timestamp;
    totalSize += len;

    if (isKeyFrame) keyFrameLen += len;

    // Forward RTP Stream
    channel->RtpRecvResult(buf, len, timestamp, sequenceNumberDiff, marker);
}

void VideoEndpoint::RecvVp8(char * buf, int len, dword timestamp, short sequenceNumberDiff, bool marker)
{
    log->Log(LOG_VIDEO, "VideoEndpoint(%p)::RecvVp8 len=%d ts=%u(%u) rtpMarker=%d,%d dropFrame=%d sequenceNumberDiff=%d", this, len, timestamp, lTimestamp, rtpMarker, marker, dropFrame, sequenceNumberDiff);

    if (!firstTimestamp) firstTimestamp = ITime::TimeStampMilliseconds();
    lastTimestamp = ITime::TimeStampMilliseconds();
    totalNumPacket++;
    if (sequenceNumberDiff > 0) {
        totalNumLost += sequenceNumberDiff;
        totalNumPacketLost++;
    }
    else if (sequenceNumberDiff < 0) {
        totalNumOoO += sequenceNumberDiff;
        totalNumPacketOoO++;
    }
    totalSize += len;

    if(lTimestamp != timestamp) {
        if(!dropFrame) {
            if(rtpMarker) {
                // Frame received with no losses
                // Old Implementation, video frames instead of RTP stream 
                //channel->RtpRecvResult(videoFrame, frameLength, lTimestamp);
                if (isKeyFrame) {
                    if(gotKeyFrame < 4) debug->printf("VideoEndpoint(%p)::RecvVp8 keyframe len=%d", this, keyFrameLen);
                    gotKeyFrame++;
                }
            }
        } 
        else SendFullIntraRequest();
        //else if(isKeyFrame) SendFullIntraRequest();
        //else if(lTimestamp) SendPacketLostIndication();
        isKeyFrame = false;
        if (sequenceNumberDiff) {
            dropFrame = true;
            isKeyFrame = true;  // First fragment or fragments of the video frame went missing, no way to know if Intra or InterFrame
        }
        else {
            dropFrame = false;
            // First fragment of a video frame, check if Intra or InterFrame
            int offHdr = 1;
            if (buf[0] & 0x80) {                     // Check payload descriptor for Extension header
                offHdr++;
                if (buf[1] & 0x80) {                   // Picture ID present
                    if (buf[2] & 0x80) {               // Long Picture ID
                        offHdr += 2;
                    }
                    else {
                        offHdr++;
                    }
                }
                if (buf[1] & 0x40) offHdr++;           // TL0PICIDX byte present
                if (buf[1] & 0x20) offHdr++;           // TID byte present, TID/KEYIDX octet
                else if (buf[1] & 0x10) offHdr++;      // KEYIDX byte present, TID/KEYIDX octet
            }
            int fragSb = (buf[0] & 0x10) ? 1 : 0;
            if (fragSb) {
                if (!(buf[0] & 0x07) && !(buf[offHdr] & 0x01)) {
                    //debug->printf("VideoEndpoint(%p)::RecvVp8 vp8 keyframe", this);
                    isKeyFrame = true;
                    keyFrameLen = 0;
                }
            }
        }
    }
    else if(sequenceNumberDiff) {
        dropFrame = true;
    }
    rtpMarker = marker;
    lTimestamp = timestamp;

    if(isKeyFrame) keyFrameLen += len;

    // Forward RTP Stream
    channel->RtpRecvResult(buf, len, timestamp, sequenceNumberDiff, marker);
}

// RTP Payload still not correctly implemented
// Wait for the PBX to handle AV1 and test it against Web Browsers with WebRTC
void VideoEndpoint::RecvAV1(char* buf, int len, dword timestamp, short sequenceNumberDiff, bool marker)
{
    log->Log(LOG_VIDEO, "VideoEndpoint(%p)::RecvAV1 len=%d ts=%u(%u) rtpMarker=%d,%d dropFrame=%d sequenceNumberDiff=%d", this, len, timestamp, lTimestamp, rtpMarker, marker, dropFrame, sequenceNumberDiff);

    if (!firstTimestamp) firstTimestamp = ITime::TimeStampMilliseconds();
    lastTimestamp = ITime::TimeStampMilliseconds();
    totalNumPacket++;
    if (sequenceNumberDiff > 0) {
        totalNumLost += sequenceNumberDiff;
        totalNumPacketLost++;
    }
    else if (sequenceNumberDiff < 0) {
        totalNumOoO += sequenceNumberDiff;
        totalNumPacketOoO++;
    }
    totalSize += len;

    if (lTimestamp != timestamp) {
        if (rtpMarker && !dropFrame) {
            // Frame received with no losses
            // Old Implementation, video frames instead of RTP stream 
            //channel->RtpRecvResult(videoFrame, frameLength, lTimestamp);
            if (isKeyFrame) {
                if (gotKeyFrame < 4) debug->printf("VideoEndpoint(%p)::RecvAV1 keyframe len=%d", this, keyFrameLen);
                gotKeyFrame++;
            }
        }
        //else if (isKeyFrame) SendFullIntraRequest();
        //else if (lTimestamp) SendPacketLostIndication();
        isKeyFrame = false;
        if (sequenceNumberDiff) {
            dropFrame = true;
            isKeyFrame = true;  // First fragment or fragments of the video frame went missing, no way to know if Intra or InterFrame
        }
        else {
            dropFrame = false;
            // First fragment of a video frame, check if Intra or InterFrame
            int offHdr = 1;
            if (buf[0] & 0x80) {                     // Check payload descriptor for Extension header
                offHdr++;
                if (buf[1] & 0x80) {                   // Picture ID present
                    if (buf[2] & 0x80) {               // Long Picture ID
                        offHdr += 2;
                    }
                    else {
                        offHdr++;
                    }
                }
                if (buf[1] & 0x40) offHdr++;           // TL0PICIDX byte present
                if (buf[1] & 0x20) offHdr++;           // TID byte present, TID/KEYIDX octet
                else if (buf[1] & 0x10) offHdr++;      // KEYIDX byte present, TID/KEYIDX octet
            }
            int fragSb = (buf[0] & 0x10) ? 1 : 0;
            if (fragSb) {
                if (!(buf[0] & 0x07) && !(buf[offHdr] & 0x01)) {
                    //debug->printf("VideoEndpoint(%p)::RecvAV1 vp8 keyframe", this);
                    isKeyFrame = true;
                    keyFrameLen = 0;
                }
            }
        }
    }
    else if(sequenceNumberDiff) {
        dropFrame = true;
    }
    rtpMarker = marker;
    lTimestamp = timestamp;

    if(isKeyFrame) keyFrameLen += len;

    // Forward RTP Stream
    channel->RtpRecvResult(buf, len, timestamp, sequenceNumberDiff, marker);
}

int VideoEndpoint::calcNalLength(char *data, int *bytesRead, int tLength, int *nalStart, int *idc, int *type)
{
    int bRead = *bytesRead;
    int endByte = tLength;

    for(; bRead+3<tLength; bRead++) {
        if(data[bRead] == 0x0 && data[bRead+1] == 0x0 && data[bRead+2] == 0x1) {
            break;
        }
    }
    if(bRead+3>=tLength) {
        *bytesRead = tLength;
        return tLength;
    }
    bRead += 3; // start code not sent over RTP
    *nalStart = bRead;
    *idc = (((byte)(data[bRead])) >> 5);
    *type = (data[bRead] & 0x1F);
    for(; bRead<tLength; bRead++) {
        if(data[bRead]) continue;
        if((bRead+2 < tLength) && data[bRead+1] == 0x0 && data[bRead+2] == 0x1) {
            endByte = bRead - (data[bRead-1] == 0x0);     // found start code, start code can contain 2 or 3 0x0 bytes...
            break;
        }
    }
    *bytesRead = bRead;
    return (endByte-*nalStart);
}

void VideoEndpoint::Send(char * buf, int len, dword timestamp, unsigned num, enum MediaType mediaTye, bool isConference)
{
    maxAppSharingData = isConference ? 4000 : 6000;
    if(coder == VideoCoder::VP8) return SendVp8(buf, len, timestamp, mediaTye);
    else if(coder == VideoCoder::H264) return SendH264(buf, len, timestamp, mediaTye);
    else if(coder == VideoCoder::AV1) return SendAV1(buf, len, timestamp, mediaTye);
    else if(coder == VideoCoder::VP9) return SendVp8(buf, len, timestamp, mediaTye);
}

void VideoEndpoint::SendH264(char * buf, int len, dword timestamp, enum MediaType mediaType)
{
    int startByte = 0;
    int bytesRead = 0;
    int nalLength;
    int startProc = 1;
    int nalRefIdc = 0;
    bool useStap = false;
    char rtpPayload[DEFAULT_MTU];
    int aggregateLen = 0;

    log->Log(LOG_VIDEO, "VideoChannel(%p)::SendH264 ch=%p len=%d timestamp=%x", this, channel, len, timestamp);

    while(bytesRead < len) {
        int tmpIdc, tmpType;

        nalLength = calcNalLength(buf, &bytesRead, len, &startByte, &tmpIdc, &tmpType);

        //debug->printf("Send read=%d tot=%d s=%d nal=%d type=%d", bytesRead, len, startByte, nalLength, tmpType);
        if (aggregateLen == 0) {
            useStap = ((bytesRead < len) && (len < maxRtpVideoData));
            rtpPayload[0] = (tmpIdc << 5) | RTP_STAP_A;
            aggregateLen = 1;
        }

        switch(tmpType) {
        case NAL_UNIT_NON_IDR:
            startProc = 0;
            break;
        case NAL_UNIT_IDR:
            // First NAL
            if(startProc) {
                if(sps && spsLength && pps && ppsLength) {
                    log->Log(LOG_VIDEO, "VideoChannel(%p)::SendH264 Resend SPS/PPS before IDRs frames sps=%d pps=%d", this, spsLength, ppsLength);
                    channel->RtpSend(sps, spsLength, timestamp, 0, false);
                    channel->RtpSend(pps, ppsLength, timestamp, 0, false);
                }
                startProc = 0;
            }
            break;
        case NAL_UNIT_SPS:
            // First NAL
            if(startProc) {
                log->Log(LOG_VIDEO, "VideoChannel(%p)::SendH264 Got new SPS/PPS from h264 encoder", this);
                startProc = 0;
            }
            if(sps) free(sps);
            sps = (char *)malloc(nalLength);
            spsLength = nalLength;
            memcpy(sps, &buf[startByte], nalLength);
            break;
        case NAL_UNIT_PPS:
            // First NAL
            if(startProc) {
                log->Log(LOG_VIDEO, "VideoChannel(%p)::SendH264 Got new SPS/PPS from h264 encoder", this);
                startProc = 0;
            }
            if(pps) free(pps);
            pps = (char *)malloc(nalLength);
            ppsLength = nalLength;
            memcpy(pps, &buf[startByte], nalLength);
            break;
        }

        if(useStap) {
            if(tmpIdc > nalRefIdc) {
                rtpPayload[0] = (tmpIdc << 5) | RTP_STAP_A;
                nalRefIdc = tmpIdc;
            }
            WRITE_WORD_NET(&rtpPayload[aggregateLen], nalLength);
            memcpy(&rtpPayload[aggregateLen+2], &buf[startByte], nalLength);
            aggregateLen += 2 + nalLength;
        }
        else if(nalLength > maxRtpVideoData) {    // fragment NAL
            int ffp = 1;                          // first fragment

            nalLength--;                          // first byte not sent
            startByte++;
            int packets = (nalLength + maxRtpVideoData - 1) / maxRtpVideoData;
            int maxLen = (packets != 0) ? (nalLength + packets - 1) / packets : 0;

            while(nalLength > 0) {
                int txLen = (nalLength > maxLen ? maxLen : nalLength);  // FU Header

                //debug->printf("Send FUA read=%d tot=%d s=%d nal=%d tot=%d", bytesRead, len, startByte, txLen, startByte+txLen);

                rtpPayload[0] = (tmpIdc<<5) | RTP_FU_A;
                rtpPayload[1] = tmpType;
                nalLength -= txLen;
                if(ffp) {
                    rtpPayload[1] |= 0x80;                  // Start Bit
                    ffp = 0;
                }
                else if(nalLength == 0) {
                    rtpPayload[1] |= 0x40;                  // End Bit
                }
                memcpy(&rtpPayload[2], &buf[startByte], txLen);
                startByte += txLen;
                channel->RtpSend(rtpPayload, 2+txLen, timestamp, 0, (bytesRead >= len) && (nalLength == 0));
            }
        }
        else {
            channel->RtpSend(&buf[startByte], nalLength, timestamp, 0, (bytesRead >= len));
        }
    }
    if (useStap) {
        channel->RtpSend(rtpPayload, aggregateLen, timestamp, 0, true);
    }
    channel->RtpSendResult();
}

void VideoEndpoint::SendVp8(char * buf, int len, dword timestamp, enum MediaType mediaType)
{
    int offset = 0;
    int packets = (len + maxRtpVideoData - 1) / maxRtpVideoData;
    int maxLen = (packets != 0) ? (len + packets - 1) / packets : 0;
    word sSeq, eSeq;
    int keyFrameLen = 0;
    int startQueueSize = videoChunkListSize;
    log->Log(LOG_VIDEO, "VideoChannel(%p)::SendVp8 len=%d timestamp=%x %s", this, len, timestamp, ((len > 0) && !(buf[0] & 0x01)) ? "keyframe" : "");

    char* b = (char*)buf;
    if (!(b[0] & 0x01)) {
        sSeq = channel->GetRtpSequence();
        //if(mediaType == MediaType::VIDEO_APPSHARING) debug->printf("VideoEndpoint(%p)::SendVp8 vp8 keyframe len=%d start sequence=%d", this, len, sSeq);
        keyFrameLen = len;
    }

    while(len > 0) {
        byte * rtpPayload = (byte *)malloc(DEFAULT_MTU);
        int hdrSize = 3;
        int sbytes = MIN(len, maxLen);
        rtpPayload[0] = 0x80;           // VP8 payload descriptor
        rtpPayload[1] = 0x80;           // PIC present
        if(vp8PicId > 127) {
            WRITE_WORD_NET(&rtpPayload[2], vp8PicId);
            rtpPayload[2] |= 0x80;      // 2 bytes
            hdrSize++;
        }
        else {
            rtpPayload[2] = vp8PicId;
        }
        if(!offset) {  // first packet of partition
            rtpPayload[0] |= 0x10;
        }
        memcpy(&rtpPayload[hdrSize], &buf[offset], sbytes);

        len -= sbytes;
        offset += sbytes;

        // To be done only with VideoAppSharing
        if (mediaType == MediaType::VIDEO_APPSHARING) {
            videoChunkList.push_back(new class VideoChunk((char*)rtpPayload, hdrSize + sbytes, timestamp, (len == 0)));
            videoChunkListSize++;
            // Switching from VIDEO to VIDEO_APPSHARING
            if (videoChunkTimer->IsRunning() == false) videoChunkTimer->Start(30);
        }
        else {
            // Switching from VIDEO_APPSHARING to VIDEO
            if (videoChunkTimer->IsRunning() == true) {
                videoChunkTimer->Cancel();
                while (!videoChunkList.empty()) delete videoChunkList.front();
            }
            channel->RtpSend((char*)rtpPayload, hdrSize + sbytes, timestamp, 0, (len == 0));
            free(rtpPayload);
        }
    }
    channel->RtpSendResult();
    vp8PicId++;

    if (keyFrameLen) {
        eSeq = sSeq + (videoChunkListSize - startQueueSize);
        dword sequences[2];
        WRITE_DWORD_NET(&sequences[0], keyFrameLen);
        WRITE_DWORD_NET(&sequences[1], (dword)sSeq | ((dword)eSeq << 16));
        channel->EventReceived(CHANNEL_KEYFRAME_SEQUENCES, sequences);
    }
}

// RTP Payload still not correctly implemented
// Wait for the PBX to handle AV1 and test it against Web Browsers with WebRTC
void VideoEndpoint::SendAV1(char* buf, int len, dword timestamp, enum MediaType mediaType)
{
    int offset = 0;
    int packets = (len + maxRtpVideoData - 1) / maxRtpVideoData;
    int maxLen = (packets != 0) ? (len + packets - 1) / packets : 0;
    word sSeq, eSeq;
    int keyFrameLen = 0;
    int startQueueSize = videoChunkListSize;
    log->Log(LOG_VIDEO, "VideoChannel(%p)::SendAV1 len=%d timestamp=%x %s", this, len, timestamp, ((len > 0) && !(buf[0] & 0x01)) ? "keyframe" : "");

    char* b = (char*)buf;
    if (!(b[0] & 0x01)) {
        sSeq = channel->GetRtpSequence();
        //if(mediaType == MediaType::VIDEO_APPSHARING) debug->printf("VideoEndpoint(%p)::SendAV1 keyframe len=%d start sequence=%d", this, len, sSeq);
        keyFrameLen = len;
    }

    while (len > 0) {
        byte* rtpPayload = (byte*)malloc(DEFAULT_MTU);
        int hdrSize = 1;
        int sbytes = MIN(len, maxLen);
        rtpPayload[0] = firstObu ? 0x18 : 0x10;                           // AV1 Aggregation Header, W == 1 -> one OBU
        memcpy(&rtpPayload[hdrSize], &buf[offset], sbytes);

        len -= sbytes;
        if(len) rtpPayload[0] |= 0x40;                   // Y bit, will continue later
        if(offset) rtpPayload[0] |= 0x80;                // Z bit, continuation
        offset += sbytes;

        // To be done only with VideoAppSharing
        if (mediaType == MediaType::VIDEO_APPSHARING) {
            videoChunkList.push_back(new class VideoChunk((char*)rtpPayload, hdrSize + sbytes, timestamp, (len == 0)));
            videoChunkListSize++;
            // Switching from VIDEO to VIDEO_APPSHARING
            if (videoChunkTimer->IsRunning() == false) videoChunkTimer->Start(30);
        }
        else {
            // Switching from VIDEO_APPSHARING to VIDEO
            if (videoChunkTimer->IsRunning() == true) {
                videoChunkTimer->Cancel();
                while (!videoChunkList.empty()) delete videoChunkList.front();
            }
            channel->RtpSend((char*)rtpPayload, hdrSize + sbytes, timestamp, 0, (len == 0));
            free(rtpPayload);
        }
    }
    channel->RtpSendResult();
    av1PicId++;
    if (firstObu) firstObu = false;

    if (keyFrameLen) {
        eSeq = sSeq + (videoChunkListSize - startQueueSize);
        dword sequences[2];
        WRITE_DWORD_NET(&sequences[0], keyFrameLen);
        WRITE_DWORD_NET(&sequences[1], (dword)sSeq | ((dword)eSeq << 16));
        channel->EventReceived(CHANNEL_KEYFRAME_SEQUENCES, sequences);
    }
}

// It is recommended to send PLI instead of FIR in case of packet lost
// We use PLI now if we recognized that the packet that went missing belongs to inter frame prediction
void VideoEndpoint::SendPacketLostIndication(void)
{
    byte buf[12];

    debug->printf("VideoEndpoint(%p)::SendPacketLostIndication", this);

    // https://tools.ietf.org/html/rfc4585#section-6.3.1
    // RTP/AVPF RFC4585 PLI
    buf[0] = 0x81;  // V=2, FMR=1 (PLI - Packet Lost Indication)
    buf[1] = 0xce;  // PT=206 (PSFB - Payload-specific feedback message)
    buf[2] = 0x00;  // length is 4 dwords
    buf[3] = 0x02;
#if 0  // This will be written afterwards
    WRITE_DWORD_NET(&buf[4], ssrc);                 // our ssrc
    WRITE_DWORD_NET(&buf[8], remoteSsrc);           // peer ssrc
#endif

    channel->RtcpSend((char*)buf, 12);
}

void VideoEndpoint::SendFullIntraRequest(void)
{
#if 1
    channel->RtcpSendFirRequest(false);
#else
    byte buf[20];

    debug->printf("VideoEndpoint(%p)::SendFullIntraRequest %d", this, firCommandSequence);

    // https://tools.ietf.org/html/rfc5104#section-4.3.1
    // RTP/AVPF RFC4585 RFC5104
    buf[0] = 0x84;  // V=2, FMR=4 (FIR - Full Intra Request)
    buf[1] = 0xce;  // PT=206 (PSFB - Payload-specific feedback message)
    buf[2] = 0x00;  // length is 4 dwords
    buf[3] = 0x04;
#if 0  // This will be written afterwards
    WRITE_DWORD_NET(&buf[4], ssrc);                 // our ssrc
    WRITE_DWORD_NET(&buf[8], remoteSsrc);           // peer ssrc
    // Feedback Control Information (FCI):
    WRITE_DWORD_NET(&buf[12], remoteSsrc);          // ssrc the FIR is asserted for
#endif
    buf[16] = firCommandSequence;                   // Full intra request command sequence
    buf[17] = 0x00;  // reserved
    buf[18] = 0x00;  // reserved
    buf[19] = 0x00;  // reserved

    firCommandSequence++;

    channel->RtcpSend((char *)buf, 20);

#if 0
    // H261
    buf[0] = 0x80;  // V=2, MBZ=0
    buf[1] = 0xc0;  // PT=192 (RTCP_FIR )
    buf[2] = 0x00;  // length is 4 dwords
    buf[3] = 0x01;
#if 0  // This will be written afterwards
    WRITE_DWORD_NET(&buf[4], ssrc);                 // our ssrc
#endif
    channel->RtcpSend((char*)buf, 8);
#endif

    // Avoid sending a FIR every 5 seconds, only on packet lost
    //fullIntraFrameTimer->Start(5000);
#endif
}

MP4Container::MP4Container(class UMediaContainer * const user, class IInstanceLog * const log):
user(user),
log(log)
{
    this->first = true;
    this->seq = 1;
#if RECORD_VIDEO
    char videoFile[256];
    _sprintf(videoFile, "C:\\temp\\video_%p.mp4", user);
    fp = fopen(videoFile, "w+b");
    if(!fp) log->Log(LOG_VIDEO, "MP4Container(%p)::MP4Container failed to create file", this);
    _sprintf(videoFile, "C:\\temp\\video_%p.h264", user);
    fpH264 = fopen(videoFile, "w+b");
    if(!fpH264) log->Log(LOG_VIDEO, "MP4Container(%p)::MP4Container failed to create file for h264", this);
#else
    fp = fpH264 = NULL;
#endif
    spsLength = 0;
    ppsLength = 0;
    fTimestamp = 0;
    lTimestamp = 0;
    level = 0xC;
    profile = 0x42;
    width = 320;
    height = 240;
    this->sps = NULL;
    this->pps = NULL;
    this->trunCnt = 0;
    this->rxSample = NULL;
    this->offDuration = 0;
    this->sliceType = 2;
    this->numSamples = 0;
    log->Log(LOG_VIDEO, "MP4Container(%p)::MP4Container", this);
}

MP4Container::~MP4Container()
{
    log->Log(LOG_VIDEO, "MP4Container(%p)::~MP4Container", this);

    if(rxSample) free(rxSample);
    if(fp) fclose(fp);
    if(fpH264) fclose(fpH264);
    if(sps) free(sps);
    if(pps) free(pps);
}

void MP4Container::Initialize(void)
{
    log->Log(LOG_VIDEO, "MP4Container(%p)::Initialize", this);

    first = true;
    if (rxSample) { free(rxSample); rxSample = NULL; }
    if (fp) { fclose(fp); fp = NULL; }
    if (fpH264) { fclose(fpH264); fpH264 = NULL; }
    if (sps) { free(sps); sps = NULL; }
    if (pps) { free(pps); pps = NULL; }
    spsLength = 0;
    ppsLength = 0;
    fTimestamp = 0;
    lTimestamp = 0;
    this->sps = NULL;
    this->pps = NULL;
    this->trunCnt = 0;
    this->rxSample = NULL;
    this->offDuration = 0;
    this->sliceType = 2;
    this->numSamples = 0;
}

int MP4Container::calcNalLength(char *data, int *bytesRead, int tLength, int *nalStart, int *idc, int *type)
{
    int bRead = *bytesRead;
    int endByte = tLength;

    for(; bRead+3<tLength; bRead++) {
        if(data[bRead] == 0x0 && data[bRead+1] == 0x0 && data[bRead+2] == 0x1) {
            break;
        }
    }
    if(bRead+3>=tLength) {
        *bytesRead = tLength;
        return tLength;
    }
    bRead += 3; // start code not sent over RTP
    *nalStart = bRead;
    *idc = (data[bRead] >> 5);
    *type = (data[bRead] & 0x1F);
    for(; bRead<tLength; bRead++) {
        if(data[bRead]) continue;
        if((bRead+2 < tLength) && data[bRead+1] == 0x0 && data[bRead+2] == 0x1) {
            endByte = bRead - (data[bRead-1] == 0x0);     // found start code, start code can contain 2 or 3 0x0 bytes...
            break;
        }
    }
    *bytesRead = bRead;
    return (endByte-*nalStart);
}

int MP4Container::CreateTkhdBox(char * buf, ulong64 tTime)
{
    WRITE_DWORD_NET(&buf[0], 0);             // Length
    strcpy(&buf[4], boxSuffixes[TRACK_HEADER_BOX].name);
    WRITE_DWORD_NET(&buf[8], 0x3);           // version+flags, Track_enabled+Track_in_movie
    WRITE_DWORD_NET(&buf[12], (dword)tTime); // creationTime
    WRITE_DWORD_NET(&buf[16], (dword)tTime); // updateTime
    WRITE_DWORD_NET(&buf[20], 0x1);          // Track Id
    WRITE_DWORD_NET(&buf[24], 0x0);          // Reserved
    WRITE_DWORD_NET(&buf[28], 0);            // Duration
    WRITE_DWORD_NET(&buf[32], 0x0);          // Reserved
    WRITE_DWORD_NET(&buf[36], 0x0);          // Reserved
    WRITE_WORD_NET(&buf[40], 0x0);           // Layer
    WRITE_WORD_NET(&buf[42], 0x0);           // Alternate Group
    WRITE_WORD_NET(&buf[44], 0x0);           // Volume
    WRITE_WORD_NET(&buf[46], 0x0);           // Reserved
    WRITE_DWORD_NET(&buf[48], 0x00010000);   // Matrix
    WRITE_DWORD_NET(&buf[52], 0);            // Matrix
    WRITE_DWORD_NET(&buf[56], 0);            // Matrix
    WRITE_DWORD_NET(&buf[60], 0);            // Matrix
    WRITE_DWORD_NET(&buf[64], 0x00010000);   // Matrix
    WRITE_DWORD_NET(&buf[68], 0);            // Matrix
    WRITE_DWORD_NET(&buf[72], 0);            // Matrix
    WRITE_DWORD_NET(&buf[76], 0);            // Matrix
    WRITE_DWORD_NET(&buf[80], 0x40000000);   // Matrix
    WRITE_WORD_NET(&buf[84], width);         // Width
    WRITE_WORD_NET(&buf[86], 0x0);           // Width
    WRITE_WORD_NET(&buf[88], height);        // Height
    WRITE_WORD_NET(&buf[90], 0x0);           // Height
    WRITE_DWORD_NET(&buf[0], 92);            // Length

    return 92;
}

int MP4Container::CreateMvhdBox(char * buf, ulong64 tTime)
{
    // mvhd
    WRITE_DWORD_NET(&buf[0], 0);              // Length
    strcpy(&buf[4], boxSuffixes[MOVIE_HEADER_BOX].name);
    WRITE_DWORD_NET(&buf[8], 0);             // version+flags 0
    WRITE_DWORD_NET(&buf[12], (dword)tTime);  // creationTime
    WRITE_DWORD_NET(&buf[16], (dword)tTime);  // updateTime
    WRITE_DWORD_NET(&buf[20], 100000);        // Time Scale (ms)
    WRITE_DWORD_NET(&buf[24], 0);             // Duration
    WRITE_DWORD_NET(&buf[28], 0x00010000);    // Rate
    WRITE_WORD_NET(&buf[32], 0x0100);         // Volume
    WRITE_WORD_NET(&buf[34], 0);              // Reserved
    WRITE_DWORD_NET(&buf[36], 0);             // Reserved
    WRITE_DWORD_NET(&buf[40], 0);             // Reserved

    WRITE_DWORD_NET(&buf[44], 0x00010000);    // Matrix
    WRITE_DWORD_NET(&buf[48], 0);             // Matrix
    WRITE_DWORD_NET(&buf[52], 0);             // Matrix
    WRITE_DWORD_NET(&buf[56], 0);             // Matrix
    WRITE_DWORD_NET(&buf[60], 0x00010000);    // Matrix
    WRITE_DWORD_NET(&buf[64], 0);             // Matrix
    WRITE_DWORD_NET(&buf[68], 0);             // Matrix
    WRITE_DWORD_NET(&buf[72], 0);             // Matrix
    WRITE_DWORD_NET(&buf[76], 0x40000000);    // Matrix

    WRITE_DWORD_NET(&buf[80], 0);             // pre_defined
    WRITE_DWORD_NET(&buf[84], 0);             // pre_defined
    WRITE_DWORD_NET(&buf[88], 0);             // pre_defined
    WRITE_DWORD_NET(&buf[92], 0);            // pre_defined
    WRITE_DWORD_NET(&buf[96], 0);            // pre_defined
    WRITE_DWORD_NET(&buf[100], 0);            // pre_defined
    WRITE_DWORD_NET(&buf[104], 2);            // Next Track Id
    WRITE_DWORD_NET(&buf[0], 108);            // Length
    // End mvhd

    return 108;
}

int MP4Container::CreateMdhdBox(char * buf, ulong64 tTime)
{
    WRITE_DWORD_NET(&buf[0], 32);            // Length
    strcpy(&buf[4], boxSuffixes[MEDIA_HEADER_BOX].name);
    WRITE_DWORD_NET(&buf[8], 0);             // Version
    WRITE_DWORD_NET(&buf[12], (dword)tTime); // creationTime
    WRITE_DWORD_NET(&buf[16], (dword)tTime); // updateTime
    WRITE_DWORD_NET(&buf[20], 100000);       // Time Scale (ms)
    WRITE_DWORD_NET(&buf[24], 0);            // Duration
    WRITE_WORD_NET(&buf[28], 0x55C4);        // Language
    WRITE_WORD_NET(&buf[30], 0x0);           // pre_defined
    // End mdhd

    return 32;
}

int MP4Container::CreateHdlrBox(char * buf)
{
    WRITE_DWORD_NET(&buf[0], 0);            // Length
    strcpy(&buf[4], boxSuffixes[HANDLER_REFERENCE_BOX].name);
    WRITE_DWORD_NET(&buf[8], 0);            // Version
    WRITE_DWORD_NET(&buf[12], 0);            // pre_defined
    strcpy(&buf[16], "vide");                // handler_type 
    WRITE_DWORD_NET(&buf[20], 0);            // Reserved
    WRITE_DWORD_NET(&buf[24], 0);            // Reserved
    WRITE_DWORD_NET(&buf[28], 0);            // Reserved
    strcpy(&buf[32], "VideoHandler");        // string
    WRITE_DWORD_NET(&buf[0], 32+strlen("VideoHandler")+1);  // Length
    // End hdlr

    return 32+strlen("VideoHandler")+1;
}

int MP4Container::CreateVmhdBox(char * buf)
{
    WRITE_DWORD_NET(&buf[0], 20);            // Length
    strcpy(&buf[4], boxSuffixes[VIDEO_MEDIA_HEADER_BOX].name);
    WRITE_DWORD_NET(&buf[8], 1);             // Version+Flag
    WRITE_WORD_NET(&buf[12], 0);             // graphicsmode
    WRITE_WORD_NET(&buf[14], 0);             // opcolor
    WRITE_WORD_NET(&buf[16], 0);             // opcolor
    WRITE_WORD_NET(&buf[18], 0);             // opcolor
    // End vmhd

    return 20;
}

int MP4Container::CreateDrefBox(char * buf)
{
    WRITE_DWORD_NET(&buf[0], 28);            // Length
    strcpy(&buf[4], boxSuffixes[DATA_REFERENCE_BOX].name);
    WRITE_DWORD_NET(&buf[8], 0);             // Version
    WRITE_DWORD_NET(&buf[12], 1);            // Entry Count
    WRITE_DWORD_NET(&buf[16], 0xC);          // Entry Version
    strcpy(&buf[20], "url ");
    WRITE_DWORD_NET(&buf[24], 1);            // Entry Count
    // End dref

    return 28;
}

int MP4Container::CreateAvccBox(char * buf)
{
    strcpy(&buf[4], boxSuffixes[AVC_CODING_BOX].name);
    buf[8] = 0x1;
    buf[9] = profile;
    buf[10] = level;
    buf[11] = 0x0D;
    WRITE_WORD_NET(&buf[12], 0xFFE1);
    WRITE_WORD_NET(&buf[14], spsLength);                    // Length SPS
    memcpy(&buf[14+2], sps, spsLength);
    buf[14+2+spsLength] = 0x1;                              // Num PPS
    WRITE_WORD_NET(&buf[14+2+spsLength+1], ppsLength);      // Length PPS
    memcpy(&buf[14+2+spsLength+1+2], pps, ppsLength);

    WRITE_DWORD_NET(&buf[0], 14+2+spsLength+1+2+ppsLength); // Length

    return 14+2+spsLength+1+2+ppsLength;
}

int MP4Container::CreateAvc1Box(char * buf)
{
    int totLength = 86;

    strcpy(&buf[4], boxSuffixes[AVC1_BOX].name);
    WRITE_DWORD_NET(&buf[8], 0);      // reserved
    WRITE_WORD_NET(&buf[12], 0);      // reserved
    WRITE_WORD_NET(&buf[14], 0x1);    // data_reference_index
    WRITE_DWORD_NET(&buf[16], 0);     // reserved
    WRITE_DWORD_NET(&buf[20], 0);     // reserved
    WRITE_DWORD_NET(&buf[24], 0);     // reserved
    WRITE_DWORD_NET(&buf[28], 0);     // reserved
    WRITE_WORD_NET(&buf[32], width);  // Width
    WRITE_WORD_NET(&buf[34], height); // Height
    WRITE_DWORD_NET(&buf[36], 0x00480000);     // horizresolution
    WRITE_DWORD_NET(&buf[40], 0x00480000);     // vertresolution
    WRITE_DWORD_NET(&buf[44], 0);     // reserved
    WRITE_WORD_NET(&buf[48], 0x1);    // frame_count
    buf[50] = 0xA;                    // string length
    strcpy(&buf[51], "AVC Coding");
    WRITE_WORD_NET(&buf[82], 0x18);   // frame_count
    WRITE_WORD_NET(&buf[84], 0xFFFF); // pre_defined

    totLength += CreateAvccBox(&buf[totLength]);

    WRITE_DWORD_NET(&buf[0], totLength);     // Length

    return totLength;
}

int MP4Container::CreateStsdBox(char * buf)
{
    int totLength = 16;

    WRITE_DWORD_NET(&buf[0], 0);      // Length
    strcpy(&buf[4], boxSuffixes[SAMPLE_DESCRIPTION_BOX].name);
    WRITE_DWORD_NET(&buf[8], 0);      // reserved
    WRITE_WORD_NET(&buf[12], 0);      // reserved
    WRITE_WORD_NET(&buf[14], 0x1);    // data_reference_index

    totLength += CreateAvc1Box(&buf[16]);

    WRITE_DWORD_NET(&buf[0], totLength);      // Length

    return totLength;
}

int MP4Container::CreateSttsBox(char * buf)
{
    WRITE_DWORD_NET(&buf[0], 16);      // Length
    strcpy(&buf[4], boxSuffixes[DECODING_SAMPLE_BOX].name);
    WRITE_DWORD_NET(&buf[8], 0);       // Version
    WRITE_DWORD_NET(&buf[12], 0);      // entry_count

    return 16;
}

int MP4Container::CreateStscBox(char * buf)
{
    WRITE_DWORD_NET(&buf[0], 16);      // Length
    strcpy(&buf[4], boxSuffixes[SAMPLE_CHUNK_BOX].name);
    WRITE_DWORD_NET(&buf[8], 0);       // Version
    WRITE_DWORD_NET(&buf[12], 0);      // entry_count

    return 16;
}

int MP4Container::CreateStszBox(char * buf)
{
    WRITE_DWORD_NET(&buf[0], 20);      // Length
    strcpy(&buf[4], boxSuffixes[SAMPLE_SIZE_BOX].name);
    WRITE_DWORD_NET(&buf[8], 0);       // Version
    WRITE_DWORD_NET(&buf[12], 0);      // sample_size
    WRITE_DWORD_NET(&buf[16], 0);      // sample_count

    return 20;
}

int MP4Container::CreateStcoBox(char * buf)
{
    WRITE_DWORD_NET(&buf[0], 16);      // Length
    strcpy(&buf[4], boxSuffixes[CHUNK_OFFSET_BOX].name);
    WRITE_DWORD_NET(&buf[8], 0);       // Version
    WRITE_DWORD_NET(&buf[12], 0);      // entry_count

    return 16;
}

int MP4Container::CreateTrexBox(char * buf)
{
    WRITE_DWORD_NET(&buf[0], 32);      // Length
    strcpy(&buf[4], boxSuffixes[TRACK_EXTEND_BOX].name);
    WRITE_DWORD_NET(&buf[8], 0);       // version+flags
    WRITE_DWORD_NET(&buf[12], 1);      // track id
    WRITE_DWORD_NET(&buf[16], 1);      // default_sample_description_index
    WRITE_DWORD_NET(&buf[20], 0);      // default_sample_duration
    WRITE_DWORD_NET(&buf[24], 0);      // default_sample_size
    WRITE_DWORD_NET(&buf[28], 0);      // default_sample_flags

    return 32;
}

char * MP4Container::CreateMoovBox(int * olen)
{
    ulong64 tTime = ITime::TimeStampMilliseconds()/100;
    int totLength = 8;
    char * rtpPayload = (char *)malloc(1024);
    memset(rtpPayload, 0x0, 1024);

    // moov
    WRITE_DWORD_NET(&rtpPayload[0], 0);              // Length
    strcpy(&rtpPayload[4], boxSuffixes[MOVIE_BOX].name);
    // mvhd
    totLength += CreateMvhdBox(&rtpPayload[totLength], tTime);

    // trak
    int startTrak = totLength;
    WRITE_DWORD_NET(&rtpPayload[totLength], 0);            // Length
    strcpy(&rtpPayload[totLength+4], boxSuffixes[TRACK_BOX].name);
    totLength += 8;
    // tkhd
    totLength += CreateTkhdBox(&rtpPayload[totLength], tTime);

    // mdia
    int startMdia = totLength;
    WRITE_DWORD_NET(&rtpPayload[totLength], 0);            // Length
    strcpy(&rtpPayload[totLength+4], boxSuffixes[MEDIA_BOX].name);
    totLength += 8;
    // mdhd
    totLength += CreateMdhdBox(&rtpPayload[totLength], tTime);
    // hdlr
    totLength += CreateHdlrBox(&rtpPayload[totLength]);

    // minf
    int startMinf = totLength;
    WRITE_DWORD_NET(&rtpPayload[totLength], 0);            // Length
    strcpy(&rtpPayload[totLength+4], boxSuffixes[MEDIA_INFORMATION_BOX].name);
    totLength += 8;
    // vmhd
    totLength += CreateVmhdBox(&rtpPayload[totLength]);

    // dinf
    int startDinf = totLength;
    WRITE_DWORD_NET(&rtpPayload[totLength], 0);            // Length
    strcpy(&rtpPayload[totLength+4], boxSuffixes[DATA_INFORMATION_BOX].name);
    totLength += 8;
    // dref
    totLength += CreateDrefBox(&rtpPayload[totLength]);
    WRITE_DWORD_NET(&rtpPayload[startDinf], totLength-startDinf);  // Length
    // End dinf

    // stbl
    int startStbl = totLength;
    WRITE_DWORD_NET(&rtpPayload[totLength], 0);            // Length
    strcpy(&rtpPayload[totLength+4], boxSuffixes[SAMPLE_TABLE_BOX].name);
    totLength += 8;
    // stsd
    totLength += CreateStsdBox(&rtpPayload[totLength]);
    // stts
    totLength += CreateSttsBox(&rtpPayload[totLength]);
    // stsc
    totLength += CreateStscBox(&rtpPayload[totLength]);
    // stsz
    totLength += CreateStszBox(&rtpPayload[totLength]);
    // stco
    totLength += CreateStcoBox(&rtpPayload[totLength]);

    WRITE_DWORD_NET(&rtpPayload[startStbl], totLength-startStbl);      // Length
    // End stbl

    WRITE_DWORD_NET(&rtpPayload[startMinf], totLength-startMinf);      // Length
    // End minf

    WRITE_DWORD_NET(&rtpPayload[startMdia], totLength-startMdia);      // Length
    // End mdia

    WRITE_DWORD_NET(&rtpPayload[startTrak], totLength-startTrak);      // Length
    // End trak

    // mvex
    int startMvex = totLength;
    WRITE_DWORD_NET(&rtpPayload[totLength], 0);            // Length
    strcpy(&rtpPayload[totLength+4], boxSuffixes[MOVIE_EXTEND_BOX].name);
    totLength += 8;
    // trex
    totLength += CreateTrexBox(&rtpPayload[totLength]);
    WRITE_DWORD_NET(&rtpPayload[startMvex], totLength-startMvex);  // Length

#if 0
    // udta
    int startUdta = totLength;
    WRITE_DWORD_NET(&rtpPayload[totLength], 0);            // Length
    strcpy(&rtpPayload[totLength+4], boxSuffixes[USER_DATA_BOX].name);
    totLength += 8;
    // meta
    totLength += CreateMetaBox(&rtpPayload[totLength]);
    WRITE_DWORD_NET(&rtpPayload[startUdta], totLength-startUdta);  // Length
#endif

    WRITE_DWORD_NET(rtpPayload, totLength);
    // End moov
    *olen = totLength;

    log->Log(LOG_VIDEO, "MP4Container(%p)::CreateMoovBox len=%d", this, totLength);
    //log->HexDump(LOG_VIDEO, rtpPayload, totLength);

    return rtpPayload;
}

char * MP4Container::CreateMoofBox(int * olen, int slen, dword timestamp)
{
    int offFlags = 8;
    int initLen = 100;
    char * rtpPayload = (char *)malloc(initLen+trunCnt*offFlags);

    // moof
    WRITE_DWORD_NET(rtpPayload, initLen+trunCnt*offFlags);         // Length
    strcpy(&rtpPayload[4], boxSuffixes[MOVIE_FRAGMENT_BOX].name);

    // mfhd 
    WRITE_DWORD_NET(&rtpPayload[8], 16);                // Length
    strcpy(&rtpPayload[12], boxSuffixes[MOVIE_FRAGMENT_HEADER_BOX].name);
    WRITE_DWORD_NET(&rtpPayload[16], 0);                // Version+Flag
    WRITE_DWORD_NET(&rtpPayload[20], seq++);            // Sequence Number

    // traf
    WRITE_DWORD_NET(&rtpPayload[24], initLen+trunCnt*offFlags-24); // Length, moof - moofhdr - mfhd
    strcpy(&rtpPayload[28], boxSuffixes[TRACK_FRAGMENT_BOX].name);

    // tfhd
    WRITE_DWORD_NET(&rtpPayload[32], 28);               // Length
    strcpy(&rtpPayload[36], boxSuffixes[TRACK_FRAGMENT_HEADER_BOX].name);
    WRITE_DWORD_NET(&rtpPayload[40], 0x00020038);       // Version+Flag
    WRITE_DWORD_NET(&rtpPayload[44], 0x1);              // Track_Id
#if 1
    offDuration = 48;
    WRITE_DWORD_NET(&rtpPayload[48], 3333);             // default_sample_duration
#else
    if(lTimestamp) {
        WRITE_DWORD_NET(&rtpPayload[48], (timestamp-lTimestamp));           // default_sample_duration
    }
    else {
        WRITE_DWORD_NET(&rtpPayload[48], 3333);           // default_sample_duration
    }
#endif
    WRITE_DWORD_NET(&rtpPayload[52], trunBuf[0]);       // default_sample_size
    if(sliceType == 2) {
        WRITE_DWORD_NET(&rtpPayload[56], 0x00410000);   // default_sample_flags, sample_is_depended_on+sample_is_difference_sample
    }
    else {
        WRITE_DWORD_NET(&rtpPayload[56], 0x01010000);   // default_sample_flags, sample_depends_on+sample_is_difference_sample
    }

    int tfhdOff = 28;

    // tfdt
    WRITE_DWORD_NET(&rtpPayload[32+tfhdOff], 16);               // Length
    strcpy(&rtpPayload[36+tfhdOff], boxSuffixes[TRACK_FRAGMENT_DECODE_TIME_BOX].name);
    WRITE_DWORD_NET(&rtpPayload[40+tfhdOff], 0x00000000);       // Version+Flag, 32 bits for baseMediaDecodeTime
    if(lTimestamp) {
        WRITE_DWORD_NET(&rtpPayload[44+tfhdOff], sampleCompositionTimeOffset);              // baseMediaDecodeTime
    }
    else {
        WRITE_DWORD_NET(&rtpPayload[44+tfhdOff], 0);              // baseMediaDecodeTime
    }
    int offTfdt = 48+tfhdOff;

    // trun
    WRITE_DWORD_NET(&rtpPayload[offTfdt], 24+trunCnt*offFlags);     // Length
    strcpy(&rtpPayload[offTfdt+4], boxSuffixes[TRACK_FRAGMENT_RUN_BOX].name);
    if(offFlags == 4) {
        WRITE_DWORD_NET(&rtpPayload[offTfdt+8], 0x000205);         // Version+Flag, size
    }
    else if(offFlags == 8) {
#if 0
        WRITE_DWORD_NET(&rtpPayload[offTfdt+8], 0x000A05);         // Version+Flag, size+composition
#else
        WRITE_DWORD_NET(&rtpPayload[offTfdt+8], 0x000605);         // Version+Flag, size+composition
#endif
    }
    else if(offFlags == 12) {
        WRITE_DWORD_NET(&rtpPayload[offTfdt+8], 0x000E05);         // Version+Flag, size+flags+composition
    }
    else if(offFlags == 16) {
        WRITE_DWORD_NET(&rtpPayload[offTfdt+8], 0x000F05);         // Version+Flag, size+flags+composition
    }
    else {
        WRITE_DWORD_NET(&rtpPayload[offTfdt+8], 0x000005);         // Version+Flag, size+flags+composition
    }
    WRITE_DWORD_NET(&rtpPayload[offTfdt+12], trunCnt);          // sample_count
    WRITE_DWORD_NET(&rtpPayload[offTfdt+16], initLen+trunCnt*offFlags+8);  // data_offset
#if 0
    if(sliceType == 2) {
        WRITE_DWORD_NET(&rtpPayload[offTfdt+20], 0x00410000);   // default_sample_flags, sample_is_depended_on+sample_is_difference_sample
    }
    else {
        WRITE_DWORD_NET(&rtpPayload[offTfdt+20], 0x01010000);   // default_sample_flags, sample_depends_on+sample_is_difference_sample
    }
#else
    WRITE_DWORD_NET(&rtpPayload[offTfdt+20], 0x02000000);      // first_sample_flags;
#endif
    if(lTimestamp) sampleCompositionTimeOffset = (timestamp-fTimestamp);
    else sampleCompositionTimeOffset = 0;
    for(int i=0; i<trunCnt; i++) {
        if(offFlags == 4) {
            WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags], trunBuf[i]); // sample_size; 
        }
        else if(offFlags == 8) {
#if 0
            if(sliceType == 2) {
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags], trunBuf[i]); // sample_size; 
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags+4], sampleCompositionTimeOffset);  // sample_size; 
            }
            else {
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags], trunBuf[i]); // sample_size;  
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags+4], sampleCompositionTimeOffset); // sample_size; 
            }
#else
            if(sliceType == 2) {
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags], trunBuf[i]); // sample_size; 
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags+4], 0x00410000);  // sample_flags; 
            }
            else {
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags], trunBuf[i]); // sample_size;  
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags+4], 0x01010000); // sample_flags; 
            }
#endif
        }
        else if(offFlags == 12) {
            if(sliceType == 2) {
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags+0], trunBuf[i]); // sample_size;
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags+4], 0x00410000); // sample_flags;
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags+8], sampleCompositionTimeOffset);  // sample_composition_time_offset;
            }
            else {
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags+0], trunBuf[i]); // sample_size;
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags+4], 0x01010000); // sample_flags;
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags+8], sampleCompositionTimeOffset);  // sample_composition_time_offset;
            }
        }
        else if(offFlags == 16) {
            if(sliceType == 2) {
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags+0], (timestamp-lTimestamp)); // sample_duration;
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags+4], trunBuf[i]); // sample_size;
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags+8], 0x00410000); // sample_flags;
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags+12], sampleCompositionTimeOffset); // sample_composition_time_offset;
            }
            else {
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags+0], (timestamp-lTimestamp)); // sample_duration;
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags+4], trunBuf[i]); // sample_size;
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags+8], 0x01010000); // sample_flags;
                WRITE_DWORD_NET(&rtpPayload[offTfdt+24+i*offFlags+12], sampleCompositionTimeOffset); // sample_composition_time_offset;
            }
        }
    }

    *olen = offTfdt+24+trunCnt*offFlags;

    log->Log(LOG_VIDEO, "MP4Container(%p)::CreateMoofBox(%d) len=%d ts=%d off=%d comp=%d,%d", this, sliceType, *olen, timestamp, timestamp-lTimestamp, sampleCompositionTimeOffset, READ_DWORD_NET(&rtpPayload[108]));
    //log->HexDump(LOG_VIDEO, rtpPayload, *olen);

    numSamples++;

    return rtpPayload;
}

char * MP4Container::CreateFtypBox(int * olen)
{
    int totLength = 24;  // size + type + major(4), minor(4), list(16)
    char * rtpPayload = (char *)malloc(totLength+1);
    memset(rtpPayload, 0x0, totLength+1);

    WRITE_DWORD_NET(rtpPayload, totLength);
    strcpy(&rtpPayload[4], boxSuffixes[FILE_TYPE_BOX].name);
    strcpy(&rtpPayload[8], "iso5");
    WRITE_DWORD_NET(&rtpPayload[12], 512);
    strcpy(&rtpPayload[16], "iso6");
    strcpy(&rtpPayload[20], "mp41");
    *olen = totLength;

    log->Log(LOG_VIDEO, "MP4Container(%p)::CreateFtypBox ftyp=%d", this, totLength);
    //log->HexDump(LOG_VIDEO, rtpPayload, totLength);

    return rtpPayload;
}

int MP4Container::DecodeRbspTrailing(const byte *nal)
{
    int i, last = *nal;

    for(i=1; i<9; i++) {
        if(last & 1) return i;
        last >>= 1;
    }
    return 0;
}

static inline void init_get_bits(GetBitContext *s, const byte *buffer, int bit_size)
{
    int buffer_size = (bit_size+7)>>3;
    if(buffer_size < 0 || bit_size < 0) {
        buffer_size = bit_size = 0;
        buffer = NULL;
    }
    s->buffer       = buffer;
    s->size_in_bits = bit_size;
    s->buffer_end   = buffer + buffer_size;
    s->index        = 0;
}

static inline int av_log2(unsigned int v)
{
    int n = 0;
    if(v & 0xffff0000) {
        v >>= 16;
        n += 16;
    }
    if(v & 0xff00) {
        v >>= 8;
        n += 8;
    }
    n += ff_log2_tab[v];

    return n;
}

static inline int get_ue_golomb(GetBitContext *gb)
{
    unsigned int re_index = (gb)->index;
    unsigned int buf = AV_RB32(((const byte *)(gb)->buffer)+(re_index>>3)) << (re_index&0x07);

    if(buf >= (1<<27)){
        buf >>= 32 - 9;
        re_index += (ff_golomb_vlc_len[buf]);
        (gb)->index = re_index;

        return ff_ue_golomb_vlc_code[buf];
    }
    else{
        int log= 2*av_log2(buf) - 31;
        buf>>= log;
        buf--;
        re_index += (32 - log);
        (gb)->index = re_index;

        return buf;
    }
}

static inline int get_ue_golomb_31(GetBitContext *gb)
{
    unsigned int re_index = (gb)->index;
    unsigned int buf = AV_RB32(((const byte *)(gb)->buffer)+(re_index>>3)) << (re_index&0x07);

    buf >>= 32 - 9;
    re_index += (ff_golomb_vlc_len[buf]);
    (gb)->index = re_index;

    return ff_ue_golomb_vlc_code[buf];
}

static inline unsigned int get_bits(GetBitContext *gb, int n)
{
    int tmp;
    // OPEN_READER   
    unsigned int re_index = (gb)->index;
    unsigned int re_cache = 0;
    // UPDATE_CACHE
    re_cache = AV_RB32(((const byte *)(gb)->buffer) + (re_index >> 3)) << (re_index & 0x07);
    tmp = (((unsigned int)(re_cache)) >> (32 - (n)));
    re_index += (n);
    (gb)->index = re_index;
    return tmp;
}

static inline unsigned int get_bits1(GetBitContext *s)
{
    unsigned int index = s->index;
    byte result = s->buffer[index >> 3];
    result <<= index & 7;
    result >>= 8 - 1;
    index++;
    s->index = index;

    return result;
}

static inline int get_se_golomb(GetBitContext *gb)
{
    unsigned int buf;
    int log;

    // OPEN_READER   
    unsigned int re_index = (gb)->index;
    unsigned int re_cache = 0;
    // UPDATE_CACHE
    re_cache = AV_RB32(((const byte *)(gb)->buffer) + (re_index >> 3)) << (re_index & 0x07);
    buf = re_cache;

    if (buf >= (1 << 27)) {
        buf >>= 32 - 9;
        re_index += (ff_golomb_vlc_len[buf]);
        (gb)->index = re_index;
        return ff_se_golomb_vlc_code[buf];
    }
    else {
        log = 2 * av_log2(buf) - 31;
        buf >>= log;

        re_index += (32 - log);
        (gb)->index = re_index;

        if (buf & 1) buf = -(int)(buf >> 1);
        else      buf = (buf >> 1);

        return buf;
    }
}

void MP4Container::DecodeSPS(GetBitContext * gb)
{
	int constraintSetFlags = 0;
	int i;

	profile = get_bits(gb, 8);
	constraintSetFlags |= get_bits1(gb) << 0;   //constraint_set0_flag
	constraintSetFlags |= get_bits1(gb) << 1;   //constraint_set1_flag
	constraintSetFlags |= get_bits1(gb) << 2;   //constraint_set2_flag
	constraintSetFlags |= get_bits1(gb) << 3;   //constraint_set3_flag
    log->Log(LOG_VIDEO, "MP4Container::DecodeSPS constraintSetFlags=%x", constraintSetFlags);

	get_bits(gb, 4); // reserved
	level = get_bits(gb, 8);
	/*unsigned int spsId = */get_ue_golomb_31(gb);

//    int time_offset_length = 24;

	if (profile >= 100) {
		int chroma_format_idc = get_ue_golomb_31(gb);
		if (chroma_format_idc > 3) {
			debug->printf("chroma_format_idc %d is illegal", chroma_format_idc);
			return;
		}
		else if (chroma_format_idc == 3) {
			/*int separate_colour_plane_flag = */get_bits1(gb);
		}
		int bit_depth_luma = get_ue_golomb(gb) + 8;
		int bit_depth_chroma = get_ue_golomb(gb) + 8;
		if (bit_depth_luma > 12 || bit_depth_chroma > 12) {
			debug->printf("illegal bit depth value(%d, %d)", bit_depth_luma, bit_depth_chroma);
			return;
		}
		/*int qpprime_y_zero_transform_bypass_flag = */get_bits1(gb);
		/*int seq_scaling_matrix_present_flag = */get_bits1(gb);
		//get_scaling_lists(pCtx, sps, NULL, scaling_matrix4x4, scaling_matrix8x8);
	}
	else {
/*
		int chroma_format_idc = 1;
		int bit_depth_luma = 8;
		int bit_depth_chroma = 8;
		int qpprime_y_zero_transform_bypass_flag = 0;
		int seq_scaling_matrix_present_flag = 0;
*/
	}
	int log2_max_frame_num = get_ue_golomb(gb) + 4;
	// The value of log2_max_frame_num_minus4 shall be in the range of 0 to 12, inclusive.
	if (log2_max_frame_num < 4 || log2_max_frame_num > 16) {
		debug->printf("illegal log2_max_frame_num %d", log2_max_frame_num);
		return;
	}
	int poc_type = get_ue_golomb_31(gb);
	if (poc_type == 0) {
		/*int log2_max_poc_lsb = */get_ue_golomb(gb)/* + 4*/;
	}
	else if (poc_type == 1) {
		/*int delta_pic_order_always_zero_flag = */get_bits1(gb);
		/*int offset_for_non_ref_pic = */get_se_golomb(gb);
		/*int offset_for_top_to_bottom_field = */get_se_golomb(gb);
		int num_ref_frames_in_poc_cycle = get_ue_golomb(gb);
#if 0
		if ((unsigned)num_ref_frames_in_poc_cycle >= ARRAY_NUM_ELEMS(offset_for_ref_frame)) {
			return;
		}
#endif
		for (i = 0; i<num_ref_frames_in_poc_cycle; i++)
			/*int offset_for_ref_frame = */get_se_golomb(gb);
	}

	/*int max_num_ref_frames = */get_ue_golomb_31(gb);

	/*int gaps_in_frame_num_value_allowed_flag = */get_bits1(gb);
	int pic_width_in_mbs = get_ue_golomb(gb) + 1;
	int pic_height_in_map_units = get_ue_golomb(gb) + 1;

	width = pic_width_in_mbs << 4;
	height = pic_height_in_map_units << 4;

	debug->printf("H264(%d,%d) %dx%d", profile, level, width, height);
}

void MP4Container::DecodeSliceHeader(GetBitContext * gb)
{
    unsigned int fMbInSlice = get_ue_golomb(gb);
    sliceType = get_ue_golomb_31(gb);

    log->Log(LOG_VIDEO, "MP4Container(%p)::DecodeSliceHeader first_mb_in_slice %d sliceType %d", this, fMbInSlice, sliceType);

    if(sliceType > 9) {
        debug->printf("slice type too large(%d)", sliceType);
        sliceType = 0;
    }
    if(sliceType > 4) {
        sliceType -= 5;
    }
}

char * MP4Container::CreateMdatBox(char * buf, int len, int * olen)
{
    int startByte = 0;
    int bytesRead = 0;
    int totLength = 8;  // size + type

    while(bytesRead < len) {
        int tmpIdc, tmpType;

        int nalLength = calcNalLength(buf, &bytesRead, len, &startByte, &tmpIdc, &tmpType);
        if(tmpType == 7) {
            if(!sps) {
                sps = (char *)malloc(nalLength);
                memcpy(sps, &buf[startByte], nalLength);
                spsLength = nalLength;
            }
            continue;
        }
        else if(tmpType == 8) {
            if(!pps) {
                pps = (char *)malloc(nalLength);
                memcpy(pps, &buf[startByte], nalLength);
                ppsLength = nalLength;
            }
            continue;
        }
        else if(tmpType == 9) continue;

        totLength += 4+nalLength;
    }
    trunCnt = 1;

    if(totLength == 8) {
        debug->printf("MP4Container(%p)::CreateMdatBox Empty Frame! dlen=%d", this, len);
        return NULL;
    }

    char * rtpPayload = (char *)malloc(totLength);
    WRITE_DWORD_NET(rtpPayload, totLength);
    strcpy(&rtpPayload[4], boxSuffixes[MOVIE_DATA_BOX].name);

    startByte = 0;
    bytesRead = 0;
    totLength = 8;  // size + type

    int idxTrun = 0;
    trunBuf[idxTrun] = 0;
    while(bytesRead < len) {
        int tmpIdc, tmpType;

        int nalLength = calcNalLength(buf, &bytesRead, len, &startByte, &tmpIdc, &tmpType);
        if(tmpType == 8 || tmpType == 9) continue; // SPS/PPS/DELIMETER
        if(tmpType == 7) {
            log->Log(LOG_VIDEO, "MP4Container(%p)::CreateMdatBox nalUnitType(SPS) %d", this, tmpType);
            int tLen = nalLength;
            char * nal = &buf[startByte + 1];
            // remove 0's at the end
            while (nal[tLen - 1] == 0 && tLen > 0) {
                tLen--;
            }
            int bitLength = !tLen ? 0 : (8 * tLen - DecodeRbspTrailing((const byte *)&nal[tLen - 1]));
            init_get_bits(&gb, (const byte *)&buf[startByte + 1], bitLength);
            DecodeSPS(&gb);
            continue;
        }

        int nalUnitType = (tmpType & 0x1F);
        if(nalUnitType == 1 || nalUnitType == 5) {
            log->Log(LOG_VIDEO, "MP4Container(%p)::CreateMdatBox nalUnitType %d startByte %d", this, nalUnitType, startByte);
            int tLen = nalLength;
            char * nal = &buf[startByte+1];
            // remove 0's at the end
            while(nal[tLen-1] == 0 && tLen > 0) {
                tLen--;
            }
            int bitLength = !tLen ? 0 : (8*tLen - DecodeRbspTrailing((const byte *)&nal[tLen-1]));
            init_get_bits(&gb, (const byte *)&buf[startByte+1], bitLength);
            DecodeSliceHeader(&gb);
            log->Log(LOG_VIDEO, "MP4Container(%p)::CreateMdatBox DecodeSliceHeader %d,%d (%d,%d)", this, sliceType, bitLength, nalLength, tLen);
        }

        WRITE_DWORD_NET(&rtpPayload[totLength], nalLength);
        memcpy(&rtpPayload[4+totLength], &buf[startByte], nalLength);
        totLength += 4+nalLength;
        trunBuf[idxTrun] += 4+nalLength;
    }

    log->Log(LOG_VIDEO, "MP4Container(%p)::CreateMdatBox len=%d mdat=%d trun=%d idxTrun=%d lenTrun=%d", this, len, totLength, trunCnt, idxTrun, trunBuf[idxTrun]);
    //log->HexDump(LOG_VIDEO, rtpPayload, totLength);

    *olen = totLength;
    return rtpPayload;
}

void MP4Container::PutSample(char * buf, int len, dword timestamp)
{
    ulong64 nTime = ITime::TimeStampMilliseconds();
    log->Log(LOG_VIDEO, "MP4Container(%p)::PutSample %d,%d timestamp=%d firstTimestamp=%d lastTimestamp=%d off=%d offLast=%d time=%llu lTime=%llu off=%llu", this, len, offDuration, timestamp, fTimestamp, lTimestamp, timestamp-fTimestamp, timestamp-lTimestamp, nTime, lTime, nTime-lTime);
    lTime = nTime;

    if(fpH264) fwrite(buf, len, 1, fpH264);

    if(rxSample) {
        if(offDuration) {
            WRITE_DWORD_NET(&rxSample[offDuration], (timestamp-lTimestamp));
        }
        if(user) user->ContainerSample(rxSample, rxSampleLen);
        if(fp) fwrite(rxSample, 1, rxSampleLen, fp);
        free(rxSample);
        rxSample = NULL;
    }

    int ftypLen = 0, moovLen = 0, moofLen, mdatLen;
    char * mdat = CreateMdatBox(buf, len, &mdatLen);  // trun before moof!
    if(mdat == NULL) {
        return;
    }
    char * moof = CreateMoofBox(&moofLen, len, timestamp);
    char * ftyp = NULL;
    char * moov = NULL;

    if(first && sps && pps) {
        ftyp = CreateFtypBox(&ftypLen);
        moov = CreateMoovBox(&moovLen);
        first = false;
        fTimestamp = timestamp;
    }

    if(!first) {
        rxSample = (char *)malloc(ftypLen+moovLen+moofLen+mdatLen);
        if(rxSample) {
            rxSampleLen = ftypLen+moovLen+moofLen+mdatLen;
            if(ftypLen && moovLen) {
                memcpy(rxSample, ftyp, ftypLen);
                memcpy(&rxSample[ftypLen], moov, moovLen);
                if(offDuration) offDuration += ftypLen + moovLen;
            }
            memcpy(&rxSample[ftypLen+moovLen], moof, moofLen);
            memcpy(&rxSample[ftypLen+moovLen+moofLen], mdat, mdatLen);
        }
        lTimestamp = timestamp;
    }
    if(ftyp) free(ftyp);
    if(moov) free(moov);
    free(moof);
    free(mdat);
}

WebMContainer::WebMContainer(class UMediaContainer * const user, class IInstanceLog * const log, bool vp9):
user(user),
log(log)
{
    this->vp9 = vp9;
    first = true;
    rxSample = NULL;
    offDuration = 0;
    rxSampleLen = 0;
    fTimestamp = 0;
    lTimestamp = 0;
    lTime = 0;
    keyframe = false;
    keyframeTimeOffset = 0;
    width = 0;
    height = 0;
    horScale = 0;
    verScale = 0;
    sampleCompositionTimeOffset = 0;
#if RECORD_VIDEO
    ulong64 tTime = ITime::TimeStampMilliseconds() / 100;
    char videoFile[256];
    _sprintf(videoFile, "C:\\temp\\video_%p_%p_%llu.webm", this, user, tTime);
    fp = fopen(videoFile, "w+b");
#else
    fp = NULL;
#endif
    log->Log(LOG_VIDEO, "WebMContainer(%p)::WebMContainer vpx=%d", this, vp9);
}

WebMContainer::~WebMContainer()
{
    log->Log(LOG_VIDEO, "WebMContainer(%p)::~WebMContainer", this);

    if(rxSample) free(rxSample);
    if(fp) fclose(fp);
}

void WebMContainer::Initialize(void)
{
    log->Log(LOG_VIDEO, "WebMContainer(%p)::Initialize", this);

    first = true;
    if (rxSample) { free(rxSample); rxSample = NULL; }
    if (fp) { 
        fclose(fp); fp = NULL; 
        char videoFile[256];
        ulong64 tTime = ITime::TimeStampMilliseconds() / 100;
        _sprintf(videoFile, "C:\\temp\\video_%p_%p_%llu.webm", this, user, (unsigned long long)tTime);
        fp = fopen(videoFile, "w+b");
    }
    offDuration = 0;
    rxSampleLen = 0;
    fTimestamp = 0;
    lTimestamp = 0;
    lTime = 0;
    keyframe = false;
    keyframeTimeOffset = 0;
    width = 0;
    height = 0;
    horScale = 0;
    verScale = 0;
    sampleCompositionTimeOffset = 0;
}

char * WebMContainer::CreateWebMHeader(int * olen)
{
    int totLength = 36;
    byte * buf = (byte *)malloc(totLength);
    memset(buf, 0x0, totLength);

    WRITE_DWORD_NET(buf, 0x1A45DFA3);
    buf[4] = 0x9F;
    WRITE_WORD_NET(&buf[5], 0x4286);   // EBMLVersion
    WRITE_WORD_NET(&buf[7], 0x8101);   // Length = 1, Value = 1
    WRITE_WORD_NET(&buf[9], 0x42F7);   // EBMLReadVersion
    WRITE_WORD_NET(&buf[11], 0x8101);  // Length = 1, Value = 1
    WRITE_WORD_NET(&buf[13], 0x42F2);  // EBMLMaxIDLength
    WRITE_WORD_NET(&buf[15], 0x8104);  // Length = 1, Value = 4
    WRITE_WORD_NET(&buf[17], 0x42F3);  // EBMLMaxSizeLength
    WRITE_WORD_NET(&buf[19], 0x8108);  // Length = 1, Value = 8
    WRITE_WORD_NET(&buf[21], 0x4282);  // DocType
    buf[23] = 0x84;                    // Length = 4
    strcpy((char *)&buf[24], "webm");
    WRITE_WORD_NET(&buf[28], 0x4287);  // DocTypeVersion
    WRITE_WORD_NET(&buf[30], 0x8101);  // Length = 1, Value = 1
    WRITE_WORD_NET(&buf[32], 0x4285);  // DocTypeReadVersion
    WRITE_WORD_NET(&buf[34], 0x8101);  // Length = 1, Value = 1

    *olen = totLength;

    return (char *)buf;
}

char * WebMContainer::CreateSegment(int * olen)
{
    int totLength = 1024;
    byte * buf = (byte *)malloc(totLength);
    memset(buf, 0x0, totLength);

    // Segment
    WRITE_DWORD_NET(buf, 0x18538067);
    WRITE_QUAD_NET(&buf[4], 0x01FFFFFFFFFFFFFF);

    int offLen = 12;
    // Seek Head
    WRITE_DWORD_NET(&buf[offLen], 0x114D9B74);       // hdrLen+5 
    buf[offLen+4] = 0xAA;                            // 0x80 | 42
    WRITE_WORD_NET(&buf[offLen+5], 0x4DBB);          // Seek
    buf[offLen+7] = 0x8B;
    WRITE_WORD_NET(&buf[offLen+8], 0x53AB);          // SeekID
    buf[offLen+10] = 0x84;
    WRITE_DWORD_NET(&buf[offLen+11], 0x1549A966);    // Segment Info
    WRITE_WORD_NET(&buf[offLen+15], 0x53AC);         // SeekPosition
    buf[offLen+17] = 0x81;
    buf[offLen+18] = 0x2F;                           // Position, 47 
    WRITE_WORD_NET(&buf[offLen+19], 0x4DBB);         // Seek
    buf[offLen+21] = 0x8B;
    WRITE_WORD_NET(&buf[offLen+22], 0x53AB);         // SeekID
    buf[offLen+24] = 0x84;
    WRITE_DWORD_NET(&buf[offLen+25], 0x1654AE6B);    // Media Tracks Info
    WRITE_WORD_NET(&buf[offLen+29], 0x53AC);         // SeekPosition
    buf[offLen+31] = 0x81;
    buf[offLen+32] = 0x0;                            // Position
    int mediaPosLen = offLen+32;
    WRITE_WORD_NET(&buf[offLen+33], 0x4DBB);         // Seek
    buf[offLen+35] = 0x8B;
    WRITE_WORD_NET(&buf[offLen+36], 0x53AB);         // SeekID
    buf[offLen+38] = 0x84;
    WRITE_DWORD_NET(&buf[offLen+39], 0x1F43B675);    // Cluster
    WRITE_WORD_NET(&buf[offLen+43], 0x53AC);         // SeekPosition
    buf[offLen+45] = 0x81;
    buf[offLen+46] = 0x0;                            // Position
    int clusterPosLen = offLen+46;

    // Segment Info
    offLen += 47;
    WRITE_DWORD_NET(&buf[offLen], 0x1549A966);    // Segment Info
    buf[offLen+4] = 0x80 | (65-5);                // Length
    WRITE_WORD_NET(&buf[offLen+5], 0x73A4);       // SegmentUID
    buf[offLen+7] = 0x90;                         // Length = 0x10 -> 16
    OS_GUID id;
    IGuuid::GenerateBinaryUUID(&id);
    memcpy(&buf[offLen+8], id.b, 16);
    buf[offLen+24] = 0x2A;                        // TimecodeScale
    buf[offLen+25] = 0xD7;
    buf[offLen+26] = 0xB1;
    WRITE_DWORD_NET(&buf[offLen+27], 0x830F4240); // 1000000
#if FILE_DURATION
    buf[offLen+4] = 0x80 | (65-5);                // Length
    WRITE_WORD_NET(&buf[offLen+31], 0x4489);      // Duration
    buf[offLen+33] = 0x84;
    WRITE_DWORD_NET(&buf[offLen+34], 0x1);        // Duration = 1, must be 4 bytes
    WRITE_WORD_NET(&buf[offLen+38], 0x4461);      // DateUTC
    buf[offLen+40] = 0x88;
    ulong64 t = ITime::TimeStampMilliseconds();
    WRITE_QUAD_LE(&buf[offLen+41], t);            // DateUTC
    int fileOffset = 49;
#elif FILE_UTC_DURATION
    buf[offLen + 4] = 0x80 | (58 - 5);              // Length
    WRITE_WORD_NET(&buf[offLen + 31], 0x4461);      // DateUTC
    buf[offLen + 33] = 0x88;
    ulong64 t = ITime::TimeStampMilliseconds();
    WRITE_QUAD_LE(&buf[offLen + 34], t);            // DateUTC
    int fileOffset = 42;
#else
    buf[offLen+4] = 0x80 | (47-5);                // Length
    int fileOffset = 31;
#endif
    WRITE_WORD_NET(&buf[offLen+fileOffset], 0x4D80);      // MuxingApp
    buf[offLen+fileOffset+2] = 0x85;
    strcpy((char *)&buf[offLen+fileOffset+3], "mypbx");
    WRITE_WORD_NET(&buf[offLen+fileOffset+8], 0x5741);      // WritingApp
    buf[offLen+fileOffset+10] = 0x85;
    strcpy((char *)&buf[offLen+fileOffset+11], "mypbx");     // totLength + 1, \0 at the end

    buf[mediaPosLen] = 47+fileOffset+16;

    int mediaTrackLen = CreateMediaTracks(&buf[offLen+fileOffset+16]);

    buf[clusterPosLen] = 47+fileOffset+16+mediaTrackLen;

    offLen += fileOffset+16+mediaTrackLen;
#if 0
    // Cluster Info
    WRITE_DWORD_NET(&buf[offLen], 0x1F43B675);     // Cluster
    WRITE_QUAD_NET(&buf[offLen+4], 0x1FFFFFFFFFFFFFF);
    buf[offLen+12] = 0xE7;                         // Timecode 
    WRITE_WORD_NET(&buf[offLen+13], 0x8100);       // Start = 0
    buf[offLen+15] = 0xA7;                         // Position 
    buf[offLen+16] = 0x81;                         // Length
    buf[offLen+17] = 47+65+mediaTrackLen;          // Cluster Position

    *olen = offLen+18;
#else
    *olen = offLen;
#endif

    return (char *)buf;
}

int WebMContainer::CreateMediaTracks(byte * buf)
{
    int totLength = 5;
    
    WRITE_DWORD_NET(buf, 0x1654AE6B);
    totLength += CreateVideoTrack(&buf[totLength]);
    buf[4] = 0x80 | (totLength-5);                // Tracks Entry Length
    
    return totLength;
}

int WebMContainer::CreateVideoTrack(byte * buf)
{
#if BLOCK_DURATION
    int totLength = 59;
#else
    int totLength = 51;
#endif

#if !TRACK_TIMESCALE
    totLength -= 8;
#endif

    int offLen = 0;

    buf[0] = 0xAE;                         // Track Entry
    buf[1] = 0x80 | (totLength-2);         // Track Entry Length
    buf[2] = 0xD7;                         // Track Number
    WRITE_WORD_NET(&buf[3], 0x8101);       // Length = 1, Value = 1 
    WRITE_WORD_NET(&buf[5], 0x73C5);       // Track UID
    buf[7] = 0x88;                         // 8 Bytes
    OS_GUID id;
    IGuuid::GenerateBinaryUUID(&id);
    memcpy(&buf[8], id.b, 8);
    buf[16] = 0x83;                        // TrackType
    WRITE_WORD_NET(&buf[17], 0x8101);      // Length = 1, Value = 1 
#if BLOCK_DURATION
    WRITE_DWORD_NET(&buf[19], 0x23E38384); // DefaultDuration, 4 Bytes
#if 1
    WRITE_DWORD_NET(&buf[23], 66666667);   // DefaultDuration = 33 msec, in nanoseconds
#else
    WRITE_DWORD_NET(&buf[23], 00000001);   // DefaultDuration = 33 msec, in nanoseconds
#endif
    offLen = 27;
#else
    offLen = 19;
#endif

#if TRACK_TIMESCALE
    WRITE_DWORD_NET(&buf[offLen+0], 0x23314F84); // TrackTimecodeScale, 4 Bytes
    WRITE_DWORD_NET(&buf[offLen+4], 0x3F800000); // Duration = 1, must be 4 bytes
    offLen += 8;
#endif
    WRITE_WORD_NET(&buf[offLen+0], 0x8685);      // CodecID, 5 Bytes
    if(vp9) {
        strcpy((char *)&buf[offLen+2], "V_VP9");
        WRITE_DWORD_NET(&buf[offLen+7], 0x25868883); // CodecName, 3 Bytes
        strcpy((char *)&buf[offLen+11], "VP9");
    }
    else {
        strcpy((char *)&buf[offLen+2], "V_VP8");
        WRITE_DWORD_NET(&buf[offLen+7], 0x25868883); // CodecName, 3 Bytes
        strcpy((char *)&buf[offLen+11], "VP8");
    } 
    WRITE_WORD_NET(&buf[offLen+14], 0xE088);     // Video, 8 Bytes
    WRITE_WORD_NET(&buf[offLen+16], 0xB082);     // height, 2 Bytes
    WRITE_WORD_NET(&buf[offLen+18], width);      // 
    WRITE_WORD_NET(&buf[offLen+20], 0xBA82);     // height, 2 Bytes
    WRITE_WORD_NET(&buf[offLen+22], height);     // 

    return totLength;
}

char * WebMContainer::CreateCluster(char * data, int len, dword timestamp, int * olen)
{
    int totLength = len+128;
    byte * buf = (byte *)malloc(totLength);
    int offLen = 0, offDataLen, offDataBlock, kfOffset = 0;
    dword tsOffset = (timestamp-keyframeTimeOffset)/100;

    if(len+4 < 127) offDataLen = 2;
    else if(len+4 < 16383) offDataLen = 3;
    else offDataLen = 4;

    sampleCompositionTimeOffset = (timestamp-fTimestamp)/100;

    if(keyframe && (data[0] & 0x1)) {
        if(tsOffset < 128) kfOffset = 3;
        else if(tsOffset < 32768) kfOffset = 4;
        else if(tsOffset < 0x800000) kfOffset = 5;
        else kfOffset = 6;
    }

#if BLOCK_DURATION2
    // ReferencePriority+ReferenceBlock+[A1]
    if(3+kfOffset+offDataLen+3+len+4 < 127) offDataBlock = 2;
    else if(3+kfOffset+offDataLen+3+len+4 < 16383) offDataBlock = 3;
    else offDataBlock = 4;

    int totalLength = offDataBlock+3+kfOffset+offDataLen+3+len+4;
#else
    // ReferencePriority+ReferenceBlock+[A1]
    if(3+kfOffset+offDataLen+len+4 < 127) offDataBlock = 2;
    else if(3+kfOffset+offDataLen+len+4 < 16383) offDataBlock = 3;
    else offDataBlock = 4;

    int totalLength = offDataBlock+3+kfOffset+offDataLen+len+4;
#endif

    if(sampleCompositionTimeOffset < 256) totalLength += 3;
    else if(sampleCompositionTimeOffset < 0x10000) totalLength += 4;
    else if(sampleCompositionTimeOffset < 0x1000000) totalLength += 5;
    else totalLength += 6;
  
    if(totalLength < 127) {
        WRITE_DWORD_NET(buf, 0x1F43B675);     // Cluster
        buf[4] = 0x80 | totalLength;
        offLen = 5;
    }
    else if(totalLength < 16383) {
        WRITE_DWORD_NET(buf, 0x1F43B675);     // Cluster
        WRITE_WORD_NET(&buf[4], 0x4000 | totalLength);
        offLen = 6;
    }
    else {
        WRITE_DWORD_NET(&buf[3], 0x200000 | totalLength);
        WRITE_DWORD_NET(buf, 0x1F43B675);     // Cluster
        offLen = 7;
    }

    if(sampleCompositionTimeOffset < 256) {
        buf[offLen+0] = 0xE7;                         // Timecode 
        buf[offLen+1] = 0x81;                         // Length
        buf[offLen+2] = sampleCompositionTimeOffset;  // Time
        offLen += 3;
    }
    else if(sampleCompositionTimeOffset < 0x10000) {
        buf[offLen+0] = 0xE7;                         // Timecode 
        buf[offLen+1] = 0x82;                         // Length
        WRITE_WORD_NET(&buf[offLen+2], sampleCompositionTimeOffset);        // Time
        offLen += 4;
    }
    else if(sampleCompositionTimeOffset < 0x1000000) {
        buf[offLen+0] = 0xE7;                         // Timecode 
        WRITE_DWORD_NET(&buf[offLen+1], 0x83000000 | sampleCompositionTimeOffset);        // Length+Time
        offLen += 5;
    }
    else {
        buf[offLen+0] = 0xE7;                         // Timecode 
        buf[offLen+1] = 0x84;                         // Length
        WRITE_DWORD_NET(&buf[offLen+2], sampleCompositionTimeOffset);        // Time
        offLen += 6;
    }

    if(offDataBlock == 2) {
        buf[offLen+0] = 0xA0;
#if BLOCK_DURATION2
        buf[offLen+1] = 0x80 | (3+kfOffset+offDataLen+3+len+4);
#else
        buf[offLen+1] = 0x80 | (3+kfOffset+offDataLen+len+4);
#endif
    }
    else if(offDataBlock == 3) {
        buf[offLen+0] = 0xA0;
#if BLOCK_DURATION2
        WRITE_WORD_NET(&buf[offLen+1], 0x4000 | (3+kfOffset+offDataLen+3+len+4));
#else
        WRITE_WORD_NET(&buf[offLen+1], 0x4000 | (3+kfOffset+offDataLen+len+4));
#endif
    }
    else {
#if BLOCK_DURATION2
        WRITE_DWORD_NET(&buf[offLen+0], 0x200000 | (3+kfOffset+offDataLen+3+len+4));
#else
        WRITE_DWORD_NET(&buf[offLen+0], 0x200000 | (3+kfOffset+offDataLen+len+4));
#endif
        buf[offLen+0] = 0xA0;
    }
    offLen += offDataBlock;

    buf[offLen+0] = 0xFA;
    buf[offLen+1] = 0x81;
    if(!(data[0] & 0x1)) buf[offLen+2] = 0x0;
    else buf[offLen+2] = 0x1;
    offLen += 3;
    if(keyframe && (data[0] & 0x1)) {
        if(tsOffset < 128) {
            buf[offLen+0] = 0xFB;
            WRITE_WORD_NET(&buf[offLen+1], 0x8100 | tsOffset);
            offLen += 3;
        }
        else if(tsOffset < 32768) {
            WRITE_DWORD_NET(&buf[offLen+0], 0xFB820000 | tsOffset);
            offLen += 4;
        }
        else if(tsOffset < 0x800000) {
            buf[offLen+0] = 0xFB;
            WRITE_DWORD_NET(&buf[offLen+1], 0x83000000 | tsOffset);
            offLen += 5;
        }
        else {
            buf[offLen+0] = 0xFB;
            buf[offLen+1] = 0x84;
            WRITE_DWORD_NET(&buf[offLen+2], tsOffset);
            offLen += 6;
        }             
    }

    if(offDataLen == 2) {
#if BLOCK_DURATION2
        buf[offLen+0] = 0x9B;
        buf[offLen+1] = 0x81;
        offDuration = offLen+2;
        buf[offLen+3] = 0xA1;
        buf[offLen+4] = 0x80 | (len+4);
        offLen += offDataLen+3;
#else
        buf[offLen+0] = 0xA1;
        buf[offLen+1] = 0x80 | (len+4);
        offLen += offDataLen;
#endif
    }
    else if(offDataLen == 3) {
#if BLOCK_DURATION2
        buf[offLen+0] = 0x9B;
        buf[offLen+1] = 0x81;
        offDuration = offLen+2;
        buf[offLen+3] = 0xA1;
        WRITE_WORD_NET(&buf[offLen+4], 0x4000 | (len+4));
        offLen += offDataLen+3;
#else
        buf[offLen+0] = 0xA1;
        WRITE_WORD_NET(&buf[offLen+1], 0x4000 | (len+4));
        offLen += offDataLen;
#endif
    }
    else {
#if BLOCK_DURATION2
        buf[offLen+0] = 0x9B;
        buf[offLen+1] = 0x81;
        offDuration = offLen+2;
        WRITE_DWORD_NET(&buf[offLen+3], 0x200000 | (len+4));
        buf[offLen+3] = 0xA1;
        offLen += offDataLen+3;
#else
        WRITE_DWORD_NET(&buf[offLen], 0x200000 | (len+4));
        buf[offLen] = 0xA1;
        offLen += offDataLen;
#endif
    }

    buf[offLen] = 0x81;
    WRITE_WORD_NET(&buf[offLen+1], 0);
    buf[offLen+3] = 0x0;
    memcpy(&buf[offLen+4], data, len);

    *olen = offLen+4+len;

    //debug->printf("CreateCluster %d,%d %d,%d,%d %d,%d,%d %d,%d tot=%d,%d", len, totalLength, tsOffset, timestamp, keyframeTimeOffset, offDataBlock, kfOffset, offDataLen, (data[0] & 0x1), keyframe, *olen, offLen);

    if(!(data[0] & 0x1)) {
        keyframe = true;
        keyframeTimeOffset = timestamp;
    }

    return (char *)buf;
}

void WebMContainer::PutSample(char * buf, int len, dword timestamp)
{
    ulong64 nTime = ITime::TimeStampMilliseconds();
    log->Log(LOG_VIDEO, "WebMContainer(%p)::PutSample %d,%d timestamp=%d firstTimestamp=%d lastTimestamp=%d off=%d offLast=%d offKey=%d time=%llu lTime=%llu off=%llu key=%d rxSample=%p", this, len, offDuration, timestamp, fTimestamp, lTimestamp, timestamp-fTimestamp, timestamp-lTimestamp, timestamp-keyframeTimeOffset, nTime, lTime, nTime-lTime, !(buf[0] & 0x1), rxSample);

    if(rxSample) {
        if(offDuration) rxSample[offDuration] = (timestamp-lTimestamp)/100;
        if(user) user->ContainerSample(rxSample, rxSampleLen);
        if(fp) fwrite(rxSample, 1, rxSampleLen, fp);
        free(rxSample);
        rxSample = NULL;
        offDuration = 0;
    }

#if 0
    if (lTimestamp && (timestamp - lTimestamp > 25000)) {
        log->Log(LOG_VIDEO, "WebMContainer(%p)::PutSample ReInit!!!");
        debug->printf("WebMContainer(%p)::PutSample ReInit!!! %d -> %d", this, timestamp, lTimestamp);
        Initialize();
    }
#endif

    /*if(keyframe && !(buf[0] & 0x1)) {
        log->Log(LOG_VIDEO, "WebMContainer(%p)::Consecutive keyframes", this);
        return;
    }*/

    int hdrLen = 0, segLen = 0, clusterLen = 0;
    char * hdr = NULL;
    char * seg = NULL;
    char * clu = NULL;
    byte * tBuf = (byte *)buf;

    if((!(tBuf[0] & 0x1)) && (tBuf[3] == 0x9d && tBuf[4] == 0x01 && tBuf[5] == 0x2a)) {
        log->Log(LOG_VIDEO, "WebMContainer(%p)::PutSample %x %x %x %x", this, buf[6], buf[7], buf[8], buf[9]);
        dword tmp = (dword)buf[7];
        width = ((tmp << 8) | (byte)buf[6]) & 0x3fff;
        horScale = ((tmp << 8) | (byte)buf[6]) >> 14;
        tmp = (dword)buf[9];
        height = ((tmp << 8) | (byte)buf[8]) & 0x3fff;
        verScale = ((tmp << 8) | (byte)buf[8]) >> 14;
        log->Log(LOG_VIDEO, "WebMContainer(%p)::KeyFrame %dx%d %dx%d", this, width, height, horScale, verScale);
    }

    if (!width || !height) return;

    if(first) {
        hdr = CreateWebMHeader(&hdrLen);
        seg = CreateSegment(&segLen);
        first = false;
        fTimestamp = timestamp;
    }

    clu = CreateCluster(buf, len, timestamp, &clusterLen);

    if(!first) {
        rxSample = (char *)malloc(hdrLen+segLen+clusterLen);
        if(rxSample) {
            rxSampleLen = hdrLen+segLen+clusterLen;
            if(hdrLen && segLen) {
                memcpy(rxSample, hdr, hdrLen);
                memcpy(&rxSample[hdrLen], seg, segLen);
            }
            if(offDuration) offDuration += hdrLen + segLen;
            if(clusterLen) memcpy(&rxSample[hdrLen+segLen], clu, clusterLen);
        }
        lTimestamp = timestamp;
    }

    if(clu) free(clu);
    if(hdr) free(hdr);
    if(seg) free(seg);

    lTime = nTime;
}