FreeRDP
Loading...
Searching...
No Matches
h264_mf.c
1
20#include <winpr/winpr.h>
21#include <winpr/library.h>
22
23#include <freerdp/log.h>
24#include <freerdp/codec/h264.h>
25
26#include <ks.h>
27#include <codecapi.h>
28
29#include <mfapi.h>
30#include <mferror.h>
31#include <wmcodecdsp.h>
32#include <mftransform.h>
33
34#include "h264.h"
35
36#define TAG FREERDP_TAG("codec")
37
38static const GUID sCLSID_CMSH264DecoderMFT = {
39 0x62CE7E72, 0x4C71, 0x4d20, { 0xB1, 0x5D, 0x45, 0x28, 0x31, 0xA8, 0x7D, 0x9D }
40};
41static const GUID sIID_IMFTransform = {
42 0xbf94c121, 0x5b05, 0x4e6f, { 0x80, 0x00, 0xba, 0x59, 0x89, 0x61, 0x41, 0x4d }
43};
44static const GUID sMF_MT_MAJOR_TYPE = {
45 0x48eba18e, 0xf8c9, 0x4687, { 0xbf, 0x11, 0x0a, 0x74, 0xc9, 0xf9, 0x6a, 0x8f }
46};
47static const GUID sMF_MT_FRAME_SIZE = {
48 0x1652c33d, 0xd6b2, 0x4012, { 0xb8, 0x34, 0x72, 0x03, 0x08, 0x49, 0xa3, 0x7d }
49};
50static const GUID sMF_MT_DEFAULT_STRIDE = {
51 0x644b4e48, 0x1e02, 0x4516, { 0xb0, 0xeb, 0xc0, 0x1c, 0xa9, 0xd4, 0x9a, 0xc6 }
52};
53static const GUID sMF_MT_SUBTYPE = {
54 0xf7e34c9a, 0x42e8, 0x4714, { 0xb7, 0x4b, 0xcb, 0x29, 0xd7, 0x2c, 0x35, 0xe5 }
55};
56static const GUID sMFMediaType_Video = {
57 0x73646976, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 }
58};
59static const GUID sMFVideoFormat_H264 = {
60 0x34363248, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
61};
62static const GUID sMFVideoFormat_IYUV = {
63 0x56555949, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
64};
65static const GUID sIID_ICodecAPI = {
66 0x901db4c7, 0x31ce, 0x41a2, { 0x85, 0xdc, 0x8f, 0xa0, 0xbf, 0x41, 0xb8, 0xda }
67};
68static const GUID sCODECAPI_AVLowLatencyMode = {
69 0x9c27891a, 0xed7a, 0x40e1, { 0x88, 0xe8, 0xb2, 0x27, 0x27, 0xa0, 0x24, 0xee }
70};
71
72/* encoder */
73static const GUID sCLSID_CMSH264EncoderMFT = {
74 0x6ca50344, 0x051a, 0x4ded, { 0x97, 0x79, 0xa4, 0x33, 0x05, 0x16, 0x5e, 0x35 }
75};
76static const GUID sMFVideoFormat_NV12 = {
77 0x3231564e, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 }
78};
79static const GUID sMF_MT_FRAME_RATE = {
80 0xc459a2e8, 0x3d2c, 0x4e44, { 0xb1, 0x32, 0xfe, 0xe5, 0x15, 0x6c, 0x7b, 0xb0 }
81};
82static const GUID sMF_MT_PIXEL_ASPECT_RATIO = {
83 0xc6376a1e, 0x8d0a, 0x4027, { 0xbe, 0x45, 0x6d, 0x9a, 0x0a, 0xd3, 0x9b, 0xb6 }
84};
85static const GUID sMF_MT_INTERLACE_MODE = {
86 0xe2724bb8, 0xe676, 0x4806, { 0xb4, 0xb2, 0xa8, 0xd6, 0xef, 0xb4, 0x4c, 0xcd }
87};
88static const GUID sMF_MT_AVG_BITRATE = {
89 0x20332624, 0xfb0d, 0x4d9e, { 0xbd, 0x0d, 0xcb, 0xf6, 0x78, 0x6c, 0x10, 0x2e }
90};
91static const GUID sMF_MT_MPEG2_PROFILE = {
92 0xad76a80b, 0x2d5c, 0x4e0b, { 0xb3, 0x75, 0x64, 0xe5, 0x20, 0x13, 0x70, 0x36 }
93};
94static const GUID sCODECAPI_AVEncCommonRateControlMode = {
95 0x1c0608e9, 0x370c, 0x4710, { 0x8a, 0x58, 0xcb, 0x61, 0x81, 0xc4, 0x24, 0x23 }
96};
97static const GUID sCODECAPI_AVEncCommonMeanBitRate = {
98 0xf7222374, 0x2144, 0x4815, { 0xb5, 0x50, 0xa3, 0x7f, 0x8e, 0x12, 0xee, 0x52 }
99};
100static const GUID sCODECAPI_AVEncCommonQuality = {
101 0xfcbf57a3, 0x7ea5, 0x4b0c, { 0x96, 0x44, 0x69, 0xb4, 0x0c, 0x39, 0xc3, 0x91 }
102};
103static const GUID sCODECAPI_AVEncVideoEncodeQP = {
104 0x2cb5696b, 0x23fb, 0x4ce1, { 0xa0, 0xf9, 0xef, 0x5b, 0x90, 0xfd, 0x55, 0xca }
105};
106static const GUID sCODECAPI_AVEncMPVGOPSize = {
107 0x95f31b26, 0x95a4, 0x41aa, { 0x93, 0x03, 0x24, 0x6a, 0x7f, 0xc6, 0xee, 0xf1 }
108};
109
110/* values from codecapi.h and mfobjects.h, spelled out like the GUIDs above */
111#define FREERDP_MF_RC_CBR 0 /* eAVEncCommonRateControlMode_CBR */
112#define FREERDP_MF_RC_QUALITY 3 /* eAVEncCommonRateControlMode_Quality */
113#define FREERDP_MF_PROFILE_BASELINE 66 /* eAVEncH264VProfile_Base */
114#define FREERDP_MF_INTERLACE_PROGRESSIVE 2 /* MFVideoInterlace_Progressive */
115
116typedef HRESULT(__stdcall* pfnMFStartup)(ULONG Version, DWORD dwFlags);
117typedef HRESULT(__stdcall* pfnMFShutdown)(void);
118typedef HRESULT(__stdcall* pfnMFCreateSample)(IMFSample** ppIMFSample);
119typedef HRESULT(__stdcall* pfnMFCreateMemoryBuffer)(DWORD cbMaxLength, IMFMediaBuffer** ppBuffer);
120typedef HRESULT(__stdcall* pfnMFCreateMediaType)(IMFMediaType** ppMFType);
121
122typedef struct
123{
124 ICodecAPI* codecApi;
125 IMFTransform* transform;
126 IMFMediaType* inputType;
127 IMFMediaType* outputType;
128 IMFSample* sample;
129 UINT32 frameWidth;
130 UINT32 frameHeight;
131 IMFSample* outputSample;
132 IMFMediaBuffer* outputBuffer;
133 HMODULE mfplat;
134 pfnMFStartup MFStartup;
135 pfnMFShutdown MFShutdown;
136 pfnMFCreateSample MFCreateSample;
137 pfnMFCreateMemoryBuffer MFCreateMemoryBuffer;
138 pfnMFCreateMediaType MFCreateMediaType;
139 UINT32 stride;
140 BOOL bottomUp;
141
142 /* encoder state: the media types are set on the first frame and again on a size change */
143 BOOL encStreaming;
144 UINT32 encWidth;
145 UINT32 encHeight;
146 UINT32 encFrameRate;
147 UINT32 encBitRate;
148 UINT32 encQP;
149 H264_RATECONTROL_MODE encRateControl;
150 DWORD encOutputFlags;
151 DWORD encOutputSize;
152 LONGLONG encSampleTime;
153 BYTE* encData;
154 size_t encDataSize;
155 size_t encDataLength;
156 UINT32 encHeld;
157 BOOL encWarned;
158} H264_CONTEXT_MF;
159
160WINPR_ATTR_NODISCARD
161static HRESULT mf_find_output_type(H264_CONTEXT_MF* sys, const GUID* guid,
162 IMFMediaType** ppMediaType)
163{
164 DWORD idx = 0;
165 GUID mediaGuid = WINPR_C_ARRAY_INIT;
166 HRESULT hr = S_OK;
167 IMFMediaType* pMediaType = nullptr;
168
169 WINPR_ASSERT(sys);
170 WINPR_ASSERT(sys->transform);
171 WINPR_ASSERT(guid);
172 WINPR_ASSERT(ppMediaType);
173
174 while (1)
175 {
176 hr = sys->transform->lpVtbl->GetOutputAvailableType(sys->transform, 0, idx, &pMediaType);
177
178 if (FAILED(hr))
179 break;
180
181 /* a type without a subtype can't be the one we look for, try the next */
182 if (SUCCEEDED(pMediaType->lpVtbl->GetGUID(pMediaType, &sMF_MT_SUBTYPE, &mediaGuid)) &&
183 IsEqualGUID(&mediaGuid, guid))
184 {
185 *ppMediaType = pMediaType;
186 return S_OK;
187 }
188
189 pMediaType->lpVtbl->Release(pMediaType);
190 idx++;
191 }
192
193 return hr;
194}
195
196WINPR_ATTR_NODISCARD
197static HRESULT mf_create_output_sample(H264_CONTEXT* h264, H264_CONTEXT_MF* sys)
198{
199 HRESULT hr = S_OK;
200 MFT_OUTPUT_STREAM_INFO streamInfo;
201
202 WINPR_ASSERT(h264);
203 WINPR_ASSERT(sys);
204 WINPR_ASSERT(sys->transform);
205
206 if (sys->outputSample)
207 {
208 sys->outputSample->lpVtbl->Release(sys->outputSample);
209 sys->outputSample = nullptr;
210 }
211
212 hr = sys->MFCreateSample(&sys->outputSample);
213
214 if (FAILED(hr))
215 {
216 WLog_Print(h264->log, WLOG_ERROR, "MFCreateSample failure: 0x%08" PRIX32 "", hr);
217 goto error;
218 }
219
220 hr = sys->transform->lpVtbl->GetOutputStreamInfo(sys->transform, 0, &streamInfo);
221
222 if (FAILED(hr))
223 {
224 WLog_Print(h264->log, WLOG_ERROR, "GetOutputStreamInfo failure: 0x%08" PRIX32 "", hr);
225 goto error;
226 }
227
228 hr = sys->MFCreateMemoryBuffer(streamInfo.cbSize, &sys->outputBuffer);
229
230 if (FAILED(hr))
231 {
232 WLog_Print(h264->log, WLOG_ERROR, "MFCreateMemoryBuffer failure: 0x%08" PRIX32 "", hr);
233 goto error;
234 }
235
236 hr = sys->outputSample->lpVtbl->AddBuffer(sys->outputSample, sys->outputBuffer);
237
238 if (FAILED(hr))
239 {
240 WLog_Print(h264->log, WLOG_ERROR, "AddBuffer failure: 0x%08" PRIX32 "", hr);
241 goto error;
242 }
243
244 sys->outputBuffer->lpVtbl->Release(sys->outputBuffer);
245error:
246 return hr;
247}
248
249/* IYUV output: full size Y plane followed by the quarter size U and V planes */
250WINPR_ATTR_NODISCARD
251static size_t getSrcOffset(const H264_CONTEXT_MF* sys, UINT32 line, size_t plane)
252{
253 WINPR_ASSERT(sys);
254
255 const size_t stride = (plane == 0) ? sys->stride : sys->stride / 2u;
256 const size_t height = (plane == 0) ? sys->frameHeight : sys->frameHeight / 2u;
257 size_t offset = 0;
258 if (plane > 0)
259 offset += (size_t)sys->stride * sys->frameHeight;
260 if (plane > 1)
261 offset += stride * height;
262
263 WINPR_ASSERT(line < height);
264 const size_t row = sys->bottomUp ? height - line - 1u : line;
265 return offset + row * stride;
266}
267
268WINPR_ATTR_NODISCARD
269static HRESULT mf_process_output(H264_CONTEXT_MF* sys)
270{
271 DWORD status = 0;
272 MFT_OUTPUT_DATA_BUFFER buffer = WINPR_C_ARRAY_INIT;
273
274 WINPR_ASSERT(sys);
275 WINPR_ASSERT(sys->transform);
276
277 buffer.pSample = sys->outputSample;
278 const HRESULT hr =
279 sys->transform->lpVtbl->ProcessOutput(sys->transform, 0, 1, &buffer, &status);
280 if (buffer.pEvents)
281 buffer.pEvents->lpVtbl->Release(buffer.pEvents);
282 return hr;
283}
284
285WINPR_ATTR_NODISCARD
286static int mf_decompress(H264_CONTEXT* WINPR_RESTRICT h264, const BYTE* WINPR_RESTRICT pSrcData,
287 UINT32 SrcSize)
288{
289 int rc = -1;
290 BYTE* pbBuffer = nullptr;
291 DWORD cbMaxLength = 0;
292 DWORD cbCurrentLength = 0;
293 IMFSample* inputSample = nullptr;
294 IMFMediaBuffer* inputBuffer = nullptr;
295 IMFMediaBuffer* outputBuffer = nullptr;
296
297 WINPR_ASSERT(h264);
298 WINPR_ASSERT(pSrcData);
299
300 H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)h264->pSystemData;
301 WINPR_ASSERT(sys);
302 UINT32* iStride = h264->iStride;
303 BYTE** pYUVData = h264->pYUVData;
304 HRESULT hr = sys->MFCreateMemoryBuffer(SrcSize, &inputBuffer);
305
306 if (FAILED(hr))
307 {
308 WLog_Print(h264->log, WLOG_ERROR, "MFCreateMemoryBuffer failure: 0x%08" PRIX32 "", hr);
309 goto error;
310 }
311
312 hr = inputBuffer->lpVtbl->Lock(inputBuffer, &pbBuffer, &cbMaxLength, &cbCurrentLength);
313
314 if (FAILED(hr))
315 {
316 WLog_Print(h264->log, WLOG_ERROR, "Lock failure: 0x%08" PRIX32 "", hr);
317 goto error;
318 }
319
320 CopyMemory(pbBuffer, pSrcData, SrcSize);
321 hr = inputBuffer->lpVtbl->SetCurrentLength(inputBuffer, SrcSize);
322
323 if (FAILED(hr))
324 {
325 WLog_Print(h264->log, WLOG_ERROR, "SetCurrentLength failure: 0x%08" PRIX32 "", hr);
326 goto error;
327 }
328
329 hr = inputBuffer->lpVtbl->Unlock(inputBuffer);
330
331 if (FAILED(hr))
332 {
333 WLog_Print(h264->log, WLOG_ERROR, "Unlock failure: 0x%08" PRIX32 "", hr);
334 goto error;
335 }
336
337 hr = sys->MFCreateSample(&inputSample);
338
339 if (FAILED(hr))
340 {
341 WLog_Print(h264->log, WLOG_ERROR, "MFCreateSample failure: 0x%08" PRIX32 "", hr);
342 goto error;
343 }
344
345 hr = inputSample->lpVtbl->AddBuffer(inputSample, inputBuffer);
346
347 if (FAILED(hr))
348 {
349 WLog_Print(h264->log, WLOG_ERROR, "AddBuffer failure: 0x%08" PRIX32 "", hr);
350 goto error;
351 }
352
353 hr = sys->transform->lpVtbl->ProcessInput(sys->transform, 0, inputSample, 0);
354
355 if (FAILED(hr))
356 {
357 WLog_Print(h264->log, WLOG_ERROR, "ProcessInput failure: 0x%08" PRIX32 "", hr);
358 goto error;
359 }
360
361 hr = mf_create_output_sample(h264, sys);
362
363 if (FAILED(hr))
364 {
365 WLog_Print(h264->log, WLOG_ERROR, "mf_create_output_sample failure: 0x%08" PRIX32 "", hr);
366 goto error;
367 }
368
369 hr = mf_process_output(sys);
370
371 if (hr == MF_E_TRANSFORM_STREAM_CHANGE)
372 {
373 UINT64 frameSize = 0;
374
375 if (sys->outputType)
376 {
377 sys->outputType->lpVtbl->Release(sys->outputType);
378 sys->outputType = nullptr;
379 }
380
381 hr = mf_find_output_type(sys, &sMFVideoFormat_IYUV, &sys->outputType);
382
383 if (FAILED(hr))
384 {
385 WLog_Print(h264->log, WLOG_ERROR, "mf_find_output_type failure: 0x%08" PRIX32 "", hr);
386 goto error;
387 }
388
389 hr = sys->transform->lpVtbl->SetOutputType(sys->transform, 0, sys->outputType, 0);
390
391 if (FAILED(hr))
392 {
393 WLog_Print(h264->log, WLOG_ERROR, "SetOutputType failure: 0x%08" PRIX32 "", hr);
394 goto error;
395 }
396
397 hr = mf_create_output_sample(h264, sys);
398
399 if (FAILED(hr))
400 {
401 WLog_Print(h264->log, WLOG_ERROR, "mf_create_output_sample failure: 0x%08" PRIX32 "",
402 hr);
403 goto error;
404 }
405
406 hr = sys->outputType->lpVtbl->GetUINT64(sys->outputType, &sMF_MT_FRAME_SIZE, &frameSize);
407
408 if (FAILED(hr))
409 {
410 WLog_Print(h264->log, WLOG_ERROR,
411 "GetUINT64(MF_MT_FRAME_SIZE) failure: 0x%08" PRIX32 "", hr);
412 goto error;
413 }
414
415 sys->frameWidth = (UINT32)(frameSize >> 32);
416 sys->frameHeight = (UINT32)frameSize;
417 hr = sys->outputType->lpVtbl->GetUINT32(sys->outputType, &sMF_MT_DEFAULT_STRIDE,
418 &sys->stride);
419
420 if (FAILED(hr))
421 {
422 WLog_Print(h264->log, WLOG_ERROR,
423 "GetUINT32(MF_MT_DEFAULT_STRIDE) failure: 0x%08" PRIX32 "", hr);
424 goto error;
425 }
426
427 const INT32 istride = (INT32)sys->stride;
428 sys->bottomUp = (istride < 0);
429 if (sys->bottomUp)
430 sys->stride = istride * -1;
431
432 if (!avc420_ensure_buffer(h264, sys->stride, sys->frameWidth, sys->frameHeight))
433 goto error;
434
435 /* the decoder still holds the frame, fetch it with the new output type */
436 hr = mf_process_output(sys);
437 }
438
439 if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
440 rc = 0;
441 else if (FAILED(hr))
442 {
443 WLog_Print(h264->log, WLOG_ERROR, "ProcessOutput failure: 0x%08" PRIX32 "", hr);
444 goto error;
445 }
446 else
447 {
448 BYTE* buffer = nullptr;
449 DWORD bufferCount = 0;
450 DWORD cbMaxLength = 0;
451 DWORD cbCurrentLength = 0;
452 hr = sys->outputSample->lpVtbl->GetBufferCount(sys->outputSample, &bufferCount);
453
454 if (FAILED(hr))
455 {
456 WLog_Print(h264->log, WLOG_ERROR, "GetBufferCount failure: 0x%08" PRIX32 "", hr);
457 goto error;
458 }
459
460 hr = sys->outputSample->lpVtbl->GetBufferByIndex(sys->outputSample, 0, &outputBuffer);
461
462 if (FAILED(hr))
463 {
464 WLog_Print(h264->log, WLOG_ERROR, "GetBufferByIndex failure: 0x%08" PRIX32 "", hr);
465 goto error;
466 }
467
468 hr = outputBuffer->lpVtbl->Lock(outputBuffer, &buffer, &cbMaxLength, &cbCurrentLength);
469
470 if (FAILED(hr))
471 {
472 WLog_Print(h264->log, WLOG_ERROR, "Lock failure: 0x%08" PRIX32 "", hr);
473 goto error;
474 }
475
476 if ((sys->stride == 0) || (cbCurrentLength / 3u / sys->stride < sys->frameHeight / 2u))
477 {
478 WLog_Print(h264->log, WLOG_ERROR, "output buffer too small: %" PRIu32 "",
479 cbCurrentLength);
480 (void)outputBuffer->lpVtbl->Unlock(outputBuffer);
481 goto error;
482 }
483
484 /* Copy data from decoder buffer to our YUV buffer.
485 * strides differ (the YUV buffer is always larger) so copy only data available from the
486 * decoder buffer but increment the YUV buffer with the YUV buffer strides.
487 */
488 for (UINT32 x = 0; x < sys->frameHeight; x++)
489 {
490 const size_t srcOffset = getSrcOffset(sys, x, 0);
491 const UINT32 dstOffset = (UINT32)iStride[0] * x;
492 CopyMemory(&pYUVData[0][dstOffset], &buffer[srcOffset], sys->stride);
493 }
494 for (UINT32 x = 0; x < sys->frameHeight / 2; x++)
495 {
496 const size_t srcUOffset = getSrcOffset(sys, x, 1);
497 const size_t srcVOffset = getSrcOffset(sys, x, 2);
498 const UINT32 dstUOffset = (UINT32)iStride[1] * x;
499 const UINT32 dstVOffset = (UINT32)iStride[2] * x;
500 CopyMemory(&pYUVData[1][dstUOffset], &buffer[srcUOffset], sys->stride / 2u);
501 CopyMemory(&pYUVData[2][dstVOffset], &buffer[srcVOffset], sys->stride / 2u);
502 }
503
504 hr = outputBuffer->lpVtbl->Unlock(outputBuffer);
505
506 if (FAILED(hr))
507 {
508 WLog_Print(h264->log, WLOG_ERROR, "Unlock failure: 0x%08" PRIX32 "", hr);
509 goto error;
510 }
511
512 h264->YUVWidth = sys->frameWidth;
513 h264->YUVHeight = sys->frameHeight;
514 rc = 1;
515 }
516
517error:
518 if (outputBuffer)
519 outputBuffer->lpVtbl->Release(outputBuffer);
520 if (inputSample)
521 inputSample->lpVtbl->Release(inputSample);
522 if (inputBuffer)
523 inputBuffer->lpVtbl->Release(inputBuffer);
524 if (rc < 0)
525 WLog_Print(h264->log, WLOG_ERROR, "decompression failed");
526 return rc;
527}
528
529WINPR_ATTR_NODISCARD
530static HRESULT mf_encoder_set_value(H264_CONTEXT* h264, H264_CONTEXT_MF* sys, const GUID* guid,
531 const char* name, VARTYPE vt, UINT64 value)
532{
533 VARIANT var = WINPR_C_ARRAY_INIT;
534
535 WINPR_ASSERT(h264);
536 WINPR_ASSERT(sys);
537 WINPR_ASSERT(guid);
538 WINPR_ASSERT(name);
539
540 if (!sys->codecApi)
541 return E_NOINTERFACE;
542
543 var.n1.n2.vt = vt;
544 if (vt == VT_UI8)
545 var.n1.n2.n3.ullVal = value;
546 else if (vt == VT_BOOL)
547 var.n1.n2.n3.boolVal = value ? VARIANT_TRUE : VARIANT_FALSE;
548 else
549 var.n1.n2.n3.ulVal = (ULONG)value;
550
551 const HRESULT hr = sys->codecApi->lpVtbl->SetValue(sys->codecApi, guid, &var);
552 if (FAILED(hr))
553 WLog_Print(h264->log, WLOG_DEBUG, "ICodecAPI::SetValue(%s) failure: 0x%08" PRIX32 "", name,
554 (UINT32)hr);
555 return hr;
556}
557
558/* Every ICodecAPI property below tunes the encoder on top of its media types, which carry what
559 * the stream needs (size, frame rate, bit rate, profile). An encoder without a property keeps
560 * its default and still produces a valid stream, so that is not an error: an encoder without
561 * ICodecAPI (E_NOINTERFACE here), or Wine's, which answers E_NOTIMPL to every property. An
562 * encoder that has the property (IsSupported) but refuses our value is an error. */
563WINPR_ATTR_NODISCARD
564static BOOL mf_encoder_check_value(H264_CONTEXT* h264, H264_CONTEXT_MF* sys, const GUID* guid,
565 const char* name, HRESULT hr)
566{
567 WINPR_ASSERT(h264);
568 WINPR_ASSERT(sys);
569 WINPR_ASSERT(guid);
570 WINPR_ASSERT(name);
571
572 if (SUCCEEDED(hr))
573 return TRUE;
574
575 if ((hr != E_NOTIMPL) && (hr != E_NOINTERFACE) && sys->codecApi &&
576 (sys->codecApi->lpVtbl->IsSupported(sys->codecApi, guid) == S_OK))
577 {
578 WLog_Print(h264->log, WLOG_ERROR,
579 "H264 encoder refuses ICodecAPI property %s: 0x%08" PRIX32 "", name, (UINT32)hr);
580 return FALSE;
581 }
582
583 WLog_Print(h264->log, WLOG_DEBUG, "H264 encoder does not support %s, using its default", name);
584 return TRUE;
585}
586
587WINPR_ATTR_NODISCARD
588static BOOL mf_encoder_set_optional(H264_CONTEXT* h264, H264_CONTEXT_MF* sys, const GUID* guid,
589 const char* name, VARTYPE vt, UINT64 value)
590{
591 const HRESULT hr = mf_encoder_set_value(h264, sys, guid, name, vt, value);
592 return mf_encoder_check_value(h264, sys, guid, name, hr);
593}
594
595static void mf_encoder_release(H264_CONTEXT_MF* sys)
596{
597 WINPR_ASSERT(sys);
598
599 if (sys->outputSample)
600 {
601 sys->outputSample->lpVtbl->Release(sys->outputSample);
602 sys->outputSample = nullptr;
603 }
604 if (sys->inputType)
605 {
606 sys->inputType->lpVtbl->Release(sys->inputType);
607 sys->inputType = nullptr;
608 }
609 if (sys->outputType)
610 {
611 sys->outputType->lpVtbl->Release(sys->outputType);
612 sys->outputType = nullptr;
613 }
614 if (sys->codecApi)
615 {
616 sys->codecApi->lpVtbl->Release(sys->codecApi);
617 sys->codecApi = nullptr;
618 }
619 if (sys->transform)
620 {
621 sys->transform->lpVtbl->Release(sys->transform);
622 sys->transform = nullptr;
623 }
624 sys->encStreaming = FALSE;
625}
626
627WINPR_ATTR_NODISCARD
628static BOOL mf_encoder_create(H264_CONTEXT* h264, H264_CONTEXT_MF* sys)
629{
630 WINPR_ASSERT(h264);
631 WINPR_ASSERT(sys);
632
633 HRESULT hr = CoCreateInstance(&sCLSID_CMSH264EncoderMFT, nullptr, CLSCTX_INPROC_SERVER,
634 &sIID_IMFTransform, (void**)&sys->transform);
635
636 if (FAILED(hr))
637 {
638 WLog_Print(h264->log, WLOG_DEBUG,
639 "CoCreateInstance(CLSID_CMSH264EncoderMFT) failure: 0x%08" PRIX32 "",
640 (UINT32)hr);
641 return FALSE;
642 }
643
644 /* rate control and latency are tuned through ICodecAPI; encode without it if it is missing */
645 hr = sys->transform->lpVtbl->QueryInterface(sys->transform, &sIID_ICodecAPI,
646 (void**)&sys->codecApi);
647 if (FAILED(hr))
648 {
649 WLog_Print(h264->log, WLOG_WARN,
650 "H264 encoder has no ICodecAPI (0x%08" PRIX32 "), using its defaults",
651 (UINT32)hr);
652 sys->codecApi = nullptr;
653 }
654
655 return TRUE;
656}
657
658WINPR_ATTR_NODISCARD
659static HRESULT mf_encoder_media_type(H264_CONTEXT_MF* sys, IMFMediaType** ppType,
660 const GUID* subtype, UINT32 width, UINT32 height,
661 UINT32 frameRate, UINT32 bitRate)
662{
663 IMFMediaType* type = nullptr;
664
665 WINPR_ASSERT(sys);
666 WINPR_ASSERT(sys->MFCreateMediaType);
667 WINPR_ASSERT(ppType);
668 WINPR_ASSERT(subtype);
669
670 HRESULT hr = sys->MFCreateMediaType(&type);
671
672 if (FAILED(hr))
673 return hr;
674
675 hr = type->lpVtbl->SetGUID(type, &sMF_MT_MAJOR_TYPE, &sMFMediaType_Video);
676 if (SUCCEEDED(hr))
677 hr = type->lpVtbl->SetGUID(type, &sMF_MT_SUBTYPE, subtype);
678 if (SUCCEEDED(hr))
679 hr = type->lpVtbl->SetUINT64(type, &sMF_MT_FRAME_SIZE, ((UINT64)width << 32) | height);
680 if (SUCCEEDED(hr))
681 hr = type->lpVtbl->SetUINT64(type, &sMF_MT_FRAME_RATE, ((UINT64)frameRate << 32) | 1);
682 if (SUCCEEDED(hr))
683 hr = type->lpVtbl->SetUINT64(type, &sMF_MT_PIXEL_ASPECT_RATIO, ((UINT64)1 << 32) | 1);
684 if (SUCCEEDED(hr))
685 hr =
686 type->lpVtbl->SetUINT32(type, &sMF_MT_INTERLACE_MODE, FREERDP_MF_INTERLACE_PROGRESSIVE);
687
688 if (SUCCEEDED(hr) && IsEqualGUID(subtype, &sMFVideoFormat_H264))
689 {
690 hr = type->lpVtbl->SetUINT32(type, &sMF_MT_AVG_BITRATE, bitRate);
691 /* constrained baseline: no B frames, CAVLC, so every FreeRDP client decoder can read it */
692 if (SUCCEEDED(hr))
693 hr = type->lpVtbl->SetUINT32(type, &sMF_MT_MPEG2_PROFILE, FREERDP_MF_PROFILE_BASELINE);
694 }
695
696 if (FAILED(hr))
697 {
698 type->lpVtbl->Release(type);
699 return hr;
700 }
701
702 *ppType = type;
703 return S_OK;
704}
705
706WINPR_ATTR_NODISCARD
707static BOOL mf_encoder_rate_control(H264_CONTEXT* h264, H264_CONTEXT_MF* sys, UINT32 bitRate)
708{
709 WINPR_ASSERT(h264);
710 WINPR_ASSERT(sys);
711
712 /* without it the encoder holds frames back (4 on Windows Server 2025); mf_compress copes
713 * with that, so the property is optional. It is a VT_BOOL; the encoder answers E_INVALIDARG
714 * to VT_UI4, which other MFTs (the decoder) take, so VT_UI4 is the second try */
715 HRESULT hr = mf_encoder_set_value(h264, sys, &sCODECAPI_AVLowLatencyMode, "AVLowLatencyMode",
716 VT_BOOL, 1);
717 if (FAILED(hr))
718 hr = mf_encoder_set_value(h264, sys, &sCODECAPI_AVLowLatencyMode, "AVLowLatencyMode",
719 VT_UI4, 1);
720 if (!mf_encoder_check_value(h264, sys, &sCODECAPI_AVLowLatencyMode, "AVLowLatencyMode", hr))
721 return FALSE;
722
723 if (!mf_encoder_set_optional(h264, sys, &sCODECAPI_AVEncMPVGOPSize, "AVEncMPVGOPSize", VT_UI4,
724 30))
725 return FALSE;
726
727 switch (h264->RateControlMode)
728 {
729 case H264_RATECONTROL_CQP:
730 {
731 if (!mf_encoder_set_optional(h264, sys, &sCODECAPI_AVEncCommonRateControlMode,
732 "AVEncCommonRateControlMode", VT_UI4,
733 FREERDP_MF_RC_QUALITY))
734 return FALSE;
735 /* AVEncVideoEncodeQP: the QP in the low 16 bits; older encoders only have the
736 * 0..100 quality scale, so when the QP is refused for any reason map it (0..51,
737 * lower is better) onto that scale; the quality scale's result decides */
738 if (FAILED(mf_encoder_set_value(h264, sys, &sCODECAPI_AVEncVideoEncodeQP,
739 "AVEncVideoEncodeQP", VT_UI8, h264->QP & 0xFFFF)))
740 {
741 const UINT32 qp = MIN(h264->QP, 51);
742 if (!mf_encoder_set_optional(h264, sys, &sCODECAPI_AVEncCommonQuality,
743 "AVEncCommonQuality", VT_UI4, 100 - (qp * 100 / 51)))
744 return FALSE;
745 }
746 }
747 break;
748
749 case H264_RATECONTROL_VBR:
750 default:
751 /* a target bit rate, like OpenH264's RC_BITRATE_MODE; the bit rate is also on the
752 * output type (MF_MT_AVG_BITRATE), which every encoder reads */
753 if (!mf_encoder_set_optional(h264, sys, &sCODECAPI_AVEncCommonRateControlMode,
754 "AVEncCommonRateControlMode", VT_UI4, FREERDP_MF_RC_CBR))
755 return FALSE;
756 if (!mf_encoder_set_optional(h264, sys, &sCODECAPI_AVEncCommonMeanBitRate,
757 "AVEncCommonMeanBitRate", VT_UI4, bitRate))
758 return FALSE;
759 break;
760 }
761
762 return TRUE;
763}
764
765WINPR_ATTR_NODISCARD
766static BOOL mf_encoder_configure(H264_CONTEXT* h264, H264_CONTEXT_MF* sys, UINT32 width,
767 UINT32 height, UINT32 frameRate, UINT32 bitRate)
768{
769 MFT_OUTPUT_STREAM_INFO streamInfo = WINPR_C_ARRAY_INIT;
770
771 WINPR_ASSERT(h264);
772 WINPR_ASSERT(sys);
773
774 /* the types of a running encoder can't change, start a new one */
775 if (sys->encStreaming || !sys->transform)
776 {
777 mf_encoder_release(sys);
778 if (!mf_encoder_create(h264, sys))
779 return FALSE;
780 }
781
782 /* rate control is read when the output type is set, so it goes first */
783 if (!mf_encoder_rate_control(h264, sys, bitRate))
784 goto fail;
785
786 /* the H.264 encoder wants the output type before the input type */
787 HRESULT hr = mf_encoder_media_type(sys, &sys->outputType, &sMFVideoFormat_H264, width, height,
788 frameRate, bitRate);
789 if (FAILED(hr))
790 {
791 WLog_Print(h264->log, WLOG_ERROR, "encoder output type failure: 0x%08" PRIX32 "",
792 (UINT32)hr);
793 goto fail;
794 }
795
796 hr = sys->transform->lpVtbl->SetOutputType(sys->transform, 0, sys->outputType, 0);
797 if (FAILED(hr))
798 {
799 WLog_Print(h264->log, WLOG_ERROR, "encoder SetOutputType failure: 0x%08" PRIX32 "",
800 (UINT32)hr);
801 goto fail;
802 }
803
804 hr = mf_encoder_media_type(sys, &sys->inputType, &sMFVideoFormat_NV12, width, height, frameRate,
805 bitRate);
806 if (FAILED(hr))
807 {
808 WLog_Print(h264->log, WLOG_ERROR, "encoder input type failure: 0x%08" PRIX32 "",
809 (UINT32)hr);
810 goto fail;
811 }
812
813 hr = sys->transform->lpVtbl->SetInputType(sys->transform, 0, sys->inputType, 0);
814 if (FAILED(hr))
815 {
816 WLog_Print(h264->log, WLOG_ERROR, "encoder SetInputType failure: 0x%08" PRIX32 "",
817 (UINT32)hr);
818 goto fail;
819 }
820
821 hr = sys->transform->lpVtbl->GetOutputStreamInfo(sys->transform, 0, &streamInfo);
822 if (FAILED(hr))
823 {
824 WLog_Print(h264->log, WLOG_ERROR, "encoder GetOutputStreamInfo failure: 0x%08" PRIX32 "",
825 (UINT32)hr);
826 goto fail;
827 }
828
829 sys->encOutputFlags = streamInfo.dwFlags;
830 sys->encOutputSize = streamInfo.cbSize;
831 if (sys->encOutputSize == 0)
832 sys->encOutputSize = (DWORD)(3ull * width * height / 2); /* checked by mf_compress */
833
834 if ((sys->encOutputFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) == 0)
835 {
836 IMFMediaBuffer* buffer = nullptr;
837
838 hr = sys->MFCreateSample(&sys->outputSample);
839 if (SUCCEEDED(hr))
840 hr = sys->MFCreateMemoryBuffer(sys->encOutputSize, &buffer);
841 if (SUCCEEDED(hr))
842 hr = sys->outputSample->lpVtbl->AddBuffer(sys->outputSample, buffer);
843 if (buffer)
844 buffer->lpVtbl->Release(buffer);
845 if (FAILED(hr))
846 {
847 WLog_Print(h264->log, WLOG_ERROR, "encoder output sample failure: 0x%08" PRIX32 "",
848 (UINT32)hr);
849 goto fail;
850 }
851 }
852
853 hr = sys->transform->lpVtbl->ProcessMessage(sys->transform, MFT_MESSAGE_NOTIFY_BEGIN_STREAMING,
854 0);
855 if (SUCCEEDED(hr))
856 hr = sys->transform->lpVtbl->ProcessMessage(sys->transform,
857 MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
858 if (FAILED(hr))
859 {
860 WLog_Print(h264->log, WLOG_ERROR, "encoder ProcessMessage failure: 0x%08" PRIX32 "",
861 (UINT32)hr);
862 goto fail;
863 }
864
865 sys->encStreaming = TRUE;
866 sys->encWidth = width;
867 sys->encHeight = height;
868 sys->encFrameRate = frameRate;
869 sys->encBitRate = bitRate;
870 sys->encQP = h264->QP;
871 sys->encRateControl = h264->RateControlMode;
872 sys->encSampleTime = 0;
873 sys->encHeld = 0;
874 WLog_Print(h264->log, WLOG_DEBUG,
875 "MediaFoundation H264 encoder: %" PRIu32 "x%" PRIu32 " %" PRIu32
876 " fps, output flags 0x%08" PRIx32 "",
877 width, height, frameRate, (UINT32)sys->encOutputFlags);
878 return TRUE;
879
880fail:
881 mf_encoder_release(sys);
882 return FALSE;
883}
884
885WINPR_ATTR_NODISCARD
886static BOOL mf_encoder_append(H264_CONTEXT* h264, H264_CONTEXT_MF* sys, IMFSample* sample)
887{
888 IMFMediaBuffer* buffer = nullptr;
889 BYTE* data = nullptr;
890 DWORD maxLength = 0;
891 DWORD length = 0;
892 BOOL rc = FALSE;
893
894 WINPR_ASSERT(h264);
895 WINPR_ASSERT(sys);
896 WINPR_ASSERT(sample);
897
898 HRESULT hr = sample->lpVtbl->ConvertToContiguousBuffer(sample, &buffer);
899 if (FAILED(hr))
900 {
901 WLog_Print(h264->log, WLOG_ERROR, "ConvertToContiguousBuffer failure: 0x%08" PRIX32 "",
902 (UINT32)hr);
903 return FALSE;
904 }
905
906 hr = buffer->lpVtbl->Lock(buffer, &data, &maxLength, &length);
907 if (FAILED(hr))
908 {
909 WLog_Print(h264->log, WLOG_ERROR, "Lock failure: 0x%08" PRIX32 "", (UINT32)hr);
910 goto out;
911 }
912
913 BOOL copied = FALSE;
914 const size_t needed = sys->encDataLength + length;
915 if (needed > sys->encDataSize)
916 {
917 BYTE* tmp = realloc(sys->encData, needed);
918 if (tmp)
919 {
920 sys->encData = tmp;
921 sys->encDataSize = needed;
922 }
923 }
924 if (needed <= sys->encDataSize)
925 {
926 if (length > 0)
927 memcpy(&sys->encData[sys->encDataLength], data, length);
928 sys->encDataLength = needed;
929 copied = TRUE;
930 }
931
932 /* one Unlock for both outcomes, so its result is checked on every path */
933 hr = buffer->lpVtbl->Unlock(buffer);
934 if (FAILED(hr))
935 WLog_Print(h264->log, WLOG_ERROR, "Unlock failure: 0x%08" PRIX32 "", (UINT32)hr);
936 else if (!copied)
937 WLog_Print(h264->log, WLOG_ERROR, "encoder output: out of memory (%" PRIuz " bytes)",
938 needed);
939 else
940 rc = TRUE;
941
942out:
943 buffer->lpVtbl->Release(buffer);
944 return rc;
945}
946
947/* collect everything the encoder has ready; MF_E_TRANSFORM_NEED_MORE_INPUT ends it */
948WINPR_ATTR_NODISCARD
949static HRESULT mf_encoder_drain(H264_CONTEXT* h264, H264_CONTEXT_MF* sys)
950{
951 WINPR_ASSERT(h264);
952 WINPR_ASSERT(sys);
953 WINPR_ASSERT(sys->transform);
954
955 const BOOL provides = (sys->encOutputFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) != 0;
956
957 for (size_t i = 0; i < 16; i++)
958 {
959 DWORD status = 0;
960 MFT_OUTPUT_DATA_BUFFER output = WINPR_C_ARRAY_INIT;
961
962 if (!provides)
963 {
964 /* the encoder writes into our sample: give it an empty buffer each time */
965 IMFMediaBuffer* buffer = nullptr;
966
967 WINPR_ASSERT(sys->outputSample);
968 HRESULT hr = sys->outputSample->lpVtbl->GetBufferByIndex(sys->outputSample, 0, &buffer);
969 if (SUCCEEDED(hr))
970 {
971 hr = buffer->lpVtbl->SetCurrentLength(buffer, 0);
972 buffer->lpVtbl->Release(buffer);
973 }
974 if (FAILED(hr))
975 {
976 WLog_Print(h264->log, WLOG_ERROR,
977 "encoder output buffer reset failure: 0x%08" PRIX32 "", (UINT32)hr);
978 return hr;
979 }
980 output.pSample = sys->outputSample;
981 }
982
983 const HRESULT hr =
984 sys->transform->lpVtbl->ProcessOutput(sys->transform, 0, 1, &output, &status);
985 if (output.pEvents)
986 output.pEvents->lpVtbl->Release(output.pEvents);
987
988 if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
989 return S_OK;
990
991 if (FAILED(hr))
992 {
993 if (provides && output.pSample)
994 output.pSample->lpVtbl->Release(output.pSample);
995 WLog_Print(h264->log, WLOG_ERROR, "encoder ProcessOutput failure: 0x%08" PRIX32 "",
996 (UINT32)hr);
997 return hr;
998 }
999
1000 const BOOL ok = output.pSample ? mf_encoder_append(h264, sys, output.pSample) : TRUE;
1001 if (provides && output.pSample)
1002 output.pSample->lpVtbl->Release(output.pSample);
1003 if (!ok)
1004 return E_FAIL; /* mf_encoder_append logged why */
1005 }
1006
1007 return S_OK;
1008}
1009
1010WINPR_ATTR_NODISCARD
1011static BOOL mf_encoder_update(H264_CONTEXT* h264, H264_CONTEXT_MF* sys, UINT32 width, UINT32 height,
1012 UINT32 frameRate, UINT32 bitRate)
1013{
1014 WINPR_ASSERT(h264);
1015 WINPR_ASSERT(sys);
1016
1017 if (!sys->encStreaming || (sys->encWidth != width) || (sys->encHeight != height) ||
1018 (sys->encFrameRate != frameRate) || (sys->encRateControl != h264->RateControlMode))
1019 return mf_encoder_configure(h264, sys, width, height, frameRate, bitRate);
1020
1021 /* bit rate and QP can change while the encoder runs; start over where they can't */
1022 if ((h264->RateControlMode == H264_RATECONTROL_VBR) && (sys->encBitRate != bitRate))
1023 {
1024 if (FAILED(mf_encoder_set_value(h264, sys, &sCODECAPI_AVEncCommonMeanBitRate,
1025 "AVEncCommonMeanBitRate", VT_UI4, bitRate)))
1026 return mf_encoder_configure(h264, sys, width, height, frameRate, bitRate);
1027 sys->encBitRate = bitRate;
1028 }
1029
1030 if ((h264->RateControlMode == H264_RATECONTROL_CQP) && (sys->encQP != h264->QP))
1031 {
1032 if (FAILED(mf_encoder_set_value(h264, sys, &sCODECAPI_AVEncVideoEncodeQP,
1033 "AVEncVideoEncodeQP", VT_UI8, h264->QP & 0xFFFF)))
1034 return mf_encoder_configure(h264, sys, width, height, frameRate, bitRate);
1035 sys->encQP = h264->QP;
1036 }
1037
1038 return TRUE;
1039}
1040
1041/* I420 planes as FreeRDP holds them -> one NV12 buffer of width x height */
1042static void mf_encoder_copy_nv12(BYTE* WINPR_RESTRICT dst, const BYTE** WINPR_RESTRICT pSrcYuv,
1043 const UINT32* WINPR_RESTRICT pStride, UINT32 width, UINT32 height)
1044{
1045 WINPR_ASSERT(dst);
1046 WINPR_ASSERT(pSrcYuv);
1047 WINPR_ASSERT(pSrcYuv[0]);
1048 WINPR_ASSERT(pSrcYuv[1]);
1049 WINPR_ASSERT(pSrcYuv[2]);
1050 WINPR_ASSERT(pStride);
1051
1052 for (UINT32 y = 0; y < height; y++)
1053 memcpy(&dst[1ull * y * width], &pSrcYuv[0][1ull * y * pStride[0]], width);
1054
1055 BYTE* uv = &dst[1ull * width * height];
1056 for (UINT32 y = 0; y < height / 2; y++)
1057 {
1058 const BYTE* u = &pSrcYuv[1][1ull * y * pStride[1]];
1059 const BYTE* v = &pSrcYuv[2][1ull * y * pStride[2]];
1060 BYTE* line = &uv[1ull * y * width];
1061
1062 for (UINT32 x = 0; x < width / 2; x++)
1063 {
1064 line[2 * x] = u[x];
1065 line[2 * x + 1] = v[x];
1066 }
1067 }
1068}
1069
1070WINPR_ATTR_NODISCARD
1071static int mf_compress(H264_CONTEXT* WINPR_RESTRICT h264, const BYTE** WINPR_RESTRICT ppSrcYuv,
1072 const UINT32* WINPR_RESTRICT pStride, BYTE** WINPR_RESTRICT ppDstData,
1073 UINT32* WINPR_RESTRICT pDstSize)
1074{
1075 int rc = -1;
1076 BYTE* data = nullptr;
1077 DWORD maxLength = 0;
1078 DWORD curLength = 0;
1079 IMFSample* sample = nullptr;
1080 IMFMediaBuffer* buffer = nullptr;
1081
1082 WINPR_ASSERT(h264);
1083 WINPR_ASSERT(ppSrcYuv);
1084 WINPR_ASSERT(pStride);
1085 WINPR_ASSERT(ppDstData);
1086 WINPR_ASSERT(pDstSize);
1087
1088 H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)h264->pSystemData;
1089 if (!sys || !ppSrcYuv[0] || !ppSrcYuv[1] || !ppSrcYuv[2])
1090 return -1;
1091
1092 if ((h264->width == 0) || (h264->height == 0) || (h264->width > UINT16_MAX) ||
1093 (h264->height > UINT16_MAX))
1094 return -1;
1095
1096 /* 4:2:0 needs even sizes; the YUV buffers are padded (yuv_ensure_buffer), so one more
1097 * column or row is readable */
1098 const UINT32 width = (h264->width + 1) & ~1u;
1099 const UINT32 height = (h264->height + 1) & ~1u;
1100 const UINT32 frameRate = (h264->FrameRate > 0) ? h264->FrameRate : 30;
1101 const UINT32 bitRate = (h264->BitRate > 0) ? h264->BitRate : 1000000;
1102
1103 /* one NV12 frame; the encoder's output buffer is sized the same way */
1104 const UINT64 size64 = 3ull * width * height / 2;
1105 if (size64 > UINT32_MAX)
1106 return -1;
1107 const DWORD size = (DWORD)size64;
1108
1109 if (!mf_encoder_update(h264, sys, width, height, frameRate, bitRate))
1110 return -1;
1111
1112 HRESULT hr = sys->MFCreateMemoryBuffer(size, &buffer);
1113 if (FAILED(hr))
1114 {
1115 WLog_Print(h264->log, WLOG_ERROR, "MFCreateMemoryBuffer failure: 0x%08" PRIX32 "",
1116 (UINT32)hr);
1117 goto out;
1118 }
1119
1120 hr = buffer->lpVtbl->Lock(buffer, &data, &maxLength, &curLength);
1121 if (FAILED(hr))
1122 {
1123 WLog_Print(h264->log, WLOG_ERROR, "Lock failure: 0x%08" PRIX32 "", (UINT32)hr);
1124 goto out;
1125 }
1126
1127 const BOOL fits = (maxLength >= size);
1128 if (fits)
1129 mf_encoder_copy_nv12(data, ppSrcYuv, pStride, width, height);
1130
1131 /* one Unlock for both outcomes, so its result is checked on every path */
1132 hr = buffer->lpVtbl->Unlock(buffer);
1133 if (FAILED(hr))
1134 {
1135 WLog_Print(h264->log, WLOG_ERROR, "Unlock failure: 0x%08" PRIX32 "", (UINT32)hr);
1136 goto out;
1137 }
1138 if (!fits)
1139 {
1140 WLog_Print(h264->log, WLOG_ERROR,
1141 "encoder input buffer too small: %" PRIu32 " < %" PRIu32 " bytes",
1142 (UINT32)maxLength, (UINT32)size);
1143 goto out;
1144 }
1145
1146 hr = buffer->lpVtbl->SetCurrentLength(buffer, size);
1147 if (SUCCEEDED(hr))
1148 hr = sys->MFCreateSample(&sample);
1149 if (SUCCEEDED(hr))
1150 hr = sample->lpVtbl->AddBuffer(sample, buffer);
1151
1152 /* 100 ns units; the encoder's rate control works from these */
1153 const LONGLONG duration = 10000000LL / frameRate;
1154 if (SUCCEEDED(hr))
1155 hr = sample->lpVtbl->SetSampleTime(sample, sys->encSampleTime);
1156 if (SUCCEEDED(hr))
1157 hr = sample->lpVtbl->SetSampleDuration(sample, duration);
1158 if (FAILED(hr))
1159 {
1160 WLog_Print(h264->log, WLOG_ERROR, "encoder input sample failure: 0x%08" PRIX32 "",
1161 (UINT32)hr);
1162 goto out;
1163 }
1164 sys->encSampleTime += duration;
1165
1166 sys->encDataLength = 0;
1167 hr = sys->transform->lpVtbl->ProcessInput(sys->transform, 0, sample, 0);
1168 if (hr == MF_E_NOTACCEPTING)
1169 {
1170 /* output is waiting first: take it, then the input fits */
1171 hr = mf_encoder_drain(h264, sys);
1172 if (SUCCEEDED(hr))
1173 hr = sys->transform->lpVtbl->ProcessInput(sys->transform, 0, sample, 0);
1174 }
1175 if (FAILED(hr))
1176 {
1177 WLog_Print(h264->log, WLOG_ERROR, "encoder ProcessInput failure: 0x%08" PRIX32 "",
1178 (UINT32)hr);
1179 goto out;
1180 }
1181
1182 hr = mf_encoder_drain(h264, sys);
1183 if (FAILED(hr))
1184 goto out;
1185
1186 if (sys->encDataLength > UINT32_MAX)
1187 goto out;
1188
1189 if (sys->encDataLength == 0)
1190 {
1191 /* the encoder kept the frame (pipeline delay); nothing to send yet. Microsoft's
1192 * encoder in low latency mode never does; an encoder that ignores the mode (look
1193 * ahead, B frames) shows every change late, so say so once */
1194 sys->encHeld++;
1195 if ((sys->encHeld == 4) && !sys->encWarned)
1196 {
1197 WLog_Print(h264->log, WLOG_WARN,
1198 "MediaFoundation H264 encoder holds frames back (low latency mode not "
1199 "honoured), the client will see changes late");
1200 sys->encWarned = TRUE;
1201 }
1202 rc = 0;
1203 goto out;
1204 }
1205
1206 sys->encHeld = 0;
1207 *ppDstData = sys->encData;
1208 *pDstSize = (UINT32)sys->encDataLength;
1209 rc = 1;
1210
1211out:
1212 if (sample)
1213 sample->lpVtbl->Release(sample);
1214 if (buffer)
1215 buffer->lpVtbl->Release(buffer);
1216 return rc;
1217}
1218
1219WINPR_ATTR_NODISCARD
1220static BOOL mf_plat_loaded(H264_CONTEXT_MF* sys)
1221{
1222 WINPR_ASSERT(sys);
1223 return sys->MFStartup && sys->MFShutdown && sys->MFCreateSample && sys->MFCreateMemoryBuffer &&
1224 sys->MFCreateMediaType;
1225}
1226
1227static void mf_uninit(H264_CONTEXT* h264)
1228{
1229 WINPR_ASSERT(h264);
1230
1231 H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)h264->pSystemData;
1232
1233 if (sys)
1234 {
1235 if (sys->transform)
1236 {
1237 sys->transform->lpVtbl->Release(sys->transform);
1238 sys->transform = nullptr;
1239 }
1240
1241 if (sys->codecApi)
1242 {
1243 sys->codecApi->lpVtbl->Release(sys->codecApi);
1244 sys->codecApi = nullptr;
1245 }
1246
1247 if (sys->inputType)
1248 {
1249 sys->inputType->lpVtbl->Release(sys->inputType);
1250 sys->inputType = nullptr;
1251 }
1252
1253 if (sys->outputType)
1254 {
1255 sys->outputType->lpVtbl->Release(sys->outputType);
1256 sys->outputType = nullptr;
1257 }
1258
1259 if (sys->outputSample)
1260 {
1261 sys->outputSample->lpVtbl->Release(sys->outputSample);
1262 sys->outputSample = nullptr;
1263 }
1264
1265 if (sys->mfplat)
1266 {
1267 if (mf_plat_loaded(sys))
1268 {
1269 /* nothing to undo in a cleanup path, say so and go on */
1270 const HRESULT hr = sys->MFShutdown();
1271 if (FAILED(hr))
1272 WLog_Print(h264->log, WLOG_WARN, "MFShutdown failure: 0x%08" PRIX32 "",
1273 (UINT32)hr);
1274 }
1275
1276 FreeLibrary(sys->mfplat);
1277 sys->mfplat = nullptr;
1278
1279 if (mf_plat_loaded(sys))
1280 CoUninitialize();
1281 }
1282
1283 for (size_t x = 0; x < sizeof(h264->pYUVData) / sizeof(h264->pYUVData[0]); x++)
1284 winpr_aligned_free(h264->pYUVData[x]);
1285
1286 memset(h264->pYUVData, 0, sizeof(h264->pYUVData));
1287 memset(h264->iStride, 0, sizeof(h264->iStride));
1288
1289 free(sys->encData);
1290 free(sys);
1291 h264->pSystemData = nullptr;
1292 }
1293}
1294
1295WINPR_ATTR_NODISCARD
1296static BOOL mf_init(H264_CONTEXT* h264)
1297{
1298 HRESULT hr;
1299
1300 WINPR_ASSERT(h264);
1301
1302 H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)calloc(1, sizeof(H264_CONTEXT_MF));
1303
1304 if (!sys)
1305 goto error;
1306
1307 h264->pSystemData = (void*)sys;
1308 /* http://decklink-sdk-delphi.googlecode.com/svn/trunk/Blackmagic%20DeckLink%20SDK%209.7/Win/Samples/Streaming/StreamingPreview/DecoderMF.cpp
1309 */
1310 sys->mfplat = LoadLibraryA("mfplat.dll");
1311
1312 if (!sys->mfplat)
1313 goto error;
1314
1315 sys->MFStartup = GetProcAddressAs(sys->mfplat, "MFStartup", pfnMFStartup);
1316 sys->MFShutdown = GetProcAddressAs(sys->mfplat, "MFShutdown", pfnMFShutdown);
1317 sys->MFCreateSample = GetProcAddressAs(sys->mfplat, "MFCreateSample", pfnMFCreateSample);
1318 sys->MFCreateMemoryBuffer =
1319 GetProcAddressAs(sys->mfplat, "MFCreateMemoryBuffer", pfnMFCreateMemoryBuffer);
1320 sys->MFCreateMediaType =
1321 GetProcAddressAs(sys->mfplat, "MFCreateMediaType", pfnMFCreateMediaType);
1322
1323 if (!mf_plat_loaded(sys))
1324 goto error;
1325
1326 CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
1327
1328 if (h264->Compressor)
1329 {
1330 hr = sys->MFStartup(MF_VERSION, 0);
1331
1332 if (FAILED(hr))
1333 {
1334 WLog_Print(h264->log, WLOG_ERROR, "MFStartup failure: 0x%08" PRIX32 "", hr);
1335 goto error;
1336 }
1337
1338 /* the media types need the frame size: they are set on the first frame (mf_compress).
1339 * Creating the encoder now lets h264_context_init() try the next subsystem when this
1340 * Windows has none (N editions, Server without the Media Foundation feature). */
1341 if (!mf_encoder_create(h264, sys))
1342 {
1343 WLog_Print(h264->log, WLOG_WARN, "MediaFoundation H264 encoder not available");
1344 goto error;
1345 }
1346 }
1347 else
1348 {
1349 VARIANT var = WINPR_C_ARRAY_INIT;
1350 hr = sys->MFStartup(MF_VERSION, 0);
1351
1352 if (FAILED(hr))
1353 {
1354 WLog_Print(h264->log, WLOG_ERROR, "MFStartup failure: 0x%08" PRIX32 "", hr);
1355 goto error;
1356 }
1357
1358 hr = CoCreateInstance(&sCLSID_CMSH264DecoderMFT, nullptr, CLSCTX_INPROC_SERVER,
1359 &sIID_IMFTransform, (void**)&sys->transform);
1360
1361 if (FAILED(hr))
1362 {
1363 WLog_Print(h264->log, WLOG_ERROR,
1364 "CoCreateInstance(CLSID_CMSH264DecoderMFT) failure: 0x%08" PRIX32 "", hr);
1365 goto error;
1366 }
1367
1368 hr = sys->transform->lpVtbl->QueryInterface(sys->transform, &sIID_ICodecAPI,
1369 (void**)&sys->codecApi);
1370
1371 if (FAILED(hr))
1372 {
1373 WLog_Print(h264->log, WLOG_ERROR,
1374 "QueryInterface(IID_ICodecAPI) failure: 0x%08" PRIX32 "", hr);
1375 goto error;
1376 }
1377
1378 var.n1.n2.vt = VT_UI4;
1379 var.n1.n2.n3.ulVal = 1;
1380 hr = sys->codecApi->lpVtbl->SetValue(sys->codecApi, &sCODECAPI_AVLowLatencyMode, &var);
1381
1382 if (FAILED(hr))
1383 {
1384 WLog_Print(h264->log, WLOG_ERROR,
1385 "SetValue(CODECAPI_AVLowLatencyMode) failure: 0x%08" PRIX32 "", hr);
1386 goto error;
1387 }
1388
1389 hr = sys->MFCreateMediaType(&sys->inputType);
1390
1391 if (FAILED(hr))
1392 {
1393 WLog_Print(h264->log, WLOG_ERROR, "MFCreateMediaType failure: 0x%08" PRIX32 "", hr);
1394 goto error;
1395 }
1396
1397 hr = sys->inputType->lpVtbl->SetGUID(sys->inputType, &sMF_MT_MAJOR_TYPE,
1398 &sMFMediaType_Video);
1399
1400 if (FAILED(hr))
1401 {
1402 WLog_Print(h264->log, WLOG_ERROR, "SetGUID(MF_MT_MAJOR_TYPE) failure: 0x%08" PRIX32 "",
1403 hr);
1404 goto error;
1405 }
1406
1407 hr = sys->inputType->lpVtbl->SetGUID(sys->inputType, &sMF_MT_SUBTYPE, &sMFVideoFormat_H264);
1408
1409 if (FAILED(hr))
1410 {
1411 WLog_Print(h264->log, WLOG_ERROR, "SetGUID(MF_MT_SUBTYPE) failure: 0x%08" PRIX32 "",
1412 hr);
1413 goto error;
1414 }
1415
1416 hr = sys->transform->lpVtbl->SetInputType(sys->transform, 0, sys->inputType, 0);
1417
1418 if (FAILED(hr))
1419 {
1420 WLog_Print(h264->log, WLOG_ERROR, "SetInputType failure: 0x%08" PRIX32 "", hr);
1421 goto error;
1422 }
1423
1424 hr = mf_find_output_type(sys, &sMFVideoFormat_IYUV, &sys->outputType);
1425
1426 if (FAILED(hr))
1427 {
1428 WLog_Print(h264->log, WLOG_ERROR, "mf_find_output_type failure: 0x%08" PRIX32 "", hr);
1429 goto error;
1430 }
1431
1432 hr = sys->transform->lpVtbl->SetOutputType(sys->transform, 0, sys->outputType, 0);
1433
1434 if (FAILED(hr))
1435 {
1436 WLog_Print(h264->log, WLOG_ERROR, "SetOutputType failure: 0x%08" PRIX32 "", hr);
1437 goto error;
1438 }
1439
1440 hr = mf_create_output_sample(h264, sys);
1441
1442 if (FAILED(hr))
1443 {
1444 WLog_Print(h264->log, WLOG_ERROR, "mf_create_output_sample failure: 0x%08" PRIX32 "",
1445 hr);
1446 goto error;
1447 }
1448 }
1449
1450 return TRUE;
1451error:
1452 WLog_Print(h264->log, WLOG_ERROR, "mf_init failure");
1453 mf_uninit(h264);
1454 return FALSE;
1455}
1456
1457const H264_CONTEXT_SUBSYSTEM g_Subsystem_MF = { "MediaFoundation", mf_init, mf_uninit,
1458 mf_decompress, mf_compress };