VirtualBox

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

Last change on this file since 13644 was 13644, checked in by vboxsync, 16 years ago

no, this is better (they changed the type later)

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