Skip to content

Commit ae26c82

Browse files
win-mf: Add Media Foundation AAC encoder for WoA
This commit introduces AAC encoder support using the Media Foundation Transform (MFT) interface, specifically targeting Windows on ARM (WoA) devices with Qualcomm hardware. It adds mf-aac.cpp and mf-aac-encoder.cpp/.hpp to implement the encoding logic.
1 parent 04d6822 commit ae26c82

File tree

3 files changed

+607
-0
lines changed

3 files changed

+607
-0
lines changed

plugins/win-mf/mf-aac-encoder.cpp

Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
#include <obs-module.h>
2+
3+
#include "mf-aac-encoder.hpp"
4+
5+
#include <mferror.h>
6+
#include <mftransform.h>
7+
#include <wmcodecdsp.h>
8+
#include <comdef.h>
9+
10+
#include <array>
11+
12+
namespace {
13+
void LogAAC(const obs_encoder_t *encoder, int level, const char *format, ...)
14+
{
15+
va_list args;
16+
va_start(args, format);
17+
18+
char formattedMessage[1024];
19+
vsnprintf(formattedMessage, sizeof(formattedMessage), format, args);
20+
va_end(args);
21+
22+
blog(level, "[Media Foundation AAC: '%s']: %s", obs_encoder_get_name(encoder), formattedMessage);
23+
}
24+
25+
void LogCOMError(const obs_encoder_t *encoder, const char *operation, HRESULT hr)
26+
{
27+
_com_error err(hr);
28+
LogAAC(encoder, LOG_ERROR, "%s failed, %S (0x%08lx)", operation, err.ErrorMessage(), hr);
29+
}
30+
} //namespace
31+
32+
#define CHECK_HR_ERROR(r) \
33+
if (FAILED(hr = (r))) { \
34+
LogCOMError(ObsEncoder(),#r, hr); \
35+
goto fail; \
36+
}
37+
38+
template<std::size_t N> constexpr std::array<UINT32, N> MakeConstArray(const UINT32 (&values)[N])
39+
{
40+
std::array<UINT32, N> arr{};
41+
for (std::size_t i = 0; i < N; ++i) {
42+
arr[i] = values[i];
43+
}
44+
return arr;
45+
}
46+
47+
constexpr auto VALID_BITRATES = MakeConstArray({96, 128, 160, 192});
48+
constexpr auto VALID_CHANNELS = MakeConstArray({1, 2});
49+
constexpr auto VALID_BITS_PER_SAMPLE = MakeConstArray({16});
50+
constexpr auto VALID_SAMPLERATES = MakeConstArray({44100, 48000});
51+
52+
template<std::size_t N> constexpr UINT32 FindBestMatch(const std::array<UINT32, N> &validValues, UINT32 value)
53+
{
54+
for (UINT32 val : validValues) {
55+
if (val >= value)
56+
return val;
57+
}
58+
59+
// Only downgrade if no values are better
60+
return validValues[N - 1];
61+
}
62+
63+
template<std::size_t N> static bool IsValid(const std::array<UINT32, N> &validValues, UINT32 value)
64+
{
65+
for (UINT32 val : validValues) {
66+
if (val == value)
67+
return true;
68+
}
69+
70+
return false;
71+
}
72+
73+
UINT32 MFAAC::FindBestBitrateMatch(UINT32 value)
74+
{
75+
return FindBestMatch(VALID_BITRATES, value);
76+
}
77+
78+
UINT32 MFAAC::FindBestChannelsMatch(UINT32 value)
79+
{
80+
return FindBestMatch(VALID_CHANNELS, value);
81+
}
82+
83+
UINT32 MFAAC::FindBestBitsPerSampleMatch(UINT32 value)
84+
{
85+
return FindBestMatch(VALID_BITS_PER_SAMPLE, value);
86+
}
87+
88+
UINT32 MFAAC::FindBestSamplerateMatch(UINT32 value)
89+
{
90+
return FindBestMatch(VALID_SAMPLERATES, value);
91+
}
92+
93+
bool MFAAC::BitrateValid(UINT32 value)
94+
{
95+
return IsValid(VALID_BITRATES, value);
96+
}
97+
98+
bool MFAAC::ChannelsValid(UINT32 value)
99+
{
100+
return IsValid(VALID_CHANNELS, value);
101+
}
102+
103+
bool MFAAC::BitsPerSampleValid(UINT32 value)
104+
{
105+
return IsValid(VALID_BITS_PER_SAMPLE, value);
106+
}
107+
108+
bool MFAAC::SamplerateValid(UINT32 value)
109+
{
110+
return IsValid(VALID_SAMPLERATES, value);
111+
}
112+
113+
HRESULT MFAAC::Encoder::CreateMediaTypes(ComPtr<IMFMediaType> &i, ComPtr<IMFMediaType> &o)
114+
{
115+
HRESULT hr;
116+
CHECK_HR_ERROR(MFCreateMediaType(&i));
117+
CHECK_HR_ERROR(MFCreateMediaType(&o));
118+
119+
CHECK_HR_ERROR(i->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio));
120+
CHECK_HR_ERROR(i->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM));
121+
CHECK_HR_ERROR(i->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample));
122+
CHECK_HR_ERROR(i->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, sampleRate));
123+
CHECK_HR_ERROR(i->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, channels));
124+
125+
CHECK_HR_ERROR(o->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio));
126+
CHECK_HR_ERROR(o->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_AAC));
127+
CHECK_HR_ERROR(o->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample));
128+
CHECK_HR_ERROR(o->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, sampleRate));
129+
CHECK_HR_ERROR(o->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, channels));
130+
CHECK_HR_ERROR(o->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, (bitrate * 1000) / 8));
131+
132+
return S_OK;
133+
fail:
134+
return hr;
135+
}
136+
137+
constexpr uint16_t SWAPU16(uint16_t x)
138+
{
139+
return (x >> 8) | (x << 8);
140+
}
141+
142+
void MFAAC::Encoder::InitializeExtraData()
143+
{
144+
constexpr uint16_t AAC_PROFILE_LC = 2;
145+
constexpr int PROFILE_SHIFT = 11;
146+
constexpr int SAMPLE_INDEX_SHIFT = 7;
147+
constexpr int CHANNELS_SHIFT = 3;
148+
constexpr int EXTENSION_ID_SHIFT = 5;
149+
constexpr uint16_t EXTENSION_ID = 0x2b7;
150+
151+
UINT16 *config = (UINT16 *)extraData;
152+
153+
*config = AAC_PROFILE_LC << PROFILE_SHIFT;
154+
155+
*config |= (sampleRate == 48000 ? 3 : 4) << SAMPLE_INDEX_SHIFT;
156+
157+
*config |= channels << CHANNELS_SHIFT;
158+
*config = SWAPU16(*config);
159+
160+
config++;
161+
*config = EXTENSION_ID << EXTENSION_ID_SHIFT;
162+
163+
*config |= AAC_PROFILE_LC;
164+
*config = SWAPU16(*config);
165+
166+
extraData[4] = 0;
167+
}
168+
169+
bool MFAAC::Encoder::Initialize()
170+
{
171+
HRESULT hr;
172+
173+
ComPtr<IMFTransform> transform_;
174+
ComPtr<IMFMediaType> inputType, outputType;
175+
176+
if (!BitrateValid(bitrate)) {
177+
LogAAC(ObsEncoder(), LOG_WARNING, "invalid bitrate (kbps) '%d'", bitrate);
178+
return false;
179+
}
180+
if (!ChannelsValid(channels)) {
181+
LogAAC(ObsEncoder(), LOG_WARNING, "invalid channel count '%d", channels);
182+
return false;
183+
}
184+
if (!SamplerateValid(sampleRate)) {
185+
LogAAC(ObsEncoder(), LOG_WARNING, "invalid sample rate (hz) '%d'", sampleRate);
186+
return false;
187+
}
188+
if (!BitsPerSampleValid(bitsPerSample)) {
189+
LogAAC(ObsEncoder(), LOG_WARNING, "invalid bits-per-sample (bits) '%d'", bitsPerSample);
190+
return false;
191+
}
192+
193+
InitializeExtraData();
194+
195+
CHECK_HR_ERROR(CoCreateInstance(CLSID_AACMFTEncoder, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&transform_)));
196+
CHECK_HR_ERROR(CreateMediaTypes(inputType, outputType));
197+
198+
CHECK_HR_ERROR(transform_->SetInputType(0, inputType.Get(), 0));
199+
CHECK_HR_ERROR(transform_->SetOutputType(0, outputType.Get(), 0));
200+
201+
CHECK_HR_ERROR(transform_->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, NULL));
202+
CHECK_HR_ERROR(transform_->ProcessMessage(MFT_MESSAGE_NOTIFY_START_OF_STREAM, NULL));
203+
204+
LogAAC(ObsEncoder(), LOG_INFO,
205+
"encoder created\n"
206+
"\tbitrate: %d\n"
207+
"\tchannels: %d\n"
208+
"\tsample rate: %d\n"
209+
"\tbits-per-sample: %d\n",
210+
bitrate, channels, sampleRate, bitsPerSample);
211+
212+
transform = transform_;
213+
return true;
214+
215+
fail:
216+
return false;
217+
}
218+
219+
HRESULT MFAAC::Encoder::CreateEmptySample(ComPtr<IMFSample> &sample, ComPtr<IMFMediaBuffer> &buffer, DWORD length)
220+
{
221+
HRESULT hr;
222+
223+
CHECK_HR_ERROR(MFCreateSample(&sample));
224+
CHECK_HR_ERROR(MFCreateMemoryBuffer(length, &buffer));
225+
CHECK_HR_ERROR(sample->AddBuffer(buffer.Get()));
226+
return S_OK;
227+
228+
fail:
229+
return hr;
230+
}
231+
232+
HRESULT MFAAC::Encoder::EnsureCapacity(ComPtr<IMFSample> &sample, DWORD length)
233+
{
234+
HRESULT hr;
235+
ComPtr<IMFMediaBuffer> buffer;
236+
DWORD currentLength;
237+
238+
if (!sample) {
239+
CHECK_HR_ERROR(CreateEmptySample(sample, buffer, length));
240+
} else {
241+
CHECK_HR_ERROR(sample->GetBufferByIndex(0, &buffer));
242+
}
243+
244+
CHECK_HR_ERROR(buffer->GetMaxLength(&currentLength));
245+
if (currentLength < length) {
246+
CHECK_HR_ERROR(sample->RemoveAllBuffers());
247+
CHECK_HR_ERROR(MFCreateMemoryBuffer(length, &buffer));
248+
CHECK_HR_ERROR(sample->AddBuffer(buffer));
249+
} else {
250+
buffer->SetCurrentLength(0);
251+
}
252+
253+
packetBuffer.reserve(length);
254+
255+
return S_OK;
256+
257+
fail:
258+
return hr;
259+
}
260+
261+
bool MFAAC::Encoder::ProcessInput(UINT8 *data, UINT32 data_length, UINT64 pts, Status *status)
262+
{
263+
HRESULT hr;
264+
ComPtr<IMFSample> sample;
265+
ComPtr<IMFMediaBuffer> buffer;
266+
BYTE *bufferData;
267+
INT64 samplePts;
268+
UINT32 samples;
269+
UINT64 sampleDur;
270+
271+
CHECK_HR_ERROR(CreateEmptySample(sample, buffer, data_length));
272+
273+
CHECK_HR_ERROR(buffer->Lock(&bufferData, NULL, NULL));
274+
memcpy(bufferData, data, data_length);
275+
CHECK_HR_ERROR(buffer->Unlock());
276+
CHECK_HR_ERROR(buffer->SetCurrentLength(data_length));
277+
278+
samples = data_length / channels / (bitsPerSample / 8);
279+
sampleDur = (UINT64)(((float)sampleRate / channels / samples) * 10000);
280+
samplePts = pts / 100;
281+
282+
CHECK_HR_ERROR(sample->SetSampleTime(samplePts));
283+
CHECK_HR_ERROR(sample->SetSampleDuration(sampleDur));
284+
285+
hr = transform->ProcessInput(0, sample, 0);
286+
if (hr == MF_E_NOTACCEPTING) {
287+
*status = NOT_ACCEPTING;
288+
return true;
289+
} else if (FAILED(hr)) {
290+
LogCOMError(ObsEncoder(), "process input", hr);
291+
return false;
292+
}
293+
294+
*status = SUCCESS;
295+
return true;
296+
297+
fail:
298+
*status = FAILURE;
299+
return false;
300+
}
301+
302+
bool MFAAC::Encoder::ProcessOutput(UINT8 **data, UINT32 *dataLength, UINT64 *pts, Status *status)
303+
{
304+
HRESULT hr;
305+
306+
DWORD outputFlags, outputStatus;
307+
MFT_OUTPUT_STREAM_INFO outputInfo = {0};
308+
MFT_OUTPUT_DATA_BUFFER output = {0};
309+
ComPtr<IMFMediaBuffer> outputBuffer;
310+
BYTE *bufferData;
311+
DWORD bufferLength;
312+
INT64 samplePts;
313+
314+
CHECK_HR_ERROR(transform->GetOutputStatus(&outputFlags));
315+
if (outputFlags != MFT_OUTPUT_STATUS_SAMPLE_READY) {
316+
*status = NEED_MORE_INPUT;
317+
return true;
318+
}
319+
320+
CHECK_HR_ERROR(transform->GetOutputStreamInfo(0, &outputInfo));
321+
EnsureCapacity(outputSample, outputInfo.cbSize);
322+
323+
output.pSample = outputSample.Get();
324+
325+
hr = transform->ProcessOutput(0, 1, &output, &outputStatus);
326+
if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
327+
*status = NEED_MORE_INPUT;
328+
return true;
329+
} else if (FAILED(hr)) {
330+
LogCOMError(ObsEncoder(), "process input", hr);
331+
return false;
332+
}
333+
334+
CHECK_HR_ERROR(outputSample->GetBufferByIndex(0, &outputBuffer));
335+
336+
CHECK_HR_ERROR(outputBuffer->Lock(&bufferData, NULL, &bufferLength));
337+
packetBuffer.assign(bufferData, bufferData + bufferLength);
338+
CHECK_HR_ERROR(outputBuffer->Unlock());
339+
340+
CHECK_HR_ERROR(outputSample->GetSampleTime(&samplePts));
341+
342+
*pts = samplePts * 100;
343+
*data = &packetBuffer[0];
344+
*dataLength = bufferLength;
345+
*status = SUCCESS;
346+
return true;
347+
348+
fail:
349+
*status = FAILURE;
350+
return false;
351+
}
352+
353+
bool MFAAC::Encoder::ExtraData(UINT8 **extraData_, UINT32 *extraDataLength)
354+
{
355+
*extraData_ = extraData;
356+
*extraDataLength = sizeof(extraData);
357+
return true;
358+
}

0 commit comments

Comments
 (0)