VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/VideoRecStream.cpp@ 74992

Last change on this file since 74992 was 74992, checked in by vboxsync, 6 years ago

VideoRec/Main: Split up code into more modules for easier maintenance.

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
File size: 18.3 KB
Line 
1/* $Id: VideoRecStream.cpp 74992 2018-10-23 11:09:22Z vboxsync $ */
2/** @file
3 * Video recording stream code.
4 */
5
6/*
7 * Copyright (C) 2012-2018 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18#ifdef LOG_GROUP
19# undef LOG_GROUP
20#endif
21#define LOG_GROUP LOG_GROUP_MAIN_DISPLAY
22#include "LoggingNew.h"
23
24#include "VideoRec.h"
25#include "VideoRecStream.h"
26
27#include <iprt/asm.h>
28#include <iprt/assert.h>
29#include <iprt/critsect.h>
30#include <iprt/file.h>
31#include <iprt/path.h>
32#include <iprt/semaphore.h>
33#include <iprt/thread.h>
34#include <iprt/time.h>
35
36#include <VBox/err.h>
37#include <VBox/com/VirtualBox.h>
38
39
40/**
41 * Retrieves a specific recording stream of a recording context.
42 *
43 * @returns Pointer to recording stream if found, or NULL if not found.
44 * @param pCtx Recording context to look up stream for.
45 * @param uScreen Screen number of recording stream to look up.
46 */
47PVIDEORECSTREAM videoRecStreamGet(PVIDEORECCONTEXT pCtx, uint32_t uScreen)
48{
49 AssertPtrReturn(pCtx, NULL);
50
51 PVIDEORECSTREAM pStream;
52
53 try
54 {
55 pStream = pCtx->vecStreams.at(uScreen);
56 }
57 catch (std::out_of_range &)
58 {
59 pStream = NULL;
60 }
61
62 return pStream;
63}
64
65/**
66 * Locks a recording stream.
67 *
68 * @param pStream Recording stream to lock.
69 */
70void videoRecStreamLock(PVIDEORECSTREAM pStream)
71{
72 int rc = RTCritSectEnter(&pStream->CritSect);
73 AssertRC(rc);
74}
75
76/**
77 * Unlocks a locked recording stream.
78 *
79 * @param pStream Recording stream to unlock.
80 */
81void videoRecStreamUnlock(PVIDEORECSTREAM pStream)
82{
83 int rc = RTCritSectLeave(&pStream->CritSect);
84 AssertRC(rc);
85}
86
87/**
88 * Opens a recording stream.
89 *
90 * @returns IPRT status code.
91 * @param pStream Recording stream to open.
92 * @param pCfg Recording configuration to use.
93 */
94int videoRecStreamOpen(PVIDEORECSTREAM pStream, PVIDEORECCFG pCfg)
95{
96 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
97 AssertPtrReturn(pCfg, VERR_INVALID_POINTER);
98
99 Assert(pStream->enmDst == VIDEORECDEST_INVALID);
100
101 int rc;
102
103 switch (pCfg->enmDst)
104 {
105 case VIDEORECDEST_FILE:
106 {
107 Assert(pCfg->File.strName.isNotEmpty());
108
109 char *pszAbsPath = RTPathAbsDup(com::Utf8Str(pCfg->File.strName).c_str());
110 AssertPtrReturn(pszAbsPath, VERR_NO_MEMORY);
111
112 RTPathStripSuffix(pszAbsPath);
113
114 char *pszSuff = RTStrDup(".webm");
115 if (!pszSuff)
116 {
117 RTStrFree(pszAbsPath);
118 rc = VERR_NO_MEMORY;
119 break;
120 }
121
122 char *pszFile = NULL;
123
124 if (pCfg->aScreens.size() > 1)
125 rc = RTStrAPrintf(&pszFile, "%s-%u%s", pszAbsPath, pStream->uScreenID + 1, pszSuff);
126 else
127 rc = RTStrAPrintf(&pszFile, "%s%s", pszAbsPath, pszSuff);
128
129 if (RT_SUCCESS(rc))
130 {
131 uint64_t fOpen = RTFILE_O_WRITE | RTFILE_O_DENY_WRITE;
132
133 /* Play safe: the file must not exist, overwriting is potentially
134 * hazardous as nothing prevents the user from picking a file name of some
135 * other important file, causing unintentional data loss. */
136 fOpen |= RTFILE_O_CREATE;
137
138 RTFILE hFile;
139 rc = RTFileOpen(&hFile, pszFile, fOpen);
140 if (rc == VERR_ALREADY_EXISTS)
141 {
142 RTStrFree(pszFile);
143 pszFile = NULL;
144
145 RTTIMESPEC ts;
146 RTTimeNow(&ts);
147 RTTIME time;
148 RTTimeExplode(&time, &ts);
149
150 if (pCfg->aScreens.size() > 1)
151 rc = RTStrAPrintf(&pszFile, "%s-%04d-%02u-%02uT%02u-%02u-%02u-%09uZ-%u%s",
152 pszAbsPath, time.i32Year, time.u8Month, time.u8MonthDay,
153 time.u8Hour, time.u8Minute, time.u8Second, time.u32Nanosecond,
154 pStream->uScreenID + 1, pszSuff);
155 else
156 rc = RTStrAPrintf(&pszFile, "%s-%04d-%02u-%02uT%02u-%02u-%02u-%09uZ%s",
157 pszAbsPath, time.i32Year, time.u8Month, time.u8MonthDay,
158 time.u8Hour, time.u8Minute, time.u8Second, time.u32Nanosecond,
159 pszSuff);
160
161 if (RT_SUCCESS(rc))
162 rc = RTFileOpen(&hFile, pszFile, fOpen);
163 }
164
165 if (RT_SUCCESS(rc))
166 {
167 pStream->enmDst = VIDEORECDEST_FILE;
168 pStream->File.hFile = hFile;
169 pStream->File.pszFile = pszFile; /* Assign allocated string to our stream's config. */
170 }
171 }
172
173 RTStrFree(pszSuff);
174 RTStrFree(pszAbsPath);
175
176 if (RT_FAILURE(rc))
177 {
178 LogRel(("VideoRec: Failed to open file '%s' for screen %RU32, rc=%Rrc\n",
179 pszFile ? pszFile : "<Unnamed>", pStream->uScreenID, rc));
180 RTStrFree(pszFile);
181 }
182
183 break;
184 }
185
186 default:
187 rc = VERR_NOT_IMPLEMENTED;
188 break;
189 }
190
191 LogFlowFuncLeaveRC(rc);
192 return rc;
193}
194
195/**
196 * VideoRec utility function to initialize video recording context.
197 *
198 * @returns IPRT status code.
199 * @param pCtx Pointer to video recording context.
200 * @param uScreen Screen number to record.
201 */
202int VideoRecStreamInit(PVIDEORECCONTEXT pCtx, uint32_t uScreen)
203{
204 AssertPtrReturn(pCtx, VERR_INVALID_POINTER);
205
206 PVIDEORECCFG pCfg = &pCtx->Cfg;
207
208#ifdef VBOX_WITH_AUDIO_VIDEOREC
209 if (pCfg->Audio.fEnabled)
210 {
211 /* Sanity. */
212 AssertReturn(pCfg->Audio.uHz, VERR_INVALID_PARAMETER);
213 AssertReturn(pCfg->Audio.cBits, VERR_INVALID_PARAMETER);
214 AssertReturn(pCfg->Audio.cChannels, VERR_INVALID_PARAMETER);
215 }
216#endif
217
218 PVIDEORECSTREAM pStream = videoRecStreamGet(pCtx, uScreen);
219 if (!pStream)
220 return VERR_NOT_FOUND;
221
222 int rc = videoRecStreamOpen(pStream, pCfg);
223 if (RT_FAILURE(rc))
224 return rc;
225
226 pStream->pCtx = pCtx;
227
228 if (pCfg->Video.fEnabled)
229 rc = videoRecStreamInitVideo(pStream, pCfg);
230
231 switch (pStream->enmDst)
232 {
233 case VIDEORECDEST_FILE:
234 {
235 rc = pStream->File.pWEBM->OpenEx(pStream->File.pszFile, &pStream->File.hFile,
236#ifdef VBOX_WITH_AUDIO_VIDEOREC
237 pCfg->Audio.fEnabled ? WebMWriter::AudioCodec_Opus : WebMWriter::AudioCodec_None,
238#else
239 WebMWriter::AudioCodec_None,
240#endif
241 pCfg->Video.fEnabled ? WebMWriter::VideoCodec_VP8 : WebMWriter::VideoCodec_None);
242 if (RT_FAILURE(rc))
243 {
244 LogRel(("VideoRec: Failed to create the capture output file '%s' (%Rrc)\n", pStream->File.pszFile, rc));
245 break;
246 }
247
248 const char *pszFile = pStream->File.pszFile;
249
250 if (pCfg->Video.fEnabled)
251 {
252 rc = pStream->File.pWEBM->AddVideoTrack(pCfg->Video.uWidth, pCfg->Video.uHeight, pCfg->Video.uFPS,
253 &pStream->uTrackVideo);
254 if (RT_FAILURE(rc))
255 {
256 LogRel(("VideoRec: Failed to add video track to output file '%s' (%Rrc)\n", pszFile, rc));
257 break;
258 }
259
260 LogRel(("VideoRec: Recording video of screen #%u with %RU32x%RU32 @ %RU32 kbps, %RU32 FPS (track #%RU8)\n",
261 uScreen, pCfg->Video.uWidth, pCfg->Video.uHeight, pCfg->Video.uRate, pCfg->Video.uFPS,
262 pStream->uTrackVideo));
263 }
264
265#ifdef VBOX_WITH_AUDIO_VIDEOREC
266 if (pCfg->Audio.fEnabled)
267 {
268 rc = pStream->File.pWEBM->AddAudioTrack(pCfg->Audio.uHz, pCfg->Audio.cChannels, pCfg->Audio.cBits,
269 &pStream->uTrackAudio);
270 if (RT_FAILURE(rc))
271 {
272 LogRel(("VideoRec: Failed to add audio track to output file '%s' (%Rrc)\n", pszFile, rc));
273 break;
274 }
275
276 LogRel(("VideoRec: Recording audio in %RU16Hz, %RU8 bit, %RU8 %s (track #%RU8)\n",
277 pCfg->Audio.uHz, pCfg->Audio.cBits, pCfg->Audio.cChannels, pCfg->Audio.cChannels ? "channels" : "channel",
278 pStream->uTrackAudio));
279 }
280#endif
281
282 if ( pCfg->Video.fEnabled
283#ifdef VBOX_WITH_AUDIO_VIDEOREC
284 || pCfg->Audio.fEnabled
285#endif
286 )
287 {
288 char szWhat[32] = { 0 };
289 if (pCfg->Video.fEnabled)
290 RTStrCat(szWhat, sizeof(szWhat), "video");
291#ifdef VBOX_WITH_AUDIO_VIDEOREC
292 if (pCfg->Audio.fEnabled)
293 {
294 if (pCfg->Video.fEnabled)
295 RTStrCat(szWhat, sizeof(szWhat), " + ");
296 RTStrCat(szWhat, sizeof(szWhat), "audio");
297 }
298#endif
299 LogRel(("VideoRec: Recording %s to '%s'\n", szWhat, pszFile));
300 }
301
302 break;
303 }
304
305 default:
306 AssertFailed(); /* Should never happen. */
307 rc = VERR_NOT_IMPLEMENTED;
308 break;
309 }
310
311 if (RT_FAILURE(rc))
312 {
313 int rc2 = videoRecStreamClose(pStream);
314 AssertRC(rc2);
315 return rc;
316 }
317
318 pStream->fEnabled = true;
319
320 return VINF_SUCCESS;
321}
322
323/**
324 * Closes a recording stream.
325 * Depending on the stream's recording destination, this function closes all associated handles
326 * and finalizes recording.
327 *
328 * @returns IPRT status code.
329 * @param pStream Recording stream to close.
330 *
331 */
332int videoRecStreamClose(PVIDEORECSTREAM pStream)
333{
334 int rc = VINF_SUCCESS;
335
336 if (pStream->fEnabled)
337 {
338 switch (pStream->enmDst)
339 {
340 case VIDEORECDEST_FILE:
341 {
342 if (pStream->File.pWEBM)
343 rc = pStream->File.pWEBM->Close();
344 break;
345 }
346
347 default:
348 AssertFailed(); /* Should never happen. */
349 break;
350 }
351
352 pStream->Blocks.Clear();
353
354 LogRel(("VideoRec: Recording screen #%u stopped\n", pStream->uScreenID));
355 }
356
357 if (RT_FAILURE(rc))
358 {
359 LogRel(("VideoRec: Error stopping recording screen #%u, rc=%Rrc\n", pStream->uScreenID, rc));
360 return rc;
361 }
362
363 switch (pStream->enmDst)
364 {
365 case VIDEORECDEST_FILE:
366 {
367 AssertPtr(pStream->File.pszFile);
368 if (RTFileIsValid(pStream->File.hFile))
369 {
370 rc = RTFileClose(pStream->File.hFile);
371 if (RT_SUCCESS(rc))
372 {
373 LogRel(("VideoRec: Closed file '%s'\n", pStream->File.pszFile));
374 }
375 else
376 {
377 LogRel(("VideoRec: Error closing file '%s', rc=%Rrc\n", pStream->File.pszFile, rc));
378 break;
379 }
380 }
381
382 RTStrFree(pStream->File.pszFile);
383 pStream->File.pszFile = NULL;
384
385 if (pStream->File.pWEBM)
386 {
387 delete pStream->File.pWEBM;
388 pStream->File.pWEBM = NULL;
389 }
390 break;
391 }
392
393 default:
394 rc = VERR_NOT_IMPLEMENTED;
395 break;
396 }
397
398 if (RT_SUCCESS(rc))
399 {
400 pStream->enmDst = VIDEORECDEST_INVALID;
401 }
402
403 LogFlowFuncLeaveRC(rc);
404 return rc;
405}
406
407/**
408 * Uninitializes a recording stream.
409 *
410 * @returns IPRT status code.
411 * @param pStream Recording stream to uninitialize.
412 */
413int videoRecStreamUninit(PVIDEORECSTREAM pStream)
414{
415 int rc = VINF_SUCCESS;
416
417 if (pStream->pCtx->Cfg.Video.fEnabled)
418 {
419 int rc2 = videoRecStreamUnitVideo(pStream);
420 if (RT_SUCCESS(rc))
421 rc = rc2;
422 }
423
424 return rc;
425}
426
427/**
428 * Uninitializes video recording for a certain recording stream.
429 *
430 * @returns IPRT status code.
431 * @param pStream Recording stream to uninitialize video recording for.
432 */
433int videoRecStreamUnitVideo(PVIDEORECSTREAM pStream)
434{
435#ifdef VBOX_WITH_LIBVPX
436 /* At the moment we only have VPX. */
437 return videoRecStreamUninitVideoVPX(pStream);
438#else
439 return VERR_NOT_SUPPORTED;
440#endif
441}
442
443#ifdef VBOX_WITH_LIBVPX
444/**
445 * Uninitializes the VPX codec for a certain recording stream.
446 *
447 * @returns IPRT status code.
448 * @param pStream Recording stream to uninitialize VPX codec for.
449 */
450int videoRecStreamUninitVideoVPX(PVIDEORECSTREAM pStream)
451{
452 vpx_img_free(&pStream->Video.Codec.VPX.RawImage);
453 vpx_codec_err_t rcv = vpx_codec_destroy(&pStream->Video.Codec.VPX.Ctx);
454 Assert(rcv == VPX_CODEC_OK); RT_NOREF(rcv);
455
456 return VINF_SUCCESS;
457}
458#endif
459
460/**
461 * Initializes the video recording for a certain recording stream.
462 *
463 * @returns IPRT status code.
464 * @param pStream Recording stream to initialize video recording for.
465 * @param pCfg Video recording configuration to use for initialization.
466 */
467int videoRecStreamInitVideo(PVIDEORECSTREAM pStream, PVIDEORECCFG pCfg)
468{
469#ifdef VBOX_WITH_LIBVPX
470 /* At the moment we only have VPX. */
471 return videoRecStreamInitVideoVPX(pStream, pCfg);
472#else
473 return VERR_NOT_SUPPORTED;
474#endif
475}
476
477#ifdef VBOX_WITH_LIBVPX
478/**
479 * Initializes the VPX codec for a certain recording stream.
480 *
481 * @returns IPRT status code.
482 * @param pStream Recording stream to initialize VPX codec for.
483 * @param pCfg Video recording configuration to use for initialization.
484 */
485int videoRecStreamInitVideoVPX(PVIDEORECSTREAM pStream, PVIDEORECCFG pCfg)
486{
487 pStream->Video.uWidth = pCfg->Video.uWidth;
488 pStream->Video.uHeight = pCfg->Video.uHeight;
489 pStream->Video.cFailedEncodingFrames = 0;
490
491 PVIDEORECVIDEOCODEC pVC = &pStream->Video.Codec;
492
493 pStream->Video.uDelayMs = RT_MS_1SEC / pCfg->Video.uFPS;
494
495# ifdef VBOX_WITH_LIBVPX_VP9
496 vpx_codec_iface_t *pCodecIface = vpx_codec_vp9_cx();
497# else /* Default is using VP8. */
498 vpx_codec_iface_t *pCodecIface = vpx_codec_vp8_cx();
499# endif
500
501 vpx_codec_err_t rcv = vpx_codec_enc_config_default(pCodecIface, &pVC->VPX.Cfg, 0 /* Reserved */);
502 if (rcv != VPX_CODEC_OK)
503 {
504 LogRel(("VideoRec: Failed to get default config for VPX encoder: %s\n", vpx_codec_err_to_string(rcv)));
505 return VERR_AVREC_CODEC_INIT_FAILED;
506 }
507
508 /* Target bitrate in kilobits per second. */
509 pVC->VPX.Cfg.rc_target_bitrate = pCfg->Video.uRate;
510 /* Frame width. */
511 pVC->VPX.Cfg.g_w = pCfg->Video.uWidth;
512 /* Frame height. */
513 pVC->VPX.Cfg.g_h = pCfg->Video.uHeight;
514 /* 1ms per frame. */
515 pVC->VPX.Cfg.g_timebase.num = 1;
516 pVC->VPX.Cfg.g_timebase.den = 1000;
517 /* Disable multithreading. */
518 pVC->VPX.Cfg.g_threads = 0;
519
520 /* Initialize codec. */
521 rcv = vpx_codec_enc_init(&pVC->VPX.Ctx, pCodecIface, &pVC->VPX.Cfg, 0 /* Flags */);
522 if (rcv != VPX_CODEC_OK)
523 {
524 LogRel(("VideoRec: Failed to initialize VPX encoder: %s\n", vpx_codec_err_to_string(rcv)));
525 return VERR_AVREC_CODEC_INIT_FAILED;
526 }
527
528 if (!vpx_img_alloc(&pVC->VPX.RawImage, VPX_IMG_FMT_I420, pCfg->Video.uWidth, pCfg->Video.uHeight, 1))
529 {
530 LogRel(("VideoRec: Failed to allocate image %RU32x%RU32\n", pCfg->Video.uWidth, pCfg->Video.uHeight));
531 return VERR_NO_MEMORY;
532 }
533
534 /* Save a pointer to the first raw YUV plane. */
535 pStream->Video.Codec.VPX.pu8YuvBuf = pVC->VPX.RawImage.planes[0];
536
537 return VINF_SUCCESS;
538}
539#endif
540
541#ifdef VBOX_WITH_LIBVPX
542/**
543 * Encodes the source image and write the encoded image to the stream's destination.
544 *
545 * @returns IPRT status code.
546 * @param pStream Stream to encode and submit to.
547 * @param uTimeStampMs Absolute timestamp (PTS) of frame (in ms) to encode.
548 * @param pFrame Frame to encode and submit.
549 */
550int videoRecStreamWriteVideoVPX(PVIDEORECSTREAM pStream, uint64_t uTimeStampMs, PVIDEORECVIDEOFRAME pFrame)
551{
552 AssertPtrReturn(pStream, VERR_INVALID_POINTER);
553 AssertPtrReturn(pFrame, VERR_INVALID_POINTER);
554
555 int rc;
556
557 AssertPtr(pStream->pCtx);
558 PVIDEORECCFG pCfg = &pStream->pCtx->Cfg;
559 PVIDEORECVIDEOCODEC pVC = &pStream->Video.Codec;
560
561 /* Presentation Time Stamp (PTS). */
562 vpx_codec_pts_t pts = uTimeStampMs;
563 vpx_codec_err_t rcv = vpx_codec_encode(&pVC->VPX.Ctx,
564 &pVC->VPX.RawImage,
565 pts /* Time stamp */,
566 pStream->Video.uDelayMs /* How long to show this frame */,
567 0 /* Flags */,
568 pCfg->Video.Codec.VPX.uEncoderDeadline /* Quality setting */);
569 if (rcv != VPX_CODEC_OK)
570 {
571 if (pStream->Video.cFailedEncodingFrames++ < 64)
572 {
573 LogRel(("VideoRec: Failed to encode video frame: %s\n", vpx_codec_err_to_string(rcv)));
574 return VERR_GENERAL_FAILURE;
575 }
576 }
577
578 pStream->Video.cFailedEncodingFrames = 0;
579
580 vpx_codec_iter_t iter = NULL;
581 rc = VERR_NO_DATA;
582 for (;;)
583 {
584 const vpx_codec_cx_pkt_t *pPacket = vpx_codec_get_cx_data(&pVC->VPX.Ctx, &iter);
585 if (!pPacket)
586 break;
587
588 switch (pPacket->kind)
589 {
590 case VPX_CODEC_CX_FRAME_PKT:
591 {
592 WebMWriter::BlockData_VP8 blockData = { &pVC->VPX.Cfg, pPacket };
593 rc = pStream->File.pWEBM->WriteBlock(pStream->uTrackVideo, &blockData, sizeof(blockData));
594 break;
595 }
596
597 default:
598 AssertFailed();
599 LogFunc(("Unexpected video packet type %ld\n", pPacket->kind));
600 break;
601 }
602 }
603
604 return rc;
605}
606#endif /* VBOX_WITH_LIBVPX */
607
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette