//------------------------------------------------------------------------------
// File: Sample.cpp
//
// Desc: DirectShow sample code - implementation of CSample class.
//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------------------------


#include "stdafx.h"

#define FIX_LOCK_NAME
#include <dmo.h>

#include <limits.h>     //  _I64_MAX
#include <crtdbg.h>
#include <dmoimpl.h>
#include <uuids.h>      // DirectShow media type guids
#include <amvideo.h>    // VIDEOINFOHEADER definition
#include "resource.h"
#include "state.h"
#include "Sample.h"
#include "util.h"       // Helpers

#pragma warning(disable:4100)  // Disable C4100: unreferenced formal parameter


HRESULT CSample::InternalGetInputStreamInfo(DWORD dwInputStreamIndex, DWORD *pdwFlags)
{
    //  We can process data on any boundary
    *pdwFlags = 0;
    return S_OK;
}

HRESULT CSample::InternalGetOutputStreamInfo(DWORD dwOutputStreamIndex, DWORD *pdwFlags)
{
    //  We output single frames
    if (0 == dwOutputStreamIndex) {
        *pdwFlags = DMO_OUTPUT_STREAMF_WHOLE_SAMPLES |
                    DMO_OUTPUT_STREAMF_SINGLE_SAMPLE_PER_BUFFER |
                    DMO_OUTPUT_STREAMF_FIXED_SAMPLE_SIZE;
    } else {
        //  Stream 1
        //  Just text, no special buffering but 1 sample per sample
        *pdwFlags = DMO_OUTPUT_STREAMF_SINGLE_SAMPLE_PER_BUFFER |
                    DMO_OUTPUT_STREAMF_OPTIONAL;
    }
    return S_OK;
}

HRESULT CSample::InternalCheckInputType(DWORD dwInputStreamIndex, const DMO_MEDIA_TYPE *pmt)
{
    //  Check if the type is already set and if so reject any type that's not identical
    if (InputTypeSet(dwInputStreamIndex)) {
        if (!TypesMatch(pmt, InputType(dwInputStreamIndex))) {
            return DMO_E_INVALIDTYPE;
        } else {
            return S_OK;
        }
    }
    
    //  We accept MEDIATYPE_Video, MEDIASUBTYPE_MPEG1Video
    //  Check the format is defined
    if (pmt->majortype == MEDIATYPE_Video &&
        pmt->subtype == MEDIASUBTYPE_MPEG1Payload &&
        pmt->formattype == FORMAT_MPEGVideo &&
        pmt->pbFormat != NULL) {
        return S_OK;
    } else {
        return DMO_E_INVALIDTYPE;
    }
}

HRESULT CSample::InternalCheckOutputType(DWORD dwOutputStreamIndex, const DMO_MEDIA_TYPE *pmt)
{
    //  Check if the type is already set and if so reject any type that's not identical
    if (OutputTypeSet(dwOutputStreamIndex)) {
        if (!TypesMatch(pmt, OutputType(dwOutputStreamIndex))) {
            return DMO_E_INVALIDTYPE;
        } else {
            return S_OK;
        }
    }
    //  We output frames on stream 1
    if (dwOutputStreamIndex == 0) {
        if (!InputTypeSet(0)) {
            return DMO_E_INVALIDTYPE;
        }

        if (pmt->majortype == MEDIATYPE_Video &&
            pmt->subtype == MEDIASUBTYPE_RGB565 &&
            pmt->formattype == FORMAT_VideoInfo &&
            pmt->pbFormat != NULL) {
            const VIDEOINFOHEADER *pvihInput = (const VIDEOINFOHEADER *)InputType(0)->pbFormat;
            const VIDEOINFOHEADER *pvihOutput = (const VIDEOINFOHEADER *)pmt->pbFormat;

            LONG lWidth, lHeight;

            if (IsRectEmpty(&pvihOutput->rcTarget)) {
                lWidth = pvihOutput->bmiHeader.biWidth;
                lHeight = pvihOutput->bmiHeader.biHeight;
            } else {
                lWidth = pvihOutput->rcTarget.right - pvihOutput->rcTarget.left;
                lHeight = pvihOutput->rcTarget.bottom - pvihOutput->rcTarget.top;
            }

            if (pvihInput->bmiHeader.biWidth == lWidth &&
                pvihInput->bmiHeader.biHeight == lHeight) {
                return S_OK;
            }
        }

        return DMO_E_INVALIDTYPE;

    } else {
        //  Stream 1
        if (pmt->majortype == MEDIATYPE_Text && pmt->subtype == GUID_NULL) {
            return S_OK;
        } else {
            return DMO_E_INVALIDTYPE;
        }
    }
}


