/*
 * QuickTime splitter + decoder
 *
 * Copyright 2011 Aric Stewart for CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "config.h"

#define ULONG CoreFoundation_ULONG
#define HRESULT CoreFoundation_HRESULT

#define LoadResource __carbon_LoadResource
#define CompareString __carbon_CompareString
#define GetCurrentThread __carbon_GetCurrentThread
#define GetCurrentProcess __carbon_GetCurrentProcess
#define AnimatePalette __carbon_AnimatePalette
#define EqualRgn __carbon_EqualRgn
#define FillRgn __carbon_FillRgn
#define FrameRgn __carbon_FrameRgn
#define GetPixel __carbon_GetPixel
#define InvertRgn __carbon_InvertRgn
#define LineTo __carbon_LineTo
#define OffsetRgn __carbon_OffsetRgn
#define PaintRgn __carbon_PaintRgn
#define Polygon __carbon_Polygon
#define ResizePalette __carbon_ResizePalette
#define SetRectRgn __carbon_SetRectRgn

#define CheckMenuItem __carbon_CheckMenuItem
#define DeleteMenu __carbon_DeleteMenu
#define DrawMenuBar __carbon_DrawMenuBar
#define EnableMenuItem __carbon_EnableMenuItem
#define EqualRect __carbon_EqualRect
#define FillRect __carbon_FillRect
#define FrameRect __carbon_FrameRect
#define GetCursor __carbon_GetCursor
#define GetMenu __carbon_GetMenu
#define InvertRect __carbon_InvertRect
#define IsWindowVisible __carbon_IsWindowVisible
#define MoveWindow __carbon_MoveWindow
#define OffsetRect __carbon_OffsetRect
#define PtInRect __carbon_PtInRect
#define SetCursor __carbon_SetCursor
#define SetRect __carbon_SetRect
#define ShowCursor __carbon_ShowCursor
#define ShowWindow __carbon_ShowWindow
#define UnionRect __carbon_UnionRect

#include <QuickTime/Movies.h>
#include <QuickTime/QuickTimeComponents.h>

#undef LoadResource
#undef CompareString
#undef GetCurrentThread
#undef _CDECL
#undef DPRINTF
#undef GetCurrentProcess
#undef AnimatePalette
#undef EqualRgn
#undef FillRgn
#undef FrameRgn
#undef GetPixel
#undef InvertRgn
#undef LineTo
#undef OffsetRgn
#undef PaintRgn
#undef Polygon
#undef ResizePalette
#undef SetRectRgn
#undef CheckMenuItem
#undef DeleteMenu
#undef DrawMenuBar
#undef EnableMenuItem
#undef EqualRect
#undef FillRect
#undef FrameRect
#undef GetCursor
#undef GetMenu
#undef InvertRect
#undef IsWindowVisible
#undef MoveWindow
#undef OffsetRect
#undef PtInRect
#undef SetCursor
#undef SetRect
#undef ShowCursor
#undef ShowWindow
#undef UnionRect

#undef ULONG
#undef HRESULT
#undef DPRINTF
#undef STDMETHODCALLTYPE

#include <assert.h>
#include <stdio.h>
#include <stdarg.h>

#define NONAMELESSSTRUCT
#define NONAMELESSUNION
#define COBJMACROS

#include "windef.h"
#include "winbase.h"
#include "wtypes.h"
#include "winuser.h"
#include "dshow.h"

#include <assert.h>

#include "wine/unicode.h"
#include "wine/debug.h"
#include "wine/strmbase.h"

#include "qtprivate.h"

WINE_DEFAULT_DEBUG_CHANNEL(qtsplitter);
extern CLSID CLSID_QTSplitter;

typedef struct QTOutPin {
    BaseOutputPin pin;

    AM_MEDIA_TYPE * pmt;
    OutputQueue * queue;
} QTOutPin;

typedef struct QTInPin {
    BasePin pin;
    GUID subType;

    IAsyncReader *pReader;
    IMemAllocator *pAlloc;
} QTInPin;

typedef struct QTSplitter {
    BaseFilter filter;

    QTInPin pInputPin;
    QTOutPin *pVideo_Pin;
    QTOutPin *pAudio_Pin;

    ALLOCATOR_PROPERTIES props;

    Movie pQTMovie;
    QTVisualContextRef vContext;

    MovieAudioExtractionRef aSession;
    HANDLE runEvent;

    DWORD outputSize;
    FILTER_STATE state;
} QTSplitter;

static const IPinVtbl QT_OutputPin_Vtbl;
static const IPinVtbl QT_InputPin_Vtbl;
static const IBaseFilterVtbl QT_Vtbl;

static HRESULT QT_AddPin(QTSplitter *This, const PIN_INFO *piOutput, const AM_MEDIA_TYPE *amt, BOOL video);
static HRESULT QT_RemoveOutputPins(QTSplitter *This);

/*
 * Base Filter
 */

static IPin* WINAPI QT_GetPin(BaseFilter *iface, int pos)
{
    QTSplitter *This = (QTSplitter *)iface;
    TRACE("Asking for pos %x\n", pos);

    if (pos > 2 || pos < 0)
        return NULL;
    switch (pos)
    {
        case 0:
            IPin_AddRef((IPin*)&This->pInputPin);
            return (IPin*)&This->pInputPin;
        case 1:
            if (This->pVideo_Pin)
                IPin_AddRef((IPin*)This->pVideo_Pin);
            return (IPin*)This->pVideo_Pin;
        case 2:
            if (This->pAudio_Pin)
                IPin_AddRef((IPin*)This->pAudio_Pin);
            return (IPin*)This->pAudio_Pin;
        default:
            return NULL;
    }
}

static LONG WINAPI QT_GetPinCount(BaseFilter *iface)
{
    QTSplitter *This = (QTSplitter *)iface;
    int c = 1;
    if (This->pAudio_Pin) c++;
    if (This->pVideo_Pin) c++;
    return c;
}

static const BaseFilterFuncTable BaseFuncTable = {
    QT_GetPin,
    QT_GetPinCount
};

IUnknown * CALLBACK QTSplitter_create(IUnknown *punkout, HRESULT *phr)
{
    IUnknown *obj = NULL;
    PIN_INFO *piInput;
    QTSplitter *This;
    static const WCHAR wcsInputPinName[] = {'I','n','p','u','t',' ','P','i','n',0};

    EnterMovies();

    RegisterWineDataHandler();

    This = CoTaskMemAlloc(sizeof(*This));
    obj = (IUnknown*)This;
    if (!This)
    {
        *phr = E_OUTOFMEMORY;
        return NULL;
    }

    BaseFilter_Init(&This->filter, &QT_Vtbl, &CLSID_QTSplitter, (DWORD_PTR)(__FILE__ ": QTSplitter.csFilter"), &BaseFuncTable);

    This->pVideo_Pin = NULL;
    This->pAudio_Pin = NULL;
    This->state = State_Stopped;
    This->aSession = NULL;
    This->runEvent = CreateEventW(NULL, 0, 0, NULL);

    piInput = &This->pInputPin.pin.pinInfo;
    piInput->dir = PINDIR_INPUT;
    piInput->pFilter = (IBaseFilter *)This;
    lstrcpynW(piInput->achName, wcsInputPinName, sizeof(piInput->achName) / sizeof(piInput->achName[0]));
    This->pInputPin.pin.lpVtbl = &QT_InputPin_Vtbl;
    This->pInputPin.pin.refCount = 1;
    This->pInputPin.pin.pConnectedTo = NULL;
    This->pInputPin.pin.pCritSec = &This->filter.csFilter;
    ZeroMemory(&This->pInputPin.pin.mtCurrent, sizeof(AM_MEDIA_TYPE));
    *phr = S_OK;
    return obj;
}

static void QT_Destroy(QTSplitter *This)
{
    IPin *connected = NULL;
    ULONG pinref;

    TRACE("Destroying\n");

    /* Don't need to clean up output pins, disconnecting input pin will do that */
    IPin_ConnectedTo((IPin *)&This->pInputPin, &connected);
    if (connected)
    {
        IPin_Disconnect(connected);
        IPin_Release(connected);
    }
    pinref = IPin_Release((IPin *)&This->pInputPin);
    if (pinref)
    {
        ERR("pinref should be null, is %u, destroying anyway\n", pinref);
        assert((LONG)pinref > 0);

        while (pinref)
            pinref = IPin_Release((IPin *)&This->pInputPin);
    }

    if (This->pQTMovie)
        DisposeMovie(This->pQTMovie);
    if (This->vContext)
        QTVisualContextRelease(This->vContext);
    if (This->aSession)
        MovieAudioExtractionEnd(This->aSession);
    CloseHandle(This->runEvent);

    ExitMovies();
    CoTaskMemFree(This);
}

static HRESULT WINAPI QT_QueryInterface(IBaseFilter *iface, REFIID riid, LPVOID *ppv)
{
    QTSplitter *This = (QTSplitter *)iface;
    TRACE("(%s, %p)\n", debugstr_guid(riid), ppv);

    *ppv = NULL;

    if (IsEqualIID(riid, &IID_IUnknown))
        *ppv = This;
    else if (IsEqualIID(riid, &IID_IPersist))
        *ppv = This;
    else if (IsEqualIID(riid, &IID_IMediaFilter))
        *ppv = This;
    else if (IsEqualIID(riid, &IID_IBaseFilter))
        *ppv = This;

    if (*ppv)
    {
        IUnknown_AddRef((IUnknown *)(*ppv));
        return S_OK;
    }

    if (!IsEqualIID(riid, &IID_IPin) && !IsEqualIID(riid, &IID_IVideoWindow))
        FIXME("No interface for %s!\n", debugstr_guid(riid));

    return E_NOINTERFACE;
}

static ULONG WINAPI QT_Release(IBaseFilter *iface)
{
    QTSplitter *This = (QTSplitter *)iface;
    ULONG refCount = BaseFilterImpl_Release(iface);

    TRACE("(%p)->() Release from %d\n", This, refCount + 1);

    if (!refCount)
        QT_Destroy(This);

    return refCount;
}

static HRESULT WINAPI QT_Stop(IBaseFilter *iface)
{
    QTSplitter *This = (QTSplitter *)iface;

    TRACE("()\n");

    IAsyncReader_BeginFlush(This->pInputPin.pReader);
    IAsyncReader_EndFlush(This->pInputPin.pReader);

    return S_OK;
}

static HRESULT WINAPI QT_Pause(IBaseFilter *iface)
{
    HRESULT hr = S_OK;
    TRACE("()\n");

    return hr;
}

