FreeRDP
Loading...
Searching...
No Matches
encoding.c
1
20#include <winpr/assert.h>
21#include <winpr/winpr.h>
22
23#include "camera.h"
24
25#define TAG CHANNELS_TAG("rdpecam-video.client")
26
27#if defined(WITH_INPUT_FORMAT_H264)
28/*
29 * demux a H264 frame from a MJPG container
30 * args:
31 * srcData - pointer to buffer with h264 muxed in MJPG container
32 * srcSize - buff size
33 * h264_data - pointer to h264 data
34 * h264_max_size - maximum size allowed by h264_data buffer
35 *
36 * Credits:
37 * guvcview http://guvcview.sourceforge.net
38 * Paulo Assis <pj.assis@gmail.com>
39 *
40 * see Figure 5 Payload Size in USB_Video_Payload_H 264_1 0.pdf
41 * for format details
42 *
43 * @return: data size and copies demuxed data to h264 buffer
44 */
45static size_t demux_uvcH264(const BYTE* srcData, size_t srcSize, BYTE* h264_data,
46 size_t h264_max_size)
47{
48 WINPR_ASSERT(h264_data);
49 WINPR_ASSERT(srcData);
50
51 if (srcSize < 30)
52 {
53 WLog_ERR(TAG, "Expected srcSize >= 30, got %" PRIuz, srcSize);
54 return 0;
55 }
56 const uint8_t* spl = NULL;
57 uint8_t* ph264 = h264_data;
58
59 /* search for 1st APP4 marker
60 * (30 = 2 APP4 marker + 2 length + 22 header + 4 payload size)
61 */
62 for (const uint8_t* sp = srcData; sp < srcData + srcSize - 30; sp++)
63 {
64 if (sp[0] == 0xFF && sp[1] == 0xE4)
65 {
66 spl = sp + 2; /* exclude APP4 marker */
67 break;
68 }
69 }
70
71 if (spl == NULL)
72 {
73 WLog_ERR(TAG, "Expected 1st APP4 marker but none found");
74 return 0;
75 }
76
77 if (spl > srcData + srcSize - 4)
78 {
79 WLog_ERR(TAG, "Payload + Header size bigger than srcData buffer");
80 return 0;
81 }
82
83 /* 1st segment length in big endian
84 * includes payload size + header + 6 bytes (2 length + 4 payload size)
85 */
86 uint16_t length = (uint16_t)(spl[0] << 8) & UINT16_MAX;
87 length |= (uint16_t)spl[1];
88
89 spl += 2; /* header */
90 /* header length in little endian at offset 2 */
91 uint16_t header_length = (uint16_t)spl[2];
92 header_length |= (uint16_t)spl[3] << 8;
93
94 spl += header_length;
95 if (spl > srcData + srcSize)
96 {
97 WLog_ERR(TAG, "Header size bigger than srcData buffer");
98 return 0;
99 }
100
101 /* payload size in little endian */
102 uint32_t payload_size = (uint32_t)spl[0] << 0;
103 payload_size |= (uint32_t)spl[1] << 8;
104 payload_size |= (uint32_t)spl[2] << 16;
105 payload_size |= (uint32_t)spl[3] << 24;
106
107 if (payload_size > h264_max_size)
108 {
109 WLog_ERR(TAG, "Payload size bigger than h264_data buffer");
110 return 0;
111 }
112
113 spl += 4; /* payload start */
114 const uint8_t* epl = spl + payload_size; /* payload end */
115
116 if (epl > srcData + srcSize)
117 {
118 WLog_ERR(TAG, "Payload size bigger than srcData buffer");
119 return 0;
120 }
121
122 length -= header_length + 6;
123
124 /* copy 1st segment to h264 buffer */
125 memcpy(ph264, spl, length);
126 ph264 += length;
127 spl += length;
128
129 /* copy other segments */
130 while (epl > spl + 4)
131 {
132 if (spl[0] != 0xFF || spl[1] != 0xE4)
133 {
134 WLog_ERR(TAG, "Expected 2nd+ APP4 marker but none found");
135 const intptr_t diff = ph264 - h264_data;
136 return WINPR_ASSERTING_INT_CAST(size_t, diff);
137 }
138
139 /* 2nd+ segment length in big endian */
140 length = (uint16_t)(spl[2] << 8) & UINT16_MAX;
141 length |= (uint16_t)spl[3];
142 if (length < 2)
143 {
144 WLog_ERR(TAG, "Expected 2nd+ APP4 length >= 2 but have %" PRIu16, length);
145 return 0;
146 }
147
148 length -= 2;
149 spl += 4; /* APP4 marker + length */
150
151 /* copy segment to h264 buffer */
152 memcpy(ph264, spl, length);
153 ph264 += length;
154 spl += length;
155 }
156
157 const intptr_t diff = ph264 - h264_data;
158 return WINPR_ASSERTING_INT_CAST(size_t, diff);
159}
160#endif
161
167UINT32 h264_get_max_bitrate(UINT32 height)
168{
169 static struct Bitrates
170 {
171 UINT32 height;
172 UINT32 bitrate; /* kbps */
173
174 } bitrates[] = {
175 /* source: https://livekit.io/webrtc/bitrate-guide (webcam streaming)
176 *
177 * sorted by height in descending order
178 */
179 { 1080, 2700 }, { 720, 1250 }, { 480, 700 }, { 360, 400 },
180 { 240, 170 }, { 180, 140 }, { 0, 100 },
181 };
182 const size_t nBitrates = ARRAYSIZE(bitrates);
183
184 for (size_t i = 0; i < nBitrates; i++)
185 {
186 if (height >= bitrates[i].height)
187 {
188 UINT32 bitrate = bitrates[i].bitrate;
189 WLog_DBG(TAG, "Setting h264 max bitrate: %u kbps", bitrate);
190 return bitrate * 1000;
191 }
192 }
193
194 WINPR_ASSERT(FALSE);
195 return 0;
196}
197
203static enum AVPixelFormat ecamToAVPixFormat(CAM_MEDIA_FORMAT ecamFormat)
204{
205 switch (ecamFormat)
206 {
207 case CAM_MEDIA_FORMAT_YUY2:
208 return AV_PIX_FMT_YUYV422;
209 case CAM_MEDIA_FORMAT_NV12:
210 return AV_PIX_FMT_NV12;
211 case CAM_MEDIA_FORMAT_I420:
212 return AV_PIX_FMT_YUV420P;
213 case CAM_MEDIA_FORMAT_RGB24:
214 return AV_PIX_FMT_RGB24;
215 case CAM_MEDIA_FORMAT_RGB32:
216 return AV_PIX_FMT_RGB32;
217 default:
218 WLog_ERR(TAG, "Unsupported ecamFormat %u", ecamFormat);
219 return AV_PIX_FMT_NONE;
220 }
221}
222
223static void ecam_sws_free(CameraDeviceStream* stream)
224{
225 if (stream->sws)
226 {
227 sws_freeContext(stream->sws);
228 stream->sws = NULL;
229 }
230}
231
232static BOOL ecam_sws_valid(const CameraDeviceStream* stream)
233{
234 if (!stream->sws)
235 return FALSE;
236 if (stream->swsWidth != stream->currMediaType.Width)
237 return FALSE;
238 if (stream->swsHeight != stream->currMediaType.Height)
239 return FALSE;
240 if (stream->currMediaType.Width > INT32_MAX)
241 return FALSE;
242 if (stream->currMediaType.Height > INT32_MAX)
243 return FALSE;
244 return TRUE;
245}
246
253static BOOL ecam_init_sws_context(CameraDeviceStream* stream, enum AVPixelFormat pixFormat)
254{
255 WINPR_ASSERT(stream);
256
257 if (stream->currMediaType.Width > INT32_MAX)
258 return FALSE;
259 if (stream->currMediaType.Height > INT32_MAX)
260 return FALSE;
261
262 if (ecam_sws_valid(stream))
263 return TRUE;
264
265 ecam_sws_free(stream);
266
267 /* replacing deprecated JPEG formats, still produced by decoder */
268 switch (pixFormat)
269 {
270 case AV_PIX_FMT_YUVJ411P:
271 pixFormat = AV_PIX_FMT_YUV411P;
272 break;
273
274 case AV_PIX_FMT_YUVJ420P:
275 pixFormat = AV_PIX_FMT_YUV420P;
276 break;
277
278 case AV_PIX_FMT_YUVJ422P:
279 pixFormat = AV_PIX_FMT_YUV422P;
280 break;
281
282 case AV_PIX_FMT_YUVJ440P:
283 pixFormat = AV_PIX_FMT_YUV440P;
284 break;
285
286 case AV_PIX_FMT_YUVJ444P:
287 pixFormat = AV_PIX_FMT_YUV444P;
288 break;
289
290 default:
291 break;
292 }
293
294 stream->swsWidth = stream->currMediaType.Width;
295 stream->swsHeight = stream->currMediaType.Height;
296 const int width = WINPR_ASSERTING_INT_CAST(int, stream->currMediaType.Width);
297 const int height = WINPR_ASSERTING_INT_CAST(int, stream->currMediaType.Height);
298
299 const enum AVPixelFormat outPixFormat =
300 h264_context_get_option(stream->h264, H264_CONTEXT_OPTION_HW_ACCEL) ? AV_PIX_FMT_NV12
301 : AV_PIX_FMT_YUV420P;
302
303 stream->sws =
304 sws_getContext(width, height, pixFormat, width, height, outPixFormat, 0, NULL, NULL, NULL);
305 if (!stream->sws)
306 {
307 WLog_ERR(TAG, "sws_getContext failed");
308 return FALSE;
309 }
310
311 return TRUE;
312}
313
319static BOOL ecam_encoder_compress_h264(CameraDeviceStream* stream, const BYTE* srcData,
320 size_t srcSize, BYTE** ppDstData, size_t* pDstSize)
321{
322 UINT32 dstSize = 0;
323 BYTE* srcSlice[4] = { 0 };
324 int srcLineSizes[4] = { 0 };
325 BYTE* yuvData[3] = { 0 };
326 UINT32 yuvLineSizes[3] = { 0 };
327 prim_size_t size = { stream->currMediaType.Width, stream->currMediaType.Height };
328 CAM_MEDIA_FORMAT inputFormat = streamInputFormat(stream);
329 enum AVPixelFormat pixFormat = AV_PIX_FMT_NONE;
330
331 if (!ecam_sws_valid(stream))
332 return FALSE;
333
334#if defined(WITH_INPUT_FORMAT_H264)
335 if (inputFormat == CAM_MEDIA_FORMAT_MJPG_H264)
336 {
337 const size_t rc =
338 demux_uvcH264(srcData, srcSize, stream->h264Frame, stream->h264FrameMaxSize);
339 dstSize = WINPR_ASSERTING_INT_CAST(uint32_t, rc);
340 *ppDstData = stream->h264Frame;
341 *pDstSize = dstSize;
342 return dstSize > 0;
343 }
344 else
345#endif
346
347#if defined(WITH_INPUT_FORMAT_MJPG)
348 if (inputFormat == CAM_MEDIA_FORMAT_MJPG)
349 {
350 stream->avInputPkt->data = WINPR_CAST_CONST_PTR_AWAY(srcData, uint8_t*);
351 WINPR_ASSERT(srcSize <= INT32_MAX);
352 stream->avInputPkt->size = (int)srcSize;
353
354 if (avcodec_send_packet(stream->avContext, stream->avInputPkt) < 0)
355 {
356 WLog_ERR(TAG, "avcodec_send_packet failed");
357 return FALSE;
358 }
359
360 if (avcodec_receive_frame(stream->avContext, stream->avOutFrame) < 0)
361 {
362 WLog_ERR(TAG, "avcodec_receive_frame failed");
363 return FALSE;
364 }
365
366 for (size_t i = 0; i < 4; i++)
367 {
368 srcSlice[i] = stream->avOutFrame->data[i];
369 srcLineSizes[i] = stream->avOutFrame->linesize[i];
370 }
371
372 /* get pixFormat produced by MJPEG decoder */
373 pixFormat = stream->avContext->pix_fmt;
374 }
375 else
376#endif
377 {
378 pixFormat = ecamToAVPixFormat(inputFormat);
379
380 if (av_image_fill_linesizes(srcLineSizes, pixFormat, (int)size.width) < 0)
381 {
382 WLog_ERR(TAG, "av_image_fill_linesizes failed");
383 return FALSE;
384 }
385
386 if (av_image_fill_pointers(srcSlice, pixFormat, (int)size.height,
387 WINPR_CAST_CONST_PTR_AWAY(srcData, BYTE*), srcLineSizes) < 0)
388 {
389 WLog_ERR(TAG, "av_image_fill_pointers failed");
390 return FALSE;
391 }
392 }
393
394 /* get buffers for YUV420P or NV12 */
395 if (h264_get_yuv_buffer(stream->h264, 0, size.width, size.height, yuvData, yuvLineSizes) < 0)
396 return FALSE;
397
398 /* convert from source format to YUV420P or NV12 */
399 if (!ecam_init_sws_context(stream, pixFormat))
400 return FALSE;
401
402 const BYTE* cSrcSlice[4] = { srcSlice[0], srcSlice[1], srcSlice[2], srcSlice[3] };
403 if (sws_scale(stream->sws, cSrcSlice, srcLineSizes, 0, (int)size.height, yuvData,
404 (int*)yuvLineSizes) <= 0)
405 return FALSE;
406
407 /* encode from YUV420P or NV12 to H264 */
408 if (h264_compress(stream->h264, ppDstData, &dstSize) < 0)
409 return FALSE;
410
411 *pDstSize = dstSize;
412
413 return TRUE;
414}
415
420static void ecam_encoder_context_free_h264(CameraDeviceStream* stream)
421{
422 WINPR_ASSERT(stream);
423
424 ecam_sws_free(stream);
425
426#if defined(WITH_INPUT_FORMAT_MJPG)
427 if (stream->avOutFrame)
428 av_frame_free(&stream->avOutFrame); /* sets to NULL */
429
430 if (stream->avInputPkt)
431 {
432 stream->avInputPkt->data = NULL;
433 stream->avInputPkt->size = 0;
434 av_packet_free(&stream->avInputPkt); /* sets to NULL */
435 }
436
437 if (stream->avContext)
438 avcodec_free_context(&stream->avContext); /* sets to NULL */
439#endif
440
441#if defined(WITH_INPUT_FORMAT_H264)
442 if (stream->h264Frame)
443 {
444 free(stream->h264Frame);
445 stream->h264Frame = NULL;
446 }
447#endif
448
449 if (stream->h264)
450 {
451 h264_context_free(stream->h264);
452 stream->h264 = NULL;
453 }
454}
455
456#if defined(WITH_INPUT_FORMAT_MJPG)
462static BOOL ecam_init_mjpeg_decoder(CameraDeviceStream* stream)
463{
464 WINPR_ASSERT(stream);
465
466 const AVCodec* avcodec = avcodec_find_decoder(AV_CODEC_ID_MJPEG);
467 if (!avcodec)
468 {
469 WLog_ERR(TAG, "avcodec_find_decoder failed to find MJPEG codec");
470 return FALSE;
471 }
472
473 stream->avContext = avcodec_alloc_context3(avcodec);
474 if (!stream->avContext)
475 {
476 WLog_ERR(TAG, "avcodec_alloc_context3 failed");
477 return FALSE;
478 }
479
480 stream->avContext->width = WINPR_ASSERTING_INT_CAST(int, stream->currMediaType.Width);
481 stream->avContext->height = WINPR_ASSERTING_INT_CAST(int, stream->currMediaType.Height);
482
483 /* AV_EF_EXPLODE flag is to abort decoding on minor error detection,
484 * return error, so we can skip corrupted frames, if any */
485 stream->avContext->err_recognition |= AV_EF_EXPLODE;
486
487 if (avcodec_open2(stream->avContext, avcodec, NULL) < 0)
488 {
489 WLog_ERR(TAG, "avcodec_open2 failed");
490 return FALSE;
491 }
492
493 stream->avInputPkt = av_packet_alloc();
494 if (!stream->avInputPkt)
495 {
496 WLog_ERR(TAG, "av_packet_alloc failed");
497 return FALSE;
498 }
499
500 stream->avOutFrame = av_frame_alloc();
501 if (!stream->avOutFrame)
502 {
503 WLog_ERR(TAG, "av_frame_alloc failed");
504 return FALSE;
505 }
506
507 return TRUE;
508}
509#endif
510
516static BOOL ecam_encoder_context_init_h264(CameraDeviceStream* stream)
517{
518 WINPR_ASSERT(stream);
519
520#if defined(WITH_INPUT_FORMAT_H264)
521 if (streamInputFormat(stream) == CAM_MEDIA_FORMAT_MJPG_H264)
522 {
523 stream->h264FrameMaxSize = 1ULL * stream->currMediaType.Width *
524 stream->currMediaType.Height; /* 1 byte per pixel */
525 stream->h264Frame = (BYTE*)calloc(stream->h264FrameMaxSize, sizeof(BYTE));
526 return TRUE; /* encoder not needed */
527 }
528#endif
529
530 if (!stream->h264)
531 stream->h264 = h264_context_new(TRUE);
532
533 if (!stream->h264)
534 {
535 WLog_ERR(TAG, "h264_context_new failed");
536 return FALSE;
537 }
538
539 if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_USAGETYPE,
540 H264_CAMERA_VIDEO_REAL_TIME))
541 goto fail;
542
543 if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_FRAMERATE,
544 stream->currMediaType.FrameRateNumerator /
545 stream->currMediaType.FrameRateDenominator))
546 goto fail;
547
548 if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_BITRATE,
549 h264_get_max_bitrate(stream->currMediaType.Height)))
550 goto fail;
551
552 /* Using CQP mode for rate control. It produces more comparable quality
553 * between VAAPI and software encoding than VBR mode
554 */
555 if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_RATECONTROL,
556 H264_RATECONTROL_CQP))
557 goto fail;
558
559 /* Using 26 as CQP value. Lower values will produce better quality but
560 * higher bitrate; higher values - lower bitrate but degraded quality
561 */
562 if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_QP, 26))
563 goto fail;
564
565 /* Requesting hardware acceleration before calling h264_context_reset */
566 if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_HW_ACCEL, TRUE))
567 goto fail;
568
569 if (!h264_context_reset(stream->h264, stream->currMediaType.Width,
570 stream->currMediaType.Height))
571 {
572 WLog_ERR(TAG, "h264_context_reset failed");
573 goto fail;
574 }
575
576#if defined(WITH_INPUT_FORMAT_MJPG)
577 if (streamInputFormat(stream) == CAM_MEDIA_FORMAT_MJPG && !ecam_init_mjpeg_decoder(stream))
578 goto fail;
579#endif
580
581 return TRUE;
582
583fail:
584 ecam_encoder_context_free_h264(stream);
585 return FALSE;
586}
587
593BOOL ecam_encoder_context_init(CameraDeviceStream* stream)
594{
595 CAM_MEDIA_FORMAT format = streamOutputFormat(stream);
596
597 switch (format)
598 {
599 case CAM_MEDIA_FORMAT_H264:
600 return ecam_encoder_context_init_h264(stream);
601
602 default:
603 WLog_ERR(TAG, "Unsupported output format %u", format);
604 return FALSE;
605 }
606}
607
613BOOL ecam_encoder_context_free(CameraDeviceStream* stream)
614{
615 CAM_MEDIA_FORMAT format = streamOutputFormat(stream);
616 switch (format)
617 {
618 case CAM_MEDIA_FORMAT_H264:
619 ecam_encoder_context_free_h264(stream);
620 break;
621
622 default:
623 return FALSE;
624 }
625 return TRUE;
626}
627
633BOOL ecam_encoder_compress(CameraDeviceStream* stream, const BYTE* srcData, size_t srcSize,
634 BYTE** ppDstData, size_t* pDstSize)
635{
636 CAM_MEDIA_FORMAT format = streamOutputFormat(stream);
637 switch (format)
638 {
639 case CAM_MEDIA_FORMAT_H264:
640 return ecam_encoder_compress_h264(stream, srcData, srcSize, ppDstData, pDstSize);
641 default:
642 WLog_ERR(TAG, "Unsupported output format %u", format);
643 return FALSE;
644 }
645}