HRESULT CSample::InternalGetInputType(DWORD dwInputStreamIndex, DWORD dwTypeIndex,
                                      DMO_MEDIA_TYPE *pmt)
{
    //  No types to all indices for dwTypeIndex are out of range.
    return DMO_E_NO_MORE_ITEMS;
}

HRESULT CSample::InternalGetOutputType(DWORD dwOutputStreamIndex, DWORD dwTypeIndex,
                                       DMO_MEDIA_TYPE *pmt)
{
    if (!InputTypeSet(0)) {
        return DMO_E_TYPE_NOT_SET;
    }

    if (dwTypeIndex != 0) {
        return DMO_E_NO_MORE_ITEMS;
    }

    // If GetOutputType()'s pmt parameter is NULL, return S_OK if the type exists.
    // Return DMO_E_NO_MORE_ITEMS if the type does not exists.  See the 
    // documentation for IMediaObject::GetOutputType() for more information.
    if (NULL != pmt) {
        if (dwOutputStreamIndex == 0) {

            //  Create our media type
            HRESULT hr = MoInitMediaType(pmt, FIELD_OFFSET(VIDEOINFO, dwBitMasks[3]));
            if (FAILED(hr)) {
                return hr;
            }

            const VIDEOINFOHEADER *pvihInput = (const VIDEOINFOHEADER *)InputType(0)->pbFormat;
            LONG lWidth  = pvihInput->bmiHeader.biWidth;
            LONG lHeight = pvihInput->bmiHeader.biHeight;

            //  Initialize the media type structure (MoInitMediaType initalized cbFormat
            //  and pbFormat)
            pmt->majortype            = MEDIATYPE_Video;
            pmt->subtype              = MEDIASUBTYPE_RGB565;
            pmt->bFixedSizeSamples    = TRUE;
            pmt->bTemporalCompression = FALSE;
            pmt->lSampleSize          = lWidth * lHeight * 2;
            pmt->formattype           = FORMAT_VideoInfo;
            pmt->pUnk                 = NULL;
            
            //  Initialize the format
            VIDEOINFO *pviOutput = (VIDEOINFO *)pmt->pbFormat;
            ZeroMemory(pviOutput, FIELD_OFFSET(VIDEOINFO, dwBitMasks[3]));
            pviOutput->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
            pviOutput->bmiHeader.biCompression = BI_BITFIELDS;
            pviOutput->bmiHeader.biBitCount = 16;
            pviOutput->bmiHeader.biPlanes = 1;
            pviOutput->bmiHeader.biWidth = lWidth;
            pviOutput->bmiHeader.biHeight = lHeight;
            pviOutput->bmiHeader.biSizeImage = pmt->lSampleSize;
            pviOutput->TrueColorInfo.dwBitMasks[0] = 0xF800;
            pviOutput->TrueColorInfo.dwBitMasks[1] = 0x07E0;
            pviOutput->TrueColorInfo.dwBitMasks[2] = 0x001F;
            pviOutput->AvgTimePerFrame = pvihInput->AvgTimePerFrame;
            
        } else {
            ZeroMemory(pmt, sizeof(*pmt));
            pmt->majortype = MEDIATYPE_Text;
        }
    }

    return S_OK;
}

