blob: 6e2ceb169a227fb1dae04592a6e87ee652439c97 [file] [log] [blame]
/*
* QuickTime Toolkit decoder filter for video
*
* Copyright 2010 Aric Stewart, 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/ImageCompression.h>
#include <CoreVideo/CVPixelBuffer.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
#define COBJMACROS
#include "windef.h"
#include "winbase.h"
#include "wtypes.h"
#include "winuser.h"
#include "dshow.h"
#include "uuids.h"
#include "amvideo.h"
#include "strmif.h"
#include "vfwmsgs.h"
#include "vfw.h"
#include "dvdmedia.h"
#include <assert.h>
#include "wine/unicode.h"
#include "wine/debug.h"
#include "wine/strmbase.h"
#include "qtprivate.h"
extern CLSID CLSID_QTVDecoder;
WINE_DEFAULT_DEBUG_CHANNEL(qtdecoder);
typedef struct QTVDecoderImpl
{
TransformFilter tf;
ImageDescriptionHandle hImageDescription;
CFMutableDictionaryRef outputBufferAttributes;
ICMDecompressionSessionRef decompressionSession;
HRESULT decodeHR;
DWORD outputSize;
} QTVDecoderImpl;
static inline QTVDecoderImpl *impl_from_IBaseFilter( IBaseFilter *iface )
{
return CONTAINING_RECORD(iface, QTVDecoderImpl, tf.filter.IBaseFilter_iface);
}
static inline QTVDecoderImpl *impl_from_TransformFilter( TransformFilter *iface )
{
return CONTAINING_RECORD(iface, QTVDecoderImpl, tf.filter);
}
static const IBaseFilterVtbl QTVDecoder_Vtbl;
static void trackingCallback(
void *decompressionTrackingRefCon,
OSStatus result,
ICMDecompressionTrackingFlags decompressionTrackingFlags,
CVPixelBufferRef pixelBuffer,
TimeValue64 displayTime,
TimeValue64 displayDuration,
ICMValidTimeFlags validTimeFlags,
void *reserved,
void *sourceFrameRefCon )
{
QTVDecoderImpl *This = (QTVDecoderImpl*)decompressionTrackingRefCon;
IMediaSample *pSample = (IMediaSample*)sourceFrameRefCon;
HRESULT hr = S_OK;
IMediaSample* pOutSample = NULL;
LPBYTE pbDstStream;
DWORD cbDstStream;
if (result != noErr)
{
ERR("Error from Codec, no frame decompressed\n");
return;
}
if (!pixelBuffer)
{
ERR("No pixel buffer, no frame decompressed\n");
return;
}
EnterCriticalSection(&This->tf.csReceive);
hr = BaseOutputPinImpl_GetDeliveryBuffer((BaseOutputPin*)This->tf.ppPins[1], &pOutSample, NULL, NULL, 0);
if (FAILED(hr)) {
ERR("Unable to get delivery buffer (%x)\n", hr);
goto error;
}
hr = IMediaSample_SetActualDataLength(pOutSample, 0);
assert(hr == S_OK);
hr = IMediaSample_GetPointer(pOutSample, &pbDstStream);
if (FAILED(hr)) {
ERR("Unable to get pointer to buffer (%x)\n", hr);
goto error;
}
cbDstStream = IMediaSample_GetSize(pOutSample);
if (cbDstStream < This->outputSize) {
ERR("Sample size is too small %d < %d\n", cbDstStream, This->outputSize);
hr = E_FAIL;
goto error;
}
hr = AccessPixelBufferPixels(pixelBuffer, pbDstStream);
if (FAILED(hr))
goto error;
IMediaSample_SetActualDataLength(pOutSample, This->outputSize);
IMediaSample_SetPreroll(pOutSample, (IMediaSample_IsPreroll(pSample) == S_OK));
IMediaSample_SetDiscontinuity(pOutSample, (IMediaSample_IsDiscontinuity(pSample) == S_OK));
IMediaSample_SetSyncPoint(pOutSample, (IMediaSample_IsSyncPoint(pSample) == S_OK));
if (!validTimeFlags)
IMediaSample_SetTime(pOutSample, NULL, NULL);
else
{
LONGLONG tStart, tStop;
if (validTimeFlags & kICMValidTime_DisplayTimeStampIsValid)
tStart = displayTime;
else
tStart = 0;
if (validTimeFlags & kICMValidTime_DisplayDurationIsValid)
tStop = tStart + displayDuration;
else
tStop = tStart;
IMediaSample_SetTime(pOutSample, &tStart, &tStop);
}
LeaveCriticalSection(&This->tf.csReceive);
hr = BaseOutputPinImpl_Deliver((BaseOutputPin*)This->tf.ppPins[1], pOutSample);
EnterCriticalSection(&This->tf.csReceive);
if (hr != S_OK && hr != VFW_E_NOT_CONNECTED)
ERR("Error sending sample (%x)\n", hr);
error:
LeaveCriticalSection(&This->tf.csReceive);
if (pOutSample)
IMediaSample_Release(pOutSample);
This->decodeHR = hr;
}
static HRESULT WINAPI QTVDecoder_StartStreaming(TransformFilter* pTransformFilter)
{
QTVDecoderImpl* This = impl_from_TransformFilter(pTransformFilter);
OSErr err = noErr;
ICMDecompressionSessionOptionsRef sessionOptions = NULL;
ICMDecompressionTrackingCallbackRecord trackingCallbackRecord;
TRACE("(%p)->()\n", This);
trackingCallbackRecord.decompressionTrackingCallback = trackingCallback;
trackingCallbackRecord.decompressionTrackingRefCon = (void*)This;
err = ICMDecompressionSessionCreate(NULL, This->hImageDescription, sessionOptions, This->outputBufferAttributes, &trackingCallbackRecord, &This->decompressionSession);
if (err != noErr)
{
ERR("Error with ICMDecompressionSessionCreate %i\n",err);
return E_FAIL;
}
return S_OK;
}
static HRESULT WINAPI QTVDecoder_Receive(TransformFilter *tf, IMediaSample *pSample)
{
QTVDecoderImpl* This = impl_from_TransformFilter(tf);
HRESULT hr;
DWORD cbSrcStream;
LPBYTE pbSrcStream;
ICMFrameTimeRecord frameTime = {{0}};
TimeValue time = 1;
TimeScale timeScale = 1;
OSStatus err = noErr;
LONGLONG tStart, tStop;
hr = IMediaSample_GetPointer(pSample, &pbSrcStream);
if (FAILED(hr))
{
ERR("Cannot get pointer to sample data (%x)\n", hr);
goto error;
}
cbSrcStream = IMediaSample_GetActualDataLength(pSample);
if (IMediaSample_GetTime(pSample, &tStart, &tStop) != S_OK)
tStart = tStop = 0;
time = tStart;
frameTime.recordSize = sizeof(ICMFrameTimeRecord);
*(TimeValue64 *)&frameTime.value = tStart;
frameTime.scale = 1;
frameTime.rate = fixed1;
frameTime.duration = tStop - tStart;
frameTime.frameNumber = 0;
frameTime.flags = icmFrameTimeIsNonScheduledDisplayTime;
err = ICMDecompressionSessionDecodeFrame(This->decompressionSession,
(UInt8 *)pbSrcStream, cbSrcStream, NULL, &frameTime, pSample);
if (err != noErr)
{
ERR("Error with ICMDecompressionSessionDecodeFrame\n");
hr = E_FAIL;
goto error;
}
ICMDecompressionSessionSetNonScheduledDisplayTime(This->decompressionSession, time, timeScale, 0);
ICMDecompressionSessionFlush(This->decompressionSession);
hr = This->decodeHR;
error:
return hr;
}
static HRESULT WINAPI QTVDecoder_StopStreaming(TransformFilter* pTransformFilter)
{
QTVDecoderImpl* This = impl_from_TransformFilter(pTransformFilter);
TRACE("(%p)->()\n", This);
if (This->decompressionSession)
ICMDecompressionSessionRelease(This->decompressionSession);
This->decompressionSession = NULL;
return S_OK;
}
static HRESULT WINAPI QTVDecoder_SetMediaType(TransformFilter *tf, PIN_DIRECTION dir, const AM_MEDIA_TYPE * pmt)
{
QTVDecoderImpl* This = impl_from_TransformFilter(tf);
HRESULT hr = VFW_E_TYPE_NOT_ACCEPTED;
OSErr err = noErr;
AM_MEDIA_TYPE *outpmt = &This->tf.pmt;
CFNumberRef n = NULL;
TRACE("(%p)->(%p)\n", This, pmt);
if (dir != PINDIR_INPUT)
return S_OK;
FreeMediaType(outpmt);
CopyMediaType(outpmt, pmt);
if (This->hImageDescription)
DisposeHandle((Handle)This->hImageDescription);
This->hImageDescription = (ImageDescriptionHandle)
NewHandleClear(sizeof(ImageDescription));
if (This->hImageDescription != NULL)
{
(**This->hImageDescription).idSize = sizeof(ImageDescription);
(**This->hImageDescription).spatialQuality = codecNormalQuality;
(**This->hImageDescription).frameCount = 1;
(**This->hImageDescription).clutID = -1;
}
else
{
ERR("Failed to create ImageDescription\n");
goto failed;
}
/* Check root (GUID w/o FOURCC) */
if ((IsEqualIID(&pmt->majortype, &MEDIATYPE_Video)) &&
(!memcmp(((const char *)&pmt->subtype)+4, ((const char *)&MEDIATYPE_Video)+4, sizeof(GUID)-4)))
{
VIDEOINFOHEADER *format1 = (VIDEOINFOHEADER *)outpmt->pbFormat;
VIDEOINFOHEADER2 *format2 = (VIDEOINFOHEADER2 *)outpmt->pbFormat;
BITMAPINFOHEADER *bmi;
OSType fourCC;
DecompressorComponent dc;
OSType format;
DWORD outputWidth, outputHeight, outputDepth;
if (IsEqualIID(&pmt->formattype, &FORMAT_VideoInfo))
bmi = &format1->bmiHeader;
else if (IsEqualIID(&pmt->formattype, &FORMAT_VideoInfo2))
bmi = &format2->bmiHeader;
else
goto failed;
TRACE("Fourcc: %s\n", debugstr_an((const char *)&pmt->subtype.Data1, 4));
fourCC = ((const char *)&pmt->subtype.Data1)[3] |
(((const char *)&pmt->subtype.Data1)[2]<<8) |
(((const char *)&pmt->subtype.Data1)[1]<<16) |
(((const char *)&pmt->subtype.Data1)[0]<<24);
err = FindCodec(fourCC,NULL,NULL,&dc);
if (err != noErr || dc == 0x0)
{
TRACE("Codec not found\n");
goto failed;
}
outputWidth = bmi->biWidth;
outputHeight = bmi->biHeight;
(**This->hImageDescription).cType = fourCC;
(**This->hImageDescription).width = outputWidth;
(**This->hImageDescription).height = outputHeight;
(**This->hImageDescription).depth = bmi->biBitCount;
(**This->hImageDescription).hRes = 72<<16;
(**This->hImageDescription).vRes = 72<<16;
if (This->outputBufferAttributes)
CFRelease(This->outputBufferAttributes);
This->outputBufferAttributes = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (!This->outputBufferAttributes)
{
ERR("Failed to create outputBufferAttributes\n");
goto failed;
}
n = CFNumberCreate(NULL, kCFNumberIntType, &outputWidth);
CFDictionaryAddValue(This->outputBufferAttributes, kCVPixelBufferWidthKey, n);
CFRelease(n);
n = CFNumberCreate(NULL, kCFNumberIntType, &outputHeight);
CFDictionaryAddValue(This->outputBufferAttributes, kCVPixelBufferHeightKey, n);
CFRelease(n);
/* yes this looks wrong. but 32ARGB is 24 RGB with an alpha channel */
format = k32ARGBPixelFormat;
n = CFNumberCreate(NULL, kCFNumberIntType, &format);
CFDictionaryAddValue(This->outputBufferAttributes, kCVPixelBufferPixelFormatTypeKey, n);
CFRelease(n);
CFDictionaryAddValue(This->outputBufferAttributes, kCVPixelBufferCGBitmapContextCompatibilityKey, kCFBooleanTrue);
CFDictionaryAddValue(This->outputBufferAttributes, kCVPixelBufferCGImageCompatibilityKey, kCFBooleanTrue);
outputDepth = 3;
This->outputSize = outputWidth * outputHeight * outputDepth;
bmi->biCompression = BI_RGB;
bmi->biBitCount = 24;
outpmt->subtype = MEDIASUBTYPE_RGB24;
return S_OK;
}
failed:
if (This->hImageDescription)
{
DisposeHandle((Handle)This->hImageDescription);
This->hImageDescription = NULL;
}
if (This->outputBufferAttributes)
{
CFRelease(This->outputBufferAttributes);
This->outputBufferAttributes = NULL;
}
TRACE("Connection refused\n");
return hr;
}
static HRESULT WINAPI QTVDecoder_BreakConnect(TransformFilter *tf, PIN_DIRECTION dir)
{
QTVDecoderImpl *This = impl_from_TransformFilter(tf);
TRACE("(%p)->()\n", This);
if (This->hImageDescription)
DisposeHandle((Handle)This->hImageDescription);
if (This->outputBufferAttributes)
CFRelease(This->outputBufferAttributes);
This->hImageDescription = NULL;
This->outputBufferAttributes = NULL;
return S_OK;
}
static HRESULT WINAPI QTVDecoder_DecideBufferSize(TransformFilter *tf, IMemAllocator *pAlloc, ALLOCATOR_PROPERTIES *ppropInputRequest)
{
QTVDecoderImpl *This = impl_from_TransformFilter(tf);
ALLOCATOR_PROPERTIES actual;
TRACE("()\n");
if (!ppropInputRequest->cbAlign)
ppropInputRequest->cbAlign = 1;
if (ppropInputRequest->cbBuffer < This->outputSize)
ppropInputRequest->cbBuffer = This->outputSize + ppropInputRequest->cbAlign;
if (!ppropInputRequest->cBuffers)
ppropInputRequest->cBuffers = 1;
return IMemAllocator_SetProperties(pAlloc, ppropInputRequest, &actual);
}
static const TransformFilterFuncTable QTVDecoder_FuncsTable = {
QTVDecoder_DecideBufferSize,
QTVDecoder_StartStreaming,
QTVDecoder_Receive,
QTVDecoder_StopStreaming,
NULL,
QTVDecoder_SetMediaType,
NULL,
QTVDecoder_BreakConnect,
NULL,
NULL,
NULL,
NULL
};
IUnknown * CALLBACK QTVDecoder_create(IUnknown * pUnkOuter, HRESULT* phr)
{
HRESULT hr;
QTVDecoderImpl * This;
TRACE("(%p, %p)\n", pUnkOuter, phr);
*phr = S_OK;
if (pUnkOuter)
{
*phr = CLASS_E_NOAGGREGATION;
return NULL;
}
hr = TransformFilter_Construct(&QTVDecoder_Vtbl, sizeof(QTVDecoderImpl), &CLSID_QTVDecoder, &QTVDecoder_FuncsTable, (IBaseFilter**)&This);
if (FAILED(hr))
{
*phr = hr;
return NULL;
}
*phr = hr;
return (IUnknown*)This;
}
static const IBaseFilterVtbl QTVDecoder_Vtbl =
{
TransformFilterImpl_QueryInterface,
BaseFilterImpl_AddRef,
TransformFilterImpl_Release,
BaseFilterImpl_GetClassID,
TransformFilterImpl_Stop,
TransformFilterImpl_Pause,
TransformFilterImpl_Run,
BaseFilterImpl_GetState,
BaseFilterImpl_SetSyncSource,
BaseFilterImpl_GetSyncSource,
BaseFilterImpl_EnumPins,
TransformFilterImpl_FindPin,
BaseFilterImpl_QueryFilterInfo,
BaseFilterImpl_JoinFilterGraph,
BaseFilterImpl_QueryVendorInfo
};