VirtualBox

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

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

VBoxHeadless: don't segfault if we couldn't open the output file

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