|
| 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 | +} |
0 commit comments