VirtualBox

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

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

Main/Recording: compile fixes when VBOX_WITH_AUDIO_RECORDING is unset

  • 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: 18.1 KB
Line 
1/* $Id: Recording.cpp 75527 2018-11-16 17:14:58Z 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.virtualbox.org. 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, const settings::RecordingSettings &a_Settings)
93 : pConsole(a_pConsole)
94 , enmState(RECORDINGSTS_UNINITIALIZED)
95 , cStreamsEnabled(0)
96{
97 int rc = RecordingContext::createInternal(a_Settings);
98 if (RT_FAILURE(rc))
99 throw rc;
100}
101
102RecordingContext::~RecordingContext(void)
103{
104 destroyInternal();
105}
106
107/**
108 * Worker thread for all streams of a recording context.
109 *
110 * For video frames, this also does the RGB/YUV conversion and encoding.
111 */
112DECLCALLBACK(int) RecordingContext::threadMain(RTTHREAD hThreadSelf, void *pvUser)
113{
114 RecordingContext *pThis = (RecordingContext *)pvUser;
115
116 /* Signal that we're up and rockin'. */
117 RTThreadUserSignal(hThreadSelf);
118
119 LogFunc(("Thread started\n"));
120
121 for (;;)
122 {
123 int rc = RTSemEventWait(pThis->WaitEvent, RT_INDEFINITE_WAIT);
124 AssertRCBreak(rc);
125
126 Log2Func(("Processing %zu streams\n", pThis->vecStreams.size()));
127
128 /** @todo r=andy This is inefficient -- as we already wake up this thread
129 * for every screen from Main, we here go again (on every wake up) through
130 * all screens. */
131 RecordingStreams::iterator itStream = pThis->vecStreams.begin();
132 while (itStream != pThis->vecStreams.end())
133 {
134 RecordingStream *pStream = (*itStream);
135
136 rc = pStream->Process(pThis->mapBlocksCommon);
137 if (RT_FAILURE(rc))
138 {
139 LogRel(("Recording: Processing stream #%RU16 failed (%Rrc)\n", pStream->GetID(), rc));
140 break;
141 }
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 if (itScreen->second.fEnabled)
194 this->cStreamsEnabled++;
195 }
196 catch (std::bad_alloc &)
197 {
198 rc = VERR_NO_MEMORY;
199 break;
200 }
201
202 ++itScreen;
203 }
204
205 if (RT_SUCCESS(rc))
206 {
207 this->tsStartMs = RTTimeMilliTS();
208 this->enmState = RECORDINGSTS_CREATED;
209 this->fShutdown = false;
210
211 /* Copy the settings to our context. */
212 this->Settings = a_Settings;
213
214 rc = RTSemEventCreate(&this->WaitEvent);
215 AssertRCReturn(rc, rc);
216 }
217
218 if (RT_FAILURE(rc))
219 {
220 int rc2 = destroyInternal();
221 AssertRC(rc2);
222 }
223
224 return rc;
225}
226
227/**
228 * Starts a recording context by creating its worker thread.
229 *
230 * @returns IPRT status code.
231 */
232int RecordingContext::startInternal(void)
233{
234 if (this->enmState == RECORDINGSTS_STARTED)
235 return VINF_SUCCESS;
236
237 Assert(this->enmState == RECORDINGSTS_CREATED);
238
239 int rc = RTThreadCreate(&this->Thread, RecordingContext::threadMain, (void *)this, 0,
240 RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "Record");
241
242 if (RT_SUCCESS(rc)) /* Wait for the thread to start. */
243 rc = RTThreadUserWait(this->Thread, 30 * RT_MS_1SEC /* 30s timeout */);
244
245 if (RT_SUCCESS(rc))
246 {
247 LogRel(("Recording: Started\n"));
248 this->enmState = RECORDINGSTS_STARTED;
249 }
250 else
251 Log(("Recording: Failed to start (%Rrc)\n", rc));
252
253 return rc;
254}
255
256/**
257 * Stops a recording context by telling the worker thread to stop and finalizing its operation.
258 *
259 * @returns IPRT status code.
260 */
261int RecordingContext::stopInternal(void)
262{
263 if (this->enmState != RECORDINGSTS_STARTED)
264 return VINF_SUCCESS;
265
266 LogThisFunc(("Shutting down thread ...\n"));
267
268 /* Set shutdown indicator. */
269 ASMAtomicWriteBool(&this->fShutdown, true);
270
271 /* Signal the thread and wait for it to shut down. */
272 int rc = threadNotify();
273 if (RT_SUCCESS(rc))
274 rc = RTThreadWait(this->Thread, 30 * 1000 /* 10s timeout */, NULL);
275
276 if (RT_SUCCESS(rc))
277 {
278 LogRel(("Recording: Stopped\n"));
279 this->enmState = RECORDINGSTS_CREATED;
280 }
281 else
282 Log(("Recording: Failed to stop (%Rrc)\n", rc));
283
284 LogFlowThisFunc(("%Rrc\n", rc));
285 return rc;
286}
287
288/**
289 * Destroys a recording context, internal version.
290 */
291int RecordingContext::destroyInternal(void)
292{
293 int rc = stopInternal();
294 if (RT_FAILURE(rc))
295 return rc;
296
297 rc = RTSemEventDestroy(this->WaitEvent);
298 AssertRC(rc);
299
300 this->WaitEvent = NIL_RTSEMEVENT;
301
302 rc = RTCritSectEnter(&this->CritSect);
303 if (RT_SUCCESS(rc))
304 {
305 RecordingStreams::iterator it = this->vecStreams.begin();
306 while (it != this->vecStreams.end())
307 {
308 RecordingStream *pStream = (*it);
309
310 int rc2 = pStream->Uninit();
311 if (RT_SUCCESS(rc))
312 rc = rc2;
313
314 delete pStream;
315 pStream = NULL;
316
317 this->vecStreams.erase(it);
318 it = this->vecStreams.begin();
319
320 delete pStream;
321 pStream = NULL;
322 }
323
324 /* Sanity. */
325 Assert(this->vecStreams.empty());
326 Assert(this->mapBlocksCommon.size() == 0);
327
328 int rc2 = RTCritSectLeave(&this->CritSect);
329 AssertRC(rc2);
330
331 RTCritSectDelete(&this->CritSect);
332 }
333
334 LogFlowThisFunc(("%Rrc\n", rc));
335 return rc;
336}
337
338/**
339 * Returns a recording context's current settings.
340 *
341 * @returns The recording context's current settings.
342 */
343const settings::RecordingSettings &RecordingContext::GetConfig(void) const
344{
345 return this->Settings;
346}
347
348/**
349 * Returns the recording stream for a specific screen.
350 *
351 * @returns Recording stream for a specific screen, or NULL if not found.
352 * @param uScreen Screen ID to retrieve recording stream for.
353 */
354RecordingStream *RecordingContext::getStreamInternal(unsigned uScreen) const
355{
356 RecordingStream *pStream;
357
358 try
359 {
360 pStream = this->vecStreams.at(uScreen);
361 }
362 catch (std::out_of_range &)
363 {
364 pStream = NULL;
365 }
366
367 return pStream;
368}
369
370int RecordingContext::lock(void)
371{
372 int rc = RTCritSectEnter(&this->CritSect);
373 AssertRC(rc);
374 return rc;
375}
376
377int RecordingContext::unlock(void)
378{
379 int rc = RTCritSectLeave(&this->CritSect);
380 AssertRC(rc);
381 return rc;
382}
383
384/**
385 * Retrieves a specific recording stream of a recording context.
386 *
387 * @returns Pointer to recording stream if found, or NULL if not found.
388 * @param uScreen Screen number of recording stream to look up.
389 */
390RecordingStream *RecordingContext::GetStream(unsigned uScreen) const
391{
392 return getStreamInternal(uScreen);
393}
394
395/**
396 * Returns the number of configured recording streams for a recording context.
397 *
398 * @returns Number of configured recording streams.
399 */
400size_t RecordingContext::GetStreamCount(void) const
401{
402 return this->vecStreams.size();
403}
404
405/**
406 * Creates a new recording context.
407 *
408 * @returns IPRT status code.
409 * @param a_Settings Recording settings to use for creation.
410 *
411 */
412int RecordingContext::Create(const settings::RecordingSettings &a_Settings)
413{
414 return createInternal(a_Settings);
415}
416
417/**
418 * Destroys a recording context.
419 */
420int RecordingContext::Destroy(void)
421{
422 return destroyInternal();
423}
424
425/**
426 * Starts a recording context.
427 *
428 * @returns IPRT status code.
429 */
430int RecordingContext::Start(void)
431{
432 return startInternal();
433}
434
435/**
436 * Stops a recording context.
437 */
438int RecordingContext::Stop(void)
439{
440 return stopInternal();
441}
442
443/**
444 * Returns if a specific recoding feature is enabled for at least one of the attached
445 * recording streams or not.
446 *
447 * @returns \c true if at least one recording stream has this feature enabled, or \c false if
448 * no recording stream has this feature enabled.
449 * @param enmFeature Recording feature to check for.
450 */
451bool RecordingContext::IsFeatureEnabled(RecordingFeature_T enmFeature)
452{
453 lock();
454
455 RecordingStreams::const_iterator itStream = this->vecStreams.begin();
456 while (itStream != this->vecStreams.end())
457 {
458 if ((*itStream)->GetConfig().isFeatureEnabled(enmFeature))
459 {
460 unlock();
461 return true;
462 }
463 ++itStream;
464 }
465
466 unlock();
467
468 return false;
469}
470
471/**
472 * Returns if this recording context is ready to start recording.
473 *
474 * @returns @c true if recording context is ready, @c false if not.
475 */
476bool RecordingContext::IsReady(void) const
477{
478 return (this->enmState >= RECORDINGSTS_CREATED);
479}
480
481/**
482 * Returns if this recording context is ready to accept new recording data for a given screen.
483 *
484 * @returns @c true if the specified screen is ready, @c false if not.
485 * @param uScreen Screen ID.
486 * @param msTimestamp Current timestamp (in ms). Currently not being used.
487 */
488bool RecordingContext::IsReady(uint32_t uScreen, uint64_t msTimestamp)
489{
490 RT_NOREF(msTimestamp);
491
492 lock();
493
494 bool fIsReady = false;
495
496 if (this->enmState != RECORDINGSTS_STARTED)
497 {
498 const RecordingStream *pStream = GetStream(uScreen);
499 if (pStream)
500 fIsReady = pStream->IsReady();
501
502 /* Note: Do not check for other constraints like the video FPS rate here,
503 * as this check then also would affect other (non-FPS related) stuff
504 * like audio data. */
505 }
506
507 unlock();
508
509 return fIsReady;
510}
511
512/**
513 * Returns whether a given recording context has been started or not.
514 *
515 * @returns true if active, false if not.
516 */
517bool RecordingContext::IsStarted(void)
518{
519 lock();
520
521 const bool fIsStarted = this->enmState == RECORDINGSTS_STARTED;
522
523 unlock();
524
525 return fIsStarted;
526}
527
528/**
529 * Checks if a specified limit for recording has been reached.
530 *
531 * @returns true if any limit has been reached.
532 */
533bool RecordingContext::IsLimitReached(void)
534{
535 lock();
536
537 LogFlowThisFunc(("cStreamsEnabled=%RU16\n", this->cStreamsEnabled));
538
539 const bool fLimitReached = this->cStreamsEnabled == 0;
540
541 unlock();
542
543 return fLimitReached;
544}
545
546/**
547 * Checks if a specified limit for recording has been reached.
548 *
549 * @returns true if any limit has been reached.
550 * @param uScreen Screen ID.
551 * @param msTimestamp Timestamp (in ms) to check for.
552 */
553bool RecordingContext::IsLimitReached(uint32_t uScreen, uint64_t msTimestamp)
554{
555 lock();
556
557 bool fLimitReached = false;
558
559 const RecordingStream *pStream = getStreamInternal(uScreen);
560 if ( !pStream
561 || pStream->IsLimitReached(msTimestamp))
562 {
563 fLimitReached = true;
564 }
565
566 unlock();
567
568 return fLimitReached;
569}
570
571DECLCALLBACK(int) RecordingContext::OnLimitReached(uint32_t uScreen, int rc)
572{
573 RT_NOREF(uScreen, rc);
574 LogFlowThisFunc(("Stream %RU32 has reached its limit (%Rrc)\n", uScreen, rc));
575
576 lock();
577
578 Assert(this->cStreamsEnabled);
579 this->cStreamsEnabled--;
580
581 LogFlowThisFunc(("cStreamsEnabled=%RU16\n", cStreamsEnabled));
582
583 unlock();
584
585 return VINF_SUCCESS;
586}
587
588/**
589 * Sends an audio frame to the video encoding thread.
590 *
591 * @thread EMT
592 *
593 * @returns IPRT status code.
594 * @param pvData Audio frame data to send.
595 * @param cbData Size (in bytes) of (encoded) audio frame data.
596 * @param msTimestamp Timestamp (in ms) of audio playback.
597 */
598int RecordingContext::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp)
599{
600#ifdef VBOX_WITH_AUDIO_RECORDING
601 AssertPtrReturn(pvData, VERR_INVALID_POINTER);
602 AssertReturn(cbData, VERR_INVALID_PARAMETER);
603
604 /* To save time spent in EMT, do the required audio multiplexing in the encoding thread.
605 *
606 * The multiplexing is needed to supply all recorded (enabled) screens with the same
607 * audio data at the same given point in time.
608 */
609 RecordingBlock *pBlock = new RecordingBlock();
610 pBlock->enmType = RECORDINGBLOCKTYPE_AUDIO;
611
612 PRECORDINGAUDIOFRAME pFrame = (PRECORDINGAUDIOFRAME)RTMemAlloc(sizeof(RECORDINGAUDIOFRAME));
613 AssertPtrReturn(pFrame, VERR_NO_MEMORY);
614
615 pFrame->pvBuf = (uint8_t *)RTMemAlloc(cbData);
616 AssertPtrReturn(pFrame->pvBuf, VERR_NO_MEMORY);
617 pFrame->cbBuf = cbData;
618
619 memcpy(pFrame->pvBuf, pvData, cbData);
620
621 pBlock->pvData = pFrame;
622 pBlock->cbData = sizeof(RECORDINGAUDIOFRAME) + cbData;
623 pBlock->cRefs = this->cStreamsEnabled;
624 pBlock->msTimestamp = msTimestamp;
625
626 int rc = RTCritSectEnter(&this->CritSect);
627 if (RT_FAILURE(rc))
628 return rc;
629
630 try
631 {
632 RecordingBlockMap::iterator itBlocks = this->mapBlocksCommon.find(msTimestamp);
633 if (itBlocks == this->mapBlocksCommon.end())
634 {
635 RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
636 pRecordingBlocks->List.push_back(pBlock);
637
638 this->mapBlocksCommon.insert(std::make_pair(msTimestamp, pRecordingBlocks));
639 }
640 else
641 itBlocks->second->List.push_back(pBlock);
642 }
643 catch (const std::exception &ex)
644 {
645 RT_NOREF(ex);
646 rc = VERR_NO_MEMORY;
647 }
648
649 int rc2 = RTCritSectLeave(&this->CritSect);
650 AssertRC(rc2);
651
652 if (RT_SUCCESS(rc))
653 rc = threadNotify();
654
655 return rc;
656#else
657 RT_NOREF(pvData, cbData, msTimestamp);
658 return VINF_SUCCESS;
659#endif
660}
661
662/**
663 * Copies a source video frame to the intermediate RGB buffer.
664 * This function is executed only once per time.
665 *
666 * @thread EMT
667 *
668 * @returns IPRT status code.
669 * @param uScreen Screen number to send video frame to.
670 * @param x Starting x coordinate of the video frame.
671 * @param y Starting y coordinate of the video frame.
672 * @param uPixelFormat Pixel format.
673 * @param uBPP Bits Per Pixel (BPP).
674 * @param uBytesPerLine Bytes per scanline.
675 * @param uSrcWidth Width of the video frame.
676 * @param uSrcHeight Height of the video frame.
677 * @param puSrcData Pointer to video frame data.
678 * @param msTimestamp Timestamp (in ms).
679 */
680int RecordingContext::SendVideoFrame(uint32_t uScreen, uint32_t x, uint32_t y,
681 uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
682 uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData,
683 uint64_t msTimestamp)
684{
685 AssertReturn(uSrcWidth, VERR_INVALID_PARAMETER);
686 AssertReturn(uSrcHeight, VERR_INVALID_PARAMETER);
687 AssertReturn(puSrcData, VERR_INVALID_POINTER);
688
689 int rc = RTCritSectEnter(&this->CritSect);
690 AssertRC(rc);
691
692 RecordingStream *pStream = GetStream(uScreen);
693 if (!pStream)
694 {
695 rc = RTCritSectLeave(&this->CritSect);
696 AssertRC(rc);
697
698 AssertFailed();
699 return VERR_NOT_FOUND;
700 }
701
702 rc = pStream->SendVideoFrame(x, y, uPixelFormat, uBPP, uBytesPerLine, uSrcWidth, uSrcHeight, puSrcData, msTimestamp);
703
704 int rc2 = RTCritSectLeave(&this->CritSect);
705 AssertRC(rc2);
706
707 if ( RT_SUCCESS(rc)
708 && rc != VINF_RECORDING_THROTTLED) /* Only signal the thread if operation was successful. */
709 {
710 threadNotify();
711 }
712
713 return rc;
714}
715
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