FreeRDP
Loading...
Searching...
No Matches
h264_mediacodec.c
1
21#include <freerdp/config.h>
22
23#include <winpr/wlog.h>
24#include <winpr/assert.h>
25#include <winpr/cast.h>
26#include <winpr/library.h>
27
28#include <freerdp/log.h>
29#include <freerdp/codec/h264.h>
30
31#include <media/NdkMediaCodec.h>
32#include <media/NdkMediaFormat.h>
33
34#include "h264.h"
35
36static const char* CODEC_NAME = "video/avc";
37
38static const int COLOR_FormatYUV420Planar = 19;
39static const int COLOR_FormatYUV420SemiPlanar = 21; /* NV12, widest encoder support */
40static const int COLOR_FormatYUV420Flexible = 0x7f420888;
41
42/* MediaFormat.KEY_PREPEND_HEADER_TO_SYNC_FRAMES has no NDK constant; emits SPS/PPS before every IDR
43 */
44static const char* MEDIACODEC_KEY_PREPEND_HEADER = "prepend-sps-pps-to-idr-frames";
45
46/* AMEDIAFORMAT_KEY_LOW_LATENCY has no NDK constant below API 30; the string works at runtime */
47static const char* MEDIACODEC_KEY_LOW_LATENCY = "low-latency";
48
49/* Output crop rectangle; inclusive bounds, no NDK constants exist for these */
50static const char* MEDIACODEC_KEY_CROP_LEFT = "crop-left";
51static const char* MEDIACODEC_KEY_CROP_RIGHT = "crop-right";
52static const char* MEDIACODEC_KEY_CROP_TOP = "crop-top";
53static const char* MEDIACODEC_KEY_CROP_BOTTOM = "crop-bottom";
54
55/* Dequeue timeouts in microseconds; output may lag a frame while the pipeline primes */
56static const int64_t MEDIACODEC_INPUT_DEQUEUE_TIMEOUT = 10000;
57static const int64_t MEDIACODEC_OUTPUT_DEQUEUE_TIMEOUT = 5000;
58
59/* Bound the decode dequeue retries so a stalled codec cannot spin forever. */
60static const int MEDIACODEC_DECODE_MAX_RETRIES = 100;
61
62/* https://developer.android.com/reference/android/media/MediaCodec#qualityFloor */
63static const int MEDIACODEC_MINIMUM_WIDTH = 320;
64static const int MEDIACODEC_MINIMUM_HEIGHT = 240;
65static const int32_t MEDIACODEC_MAXIMUM_DIMENSION = 16384;
66
67/* Keyframe interval in seconds; short so a late-joining server recovers quickly */
68static const int MEDIACODEC_IFRAME_INTERVAL = 1;
69/* Fallbacks if the context did not specify a rate. */
70static const int MEDIACODEC_DEFAULT_FRAMERATE = 30;
71static const int MEDIACODEC_DEFAULT_BITRATE = 4000000;
72
73/* MediaCodecInfo.EncoderCapabilities bitrate modes */
74static const int MEDIACODEC_BITRATE_MODE_VBR = 1;
75
76/* setParameters keys; no NDK constant below API 31, the strings work at runtime */
77static const char* MEDIACODEC_PARAM_VIDEO_BITRATE = "video-bitrate";
78
79typedef struct
80{
81 AMediaCodec* decoder;
82 AMediaCodec* encoder;
83 AMediaFormat* inputFormat;
84 AMediaFormat* outputFormat;
85 int32_t width;
86 int32_t height;
87 int32_t outputWidth;
88 int32_t outputHeight;
89 int32_t outputStride;
90 int32_t outputSliceHeight;
91 int32_t cropLeft;
92 int32_t cropTop;
93 int32_t colorFormat;
94 ssize_t currentOutputBufferIndex;
95
96 BOOL decoderStarted;
97
98 /* decoder deinterleave buffer for NV12 / semiplanar output */
99 BYTE* decodeI420Buffer;
100 size_t decodeI420Capacity;
101
102 /* encoder state */
103 BOOL encoderStarted;
104 int32_t encoderWidth; /* dimensions the encoder is currently configured for */
105 int32_t encoderHeight;
106 int32_t encoderStride; /* input buffer layout reported by the encoder */
107 int32_t encoderSliceHeight;
108 int32_t encoderBitrate;
109 BOOL syncRequested; /* an IDR was already asked for since the last configure */
110 int64_t frameIndex; /* monotonic source for presentation timestamps */
111 BYTE* outputData; /* staging copy of the most recent encoded frame */
112 size_t outputCapacity; /* allocated size of outputData */
113 int32_t encoderFailedWidth; /* size the encoder refused */
114 int32_t encoderFailedHeight;
115
116 /* encoder cached SPS/PPS header */
117 BYTE* spsPpsData;
118 size_t spsPpsSize;
119} H264_CONTEXT_MEDIACODEC;
120
121#define mediacodec_warn(log, status, what) \
122 mediacodec_warn_((log), (status), (what), __FILE__, __func__, __LINE__)
123
124static void mediacodec_warn_(wLog* log, media_status_t status, const char* what, const char* file,
125 const char* fkt, size_t line)
126{
127 if ((status != AMEDIA_OK) && WLog_IsLevelActive(log, WLOG_WARN))
128 WLog_PrintTextMessage(log, WLOG_WARN, line, file, fkt, "%s failed: %d", what, status);
129}
130
131WINPR_ATTR_NODISCARD
132static AMediaFormat* mediacodec_format_new(wLog* log, int width, int height)
133{
134 const char* media_format = nullptr;
135 AMediaFormat* format = AMediaFormat_new();
136 if (format == nullptr)
137 {
138 WLog_Print(log, WLOG_ERROR, "AMediaFormat_new failed");
139 return nullptr;
140 }
141
142 AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, CODEC_NAME);
143 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, width);
144 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, height);
145 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, COLOR_FormatYUV420Planar);
146 /* Low-latency decode for remote desktop: drop frame reordering, ask for realtime scheduling. */
147 AMediaFormat_setInt32(format, MEDIACODEC_KEY_LOW_LATENCY, 1);
148 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_PRIORITY, 0);
149
150 media_format = AMediaFormat_toString(format);
151 if (media_format == nullptr)
152 {
153 WLog_Print(log, WLOG_ERROR, "AMediaFormat_toString failed");
154 AMediaFormat_delete(format);
155 return nullptr;
156 }
157
158 WLog_Print(log, WLOG_DEBUG, "MediaCodec configuring with desired output format [%s]",
159 media_format);
160
161 return format;
162}
163
164static void set_mediacodec_format(H264_CONTEXT* h264, AMediaFormat** formatVariable,
165 AMediaFormat* newFormat)
166{
167 media_status_t status = AMEDIA_OK;
168 H264_CONTEXT_MEDIACODEC* sys = nullptr;
169
170 WINPR_ASSERT(h264);
171 WINPR_ASSERT(formatVariable);
172
173 sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
174 WINPR_ASSERT(sys);
175
176 if (*formatVariable == newFormat)
177 return;
178
179 if (*formatVariable != nullptr)
180 {
181 status = AMediaFormat_delete(*formatVariable);
182 if (status != AMEDIA_OK)
183 {
184 WLog_Print(h264->log, WLOG_ERROR, "Error AMediaFormat_delete %d", status);
185 }
186 }
187
188 *formatVariable = newFormat;
189}
190
191WINPR_ATTR_NODISCARD
192static int update_mediacodec_inputformat(H264_CONTEXT* h264)
193{
194 H264_CONTEXT_MEDIACODEC* sys = nullptr;
195 AMediaFormat* inputFormat = nullptr;
196 const char* mediaFormatName = nullptr;
197
198 WINPR_ASSERT(h264);
199
200 sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
201 WINPR_ASSERT(sys);
202
203 inputFormat = AMediaCodec_getInputFormat(sys->decoder);
204 if (inputFormat == nullptr)
205 {
206 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getInputFormat failed");
207 return -1;
208 }
209 set_mediacodec_format(h264, &sys->inputFormat, inputFormat);
210
211 mediaFormatName = AMediaFormat_toString(sys->inputFormat);
212 if (mediaFormatName == nullptr)
213 {
214 WLog_Print(h264->log, WLOG_ERROR, "AMediaFormat_toString failed");
215 return -1;
216 }
217 WLog_Print(h264->log, WLOG_DEBUG, "Using MediaCodec with input MediaFormat [%s]",
218 mediaFormatName);
219
220 return 1;
221}
222
223WINPR_ATTR_NODISCARD
224static int update_mediacodec_outputformat(H264_CONTEXT* h264)
225{
226 H264_CONTEXT_MEDIACODEC* sys = nullptr;
227 AMediaFormat* outputFormat = nullptr;
228 const char* mediaFormatName = nullptr;
229 int32_t outputWidth = 0;
230 int32_t outputHeight = 0;
231
232 WINPR_ASSERT(h264);
233
234 sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
235 WINPR_ASSERT(sys);
236
237 outputFormat = AMediaCodec_getOutputFormat(sys->decoder);
238 if (outputFormat == nullptr)
239 {
240 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getOutputFormat failed");
241 return -1;
242 }
243 set_mediacodec_format(h264, &sys->outputFormat, outputFormat);
244
245 mediaFormatName = AMediaFormat_toString(sys->outputFormat);
246 if (mediaFormatName == nullptr)
247 {
248 WLog_Print(h264->log, WLOG_ERROR, "AMediaFormat_toString failed");
249 return -1;
250 }
251 WLog_Print(h264->log, WLOG_DEBUG, "Using MediaCodec with output MediaFormat [%s]",
252 mediaFormatName);
253
254 if (!AMediaFormat_getInt32(sys->outputFormat, AMEDIAFORMAT_KEY_WIDTH, &outputWidth))
255 {
256 WLog_Print(h264->log, WLOG_ERROR, "fnAMediaFormat_getInt32 failed getting width");
257 return -1;
258 }
259
260 if (!AMediaFormat_getInt32(sys->outputFormat, AMEDIAFORMAT_KEY_HEIGHT, &outputHeight))
261 {
262 WLog_Print(h264->log, WLOG_ERROR, "fnAMediaFormat_getInt32 failed getting height");
263 return -1;
264 }
265
266 /* Honor codec stride/slice-height padding; fall back to tight packing. */
267 int32_t outputStride = 0;
268 int32_t outputSliceHeight = 0;
269 if (!AMediaFormat_getInt32(sys->outputFormat, AMEDIAFORMAT_KEY_STRIDE, &outputStride) ||
270 outputStride < outputWidth)
271 outputStride = outputWidth;
272 if (!AMediaFormat_getInt32(sys->outputFormat, AMEDIAFORMAT_KEY_SLICE_HEIGHT,
273 &outputSliceHeight) ||
274 outputSliceHeight < outputHeight)
275 outputSliceHeight = outputHeight;
276 if ((outputWidth <= 0) || (outputHeight <= 0) ||
277 (outputStride > MEDIACODEC_MAXIMUM_DIMENSION) ||
278 (outputSliceHeight > MEDIACODEC_MAXIMUM_DIMENSION))
279 {
280 WLog_Print(h264->log, WLOG_ERROR,
281 "MediaCodec output layout out of range: %dx%d stride %d slice-height %d",
282 outputWidth, outputHeight, outputStride, outputSliceHeight);
283 return -1;
284 }
285 sys->outputStride = outputStride;
286 sys->outputSliceHeight = outputSliceHeight;
287
288 /* Width and height above describe the padded allocation; the picture inside it is the crop
289 * rectangle, with inclusive bounds. */
290 sys->outputWidth = outputWidth;
291 sys->outputHeight = outputHeight;
292 sys->cropLeft = 0;
293 sys->cropTop = 0;
294
295 int32_t cropLeft = 0;
296 int32_t cropRight = 0;
297 int32_t cropTop = 0;
298 int32_t cropBottom = 0;
299 /* Only the far edges are always published; a missing near edge means zero. */
300 if (!AMediaFormat_getInt32(sys->outputFormat, MEDIACODEC_KEY_CROP_LEFT, &cropLeft))
301 cropLeft = 0;
302 if (!AMediaFormat_getInt32(sys->outputFormat, MEDIACODEC_KEY_CROP_TOP, &cropTop))
303 cropTop = 0;
304
305 if (AMediaFormat_getInt32(sys->outputFormat, MEDIACODEC_KEY_CROP_RIGHT, &cropRight) &&
306 AMediaFormat_getInt32(sys->outputFormat, MEDIACODEC_KEY_CROP_BOTTOM, &cropBottom) &&
307 (cropLeft >= 0) && (cropTop >= 0) && (cropRight >= cropLeft) && (cropBottom >= cropTop) &&
308 (cropRight < outputWidth) && (cropBottom < outputHeight))
309 {
310 sys->outputWidth = cropRight - cropLeft + 1;
311 sys->outputHeight = cropBottom - cropTop + 1;
312 sys->cropLeft = cropLeft;
313 sys->cropTop = cropTop;
314 }
315
316 /* The requested planar format is a hint; vendors may output NV12 or flexible YUV. */
317 int32_t colorFormat = 0;
318 if (!AMediaFormat_getInt32(sys->outputFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT, &colorFormat))
319 colorFormat = COLOR_FormatYUV420Planar;
320 sys->colorFormat = colorFormat;
321
322 if (colorFormat == COLOR_FormatYUV420Flexible)
323 WLog_Print(h264->log, WLOG_DEBUG, "MediaCodec output is flexible YUV, assuming NV12");
324
325 /* Publish visible dimensions for region rectangle validation. */
326 h264->YUVWidth = (UINT32)sys->outputWidth;
327 h264->YUVHeight = (UINT32)sys->outputHeight;
328
329 WLog_Print(h264->log, WLOG_DEBUG,
330 "MediaCodec output %dx%d stride %d slice-height %d crop [%d,%d] color 0x%x",
331 sys->outputWidth, sys->outputHeight, sys->outputStride, sys->outputSliceHeight,
332 sys->cropLeft, sys->cropTop, colorFormat);
333
334 return 1;
335}
336
337static void release_current_outputbuffer(H264_CONTEXT* h264)
338{
339 media_status_t status = AMEDIA_OK;
340 H264_CONTEXT_MEDIACODEC* sys = nullptr;
341
342 WINPR_ASSERT(h264);
343 sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
344 WINPR_ASSERT(sys);
345
346 if (sys->currentOutputBufferIndex < 0)
347 {
348 return;
349 }
350
351 status = AMediaCodec_releaseOutputBuffer(sys->decoder, sys->currentOutputBufferIndex, FALSE);
352 if (status != AMEDIA_OK)
353 {
354 WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_releaseOutputBuffer %d", status);
355 }
356
357 sys->currentOutputBufferIndex = -1;
358}
359
360/* Tear down the encoder (used on reconfigure and uninit). */
361static void mediacodec_encoder_release(wLog* log, H264_CONTEXT_MEDIACODEC* sys)
362{
363 if (!sys || !sys->encoder)
364 return;
365
366 if (sys->encoderStarted)
367 mediacodec_warn(log, AMediaCodec_stop(sys->encoder), "AMediaCodec_stop");
368 mediacodec_warn(log, AMediaCodec_delete(sys->encoder), "AMediaCodec_delete");
369 sys->encoder = nullptr;
370 sys->encoderStarted = FALSE;
371
372 /* Invalidate cached SPS/PPS on release. */
373 free(sys->spsPpsData);
374 sys->spsPpsData = nullptr;
375 sys->spsPpsSize = 0;
376}
377
378/* Probe H.264 encoder support to allow fallback if unsupported. */
379WINPR_ATTR_NODISCARD
380static BOOL mediacodec_encoder_probe(wLog* log)
381{
382 AMediaCodec* probe = AMediaCodec_createEncoderByType(CODEC_NAME);
383 if (!probe)
384 {
385 WLog_Print(log, WLOG_WARN, "No MediaCodec H.264 encoder on this device");
386 return FALSE;
387 }
388
389 BOOL ok = FALSE;
390 AMediaFormat* format = AMediaFormat_new();
391 if (format)
392 {
393 AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, CODEC_NAME);
394 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, MEDIACODEC_MINIMUM_WIDTH);
395 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, MEDIACODEC_MINIMUM_HEIGHT);
396 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, COLOR_FormatYUV420SemiPlanar);
397 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, MEDIACODEC_DEFAULT_BITRATE);
398 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BITRATE_MODE, MEDIACODEC_BITRATE_MODE_VBR);
399 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, MEDIACODEC_DEFAULT_FRAMERATE);
400 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL,
401 MEDIACODEC_IFRAME_INTERVAL);
402
403 if (AMediaCodec_configure(probe, format, nullptr, nullptr,
404 AMEDIACODEC_CONFIGURE_FLAG_ENCODE) == AMEDIA_OK &&
405 AMediaCodec_start(probe) == AMEDIA_OK)
406 {
407 ok = TRUE;
408 mediacodec_warn(log, AMediaCodec_stop(probe), "AMediaCodec_stop");
409 }
410 mediacodec_warn(log, AMediaFormat_delete(format), "AMediaFormat_delete");
411 }
412
413 mediacodec_warn(log, AMediaCodec_delete(probe), "AMediaCodec_delete");
414 if (!ok)
415 WLog_Print(log, WLOG_WARN,
416 "MediaCodec H.264 encoder cannot be configured (color format unsupported?)");
417 return ok;
418}
419
420WINPR_ATTR_NODISCARD
421static int32_t mediacodec_rate(UINT32 value, int32_t fallback)
422{
423 if (value == 0)
424 return fallback;
425 return (value > INT32_MAX) ? INT32_MAX : WINPR_ASSERTING_INT_CAST(int32_t, value);
426}
427
428/* (Re)configure the encoder for current dimensions. */
429WINPR_ATTR_NODISCARD
430static BOOL mediacodec_encoder_configure(H264_CONTEXT* h264)
431{
432 WINPR_ASSERT(h264);
433 H264_CONTEXT_MEDIACODEC* sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
434 WINPR_ASSERT(sys);
435
436 mediacodec_encoder_release(h264->log, sys);
437
438 if (h264->width < MEDIACODEC_MINIMUM_WIDTH || h264->height < MEDIACODEC_MINIMUM_HEIGHT ||
439 h264->width > (UINT32)MEDIACODEC_MAXIMUM_DIMENSION ||
440 h264->height > (UINT32)MEDIACODEC_MAXIMUM_DIMENSION)
441 {
442 WLog_Print(h264->log, WLOG_ERROR, "MediaCodec encoder dimensions out of range [%u,%u]",
443 h264->width, h264->height);
444 return FALSE;
445 }
446
447 const int32_t width = WINPR_ASSERTING_INT_CAST(int32_t, h264->width);
448 const int32_t height = WINPR_ASSERTING_INT_CAST(int32_t, h264->height);
449 const int32_t framerate = mediacodec_rate(h264->FrameRate, MEDIACODEC_DEFAULT_FRAMERATE);
450 const int32_t bitrate = mediacodec_rate(h264->BitRate, MEDIACODEC_DEFAULT_BITRATE);
451
452 sys->encoder = AMediaCodec_createEncoderByType(CODEC_NAME);
453 if (!sys->encoder)
454 {
455 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_createEncoderByType failed");
456 return FALSE;
457 }
458
459 AMediaFormat* format = AMediaFormat_new();
460 if (!format)
461 {
462 WLog_Print(h264->log, WLOG_ERROR, "AMediaFormat_new failed");
463 mediacodec_encoder_release(h264->log, sys);
464 return FALSE;
465 }
466
467 AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, CODEC_NAME);
468 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, width);
469 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, height);
470 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, COLOR_FormatYUV420SemiPlanar);
471 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, bitrate);
472 /* Hardware AVC encoders commonly reject CQ, so CQP requests are served as VBR. */
473 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BITRATE_MODE, MEDIACODEC_BITRATE_MODE_VBR);
474 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, framerate);
475 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, MEDIACODEC_IFRAME_INTERVAL);
476 AMediaFormat_setInt32(format, MEDIACODEC_KEY_PREPEND_HEADER, 1);
477
478 media_status_t status = AMediaCodec_configure(sys->encoder, format, nullptr, nullptr,
479 AMEDIACODEC_CONFIGURE_FLAG_ENCODE);
480 mediacodec_warn(h264->log, AMediaFormat_delete(format), "AMediaFormat_delete");
481 if (status != AMEDIA_OK)
482 {
483 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_configure (encoder) failed: %d", status);
484 mediacodec_encoder_release(h264->log, sys);
485 return FALSE;
486 }
487
488 status = AMediaCodec_start(sys->encoder);
489 if (status != AMEDIA_OK)
490 {
491 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_start (encoder) failed: %d", status);
492 mediacodec_encoder_release(h264->log, sys);
493 return FALSE;
494 }
495
496 sys->encoderStarted = TRUE;
497 sys->encoderWidth = width;
498 sys->encoderHeight = height;
499 sys->encoderBitrate = bitrate;
500 sys->syncRequested = FALSE;
501 sys->frameIndex = 0;
502
503 /* Encoder stride/slice-height may be padded. Align stride to even for NV12. */
504 sys->encoderStride = sys->encoderWidth + (sys->encoderWidth & 1);
505 sys->encoderSliceHeight = sys->encoderHeight;
506 AMediaFormat* inputFormat = AMediaCodec_getInputFormat(sys->encoder);
507 if (inputFormat)
508 {
509 int32_t stride = 0;
510 int32_t sliceHeight = 0;
511 if (AMediaFormat_getInt32(inputFormat, AMEDIAFORMAT_KEY_STRIDE, &stride) &&
512 (stride >= sys->encoderWidth))
513 sys->encoderStride = stride;
514 if (AMediaFormat_getInt32(inputFormat, AMEDIAFORMAT_KEY_SLICE_HEIGHT, &sliceHeight) &&
515 (sliceHeight >= sys->encoderHeight))
516 sys->encoderSliceHeight = sliceHeight;
517 mediacodec_warn(h264->log, AMediaFormat_delete(inputFormat), "AMediaFormat_delete");
518 }
519
520 if ((sys->encoderStride > MEDIACODEC_MAXIMUM_DIMENSION) ||
521 (sys->encoderSliceHeight > MEDIACODEC_MAXIMUM_DIMENSION))
522 {
523 WLog_Print(h264->log, WLOG_ERROR,
524 "MediaCodec encoder input layout out of range: stride %d slice-height %d",
525 sys->encoderStride, sys->encoderSliceHeight);
526 mediacodec_encoder_release(h264->log, sys);
527 return FALSE;
528 }
529
530 WLog_Print(
531 h264->log, WLOG_DEBUG,
532 "MediaCodec H.264 encoder started: %ux%u @ %d fps, %d bps, stride %d, slice-height %d",
533 h264->width, h264->height, framerate, bitrate, sys->encoderStride, sys->encoderSliceHeight);
534 return TRUE;
535}
536
537/* Copy a planar I420 source frame into the encoder's NV12 (COLOR_FormatYUV420SemiPlanar) input,
538 * honoring the destination stride and slice height. */
539WINPR_ATTR_NODISCARD
540static BOOL mediacodec_copy_i420_to_nv12(uint8_t* dst, size_t dstCapacity, const BYTE** pSrcYuv,
541 const UINT32* pStride, int32_t width, int32_t height,
542 int32_t dstStride, int32_t dstSliceHeight)
543{
544 WINPR_ASSERT(dst);
545 WINPR_ASSERT(pSrcYuv);
546 WINPR_ASSERT(pStride);
547
548 const int32_t cw = (width + 1) / 2;
549 const int32_t ch = (height + 1) / 2;
550 const size_t ySize = (size_t)dstStride * dstSliceHeight;
551
552 if (dstCapacity < ySize + (size_t)dstStride * ((dstSliceHeight + 1) / 2))
553 return FALSE;
554
555 for (int32_t row = 0; row < height; row++)
556 memcpy(dst + (size_t)row * dstStride, pSrcYuv[0] + (size_t)row * pStride[0], (size_t)width);
557
558 /* Clamp pairs to prevent overflow on odd width. */
559 const int32_t pairs = (dstStride / 2 < cw) ? dstStride / 2 : cw;
560 uint8_t* uv = dst + ySize;
561 for (int32_t row = 0; row < ch; row++)
562 {
563 const BYTE* uRow = pSrcYuv[1] + (size_t)row * pStride[1];
564 const BYTE* vRow = pSrcYuv[2] + (size_t)row * pStride[2];
565 uint8_t* uvRow = uv + (size_t)row * dstStride;
566 for (int32_t col = 0; col < pairs; col++)
567 {
568 uvRow[(size_t)col * 2] = uRow[col];
569 uvRow[(size_t)col * 2 + 1] = vRow[col];
570 }
571 }
572
573 return TRUE;
574}
575
576/* Check if an SPS NAL appears before the first coded slice. */
577WINPR_ATTR_NODISCARD
578static BOOL mediacodec_has_sps(const uint8_t* data, size_t size)
579{
580 WINPR_ASSERT(data);
581
582 for (size_t i = 0; i + 3 < size; i++)
583 {
584 if ((data[i] != 0) || (data[i + 1] != 0))
585 continue;
586
587 size_t nal = 0;
588 if (data[i + 2] == 1)
589 nal = i + 3;
590 else if ((data[i + 2] == 0) && (data[i + 3] == 1))
591 nal = i + 4;
592 else
593 continue;
594
595 if (nal >= size)
596 break;
597
598 const uint8_t type = data[nal] & 0x1F;
599 if (type == 7)
600 return TRUE;
601 if ((type == 1) || (type == 5))
602 break; /* coded slice reached, no parameter set in the header */
603
604 i = nal;
605 }
606
607 return FALSE;
608}
609
610static void mediacodec_copy_nv12_to_i420(BYTE* dstYuv[3], const UINT32 dstStride[3],
611 const uint8_t* src, int32_t width, int32_t height,
612 int32_t srcStride, int32_t srcSliceHeight,
613 int32_t cropLeft, int32_t cropTop)
614{
615 WINPR_ASSERT(dstYuv);
616 WINPR_ASSERT(dstStride);
617 WINPR_ASSERT(src);
618
619 const int32_t cw = (width + 1) / 2;
620 const int32_t ch = (height + 1) / 2;
621 const size_t srcLumaOffset = (size_t)cropTop * (size_t)srcStride + (size_t)cropLeft;
622 const size_t srcChromaOffset = (size_t)srcStride * (size_t)srcSliceHeight +
623 (size_t)(cropTop / 2) * (size_t)srcStride +
624 ((size_t)cropLeft & ~1ULL);
625
626 const uint8_t* srcY = src + srcLumaOffset;
627 const uint8_t* srcUV = src + srcChromaOffset;
628
629 /* Clamp chroma pair count to row bounds. */
630 const int32_t chromaLeft = cropLeft & ~1;
631 const int32_t avail = (srcStride > chromaLeft) ? (srcStride - chromaLeft) / 2 : 0;
632 const int32_t pairs = (avail < cw) ? avail : cw;
633
634 for (int32_t row = 0; row < height; row++)
635 {
636 memcpy(dstYuv[0] + (size_t)row * (size_t)dstStride[0],
637 srcY + (size_t)row * (size_t)srcStride, (size_t)width);
638 }
639
640 for (int32_t row = 0; row < ch; row++)
641 {
642 const uint8_t* uvRow = srcUV + (size_t)row * (size_t)srcStride;
643 BYTE* uRow = dstYuv[1] + (size_t)row * (size_t)dstStride[1];
644 BYTE* vRow = dstYuv[2] + (size_t)row * (size_t)dstStride[2];
645 for (int32_t col = 0; col < pairs; col++)
646 {
647 uRow[col] = uvRow[(size_t)col * 2];
648 vRow[col] = uvRow[(size_t)col * 2 + 1];
649 }
650
651 /* Fill unused chroma with neutral gray (0x80). */
652 for (int32_t col = pairs; col < cw; col++)
653 {
654 uRow[col] = 0x80;
655 vRow[col] = 0x80;
656 }
657 }
658}
659
660WINPR_ATTR_NODISCARD
661static int mediacodec_compress(H264_CONTEXT* h264, const BYTE** pSrcYuv, const UINT32* pStride,
662 BYTE** ppDstData, UINT32* pDstSize)
663{
664 WINPR_ASSERT(h264);
665 WINPR_ASSERT(pSrcYuv);
666 WINPR_ASSERT(pStride);
667 WINPR_ASSERT(ppDstData);
668 WINPR_ASSERT(pDstSize);
669
670 H264_CONTEXT_MEDIACODEC* sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
671 WINPR_ASSERT(sys);
672
673 if (!pSrcYuv[0] || !pSrcYuv[1] || !pSrcYuv[2])
674 return -1;
675
676 if ((h264->width > (UINT32)MEDIACODEC_MAXIMUM_DIMENSION) ||
677 (h264->height > (UINT32)MEDIACODEC_MAXIMUM_DIMENSION))
678 return -1;
679
680 const int32_t width = WINPR_ASSERTING_INT_CAST(int32_t, h264->width);
681 const int32_t height = WINPR_ASSERTING_INT_CAST(int32_t, h264->height);
682
683 if (!sys->encoderStarted || sys->encoderWidth != width || sys->encoderHeight != height)
684 {
685 if ((width == sys->encoderFailedWidth) && (height == sys->encoderFailedHeight))
686 return -1;
687
688 if (!mediacodec_encoder_configure(h264))
689 {
690 sys->encoderFailedWidth = width;
691 sys->encoderFailedHeight = height;
692 return -1;
693 }
694 sys->encoderFailedWidth = 0;
695 sys->encoderFailedHeight = 0;
696 }
697
698 /* Adapt the encoder bitrate on the fly when the caller changes it. */
699 const int32_t bitrate = mediacodec_rate(h264->BitRate, sys->encoderBitrate);
700 if (bitrate != sys->encoderBitrate)
701 {
702 AMediaFormat* params = AMediaFormat_new();
703 if (params)
704 {
705 AMediaFormat_setInt32(params, MEDIACODEC_PARAM_VIDEO_BITRATE, bitrate);
706 if (AMediaCodec_setParameters(sys->encoder, params) == AMEDIA_OK)
707 sys->encoderBitrate = bitrate;
708 mediacodec_warn(h264->log, AMediaFormat_delete(params), "AMediaFormat_delete");
709 }
710 }
711
712 if (!h264->firstLumaFrameDone && (sys->frameIndex > 0) && !sys->syncRequested)
713 {
714 AMediaFormat* syncParams = AMediaFormat_new();
715 if (syncParams)
716 {
717 AMediaFormat_setInt32(syncParams, "request-sync", 0);
718 if (AMediaCodec_setParameters(sys->encoder, syncParams) == AMEDIA_OK)
719 sys->syncRequested = TRUE;
720 mediacodec_warn(h264->log, AMediaFormat_delete(syncParams), "AMediaFormat_delete");
721 }
722 }
723
724 const int32_t dstStride = sys->encoderStride;
725 const int32_t dstSliceHeight = sys->encoderSliceHeight;
726 const size_t frameSize =
727 (size_t)dstStride * dstSliceHeight + (size_t)dstStride * ((dstSliceHeight + 1) / 2);
728
729 /* 1. Feed one raw frame to the encoder. */
730 const ssize_t inIdx =
731 AMediaCodec_dequeueInputBuffer(sys->encoder, MEDIACODEC_INPUT_DEQUEUE_TIMEOUT);
732 if (inIdx < 0)
733 {
734 /* Drain pending output buffer to unblock input queue. */
735 AMediaCodecBufferInfo stale = { 0 };
736 const ssize_t staleIdx = AMediaCodec_dequeueOutputBuffer(sys->encoder, &stale, 0);
737 if (staleIdx >= 0)
738 mediacodec_warn(h264->log,
739 AMediaCodec_releaseOutputBuffer(sys->encoder, (size_t)staleIdx, FALSE),
740 "AMediaCodec_releaseOutputBuffer");
741
742 WLog_Print(h264->log, WLOG_WARN, "MediaCodec encoder has no input buffer, dropping frame");
743 return -1;
744 }
745
746 size_t inCapacity = 0;
747 uint8_t* inBuffer = AMediaCodec_getInputBuffer(sys->encoder, (size_t)inIdx, &inCapacity);
748 if (!inBuffer || inCapacity < frameSize)
749 {
750 WLog_Print(h264->log, WLOG_ERROR, "MediaCodec encoder input buffer too small: %zu < %zu",
751 inCapacity, frameSize);
752 mediacodec_warn(h264->log,
753 AMediaCodec_queueInputBuffer(sys->encoder, (size_t)inIdx, 0, 0, 0, 0),
754 "AMediaCodec_queueInputBuffer");
755 return -1;
756 }
757
758 if (!mediacodec_copy_i420_to_nv12(inBuffer, inCapacity, pSrcYuv, pStride, width, height,
759 dstStride, dstSliceHeight))
760 {
761 WLog_Print(h264->log, WLOG_ERROR, "MediaCodec encoder input buffer too small for layout");
762 mediacodec_warn(h264->log,
763 AMediaCodec_queueInputBuffer(sys->encoder, (size_t)inIdx, 0, 0, 0, 0),
764 "AMediaCodec_queueInputBuffer");
765 return -1;
766 }
767
768 const int32_t framerate = mediacodec_rate(h264->FrameRate, MEDIACODEC_DEFAULT_FRAMERATE);
769 const uint64_t pts = (uint64_t)sys->frameIndex++ * 1000000ULL / (uint64_t)framerate;
770 media_status_t status =
771 AMediaCodec_queueInputBuffer(sys->encoder, (size_t)inIdx, 0, frameSize, pts, 0);
772 if (status != AMEDIA_OK)
773 {
774 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_queueInputBuffer (encoder) failed: %d",
775 status);
776 return -1;
777 }
778
779 /* 2. Collect one encoded frame; skip standalone codec-config buffer. */
780 for (;;)
781 {
782 AMediaCodecBufferInfo info = { 0 };
783 const ssize_t outIdx =
784 AMediaCodec_dequeueOutputBuffer(sys->encoder, &info, MEDIACODEC_OUTPUT_DEQUEUE_TIMEOUT);
785 if (outIdx >= 0)
786 {
787 size_t outCapacity = 0;
788 uint8_t* outBuffer =
789 AMediaCodec_getOutputBuffer(sys->encoder, (size_t)outIdx, &outCapacity);
790 if (!outBuffer || (info.size < 0) || (info.offset < 0) ||
791 ((size_t)info.offset > outCapacity) ||
792 ((size_t)info.size > outCapacity - (size_t)info.offset))
793 {
794 WLog_Print(h264->log, WLOG_ERROR,
795 "MediaCodec encoder output out of range: offset %d size %d cap %zu",
796 info.offset, info.size, outCapacity);
797 mediacodec_warn(
798 h264->log, AMediaCodec_releaseOutputBuffer(sys->encoder, (size_t)outIdx, FALSE),
799 "AMediaCodec_releaseOutputBuffer");
800 return -1;
801 }
802
803 if (((info.flags & AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG) != 0) && (info.size > 0))
804 {
805 BYTE* newCfg = (BYTE*)realloc(sys->spsPpsData, (size_t)info.size);
806 if (newCfg)
807 {
808 sys->spsPpsData = newCfg;
809 sys->spsPpsSize = (size_t)info.size;
810 memcpy(sys->spsPpsData, outBuffer + info.offset, (size_t)info.size);
811 }
812 else
813 {
814 WLog_Print(h264->log, WLOG_WARN, "MediaCodec encoder failed to store SPS/PPS");
815 }
816 }
817
818 if (((info.flags & AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG) != 0) || (info.size == 0))
819 {
820 const media_status_t rc =
821 AMediaCodec_releaseOutputBuffer(sys->encoder, (size_t)outIdx, FALSE);
822 if (rc != AMEDIA_OK)
823 {
824 WLog_Print(h264->log, WLOG_ERROR,
825 "AMediaCodec_releaseOutputBuffer (encoder) failed: %d", rc);
826 return -1;
827 }
828 continue;
829 }
830
831 const BOOL isKeyFrame = (info.flags & AMEDIACODEC_BUFFER_FLAG_KEY_FRAME) != 0;
832 const BOOL prependSps = isKeyFrame && sys->spsPpsData && sys->spsPpsSize > 0 &&
833 !mediacodec_has_sps(outBuffer + info.offset, (size_t)info.size);
834 const size_t totalSize = (size_t)info.size + (prependSps ? sys->spsPpsSize : 0);
835
836 if (sys->outputCapacity < totalSize)
837 {
838 BYTE* resized = (BYTE*)realloc(sys->outputData, totalSize);
839 if (!resized)
840 {
841 mediacodec_warn(
842 h264->log,
843 AMediaCodec_releaseOutputBuffer(sys->encoder, (size_t)outIdx, FALSE),
844 "AMediaCodec_releaseOutputBuffer");
845 return -1;
846 }
847 sys->outputData = resized;
848 sys->outputCapacity = totalSize;
849 }
850
851 if (prependSps)
852 {
853 memcpy(sys->outputData, sys->spsPpsData, sys->spsPpsSize);
854 memcpy(sys->outputData + sys->spsPpsSize, outBuffer + info.offset,
855 (size_t)info.size);
856 }
857 else
858 {
859 memcpy(sys->outputData, outBuffer + info.offset, (size_t)info.size);
860 }
861
862 *ppDstData = sys->outputData;
863 *pDstSize = WINPR_ASSERTING_INT_CAST(UINT32, totalSize);
864 const media_status_t rc =
865 AMediaCodec_releaseOutputBuffer(sys->encoder, (size_t)outIdx, FALSE);
866 if (rc != AMEDIA_OK)
867 WLog_Print(h264->log, WLOG_ERROR,
868 "AMediaCodec_releaseOutputBuffer (encoder) failed: %d", rc);
869 return 1;
870 }
871 else if (outIdx == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED ||
872 outIdx == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED)
873 {
874 continue;
875 }
876 else if (outIdx == AMEDIACODEC_INFO_TRY_AGAIN_LATER)
877 {
878 /* Output not ready yet (pipeline priming). */
879 WLog_Print(h264->log, WLOG_DEBUG,
880 "MediaCodec encoder output not ready, dropping frame");
881 return -1;
882 }
883 else
884 {
885 WLog_Print(h264->log, WLOG_ERROR,
886 "AMediaCodec_dequeueOutputBuffer (encoder) returned %zd", outIdx);
887 return -1;
888 }
889 }
890}
891
892WINPR_ATTR_NODISCARD
893static int mediacodec_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize)
894{
895 WINPR_ASSERT(h264);
896 WINPR_ASSERT(pSrcData);
897
898 H264_CONTEXT_MEDIACODEC* sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
899 WINPR_ASSERT(sys);
900
901 BYTE** pYUVData = h264->pYUVData;
902 WINPR_ASSERT(pYUVData);
903
904 UINT32* iStride = h264->iStride;
905 WINPR_ASSERT(iStride);
906
907 release_current_outputbuffer(h264);
908
909 if ((h264->width > (UINT32)MEDIACODEC_MAXIMUM_DIMENSION) ||
910 (h264->height > (UINT32)MEDIACODEC_MAXIMUM_DIMENSION))
911 {
912 WLog_Print(h264->log, WLOG_ERROR,
913 "MediaCodec size [%" PRIu32 ",%" PRIu32 "] exceeds the maximum", h264->width,
914 h264->height);
915 return -1;
916 }
917
918 if ((sys->width != WINPR_ASSERTING_INT_CAST(int32_t, h264->width)) ||
919 (sys->height != WINPR_ASSERTING_INT_CAST(int32_t, h264->height)))
920 {
921 sys->width = WINPR_ASSERTING_INT_CAST(int32_t, h264->width);
922 sys->height = WINPR_ASSERTING_INT_CAST(int32_t, h264->height);
923
924 if (sys->width < MEDIACODEC_MINIMUM_WIDTH || sys->height < MEDIACODEC_MINIMUM_HEIGHT)
925 {
926 WLog_Print(h264->log, WLOG_ERROR,
927 "MediaCodec got width or height smaller than minimum [%d,%d]", sys->width,
928 sys->height);
929 return -1;
930 }
931
932 WLog_Print(h264->log, WLOG_DEBUG, "MediaCodec setting new input width and height [%d,%d]",
933 sys->width, sys->height);
934
935 AMediaFormat_setInt32(sys->inputFormat, AMEDIAFORMAT_KEY_WIDTH, sys->width);
936 AMediaFormat_setInt32(sys->inputFormat, AMEDIAFORMAT_KEY_HEIGHT, sys->height);
937 const media_status_t status = AMediaCodec_setParameters(sys->decoder, sys->inputFormat);
938 if (status != AMEDIA_OK)
939 {
940 /* Decoder adapts via in-band SPS/PPS; ignoring failure here. */
941 WLog_Print(h264->log, WLOG_DEBUG,
942 "AMediaCodec_setParameters returned %d, relying on "
943 "in-band SPS/PPS for the size change",
944 status);
945 }
946
947 /* The codec can change output width and height */
948 if (update_mediacodec_outputformat(h264) < 0)
949 {
950 WLog_Print(h264->log, WLOG_ERROR, "MediaCodec failed updating input format");
951 return -1;
952 }
953 }
954
955 while (true)
956 {
957 UINT32 inputBufferCurrnetOffset = 0;
958 int inputRetries = 0;
959 while (inputBufferCurrnetOffset < SrcSize)
960 {
961 UINT32 numberOfBytesToCopy = SrcSize - inputBufferCurrnetOffset;
962 const ssize_t inputBufferId =
963 AMediaCodec_dequeueInputBuffer(sys->decoder, MEDIACODEC_INPUT_DEQUEUE_TIMEOUT);
964 if (inputBufferId < 0)
965 {
966 if (++inputRetries > MEDIACODEC_DECODE_MAX_RETRIES)
967 {
968 WLog_Print(h264->log, WLOG_ERROR,
969 "AMediaCodec_dequeueInputBuffer gave up [%zd]", inputBufferId);
970 return -1;
971 }
972 continue;
973 }
974 inputRetries = 0;
975
976 size_t inputBufferSize = 0;
977 uint8_t* inputBuffer =
978 AMediaCodec_getInputBuffer(sys->decoder, inputBufferId, &inputBufferSize);
979 if (inputBuffer == nullptr)
980 {
981 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getInputBuffer failed");
982 return -1;
983 }
984
985 if (numberOfBytesToCopy > inputBufferSize)
986 {
987 WLog_Print(h264->log, WLOG_WARN,
988 "MediaCodec inputBufferSize: got [%zu] but wanted [%u]", inputBufferSize,
989 numberOfBytesToCopy);
990 numberOfBytesToCopy = WINPR_ASSERTING_INT_CAST(UINT32, inputBufferSize);
991 }
992
993 memcpy(inputBuffer, &pSrcData[inputBufferCurrnetOffset], numberOfBytesToCopy);
994 inputBufferCurrnetOffset += numberOfBytesToCopy;
995
996 const media_status_t status = AMediaCodec_queueInputBuffer(
997 sys->decoder, inputBufferId, 0, numberOfBytesToCopy, 0, 0);
998 if (status != AMEDIA_OK)
999 {
1000 WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_queueInputBuffer %d", status);
1001 return -1;
1002 }
1003 }
1004
1005 int outputRetries = 0;
1006 while (true)
1007 {
1008 AMediaCodecBufferInfo bufferInfo = WINPR_C_ARRAY_INIT;
1009 ssize_t outputBufferId = AMediaCodec_dequeueOutputBuffer(
1010 sys->decoder, &bufferInfo, MEDIACODEC_OUTPUT_DEQUEUE_TIMEOUT);
1011 if (outputBufferId >= 0)
1012 {
1013 sys->currentOutputBufferIndex = outputBufferId;
1014
1015 size_t outputBufferSize = 0;
1016 uint8_t* outputBuffer = nullptr;
1017 outputBuffer =
1018 AMediaCodec_getOutputBuffer(sys->decoder, outputBufferId, &outputBufferSize);
1019 sys->currentOutputBufferIndex = outputBufferId;
1020
1021 /* End of stream and metadata buffers carry no picture. */
1022 if (bufferInfo.size <= 0)
1023 {
1024 release_current_outputbuffer(h264);
1025 if (++outputRetries > MEDIACODEC_DECODE_MAX_RETRIES)
1026 return 0;
1027 continue;
1028 }
1029
1030 /* The picture starts at the reported offset, not at the buffer base. */
1031 if (!outputBuffer || (bufferInfo.offset < 0) ||
1032 ((size_t)bufferInfo.offset > outputBufferSize))
1033 {
1034 WLog_Print(h264->log, WLOG_ERROR,
1035 "MediaCodec output offset %d out of range %zu", bufferInfo.offset,
1036 outputBufferSize);
1037 return -1;
1038 }
1039 outputBuffer += bufferInfo.offset;
1040 outputBufferSize -= (size_t)bufferInfo.offset;
1041
1042 const size_t lumaSize = (size_t)sys->outputStride * (size_t)sys->outputSliceHeight;
1043 const int32_t chromaStride = sys->outputStride / 2;
1044 const int32_t chromaHeight = (sys->outputSliceHeight + 1) / 2;
1045 const size_t chromaSize = (size_t)chromaStride * (size_t)chromaHeight;
1046 const size_t minRequiredSize =
1047 lumaSize + (size_t)sys->outputStride * (size_t)chromaHeight;
1048
1049 if (outputBufferSize < minRequiredSize)
1050 {
1051 WLog_Print(h264->log, WLOG_ERROR,
1052 "Error MediaCodec output buffer too small %zu < %zu",
1053 outputBufferSize, minRequiredSize);
1054 return -1;
1055 }
1056
1057 if (sys->colorFormat == COLOR_FormatYUV420Planar)
1058 {
1059 iStride[0] = (UINT32)sys->outputStride;
1060 iStride[1] = (UINT32)chromaStride;
1061 iStride[2] = (UINT32)chromaStride;
1062 pYUVData[0] = outputBuffer +
1063 ((size_t)sys->cropTop * (size_t)sys->outputStride) +
1064 (size_t)sys->cropLeft;
1065 pYUVData[1] = outputBuffer + lumaSize +
1066 ((size_t)(sys->cropTop / 2) * (size_t)chromaStride) +
1067 ((size_t)sys->cropLeft / 2);
1068 pYUVData[2] = outputBuffer + lumaSize + chromaSize +
1069 ((size_t)(sys->cropTop / 2) * (size_t)chromaStride) +
1070 ((size_t)sys->cropLeft / 2);
1071 }
1072 else
1073 {
1074 /* SemiPlanar (NV12) or flexible YUV: deinterleave into staging I420 buffer */
1075 const UINT32 dstYStride = (UINT32)sys->outputWidth;
1076 const UINT32 dstUVStride = (UINT32)((sys->outputWidth + 1) / 2);
1077 const size_t yBytes = (size_t)dstYStride * (size_t)sys->outputHeight;
1078 const size_t uvBytes =
1079 (size_t)dstUVStride * (size_t)((sys->outputHeight + 1) / 2);
1080 const size_t neededCapacity = yBytes + uvBytes * 2;
1081
1082 if (sys->decodeI420Capacity < neededCapacity)
1083 {
1084 BYTE* newBuf = (BYTE*)realloc(sys->decodeI420Buffer, neededCapacity);
1085 if (!newBuf)
1086 {
1087 WLog_Print(h264->log, WLOG_ERROR,
1088 "Failed to allocate I420 deinterleave buffer");
1089 return -1;
1090 }
1091 sys->decodeI420Buffer = newBuf;
1092 sys->decodeI420Capacity = neededCapacity;
1093 }
1094
1095 iStride[0] = dstYStride;
1096 iStride[1] = dstUVStride;
1097 iStride[2] = dstUVStride;
1098 pYUVData[0] = sys->decodeI420Buffer;
1099 pYUVData[1] = sys->decodeI420Buffer + yBytes;
1100 pYUVData[2] = sys->decodeI420Buffer + yBytes + uvBytes;
1101
1102 mediacodec_copy_nv12_to_i420(
1103 pYUVData, iStride, outputBuffer, sys->outputWidth, sys->outputHeight,
1104 sys->outputStride, sys->outputSliceHeight, sys->cropLeft, sys->cropTop);
1105 release_current_outputbuffer(h264);
1106 }
1107
1108 h264->YUVWidth = (UINT32)sys->outputWidth;
1109 h264->YUVHeight = (UINT32)sys->outputHeight;
1110 break;
1111 }
1112 else if (outputBufferId == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED)
1113 {
1114 if (update_mediacodec_outputformat(h264) < 0)
1115 {
1116 WLog_Print(h264->log, WLOG_ERROR,
1117 "MediaCodec failed updating output format in decompress");
1118 return -1;
1119 }
1120 }
1121 else if (outputBufferId == AMEDIACODEC_INFO_TRY_AGAIN_LATER)
1122 {
1123 if (++outputRetries > MEDIACODEC_DECODE_MAX_RETRIES)
1124 {
1125 WLog_Print(h264->log, WLOG_DEBUG,
1126 "AMediaCodec_dequeueOutputBuffer no buffer ready yet");
1127 return 0;
1128 }
1129 }
1130 else if (outputBufferId == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED)
1131 {
1132 WLog_Print(h264->log, WLOG_WARN,
1133 "AMediaCodec_dequeueOutputBuffer returned deprecated value "
1134 "AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED, ignoring");
1135 }
1136 else
1137 {
1138 WLog_Print(h264->log, WLOG_ERROR,
1139 "AMediaCodec_dequeueOutputBuffer returned unknown value [%zd]",
1140 outputBufferId);
1141 return -1;
1142 }
1143 }
1144
1145 break;
1146 }
1147
1148 return 1;
1149}
1150
1151static void mediacodec_uninit(H264_CONTEXT* h264)
1152{
1153 media_status_t status = AMEDIA_OK;
1154 H264_CONTEXT_MEDIACODEC* sys = nullptr;
1155
1156 WINPR_ASSERT(h264);
1157
1158 sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
1159
1160 WLog_Print(h264->log, WLOG_DEBUG, "Uninitializing MediaCodec");
1161
1162 if (!sys)
1163 return;
1164
1165 if (sys->decoder != nullptr)
1166 {
1167 release_current_outputbuffer(h264);
1168
1169 if (sys->decoderStarted)
1170 {
1171 status = AMediaCodec_stop(sys->decoder);
1172 if (status != AMEDIA_OK)
1173 {
1174 WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_stop %d", status);
1175 }
1176 sys->decoderStarted = FALSE;
1177 }
1178
1179 status = AMediaCodec_delete(sys->decoder);
1180 if (status != AMEDIA_OK)
1181 {
1182 WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_delete %d", status);
1183 }
1184
1185 sys->decoder = nullptr;
1186 }
1187
1188 mediacodec_encoder_release(h264->log, sys);
1189 free(sys->outputData);
1190 sys->outputData = nullptr;
1191 free(sys->decodeI420Buffer);
1192 sys->decodeI420Buffer = nullptr;
1193 free(sys->spsPpsData);
1194 sys->spsPpsData = nullptr;
1195
1196 set_mediacodec_format(h264, &sys->inputFormat, nullptr);
1197 set_mediacodec_format(h264, &sys->outputFormat, nullptr);
1198
1199 free(sys);
1200 h264->pSystemData = nullptr;
1201}
1202
1203WINPR_ATTR_NODISCARD
1204static BOOL mediacodec_init(H264_CONTEXT* h264)
1205{
1206 H264_CONTEXT_MEDIACODEC* sys = nullptr;
1207 media_status_t status = AMEDIA_OK;
1208
1209 WINPR_ASSERT(h264);
1210
1211 WLog_Print(h264->log, WLOG_DEBUG, "Initializing MediaCodec");
1212
1213 sys = (H264_CONTEXT_MEDIACODEC*)calloc(1, sizeof(H264_CONTEXT_MEDIACODEC));
1214
1215 if (!sys)
1216 {
1217 goto EXCEPTION;
1218 }
1219
1220 h264->pSystemData = (void*)sys;
1221
1222 sys->currentOutputBufferIndex = -1;
1223
1224 /* Updated when given height and width for the first time */
1225 sys->width = sys->outputWidth = MEDIACODEC_MINIMUM_WIDTH;
1226 sys->height = sys->outputHeight = MEDIACODEC_MINIMUM_HEIGHT;
1227
1228 /* Probe encoder support to allow fallback during initialization. */
1229 if (h264->Compressor)
1230 {
1231 if (!mediacodec_encoder_probe(h264->log))
1232 goto EXCEPTION;
1233 WLog_Print(h264->log, WLOG_DEBUG, "MediaCodec H.264 encoder available");
1234 return TRUE;
1235 }
1236
1237 sys->decoder = AMediaCodec_createDecoderByType(CODEC_NAME);
1238 if (sys->decoder == nullptr)
1239 {
1240 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_createCodecByName failed");
1241 goto EXCEPTION;
1242 }
1243
1244 char* codec_name = nullptr;
1245 status = AMediaCodec_getName(sys->decoder, &codec_name);
1246 if (status != AMEDIA_OK)
1247 {
1248 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getName failed: %d", status);
1249 goto EXCEPTION;
1250 }
1251
1252 WLog_Print(h264->log, WLOG_DEBUG, "MediaCodec using %s codec [%s]", CODEC_NAME, codec_name);
1253 AMediaCodec_releaseName(sys->decoder, codec_name);
1254
1255 set_mediacodec_format(h264, &sys->inputFormat,
1256 mediacodec_format_new(h264->log, sys->width, sys->height));
1257
1258 status = AMediaCodec_configure(sys->decoder, sys->inputFormat, nullptr, nullptr, 0);
1259 if (status != AMEDIA_OK)
1260 {
1261 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_configure failed: %d", status);
1262 goto EXCEPTION;
1263 }
1264
1265 if (update_mediacodec_inputformat(h264) < 0)
1266 {
1267 WLog_Print(h264->log, WLOG_ERROR, "MediaCodec failed updating input format");
1268 goto EXCEPTION;
1269 }
1270
1271 if (update_mediacodec_outputformat(h264) < 0)
1272 {
1273 WLog_Print(h264->log, WLOG_ERROR, "MediaCodec failed updating output format");
1274 goto EXCEPTION;
1275 }
1276
1277 WLog_Print(h264->log, WLOG_DEBUG, "Starting MediaCodec");
1278 status = AMediaCodec_start(sys->decoder);
1279 if (status != AMEDIA_OK)
1280 {
1281 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_start failed %d", status);
1282 goto EXCEPTION;
1283 }
1284
1285 sys->decoderStarted = TRUE;
1286 return TRUE;
1287EXCEPTION:
1288 mediacodec_uninit(h264);
1289 return FALSE;
1290}
1291
1292const H264_CONTEXT_SUBSYSTEM g_Subsystem_mediacodec = { "MediaCodec", mediacodec_init,
1293 mediacodec_uninit, mediacodec_decompress,
1294 mediacodec_compress };