VirtualBox

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

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

Recording/Main: Logging.

  • 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: 15.6 KB
Line 
1/* $Id: Recording.cpp 75391 2018-11-12 09:22:42Z vboxsync $ */
2/** @file
3 * Recording (with optional audio recording) 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 Capture 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
225int RecordingContext::startInternal(void)
226{
227 if (this->enmState == RECORDINGSTS_STARTED)
228 return VINF_SUCCESS;
229
230 Assert(this->enmState == RECORDINGSTS_CREATED);
231
232 int rc = RTThreadCreate(&this->Thread, RecordingContext::threadMain, (void *)this, 0,
233 RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "Record");
234
235 if (RT_SUCCESS(rc)) /* Wait for the thread to start. */
236 rc = RTThreadUserWait(this->Thread, 30 * RT_MS_1SEC /* 30s timeout */);
237
238 if (RT_SUCCESS(rc))
239 {
240 LogRel(("Recording: Started\n"));
241 this->enmState = RECORDINGSTS_STARTED;
242 }
243 else
244 Log(("Recording: Failed to start (%Rrc)\n", rc));
245
246 return rc;
247}
248
249int RecordingContext::stopInternal(void)
250{
251 if (this->enmState != RECORDINGSTS_STARTED)
252 return VINF_SUCCESS;
253
254 LogThisFunc(("Shutting down thread ...\n"));
255
256 /* Set shutdown indicator. */
257 ASMAtomicWriteBool(&this->fShutdown, true);
258
259 /* Signal the thread and wait for it to shut down. */
260 int rc = threadNotify();
261 if (RT_SUCCESS(rc))
262 rc = RTThreadWait(this->Thread, 30 * 1000 /* 10s timeout */, NULL);
263
264 if (RT_SUCCESS(rc))
265 {
266 LogRel(("Recording: Stopped\n"));
267 this->enmState = RECORDINGSTS_CREATED;
268 }
269 else
270 Log(("Recording: Failed to stop (%Rrc)\n", rc));
271
272 LogFlowThisFunc(("%Rrc\n", rc));
273 return rc;
274}
275
276/**
277 * Destroys a recording context.
278 */
279int RecordingContext::destroyInternal(void)
280{
281 int rc = stopInternal();
282 if (RT_FAILURE(rc))
283 return rc;
284
285 rc = RTSemEventDestroy(this->WaitEvent);
286 AssertRC(rc);
287
288 this->WaitEvent = NIL_RTSEMEVENT;
289
290 rc = RTCritSectEnter(&this->CritSect);
291 if (RT_SUCCESS(rc))
292 {
293 RecordingStreams::iterator it = this->vecStreams.begin();
294 while (it != this->vecStreams.end())
295 {
296 RecordingStream *pStream = (*it);
297
298 int rc2 = pStream->Uninit();
299 if (RT_SUCCESS(rc))
300 rc = rc2;
301
302 delete pStream;
303 pStream = NULL;
304
305 this->vecStreams.erase(it);
306 it = this->vecStreams.begin();
307
308 delete pStream;
309 pStream = NULL;
310 }
311
312 /* Sanity. */
313 Assert(this->vecStreams.empty());
314 Assert(this->mapBlocksCommon.size() == 0);
315
316 int rc2 = RTCritSectLeave(&this->CritSect);
317 AssertRC(rc2);
318
319 RTCritSectDelete(&this->CritSect);
320 }
321
322 LogFlowThisFunc(("%Rrc\n", rc));
323 return rc;
324}
325
326const settings::RecordingSettings &RecordingContext::GetConfig(void) const
327{
328 return this->Settings;
329}
330
331RecordingStream *RecordingContext::getStreamInternal(unsigned uScreen) const
332{
333 RecordingStream *pStream;
334
335 try
336 {
337 pStream = this->vecStreams.at(uScreen);
338 }
339 catch (std::out_of_range &)
340 {
341 pStream = NULL;
342 }
343
344 return pStream;
345}
346
347/**
348 * Retrieves a specific recording stream of a recording context.
349 *
350 * @returns Pointer to recording stream if found, or NULL if not found.
351 * @param uScreen Screen number of recording stream to look up.
352 */
353RecordingStream *RecordingContext::GetStream(unsigned uScreen) const
354{
355 return getStreamInternal(uScreen);
356}
357
358size_t RecordingContext::GetStreamCount(void) const
359{
360 return this->vecStreams.size();
361}
362
363int RecordingContext::Create(const settings::RecordingSettings &a_Settings)
364{
365 return createInternal(a_Settings);
366}
367
368int RecordingContext::Destroy(void)
369{
370 return destroyInternal();
371}
372
373int RecordingContext::Start(void)
374{
375 return startInternal();
376}
377
378int RecordingContext::Stop(void)
379{
380 return stopInternal();
381}
382
383bool RecordingContext::IsFeatureEnabled(RecordingFeature_T enmFeature) const
384{
385 RecordingStreams::const_iterator itStream = this->vecStreams.begin();
386 while (itStream != this->vecStreams.end())
387 {
388 if ((*itStream)->GetConfig().isFeatureEnabled(enmFeature))
389 return true;
390 ++itStream;
391 }
392
393 return false;
394}
395
396/**
397 * Returns if this recording context is ready to start recording.
398 *
399 * @returns @c true if recording context is ready, @c false if not.
400 */
401bool RecordingContext::IsReady(void) const
402{
403 return (this->enmState >= RECORDINGSTS_CREATED);
404}
405
406/**
407 * Returns if this recording context is ready to accept new recording data for a given screen.
408 *
409 * @returns @c true if the specified screen is ready, @c false if not.
410 * @param uScreen Screen ID.
411 * @param uTimeStampMs Current time stamp (in ms). Currently not being used.
412 */
413bool RecordingContext::IsReady(uint32_t uScreen, uint64_t uTimeStampMs) const
414{
415 RT_NOREF(uTimeStampMs);
416
417 if (this->enmState != RECORDINGSTS_STARTED)
418 return false;
419
420 bool fIsReady = false;
421
422 const RecordingStream *pStream = GetStream(uScreen);
423 if (pStream)
424 fIsReady = pStream->IsReady();
425
426 /* Note: Do not check for other constraints like the video FPS rate here,
427 * as this check then also would affect other (non-FPS related) stuff
428 * like audio data. */
429
430 return fIsReady;
431}
432
433/**
434 * Returns whether a given recording context has been started or not.
435 *
436 * @returns true if active, false if not.
437 */
438bool RecordingContext::IsStarted(void) const
439{
440 return (this->enmState == RECORDINGSTS_STARTED);
441}
442
443/**
444 * Checks if a specified limit for recording has been reached.
445 *
446 * @returns true if any limit has been reached.
447 * @param uScreen Screen ID.
448 * @param tsNowMs Current time stamp (in ms).
449 */
450bool RecordingContext::IsLimitReached(uint32_t uScreen, uint64_t tsNowMs) const
451{
452 const RecordingStream *pStream = GetStream(uScreen);
453 if ( !pStream
454 || pStream->IsLimitReached(tsNowMs))
455 {
456 return true;
457 }
458
459 return false;
460}
461
462/**
463 * Sends an audio frame to the video encoding thread.
464 *
465 * @thread EMT
466 *
467 * @returns IPRT status code.
468 * @param pvData Audio frame data to send.
469 * @param cbData Size (in bytes) of (encoded) audio frame data.
470 * @param uTimeStampMs Time stamp (in ms) of audio playback.
471 */
472int RecordingContext::SendAudioFrame(const void *pvData, size_t cbData, uint64_t uTimeStampMs)
473{
474#ifdef VBOX_WITH_AUDIO_RECORDING
475 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
476 AssertReturn(cbData, VERR_INVALID_PARAMETER);
477
478 /* To save time spent in EMT, do the required audio multiplexing in the encoding thread.
479 *
480 * The multiplexing is needed to supply all recorded (enabled) screens with the same
481 * audio data at the same given point in time.
482 */
483 PRECORDINGBLOCK pBlock = (PRECORDINGBLOCK)RTMemAlloc(sizeof(RECORDINGBLOCK));
484 AssertPtrReturn(pBlock, VERR_NO_MEMORY);
485 pBlock->enmType = RECORDINGBLOCKTYPE_AUDIO;
486
487 PRECORDINGAUDIOFRAME pFrame = (PRECORDINGAUDIOFRAME)RTMemAlloc(sizeof(RECORDINGAUDIOFRAME));
488 AssertPtrReturn(pFrame, VERR_NO_MEMORY);
489
490 pFrame->pvBuf = (uint8_t *)RTMemAlloc(cbData);
491 AssertPtrReturn(pFrame->pvBuf, VERR_NO_MEMORY);
492 pFrame->cbBuf = cbData;
493
494 memcpy(pFrame->pvBuf, pvData, cbData);
495
496 pBlock->pvData = pFrame;
497 pBlock->cbData = sizeof(RECORDINGAUDIOFRAME) + cbData;
498 pBlock->cRefs = (uint16_t)this->vecStreams.size(); /* All streams need the same audio data. */
499 pBlock->uTimeStampMs = uTimeStampMs;
500
501 int rc = RTCritSectEnter(&this->CritSect);
502 if (RT_FAILURE(rc))
503 return rc;
504
505 try
506 {
507 RecordingBlockMap::iterator itBlocks = this->mapBlocksCommon.find(uTimeStampMs);
508 if (itBlocks == this->mapBlocksCommon.end())
509 {
510 RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
511 pRecordingBlocks->List.push_back(pBlock);
512
513 this->mapBlocksCommon.insert(std::make_pair(uTimeStampMs, pRecordingBlocks));
514 }
515 else
516 itBlocks->second->List.push_back(pBlock);
517 }
518 catch (const std::exception &ex)
519 {
520 RT_NOREF(ex);
521 rc = VERR_NO_MEMORY;
522 }
523
524 int rc2 = RTCritSectLeave(&this->CritSect);
525 AssertRC(rc2);
526
527 if (RT_SUCCESS(rc))
528 rc = threadNotify();
529
530 return rc;
531#else
532 RT_NOREF(pCtx, pvData, cbData, uTimeStampMs);
533 return VINF_SUCCESS;
534#endif
535}
536
537/**
538 * Copies a source video frame to the intermediate RGB buffer.
539 * This function is executed only once per time.
540 *
541 * @thread EMT
542 *
543 * @returns IPRT status code.
544 * @param uScreen Screen number to send video frame to.
545 * @param x Starting x coordinate of the video frame.
546 * @param y Starting y coordinate of the video frame.
547 * @param uPixelFormat Pixel format.
548 * @param uBPP Bits Per Pixel (BPP).
549 * @param uBytesPerLine Bytes per scanline.
550 * @param uSrcWidth Width of the video frame.
551 * @param uSrcHeight Height of the video frame.
552 * @param puSrcData Pointer to video frame data.
553 * @param uTimeStampMs Time stamp (in ms).
554 */
555int RecordingContext::SendVideoFrame(uint32_t uScreen, uint32_t x, uint32_t y,
556 uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
557 uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData,
558 uint64_t uTimeStampMs)
559{
560 AssertReturn(uSrcWidth, VERR_INVALID_PARAMETER);
561 AssertReturn(uSrcHeight, VERR_INVALID_PARAMETER);
562 AssertReturn(puSrcData, VERR_INVALID_POINTER);
563
564 int rc = RTCritSectEnter(&this->CritSect);
565 AssertRC(rc);
566
567 RecordingStream *pStream = GetStream(uScreen);
568 if (!pStream)
569 {
570 rc = RTCritSectLeave(&this->CritSect);
571 AssertRC(rc);
572
573 return VERR_NOT_FOUND;
574 }
575
576 rc = pStream->SendVideoFrame(x, y, uPixelFormat, uBPP, uBytesPerLine, uSrcWidth, uSrcHeight, puSrcData, uTimeStampMs);
577
578 int rc2 = RTCritSectLeave(&this->CritSect);
579 AssertRC(rc2);
580
581 if ( RT_SUCCESS(rc)
582 && rc != VINF_TRY_AGAIN) /* Only signal the thread if operation was successful. */
583 {
584 threadNotify();
585 }
586
587 return rc;
588}
589
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