HRESULT CSample::InternalGetInputSizeInfo(DWORD dwInputStreamIndex, DWORD *pcbSize,
                                          DWORD *pcbMaxLookahead, DWORD *pcbAlignment)
{
    *pcbSize = 1;
    *pcbMaxLookahead = 0;
    *pcbAlignment = 1;
    return S_OK;
}

HRESULT CSample::InternalGetOutputSizeInfo(DWORD dwOutputStreamIndex, DWORD *pcbSize,
                                           DWORD *pcbAlignment)
{
    *pcbAlignment = 1;

    if (dwOutputStreamIndex == 0) {
        *pcbSize = OutputType(0)->lSampleSize;
        return S_OK;
    } else {
        *pcbSize = sizeof(L"hh:mm:ss:ff"); //  hh:mm:ss:ff
        return S_OK;
    }
}

HRESULT CSample::InternalGetInputMaxLatency(DWORD dwInputStreamIndex, 
                                            REFERENCE_TIME *prtMaxLatency)
{
    return E_NOTIMPL;
}

HRESULT CSample::InternalSetInputMaxLatency(DWORD dwInputStreamIndex, 
                                            REFERENCE_TIME rtMaxLatency)
{
    return E_NOTIMPL;
}

HRESULT CSample::InternalFlush()
{
    InternalDiscontinuity(0);

    //  Release buffer
    m_pBuffer = NULL;

    return S_OK;
}

HRESULT CSample::InternalDiscontinuity(DWORD dwInputStreamIndex)
{
    //  Zero our timestamp
    m_rtFrame = 0;

    //  No pictures yet
   m_bPicture   = false;

   //  Reset state machine
   m_StreamState.Reset();
   return S_OK;
}

HRESULT CSample::InternalAllocateStreamingResources()
{
    //  Reinitialize variables
    InternalDiscontinuity(0);

    //  Allocate our bitmap
    return S_OK;
}

HRESULT CSample::InternalFreeStreamingResources()
{
    return S_OK;
}

HRESULT CSample::InternalProcessInput(DWORD dwInputStreamIndex, IMediaBuffer *pBuffer,
                                      DWORD dwFlags, REFERENCE_TIME rtTimestamp,
                                      REFERENCE_TIME rtTimelength)
{
    //  Check parameters
    _ASSERTE(m_pBuffer == NULL);

    HRESULT hr = pBuffer->GetBufferAndLength(&m_pbData, &m_cbData);
    if (FAILED(hr)) {
        return hr;
    }
    m_pBuffer = pBuffer;

    if (0 == (dwFlags & DMO_INPUT_DATA_BUFFERF_TIME)) {
        rtTimestamp = INVALID_TIME;
    }

    m_StreamState.TimeStamp(rtTimestamp);

    //  Process() returns S_FALSE if there is no output, S_OK otherwise
    hr = Process();
    return hr;
}

