[45841] | 1 | /* $Id: Recording.cpp 82968 2020-02-04 10:35:17Z vboxsync $ */
|
---|
| 2 | /** @file
|
---|
[75392] | 3 | * Recording context code.
|
---|
[74904] | 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.
|
---|
[45841] | 11 | */
|
---|
| 12 |
|
---|
| 13 | /*
|
---|
[82968] | 14 | * Copyright (C) 2012-2020 Oracle Corporation
|
---|
[45841] | 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 |
|
---|
[69683] | 25 | #ifdef LOG_GROUP
|
---|
| 26 | # undef LOG_GROUP
|
---|
| 27 | #endif
|
---|
[67914] | 28 | #define LOG_GROUP LOG_GROUP_MAIN_DISPLAY
|
---|
| 29 | #include "LoggingNew.h"
|
---|
[65173] | 30 |
|
---|
[65401] | 31 | #include <stdexcept>
|
---|
[65173] | 32 | #include <vector>
|
---|
| 33 |
|
---|
[45878] | 34 | #include <iprt/asm.h>
|
---|
[45841] | 35 | #include <iprt/assert.h>
|
---|
[68453] | 36 | #include <iprt/critsect.h>
|
---|
[68798] | 37 | #include <iprt/path.h>
|
---|
[45878] | 38 | #include <iprt/semaphore.h>
|
---|
| 39 | #include <iprt/thread.h>
|
---|
[52791] | 40 | #include <iprt/time.h>
|
---|
[45841] | 41 |
|
---|
[71164] | 42 | #include <VBox/err.h>
|
---|
[45841] | 43 | #include <VBox/com/VirtualBox.h>
|
---|
| 44 |
|
---|
[75251] | 45 | #include "ConsoleImpl.h"
|
---|
[75344] | 46 | #include "Recording.h"
|
---|
| 47 | #include "RecordingInternals.h"
|
---|
| 48 | #include "RecordingStream.h"
|
---|
| 49 | #include "RecordingUtils.h"
|
---|
[75251] | 50 | #include "WebMWriter.h"
|
---|
[45841] | 51 |
|
---|
[65221] | 52 | using namespace com;
|
---|
| 53 |
|
---|
[68453] | 54 | #ifdef DEBUG_andy
|
---|
| 55 | /** Enables dumping audio / video data for debugging reasons. */
|
---|
[75354] | 56 | //# define VBOX_RECORDING_DUMP
|
---|
[68453] | 57 | #endif
|
---|
| 58 |
|
---|
[45841] | 59 |
|
---|
[75361] | 60 | RecordingContext::RecordingContext(Console *a_pConsole, const settings::RecordingSettings &a_Settings)
|
---|
[75251] | 61 | : pConsole(a_pConsole)
|
---|
[75354] | 62 | , enmState(RECORDINGSTS_UNINITIALIZED)
|
---|
[75441] | 63 | , cStreamsEnabled(0)
|
---|
[75251] | 64 | {
|
---|
[75354] | 65 | int rc = RecordingContext::createInternal(a_Settings);
|
---|
[75251] | 66 | if (RT_FAILURE(rc))
|
---|
| 67 | throw rc;
|
---|
| 68 | }
|
---|
| 69 |
|
---|
[75354] | 70 | RecordingContext::~RecordingContext(void)
|
---|
[75251] | 71 | {
|
---|
| 72 | destroyInternal();
|
---|
| 73 | }
|
---|
| 74 |
|
---|
[45841] | 75 | /**
|
---|
[75354] | 76 | * Worker thread for all streams of a recording context.
|
---|
[45841] | 77 | *
|
---|
[74904] | 78 | * For video frames, this also does the RGB/YUV conversion and encoding.
|
---|
[45878] | 79 | */
|
---|
[75354] | 80 | DECLCALLBACK(int) RecordingContext::threadMain(RTTHREAD hThreadSelf, void *pvUser)
|
---|
[45878] | 81 | {
|
---|
[75354] | 82 | RecordingContext *pThis = (RecordingContext *)pvUser;
|
---|
[65401] | 83 |
|
---|
[65435] | 84 | /* Signal that we're up and rockin'. */
|
---|
| 85 | RTThreadUserSignal(hThreadSelf);
|
---|
| 86 |
|
---|
[74904] | 87 | LogFunc(("Thread started\n"));
|
---|
| 88 |
|
---|
[45878] | 89 | for (;;)
|
---|
| 90 | {
|
---|
[75251] | 91 | int rc = RTSemEventWait(pThis->WaitEvent, RT_INDEFINITE_WAIT);
|
---|
[45878] | 92 | AssertRCBreak(rc);
|
---|
| 93 |
|
---|
[75251] | 94 | Log2Func(("Processing %zu streams\n", pThis->vecStreams.size()));
|
---|
[75040] | 95 |
|
---|
[65429] | 96 | /** @todo r=andy This is inefficient -- as we already wake up this thread
|
---|
| 97 | * for every screen from Main, we here go again (on every wake up) through
|
---|
| 98 | * all screens. */
|
---|
[75354] | 99 | RecordingStreams::iterator itStream = pThis->vecStreams.begin();
|
---|
[75251] | 100 | while (itStream != pThis->vecStreams.end())
|
---|
[45878] | 101 | {
|
---|
[75354] | 102 | RecordingStream *pStream = (*itStream);
|
---|
[65173] | 103 |
|
---|
[75251] | 104 | rc = pStream->Process(pThis->mapBlocksCommon);
|
---|
[75040] | 105 | if (RT_FAILURE(rc))
|
---|
[75441] | 106 | {
|
---|
| 107 | LogRel(("Recording: Processing stream #%RU16 failed (%Rrc)\n", pStream->GetID(), rc));
|
---|
[75040] | 108 | break;
|
---|
[75441] | 109 | }
|
---|
[68453] | 110 |
|
---|
[75040] | 111 | ++itStream;
|
---|
| 112 | }
|
---|
[65429] | 113 |
|
---|
[75040] | 114 | if (RT_FAILURE(rc))
|
---|
[75391] | 115 | LogRel(("Recording: Encoding thread failed (%Rrc)\n", rc));
|
---|
[65173] | 116 |
|
---|
[68720] | 117 | /* Keep going in case of errors. */
|
---|
| 118 |
|
---|
[75251] | 119 | if (ASMAtomicReadBool(&pThis->fShutdown))
|
---|
[74904] | 120 | {
|
---|
| 121 | LogFunc(("Thread is shutting down ...\n"));
|
---|
| 122 | break;
|
---|
| 123 | }
|
---|
| 124 |
|
---|
[68720] | 125 | } /* for */
|
---|
| 126 |
|
---|
[74904] | 127 | LogFunc(("Thread ended\n"));
|
---|
[45878] | 128 | return VINF_SUCCESS;
|
---|
| 129 | }
|
---|
| 130 |
|
---|
| 131 | /**
|
---|
[74904] | 132 | * Notifies a recording context's encoding thread.
|
---|
| 133 | *
|
---|
| 134 | * @returns IPRT status code.
|
---|
| 135 | */
|
---|
[75354] | 136 | int RecordingContext::threadNotify(void)
|
---|
[74904] | 137 | {
|
---|
[75251] | 138 | return RTSemEventSignal(this->WaitEvent);
|
---|
[74904] | 139 | }
|
---|
| 140 |
|
---|
| 141 | /**
|
---|
[75355] | 142 | * Creates a recording context.
|
---|
[45950] | 143 | *
|
---|
| 144 | * @returns IPRT status code.
|
---|
[75392] | 145 | * @param a_Settings Recording settings to use for context creation.
|
---|
[45950] | 146 | */
|
---|
[75361] | 147 | int RecordingContext::createInternal(const settings::RecordingSettings &a_Settings)
|
---|
[45950] | 148 | {
|
---|
[75251] | 149 | int rc = RTCritSectInit(&this->CritSect);
|
---|
[68453] | 150 | if (RT_FAILURE(rc))
|
---|
| 151 | return rc;
|
---|
| 152 |
|
---|
[75361] | 153 | settings::RecordingScreenMap::const_iterator itScreen = a_Settings.mapScreens.begin();
|
---|
[75251] | 154 | while (itScreen != a_Settings.mapScreens.end())
|
---|
[52312] | 155 | {
|
---|
[75354] | 156 | RecordingStream *pStream = NULL;
|
---|
[74904] | 157 | try
|
---|
[65173] | 158 | {
|
---|
[75354] | 159 | pStream = new RecordingStream(this, itScreen->first /* Screen ID */, itScreen->second);
|
---|
[75251] | 160 | this->vecStreams.push_back(pStream);
|
---|
[75441] | 161 | if (itScreen->second.fEnabled)
|
---|
| 162 | this->cStreamsEnabled++;
|
---|
[74904] | 163 | }
|
---|
| 164 | catch (std::bad_alloc &)
|
---|
| 165 | {
|
---|
[65173] | 166 | rc = VERR_NO_MEMORY;
|
---|
| 167 | break;
|
---|
| 168 | }
|
---|
[75313] | 169 |
|
---|
| 170 | ++itScreen;
|
---|
[52312] | 171 | }
|
---|
[45956] | 172 |
|
---|
[65173] | 173 | if (RT_SUCCESS(rc))
|
---|
| 174 | {
|
---|
[75251] | 175 | this->tsStartMs = RTTimeMilliTS();
|
---|
[75354] | 176 | this->enmState = RECORDINGSTS_CREATED;
|
---|
[75251] | 177 | this->fShutdown = false;
|
---|
[65435] | 178 |
|
---|
[75251] | 179 | /* Copy the settings to our context. */
|
---|
| 180 | this->Settings = a_Settings;
|
---|
[68798] | 181 |
|
---|
[75251] | 182 | rc = RTSemEventCreate(&this->WaitEvent);
|
---|
[65173] | 183 | AssertRCReturn(rc, rc);
|
---|
| 184 | }
|
---|
[65435] | 185 |
|
---|
| 186 | if (RT_FAILURE(rc))
|
---|
[76893] | 187 | destroyInternal();
|
---|
[65173] | 188 |
|
---|
| 189 | return rc;
|
---|
[45950] | 190 | }
|
---|
| 191 |
|
---|
[75392] | 192 | /**
|
---|
| 193 | * Starts a recording context by creating its worker thread.
|
---|
| 194 | *
|
---|
| 195 | * @returns IPRT status code.
|
---|
| 196 | */
|
---|
[75354] | 197 | int RecordingContext::startInternal(void)
|
---|
[75307] | 198 | {
|
---|
[75354] | 199 | if (this->enmState == RECORDINGSTS_STARTED)
|
---|
[75307] | 200 | return VINF_SUCCESS;
|
---|
| 201 |
|
---|
[75354] | 202 | Assert(this->enmState == RECORDINGSTS_CREATED);
|
---|
[75307] | 203 |
|
---|
[75354] | 204 | int rc = RTThreadCreate(&this->Thread, RecordingContext::threadMain, (void *)this, 0,
|
---|
[75313] | 205 | RTTHREADTYPE_MAIN_WORKER, RTTHREADFLAGS_WAITABLE, "Record");
|
---|
[75307] | 206 |
|
---|
| 207 | if (RT_SUCCESS(rc)) /* Wait for the thread to start. */
|
---|
| 208 | rc = RTThreadUserWait(this->Thread, 30 * RT_MS_1SEC /* 30s timeout */);
|
---|
| 209 |
|
---|
| 210 | if (RT_SUCCESS(rc))
|
---|
| 211 | {
|
---|
[75391] | 212 | LogRel(("Recording: Started\n"));
|
---|
[75354] | 213 | this->enmState = RECORDINGSTS_STARTED;
|
---|
[75307] | 214 | }
|
---|
[75391] | 215 | else
|
---|
| 216 | Log(("Recording: Failed to start (%Rrc)\n", rc));
|
---|
[75307] | 217 |
|
---|
| 218 | return rc;
|
---|
| 219 | }
|
---|
| 220 |
|
---|
[75392] | 221 | /**
|
---|
| 222 | * Stops a recording context by telling the worker thread to stop and finalizing its operation.
|
---|
| 223 | *
|
---|
| 224 | * @returns IPRT status code.
|
---|
| 225 | */
|
---|
[75354] | 226 | int RecordingContext::stopInternal(void)
|
---|
[75307] | 227 | {
|
---|
[75354] | 228 | if (this->enmState != RECORDINGSTS_STARTED)
|
---|
[75307] | 229 | return VINF_SUCCESS;
|
---|
| 230 |
|
---|
[75313] | 231 | LogThisFunc(("Shutting down thread ...\n"));
|
---|
[75307] | 232 |
|
---|
| 233 | /* Set shutdown indicator. */
|
---|
| 234 | ASMAtomicWriteBool(&this->fShutdown, true);
|
---|
| 235 |
|
---|
| 236 | /* Signal the thread and wait for it to shut down. */
|
---|
| 237 | int rc = threadNotify();
|
---|
| 238 | if (RT_SUCCESS(rc))
|
---|
| 239 | rc = RTThreadWait(this->Thread, 30 * 1000 /* 10s timeout */, NULL);
|
---|
| 240 |
|
---|
[76893] | 241 | lock();
|
---|
| 242 |
|
---|
[75313] | 243 | if (RT_SUCCESS(rc))
|
---|
| 244 | {
|
---|
[75391] | 245 | LogRel(("Recording: Stopped\n"));
|
---|
[75354] | 246 | this->enmState = RECORDINGSTS_CREATED;
|
---|
[75313] | 247 | }
|
---|
[75391] | 248 | else
|
---|
| 249 | Log(("Recording: Failed to stop (%Rrc)\n", rc));
|
---|
[75313] | 250 |
|
---|
[76893] | 251 | unlock();
|
---|
| 252 |
|
---|
[75313] | 253 | LogFlowThisFunc(("%Rrc\n", rc));
|
---|
[75307] | 254 | return rc;
|
---|
| 255 | }
|
---|
| 256 |
|
---|
[45950] | 257 | /**
|
---|
[75392] | 258 | * Destroys a recording context, internal version.
|
---|
[65173] | 259 | */
|
---|
[76893] | 260 | void RecordingContext::destroyInternal(void)
|
---|
[65173] | 261 | {
|
---|
[76893] | 262 | if (this->enmState == RECORDINGSTS_UNINITIALIZED)
|
---|
| 263 | return;
|
---|
| 264 |
|
---|
[75313] | 265 | int rc = stopInternal();
|
---|
[76893] | 266 | AssertRCReturnVoid(rc);
|
---|
[74904] | 267 |
|
---|
[76893] | 268 | lock();
|
---|
| 269 |
|
---|
[75313] | 270 | rc = RTSemEventDestroy(this->WaitEvent);
|
---|
[76893] | 271 | AssertRCReturnVoid(rc);
|
---|
[65173] | 272 |
|
---|
[75313] | 273 | this->WaitEvent = NIL_RTSEMEVENT;
|
---|
[72014] | 274 |
|
---|
[76893] | 275 | RecordingStreams::iterator it = this->vecStreams.begin();
|
---|
| 276 | while (it != this->vecStreams.end())
|
---|
[65173] | 277 | {
|
---|
[76893] | 278 | RecordingStream *pStream = (*it);
|
---|
[65173] | 279 |
|
---|
[76893] | 280 | rc = pStream->Uninit();
|
---|
| 281 | AssertRC(rc);
|
---|
[68798] | 282 |
|
---|
[76893] | 283 | delete pStream;
|
---|
| 284 | pStream = NULL;
|
---|
[68798] | 285 |
|
---|
[76893] | 286 | this->vecStreams.erase(it);
|
---|
| 287 | it = this->vecStreams.begin();
|
---|
| 288 | }
|
---|
[68453] | 289 |
|
---|
[76893] | 290 | /* Sanity. */
|
---|
| 291 | Assert(this->vecStreams.empty());
|
---|
| 292 | Assert(this->mapBlocksCommon.size() == 0);
|
---|
[65177] | 293 |
|
---|
[76893] | 294 | unlock();
|
---|
[65177] | 295 |
|
---|
[76893] | 296 | if (RTCritSectIsInitialized(&this->CritSect))
|
---|
| 297 | {
|
---|
| 298 | Assert(RTCritSectGetWaiters(&this->CritSect) == -1);
|
---|
[75251] | 299 | RTCritSectDelete(&this->CritSect);
|
---|
[65173] | 300 | }
|
---|
| 301 |
|
---|
[76893] | 302 | this->enmState = RECORDINGSTS_UNINITIALIZED;
|
---|
[65173] | 303 | }
|
---|
| 304 |
|
---|
[75392] | 305 | /**
|
---|
| 306 | * Returns a recording context's current settings.
|
---|
| 307 | *
|
---|
| 308 | * @returns The recording context's current settings.
|
---|
| 309 | */
|
---|
[75361] | 310 | const settings::RecordingSettings &RecordingContext::GetConfig(void) const
|
---|
[45841] | 311 | {
|
---|
[75251] | 312 | return this->Settings;
|
---|
| 313 | }
|
---|
[65438] | 314 |
|
---|
[75392] | 315 | /**
|
---|
| 316 | * Returns the recording stream for a specific screen.
|
---|
| 317 | *
|
---|
| 318 | * @returns Recording stream for a specific screen, or NULL if not found.
|
---|
| 319 | * @param uScreen Screen ID to retrieve recording stream for.
|
---|
| 320 | */
|
---|
[75354] | 321 | RecordingStream *RecordingContext::getStreamInternal(unsigned uScreen) const
|
---|
[75251] | 322 | {
|
---|
[75354] | 323 | RecordingStream *pStream;
|
---|
[68798] | 324 |
|
---|
[75251] | 325 | try
|
---|
| 326 | {
|
---|
| 327 | pStream = this->vecStreams.at(uScreen);
|
---|
| 328 | }
|
---|
| 329 | catch (std::out_of_range &)
|
---|
| 330 | {
|
---|
| 331 | pStream = NULL;
|
---|
| 332 | }
|
---|
[68798] | 333 |
|
---|
[75251] | 334 | return pStream;
|
---|
[45841] | 335 | }
|
---|
| 336 |
|
---|
[75488] | 337 | int RecordingContext::lock(void)
|
---|
| 338 | {
|
---|
| 339 | int rc = RTCritSectEnter(&this->CritSect);
|
---|
| 340 | AssertRC(rc);
|
---|
| 341 | return rc;
|
---|
| 342 | }
|
---|
| 343 |
|
---|
| 344 | int RecordingContext::unlock(void)
|
---|
| 345 | {
|
---|
| 346 | int rc = RTCritSectLeave(&this->CritSect);
|
---|
| 347 | AssertRC(rc);
|
---|
| 348 | return rc;
|
---|
| 349 | }
|
---|
| 350 |
|
---|
[45841] | 351 | /**
|
---|
[75066] | 352 | * Retrieves a specific recording stream of a recording context.
|
---|
| 353 | *
|
---|
| 354 | * @returns Pointer to recording stream if found, or NULL if not found.
|
---|
| 355 | * @param uScreen Screen number of recording stream to look up.
|
---|
| 356 | */
|
---|
[75354] | 357 | RecordingStream *RecordingContext::GetStream(unsigned uScreen) const
|
---|
[75066] | 358 | {
|
---|
[75251] | 359 | return getStreamInternal(uScreen);
|
---|
| 360 | }
|
---|
[75066] | 361 |
|
---|
[75392] | 362 | /**
|
---|
| 363 | * Returns the number of configured recording streams for a recording context.
|
---|
| 364 | *
|
---|
| 365 | * @returns Number of configured recording streams.
|
---|
| 366 | */
|
---|
[75354] | 367 | size_t RecordingContext::GetStreamCount(void) const
|
---|
[75251] | 368 | {
|
---|
| 369 | return this->vecStreams.size();
|
---|
| 370 | }
|
---|
[75066] | 371 |
|
---|
[75392] | 372 | /**
|
---|
| 373 | * Creates a new recording context.
|
---|
| 374 | *
|
---|
| 375 | * @returns IPRT status code.
|
---|
| 376 | * @param a_Settings Recording settings to use for creation.
|
---|
| 377 | *
|
---|
| 378 | */
|
---|
[75361] | 379 | int RecordingContext::Create(const settings::RecordingSettings &a_Settings)
|
---|
[75251] | 380 | {
|
---|
| 381 | return createInternal(a_Settings);
|
---|
| 382 | }
|
---|
| 383 |
|
---|
[75392] | 384 | /**
|
---|
| 385 | * Destroys a recording context.
|
---|
| 386 | */
|
---|
[76893] | 387 | void RecordingContext::Destroy(void)
|
---|
[75251] | 388 | {
|
---|
[76893] | 389 | destroyInternal();
|
---|
[75251] | 390 | }
|
---|
| 391 |
|
---|
[75392] | 392 | /**
|
---|
| 393 | * Starts a recording context.
|
---|
| 394 | *
|
---|
| 395 | * @returns IPRT status code.
|
---|
| 396 | */
|
---|
[75354] | 397 | int RecordingContext::Start(void)
|
---|
[75307] | 398 | {
|
---|
| 399 | return startInternal();
|
---|
| 400 | }
|
---|
| 401 |
|
---|
[75392] | 402 | /**
|
---|
| 403 | * Stops a recording context.
|
---|
| 404 | */
|
---|
[75354] | 405 | int RecordingContext::Stop(void)
|
---|
[75307] | 406 | {
|
---|
| 407 | return stopInternal();
|
---|
| 408 | }
|
---|
| 409 |
|
---|
[75392] | 410 | /**
|
---|
| 411 | * Returns if a specific recoding feature is enabled for at least one of the attached
|
---|
| 412 | * recording streams or not.
|
---|
| 413 | *
|
---|
| 414 | * @returns \c true if at least one recording stream has this feature enabled, or \c false if
|
---|
| 415 | * no recording stream has this feature enabled.
|
---|
| 416 | * @param enmFeature Recording feature to check for.
|
---|
| 417 | */
|
---|
[75488] | 418 | bool RecordingContext::IsFeatureEnabled(RecordingFeature_T enmFeature)
|
---|
[75251] | 419 | {
|
---|
[75488] | 420 | lock();
|
---|
| 421 |
|
---|
[75354] | 422 | RecordingStreams::const_iterator itStream = this->vecStreams.begin();
|
---|
[75251] | 423 | while (itStream != this->vecStreams.end())
|
---|
[75066] | 424 | {
|
---|
[75251] | 425 | if ((*itStream)->GetConfig().isFeatureEnabled(enmFeature))
|
---|
[75488] | 426 | {
|
---|
| 427 | unlock();
|
---|
[75251] | 428 | return true;
|
---|
[75488] | 429 | }
|
---|
[75251] | 430 | ++itStream;
|
---|
[75066] | 431 | }
|
---|
| 432 |
|
---|
[75488] | 433 | unlock();
|
---|
| 434 |
|
---|
[75251] | 435 | return false;
|
---|
[75066] | 436 | }
|
---|
| 437 |
|
---|
[75313] | 438 | /**
|
---|
| 439 | * Returns if this recording context is ready to start recording.
|
---|
| 440 | *
|
---|
| 441 | * @returns @c true if recording context is ready, @c false if not.
|
---|
| 442 | */
|
---|
[76893] | 443 | bool RecordingContext::IsReady(void)
|
---|
[75251] | 444 | {
|
---|
[76893] | 445 | lock();
|
---|
| 446 |
|
---|
| 447 | const bool fIsReady = this->enmState >= RECORDINGSTS_CREATED;
|
---|
| 448 |
|
---|
| 449 | unlock();
|
---|
| 450 |
|
---|
| 451 | return fIsReady;
|
---|
[75251] | 452 | }
|
---|
| 453 |
|
---|
[75066] | 454 | /**
|
---|
[75313] | 455 | * Returns if this recording context is ready to accept new recording data for a given screen.
|
---|
[50314] | 456 | *
|
---|
[75313] | 457 | * @returns @c true if the specified screen is ready, @c false if not.
|
---|
[65173] | 458 | * @param uScreen Screen ID.
|
---|
[75499] | 459 | * @param msTimestamp Current timestamp (in ms). Currently not being used.
|
---|
[50314] | 460 | */
|
---|
[75499] | 461 | bool RecordingContext::IsReady(uint32_t uScreen, uint64_t msTimestamp)
|
---|
[50314] | 462 | {
|
---|
[75499] | 463 | RT_NOREF(msTimestamp);
|
---|
[65438] | 464 |
|
---|
[75488] | 465 | lock();
|
---|
[50314] | 466 |
|
---|
[74907] | 467 | bool fIsReady = false;
|
---|
| 468 |
|
---|
[75488] | 469 | if (this->enmState != RECORDINGSTS_STARTED)
|
---|
| 470 | {
|
---|
| 471 | const RecordingStream *pStream = GetStream(uScreen);
|
---|
| 472 | if (pStream)
|
---|
| 473 | fIsReady = pStream->IsReady();
|
---|
[50314] | 474 |
|
---|
[75488] | 475 | /* Note: Do not check for other constraints like the video FPS rate here,
|
---|
| 476 | * as this check then also would affect other (non-FPS related) stuff
|
---|
| 477 | * like audio data. */
|
---|
| 478 | }
|
---|
[50314] | 479 |
|
---|
[75488] | 480 | unlock();
|
---|
| 481 |
|
---|
[74907] | 482 | return fIsReady;
|
---|
[50314] | 483 | }
|
---|
| 484 |
|
---|
| 485 | /**
|
---|
[72014] | 486 | * Returns whether a given recording context has been started or not.
|
---|
[52312] | 487 | *
|
---|
[68798] | 488 | * @returns true if active, false if not.
|
---|
| 489 | */
|
---|
[75488] | 490 | bool RecordingContext::IsStarted(void)
|
---|
[68798] | 491 | {
|
---|
[75488] | 492 | lock();
|
---|
| 493 |
|
---|
| 494 | const bool fIsStarted = this->enmState == RECORDINGSTS_STARTED;
|
---|
| 495 |
|
---|
| 496 | unlock();
|
---|
| 497 |
|
---|
| 498 | return fIsStarted;
|
---|
[68798] | 499 | }
|
---|
| 500 |
|
---|
| 501 | /**
|
---|
| 502 | * Checks if a specified limit for recording has been reached.
|
---|
| 503 | *
|
---|
[65173] | 504 | * @returns true if any limit has been reached.
|
---|
[75488] | 505 | */
|
---|
| 506 | bool RecordingContext::IsLimitReached(void)
|
---|
| 507 | {
|
---|
| 508 | lock();
|
---|
| 509 |
|
---|
| 510 | LogFlowThisFunc(("cStreamsEnabled=%RU16\n", this->cStreamsEnabled));
|
---|
| 511 |
|
---|
| 512 | const bool fLimitReached = this->cStreamsEnabled == 0;
|
---|
| 513 |
|
---|
| 514 | unlock();
|
---|
| 515 |
|
---|
| 516 | return fLimitReached;
|
---|
| 517 | }
|
---|
| 518 |
|
---|
| 519 | /**
|
---|
| 520 | * Checks if a specified limit for recording has been reached.
|
---|
| 521 | *
|
---|
| 522 | * @returns true if any limit has been reached.
|
---|
[65173] | 523 | * @param uScreen Screen ID.
|
---|
[75499] | 524 | * @param msTimestamp Timestamp (in ms) to check for.
|
---|
[52312] | 525 | */
|
---|
[75499] | 526 | bool RecordingContext::IsLimitReached(uint32_t uScreen, uint64_t msTimestamp)
|
---|
[52312] | 527 | {
|
---|
[75488] | 528 | lock();
|
---|
| 529 |
|
---|
| 530 | bool fLimitReached = false;
|
---|
| 531 |
|
---|
| 532 | const RecordingStream *pStream = getStreamInternal(uScreen);
|
---|
[65401] | 533 | if ( !pStream
|
---|
[75499] | 534 | || pStream->IsLimitReached(msTimestamp))
|
---|
[65401] | 535 | {
|
---|
[75488] | 536 | fLimitReached = true;
|
---|
[65401] | 537 | }
|
---|
[52312] | 538 |
|
---|
[75488] | 539 | unlock();
|
---|
| 540 |
|
---|
| 541 | return fLimitReached;
|
---|
[52312] | 542 | }
|
---|
| 543 |
|
---|
[75488] | 544 | DECLCALLBACK(int) RecordingContext::OnLimitReached(uint32_t uScreen, int rc)
|
---|
| 545 | {
|
---|
[75499] | 546 | RT_NOREF(uScreen, rc);
|
---|
[75488] | 547 | LogFlowThisFunc(("Stream %RU32 has reached its limit (%Rrc)\n", uScreen, rc));
|
---|
| 548 |
|
---|
| 549 | lock();
|
---|
| 550 |
|
---|
| 551 | Assert(this->cStreamsEnabled);
|
---|
| 552 | this->cStreamsEnabled--;
|
---|
| 553 |
|
---|
| 554 | LogFlowThisFunc(("cStreamsEnabled=%RU16\n", cStreamsEnabled));
|
---|
| 555 |
|
---|
| 556 | unlock();
|
---|
| 557 |
|
---|
| 558 | return VINF_SUCCESS;
|
---|
| 559 | }
|
---|
| 560 |
|
---|
[52312] | 561 | /**
|
---|
[65418] | 562 | * Sends an audio frame to the video encoding thread.
|
---|
| 563 | *
|
---|
[65429] | 564 | * @thread EMT
|
---|
| 565 | *
|
---|
[65418] | 566 | * @returns IPRT status code.
|
---|
| 567 | * @param pvData Audio frame data to send.
|
---|
[74904] | 568 | * @param cbData Size (in bytes) of (encoded) audio frame data.
|
---|
[75499] | 569 | * @param msTimestamp Timestamp (in ms) of audio playback.
|
---|
[65418] | 570 | */
|
---|
[75499] | 571 | int RecordingContext::SendAudioFrame(const void *pvData, size_t cbData, uint64_t msTimestamp)
|
---|
[65410] | 572 | {
|
---|
[75346] | 573 | #ifdef VBOX_WITH_AUDIO_RECORDING
|
---|
[74904] | 574 | AssertPtrReturn(pvData, VERR_INVALID_POINTER);
|
---|
| 575 | AssertReturn(cbData, VERR_INVALID_PARAMETER);
|
---|
[65429] | 576 |
|
---|
| 577 | /* To save time spent in EMT, do the required audio multiplexing in the encoding thread.
|
---|
| 578 | *
|
---|
| 579 | * The multiplexing is needed to supply all recorded (enabled) screens with the same
|
---|
| 580 | * audio data at the same given point in time.
|
---|
| 581 | */
|
---|
[75441] | 582 | RecordingBlock *pBlock = new RecordingBlock();
|
---|
[75354] | 583 | pBlock->enmType = RECORDINGBLOCKTYPE_AUDIO;
|
---|
[65429] | 584 |
|
---|
[75354] | 585 | PRECORDINGAUDIOFRAME pFrame = (PRECORDINGAUDIOFRAME)RTMemAlloc(sizeof(RECORDINGAUDIOFRAME));
|
---|
[74904] | 586 | AssertPtrReturn(pFrame, VERR_NO_MEMORY);
|
---|
[65429] | 587 |
|
---|
[74904] | 588 | pFrame->pvBuf = (uint8_t *)RTMemAlloc(cbData);
|
---|
| 589 | AssertPtrReturn(pFrame->pvBuf, VERR_NO_MEMORY);
|
---|
| 590 | pFrame->cbBuf = cbData;
|
---|
[65438] | 591 |
|
---|
[74904] | 592 | memcpy(pFrame->pvBuf, pvData, cbData);
|
---|
[65435] | 593 |
|
---|
[75499] | 594 | pBlock->pvData = pFrame;
|
---|
| 595 | pBlock->cbData = sizeof(RECORDINGAUDIOFRAME) + cbData;
|
---|
| 596 | pBlock->cRefs = this->cStreamsEnabled;
|
---|
| 597 | pBlock->msTimestamp = msTimestamp;
|
---|
[74904] | 598 |
|
---|
[76893] | 599 | lock();
|
---|
[74904] | 600 |
|
---|
[76893] | 601 | int rc;
|
---|
| 602 |
|
---|
[74904] | 603 | try
|
---|
| 604 | {
|
---|
[75499] | 605 | RecordingBlockMap::iterator itBlocks = this->mapBlocksCommon.find(msTimestamp);
|
---|
[75251] | 606 | if (itBlocks == this->mapBlocksCommon.end())
|
---|
[74904] | 607 | {
|
---|
[75354] | 608 | RecordingBlocks *pRecordingBlocks = new RecordingBlocks();
|
---|
| 609 | pRecordingBlocks->List.push_back(pBlock);
|
---|
[74904] | 610 |
|
---|
[75499] | 611 | this->mapBlocksCommon.insert(std::make_pair(msTimestamp, pRecordingBlocks));
|
---|
[74904] | 612 | }
|
---|
| 613 | else
|
---|
| 614 | itBlocks->second->List.push_back(pBlock);
|
---|
[76893] | 615 |
|
---|
| 616 | rc = VINF_SUCCESS;
|
---|
[74904] | 617 | }
|
---|
| 618 | catch (const std::exception &ex)
|
---|
| 619 | {
|
---|
| 620 | RT_NOREF(ex);
|
---|
| 621 | rc = VERR_NO_MEMORY;
|
---|
| 622 | }
|
---|
| 623 |
|
---|
[76893] | 624 | unlock();
|
---|
[74904] | 625 |
|
---|
[68453] | 626 | if (RT_SUCCESS(rc))
|
---|
[75251] | 627 | rc = threadNotify();
|
---|
[68453] | 628 |
|
---|
| 629 | return rc;
|
---|
[65435] | 630 | #else
|
---|
[75527] | 631 | RT_NOREF(pvData, cbData, msTimestamp);
|
---|
[68453] | 632 | return VINF_SUCCESS;
|
---|
[65435] | 633 | #endif
|
---|
[65410] | 634 | }
|
---|
| 635 |
|
---|
[45841] | 636 | /**
|
---|
[68798] | 637 | * Copies a source video frame to the intermediate RGB buffer.
|
---|
| 638 | * This function is executed only once per time.
|
---|
[45841] | 639 | *
|
---|
[46549] | 640 | * @thread EMT
|
---|
| 641 | *
|
---|
[45841] | 642 | * @returns IPRT status code.
|
---|
[75251] | 643 | * @param uScreen Screen number to send video frame to.
|
---|
[65415] | 644 | * @param x Starting x coordinate of the video frame.
|
---|
| 645 | * @param y Starting y coordinate of the video frame.
|
---|
| 646 | * @param uPixelFormat Pixel format.
|
---|
| 647 | * @param uBPP Bits Per Pixel (BPP).
|
---|
| 648 | * @param uBytesPerLine Bytes per scanline.
|
---|
| 649 | * @param uSrcWidth Width of the video frame.
|
---|
| 650 | * @param uSrcHeight Height of the video frame.
|
---|
| 651 | * @param puSrcData Pointer to video frame data.
|
---|
[75499] | 652 | * @param msTimestamp Timestamp (in ms).
|
---|
[45841] | 653 | */
|
---|
[75354] | 654 | int RecordingContext::SendVideoFrame(uint32_t uScreen, uint32_t x, uint32_t y,
|
---|
[75392] | 655 | uint32_t uPixelFormat, uint32_t uBPP, uint32_t uBytesPerLine,
|
---|
| 656 | uint32_t uSrcWidth, uint32_t uSrcHeight, uint8_t *puSrcData,
|
---|
[75499] | 657 | uint64_t msTimestamp)
|
---|
[45841] | 658 | {
|
---|
[68453] | 659 | AssertReturn(uSrcWidth, VERR_INVALID_PARAMETER);
|
---|
| 660 | AssertReturn(uSrcHeight, VERR_INVALID_PARAMETER);
|
---|
| 661 | AssertReturn(puSrcData, VERR_INVALID_POINTER);
|
---|
[45841] | 662 |
|
---|
[76893] | 663 | lock();
|
---|
[74904] | 664 |
|
---|
[75354] | 665 | RecordingStream *pStream = GetStream(uScreen);
|
---|
[68453] | 666 | if (!pStream)
|
---|
[74904] | 667 | {
|
---|
[76893] | 668 | unlock();
|
---|
[74904] | 669 |
|
---|
[75488] | 670 | AssertFailed();
|
---|
[68453] | 671 | return VERR_NOT_FOUND;
|
---|
[74904] | 672 | }
|
---|
[68453] | 673 |
|
---|
[76893] | 674 | int rc = pStream->SendVideoFrame(x, y, uPixelFormat, uBPP, uBytesPerLine, uSrcWidth, uSrcHeight, puSrcData, msTimestamp);
|
---|
[68453] | 675 |
|
---|
[76893] | 676 | unlock();
|
---|
[74904] | 677 |
|
---|
[68798] | 678 | if ( RT_SUCCESS(rc)
|
---|
[75488] | 679 | && rc != VINF_RECORDING_THROTTLED) /* Only signal the thread if operation was successful. */
|
---|
[68453] | 680 | {
|
---|
[75251] | 681 | threadNotify();
|
---|
[68453] | 682 | }
|
---|
[45878] | 683 |
|
---|
[46549] | 684 | return rc;
|
---|
[45841] | 685 | }
|
---|
[74904] | 686 |
|
---|