static OSErr QT_Create_Extract_Session(QTSplitter *filter)
{
    AudioStreamBasicDescription aDesc;
    OSErr err;
    WAVEFORMATEX* pvi;

    pvi = (WAVEFORMATEX*)filter->pAudio_Pin->pmt->pbFormat;

    err = MovieAudioExtractionBegin(filter->pQTMovie, 0, &filter->aSession);
    if (err != noErr)
    {
        ERR("Failed to begin Extraction session %i\n",err);
        return err;
    }

    err = MovieAudioExtractionGetProperty(filter->aSession,
            kQTPropertyClass_MovieAudioExtraction_Audio,
kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription,
            sizeof(AudioStreamBasicDescription), &aDesc, NULL);

    if (err != noErr)
    {
        MovieAudioExtractionEnd(filter->aSession);
        filter->aSession = NULL;
        ERR("Failed to get session description %i\n",err);
        return err;
    }

    aDesc.mFormatID = kAudioFormatLinearPCM;
    aDesc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger +
        kAudioFormatFlagIsPacked;
    aDesc.mFramesPerPacket = 1;
    aDesc.mChannelsPerFrame = pvi->nChannels;
    aDesc.mBitsPerChannel = pvi->wBitsPerSample;
    aDesc.mSampleRate = pvi->nSamplesPerSec;
    aDesc.mBytesPerFrame = (aDesc.mBitsPerChannel * aDesc.mChannelsPerFrame) / 8;
    aDesc.mBytesPerPacket = aDesc.mBytesPerFrame * aDesc.mFramesPerPacket;

    err = MovieAudioExtractionSetProperty(filter->aSession,
        kQTPropertyClass_MovieAudioExtraction_Audio,
kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription,
        sizeof(AudioStreamBasicDescription), &aDesc);

    if (aDesc.mFormatID != kAudioFormatLinearPCM)
    {
        ERR("Not PCM Wave\n");
        err = -1;
    }
    if (aDesc.mFormatFlags != kLinearPCMFormatFlagIsSignedInteger +
        kAudioFormatFlagIsPacked)
    {
        ERR("Unhandled Flags\n");
        err = -1;
    }
    if (aDesc.mFramesPerPacket != 1)
    {
        ERR("Unhandled Frames per packet %li\n",aDesc.mFramesPerPacket);
        err = -1;
    }
    if (aDesc.mChannelsPerFrame != pvi->nChannels)
    {
        ERR("Unhandled channel count %li\n",aDesc.mChannelsPerFrame);
        err = -1;
    }
    if (aDesc.mBitsPerChannel != pvi->wBitsPerSample)
    {
        ERR("Unhandled bits per channel %li\n",aDesc.mBitsPerChannel);
        err = -1;
    }
    if (aDesc.mSampleRate != pvi->nSamplesPerSec)
    {
        ERR("Unhandled sample rate %f\n",aDesc.mSampleRate);
        err = -1;
    }

    if (err != noErr)
    {
        ERR("Failed to create Extraction Session\n");
        MovieAudioExtractionEnd(filter->aSession);
        filter->aSession = NULL;
    }

    return err;
}

static DWORD WINAPI QTSplitter_thread(LPVOID data)
{
    QTSplitter *This = (QTSplitter *)data;
    HRESULT hr = S_OK;
    TimeValue movie_time=0, next_time;
    CVPixelBufferRef pixelBuffer = NULL;
    OSStatus err;
    TimeRecord tr;

    if (This->pAudio_Pin)
    {
        /* according to QA1469 a movie has to be fully loaded before we
           can reliably start the Extraction session */

        while(GetMovieLoadState(This->pQTMovie) < kMovieLoadStateComplete)
            MoviesTask(This->pQTMovie,1000);

        QT_Create_Extract_Session(This);
    }

    WaitForSingleObject(This->runEvent, -1);

    This->state = State_Running;
    /* Prime the pump:  Needed for MPEG streams */
    GetMovieNextInterestingTime(This->pQTMovie, nextTimeEdgeOK | nextTimeStep, 0, NULL, movie_time, 1, &next_time, NULL);

    GetMovieTime(This->pQTMovie, &tr);
    do
    {
        LONGLONG tStart=0, tStop=0;
        float time;

        GetMovieNextInterestingTime(This->pQTMovie, nextTimeStep, 0, NULL, movie_time, 1, &next_time, NULL);

        if (next_time == -1)
        {
            TRACE("No next time\n");
            break;
        }

        tr.value = SInt64ToWide(next_time);
        SetMovieTime(This->pQTMovie, &tr);
        MoviesTask(This->pQTMovie,0);
        QTVisualContextTask(This->vContext);

        TRACE("In loop at time %ld\n",movie_time);
        TRACE("In Next time %ld\n",next_time);

        time = (float)movie_time / tr.scale;
        tStart = time * 10000000;
        time = (float)next_time / tr.scale;
        tStop = time * 10000000;

        /* Deliver Audio */
        if (This->pAudio_Pin && ((BaseOutputPin*)This->pAudio_Pin)->pin.pConnectedTo && This->aSession)
        {
            int data_size=0;
            BYTE* ptr;
            IMediaSample *sample = NULL;
            AudioBufferList aData;
            UInt32 flags;
            UInt32 frames;
            WAVEFORMATEX* pvi;
            float duration;

            pvi = (WAVEFORMATEX*)This->pAudio_Pin->pmt->pbFormat;

            hr = BaseOutputPinImpl_GetDeliveryBuffer((BaseOutputPin*)This->pAudio_Pin, &sample, NULL, NULL, 0);

            if (FAILED(hr))
            {
                ERR("Audio: Unable to get delivery buffer (%x)\n", hr);
                goto audio_error;
            }

            hr = IMediaSample_GetPointer(sample, &ptr);
            if (FAILED(hr))
            {
                ERR("Audio: Unable to get pointer to buffer (%x)\n", hr);
                goto audio_error;
            }

            duration = (float)next_time / tr.scale;
            time = (float)movie_time / tr.scale;
            duration -= time;
            frames = pvi->nSamplesPerSec * duration;
            TRACE("Need audio for %f seconds (%li frames)\n",duration,frames);

            data_size = IMediaSample_GetSize(sample);
            if (data_size < frames * pvi->nBlockAlign)
                FIXME("Audio buffer is too small\n");

            aData.mNumberBuffers = 1;
            aData.mBuffers[0].mNumberChannels = pvi->nChannels;
            aData.mBuffers[0].mDataByteSize = data_size;
            aData.mBuffers[0].mData = ptr;

            err = MovieAudioExtractionFillBuffer(This->aSession, &frames, &aData, &flags);
            TRACE("Got %i frames\n",(int)frames);

            IMediaSample_SetActualDataLength(sample, frames * pvi->nBlockAlign);

            IMediaSample_SetMediaTime(sample, &tStart, &tStop);
            if (tStart)
                IMediaSample_SetTime(sample, &tStart, &tStop);
            else
                IMediaSample_SetTime(sample, NULL, NULL);

            hr = OutputQueue_Receive(This->pAudio_Pin->queue, sample);
            TRACE("Audio Delivered (%x)\n",hr);

audio_error:
            if (sample)
                IMediaSample_Release(sample);
        }
        else
            TRACE("Audio Pin not connected or no Audio\n");

        /* Deliver Video */
        if (This->pVideo_Pin && QTVisualContextIsNewImageAvailable(This->vContext,0))
        {
            err = QTVisualContextCopyImageForTime(This->vContext, NULL, NULL, &pixelBuffer);
            if (err == noErr)
            {
                int data_size=0;
                BYTE* ptr;
                IMediaSample *sample = NULL;

                hr = BaseOutputPinImpl_GetDeliveryBuffer((BaseOutputPin*)This->pVideo_Pin, &sample, NULL, NULL, 0);
                if (FAILED(hr))
                {
                    ERR("Video: Unable to get delivery buffer (%x)\n", hr);
                    goto video_error;
                }

                data_size = IMediaSample_GetSize(sample);
                if (data_size < This->outputSize)
                {
                    ERR("Sample size is too small %d < %d\n", data_size, This->outputSize)
    ;
                    hr = E_FAIL;
                    goto video_error;
                }

                hr = IMediaSample_GetPointer(sample, &ptr);
                if (FAILED(hr))
                {
                    ERR("Video: Unable to get pointer to buffer (%x)\n", hr);
                    goto video_error;
                }

                hr = AccessPixelBufferPixels( pixelBuffer, ptr);
                if (FAILED(hr))
                {
                    ERR("Failed to access Pixels\n");
                    goto video_error;
                }

                IMediaSample_SetActualDataLength(sample, This->outputSize);

                IMediaSample_SetMediaTime(sample, &tStart, &tStop);
                if (tStart)
                    IMediaSample_SetTime(sample, &tStart, &tStop);
                else
                    IMediaSample_SetTime(sample, NULL, NULL);

                hr = OutputQueue_Receive(This->pVideo_Pin->queue, sample);
                TRACE("Video Delivered (%x)\n",hr);

    video_error:
                if (sample)
                    IMediaSample_Release(sample);
                if (pixelBuffer)
                    CVPixelBufferRelease(pixelBuffer);
            }
        }
        else
            TRACE("No video to deliver\n");

        movie_time = next_time;
    } while (hr == S_OK);

    This->state = State_Stopped;
    if (This->pAudio_Pin)
        OutputQueue_EOS(This->pAudio_Pin->queue);
    if (This->pVideo_Pin)
        OutputQueue_EOS(This->pVideo_Pin->queue);

    return hr;
}

