VirtualBox

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

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

The Big Sun Rebranding Header Change

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

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