FreeRDP
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 
32 static UINT32 ecam_encoder_h264_get_max_bitrate(CameraDeviceStream* stream)
33 {
34  static struct Bitrates
35  {
36  UINT32 height;
37  UINT32 bitrate; /* kbps */
38 
39  } bitrates[] = {
40  /* source: https://livekit.io/webrtc/bitrate-guide (webcam streaming)
41  *
42  * sorted by height in descending order
43  */
44  { 1080, 2700 }, { 720, 1250 }, { 480, 700 }, { 360, 400 },
45  { 240, 170 }, { 180, 140 }, { 0, 100 },
46  };
47  const size_t nBitrates = ARRAYSIZE(bitrates);
48 
49  UINT32 height = stream->currMediaType.Height;
50 
51  for (size_t i = 0; i < nBitrates; i++)
52  {
53  if (height >= bitrates[i].height)
54  {
55  UINT32 bitrate = bitrates[i].bitrate;
56  WLog_DBG(TAG, "Setting h264 max bitrate: %u kbps", bitrate);
57  return bitrate * 1000;
58  }
59  }
60 
61  WINPR_ASSERT(FALSE);
62  return 0;
63 }
64 
70 static enum AVPixelFormat ecamToAVPixFormat(CAM_MEDIA_FORMAT ecamFormat)
71 {
72  switch (ecamFormat)
73  {
74  case CAM_MEDIA_FORMAT_YUY2:
75  return AV_PIX_FMT_YUYV422;
76  case CAM_MEDIA_FORMAT_NV12:
77  return AV_PIX_FMT_NV12;
78  case CAM_MEDIA_FORMAT_I420:
79  return AV_PIX_FMT_YUV420P;
80  case CAM_MEDIA_FORMAT_RGB24:
81  return AV_PIX_FMT_RGB24;
82  case CAM_MEDIA_FORMAT_RGB32:
83  return AV_PIX_FMT_RGB32;
84  default:
85  WLog_ERR(TAG, "Unsupported ecamFormat %d", ecamFormat);
86  return AV_PIX_FMT_NONE;
87  }
88 }
89 
96 static BOOL ecam_init_sws_context(CameraDeviceStream* stream, enum AVPixelFormat pixFormat)
97 {
98  WINPR_ASSERT(stream);
99 
100  if (stream->sws)
101  return TRUE;
102 
103  /* replacing deprecated JPEG formats, still produced by decoder */
104  switch (pixFormat)
105  {
106  case AV_PIX_FMT_YUVJ411P:
107  pixFormat = AV_PIX_FMT_YUV411P;
108  break;
109 
110  case AV_PIX_FMT_YUVJ420P:
111  pixFormat = AV_PIX_FMT_YUV420P;
112  break;
113 
114  case AV_PIX_FMT_YUVJ422P:
115  pixFormat = AV_PIX_FMT_YUV422P;
116  break;
117 
118  case AV_PIX_FMT_YUVJ440P:
119  pixFormat = AV_PIX_FMT_YUV440P;
120  break;
121 
122  case AV_PIX_FMT_YUVJ444P:
123  pixFormat = AV_PIX_FMT_YUV444P;
124  break;
125 
126  default:
127  break;
128  }
129 
130  const int width = (int)stream->currMediaType.Width;
131  const int height = (int)stream->currMediaType.Height;
132 
133  stream->sws = sws_getContext(width, height, pixFormat, width, height, AV_PIX_FMT_YUV420P, 0,
134  NULL, NULL, NULL);
135  if (!stream->sws)
136  {
137  WLog_ERR(TAG, "sws_getContext failed");
138  return FALSE;
139  }
140 
141  return TRUE;
142 }
143 
149 static BOOL ecam_encoder_compress_h264(CameraDeviceStream* stream, const BYTE* srcData,
150  size_t srcSize, BYTE** ppDstData, size_t* pDstSize)
151 {
152  UINT32 dstSize = 0;
153  BYTE* srcSlice[4] = { 0 };
154  int srcLineSizes[4] = { 0 };
155  BYTE* yuv420pData[3] = { 0 };
156  UINT32 yuv420pStride[3] = { 0 };
157  prim_size_t size = { stream->currMediaType.Width, stream->currMediaType.Height };
158  CAM_MEDIA_FORMAT inputFormat = streamInputFormat(stream);
159  enum AVPixelFormat pixFormat = AV_PIX_FMT_NONE;
160 
161 #if defined(WITH_INPUT_FORMAT_MJPG)
162  if (inputFormat == CAM_MEDIA_FORMAT_MJPG)
163  {
164  stream->avInputPkt->data = WINPR_CAST_CONST_PTR_AWAY(srcData, uint8_t*);
165  WINPR_ASSERT(srcSize <= INT32_MAX);
166  stream->avInputPkt->size = (int)srcSize;
167 
168  if (avcodec_send_packet(stream->avContext, stream->avInputPkt) < 0)
169  {
170  WLog_ERR(TAG, "avcodec_send_packet failed");
171  return FALSE;
172  }
173 
174  if (avcodec_receive_frame(stream->avContext, stream->avOutFrame) < 0)
175  {
176  WLog_ERR(TAG, "avcodec_receive_frame failed");
177  return FALSE;
178  }
179 
180  for (size_t i = 0; i < 4; i++)
181  {
182  srcSlice[i] = stream->avOutFrame->data[i];
183  srcLineSizes[i] = stream->avOutFrame->linesize[i];
184  }
185 
186  /* get pixFormat produced by MJPEG decoder */
187  pixFormat = stream->avContext->pix_fmt;
188  }
189  else
190 #endif
191  {
192  pixFormat = ecamToAVPixFormat(inputFormat);
193 
194  if (av_image_fill_linesizes(srcLineSizes, pixFormat, (int)size.width) < 0)
195  {
196  WLog_ERR(TAG, "av_image_fill_linesizes failed");
197  return FALSE;
198  }
199 
200  if (av_image_fill_pointers(srcSlice, pixFormat, (int)size.height,
201  WINPR_CAST_CONST_PTR_AWAY(srcData, BYTE*), srcLineSizes) < 0)
202  {
203  WLog_ERR(TAG, "av_image_fill_pointers failed");
204  return FALSE;
205  }
206  }
207 
208  /* get buffers for YUV420P */
209  if (h264_get_yuv_buffer(stream->h264, srcLineSizes[0], size.width, size.height, yuv420pData,
210  yuv420pStride) < 0)
211  return FALSE;
212 
213  /* convert from source format to YUV420P */
214  if (!ecam_init_sws_context(stream, pixFormat))
215  return FALSE;
216 
217  const BYTE* cSrcSlice[4] = { srcSlice[0], srcSlice[1], srcSlice[2], srcSlice[3] };
218  if (sws_scale(stream->sws, cSrcSlice, srcLineSizes, 0, (int)size.height, yuv420pData,
219  (int*)yuv420pStride) <= 0)
220  return FALSE;
221 
222  /* encode from YUV420P to H264 */
223  if (h264_compress(stream->h264, ppDstData, &dstSize) < 0)
224  return FALSE;
225 
226  *pDstSize = dstSize;
227 
228  return TRUE;
229 }
230 
235 static void ecam_encoder_context_free_h264(CameraDeviceStream* stream)
236 {
237  WINPR_ASSERT(stream);
238 
239  if (stream->sws)
240  {
241  sws_freeContext(stream->sws);
242  stream->sws = NULL;
243  }
244 
245 #if defined(WITH_INPUT_FORMAT_MJPG)
246  if (stream->avOutFrame)
247  av_frame_free(&stream->avOutFrame); /* sets to NULL */
248 
249  if (stream->avInputPkt)
250  {
251  stream->avInputPkt->data = NULL;
252  stream->avInputPkt->size = 0;
253  av_packet_free(&stream->avInputPkt); /* sets to NULL */
254  }
255 
256  if (stream->avContext)
257  avcodec_free_context(&stream->avContext); /* sets to NULL */
258 #endif
259 
260  if (stream->h264)
261  {
262  h264_context_free(stream->h264);
263  stream->h264 = NULL;
264  }
265 }
266 
267 #if defined(WITH_INPUT_FORMAT_MJPG)
273 static BOOL ecam_init_mjpeg_decoder(CameraDeviceStream* stream)
274 {
275  WINPR_ASSERT(stream);
276 
277  const AVCodec* avcodec = avcodec_find_decoder(AV_CODEC_ID_MJPEG);
278  if (!avcodec)
279  {
280  WLog_ERR(TAG, "avcodec_find_decoder failed to find MJPEG codec");
281  return FALSE;
282  }
283 
284  stream->avContext = avcodec_alloc_context3(avcodec);
285  if (!stream->avContext)
286  {
287  WLog_ERR(TAG, "avcodec_alloc_context3 failed");
288  return FALSE;
289  }
290 
291  stream->avContext->width = stream->currMediaType.Width;
292  stream->avContext->height = stream->currMediaType.Height;
293 
294  /* AV_EF_EXPLODE flag is to abort decoding on minor error detection,
295  * return error, so we can skip corrupted frames, if any */
296  stream->avContext->err_recognition |= AV_EF_EXPLODE;
297 
298  if (avcodec_open2(stream->avContext, avcodec, NULL) < 0)
299  {
300  WLog_ERR(TAG, "avcodec_open2 failed");
301  return FALSE;
302  }
303 
304  stream->avInputPkt = av_packet_alloc();
305  if (!stream->avInputPkt)
306  {
307  WLog_ERR(TAG, "av_packet_alloc failed");
308  return FALSE;
309  }
310 
311  stream->avOutFrame = av_frame_alloc();
312  if (!stream->avOutFrame)
313  {
314  WLog_ERR(TAG, "av_frame_alloc failed");
315  return FALSE;
316  }
317 
318  return TRUE;
319 }
320 #endif
321 
327 static BOOL ecam_encoder_context_init_h264(CameraDeviceStream* stream)
328 {
329  WINPR_ASSERT(stream);
330 
331  if (!stream->h264)
332  stream->h264 = h264_context_new(TRUE);
333 
334  if (!stream->h264)
335  {
336  WLog_ERR(TAG, "h264_context_new failed");
337  return FALSE;
338  }
339 
340  if (!h264_context_reset(stream->h264, stream->currMediaType.Width,
341  stream->currMediaType.Height))
342  goto fail;
343 
344  if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_USAGETYPE,
345  H264_CAMERA_VIDEO_REAL_TIME))
346  goto fail;
347 
348  if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_FRAMERATE,
349  stream->currMediaType.FrameRateNumerator /
350  stream->currMediaType.FrameRateDenominator))
351  goto fail;
352 
353  if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_BITRATE,
354  ecam_encoder_h264_get_max_bitrate(stream)))
355  goto fail;
356 
357  if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_RATECONTROL,
358  H264_RATECONTROL_VBR))
359  goto fail;
360 
361  if (!h264_context_set_option(stream->h264, H264_CONTEXT_OPTION_QP, 0))
362  goto fail;
363 
364 #if defined(WITH_INPUT_FORMAT_MJPG)
365  if (streamInputFormat(stream) == CAM_MEDIA_FORMAT_MJPG && !ecam_init_mjpeg_decoder(stream))
366  goto fail;
367 #endif
368 
369  return TRUE;
370 
371 fail:
372  ecam_encoder_context_free_h264(stream);
373  return FALSE;
374 }
375 
381 BOOL ecam_encoder_context_init(CameraDeviceStream* stream)
382 {
383  CAM_MEDIA_FORMAT format = streamOutputFormat(stream);
384 
385  switch (format)
386  {
387  case CAM_MEDIA_FORMAT_H264:
388  return ecam_encoder_context_init_h264(stream);
389 
390  default:
391  WLog_ERR(TAG, "Unsupported output format %d", format);
392  return FALSE;
393  }
394 }
395 
401 BOOL ecam_encoder_context_free(CameraDeviceStream* stream)
402 {
403  CAM_MEDIA_FORMAT format = streamOutputFormat(stream);
404  switch (format)
405  {
406  case CAM_MEDIA_FORMAT_H264:
407  ecam_encoder_context_free_h264(stream);
408  break;
409 
410  default:
411  return FALSE;
412  }
413  return TRUE;
414 }
415 
421 BOOL ecam_encoder_compress(CameraDeviceStream* stream, const BYTE* srcData, size_t srcSize,
422  BYTE** ppDstData, size_t* pDstSize)
423 {
424  CAM_MEDIA_FORMAT format = streamOutputFormat(stream);
425  switch (format)
426  {
427  case CAM_MEDIA_FORMAT_H264:
428  return ecam_encoder_compress_h264(stream, srcData, srcSize, ppDstData, pDstSize);
429  default:
430  WLog_ERR(TAG, "Unsupported output format %d", format);
431  return FALSE;
432  }
433 }