From f1da93da80773afcbf69fc727a112c48ab5a7d3c Mon Sep 17 00:00:00 2001 From: Aric Stewart Date: Tue, 5 Apr 2011 12:56:02 -0500 Subject: [PATCH] wineqtdecoder: Add processing for audio in the movie. --- dlls/wineqtdecoder/qtsplitter.c | 286 ++++++++++++++++++++++++++++++-- 1 file changed, 271 insertions(+), 15 deletions(-) diff --git a/dlls/wineqtdecoder/qtsplitter.c b/dlls/wineqtdecoder/qtsplitter.c index 4a369301298..8a3da5a8081 100644 --- a/dlls/wineqtdecoder/qtsplitter.c +++ b/dlls/wineqtdecoder/qtsplitter.c @@ -151,12 +151,16 @@ typedef struct QTSplitter { 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; @@ -165,7 +169,7 @@ 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); +static HRESULT QT_AddPin(QTSplitter *This, const PIN_INFO *piOutput, const AM_MEDIA_TYPE *amt, BOOL video); static HRESULT QT_RemoveOutputPins(QTSplitter *This); /* @@ -188,8 +192,10 @@ static IPin* WINAPI QT_GetPin(BaseFilter *iface, int pos) if (This->pVideo_Pin) IPin_AddRef((IPin*)This->pVideo_Pin); return (IPin*)This->pVideo_Pin; - case 2: /* TODO: Audio */ - return NULL; + case 2: + if (This->pAudio_Pin) + IPin_AddRef((IPin*)This->pAudio_Pin); + return (IPin*)This->pAudio_Pin; default: return NULL; } @@ -199,6 +205,7 @@ 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; } @@ -230,7 +237,10 @@ IUnknown * CALLBACK QTSplitter_create(IUnknown *punkout, HRESULT *phr) 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; @@ -273,6 +283,9 @@ static void QT_Destroy(QTSplitter *This) DisposeMovie(This->pQTMovie); if (This->vContext) QTVisualContextRelease(This->vContext); + if (This->aSession) + MovieAudioExtractionEnd(This->aSession); + CloseHandle(This->runEvent); ExitMovies(); CoTaskMemFree(This); @@ -339,7 +352,89 @@ static HRESULT WINAPI QT_Pause(IBaseFilter *iface) return hr; } -static DWORD WINAPI QTSplitter_thread_reader(LPVOID data) +static OSErr QT_Create_Extract_Session(QTSplitter *filter) +{ + AudioStreamBasicDescription aDesc; + OSErr err; + + 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 = 2; + aDesc.mBitsPerChannel = 16; + aDesc.mBytesPerFrame = (aDesc.mBitsPerChannel * aDesc.mChannelsPerFrame) / 8; + aDesc.mBytesPerPacket = aDesc.mBytesPerFrame * aDesc.mFramesPerPacket; + aDesc.mSampleRate = 48000; + + 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 != 2) + { + ERR("Unhandled channel count %li\n",aDesc.mChannelsPerFrame); + err = -1; + } + if (aDesc.mBitsPerChannel != 16) + { + ERR("Unhandled bits per channel %li\n",aDesc.mBitsPerChannel); + err = -1; + } + if (aDesc.mSampleRate != 48000) + { + 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; @@ -348,9 +443,21 @@ static DWORD WINAPI QTSplitter_thread_reader(LPVOID data) 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; GetMovieTime(This->pQTMovie, &tr); - do { LONGLONG tStart=0, tStop=0; @@ -379,8 +486,90 @@ static DWORD WINAPI QTSplitter_thread_reader(LPVOID data) 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; + static int audio_frame_size = 0; + + pvi = (WAVEFORMATEX*)This->pAudio_Pin->pmt->pbFormat; + + if (audio_frame_size == 0) + audio_frame_size = (pvi->wBitsPerSample * pvi->nChannels) / 8; + + tr.value = SInt64ToWide(movie_time); + + err = MovieAudioExtractionSetProperty(This->aSession, + kQTPropertyClass_MovieAudioExtraction_Movie, + kQTMovieAudioExtractionMoviePropertyID_CurrentTime, + sizeof(TimeRecord), &tr); + + if (err != noErr) + { + ERR("Failed to set audio media time\n"); + goto audio_error; + } + + 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 * audio_frame_size ) + 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 * audio_frame_size); + + 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 (QTVisualContextIsNewImageAvailable(This->vContext,0)) + if (This->pVideo_Pin && QTVisualContextIsNewImageAvailable(This->vContext,0)) { err = QTVisualContextCopyImageForTime(This->vContext, NULL, NULL, &pixelBuffer); if (err == noErr) @@ -437,12 +626,17 @@ static DWORD WINAPI QTSplitter_thread_reader(LPVOID data) CVPixelBufferRelease(pixelBuffer); } } + else + TRACE("No video to deliver\n"); movie_time = next_time; } while (hr == S_OK); This->state = State_Stopped; - OutputQueue_EOS(This->pVideo_Pin->queue); + if (This->pAudio_Pin) + OutputQueue_EOS(This->pAudio_Pin->queue); + if (This->pVideo_Pin) + OutputQueue_EOS(This->pVideo_Pin->queue); return hr; } @@ -452,7 +646,6 @@ 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; - DWORD tid; TRACE("(%s)\n", wine_dbgstr_longlong(tStart)); @@ -463,12 +656,15 @@ static HRESULT WINAPI QT_Run(IBaseFilter *iface, REFERENCE_TIME tStart) 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); - CreateThread(NULL, 0, QTSplitter_thread_reader, This, 0, &tid); - TRACE("Created thread 0x%08x\n",tid); + SetEvent(This->runEvent); return hr; } @@ -522,6 +718,13 @@ static HRESULT QT_RemoveOutputPins(QTSplitter *This) 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; @@ -631,7 +834,7 @@ static HRESULT QT_Process_Video_Track(QTSplitter* filter, Track trk) piOutput.pFilter = (IBaseFilter *)filter; lstrcpyW(piOutput.achName,szwVideoOut); - hr = QT_AddPin(filter, &piOutput, &amt); + hr = QT_AddPin(filter, &piOutput, &amt, TRUE); if (FAILED(hr)) ERR("Failed to add Video Track\n"); else @@ -640,6 +843,45 @@ static HRESULT QT_Process_Video_Track(QTSplitter* filter, Track trk) 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}; + + 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 = 2; + pvi->nSamplesPerSec = 48000; + pvi->wBitsPerSample = 16; + pvi->nBlockAlign = (pvi->nChannels * pvi->wBitsPerSample) / 8; + pvi->nAvgBytesPerSec = pvi->nSamplesPerSec * pvi->nBlockAlign; + + 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; @@ -648,6 +890,7 @@ static HRESULT QT_Process_Movie(QTSplitter* filter) Handle dataRef = NULL; Track trk; short id = 0; + DWORD tid; TRACE("Trying movie connect\n"); @@ -655,7 +898,9 @@ static HRESULT QT_Process_Movie(QTSplitter* filter) ptrDataRefRec.streamSubtype = filter->pInputPin.subType; PtrToHand( &ptrDataRefRec, &dataRef, sizeof(WineDataRefRecord)); - err = NewMovieFromDataRef(&filter->pQTMovie, newMovieActive|newMovieDontInteractWithUser|newMovieDontAutoUpdateClock|newMovieDontAskUnresolvedDataRefs|newMovieIdleImportOK, &id, dataRef, 'WINE'); + err = NewMovieFromDataRef(&filter->pQTMovie, newMovieActive|newMovieDontInteractWithUser|newMovieDontAutoUpdateClock|newMovieDontAskUnresolvedDataRefs|newMovieAsyncOK, &id, dataRef, 'WINE'); + + DisposeHandle(dataRef); if (err != noErr) { @@ -679,6 +924,14 @@ static HRESULT QT_Process_Movie(QTSplitter* filter) 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; } @@ -1032,16 +1285,19 @@ static const OutputQueueFuncTable output_OutputQueueFuncTable = { OutputQueueImpl_ThreadProc }; -static HRESULT QT_AddPin(QTSplitter *This, const PIN_INFO *piOutput, const AM_MEDIA_TYPE *amt) +static HRESULT QT_AddPin(QTSplitter *This, const PIN_INFO *piOutput, const AM_MEDIA_TYPE *amt, BOOL video) { HRESULT hr; IPin **target; - target = (IPin**)&This->pVideo_Pin; + if (video) + target = (IPin**)&This->pVideo_Pin; + else + target = (IPin**)&This->pAudio_Pin; if (*target != NULL) { - ERR("We we already have a video pin\n"); + FIXME("We already have a %s pin\n",(video)?"video":"audio"); return E_FAIL; }