From 0986fd3ab8cef6bb47669c067c50b9ecd12a2b01 Mon Sep 17 00:00:00 2001 From: korenkonder Date: Fri, 14 Feb 2025 00:41:49 +0300 Subject: [PATCH] Added TaskMovie --- src/CRE/ogg_vorbis.cpp | 2 +- src/CRE/render.cpp | 15 + src/CRE/sound.cpp | 6 +- src/CRE/sound.hpp | 1 + src/ReDIVA/app.cpp | 16 +- src/ReDIVA/movie_play_lib/audio_decoder.cpp | 193 ++++ src/ReDIVA/movie_play_lib/audio_decoder.hpp | 37 + src/ReDIVA/movie_play_lib/command_args.cpp | 97 ++ src/ReDIVA/movie_play_lib/command_args.hpp | 52 + .../movie_play_lib/dx_surface_buffer.cpp | 128 +++ .../movie_play_lib/dx_surface_buffer.hpp | 41 + src/ReDIVA/movie_play_lib/dxva2.cpp | 74 ++ src/ReDIVA/movie_play_lib/dxva2.hpp | 14 + src/ReDIVA/movie_play_lib/external_clock.cpp | 78 ++ src/ReDIVA/movie_play_lib/external_clock.hpp | 39 + .../movie_play_lib/gl_dx_interop_texture.cpp | 244 +++++ .../movie_play_lib/gl_dx_interop_texture.hpp | 53 + src/ReDIVA/movie_play_lib/media_clock.cpp | 107 ++ src/ReDIVA/movie_play_lib/media_clock.hpp | 41 + .../movie_play_lib/media_foundation.cpp | 163 +++ .../movie_play_lib/media_foundation.hpp | 16 + src/ReDIVA/movie_play_lib/media_session.cpp | 963 ++++++++++++++++++ src/ReDIVA/movie_play_lib/media_session.hpp | 115 +++ src/ReDIVA/movie_play_lib/media_source.cpp | 632 ++++++++++++ src/ReDIVA/movie_play_lib/media_source.hpp | 70 ++ src/ReDIVA/movie_play_lib/media_stream.cpp | 214 ++++ src/ReDIVA/movie_play_lib/media_stream.hpp | 50 + src/ReDIVA/movie_play_lib/mf_sample_list.cpp | 66 ++ src/ReDIVA/movie_play_lib/mf_sample_list.hpp | 53 + src/ReDIVA/movie_play_lib/movie_play_lib.cpp | 128 +++ src/ReDIVA/movie_play_lib/movie_play_lib.hpp | 280 +++++ src/ReDIVA/movie_play_lib/player.cpp | 288 ++++++ src/ReDIVA/movie_play_lib/player.hpp | 51 + src/ReDIVA/movie_play_lib/tracked_sample.cpp | 333 ++++++ src/ReDIVA/movie_play_lib/tracked_sample.hpp | 93 ++ src/ReDIVA/movie_play_lib/transform_base.cpp | 288 ++++++ src/ReDIVA/movie_play_lib/transform_base.hpp | 68 ++ src/ReDIVA/movie_play_lib/video_decoder.cpp | 459 +++++++++ src/ReDIVA/movie_play_lib/video_decoder.hpp | 56 + src/ReDIVA/movie_play_lib/video_renderer.cpp | 127 +++ src/ReDIVA/movie_play_lib/video_renderer.hpp | 42 + src/ReDIVA/movie_play_lib/wasapi_renderer.cpp | 284 ++++++ src/ReDIVA/movie_play_lib/wasapi_renderer.hpp | 57 ++ src/ReDIVA/pv_game/pv_game.cpp | 77 +- src/ReDIVA/pv_game/pv_game.hpp | 2 +- src/ReDIVA/pv_game/pv_game_pv_data.cpp | 106 +- src/ReDIVA/task_movie.cpp | 510 ++++++++++ src/ReDIVA/task_movie.hpp | 127 +++ 48 files changed, 6863 insertions(+), 93 deletions(-) create mode 100644 src/ReDIVA/movie_play_lib/audio_decoder.cpp create mode 100644 src/ReDIVA/movie_play_lib/audio_decoder.hpp create mode 100644 src/ReDIVA/movie_play_lib/command_args.cpp create mode 100644 src/ReDIVA/movie_play_lib/command_args.hpp create mode 100644 src/ReDIVA/movie_play_lib/dx_surface_buffer.cpp create mode 100644 src/ReDIVA/movie_play_lib/dx_surface_buffer.hpp create mode 100644 src/ReDIVA/movie_play_lib/dxva2.cpp create mode 100644 src/ReDIVA/movie_play_lib/dxva2.hpp create mode 100644 src/ReDIVA/movie_play_lib/external_clock.cpp create mode 100644 src/ReDIVA/movie_play_lib/external_clock.hpp create mode 100644 src/ReDIVA/movie_play_lib/gl_dx_interop_texture.cpp create mode 100644 src/ReDIVA/movie_play_lib/gl_dx_interop_texture.hpp create mode 100644 src/ReDIVA/movie_play_lib/media_clock.cpp create mode 100644 src/ReDIVA/movie_play_lib/media_clock.hpp create mode 100644 src/ReDIVA/movie_play_lib/media_foundation.cpp create mode 100644 src/ReDIVA/movie_play_lib/media_foundation.hpp create mode 100644 src/ReDIVA/movie_play_lib/media_session.cpp create mode 100644 src/ReDIVA/movie_play_lib/media_session.hpp create mode 100644 src/ReDIVA/movie_play_lib/media_source.cpp create mode 100644 src/ReDIVA/movie_play_lib/media_source.hpp create mode 100644 src/ReDIVA/movie_play_lib/media_stream.cpp create mode 100644 src/ReDIVA/movie_play_lib/media_stream.hpp create mode 100644 src/ReDIVA/movie_play_lib/mf_sample_list.cpp create mode 100644 src/ReDIVA/movie_play_lib/mf_sample_list.hpp create mode 100644 src/ReDIVA/movie_play_lib/movie_play_lib.cpp create mode 100644 src/ReDIVA/movie_play_lib/movie_play_lib.hpp create mode 100644 src/ReDIVA/movie_play_lib/player.cpp create mode 100644 src/ReDIVA/movie_play_lib/player.hpp create mode 100644 src/ReDIVA/movie_play_lib/tracked_sample.cpp create mode 100644 src/ReDIVA/movie_play_lib/tracked_sample.hpp create mode 100644 src/ReDIVA/movie_play_lib/transform_base.cpp create mode 100644 src/ReDIVA/movie_play_lib/transform_base.hpp create mode 100644 src/ReDIVA/movie_play_lib/video_decoder.cpp create mode 100644 src/ReDIVA/movie_play_lib/video_decoder.hpp create mode 100644 src/ReDIVA/movie_play_lib/video_renderer.cpp create mode 100644 src/ReDIVA/movie_play_lib/video_renderer.hpp create mode 100644 src/ReDIVA/movie_play_lib/wasapi_renderer.cpp create mode 100644 src/ReDIVA/movie_play_lib/wasapi_renderer.hpp create mode 100644 src/ReDIVA/task_movie.cpp create mode 100644 src/ReDIVA/task_movie.hpp diff --git a/src/CRE/ogg_vorbis.cpp b/src/CRE/ogg_vorbis.cpp index f56c3a56..b30bc4b0 100644 --- a/src/CRE/ogg_vorbis.cpp +++ b/src/CRE/ogg_vorbis.cpp @@ -380,7 +380,7 @@ void OggFileHandler::Ctrl() { streaming_channel->SetCallback(OggFileHandler::FillBufferStatic, this); break; case OGG_FILE_HANDLER_PLAYBACK_STATE_UNLOAD: - streaming_channel->reset_state = 1; + streaming_channel->SetResetOne(); break; } } diff --git a/src/CRE/render.cpp b/src/CRE/render.cpp index f97a26ae..417d40ca 100644 --- a/src/CRE/render.cpp +++ b/src/CRE/render.cpp @@ -1090,6 +1090,21 @@ namespace rndr { } else if (stage_index != stage_index_prev) reset_exposure = true; + + if (movie_textures_data[0]) { + extern texture* task_movie_get_texture(int32_t index = 0); + texture* tex = task_movie_get_texture(); + if (tex) { + movie_textures[0].Bind(); + gl_state_active_bind_texture_2d(0, tex->glid); + gl_state_bind_sampler(0, rctx_ptr->render_samplers[0]); + gl_state_set_viewport(0, 0, movie_textures_data[0]->width, movie_textures_data[0]->height); + uniform_value[U_REDUCE] = 0; + shaders_ft.set(SHADER_FT_REDUCE); + draw_quad(tex->width, tex->height, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f); + gl_state_bind_framebuffer(0); + } + } } int32_t Render::render_texture_set(texture* render_texture, bool task_photo) { diff --git a/src/CRE/sound.cpp b/src/CRE/sound.cpp index b38537a6..8362e32c 100644 --- a/src/CRE/sound.cpp +++ b/src/CRE/sound.cpp @@ -794,7 +794,7 @@ namespace sound { } void StreamingChannel::Reset() { - reset_state = 1; + SetResetOne(); FillBuffer(0, 0, 0.0f); if (buffer) { free(buffer); @@ -831,6 +831,10 @@ namespace sound { std::unique_lock u_lock(mtx); master_volume = value; } + + void StreamingChannel::SetResetOne() { + reset_state = 1; + } } } diff --git a/src/CRE/sound.hpp b/src/CRE/sound.hpp index bcdd4945..dec5e607 100644 --- a/src/CRE/sound.hpp +++ b/src/CRE/sound.hpp @@ -151,6 +151,7 @@ namespace sound { bool SetCallback(CallbackFunc func, void* data); void SetChannelsVolume(int32_t mask, float_t value); void SetMasterVolume(float_t value); + void SetResetOne(); }; } } diff --git a/src/ReDIVA/app.cpp b/src/ReDIVA/app.cpp index d354da33..846a7b0c 100644 --- a/src/ReDIVA/app.cpp +++ b/src/ReDIVA/app.cpp @@ -77,8 +77,8 @@ #include "shared.hpp" #include "task_window.hpp" #include "x_pv_game.hpp" -#if BAKE_VIDEO #include +#if BAKE_VIDEO #include #pragma comment(lib, "d3d11.lib") #endif @@ -510,13 +510,13 @@ bool app_render_data::load() { } #endif -#if BAKE_VIDEO if (!Vulkan::use && !gladLoadWGLLoader((GLADloadproc)glfwGetProcAddress, GetDC(window_handle))) return false; - + +#if BAKE_VIDEO D3D11CreateDevice(0, D3D_DRIVER_TYPE_HARDWARE, 0, 0, 0, 0, D3D11_SDK_VERSION, &d3d_device, 0, &d3d_device_context); - + if (!Vulkan::use && GLAD_WGL_NV_DX_interop2) d3d_gl_handle = wglDXOpenDeviceNV(d3d_device); #endif @@ -765,6 +765,10 @@ static render_context* render_context_load() { font_info_default_init(); dw_console_c_buff_array_init(); sound_init(); + + extern HRESULT mf_init(); + mf_init(); + wave_audio_storage_init(); ogg_file_handler_storage_init(); ogg_playback_data_init(); @@ -1754,6 +1758,10 @@ static void render_context_dispose(render_context* rctx) { ogg_playback_data_free(); ogg_file_handler_storage_free(); wave_audio_storage_free(); + + extern HRESULT mf_shutdown(); + mf_shutdown(); + sound_free(); dw_console_c_buff_array_free(); font_info_default_free(); diff --git a/src/ReDIVA/movie_play_lib/audio_decoder.cpp b/src/ReDIVA/movie_play_lib/audio_decoder.cpp new file mode 100644 index 00000000..5b2eefc4 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/audio_decoder.cpp @@ -0,0 +1,193 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "audio_decoder.hpp" +#include "media_foundation.hpp" +#include + +namespace MoviePlayLib { + AudioDecoder::AudioDecoder(HRESULT& hr, MediaStatsLock* media_stats_lock, + IMediaClock* media_clock, IMediaSource* media_source) + : TransformBase(hr, media_stats_lock, media_clock, media_source), + output_sub_type(MFAudioFormat_Float), sample_rate(), channels(), buffer_size() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + if (SUCCEEDED(hr)) { + IMFMediaType* mf_media_type = 0; + hr = media_source->GetAudioMFMediaType(&mf_media_type); + if (SUCCEEDED(hr)) + hr = GetMFTransform(mf_media_type, output_sub_type, mf_transform); + + if (mf_media_type) { + mf_media_type->Release(); + mf_media_type = 0; + } + + if (SUCCEEDED(hr)) + hr = InitFromMediaSource(); + } + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + AudioDecoder::~AudioDecoder() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + HRESULT AudioDecoder::ProcessOutput() { + if (sample_list_active.GetSamplesCount() >= 10) + return S_FALSE; + + MFT_OUTPUT_DATA_BUFFER output_sample = {}; + DWORD output_status = 0; + IMFMediaBuffer* mf_media_buffer = 0; + IMFSample* mf_sample = 0; + + if (SUCCEEDED(MFCreateMemoryBuffer(buffer_size, &mf_media_buffer)) + && SUCCEEDED(MFCreateSample(&mf_sample)) && SUCCEEDED(mf_sample->AddBuffer(mf_media_buffer))) { + output_sample.pSample = mf_sample; + mf_sample->AddRef(); + } + + if (mf_media_buffer) { + mf_media_buffer->Release(); + mf_media_buffer = 0; + } + + if (mf_sample) { + mf_sample->Release(); + mf_sample = 0; + } + + const int64_t process_output_begin = GetTimestamp(); + HRESULT hr = mf_transform->ProcessOutput(0, 1, &output_sample, &output_status); + const int64_t process_output_end = GetTimestamp(); + + int64_t sample_time; + if (FAILED(hr)) { + switch (hr) { + case MF_E_TRANSFORM_STREAM_CHANGE: + hr = InitFromMediaSource(); + break; + case MF_E_TRANSFORM_NEED_MORE_INPUT: + hr = S_OK; + if (stream_state == 2) + stream_state = 3; + else + stream_state = 0; + break; + default: + hr = S_FALSE; + stream_state = 0; + break; + } + goto End; + } + + sample_list_active.AddSample(mf_sample, false); + + sample_time = 0; + output_sample.pSample->GetSampleTime(&sample_time); + + media_stats_lock->SetAudioProcessOutput( + CalcTimeMsec(process_output_begin, process_output_end), (double_t)sample_time * 0.0000001); + + End: + if (output_sample.pSample) { + output_sample.pSample->Release(); + output_sample.pSample = 0; + } + + if (output_sample.pEvents) { + output_sample.pEvents->Release(); + output_sample.pEvents = 0; + } + return hr; + } + + void AudioDecoder::SetSampleTime(double_t value) { + media_stats_lock->SetAudioSampleTime(value); + } + + HRESULT AudioDecoder::InitFromMediaSource() { + IMFMediaType* video_mf_media_type = 0; + IMFMediaType* mf_media_type = 0; + + HRESULT hr; + hr = media_source->GetAudioMFMediaType(&video_mf_media_type); + if (SUCCEEDED(hr)) + hr = MoviePlayLib::GetMFMediaType(mf_transform, output_sub_type, mf_media_type); + + uint32_t buffer_size = 0; + MFT_OUTPUT_STREAM_INFO stream_info = {}; + if (SUCCEEDED(hr)) { + hr = this->mf_transform->GetOutputStreamInfo(0, &stream_info); + buffer_size = stream_info.cbSize; + } + this->buffer_size = buffer_size; + + uint32_t bits_per_sample = 0; + uint32_t block_alignment = 0; + uint32_t avg_bytes_per_second = 0; + if (FAILED(hr)) + goto End; + + hr = mf_media_type->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &channels); + if (FAILED(hr)) + goto End; + + hr = mf_media_type->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &sample_rate); + if (FAILED(hr)) + goto End; + + hr = mf_media_type->GetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, &bits_per_sample); + if (FAILED(hr)) + goto End; + + hr = mf_media_type->GetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, &block_alignment); + if (FAILED(hr)) + goto End; + + hr = mf_media_type->GetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, &avg_bytes_per_second); + if (SUCCEEDED(hr)) + media_stats_lock->SetAudioParams(sample_rate, channels); + + End: + if (mf_media_type) { + mf_media_type->Release(); + mf_media_type = 0; + } + + if (video_mf_media_type) { + video_mf_media_type->Release(); + video_mf_media_type = 0; + } + return hr; + } + + HRESULT AudioDecoder::Create(MediaStatsLock* media_stats_lock, + IMediaClock* media_clock, IMediaSource* media_source, IMediaTransform*& ptr) { + HRESULT hr; + AudioDecoder* p = new AudioDecoder(hr, media_stats_lock, media_clock, media_source); + if (!p) + return E_OUTOFMEMORY; + + if (SUCCEEDED(hr)) { + ptr = p; + p->AddRef(); + } + else + p->Shutdown(); + + p->Release(); + return S_OK; + } + + inline void AudioDecoder::Destroy(AudioDecoder* ptr) { + if (!ptr) + return; + + delete ptr; + } +} diff --git a/src/ReDIVA/movie_play_lib/audio_decoder.hpp b/src/ReDIVA/movie_play_lib/audio_decoder.hpp new file mode 100644 index 00000000..e4a80afd --- /dev/null +++ b/src/ReDIVA/movie_play_lib/audio_decoder.hpp @@ -0,0 +1,37 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "transform_base.hpp" + +namespace MoviePlayLib { + class AudioDecoder : public TransformBase { + protected: + GUID output_sub_type; + uint32_t sample_rate; + uint32_t channels; + uint32_t buffer_size; + + public: + + AudioDecoder(HRESULT& hr, MediaStatsLock* media_stats_lock, + IMediaClock* media_clock, IMediaSource* media_source); + virtual ~AudioDecoder() override; + + virtual HRESULT ProcessOutput() override; + virtual void SetSampleTime(double_t value) override; + + HRESULT InitFromMediaSource(); + + static HRESULT Create(MediaStatsLock* media_stats_lock, + IMediaClock* media_clock, IMediaSource* media_source, IMediaTransform*& ptr); + static void Destroy(AudioDecoder* ptr); + + inline void Destroy() { + Destroy(this); + } + }; +} diff --git a/src/ReDIVA/movie_play_lib/command_args.cpp b/src/ReDIVA/movie_play_lib/command_args.cpp new file mode 100644 index 00000000..2fe9054c --- /dev/null +++ b/src/ReDIVA/movie_play_lib/command_args.cpp @@ -0,0 +1,97 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "command_args.hpp" + +namespace MoviePlayLib { + HRESULT CommandArgs::QueryInterface(const IID& riid, void** ppvObject) { + if (!ppvObject) + return E_INVALIDARG; + + if (riid == __uuidof(CommandArgs) || riid == __uuidof(IUnknown)) { + *ppvObject = this; + AddRef(); + return S_OK; + } + + *ppvObject = 0; + return E_NOINTERFACE; + } + + ULONG CommandArgs::AddRef() { + return ++ref_count; + } + + ULONG CommandArgs::Release() { + if (!--ref_count) + Destroy(); + return ref_count; + } + + CommandArgs::~CommandArgs() { + if (str) + SysFreeString(str); + } + + CommandArgs::Type CommandArgs::GetType() { + return type; + } + + int64_t CommandArgs::GetPosition() { + return position; + } + + OLECHAR* CommandArgs::GetPath() { + return str; + } + + HRESULT CommandArgs::SetType(CommandArgs::Type value) { + type = value; + return S_OK; + } + + HRESULT CommandArgs::SetPosition(int64_t value) { + position = value; + return S_OK; + } + + CommandArgs::CommandArgs(CommandArgs::Type type) + : ref_count(), lock(), type(type), position(), str() { + + } + + HRESULT CommandArgs::SetPath(const OLECHAR* value) { + if (str) { + SysFreeString(str); + str = 0; + } + + if (!value) + return S_OK; + + str = SysAllocString(value); + if (!str) + return E_OUTOFMEMORY; + return S_OK; + } + + HRESULT CommandArgs::Create(CommandArgs::Type type, CommandArgs*& ptr) { + CommandArgs* p = new CommandArgs(type); + if (!p) + return E_OUTOFMEMORY; + + ptr = p; + p->AddRef(); + p->Release(); + return S_OK; + } + + inline void CommandArgs::Destroy(CommandArgs* ptr) { + if (!ptr) + return; + + delete ptr; + } +} diff --git a/src/ReDIVA/movie_play_lib/command_args.hpp b/src/ReDIVA/movie_play_lib/command_args.hpp new file mode 100644 index 00000000..dac653dc --- /dev/null +++ b/src/ReDIVA/movie_play_lib/command_args.hpp @@ -0,0 +1,52 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "movie_play_lib.hpp" + +namespace MoviePlayLib { + class DECLSPEC_UUID("5D520F14-C168-43DA-B799-4FEC99669FB0") + CommandArgs : public IUnknown { + public: + enum class Type { + Open = 0, + SetPosition, + Play, + Pause, + Close, + Max, + }; + + protected: + RefCount ref_count; + Lock lock; + Type type; + int64_t position; + OLECHAR* str; + + public: + virtual HRESULT QueryInterface(const IID& riid, void** ppvObject) override; + virtual ULONG AddRef() override; + virtual ULONG Release() override; + + CommandArgs(CommandArgs::Type type); + virtual ~CommandArgs(); + + virtual Type GetType(); + virtual int64_t GetPosition(); + virtual OLECHAR* GetPath(); + virtual HRESULT SetType(Type value); + virtual HRESULT SetPosition(int64_t value); + virtual HRESULT SetPath(const OLECHAR* value); + + static HRESULT Create(CommandArgs::Type type, CommandArgs*& ptr); + static void Destroy(CommandArgs* ptr); + + inline void Destroy() { + Destroy(this); + } + }; +} diff --git a/src/ReDIVA/movie_play_lib/dx_surface_buffer.cpp b/src/ReDIVA/movie_play_lib/dx_surface_buffer.cpp new file mode 100644 index 00000000..38fc64d9 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/dx_surface_buffer.cpp @@ -0,0 +1,128 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "dx_surface_buffer.hpp" +#include +#pragma comment(lib, "strmiids.lib") + +namespace MoviePlayLib { + HRESULT DXSurfaceBuffer::QueryInterface(const IID& riid, void** ppvObject) { + if (!ppvObject) + return E_INVALIDARG; + + if (riid == __uuidof(IMFMediaBuffer)) { + *ppvObject = this; + AddRef(); + return S_OK; + } + else if (riid == __uuidof(IMFGetService)) { + IMFGetService* mf_get_service = this; + *ppvObject = mf_get_service; + mf_get_service->AddRef(); + return S_OK; + } + else if (riid == __uuidof(IDirect3DTexture9) && d3d_texture) { + d3d_texture->AddRef(); + *ppvObject = d3d_texture; + return S_OK; + } + else if (riid == __uuidof(IDirect3DSurface9) && d3d_surface) { + d3d_surface->AddRef(); + *ppvObject = d3d_surface; + return S_OK; + } + else if (riid == __uuidof(IUnknown)) { + *ppvObject = this; + AddRef(); + return S_OK; + } + + *ppvObject = 0; + return E_NOINTERFACE; + } + + ULONG DXSurfaceBuffer::AddRef() { + return ++ref_count; + } + + ULONG DXSurfaceBuffer::Release() { + if (!--ref_count) + Destroy(); + return ref_count; + } + + HRESULT DXSurfaceBuffer::Lock(BYTE** ppbBuffer, DWORD* pcbMaxLength, DWORD* pcbCurrentLength) { + return E_NOTIMPL; + } + + HRESULT DXSurfaceBuffer::Unlock() { + return E_NOTIMPL; + } + + HRESULT DXSurfaceBuffer::GetCurrentLength(DWORD* pcbCurrentLength) { + return E_NOTIMPL; + } + + HRESULT DXSurfaceBuffer::SetCurrentLength(DWORD cbCurrentLength) { + return E_NOTIMPL; + } + + HRESULT DXSurfaceBuffer::GetMaxLength(DWORD* pcbMaxLength) { + return E_NOTIMPL; + } + + HRESULT DXSurfaceBuffer::GetService(const GUID& guidService, const IID& riid, LPVOID* ppvObject) { + if (!ppvObject) + return E_INVALIDARG; + + if (guidService == MR_BUFFER_SERVICE) + return QueryInterface(riid, ppvObject); + return MF_E_UNSUPPORTED_SERVICE; + } + + DXSurfaceBuffer::DXSurfaceBuffer(HRESULT& hr, IUnknown* object) : ref_count(), d3d_texture(), d3d_surface() { + hr = object->QueryInterface(__uuidof(IDirect3DTexture9), (void**)&d3d_texture); + if (SUCCEEDED(hr)) + hr = d3d_texture->GetSurfaceLevel(0, &d3d_surface); + else { + hr = object->QueryInterface(__uuidof(IDirect3DSurface9), (void**)&d3d_surface); + if (SUCCEEDED(hr)) + d3d_surface->GetContainer(__uuidof(IDirect3DTexture9), (void**)&d3d_texture); + } + } + + DXSurfaceBuffer::~DXSurfaceBuffer() { + if (d3d_texture) { + d3d_texture->Release(); + d3d_texture = 0; + } + + if (d3d_surface) { + d3d_surface->Release(); + d3d_surface = 0; + } + } + + HRESULT DXSurfaceBuffer::Create(IUnknown* object, IMFMediaBuffer*& ptr) { + HRESULT hr = S_OK; + DXSurfaceBuffer* p = new DXSurfaceBuffer(hr, object); + if (!p) + return E_OUTOFMEMORY; + + if (SUCCEEDED(hr)) { + ptr = p; + p->AddRef(); + } + p->Release(); + return S_OK; + } + + inline void DXSurfaceBuffer::Destroy(DXSurfaceBuffer* ptr) { + if (!ptr) + return; + + delete ptr; + } +} diff --git a/src/ReDIVA/movie_play_lib/dx_surface_buffer.hpp b/src/ReDIVA/movie_play_lib/dx_surface_buffer.hpp new file mode 100644 index 00000000..7596f93a --- /dev/null +++ b/src/ReDIVA/movie_play_lib/dx_surface_buffer.hpp @@ -0,0 +1,41 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "movie_play_lib.hpp" +#include + +namespace MoviePlayLib { + class DXSurfaceBuffer : public IMFMediaBuffer, public IMFGetService { + protected: + RefCount ref_count; + IDirect3DTexture9* d3d_texture; + IDirect3DSurface9* d3d_surface; + + public: + virtual HRESULT QueryInterface(const IID& riid, void** ppvObject) override; + virtual ULONG AddRef() override; + virtual ULONG Release() override; + + virtual HRESULT Lock(BYTE** ppbBuffer, DWORD* pcbMaxLength, DWORD* pcbCurrentLength) override; + virtual HRESULT Unlock() override; + virtual HRESULT GetCurrentLength(DWORD* pcbCurrentLength) override; + virtual HRESULT SetCurrentLength(DWORD cbCurrentLength) override; + virtual HRESULT GetMaxLength(DWORD* pcbMaxLength) override; + + virtual HRESULT GetService(const GUID& guidService, const IID& riid, LPVOID* ppvObject) override; + + DXSurfaceBuffer(HRESULT& hr, IUnknown* object); + virtual ~DXSurfaceBuffer(); + + static HRESULT Create(IUnknown* object, IMFMediaBuffer*& ptr); + static void Destroy(DXSurfaceBuffer* ptr); + + inline void Destroy() { + Destroy(this); + } + }; +} diff --git a/src/ReDIVA/movie_play_lib/dxva2.cpp b/src/ReDIVA/movie_play_lib/dxva2.cpp new file mode 100644 index 00000000..a1da2bd8 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/dxva2.cpp @@ -0,0 +1,74 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "dxva2.hpp" + +namespace MoviePlayLib { + HRESULT GetDirectXVideoProcessor(IDirect3DDeviceManager9* d3d_device_manager, + const DXVA2_VideoDesc* video_desc, IDirectXVideoProcessor*& ptr) { + HANDLE output_type_info = 0; + HRESULT hr; + hr = d3d_device_manager->OpenDeviceHandle(&output_type_info); + if (FAILED(hr)) + return hr; + + IDirectXVideoProcessorService* dx_video_processor_service = 0; + IDirectXVideoProcessor* dx_video_processor = 0; + UINT guid_count = 0; + GUID* guids = 0; + hr = d3d_device_manager->GetVideoService(output_type_info, + __uuidof(IDirectXVideoProcessorService), (void**)&dx_video_processor_service); + if (FAILED(hr)) + goto End; + + hr = dx_video_processor_service->GetVideoProcessorDeviceGuids(video_desc, &guid_count, &guids); + if (FAILED(hr) || !guids) + goto End; + + for (UINT i = 0; i < guid_count && !dx_video_processor; i++) { + UINT format_count = 0; + D3DFORMAT* formats = 0; + hr = dx_video_processor_service->GetVideoProcessorRenderTargets( + guids[i], video_desc, &format_count, &formats); + if (SUCCEEDED(hr) && formats) + for (UINT j = 0; j < format_count && !dx_video_processor; j++) + if (formats[j] == D3DFMT_X8R8G8B8) + hr = dx_video_processor_service->CreateVideoProcessor(guids[i], + video_desc, D3DFMT_X8R8G8B8, 1, &dx_video_processor); + + if (formats) + CoTaskMemFree(formats); + } + + if (dx_video_processor) { + ptr = dx_video_processor; + ptr->AddRef(); + goto End; + } + else + hr = MF_E_UNSUPPORTED_D3D_TYPE; + + End: + if (guids) { + CoTaskMemFree(guids); + guids = 0; + } + + if (dx_video_processor) { + dx_video_processor->Release(); + dx_video_processor = 0; + } + + if (dx_video_processor_service) { + dx_video_processor_service->Release(); + dx_video_processor_service = 0; + } + + HRESULT _hr = d3d_device_manager->CloseDeviceHandle(output_type_info); + if (SUCCEEDED(hr)) + hr = _hr; + return hr; + } +} diff --git a/src/ReDIVA/movie_play_lib/dxva2.hpp b/src/ReDIVA/movie_play_lib/dxva2.hpp new file mode 100644 index 00000000..0db995ec --- /dev/null +++ b/src/ReDIVA/movie_play_lib/dxva2.hpp @@ -0,0 +1,14 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "movie_play_lib.hpp" +#include + +namespace MoviePlayLib { + HRESULT GetDirectXVideoProcessor(IDirect3DDeviceManager9* d3d_device_manager, + const DXVA2_VideoDesc* video_desc, IDirectXVideoProcessor*& ptr); +} diff --git a/src/ReDIVA/movie_play_lib/external_clock.cpp b/src/ReDIVA/movie_play_lib/external_clock.cpp new file mode 100644 index 00000000..657cb937 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/external_clock.cpp @@ -0,0 +1,78 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "external_clock.hpp" + +namespace MoviePlayLib { + HRESULT ExternalClock::QueryInterface(const IID& riid, void** ppvObject) { + if (!ppvObject) + return E_INVALIDARG; + + if (riid == __uuidof(IMediaClock) || riid == __uuidof(IUnknown)) { + *ppvObject = this; + AddRef(); + return S_OK; + } + + *ppvObject = 0; + return E_NOINTERFACE; + } + + ULONG ExternalClock::AddRef() { + return ++ref_count; + } + + ULONG ExternalClock::Release() { + if (!--ref_count) + Destroy(); + return ref_count; + } + + HRESULT ExternalClock::TimeEnd() { + return S_OK; + } + + HRESULT ExternalClock::TimeBegin() { + return S_OK; + } + + HRESULT ExternalClock::SetTime(INT64 value) { + return S_OK; + } + + INT64 ExternalClock::GetTime() { + if (callback) + return callback() / 100; + return 0; + } + + ExternalClock::ExternalClock(Callback callback) : ref_count(), callback(callback) { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + ExternalClock::~ExternalClock() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + HRESULT ExternalClock::Create(Callback callback, IMediaClock*& ptr) { + ExternalClock* p = new ExternalClock(callback); + if (!p) + return E_OUTOFMEMORY; + + ptr = p; + p->AddRef(); + p->Release(); + return S_OK; + } + + inline void ExternalClock::Destroy(ExternalClock* ptr) { + if (!ptr) + return; + + delete ptr; + } +} diff --git a/src/ReDIVA/movie_play_lib/external_clock.hpp b/src/ReDIVA/movie_play_lib/external_clock.hpp new file mode 100644 index 00000000..ec3935dc --- /dev/null +++ b/src/ReDIVA/movie_play_lib/external_clock.hpp @@ -0,0 +1,39 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "movie_play_lib.hpp" + +namespace MoviePlayLib { + class ExternalClock : public IMediaClock { + public: + typedef int64_t(*Callback)(); + + protected: + RefCount ref_count; + Callback callback; + + public: + virtual HRESULT QueryInterface(const IID& riid, void** ppvObject) override; + virtual ULONG AddRef() override; + virtual ULONG Release() override; + + virtual HRESULT TimeEnd() override; + virtual HRESULT TimeBegin() override; + virtual HRESULT SetTime(INT64 value) override; + virtual INT64 GetTime() override; + + ExternalClock(Callback callback); + virtual ~ExternalClock(); + + static HRESULT Create(Callback callback, IMediaClock*& ptr); + static void Destroy(ExternalClock* ptr); + + inline void Destroy() { + Destroy(this); + } + }; +} diff --git a/src/ReDIVA/movie_play_lib/gl_dx_interop_texture.cpp b/src/ReDIVA/movie_play_lib/gl_dx_interop_texture.cpp new file mode 100644 index 00000000..839f42b1 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/gl_dx_interop_texture.cpp @@ -0,0 +1,244 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "../../CRE/texture.hpp" +#include "gl_dx_interop_texture.hpp" +#include + +namespace MoviePlayLib { + static uint16_t gl_dx_interop_texture_counter = 0; + + HRESULT GLDXInteropTexture::QueryInterface(const IID& riid, void** ppvObject) { + if (!ppvObject) + return E_INVALIDARG; + + if (riid == __uuidof(IGLDXInteropTexture) || riid == __uuidof(IUnknown)) { + *ppvObject = this; + AddRef(); + return S_OK; + } + + *ppvObject = 0; + return E_NOINTERFACE; + } + + ULONG GLDXInteropTexture::AddRef() { + return ++ref_count; + } + + ULONG GLDXInteropTexture::Release() { + ULONG ref_count = --this->ref_count; + if (ref_count == 1) { + GetCurrentThreadId(); + wglGetCurrentContext(); + + if (index >= 0 && index < count) { + wglDXUnlockObjectsNV(device, 1, &textures[index].object); + index = -1; + } + } + else if (!ref_count) { + GetCurrentThreadId(); + wglGetCurrentContext(); + + if (index >= 0 && index < count) { + wglDXUnlockObjectsNV(device, 1, &textures[index].object); + index = -1; + } + + Destroy(); + } + return ref_count; + } + + texture* GLDXInteropTexture::GetTexture() { + if (index >= 0 && index < count) + return textures[index].tex; + return 0; + } + + GLDXInteropTexture::GLDXInteropTexture(HRESULT& hr) : ref_count(), thread_id(), wgl_ctx(), + device(), d3d_device(), video_index(), index(-1), count(), textures() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + memset(textures, 0, sizeof(textures)); + if (SUCCEEDED(hr)) { + if (!GLAD_WGL_NV_DX_interop2) + hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); + else if (SUCCEEDED(hr)) { + thread_id = GetCurrentThreadId(); + wgl_ctx = wglGetCurrentContext(); + if (!wgl_ctx) + hr = HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE); + } + } + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + GLDXInteropTexture::~GLDXInteropTexture() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + HRESULT GLDXInteropTexture::SetMFSample(IMFSample* mf_sample) { + GetCurrentThreadId(); + wglGetCurrentContext(); + + if (index >= 0 && index < count) { + wglDXUnlockObjectsNV(device, 1, &textures[index].object); + index = -1; + } + + IMFMediaBuffer* mf_media_buffer = 0; + IDirect3DTexture9* d3d_texture = 0; + IDirect3DDevice9* d3d_device = 0; + + HRESULT hr; + HANDLE share_handle; + uint32_t video_index; + uint32_t index; + + hr = mf_sample->GetBufferByIndex(0, &mf_media_buffer); + if (FAILED(hr)) + goto End; + + share_handle = 0; + hr = mf_sample->GetUINT64(TextureSharedHandleGUID, (UINT64*)&share_handle); + if (FAILED(hr)) + goto End; + + video_index = 0; + hr = mf_sample->GetUINT32(VideoIndexGUID, &video_index); + if (FAILED(hr)) + goto End; + + hr = mf_media_buffer->QueryInterface(__uuidof(IDirect3DTexture9), (void**)&d3d_texture); + if (FAILED(hr)) + goto End; + + hr = d3d_texture->GetDevice(&d3d_device); + if (FAILED(hr)) + goto End; + + if (this->video_index != video_index || this->d3d_device != d3d_device) { + CloseDevice(); + + this->video_index = video_index; + this->d3d_device = d3d_device; + + device = wglDXOpenDeviceNV(d3d_device); + + if (!device) + hr = HRESULT_FROM_WIN32(GetLastError()); + } + if (FAILED(hr)) + goto End; + + for (index = 0; index < count; index++) + if (textures[index].share_handle == share_handle) + break; + + if (index == count) { + if (count >= 10) { + hr = E_OUTOFMEMORY; + goto End; + } + + if (LoadTexture(textures[index], d3d_texture, share_handle)) + count++; + else { + hr = HRESULT_FROM_WIN32(GetLastError()); + ReleaseTexture(textures[index]); + } + } + + if (FAILED(hr)) + goto End; + + if (index < count && wglDXLockObjectsNV(device, 1, &textures[index].object)) + this->index = index; + else + hr = HRESULT_FROM_WIN32(GetLastError()); + + End: + if (d3d_device) { + d3d_device->Release(); + d3d_device = 0; + } + + if (d3d_texture) { + d3d_texture->Release(); + d3d_texture = 0; + } + + if (mf_media_buffer) { + mf_media_buffer->Release(); + mf_media_buffer = 0; + } + return hr; + } + + void GLDXInteropTexture::CloseDevice() { + if (count) { + for (uint32_t i = 0; i < count; i++) + ReleaseTexture(textures[i]); + count = 0; + } + + if (device) { + wglDXCloseDeviceNV(device); + device = 0; + } + } + + bool GLDXInteropTexture::LoadTexture(Texture& gl_dx_tex, void* dxObject, HANDLE share_handle) { + gl_dx_tex.tex = texture_load_tex_2d(texture_id(0x24, gl_dx_interop_texture_counter), GL_RGBA8, 1280, 720, 0, 0, 0); + if (!gl_dx_tex.tex) + return false; + + gl_dx_interop_texture_counter++; + + glDeleteTextures(1, &gl_dx_tex.tex->glid); + glGenTextures(1, &gl_dx_tex.tex->glid); + + wglDXSetResourceShareHandleNV(dxObject, share_handle); + gl_dx_tex.share_handle = share_handle; + gl_dx_tex.object = wglDXRegisterObjectNV(device, dxObject, gl_dx_tex.tex->glid, GL_TEXTURE_2D, GL_ZERO); + return !!gl_dx_tex.object; + } + + void GLDXInteropTexture::ReleaseTexture(Texture& gl_dx_tex) { + if (gl_dx_tex.object) { + wglDXUnregisterObjectNV(device, gl_dx_tex.object); + gl_dx_tex.object = 0; + } + + if (gl_dx_tex.tex) { + texture_release(gl_dx_tex.tex); + gl_dx_tex.tex = 0; + } + gl_dx_tex.share_handle = 0; + } + + HRESULT GLDXInteropTexture::Create(GLDXInteropTexture*& ptr) { + HRESULT hr = S_OK; + GLDXInteropTexture* p = new GLDXInteropTexture(hr); + if (!p) + return E_OUTOFMEMORY; + + if (SUCCEEDED(hr)) { + ptr = p; + p->AddRef(); + } + p->Release(); + return hr; + } + + inline void GLDXInteropTexture::Destroy(GLDXInteropTexture* ptr) { + if (!ptr) + return; + + delete ptr; + } +} diff --git a/src/ReDIVA/movie_play_lib/gl_dx_interop_texture.hpp b/src/ReDIVA/movie_play_lib/gl_dx_interop_texture.hpp new file mode 100644 index 00000000..ebea0f1c --- /dev/null +++ b/src/ReDIVA/movie_play_lib/gl_dx_interop_texture.hpp @@ -0,0 +1,53 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "movie_play_lib.hpp" + +namespace MoviePlayLib { + class GLDXInteropTexture : public IGLDXInteropTexture { + public: + struct Texture { + HANDLE share_handle; + HANDLE object; + texture* tex; + }; + + protected: + RefCount ref_count; + int32_t thread_id; + HGLRC wgl_ctx; + HANDLE device; + IDirect3DDevice9* d3d_device; + uint32_t video_index; + uint32_t index; + uint32_t count; + Texture textures[10]; + + public: + virtual HRESULT QueryInterface(const IID& riid, void** ppvObject) override; + virtual ULONG AddRef() override; + virtual ULONG Release() override; + + virtual texture* GetTexture() override; + + GLDXInteropTexture(HRESULT& result); + virtual ~GLDXInteropTexture(); + + virtual HRESULT SetMFSample(IMFSample* mf_sample); + + void CloseDevice(); + bool LoadTexture(Texture& gl_dx_tex, void* dxObject, HANDLE shareHandle); + void ReleaseTexture(Texture& gl_dx_tex); + + static HRESULT Create(GLDXInteropTexture*& ptr); + static void Destroy(GLDXInteropTexture* ptr); + + inline void Destroy() { + Destroy(this); + } + }; +} diff --git a/src/ReDIVA/movie_play_lib/media_clock.cpp b/src/ReDIVA/movie_play_lib/media_clock.cpp new file mode 100644 index 00000000..124440de --- /dev/null +++ b/src/ReDIVA/movie_play_lib/media_clock.cpp @@ -0,0 +1,107 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "media_clock.hpp" +#include + +namespace MoviePlayLib { + HRESULT MediaClock::QueryInterface(const IID& riid, void** ppvObject) { + if (!ppvObject) + return E_INVALIDARG; + + if (riid == __uuidof(IMediaClock) || riid == __uuidof(IUnknown)) { + *ppvObject = this; + AddRef(); + return S_OK; + } + + *ppvObject = 0; + return E_NOINTERFACE; + } + + ULONG MediaClock::AddRef() { + return ++ref_count; + } + + ULONG MediaClock::Release() { + if (!--ref_count) + Destroy(); + return ref_count; + } + + HRESULT MediaClock::TimeEnd() { + lock.Acquire(); + if (time_begin_init) { + time = GetTimeInner(); + time_begin = GetTimestamp(); + time_begin_init = false; + } + lock.Release(); + return S_OK; + } + + HRESULT MediaClock::TimeBegin() { + lock.Acquire(); + if (!time_begin_init) { + time_begin = GetTimestamp(); + time_begin_init = true; + } + lock.Release(); + return S_OK; + } + + HRESULT MediaClock::SetTime(INT64 value) { + lock.Acquire(); + time = value; + time_begin = GetTimestamp(); + lock.Release(); + return S_OK; + } + + INT64 MediaClock::GetTime() { + lock.Acquire(); + int64_t res = GetTimeInner(); + lock.Release(); + return res; + } + + MediaClock::MediaClock() : ref_count(), lock(), time_begin_init(), time(), time_begin() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + MediaClock::~MediaClock() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + int64_t MediaClock::GetTimeInner() { + if (!time_begin_init) + return time; + + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + + return time + MFllMulDiv(GetTimestamp() - time_begin, 10000000, freq.QuadPart, 0); + } + + HRESULT MediaClock::Create(IMediaClock*& ptr) { + MediaClock* p = new MediaClock; + if (!p) + return E_OUTOFMEMORY; + + ptr = p; + p->AddRef(); + p->Release(); + return S_OK; + } + + inline void MediaClock::Destroy(MediaClock* ptr) { + if (!ptr) + return; + + delete ptr; + } +} diff --git a/src/ReDIVA/movie_play_lib/media_clock.hpp b/src/ReDIVA/movie_play_lib/media_clock.hpp new file mode 100644 index 00000000..23bbdb7b --- /dev/null +++ b/src/ReDIVA/movie_play_lib/media_clock.hpp @@ -0,0 +1,41 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "movie_play_lib.hpp" + +namespace MoviePlayLib { + class MediaClock : public IMediaClock { + protected: + RefCount ref_count; + Lock lock; + BOOL time_begin_init; + int64_t time; + int64_t time_begin; + + public: + virtual HRESULT QueryInterface(const IID& riid, void** ppvObject) override; + virtual ULONG AddRef() override; + virtual ULONG Release() override; + + virtual HRESULT TimeEnd() override; + virtual HRESULT TimeBegin() override; + virtual HRESULT SetTime(INT64 value) override; + virtual INT64 GetTime() override; + + MediaClock(); + virtual ~MediaClock(); + + int64_t GetTimeInner(); + + static HRESULT Create(IMediaClock*& ptr); + static void Destroy(MediaClock* ptr); + + inline void Destroy() { + Destroy(this); + } + }; +} diff --git a/src/ReDIVA/movie_play_lib/media_foundation.cpp b/src/ReDIVA/movie_play_lib/media_foundation.cpp new file mode 100644 index 00000000..098f1c7e --- /dev/null +++ b/src/ReDIVA/movie_play_lib/media_foundation.cpp @@ -0,0 +1,163 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "media_foundation.hpp" +#include +#include +#pragma comment(lib, "mfplat.lib") +#pragma comment(lib, "mfuuid.lib") + +namespace MoviePlayLib { + HRESULT GetMFMediaType(IMFTransform* mf_transform, const GUID& sub_type, IMFMediaType*& ptr) { + DWORD type_index = 0; + HRESULT hr; + IMFMediaType* mf_media_type = 0; + do { + if (mf_media_type) { + mf_media_type->Release(); + mf_media_type = 0; + } + + hr = mf_transform->GetOutputAvailableType(0, type_index, &mf_media_type); + if (FAILED(hr)) + break; + + GUID _sub_type = {}; + if (SUCCEEDED(mf_media_type->GetGUID(MF_MT_SUBTYPE, &_sub_type)) && _sub_type == sub_type) { + hr = mf_transform->SetOutputType(0, mf_media_type, 0); + if (SUCCEEDED(hr)) { + ptr = mf_media_type; + ptr->AddRef(); + break; + } + } + type_index++; + } while (true); + + if (mf_media_type) { + mf_media_type->Release(); + mf_media_type = 0; + } + return hr; + } + + HRESULT GetMFTransform(IMFMediaType* mf_media_type, const GUID& sub_type, IMFTransform*& ptr) { + ptr = 0; + + GUID guid_major_type = {}; + GUID guid_format_subtype = {}; + IID* pclsidMFT = 0; + UINT32 cMFTs = 0; + IMFTransform* mf_transform = 0; + + HRESULT hr; + hr = mf_media_type->GetGUID(MF_MT_MAJOR_TYPE, &guid_major_type); + if (FAILED(hr)) + goto End; + + hr = mf_media_type->GetGUID(MF_MT_SUBTYPE, &guid_format_subtype); + if (FAILED(hr)) + goto End; + + MFT_REGISTER_TYPE_INFO formats; + formats.guidMajorType = guid_major_type; + formats.guidSubtype = guid_format_subtype; + MFT_REGISTER_TYPE_INFO output_type_info; + output_type_info.guidMajorType = guid_major_type; + output_type_info.guidSubtype = sub_type; + hr = MFTEnum(guid_major_type == MFMediaType_Audio ? MFT_CATEGORY_AUDIO_DECODER + : MFT_CATEGORY_VIDEO_DECODER, 0, &formats, &output_type_info, 0, &pclsidMFT, &cMFTs); + if (FAILED(hr)) + goto End; + + if (cMFTs) + hr = CoCreateInstance(*pclsidMFT, 0, 1, __uuidof(IMFTransform), (void**)&mf_transform); + + if (FAILED(hr) || !mf_transform) + goto End; + + hr = mf_transform->SetInputType(0, mf_media_type, 0); + if (SUCCEEDED(hr)) { + ptr = mf_transform; + ptr->AddRef(); + } + + End: + if (mf_transform) { + mf_transform->Release(); + mf_transform = 0; + } + + if (SUCCEEDED(hr) && !ptr) + hr = MF_E_INVALIDTYPE; + + if (pclsidMFT) + CoTaskMemFree(pclsidMFT); + return hr; + } + + HRESULT MFMediaEventQueryInterface(IMFMediaEvent* mf_media_event, const IID& iid, void** ppvObject) { + PROPVARIANT pvar = {}; + HRESULT hr = mf_media_event->GetValue(&pvar); + if (SUCCEEDED(hr) && pvar.vt == VT_UNKNOWN) + hr = pvar.punkVal->QueryInterface(iid, ppvObject); + PropVariantClear(&pvar); + return hr; + } + + HRESULT MFSampleSetAllocator(IMFSample* mf_sample, IMFAsyncCallback* sample_allocator) { + IMFTrackedSample* mf_tracked_sample = 0; + HRESULT hr = mf_sample->QueryInterface(__uuidof(IMFTrackedSample), (void**)&mf_tracked_sample); + if (SUCCEEDED(hr)) + hr = mf_tracked_sample->SetAllocator(sample_allocator, 0); + + if (mf_tracked_sample) { + mf_tracked_sample->Release(); + mf_tracked_sample = 0; + } + return hr; + } +} + +BOOL mf_init_status = FALSE; +std::atomic_int32_t mf_media_session_count = 0; + +void mf_media_session_count_increment() { + mf_media_session_count++; +} + +void mf_media_session_count_decrement() { + mf_media_session_count--; +} + +HRESULT mf_init() { + if (mf_init_status) + return S_OK; + + HRESULT hr = MFStartup(0x20070u, 1u); + if (SUCCEEDED(hr)) + mf_init_status = TRUE; + return hr; +} + +HRESULT mf_shutdown() { + HRESULT hr = S_OK; + const int64_t time_begin = MoviePlayLib::GetTimestamp(); + for (int32_t i = 0; i < 500; i++) { + if (!mf_media_session_count) + break; + + Sleep(10); + } + + if (mf_init_status) { + hr = MFShutdown(); + mf_init_status = FALSE; + } + const int64_t time_end = MoviePlayLib::GetTimestamp(); + + const double_t time = MoviePlayLib::CalcTimeMsec(time_begin, time_end); + return hr; +} diff --git a/src/ReDIVA/movie_play_lib/media_foundation.hpp b/src/ReDIVA/movie_play_lib/media_foundation.hpp new file mode 100644 index 00000000..b6e977ae --- /dev/null +++ b/src/ReDIVA/movie_play_lib/media_foundation.hpp @@ -0,0 +1,16 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "movie_play_lib.hpp" +#include + +namespace MoviePlayLib { + HRESULT GetMFMediaType(IMFTransform* mf_transform, const GUID& sub_type, IMFMediaType*& ptr); + HRESULT GetMFTransform(IMFMediaType* mf_media_type, const GUID& sub_type, IMFTransform*& ptr); + HRESULT MFMediaEventQueryInterface(IMFMediaEvent* mf_media_event, const IID& iid, void** ppvObject); + HRESULT MFSampleSetAllocator(IMFSample* mf_sample, IMFAsyncCallback* sample_allocator); +} diff --git a/src/ReDIVA/movie_play_lib/media_session.cpp b/src/ReDIVA/movie_play_lib/media_session.cpp new file mode 100644 index 00000000..5185bf0e --- /dev/null +++ b/src/ReDIVA/movie_play_lib/media_session.cpp @@ -0,0 +1,963 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "media_session.hpp" +#include "../../KKdLib/prj/math.hpp" +#include "audio_decoder.hpp" +#include "command_args.hpp" +#include "media_clock.hpp" +#include "media_source.hpp" +#include "video_decoder.hpp" +#include +#pragma comment(lib, "avrt.lib") +#include +#pragma comment(lib, "dxva2.lib") +#include +#pragma comment(lib, "mfplat.lib") + +extern int32_t mf_media_session_count; +extern void mf_media_session_count_increment(); +extern void mf_media_session_count_decrement(); + +namespace MoviePlayLib { + HRESULT MediaSession::QueryInterface(const IID& riid, void** ppvObject) { + if (!ppvObject) + return E_INVALIDARG; + + if (riid == __uuidof(IUnknown)) { + *ppvObject = this; + AddRef(); + return S_OK; + } + + *ppvObject = 0; + return E_NOINTERFACE; + } + + ULONG MediaSession::AddRef() { + return ++ref_count; + } + + ULONG MediaSession::Release() { + if (!--ref_count) + Destroy(); + return ref_count; + } + + MediaSession::MediaSession(HRESULT& hr, const wchar_t* path, IMediaClock* media_clock) : ref_count(), lock(), + dwQueue(), state(), process(), media_stats_lock(), media_clock(), media_source(), audio_media_transform(), + video_media_transform(), wasapi_renderer(), video_renderer(), interop_texture(), duration(), + audio_sample_time(INT64_MAX), video_sample_time(INT64_MAX), hThread(), hTimer(), hEvent(), async_callback(this) { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + mf_media_session_count_increment(); + + LARGE_INTEGER due_time; + CommandArgs* command_args; + if (FAILED(hr)) + goto End; + + if (media_clock) { + this->media_clock = media_clock; + media_clock->AddRef(); + } + else + hr = MediaClock::Create(this->media_clock); + + if (FAILED(hr)) + goto End; + + hEvent = CreateEventA(0, TRUE, FALSE, 0); + if (!hEvent) + hr = HRESULT_FROM_WIN32(GetLastError()); + + if (FAILED(hr)) + goto End; + + hTimer = CreateWaitableTimerA(0, FALSE, 0); + if (!hTimer) + hr = HRESULT_FROM_WIN32(GetLastError()); + + if (FAILED(hr)) + goto End; + + due_time = {}; + if (!SetWaitableTimer(hTimer, &due_time, 10, 0, 0, FALSE)) + hr = HRESULT_FROM_WIN32(GetLastError()); + + if (FAILED(hr)) + goto End; + + hThread = (HANDLE)_beginthreadex(0, 0, (_beginthreadex_proc_type)MediaSession::ThreadMain, this, 0, 0); + if (!hThread) + hr = HRESULT_FROM_WIN32(GetLastError()); + + if (FAILED(hr)) + goto End; + + hr = MFAllocateWorkQueue(&dwQueue); + command_args = 0; + if (SUCCEEDED(hr)) { + hr = CommandArgs::Create(CommandArgs::Type::Open, command_args); + if (SUCCEEDED(hr)) { + hr = command_args->SetPath(path); + if (SUCCEEDED(hr)) + hr = MFPutWorkItem(dwQueue, &async_callback, command_args); + } + } + + if (command_args) { + command_args->Release(); + command_args = 0; + } + + End: + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + MediaSession::~MediaSession() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + Shutdown(); + + if (hThread) { + SetEvent(hEvent); + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + hThread = 0; + } + + if (hTimer) { + CloseHandle(hTimer); + hTimer = 0; + } + + if (hEvent) { + CloseHandle(hEvent); + hEvent = 0; + } + + if (media_clock) { + media_clock->Release(); + media_clock = 0; + } + + MFUnlockWorkQueue(dwQueue); + dwQueue = 0; + + mf_media_session_count_decrement(); + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + HRESULT MediaSession::AsyncCallback(IMFAsyncResult* mf_async_result) { + IUnknown* object = 0; + CommandArgs* command_args = 0; + HRESULT hr = mf_async_result->GetState(&object); + if (FAILED(hr)) + goto End; + + hr = object->QueryInterface(__uuidof(CommandArgs), (void**)&command_args); + if (FAILED(hr)) + goto End; + + switch (command_args->GetType()) { + case CommandArgs::Type::Open: + hr = open(command_args->GetPath()); + break; + case CommandArgs::Type::SetPosition: + hr = set_position(command_args->GetPosition()); + + state.SetInnerStateAdvance(state.state_inner); + break; + case CommandArgs::Type::Play: + if (wasapi_renderer) + wasapi_renderer->Open(); + if (video_renderer) + video_renderer->Open(); + + SetProcessEnable(); + + Update(); + + media_clock->TimeBegin(); + + state.SetInnerStateAdvance(State::Play); + break; + case CommandArgs::Type::Pause: + media_clock->TimeEnd(); + + SetProcessDisable(); + + if (wasapi_renderer) + wasapi_renderer->Flush(); + if (video_renderer) + video_renderer->Flush(); + + state.SetInnerStateAdvance(State::Pause); + break; + case CommandArgs::Type::Close: + Shutdown(); + break; + } + + End: + if (command_args) { + command_args->Release(); + command_args = 0; + } + + if (object) { + object->Release(); + object = 0; + } + + if (FAILED(hr)) + media_stats_lock.SetCurrError(hr, this); + + return mf_async_result->SetStatus(hr); + } + + HRESULT MediaSession::Close() { + lock.Acquire(); + if (interop_texture) { + interop_texture->Release(); + interop_texture = 0; + } + + if (dwQueue) { + CommandArgs* command_args = 0; + HRESULT hr = CommandArgs::Create(CommandArgs::Type::Close, command_args); + if (SUCCEEDED(hr)) + MFPutWorkItem(dwQueue, &async_callback, command_args); + + if (command_args) { + command_args->Release(); + command_args = 0; + } + } + lock.Release(); + return S_OK; + } + + HRESULT MediaSession::Play() { + lock.Acquire(); + + HRESULT hr = S_OK; + switch (state.state) { + case State::None: + case State::Init: + hr = MF_E_INVALIDREQUEST; + break; + case State::Open: + case State::Wait: + case State::Pause: + case State::Stop: { + CommandArgs* command_args = 0; + hr = CommandArgs::Create(CommandArgs::Type::Play, command_args); + if (SUCCEEDED(hr)) + hr = MFPutWorkItem(dwQueue, &async_callback, command_args); + + if (command_args) { + command_args->Release(); + command_args = 0; + } + + if (SUCCEEDED(hr)) + state.SetStateAdvance(State::Play); + } break; + } + + lock.Release(); + return hr; + } + + HRESULT MediaSession::Pause() { + lock.Acquire(); + + HRESULT hr = S_OK; + switch (state.state) { + case State::None: + case State::Init: + hr = MF_E_INVALIDREQUEST; + break; + case State::Open: + case State::Wait: + case State::Play: + case State::Stop: { + CommandArgs* command_args = 0; + hr = CommandArgs::Create(CommandArgs::Type::Pause, command_args); + if (SUCCEEDED(hr)) + hr = MFPutWorkItem(dwQueue, &async_callback, command_args); + + if (command_args) { + command_args->Release(); + command_args = 0; + } + + if (SUCCEEDED(hr)) { + state.lock.Acquire(); + state.state = State::Pause; + state.command_index_queue++; + state.lock.Release(); + } + } break; + } + + lock.Release(); + return hr; + } + + HRESULT MediaSession::SetTime(double_t value) { + lock.Acquire(); + + HRESULT hr = S_OK; + switch (state.state) { + case State::None: + case State::Init: + hr = MF_E_INVALIDREQUEST; + break; + case State::Open: + case State::Wait: + case State::Pause: + case State::Play: + case State::Stop: { + CommandArgs* command_args = 0; + hr = CommandArgs::Create(CommandArgs::Type::Pause, command_args); + if (SUCCEEDED(hr)) { + hr = command_args->SetPosition((int64_t)prj::floor(value * 10000000.0)); + if (SUCCEEDED(hr)) + hr = MFPutWorkItem(dwQueue, &async_callback, command_args); + } + + if (command_args) { + command_args->Release(); + command_args = 0; + } + + if (SUCCEEDED(hr)) + state.SetStateAdvance(state.state); + } break; + } + + lock.Release(); + return hr; + } + + State MediaSession::GetState() { + return state.state; + } + + double_t MediaSession::GetDuration() { + return (double_t)duration * 0.0000001; + } + + double_t MediaSession::GetTime() { + return (double_t)media_clock->GetTime() * 0.0000001; + } + + HRESULT MediaSession::GetAudioParams(AudioParams* value) { + if (!value) + return E_POINTER; + else if (wasapi_renderer) + return wasapi_renderer->GetParams(value); + return S_OK; + } + + HRESULT MediaSession::SetAudioParams(const AudioParams* value) { + if (!value) + return E_POINTER; + else if (wasapi_renderer) + return wasapi_renderer->SetParams(value); + return S_OK; + } + + HRESULT MediaSession::GetVideoParams(VideoParams* value) { + if (!value) + return E_POINTER; + + MoviePlayLib::MediaStats stats; + media_stats_lock.GetStats(stats); + + value->width = stats.width; + value->height = stats.height; + value->frame_size_width = stats.frame_size_width; + value->frame_size_height = stats.frame_size_height; + value->frame_rate = stats.frame_rate; + value->pixel_aspect_ratio = stats.pixel_aspect_ratio; + return S_OK; + } + + HRESULT MediaSession::GetD3D9Texture(IDirect3DDevice9* d3d_device, IDirect3DTexture9** ptr) { + if (!d3d_device || !ptr) + return E_POINTER; + + *ptr = 0; + + HRESULT hr = S_FALSE; + if (!video_renderer) + return hr; + + Update(); + + IMFSample* mf_sample = 0; + HRESULT mf_sample_hr = video_renderer->GetMFSample(&mf_sample); + if (mf_sample) { + const int64_t time_begin = GetTimestamp(); + + HANDLE shared_handle = 0; + IMFMediaBuffer* mf_media_buffer = 0; + IDirect3DTexture9* dx_texture = 0; + D3DSURFACE_DESC desc = {}; + + hr = mf_sample->GetUINT64(TextureSharedHandleGUID, (UINT64*)&shared_handle); + if (FAILED(hr)) + goto End; + + hr = mf_sample->GetBufferByIndex(0, &mf_media_buffer); + if (FAILED(hr)) + goto End; + + hr = mf_media_buffer->QueryInterface(__uuidof(IDirect3DTexture9), (void**)&dx_texture); + if (FAILED(hr)) + goto End; + + hr = dx_texture->GetLevelDesc(0, &desc); + if (SUCCEEDED(hr)) + hr = d3d_device->CreateTexture(desc.Width, desc.Height, 1, + desc.Usage, desc.Format, desc.Pool, ptr, &shared_handle); + + End: + if (dx_texture) { + dx_texture->Release(); + dx_texture = 0; + } + + if (mf_media_buffer) { + mf_media_buffer->Release(); + mf_media_buffer = 0; + } + + if (mf_sample) { + mf_sample->Release(); + mf_sample = 0; + } + + const int64_t time_end = GetTimestamp(); + + media_stats_lock.SetVideoOutputTime(CalcTimeMsec(time_begin, time_end)); + } + + if (SUCCEEDED(hr)) + hr = mf_sample_hr; + return hr; + } + + HRESULT MediaSession::GetD3D11Texture(ID3D11Device* d3d_device, ID3D11Texture2D** ptr) { + if (!d3d_device || !ptr) + return E_POINTER; + + *ptr = 0; + + HRESULT hr = S_FALSE; + if (!video_renderer) + return hr; + + Update(); + + IMFSample* mf_sample = 0; + HRESULT mf_sample_hr = video_renderer->GetMFSample(&mf_sample); + if (mf_sample) { + const int64_t time_begin = GetTimestamp(); + + HANDLE shared_handle = 0; + hr = mf_sample->GetUINT64(TextureSharedHandleGUID, (UINT64*)&shared_handle); + if (SUCCEEDED(hr)) + hr = d3d_device->OpenSharedResource(shared_handle, __uuidof(ID3D11Texture2D), (void**)ptr); + + if (mf_sample) { + mf_sample->Release(); + mf_sample = 0; + } + + const int64_t time_end = GetTimestamp(); + + media_stats_lock.SetVideoOutputTime(CalcTimeMsec(time_begin, time_end)); + } + + if (SUCCEEDED(hr)) + hr = mf_sample_hr; + return hr; + } + + HRESULT MediaSession::GetGLDXIntreropTexture(IGLDXInteropTexture** ptr) { + if (!ptr) + return E_POINTER; + + *ptr = 0; + + HRESULT hr = S_FALSE; + if (!video_renderer) + return hr; + + Update(); + + if (!interop_texture) { + hr = GLDXInteropTexture::Create(interop_texture); + if (FAILED(hr)) + return hr; + } + + IMFSample* mf_sample = 0; + HRESULT mf_sample_hr = video_renderer->GetMFSample(&mf_sample); + if (mf_sample) { + const int64_t time_begin = GetTimestamp(); + + hr = interop_texture->SetMFSample(mf_sample); + if (SUCCEEDED(hr)) { + *ptr = interop_texture; + interop_texture->AddRef(); + } + + if (mf_sample) { + mf_sample->Release(); + mf_sample = 0; + } + + const int64_t time_end = GetTimestamp(); + + media_stats_lock.SetVideoOutputTime(CalcTimeMsec(time_begin, time_end)); + } + + if (SUCCEEDED(hr)) + hr = mf_sample_hr; + return hr; + } + + void MediaSession::GetSamplesCount() { + uint32_t audio_mf_samples_wait_count = 0; + uint32_t video_mf_samples_wait_count = 0; + uint32_t audio_mf_samples_count = 0; + uint32_t video_mf_samples_count = 0; + + BOOL stop = FALSE; + int64_t time = media_clock->GetTime(); + + if (audio_media_transform) { + audio_mf_samples_wait_count = audio_media_transform->GetMFSamplesWaitCount(); + audio_mf_samples_count = audio_media_transform->GetMFSamplesCount(); + if (audio_media_transform->CanShutdown() + && (audio_sample_time == INT64_MAX || audio_sample_time <= time)) + stop = TRUE; + } + + if (video_media_transform) { + video_mf_samples_wait_count = video_media_transform->GetMFSamplesWaitCount(); + video_mf_samples_count = video_media_transform->GetMFSamplesCount(); + if (video_media_transform->CanShutdown() + && (video_sample_time == INT64_MAX || video_sample_time <= time)) + stop = TRUE; + } + + if (stop) + state.SetInnerState(State::Stop); + + media_stats_lock.SetMFSamplesCount(audio_mf_samples_wait_count, + video_mf_samples_wait_count, audio_mf_samples_count, video_mf_samples_count); + } + + void MediaSession::Process(const char* name, IMediaTransform* media_transform, + IMediaRenderer* media_renderer, int64_t process_time) { + if (!media_transform) + return; + + int64_t time = media_clock->GetTime(); + for (int64_t sample_time = media_transform->GetSampleTime(); + sample_time - process_time <= time; + sample_time = media_transform->GetSampleTime()) { + IMFSample* mf_sample = 0; + media_transform->GetMFSample(mf_sample); + if (!mf_sample) + break; + + int64_t sample_duration = 0; + mf_sample->GetSampleDuration(&sample_duration); + if (sample_time + sample_duration > time) { + if (media_renderer) + media_renderer->SetMFSample(mf_sample); + + if (mf_sample) { + mf_sample->Release(); + mf_sample = 0; + } + + media_transform->SignalEvent(); + if (media_transform == audio_media_transform) { + audio_sample_time = sample_time + sample_duration; + + media_stats_lock.SetAudioTimeAdvance((double_t)(time - sample_time) * 0.0001); + } + else { + video_sample_time = sample_time + sample_duration; + + media_stats_lock.SetVideoTimeAdvance((double_t)(time - sample_time) * 0.0001); + } + } + + if (mf_sample) { + mf_sample->Release(); + mf_sample = 0; + } + } + } + + void MediaSession::SetProcessDisable() { + lock.Acquire(); + process = FALSE; + lock.Release(); + } + + void MediaSession::SetProcessEnable() { + lock.Acquire(); + process = TRUE; + lock.Release(); + } + + void MediaSession::Shutdown() { + SetProcessDisable(); + + if (hThread) { + SetEvent(hEvent); + WaitForSingleObject(hThread, INFINITE); + } + + if (video_renderer) + video_renderer->Shutdown(); + if (wasapi_renderer) + wasapi_renderer->Shutdown(); + if (video_media_transform) + video_media_transform->Shutdown(); + if (audio_media_transform) + audio_media_transform->Shutdown(); + if (media_source) + media_source->Shutdown(); + + if (wasapi_renderer) { + wasapi_renderer->Release(); + wasapi_renderer = 0; + } + + if (video_renderer) { + video_renderer->Release(); + video_renderer = 0; + } + + if (video_media_transform) { + video_media_transform->Release(); + video_media_transform = 0; + } + + if (audio_media_transform) { + audio_media_transform->Release(); + audio_media_transform = 0; + } + + if (media_source) { + media_source->Release(); + media_source = 0; + } + + state.SetInnerState(State::None); + } + + void MediaSession::Update() { + lock.Acquire(); + if (process) { + if (state.state_inner == State::Play) { + Process("Audio", audio_media_transform, wasapi_renderer, 500000); + Process("Video", video_media_transform, video_renderer, 100000); + GetSamplesCount(); + } + } + + if (media_stats_lock.HasError()) + state.SetInnerState(State::None); + lock.Release(); + } + + HRESULT MediaSession::WaitSamplesLoad(uint32_t min_audio_samples_count, uint32_t min_video_samples_count) { + ULONGLONG tick_start = GetTickCount64(); + while (true) { + BOOL ready = TRUE; + if (audio_media_transform && !audio_media_transform->CanShutdown() + && audio_media_transform->GetMFSamplesCount() < min_audio_samples_count) + ready = FALSE; + + if (video_media_transform && !video_media_transform->CanShutdown() + && video_media_transform->GetMFSamplesCount() < min_audio_samples_count) + ready = FALSE; + + if (ready) + break; + + if (GetTickCount64() - tick_start >= 1000) + return S_FALSE; + + Sleep(0); + } + return S_OK; + } + + HRESULT MediaSession::open(const wchar_t* url) { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + IMFMediaType* audio_mf_media_type = 0; + IMFMediaType* video_mf_media_type = 0; + IDirect3DDeviceManager9* d3d_device_manager = 0; + IDirect3DDevice9Ex* d3d_device = 0; + HRESULT hr; + + const int64_t time0_begin = GetTimestamp(); + hr = MediaSource::Create(&media_stats_lock, media_clock, url, media_source); + if (SUCCEEDED(hr)) { + media_source->GetAudioMFMediaType(&audio_mf_media_type); + media_source->GetVideoMFMediaType(&video_mf_media_type); + } + + if (video_mf_media_type && SUCCEEDED(hr)) { + IDirect3D9Ex* d3d_device_temp = 0; + hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &d3d_device_temp); + + D3DPRESENT_PARAMETERS present_param = {}; + present_param.BackBufferWidth = 1; + present_param.BackBufferHeight = 1; + present_param.BackBufferFormat = D3DFMT_UNKNOWN; + present_param.Windowed = TRUE; + present_param.SwapEffect = D3DSWAPEFFECT_COPY; + present_param.Flags = D3DPRESENTFLAG_VIDEO; + + UINT reset_token = 0; + D3DCAPS9 caps = {}; + if (SUCCEEDED(hr)) + hr = d3d_device_temp->GetDeviceCaps(0, D3DDEVTYPE_HAL, &caps); + + DWORD d3d_device_flags; + if (caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) + d3d_device_flags = D3DCREATE_NOWINDOWCHANGES | D3DCREATE_HARDWARE_VERTEXPROCESSING + | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE; + else + d3d_device_flags = D3DCREATE_NOWINDOWCHANGES | D3DCREATE_SOFTWARE_VERTEXPROCESSING + | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE; + + if (SUCCEEDED(hr)) { + hr = d3d_device_temp->CreateDeviceEx(0, D3DDEVTYPE_HAL, present_param.hDeviceWindow, + d3d_device_flags, &present_param, 0i64, &d3d_device); + if (SUCCEEDED(hr)) + { + hr = DXVA2CreateDirect3DDeviceManager9(&reset_token, &d3d_device_manager); + if (SUCCEEDED(hr)) + hr = d3d_device_manager->ResetDevice(d3d_device, reset_token); + } + } + + if (d3d_device_temp) { + d3d_device_temp->Release(); + d3d_device_temp = 0; + } + + if (SUCCEEDED(hr)) { + hr = VideoDecoder::Create(&media_stats_lock, media_clock, media_source, + d3d_device_manager, d3d_device, video_media_transform); + if (SUCCEEDED(hr)) { + hr = media_source->SetVideoMediaTransform(video_media_transform); + if (SUCCEEDED(hr)) + hr = VideoRenderer::Create(media_clock, video_renderer); + } + } + } + + if (audio_mf_media_type && SUCCEEDED(hr)) { + hr = AudioDecoder::Create(&media_stats_lock, media_clock, media_source, audio_media_transform); + if (SUCCEEDED(hr)) { + hr = media_source->SetAudioMediaTransform(audio_media_transform); + if (SUCCEEDED(hr)) { + uint32_t channels = 0; + uint32_t sample_rate = 0; + hr = audio_mf_media_type->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, &channels); + if (SUCCEEDED(hr)) { + hr = audio_mf_media_type->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, &sample_rate); + if (SUCCEEDED(hr)) + hr = WASAPIRenderer::Create(media_clock, sample_rate, channels, wasapi_renderer); + } + } + } + } + + if ((video_mf_media_type || audio_mf_media_type) && SUCCEEDED(hr)) { + duration = media_source->GetDuration(); + media_stats_lock.SetSessionInfo((double_t)duration * 0.0000001, + !!audio_media_transform, !!video_media_transform); + } + + if (d3d_device) { + d3d_device->Release(); + d3d_device = 0; + } + + if (d3d_device_manager) { + d3d_device_manager->Release(); + d3d_device_manager = 0; + } + + if (video_mf_media_type) { + video_mf_media_type->Release(); + video_mf_media_type = 0; + } + + if (audio_mf_media_type) { + audio_mf_media_type->Release(); + audio_mf_media_type = 0; + } + const int64_t time0_end = GetTimestamp(); + + const double_t time0 = CalcTimeMsec(time0_begin, time0_end); + + const int64_t time1_begin = GetTimestamp(); + if (SUCCEEDED(hr)) { + hr = media_clock->TimeEnd(); + if (SUCCEEDED(hr)) { + hr = media_clock->SetTime(0); + if (SUCCEEDED(hr)) + hr = media_source->Start(0i64); + } + + if (audio_media_transform && SUCCEEDED(hr)) + hr = audio_media_transform->Open(); + + if (video_media_transform && SUCCEEDED(hr)) + hr = video_media_transform->Open(); + + if (wasapi_renderer && SUCCEEDED(hr)) + hr = wasapi_renderer->Open(); + + if (video_renderer && SUCCEEDED(hr)) + hr = video_renderer->Open(); + } + const int64_t time1_end = GetTimestamp(); + + const double_t time1 = CalcTimeMsec(time1_begin, time1_end); + + const int64_t time2_begin = GetTimestamp(); + if (SUCCEEDED(hr)) + hr = WaitSamplesLoad(5, 5); + const int64_t time2_end = GetTimestamp(); + + const double_t time2 = CalcTimeMsec(time2_begin, time2_end); + + if (SUCCEEDED(hr)) + state.SetInnerState(State::Open); + else + state.SetInnerState(State::None); + MOVIE_PLAY_LIB_PRINT_FUNC_END; + return hr; + } + + HRESULT MediaSession::set_position(int64_t time) { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + time = clamp_def(time, 0, duration); + + const int64_t time_begin = GetTimestamp(); + + HRESULT hr = media_clock->TimeEnd(); + if (SUCCEEDED(hr)) + hr = media_source->Stop(); + + SetProcessDisable(); + + if (audio_media_transform && SUCCEEDED(hr)) + hr = audio_media_transform->Flush(); + if (video_media_transform && SUCCEEDED(hr)) + hr = video_media_transform->Flush(); + if (wasapi_renderer && SUCCEEDED(hr)) + hr = wasapi_renderer->Flush(); + if (video_renderer && SUCCEEDED(hr)) + hr = video_renderer->Flush(); + + if (audio_media_transform && SUCCEEDED(hr)) + hr = audio_media_transform->Close(); + if (video_media_transform && SUCCEEDED(hr)) + hr = video_media_transform->Close(); + if (wasapi_renderer && SUCCEEDED(hr)) + hr = wasapi_renderer->Close(); + if (video_renderer && SUCCEEDED(hr)) + hr = video_renderer->Close(); + + if (SUCCEEDED(hr)) + hr = media_clock->SetTime(time); + + if (audio_media_transform && SUCCEEDED(hr)) + hr = audio_media_transform->Open(); + if (video_media_transform && SUCCEEDED(hr)) + hr = video_media_transform->Open(); + if (wasapi_renderer && SUCCEEDED(hr)) + hr = wasapi_renderer->Open(); + if (video_renderer && SUCCEEDED(hr)) + hr = video_renderer->Open(); + + if (FAILED(hr)) + goto End; + + hr = media_source->Start(time); + if (FAILED(hr)) + goto End; + + hr = WaitSamplesLoad(2, 2); + if (SUCCEEDED(hr)) { + IMediaTransform* media_transform = 0; + if (video_media_transform && video_media_transform->GetMFSamplesCount()) + media_transform = video_media_transform; + else if (audio_media_transform && audio_media_transform->GetMFSamplesCount()) + media_transform = audio_media_transform; + + if (media_transform) + time = media_transform->GetSampleTime(); + + hr = media_clock->SetTime(time); + } + + End: + const int64_t time_end = GetTimestamp(); + + const double_t _time = CalcTimeMsec(time_begin, time_end); + + if (SUCCEEDED(hr) && state.state_inner == State::Play) { + SetProcessEnable(); + hr = media_clock->TimeBegin(); + } + MOVIE_PLAY_LIB_PRINT_FUNC_END; + return hr; + } + + inline void MediaSession::Destroy(MediaSession* ptr) { + if (!ptr) + return; + + delete ptr; + } + + uint32_t __stdcall MediaSession::ThreadMain(MediaSession* media_session) { + DWORD task_index = 0; + HANDLE avrt_handle = AvSetMmThreadCharacteristicsW(L"Audio", &task_index); + if (!avrt_handle) +#pragma warning(suppress: 6031) + GetLastError(); + + HANDLE handles[2]; + handles[0] = media_session->hTimer; + handles[1] = media_session->hEvent; + while (!WaitForMultipleObjects(2, handles, 0, INFINITE)) + media_session->Update(); + + if (avrt_handle) + AvRevertMmThreadCharacteristics(avrt_handle); + return 0; + } +} diff --git a/src/ReDIVA/movie_play_lib/media_session.hpp b/src/ReDIVA/movie_play_lib/media_session.hpp new file mode 100644 index 00000000..58226a67 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/media_session.hpp @@ -0,0 +1,115 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "movie_play_lib.hpp" +#include "gl_dx_interop_texture.hpp" +#include "video_renderer.hpp" +#include "wasapi_renderer.hpp" + +namespace MoviePlayLib { + struct MediaSessionState { + Lock lock; + std::atomic state; + std::atomic state_inner; + int32_t command_index_queue; + int32_t command_index; + + inline MediaSessionState() : lock(), state(State::Init), + state_inner(State::Init), command_index_queue(), command_index() { + + } + + inline void SetStateAdvance(State state) { + lock.Acquire(); + this->state = state; + command_index_queue++; + lock.Release(); + } + + inline void SetInnerState(State state) { + lock.Acquire(); + state_inner = state; + if (command_index == command_index_queue) + this->state = state; + lock.Release(); + } + + inline void SetInnerStateAdvance(State state) { + lock.Acquire(); + state_inner = state; + if (++command_index == command_index_queue) + this->state = state; + lock.Release(); + } + }; + + class MediaSession : public IUnknown { + protected: + RefCount ref_count; + Lock lock; + DWORD dwQueue; + MediaSessionState state; + BOOL process; + MediaStatsLock media_stats_lock; + IMediaClock* media_clock; + IMediaSource* media_source; + IMediaTransform* audio_media_transform; + IMediaTransform* video_media_transform; + WASAPIRenderer* wasapi_renderer; + VideoRenderer* video_renderer; + GLDXInteropTexture* interop_texture; + int64_t duration; + int64_t audio_sample_time; + int64_t video_sample_time; + HANDLE hThread; + HANDLE hTimer; + HANDLE hEvent; + AsyncCallback async_callback; + + public: + virtual HRESULT QueryInterface(const IID& riid, void** ppvObject) override; + virtual ULONG AddRef() override; + virtual ULONG Release() override; + + MediaSession(HRESULT& hr, const wchar_t* path, IMediaClock* media_clock = 0); + virtual ~MediaSession(); + + virtual HRESULT AsyncCallback(IMFAsyncResult* mf_async_result); + virtual HRESULT Close(); + virtual HRESULT Play(); + virtual HRESULT Pause(); + virtual HRESULT SetTime(double_t value); + virtual State GetState(); + virtual double_t GetDuration(); + virtual double_t GetTime(); + virtual HRESULT GetAudioParams(AudioParams* value); + virtual HRESULT SetAudioParams(const AudioParams* value); + virtual HRESULT GetVideoParams(VideoParams* value); + virtual HRESULT GetD3D9Texture(IDirect3DDevice9* d3d_device, IDirect3DTexture9** ptr); + virtual HRESULT GetD3D11Texture(ID3D11Device* d3d_device, ID3D11Texture2D** ptr); + virtual HRESULT GetGLDXIntreropTexture(IGLDXInteropTexture** ptr); + + void GetSamplesCount(); + void Process(const char* name, IMediaTransform* media_transform, + IMediaRenderer* media_renderer, int64_t process_time); + void SetProcessDisable(); + void SetProcessEnable(); + void Shutdown(); + void Update(); + HRESULT WaitSamplesLoad(uint32_t min_audio_samples_count, uint32_t min_video_samples_count); + + HRESULT open(const wchar_t* url); + HRESULT set_position(int64_t time); + + static void Destroy(MediaSession* ptr); + static uint32_t __stdcall ThreadMain(MediaSession* media_session); + + inline void Destroy() { + Destroy(this); + } + }; +} diff --git a/src/ReDIVA/movie_play_lib/media_source.cpp b/src/ReDIVA/movie_play_lib/media_source.cpp new file mode 100644 index 00000000..2726dfa1 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/media_source.cpp @@ -0,0 +1,632 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "media_source.hpp" +#include "media_foundation.hpp" +#include +#include + +namespace MoviePlayLib { + HRESULT MediaSource::QueryInterface(const IID& riid, void** ppvObject) { + if (!ppvObject) + return E_INVALIDARG; + + if (riid == __uuidof(IMediaSource) || riid == __uuidof(IUnknown)) { + *ppvObject = this; + AddRef(); + return S_OK; + } + + *ppvObject = 0; + return E_NOINTERFACE; + } + + ULONG MediaSource::AddRef() { + return ++ref_count; + } + + ULONG MediaSource::Release() { + if (!--ref_count) + Destroy(); + return ref_count; + } + + HRESULT MediaSource::Shutdown() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + lock.Acquire(); + async_lock.Acquire(); + + shutdown = TRUE; + start = FALSE; + + if (video_media_stream) + video_media_stream->Shutdown(); + if (audio_media_stream) + audio_media_stream->Shutdown(); + if (mf_media_source) + mf_media_source->Shutdown(); + + async_lock.Release(); + lock.Release(); + MOVIE_PLAY_LIB_PRINT_FUNC_END; + return S_OK; + } + + HRESULT MediaSource::Stop() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + lock.Acquire(); + + HRESULT hr; + if (shutdown) + hr = MF_E_SHUTDOWN; + else { + hr = S_OK; + + if (start) { + start = FALSE; + + hr = mf_media_source->Stop(); + if (SUCCEEDED(hr)) + WaitForSingleObject(hMediaSourceEvent, INFINITE); + } + } + + lock.Release(); + MOVIE_PLAY_LIB_PRINT_FUNC_END; + return S_OK; + } + + HRESULT MediaSource::Start(INT64 time) { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + lock.Acquire(); + + HRESULT hr; + if (shutdown) + hr = MF_E_SHUTDOWN; + else { + hr = S_OK; + + if (!start) { + PROPVARIANT propvar = {}; + start = TRUE; + propvar.hVal.QuadPart = time; + propvar.vt = VT_I8; + hr = mf_media_source->Start(mf_presentation_descriptor, 0, &propvar); + if (SUCCEEDED(hr)) + WaitForSingleObject(hMediaSourceEvent, INFINITE); + } + } + + lock.Release(); + MOVIE_PLAY_LIB_PRINT_FUNC_END; + return hr; + } + + UINT64 MediaSource::GetDuration() { + lock.Acquire(); + uint64_t value = this->duration; + lock.Release(); + return value; + } + + HRESULT MediaSource::GetAudioMFMediaType(IMFMediaType** ptr) { + lock.Acquire(); + async_lock.Acquire(); + + HRESULT hr; + if (shutdown) + hr = MF_E_SHUTDOWN; + else { + hr = S_OK; + + if (audio_mf_stream_identifier == -1) + hr = MF_E_INVALIDSTREAMNUMBER; + else if (audio_media_stream) + hr = audio_media_stream->GetMFMediaType(ptr); + else { + *ptr = audio_mf_media_type; + audio_mf_media_type->AddRef(); + } + } + + async_lock.Release(); + lock.Release(); + return hr; + } + + HRESULT MediaSource::GetVideoMFMediaType(IMFMediaType** ptr) { + lock.Acquire(); + async_lock.Acquire(); + + HRESULT hr; + if (shutdown) + hr = MF_E_SHUTDOWN; + else { + hr = S_OK; + + if (video_mf_stream_identifier == -1) + hr = MF_E_INVALIDSTREAMNUMBER; + else if (video_media_stream) + hr = video_media_stream->GetMFMediaType(ptr); + else { + *ptr = video_mf_media_type; + video_mf_media_type->AddRef(); + } + } + + async_lock.Release(); + lock.Release(); + return hr; + } + + HRESULT MediaSource::SetAudioMediaTransform(IMediaTransform* media_transform) { + lock.Acquire(); + async_lock.Acquire(); + + if (!shutdown && audio_mf_stream_identifier != -1) { + if (audio_media_transform) { + audio_media_transform->Release(); + audio_media_transform = 0; + } + + audio_media_transform = media_transform; + media_transform->AddRef(); + } + + async_lock.Release(); + lock.Release(); + return S_OK; + } + + HRESULT MediaSource::SetVideoMediaTransform(IMediaTransform* media_transform) { + lock.Acquire(); + async_lock.Acquire(); + + if (!shutdown && video_mf_stream_identifier != -1) { + if (video_media_transform) { + video_media_transform->Release(); + video_media_transform = 0; + } + + video_media_transform = media_transform; + media_transform->AddRef(); + } + + async_lock.Release(); + lock.Release(); + return S_OK; + } + + MediaSource::MediaSource(HRESULT& hr, MediaStatsLock* media_stats_lock, + IMediaClock* media_clock, const wchar_t* url) : ref_count(), lock(), async_lock(), media_stats_lock(), + media_clock(), mf_media_source(), audio_mf_media_type(), video_mf_media_type(), audio_media_stream(), + video_media_stream(), audio_media_transform(), video_media_transform(), mf_presentation_descriptor(), + audio_mf_stream_identifier(-1), video_mf_stream_identifier(-1), shutdown(), start(), duration(), + hThread(), hTimer(), hManualEvent(), hMediaStreamEvent(), hMediaSourceEvent(), async_callback(this) { + LARGE_INTEGER due_time; + + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + if (FAILED(hr)) + goto End; + + this->media_clock = media_clock; + media_clock->AddRef(); + + if (FAILED(hr)) + goto End; + + hManualEvent = CreateEventA(0, TRUE, FALSE, 0); + if (!hManualEvent) + hr = HRESULT_FROM_WIN32(GetLastError()); + + if (FAILED(hr)) + goto End; + + hMediaSourceEvent = CreateEventA(0, FALSE, FALSE, 0); + if (!hMediaSourceEvent) + hr = HRESULT_FROM_WIN32(GetLastError()); + + if (FAILED(hr)) + goto End; + + hMediaStreamEvent = CreateEventA(0, FALSE, FALSE, 0); + if (!hMediaStreamEvent) + hr = HRESULT_FROM_WIN32(GetLastError()); + + if (FAILED(hr)) + goto End; + + hTimer = CreateWaitableTimerA(0, FALSE, 0); + if (!hTimer) + hr = HRESULT_FROM_WIN32(GetLastError()); + + if (FAILED(hr)) + goto End; + + due_time = {}; + if (!SetWaitableTimer(hTimer, &due_time, 50, 0, 0, FALSE)) + hr = HRESULT_FROM_WIN32(GetLastError()); + + if (FAILED(hr)) + goto End; + + hThread = (HANDLE)_beginthreadex(0, 0, (_beginthreadex_proc_type)MediaSource::ThreadMain, this, 0, 0); + if (!hThread) + hr = HRESULT_FROM_WIN32(GetLastError()); + + if (FAILED(hr)) + goto End; + + hr = LoadURL(url); + if (SUCCEEDED(hr)) + hr = mf_media_source->BeginGetEvent(&async_callback, 0); + End: + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + MediaSource::~MediaSource() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + shutdown = TRUE; + start = FALSE; + + if (video_media_stream) + video_media_stream->Shutdown(); + if (audio_media_stream) + audio_media_stream->Shutdown(); + if (mf_media_source) + mf_media_source->Shutdown(); + + if (video_media_transform) { + video_media_transform->Release(); + video_media_transform = 0; + } + + if (audio_media_transform) { + audio_media_transform->Release(); + audio_media_transform = 0; + } + + if (video_media_stream) { + video_media_stream->Release(); + video_media_stream = 0; + } + + if (audio_media_stream) { + audio_media_stream->Release(); + audio_media_stream = 0; + } + + if (video_mf_media_type) { + video_mf_media_type->Release(); + video_mf_media_type = 0; + } + + if (audio_mf_media_type) { + audio_mf_media_type->Release(); + audio_mf_media_type = 0; + } + + if (mf_presentation_descriptor) { + mf_presentation_descriptor->Release(); + mf_presentation_descriptor = 0; + } + + if (mf_media_source) { + mf_media_source->Release(); + mf_media_source = 0; + } + + if (media_clock) { + media_clock->Release(); + media_clock = 0; + } + + if (hThread) { + SetEvent(hManualEvent); + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + hThread = 0; + } + + if (hTimer) { + CloseHandle(hTimer); + hTimer = 0; + } + + if (hMediaStreamEvent) { + CloseHandle(hMediaStreamEvent); + hMediaStreamEvent = 0; + } + + if (hMediaSourceEvent) { + CloseHandle(hMediaSourceEvent); + hMediaSourceEvent = 0; + } + + if (hManualEvent) { + CloseHandle(hManualEvent); + hManualEvent = 0; + } + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + HRESULT MediaSource::AsyncCallback(IMFAsyncResult* mf_async_result) { + async_lock.Acquire(); + HRESULT media_event_status = 0; + MediaEventType media_event_type = 0; + IMFMediaEvent* mf_media_event = 0; + + HRESULT hr; + hr = mf_media_source->EndGetEvent(mf_async_result, &mf_media_event); + if (FAILED(hr)) + goto End; + + hr = mf_media_event->GetStatus(&media_event_status); + if (FAILED(hr)) + goto End; + + hr = mf_media_event->GetType(&media_event_type); + if (FAILED(hr)) + goto End; + + switch (media_event_type) { + case MEError: + hr = E_FAIL; + if (media_event_status < 0) + hr = media_event_status; + break; + case MESourceStarted: + case MESourceStopped: + SetEvent(hMediaSourceEvent); + break; + case MENewStream: { + if (shutdown) + break; + + IMFMediaStream* mf_media_stream = 0; + IMFStreamDescriptor* mf_stream_decriptor = 0; + DWORD mf_stream_identifier = 0; + MoviePlayLib::MediaStream** media_stream = 0; + IMediaTransform* media_transform = 0; + + hr = MFMediaEventQueryInterface(mf_media_event, __uuidof(IMFMediaStream), (void**)&mf_media_stream); + if (FAILED(hr)) + goto MENewStreamEnd; + + hr = mf_media_stream->GetStreamDescriptor(&mf_stream_decriptor); + if (FAILED(hr)) + goto MENewStreamEnd; + + hr = mf_stream_decriptor->GetStreamIdentifier(&mf_stream_identifier); + if (FAILED(hr)) + goto MENewStreamEnd; + + if (mf_stream_identifier == audio_mf_stream_identifier) { + if (audio_media_stream) { + audio_media_stream->Release(); + audio_media_stream = 0; + } + + media_stream = &audio_media_stream; + media_transform = audio_media_transform; + } + else if (mf_stream_identifier == video_mf_stream_identifier) { + if (video_media_stream) { + video_media_stream->Release(); + video_media_stream = 0; + } + + media_stream = &video_media_stream; + media_transform = video_media_transform; + } + else + goto MENewStreamEnd; + + if (media_transform) + hr = MediaStream::Create(media_stats_lock, media_clock, + mf_media_stream, hMediaStreamEvent, media_transform, *media_stream); + + MENewStreamEnd: + if (mf_stream_decriptor) { + mf_stream_decriptor->Release(); + mf_stream_decriptor = 0; + } + + if (mf_media_stream) { + mf_media_stream->Release(); + mf_media_stream = 0; + } + } break; + } + + End: + if (mf_media_event) { + mf_media_event->Release(); + mf_media_event = 0; + } + + if (!shutdown && SUCCEEDED(hr)) + hr = mf_media_source->BeginGetEvent(&async_callback, 0); + + if (FAILED(hr)) { + media_stats_lock->SetCurrError(hr, this); + + if (video_media_stream) + video_media_stream->Shutdown(); + if (audio_media_stream) + audio_media_stream->Shutdown(); + } + + hr = mf_async_result->SetStatus(hr); + async_lock.Release(); + return hr; + } + + HRESULT MediaSource::LoadURL(const wchar_t* url) { + HRESULT hr = S_OK; + if (!mf_media_source) { + IMFSourceResolver* mf_source_resolver = 0; + IUnknown* object = 0; + MF_OBJECT_TYPE object_type = MF_OBJECT_INVALID; + + hr = MFCreateSourceResolver(&mf_source_resolver); + if (SUCCEEDED(hr)) { + hr = mf_source_resolver->CreateObjectFromURL(url, MF_RESOLUTION_MEDIASOURCE, 0, &object_type, &object); + if (SUCCEEDED(hr)) + hr = object->QueryInterface(__uuidof(IMFMediaSource), (void**)&mf_media_source); + } + + if (object) { + object->Release(); + object = 0; + } + + if (mf_source_resolver) { + mf_source_resolver->Release(); + mf_source_resolver = 0; + } + } + + DWORD v28 = 0; + if (SUCCEEDED(hr)) { + hr = mf_media_source->CreatePresentationDescriptor(&mf_presentation_descriptor); + if (SUCCEEDED(hr)) { + hr = mf_presentation_descriptor->GetStreamDescriptorCount(&v28); + if (SUCCEEDED(hr)) + hr = mf_presentation_descriptor->GetUINT64(MF_PD_DURATION, &duration); + } + } + + if (FAILED(hr)) + return hr; + + for (DWORD index = 0; index < v28 && SUCCEEDED(hr); index++) { + BOOL selected = FALSE; + IMFStreamDescriptor* mf_stream_decriptor = 0; + IMFMediaTypeHandler* mf_media_type_handler = 0; + DWORD mf_stream_identifier = 0; + + IMFMediaType* mf_media_type = 0; + GUID guid_major_type = {}; + GUID guid_format_subtype = {}; + hr = mf_presentation_descriptor->GetStreamDescriptorByIndex(index, &selected, &mf_stream_decriptor); + if (SUCCEEDED(hr)) { + hr = mf_stream_decriptor->GetMediaTypeHandler(&mf_media_type_handler); + if (SUCCEEDED(hr)) { + hr = mf_stream_decriptor->GetStreamIdentifier(&mf_stream_identifier); + if (SUCCEEDED(hr)) { + hr = mf_media_type_handler->GetMajorType(&guid_major_type); + if (SUCCEEDED(hr)) { + hr = mf_media_type_handler->GetMediaTypeByIndex(0, &mf_media_type); + if (SUCCEEDED(hr)) + hr = mf_media_type->GetGUID(MF_MT_SUBTYPE, &guid_format_subtype); + } + } + } + } + + BOOL select_stream = FALSE; + if (SUCCEEDED(hr)) { + if (audio_mf_stream_identifier == -1 + && guid_major_type == MFMediaType_Audio + && (guid_format_subtype == MFAudioFormat_WMAudioV8 + || guid_format_subtype == MFAudioFormat_WMAudioV9 + || guid_format_subtype == MFAudioFormat_WMAudio_Lossless + || guid_format_subtype == MFAudioFormat_AAC + || guid_format_subtype == MFAudioFormat_Vorbis + || guid_format_subtype == MFAudioFormat_MPEG + || guid_format_subtype == MFAudioFormat_MP3)) { + audio_mf_media_type = mf_media_type; + audio_mf_stream_identifier = mf_stream_identifier; + mf_media_type->AddRef(); + select_stream = TRUE; + } + if (video_mf_stream_identifier == -1 + && guid_major_type == MFMediaType_Video + && (guid_format_subtype == MFVideoFormat_WMV1 + || guid_format_subtype == MFVideoFormat_WMV2 + || guid_format_subtype == MFVideoFormat_WMV3 + || guid_format_subtype == MFVideoFormat_WVC1 + || guid_format_subtype == MFVideoFormat_H264 + || guid_format_subtype == MFVideoFormat_VP80 + || guid_format_subtype == MFVideoFormat_MPG1 + || guid_format_subtype == MFVideoFormat_MPEG2)) { + video_mf_media_type = mf_media_type; + video_mf_stream_identifier = mf_stream_identifier; + mf_media_type->AddRef(); + select_stream = TRUE; + } + + if (select_stream) + hr = mf_presentation_descriptor->SelectStream(index); + else + hr = mf_presentation_descriptor->DeselectStream(index); + } + + if (mf_media_type) { + mf_media_type->Release(); + mf_media_type = 0; + } + + if (mf_media_type_handler) { + mf_media_type_handler->Release(); + mf_media_type_handler = 0; + } + + if (mf_stream_decriptor) { + mf_stream_decriptor->Release(); + mf_stream_decriptor = 0; + } + } + return hr; + } + + HRESULT MediaSource::Create(MediaStatsLock* media_stats_lock, + IMediaClock* media_clock, const wchar_t* url, IMediaSource*& ptr) { + HRESULT hr = S_OK; + MediaSource* p = new MediaSource(hr, media_stats_lock, media_clock, url); + if (!p) + return E_OUTOFMEMORY; + + if (SUCCEEDED(hr)) { + ptr = p; + p->AddRef(); + } + else + p->Shutdown(); + + p->Release(); + return S_OK; + } + + inline void MediaSource::Destroy(MediaSource* ptr) { + if (!ptr) + return; + + delete ptr; + } + + uint32_t __stdcall MediaSource::ThreadMain(MediaSource* media_source) { + HANDLE handles[3]; + handles[0] = media_source->hManualEvent; + handles[1] = media_source->hMediaStreamEvent; + handles[2] = media_source->hTimer; + while (WaitForMultipleObjects(3, handles, 0, INFINITE) - 1 <= 1) { + media_source->lock.Acquire(); + media_source->async_lock.Acquire(); + + if (!media_source->shutdown && media_source->start) { + if (media_source->audio_media_stream) + media_source->audio_media_stream->RequestSample(); + if (media_source->video_media_stream) + media_source->video_media_stream->RequestSample(); + } + + media_source->async_lock.Release(); + media_source->lock.Release(); + } + return 0; + } +} diff --git a/src/ReDIVA/movie_play_lib/media_source.hpp b/src/ReDIVA/movie_play_lib/media_source.hpp new file mode 100644 index 00000000..0d4a336b --- /dev/null +++ b/src/ReDIVA/movie_play_lib/media_source.hpp @@ -0,0 +1,70 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "movie_play_lib.hpp" +#include "media_stream.hpp" + +namespace MoviePlayLib { + class MediaSource : public IMediaSource { + protected: + RefCount ref_count; + Lock lock; + Lock async_lock; + MediaStatsLock* media_stats_lock; + IMediaClock* media_clock; + IMFMediaSource* mf_media_source; + IMFPresentationDescriptor* mf_presentation_descriptor; + IMFMediaType* audio_mf_media_type; + IMFMediaType* video_mf_media_type; + MediaStream* audio_media_stream; + MediaStream* video_media_stream; + IMediaTransform* audio_media_transform; + IMediaTransform* video_media_transform; + DWORD audio_mf_stream_identifier; + DWORD video_mf_stream_identifier; + BOOL shutdown; + BOOL start; + uint64_t duration; + HANDLE hThread; + HANDLE hTimer; + HANDLE hManualEvent; + HANDLE hMediaStreamEvent; + HANDLE hMediaSourceEvent; + AsyncCallback async_callback; + + public: + virtual HRESULT QueryInterface(const IID& riid, void** ppvObject) override; + virtual ULONG AddRef() override; + virtual ULONG Release() override; + + virtual HRESULT Shutdown() override; + virtual HRESULT Stop() override; + virtual HRESULT Start(INT64 time) override; + virtual UINT64 GetDuration() override; + virtual HRESULT GetAudioMFMediaType(IMFMediaType** ptr) override; + virtual HRESULT GetVideoMFMediaType(IMFMediaType** ptr) override; + virtual HRESULT SetAudioMediaTransform(IMediaTransform* media_transform) override; + virtual HRESULT SetVideoMediaTransform(IMediaTransform* media_transform) override; + + MediaSource(HRESULT& hr, MediaStatsLock* media_stats_lock, + IMediaClock* media_clock, const wchar_t* url); + virtual ~MediaSource(); + + virtual HRESULT AsyncCallback(IMFAsyncResult* mf_async_result); + + HRESULT LoadURL(const wchar_t* url); + + static HRESULT Create(MediaStatsLock* media_stats_lock, + IMediaClock* media_clock, const wchar_t* url, IMediaSource*& ptr); + static void Destroy(MediaSource* ptr); + static uint32_t __stdcall ThreadMain(MoviePlayLib::MediaSource* media_source); + + inline void Destroy() { + Destroy(this); + } + }; +} diff --git a/src/ReDIVA/movie_play_lib/media_stream.cpp b/src/ReDIVA/movie_play_lib/media_stream.cpp new file mode 100644 index 00000000..03c82175 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/media_stream.cpp @@ -0,0 +1,214 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "media_stream.hpp" +#include "media_foundation.hpp" +#include "tracked_sample.hpp" + +namespace MoviePlayLib { + HRESULT MediaStream::QueryInterface(const IID& riid, void** ppvObject) { + if (!ppvObject) + return E_INVALIDARG; + + if (riid == __uuidof(IUnknown)) { + *ppvObject = this; + AddRef(); + return S_OK; + } + + *ppvObject = 0; + return E_NOINTERFACE; + } + + ULONG MediaStream::AddRef() { + return ++ref_count; + } + + ULONG MediaStream::Release() { + if (!--ref_count) + Destroy(); + return ref_count; + } + + MediaStream::MediaStream(HRESULT& hr, MediaStatsLock* media_stats_lock, IMediaClock* media_clock, + IMFMediaStream* mf_media_stream, HANDLE hEvent, IMediaTransform* media_transform) + : ref_count(), lock(), media_stats_lock(media_stats_lock), media_clock(), mf_media_stream(), + media_transform(), shutdown(), sample_recieve(), hEvent(hEvent), sample_time(), async_callback(this) { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + if (SUCCEEDED(hr)) { + this->media_clock = media_clock; + media_clock->AddRef(); + + this->mf_media_stream = mf_media_stream; + mf_media_stream->AddRef(); + + this->media_transform = media_transform; + media_transform->AddRef(); + + if (SUCCEEDED(hr)) + hr = this->mf_media_stream->BeginGetEvent(&async_callback, 0); + } + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + MediaStream::~MediaStream() { + + } + + HRESULT MediaStream::AsyncCallback(IMFAsyncResult* mf_async_result) { + lock.Acquire(); + HRESULT media_event_status = 0; + MediaEventType media_event_type = 0; + IMFMediaEvent* mf_media_event = 0; + + HRESULT hr; + hr = mf_media_stream->EndGetEvent(mf_async_result, &mf_media_event); + if (FAILED(hr)) + goto End; + + hr = mf_media_event->GetStatus(&media_event_status); + if (FAILED(hr)) + goto End; + + hr = mf_media_event->GetType(&media_event_type); + if (FAILED(hr)) + goto End; + + switch (media_event_type) { + case MEError: + hr = E_FAIL; + if (FAILED(media_event_status)) + hr = media_event_status; + break; + case MEStreamStarted: + if (!shutdown) { + sample_recieve = FALSE; + sample_time = 0; + RequestSampleInner(); + } + break; + case MEEndOfStream: + if (!shutdown) + media_transform->SendMFSample(0); + break; + case MEMediaSample: + if (!shutdown) { + TrackedSample* tracked_sample = 0; + hr = MFMediaEventQueryInterface(mf_media_event, __uuidof(TrackedSample), (void**)&tracked_sample); + if (SUCCEEDED(hr)) { + int64_t sample_time = 0; + if (SUCCEEDED(tracked_sample->GetSampleTime(&sample_time))) { + media_transform->SendMFSample(tracked_sample); + this->sample_time = sample_time; + } + } + + if (tracked_sample) { + tracked_sample->Release(); + tracked_sample = 0; + } + + sample_recieve = FALSE; + SetEvent(hEvent); + } + break; + } + + End: + if (mf_media_event) { + mf_media_event->Release(); + mf_media_event = 0; + } + + if (!shutdown && SUCCEEDED(hr)) + hr = mf_media_stream->BeginGetEvent(&async_callback, 0); + + if (FAILED(hr)) { + media_stats_lock->SetCurrError(hr, this); + media_transform->SendMFSample(0); + } + + hr = mf_async_result->SetStatus(hr); + lock.Release(); + return hr; + } + + HRESULT MediaStream::Shutdown() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + lock.Acquire(); + media_transform->SendMFSample(0); + shutdown = TRUE; + lock.Release(); + MOVIE_PLAY_LIB_PRINT_FUNC_END; + return S_OK; + } + + HRESULT MediaStream::GetMFMediaType(IMFMediaType** mf_media_type) { + lock.Acquire(); + HRESULT hr = shutdown ? MF_E_SHUTDOWN : S_OK; + if (!shutdown) { + IMFStreamDescriptor* mf_stream_descriptor = 0; + IMFMediaTypeHandler* mf_media_type_handler = 0; + hr = mf_media_stream->GetStreamDescriptor(&mf_stream_descriptor); + if (SUCCEEDED(hr)) { + hr = mf_stream_descriptor->GetMediaTypeHandler(&mf_media_type_handler); + if (SUCCEEDED(hr)) + hr = mf_media_type_handler->GetCurrentMediaType(mf_media_type); + } + + if (mf_media_type_handler) { + mf_media_type_handler->Release(); + mf_media_type_handler = 0; + } + + if (mf_stream_descriptor) { + mf_stream_descriptor->Release(); + mf_stream_descriptor = 0; + } + } + lock.Release(); + return hr; + } + + HRESULT MediaStream::RequestSample() { + lock.Acquire(); + HRESULT hr = shutdown ? MF_E_SHUTDOWN : S_OK; + RequestSampleInner(); + lock.Release(); + return hr; + } + + void MediaStream::RequestSampleInner() { + if (!shutdown && !sample_recieve + && sample_time < media_clock->GetTime() + 50000000 + && SUCCEEDED(mf_media_stream->RequestSample(0))) + sample_recieve = TRUE; + } + + HRESULT MediaStream::Create(MediaStatsLock* media_stats_lock, IMediaClock* media_clock, + IMFMediaStream* mf_media_stream, HANDLE hEvent, IMediaTransform* media_transform, MediaStream*& ptr) { + HRESULT hr = S_OK; + MediaStream* p = new MediaStream(hr, media_stats_lock, media_clock, mf_media_stream, hEvent, media_transform); + if (!p) + return E_OUTOFMEMORY; + + if (SUCCEEDED(hr)) { + ptr = p; + p->AddRef(); + } + else + p->Shutdown(); + + p->Release(); + return S_OK; + } + + inline void MediaStream::Destroy(MediaStream* ptr) { + if (!ptr) + return; + + delete ptr; + } +} diff --git a/src/ReDIVA/movie_play_lib/media_stream.hpp b/src/ReDIVA/movie_play_lib/media_stream.hpp new file mode 100644 index 00000000..fd95f4a6 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/media_stream.hpp @@ -0,0 +1,50 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "movie_play_lib.hpp" +#include + +namespace MoviePlayLib { + class MediaStream : public IUnknown { + protected: + RefCount ref_count; + Lock lock; + MediaStatsLock* media_stats_lock; + IMediaClock* media_clock; + IMFMediaStream* mf_media_stream; + IMediaTransform* media_transform; + BOOL shutdown; + BOOL sample_recieve; + HANDLE hEvent; + int64_t sample_time; + AsyncCallback async_callback; + + public: + virtual HRESULT QueryInterface(const IID& riid, void** ppvObject) override; + virtual ULONG AddRef() override; + virtual ULONG Release() override; + + MediaStream(HRESULT& hr, MediaStatsLock* media_stats_lock, IMediaClock* media_clock, + IMFMediaStream* mf_media_stream, HANDLE hEvent, IMediaTransform* media_transform); + virtual ~MediaStream(); + + virtual HRESULT AsyncCallback(IMFAsyncResult* mf_async_result); + virtual HRESULT Shutdown(); + virtual HRESULT GetMFMediaType(IMFMediaType** mf_media_type); + virtual HRESULT RequestSample(); + + void RequestSampleInner(); + + static HRESULT Create(MediaStatsLock* media_stats_lock, IMediaClock* media_clock, + IMFMediaStream* mf_media_stream, HANDLE hEvent, IMediaTransform* media_transform, MediaStream*& ptr); + static void Destroy(MediaStream* ptr); + + inline void Destroy() { + Destroy(this); + } + }; +} diff --git a/src/ReDIVA/movie_play_lib/mf_sample_list.cpp b/src/ReDIVA/movie_play_lib/mf_sample_list.cpp new file mode 100644 index 00000000..7be24279 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/mf_sample_list.cpp @@ -0,0 +1,66 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "mf_sample_list.hpp" + +namespace MoviePlayLib { + MFSampleList::MFSampleList() : lock(), index(1), shutdown(), list() { + + } + + MFSampleList::~MFSampleList() { + ClearList(); + } + + void MFSampleList::AddSample(IMFSample* mf_sample, bool check) { + lock.Acquire(); + if (CheckVideoIndex(mf_sample, check)) { + mf_sample->AddRef(); + list.push_back(mf_sample); + } + lock.Release(); + } + + bool MFSampleList::CheckVideoIndex(IMFSample* mf_sample, bool check) { + if (!mf_sample) + return false; + + uint32_t video_index = 0; + if (!check || SUCCEEDED(mf_sample->GetUINT32(VideoIndexGUID, &video_index)) && video_index == index) + return true; + return false; + } + + uint32_t MFSampleList::ClearList() { + lock.Acquire(); + while (list.size()) { + list.front()->Release(); + list.pop_front(); + } + uint32_t index = ++this->index; + lock.Release(); + return index; + } + + IMFSample* MFSampleList::PopSample() { + lock.Acquire(); + IMFSample* mf_sample = 0; + if (list.size()) { + mf_sample = list.front(); + list.pop_front(); + } + lock.Release(); + return mf_sample; + } + + int64_t MFSampleList::GetSampleTime() { + lock.Acquire(); + int64_t sample_time = INT64_MAX; + if (list.size()) + list.front()->GetSampleTime(&sample_time); + lock.Release(); + return sample_time; + } +} diff --git a/src/ReDIVA/movie_play_lib/mf_sample_list.hpp b/src/ReDIVA/movie_play_lib/mf_sample_list.hpp new file mode 100644 index 00000000..c36745fb --- /dev/null +++ b/src/ReDIVA/movie_play_lib/mf_sample_list.hpp @@ -0,0 +1,53 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "movie_play_lib.hpp" +#include + +namespace MoviePlayLib { + struct MFSampleList { + Lock lock; + uint32_t index; + bool shutdown; + std::list list; + + MFSampleList(); + ~MFSampleList(); + + void AddSample(IMFSample* mf_sample, bool check); + bool CheckVideoIndex(IMFSample* mf_sample, bool check); + uint32_t ClearList(); + int64_t GetSampleTime(); + IMFSample* PopSample(); + + inline bool CanShutdown() { + lock.Acquire(); + bool can_shutdown = shutdown && !list.size(); + lock.Release(); + return can_shutdown; + } + + inline size_t GetSamplesCount() { + lock.Acquire(); + size_t size = list.size(); + lock.Release(); + return size; + } + + inline void ResetShutdown() { + lock.Acquire(); + shutdown = FALSE; + lock.Release(); + } + + inline void SetShutdown() { + lock.Acquire(); + shutdown = TRUE; + lock.Release(); + } + }; +} diff --git a/src/ReDIVA/movie_play_lib/movie_play_lib.cpp b/src/ReDIVA/movie_play_lib/movie_play_lib.cpp new file mode 100644 index 00000000..250a74b9 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/movie_play_lib.cpp @@ -0,0 +1,128 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "movie_play_lib.hpp" + +namespace MoviePlayLib { + const GUID TextureSharedHandleGUID = { + 0xB4127AF5, 0xA697, 0x4037, 0xAB, 0x09, 0xC8, 0xF4, 0xCF, 0x0F, 0x80, 0xAC, + }; + + const GUID VideoIndexGUID = { + 0x1F42D8B6, 0x6A11, 0x4E2C, 0x91, 0x70, 0xF2, 0x01, 0x5D, 0xA7, 0x31, 0xF2 + }; + + void MediaStatsLock::GetStats(MediaStats& value) { + lock.Acquire(); + value = stats; + lock.Release(); + } + + void MediaStatsLock::SetAudioParams(uint32_t sample_rate, uint32_t channels) { + lock.Acquire(); + stats.sample_rate = sample_rate; + stats.channels = channels; + lock.Release(); + } + + void MediaStatsLock::SetAudioProcessOutput(double_t output_time, double_t sample_time) { + lock.Acquire(); + stats.audio_process_output_time = output_time; + stats.audio_process_sample_time = sample_time; + lock.Release(); + } + + void MediaStatsLock::SetAudioSampleTime(double_t value) { + lock.Acquire(); + stats.audio_sample_time = value; + lock.Release(); + } + + void MediaStatsLock::SetAudioTimeAdvance(double_t value) { + lock.Acquire(); + stats.audio_time_advance = value; + lock.Release(); + } + + bool MediaStatsLock::HasError() { + lock.Acquire(); + bool has_error = FAILED(stats.curr_error); + lock.Release(); + return has_error; + } + + void MediaStatsLock::SetCurrError(HRESULT hr, IUnknown* object) { + lock.Acquire(); + stats.curr_error = hr; + stats.curr_object = object; + lock.Release(); + } + + void MediaStatsLock::SetMFSamplesCount( + uint32_t audio_mf_samples_wait_count, uint32_t video_mf_samples_wait_count, + uint32_t audio_mf_samples_count, uint32_t video_mf_samples_count) { + lock.Acquire(); + stats.audio_mf_samples_wait_count = audio_mf_samples_wait_count; + stats.video_mf_samples_wait_count = video_mf_samples_wait_count; + stats.audio_mf_samples_count = audio_mf_samples_count; + stats.video_mf_samples_count = video_mf_samples_count; + lock.Release(); + } + + void MediaStatsLock::SetSessionInfo(double_t duration, bool audio_present, bool video_present) { + lock.Acquire(); + stats.duration = duration; + stats.audio_present = audio_present; + stats.video_present = video_present; + lock.Release(); + } + + void MediaStatsLock::SetVideoOutputTime(double_t value) { + lock.Acquire(); + stats.video_output_time = value; + lock.Release(); + } + + void MediaStatsLock::SetVideoParams(uint32_t width, uint32_t height, uint32_t frame_size_width, + uint32_t frame_size_height, float_t frame_rate, float_t pixel_aspect_ratio) { + lock.Acquire(); + stats.width = width; + stats.height = height; + stats.frame_size_width = frame_size_width; + stats.frame_size_height = frame_size_height; + stats.frame_rate = frame_rate; + stats.pixel_aspect_ratio = pixel_aspect_ratio; + lock.Release(); + } + + void MediaStatsLock::SetVideoProcessOutput(double_t output_time, double_t blt_time, double_t sample_time) { + lock.Acquire(); + stats.video_process_output_time = output_time; + stats.video_process_blt_time = blt_time; + stats.video_process_sample_time = sample_time; + lock.Release(); + } + + void MediaStatsLock::SetVideoSampleTime(double_t value) { + lock.Acquire(); + stats.video_sample_time = value; + lock.Release(); + } + + void MediaStatsLock::SetVideoTimeAdvance(double_t value) { + lock.Acquire(); + stats.video_time_advance = value; + lock.Release(); + } + + void PrintDebug(const wchar_t* fmt, ...) { +#ifdef DEBUG + va_list args; + va_start(args, fmt); + vwprintf(fmt, args); + va_end(args); +#endif + } +} diff --git a/src/ReDIVA/movie_play_lib/movie_play_lib.hpp b/src/ReDIVA/movie_play_lib/movie_play_lib.hpp new file mode 100644 index 00000000..34b144ae --- /dev/null +++ b/src/ReDIVA/movie_play_lib/movie_play_lib.hpp @@ -0,0 +1,280 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "../../KKdLib/default.hpp" +#include +#include +#include +#include +#pragma comment(lib, "d3d9.lib") +#include +#pragma comment(lib, "d3d11.lib") +#include + +struct texture; + +#define MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN MoviePlayLib::PrintDebug(L">%S(%p)\n", __FUNCTION__, this) +#define MOVIE_PLAY_LIB_PRINT_FUNC_END MoviePlayLib::PrintDebug(L"<%S(%p)\n", __FUNCTION__, this) + +namespace MoviePlayLib { + enum class State { + None = 0x0, + Init, + Open, + Wait, + Pause, + Play, + Stop, + Max, + }; + + struct AudioParams { + float_t spk_l_volume; + float_t spk_r_volume; + float_t field_8; + float_t field_C; + float_t hph_l_volume; + float_t hph_r_volume; + }; + + struct Lock : SRWLOCK { + inline Lock() { + InitializeSRWLock(this); + } + + inline void Acquire() { +#pragma warning(suppress: 26110) + AcquireSRWLockExclusive(this); + } + + inline void Release() { +#pragma warning(suppress: 26110) + ReleaseSRWLockExclusive(this); + } + }; + + struct MediaStats { + double_t duration; + bool audio_present; + bool video_present; + uint32_t width; + uint32_t height; + uint32_t frame_size_width; + uint32_t frame_size_height; + float_t frame_rate; + float_t pixel_aspect_ratio; + uint32_t sample_rate; + uint32_t channels; + HRESULT curr_error; + IUnknown* curr_object; + uint32_t audio_mf_samples_wait_count; + uint32_t video_mf_samples_wait_count; + uint32_t audio_mf_samples_count; + uint32_t video_mf_samples_count; + double_t audio_sample_time; + double_t video_sample_time; + double_t audio_process_sample_time; + double_t video_process_sample_time; + double_t audio_process_output_time; + double_t video_process_output_time; + double_t video_process_blt_time; + double_t audio_time_advance; + double_t video_time_advance; + double_t audio_output_time; + double_t video_output_time; + }; + + struct MediaStatsLock { + protected: + Lock lock; + __int64 field_8; + MediaStats stats; + + public: + inline MediaStatsLock() : field_8(), stats() { + + } + + void GetStats(MediaStats& value); + bool HasError(); + void SetAudioParams(uint32_t sample_rate, uint32_t channels); + void SetAudioProcessOutput(double_t output_time, double_t sample_time); + void SetAudioSampleTime(double_t value); + void SetAudioTimeAdvance(double_t value); + void SetCurrError(HRESULT hr, IUnknown* object); + void SetMFSamplesCount( + uint32_t audio_mf_samples_wait_count, uint32_t video_mf_samples_wait_count, + uint32_t audio_mf_samples_count, uint32_t video_mf_samples_count); + void SetSessionInfo(double_t duration, bool audio_present, bool video_present); + void SetVideoOutputTime(double_t value); + void SetVideoParams(uint32_t width, uint32_t height, uint32_t frame_size_width, + uint32_t frame_size_height, float_t frame_rate, float_t pixel_aspect_ratio); + void SetVideoProcessOutput(double_t output_time, double_t blt_time, double_t sample_time); + void SetVideoSampleTime(double_t value); + void SetVideoTimeAdvance(double_t value); + }; + + struct RefCount : std::atomic { + RefCount() : std::atomic(1) { + + } + }; + + struct VideoParams { + int32_t width; + int32_t height; + int32_t frame_size_width; + int32_t frame_size_height; + float_t frame_rate; + float_t pixel_aspect_ratio; + }; + + class DECLSPEC_UUID("BE82EF2E-4B9F-4470-9C6F-F78E002FCDEF") + IMediaRenderer : public IUnknown { + public: + virtual HRESULT Shutdown() = 0; + virtual HRESULT Close() = 0; + virtual HRESULT Flush() = 0; + virtual HRESULT Open() = 0; + virtual HRESULT SetMFSample(IMFSample* mf_sample) = 0; + }; + + class DECLSPEC_UUID("4A5C73B9-FBFE-4A00-A298-FD3FAB0C5AF1") + IAudioRenderer : public IMediaRenderer { + public: + virtual HRESULT GetParams(AudioParams* value) = 0; + virtual HRESULT SetParams(const AudioParams* value) = 0; + }; + + class DECLSPEC_UUID("D19AC022-EA49-4B6F-9D51-673C1C15ECE8") + IGLDXInteropTexture : public IUnknown { + public: + virtual texture* GetTexture() = 0; + }; + + class DECLSPEC_UUID("D4FA34F6-D03C-4F21-8591-6858161CA5D7") + IMediaClock : public IUnknown { + public: + virtual HRESULT TimeEnd() = 0; + virtual HRESULT TimeBegin() = 0; + virtual HRESULT SetTime(INT64 value) = 0; + virtual INT64 GetTime() = 0; + }; + + class DECLSPEC_UUID("0D41578B-4514-407D-B654-870BCCDC630A") + IMediaTransform : public IUnknown { + public: + virtual HRESULT Shutdown() = 0; + virtual HRESULT Close() = 0; + virtual HRESULT Flush() = 0; + virtual HRESULT Open() = 0; + virtual HRESULT GetMFMediaType(IMFMediaType** mf_media_type) = 0; + virtual void SendMFSample(IMFSample* mf_sample) = 0; + virtual BOOL SignalEvent() = 0; + virtual BOOL CanShutdown() = 0; + virtual UINT32 GetMFSamplesWaitCount() = 0; + virtual UINT32 GetMFSamplesCount() = 0; + virtual INT64 GetSampleTime() = 0; + virtual void GetMFSample(IMFSample*& mf_sample) = 0; + }; + + class DECLSPEC_UUID("0D41578B-4514-407D-B654-870BCCDC630A") + IMediaSource : public IUnknown { + public: + virtual HRESULT Shutdown() = 0; + virtual HRESULT Stop() = 0; + virtual HRESULT Start(INT64 time) = 0; + virtual UINT64 GetDuration() = 0; + virtual HRESULT GetAudioMFMediaType(IMFMediaType** ptr) = 0; + virtual HRESULT GetVideoMFMediaType(IMFMediaType** ptr) = 0; + virtual HRESULT SetAudioMediaTransform(IMediaTransform* media_transform) = 0; + virtual HRESULT SetVideoMediaTransform(IMediaTransform* media_transform) = 0; + }; + + class DECLSPEC_UUID("12AC5FCE-C816-47FE-84B4-4915D6B42FA7") + IPlayer : public IUnknown { + public: + virtual HRESULT Open(const wchar_t* path) = 0; + virtual HRESULT Open(const char* path) = 0; + virtual HRESULT Close() = 0; + virtual HRESULT SetMediaClock(IMediaClock* media_clock) = 0; + virtual State GetState() = 0; + virtual HRESULT Play() = 0; + virtual HRESULT Pause() = 0; + virtual double_t GetDuration() = 0; + virtual double_t GetTime() = 0; + virtual HRESULT SetTime(double_t value) = 0; + virtual HRESULT GetAudioParams(AudioParams* value) = 0; + virtual HRESULT SetAudioParams(const AudioParams* value) = 0; + virtual HRESULT GetVideoParams(VideoParams* value) = 0; + virtual HRESULT GetD3D9Texture(IDirect3DDevice9* d3d_device, IDirect3DTexture9** ptr) = 0; + virtual HRESULT GetD3D11Texture(ID3D11Device* d3d_device, ID3D11Texture2D** ptr) = 0; + virtual HRESULT GetGLDXIntreropTexture(IGLDXInteropTexture** value) = 0; + }; + + template + class AsyncCallback : public IMFAsyncCallback { + public: + HRESULT(*func)(T* ptr, IMFAsyncResult* mf_async_result); + T* data; + + virtual HRESULT QueryInterface(const IID& riid, void** ppvObject) override { + if (!ppvObject) + return E_INVALIDARG; + + if (riid == __uuidof(IUnknown) || riid == __uuidof(IMFAsyncCallback)) { + *ppvObject = this; + AddRef(); + return S_OK; + } + + *ppvObject = 0; + return E_NOINTERFACE; + } + + virtual ULONG AddRef() override { + return data->AddRef(); + } + + virtual ULONG Release() override { + return data->Release(); + } + + virtual HRESULT GetParameters(DWORD* pdwFlags, DWORD* pdwQueue) override { + return E_NOTIMPL; + } + + virtual HRESULT Invoke(IMFAsyncResult* pAsyncResult) override { + return func(data, pAsyncResult); + } + + AsyncCallback(T* data) : func(Callback), data(data) { + + } + + static HRESULT Callback(T* data, IMFAsyncResult* mf_async_result) { + return data->AsyncCallback(mf_async_result); + } + }; + + inline double_t CalcTimeMsec(int64_t begin, int64_t end) { + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + return (double_t)(end - begin) / (double_t)freq.QuadPart * 1000.0; + } + + inline int64_t GetTimestamp() { + LARGE_INTEGER timestamp = {}; + QueryPerformanceCounter(×tamp); + return timestamp.QuadPart; + } + + extern const GUID TextureSharedHandleGUID; + extern const GUID VideoIndexGUID; + + extern void PrintDebug(const wchar_t* fmt, ...); +} diff --git a/src/ReDIVA/movie_play_lib/player.cpp b/src/ReDIVA/movie_play_lib/player.cpp new file mode 100644 index 00000000..4f382389 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/player.cpp @@ -0,0 +1,288 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "player.hpp" + +namespace MoviePlayLib { + HRESULT Player::QueryInterface(const IID& riid, void** ppvObject) { + if (!ppvObject) + return E_INVALIDARG; + + if (riid == __uuidof(IPlayer) || riid == __uuidof(IUnknown)) { + *ppvObject = this; + AddRef(); + return S_OK; + } + + *ppvObject = 0; + return E_NOINTERFACE; + } + + ULONG Player::AddRef() { + return ++ref_count; + } + + ULONG Player::Release() { + if (!--ref_count) + Destroy(); + return ref_count; + } + + HRESULT Player::Open(const wchar_t* path) { + lock.Acquire(); + + if (media_session) { + media_session->Close(); + + if (media_session) { + media_session->Release(); + media_session = 0; + } + } + + HRESULT hr = 0; + MediaSession* media_session = new MediaSession(hr, path, media_clock); + if (media_session) { + if (SUCCEEDED(hr)) { + this->media_session = media_session; + media_session->AddRef(); + } + else + media_session->Close(); + media_session->Release(); + } + else + hr = E_OUTOFMEMORY; + + lock.Release(); + return hr; + } + + HRESULT Player::Open(const char* path) { + if (!path) + return E_INVALIDARG; + + int32_t path_len = (int32_t)utf8_length(path); + if (path_len > 0 && path_len < MAX_PATH) { + wchar_t buf[MAX_PATH]; + int32_t buf_len = MultiByteToWideChar(0, 0, path, path_len, buf, sizeof(buf) / sizeof(wchar_t)); + if (buf_len > 0 && buf_len < MAX_PATH) { + if (buf_len >= MAX_PATH) + __debugbreak(); + buf[buf_len] = 0; + return Open(buf); + } + } + return E_INVALIDARG; + } + + HRESULT Player::Close() { + lock.Acquire(); + if (media_session) { + media_session->Close(); + + if (media_session) { + media_session->Release(); + media_session = 0; + } + } + lock.Release(); + return S_OK; + } + + HRESULT Player::SetMediaClock(IMediaClock* media_clock) { + lock.Acquire(); + HRESULT hr = S_OK; + if (!media_session) { + if (this->media_clock) { + this->media_clock->Release(); + this->media_clock = 0; + } + + this->media_clock = media_clock; + + if (media_clock) + media_clock->AddRef(); + } + else + hr = MF_E_INVALIDREQUEST; + lock.Release(); + return hr; + } + + State Player::GetState() { + lock.Acquire(); + State state; + if (media_session) + state = media_session->GetState(); + else + state = State::None; + lock.Release(); + return state; + } + + HRESULT Player::Play() { + lock.Acquire(); + HRESULT hr; + if (media_session) + hr = media_session->Play(); + else + hr = MF_E_NOT_INITIALIZED; + lock.Release(); + return hr; + } + + HRESULT Player::Pause() { + lock.Acquire(); + HRESULT hr; + if (media_session) + hr = media_session->Pause(); + else + hr = MF_E_NOT_INITIALIZED; + lock.Release(); + return hr; + } + + double_t Player::GetDuration() { + lock.Acquire(); + double_t duration; + if (media_session) + duration = media_session->GetDuration(); + else + duration = 0.0; + lock.Release(); + return duration; + } + + double_t Player::GetTime() { + lock.Acquire(); + double_t time; + if (media_session) + time = media_session->GetTime(); + else + time = 0.0; + lock.Release(); + return time; + } + + HRESULT Player::SetTime(double_t value) { + lock.Acquire(); + HRESULT hr; + if (media_session) + hr = media_session->SetTime(value); + else + hr = S_OK; + lock.Release(); + return hr; + } + + HRESULT Player::GetAudioParams(AudioParams* value) { + lock.Acquire(); + HRESULT hr; + if (media_session) + hr = media_session->GetAudioParams(value); + else + hr = S_OK; + lock.Release(); + return hr; + } + + HRESULT Player::SetAudioParams(const AudioParams* value) { + lock.Acquire(); + HRESULT hr; + if (media_session) + hr = media_session->SetAudioParams(value); + else + hr = S_OK; + lock.Release(); + return hr; + } + + HRESULT Player::GetVideoParams(VideoParams* value) { + lock.Acquire(); + HRESULT hr; + if (media_session) + hr = media_session->GetVideoParams(value); + else + hr = S_OK; + lock.Release(); + return hr; + } + + HRESULT Player::GetD3D9Texture(IDirect3DDevice9* d3d_device, IDirect3DTexture9** ptr) { + lock.Acquire(); + HRESULT hr; + if (media_session) + hr = media_session->GetD3D9Texture(d3d_device, ptr); + else + hr = S_OK; + lock.Release(); + return hr; + } + + HRESULT Player::GetD3D11Texture(ID3D11Device* d3d_device, ID3D11Texture2D** ptr) { + lock.Acquire(); + HRESULT hr; + if (media_session) + hr = media_session->GetD3D11Texture(d3d_device, ptr); + else + hr = S_OK; + lock.Release(); + return hr; + } + + HRESULT Player::GetGLDXIntreropTexture(IGLDXInteropTexture** value) { + lock.Acquire(); + HRESULT hr; + if (media_session) + hr = media_session->GetGLDXIntreropTexture(value); + else + hr = S_OK; + lock.Release(); + return hr; + } + + Player::Player() : ref_count(), lock(), media_clock(), media_session() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + Player::~Player() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + + if (media_session) { + media_session->Close(); + + if (media_session) { + media_session->Release(); + media_session = 0; + } + } + + if (media_clock) { + media_clock->Release(); + media_clock = 0; + } + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + HRESULT Player::Create(IPlayer*& ptr) { + Player* p = new Player; + if (!p) + return E_OUTOFMEMORY; + + ptr = p; + p->AddRef(); + p->Release(); + return S_OK; + } + + inline void Player::Destroy(Player* ptr) { + if (!ptr) + return; + + delete ptr; + } +} diff --git a/src/ReDIVA/movie_play_lib/player.hpp b/src/ReDIVA/movie_play_lib/player.hpp new file mode 100644 index 00000000..2c560fc6 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/player.hpp @@ -0,0 +1,51 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "movie_play_lib.hpp" +#include "media_session.hpp" + +namespace MoviePlayLib { + class Player : public IPlayer { + protected: + RefCount ref_count; + Lock lock; + IMediaClock* media_clock; + MediaSession* media_session; + + public: + virtual HRESULT QueryInterface(const IID& riid, void** ppvObject) override; + virtual ULONG AddRef() override; + virtual ULONG Release() override; + + virtual HRESULT Open(const wchar_t* path) override; + virtual HRESULT Open(const char* path) override; + virtual HRESULT Close() override; + virtual HRESULT SetMediaClock(IMediaClock* media_clock) override; + virtual State GetState() override; + virtual HRESULT Play() override; + virtual HRESULT Pause() override; + virtual double_t GetDuration() override; + virtual double_t GetTime() override; + virtual HRESULT SetTime(double_t value) override; + virtual HRESULT GetAudioParams(AudioParams* value) override; + virtual HRESULT SetAudioParams(const AudioParams* value) override; + virtual HRESULT GetVideoParams(VideoParams* value) override; + virtual HRESULT GetD3D9Texture(IDirect3DDevice9* d3d_device, IDirect3DTexture9** ptr) override; + virtual HRESULT GetD3D11Texture(ID3D11Device* d3d_device, ID3D11Texture2D** ptr) override; + virtual HRESULT GetGLDXIntreropTexture(IGLDXInteropTexture** value) override; + + Player(); + virtual ~Player(); + + static HRESULT Create(IPlayer*& ptr); + static void Destroy(Player* ptr); + + inline void Destroy() { + Destroy(this); + } + }; +} diff --git a/src/ReDIVA/movie_play_lib/tracked_sample.cpp b/src/ReDIVA/movie_play_lib/tracked_sample.cpp new file mode 100644 index 00000000..449ef7b0 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/tracked_sample.cpp @@ -0,0 +1,333 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "tracked_sample.hpp" +#include +#pragma comment(lib, "mfplat.lib") + +namespace MoviePlayLib { + HRESULT TrackedSample::QueryInterface(const IID& riid, void** ppvObject) { + if (!ppvObject) + return E_INVALIDARG; + + if (riid == __uuidof(TrackedSample)) { + *ppvObject = this; + AddRef(); + return S_OK; + } + else if (riid == __uuidof(IMFTrackedSample)) { + IMFTrackedSample* mf_tracked_sample = this; + *ppvObject = mf_tracked_sample; + AddRef(); + return S_OK; + } + else if (riid == __uuidof(IMFAsyncResult)) { + IMFAsyncResult* mf_async_result = this; + *ppvObject = mf_async_result; + AddRef(); + return S_OK; + } + else if (riid == __uuidof(IUnknown)) { + *ppvObject = this; + AddRef(); + return S_OK; + } + + *ppvObject = 0; + return E_NOINTERFACE; + } + + ULONG TrackedSample::AddRef() { + return ++ref_count; + } + + ULONG TrackedSample::Release() { + if (!--ref_count) { + if (mf_async_callback) { + AddRef(); + + IMFAsyncCallback* mf_async_callback = this->mf_async_callback; + this->mf_async_callback = 0; + mf_async_callback->Invoke(this); + mf_async_callback->Release(); + return Release(); + } + Destroy(); + } + return ref_count; + } + + HRESULT TrackedSample::GetItem(const IID& guidKey, PROPVARIANT* pValue) { + return mf_attributes->GetItem(guidKey, pValue); + } + + HRESULT TrackedSample::GetItemType(const GUID& guidKey, MF_ATTRIBUTE_TYPE* pType) { + return mf_attributes->GetItemType(guidKey, pType); + } + + HRESULT TrackedSample::CompareItem(const GUID& guidKey, const PROPVARIANT& Value, BOOL* pbResult) { + return mf_attributes->CompareItem(guidKey, Value, pbResult); + } + + HRESULT TrackedSample::Compare(IMFAttributes* pTheirs, MF_ATTRIBUTES_MATCH_TYPE MatchType, BOOL* pbResult) { + return mf_attributes->Compare(pTheirs, MatchType, pbResult); + } + + HRESULT TrackedSample::GetUINT32(const GUID& guidKey, UINT32* punValue) { + return mf_attributes->GetUINT32(guidKey, punValue); + } + + HRESULT TrackedSample::GetUINT64(const GUID& guidKey, UINT64* punValue) { + return mf_attributes->GetUINT64(guidKey, punValue); + } + + HRESULT TrackedSample::GetDouble(const GUID& guidKey, double* pfValue) { + return mf_attributes->GetDouble(guidKey, pfValue); + } + + HRESULT TrackedSample::GetGUID(const GUID& guidKey, GUID* pguidValue) { + return mf_attributes->GetGUID(guidKey, pguidValue); + } + + HRESULT TrackedSample::GetStringLength(const GUID& guidKey, UINT32* pcchLength) { + return mf_attributes->GetStringLength(guidKey, pcchLength); + } + + HRESULT TrackedSample::GetString(const GUID& guidKey, LPWSTR pwszValue, UINT32 cchBufSize, UINT32* pcchLength) { + return mf_attributes->GetString(guidKey, pwszValue, cchBufSize, pcchLength); + } + + HRESULT TrackedSample::GetAllocatedString(const GUID& guidKey, LPWSTR* ppwszValue, UINT32* pcchLength) { + return mf_attributes->GetAllocatedString(guidKey, ppwszValue, pcchLength); + } + + HRESULT TrackedSample::GetBlobSize(const GUID& guidKey, UINT32* pcbBlobSize) { + return mf_attributes->GetBlobSize(guidKey, pcbBlobSize); + } + + HRESULT TrackedSample::GetBlob(const GUID& guidKey, UINT8* pBuf, UINT32 cbBufSize, UINT32* pcbBlobSize) { + return mf_attributes->GetBlob(guidKey, pBuf, cbBufSize, pcbBlobSize); + } + + HRESULT TrackedSample::GetAllocatedBlob(const GUID& guidKey, UINT8** ppBuf, UINT32* pcbSize) { + return mf_attributes->GetAllocatedBlob(guidKey, ppBuf, pcbSize); + } + + HRESULT TrackedSample::GetUnknown(const GUID& guidKey, const IID& riid, LPVOID* ppv) { + return mf_attributes->GetUnknown(guidKey, riid, ppv); + } + + HRESULT TrackedSample::SetItem(const GUID& guidKey, const PROPVARIANT& Value) { + return mf_attributes->SetItem(guidKey, Value); + } + + HRESULT TrackedSample::DeleteItem(const GUID& guidKey) { + return mf_attributes->DeleteItem(guidKey); + } + + HRESULT TrackedSample::DeleteAllItems() { + return mf_attributes->DeleteAllItems(); + } + + HRESULT TrackedSample::SetUINT32(const GUID& guidKey, UINT32 unValue) { + return mf_attributes->SetUINT32(guidKey, unValue); + } + + HRESULT TrackedSample::SetUINT64(const GUID& guidKey, UINT64 unValue) { + return mf_attributes->SetUINT64(guidKey, unValue); + } + + HRESULT TrackedSample::SetDouble(const GUID& guidKey, double fValue) { + return mf_attributes->SetDouble(guidKey, fValue); + } + + HRESULT TrackedSample::SetGUID(const GUID& guidKey, const GUID& guidValue) { + return mf_attributes->SetGUID(guidKey, guidValue); + } + + HRESULT TrackedSample::SetString(const GUID& guidKey, LPCWSTR wszValue) { + return mf_attributes->SetString(guidKey, wszValue); + } + + HRESULT TrackedSample::SetBlob(const GUID& guidKey, const UINT8* pBuf, UINT32 cbBufSize) { + return mf_attributes->SetBlob(guidKey, pBuf, cbBufSize); + } + + HRESULT TrackedSample::SetUnknown(const GUID& guidKey, IUnknown* pUnknown) { + return mf_attributes->SetUnknown(guidKey, pUnknown); + } + + HRESULT TrackedSample::LockStore() { + return mf_attributes->LockStore(); + } + + HRESULT TrackedSample::UnlockStore() { + return mf_attributes->UnlockStore(); + } + + HRESULT TrackedSample::GetCount(UINT32* pcItems) { + return mf_attributes->GetCount(pcItems); + } + + HRESULT TrackedSample::GetItemByIndex(UINT32 unIndex, GUID* pguidKey, PROPVARIANT* pValue) { + return mf_attributes->GetItemByIndex(unIndex, pguidKey, pValue); + } + + HRESULT TrackedSample::CopyAllItems(IMFAttributes* pDest) { + return mf_attributes->CopyAllItems(pDest); + } + + HRESULT TrackedSample::GetSampleFlags(DWORD* pdwSampleFlags) { + *pdwSampleFlags = sample_flags; + return S_OK; + } + + HRESULT TrackedSample::SetSampleFlags(DWORD dwSampleFlags) { + sample_flags = dwSampleFlags; + return S_OK; + } + + HRESULT TrackedSample::GetSampleTime(LONGLONG* phnsSampleTime) { + *phnsSampleTime = sample_time; + return S_OK; + } + + HRESULT TrackedSample::SetSampleTime(LONGLONG hnsSampleTime) { + sample_time = hnsSampleTime; + return S_OK; + } + + HRESULT TrackedSample::GetSampleDuration(LONGLONG* phnsSampleDuration) { + *phnsSampleDuration = sample_duration; + return S_OK; + } + + HRESULT TrackedSample::SetSampleDuration(LONGLONG hnsSampleDuration) { + sample_duration = hnsSampleDuration; + return S_OK; + } + + HRESULT TrackedSample::GetBufferCount(DWORD* pdwBufferCount) { + *pdwBufferCount = 1; + return S_OK; + } + + HRESULT TrackedSample::GetBufferByIndex(DWORD dwIndex, IMFMediaBuffer** ppBuffer) { + if (dwIndex != 0) + return E_INVALIDARG; + + *ppBuffer = mf_media_buffer; + mf_media_buffer->AddRef(); + return S_OK; + } + + HRESULT TrackedSample::ConvertToContiguousBuffer(IMFMediaBuffer** ppBuffer) { + return E_NOTIMPL; + } + + HRESULT TrackedSample::AddBuffer(IMFMediaBuffer* pBuffer) { + return E_NOTIMPL; + } + + HRESULT TrackedSample::RemoveBufferByIndex(DWORD dwIndex) { + return E_NOTIMPL; + } + + HRESULT TrackedSample::RemoveAllBuffers() { + return E_NOTIMPL; + } + + HRESULT TrackedSample::GetTotalLength(DWORD* pcbTotalLength) { + return E_NOTIMPL; + } + + HRESULT TrackedSample::CopyToBuffer(IMFMediaBuffer* pBuffer) { + return E_NOTIMPL; + } + + HRESULT TrackedSample::SetAllocator(IMFAsyncCallback* pSampleAllocator, IUnknown* pUnkState) { + if (mf_async_callback) { + mf_async_callback->Release(); + mf_async_callback = 0; + } + + if (pSampleAllocator) { + mf_async_callback = pSampleAllocator; + pSampleAllocator->AddRef(); + } + return S_OK; + } + + HRESULT TrackedSample::GetState(IUnknown** ppunkState) { + return E_NOTIMPL; + } + + HRESULT TrackedSample::GetStatus() { + return E_NOTIMPL; + } + + HRESULT TrackedSample::SetStatus(HRESULT hrStatus) { + return E_NOTIMPL; + } + + HRESULT TrackedSample::GetObject(IUnknown** ppObject) { + return E_NOTIMPL; + } + + IUnknown* TrackedSample::GetStateNoAddRef() { + return 0; + } + + TrackedSample::TrackedSample(HRESULT& hr, IMFMediaBuffer* mf_media_buffer) : ref_count(), mf_attributes(), + mf_media_buffer(), mf_async_callback(), sample_time(), sample_duration(), sample_flags() { + hr = MFCreateAttributes(&mf_attributes, 1); + if (SUCCEEDED(hr)) { + this->mf_media_buffer = mf_media_buffer; + mf_media_buffer->AddRef(); + } + } + + TrackedSample::~TrackedSample() { + if (mf_attributes) { + mf_attributes->Release(); + mf_attributes = 0; + } + + if (mf_media_buffer) { + mf_media_buffer->Release(); + mf_media_buffer = 0; + } + + if (mf_async_callback) { + mf_async_callback->Release(); + mf_async_callback = 0; + } + } + + HRESULT TrackedSample::Create(IMFMediaBuffer* mf_media_buffer, uint32_t video_index, IMFSample*& ptr) { + HRESULT hr = S_OK; + TrackedSample* p = new TrackedSample(hr, mf_media_buffer); + if (!p) + return E_OUTOFMEMORY; + + if (SUCCEEDED(hr)) { + hr = p->SetUINT32(VideoIndexGUID, video_index); + if (SUCCEEDED(hr)) { + ptr = p; + p->AddRef(); + } + } + + p->Release(); + return S_OK; + } + + inline void TrackedSample::Destroy(TrackedSample* ptr) { + if (!ptr) + return; + + delete ptr; + } +} diff --git a/src/ReDIVA/movie_play_lib/tracked_sample.hpp b/src/ReDIVA/movie_play_lib/tracked_sample.hpp new file mode 100644 index 00000000..490ba932 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/tracked_sample.hpp @@ -0,0 +1,93 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#undef UNICODE +#include "movie_play_lib.hpp" +#include + +namespace MoviePlayLib { + class DECLSPEC_UUID("C40A00F2-B93A-4D80-AE8C-5A1C634F58E4") + TrackedSample : public IMFSample, public IMFTrackedSample, public IMFAsyncResult { + protected: + RefCount ref_count; + IMFAttributes* mf_attributes; + IMFMediaBuffer* mf_media_buffer; + IMFAsyncCallback* mf_async_callback; + int64_t sample_time; + int64_t sample_duration; + uint32_t sample_flags; + + public: + virtual HRESULT QueryInterface(const IID& riid, void** ppvObject) override; + virtual ULONG AddRef() override; + virtual ULONG Release() override; + + virtual HRESULT GetItem(const GUID& guidKey, PROPVARIANT* pValue) override; + virtual HRESULT GetItemType(const GUID& guidKey, MF_ATTRIBUTE_TYPE* pType) override; + virtual HRESULT CompareItem(const GUID& guidKey, const PROPVARIANT& Value, BOOL* pbResult) override; + virtual HRESULT Compare(IMFAttributes* pTheirs, MF_ATTRIBUTES_MATCH_TYPE MatchType, BOOL* pbResult) override; + virtual HRESULT GetUINT32(const GUID& guidKey, UINT32* punValue) override; + virtual HRESULT GetUINT64(const GUID& guidKey, UINT64* punValue) override; + virtual HRESULT GetDouble(const GUID& guidKey, double* pfValue) override; + virtual HRESULT GetGUID(const GUID& guidKey, GUID* pguidValue) override; + virtual HRESULT GetStringLength(const GUID& guidKey, UINT32* pcchLength) override; + virtual HRESULT GetString(const GUID& guidKey, LPWSTR pwszValue, UINT32 cchBufSize, UINT32* pcchLength) override; + virtual HRESULT GetAllocatedString(const GUID& guidKey, LPWSTR* ppwszValue, UINT32* pcchLength) override; + virtual HRESULT GetBlobSize(const GUID& guidKey, UINT32* pcbBlobSize) override; + virtual HRESULT GetBlob(const GUID& guidKey, UINT8* pBuf, UINT32 cbBufSize, UINT32* pcbBlobSize) override; + virtual HRESULT GetAllocatedBlob(const GUID& guidKey, UINT8** ppBuf, UINT32* pcbSize) override; + virtual HRESULT GetUnknown(const GUID& guidKey, const IID& riid, LPVOID* ppv) override; + virtual HRESULT SetItem(const GUID& guidKey, const PROPVARIANT& Value) override; + virtual HRESULT DeleteItem(const GUID& guidKey) override; + virtual HRESULT DeleteAllItems() override; + virtual HRESULT SetUINT32(const GUID& guidKey, UINT32 unValue) override; + virtual HRESULT SetUINT64(const GUID& guidKey, UINT64 unValue) override; + virtual HRESULT SetDouble(const GUID& guidKey, double fValue) override; + virtual HRESULT SetGUID(const GUID& guidKey, const GUID& guidValue) override; + virtual HRESULT SetString(const GUID& guidKey, LPCWSTR wszValue) override; + virtual HRESULT SetBlob(const GUID& guidKey, const UINT8* pBuf, UINT32 cbBufSize) override; + virtual HRESULT SetUnknown(const GUID& guidKey, IUnknown* pUnknown) override; + virtual HRESULT LockStore() override; + virtual HRESULT UnlockStore() override; + virtual HRESULT GetCount(UINT32* pcItems) override; + virtual HRESULT GetItemByIndex(UINT32 unIndex, GUID* pguidKey, PROPVARIANT* pValue) override; + virtual HRESULT CopyAllItems(IMFAttributes* pDest) override; + + virtual HRESULT GetSampleFlags(DWORD* pdwSampleFlags) override; + virtual HRESULT SetSampleFlags(DWORD dwSampleFlags) override; + virtual HRESULT GetSampleTime(LONGLONG* phnsSampleTime) override; + virtual HRESULT SetSampleTime(LONGLONG hnsSampleTime) override; + virtual HRESULT GetSampleDuration(LONGLONG* phnsSampleDuration) override; + virtual HRESULT SetSampleDuration(LONGLONG hnsSampleDuration) override; + virtual HRESULT GetBufferCount(DWORD* pdwBufferCount) override; + virtual HRESULT GetBufferByIndex(DWORD dwIndex, IMFMediaBuffer** ppBuffer) override; + virtual HRESULT ConvertToContiguousBuffer(IMFMediaBuffer** ppBuffer) override; + virtual HRESULT AddBuffer(IMFMediaBuffer* pBuffer) override; + virtual HRESULT RemoveBufferByIndex(DWORD dwIndex) override; + virtual HRESULT RemoveAllBuffers() override; + virtual HRESULT GetTotalLength(DWORD* pcbTotalLength) override; + virtual HRESULT CopyToBuffer(IMFMediaBuffer* pBuffer) override; + + virtual HRESULT SetAllocator(IMFAsyncCallback* pSampleAllocator, IUnknown* pUnkState) override; + + virtual HRESULT GetState(IUnknown** ppunkState) override; + virtual HRESULT GetStatus() override; + virtual HRESULT SetStatus(HRESULT hrStatus) override; + virtual HRESULT GetObject(IUnknown** ppObject) override; + virtual IUnknown* GetStateNoAddRef() override; + + TrackedSample(HRESULT& hr, IMFMediaBuffer* mf_media_buffer); + virtual ~TrackedSample(); + + static HRESULT Create(IMFMediaBuffer* mf_media_buffer, uint32_t video_index, IMFSample*& ptr); + static void Destroy(TrackedSample* ptr); + + inline void Destroy() { + Destroy(this); + } + }; +} diff --git a/src/ReDIVA/movie_play_lib/transform_base.cpp b/src/ReDIVA/movie_play_lib/transform_base.cpp new file mode 100644 index 00000000..888025d3 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/transform_base.cpp @@ -0,0 +1,288 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "transform_base.hpp" +#include +#pragma comment(lib, "avrt.lib") +#include + +namespace MoviePlayLib { + HRESULT TransformBase::QueryInterface(const IID& riid, void** ppvObject) { + if (!ppvObject) + return E_INVALIDARG; + + if (riid == __uuidof(IMediaTransform) || riid == __uuidof(IUnknown)) { + *ppvObject = this; + AddRef(); + return S_OK; + } + + *ppvObject = 0; + return E_NOINTERFACE; + } + + ULONG TransformBase::AddRef() { + return ++ref_count; + } + + ULONG TransformBase::Release() { + if (!--ref_count) + Destroy(); + return ref_count; + } + + HRESULT TransformBase::Shutdown() { + lock.Acquire(); + Free(); + lock.Release(); + return S_OK; + } + + HRESULT TransformBase::Close() { + lock.Acquire(); + if (!shutdown) + CommandFlush(); + HRESULT hr = CommandFlush(); + lock.Release(); + return hr; + } + + HRESULT TransformBase::Flush() { + lock.Acquire(); + HRESULT hr = shutdown ? MF_E_SHUTDOWN : S_OK; + lock.Release(); + return hr; + } + + HRESULT TransformBase::Open() { + lock.Acquire(); + HRESULT hr = shutdown ? MF_E_SHUTDOWN : S_OK; + if (!shutdown && !end_streaming) { + end_streaming = TRUE; + hr = Start(); + } + lock.Release(); + return hr; + } + + HRESULT TransformBase::GetMFMediaType(IMFMediaType** mf_media_type) { + lock.Acquire(); + HRESULT hr = shutdown ? MF_E_SHUTDOWN : S_OK; + if (!shutdown) + hr = mf_transform->GetOutputCurrentType(0, mf_media_type); + lock.Release(); + return hr; + } + + void TransformBase::SendMFSample(IMFSample* mf_sample) { + if (mf_sample) + sample_list_wait.AddSample(mf_sample, false); + else + sample_list_wait.SetShutdown(); + + SetEvent(hEvent); + + if (mf_sample) { + int64_t sample_time = 0; + mf_sample->GetSampleTime(&sample_time); + SetSampleTime((double_t)sample_time * 0.0000001); + } + } + + BOOL TransformBase::SignalEvent() { + return SetEvent(hEvent); + } + + BOOL TransformBase::CanShutdown() { + return sample_list_active.CanShutdown(); + } + + UINT32 TransformBase::GetMFSamplesWaitCount() { + return (uint32_t)sample_list_wait.GetSamplesCount(); + } + + UINT32 TransformBase::GetMFSamplesCount() { + return (uint32_t)sample_list_active.GetSamplesCount(); + } + + INT64 TransformBase::GetSampleTime() { + return sample_list_active.GetSampleTime(); + } + + void TransformBase::GetMFSample(IMFSample*& mf_sample) { + mf_sample = sample_list_active.PopSample(); + } + + TransformBase::TransformBase(HRESULT& hr, MediaStatsLock* media_stats_lock, + IMediaClock* media_clock, IMediaSource* media_source) : ref_count(), lock(), + media_stats_lock(media_stats_lock), media_clock(), media_source(), mf_transform(), stream_state(), + shutdown(), end_streaming(), hThread(), hEvent(), sample_list_wait(), sample_list_active() { + if (SUCCEEDED(hr)) { + hEvent = CreateEventA(0, FALSE, FALSE, 0); + if (!hEvent) + hr = HRESULT_FROM_WIN32(GetLastError()); + + if (SUCCEEDED(hr)) { + hThread = (HANDLE)_beginthreadex(0, 0, (_beginthreadex_proc_type)TransformBase::ThreadMain, this, 0, 0); + if (!hThread) + hr = HRESULT_FROM_WIN32(GetLastError()); + + if (SUCCEEDED(hr)) { + this->media_clock = media_clock; + media_clock->AddRef(); + this->media_source = media_source; + media_source->AddRef(); + } + } + } + } + + TransformBase::~TransformBase() { + if (hThread) { + SetEvent(hEvent); + WaitForSingleObject(hThread, INFINITE); + CloseHandle(hThread); + hThread = 0; + } + + if (hEvent) { + CloseHandle(hEvent); + hEvent = 0; + } + } + + void TransformBase::Free() { + CommandFlush(); + + if (mf_transform) { + mf_transform->Release(); + mf_transform = 0; + } + + if (media_source) { + media_source->Release(); + media_source = 0; + } + + if (media_clock) { + media_clock->Release(); + media_clock = 0; + } + + shutdown = TRUE; + end_streaming = FALSE; + } + + HRESULT TransformBase::CommandFlush() { + sample_list_wait.ClearList(); + sample_list_active.ClearList(); + + HRESULT hr; + if (mf_transform) + hr = mf_transform->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, 0); + else + hr = S_OK; + stream_state = 0; + return hr; + } + + HRESULT TransformBase::NotifyEndOfStream() { + HRESULT hr = mf_transform->ProcessMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0); + if (SUCCEEDED(hr)) + hr = mf_transform->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, 0); + return hr; + } + + HRESULT TransformBase::NotifyEndStreaming() { + return mf_transform->ProcessMessage(MFT_MESSAGE_NOTIFY_END_STREAMING, 0); + } + + HRESULT TransformBase::Start() { + HRESULT hr = mf_transform->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0); + if (SUCCEEDED(hr)) + hr = mf_transform->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0); + stream_state = 0; + + sample_list_wait.ResetShutdown(); + sample_list_active.ResetShutdown(); + return hr; + } + + HRESULT TransformBase::ProcessInput() { + while (!stream_state) { + IMFSample* mf_sample = sample_list_wait.PopSample(); + if (mf_sample) { + if (SUCCEEDED(mf_transform->ProcessInput(0, mf_sample, 0))) + stream_state = 1; + } + else { + if (!sample_list_wait.CanShutdown()) + return S_FALSE; + + NotifyEndOfStream(); + stream_state = 2; + } + + if (mf_sample) { + mf_sample->Release(); + mf_sample = 0; + } + } + return S_OK; + } + + BOOL TransformBase::Playback() { + BOOL shutdown = FALSE; + HRESULT hr = S_OK; + do + { + lock.Acquire(); + if (this->shutdown) { + hr = MF_E_SHUTDOWN; + shutdown = TRUE; + } + else if (!end_streaming) + hr = S_FALSE; + else if (stream_state || (hr = ProcessInput()) == S_OK) { + if ((!stream_state || stream_state == 3 + || (hr = ProcessOutput()) == S_OK) && stream_state == 3) { + sample_list_active.SetShutdown(); + hr = S_FALSE; + } + } + lock.Release(); + Sleep(0); + } while (hr == S_OK); + + if (FAILED(hr)) { + media_stats_lock->SetCurrError(hr, this); + sample_list_active.SetShutdown(); + } + return shutdown; + } + + inline void TransformBase::Destroy(TransformBase* ptr) { + if (!ptr) + return; + + delete ptr; + } + + uint32_t __stdcall TransformBase::ThreadMain(TransformBase* transform_base) { + DWORD task_index = 0; + HANDLE avrt_handle = AvSetMmThreadCharacteristicsW(L"Playback", &task_index); + if (!avrt_handle) +#pragma warning(suppress: 6031) + GetLastError(); + + while (!WaitForSingleObject(transform_base->hEvent, INFINITE)) + if (transform_base->Playback()) + break; + + if (avrt_handle) + AvRevertMmThreadCharacteristics(avrt_handle); + return 0; + } +} diff --git a/src/ReDIVA/movie_play_lib/transform_base.hpp b/src/ReDIVA/movie_play_lib/transform_base.hpp new file mode 100644 index 00000000..fb3914cf --- /dev/null +++ b/src/ReDIVA/movie_play_lib/transform_base.hpp @@ -0,0 +1,68 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "movie_play_lib.hpp" +#include "mf_sample_list.hpp" +#include + +namespace MoviePlayLib { + class TransformBase : public IMediaTransform { + protected: + RefCount ref_count; + Lock lock; + MediaStatsLock* media_stats_lock; + IMediaClock* media_clock; + IMediaSource* media_source; + IMFTransform* mf_transform; + int32_t stream_state; + BOOL shutdown; + BOOL end_streaming; + HANDLE hThread; + HANDLE hEvent; + MFSampleList sample_list_wait; + MFSampleList sample_list_active; + + public: + virtual HRESULT QueryInterface(const IID& riid, void** ppvObject) override; + virtual ULONG AddRef() override; + virtual ULONG Release() override; + + virtual HRESULT Shutdown() override; + virtual HRESULT Close() override; + virtual HRESULT Flush() override; + virtual HRESULT Open() override; + virtual HRESULT GetMFMediaType(IMFMediaType** mf_media_type) override; + virtual void SendMFSample(IMFSample* mf_sample) override; + virtual BOOL SignalEvent() override; + virtual BOOL CanShutdown() override; + virtual UINT32 GetMFSamplesWaitCount() override; + virtual UINT32 GetMFSamplesCount() override; + virtual INT64 GetSampleTime() override; + virtual void GetMFSample(IMFSample*& mf_sample) override; + + TransformBase(HRESULT& hr, MediaStatsLock* media_stats_lock, + IMediaClock* media_clock, IMediaSource* media_source); + virtual ~TransformBase(); + + virtual void Free(); + virtual HRESULT CommandFlush(); + virtual HRESULT NotifyEndOfStream(); + virtual HRESULT NotifyEndStreaming(); + virtual HRESULT Start(); + virtual HRESULT ProcessInput(); + virtual HRESULT ProcessOutput() = 0; + virtual void SetSampleTime(double_t value) = 0; + virtual BOOL Playback(); + + static void Destroy(TransformBase* ptr); + static uint32_t __stdcall ThreadMain(TransformBase* transform_base); + + inline void Destroy() { + Destroy(this); + } + }; +} diff --git a/src/ReDIVA/movie_play_lib/video_decoder.cpp b/src/ReDIVA/movie_play_lib/video_decoder.cpp new file mode 100644 index 00000000..0fc508d7 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/video_decoder.cpp @@ -0,0 +1,459 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "video_decoder.hpp" +#include "dx_surface_buffer.hpp" +#include "media_foundation.hpp" +#include "tracked_sample.hpp" +#include + +namespace MoviePlayLib { + VideoDecoder::VideoDecoder(HRESULT& hr, MediaStatsLock* media_stats_lock, IMediaClock* media_clock, + IMediaSource* media_source, IDirect3DDeviceManager9* d3d_device_manager, IDirect3DDevice9* d3d_device) + : TransformBase(hr, media_stats_lock, media_clock, media_source), sub_type(MFVideoFormat_NV12), + d3d_device_manager(), d3d_device(), dx_video_processor(), buffer_size(), width(), height(), + frame_size_width(), frame_size_height(), frame_rate(), frame_rate_numerator(), frame_rate_denominator(), + pixel_aspect_ratio_numerator(), pixel_aspect_ratio_denominator(), video_area(), dxva2_video_desc(), + dxva2_video_process_blt_params(), dxva2_video_sample(), sample_list(), async_callback(this) { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + if (SUCCEEDED(hr)) { + IMFMediaType* media_type = 0; + hr = media_source->GetVideoMFMediaType(&media_type); + if (SUCCEEDED(hr)) + hr = GetMFTransform(media_type, sub_type, mf_transform); + + if (media_type) { + media_type->Release(); + media_type = 0; + } + + if (SUCCEEDED(hr)) { + this->d3d_device_manager = d3d_device_manager; + d3d_device_manager->AddRef(); + this->d3d_device = d3d_device; + d3d_device->AddRef(); + + if (SUCCEEDED(hr)) { + hr = mf_transform->ProcessMessage(MFT_MESSAGE_SET_D3D_MANAGER, (ULONG_PTR)d3d_device_manager); + if (SUCCEEDED(hr)) + hr = InitFromMediaSource(); + } + } + } + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + VideoDecoder::~VideoDecoder() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + HRESULT VideoDecoder::ProcessOutput() { + if (sample_list.GetSamplesCount() < 3) + return TRUE; + + IMFSample* mf_sample = sample_list.PopSample(); + if (!mf_sample) + return TRUE; + + HRESULT hr = MFSampleSetAllocator(mf_sample, &async_callback); + + MFT_OUTPUT_DATA_BUFFER output_sample = {}; + DWORD output_status = 0; + + int64_t process_output_begin; + HRESULT process_output_hr; + int64_t process_output_end; + + int64_t sample_time; + int64_t sample_duration; + DWORD sample_flags; + IDirect3DSurface9* src_surface; + IDirect3DSurface9* dst_surface; + IMFMediaBuffer* mf_media_buffer; + IMFGetService* mf_get_service; + if (FAILED(hr)) + goto End; + + process_output_begin = GetTimestamp(); + process_output_hr = mf_transform->ProcessOutput(0, 1, &output_sample, &output_status); + process_output_end = GetTimestamp(); + + if (FAILED(process_output_hr)) { + switch (process_output_hr) { + case MF_E_TRANSFORM_STREAM_CHANGE: + hr = InitFromMediaSource(); + break; + case MF_E_TRANSFORM_NEED_MORE_INPUT: + hr = S_OK; + if (stream_state == 2) + stream_state = 3; + else + stream_state = 0; + break; + default: + hr = S_FALSE; + stream_state = 0; + break; + } + goto End; + } + + sample_time = 0; + sample_duration = 0; + sample_flags = 0; + hr = output_sample.pSample->GetSampleTime(&sample_time); + if (SUCCEEDED(hr)) { + hr = output_sample.pSample->GetSampleDuration(&sample_duration); + if (SUCCEEDED(hr)) + hr = output_sample.pSample->GetSampleFlags(&sample_flags); + } + + if (sample_duration < 1) + sample_duration = frame_rate; + + if (FAILED(hr)) + goto End; + + hr = mf_sample->SetSampleTime(sample_time); + if (FAILED(hr)) + goto End; + + hr = mf_sample->SetSampleDuration(sample_duration); + if (FAILED(hr)) + goto End; + + hr = mf_sample->SetSampleFlags(sample_flags); + if (FAILED(hr)) + goto End; + + mf_media_buffer = 0; + dst_surface = 0; + hr = mf_sample->GetBufferByIndex(0, &mf_media_buffer); + if (SUCCEEDED(hr)) + hr = mf_media_buffer->QueryInterface(__uuidof(IDirect3DSurface9), (void**)&dst_surface); + + if (mf_media_buffer) { + mf_media_buffer->Release(); + mf_media_buffer = 0; + } + + mf_media_buffer = 0; + mf_get_service = 0; + src_surface = 0; + if (SUCCEEDED(hr)) { + hr = output_sample.pSample->GetBufferByIndex(0, &mf_media_buffer); + if (SUCCEEDED(hr)) { + hr = mf_media_buffer->QueryInterface(__uuidof(IMFGetService), (void**)&mf_get_service); + if (SUCCEEDED(hr)) + hr = mf_get_service->GetService(MR_BUFFER_SERVICE, __uuidof(IDirect3DSurface9), (void**)&src_surface); + } + } + + if (mf_get_service) { + mf_get_service->Release(); + mf_get_service = 0; + } + + if (mf_media_buffer) { + mf_media_buffer->Release(); + mf_media_buffer = 0; + } + + if (SUCCEEDED(hr)) { + dxva2_video_sample.SrcSurface = src_surface; + const int64_t video_process_blt_begin = GetTimestamp(); + hr = dx_video_processor->VideoProcessBlt(dst_surface, &dxva2_video_process_blt_params, &dxva2_video_sample, 1, 0); + const int64_t video_process_blt_end = GetTimestamp(); + if (SUCCEEDED(hr)) { + sample_list_active.AddSample(mf_sample, false); + + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + media_stats_lock->SetVideoProcessOutput( + CalcTimeMsec(process_output_begin, process_output_end), + CalcTimeMsec(video_process_blt_begin, video_process_blt_end), + (double_t)sample_time * 0.0000001); + } + } + + if (src_surface) { + src_surface->Release(); + src_surface = 0; + } + + if (dst_surface) { + dst_surface->Release(); + dst_surface = 0; + } + + End: + if (output_sample.pSample) { + output_sample.pSample->Release(); + output_sample.pSample = 0; + } + + if (output_sample.pEvents) { + output_sample.pEvents->Release(); + output_sample.pEvents = 0; + } + + mf_sample->Release(); + return hr; + } + + void VideoDecoder::SetSampleTime(double_t value) { + media_stats_lock->SetVideoSampleTime(value); + } + + HRESULT VideoDecoder::AsyncCallback(IMFAsyncResult* mf_async_result) { + TrackedSample* tracked_sample = 0; + if (SUCCEEDED(mf_async_result->QueryInterface(__uuidof(TrackedSample), + (void**)&tracked_sample)) && tracked_sample) + sample_list.AddSample(tracked_sample, true); + + if (tracked_sample) { + tracked_sample->Release(); + tracked_sample = 0; + } + return S_OK; + } + + HRESULT VideoDecoder::InitFromMediaSource() { + IMFMediaType* video_mf_media_type = 0; + IMFMediaType* mf_media_type = 0; + + HRESULT hr; + hr = media_source->GetVideoMFMediaType(&video_mf_media_type); + if (SUCCEEDED(hr)) + hr = MoviePlayLib::GetMFMediaType(mf_transform, sub_type, mf_media_type); + + uint32_t buffer_size = 0; + MFT_OUTPUT_STREAM_INFO stream_info = {}; + if (SUCCEEDED(hr)) { + hr = mf_transform->GetOutputStreamInfo(0, &stream_info); + buffer_size = stream_info.cbSize; + } + this->buffer_size = buffer_size; + + if (SUCCEEDED(hr)) { + uint64_t frame_size = 0; + hr = mf_media_type->GetUINT64(MF_MT_FRAME_SIZE, &frame_size); + if (SUCCEEDED(hr)) { + frame_size_height = (uint32_t)frame_size; + frame_size_width = (uint32_t)(frame_size >> 32); + } + } + + width = frame_size_width; + height = frame_size_height; + + if (SUCCEEDED(hr)) { + uint64_t frame_rate = 0; + if (SUCCEEDED(mf_media_type->GetUINT64(MF_MT_FRAME_RATE, &frame_rate))) { + frame_rate_denominator = (uint32_t)frame_rate; + frame_rate_numerator = (uint32_t)(frame_rate >> 32); + } + else { + frame_rate_numerator = 60000; + frame_rate_denominator = 1001; + } + + this->frame_rate = MFllMulDiv(frame_rate_denominator, 10000000, frame_rate_numerator, 0); + + if (SUCCEEDED(mf_media_type->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE, + (UINT8*)&video_area, sizeof(video_area), 0))) { + if (video_area.Area.cx > 0 && video_area.Area.cy > 0) { + frame_size_width = video_area.Area.cx; + frame_size_height = video_area.Area.cy; + } + } + else { + video_area.OffsetX = {}; + video_area.OffsetY = {}; + video_area.Area.cx = frame_size_width; + video_area.Area.cy = frame_size_height; + } + + uint64_t pixel_aspect_ratio = 0; + if (SUCCEEDED(mf_media_type->GetUINT64(MF_MT_PIXEL_ASPECT_RATIO, &pixel_aspect_ratio))) { + pixel_aspect_ratio_denominator = (uint32_t)pixel_aspect_ratio; + pixel_aspect_ratio_numerator = (uint32_t)(pixel_aspect_ratio >> 32); + } + else { + pixel_aspect_ratio_numerator = 1; + pixel_aspect_ratio_denominator = 1; + } + + dxva2_video_desc = {}; + dxva2_video_desc.SampleWidth = frame_size_width; + dxva2_video_desc.SampleHeight = frame_size_height; + + dxva2_video_desc.SampleFormat.SampleFormat = DXVA2_SampleProgressiveFrame; + dxva2_video_desc.SampleFormat.VideoChromaSubsampling + = DXVA2_VideoChromaSubsampling_ProgressiveChroma | DXVA2_VideoChromaSubsampling_Vertically_Cosited; + dxva2_video_desc.SampleFormat.VideoLighting = DXVA2_VideoLighting_dim; + + uint32_t nominal_range; + if (FAILED(mf_media_type->GetUINT32(MF_MT_VIDEO_NOMINAL_RANGE, &nominal_range))) + nominal_range = DXVA2_NominalRange_Normal; + dxva2_video_desc.SampleFormat.NominalRange = nominal_range; + + uint32_t video_primaries; + if (FAILED(mf_media_type->GetUINT32(MF_MT_VIDEO_PRIMARIES, &video_primaries))) + video_primaries = DXVA2_VideoPrimaries_BT709; + dxva2_video_desc.SampleFormat.VideoPrimaries = video_primaries; + + uint32_t video_transfer_matrix; + if (FAILED(mf_media_type->GetUINT32(MF_MT_YUV_MATRIX, &video_transfer_matrix))) + video_transfer_matrix = DXVA2_VideoTransferMatrix_BT709; + dxva2_video_desc.SampleFormat.VideoTransferMatrix = video_transfer_matrix; + + uint32_t video_transfer_function; + if (FAILED(mf_media_type->GetUINT32(MF_MT_TRANSFER_FUNCTION, &video_transfer_function))) + video_transfer_function = DXVA2_VideoTransFunc_709; + dxva2_video_desc.SampleFormat.VideoTransferFunction = video_transfer_function; + + dxva2_video_desc.Format = (D3DFORMAT)sub_type.Data1; + dxva2_video_desc.InputSampleFreq.Numerator = frame_rate_numerator; + dxva2_video_desc.InputSampleFreq.Denominator = frame_rate_denominator; + dxva2_video_desc.OutputFrameFreq.Numerator = frame_rate_numerator; + dxva2_video_desc.OutputFrameFreq.Denominator = frame_rate_denominator; + + if (dx_video_processor) { + dx_video_processor->Release(); + dx_video_processor = 0; + } + + hr = GetDirectXVideoProcessor(d3d_device_manager, &dxva2_video_desc, dx_video_processor); + if (SUCCEEDED(hr)) { + DXVA2_ValueRange proc_amp_brightness = {}; + DXVA2_ValueRange proc_amp_contrast = {}; + DXVA2_ValueRange proc_amp_hue = {}; + DXVA2_ValueRange proc_amp_saturation = {}; + dx_video_processor->GetProcAmpRange(DXVA2_ProcAmp_Brightness, &proc_amp_brightness); + dx_video_processor->GetProcAmpRange(DXVA2_ProcAmp_Contrast, &proc_amp_contrast); + dx_video_processor->GetProcAmpRange(DXVA2_ProcAmp_Hue, &proc_amp_hue); + dx_video_processor->GetProcAmpRange(DXVA2_ProcAmp_Saturation, &proc_amp_saturation); + + dxva2_video_process_blt_params = {}; + dxva2_video_process_blt_params.TargetFrame = 0; + dxva2_video_process_blt_params.TargetRect.left = 0; + dxva2_video_process_blt_params.TargetRect.top = 0; + dxva2_video_process_blt_params.TargetRect.right = frame_size_width; + dxva2_video_process_blt_params.TargetRect.bottom = frame_size_height; + dxva2_video_process_blt_params.BackgroundColor = { 0x8000, 0x8000, 0x1000, 0xFFFF }; + dxva2_video_process_blt_params.DestFormat.SampleFormat = DXVA2_SampleProgressiveFrame; + dxva2_video_process_blt_params.DestFormat.VideoChromaSubsampling = DXVA2_VideoChromaSubsampling_Unknown; + dxva2_video_process_blt_params.DestFormat.NominalRange = DXVA2_NominalRange_Normal; + dxva2_video_process_blt_params.DestFormat.VideoTransferMatrix = DXVA2_VideoTransferMatrix_Unknown; + dxva2_video_process_blt_params.DestFormat.VideoLighting = DXVA2_VideoLighting_dim; + dxva2_video_process_blt_params.DestFormat.VideoPrimaries = DXVA2_VideoPrimaries_Unknown; + dxva2_video_process_blt_params.DestFormat.VideoTransferFunction = DXVA2_VideoTransFunc_Unknown; + dxva2_video_process_blt_params.ProcAmpValues.Brightness = proc_amp_brightness.DefaultValue; + dxva2_video_process_blt_params.ProcAmpValues.Contrast = proc_amp_contrast.DefaultValue; + dxva2_video_process_blt_params.ProcAmpValues.Hue = proc_amp_hue.DefaultValue; + dxva2_video_process_blt_params.ProcAmpValues.Saturation = proc_amp_saturation.DefaultValue; + dxva2_video_process_blt_params.Alpha.ll = 0x10000; + + dxva2_video_sample = {}; + dxva2_video_sample.Start = 0; + dxva2_video_sample.End = 0; + dxva2_video_sample.SampleFormat = dxva2_video_desc.SampleFormat; + dxva2_video_sample.SrcSurface = 0; + dxva2_video_sample.SrcRect.left = video_area.OffsetX.value; + dxva2_video_sample.SrcRect.top = video_area.OffsetY.value; + dxva2_video_sample.SrcRect.right = video_area.OffsetX.value + frame_size_width; + dxva2_video_sample.SrcRect.bottom = video_area.OffsetY.value + frame_size_height; + dxva2_video_sample.DstRect.left = 0; + dxva2_video_sample.DstRect.top = 0; + dxva2_video_sample.DstRect.right = frame_size_width; + dxva2_video_sample.DstRect.bottom = frame_size_height; + dxva2_video_sample.PlanarAlpha.ll = 0x10000; + + uint32_t video_index = sample_list.ClearList(); + for (int32_t i = 0; i < 10; i++) { + IMFSample* mf_sample = 0; + IMFMediaBuffer* mf_media_buffer = 0; + IDirect3DTexture9* d3d_texture = 0; + HANDLE v59 = 0; + if (SUCCEEDED(hr)) { + hr = d3d_device->CreateTexture(frame_size_width, frame_size_height, + 1, 1, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &d3d_texture, &v59); + if (SUCCEEDED(hr)) { + hr = DXSurfaceBuffer::Create(d3d_texture, mf_media_buffer); + if (SUCCEEDED(hr)) { + hr = TrackedSample::Create(mf_media_buffer, video_index, mf_sample); + if (SUCCEEDED(hr)) { + hr = mf_sample->SetUINT64(TextureSharedHandleGUID, (UINT64)v59); + if (SUCCEEDED(hr)) + sample_list.AddSample(mf_sample, false); + } + } + } + + if (d3d_texture) { + d3d_texture->Release(); + d3d_texture = 0; + } + + if (mf_sample) { + mf_sample->Release(); + mf_sample = 0; + } + + if (mf_media_buffer) { + mf_media_buffer->Release(); + mf_media_buffer = 0; + } + } + } + + if (SUCCEEDED(hr)) + media_stats_lock->SetVideoParams(frame_size_width, frame_size_height, frame_size_width, frame_size_height, + (float_t)(int32_t)frame_rate_numerator / (float_t)(int32_t)frame_rate_denominator, + (float_t)(int32_t)pixel_aspect_ratio_numerator / (float_t)(int32_t)pixel_aspect_ratio_denominator); + } + } + + if (mf_media_type) { + mf_media_type->Release(); + mf_media_type = 0; + } + + if (video_mf_media_type) { + video_mf_media_type->Release(); + video_mf_media_type = 0; + } + return hr; + } + + HRESULT VideoDecoder::Create(MediaStatsLock* media_stats_lock, IMediaClock* media_clock, + IMediaSource* media_source, IDirect3DDeviceManager9* d3d_device_manager, + IDirect3DDevice9* d3d_device, IMediaTransform*& ptr) { + HRESULT hr = S_OK; + VideoDecoder* p = new VideoDecoder(hr, media_stats_lock, + media_clock, media_source, d3d_device_manager, d3d_device); + if (!p) + return E_OUTOFMEMORY; + + if (SUCCEEDED(hr)) { + ptr = p; + p->AddRef(); + } + else + p->Shutdown(); + + p->Release(); + return S_OK; + } + + inline void VideoDecoder::Destroy(VideoDecoder* ptr) { + if (!ptr) + return; + + delete ptr; + } +} diff --git a/src/ReDIVA/movie_play_lib/video_decoder.hpp b/src/ReDIVA/movie_play_lib/video_decoder.hpp new file mode 100644 index 00000000..b40a7dae --- /dev/null +++ b/src/ReDIVA/movie_play_lib/video_decoder.hpp @@ -0,0 +1,56 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "movie_play_lib.hpp" +#include "dxva2.hpp" +#include "transform_base.hpp" + +namespace MoviePlayLib { + class VideoDecoder : public TransformBase { + protected: + GUID sub_type; + IDirect3DDeviceManager9* d3d_device_manager; + IDirect3DDevice9* d3d_device; + IDirectXVideoProcessor* dx_video_processor; + uint32_t buffer_size; + uint32_t width; + uint32_t height; + uint32_t frame_size_width; + uint32_t frame_size_height; + int64_t frame_rate; + uint32_t frame_rate_numerator; + uint32_t frame_rate_denominator; + uint32_t pixel_aspect_ratio_numerator; + uint32_t pixel_aspect_ratio_denominator; + MFVideoArea video_area; + DXVA2_VideoDesc dxva2_video_desc; + DXVA2_VideoProcessBltParams dxva2_video_process_blt_params; + DXVA2_VideoSample dxva2_video_sample; + MFSampleList sample_list; + AsyncCallback async_callback; + + public: + VideoDecoder(HRESULT& hr, MediaStatsLock* media_stats_lock, IMediaClock* media_clock, + IMediaSource* media_source, IDirect3DDeviceManager9* d3d_device_manager, IDirect3DDevice9* d3d_device); + virtual ~VideoDecoder() override; + + virtual HRESULT ProcessOutput() override; + virtual void SetSampleTime(double_t value) override; + + virtual HRESULT AsyncCallback(IMFAsyncResult* mf_async_result); + + HRESULT InitFromMediaSource(); + + static HRESULT Create(MediaStatsLock* media_stats_lock, IMediaClock* media_clock, IMediaSource* media_source, + IDirect3DDeviceManager9* d3d_device_manager, IDirect3DDevice9* d3d_device, IMediaTransform*& ptr); + static void Destroy(VideoDecoder* ptr); + + inline void Destroy() { + Destroy(this); + } + }; +} diff --git a/src/ReDIVA/movie_play_lib/video_renderer.cpp b/src/ReDIVA/movie_play_lib/video_renderer.cpp new file mode 100644 index 00000000..05a1703b --- /dev/null +++ b/src/ReDIVA/movie_play_lib/video_renderer.cpp @@ -0,0 +1,127 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "video_renderer.hpp" + +namespace MoviePlayLib { + HRESULT VideoRenderer::QueryInterface(const IID& riid, void** ppvObject) { + if (!ppvObject) + return E_INVALIDARG; + + if (riid == __uuidof(IMediaRenderer) || riid == __uuidof(IUnknown)) { + *ppvObject = this; + AddRef(); + return S_OK; + } + + *ppvObject = 0; + return E_NOINTERFACE; + } + + ULONG VideoRenderer::AddRef() { + return ++ref_count; + } + + ULONG VideoRenderer::Release() { + if (!--ref_count) + Destroy(); + return ref_count; + } + + HRESULT VideoRenderer::Shutdown() { + lock.Acquire(); + if (mf_sample) { + mf_sample->Release(); + mf_sample = 0; + } + shutdown = TRUE; + lock.Release(); + return S_OK; + } + + HRESULT VideoRenderer::Close() { + lock.Acquire(); + HRESULT hr = shutdown ? MF_E_SHUTDOWN : S_OK; + if (!shutdown) + if (mf_sample) { + mf_sample->Release(); + mf_sample = 0; + } + lock.Release(); + return hr; + } + + HRESULT VideoRenderer::Flush() { + lock.Acquire(); + HRESULT hr = shutdown ? MF_E_SHUTDOWN : S_OK; + lock.Release(); + return hr; + } + + HRESULT VideoRenderer::Open() { + lock.Acquire(); + HRESULT hr = shutdown ? MF_E_SHUTDOWN : S_OK; + lock.Release(); + return hr; + } + + HRESULT VideoRenderer::SetMFSample(IMFSample* mf_sample) { + lock.Acquire(); + HRESULT hr = shutdown ? MF_E_SHUTDOWN : 0; + if (!shutdown) { + if (mf_sample) + mf_sample->Release(); + + this->mf_sample = mf_sample; + mf_sample->AddRef(); + + sample_recieve = TRUE; + } + lock.Release(); + return hr; + } + + VideoRenderer::VideoRenderer(IMediaClock* media_clock) : ref_count(), + lock(), shutdown(), sample_recieve(), mf_sample() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + VideoRenderer::~VideoRenderer() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + HRESULT VideoRenderer::GetMFSample(IMFSample** ptr) { + lock.Acquire(); + bool ret = !sample_recieve; + sample_recieve = FALSE; + + *ptr = mf_sample; + + if (mf_sample) + mf_sample->AddRef(); + lock.Release(); + return ret ? S_FALSE : S_OK; + } + + HRESULT VideoRenderer::Create(IMediaClock* media_clock, VideoRenderer*& ptr) { + VideoRenderer* p = new VideoRenderer(media_clock); + if (!p) + return E_OUTOFMEMORY; + + ptr = p; + p->AddRef(); + p->Release(); + return S_OK; + } + + inline void VideoRenderer::Destroy(VideoRenderer* ptr) { + if (!ptr) + return; + + delete ptr; + } +} diff --git a/src/ReDIVA/movie_play_lib/video_renderer.hpp b/src/ReDIVA/movie_play_lib/video_renderer.hpp new file mode 100644 index 00000000..aeeb0c37 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/video_renderer.hpp @@ -0,0 +1,42 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "movie_play_lib.hpp" + +namespace MoviePlayLib { + class VideoRenderer : public IMediaRenderer { + protected: + RefCount ref_count; + Lock lock; + BOOL shutdown; + BOOL sample_recieve; + IMFSample* mf_sample; + + public: + virtual HRESULT QueryInterface(const IID& riid, void** ppvObject) override; + virtual ULONG AddRef() override; + virtual ULONG Release() override; + + virtual HRESULT Shutdown() override; + virtual HRESULT Close() override; + virtual HRESULT Flush() override; + virtual HRESULT Open() override; + virtual HRESULT SetMFSample(IMFSample* mf_sample) override; + + VideoRenderer(IMediaClock* media_clock); + virtual ~VideoRenderer(); + + virtual HRESULT GetMFSample(IMFSample** ptr); + + static HRESULT Create(IMediaClock* media_clock, VideoRenderer*& ptr); + static void Destroy(VideoRenderer* ptr); + + inline void Destroy() { + Destroy(this); + } + }; +} diff --git a/src/ReDIVA/movie_play_lib/wasapi_renderer.cpp b/src/ReDIVA/movie_play_lib/wasapi_renderer.cpp new file mode 100644 index 00000000..5f81e025 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/wasapi_renderer.cpp @@ -0,0 +1,284 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "wasapi_renderer.hpp" + +namespace MoviePlayLib { + HRESULT WASAPIRenderer::QueryInterface(const IID& riid, void** ppvObject) { + if (!ppvObject) + return E_INVALIDARG; + + if (riid == __uuidof(IAudioRenderer) || riid == __uuidof(IUnknown)) { + *ppvObject = this; + AddRef(); + return S_OK; + } + + *ppvObject = 0; + return E_NOINTERFACE; + } + + ULONG WASAPIRenderer::AddRef() { + return ++ref_count; + } + + ULONG WASAPIRenderer::Release() { + if (!--ref_count) + Destroy(); + return ref_count; + } + + HRESULT WASAPIRenderer::Shutdown() { + lock.Acquire(); + Stop(); + + if (streaming_channel) { + streaming_channel->SetResetOne(); + streaming_channel = 0; + } + + shutdown = TRUE; + sample_recieve = FALSE; + lock.Release(); + return 0; + } + + HRESULT WASAPIRenderer::Close() { + lock.Acquire(); + HRESULT hr = shutdown ? MF_E_SHUTDOWN : S_OK; + if (!shutdown) + Stop(); + lock.Release(); + return hr; + } + + HRESULT WASAPIRenderer::Flush() { + lock.Acquire(); + HRESULT hr = shutdown ? MF_E_SHUTDOWN : S_OK; + if (!shutdown && sample_recieve) + sample_recieve = FALSE; + lock.Release(); + return hr; + } + + HRESULT WASAPIRenderer::Open() { + lock.Acquire(); + HRESULT hr = shutdown ? MF_E_SHUTDOWN : S_OK; + if (!shutdown && !sample_recieve) + sample_recieve = TRUE; + lock.Release(); + return hr; + } + + HRESULT WASAPIRenderer::SetMFSample(IMFSample* mf_sample) { + lock.Acquire(); + + HRESULT hr = shutdown ? MF_E_SHUTDOWN : S_OK; + if (!shutdown) { + IMFMediaBuffer* mf_media_buffer = 0; + DWORD current_length = 0; + hr = mf_sample->ConvertToContiguousBuffer(&mf_media_buffer); + if (FAILED(hr)) + goto End; + + hr = mf_media_buffer->GetCurrentLength(¤t_length); + if (FAILED(hr)) + goto End; + + if (current_length / (sizeof(float_t) * channels) && media_buffer_count < 10) { + media_buffers[media_buffer_count] = mf_media_buffer; + media_buffers[media_buffer_count]->AddRef(); + media_buffer_count++; + } + + End: + if (mf_media_buffer) { + mf_media_buffer->Release(); + mf_media_buffer = 0; + } + } + + lock.Release(); + return hr; + } + + HRESULT WASAPIRenderer::GetParams(AudioParams* value) { + lock.Acquire(); + *value = params; + lock.Release(); + return S_OK; + } + + HRESULT WASAPIRenderer::SetParams(const AudioParams* value) { + lock.Acquire(); + params = *value; + lock.Release(); + return S_OK; + } + + WASAPIRenderer::WASAPIRenderer(HRESULT& hr, int32_t sample_rate, int32_t channels) : ref_count(), + lock(), shutdown(), sample_recieve(), params(), channels(channels), sample_rate(sample_rate), + streaming_channel(), media_buffers(), media_buffer_count(), curr_media_buffer(), buffer(), samples_count() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + if (SUCCEEDED(hr)) { + params.spk_l_volume = 1.0f; + params.spk_r_volume = 1.0f; + params.field_8 = 1.0f; + params.field_C = 1.0f; + params.hph_l_volume = 1.0f; + params.hph_r_volume = 1.0f; + memset(media_buffers, 0, sizeof(media_buffers)); + + sound::wasapi::System* system = sound_wasapi_system_data_get(); + if (system) { + if (sample_rate != system->config.sample_rate) + hr = MF_E_INVALIDTYPE; + + sound::wasapi::Mixer* mixer = system->mixer; + if (mixer) { + size_t streaming_channels_count = mixer->streaming_channels_count; + if (streaming_channels_count) { + sound::wasapi::StreamingChannel* streaming_channel + = mixer->GetStreamingChannel(streaming_channels_count - 1); + this->streaming_channel = streaming_channel; + if (streaming_channel) { + streaming_channel->SetMasterVolume(1.0f); + streaming_channel->SetCallback(WASAPIRenderer::FillBufferStatic, this); + } + else + hr = E_FAIL; + } + } + } + } + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + WASAPIRenderer::~WASAPIRenderer() { + MOVIE_PLAY_LIB_PRINT_FUNC_BEGIN; + MOVIE_PLAY_LIB_PRINT_FUNC_END; + } + + void WASAPIRenderer::FillBuffer(sound_buffer_data* buffer, size_t samples_count) { + lock.Acquire(); + + const int8_t* channel_map = 0; + switch (channels) { + case 1: { + static const int8_t channel_map_mono[] = { 0, 0, 0, 0 }; + channel_map = channel_map_mono; + } break; + case 2: { + static const int8_t channel_map_stereo[] = { 0, 1, 0, 1 }; + channel_map = channel_map_stereo; + } break; + case 4: { + static const int8_t channel_map_quad[] = { 0, 1, 2, 3 }; + channel_map = channel_map_quad; + } break; + } + + if (channel_map && !shutdown && sample_recieve) + while (samples_count) { + while (!curr_media_buffer) { + if (!media_buffer_count) + break; + + curr_media_buffer = media_buffers[0]; + for (size_t j = 1; j < media_buffer_count; j++) + media_buffers[j - 1] = media_buffers[j]; + media_buffer_count--; + + BYTE* buffer = 0; + DWORD current_length = 0; + if (SUCCEEDED(curr_media_buffer->Lock(&buffer, 0, ¤t_length))) { + this->buffer = (float_t*)buffer; + this->samples_count = current_length / (4ULL * channels); + } + else { + curr_media_buffer->Release(); + curr_media_buffer = 0; + } + } + + if (!curr_media_buffer) + break; + + size_t _samples_count = min_def(samples_count, this->samples_count); + + const float_t* src_buffer = this->buffer; + for (size_t v20 = _samples_count; v20; v20--, src_buffer += channels, buffer++) { + buffer->spk_l = src_buffer[channel_map[0]] * this->params.spk_l_volume; + buffer->spk_r = src_buffer[channel_map[1]] * this->params.spk_r_volume; + buffer->hph_l = src_buffer[channel_map[2]] * this->params.hph_l_volume; + buffer->hph_r = src_buffer[channel_map[3]] * this->params.hph_r_volume; + } + this->buffer = src_buffer; + + this->samples_count -= _samples_count; + samples_count -= _samples_count; + + if (!this->samples_count) { + curr_media_buffer->Unlock(); + curr_media_buffer->Release(); + curr_media_buffer = 0; + this->buffer = 0; + this->samples_count = 0; + } + } + + while (samples_count--) + *buffer++ = {}; + + lock.Release(); + } + + void WASAPIRenderer::Stop() { + if (curr_media_buffer) { + curr_media_buffer->Unlock(); + curr_media_buffer->Release(); + curr_media_buffer = 0; + + buffer = 0; + samples_count = 0; + } + + for (size_t i = 0; i < media_buffer_count; i++) { + media_buffers[i]->Release(); + media_buffers[i] = 0; + } + media_buffer_count = 0; + } + + HRESULT WASAPIRenderer::Create(IMediaClock* media_clock, + int32_t sample_rate, int32_t channels, WASAPIRenderer*& ptr) { + HRESULT hr = S_OK; + WASAPIRenderer* p = new WASAPIRenderer(hr, sample_rate, channels); + if (!p) + return E_OUTOFMEMORY; + + if (SUCCEEDED(hr)) { + ptr = p; + p->AddRef(); + } + else + p->Shutdown(); + + p->Release(); + return S_OK; + } + + inline void WASAPIRenderer::Destroy(WASAPIRenderer* ptr) { + if (!ptr) + return; + + delete ptr; + } + + void WASAPIRenderer::FillBufferStatic(sound_buffer_data* buffer, size_t samples_count, void* data) { + if (data) + ((WASAPIRenderer*)data)->FillBuffer(buffer, samples_count); + } +} diff --git a/src/ReDIVA/movie_play_lib/wasapi_renderer.hpp b/src/ReDIVA/movie_play_lib/wasapi_renderer.hpp new file mode 100644 index 00000000..c5618027 --- /dev/null +++ b/src/ReDIVA/movie_play_lib/wasapi_renderer.hpp @@ -0,0 +1,57 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "movie_play_lib.hpp" +#include "../../CRE/sound.hpp" + +namespace MoviePlayLib { + class WASAPIRenderer : public IAudioRenderer { + protected: + RefCount ref_count; + Lock lock; + BOOL shutdown; + BOOL sample_recieve; + AudioParams params; + uint32_t channels; + int32_t sample_rate; + sound::wasapi::StreamingChannel* streaming_channel; + IMFMediaBuffer* media_buffers[10]; + size_t media_buffer_count; + IMFMediaBuffer* curr_media_buffer; + const float_t* buffer; + size_t samples_count; + + public: + virtual HRESULT QueryInterface(const IID& riid, void** ppvObject) override; + virtual ULONG AddRef() override; + virtual ULONG Release() override; + + virtual HRESULT Shutdown() override; + virtual HRESULT Close() override; + virtual HRESULT Flush() override; + virtual HRESULT Open() override; + virtual HRESULT SetMFSample(IMFSample* mf_sample) override; + + virtual HRESULT GetParams(AudioParams* value) override; + virtual HRESULT SetParams(const AudioParams* value) override; + + WASAPIRenderer(HRESULT& hr, int32_t sample_rate, int32_t channels); + virtual ~WASAPIRenderer(); + + void FillBuffer(sound_buffer_data* buffer, size_t samples_count); + void Stop(); + + static HRESULT Create(IMediaClock* media_clock, + int32_t sample_rate, int32_t channels, WASAPIRenderer*& ptr); + static void Destroy(WASAPIRenderer* ptr); + static void FillBufferStatic(sound_buffer_data* buffer, size_t samples_count, void* data); + + inline void Destroy() { + Destroy(this); + } + }; +} diff --git a/src/ReDIVA/pv_game/pv_game.cpp b/src/ReDIVA/pv_game/pv_game.cpp index 0fda3c91..7fa6e9c9 100644 --- a/src/ReDIVA/pv_game/pv_game.cpp +++ b/src/ReDIVA/pv_game/pv_game.cpp @@ -21,6 +21,7 @@ #include "../game_state.hpp" #include "../imgui_helper.hpp" #include "../input.hpp" +#include "../task_movie.hpp" #include "player_data.hpp" #include "pv_game_camera.hpp" #include "pv_game_music.hpp" @@ -301,7 +302,7 @@ disp_lyrics_now(), field_2D093(), field_2D094(), field_2D095(), field_2D096(), s field_2D09C(), use_osage_play_data(), pv_end_fadeout(), rival_percentage(), field_2D0A8(), field_2D0AC(), field_2D0B0(), field_2D0BC(), next_stage(), has_frame_texture(), field_2D0BF(), movie_index(), field_2D0C4(), field_2D7E8(), field_2D7EC(), field_2D808(), field_2D80C(), edit_effect(), camera_auth_3d_uid(), -se_index(), task_effect_init(), field_2D87C(), current_field(), field_2D8A0(), field_2D8A4(), +se_index(), task_effect_init(), field_2D87C(), current_field(), enable_movie(), field_2D8A4(), campv_index(), field_2D954(), field_2DAC8(), field_2DACC(), height_adjust(), field_2DB2C(), field_2DB34() { disp_lyrics = true; } @@ -1541,10 +1542,10 @@ int32_t pv_game::ctrl(float_t delta_time, int64_t curr_time) { if (data.play_data.field_64C) { if (!data.field_2D093) data.pv_data.ctrl(delta_time, curr_time, false); - /*else - for (int32_t i = 0; i < 2; i++) + else + for (int32_t i = 0; i < TASK_MOVIE_COUNT; i++) if (app::TaskWork::check_task_ready(task_movie_get(i))) - task_movie_get(i)->sub_14041F4E0();*/ + task_movie_get(i)->Unload(); if (!sub_14013C8C0()->sub_1400E7920()) { hit_state hit_state = HIT_MAX; @@ -2553,27 +2554,28 @@ bool pv_game::load() { case 2: { const pv_db_pv_difficulty* diff = get_pv_db_pv()->get_difficulty( sub_14013C8C0()->difficulty, sub_14013C8C0()->edition); - /*if (diff && diff->movie_list.size()) { + if (diff && diff->movie_list.size()) { resolution_struct* res_wind = res_window_get(); resolution_struct* res_wind_int = res_window_internal_get(); - struc_15 v750; - v750.field_0.field_0.x = 0.0f; - v750.field_0.field_0.y = (float_t)(res_wind->height - res_wind_int->height) * 0.5f; - v750.field_0.field_8.x = (float_t)res_wind_int->width; - v750.field_0.field_8.y = (float_t)res_wind_int->height; - v750.field_0.field_10 = res_wind->resolution_mode; - v750.field_0.field_14 = -1.0f; - v750.field_0.field_1C = diff->movie_surface == PV_MOVIE_SURFACE_FRONT ? 0 : 2; - v750.field_20 = true; + TaskMovie::SprParams spr_params; + spr_params.disp.rect.pos.x = 0.0f; + spr_params.disp.rect.pos.y = (float_t)(res_wind->height - res_wind_int->height) * 0.5f; + spr_params.disp.rect.size.x = (float_t)res_wind_int->width; + spr_params.disp.rect.size.y = (float_t)res_wind_int->height; + spr_params.disp.resolution_mode = res_wind->resolution_mode; + spr_params.disp.scale = -1.0f; + spr_params.disp.index = diff->movie_surface == PV_MOVIE_SURFACE_FRONT ? 0 : 2; + spr_params.prio = spr::SPR_PRIO_01; - for (pv_db_pv_movie& i : diff->movie_list) + for (const pv_db_pv_movie& i : diff->movie_list) if (i.name.size() && aft_data->check_file_exists(i.name.c_str())) { - task_movie_get(v43->index)->sub_14041F260(v750); - task_movie_get(v43->index)->sub_14041F0A0(i.name); - task_movie_get(v43->index)->sub_14041F240(0.0f); + task_movie_get(i.index)->Reset(spr_params); + movie_external_clock_set_true(); + task_movie_get(i.index)->LoadWait(i.name); + task_movie_get(i.index)->SetVolume(0.0f); } - }*/ + } if (sub_14013C8C0()->sub_1400E7910() >= 4) { state = 12; @@ -2673,10 +2675,10 @@ bool pv_game::load() { case 3: { const pv_db_pv_difficulty* diff = get_pv_db_pv()->get_difficulty( sub_14013C8C0()->difficulty, sub_14013C8C0()->edition); - /*for (pv_db_pv_movie& i : diff->movie_list) + for (const pv_db_pv_movie& i : diff->movie_list) if (app::TaskWork::check_task_ready(task_movie_get(i.index)) - && !task_movie_get(i.index)->sub_14041F0E0()) - return false;*/ + && !task_movie_get(i.index)->CheckDisp()) + return false; for (uint32_t& i : data.motion_set_ids) if (motion_storage_check_mot_file_not_ready(i) @@ -3983,7 +3985,7 @@ void pv_game::reset() { data.current_field = 0; data.field_data.clear(); - data.field_2D8A0 = true; + data.enable_movie = true; data.camera_auth_3d_uid = -1; data.campv_string.clear(); @@ -4444,9 +4446,9 @@ bool pv_game::unload() { case 0: { pv_game_music_get()->stop(); - /*for (int32_t i = 0; i < 2; i++) - if (app::TaskWork::check_task_ready(task_movie_get())) - sub_14041F4E0(task_movie_get());*/ + for (int32_t i = 0; i < TASK_MOVIE_COUNT; i++) + if (app::TaskWork::check_task_ready(task_movie_get(i))) + task_movie_get(i)->Unload(); std::string effect_se_file_name = get_effect_se_file_name(); if (effect_se_file_name.size()) @@ -4544,7 +4546,7 @@ bool pv_game::unload() { data.current_field = 0; data.field_data.clear(); - data.field_2D8A0 = true; + data.enable_movie = true; for (auto& i : data.auth_3d) i.second.unload(rctx_ptr); @@ -4625,13 +4627,13 @@ bool pv_game::unload() { data.effect_rs_list.clear(); data.effect_rs_list_hashes.clear(); - /*for (int32_t i = 0; i < 2; i++) { + for (int32_t i = 0; i < TASK_MOVIE_COUNT; i++) { if (!app::TaskWork::check_task_ready(task_movie_get(i))) continue; - task_movie_get(i)->sub_14041F4E0(); - task_movie->DelTask(); - }*/ + task_movie_get(i)->Unload(); + task_movie_get(i)->del(); + } data.title_image_state = 0; @@ -4845,17 +4847,18 @@ void pv_game::sub_140106640() { pv_game_music_get()->load(4, get_play_data_song_file_name(), true, sub_1400FCEB0(), false); } - /*for (int32_t i = 0; i < 2; i++) { + for (int32_t i = 0; i < TASK_MOVIE_COUNT; i++) { if (!app::TaskWork::check_task_ready(task_movie_get(i))) continue; - task_movie_get(i)->sub_14041F4E0(); - std::string name = get_pv_movie_file_name(i); + task_movie_get(i)->Unload(); + + std::string name(get_pv_movie_file_name(i)); if (name.size()) { - task_movie_get(i)->sub_14041F0A0(name); - while (!task_movie_get(i)->sub_14041F0E0(v14)); + task_movie_get(i)->LoadWait(name); + while (!task_movie_get(i)->CheckDisp()); } - }*/ + } sub_140104FB0(); diff --git a/src/ReDIVA/pv_game/pv_game.hpp b/src/ReDIVA/pv_game/pv_game.hpp index be50d0ba..a8b97135 100644 --- a/src/ReDIVA/pv_game/pv_game.hpp +++ b/src/ReDIVA/pv_game/pv_game.hpp @@ -272,7 +272,7 @@ struct pv_game_data { int32_t field_2D87C; size_t current_field; std::vector field_data; - bool field_2D8A0; + bool enable_movie; int32_t field_2D8A4; std::map auth_3d_time; std::map light_time; diff --git a/src/ReDIVA/pv_game/pv_game_pv_data.cpp b/src/ReDIVA/pv_game/pv_game_pv_data.cpp index a18998a2..5570f9b2 100644 --- a/src/ReDIVA/pv_game/pv_game_pv_data.cpp +++ b/src/ReDIVA/pv_game/pv_game_pv_data.cpp @@ -9,6 +9,7 @@ #include "../../CRE/pv_expression.hpp" #include "../../CRE/pv_param.hpp" #include "../information/dw_console.hpp" +#include "../task_movie.hpp" #include "pv_game.hpp" #include "pv_game_camera.hpp" #include "pv_game_music.hpp" @@ -360,12 +361,12 @@ int64_t pv_game_pv_data::ctrl(float_t delta_time, int64_t curr_time, bool a4) { this->curr_time = curr_time + field_2BF38 - field_2BF30; curr_time_float = (float_t)((double_t)this->curr_time * 0.000000001); - /*for (int32_t i = 0; i < 2; i++) + for (int32_t i = 0; i < TASK_MOVIE_COUNT; i++) if (app::TaskWork::check_task_ready(task_movie_get(i))) { if (pv_game_get()->loaded) - sub_14041F1B0(this->curr_time); + movie_current_time_set(this->curr_time); break; - }*/ + } dsc_data* prev_dsc_data_ptr = dsc_data_ptr; targets_remaining = 0; @@ -1707,39 +1708,43 @@ bool pv_game_pv_data::dsc_ctrl(float_t delta_time, int64_t curr_time, } } break; case DSC_FT_MOVIE_PLAY: { - int32_t v408 = data[0]; + int32_t play = data[0]; - /*if (pv_game_get()->loaded && v408 && app::TaskWork::check_task_ready(task_movie_get(0))) { - v410 = task_movie_get(0); - task_movie_get(0)->sub_14041EF10(); - pv_game_get()->data.movie_index = 0; - sub_14041F1D0(curr_time); - }*/ - } break; - case DSC_FT_MOVIE_DISP: { - int32_t v412 = data[0]; if (!pv_game_get()->loaded) break; - /*for (int32_t i = 0; i < 2; i++) { + if (play && app::TaskWork::check_task_ready(task_movie_get(0))) { + task_movie_get(0)->Play(); + pv_game_get()->data.movie_index = 0; + movie_play_time_set_begin(curr_time); + } + } break; + case DSC_FT_MOVIE_DISP: { + int32_t state = data[0]; + if (!pv_game_get()->loaded) + break; + + for (int32_t i = 0; i < TASK_MOVIE_COUNT; i++) { if (!app::TaskWork::check_task_ready(task_movie_get(i))) continue; - int32_t v415 = 0; + TaskMovie::DispState disp_state = TaskMovie::DispState::None; if (pv_game_get()->data.movie_index == i) { - if (pv_game->data.field_2D8A0) - switch (v412) { - case 1: - v415 = 1; - break; - case 2: - v415 = 2; - break; - } + if (!pv_game->data.enable_movie) + state = 0; + + switch (state) { + case 1: + disp_state = TaskMovie::DispState::Disp; + break; + case 2: + disp_state = TaskMovie::DispState::Hide; + break; + } } - task_movie_get(l)->field_88 = v415; + task_movie_get(i)->disp_state = disp_state; break; - }*/ + } } break; case DSC_FT_WIND: { chara_id = data[0]; @@ -1971,32 +1976,35 @@ bool pv_game_pv_data::dsc_ctrl(float_t delta_time, int64_t curr_time, pv_game->set_pv_param_post_process_chara_item_alpha_data(chara_id, alpha, type, duration); } break; case DSC_FT_MOVIE_CUT_CHG: { - /*int32_t v486 = data[0]; - int32_t v487 = data[1]; + int32_t index = data[0]; + int32_t state = data[1]; - if (pv_game_get()->loaded) { - for (int32_t m = 0; m < 2; m++) - if (app::TaskWork::check_task_ready(task_movie_get(m))) - task_movie_get(m)->field_88 = 0; + if (!pv_game_get()->loaded) + break; - if (v486 != -1 && app::TaskWork::check_task_ready(task_movie_get(v486))) { - task_movie_get(v486)->sub_14041EF10((__int64)v492); - pv_game_get()->data.movie_index = v486; - //sub_14041F210(curr_time); - if (!pv_game->data.field_2D8A0) - v487 = 0; - if (v487) - switch (v487) { - case 1: - v488 = 1; - break; - case 2: - v488 = 2; - break; - } - task_movie_get(v486)->field_88 = v488; + for (int32_t m = 0; m < TASK_MOVIE_COUNT; m++) + if (app::TaskWork::check_task_ready(task_movie_get(m))) + task_movie_get(m)->disp_state = TaskMovie::DispState::None; + + if (index != -1 && app::TaskWork::check_task_ready(task_movie_get(index))) { + task_movie_get(index)->Play(); + pv_game_get()->data.movie_index = index; + movie_play_time_set(curr_time); + + TaskMovie::DispState disp_state = TaskMovie::DispState::None; + if (!pv_game->data.enable_movie) + state = 0; + + switch (state) { + case 1: + disp_state = TaskMovie::DispState::Disp; + break; + case 2: + disp_state = TaskMovie::DispState::Hide; + break; } - }*/ + task_movie_get(index)->disp_state = disp_state; + } } break; case DSC_FT_CHARA_LIGHT: { int32_t id = data[0]; diff --git a/src/ReDIVA/task_movie.cpp b/src/ReDIVA/task_movie.cpp new file mode 100644 index 00000000..f2acb5fe --- /dev/null +++ b/src/ReDIVA/task_movie.cpp @@ -0,0 +1,510 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#include "task_movie.hpp" +#include "../CRE/data.hpp" +#include "movie_play_lib/movie_play_lib.hpp" +#include "movie_play_lib/external_clock.hpp" +#include "movie_play_lib/player.hpp" +#include + +const int32_t TASK_MOVIE_COUNT = 2; + +std::atomic_int32_t movie_external_clock; +std::atomic_int64_t movie_current_time; +std::atomic_int64_t movie_play_time; + +TaskMovie task_movie[TASK_MOVIE_COUNT]; + +static int32_t dword_1411A1840 = 0; + +static bool movie_external_clock_get(); +static void movie_reset(); + +static texture* sub_14041E560(); + +inline TaskMovie::DispParams::DispParams() : rect(), resolution_mode(), field_18() { + scale = -1.0f; + index = -1; +} + +TaskMovie::SprParams::SprParams() { + prio = spr::SPR_PRIO_07; + + resolution_struct* res_wind = res_window_get(); + disp.rect.size.x = (float_t)res_wind->width; + disp.rect.size.y = (float_t)res_wind->height; + disp.resolution_mode = res_wind->resolution_mode; +} + +TaskMovie::Player::Player() : pause(), volume(), state(), player(), +external_clock(), video_params(), interop_texture(), duration(), time() { + set_audio_params = true; + player_state = MoviePlayLib::State::Wait; +} + +void TaskMovie::Player::Ctrl() { + player_state = MoviePlayLib::State::Wait; + time = 0.0f; + + if (player && !dword_1411A1840) { + player_state = player->GetState(); + time = player->GetTime(); + } + + switch (player_state) { + case MoviePlayLib::State::None: + case MoviePlayLib::State::Init: + state = TaskMovie::State::Init; + break; + case MoviePlayLib::State::Open: + player->GetVideoParams(&video_params); + + duration = player->GetDuration(); + player->Play(); + + state = TaskMovie::State::Init; + break; + case MoviePlayLib::State::Wait: + state = TaskMovie::State::Wait; + break; + case MoviePlayLib::State::Pause: + if (!pause) + player->Play(); + + state = TaskMovie::State::Disp; + break; + case MoviePlayLib::State::Play: + if (interop_texture && interop_texture->GetTexture() && pause) + player->Pause(); + + state = TaskMovie::State::Disp; + break; + case MoviePlayLib::State::Stop: + if (time <= 0.0f || time >= duration) + state = TaskMovie::State::Stop; + break; + } + + if (state == TaskMovie::State::Disp && player && set_audio_params) { + MoviePlayLib::AudioParams audio_params; + audio_params.spk_l_volume = volume; + audio_params.spk_r_volume = volume; + audio_params.field_8 = 0.0f; + audio_params.field_C = 0.0f; + audio_params.hph_l_volume = volume; + audio_params.hph_r_volume = volume; + if (player->SetAudioParams(&audio_params) >= 0) + set_audio_params = false; + } +} + +void TaskMovie::Player::Destroy(Player* ptr) { + if (!ptr) + return; + + if (ptr->interop_texture) { + ptr->interop_texture->Release(); + ptr->interop_texture = 0; + } + + if (ptr->player) { + ptr->player->Release(); + ptr->player = 0; + } + + if (ptr->external_clock) { + ptr->external_clock->Release(); + ptr->external_clock = 0; + } + + delete ptr; +} + +void TaskMovie::Player::GetInteropTexture(texture*& ptr) { + if (!interop_texture) + return; + + texture* tex = interop_texture->GetTexture(); + if (!tex) + return; + + ptr = tex; + ptr->width = video_params.frame_size_width; + ptr->height = video_params.frame_size_height; +} + +void TaskMovie::Player::UpdateInteropTexture(TaskMovie::PlayerVideoParams* player_video_params) { + if (interop_texture) { + interop_texture->Release(); + interop_texture = 0; + } + + if (player) + player->GetGLDXIntreropTexture(&interop_texture); + + if (!player_video_params) + return; + + if (state == TaskMovie::State::Disp) { + player_video_params->width = video_params.width; + player_video_params->height = video_params.height; + player_video_params->frame_size_width = video_params.frame_size_width; + player_video_params->frame_size_height = video_params.frame_size_height; + } + else { + player_video_params->width = 0; + player_video_params->height = 0; + player_video_params->frame_size_width = 0; + player_video_params->frame_size_height = 0; + } +} + +TaskMovie::TaskMovie() : player(), player_video_params(), tex(), pause(), wait_play() { + disp_state = TaskMovie::DispState::Disp; + state = TaskMovie::State::Wait; +} + +TaskMovie::~TaskMovie() { + +} + +bool TaskMovie::init() { + return true; +} + +bool TaskMovie::ctrl() { + state = TaskMovie::State::Wait; + if (player) { + player->Ctrl(); + state = player->state; + } + + if (CheckState()) { + if (player->interop_texture) { + player->interop_texture->Release(); + player->interop_texture = 0; + } + + player->UpdateInteropTexture(player_video_params); + + if (player_video_params->frame_size_width && player_video_params->frame_size_height) { + if (!tex) + tex = sub_14041E560(); + + if (disp_state != TaskMovie::DispState::None) + player->GetInteropTexture(tex); + } + } + return false; +} + +bool TaskMovie::dest() { + Unload(); + + if (player_video_params) { + delete player_video_params; + player_video_params = 0; + } + + if (tex) + tex = 0; + + if (player) { + player->Destroy(); + player = 0; + } + + movie_reset(); + return true; +} + +void TaskMovie::disp() { + if (state != TaskMovie::State::Disp || disp_state != TaskMovie::DispState::Disp || !tex) + return; + + float_t width = (float_t)player_video_params->width; + if (player_video_params->width < 0) + width += (float_t)UINT64_MAX; + if (fabsf(width) <= 0.000001f) + return; + + float_t height = (float_t)player_video_params->height; + if (player_video_params->height < 0) + height += (float_t)UINT64_MAX; + if (fabsf(height) <= 0.000001f) + return; + + if (!tex->width || !tex->height) + return; + + float_t scale = spr_params.disp.scale; + + vec2 sprite_size; + sprite_size.x = width; + sprite_size.y = height; + + float_t tex_x = 0.0f; + float_t tex_y = (float_t)tex->height - height; + float_t tex_width; + float_t tex_height; + + if (scale <= 0.0f) { + scale = max_def(spr_params.disp.rect.size.x / width, spr_params.disp.rect.size.y / height); + + tex_width = (1.0f / scale) * spr_params.disp.rect.size.x; + tex_height = (1.0f / scale) * spr_params.disp.rect.size.y; + + sprite_size.x = tex_width; + sprite_size.y = tex_height; + + tex_x = width * 0.5f - tex_width * 0.5f; + tex_y = (float_t)tex->height - height * 0.5f - tex_height * 0.5f; + } + else { + tex_width = width; + tex_height = height; + } + + data_struct* aft_data = &data_list[DATA_AFT]; + sprite_database* aft_spr_db = &aft_data->data_ft.spr_db; + + spr::SprArgs args; + args.Reset(); + args.texture = tex; + args.SetSpriteSize(sprite_size); + args.SetTexturePosSize(tex_x, tex_y, tex_width, tex_height); + args.attr = (spr::SprAttr)(spr::SPR_ATTR_CTR_CC | spr::SPR_ATTR_FLIP_V); + args.index = spr_params.disp.index; + args.prio = spr_params.prio; + args.resolution_mode_screen = spr_params.disp.resolution_mode; + args.resolution_mode_sprite = spr_params.disp.resolution_mode; + args.trans.x = spr_params.disp.rect.pos.x + spr_params.disp.rect.size.x * 0.5f; + args.trans.y = spr_params.disp.rect.pos.y + spr_params.disp.rect.size.y * 0.5f; + args.trans.z = 0.0f; + args.scale.x = scale; + args.scale.y = scale; + args.scale.z = 1.0f; + spr::put_sprite(args, aft_spr_db); +} + +bool TaskMovie::CheckDisp() { + if (!player || !wait_play) + return false; + + player->Ctrl(); + + return player->state == TaskMovie::State::Disp + || player->state == TaskMovie::State::Max; +} + +bool TaskMovie::CheckState() { + return state == TaskMovie::State::Init || state == TaskMovie::State::Disp; +} + +void TaskMovie::CtrlPlayer() { + if (!player) + return; + + if (pause || wait_play) { + player->pause = true; + player->Ctrl(); + } + else { + player->pause = false; + player->Ctrl(); + } +} + +texture* TaskMovie::GetTexture() { + if (app::TaskWork::check_task_ready(this) && CheckState()) + return tex; + return 0; +} + +void TaskMovie::Load(const std::string& path) { + Shutdown(); + + wait_play = false; + + CtrlPlayer(); + Start(path); +} + +void TaskMovie::LoadWait(const std::string& path) { + Shutdown(); + + wait_play = true; + + CtrlPlayer(); + Start(path); +} + +bool TaskMovie::Play() { + if (!player || !wait_play) + return false; + + wait_play = false; + + CtrlPlayer(); + return true; +} + +bool TaskMovie::Reset(const TaskMovie::SprParams& spr_params) { + this->spr_params = spr_params; + + bool ret; + if (app::TaskWork::check_task_ready(this)) { + if (player) { + player->Destroy(); + player = 0; + } + + player = new TaskMovie::Player; + + ret = true; + } + else { + player = new TaskMovie::Player; + player_video_params = new TaskMovie::PlayerVideoParams; + + ret = app::TaskWork::add_task(this, "TaskMovie"); + } + + SetVolume(1.0f); + + movie_reset(); + return ret; +} + +void TaskMovie::SetPause(bool value) { + pause = value; + + CtrlPlayer(); +} + +bool TaskMovie::SetVolume(float_t value) { + if (!player) + return false; + + player->volume = value; + player->set_audio_params = true; + return true; +} + +bool TaskMovie::Shutdown() { + TaskMovie::Player* player = this->player; + + bool res = false; + if (player) { + if (player->interop_texture) { + player->interop_texture->Release(); + player->interop_texture = 0; + } + + tex = 0; + + if (player->interop_texture) { + player->interop_texture->Release(); + player->interop_texture = 0; + } + + if (player->player) { + player->player->Release(); + player->player = 0; + } + + if (player->external_clock) { + player->external_clock->Release(); + player->external_clock = 0; + } + + res = true; + } + + path.clear(); + return res; +} + +void TaskMovie::Start(const std::string& path) { + SetPause(false); + + if (!player) + return; + + data_struct* aft_data = &data_list[DATA_AFT]; + + std::string _path(path); + aft_data->get_file_path(_path); + this->path.assign(_path); + + player->external_clock = 0; + if (movie_external_clock_get()) + MoviePlayLib::ExternalClock::Create(TaskMovie::GetElapsedTime, player->external_clock); + + MoviePlayLib::Player::Create(player->player); + + if (player->player) { + player->player->SetMediaClock(player->external_clock); + player->player->Open(_path.c_str()); + } +} + +bool TaskMovie::Unload() { + bool res = TaskMovie::Shutdown(); + disp_state = TaskMovie::DispState::Disp; + return res; +} + +int64_t TaskMovie::GetElapsedTime() { + if (!movie_external_clock_get()) + return 0; + + return max_def(movie_current_time.load() - movie_play_time.load(), 0); +} + +TaskMovie* task_movie_get(int32_t index) { + if (index >= 0 && index < TASK_MOVIE_COUNT) + return &task_movie[index]; + return &task_movie[0]; +} + +texture* task_movie_get_texture(int32_t index) { + TaskMovie* task_movie = task_movie_get(index); + return task_movie ? task_movie->tex : 0; +} + +void movie_current_time_set(int64_t value) { + if (movie_external_clock_get()) + movie_current_time = value; +} + +void movie_external_clock_set_true() { + movie_external_clock = 1; +} + +void movie_play_time_set(int64_t value) { + if (movie_external_clock_get()) + movie_play_time = value; +} + +void movie_play_time_set_begin(int64_t value) { + if (movie_external_clock_get() && movie_play_time < 0) + movie_play_time = value; +} + +static bool movie_external_clock_get() { + return !!movie_external_clock; +} + +static void movie_reset() { + movie_external_clock = 0; + movie_current_time = 0; + movie_play_time = -1; +} + +static texture* sub_14041E560() { + return 0; +} diff --git a/src/ReDIVA/task_movie.hpp b/src/ReDIVA/task_movie.hpp new file mode 100644 index 00000000..18da505a --- /dev/null +++ b/src/ReDIVA/task_movie.hpp @@ -0,0 +1,127 @@ +/* + by korenkonder + GitHub/GitLab: korenkonder +*/ + +#pragma once + +#include "../KKdLib/default.hpp" +#include "../CRE/sprite.hpp" +#include "../CRE/task.hpp" +#include "../CRE/texture.hpp" +#include "movie_play_lib/movie_play_lib.hpp" + +class TaskMovie : public app::Task { +public: + enum class State { + Wait = 0, + Init, + Disp, + Stop, + Max, + }; + + enum class DispState { + None = 0, + Disp, + Hide, + Max, + }; + + struct PlayerVideoParams { + int64_t width; + int64_t height; + int64_t frame_size_width; + int64_t frame_size_height; + + inline PlayerVideoParams() : width(), height(), frame_size_width(), frame_size_height() { + + } + }; + + struct DispParams { + rectangle rect; + resolution_mode resolution_mode; + float_t scale; + uint32_t field_18; + int32_t index; + + DispParams(); + }; + + struct SprParams { + DispParams disp; + spr::SprPrio prio; + + SprParams(); + }; + + struct Player { + bool pause; + float_t volume; + bool set_audio_params; + State state; + MoviePlayLib::IPlayer* player; + MoviePlayLib::IMediaClock* external_clock; + MoviePlayLib::State player_state; + MoviePlayLib::VideoParams video_params; + MoviePlayLib::IGLDXInteropTexture* interop_texture; + double_t duration; + double_t time; + + Player(); + + void Ctrl(); + void GetInteropTexture(texture*& ptr); + void UpdateInteropTexture(TaskMovie::PlayerVideoParams* player_video_params); + + static void Destroy(Player* ptr); + + inline void Destroy() { + Destroy(this); + } + }; + + std::string path; + DispState disp_state; + SprParams spr_params; + State state; + Player* player; + PlayerVideoParams* player_video_params; + texture* tex; + bool pause; + bool wait_play; + + TaskMovie(); + virtual ~TaskMovie() override; + + virtual bool init() override; + virtual bool ctrl() override; + virtual bool dest() override; + virtual void disp() override; + + bool CheckDisp(); + bool CheckState(); + void CtrlPlayer(); + texture* GetTexture(); + void Load(const std::string& path); + void LoadWait(const std::string& path); + bool Play(); + bool Reset(const TaskMovie::SprParams& spr_params); + void SetPause(bool value); + bool SetVolume(float_t value); + bool Shutdown(); + void Start(const std::string& path); + bool Unload(); + + static int64_t GetElapsedTime(); +}; + +extern const int32_t TASK_MOVIE_COUNT; + +extern TaskMovie* task_movie_get(int32_t index = 0); + +extern void movie_current_time_set(int64_t value); +extern void movie_external_clock_set_true(); +extern void movie_play_time_set(int64_t value); +extern void movie_play_time_set_begin(int64_t value);