HRESULT CSample::InternalProcessOutput(DWORD dwFlags,
                                       DWORD cOutputBufferCount,
                                       DMO_OUTPUT_DATA_BUFFER *pOutputBuffers,
                                       DWORD *pdwStatus)
{
    //  Check buffer
    PBYTE pbData;
    DWORD cbData;
    DWORD cbCurrent;

    //  Do we have any output?
    if (!m_bPicture) {
        return S_FALSE;
    }

    HRESULT hr = pOutputBuffers[0].pBuffer->GetBufferAndLength(&pbData, &cbCurrent);
    if (FAILED(hr)) {
        return hr;
    }

    hr = pOutputBuffers[0].pBuffer->GetMaxLength(&cbData);
    if (FAILED(hr)) {
        return hr;
    }

    if (cbData < cbCurrent + (DWORD)OutputType(0)->lSampleSize) {
        return E_INVALIDARG;
    }

    //  Say we've filled the buffer
    hr = pOutputBuffers[0].pBuffer->SetLength(cbCurrent + (DWORD)OutputType(0)->lSampleSize);

    cbData -= cbCurrent;
    pbData += cbCurrent;

    //  Generate our data
    DWORD dwTimeCode;
    REFERENCE_TIME rt = m_StreamState.PictureTime(&dwTimeCode);
    TCHAR szBuffer[20];
    wsprintf(szBuffer, TEXT("%2.2d:%2.2d:%2.2d:%2.2d\0"),
             (dwTimeCode >> 19) & 0x1F,
             (dwTimeCode >> 13) & 0x3F,
             (dwTimeCode >> 6) & 0x3F,
              dwTimeCode & 0x3F);

    //  Update our bitmap with turquoise
    OurFillRect((const VIDEOINFOHEADER *)OutputType(0)->pbFormat, pbData, 0x03EF);

    //  Draw our text
    DrawOurText((const VIDEOINFOHEADER *)OutputType(0)->pbFormat, pbData, szBuffer);

    pOutputBuffers[0].dwStatus = DMO_OUTPUT_DATA_BUFFERF_SYNCPOINT;

    //  Set the timestamp
    if (rt != INVALID_TIME) {
        pOutputBuffers[0].rtTimestamp = rt;

    } else {
        pOutputBuffers[0].rtTimestamp = m_rtFrame;
    }

    REFERENCE_TIME rtLength = ((const VIDEOINFOHEADER *)OutputType(0)->pbFormat)->AvgTimePerFrame;
    pOutputBuffers[0].rtTimelength = rtLength;
    m_rtFrame = pOutputBuffers[0].rtTimestamp + rtLength;

    //  Uncompressed video must always have a timestamp
    pOutputBuffers[0].dwStatus |= DMO_OUTPUT_DATA_BUFFERF_TIME | DMO_OUTPUT_DATA_BUFFERF_TIMELENGTH;

    //  Update our state
    m_bPicture = false;

    //  Is there any more data to output at this point?
    if (S_OK == Process()) {
        pOutputBuffers[0].dwStatus = DMO_OUTPUT_DATA_BUFFERF_INCOMPLETE;
    }

    //  Is there an output buffer for stream 1?
    if (pOutputBuffers[1].pBuffer) {
        PBYTE pbData;
        DWORD cbLength;
        DWORD cbMax;            
        DWORD dwLen = lstrlen(szBuffer) * sizeof(WCHAR);

        if (S_OK == pOutputBuffers[1].pBuffer->GetBufferAndLength(&pbData, &cbLength) &&
            S_OK == pOutputBuffers[1].pBuffer->GetMaxLength(&cbMax) &&
            cbLength + dwLen <= cbMax) {

            // Convert to UNICODE
            USES_CONVERSION;
            LPWSTR lpsz = T2W(szBuffer);

            CopyMemory(pbData + cbLength, lpsz, dwLen);
            pOutputBuffers[1].pBuffer->SetLength(cbLength + dwLen);
            pOutputBuffers[1].dwStatus = pOutputBuffers[0].dwStatus;
            pOutputBuffers[1].rtTimestamp = pOutputBuffers[0].rtTimestamp;
            pOutputBuffers[1].rtTimelength = rtLength;
        }
    }
    return S_OK;
}

HRESULT CSample::InternalAcceptingInput(DWORD dwInputStreamIndex)
{
    return m_pBuffer == NULL ? S_OK : S_FALSE;
}

//  Scan input data until either we're exhausted or we find
//  a picture start code
//  Note GOP time codes as we encounter them so we can
//  output time codes

HRESULT CSample::Process()
{
    //  Process bytes and update our state machine
    while (m_cbData && !m_bPicture) {
        m_bPicture = m_StreamState.NextByte(*m_pbData);
        m_cbData--;
        m_pbData++;
    }

    //  Release buffer if we're done with it
    if (m_cbData == 0) {
        m_pBuffer = NULL;
    }

    //  assert that if have no picture to output then we ate all the data
    _ASSERTE(m_bPicture || m_cbData == 0);
    return m_bPicture ? S_OK : S_FALSE;
}
