VirtualBox

source: vbox/trunk/src/VBox/HostServices/VideoCapture/FFmpegFB.cpp@ 5780

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

VideoCapture: moved from RDP to HostServices and exported to OSE

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