VirtualBox

source: vbox/trunk/src/VBox/Main/src-client/RecordingInternals.cpp@ 105006

最後變更 在這個檔案從105006是 105006,由 vboxsync 提交於 9 月 前

Video Recording: Big revamp to improve overall performance. We now don't rely on the periodic display refresh callback anymore to render the entire framebuffer but now rely on delta updates ("dirty rectangles"). Also, we now only encode new frames when an area has changed. This also needed cursor position + change change notifications, as we render the cursor on the host side if mouse integration is enabled (requires 7.1 Guest Additions as of now). Optimized the BGRA32->YUV IV420 color space conversion as well as the overall amount of pixel data shuffled forth and back. Added a new testcase for the cropping/centering code. bugref:10650

  • 屬性 svn:eol-style 設為 native
  • 屬性 svn:keywords 設為 Author Date Id Revision
檔案大小: 20.0 KB
 
1/* $Id: RecordingInternals.cpp 105006 2024-06-24 17:43:00Z vboxsync $ */
2/** @file
3 * Recording internals code.
4 */
5
6/*
7 * Copyright (C) 2012-2023 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.alldomusa.eu.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27
28#include "RecordingInternals.h"
29#include "RecordingUtils.h"
30
31#include <iprt/assert.h>
32#include <iprt/mem.h>
33
34#ifdef DEBUG
35# include <math.h>
36# include <iprt/file.h>
37# include <iprt/formats/bmp.h>
38#endif
39
40#include "../src-client/Remotery.h"
41
42
43/*********************************************************************************************************************************
44* Prototypes *
45*********************************************************************************************************************************/
46DECLINLINE(int) recordingVideoFrameInit(PRECORDINGVIDEOFRAME pFrame, uint32_t fFlags, uint32_t uWidth, uint32_t uHeight, uint32_t uPosX, uint32_t uPosY,
47 uint8_t uBPP, RECORDINGPIXELFMT enmFmt);
48
49
50/**
51 * Allocates an empty video frame, inline version.
52 *
53 * @returns Allocated video frame on success, or NULL on failure.
54 */
55DECLINLINE(PRECORDINGVIDEOFRAME) recordingVideoFrameAlloc(void)
56{
57 return (PRECORDINGVIDEOFRAME)RTMemAlloc(sizeof(RECORDINGVIDEOFRAME));
58}
59
60/**
61 * Allocates an empty video frame.
62 *
63 * @returns Allocated video frame on success, or NULL on failure.
64 */
65PRECORDINGVIDEOFRAME RecordingVideoFrameAlloc(void)
66{
67 PRECORDINGVIDEOFRAME pFrame = recordingVideoFrameAlloc();
68 AssertPtrReturn(pFrame, NULL);
69 RT_BZERO(pFrame, sizeof(RECORDINGVIDEOFRAME));
70 return pFrame;
71}
72
73/**
74 * Returns an allocated video frame from given image data.
75 *
76 * @returns Allocated video frame on success, or NULL on failure.
77 * @param pvData Pointer to image data to use.
78 * @param x X location hint (in pixel) to use for allocated frame.
79 * This is *not* the offset within \a pvData!
80 * @param y X location hint (in pixel) to use for allocated frame.
81 * This is *not* the offset within \a pvData!
82 * @param w Width (in pixel) of \a pvData image data.
83 * @param h Height (in pixel) of \a pvData image data.
84 * @param uBPP Bits per pixel) of \a pvData image data.
85 * @param enmFmt Pixel format of \a pvData image data.
86 */
87PRECORDINGVIDEOFRAME RecordingVideoFrameAllocEx(const void *pvData, uint32_t x, uint32_t y, uint32_t w, uint32_t h,
88 uint8_t uBPP, RECORDINGPIXELFMT enmFmt)
89{
90 PRECORDINGVIDEOFRAME pFrame = recordingVideoFrameAlloc();
91 AssertPtrReturn(pFrame, NULL);
92 int rc = recordingVideoFrameInit(pFrame, RECORDINGVIDEOFRAME_F_VISIBLE, w, h, x, y, uBPP, enmFmt);
93 AssertRCReturn(rc, NULL);
94 memcpy(pFrame->pau8Buf, pvData, pFrame->cbBuf);
95
96 return VINF_SUCCESS;
97}
98
99/**
100 * Frees a recording video frame.
101 *
102 * @param pFrame Pointer to video frame to free. The pointer will be invalid after return.
103 */
104void RecordingVideoFrameFree(PRECORDINGVIDEOFRAME pFrame)
105{
106 if (!pFrame)
107 return;
108
109 RecordingVideoFrameDestroy(pFrame);
110
111 RTMemFree(pFrame);
112}
113
114/**
115 * Initializes a recording frame, inline version.
116 *
117 * @returns VBox status code.
118 * @param pFrame Pointer to video frame to initialize.
119 * @param fFlags Flags of type RECORDINGVIDEOFRAME_F_XXX.
120 * @param uWidth Width (in pixel) of video frame.
121 * @param uHeight Height (in pixel) of video frame.
122 * @param uPosX X positioning hint.
123 * @param uPosY Y positioning hint.
124 * @param uBPP Bits per pixel (BPP).
125 * @param enmFmt Pixel format to use.
126 */
127DECLINLINE(int) recordingVideoFrameInit(PRECORDINGVIDEOFRAME pFrame, uint32_t fFlags, uint32_t uWidth, uint32_t uHeight,
128 uint32_t uPosX, uint32_t uPosY, uint8_t uBPP, RECORDINGPIXELFMT enmFmt)
129{
130 AssertPtrReturn(pFrame, VERR_INVALID_POINTER);
131 AssertReturn(uWidth, VERR_INVALID_PARAMETER);
132 AssertReturn(uHeight, VERR_INVALID_PARAMETER);
133 AssertReturn(uBPP && uBPP % 8 == 0, VERR_INVALID_PARAMETER);
134
135 /* Calculate bytes per pixel and set pixel format. */
136 const unsigned uBytesPerPixel = uBPP / 8;
137
138 /* Calculate bytes per pixel and set pixel format. */
139 const size_t cbRGBBuf = uWidth * uHeight * uBytesPerPixel;
140 AssertReturn(cbRGBBuf, VERR_INVALID_PARAMETER);
141
142 pFrame->pau8Buf = (uint8_t *)RTMemAlloc(cbRGBBuf);
143 AssertPtrReturn(pFrame->pau8Buf, VERR_NO_MEMORY);
144 pFrame->cbBuf = cbRGBBuf;
145
146 pFrame->fFlags = fFlags;
147 pFrame->Info.uWidth = uWidth;
148 pFrame->Info.uHeight = uHeight;
149 pFrame->Info.uBPP = uBPP;
150 pFrame->Info.enmPixelFmt = enmFmt;
151 pFrame->Info.uBytesPerLine = uWidth * uBytesPerPixel;
152 pFrame->Pos.x = uPosX;
153 pFrame->Pos.y = uPosY;
154
155 return VINF_SUCCESS;
156}
157
158/**
159 * Initializes a recording frame.
160 *
161 * @param pFrame Pointer to video frame to initialize.
162 * @param fFlags Flags of type RECORDINGVIDEOFRAME_F_XXX.
163 * @param uWidth Width (in pixel) of video frame.
164 * @param uHeight Height (in pixel) of video frame.
165 * @param uPosX X positioning hint.
166 * @param uPosY Y positioning hint.
167 * @param uBPP Bits per pixel (BPP).
168 * @param enmFmt Pixel format to use.
169 */
170int RecordingVideoFrameInit(PRECORDINGVIDEOFRAME pFrame, uint32_t fFlags, uint32_t uWidth, uint32_t uHeight, uint32_t uPosX, uint32_t uPosY,
171 uint8_t uBPP, RECORDINGPIXELFMT enmFmt)
172{
173 return recordingVideoFrameInit(pFrame, fFlags, uWidth, uHeight, uPosX, uPosY, uBPP, enmFmt);
174}
175
176/**
177 * Destroys a recording video frame.
178 *
179 * @param pFrame Pointer to video frame to destroy.
180 */
181void RecordingVideoFrameDestroy(PRECORDINGVIDEOFRAME pFrame)
182{
183 if (!pFrame)
184 return;
185
186 if (pFrame->pau8Buf)
187 {
188 Assert(pFrame->cbBuf);
189 RTMemFree(pFrame->pau8Buf);
190 pFrame->pau8Buf = NULL;
191 pFrame->cbBuf = 0;
192 }
193}
194
195/**
196 * Duplicates a video frame.
197 *
198 * @returns Pointer to duplicated frame on success, or NULL on failure.
199 * @param pFrame Video frame to duplicate.
200 */
201PRECORDINGVIDEOFRAME RecordingVideoFrameDup(PRECORDINGVIDEOFRAME pFrame)
202{
203 PRECORDINGVIDEOFRAME pFrameDup = (PRECORDINGVIDEOFRAME)RTMemDup(pFrame, sizeof(RECORDINGVIDEOFRAME));
204 AssertPtrReturn(pFrameDup, NULL);
205 pFrameDup->pau8Buf = (uint8_t *)RTMemDup(pFrame->pau8Buf, pFrame->cbBuf);
206 AssertPtrReturnStmt(pFrameDup, RTMemFree(pFrameDup), NULL);
207
208 return pFrameDup;
209}
210
211/**
212 * Clears the content of a video recording frame, inlined version.
213 *
214 * @param pFrame Video recording frame to clear content for.
215 */
216DECLINLINE(void) recordingVideoFrameClear(PRECORDINGVIDEOFRAME pFrame)
217{
218 RT_BZERO(pFrame->pau8Buf, pFrame->cbBuf);
219}
220
221/**
222 * Clears the content of a video recording frame.
223 *
224 * @param pFrame Video recording frame to clear content for.
225 */
226void RecordingVideoFrameClear(PRECORDINGVIDEOFRAME pFrame)
227{
228 recordingVideoFrameClear(pFrame);
229}
230
231/**
232 * Simple blitting function for raw image data, inlined version.
233 *
234 * @returns VBox status code.
235 * @param pFrame Destination frame.
236 * @param uDstX X destination (in pixel) within destination frame.
237 * @param uDstY Y destination (in pixel) within destination frame.
238 * @param uDstBytesPerLine Bytes per line in destination buffer.
239 * @param uDstBPP BPP of destination buffer.
240 * @param enmDstFmt Pixel format of source data. Must match \a pFrame.
241 * @param pu8Src Source data to blit. Must be in the same pixel format as \a pFrame.
242 * @param cbSrc Size (in bytes) of \a pu8Src.
243 * @param uSrcX X start (in pixel) within source data.
244 * @param uSrcY Y start (in pixel) within source data.
245 * @param uSrcWidth Width (in pixel) to blit from source data.
246 * @param uSrcHeight Height (in pixel) to blit from data.
247 * @param uSrcBytesPerLine Bytes per line in source data.
248 * @param uSrcBPP BPP of source data. Must match \a pFrame.
249 * @param enmSrcFmt Pixel format of source data. Must match \a pFrame.
250 */
251DECLINLINE(int) recordingVideoFrameBlitRaw(uint8_t *pu8Dst, size_t cbDst, uint32_t uDstX, uint32_t uDstY,
252 uint32_t uDstBytesPerLine, uint8_t uDstBPP, RECORDINGPIXELFMT enmDstFmt,
253 const uint8_t *pu8Src, size_t cbSrc, uint32_t uSrcX, uint32_t uSrcY, uint32_t uSrcWidth, uint32_t uSrcHeight,
254 uint32_t uSrcBytesPerLine, uint8_t uSrcBPP, RECORDINGPIXELFMT enmSrcFmt)
255{
256 RT_NOREF(enmDstFmt, enmSrcFmt);
257
258 uint8_t const uDstBytesPerPixel = uDstBPP / 8;
259 uint8_t const uSrcBytesPerPixel = uSrcBPP / 8;
260
261 size_t offSrc = RT_MIN(uSrcY * uSrcBytesPerLine + uSrcX * uSrcBytesPerPixel, cbSrc);
262 size_t offDst = RT_MIN(uDstY * uDstBytesPerLine + uDstX * uDstBytesPerPixel, cbDst);
263
264 for (uint32_t y = 0; y < uSrcHeight; y++)
265 {
266 size_t const cbToCopy = RT_MIN(cbDst - offDst,
267 RT_MIN(uSrcWidth * uSrcBytesPerPixel, cbSrc - offSrc));
268 if (!cbToCopy)
269 break;
270 memcpy(pu8Dst + offDst, (const uint8_t *)pu8Src + offSrc, cbToCopy);
271 offDst = RT_MIN(offDst + uDstBytesPerLine, cbDst);
272 Assert(offDst <= cbDst);
273 offSrc = RT_MIN(offSrc + uSrcBytesPerLine, cbSrc);
274 Assert(offSrc <= cbSrc);
275 }
276
277 return VINF_SUCCESS;
278}
279
280/**
281 * Simple blitting function for raw image data with alpha channel, inlined version.
282 *
283 * @returns VBox status code.
284 * @param pFrame Destination frame.
285 * @param uDstX X destination (in pixel) within destination frame.
286 * @param uDstY Y destination (in pixel) within destination frame.
287 * @param pu8Src Source data to blit. Must be in the same pixel format as \a pFrame.
288 * @param cbSrc Size (in bytes) of \a pu8Src.
289 * @param uSrcX X start (in pixel) within source data.
290 * @param uSrcY Y start (in pixel) within source data.
291 * @param uSrcWidth Width (in pixel) to blit from source data.
292 * @param uSrcHeight Height (in pixel) to blit from data.
293 * @param uSrcBytesPerLine Bytes per line in source data.
294 * @param uSrcBPP BPP of source data. Must match \a pFrame.
295 * @param enmFmt Pixel format of source data. Must match \a pFrame.
296 */
297DECLINLINE(int) recordingVideoFrameBlitRawAlpha(PRECORDINGVIDEOFRAME pFrame, uint32_t uDstX, uint32_t uDstY,
298 const uint8_t *pu8Src, size_t cbSrc, uint32_t uSrcX, uint32_t uSrcY, uint32_t uSrcWidth, uint32_t uSrcHeight,
299 uint32_t uSrcBytesPerLine, uint8_t uSrcBPP, RECORDINGPIXELFMT enmFmt)
300{
301 AssertReturn(pFrame->Info.enmPixelFmt == enmFmt, VERR_NOT_SUPPORTED);
302 AssertReturn(pFrame->Info.uBPP == uSrcBPP, VERR_NOT_SUPPORTED);
303
304 RT_NOREF(uDstX, uDstY, cbSrc, uSrcX, uSrcY, uSrcBytesPerLine);
305 uint8_t const uDstBytesPerPixel = pFrame->Info.uBPP / 8;
306 uint8_t const uSrcBytesPerPixel = uSrcBPP / 8;
307
308 for (uint32_t y = 0; y < uSrcHeight; y++)
309 {
310 size_t offSrc = RT_MIN((uSrcY + y) * uSrcBytesPerLine + uSrcX * uSrcBytesPerPixel, cbSrc);
311 size_t offDst = RT_MIN((uDstY + y) * pFrame->Info.uBytesPerLine + uDstX * uDstBytesPerPixel, pFrame->cbBuf);
312
313 for (uint32_t x = 0; x < uSrcWidth; x++)
314 {
315 /* BGRA */
316 int const idx_b = 0;
317 int const idx_g = 1;
318 int const idx_r = 2;
319 int const idx_a = 3;
320
321 unsigned int const alpha = pu8Src[offSrc + idx_a] + 1;
322 unsigned int const inv_alpha = 256 - pu8Src[offSrc + idx_a];
323 if (pu8Src[offSrc + idx_a])
324 {
325 pFrame->pau8Buf[offDst + idx_r] = (unsigned char)((alpha * pu8Src[offSrc + idx_r] + inv_alpha * pFrame->pau8Buf[offDst + idx_r]) >> 8);
326 pFrame->pau8Buf[offDst + idx_g] = (unsigned char)((alpha * pu8Src[offSrc + idx_g] + inv_alpha * pFrame->pau8Buf[offDst + idx_g]) >> 8);
327 pFrame->pau8Buf[offDst + idx_b] = (unsigned char)((alpha * pu8Src[offSrc + idx_b] + inv_alpha * pFrame->pau8Buf[offDst + idx_b]) >> 8);
328 pFrame->pau8Buf[offDst + idx_a] = 0xff;
329 }
330
331 offSrc = RT_MIN(offSrc + uSrcBytesPerPixel, cbSrc);
332 if (offSrc >= cbSrc)
333 break;
334 offDst = RT_MIN(offDst + uDstBytesPerPixel, pFrame->cbBuf);
335 if (offDst >= pFrame->cbBuf)
336 break;
337 }
338 }
339
340#if 0
341 RecordingUtilsDbgDumpImageData(pu8Src, cbSrc, "/tmp", "cursor-src", uSrcWidth, uSrcHeight, uSrcBytesPerLine, 32);
342 RecordingUtilsDbgDumpVideoFrameEx(pFrame, "/tmp", "cursor-dst");
343#endif
344
345 return VINF_SUCCESS;
346}
347
348/**
349 * Simple blitting function for raw image data.
350 *
351 * @returns VBox status code.
352 * @param pDstFrame Destination frame.
353 * @param uDstX X destination (in pixel) within destination frame.
354 * @param uDstY Y destination (in pixel) within destination frame.
355 * @param pu8Src Source data to blit. Must be in the same pixel format as \a pFrame.
356 * @param cbSrc Size (in bytes) of \a pu8Src.
357 * @param uSrcX X start (in pixel) within source data.
358 * @param uSrcY Y start (in pixel) within source data.
359 * @param uSrcWidth Width (in pixel) to blit from source data.
360 * @param uSrcHeight Height (in pixel) to blit from data.
361 * @param uSrcBytesPerLine Bytes per line in source data.
362 * @param uSrcBPP BPP of source data. Must match \a pFrame.
363 * @param enmFmt Pixel format of source data. Must match \a pFrame.
364 */
365int RecordingVideoFrameBlitRaw(PRECORDINGVIDEOFRAME pDstFrame, uint32_t uDstX, uint32_t uDstY,
366 const uint8_t *pu8Src, size_t cbSrc, uint32_t uSrcX, uint32_t uSrcY, uint32_t uSrcWidth, uint32_t uSrcHeight,
367 uint32_t uSrcBytesPerLine, uint8_t uSrcBPP, RECORDINGPIXELFMT enmFmt)
368{
369 return recordingVideoFrameBlitRaw(/* Destination */
370 pDstFrame->pau8Buf, pDstFrame->cbBuf, uDstX, uDstY,
371 pDstFrame->Info.uBytesPerLine, pDstFrame->Info.uBPP, pDstFrame->Info.enmPixelFmt,
372 /* Source */
373 pu8Src, cbSrc, uSrcX, uSrcY, uSrcWidth, uSrcHeight, uSrcBytesPerLine, uSrcBPP, enmFmt);
374}
375
376/**
377 * Simple blitting function for raw image data with alpha channel.
378 *
379 * @returns VBox status code.
380 * @param pFrame Destination frame.
381 * @param uDstX X destination (in pixel) within destination frame.
382 * @param uDstY Y destination (in pixel) within destination frame.
383 * @param pu8Src Source data to blit. Must be in the same pixel format as \a pFrame.
384 * @param cbSrc Size (in bytes) of \a pu8Src.
385 * @param uSrcX X start (in pixel) within source data.
386 * @param uSrcY Y start (in pixel) within source data.
387 * @param uSrcWidth Width (in pixel) to blit from source data.
388 * @param uSrcHeight Height (in pixel) to blit from data.
389 * @param uSrcBytesPerLine Bytes per line in source data.
390 * @param uSrcBPP BPP of source data. Must match \a pFrame.
391 * @param enmFmt Pixel format of source data. Must match \a pFrame.
392 */
393int RecordingVideoFrameBlitRawAlpha(PRECORDINGVIDEOFRAME pFrame, uint32_t uDstX, uint32_t uDstY,
394 const uint8_t *pu8Src, size_t cbSrc, uint32_t uSrcX, uint32_t uSrcY, uint32_t uSrcWidth, uint32_t uSrcHeight,
395 uint32_t uSrcBytesPerLine, uint8_t uSrcBPP, RECORDINGPIXELFMT enmFmt)
396{
397 return recordingVideoFrameBlitRawAlpha(pFrame, uDstX, uDstY,
398 pu8Src, cbSrc, uSrcX, uSrcY, uSrcWidth, uSrcHeight, uSrcBytesPerLine, uSrcBPP, enmFmt);
399}
400
401/**
402 * Simple blitting function for video frames.
403 *
404 * @returns VBox status code.
405 * @param pDstFrame Destination frame.
406 * @param uDstX X destination (in pixel) within destination frame.
407 * @param uDstY Y destination (in pixel) within destination frame.
408 * @param pSrcFrame Source frame.
409 * @param uSrcX X start (in pixel) within source frame.
410 * @param uSrcY Y start (in pixel) within source frame.
411 * @param uSrcWidth Width (in pixel) to blit from source frame.
412 * @param uSrcHeight Height (in pixel) to blit from frame.
413 *
414 * @note Does NOT check for limits, so use with care!
415 */
416int RecordingVideoFrameBlitFrame(PRECORDINGVIDEOFRAME pDstFrame, uint32_t uDstX, uint32_t uDstY,
417 PRECORDINGVIDEOFRAME pSrcFrame, uint32_t uSrcX, uint32_t uSrcY, uint32_t uSrcWidth, uint32_t uSrcHeight)
418{
419 return recordingVideoFrameBlitRaw(/* Dest */
420 pDstFrame->pau8Buf, pDstFrame->cbBuf, uDstX, uDstY,
421 pDstFrame->Info.uBytesPerLine, pDstFrame->Info.uBPP, pDstFrame->Info.enmPixelFmt,
422 /* Source */
423 pSrcFrame->pau8Buf, pSrcFrame->cbBuf, uSrcX, uSrcY, uSrcWidth, uSrcHeight,
424 pSrcFrame->Info.uBytesPerLine, pSrcFrame->Info.uBPP, pSrcFrame->Info.enmPixelFmt);
425}
426
427#ifdef VBOX_WITH_AUDIO_RECORDING
428/**
429 * Destroys a recording audio frame.
430 *
431 * @param pFrame Pointer to audio frame to destroy.
432 */
433DECLINLINE(void) recordingAudioFrameDestroy(PRECORDINGAUDIOFRAME pFrame)
434{
435 if (!pFrame)
436 return;
437
438 if (pFrame->pvBuf)
439 {
440 Assert(pFrame->cbBuf);
441 RTMemFree(pFrame->pvBuf);
442 pFrame->pvBuf = NULL;
443 pFrame->cbBuf = 0;
444 }
445}
446
447/**
448 * Frees a previously allocated recording audio frame.
449 *
450 * @param pFrame Audio frame to free. The pointer will be invalid after return.
451 */
452void RecordingAudioFrameFree(PRECORDINGAUDIOFRAME pFrame)
453{
454 if (!pFrame)
455 return;
456
457 recordingAudioFrameDestroy(pFrame);
458
459 RTMemFree(pFrame);
460 pFrame = NULL;
461}
462#endif /* VBOX_WITH_AUDIO_RECORDING */
463
464/**
465 * Frees a recording frame.
466 *
467 * @param pFrame Pointer to recording frame to free.
468 * The pointer will be invalid after return.
469 */
470void RecordingFrameFree(PRECORDINGFRAME pFrame)
471{
472 if (!pFrame)
473 return;
474
475 switch (pFrame->enmType)
476 {
477#ifdef VBOX_WITH_AUDIO_RECORDING
478 case RECORDINGFRAME_TYPE_AUDIO:
479 recordingAudioFrameDestroy(&pFrame->u.Audio);
480 break;
481#endif
482 case RECORDINGFRAME_TYPE_VIDEO:
483 RecordingVideoFrameDestroy(&pFrame->u.Video);
484 break;
485
486 case RECORDINGFRAME_TYPE_CURSOR_SHAPE:
487 RecordingVideoFrameDestroy(&pFrame->u.CursorShape);
488 break;
489
490 default:
491 /* Nothing to do here. */
492 break;
493 }
494
495 RTMemFree(pFrame);
496 pFrame = NULL;
497}
498
注意: 瀏覽 TracBrowser 來幫助您使用儲存庫瀏覽器

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