VirtualBox

source: vbox/trunk/src/VBox/Frontends/VBoxHeadless/VideoCapture/FFmpegFB.cpp@ 7207

Last change on this file since 7207 was 7207, checked in by vboxsync, 17 years ago

Main: Reworked enums to avoid 1) weird duplication of enum name when referring to enum values in cross-platform code; 2) possible clashes on Win32 due to putting identifiers like Paused or Disabled to the global namespace (via C enums). In the new style, enums are used like this: a) USBDeviceState_T v = USBDeviceState_Busy from cross-platform non-Qt code; b) KUSBDeviceState v = KUSBDeviceState_Busy from Qt code; c) USBDeviceState v = USBDeviceState_Busy from plain Win32 and d) PRUInt32 USBDeviceState v = USBDeviceState::Busy from plain XPCOM.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 34.1 KB
Line 
1/** @file
2 *
3 * Framebuffer implementation that interfaces with FFmpeg
4 * to create a video of the guest.
5 */
6
7/*
8 * Copyright (C) 2006-2007 innotek GmbH
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.virtualbox.org. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 */
18
19#define LOG_GROUP LOG_GROUP_GUI
20
21#include "FFmpegFB.h"
22
23#include <iprt/file.h>
24#include <iprt/param.h>
25#include <iprt/assert.h>
26#include <VBox/log.h>
27#include <png.h>
28
29// external constructor for dynamic loading
30/////////////////////////////////////////////////////////////////////////////
31
32/**
33 * Callback function to register an ffmpeg framebuffer.
34 *
35 * @returns COM status code.
36 * @param width Framebuffer width.
37 * @param height Framebuffer height.
38 * @param bitrate Bitrate of mpeg file to be created.
39 * @param filename Name of mpeg file to be created
40 * @retval retVal The new framebuffer
41 */
42extern "C" DECLEXPORT(HRESULT) VBoxRegisterFFmpegFB(ULONG width,
43 ULONG height, ULONG bitrate,
44 com::Bstr filename,
45 IFramebuffer **retVal)
46{
47 Log2(("VBoxRegisterFFmpegFB: called\n"));
48 FFmpegFB *pFramebuffer = new FFmpegFB(width, height, bitrate, filename);
49 int rc = pFramebuffer->init();
50 AssertMsg(rc == S_OK,
51 ("failed to initialise the FFmpeg framebuffer, rc = %d\n",
52 rc));
53 if (rc == S_OK)
54 {
55 *retVal = pFramebuffer;
56 return S_OK;
57 }
58 delete pFramebuffer;
59 return rc;
60}
61
62
63
64
65
66// constructor / destructor
67/////////////////////////////////////////////////////////////////////////////
68
69/**
70 * Perform parts of initialisation which are guaranteed not to fail
71 * unless we run out of memory. In this case, we just set the guest
72 * buffer to 0 so that RequestResize() does not free it the first time
73 * it is called.
74 */
75FFmpegFB::FFmpegFB(ULONG width, ULONG height, ULONG bitrate,
76 com::Bstr filename) :
77 mBitRate(bitrate),
78 mPixelFormat(FramebufferPixelFormat_Opaque),
79 mBitsPerPixel(0),
80 mFileName(filename),
81 mBytesPerLine(0),
82 mFrameWidth(width), mFrameHeight(height),
83 mYUVFrameSize(width * height * 3 / 2),
84 mRGBBuffer(0), mpFormatContext(0), mpStream(0),
85 mOutOfMemory(false), mToggle(false)
86{
87 ULONG cPixels = width * height;
88
89 LogFlow(("Creating FFmpegFB object %p, width=%lu, height=%lu\n",
90 this, width, height));
91 Assert(width % 2 == 0 && height % 2 == 0);
92 /* For temporary RGB frame we allocate enough memory to deal with
93 RGB16 to RGB32 */
94 mTempRGBBuffer = reinterpret_cast<uint8_t *>(av_malloc(cPixels * 4));
95 if (mTempRGBBuffer == 0)
96 goto nomem_temp_rgb_buffer;
97 mYUVBuffer = reinterpret_cast<uint8_t *>(av_malloc(mYUVFrameSize));
98 if (mYUVBuffer == 0)
99 goto nomem_yuv_buffer;
100 mFrame = avcodec_alloc_frame();
101 if (mFrame == 0)
102 goto nomem_mframe;
103 mOutBuf = reinterpret_cast<uint8_t *>(av_malloc(mYUVFrameSize * 2));
104 if (mOutBuf == 0)
105 goto nomem_moutbuf;
106
107 return;
108
109 /* C-based memory allocation and how to deal with it in C++ :) */
110nomem_moutbuf:
111 Log(("Failed to allocate memory for mOutBuf\n"));
112 av_free(mFrame);
113nomem_mframe:
114 Log(("Failed to allocate memory for mFrame\n"));
115 av_free(mYUVBuffer);
116nomem_yuv_buffer:
117 Log(("Failed to allocate memory for mYUVBuffer\n"));
118 av_free(mTempRGBBuffer);
119nomem_temp_rgb_buffer:
120 Log(("Failed to allocate memory for mTempRGBBuffer\n"));
121 mOutOfMemory = true;
122}
123
124
125/**
126 * Write the last frame to disk and free allocated memory
127 */
128FFmpegFB::~FFmpegFB()
129{
130 LogFlow(("Destroying FFmpegFB object %p\n", this));
131 if (mpFormatContext != 0)
132 {
133 /* Dummy update to make sure we get all the frame (timing). */
134 BOOL dummy;
135 NotifyUpdate(0, 0, 0, 0, &dummy);
136 /* Write the last pending frame before exiting */
137 int rc = do_rgb_to_yuv_conversion();
138 if (rc == S_OK)
139 do_encoding_and_write();
140#if 1
141 /* Add another 10 seconds. */
142 for (int i = 10*25; i > 0; i--)
143 do_encoding_and_write();
144#endif
145 /* write a png file of the last frame */
146 write_png();
147 avcodec_close(mpStream->codec);
148 av_write_trailer(mpFormatContext);
149 /* free the streams */
150 for(int i = 0; i < mpFormatContext->nb_streams; i++) {
151 av_freep(&mpFormatContext->streams[i]->codec);
152 av_freep(&mpFormatContext->streams[i]);
153 }
154 url_fclose(&mpFormatContext->pb);
155 av_free(mpFormatContext);
156 }
157 RTCritSectDelete(&mCritSect);
158 /* We have already freed the stream above */
159 mpStream = 0;
160 if (mTempRGBBuffer != 0)
161 av_free(mTempRGBBuffer);
162 if (mYUVBuffer != 0)
163 av_free(mYUVBuffer);
164 if (mFrame != 0)
165 av_free(mFrame);
166 if (mOutBuf != 0)
167 av_free(mOutBuf);
168 if (mRGBBuffer != 0)
169 RTMemFree(mRGBBuffer);
170}
171
172// public methods only for internal purposes
173/////////////////////////////////////////////////////////////////////////////
174
175/**
176 * Perform any parts of the initialisation which could potentially fail
177 * for reasons other than "out of memory".
178 *
179 * @returns COM status code
180 * @param width width to be used for MPEG frame framebuffer and initially
181 * for the guest frame buffer - must be a multiple of two
182 * @param height height to be used for MPEG frame framebuffer and
183 * initially for the guest framebuffer - must be a multiple
184 * of two
185 * @param depth depth to be used initially for the guest framebuffer
186 */
187HRESULT FFmpegFB::init()
188{
189 LogFlow(("Initialising FFmpegFB object %p\n", this));
190 if (mOutOfMemory == true)
191 return E_OUTOFMEMORY;
192 int rc = RTCritSectInit(&mCritSect);
193 AssertReturn(rc == VINF_SUCCESS, E_UNEXPECTED);
194 int rcSetupLibrary = setup_library();
195 AssertReturn(rcSetupLibrary == S_OK, rcSetupLibrary);
196 int rcSetupFormat = setup_output_format();
197 AssertReturn(rcSetupFormat == S_OK, rcSetupFormat);
198 int rcOpenCodec = open_codec();
199 AssertReturn(rcOpenCodec == S_OK, rcOpenCodec);
200 int rcOpenFile = open_output_file();
201 AssertReturn(rcOpenFile == S_OK, rcOpenFile);
202 /* Fill in the picture data for the AVFrame - not particularly
203 elegant, but that is the API. */
204 avpicture_fill((AVPicture *) mFrame, mYUVBuffer, PIX_FMT_YUV420P,
205 mFrameWidth, mFrameHeight);
206 /* Fill in the AVPicture structure describing the MPEG frame
207 framebuffer - in fact another representation of the same data */
208 avpicture_fill(&mFramePicture, mYUVBuffer, PIX_FMT_YUV420P,
209 mFrameWidth, mFrameHeight);
210 /* Set the initial framebuffer size to the mpeg frame dimensions */
211 BOOL finished;
212 RequestResize(0, FramebufferPixelFormat_Opaque, NULL, 0, 0,
213 mFrameWidth, mFrameHeight, &finished);
214 /* Start counting time */
215 mLastTime = RTTimeMilliTS();
216 mLastTime = mLastTime - mLastTime % 40;
217 return rc;
218}
219
220// IFramebuffer properties
221/////////////////////////////////////////////////////////////////////////////
222
223/**
224 * Return the address of the frame buffer for the virtual VGA device to
225 * write to. If COMGETTER(UsesGuestVRAM) returns FLASE (or if this address
226 * is not the same as the guests VRAM buffer), the device will perform
227 * translation.
228 *
229 * @returns COM status code
230 * @retval address The address of the buffer
231 */
232STDMETHODIMP FFmpegFB::COMGETTER(Address) (BYTE **address)
233{
234 if (!address)
235 return E_POINTER;
236 LogFlow(("FFmpeg::COMGETTER(Address): returning address %p\n", mBufferAddress));
237 *address = mBufferAddress;
238 return S_OK;
239}
240
241/**
242 * Return the width of our frame buffer.
243 *
244 * @returns COM status code
245 * @retval width The width of the frame buffer
246 */
247STDMETHODIMP FFmpegFB::COMGETTER(Width) (ULONG *width)
248{
249 if (!width)
250 return E_POINTER;
251 LogFlow(("FFmpeg::COMGETTER(Width): returning width %lu\n", mGuestWidth));
252 *width = mGuestWidth;
253 return S_OK;
254}
255
256/**
257 * Return the height of our frame buffer.
258 *
259 * @returns COM status code
260 * @retval height The height of the frame buffer
261 */
262STDMETHODIMP FFmpegFB::COMGETTER(Height) (ULONG *height)
263{
264 if (!height)
265 return E_POINTER;
266 LogFlow(("FFmpeg::COMGETTER(Height): returning height %lu\n", mGuestHeight));
267 *height = mGuestHeight;
268 return S_OK;
269}
270
271/**
272 * Return the colour depth of our frame buffer. Note that we actually
273 * store the pixel format, not the colour depth internally, since
274 * when display sets FramebufferPixelFormat_Opaque, it
275 * wants to retreive FramebufferPixelFormat_Opaque and
276 * nothing else.
277 *
278 * @returns COM status code
279 * @retval bitsPerPixel The colour depth of the frame buffer
280 */
281STDMETHODIMP FFmpegFB::COMGETTER(BitsPerPixel) (ULONG *bitsPerPixel)
282{
283 if (!bitsPerPixel)
284 return E_POINTER;
285 *bitsPerPixel = mBitsPerPixel;
286 LogFlow(("FFmpeg::COMGETTER(BitsPerPixel): returning depth %lu\n",
287 *bitsPerPixel));
288 return S_OK;
289}
290
291/**
292 * Return the number of bytes per line in our frame buffer.
293 *
294 * @returns COM status code
295 * @retval bytesPerLine The number of bytes per line
296 */
297STDMETHODIMP FFmpegFB::COMGETTER(BytesPerLine) (ULONG *bytesPerLine)
298{
299 if (!bytesPerLine)
300 return E_POINTER;
301 LogFlow(("FFmpeg::COMGETTER(BytesPerLine): returning line size %lu\n", mBytesPerLine));
302 *bytesPerLine = mBytesPerLine;
303 return S_OK;
304}
305
306/**
307 * Return the pixel layout of our frame buffer.
308 *
309 * @returns COM status code
310 * @retval pixelFormat The pixel layout
311 */
312STDMETHODIMP FFmpegFB::COMGETTER(PixelFormat) (ULONG *pixelFormat)
313{
314 if (!pixelFormat)
315 return E_POINTER;
316 LogFlow(("FFmpeg::COMGETTER(PixelFormat): returning pixel format: %d\n",
317 mPixelFormat));
318 *pixelFormat = mPixelFormat;
319 return S_OK;
320}
321
322/**
323 * Return whether we use the guest VRAM directly.
324 *
325 * @returns COM status code
326 * @retval pixelFormat The pixel layout
327 */
328STDMETHODIMP FFmpegFB::COMGETTER(UsesGuestVRAM) (BOOL *usesGuestVRAM)
329{
330 if (!usesGuestVRAM)
331 return E_POINTER;
332 LogFlow(("FFmpeg::COMGETTER(UsesGuestVRAM): uses guest VRAM? %d\n",
333 FALSE));
334 *usesGuestVRAM = mRGBBuffer == NULL;
335 return S_OK;
336}
337
338/**
339 * Return the number of lines of our frame buffer which can not be used
340 * (e.g. for status lines etc?).
341 *
342 * @returns COM status code
343 * @retval heightReduction The number of unused lines
344 */
345STDMETHODIMP FFmpegFB::COMGETTER(HeightReduction) (ULONG *heightReduction)
346{
347 if (!heightReduction)
348 return E_POINTER;
349 /* no reduction */
350 *heightReduction = 0;
351 LogFlow(("FFmpeg::COMGETTER(HeightReduction): returning 0\n"));
352 return S_OK;
353}
354
355/**
356 * Return a pointer to the alpha-blended overlay used to render status icons
357 * etc above the framebuffer.
358 *
359 * @returns COM status code
360 * @retval aOverlay The overlay framebuffer
361 */
362STDMETHODIMP FFmpegFB::COMGETTER(Overlay) (IFramebufferOverlay **aOverlay)
363{
364 if (!aOverlay)
365 return E_POINTER;
366 /* not yet implemented */
367 *aOverlay = 0;
368 LogFlow(("FFmpeg::COMGETTER(Overlay): returning 0\n"));
369 return S_OK;
370}
371
372// IFramebuffer methods
373/////////////////////////////////////////////////////////////////////////////
374
375STDMETHODIMP FFmpegFB::Lock()
376{
377 LogFlow(("FFmpeg::Lock: called\n"));
378 int rc = RTCritSectEnter(&mCritSect);
379 AssertRC(rc);
380 if (rc == VINF_SUCCESS)
381 return S_OK;
382 return E_UNEXPECTED;
383}
384
385STDMETHODIMP FFmpegFB::Unlock()
386{
387 LogFlow(("FFmpeg::Unlock: called\n"));
388 RTCritSectLeave(&mCritSect);
389 return S_OK;
390}
391
392
393/**
394 * This method is used to notify us that an area of the guest framebuffer
395 * has been updated.
396 *
397 * @returns COM status code
398 * @param x X co-ordinate of the upper left-hand corner of the
399 * area which has been updated
400 * @param y Y co-ordinate of the upper left-hand corner of the
401 * area which has been updated
402 * @param w width of the area which has been updated
403 * @param h height of the area which has been updated
404 * @param finished
405 */
406STDMETHODIMP FFmpegFB::NotifyUpdate(ULONG x, ULONG y, ULONG w, ULONG h,
407 BOOL *finished)
408{
409 int rc;
410 int64_t iCurrentTime = RTTimeMilliTS();
411
412 LogFlow(("FFmpeg::NotifyUpdate called: x=%lu, y=%lu, w=%lu, h=%lu\n",
413 x, y, w, h));
414 if (!finished)
415 return E_POINTER;
416 /* For now we will do things synchronously */
417 *finished = true;
418 /* We always leave at least one frame update pending, which we
419 process when the time until the next frame has elapsed. */
420 if (iCurrentTime - mLastTime >= 40)
421 {
422 rc = do_rgb_to_yuv_conversion();
423 if (rc != S_OK)
424 {
425 copy_to_intermediate_buffer(x, y, w, h);
426 return rc;
427 }
428 rc = do_encoding_and_write();
429 if (rc != S_OK)
430 {
431 copy_to_intermediate_buffer(x, y, w, h);
432 return rc;
433 }
434 mLastTime = mLastTime + 40;
435 /* Write frames for the time in-between. Not a good way
436 to handle this. */
437 while (iCurrentTime - mLastTime >= 40)
438 {
439/* rc = do_rgb_to_yuv_conversion();
440 if (rc != S_OK)
441 {
442 copy_to_intermediate_buffer(x, y, w, h);
443 return rc;
444 }
445*/ rc = do_encoding_and_write();
446 if (rc != S_OK)
447 {
448 copy_to_intermediate_buffer(x, y, w, h);
449 return rc;
450 }
451 mLastTime = mLastTime + 40;
452 }
453 }
454 /* Finally we copy the updated data to the intermediate buffer,
455 ready for the next update. */
456 copy_to_intermediate_buffer(x, y, w, h);
457 return S_OK;
458}
459
460
461/**
462 * Requests a resize of our "screen".
463 *
464 * @returns COM status code
465 * @param pixelFormat Layout of the guest video RAM (i.e. 16, 24,
466 * 32 bpp)
467 * @param vram host context pointer to the guest video RAM,
468 * in case we can cope with the format
469 * @param bitsPerPixel color depth of the guest video RAM
470 * @param bytesPerLine length of a screen line in the guest video RAM
471 * @param w video mode width in pixels
472 * @param h video mode height in pixels
473 * @retval finished set to true if the method is synchronous and
474 * to false otherwise
475 *
476 * This method is called when the guest attempts to resize the virtual
477 * screen. The pointer to the guest's video RAM is supplied in case
478 * the framebuffer can handle the pixel format. If it can't, it should
479 * allocate a memory buffer itself, and the virtual VGA device will copy
480 * the guest VRAM to that in a format we can handle. The
481 * COMGETTER(UsesGuestVRAM) method is used to tell the VGA device which method
482 * we have chosen, and the other COMGETTER methods tell the device about
483 * the layout of our buffer. We currently handle all VRAM layouts except
484 * FramebufferPixelFormat_Opaque (which cannot be handled by
485 * definition).
486 */
487STDMETHODIMP FFmpegFB::RequestResize(ULONG aScreenId, ULONG pixelFormat,
488 BYTE *vram, ULONG bitsPerPixel,
489 ULONG bytesPerLine,
490 ULONG w, ULONG h, BOOL *finished)
491{
492 NOREF(aScreenId);
493 if (!finished)
494 return E_POINTER;
495 LogFlow(("FFmpeg::RequestResize called: pixelFormat=%d, vram=%lu, "
496 "bpp=%lu bpl=%lu, w=%lu, h=%lu\n",
497 pixelFormat, vram, bitsPerPixel, bytesPerLine, w, h));
498 /* For now, we are doing things synchronously */
499 *finished = true;
500
501 /* We always reallocate our buffer */
502 if (mRGBBuffer)
503 RTMemFree(mRGBBuffer);
504 mGuestWidth = w;
505 mGuestHeight = h;
506
507 bool fallback = false;
508
509 /* See if there are conditions under which we can use the guest's VRAM,
510 * fallback to our own memory buffer otherwise */
511
512 if (pixelFormat == FramebufferPixelFormat_FOURCC_RGB)
513 {
514 switch (bitsPerPixel)
515 {
516 case 32:
517 mFFMPEGPixelFormat = PIX_FMT_RGBA32;
518 Log2(("FFmpeg::RequestResize: setting ffmpeg pixel format to PIX_FMT_RGBA32\n"));
519 break;
520 case 24:
521 mFFMPEGPixelFormat = PIX_FMT_RGB24;
522 Log2(("FFmpeg::RequestResize: setting ffmpeg pixel format to PIX_FMT_RGB24\n"));
523 break;
524 case 16:
525 mFFMPEGPixelFormat = PIX_FMT_RGB565;
526 Log2(("FFmpeg::RequestResize: setting ffmpeg pixel format to PIX_FMT_RGB565\n"));
527 break;
528 default:
529 fallback = true;
530 break;
531 }
532 }
533 else
534 {
535 fallback = true;
536 }
537
538 if (!fallback)
539 {
540 mPixelFormat = FramebufferPixelFormat_FOURCC_RGB;
541 mBufferAddress = reinterpret_cast<uint8_t *>(vram);
542 mBytesPerLine = bytesPerLine;
543 mBitsPerPixel = bitsPerPixel;
544 mRGBBuffer = 0;
545 Log2(("FFmpeg::RequestResize: setting mBufferAddress to vram and mLineSize to %lu\n", mBytesPerLine));
546 }
547 else
548 {
549 /* we always fallback to 32bpp RGB */
550 mPixelFormat = FramebufferPixelFormat_FOURCC_RGB;
551 mFFMPEGPixelFormat = PIX_FMT_RGBA32;
552 Log2(("FFmpeg::RequestResize: setting ffmpeg pixel format to PIX_FMT_RGBA32\n"));
553
554 mBytesPerLine = w * 4;
555 mBitsPerPixel = 32;
556 mRGBBuffer = reinterpret_cast<uint8_t *>(RTMemAlloc(mBytesPerLine * h));
557 AssertReturn(mRGBBuffer != 0, E_OUTOFMEMORY);
558 Log2(("FFmpeg::RequestResize: alloc'ing mBufferAddress and mRGBBuffer to %p and mBytesPerLine to %lu\n", mBufferAddress,
559 mBytesPerLine));
560 mBufferAddress = mRGBBuffer;
561 }
562
563 /* Fill in the structure describing the (intermediate) guest
564 framebuffer so that ffmpeg can copy correctly between the two */
565 avpicture_fill(&mGuestPicture, mTempRGBBuffer, mFFMPEGPixelFormat,
566 mFrameWidth, mFrameHeight);
567 /* Blank out the intermediate frame framebuffer */
568 memset(mTempRGBBuffer, 0, mFrameWidth * mFrameHeight * 4);
569 return S_OK;
570}
571
572
573/**
574 * Queries whether we support a given accelerated opperation. Since we
575 * do not have any way of performing accelerated operations, we always
576 * return false in supported.
577 *
578 * @returns COM status code
579 * @param operation The operation being queried
580 * @retval supported Whether or not we support that operation
581 */
582STDMETHODIMP FFmpegFB::OperationSupported(FramebufferAccelerationOperation_T operation,
583 BOOL *supported)
584{
585 if (!supported)
586 return E_POINTER;
587 *supported = false;
588 return S_OK;
589}
590
591/**
592 * Returns whether we like the given video mode.
593 *
594 * @returns COM status code
595 * @param width video mode width in pixels
596 * @param height video mode height in pixels
597 * @param bpp video mode bit depth in bits per pixel
598 * @param supported pointer to result variable
599 *
600 * As far as I know, the only restruction we have on video modes is that
601 * we have to have an even number of horizontal and vertical pixels.
602 * I sincerely doubt that anything else will be requested, and if it
603 * is anyway, we will just silently amputate one line when we write to
604 * the mpeg file.
605 */
606STDMETHODIMP FFmpegFB::VideoModeSupported(ULONG width, ULONG height,
607 ULONG bpp, BOOL *supported)
608{
609 if (!supported)
610 return E_POINTER;
611 *supported = true;
612 return S_OK;
613}
614
615/**
616 * Since we currently do not have any way of doing this faster than
617 * the VGA device, we simply false in handled. Behaviour taken from
618 * src/VBox/RDP/server/framebuffer.cpp.
619 */
620STDMETHODIMP FFmpegFB::SolidFill(ULONG x, ULONG y, ULONG width,
621 ULONG height, ULONG color, BOOL *handled)
622{
623 LogFlow(("FFmpeg::SolidFill called.\n"));
624 if (!handled)
625 return E_POINTER;
626 *handled = false;
627 return S_OK;
628}
629
630/**
631 * Since we currently do not have any way of doing this faster than
632 * the VGA device, we simply false in handled. Behaviour taken from
633 * src/VBox/RDP/server/framebuffer.cpp.
634 */
635STDMETHODIMP FFmpegFB::CopyScreenBits(ULONG xDst, ULONG yDst, ULONG xSrc,
636 ULONG ySrc, ULONG width,
637 ULONG height, BOOL *handled)
638{
639 LogFlow(("FFmpeg::CopyScreenBits called.\n"));
640 if (!handled)
641 return E_POINTER;
642 *handled = false;
643 return S_OK;
644}
645
646/** Stubbed */
647STDMETHODIMP FFmpegFB::GetVisibleRegion(BYTE *rectangles, ULONG /* count */, ULONG * /* countCopied */)
648{
649 if (!rectangles)
650 return E_POINTER;
651 *rectangles = 0;
652 return S_OK;
653}
654
655/** Stubbed */
656STDMETHODIMP FFmpegFB::SetVisibleRegion(BYTE *rectangles, ULONG /* count */)
657{
658 if (!rectangles)
659 return E_POINTER;
660 return S_OK;
661}
662
663
664// Private Methods
665//////////////////////////////////////////////////////////////////////////
666//
667
668HRESULT FFmpegFB::setup_library()
669{
670 /* Set up the avcodec library */
671 avcodec_init();
672 /* Register all codecs in the library. */
673 avcodec_register_all();
674 /* Register all formats in the format library */
675 av_register_all();
676 mpFormatContext = av_alloc_format_context();
677 AssertReturn(mpFormatContext != 0, E_OUTOFMEMORY);
678 mpStream = av_new_stream(mpFormatContext, 0);
679 AssertReturn(mpStream != 0, E_UNEXPECTED);
680 strncpy(mpFormatContext->filename, com::Utf8Str(mFileName),
681 sizeof(mpFormatContext->filename));
682 return S_OK;
683}
684
685
686/**
687 * Determine the correct output format and codec for our MPEG file.
688 *
689 * @returns COM status code
690 *
691 * @pre The format context (mpFormatContext) should have already been
692 * allocated.
693 */
694HRESULT FFmpegFB::setup_output_format()
695{
696 Assert(mpFormatContext != 0);
697 AVOutputFormat *pOutFormat = guess_format(0, com::Utf8Str(mFileName),
698 0);
699 AssertMsgReturn(pOutFormat != 0,
700 ("Could not deduce output format from file name\n"),
701 E_INVALIDARG);
702 AssertMsgReturn((pOutFormat->flags & AVFMT_RAWPICTURE) == 0,
703 ("Can't handle output format for file\n"),
704 E_INVALIDARG);
705 AssertMsgReturn((pOutFormat->flags & AVFMT_NOFILE) == 0,
706 ("pOutFormat->flags=%x, pOutFormat->name=%s\n",
707 pOutFormat->flags, pOutFormat->name), E_UNEXPECTED);
708 AssertMsgReturn(pOutFormat->video_codec != CODEC_ID_NONE,
709 ("No video codec available - you have probably selected a non-video file format\n"), E_UNEXPECTED);
710 mpFormatContext->oformat = pOutFormat;
711 /* Set format specific parameters - requires the format to be set. */
712 int rcSetParam = av_set_parameters(mpFormatContext, 0);
713 AssertReturn(rcSetParam >= 0, E_UNEXPECTED);
714#if 1 /* bird: This works for me on the mac, please review & test elsewhere. */
715 /* Fill in any uninitialized parameters like opt_output_file in ffpmeg.c does.
716 This fixes most of the buffer underflow warnings:
717 http://lists.mplayerhq.hu/pipermail/ffmpeg-devel/2005-June/001699.html */
718 if (!mpFormatContext->preload)
719 mpFormatContext->preload = (int)(0.5 * AV_TIME_BASE);
720 if (!mpFormatContext->max_delay)
721 mpFormatContext->max_delay = (int)(0.7 * AV_TIME_BASE);
722#endif
723 return S_OK;
724}
725
726
727/**
728 * Open the FFmpeg codec and set it up (width, etc) for our MPEG file.
729 *
730 * @returns COM status code
731 *
732 * @pre The format context (mpFormatContext) and the stream (mpStream)
733 * should have already been allocated.
734 */
735HRESULT FFmpegFB::open_codec()
736{
737 Assert(mpFormatContext != 0);
738 Assert(mpStream != 0);
739 AVOutputFormat *pOutFormat = mpFormatContext->oformat;
740 AVCodecContext *pCodecContext = mpStream->codec;
741 AVCodec *pCodec = avcodec_find_encoder(pOutFormat->video_codec);
742 AssertReturn(pCodec != 0, E_UNEXPECTED);
743 pCodecContext->codec_id = pOutFormat->video_codec;
744 pCodecContext->codec_type = CODEC_TYPE_VIDEO;
745 pCodecContext->bit_rate = mBitRate;
746 pCodecContext->width = mFrameWidth;
747 pCodecContext->height = mFrameHeight;
748 pCodecContext->time_base.den = 25;
749 pCodecContext->time_base.num = 1;
750 pCodecContext->gop_size = 12; /* at most one intra frame in 12 */
751 pCodecContext->max_b_frames = 1;
752 pCodecContext->pix_fmt = PIX_FMT_YUV420P;
753 /* taken from the ffmpeg output example */
754 // some formats want stream headers to be seperate
755 if (!strcmp(pOutFormat->name, "mp4")
756 || !strcmp(pOutFormat->name, "mov")
757 || !strcmp(pOutFormat->name, "3gp"))
758 pCodecContext->flags |= CODEC_FLAG_GLOBAL_HEADER;
759 /* end output example section */
760 int rcOpenCodec = avcodec_open(pCodecContext, pCodec);
761 AssertReturn(rcOpenCodec >= 0, E_UNEXPECTED);
762 return S_OK;
763}
764
765
766/**
767 * Open our MPEG file and write the header.
768 *
769 * @returns COM status code
770 *
771 * @pre The format context (mpFormatContext) and the stream (mpStream)
772 * should have already been allocated and set up.
773 */
774HRESULT FFmpegFB::open_output_file()
775{
776 char szFileName[RTPATH_MAX];
777 Assert(mpFormatContext);
778 Assert(mpFormatContext->oformat);
779 strcpy(szFileName, com::Utf8Str(mFileName));
780 int rcUrlFopen = url_fopen(&mpFormatContext->pb,
781 szFileName, URL_WRONLY);
782 AssertReturn(rcUrlFopen >= 0, E_UNEXPECTED);
783 av_write_header(mpFormatContext);
784 return S_OK;
785}
786
787
788/**
789 * Copy an area from the output buffer used by the virtual VGA (may
790 * just be the guest's VRAM) to our fixed size intermediate buffer.
791 * The picture in the intermediate buffer is centred if the guest
792 * screen dimensions are smaller and amputated if they are larger than
793 * our frame dimensions.
794 *
795 * @param x X co-ordinate of the upper left-hand corner of the
796 * area which has been updated
797 * @param y Y co-ordinate of the upper left-hand corner of the
798 * area which has been updated
799 * @param w width of the area which has been updated
800 * @param h height of the area which has been updated
801 */
802void FFmpegFB::copy_to_intermediate_buffer(ULONG x, ULONG y, ULONG w, ULONG h)
803{
804 Log2(("FFmpegFB::copy_to_intermediate_buffer: x=%lu, y=%lu, w=%lu, h=%lu\n",
805 x, y, w, h));
806 /* Perform clipping and calculate the destination co-ordinates */
807 ULONG destX, destY, bpp;
808 LONG xDiff = (LONG(mFrameWidth) - LONG(mGuestWidth)) / 2;
809 LONG yDiff = (LONG(mFrameHeight) - LONG(mGuestHeight)) / 2;
810 if (LONG(w) + xDiff + LONG(x) <= 0) /* nothing visible */
811 return;
812 if (LONG(x) < -xDiff)
813 {
814 w = LONG(w) + xDiff + x;
815 x = -xDiff;
816 destX = 0;
817 }
818 else
819 destX = x + xDiff;
820 if (LONG(h) + yDiff + LONG(y) <= 0) /* nothing visible */
821 return;
822 if (LONG(y) < -yDiff)
823 {
824 h = LONG(h) + yDiff + LONG(y);
825 y = -yDiff;
826 destY = 0;
827 }
828 else
829 destY = y + yDiff;
830 if (destX > mFrameWidth || destY > mFrameHeight)
831 return; /* nothing visible */
832 if (destX + w > mFrameWidth)
833 w = mFrameWidth - destX;
834 if (destY + h > mFrameHeight)
835 h = mFrameHeight - destY;
836 /* Calculate bytes per pixel */
837 if (mPixelFormat == FramebufferPixelFormat_FOURCC_RGB)
838 {
839 switch (mBitsPerPixel)
840 {
841 case 32:
842 case 24:
843 case 16:
844 bpp = mBitsPerPixel / 8;
845 break;
846 default:
847 AssertMsgFailed(("Unknown color depth! mBitsPerPixel=%d\n", mBitsPerPixel));
848 bpp = 1;
849 break;
850 }
851 }
852 else
853 {
854 AssertMsgFailed(("Unknown pixel format! mPixelFormat=%d\n", mPixelFormat));
855 bpp = 1;
856 }
857 /* Calculate start offset in source and destination buffers */
858 ULONG srcOffs = y * mBytesPerLine + x * bpp;
859 ULONG destOffs = (destY * mFrameWidth + destX) * bpp;
860 /* do the copy */
861 for (unsigned int i = 0; i < h; i++)
862 {
863 /* Overflow check */
864 Assert(srcOffs + w * bpp <= mGuestHeight * mBytesPerLine);
865 Assert(destOffs + w * bpp <= mFrameHeight * mFrameWidth * bpp);
866 memcpy(mTempRGBBuffer + destOffs, mBufferAddress + srcOffs,
867 w * bpp);
868 srcOffs = srcOffs + mBytesPerLine;
869 destOffs = destOffs + mFrameWidth * bpp;
870 }
871}
872
873
874/**
875 * Copy the RGB data in the intermediate framebuffer to YUV data in
876 * the YUV framebuffer.
877 *
878 * @returns COM status code
879 */
880HRESULT FFmpegFB::do_rgb_to_yuv_conversion()
881{
882 int rc = img_convert(&mFramePicture, PIX_FMT_YUV420P,
883 &mGuestPicture, mFFMPEGPixelFormat,
884 mFrameWidth, mFrameHeight);
885 AssertMsg(rc >= 0, ("img_convert() failed. rc=%d, mFFMPEGPixelFormat=%d\n"
886 "mFrameWidth=%u, mFrameHeight=%u\n", rc,
887 mFFMPEGPixelFormat, mFrameWidth, mFrameHeight));
888 if (rc < 0)
889 return E_UNEXPECTED;
890 return S_OK;
891}
892
893
894/**
895 * Encode the YUV framebuffer as an MPEG frame and write it to the file.
896 *
897 * @returns COM status code
898 */
899HRESULT FFmpegFB::do_encoding_and_write()
900{
901 AVCodecContext *pContext = mpStream->codec;
902
903 /* A hack: ffmpeg mpeg2 only writes a frame if something has
904 changed. So we flip the low luminance bit of the first
905 pixel every frame. */
906 if (mToggle)
907 mYUVBuffer[0] |= 1;
908 else
909 mYUVBuffer[0] &= 0xfe;
910 mToggle = !mToggle;
911 int cSize = avcodec_encode_video(pContext, mOutBuf, mYUVFrameSize * 2,
912 mFrame);
913 AssertMsgReturn(cSize >= 0,
914 ("avcodec_encode_video() failed with rc=%d.\n"
915 "mFrameWidth=%u, mFrameHeight=%u\n", cSize,
916 mFrameWidth, mFrameHeight), E_UNEXPECTED);
917 if (cSize > 0)
918 {
919 AVPacket Packet;
920 av_init_packet(&Packet);
921 Packet.pts = av_rescale_q(pContext->coded_frame->pts,
922 pContext->time_base,
923 mpStream->time_base);
924 if(pContext->coded_frame->key_frame)
925 Packet.flags |= PKT_FLAG_KEY;
926 Packet.stream_index = mpStream->index;
927 Packet.data = mOutBuf;
928 Packet.size = cSize;
929
930 /* write the compressed frame in the media file */
931 int rcWriteFrame = av_write_frame(mpFormatContext, &Packet);
932 AssertReturn(rcWriteFrame == 0, E_UNEXPECTED);
933 }
934 return S_OK;
935}
936
937
938/**
939 * Capture the current (i.e. the last) frame as a PNG file with the
940 * same basename as the captured video file.
941 */
942HRESULT FFmpegFB::write_png()
943{
944 HRESULT errorCode = E_OUTOFMEMORY;
945 png_bytep *row_pointers;
946 char PNGFileName[RTPATH_MAX], oldName[RTPATH_MAX];
947 png_structp png_ptr;
948 png_infop info_ptr;
949 AVPicture libPNGPicture;
950 uint8_t *PNGBuffer;
951 int rc;
952 /* Work out the new file name - for some reason, we can't use
953 the com::Utf8Str() directly, but have to copy it */
954 strcpy(oldName, com::Utf8Str(mFileName));
955 int baseLen = strrchr(oldName, '.') - oldName;
956 if (baseLen == 0)
957 baseLen = strlen(oldName);
958 if (baseLen >= RTPATH_MAX - 5) /* for whatever reason */
959 baseLen = RTPATH_MAX - 5;
960 memcpy(&PNGFileName[0], oldName, baseLen);
961 PNGFileName[baseLen] = '.';
962 PNGFileName[baseLen + 1] = 'p';
963 PNGFileName[baseLen + 2] = 'n';
964 PNGFileName[baseLen + 3] = 'g';
965 PNGFileName[baseLen + 4] = 0;
966 /* Open output file */
967 FILE *fp = fopen(PNGFileName, "wb");
968 if (fp == 0)
969 {
970 errorCode = E_UNEXPECTED;
971 goto fopen_failed;
972 }
973 /* Create libpng basic structures */
974 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL,
975 0 /* error function */, 0 /* warning function */);
976 if (png_ptr == 0)
977 goto png_create_write_struct_failed;
978 info_ptr = png_create_info_struct(png_ptr);
979 if (info_ptr == 0)
980 {
981 png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
982 goto png_create_info_struct_failed;
983 }
984 /* Convert image to standard RGB24 to simplify life */
985 PNGBuffer = reinterpret_cast<uint8_t *>(av_malloc(mFrameWidth
986 * mFrameHeight * 4));
987 if (PNGBuffer == 0)
988 goto av_malloc_buffer_failed;
989 row_pointers =
990 reinterpret_cast<png_bytep *>(av_malloc(mFrameHeight
991 * sizeof(png_bytep)));
992 if (row_pointers == 0)
993 goto av_malloc_pointers_failed;
994 avpicture_fill(&libPNGPicture, PNGBuffer, PIX_FMT_RGB24,
995 mFrameWidth, mFrameHeight);
996 rc = img_convert(&libPNGPicture, PIX_FMT_RGB24,
997 &mGuestPicture, mFFMPEGPixelFormat,
998 mFrameWidth, mFrameHeight);
999 if (rc < 0)
1000 goto setjmp_exception;
1001 /* libpng exception handling */
1002 if (setjmp(png_jmpbuf(png_ptr)))
1003 goto setjmp_exception;
1004 /* pass libpng the file pointer */
1005 png_init_io(png_ptr, fp);
1006 /* set the image properties */
1007 png_set_IHDR(png_ptr, info_ptr, mFrameWidth, mFrameHeight,
1008 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
1009 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
1010 /* set up the information about the bitmap for libpng */
1011 row_pointers[0] = png_bytep(PNGBuffer);
1012 for (unsigned i = 1; i < mFrameHeight; i++)
1013 row_pointers[i] = row_pointers[i - 1] + mFrameWidth * 3;
1014 png_set_rows(png_ptr, info_ptr, &row_pointers[0]);
1015 /* and write the thing! */
1016 png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, 0);
1017 /* drop through to cleanup */
1018 errorCode = S_OK;
1019setjmp_exception:
1020 av_free(row_pointers);
1021av_malloc_pointers_failed:
1022 av_free(PNGBuffer);
1023av_malloc_buffer_failed:
1024 png_destroy_write_struct(&png_ptr, &info_ptr);
1025png_create_info_struct_failed:
1026png_create_write_struct_failed:
1027 fclose(fp);
1028fopen_failed:
1029 if (errorCode != S_OK)
1030 Log(("FFmpegFB::write_png: Failed to write .png image of final frame\n"));
1031 return errorCode;
1032}
1033
1034
1035#ifdef VBOX_WITH_XPCOM
1036NS_DECL_CLASSINFO(FFmpegFB)
1037NS_IMPL_THREADSAFE_ISUPPORTS1_CI(FFmpegFB, IFramebuffer)
1038#endif
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