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 /* the encoder's default is a keyframe every second (30 frames); over RDP's reliable
724 * transport a new encoder starts with one and that is enough, as with OpenH264, so ask for
725 * a long interval (65535 frames, 36 minutes at 30 fps); an encoder without the property
726 * keeps its own */
727 if (!mf_encoder_set_optional(h264, sys, &sCODECAPI_AVEncMPVGOPSize, "AVEncMPVGOPSize", VT_UI4,
728 UINT16_MAX))
729 return FALSE;
730
731 switch (h264->RateControlMode)
732 {
733 case H264_RATECONTROL_CQP:
734 {
735 if (!mf_encoder_set_optional(h264, sys, &sCODECAPI_AVEncCommonRateControlMode,
736 "AVEncCommonRateControlMode", VT_UI4,
737 FREERDP_MF_RC_QUALITY))
738 return FALSE;
739 /* AVEncVideoEncodeQP: the QP in the low 16 bits; older encoders only have the
740 * 0..100 quality scale, so when the QP is refused for any reason map it (0..51,
741 * lower is better) onto that scale; the quality scale's result decides */
742 if (FAILED(mf_encoder_set_value(h264, sys, &sCODECAPI_AVEncVideoEncodeQP,
743 "AVEncVideoEncodeQP", VT_UI8, h264->QP & 0xFFFF)))
744 {
745 const UINT32 qp = MIN(h264->QP, 51);
746 if (!mf_encoder_set_optional(h264, sys, &sCODECAPI_AVEncCommonQuality,
747 "AVEncCommonQuality", VT_UI4, 100 - (qp * 100 / 51)))
748 return FALSE;
749 }
750 }
751 break;
752
753 case H264_RATECONTROL_VBR:
754 default:
755 /* a target bit rate, like OpenH264's RC_BITRATE_MODE; the bit rate is also on the
756 * output type (MF_MT_AVG_BITRATE), which every encoder reads */
757 if (!mf_encoder_set_optional(h264, sys, &sCODECAPI_AVEncCommonRateControlMode,
758 "AVEncCommonRateControlMode", VT_UI4, FREERDP_MF_RC_CBR))
759 return FALSE;
760 if (!mf_encoder_set_optional(h264, sys, &sCODECAPI_AVEncCommonMeanBitRate,
761 "AVEncCommonMeanBitRate", VT_UI4, bitRate))
762 return FALSE;
763 break;
764 }
765
766 return TRUE;
767}
768
769WINPR_ATTR_NODISCARD
770static BOOL mf_encoder_configure(H264_CONTEXT* h264, H264_CONTEXT_MF* sys, UINT32 width,
771 UINT32 height, UINT32 frameRate, UINT32 bitRate)
772{
773 MFT_OUTPUT_STREAM_INFO streamInfo = WINPR_C_ARRAY_INIT;
774
775 WINPR_ASSERT(h264);
776 WINPR_ASSERT(sys);
777
778 /* the types of a running encoder can't change, start a new one */
779 if (sys->encStreaming || !sys->transform)
780 {
781 mf_encoder_release(sys);
782 if (!mf_encoder_create(h264, sys))
783 return FALSE;
784 }
785
786 /* rate control is read when the output type is set, so it goes first */
787 if (!mf_encoder_rate_control(h264, sys, bitRate))
788 goto fail;
789
790 /* the H.264 encoder wants the output type before the input type */
791 HRESULT hr = mf_encoder_media_type(sys, &sys->outputType, &sMFVideoFormat_H264, width, height,
792 frameRate, bitRate);
793 if (FAILED(hr))
794 {
795 WLog_Print(h264->log, WLOG_ERROR, "encoder output type failure: 0x%08" PRIX32 "",
796 (UINT32)hr);
797 goto fail;
798 }
799
800 hr = sys->transform->lpVtbl->SetOutputType(sys->transform, 0, sys->outputType, 0);
801 if (FAILED(hr))
802 {
803 WLog_Print(h264->log, WLOG_ERROR, "encoder SetOutputType failure: 0x%08" PRIX32 "",
804 (UINT32)hr);
805 goto fail;
806 }
807
808 hr = mf_encoder_media_type(sys, &sys->inputType, &sMFVideoFormat_NV12, width, height, frameRate,
809 bitRate);
810 if (FAILED(hr))
811 {
812 WLog_Print(h264->log, WLOG_ERROR, "encoder input type failure: 0x%08" PRIX32 "",
813 (UINT32)hr);
814 goto fail;
815 }
816
817 hr = sys->transform->lpVtbl->SetInputType(sys->transform, 0, sys->inputType, 0);
818 if (FAILED(hr))
819 {
820 WLog_Print(h264->log, WLOG_ERROR, "encoder SetInputType failure: 0x%08" PRIX32 "",
821 (UINT32)hr);
822 goto fail;
823 }
824
825 hr = sys->transform->lpVtbl->GetOutputStreamInfo(sys->transform, 0, &streamInfo);
826 if (FAILED(hr))
827 {
828 WLog_Print(h264->log, WLOG_ERROR, "encoder GetOutputStreamInfo failure: 0x%08" PRIX32 "",
829 (UINT32)hr);
830 goto fail;
831 }
832
833 sys->encOutputFlags = streamInfo.dwFlags;
834 sys->encOutputSize = streamInfo.cbSize;
835 if (sys->encOutputSize == 0)
836 sys->encOutputSize = (DWORD)(3ull * width * height / 2); /* checked by mf_compress */
837
838 if ((sys->encOutputFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) == 0)
839 {
840 IMFMediaBuffer* buffer = nullptr;
841
842 hr = sys->MFCreateSample(&sys->outputSample);
843 if (SUCCEEDED(hr))
844 hr = sys->MFCreateMemoryBuffer(sys->encOutputSize, &buffer);
845 if (SUCCEEDED(hr))
846 hr = sys->outputSample->lpVtbl->AddBuffer(sys->outputSample, buffer);
847 if (buffer)
848 buffer->lpVtbl->Release(buffer);
849 if (FAILED(hr))
850 {
851 WLog_Print(h264->log, WLOG_ERROR, "encoder output sample failure: 0x%08" PRIX32 "",
852 (UINT32)hr);
853 goto fail;
854 }
855 }
856
857 hr = sys->transform->lpVtbl->ProcessMessage(sys->transform, MFT_MESSAGE_NOTIFY_BEGIN_STREAMING,
858 0);
859 if (SUCCEEDED(hr))
860 hr = sys->transform->lpVtbl->ProcessMessage(sys->transform,
861 MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0);
862 if (FAILED(hr))
863 {
864 WLog_Print(h264->log, WLOG_ERROR, "encoder ProcessMessage failure: 0x%08" PRIX32 "",
865 (UINT32)hr);
866 goto fail;
867 }
868
869 sys->encStreaming = TRUE;
870 sys->encWidth = width;
871 sys->encHeight = height;
872 sys->encFrameRate = frameRate;
873 sys->encBitRate = bitRate;
874 sys->encQP = h264->QP;
875 sys->encRateControl = h264->RateControlMode;
876 sys->encSampleTime = 0;
877 sys->encHeld = 0;
878 WLog_Print(h264->log, WLOG_DEBUG,
879 "MediaFoundation H264 encoder: %" PRIu32 "x%" PRIu32 " %" PRIu32
880 " fps, output flags 0x%08" PRIx32 "",
881 width, height, frameRate, (UINT32)sys->encOutputFlags);
882 return TRUE;
883
884fail:
885 mf_encoder_release(sys);
886 return FALSE;
887}
888
889WINPR_ATTR_NODISCARD
890static BOOL mf_encoder_append(H264_CONTEXT* h264, H264_CONTEXT_MF* sys, IMFSample* sample)
891{
892 IMFMediaBuffer* buffer = nullptr;
893 BYTE* data = nullptr;
894 DWORD maxLength = 0;
895 DWORD length = 0;
896 BOOL rc = FALSE;
897
898 WINPR_ASSERT(h264);
899 WINPR_ASSERT(sys);
900 WINPR_ASSERT(sample);
901
902 HRESULT hr = sample->lpVtbl->ConvertToContiguousBuffer(sample, &buffer);
903 if (FAILED(hr))
904 {
905 WLog_Print(h264->log, WLOG_ERROR, "ConvertToContiguousBuffer failure: 0x%08" PRIX32 "",
906 (UINT32)hr);
907 return FALSE;
908 }
909
910 hr = buffer->lpVtbl->Lock(buffer, &data, &maxLength, &length);
911 if (FAILED(hr))
912 {
913 WLog_Print(h264->log, WLOG_ERROR, "Lock failure: 0x%08" PRIX32 "", (UINT32)hr);
914 goto out;
915 }
916
917 BOOL copied = FALSE;
918 const size_t needed = sys->encDataLength + length;
919 if (needed > sys->encDataSize)
920 {
921 BYTE* tmp = realloc(sys->encData, needed);
922 if (tmp)
923 {
924 sys->encData = tmp;
925 sys->encDataSize = needed;
926 }
927 }
928 if (needed <= sys->encDataSize)
929 {
930 if (length > 0)
931 memcpy(&sys->encData[sys->encDataLength], data, length);
932 sys->encDataLength = needed;
933 copied = TRUE;
934 }
935
936 /* one Unlock for both outcomes, so its result is checked on every path */
937 hr = buffer->lpVtbl->Unlock(buffer);
938 if (FAILED(hr))
939 WLog_Print(h264->log, WLOG_ERROR, "Unlock failure: 0x%08" PRIX32 "", (UINT32)hr);
940 else if (!copied)
941 WLog_Print(h264->log, WLOG_ERROR, "encoder output: out of memory (%" PRIuz " bytes)",
942 needed);
943 else
944 rc = TRUE;
945
946out:
947 buffer->lpVtbl->Release(buffer);
948 return rc;
949}
950
951/* collect everything the encoder has ready; MF_E_TRANSFORM_NEED_MORE_INPUT ends it */
952WINPR_ATTR_NODISCARD
953static HRESULT mf_encoder_drain(H264_CONTEXT* h264, H264_CONTEXT_MF* sys)
954{
955 WINPR_ASSERT(h264);
956 WINPR_ASSERT(sys);
957 WINPR_ASSERT(sys->transform);
958
959 const BOOL provides = (sys->encOutputFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) != 0;
960
961 for (size_t i = 0; i < 16; i++)
962 {
963 DWORD status = 0;
964 MFT_OUTPUT_DATA_BUFFER output = WINPR_C_ARRAY_INIT;
965
966 if (!provides)
967 {
968 /* the encoder writes into our sample: give it an empty buffer each time */
969 IMFMediaBuffer* buffer = nullptr;
970
971 WINPR_ASSERT(sys->outputSample);
972 HRESULT hr = sys->outputSample->lpVtbl->GetBufferByIndex(sys->outputSample, 0, &buffer);
973 if (SUCCEEDED(hr))
974 {
975 hr = buffer->lpVtbl->SetCurrentLength(buffer, 0);
976 buffer->lpVtbl->Release(buffer);
977 }
978 if (FAILED(hr))
979 {
980 WLog_Print(h264->log, WLOG_ERROR,
981 "encoder output buffer reset failure: 0x%08" PRIX32 "", (UINT32)hr);
982 return hr;
983 }
984 output.pSample = sys->outputSample;
985 }
986
987 const HRESULT hr =
988 sys->transform->lpVtbl->ProcessOutput(sys->transform, 0, 1, &output, &status);
989 if (output.pEvents)
990 output.pEvents->lpVtbl->Release(output.pEvents);
991
992 if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT)
993 return S_OK;
994
995 if (FAILED(hr))
996 {
997 if (provides && output.pSample)
998 output.pSample->lpVtbl->Release(output.pSample);
999 WLog_Print(h264->log, WLOG_ERROR, "encoder ProcessOutput failure: 0x%08" PRIX32 "",
1000 (UINT32)hr);
1001 return hr;
1002 }
1003
1004 const BOOL ok = output.pSample ? mf_encoder_append(h264, sys, output.pSample) : TRUE;
1005 if (provides && output.pSample)
1006 output.pSample->lpVtbl->Release(output.pSample);
1007 if (!ok)
1008 return E_FAIL; /* mf_encoder_append logged why */
1009 }
1010
1011 return S_OK;
1012}
1013
1014WINPR_ATTR_NODISCARD
1015static BOOL mf_encoder_update(H264_CONTEXT* h264, H264_CONTEXT_MF* sys, UINT32 width, UINT32 height,
1016 UINT32 frameRate, UINT32 bitRate)
1017{
1018 WINPR_ASSERT(h264);
1019 WINPR_ASSERT(sys);
1020
1021 if (!sys->encStreaming || (sys->encWidth != width) || (sys->encHeight != height) ||
1022 (sys->encFrameRate != frameRate) || (sys->encRateControl != h264->RateControlMode))
1023 return mf_encoder_configure(h264, sys, width, height, frameRate, bitRate);
1024
1025 /* bit rate and QP can change while the encoder runs; start over where they can't */
1026 if ((h264->RateControlMode == H264_RATECONTROL_VBR) && (sys->encBitRate != bitRate))
1027 {
1028 if (FAILED(mf_encoder_set_value(h264, sys, &sCODECAPI_AVEncCommonMeanBitRate,
1029 "AVEncCommonMeanBitRate", VT_UI4, bitRate)))
1030 return mf_encoder_configure(h264, sys, width, height, frameRate, bitRate);
1031 sys->encBitRate = bitRate;
1032 }
1033
1034 if ((h264->RateControlMode == H264_RATECONTROL_CQP) && (sys->encQP != h264->QP))
1035 {
1036 if (FAILED(mf_encoder_set_value(h264, sys, &sCODECAPI_AVEncVideoEncodeQP,
1037 "AVEncVideoEncodeQP", VT_UI8, h264->QP & 0xFFFF)))
1038 return mf_encoder_configure(h264, sys, width, height, frameRate, bitRate);
1039 sys->encQP = h264->QP;
1040 }
1041
1042 return TRUE;
1043}
1044
1045/* I420 planes as FreeRDP holds them -> one NV12 buffer of width x height */
1046static void mf_encoder_copy_nv12(BYTE* WINPR_RESTRICT dst, const BYTE** WINPR_RESTRICT pSrcYuv,
1047 const UINT32* WINPR_RESTRICT pStride, UINT32 width, UINT32 height)
1048{
1049 WINPR_ASSERT(dst);
1050 WINPR_ASSERT(pSrcYuv);
1051 WINPR_ASSERT(pSrcYuv[0]);
1052 WINPR_ASSERT(pSrcYuv[1]);
1053 WINPR_ASSERT(pSrcYuv[2]);
1054 WINPR_ASSERT(pStride);
1055
1056 for (UINT32 y = 0; y < height; y++)
1057 memcpy(&dst[1ull * y * width], &pSrcYuv[0][1ull * y * pStride[0]], width);
1058
1059 BYTE* uv = &dst[1ull * width * height];
1060 for (UINT32 y = 0; y < height / 2; y++)
1061 {
1062 const BYTE* u = &pSrcYuv[1][1ull * y * pStride[1]];
1063 const BYTE* v = &pSrcYuv[2][1ull * y * pStride[2]];
1064 BYTE* line = &uv[1ull * y * width];
1065
1066 for (UINT32 x = 0; x < width / 2; x++)
1067 {
1068 line[2 * x] = u[x];
1069 line[2 * x + 1] = v[x];
1070 }
1071 }
1072}
1073
1074WINPR_ATTR_NODISCARD
1075static int mf_compress(H264_CONTEXT* WINPR_RESTRICT h264, const BYTE** WINPR_RESTRICT ppSrcYuv,
1076 const UINT32* WINPR_RESTRICT pStride, BYTE** WINPR_RESTRICT ppDstData,
1077 UINT32* WINPR_RESTRICT pDstSize)
1078{
1079 int rc = -1;
1080 BYTE* data = nullptr;
1081 DWORD maxLength = 0;
1082 DWORD curLength = 0;
1083 IMFSample* sample = nullptr;
1084 IMFMediaBuffer* buffer = nullptr;
1085
1086 WINPR_ASSERT(h264);
1087 WINPR_ASSERT(ppSrcYuv);
1088 WINPR_ASSERT(pStride);
1089 WINPR_ASSERT(ppDstData);
1090 WINPR_ASSERT(pDstSize);
1091
1092 H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)h264->pSystemData;
1093 if (!sys || !ppSrcYuv[0] || !ppSrcYuv[1] || !ppSrcYuv[2])
1094 return -1;
1095
1096 if ((h264->width == 0) || (h264->height == 0) || (h264->width > UINT16_MAX) ||
1097 (h264->height > UINT16_MAX))
1098 return -1;
1099
1100 /* 4:2:0 needs even sizes; the YUV buffers are padded (yuv_ensure_buffer), so one more
1101 * column or row is readable */
1102 const UINT32 width = (h264->width + 1) & ~1u;
1103 const UINT32 height = (h264->height + 1) & ~1u;
1104 const UINT32 frameRate = (h264->FrameRate > 0) ? h264->FrameRate : 30;
1105 const UINT32 bitRate = (h264->BitRate > 0) ? h264->BitRate : 1000000;
1106
1107 /* one NV12 frame; the encoder's output buffer is sized the same way */
1108 const UINT64 size64 = 3ull * width * height / 2;
1109 if (size64 > UINT32_MAX)
1110 return -1;
1111 const DWORD size = (DWORD)size64;
1112
1113 if (!mf_encoder_update(h264, sys, width, height, frameRate, bitRate))
1114 return -1;
1115
1116 HRESULT hr = sys->MFCreateMemoryBuffer(size, &buffer);
1117 if (FAILED(hr))
1118 {
1119 WLog_Print(h264->log, WLOG_ERROR, "MFCreateMemoryBuffer failure: 0x%08" PRIX32 "",
1120 (UINT32)hr);
1121 goto out;
1122 }
1123
1124 hr = buffer->lpVtbl->Lock(buffer, &data, &maxLength, &curLength);
1125 if (FAILED(hr))
1126 {
1127 WLog_Print(h264->log, WLOG_ERROR, "Lock failure: 0x%08" PRIX32 "", (UINT32)hr);
1128 goto out;
1129 }
1130
1131 const BOOL fits = (maxLength >= size);
1132 if (fits)
1133 mf_encoder_copy_nv12(data, ppSrcYuv, pStride, width, height);
1134
1135 /* one Unlock for both outcomes, so its result is checked on every path */
1136 hr = buffer->lpVtbl->Unlock(buffer);
1137 if (FAILED(hr))
1138 {
1139 WLog_Print(h264->log, WLOG_ERROR, "Unlock failure: 0x%08" PRIX32 "", (UINT32)hr);
1140 goto out;
1141 }
1142 if (!fits)
1143 {
1144 WLog_Print(h264->log, WLOG_ERROR,
1145 "encoder input buffer too small: %" PRIu32 " < %" PRIu32 " bytes",
1146 (UINT32)maxLength, (UINT32)size);
1147 goto out;
1148 }
1149
1150 hr = buffer->lpVtbl->SetCurrentLength(buffer, size);
1151 if (SUCCEEDED(hr))
1152 hr = sys->MFCreateSample(&sample);
1153 if (SUCCEEDED(hr))
1154 hr = sample->lpVtbl->AddBuffer(sample, buffer);
1155
1156 /* 100 ns units; the encoder's rate control works from these */
1157 const LONGLONG duration = 10000000LL / frameRate;
1158 if (SUCCEEDED(hr))
1159 hr = sample->lpVtbl->SetSampleTime(sample, sys->encSampleTime);
1160 if (SUCCEEDED(hr))
1161 hr = sample->lpVtbl->SetSampleDuration(sample, duration);
1162 if (FAILED(hr))
1163 {
1164 WLog_Print(h264->log, WLOG_ERROR, "encoder input sample failure: 0x%08" PRIX32 "",
1165 (UINT32)hr);
1166 goto out;
1167 }
1168 sys->encSampleTime += duration;
1169
1170 sys->encDataLength = 0;
1171 hr = sys->transform->lpVtbl->ProcessInput(sys->transform, 0, sample, 0);
1172 if (hr == MF_E_NOTACCEPTING)
1173 {
1174 /* output is waiting first: take it, then the input fits */
1175 hr = mf_encoder_drain(h264, sys);
1176 if (SUCCEEDED(hr))
1177 hr = sys->transform->lpVtbl->ProcessInput(sys->transform, 0, sample, 0);
1178 }
1179 if (FAILED(hr))
1180 {
1181 WLog_Print(h264->log, WLOG_ERROR, "encoder ProcessInput failure: 0x%08" PRIX32 "",
1182 (UINT32)hr);
1183 goto out;
1184 }
1185
1186 hr = mf_encoder_drain(h264, sys);
1187 if (FAILED(hr))
1188 goto out;
1189
1190 if (sys->encDataLength > UINT32_MAX)
1191 goto out;
1192
1193 if (sys->encDataLength == 0)
1194 {
1195 /* the encoder kept the frame (pipeline delay); nothing to send yet. Microsoft's
1196 * encoder in low latency mode never does; an encoder that ignores the mode (look
1197 * ahead, B frames) shows every change late, so say so once */
1198 sys->encHeld++;
1199 if ((sys->encHeld == 4) && !sys->encWarned)
1200 {
1201 WLog_Print(h264->log, WLOG_WARN,
1202 "MediaFoundation H264 encoder holds frames back (low latency mode not "
1203 "honoured), the client will see changes late");
1204 sys->encWarned = TRUE;
1205 }
1206 rc = 0;
1207 goto out;
1208 }
1209
1210 sys->encHeld = 0;
1211 *ppDstData = sys->encData;
1212 *pDstSize = (UINT32)sys->encDataLength;
1213 rc = 1;
1214
1215out:
1216 if (sample)
1217 sample->lpVtbl->Release(sample);
1218 if (buffer)
1219 buffer->lpVtbl->Release(buffer);
1220 return rc;
1221}
1222
1223WINPR_ATTR_NODISCARD
1224static BOOL mf_plat_loaded(H264_CONTEXT_MF* sys)
1225{
1226 WINPR_ASSERT(sys);
1227 return sys->MFStartup && sys->MFShutdown && sys->MFCreateSample && sys->MFCreateMemoryBuffer &&
1228 sys->MFCreateMediaType;
1229}
1230
1231static void mf_uninit(H264_CONTEXT* h264)
1232{
1233 WINPR_ASSERT(h264);
1234
1235 H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)h264->pSystemData;
1236
1237 if (sys)
1238 {
1239 if (sys->transform)
1240 {
1241 sys->transform->lpVtbl->Release(sys->transform);
1242 sys->transform = nullptr;
1243 }
1244
1245 if (sys->codecApi)
1246 {
1247 sys->codecApi->lpVtbl->Release(sys->codecApi);
1248 sys->codecApi = nullptr;
1249 }
1250
1251 if (sys->inputType)
1252 {
1253 sys->inputType->lpVtbl->Release(sys->inputType);
1254 sys->inputType = nullptr;
1255 }
1256
1257 if (sys->outputType)
1258 {
1259 sys->outputType->lpVtbl->Release(sys->outputType);
1260 sys->outputType = nullptr;
1261 }
1262
1263 if (sys->outputSample)
1264 {
1265 sys->outputSample->lpVtbl->Release(sys->outputSample);
1266 sys->outputSample = nullptr;
1267 }
1268
1269 if (sys->mfplat)
1270 {
1271 if (mf_plat_loaded(sys))
1272 {
1273 /* nothing to undo in a cleanup path, say so and go on */
1274 const HRESULT hr = sys->MFShutdown();
1275 if (FAILED(hr))
1276 WLog_Print(h264->log, WLOG_WARN, "MFShutdown failure: 0x%08" PRIX32 "",
1277 (UINT32)hr);
1278 }
1279
1280 FreeLibrary(sys->mfplat);
1281 sys->mfplat = nullptr;
1282
1283 if (mf_plat_loaded(sys))
1284 CoUninitialize();
1285 }
1286
1287 for (size_t x = 0; x < sizeof(h264->pYUVData) / sizeof(h264->pYUVData[0]); x++)
1288 winpr_aligned_free(h264->pYUVData[x]);
1289
1290 memset(h264->pYUVData, 0, sizeof(h264->pYUVData));
1291 memset(h264->iStride, 0, sizeof(h264->iStride));
1292
1293 free(sys->encData);
1294 free(sys);
1295 h264->pSystemData = nullptr;
1296 }
1297}
1298
1299WINPR_ATTR_NODISCARD
1300static BOOL mf_init(H264_CONTEXT* h264)
1301{
1302 HRESULT hr;
1303
1304 WINPR_ASSERT(h264);
1305
1306 H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)calloc(1, sizeof(H264_CONTEXT_MF));
1307
1308 if (!sys)
1309 goto error;
1310
1311 h264->pSystemData = (void*)sys;
1312 /* http://decklink-sdk-delphi.googlecode.com/svn/trunk/Blackmagic%20DeckLink%20SDK%209.7/Win/Samples/Streaming/StreamingPreview/DecoderMF.cpp
1313 */
1314 sys->mfplat = LoadLibraryA("mfplat.dll");
1315
1316 if (!sys->mfplat)
1317 goto error;
1318
1319 sys->MFStartup = GetProcAddressAs(sys->mfplat, "MFStartup", pfnMFStartup);
1320 sys->MFShutdown = GetProcAddressAs(sys->mfplat, "MFShutdown", pfnMFShutdown);
1321 sys->MFCreateSample = GetProcAddressAs(sys->mfplat, "MFCreateSample", pfnMFCreateSample);
1322 sys->MFCreateMemoryBuffer =
1323 GetProcAddressAs(sys->mfplat, "MFCreateMemoryBuffer", pfnMFCreateMemoryBuffer);
1324 sys->MFCreateMediaType =
1325 GetProcAddressAs(sys->mfplat, "MFCreateMediaType", pfnMFCreateMediaType);
1326
1327 if (!mf_plat_loaded(sys))
1328 goto error;
1329
1330 CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
1331
1332 if (h264->Compressor)
1333 {
1334 hr = sys->MFStartup(MF_VERSION, 0);
1335
1336 if (FAILED(hr))
1337 {
1338 WLog_Print(h264->log, WLOG_ERROR, "MFStartup failure: 0x%08" PRIX32 "", hr);
1339 goto error;
1340 }
1341
1342 /* the media types need the frame size: they are set on the first frame (mf_compress).
1343 * Creating the encoder now lets h264_context_init() try the next subsystem when this
1344 * Windows has none (N editions, Server without the Media Foundation feature). */
1345 if (!mf_encoder_create(h264, sys))
1346 {
1347 WLog_Print(h264->log, WLOG_WARN, "MediaFoundation H264 encoder not available");
1348 goto error;
1349 }
1350 }
1351 else
1352 {
1353 VARIANT var = WINPR_C_ARRAY_INIT;
1354 hr = sys->MFStartup(MF_VERSION, 0);
1355
1356 if (FAILED(hr))
1357 {
1358 WLog_Print(h264->log, WLOG_ERROR, "MFStartup failure: 0x%08" PRIX32 "", hr);
1359 goto error;
1360 }
1361
1362 hr = CoCreateInstance(&sCLSID_CMSH264DecoderMFT, nullptr, CLSCTX_INPROC_SERVER,
1363 &sIID_IMFTransform, (void**)&sys->transform);
1364
1365 if (FAILED(hr))
1366 {
1367 WLog_Print(h264->log, WLOG_ERROR,
1368 "CoCreateInstance(CLSID_CMSH264DecoderMFT) failure: 0x%08" PRIX32 "", hr);
1369 goto error;
1370 }
1371
1372 hr = sys->transform->lpVtbl->QueryInterface(sys->transform, &sIID_ICodecAPI,
1373 (void**)&sys->codecApi);
1374
1375 if (FAILED(hr))
1376 {
1377 WLog_Print(h264->log, WLOG_ERROR,
1378 "QueryInterface(IID_ICodecAPI) failure: 0x%08" PRIX32 "", hr);
1379 goto error;
1380 }
1381
1382 var.n1.n2.vt = VT_UI4;
1383 var.n1.n2.n3.ulVal = 1;
1384 hr = sys->codecApi->lpVtbl->SetValue(sys->codecApi, &sCODECAPI_AVLowLatencyMode, &var);
1385
1386 if (FAILED(hr))
1387 {
1388 WLog_Print(h264->log, WLOG_ERROR,
1389 "SetValue(CODECAPI_AVLowLatencyMode) failure: 0x%08" PRIX32 "", hr);
1390 goto error;
1391 }
1392
1393 hr = sys->MFCreateMediaType(&sys->inputType);
1394
1395 if (FAILED(hr))
1396 {
1397 WLog_Print(h264->log, WLOG_ERROR, "MFCreateMediaType failure: 0x%08" PRIX32 "", hr);
1398 goto error;
1399 }
1400
1401 hr = sys->inputType->lpVtbl->SetGUID(sys->inputType, &sMF_MT_MAJOR_TYPE,
1402 &sMFMediaType_Video);
1403
1404 if (FAILED(hr))
1405 {
1406 WLog_Print(h264->log, WLOG_ERROR, "SetGUID(MF_MT_MAJOR_TYPE) failure: 0x%08" PRIX32 "",
1407 hr);
1408 goto error;
1409 }
1410
1411 hr = sys->inputType->lpVtbl->SetGUID(sys->inputType, &sMF_MT_SUBTYPE, &sMFVideoFormat_H264);
1412
1413 if (FAILED(hr))
1414 {
1415 WLog_Print(h264->log, WLOG_ERROR, "SetGUID(MF_MT_SUBTYPE) failure: 0x%08" PRIX32 "",
1416 hr);
1417 goto error;
1418 }
1419
1420 hr = sys->transform->lpVtbl->SetInputType(sys->transform, 0, sys->inputType, 0);
1421
1422 if (FAILED(hr))
1423 {
1424 WLog_Print(h264->log, WLOG_ERROR, "SetInputType failure: 0x%08" PRIX32 "", hr);
1425 goto error;
1426 }
1427
1428 hr = mf_find_output_type(sys, &sMFVideoFormat_IYUV, &sys->outputType);
1429
1430 if (FAILED(hr))
1431 {
1432 WLog_Print(h264->log, WLOG_ERROR, "mf_find_output_type failure: 0x%08" PRIX32 "", hr);
1433 goto error;
1434 }
1435
1436 hr = sys->transform->lpVtbl->SetOutputType(sys->transform, 0, sys->outputType, 0);
1437
1438 if (FAILED(hr))
1439 {
1440 WLog_Print(h264->log, WLOG_ERROR, "SetOutputType failure: 0x%08" PRIX32 "", hr);
1441 goto error;
1442 }
1443
1444 hr = mf_create_output_sample(h264, sys);
1445
1446 if (FAILED(hr))
1447 {
1448 WLog_Print(h264->log, WLOG_ERROR, "mf_create_output_sample failure: 0x%08" PRIX32 "",
1449 hr);
1450 goto error;
1451 }
1452 }
1453
1454 return TRUE;
1455error:
1456 WLog_Print(h264->log, WLOG_ERROR, "mf_init failure");
1457 mf_uninit(h264);
1458 return FALSE;
1459}
1460
1461const H264_CONTEXT_SUBSYSTEM g_Subsystem_MF = { "MediaFoundation", mf_init, mf_uninit,
1462 mf_decompress, mf_compress };