VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/Recording.cpp@ 75392

Last change on this file since 75392 was 75392, checked in by vboxsync, 7 years ago

Recording/Main: Docs.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
  • Property svn:mergeinfo set to (toggle deleted branches)
    /branches/VBox-3.0/src/VBox/Frontends/VBoxHeadless/VideoCapture/EncodeAndWrite.cpp58652,​70973
    /branches/VBox-3.2/src/VBox/Frontends/VBoxHeadless/VideoCapture/EncodeAndWrite.cpp66309,​66318
    /branches/VBox-4.0/src/VBox/Frontends/VBoxHeadless/VideoCapture/EncodeAndWrite.cpp70873
    /branches/VBox-4.1/src/VBox/Frontends/VBoxHeadless/VideoCapture/EncodeAndWrite.cpp74233
    /branches/VBox-4.2/src/VBox/Main/src-client/VideoRec.cpp91503-91504,​91506-91508,​91510,​91514-91515,​91521
    /branches/VBox-4.3/src/VBox/Main/src-client/VideoRec.cpp91223
    /branches/VBox-4.3/trunk/src/VBox/Main/src-client/VideoRec.cpp91223
    /branches/dsen/gui/src/VBox/Frontends/VBoxHeadless/VideoCapture/EncodeAndWrite.cpp79076-79078,​79089,​79109-79110,​79112-79113,​79127-79130,​79134,​79141,​79151,​79155,​79157-79159,​79193,​79197
    /branches/dsen/gui2/src/VBox/Frontends/VBoxHeadless/VideoCapture/EncodeAndWrite.cpp79224,​79228,​79233,​79235,​79258,​79262-79263,​79273,​79341,​79345,​79354,​79357,​79387-79388,​79559-79569,​79572-79573,​79578,​79581-79582,​79590-79591,​79598-79599,​79602-79603,​79605-79606,​79632,​79635,​79637,​79644
    /branches/dsen/gui3/src/VBox/Frontends/VBoxHeadless/VideoCapture/EncodeAndWrite.cpp79645-79692