static HRESULT WINAPI QT_Run(IBaseFilter *iface, REFERENCE_TIME tStart)
{
    HRESULT hr = S_OK;
    QTSplitter *This = (QTSplitter *)iface;
    HRESULT hr_any = VFW_E_NOT_CONNECTED;

    TRACE("(%s)\n", wine_dbgstr_longlong(tStart));

    EnterCriticalSection(&This->filter.csFilter);
    This->filter.rtStreamStart = tStart;

    if (This->pVideo_Pin)
        hr = BaseOutputPinImpl_Active((BaseOutputPin *)This->pVideo_Pin);
    if (SUCCEEDED(hr))
        hr_any = hr;
    if (This->pAudio_Pin)
        hr = BaseOutputPinImpl_Active((BaseOutputPin *)This->pAudio_Pin);
    if (SUCCEEDED(hr))
        hr_any = hr;

    hr = hr_any;
    LeaveCriticalSection(&This->filter.csFilter);

    SetEvent(This->runEvent);

    return hr;
}

static HRESULT WINAPI QT_GetState(IBaseFilter *iface, DWORD dwMilliSecsTimeout, FILTER_STATE *pState)
{
    QTSplitter *This = (QTSplitter *)iface;
    TRACE("(%d, %p)\n", dwMilliSecsTimeout, pState);

    *pState = This->state;

    return S_OK;
}

static HRESULT WINAPI QT_FindPin(IBaseFilter *iface, LPCWSTR Id, IPin **ppPin)
{
    FIXME("(%p)->(%s,%p) stub\n", iface, debugstr_w(Id), ppPin);
    return E_NOTIMPL;
}

static const IBaseFilterVtbl QT_Vtbl = {
    QT_QueryInterface,
    BaseFilterImpl_AddRef,
    QT_Release,
    BaseFilterImpl_GetClassID,
    QT_Stop,
    QT_Pause,
    QT_Run,
    QT_GetState,
    BaseFilterImpl_SetSyncSource,
    BaseFilterImpl_GetSyncSource,
    BaseFilterImpl_EnumPins,
    QT_FindPin,
    BaseFilterImpl_QueryFilterInfo,
    BaseFilterImpl_JoinFilterGraph,
    BaseFilterImpl_QueryVendorInfo
};

/*
 * Input Pin
 */
static HRESULT QT_RemoveOutputPins(QTSplitter *This)
{
    HRESULT hr;
    TRACE("(%p)\n", This);

    if (This->pVideo_Pin)
    {
        hr = BaseOutputPinImpl_BreakConnect(&This->pVideo_Pin->pin);
        TRACE("Disconnect: %08x\n", hr);
        IPin_Release((IPin*)This->pVideo_Pin);
        This->pVideo_Pin = NULL;
    }
    if (This->pAudio_Pin)
    {
        hr = BaseOutputPinImpl_BreakConnect(&This->pAudio_Pin->pin);
        TRACE("Disconnect: %08x\n", hr);
        IPin_Release((IPin*)This->pAudio_Pin);
        This->pAudio_Pin = NULL;
    }

    BaseFilterImpl_IncrementPinVersion((BaseFilter*)This);
    return S_OK;
}

static ULONG WINAPI QTInPin_Release(IPin *iface)
{
    QTInPin *This = (QTInPin*)iface;
    ULONG refCount = InterlockedDecrement(&This->pin.refCount);

    TRACE("(%p)->() Release from %d\n", iface, refCount + 1);
    if (!refCount)
    {
        FreeMediaType(&This->pin.mtCurrent);
        if (This->pAlloc)
            IMemAllocator_Release(This->pAlloc);
        This->pAlloc = NULL;
        This->pin.lpVtbl = NULL;
        return 0;
    }
    else
        return refCount;
}

static HRESULT QT_Process_Video_Track(QTSplitter* filter, Track trk)
{
    AM_MEDIA_TYPE amt;
    VIDEOINFOHEADER * pvi;
    PIN_INFO piOutput;
    HRESULT hr = S_OK;
    OSErr err;
    static const WCHAR szwVideoOut[] = {'V','i','d','e','o',0};
    CFMutableDictionaryRef  pixelBufferOptions = NULL;
    CFMutableDictionaryRef  visualContextOptions = NULL;
    CFNumberRef n = NULL;
    int t;
    DWORD outputWidth, outputHeight, outputDepth;
    Fixed trackWidth, trackHeight;

    ZeroMemory(&amt, sizeof(amt));
    amt.formattype = FORMAT_VideoInfo;
    amt.majortype = MEDIATYPE_Video;
    amt.subtype = MEDIASUBTYPE_RGB24;

    GetTrackDimensions(trk, &trackWidth, &trackHeight);

    outputDepth = 3;
    outputWidth = Fix2Long(trackWidth);
    outputHeight = Fix2Long(trackHeight);
    TRACE("Width %i  Height %i\n",outputWidth, outputHeight);

    amt.cbFormat = sizeof(VIDEOINFOHEADER);
    amt.pbFormat = CoTaskMemAlloc(amt.cbFormat);
    ZeroMemory(amt.pbFormat, amt.cbFormat);
    pvi = (VIDEOINFOHEADER *)amt.pbFormat;
    pvi->bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
    pvi->bmiHeader.biWidth = outputWidth;
    pvi->bmiHeader.biHeight = -outputHeight;
    pvi->bmiHeader.biPlanes = 1;
    pvi->bmiHeader.biBitCount = 24;
    pvi->bmiHeader.biCompression = BI_RGB;

    filter->outputSize = outputWidth * outputHeight * outputDepth;
    amt.lSampleSize = 0;

    pixelBufferOptions = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

    t = k32ARGBPixelFormat;
    n = CFNumberCreate(NULL, kCFNumberIntType, &t);
    CFDictionaryAddValue(pixelBufferOptions, kCVPixelBufferPixelFormatTypeKey, n);
    CFRelease(n);

    n = CFNumberCreate(NULL, kCFNumberIntType, &outputWidth);
    CFDictionaryAddValue(pixelBufferOptions, kCVPixelBufferWidthKey, n);
    CFRelease(n);

    n = CFNumberCreate(NULL, kCFNumberIntType, &outputHeight);
    CFDictionaryAddValue(pixelBufferOptions, kCVPixelBufferHeightKey, n);
    CFRelease(n);

    t = 16;
    n = CFNumberCreate(NULL, kCFNumberIntType, &t);
    CFDictionaryAddValue(pixelBufferOptions, kCVPixelBufferBytesPerRowAlignmentKey, n);
    CFRelease(n);

    visualContextOptions = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

    CFDictionarySetValue(visualContextOptions, kQTVisualContextPixelBufferAttributesKey, pixelBufferOptions);

    err = QTPixelBufferContextCreate(NULL, visualContextOptions,&filter->vContext);
    CFRelease(pixelBufferOptions);
    CFRelease(visualContextOptions);
    if (err != noErr)
    {
        ERR("Failed to create Visual Context\n");
        return E_FAIL;
    }

    err = SetMovieVisualContext(filter->pQTMovie, filter->vContext);
    if (err != noErr)
    {
        ERR("Failed to set Visual Context\n");
        return E_FAIL;
    }

    piOutput.dir = PINDIR_OUTPUT;
    piOutput.pFilter = (IBaseFilter *)filter;
    lstrcpyW(piOutput.achName,szwVideoOut);

    hr = QT_AddPin(filter, &piOutput, &amt, TRUE);
    if (FAILED(hr))
        ERR("Failed to add Video Track\n");
     else
        TRACE("Video Pin %p\n",filter->pVideo_Pin);

    return hr;
}

static HRESULT QT_Process_Audio_Track(QTSplitter* filter, Track trk)
{
    AM_MEDIA_TYPE amt;
    WAVEFORMATEX* pvi;
    PIN_INFO piOutput;
    HRESULT hr = S_OK;
    static const WCHAR szwAudioOut[] = {'A','u','d','i','o',0};
    Media audioMedia;

    SoundDescriptionHandle  aDesc = (SoundDescriptionHandle) NewHandle(sizeof(SoundDescription));

    audioMedia = GetTrackMedia(trk);
    GetMediaSampleDescription(audioMedia, 1, (SampleDescriptionHandle)aDesc);

    ZeroMemory(&amt, sizeof(amt));
    amt.formattype = FORMAT_WaveFormatEx;
    amt.majortype = MEDIATYPE_Audio;
    amt.subtype = MEDIASUBTYPE_PCM;
    amt.bTemporalCompression = 0;

    amt.cbFormat = sizeof(WAVEFORMATEX);
    amt.pbFormat = CoTaskMemAlloc(amt.cbFormat);
    ZeroMemory(amt.pbFormat, amt.cbFormat);
    pvi = (WAVEFORMATEX*)amt.pbFormat;

    pvi->cbSize = sizeof(WAVEFORMATEX);
    pvi->wFormatTag = WAVE_FORMAT_PCM;
    pvi->nChannels = ((SoundDescription)**aDesc).numChannels;
    if (pvi->nChannels < 1 || pvi->nChannels > 2)
        pvi->nChannels = 2;
    pvi->nSamplesPerSec = (((SoundDescription)**aDesc).sampleRate/65536);
    if (pvi->nSamplesPerSec < 8000 || pvi->nChannels > 48000)
        pvi->nSamplesPerSec = 44100;
    pvi->wBitsPerSample = ((SoundDescription)**aDesc).sampleSize;
    if (pvi->wBitsPerSample < 8 || pvi->wBitsPerSample > 32)
        pvi->wBitsPerSample = 16;
    pvi->nBlockAlign = (pvi->nChannels * pvi->wBitsPerSample) / 8;
    pvi->nAvgBytesPerSec = pvi->nSamplesPerSec * pvi->nBlockAlign;

    DisposeHandle((Handle)aDesc);

    piOutput.dir = PINDIR_OUTPUT;
    piOutput.pFilter = (IBaseFilter *)filter;
    lstrcpyW(piOutput.achName,szwAudioOut);

    hr = QT_AddPin(filter, &piOutput, &amt, FALSE);
    if (FAILED(hr))
        ERR("Failed to add Audio Track\n");
    else
        TRACE("Audio Pin %p\n",filter->pAudio_Pin);
    return hr;
}

