From: Anton Baskanov Subject: [PATCH v2 4/5] amstream: Implement audio stream sample Update. Message-Id: <20200422161045.2095-4-baskanov@gmail.com> Date: Wed, 22 Apr 2020 23:10:44 +0700 In-Reply-To: <20200422161045.2095-2-baskanov@gmail.com> References: <20200422161045.2095-2-baskanov@gmail.com> Signed-off-by: Anton Baskanov --- dlls/amstream/audiostream.c | 142 ++++++++++++++- dlls/amstream/tests/amstream.c | 315 +++++++++++++++++++++++++++++++++ 2 files changed, 454 insertions(+), 3 deletions(-) diff --git a/dlls/amstream/audiostream.c b/dlls/amstream/audiostream.c index 9c849a4831..6d46a9d956 100644 --- a/dlls/amstream/audiostream.c +++ b/dlls/amstream/audiostream.c @@ -57,6 +57,7 @@ struct audio_stream FILTER_STATE state; BOOL eos; struct list receive_queue; + struct list update_queue; }; typedef struct { @@ -64,6 +65,13 @@ typedef struct { LONG ref; struct audio_stream *parent; IAudioData *audio_data; + HANDLE update_event; + + struct list entry; + DWORD length; + BYTE *pointer; + DWORD position; + HRESULT update_hr; } IAudioStreamSampleImpl; static void remove_queued_receive(struct queued_receive *receive) @@ -73,6 +81,18 @@ static void remove_queued_receive(struct queued_receive *receive) HeapFree(GetProcessHeap(), 0, receive); } +static void remove_queued_update(IAudioStreamSampleImpl *sample) +{ + HRESULT hr; + + hr = IAudioData_SetActual(sample->audio_data, sample->position); + if (FAILED(hr)) + sample->update_hr = hr; + + list_remove(&sample->entry); + SetEvent(sample->update_event); +} + static void flush_receive_queue(struct audio_stream *stream) { while (!list_empty(&stream->receive_queue)) @@ -84,6 +104,45 @@ static void flush_receive_queue(struct audio_stream *stream) } } +static void process_update(IAudioStreamSampleImpl *sample, struct queued_receive *receive) +{ + DWORD advance; + + advance = min(receive->length - receive->position, sample->length - sample->position); + memcpy(&sample->pointer[sample->position], &receive->pointer[receive->position], advance); + + receive->position += advance; + sample->position += advance; + + sample->update_hr = (sample->position == sample->length) ? S_OK : MS_S_PENDING; +} + +static void process_updates(struct audio_stream *stream) +{ + while (!list_empty(&stream->update_queue) && !list_empty(&stream->receive_queue)) + { + IAudioStreamSampleImpl *sample = LIST_ENTRY(list_head(&stream->update_queue), IAudioStreamSampleImpl, entry); + struct queued_receive *receive = LIST_ENTRY(list_head(&stream->receive_queue), struct queued_receive, entry); + + process_update(sample, receive); + + if (MS_S_PENDING != sample->update_hr) + remove_queued_update(sample); + if (receive->position == receive->length) + remove_queued_receive(receive); + } + if (stream->eos) + { + while (!list_empty(&stream->update_queue)) + { + IAudioStreamSampleImpl *sample = LIST_ENTRY(list_head(&stream->update_queue), IAudioStreamSampleImpl, entry); + + sample->update_hr = sample->position ? S_OK : MS_S_ENDOFSTREAM; + remove_queued_update(sample); + } + } +} + static inline IAudioStreamSampleImpl *impl_from_IAudioStreamSample(IAudioStreamSample *iface) { return CONTAINING_RECORD(iface, IAudioStreamSampleImpl, IAudioStreamSample_iface); @@ -128,7 +187,10 @@ static ULONG WINAPI IAudioStreamSampleImpl_Release(IAudioStreamSample *iface) TRACE("(%p)->(): new ref = %u\n", iface, ref); if (!ref) + { + CloseHandle(This->update_event); HeapFree(GetProcessHeap(), 0, This); + } return ref; } @@ -158,11 +220,79 @@ static HRESULT WINAPI IAudioStreamSampleImpl_SetSampleTimes(IAudioStreamSample * } static HRESULT WINAPI IAudioStreamSampleImpl_Update(IAudioStreamSample *iface, DWORD flags, HANDLE event, - PAPCFUNC func_APC, DWORD APC_data) + PAPCFUNC func_APC, DWORD APC_data) { - FIXME("(%p)->(%x,%p,%p,%u): stub\n", iface, flags, event, func_APC, APC_data); + IAudioStreamSampleImpl *sample = impl_from_IAudioStreamSample(iface); + DWORD length; + BYTE *pointer; + HRESULT hr; - return E_NOTIMPL; + TRACE("(%p)->(%x,%p,%p,%u)\n", iface, flags, event, func_APC, APC_data); + + hr = IAudioData_GetInfo(sample->audio_data, &length, &pointer, NULL); + if (FAILED(hr)) + return hr; + + if (event && func_APC) + return E_INVALIDARG; + + if (func_APC) + { + FIXME("APC support is not implemented!\n"); + return E_NOTIMPL; + } + + if (event) + { + FIXME("Event parameter support is not implemented!\n"); + return E_NOTIMPL; + } + + if (flags & ~SSUPDATE_ASYNC) + { + FIXME("Unsupported flags: %x\n", flags); + return E_NOTIMPL; + } + + EnterCriticalSection(&sample->parent->cs); + + if (sample->parent->state != State_Running) + { + LeaveCriticalSection(&sample->parent->cs); + return MS_E_NOTRUNNING; + } + if (!sample->parent->peer) + { + LeaveCriticalSection(&sample->parent->cs); + return MS_S_ENDOFSTREAM; + } + if (MS_S_PENDING == sample->update_hr) + { + LeaveCriticalSection(&sample->parent->cs); + return MS_E_BUSY; + } + + sample->length = length; + sample->pointer = pointer; + sample->position = 0; + sample->update_hr = MS_S_PENDING; + ResetEvent(sample->update_event); + list_add_tail(&sample->parent->update_queue, &sample->entry); + + process_updates(sample->parent); + + hr = sample->update_hr; + if (hr != MS_S_PENDING || (flags & SSUPDATE_ASYNC)) + { + LeaveCriticalSection(&sample->parent->cs); + return hr; + } + + LeaveCriticalSection(&sample->parent->cs); + + WaitForSingleObject(sample->update_event, INFINITE); + + return sample->update_hr; } static HRESULT WINAPI IAudioStreamSampleImpl_CompletionStatus(IAudioStreamSample *iface, DWORD flags, DWORD milliseconds) @@ -210,6 +340,7 @@ static HRESULT audiostreamsample_create(struct audio_stream *parent, IAudioData object->ref = 1; object->parent = parent; object->audio_data = audio_data; + object->update_event = CreateEventW(NULL, FALSE, FALSE, NULL); *audio_stream_sample = &object->IAudioStreamSample_iface; @@ -967,6 +1098,8 @@ static HRESULT WINAPI audio_sink_EndOfStream(IPin *iface) stream->eos = TRUE; + process_updates(stream); + LeaveCriticalSection(&stream->cs); return S_OK; @@ -1113,6 +1246,8 @@ static HRESULT WINAPI audio_meminput_Receive(IMemInputPin *iface, IMediaSample * IMediaSample_AddRef(receive->sample); list_add_tail(&stream->receive_queue, &receive->entry); + process_updates(stream); + LeaveCriticalSection(&stream->cs); return S_OK; @@ -1169,6 +1304,7 @@ HRESULT audio_stream_create(IMultiMediaStream *parent, const MSPID *purpose_id, object->purpose_id = *purpose_id; object->stream_type = stream_type; list_init(&object->receive_queue); + list_init(&object->update_queue); *media_stream = &object->IAMMediaStream_iface; diff --git a/dlls/amstream/tests/amstream.c b/dlls/amstream/tests/amstream.c index 2582d3c69d..a0ac76d557 100644 --- a/dlls/amstream/tests/amstream.c +++ b/dlls/amstream/tests/amstream.c @@ -2370,10 +2370,28 @@ static void testfilter_destroy(struct strmbase_filter *iface) strmbase_filter_cleanup(&filter->filter); } +static HRESULT testfilter_init_stream(struct strmbase_filter *iface) +{ + struct testfilter *filter = impl_from_BaseFilter(iface); + + BaseOutputPinImpl_Active(&filter->source); + return S_OK; +} + +static HRESULT testfilter_cleanup_stream(struct strmbase_filter *iface) +{ + struct testfilter *filter = impl_from_BaseFilter(iface); + + BaseOutputPinImpl_Inactive(&filter->source); + return S_OK; +} + static const struct strmbase_filter_ops testfilter_ops = { .filter_get_pin = testfilter_get_pin, .filter_destroy = testfilter_destroy, + .filter_init_stream = testfilter_init_stream, + .filter_cleanup_stream = testfilter_cleanup_stream, }; static HRESULT testsource_query_accept(struct strmbase_pin *iface, const AM_MEDIA_TYPE *mt) @@ -3038,6 +3056,301 @@ static void test_audiostream_receive(void) ok(!ref, "Got outstanding refcount %d.\n", ref); } +static void CALLBACK apc_func(ULONG_PTR param) +{ +} + +static IMediaSample *audiostream_allocate_sample(struct testfilter *source, const BYTE *input_data, DWORD input_length) +{ + IMediaSample *sample; + BYTE *sample_data; + HRESULT hr; + + hr = BaseOutputPinImpl_GetDeliveryBuffer(&source->source, &sample, NULL, NULL, 0); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IMediaSample_GetPointer(sample, &sample_data); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IMediaSample_SetActualDataLength(sample, input_length); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + memcpy(sample_data, input_data, input_length); + + return sample; +} + +static IPin *audiostream_pin; +static IMemInputPin *audiostream_mem_input_pin; +static IMediaSample *audiostream_media_sample; + +static DWORD CALLBACK audiostream_end_of_stream(void *param) +{ + HRESULT hr; + + Sleep(100); + hr = IPin_EndOfStream(audiostream_pin); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + return 0; +} + +static DWORD CALLBACK audiostream_receive(void *param) +{ + HRESULT hr; + + Sleep(100); + hr = IMemInputPin_Receive(audiostream_mem_input_pin, audiostream_media_sample); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + return 0; +} + +static void test_audiostreamsample_update(void) +{ + static const WAVEFORMATEX format = + { + .wFormatTag = WAVE_FORMAT_PCM, + .nChannels = 1, + .nSamplesPerSec = 11025, + .wBitsPerSample = 16, + .nBlockAlign = 2, + .nAvgBytesPerSec = 2 * 11025, + }; + + const AM_MEDIA_TYPE mt = + { + .majortype = MEDIATYPE_Audio, + .subtype = MEDIASUBTYPE_PCM, + .formattype = FORMAT_WaveFormatEx, + .cbFormat = sizeof(WAVEFORMATEX), + .pbFormat = (BYTE *)&format, + }; + + static const BYTE test_data[] = { 0, 1, 2, 3, 4, 5, 6, 7 }; + IAMMultiMediaStream *mmstream = create_ammultimediastream(); + IAudioStreamSample *stream_sample; + IAudioMediaStream *audio_stream; + IMediaControl *media_control; + IMemInputPin *mem_input_pin; + IMediaSample *media_sample1; + IMediaSample *media_sample2; + struct testfilter source; + IAudioData *audio_data; + IGraphBuilder *graph; + IMediaStream *stream; + DWORD actual_length; + BYTE buffer[6]; + HANDLE thread; + HANDLE event; + HRESULT hr; + ULONG ref; + IPin *pin; + + hr = IAMMultiMediaStream_Initialize(mmstream, STREAMTYPE_READ, 0, NULL); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IAMMultiMediaStream_AddMediaStream(mmstream, NULL, &MSPID_PrimaryAudio, 0, &stream); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IMediaStream_QueryInterface(stream, &IID_IAudioMediaStream, (void **)&audio_stream); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IMediaStream_QueryInterface(stream, &IID_IPin, (void **)&pin); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IMediaStream_QueryInterface(stream, &IID_IMemInputPin, (void **)&mem_input_pin); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IAMMultiMediaStream_GetFilterGraph(mmstream, &graph); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(graph != NULL, "Expected non-null graph\n");\ + hr = IGraphBuilder_QueryInterface(graph, &IID_IMediaControl, (void **)&media_control); + ok(hr == S_OK, "Got hr %#x.\n", hr); + testfilter_init(&source); + hr = IGraphBuilder_AddFilter(graph, &source.filter.IBaseFilter_iface, NULL); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = CoCreateInstance(&CLSID_AMAudioData, NULL, CLSCTX_INPROC_SERVER, &IID_IAudioData, (void **)&audio_data); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IAudioMediaStream_CreateSample(audio_stream, audio_data, 0, &stream_sample); + ok(hr == S_OK, "Got hr %#x.\n", hr); + event = CreateEventW(NULL, FALSE, FALSE, NULL); + ok(event != NULL, "Expected non-NULL event."); + + hr = IAudioStreamSample_Update(stream_sample, 0, event, apc_func, 0); + ok(hr == MS_E_NOTINIT, "Got hr %#x.\n", hr); + + hr = IAudioStreamSample_Update(stream_sample, 0, NULL, NULL, 0); + ok(hr == MS_E_NOTINIT, "Got hr %#x.\n", hr); + + hr = IAudioData_SetBuffer(audio_data, sizeof(buffer), buffer, 0); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IAudioStreamSample_Update(stream_sample, 0, event, apc_func, 0); + ok(hr == E_INVALIDARG, "Got hr %#x.\n", hr); + + hr = IAudioStreamSample_Update(stream_sample, 0, NULL, NULL, 0); + ok(hr == MS_E_NOTRUNNING, "Got hr %#x.\n", hr); + + hr = IAMMultiMediaStream_SetState(mmstream, STREAMSTATE_RUN); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IAudioStreamSample_Update(stream_sample, 0, NULL, NULL, 0); + ok(hr == MS_S_ENDOFSTREAM, "Got hr %#x.\n", hr); + + hr = IAMMultiMediaStream_SetState(mmstream, STREAMSTATE_STOP); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IGraphBuilder_ConnectDirect(graph, &source.source.pin.IPin_iface, pin, &mt); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IAMMultiMediaStream_SetState(mmstream, STREAMSTATE_RUN); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + media_sample1 = audiostream_allocate_sample(&source, test_data, 8); + hr = IMemInputPin_Receive(mem_input_pin, media_sample1); + ok(hr == S_OK, "Got hr %#x.\n", hr); + IMediaSample_AddRef(media_sample1); + ref = IMediaSample_Release(media_sample1); + ok(ref == 2, "Got outstanding refcount %d.\n", ref); + + hr = IAudioStreamSample_Update(stream_sample, 0, NULL, NULL, 0); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IAudioData_GetInfo(audio_data, NULL, NULL, &actual_length); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(actual_length == 6, "Got actual length %u.\n", actual_length); + + ok(memcmp(buffer, test_data, 6) == 0, "Sample data didn't match.\n"); + + IMediaSample_AddRef(media_sample1); + ref = IMediaSample_Release(media_sample1); + ok(ref == 2, "Got outstanding refcount %d.\n", ref); + + media_sample2 = audiostream_allocate_sample(&source, test_data, 8); + hr = IMemInputPin_Receive(mem_input_pin, media_sample2); + ok(hr == S_OK, "Got hr %#x.\n", hr); + IMediaSample_AddRef(media_sample2); + ref = IMediaSample_Release(media_sample2); + ok(ref == 2, "Got outstanding refcount %d.\n", ref); + + hr = IAudioStreamSample_Update(stream_sample, 0, NULL, NULL, 0); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IAudioData_GetInfo(audio_data, NULL, NULL, &actual_length); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(actual_length == 6, "Got actual length %u.\n", actual_length); + + ok(memcmp(buffer, &test_data[6], 2) == 0, "Sample data didn't match.\n"); + ok(memcmp(&buffer[2], test_data, 4) == 0, "Sample data didn't match.\n"); + + ref = IMediaSample_Release(media_sample1); + ok(!ref, "Got outstanding refcount %d.\n", ref); + + hr = IPin_EndOfStream(pin); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IAudioStreamSample_Update(stream_sample, 0, NULL, NULL, 0); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IAudioData_GetInfo(audio_data, NULL, NULL, &actual_length); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(actual_length == 4, "Got actual length %u.\n", actual_length); + + ok(memcmp(buffer, &test_data[4], 4) == 0, "Sample data didn't match.\n"); + + ref = IMediaSample_Release(media_sample2); + ok(!ref, "Got outstanding refcount %d.\n", ref); + + hr = IAudioStreamSample_Update(stream_sample, 0, NULL, NULL, 0); + ok(hr == MS_S_ENDOFSTREAM, "Got hr %#x.\n", hr); + + hr = IAMMultiMediaStream_SetState(mmstream, STREAMSTATE_STOP); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IMediaControl_Pause(media_control); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + media_sample1 = audiostream_allocate_sample(&source, test_data, 6); + hr = IMemInputPin_Receive(mem_input_pin, media_sample1); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ref = IMediaSample_Release(media_sample1); + ok(ref == 1, "Got outstanding refcount %d.\n", ref); + + hr = IAudioStreamSample_Update(stream_sample, 0, NULL, NULL, 0); + ok(hr == MS_E_NOTRUNNING, "Got hr %#x.\n", hr); + + hr = IMediaControl_Stop(media_control); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IAMMultiMediaStream_SetState(mmstream, STREAMSTATE_RUN); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + media_sample1 = audiostream_allocate_sample(&source, test_data, 6); + + audiostream_mem_input_pin = mem_input_pin; + audiostream_media_sample = media_sample1; + thread = CreateThread(NULL, 0, audiostream_receive, NULL, 0, NULL); + + hr = IAudioStreamSample_Update(stream_sample, 0, NULL, NULL, 0); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IAudioData_GetInfo(audio_data, NULL, NULL, &actual_length); + ok(hr == S_OK, "Got hr %#x.\n", hr); + ok(actual_length == 6, "Got actual length %u.\n", actual_length); + + ok(memcmp(buffer, test_data, 6) == 0, "Sample data didn't match.\n"); + + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + + ref = IMediaSample_Release(media_sample1); + ok(!ref, "Got outstanding refcount %d.\n", ref); + + audiostream_pin = pin; + thread = CreateThread(NULL, 0, audiostream_end_of_stream, NULL, 0, NULL); + + hr = IAudioStreamSample_Update(stream_sample, 0, NULL, NULL, 0); + ok(hr == MS_S_ENDOFSTREAM, "Got hr %#x.\n", hr); + + WaitForSingleObject(thread, INFINITE); + CloseHandle(thread); + + hr = IAMMultiMediaStream_SetState(mmstream, STREAMSTATE_STOP); + ok(hr == S_OK, "Got hr %#x.\n", hr); + hr = IAMMultiMediaStream_SetState(mmstream, STREAMSTATE_RUN); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IAudioStreamSample_Update(stream_sample, SSUPDATE_ASYNC, NULL, NULL, 0); + ok(hr == MS_S_PENDING, "Got hr %#x.\n", hr); + + IAudioStreamSample_AddRef(stream_sample); + ref = IAudioStreamSample_Release(stream_sample); + ok(ref == 1, "Got outstanding refcount %d.\n", ref); + + hr = IAudioStreamSample_Update(stream_sample, SSUPDATE_ASYNC, NULL, NULL, 0); + ok(hr == MS_E_BUSY, "Got hr %#x.\n", hr); + + hr = IAMMultiMediaStream_SetState(mmstream, STREAMSTATE_STOP); + ok(hr == S_OK, "Got hr %#x.\n", hr); + IGraphBuilder_Disconnect(graph, pin); + IGraphBuilder_Disconnect(graph, &source.source.pin.IPin_iface); + hr = IAMMultiMediaStream_SetState(mmstream, STREAMSTATE_RUN); + ok(hr == S_OK, "Got hr %#x.\n", hr); + + hr = IAudioStreamSample_Update(stream_sample, 0, NULL, NULL, 0); + ok(hr == MS_S_ENDOFSTREAM, "Got hr %#x.\n", hr); + + hr = IAMMultiMediaStream_SetState(mmstream, STREAMSTATE_STOP); + + CloseHandle(event); + ref = IAudioStreamSample_Release(stream_sample); + ok(!ref, "Got outstanding refcount %d.\n", ref); + ref = IAudioData_Release(audio_data); + ok(!ref, "Got outstanding refcount %d.\n", ref); + ref = IAMMultiMediaStream_Release(mmstream); + ok(!ref, "Got outstanding refcount %d.\n", ref); + IMediaControl_Release(media_control); + ref = IGraphBuilder_Release(graph); + ok(!ref, "Got outstanding refcount %d.\n", ref); + IPin_Release(pin); + IMemInputPin_Release(mem_input_pin); + IAudioMediaStream_Release(audio_stream); + ref = IMediaStream_Release(stream); + ok(!ref, "Got outstanding refcount %d.\n", ref); +} + void test_mediastreamfilter_get_state(void) { IAMMultiMediaStream *mmstream = create_ammultimediastream(); @@ -3213,6 +3526,8 @@ START_TEST(amstream) test_audiostream_end_of_stream(); test_audiostream_receive(); + test_audiostreamsample_update(); + test_mediastreamfilter_get_state(); test_mediastreamfilter_stop_pause_run(); -- 2.17.1