VirtualBox

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

Last change on this file since 39296 was 33540, checked in by vboxsync, 14 years ago

*: spelling fixes, thanks Timeless!

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 35.0 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 Oracle Corporation
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#define VBOX_SHOW_AVAILABLE_FORMATS
31
32// external constructor for dynamic loading
33/////////////////////////////////////////////////////////////////////////////
34
35/**
36 * Callback function to register an ffmpeg framebuffer.
37 *
38 * @returns COM status code.
39 * @param width Framebuffer width.
40 * @param height Framebuffer height.
41 * @param bitrate Bitrate of mpeg file to be created.
42 * @param filename Name of mpeg file to be created
43 * @retval retVal The new framebuffer
44 */
45extern "C" DECLEXPORT(HRESULT) VBoxRegisterFFmpegFB(ULONG width,
46 ULONG height, ULONG bitrate,
47 com::Bstr filename,
48 IFramebuffer **retVal)
49{
50 Log2(("VBoxRegisterFFmpegFB: called\n"));
51 FFmpegFB *pFramebuffer = new FFmpegFB(width, height, bitrate, filename);
52 int rc = pFramebuffer->init();
53 AssertMsg(rc == S_OK,
54 ("failed to initialise the FFmpeg framebuffer, rc = %d\n",
55 rc));
56 if (rc == S_OK)
57 {
58 *retVal = pFramebuffer;
59 return S_OK;
60 }
61 delete pFramebuffer;
62 return rc;
63}
64
65
66
67
68
69// constructor / destructor
70/////////////////////////////////////////////////////////////////////////////
71
72/**
73 * Perform parts of initialisation which are guaranteed not to fail
74 * unless we run out of memory. In this case, we just set the guest
75 * buffer to 0 so that RequestResize() does not free it the first time
76 * it is called.
77 */
78FFmpegFB::FFmpegFB(ULONG width, ULONG height, ULONG bitrate,
79 com::Bstr filename) :
80 mfUrlOpen(false),
81 mBitRate(bitrate),
82 mPixelFormat(FramebufferPixelFormat_Opaque),
83 mBitsPerPixel(0),
84 mFileName(filename),
85 mBytesPerLine(0),
86 mFrameWidth(width), mFrameHeight(height),
87 mYUVFrameSize(width * height * 3 / 2),
88 mRGBBuffer(0), mpFormatContext(0), mpStream(0),
89 mOutOfMemory(false), mToggle(false)
90{
91 ULONG cPixels = width * height;
92
93 LogFlow(("Creating FFmpegFB object %p, width=%lu, height=%lu\n",
94 this, (unsigned long) width, (unsigned long) height));
95 Assert(width % 2 == 0 && height % 2 == 0);
96 /* For temporary RGB frame we allocate enough memory to deal with
97 RGB16 to RGB32 */
98 mTempRGBBuffer = reinterpret_cast<uint8_t *>(av_malloc(cPixels * 4));
99 if (mTempRGBBuffer == 0)
100 goto nomem_temp_rgb_buffer;
101 mYUVBuffer = reinterpret_cast<uint8_t *>(av_malloc(mYUVFrameSize));
102 if (mYUVBuffer == 0)
103 goto nomem_yuv_buffer;
104 mFrame = avcodec_alloc_frame();
105 if (mFrame == 0)
106 goto nomem_mframe;
107 mOutBuf = reinterpret_cast<uint8_t *>(av_malloc(mYUVFrameSize * 2));
108 if (mOutBuf == 0)
109 goto nomem_moutbuf;
110
111 return;
112
113 /* C-based memory allocation and how to deal with it in C++ :) */
114nomem_moutbuf:
115 Log(("Failed to allocate memory for mOutBuf\n"));
116 av_free(mFrame);
117nomem_mframe:
118 Log(("Failed to allocate memory for mFrame\n"));
119 av_free(mYUVBuffer);
120nomem_yuv_buffer:
121 Log(("Failed to allocate memory for mYUVBuffer\n"));
122 av_free(mTempRGBBuffer);
123nomem_temp_rgb_buffer:
124 Log(("Failed to allocate memory for mTempRGBBuffer\n"));
125 mOutOfMemory = true;
126}
127
128
129/**
130 * Write the last frame to disk and free allocated memory
131 */
132FFmpegFB::~FFmpegFB()
133{
134 LogFlow(("Destroying FFmpegFB object %p\n", this));
135 if (mpFormatContext != 0)
136 {
137 if (mfUrlOpen)
138 {
139 /* Dummy update to make sure we get all the frame (timing). */
140 NotifyUpdate(0, 0, 0, 0);
141 /* Write the last pending frame before exiting */
142 int rc = do_rgb_to_yuv_conversion();
143 if (rc == S_OK)
144 do_encoding_and_write();
145#if 1
146 /* Add another 10 seconds. */
147 for (int i = 10*25; i > 0; i--)
148 do_encoding_and_write();
149#endif
150 /* write a png file of the last frame */
151 write_png();
152 avcodec_close(mpStream->codec);
153 av_write_trailer(mpFormatContext);
154 /* free the streams */
155 for(unsigned i = 0; i < (unsigned)mpFormatContext->nb_streams; i++) {
156 av_freep(&mpFormatContext->streams[i]->codec);
157 av_freep(&mpFormatContext->streams[i]);
158 }
159/* Changed sometime between 50.5.0 and 52.7.0 */
160#if LIBAVFORMAT_VERSION_INT >= (52 << 16)
161 url_fclose(mpFormatContext->pb);
162#else /* older version */
163 url_fclose(&mpFormatContext->pb);
164#endif /* older version */
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 retrieve 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/**
383 * Return id of associated window
384 *
385 * @returns COM status code
386 * @retval winId Associated window id
387 */
388STDMETHODIMP FFmpegFB::COMGETTER(WinId) (LONG64 *winId)
389{
390 if (!winId)
391 return E_POINTER;
392 *winId = 0;
393 return S_OK;
394}
395
396// IFramebuffer methods
397/////////////////////////////////////////////////////////////////////////////
398
399STDMETHODIMP FFmpegFB::Lock()
400{
401 LogFlow(("FFmpeg::Lock: called\n"));
402 int rc = RTCritSectEnter(&mCritSect);
403 AssertRC(rc);
404 if (rc == VINF_SUCCESS)
405 return S_OK;
406 return E_UNEXPECTED;
407}
408
409STDMETHODIMP FFmpegFB::Unlock()
410{
411 LogFlow(("FFmpeg::Unlock: called\n"));
412 RTCritSectLeave(&mCritSect);
413 return S_OK;
414}
415
416
417/**
418 * This method is used to notify us that an area of the guest framebuffer
419 * has been updated.
420 *
421 * @returns COM status code
422 * @param x X co-ordinate of the upper left-hand corner of the
423 * area which has been updated
424 * @param y Y co-ordinate of the upper left-hand corner of the
425 * area which has been updated
426 * @param w width of the area which has been updated
427 * @param h height of the area which has been updated
428 */
429STDMETHODIMP FFmpegFB::NotifyUpdate(ULONG x, ULONG y, ULONG w, ULONG h)
430{
431 int rc;
432 int64_t iCurrentTime = RTTimeMilliTS();
433
434 LogFlow(("FFmpeg::NotifyUpdate called: x=%lu, y=%lu, w=%lu, h=%lu\n",
435 (unsigned long) x, (unsigned long) y, (unsigned long) w,
436 (unsigned long) h));
437
438 /* We always leave at least one frame update pending, which we
439 process when the time until the next frame has elapsed. */
440 if (iCurrentTime - mLastTime >= 40)
441 {
442 rc = do_rgb_to_yuv_conversion();
443 if (rc != S_OK)
444 {
445 copy_to_intermediate_buffer(x, y, w, h);
446 return rc;
447 }
448 rc = do_encoding_and_write();
449 if (rc != S_OK)
450 {
451 copy_to_intermediate_buffer(x, y, w, h);
452 return rc;
453 }
454 mLastTime = mLastTime + 40;
455 /* Write frames for the time in-between. Not a good way
456 to handle this. */
457 while (iCurrentTime - mLastTime >= 40)
458 {
459/* rc = do_rgb_to_yuv_conversion();
460 if (rc != S_OK)
461 {
462 copy_to_intermediate_buffer(x, y, w, h);
463 return rc;
464 }
465*/ rc = do_encoding_and_write();
466 if (rc != S_OK)
467 {
468 copy_to_intermediate_buffer(x, y, w, h);
469 return rc;
470 }
471 mLastTime = mLastTime + 40;
472 }
473 }
474 /* Finally we copy the updated data to the intermediate buffer,
475 ready for the next update. */
476 copy_to_intermediate_buffer(x, y, w, h);
477 return S_OK;
478}
479
480
481/**
482 * Requests a resize of our "screen".
483 *
484 * @returns COM status code
485 * @param pixelFormat Layout of the guest video RAM (i.e. 16, 24,
486 * 32 bpp)
487 * @param vram host context pointer to the guest video RAM,
488 * in case we can cope with the format
489 * @param bitsPerPixel color depth of the guest video RAM
490 * @param bytesPerLine length of a screen line in the guest video RAM
491 * @param w video mode width in pixels
492 * @param h video mode height in pixels
493 * @retval finished set to true if the method is synchronous and
494 * to false otherwise
495 *
496 * This method is called when the guest attempts to resize the virtual
497 * screen. The pointer to the guest's video RAM is supplied in case
498 * the framebuffer can handle the pixel format. If it can't, it should
499 * allocate a memory buffer itself, and the virtual VGA device will copy
500 * the guest VRAM to that in a format we can handle. The
501 * COMGETTER(UsesGuestVRAM) method is used to tell the VGA device which method
502 * we have chosen, and the other COMGETTER methods tell the device about
503 * the layout of our buffer. We currently handle all VRAM layouts except
504 * FramebufferPixelFormat_Opaque (which cannot be handled by
505 * definition).
506 */
507STDMETHODIMP FFmpegFB::RequestResize(ULONG aScreenId, ULONG pixelFormat,
508 BYTE *vram, ULONG bitsPerPixel,
509 ULONG bytesPerLine,
510 ULONG w, ULONG h, BOOL *finished)
511{
512 NOREF(aScreenId);
513 if (!finished)
514 return E_POINTER;
515 LogFlow(("FFmpeg::RequestResize called: pixelFormat=%lu, vram=%lu, "
516 "bpp=%lu bpl=%lu, w=%lu, h=%lu\n",
517 (unsigned long) pixelFormat, (unsigned long) vram,
518 (unsigned long) bitsPerPixel, (unsigned long) bytesPerLine,
519 (unsigned long) w, (unsigned long) h));
520 /* For now, we are doing things synchronously */
521 *finished = true;
522
523 /* We always reallocate our buffer */
524 if (mRGBBuffer)
525 RTMemFree(mRGBBuffer);
526 mGuestWidth = w;
527 mGuestHeight = h;
528
529 bool fallback = false;
530
531 /* See if there are conditions under which we can use the guest's VRAM,
532 * fallback to our own memory buffer otherwise */
533
534 if (pixelFormat == FramebufferPixelFormat_FOURCC_RGB)
535 {
536 switch (bitsPerPixel)
537 {
538 case 32:
539 mFFMPEGPixelFormat = PIX_FMT_RGBA32;
540 Log2(("FFmpeg::RequestResize: setting ffmpeg pixel format to PIX_FMT_RGBA32\n"));
541 break;
542 case 24:
543 mFFMPEGPixelFormat = PIX_FMT_RGB24;
544 Log2(("FFmpeg::RequestResize: setting ffmpeg pixel format to PIX_FMT_RGB24\n"));
545 break;
546 case 16:
547 mFFMPEGPixelFormat = PIX_FMT_RGB565;
548 Log2(("FFmpeg::RequestResize: setting ffmpeg pixel format to PIX_FMT_RGB565\n"));
549 break;
550 default:
551 fallback = true;
552 break;
553 }
554 }
555 else
556 {
557 fallback = true;
558 }
559
560 if (!fallback)
561 {
562 mPixelFormat = FramebufferPixelFormat_FOURCC_RGB;
563 mBufferAddress = reinterpret_cast<uint8_t *>(vram);
564 mBytesPerLine = bytesPerLine;
565 mBitsPerPixel = bitsPerPixel;
566 mRGBBuffer = 0;
567 Log2(("FFmpeg::RequestResize: setting mBufferAddress to vram and mLineSize to %lu\n",
568 (unsigned long) mBytesPerLine));
569 }
570 else
571 {
572 /* we always fallback to 32bpp RGB */
573 mPixelFormat = FramebufferPixelFormat_FOURCC_RGB;
574 mFFMPEGPixelFormat = PIX_FMT_RGBA32;
575 Log2(("FFmpeg::RequestResize: setting ffmpeg pixel format to PIX_FMT_RGBA32\n"));
576
577 mBytesPerLine = w * 4;
578 mBitsPerPixel = 32;
579 mRGBBuffer = reinterpret_cast<uint8_t *>(RTMemAlloc(mBytesPerLine * h));
580 AssertReturn(mRGBBuffer != 0, E_OUTOFMEMORY);
581 Log2(("FFmpeg::RequestResize: alloc'ing mBufferAddress and mRGBBuffer to %p and mBytesPerLine to %lu\n",
582 mBufferAddress, (unsigned long) mBytesPerLine));
583 mBufferAddress = mRGBBuffer;
584 }
585
586 /* Blank out the intermediate frame framebuffer */
587 memset(mTempRGBBuffer, 0, mFrameWidth * mFrameHeight * 4);
588 return S_OK;
589}
590
591/**
592 * Returns whether we like the given video mode.
593 *
594 * @returns COM status code
595 * @param width video mode width in pixels
596 * @param height video mode height in pixels
597 * @param bpp video mode bit depth in bits per pixel
598 * @param supported pointer to result variable
599 *
600 * As far as I know, the only restriction we have on video modes is that
601 * we have to have an even number of horizontal and vertical pixels.
602 * I sincerely doubt that anything else will be requested, and if it
603 * is anyway, we will just silently amputate one line when we write to
604 * the mpeg file.
605 */
606STDMETHODIMP FFmpegFB::VideoModeSupported(ULONG width, ULONG height,
607 ULONG bpp, BOOL *supported)
608{
609 if (!supported)
610 return E_POINTER;
611 *supported = true;
612 return S_OK;
613}
614
615/** Stubbed */
616STDMETHODIMP FFmpegFB::GetVisibleRegion(BYTE *rectangles, ULONG /* count */, ULONG * /* countCopied */)
617{
618 if (!rectangles)
619 return E_POINTER;
620 *rectangles = 0;
621 return S_OK;
622}
623
624/** Stubbed */
625STDMETHODIMP FFmpegFB::SetVisibleRegion(BYTE *rectangles, ULONG /* count */)
626{
627 if (!rectangles)
628 return E_POINTER;
629 return S_OK;
630}
631
632STDMETHODIMP FFmpegFB::ProcessVHWACommand(BYTE *pCommand)
633{
634 return E_NOTIMPL;
635}
636// Private Methods
637//////////////////////////////////////////////////////////////////////////
638//
639
640HRESULT FFmpegFB::setup_library()
641{
642 /* Set up the avcodec library */
643 avcodec_init();
644 /* Register all codecs in the library. */
645 avcodec_register_all();
646 /* Register all formats in the format library */
647 av_register_all();
648 mpFormatContext = av_alloc_format_context();
649 AssertReturn(mpFormatContext != 0, E_OUTOFMEMORY);
650 mpStream = av_new_stream(mpFormatContext, 0);
651 AssertReturn(mpStream != 0, E_UNEXPECTED);
652 strncpy(mpFormatContext->filename, com::Utf8Str(mFileName).c_str(),
653 sizeof(mpFormatContext->filename));
654 return S_OK;
655}
656
657
658/**
659 * Determine the correct output format and codec for our MPEG file.
660 *
661 * @returns COM status code
662 *
663 * @pre The format context (mpFormatContext) should have already been
664 * allocated.
665 */
666HRESULT FFmpegFB::setup_output_format()
667{
668 Assert(mpFormatContext != 0);
669 AVOutputFormat *pOutFormat = guess_format(0, com::Utf8Str(mFileName).c_str(),
670 0);
671#ifdef VBOX_SHOW_AVAILABLE_FORMATS
672 if (!pOutFormat)
673 {
674 RTPrintf("Could not guess an output format for that extension.\n"
675 "Available formats:\n");
676 list_formats();
677 }
678#endif
679 AssertMsgReturn(pOutFormat != 0,
680 ("Could not deduce output format from file name\n"),
681 E_INVALIDARG);
682 AssertMsgReturn((pOutFormat->flags & AVFMT_RAWPICTURE) == 0,
683 ("Can't handle output format for file\n"),
684 E_INVALIDARG);
685 AssertMsgReturn((pOutFormat->flags & AVFMT_NOFILE) == 0,
686 ("pOutFormat->flags=%x, pOutFormat->name=%s\n",
687 pOutFormat->flags, pOutFormat->name), E_UNEXPECTED);
688 AssertMsgReturn(pOutFormat->video_codec != CODEC_ID_NONE,
689 ("No video codec available - you have probably selected a non-video file format\n"), E_UNEXPECTED);
690 mpFormatContext->oformat = pOutFormat;
691 /* Set format specific parameters - requires the format to be set. */
692 int rcSetParam = av_set_parameters(mpFormatContext, 0);
693 AssertReturn(rcSetParam >= 0, E_UNEXPECTED);
694#if 1 /* bird: This works for me on the mac, please review & test elsewhere. */
695 /* Fill in any uninitialized parameters like opt_output_file in ffpmeg.c does.
696 This fixes most of the buffer underflow warnings:
697 http://lists.mplayerhq.hu/pipermail/ffmpeg-devel/2005-June/001699.html */
698 if (!mpFormatContext->preload)
699 mpFormatContext->preload = (int)(0.5 * AV_TIME_BASE);
700 if (!mpFormatContext->max_delay)
701 mpFormatContext->max_delay = (int)(0.7 * AV_TIME_BASE);
702#endif
703 return S_OK;
704}
705
706
707HRESULT FFmpegFB::list_formats()
708{
709 AVCodec *codec;
710 for (codec = first_avcodec; codec != NULL; codec = codec->next)
711 {
712 if (codec->type == CODEC_TYPE_VIDEO && codec->encode)
713 {
714 AVOutputFormat *ofmt;
715 for (ofmt = first_oformat; ofmt != NULL; ofmt = ofmt->next)
716 {
717 if (ofmt->video_codec == codec->id)
718 RTPrintf(" %20s: %20s => '%s'\n", codec->name, ofmt->extensions, ofmt->long_name);
719 }
720 }
721 }
722 return S_OK;
723}
724
725
726/**
727 * Open the FFmpeg codec and set it up (width, etc) for our MPEG file.
728 *
729 * @returns COM status code
730 *
731 * @pre The format context (mpFormatContext) and the stream (mpStream)
732 * should have already been allocated.
733 */
734HRESULT FFmpegFB::open_codec()
735{
736 Assert(mpFormatContext != 0);
737 Assert(mpStream != 0);
738 AVOutputFormat *pOutFormat = mpFormatContext->oformat;
739 AVCodecContext *pCodecContext = mpStream->codec;
740 AssertReturn(pCodecContext != 0, E_UNEXPECTED);
741 AVCodec *pCodec = avcodec_find_encoder(pOutFormat->video_codec);
742#ifdef VBOX_SHOW_AVAILABLE_FORMATS
743 if (!pCodec)
744 {
745 RTPrintf("Could not find a suitable codec for the output format on your system\n"
746 "Available formats:\n");
747 list_formats();
748 }
749#endif
750 AssertReturn(pCodec != 0, E_UNEXPECTED);
751 pCodecContext->codec_id = pOutFormat->video_codec;
752 pCodecContext->codec_type = CODEC_TYPE_VIDEO;
753 pCodecContext->bit_rate = mBitRate;
754 pCodecContext->width = mFrameWidth;
755 pCodecContext->height = mFrameHeight;
756 pCodecContext->time_base.den = 25;
757 pCodecContext->time_base.num = 1;
758 pCodecContext->gop_size = 12; /* at most one intra frame in 12 */
759 pCodecContext->max_b_frames = 1;
760 pCodecContext->pix_fmt = PIX_FMT_YUV420P;
761 /* taken from the ffmpeg output example */
762 // some formats want stream headers to be separate
763 if (!strcmp(pOutFormat->name, "mp4")
764 || !strcmp(pOutFormat->name, "mov")
765 || !strcmp(pOutFormat->name, "3gp"))
766 pCodecContext->flags |= CODEC_FLAG_GLOBAL_HEADER;
767 /* end output example section */
768 int rcOpenCodec = avcodec_open(pCodecContext, pCodec);
769 AssertReturn(rcOpenCodec >= 0, E_UNEXPECTED);
770 return S_OK;
771}
772
773
774/**
775 * Open our MPEG file and write the header.
776 *
777 * @returns COM status code
778 *
779 * @pre The format context (mpFormatContext) and the stream (mpStream)
780 * should have already been allocated and set up.
781 */
782HRESULT FFmpegFB::open_output_file()
783{
784 char szFileName[RTPATH_MAX];
785 Assert(mpFormatContext);
786 Assert(mpFormatContext->oformat);
787 strcpy(szFileName, com::Utf8Str(mFileName).c_str());
788 int rcUrlFopen = url_fopen(&mpFormatContext->pb,
789 szFileName, URL_WRONLY);
790 AssertReturn(rcUrlFopen >= 0, E_UNEXPECTED);
791 mfUrlOpen = true;
792 av_write_header(mpFormatContext);
793 return S_OK;
794}
795
796
797/**
798 * Copy an area from the output buffer used by the virtual VGA (may
799 * just be the guest's VRAM) to our fixed size intermediate buffer.
800 * The picture in the intermediate buffer is centred if the guest
801 * screen dimensions are smaller and amputated if they are larger than
802 * our frame dimensions.
803 *
804 * @param x X co-ordinate of the upper left-hand corner of the
805 * area which has been updated
806 * @param y Y co-ordinate of the upper left-hand corner of the
807 * area which has been updated
808 * @param w width of the area which has been updated
809 * @param h height of the area which has been updated
810 */
811void FFmpegFB::copy_to_intermediate_buffer(ULONG x, ULONG y, ULONG w, ULONG h)
812{
813 Log2(("FFmpegFB::copy_to_intermediate_buffer: x=%lu, y=%lu, w=%lu, h=%lu\n",
814 (unsigned long) x, (unsigned long) y, (unsigned long) w, (unsigned long) h));
815 /* Perform clipping and calculate the destination co-ordinates */
816 ULONG destX, destY, bpp;
817 LONG xDiff = (LONG(mFrameWidth) - LONG(mGuestWidth)) / 2;
818 LONG yDiff = (LONG(mFrameHeight) - LONG(mGuestHeight)) / 2;
819 if (LONG(w) + xDiff + LONG(x) <= 0) /* nothing visible */
820 return;
821 if (LONG(x) < -xDiff)
822 {
823 w = LONG(w) + xDiff + x;
824 x = -xDiff;
825 destX = 0;
826 }
827 else
828 destX = x + xDiff;
829 if (LONG(h) + yDiff + LONG(y) <= 0) /* nothing visible */
830 return;
831 if (LONG(y) < -yDiff)
832 {
833 h = LONG(h) + yDiff + LONG(y);
834 y = -yDiff;
835 destY = 0;
836 }
837 else
838 destY = y + yDiff;
839 if (destX > mFrameWidth || destY > mFrameHeight)
840 return; /* nothing visible */
841 if (destX + w > mFrameWidth)
842 w = mFrameWidth - destX;
843 if (destY + h > mFrameHeight)
844 h = mFrameHeight - destY;
845 /* Calculate bytes per pixel */
846 if (mPixelFormat == FramebufferPixelFormat_FOURCC_RGB)
847 {
848 switch (mBitsPerPixel)
849 {
850 case 32:
851 case 24:
852 case 16:
853 bpp = mBitsPerPixel / 8;
854 break;
855 default:
856 AssertMsgFailed(("Unknown color depth! mBitsPerPixel=%d\n", mBitsPerPixel));
857 bpp = 1;
858 break;
859 }
860 }
861 else
862 {
863 AssertMsgFailed(("Unknown pixel format! mPixelFormat=%d\n", mPixelFormat));
864 bpp = 1;
865 }
866 /* Calculate start offset in source and destination buffers */
867 ULONG srcOffs = y * mBytesPerLine + x * bpp;
868 ULONG destOffs = (destY * mFrameWidth + destX) * bpp;
869 /* do the copy */
870 for (unsigned int i = 0; i < h; i++)
871 {
872 /* Overflow check */
873 Assert(srcOffs + w * bpp <= mGuestHeight * mBytesPerLine);
874 Assert(destOffs + w * bpp <= mFrameHeight * mFrameWidth * bpp);
875 memcpy(mTempRGBBuffer + destOffs, mBufferAddress + srcOffs,
876 w * bpp);
877 srcOffs = srcOffs + mBytesPerLine;
878 destOffs = destOffs + mFrameWidth * bpp;
879 }
880}
881
882
883/**
884 * Copy the RGB data in the intermediate framebuffer to YUV data in
885 * the YUV framebuffer.
886 *
887 * @returns COM status code
888 */
889HRESULT FFmpegFB::do_rgb_to_yuv_conversion()
890{
891 switch (mFFMPEGPixelFormat)
892 {
893 case PIX_FMT_RGBA32:
894 if (!FFmpegWriteYUV420p<FFmpegBGRA32Iter>(mFrameWidth, mFrameHeight,
895 mYUVBuffer, mTempRGBBuffer))
896 return E_UNEXPECTED;
897 break;
898 case PIX_FMT_RGB24:
899 if (!FFmpegWriteYUV420p<FFmpegBGR24Iter>(mFrameWidth, mFrameHeight,
900 mYUVBuffer, mTempRGBBuffer))
901 return E_UNEXPECTED;
902 break;
903 case PIX_FMT_RGB565:
904 if (!FFmpegWriteYUV420p<FFmpegBGR565Iter>(mFrameWidth, mFrameHeight,
905 mYUVBuffer, mTempRGBBuffer))
906 return E_UNEXPECTED;
907 break;
908 default:
909 return E_UNEXPECTED;
910 }
911 return S_OK;
912}
913
914
915/**
916 * Encode the YUV framebuffer as an MPEG frame and write it to the file.
917 *
918 * @returns COM status code
919 */
920HRESULT FFmpegFB::do_encoding_and_write()
921{
922 AVCodecContext *pContext = mpStream->codec;
923
924 /* A hack: ffmpeg mpeg2 only writes a frame if something has
925 changed. So we flip the low luminance bit of the first
926 pixel every frame. */
927 if (mToggle)
928 mYUVBuffer[0] |= 1;
929 else
930 mYUVBuffer[0] &= 0xfe;
931 mToggle = !mToggle;
932 int cSize = avcodec_encode_video(pContext, mOutBuf, mYUVFrameSize * 2,
933 mFrame);
934 AssertMsgReturn(cSize >= 0,
935 ("avcodec_encode_video() failed with rc=%d.\n"
936 "mFrameWidth=%u, mFrameHeight=%u\n", cSize,
937 mFrameWidth, mFrameHeight), E_UNEXPECTED);
938 if (cSize > 0)
939 {
940 AVPacket Packet;
941 av_init_packet(&Packet);
942 Packet.pts = av_rescale_q(pContext->coded_frame->pts,
943 pContext->time_base,
944 mpStream->time_base);
945 if(pContext->coded_frame->key_frame)
946 Packet.flags |= PKT_FLAG_KEY;
947 Packet.stream_index = mpStream->index;
948 Packet.data = mOutBuf;
949 Packet.size = cSize;
950
951 /* write the compressed frame in the media file */
952 int rcWriteFrame = av_write_frame(mpFormatContext, &Packet);
953 AssertReturn(rcWriteFrame == 0, E_UNEXPECTED);
954 }
955 return S_OK;
956}
957
958
959/**
960 * Capture the current (i.e. the last) frame as a PNG file with the
961 * same basename as the captured video file.
962 */
963HRESULT FFmpegFB::write_png()
964{
965 HRESULT errorCode = E_OUTOFMEMORY;
966 png_bytep *row_pointers;
967 char PNGFileName[RTPATH_MAX], oldName[RTPATH_MAX];
968 png_structp png_ptr;
969 png_infop info_ptr;
970 uint8_t *PNGBuffer;
971 /* Work out the new file name - for some reason, we can't use
972 the com::Utf8Str() directly, but have to copy it */
973 strcpy(oldName, com::Utf8Str(mFileName).c_str());
974 int baseLen = strrchr(oldName, '.') - oldName;
975 if (baseLen == 0)
976 baseLen = strlen(oldName);
977 if (baseLen >= RTPATH_MAX - 5) /* for whatever reason */
978 baseLen = RTPATH_MAX - 5;
979 memcpy(&PNGFileName[0], oldName, baseLen);
980 PNGFileName[baseLen] = '.';
981 PNGFileName[baseLen + 1] = 'p';
982 PNGFileName[baseLen + 2] = 'n';
983 PNGFileName[baseLen + 3] = 'g';
984 PNGFileName[baseLen + 4] = 0;
985 /* Open output file */
986 FILE *fp = fopen(PNGFileName, "wb");
987 if (fp == 0)
988 {
989 errorCode = E_UNEXPECTED;
990 goto fopen_failed;
991 }
992 /* Create libpng basic structures */
993 png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL,
994 0 /* error function */, 0 /* warning function */);
995 if (png_ptr == 0)
996 goto png_create_write_struct_failed;
997 info_ptr = png_create_info_struct(png_ptr);
998 if (info_ptr == 0)
999 {
1000 png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
1001 goto png_create_info_struct_failed;
1002 }
1003 /* Convert image to standard RGB24 to simplify life */
1004 PNGBuffer = reinterpret_cast<uint8_t *>(av_malloc(mFrameWidth
1005 * mFrameHeight * 4));
1006 if (PNGBuffer == 0)
1007 goto av_malloc_buffer_failed;
1008 row_pointers =
1009 reinterpret_cast<png_bytep *>(av_malloc(mFrameHeight
1010 * sizeof(png_bytep)));
1011 if (row_pointers == 0)
1012 goto av_malloc_pointers_failed;
1013 switch (mFFMPEGPixelFormat)
1014 {
1015 case PIX_FMT_RGBA32:
1016 if (!FFmpegWriteRGB24<FFmpegBGRA32Iter>(mFrameWidth, mFrameHeight,
1017 PNGBuffer, mTempRGBBuffer))
1018 goto setjmp_exception;
1019 break;
1020 case PIX_FMT_RGB24:
1021 if (!FFmpegWriteRGB24<FFmpegBGR24Iter>(mFrameWidth, mFrameHeight,
1022 PNGBuffer, mTempRGBBuffer))
1023 goto setjmp_exception;
1024 break;
1025 case PIX_FMT_RGB565:
1026 if (!FFmpegWriteRGB24<FFmpegBGR565Iter>(mFrameWidth, mFrameHeight,
1027 PNGBuffer, mTempRGBBuffer))
1028 goto setjmp_exception;
1029 break;
1030 default:
1031 goto setjmp_exception;
1032 }
1033 /* libpng exception handling */
1034 if (setjmp(png_jmpbuf(png_ptr)))
1035 goto setjmp_exception;
1036 /* pass libpng the file pointer */
1037 png_init_io(png_ptr, fp);
1038 /* set the image properties */
1039 png_set_IHDR(png_ptr, info_ptr, mFrameWidth, mFrameHeight,
1040 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
1041 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
1042 /* set up the information about the bitmap for libpng */
1043 row_pointers[0] = png_bytep(PNGBuffer);
1044 for (unsigned i = 1; i < mFrameHeight; i++)
1045 row_pointers[i] = row_pointers[i - 1] + mFrameWidth * 3;
1046 png_set_rows(png_ptr, info_ptr, &row_pointers[0]);
1047 /* and write the thing! */
1048 png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, 0);
1049 /* drop through to cleanup */
1050 errorCode = S_OK;
1051setjmp_exception:
1052 av_free(row_pointers);
1053av_malloc_pointers_failed:
1054 av_free(PNGBuffer);
1055av_malloc_buffer_failed:
1056 png_destroy_write_struct(&png_ptr, &info_ptr);
1057png_create_info_struct_failed:
1058png_create_write_struct_failed:
1059 fclose(fp);
1060fopen_failed:
1061 if (errorCode != S_OK)
1062 Log(("FFmpegFB::write_png: Failed to write .png image of final frame\n"));
1063 return errorCode;
1064}
1065
1066
1067#ifdef VBOX_WITH_XPCOM
1068NS_DECL_CLASSINFO(FFmpegFB)
1069NS_IMPL_THREADSAFE_ISUPPORTS1_CI(FFmpegFB, IFramebuffer)
1070#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