Skip to content

Commit bb607cb

Browse files
starryalleyfire
authored andcommitted
Add WMF video playing (audio control isn't implemented)
Thanks to the DisplaySweet team https://github.com/DisplaySweet. Implement WMF video resource loading and playback functionality Audio does not work yet. Co-Authored-by: Mark Kuo <starryalley@gmail.com>
1 parent e0603ae commit bb607cb

13 files changed

+1901
-0
lines changed

modules/wmf/SCsub

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/usr/bin/env python
2+
from misc.utility.scons_hints import *
3+
4+
Import("env")
5+
6+
if env.msvc:
7+
env.Append(
8+
LINKFLAGS=["mfplat.lib", "mf.lib", "wmcodecdspuuid.lib", "mfuuid.lib", "mfreadwrite.lib", "strmiids.lib"]
9+
)
10+
else:
11+
env.Append(LIBS=["mfplat", "mf", "wmcodecdspuuid", "mfuuid", "mfreadwrite", "strmiids"])
12+
13+
Import("env_modules")
14+
15+
env_wmf = env_modules.Clone()
16+
17+
env_wmf.add_source_files(env.modules_sources, "*.cpp")
18+
Export("env")
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
/**************************************************************************/
2+
/* audio_sample_grabber_callback.cpp */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#include "audio_sample_grabber_callback.h"
32+
#include "core/string/print_string.h"
33+
#include "video_stream_wmf.h"
34+
#include <mfapi.h>
35+
#include <minwindef.h>
36+
#include <shlwapi.h>
37+
#include <cassert>
38+
#include <cstdio>
39+
#include <new>
40+
41+
AudioSampleGrabberCallback::AudioSampleGrabberCallback(VideoStreamPlaybackWMF *p_playback, Mutex &p_mtx) :
42+
m_cRef(1), playback(p_playback) {
43+
// Get Godot's current mix rate from AudioServer
44+
AudioServer *audio_server = AudioServer::get_singleton();
45+
if (audio_server) {
46+
output_sample_rate = audio_server->get_mix_rate();
47+
print_line("AudioSampleGrabberCallback: Using Godot mix rate: " + itos(output_sample_rate));
48+
} else {
49+
output_sample_rate = 44100; // Fallback
50+
print_line("AudioSampleGrabberCallback: AudioServer not available, using fallback: " + itos(output_sample_rate));
51+
}
52+
}
53+
54+
HRESULT AudioSampleGrabberCallback::CreateInstance(AudioSampleGrabberCallback **ppCB, VideoStreamPlaybackWMF *playback, Mutex &mtx) {
55+
*ppCB = new (std::nothrow) AudioSampleGrabberCallback(playback, mtx);
56+
57+
if (ppCB == nullptr) {
58+
return E_OUTOFMEMORY;
59+
}
60+
return S_OK;
61+
}
62+
63+
AudioSampleGrabberCallback::~AudioSampleGrabberCallback() {
64+
}
65+
66+
STDMETHODIMP AudioSampleGrabberCallback::QueryInterface(REFIID riid, void **ppv) {
67+
static const QITAB qit[] = {
68+
#ifdef _MSC_VER
69+
#pragma warning(push)
70+
#pragma warning(disable : 4838)
71+
#endif
72+
QITABENT(AudioSampleGrabberCallback, IMFSampleGrabberSinkCallback),
73+
#ifdef _MSC_VER
74+
#pragma warning(pop)
75+
#endif
76+
{ 0, 0 }
77+
};
78+
return QISearch(this, qit, riid, ppv);
79+
}
80+
81+
STDMETHODIMP_(ULONG)
82+
AudioSampleGrabberCallback::AddRef() {
83+
return InterlockedIncrement(&m_cRef);
84+
}
85+
86+
STDMETHODIMP_(ULONG)
87+
AudioSampleGrabberCallback::Release() {
88+
ULONG cRef = InterlockedDecrement(&m_cRef);
89+
if (cRef == 0) {
90+
delete this;
91+
}
92+
return cRef;
93+
}
94+
95+
// IMFClockStateSink methods
96+
STDMETHODIMP AudioSampleGrabberCallback::OnClockStart(MFTIME hnsSystemTime, LONGLONG llClockStartOffset) {
97+
return S_OK;
98+
}
99+
100+
STDMETHODIMP AudioSampleGrabberCallback::OnClockStop(MFTIME hnsSystemTime) {
101+
return S_OK;
102+
}
103+
104+
STDMETHODIMP AudioSampleGrabberCallback::OnClockPause(MFTIME hnsSystemTime) {
105+
return S_OK;
106+
}
107+
108+
STDMETHODIMP AudioSampleGrabberCallback::OnClockRestart(MFTIME hnsSystemTime) {
109+
return S_OK;
110+
}
111+
112+
STDMETHODIMP AudioSampleGrabberCallback::OnClockSetRate(MFTIME hnsSystemTime, float flRate) {
113+
return S_OK;
114+
}
115+
116+
// IMFSampleGrabberSinkCallback methods
117+
STDMETHODIMP AudioSampleGrabberCallback::OnSetPresentationClock(IMFPresentationClock *pClock) {
118+
return S_OK;
119+
}
120+
121+
STDMETHODIMP AudioSampleGrabberCallback::OnProcessSample(REFGUID guidMajorMediaType,
122+
DWORD dwSampleFlags,
123+
LONGLONG llSampleTime,
124+
LONGLONG llSampleDuration,
125+
const BYTE *pSampleBuffer,
126+
DWORD dwSampleSize) {
127+
if (input_sample_rate == 0 || input_channels == 0) {
128+
// Audio format not set yet
129+
return S_OK;
130+
}
131+
132+
// Convert input audio to float
133+
Vector<float> input_float;
134+
convert_to_float(pSampleBuffer, dwSampleSize, input_float);
135+
136+
// Resample if needed
137+
Vector<float> output_float;
138+
if (input_sample_rate != output_sample_rate) {
139+
resample_audio(input_float.ptr(), input_float.size() / input_channels, output_float);
140+
} else {
141+
output_float = input_float;
142+
}
143+
144+
// Store audio data for Godot
145+
if (playback) {
146+
playback->add_audio_data(llSampleTime, output_float);
147+
}
148+
149+
return S_OK;
150+
}
151+
152+
STDMETHODIMP AudioSampleGrabberCallback::OnShutdown() {
153+
print_line("AudioSampleGrabberCallback::OnShutdown");
154+
return S_OK;
155+
}
156+
157+
void AudioSampleGrabberCallback::set_audio_format(int sample_rate, int channels) {
158+
input_sample_rate = sample_rate;
159+
input_channels = channels;
160+
161+
if (output_sample_rate > 0) {
162+
resample_ratio = (double)output_sample_rate / (double)input_sample_rate;
163+
}
164+
165+
print_line("Audio format set: " + itos(input_sample_rate) + "Hz, " + itos(input_channels) + " channels");
166+
print_line("Resample ratio: " + rtos(resample_ratio));
167+
}
168+
169+
void AudioSampleGrabberCallback::set_output_format(int sample_rate, int channels) {
170+
output_sample_rate = sample_rate;
171+
output_channels = channels;
172+
173+
if (input_sample_rate > 0) {
174+
resample_ratio = (double)output_sample_rate / (double)input_sample_rate;
175+
}
176+
}
177+
178+
void AudioSampleGrabberCallback::convert_to_float(const BYTE *input, DWORD input_size, Vector<float> &output) {
179+
// Assume 16-bit PCM input (most common)
180+
int sample_count = input_size / 2; // 2 bytes per 16-bit sample
181+
output.resize(sample_count);
182+
183+
const int16_t *input_samples = reinterpret_cast<const int16_t *>(input);
184+
float *output_samples = output.ptrw();
185+
186+
for (int i = 0; i < sample_count; i++) {
187+
// Convert 16-bit PCM to float (-1.0 to 1.0)
188+
output_samples[i] = (float)input_samples[i] / 32768.0f;
189+
}
190+
}
191+
192+
void AudioSampleGrabberCallback::resample_audio(const float *input, int input_samples, Vector<float> &output) {
193+
if (resample_ratio == 1.0) {
194+
// No resampling needed
195+
int total_samples = input_samples * input_channels;
196+
output.resize(total_samples);
197+
memcpy(output.ptrw(), input, total_samples * sizeof(float));
198+
return;
199+
}
200+
201+
// Simple linear interpolation resampling
202+
int output_samples = (int)(input_samples * resample_ratio);
203+
output.resize(output_samples * output_channels);
204+
205+
float *output_ptr = output.ptrw();
206+
207+
for (int i = 0; i < output_samples; i++) {
208+
double src_index = (double)i / resample_ratio;
209+
int src_index_int = (int)src_index;
210+
double frac = src_index - src_index_int;
211+
212+
for (int ch = 0; ch < input_channels && ch < output_channels; ch++) {
213+
float sample1 = 0.0f;
214+
float sample2 = 0.0f;
215+
216+
if (src_index_int < input_samples) {
217+
sample1 = input[src_index_int * input_channels + ch];
218+
}
219+
if (src_index_int + 1 < input_samples) {
220+
sample2 = input[(src_index_int + 1) * input_channels + ch];
221+
}
222+
223+
// Linear interpolation
224+
float interpolated = sample1 + (sample2 - sample1) * frac;
225+
output_ptr[i * output_channels + ch] = interpolated;
226+
}
227+
228+
// Fill remaining output channels with zeros if input has fewer channels
229+
for (int ch = input_channels; ch < output_channels; ch++) {
230+
output_ptr[i * output_channels + ch] = 0.0f;
231+
}
232+
}
233+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**************************************************************************/
2+
/* audio_sample_grabber_callback.h */
3+
/**************************************************************************/
4+
/* This file is part of: */
5+
/* GODOT ENGINE */
6+
/* https://godotengine.org */
7+
/**************************************************************************/
8+
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9+
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10+
/* */
11+
/* Permission is hereby granted, free of charge, to any person obtaining */
12+
/* a copy of this software and associated documentation files (the */
13+
/* "Software"), to deal in the Software without restriction, including */
14+
/* without limitation the rights to use, copy, modify, merge, publish, */
15+
/* distribute, sublicense, and/or sell copies of the Software, and to */
16+
/* permit persons to whom the Software is furnished to do so, subject to */
17+
/* the following conditions: */
18+
/* */
19+
/* The above copyright notice and this permission notice shall be */
20+
/* included in all copies or substantial portions of the Software. */
21+
/* */
22+
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23+
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24+
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25+
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26+
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27+
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28+
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29+
/**************************************************************************/
30+
31+
#pragma once
32+
33+
#include "core/os/mutex.h"
34+
#include "core/templates/vector.h"
35+
#include "servers/audio_server.h"
36+
#include <mfapi.h>
37+
#include <mfidl.h>
38+
39+
class VideoStreamPlaybackWMF;
40+
41+
#ifdef __GNUC__
42+
#pragma GCC diagnostic push
43+
#pragma GCC diagnostic ignored "-Wnon-virtual-dtor"
44+
#endif
45+
46+
class AudioSampleGrabberCallback : public IMFSampleGrabberSinkCallback {
47+
private:
48+
long m_cRef;
49+
VideoStreamPlaybackWMF *playback;
50+
51+
int input_sample_rate = 0;
52+
int input_channels = 0;
53+
int output_sample_rate = 0; // Will be set from AudioServer
54+
int output_channels = 2; // Stereo
55+
56+
Vector<float> resample_buffer;
57+
double resample_ratio = 1.0;
58+
59+
public:
60+
AudioSampleGrabberCallback(VideoStreamPlaybackWMF *playback, Mutex &mtx);
61+
virtual ~AudioSampleGrabberCallback();
62+
63+
static HRESULT CreateInstance(AudioSampleGrabberCallback **ppCB, VideoStreamPlaybackWMF *playback, Mutex &mtx);
64+
65+
// IUnknown methods
66+
STDMETHODIMP QueryInterface(REFIID riid, void **ppv) override;
67+
STDMETHODIMP_(ULONG)
68+
AddRef() override;
69+
STDMETHODIMP_(ULONG)
70+
Release() override;
71+
72+
// IMFClockStateSink methods
73+
STDMETHODIMP OnClockStart(MFTIME hnsSystemTime, LONGLONG llClockStartOffset) override;
74+
STDMETHODIMP OnClockStop(MFTIME hnsSystemTime) override;
75+
STDMETHODIMP OnClockPause(MFTIME hnsSystemTime) override;
76+
STDMETHODIMP OnClockRestart(MFTIME hnsSystemTime) override;
77+
STDMETHODIMP OnClockSetRate(MFTIME hnsSystemTime, float flRate) override;
78+
79+
// IMFSampleGrabberSinkCallback methods
80+
STDMETHODIMP OnSetPresentationClock(IMFPresentationClock *pClock) override;
81+
STDMETHODIMP OnProcessSample(REFGUID guidMajorMediaType, DWORD dwSampleFlags,
82+
LONGLONG llSampleTime, LONGLONG llSampleDuration, const BYTE *pSampleBuffer,
83+
DWORD dwSampleSize) override;
84+
STDMETHODIMP OnShutdown() override;
85+
86+
void set_audio_format(int sample_rate, int channels);
87+
void set_output_format(int sample_rate, int channels);
88+
89+
private:
90+
void resample_audio(const float *input, int input_samples, Vector<float> &output);
91+
void convert_to_float(const BYTE *input, DWORD input_size, Vector<float> &output);
92+
};
93+
94+
#ifdef __GNUC__
95+
#pragma GCC diagnostic pop
96+
#endif

modules/wmf/config.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
def can_build(env, platform):
2+
return platform == "windows"
3+
4+
5+
def configure(env):
6+
pass
7+
8+
9+
def get_doc_classes():
10+
return [
11+
"WindowsMediaFoundation",
12+
]
13+
14+
15+
def get_doc_path():
16+
return "doc_classes"

0 commit comments

Comments
 (0)