static HRESULT QT_Process_Movie(QTSplitter* filter)
{
    HRESULT hr = S_OK;
    OSErr err;
    WineDataRefRecord ptrDataRefRec;
    Handle dataRef = NULL;
    Track trk;
    short id = 0;
    DWORD tid;

    TRACE("Trying movie connect\n");

    ptrDataRefRec.pReader = filter->pInputPin.pReader;
    ptrDataRefRec.streamSubtype = filter->pInputPin.subType;
    PtrToHand( &ptrDataRefRec, &dataRef, sizeof(WineDataRefRecord));

    err = NewMovieFromDataRef(&filter->pQTMovie, newMovieActive|newMovieDontInteractWithUser|newMovieDontAutoUpdateClock|newMovieDontAskUnresolvedDataRefs|newMovieAsyncOK, &id, dataRef, 'WINE');

    DisposeHandle(dataRef);

    if (err != noErr)
    {
        FIXME("QuickTime cannot handle media type(%i)\n",err);
        return VFW_E_TYPE_NOT_ACCEPTED;
    }

    PrePrerollMovie(filter->pQTMovie, 0, fixed1, NULL, NULL);
    PrerollMovie(filter->pQTMovie, 0, fixed1);
    GoToBeginningOfMovie(filter->pQTMovie);
    SetMovieActive(filter->pQTMovie,TRUE);

    if (GetMovieLoadState(filter->pQTMovie) < kMovieLoadStateLoaded)
        MoviesTask(filter->pQTMovie,100);

    trk = GetMovieIndTrackType(filter->pQTMovie, 1, VisualMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly);
    TRACE("%p is a video track\n",trk);
    if (trk)
       hr = QT_Process_Video_Track(filter, trk);

    if (!SUCCEEDED(hr))
        return hr;

    trk = GetMovieIndTrackType(filter->pQTMovie, 1, AudioMediaCharacteristic, movieTrackCharacteristic | movieTrackEnabledOnly);
    TRACE("%p is a audio track\n",trk);
    if (trk)
        hr = QT_Process_Audio_Track(filter, trk);

    CreateThread(NULL, 0, QTSplitter_thread, filter, 0, &tid);
    TRACE("Created thread 0x%08x\n",tid);

    return hr;
}

static HRESULT WINAPI QTInPin_ReceiveConnection(IPin *iface, IPin *pReceivePin, const AM_MEDIA_TYPE *pmt)
{
    HRESULT hr = S_OK;
    ALLOCATOR_PROPERTIES props;
    QTInPin *This = (QTInPin*)iface;

    TRACE("(%p/%p)->(%p, %p)\n", This, iface, pReceivePin, pmt);

    EnterCriticalSection(This->pin.pCritSec);
    This->pReader = NULL;

    if (This->pin.pConnectedTo)
        hr = VFW_E_ALREADY_CONNECTED;
    else if (IPin_QueryAccept(iface, pmt) != S_OK)
        hr = VFW_E_TYPE_NOT_ACCEPTED;
    else
    {
        PIN_DIRECTION pindirReceive;
        IPin_QueryDirection(pReceivePin, &pindirReceive);
        if (pindirReceive != PINDIR_OUTPUT)
            hr = VFW_E_INVALID_DIRECTION;
    }

    if (!SUCCEEDED(hr))
    {
        LeaveCriticalSection(This->pin.pCritSec);
        return hr;
    }

    hr = IPin_QueryInterface(pReceivePin, &IID_IAsyncReader, (LPVOID *)&This->pReader);
    if (!SUCCEEDED(hr))
    {
        LeaveCriticalSection(This->pin.pCritSec);
        TRACE("Input source is not an AsyncReader\n");
        return hr;
    }

    LeaveCriticalSection(This->pin.pCritSec);
    EnterCriticalSection(&((QTSplitter *)This->pin.pinInfo.pFilter)->filter.csFilter);
    hr = QT_Process_Movie((QTSplitter *)This->pin.pinInfo.pFilter);
    if (!SUCCEEDED(hr))
    {
        LeaveCriticalSection(&((QTSplitter *)This->pin.pinInfo.pFilter)->filter.csFilter);
        TRACE("Unable to process movie\n");
        return hr;
    }

    This->pAlloc = NULL;
    props.cBuffers = 8;
    props.cbAlign = 1;
    props.cbBuffer = ((QTSplitter *)This->pin.pinInfo.pFilter)->outputSize + props.cbAlign;
    props.cbPrefix = 0;

    hr = IAsyncReader_RequestAllocator(This->pReader, NULL, &props, &This->pAlloc);
    if (SUCCEEDED(hr))
    {
        CopyMediaType(&This->pin.mtCurrent, pmt);
        This->pin.pConnectedTo = pReceivePin;
        IPin_AddRef(pReceivePin);
        hr = IMemAllocator_Commit(This->pAlloc);
    }
    else
    {
        QT_RemoveOutputPins((QTSplitter *)This->pin.pinInfo.pFilter);
        if (This->pReader)
            IAsyncReader_Release(This->pReader);
        This->pReader = NULL;
        if (This->pAlloc)
            IMemAllocator_Release(This->pAlloc);
        This->pAlloc = NULL;
    }
    TRACE("Size: %i\n", props.cbBuffer);
    LeaveCriticalSection(&((QTSplitter *)This->pin.pinInfo.pFilter)->filter.csFilter);

    return hr;
}

