FreeRDP
Loading...
Searching...
No Matches
h264_mediacodec.c
1
20#include <winpr/wlog.h>
21#include <winpr/assert.h>
22#include <winpr/library.h>
23
24#include <freerdp/log.h>
25#include <freerdp/codec/h264.h>
26
27#include <media/NdkMediaCodec.h>
28#include <media/NdkMediaFormat.h>
29
30#include "h264.h"
31
32static const char* CODEC_NAME = "video/avc";
33
34static const int COLOR_FormatYUV420Planar = 19;
35static const int COLOR_FormatYUV420Flexible = 0x7f420888;
36
37/* https://developer.android.com/reference/android/media/MediaCodec#qualityFloor */
38static const int MEDIACODEC_MINIMUM_WIDTH = 320;
39static const int MEDIACODEC_MINIMUM_HEIGHT = 240;
40
41typedef struct
42{
43 AMediaCodec* decoder;
44 AMediaFormat* inputFormat;
45 AMediaFormat* outputFormat;
46 int32_t width;
47 int32_t height;
48 int32_t outputWidth;
49 int32_t outputHeight;
50 ssize_t currentOutputBufferIndex;
51} H264_CONTEXT_MEDIACODEC;
52
53static AMediaFormat* mediacodec_format_new(wLog* log, int width, int height)
54{
55 const char* media_format;
56 AMediaFormat* format = AMediaFormat_new();
57 if (format == nullptr)
58 {
59 WLog_Print(log, WLOG_ERROR, "AMediaFormat_new failed");
60 return nullptr;
61 }
62
63 AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, CODEC_NAME);
64 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, width);
65 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, height);
66 AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, COLOR_FormatYUV420Planar);
67
68 media_format = AMediaFormat_toString(format);
69 if (media_format == nullptr)
70 {
71 WLog_Print(log, WLOG_ERROR, "AMediaFormat_toString failed");
72 AMediaFormat_delete(format);
73 return nullptr;
74 }
75
76 WLog_Print(log, WLOG_DEBUG, "MediaCodec configuring with desired output format [%s]",
77 media_format);
78
79 return format;
80}
81
82static void set_mediacodec_format(H264_CONTEXT* h264, AMediaFormat** formatVariable,
83 AMediaFormat* newFormat)
84{
85 media_status_t status = AMEDIA_OK;
86 H264_CONTEXT_MEDIACODEC* sys;
87
88 WINPR_ASSERT(h264);
89 WINPR_ASSERT(formatVariable);
90
91 sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
92 WINPR_ASSERT(sys);
93
94 if (*formatVariable == newFormat)
95 return;
96
97 if (*formatVariable != nullptr)
98 {
99 status = AMediaFormat_delete(*formatVariable);
100 if (status != AMEDIA_OK)
101 {
102 WLog_Print(h264->log, WLOG_ERROR, "Error AMediaFormat_delete %d", status);
103 }
104 }
105
106 *formatVariable = newFormat;
107}
108
109static int update_mediacodec_inputformat(H264_CONTEXT* h264)
110{
111 H264_CONTEXT_MEDIACODEC* sys;
112 AMediaFormat* inputFormat;
113 const char* mediaFormatName;
114
115 WINPR_ASSERT(h264);
116
117 sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
118 WINPR_ASSERT(sys);
119
120#if __ANDROID__ >= 21
121 inputFormat = AMediaCodec_getInputFormat(sys->decoder);
122 if (inputFormat == nullptr)
123 {
124 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getInputFormat failed");
125 return -1;
126 }
127#else
128 inputFormat = sys->inputFormat;
129#endif
130 set_mediacodec_format(h264, &sys->inputFormat, inputFormat);
131
132 mediaFormatName = AMediaFormat_toString(sys->inputFormat);
133 if (mediaFormatName == nullptr)
134 {
135 WLog_Print(h264->log, WLOG_ERROR, "AMediaFormat_toString failed");
136 return -1;
137 }
138 WLog_Print(h264->log, WLOG_DEBUG, "Using MediaCodec with input MediaFormat [%s]",
139 mediaFormatName);
140
141 return 1;
142}
143
144static int update_mediacodec_outputformat(H264_CONTEXT* h264)
145{
146 H264_CONTEXT_MEDIACODEC* sys;
147 AMediaFormat* outputFormat;
148 const char* mediaFormatName;
149 int32_t outputWidth, outputHeight;
150
151 WINPR_ASSERT(h264);
152
153 sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
154 WINPR_ASSERT(sys);
155
156 outputFormat = AMediaCodec_getOutputFormat(sys->decoder);
157 if (outputFormat == nullptr)
158 {
159 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getOutputFormat failed");
160 return -1;
161 }
162 set_mediacodec_format(h264, &sys->outputFormat, outputFormat);
163
164 mediaFormatName = AMediaFormat_toString(sys->outputFormat);
165 if (mediaFormatName == nullptr)
166 {
167 WLog_Print(h264->log, WLOG_ERROR, "AMediaFormat_toString failed");
168 return -1;
169 }
170 WLog_Print(h264->log, WLOG_DEBUG, "Using MediaCodec with output MediaFormat [%s]",
171 mediaFormatName);
172
173 if (!AMediaFormat_getInt32(sys->outputFormat, AMEDIAFORMAT_KEY_WIDTH, &outputWidth))
174 {
175 WLog_Print(h264->log, WLOG_ERROR, "fnAMediaFormat_getInt32 failed getting width");
176 return -1;
177 }
178
179 if (!AMediaFormat_getInt32(sys->outputFormat, AMEDIAFORMAT_KEY_HEIGHT, &outputHeight))
180 {
181 WLog_Print(h264->log, WLOG_ERROR, "fnAMediaFormat_getInt32 failed getting height");
182 return -1;
183 }
184
185 sys->outputWidth = outputWidth;
186 sys->outputHeight = outputHeight;
187
188 return 1;
189}
190
191static void release_current_outputbuffer(H264_CONTEXT* h264)
192{
193 media_status_t status = AMEDIA_OK;
194 H264_CONTEXT_MEDIACODEC* sys;
195
196 WINPR_ASSERT(h264);
197 sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
198 WINPR_ASSERT(sys);
199
200 if (sys->currentOutputBufferIndex < 0)
201 {
202 return;
203 }
204
205 status = AMediaCodec_releaseOutputBuffer(sys->decoder, sys->currentOutputBufferIndex, FALSE);
206 if (status != AMEDIA_OK)
207 {
208 WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_releaseOutputBuffer %d", status);
209 }
210
211 sys->currentOutputBufferIndex = -1;
212}
213
214static int mediacodec_compress(H264_CONTEXT* h264, const BYTE** pSrcYuv, const UINT32* pStride,
215 BYTE** ppDstData, UINT32* pDstSize)
216{
217 WINPR_ASSERT(h264);
218 WINPR_ASSERT(pSrcYuv);
219 WINPR_ASSERT(pStride);
220 WINPR_ASSERT(ppDstData);
221 WINPR_ASSERT(pDstSize);
222
223 WLog_Print(h264->log, WLOG_ERROR, "MediaCodec is not supported as an encoder");
224 return -1;
225}
226
227static int mediacodec_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize)
228{
229 WINPR_ASSERT(h264);
230 WINPR_ASSERT(pSrcData);
231
232 H264_CONTEXT_MEDIACODEC* sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
233 WINPR_ASSERT(sys);
234
235 BYTE** pYUVData = h264->pYUVData;
236 WINPR_ASSERT(pYUVData);
237
238 UINT32* iStride = h264->iStride;
239 WINPR_ASSERT(iStride);
240
241 release_current_outputbuffer(h264);
242
243 if (sys->width != h264->width || sys->height != h264->height)
244 {
245 sys->width = h264->width;
246 sys->height = h264->height;
247
248 if (sys->width < MEDIACODEC_MINIMUM_WIDTH || sys->height < MEDIACODEC_MINIMUM_HEIGHT)
249 {
250 WLog_Print(h264->log, WLOG_ERROR,
251 "MediaCodec got width or height smaller than minimum [%d,%d]", sys->width,
252 sys->height);
253 return -1;
254 }
255
256 WLog_Print(h264->log, WLOG_DEBUG, "MediaCodec setting new input width and height [%d,%d]",
257 sys->width, sys->height);
258
259#if __ANDROID__ >= 26
260 AMediaFormat_setInt32(sys->inputFormat, AMEDIAFORMAT_KEY_WIDTH, sys->width);
261 AMediaFormat_setInt32(sys->inputFormat, AMEDIAFORMAT_KEY_HEIGHT, sys->height);
262 const media_status_t status = AMediaCodec_setParameters(sys->decoder, sys->inputFormat);
263 if (status != AMEDIA_OK)
264 {
265 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_setParameters failed: %d", status);
266 return -1;
267 }
268#else
269 set_mediacodec_format(h264, &sys->inputFormat,
270 mediacodec_format_new(h264->log, sys->width, sys->height));
271#endif
272
273 // The codec can change output width and height
274 if (update_mediacodec_outputformat(h264) < 0)
275 {
276 WLog_Print(h264->log, WLOG_ERROR, "MediaCodec failed updating input format");
277 return -1;
278 }
279 }
280
281 while (true)
282 {
283 UINT32 inputBufferCurrnetOffset = 0;
284 while (inputBufferCurrnetOffset < SrcSize)
285 {
286 UINT32 numberOfBytesToCopy = SrcSize - inputBufferCurrnetOffset;
287 const ssize_t inputBufferId = AMediaCodec_dequeueInputBuffer(sys->decoder, -1);
288 if (inputBufferId < 0)
289 {
290 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_dequeueInputBuffer failed [%d]",
291 inputBufferId);
292 // TODO: sleep?
293 continue;
294 }
295
296 size_t inputBufferSize = 0;
297 uint8_t* inputBuffer =
298 AMediaCodec_getInputBuffer(sys->decoder, inputBufferId, &inputBufferSize);
299 if (inputBuffer == nullptr)
300 {
301 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getInputBuffer failed");
302 return -1;
303 }
304
305 if (numberOfBytesToCopy > inputBufferSize)
306 {
307 WLog_Print(h264->log, WLOG_WARN,
308 "MediaCodec inputBufferSize: got [%d] but wanted [%d]", inputBufferSize,
309 numberOfBytesToCopy);
310 numberOfBytesToCopy = inputBufferSize;
311 }
312
313 memcpy(inputBuffer, &pSrcData[inputBufferCurrnetOffset], numberOfBytesToCopy);
314 inputBufferCurrnetOffset += numberOfBytesToCopy;
315
316 const media_status_t status = AMediaCodec_queueInputBuffer(
317 sys->decoder, inputBufferId, 0, numberOfBytesToCopy, 0, 0);
318 if (status != AMEDIA_OK)
319 {
320 WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_queueInputBuffer %d", status);
321 return -1;
322 }
323 }
324
325 while (true)
326 {
327 AMediaCodecBufferInfo bufferInfo = WINPR_C_ARRAY_INIT;
328 ssize_t outputBufferId = AMediaCodec_dequeueOutputBuffer(sys->decoder, &bufferInfo, -1);
329 if (outputBufferId >= 0)
330 {
331 sys->currentOutputBufferIndex = outputBufferId;
332
333 size_t outputBufferSize = 0;
334 uint8_t* outputBuffer = nullptr;
335 outputBuffer =
336 AMediaCodec_getOutputBuffer(sys->decoder, outputBufferId, &outputBufferSize);
337 sys->currentOutputBufferIndex = outputBufferId;
338
339 if (outputBufferSize !=
340 (sys->outputWidth * sys->outputHeight +
341 ((sys->outputWidth + 1) / 2) * ((sys->outputHeight + 1) / 2) * 2))
342 {
343 WLog_Print(h264->log, WLOG_ERROR,
344 "Error MediaCodec unexpected output buffer size %d",
345 outputBufferSize);
346 return -1;
347 }
348
349 // TODO: work with AImageReader and get AImage object instead of
350 // COLOR_FormatYUV420Planar buffer.
351 iStride[0] = sys->outputWidth;
352 iStride[1] = (sys->outputWidth + 1) / 2;
353 iStride[2] = (sys->outputWidth + 1) / 2;
354 pYUVData[0] = outputBuffer;
355 pYUVData[1] = outputBuffer + iStride[0] * sys->outputHeight;
356 pYUVData[2] = outputBuffer + iStride[0] * sys->outputHeight +
357 iStride[1] * ((sys->outputHeight + 1) / 2);
358
359 h264->YUVWidth = sys->outputWidth;
360 h264->YUVHeigth = sys->outputHeigth;
361 break;
362 }
363 else if (outputBufferId == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED)
364 {
365 if (update_mediacodec_outputformat(h264) < 0)
366 {
367 WLog_Print(h264->log, WLOG_ERROR,
368 "MediaCodec failed updating output format in decompress");
369 return -1;
370 }
371 }
372 else if (outputBufferId == AMEDIACODEC_INFO_TRY_AGAIN_LATER)
373 {
374 WLog_Print(h264->log, WLOG_WARN,
375 "AMediaCodec_dequeueOutputBuffer need to try again later");
376 // TODO: sleep?
377 }
378 else if (outputBufferId == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED)
379 {
380 WLog_Print(h264->log, WLOG_WARN,
381 "AMediaCodec_dequeueOutputBuffer returned deprecated value "
382 "AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED, ignoring");
383 }
384 else
385 {
386 WLog_Print(h264->log, WLOG_ERROR,
387 "AMediaCodec_dequeueOutputBuffer returned unknown value [%d]",
388 outputBufferId);
389 return -1;
390 }
391 }
392
393 break;
394 }
395
396 return 1;
397}
398
399static void mediacodec_uninit(H264_CONTEXT* h264)
400{
401 media_status_t status = AMEDIA_OK;
402 H264_CONTEXT_MEDIACODEC* sys;
403
404 WINPR_ASSERT(h264);
405
406 sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData;
407
408 WLog_Print(h264->log, WLOG_DEBUG, "Uninitializing MediaCodec");
409
410 if (!sys)
411 return;
412
413 if (sys->decoder != nullptr)
414 {
415 release_current_outputbuffer(h264);
416 status = AMediaCodec_stop(sys->decoder);
417 if (status != AMEDIA_OK)
418 {
419 WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_stop %d", status);
420 }
421
422 status = AMediaCodec_delete(sys->decoder);
423 if (status != AMEDIA_OK)
424 {
425 WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_delete %d", status);
426 }
427
428 sys->decoder = nullptr;
429 }
430
431 set_mediacodec_format(h264, &sys->inputFormat, nullptr);
432 set_mediacodec_format(h264, &sys->outputFormat, nullptr);
433
434 free(sys);
435 h264->pSystemData = nullptr;
436}
437
438static BOOL mediacodec_init(H264_CONTEXT* h264)
439{
440 H264_CONTEXT_MEDIACODEC* sys;
441 media_status_t status;
442
443 WINPR_ASSERT(h264);
444
445 if (h264->Compressor)
446 {
447 WLog_Print(h264->log, WLOG_ERROR, "MediaCodec is not supported as an encoder");
448 goto EXCEPTION;
449 }
450
451 WLog_Print(h264->log, WLOG_DEBUG, "Initializing MediaCodec");
452
453 sys = (H264_CONTEXT_MEDIACODEC*)calloc(1, sizeof(H264_CONTEXT_MEDIACODEC));
454
455 if (!sys)
456 {
457 goto EXCEPTION;
458 }
459
460 h264->pSystemData = (void*)sys;
461
462 sys->currentOutputBufferIndex = -1;
463
464 // updated when we're given the height and width for the first time
465 sys->width = sys->outputWidth = MEDIACODEC_MINIMUM_WIDTH;
466 sys->height = sys->outputHeight = MEDIACODEC_MINIMUM_HEIGHT;
467 sys->decoder = AMediaCodec_createDecoderByType(CODEC_NAME);
468 if (sys->decoder == nullptr)
469 {
470 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_createCodecByName failed");
471 goto EXCEPTION;
472 }
473
474#if __ANDROID_API__ >= 28
475 char* codec_name;
476 status = AMediaCodec_getName(sys->decoder, &codec_name);
477 if (status != AMEDIA_OK)
478 {
479 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getName failed: %d", status);
480 goto EXCEPTION;
481 }
482
483 WLog_Print(h264->log, WLOG_DEBUG, "MediaCodec using %s codec [%s]", CODEC_NAME, codec_name);
484 AMediaCodec_releaseName(sys->decoder, codec_name);
485#endif
486
487 set_mediacodec_format(h264, &sys->inputFormat,
488 mediacodec_format_new(h264->log, sys->width, sys->height));
489
490 status = AMediaCodec_configure(sys->decoder, sys->inputFormat, nullptr, nullptr, 0);
491 if (status != AMEDIA_OK)
492 {
493 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_configure failed: %d", status);
494 goto EXCEPTION;
495 }
496
497 if (update_mediacodec_inputformat(h264) < 0)
498 {
499 WLog_Print(h264->log, WLOG_ERROR, "MediaCodec failed updating input format");
500 goto EXCEPTION;
501 }
502
503 if (update_mediacodec_outputformat(h264) < 0)
504 {
505 WLog_Print(h264->log, WLOG_ERROR, "MediaCodec failed updating output format");
506 goto EXCEPTION;
507 }
508
509 WLog_Print(h264->log, WLOG_DEBUG, "Starting MediaCodec");
510 status = AMediaCodec_start(sys->decoder);
511 if (status != AMEDIA_OK)
512 {
513 WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_start failed %d", status);
514 goto EXCEPTION;
515 }
516
517 return TRUE;
518EXCEPTION:
519 mediacodec_uninit(h264);
520 return FALSE;
521}
522
523const H264_CONTEXT_SUBSYSTEM g_Subsystem_mediacodec = { "MediaCodec", mediacodec_init,
524 mediacodec_uninit, mediacodec_decompress,
525 mediacodec_compress };