File size: 16.9 KB
Line 
1/* $Id: Recording.cpp 75392 2018-11-12 09:48:37Z vboxsync $ */
2/** @file
3 * Recording context code.
4 *
5 * This code employs a separate encoding thread per recording context
6 * to keep time spent in EMT as short as possible. Each configured VM display
7 * is represented by an own recording stream, which in turn has its own rendering
8 * queue. Common recording data across all recording streams is kept in a
9 * separate queue in the recording context to minimize data duplication and
10 * multiplexing overhead in EMT.
11 */
12
13/*
14 * Copyright (C) 2012-2018 Oracle Corporation
15 *
16 * This file is part of VirtualBox Open Source Edition (OSE), as
17 * available from http://www.215389.xyz. This file is free software;
18 * you can redistribute it and/or modify it under the terms of the GNU
19 * General Public License (GPL) as published by the Free Software
20 * Foundation, in version 2 as it comes in the "COPYING" file of the
21 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
22 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
23 */
24
25#ifdef LOG_GROUP
26# undef LOG_GROUP
27#endif
28#define LOG_GROUP LOG_GROUP_MAIN_DISPLAY
29#include "LoggingNew.h"
30
31#include <stdexcept>
32#include <vector>
33
34#include <iprt/asm.h>
35#include <iprt/assert.h>
36#include <iprt/critsect.h>
37#include <iprt/path.h>
38#include <iprt/semaphore.h>
39#include <iprt/thread.h>
40#include <iprt/time.h>
41
42#include <VBox/err.h>
43#include <VBox/com/VirtualBox.h>
44
45#include "ConsoleImpl.h"
46#include "Recording.h"
47#include "RecordingInternals.h"
48#include "RecordingStream.h"
49#include "RecordingUtils.h"
50#include "WebMWriter.h"
51
52using namespace com;
53
54#ifdef DEBUG_andy
55/** Enables dumping audio / video data for debugging reasons. */
56//# define VBOX_RECORDING_DUMP
57#endif
58
59#ifdef VBOX_RECORDING_DUMP
60#pragma pack(push)
61#pragma pack(1)
62typedef struct
63{
64 uint16_t u16Magic;
65 uint32_t u32Size;
66 uint16_t u16Reserved1;
67 uint16_t u16Reserved2;
68 uint32_t u32OffBits;
69} RECORDINGBMPHDR, *PRECORDINGBMPHDR;
70AssertCompileSize(RECORDINGBMPHDR, 14);
71
72typedef struct
73{
74 uint32_t u32Size;
75 uint32_t u32Width;
76 uint32_t u32Height;
77 uint16_t u16Planes;
78 uint16_t u16BitCount;
79 uint32_t u32Compression;
80 uint32_t u32SizeImage;
81 uint32_t u32XPelsPerMeter;
82 uint32_t u32YPelsPerMeter;
83 uint32_t u32ClrUsed;
84 uint32_t u32ClrImportant;
85} RECORDINGBMPDIBHDR, *PRECORDINGBMPDIBHDR;
86AssertCompileSize(RECORDINGBMPDIBHDR, 40);
87
88#pragma pack(pop)
89#endif /* VBOX_RECORDING_DUMP */
90
91
92RecordingContext::RecordingContext(Console *a_pConsole)
93 : pConsole(a_pConsole)
94 , enmState(RECORDINGSTS_UNINITIALIZED) { }
95
96RecordingContext::RecordingContext(Console *a_pConsole, const settings::RecordingSettings &a_Settings)
97 : pConsole(a_pConsole)
98 , enmState(RECORDINGSTS_UNINITIALIZED)
99{
100 int rc = RecordingContext::createInternal(a_Settings);
101 if (RT_FAILURE(rc))
102 throw rc;
103}
104
105RecordingContext::~RecordingContext(void)
106{
107 destroyInternal();
108}
109
110/**
111 * Worker thread for all streams of a recording context.
112 *
113 * For video frames, this also does the RGB/YUV conversion and encoding.
114 */
115DECLCALLBACK(int) RecordingContext::threadMain(RTTHREAD hThreadSelf, void *pvUser)
116{
117 RecordingContext *pThis = (RecordingContext *)pvUser;
118
119 /* Signal that we're up and rockin'. */
120 RTThreadUserSignal(hThreadSelf);
121
122 LogFunc(("Thread started\n"));
123
124 for (;;)
125 {
126 int rc = RTSemEventWait(pThis->WaitEvent, RT_INDEFINITE_WAIT);
127 AssertRCBreak(rc);
128
129 Log2Func(("Processing %zu streams\n", pThis->vecStreams.size()));
130
131 /** @todo r=andy This is inefficient -- as we already wake up this thread
132 * for every screen from Main, we here go again (on every wake up) through
133 * all screens. */
134 RecordingStreams::iterator itStream = pThis->vecStreams.begin();
135 while (itStream != pThis->vecStreams.end())
136 {
137 RecordingStream *pStream = (*itStream);
138
139 rc = pStream->Process(pThis->mapBlocksCommon);
140 if (RT_FAILURE(rc))
141 break;
142
143 ++itStream;
144 }
145
146 if (RT_FAILURE(rc))
147 LogRel(("Recording: Encoding thread failed (%Rrc)\n", rc));
148
149 /* Keep going in case of errors. */
150
151 if (ASMAtomicReadBool(&pThis->fShutdown))
152 {
153 LogFunc(("Thread is shutting down ...\n"));
154 break;
155 }
156
157 } /* for */
158
159 LogFunc(("Thread ended\n"));
160 return VINF_SUCCESS;
161}
162
163/**
164 * Notifies a recording context's encoding thread.
165 *
166 * @returns IPRT status code.
167 */
168int RecordingContext::threadNotify(void)
169{
170 return RTSemEventSignal(this->WaitEvent);
171}
172
173/**
174 * Creates a recording context.
175 *
176 * @returns IPRT status code.
177 * @param a_Settings Recording settings to use for context creation.
178 */
179int RecordingContext::createInternal(const settings::RecordingSettings &a_Settings)
180{
181 int rc = RTCritSectInit(&this->CritSect);
182 if (RT_FAILURE(rc))
183 return rc;
184
185 settings::RecordingScreenMap::const_iterator itScreen = a_Settings.mapScreens.begin();
186 while (itScreen != a_Settings.mapScreens.end())
187 {
188 RecordingStream *pStream = NULL;
189 try
190 {
191 pStream = new RecordingStream(this, itScreen->first /* Screen ID */, itScreen->second);
192 this->vecStreams.push_back(pStream);
193 }
194 catch (std::bad_alloc &)
195 {
196 rc = VERR_NO_MEMORY;
197 break;
198 }
199
200 ++itScreen;
201 }
202
203 if (RT_SUCCESS(rc))
204 {
205 this->tsStartMs = RTTimeMilliTS();
206 this->enmState = RECORDINGSTS_CREATED;
207 this->fShutdown = false;
208
209 /* Copy the settings to our context. */
210 this->Settings = a_Settings;
211
212 rc = RTSemEventCreate(&this->WaitEvent);
213 AssertRCReturn(rc, rc);
214 }
215
216 if (RT_FAILURE(rc))
217 {
218 int rc2 = destroyInternal();
219 AssertRC(rc2);
220 }
221
222 return rc;
223}
224
225/**
226 * Starts a recording context by creating its worker thread.
227 *
228 * @returns IPRT status code.
229 */
230int RecordingContext::startInternal(void)
231{
232 if (this->enmState == RECORDINGSTS_STARTED)
233 return VINF_SUCCESS;
234
235 Assert(this->enmState == RECORDINGSTS_CREATED);
236
237 int rc = RTThreadCreate(&this->Thread, RecordingContext::threadMain, (void *)this, 0,
238 RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "Record");
239
240 if (RT_SUCCESS(rc)) /* Wait for the thread to start. */
241 rc = RTThreadUserWait(this->Thread, 30 * RT_MS_1SEC /* 30s timeout */);
242
243 if (RT_SUCCESS(rc))
244 {
245 LogRel(("Recording: Started\n"));
246 this->enmState = RECORDINGSTS_STARTED;
247 }
248 else
249 Log(("Recording: Failed to start (%Rrc)\n", rc));
250
251 return rc;
252}
253
254/**
255 * Stops a recording context by telling the worker thread to stop and finalizing its operation.
256 *
257 * @returns IPRT status code.
258 */
259int RecordingContext::stopInternal(void)
260{
261 if (this->enmState != RECORDINGSTS_STARTED)
262 return VINF_SUCCESS;
263
264 LogThisFunc(("Shutting down thread ...\n"));
265
266 /* Set shutdown indicator. */
267 ASMAtomicWriteBool(&this->fShutdown, true);
268
269 /* Signal the thread and wait for it to shut down. */
270 int rc = threadNotify();
271 if (RT_SUCCESS(rc))
272 rc = RTThreadWait(this->Thread, 30 * 1000 /* 10s timeout */, NULL);
273
274 if (RT_SUCCESS(rc))
275 {
276 LogRel(("Recording: Stopped\n"));
277 this->enmState = RECORDINGSTS_CREATED;
278 }
279 else
280 Log(("Recording: Failed to stop (%Rrc)\n", rc));
281
282 LogFlowThisFunc(("%Rrc\n", rc));
283 return rc;
284}
285
286/**
287 * Destroys a recording context, internal version.
288 */
289int RecordingContext::destroyInternal(void)
290{
291 int rc = stopInternal();
292 if (RT_FAILURE(rc))
293 return rc;
294
295 rc = RTSemEventDestroy(this->WaitEvent);
296 AssertRC(rc);
297
298 this->WaitEvent = NIL_RTSEMEVENT;
299
300 rc = RTCritSectEnter(&this->CritSect);
301 if (RT_SUCCESS(rc))
302 {
303 RecordingStreams::iterator it = this->vecStreams.begin();
304 while (it != this->vecStreams.end())
305 {
306 RecordingStream *pStream = (*it);
307
308 int rc2 = pStream->Uninit();
309 if (RT_SUCCESS(rc))
310 rc = rc2;
311
312 delete pStream;
313 pStream = NULL;
314
315 this->vecStreams.erase(it);
316 it = this->vecStreams.begin();
317
318 delete pStream;
319 pStream = NULL;
320 }
321
322 /* Sanity. */
323 Assert(this->vecStreams.empty());
324 Assert(this->mapBlocksCommon.size() == 0);
325
326 int rc2 = RTCritSectLeave(&this->CritSect);
327 AssertRC(rc2);
328
329 RTCritSectDelete(&this->CritSect);
330 }
331
332 LogFlowThisFunc(("%Rrc\n", rc));
333 return rc;
334}
335
336/**
337 * Returns a recording context's current settings.
338 *
339 * @returns The recording context's current settings.
340 */
341const settings::RecordingSettings &RecordingContext::GetConfig(void) const
342{
343 return this->Settings;
344}
345
346/**
347 * Returns the recording stream for a specific screen.
348 *
349 * @returns Recording stream for a specific screen, or NULL if not found.
350 * @param uScreen Screen ID to retrieve recording stream for.
351 */
352RecordingStream *RecordingContext::getStreamInternal(unsigned uScreen) const
353{
354 RecordingStream *pStream;
355
356 try
357 {
358 pStream = this->vecStreams.at(uScreen);
359 }
360 catch (std::out_of_range &)
361 {
362 pStream = NULL;
363 }
364
365 return pStream;
366}
367
368/**
369 * Retrieves a specific recording stream of a recording context.
370 *
371 * @returns Pointer to recording stream if found, or NULL if not found.
372 * @param uScreen Screen number of recording stream to look up.
373 */
374RecordingStream *RecordingContext::GetStream(unsigned uScreen) const
375{
376 return getStreamInternal(uScreen);
377}
378
379/**
380 * Returns the number of configured recording streams for a recording context.
381 *
382 * @returns Number of configured recording streams.
383 */
384size_t RecordingContext::GetStreamCount(void) const
385{
386 return this->vecStreams.size();
387}
388
389/**
390 * Creates a new recording context.
391 *
392 * @returns IPRT status code.
393 * @param a_Settings Recording settings to use for creation.
394 *
395 */
396int RecordingContext::Create(const settings::RecordingSettings &a_Settings)
397{
398 return createInternal(a_Settings);
399}
400
401/**
402 * Destroys a recording context.
403 */
404int RecordingContext::Destroy(void)
405{
406 return destroyInternal();
407}
408
409/**
410 * Starts a recording context.
411 *
412 * @returns IPRT status code.
413 */
414int RecordingContext::Start(void)
415{
416 return startInternal();
417}
418
419/**
420 * Stops a recording context.
421 */
422int RecordingContext::Stop(void)
423{
424 return stopInternal();
425}
426
427/**
428 * Returns if a specific recoding feature is enabled for at least one of the attached
429 * recording streams or not.
430 *
431 * @returns \c true if at least one recording stream has this feature enabled, or \c false if
432 * no recording stream has this feature enabled.
433 * @param enmFeature Recording feature to check for.
434 */
435bool RecordingContext::IsFeatureEnabled(RecordingFeature_T enmFeature) const
436{
437 RecordingStreams::const_iterator itStream = this->vecStreams.begin();
438 while (itStream != this->vecStreams.end())
439 {
440 if ((*itStream)->GetConfig().isFeatureEnabled(enmFeature))
441 return true;
442 ++itStream;
443 }
444
445 return false;
446}
447
448/**
449 * Returns if this recording context is ready to start recording.
450 *
451 * @returns @c true if recording context is ready, @c false if not.
452 */
453bool RecordingContext::IsReady(void) const
454{
455 return (this->enmState >= RECORDINGSTS_CREATED);
456}
457
458/**
459 * Returns if this recording context is ready to accept new recording data for a given screen.
460 *
461 * @returns @c true if the specified screen is ready, @c false if not.
462 * @param uScreen Screen ID.
463 * @param uTimeStampMs Current time stamp (in ms). Currently not being used.
464 */
465bool RecordingContext::IsReady(uint32_t uScreen, uint64_t uTimeStampMs) const
466{
467 RT_NOREF(uTimeStampMs);
468
469 if (this->enmState != RECORDINGSTS_STARTED)
470 return false;
471
472 bool fIsReady = false;
473
474 const RecordingStream *pStream = GetStream(uScreen);
475 if (pStream)
476 fIsReady = pStream->IsReady();
477
478 /* Note: Do not check for other constraints like the video FPS rate here,
479 * as this check then also would affect other (non-FPS related) stuff
480 * like audio data. */
481
482 return fIsReady;
483}
484
485/**
486 * Returns whether a given recording context has been started or not.
487 *
488 * @returns true if active, false if not.
489 */
490bool RecordingContext::IsStarted(void) const
491{
492 return (this->enmState == RECORDINGSTS_STARTED);
493}
494
495/**
496 * Checks if a specified limit for recording has been reached.
497 *
498 * @returns true if any limit has been reached.
499 * @param uScreen Screen ID.
500 * @param tsNowMs Current time stamp (in ms).
501 */
502bool RecordingContext::IsLimitReached(uint32_t uScreen, uint64_t tsNowMs) const
503{
504 const RecordingStream *pStream = GetStream(uScreen);
505 if ( !pStream
506 || pStream->IsLimitReached(tsNowMs))
507 {
508 return true;
509 }
510
511 return false;
512}
513
514/**
515 * Sends an audio frame to the video encoding thread.
516 *
517 * @thread EMT
518 *
519 * @returns IPRT status code.
520 * @param pvData Audio frame data to send.
521 * @param cbData Size (in bytes) of (encoded) audio frame data.
522 * @param uTimeStampMs Time stamp (in ms) of audio playback.
523 */
524int RecordingContext::SendAudioFrame(const void *pvData, size_t cbData, uint64_t uTimeStampMs)
525{
526#ifdef VBOX_WITH_AUDIO_RECORDING
527 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
528 AssertReturn(cbData, VERR_INVALID_PARAMETER);
529
530 /* To save time spent in EMT, do the required audio multiplexing in the encoding thread.
531 *
532 * The multiplexing is needed to supply all recorded (enabled) screens with the same
533 * audio data at the same given point in time.
534 */
535 PRECORDINGBLOCK pBlock = (PRECORDINGBLOCK)RTMemAlloc(sizeof(RECORDINGBLOCK));
536 AssertPtrReturn(pBlock, VERR_NO_MEMORY);
537 pBlock->enmType = RECORDINGBLOCKTYPE_AUDIO;
538
539 PRECORDINGAUDIOFRAME pFrame = (PRECORDINGAUDIOFRAME)RTMemAlloc(sizeof(RECORDINGAUDIOFRAME));
540 AssertPtrReturn(pFrame, VERR_NO_MEMORY);
541
542 pFrame->pvBuf = (uint8_t *)RTMemAlloc(cbData);
543 AssertPtrReturn(pFrame->pvBuf, VERR_NO_MEMORY);
544 pFrame->cbBuf = cbData;
545
546 memcpy(pFrame->pvBuf, pvData, cbData);
547
548 pBlock->pvData = pFrame;
549 pBlock->cbData = sizeof(RECORDINGAUDIOFRAME) + cbData;
550 pBlock->cRefs = (uint16_t)this->vecStreams.size(); /* All streams need the same audio data. */
551 pBlock->uTimeStampMs = uTimeStampMs;
552
553 int rc = RTCritSectEnter(&this->CritSect);
554 if (RT_FAILURE(rc))
555 return rc;
556
557 try
558 {
559 RecordingBlockMap::iterator itBlocks = this->mapBlocksCommon.find(uTimeStampMs);
560 if (itBlocks == this->mapBlocksCommon.end())
561 {
562 RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
563 pRecordingBlocks->List.push_back(pBlock);
564
565 this->mapBlocksCommon.insert(std::make_pair(uTimeStampMs, pRecordingBlocks));
566 }
567 else
568 itBlocks->second->List.push_back(pBlock);
569 }
570 catch (const std::exception &ex)
571 {
572 RT_NOREF(ex);
573 rc = VERR_NO_MEMORY;
574 }
575
576 int rc2 = RTCritSectLeave(&this->CritSect);
577 AssertRC(rc2);
578
579 if (RT_SUCCESS(rc))
580 rc = threadNotify();
581
582 return rc;
583#else
584 RT_NOREF(pCtx, pvData, cbData, uTimeStampMs);
585 return VINF_SUCCESS;
586#endif
587}
588
589/**
590 * Copies a source video frame to the intermediate RGB buffer.
591 * This function is executed only once per time.
592 *
593 * @thread EMT
594 *
595 * @returns IPRT status code.
596 * @param uScreen Screen number to send video frame to.
597 * @param x Starting x coordinate of the video frame.
598 * @param y Starting y coordinate of the video frame.
599 * @param uPixelFormat Pixel format.
600 * @param uBPP Bits Per Pixel (BPP).
601 * @param uBytesPerLine Bytes per scanline.
602 * @param uSrcWidth Width of the video frame.
603 * @param uSrcHeight Height of the video frame.
604 * @param puSrcData Pointer to video frame data.
605 * @param uTimeStampMs Time stamp (in ms).
606 */
607int RecordingContext::SendVideoFrame(uint32_t uScreen, uint32_t x, uint32_t y,
608 uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
609 uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData,
610 uint64_t uTimeStampMs)
611{
612 AssertReturn(uSrcWidth, VERR_INVALID_PARAMETER);
613 AssertReturn(uSrcHeight, VERR_INVALID_PARAMETER);
614 AssertReturn(puSrcData, VERR_INVALID_POINTER);
615
616 int rc = RTCritSectEnter(&this->CritSect);
617 AssertRC(rc);
618
619 RecordingStream *pStream = GetStream(uScreen);
620 if (!pStream)
621 {
622 rc = RTCritSectLeave(&this->CritSect);
623 AssertRC(rc);
624
625 return VERR_NOT_FOUND;
626 }
627
628 rc = pStream->SendVideoFrame(x, y, uPixelFormat, uBPP, uBytesPerLine, uSrcWidth, uSrcHeight, puSrcData, uTimeStampMs);
629
630 int rc2 = RTCritSectLeave(&this->CritSect);
631 AssertRC(rc2);
632
633 if ( RT_SUCCESS(rc)
634 && rc != VINF_TRY_AGAIN) /* Only signal the thread if operation was successful. */
635 {
636 threadNotify();
637 }
638
639 return rc;
640}
641
Note: See TracBrowser for help on using the repository browser.

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