static HRESULT WINAPI QTInPin_Disconnect(IPin *iface)
{
    HRESULT hr;
    QTInPin *This = (QTInPin*)iface;
    FILTER_STATE state;
    TRACE("()\n");

    hr = IBaseFilter_GetState(This->pin.pinInfo.pFilter, INFINITE, &state);
    EnterCriticalSection(This->pin.pCritSec);
    if (This->pin.pConnectedTo)
    {
        QTSplitter *Parser = (QTSplitter *)This->pin.pinInfo.pFilter;

        if (SUCCEEDED(hr) && state == State_Stopped)
        {
            IMemAllocator_Decommit(This->pAlloc);
            IPin_Disconnect(This->pin.pConnectedTo);
            This->pin.pConnectedTo = NULL;
            hr = QT_RemoveOutputPins(Parser);
        }
        else
            hr = VFW_E_NOT_STOPPED;
    }
    else
        hr = S_FALSE;
    LeaveCriticalSection(This->pin.pCritSec);
    return hr;
}

static HRESULT WINAPI QTInPin_QueryAccept(IPin *iface, const AM_MEDIA_TYPE *pmt)
{
    QTInPin *This = (QTInPin*)iface;

    TRACE("(%p)->(%p)\n", This, pmt);

    if (IsEqualIID(&pmt->majortype, &MEDIATYPE_Stream))
    {
        This->subType = pmt->subtype;
        return S_OK;
    }
    return S_FALSE;
}

static HRESULT WINAPI QTInPin_EndOfStream(IPin *iface)
{
    QTInPin *pin = (QTInPin*)iface;
    QTSplitter *This = (QTSplitter*)pin->pin.pinInfo.pFilter;

    FIXME("Propagate message on %p\n", This);
    return S_OK;
}

static HRESULT WINAPI QTInPin_BeginFlush(IPin *iface)
{
    QTInPin *pin = (QTInPin*)iface;
    QTSplitter *This = (QTSplitter*)pin->pin.pinInfo.pFilter;

    FIXME("Propagate message on %p\n", This);
    return S_OK;
}

static HRESULT WINAPI QTInPin_EndFlush(IPin *iface)
{
    QTInPin *pin = (QTInPin*)iface;
    QTSplitter *This = (QTSplitter*)pin->pin.pinInfo.pFilter;

    FIXME("Propagate message on %p\n", This);
    return S_OK;
}

static HRESULT WINAPI QTInPin_NewSegment(IPin *iface, REFERENCE_TIME tStart, REFERENCE_TIME tStop, double dRate)
{
    QTInPin *pin = (QTInPin*)iface;
    QTSplitter *This = (QTSplitter*)pin->pin.pinInfo.pFilter;

    BasePinImpl_NewSegment(iface, tStart, tStop, dRate);
    FIXME("Propagate message on %p\n", This);
    return S_OK;
}

static HRESULT WINAPI QTInPin_QueryInterface(IPin * iface, REFIID riid, LPVOID * ppv)
{
    QTInPin *This = (QTInPin*)iface;

    TRACE("(%p/%p)->(%s, %p)\n", This, iface, debugstr_guid(riid), ppv);

    *ppv = NULL;

    if (IsEqualIID(riid, &IID_IUnknown))
        *ppv = iface;
    else if (IsEqualIID(riid, &IID_IPin))
        *ppv = iface;
    else if (IsEqualIID(riid, &IID_IMediaSeeking))
    {
        return IBaseFilter_QueryInterface(This->pin.pinInfo.pFilter, &IID_IMediaSeeking, ppv);
    }

    if (*ppv)
    {
        IUnknown_AddRef((IUnknown *)(*ppv));
        return S_OK;
    }

    FIXME("No interface for %s!\n", debugstr_guid(riid));

    return E_NOINTERFACE;
}

static HRESULT WINAPI QTInPin_EnumMediaTypes(IPin *iface, IEnumMediaTypes **ppEnum)
{
    BasePin *This = (BasePin *)iface;

    TRACE("(%p/%p)->(%p)\n", This, iface, ppEnum);

    return EnumMediaTypes_Construct(This, BasePinImpl_GetMediaType, BasePinImpl_GetMediaTypeVersion, ppEnum);
}

static const IPinVtbl QT_InputPin_Vtbl = {
    QTInPin_QueryInterface,
    BasePinImpl_AddRef,
    QTInPin_Release,
    BaseInputPinImpl_Connect,
    QTInPin_ReceiveConnection,
    QTInPin_Disconnect,
    BasePinImpl_ConnectedTo,
    BasePinImpl_ConnectionMediaType,
    BasePinImpl_QueryPinInfo,
    BasePinImpl_QueryDirection,
    BasePinImpl_QueryId,
    QTInPin_QueryAccept,
    QTInPin_EnumMediaTypes,
    BasePinImpl_QueryInternalConnections,
    QTInPin_EndOfStream,
    QTInPin_BeginFlush,
    QTInPin_EndFlush,
    QTInPin_NewSegment
};

/*
 * Output Pin
 */

static HRESULT WINAPI QTOutPin_QueryInterface(IPin *iface, REFIID riid, void **ppv)
{
    QTOutPin *This = (QTOutPin *)iface;

    TRACE("(%s, %p)\n", debugstr_guid(riid), ppv);

    *ppv = NULL;

    if (IsEqualIID(riid, &IID_IUnknown))
        *ppv = iface;
    else if (IsEqualIID(riid, &IID_IPin))
        *ppv = iface;
    else if (IsEqualIID(riid, &IID_IMediaSeeking))
        return IBaseFilter_QueryInterface(This->pin.pin.pinInfo.pFilter, &IID_IMediaSeeking, ppv);

    if (*ppv)
    {
        IUnknown_AddRef((IUnknown *)(*ppv));
        return S_OK;
    }
    FIXME("No interface for %s!\n", debugstr_guid(riid));
    return E_NOINTERFACE;
}

static ULONG WINAPI QTOutPin_Release(IPin *iface)
{
    QTOutPin *This = (QTOutPin *)iface;
    ULONG refCount = InterlockedDecrement(&This->pin.pin.refCount);
    TRACE("(%p)->() Release from %d\n", iface, refCount + 1);

    if (!refCount)
    {
        DeleteMediaType(This->pmt);
        FreeMediaType(&This->pin.pin.mtCurrent);
        OutputQueue_Destroy(This->queue);
        CoTaskMemFree(This);
        return 0;
    }
    return refCount;
}

static HRESULT WINAPI QTOutPin_GetMediaType(BasePin *iface, int iPosition, AM_MEDIA_TYPE *pmt)
{
    QTOutPin *This = (QTOutPin *)iface;

    if (iPosition < 0)
        return E_INVALIDARG;
    if (iPosition > 0)
        return VFW_S_NO_MORE_ITEMS;
    CopyMediaType(pmt, This->pmt);
    return S_OK;
}

static HRESULT WINAPI QTOutPin_DecideBufferSize(BaseOutputPin *iface, IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest)
{
    /* Unused */
    return S_OK;
}

static HRESULT WINAPI QTOutPin_DecideAllocator(BaseOutputPin *iface, IMemInputPin *pPin, IMemAllocator **pAlloc)
{
    HRESULT hr;
    QTOutPin *This = (QTOutPin *)iface;
    QTSplitter *QTfilter = (QTSplitter*)This->pin.pin.pinInfo.pFilter;

    *pAlloc = NULL;
    if (QTfilter->pInputPin.pAlloc)
        hr = IMemInputPin_NotifyAllocator(pPin, QTfilter->pInputPin.pAlloc, FALSE);
    else
        hr = VFW_E_NO_ALLOCATOR;

    return hr;
}

static HRESULT WINAPI QTOutPin_BreakConnect(BaseOutputPin *This)
{
    HRESULT hr;

    TRACE("(%p)->()\n", This);

    EnterCriticalSection(This->pin.pCritSec);
    if (!This->pin.pConnectedTo || !This->pMemInputPin)
        hr = VFW_E_NOT_CONNECTED;
    else
    {
        hr = IPin_Disconnect(This->pin.pConnectedTo);
        IPin_Disconnect((IPin *)This);
    }
    LeaveCriticalSection(This->pin.pCritSec);

    return hr;
}

static const IPinVtbl QT_OutputPin_Vtbl = {
    QTOutPin_QueryInterface,
    BasePinImpl_AddRef,
    QTOutPin_Release,
    BaseOutputPinImpl_Connect,
    BaseOutputPinImpl_ReceiveConnection,
    BaseOutputPinImpl_Disconnect,
    BasePinImpl_ConnectedTo,
    BasePinImpl_ConnectionMediaType,
    BasePinImpl_QueryPinInfo,
    BasePinImpl_QueryDirection,
    BasePinImpl_QueryId,
    BasePinImpl_QueryAccept,
    BasePinImpl_EnumMediaTypes,
    BasePinImpl_QueryInternalConnections,
    BaseOutputPinImpl_EndOfStream,
    BaseOutputPinImpl_BeginFlush,
    BaseOutputPinImpl_EndFlush,
    BasePinImpl_NewSegment
};

static const BasePinFuncTable output_BaseFuncTable = {
    NULL,
    BaseOutputPinImpl_AttemptConnection,
    BasePinImpl_GetMediaTypeVersion,
    QTOutPin_GetMediaType
};

static const BaseOutputPinFuncTable output_BaseOutputFuncTable = {
    QTOutPin_DecideBufferSize,
    QTOutPin_DecideAllocator,
    QTOutPin_BreakConnect
};

static const OutputQueueFuncTable output_OutputQueueFuncTable = {
    OutputQueueImpl_ThreadProc
};

static HRESULT QT_AddPin(QTSplitter *This, const PIN_INFO *piOutput, const AM_MEDIA_TYPE *amt, BOOL video)
{
    HRESULT hr;
    IPin **target;

    if (video)
        target = (IPin**)&This->pVideo_Pin;
    else
        target = (IPin**)&This->pAudio_Pin;

    if (*target != NULL)
    {
        FIXME("We already have a %s pin\n",(video)?"video":"audio");
        return E_FAIL;
    }

    hr = BaseOutputPin_Construct(&QT_OutputPin_Vtbl, sizeof(QTOutPin), piOutput, &output_BaseFuncTable, &output_BaseOutputFuncTable, &This->filter.csFilter, (IPin**)target);
    if (SUCCEEDED(hr))
    {
        QTOutPin *pin = (QTOutPin*)*target;
        pin->pmt = CoTaskMemAlloc(sizeof(AM_MEDIA_TYPE));
        CopyMediaType(pin->pmt, amt);
        pin->pin.pin.pinInfo.pFilter = (LPVOID)This;

        BaseFilterImpl_IncrementPinVersion((BaseFilter*)This);

        hr = OutputQueue_Construct((BaseOutputPin*)pin, TRUE, TRUE, 5, FALSE, THREAD_PRIORITY_NORMAL, &output_OutputQueueFuncTable, &pin->queue);
    }
    else
        ERR("Failed with error %x\n", hr);
    return hr;
}