2017-07-13 10:26:03 +02:00
|
|
|
/*
|
|
|
|
* CVideoHandler.cpp, part of VCMI engine
|
|
|
|
*
|
|
|
|
* Authors: listed in file AUTHORS in main folder
|
|
|
|
*
|
|
|
|
* License: GNU General Public License v2.0 or later
|
|
|
|
* Full text of license available in license.txt file, in main folder
|
|
|
|
*
|
|
|
|
*/
|
2011-12-14 00:23:17 +03:00
|
|
|
#include "StdInc.h"
|
2007-08-08 22:28:56 +03:00
|
|
|
#include "CVideoHandler.h"
|
2011-12-14 00:23:17 +03:00
|
|
|
|
2023-02-03 18:23:53 +02:00
|
|
|
#include "CMT.h"
|
2013-04-07 14:52:07 +03:00
|
|
|
#include "gui/CGuiHandler.h"
|
2023-05-18 22:31:05 +02:00
|
|
|
#include "eventsSDL/InputHandler.h"
|
2023-05-14 21:30:59 +02:00
|
|
|
#include "gui/FramerateManager.h"
|
2023-02-01 20:42:06 +02:00
|
|
|
#include "renderSDL/SDL_Extensions.h"
|
2011-04-07 20:54:08 +03:00
|
|
|
#include "CPlayerInterface.h"
|
2013-07-28 17:49:50 +03:00
|
|
|
#include "../lib/filesystem/Filesystem.h"
|
2023-09-16 23:14:51 +02:00
|
|
|
#include "../lib/filesystem/CInputStream.h"
|
2009-06-28 02:33:59 +03:00
|
|
|
|
2023-01-30 00:12:43 +02:00
|
|
|
#include <SDL_render.h>
|
2012-01-12 18:23:00 +03:00
|
|
|
|
2014-05-24 16:52:43 +03:00
|
|
|
#ifndef DISABLE_VIDEO
|
2023-01-27 01:52:40 +02:00
|
|
|
|
|
|
|
extern "C" {
|
|
|
|
#include <libavformat/avformat.h>
|
|
|
|
#include <libavcodec/avcodec.h>
|
|
|
|
#include <libavutil/imgutils.h>
|
|
|
|
#include <libswscale/swscale.h>
|
|
|
|
}
|
|
|
|
|
2014-01-06 22:12:21 +03:00
|
|
|
#ifdef _MSC_VER
|
|
|
|
#pragma comment(lib, "avcodec.lib")
|
|
|
|
#pragma comment(lib, "avutil.lib")
|
|
|
|
#pragma comment(lib, "avformat.lib")
|
|
|
|
#pragma comment(lib, "swscale.lib")
|
|
|
|
#endif // _MSC_VER
|
|
|
|
|
2012-08-21 20:37:06 +03:00
|
|
|
// Define a set of functions to read data
|
|
|
|
static int lodRead(void* opaque, uint8_t* buf, int size)
|
|
|
|
{
|
|
|
|
auto video = reinterpret_cast<CVideoPlayer *>(opaque);
|
|
|
|
|
2020-10-01 10:38:06 +02:00
|
|
|
return static_cast<int>(video->data->read(buf, size));
|
2009-06-20 04:34:26 +03:00
|
|
|
}
|
|
|
|
|
2012-08-21 20:37:06 +03:00
|
|
|
static si64 lodSeek(void * opaque, si64 pos, int whence)
|
|
|
|
{
|
|
|
|
auto video = reinterpret_cast<CVideoPlayer *>(opaque);
|
|
|
|
|
|
|
|
if (whence & AVSEEK_SIZE)
|
2013-02-06 02:11:48 +03:00
|
|
|
return video->data->getSize();
|
2009-06-20 04:34:26 +03:00
|
|
|
|
2013-02-06 02:11:48 +03:00
|
|
|
return video->data->seek(pos);
|
2009-06-20 04:34:26 +03:00
|
|
|
}
|
|
|
|
|
2023-10-07 20:09:40 +02:00
|
|
|
// Define a set of functions to read data
|
|
|
|
static int lodReadAudio(void* opaque, uint8_t* buf, int size)
|
|
|
|
{
|
|
|
|
auto video = reinterpret_cast<CVideoPlayer *>(opaque);
|
|
|
|
|
|
|
|
return static_cast<int>(video->dataAudio->read(buf, size));
|
|
|
|
}
|
|
|
|
|
|
|
|
static si64 lodSeekAudio(void * opaque, si64 pos, int whence)
|
|
|
|
{
|
|
|
|
auto video = reinterpret_cast<CVideoPlayer *>(opaque);
|
|
|
|
|
|
|
|
if (whence & AVSEEK_SIZE)
|
|
|
|
return video->dataAudio->getSize();
|
|
|
|
|
|
|
|
return video->dataAudio->seek(pos);
|
|
|
|
}
|
|
|
|
|
2009-06-20 04:34:26 +03:00
|
|
|
CVideoPlayer::CVideoPlayer()
|
2023-01-27 01:52:40 +02:00
|
|
|
: stream(-1)
|
|
|
|
, format (nullptr)
|
|
|
|
, codecContext(nullptr)
|
|
|
|
, codec(nullptr)
|
|
|
|
, frame(nullptr)
|
|
|
|
, sws(nullptr)
|
|
|
|
, context(nullptr)
|
|
|
|
, texture(nullptr)
|
|
|
|
, dest(nullptr)
|
|
|
|
, destRect(0,0,0,0)
|
|
|
|
, pos(0,0,0,0)
|
|
|
|
, frameTime(0)
|
|
|
|
, doLoop(false)
|
|
|
|
{}
|
2009-06-20 04:34:26 +03:00
|
|
|
|
2023-09-01 23:57:25 +02:00
|
|
|
bool CVideoPlayer::open(const VideoPath & fname, bool scale)
|
2011-08-08 17:20:22 +03:00
|
|
|
{
|
|
|
|
return open(fname, true, false);
|
|
|
|
}
|
|
|
|
|
2009-06-28 02:33:59 +03:00
|
|
|
// loop = to loop through the video
|
|
|
|
// useOverlay = directly write to the screen.
|
2023-09-04 13:29:02 +02:00
|
|
|
bool CVideoPlayer::open(const VideoPath & videoToOpen, bool loop, bool useOverlay, bool scale)
|
2009-06-20 04:34:26 +03:00
|
|
|
{
|
|
|
|
close();
|
2012-09-15 22:16:16 +03:00
|
|
|
|
2009-06-28 02:33:59 +03:00
|
|
|
doLoop = loop;
|
2023-01-27 01:52:40 +02:00
|
|
|
frameTime = 0;
|
2009-06-20 04:34:26 +03:00
|
|
|
|
2023-09-04 13:29:02 +02:00
|
|
|
if (CResourceHandler::get()->existsResource(videoToOpen))
|
|
|
|
fname = videoToOpen;
|
|
|
|
else
|
|
|
|
fname = videoToOpen.addPrefix("VIDEO/");
|
|
|
|
|
2023-09-01 23:57:25 +02:00
|
|
|
if (!CResourceHandler::get()->existsResource(fname))
|
2013-02-27 18:46:14 +03:00
|
|
|
{
|
2023-09-01 23:57:25 +02:00
|
|
|
logGlobal->error("Error: video %s was not found", fname.getName());
|
2012-08-08 15:17:25 +03:00
|
|
|
return false;
|
2013-02-27 18:46:14 +03:00
|
|
|
}
|
2009-06-20 04:34:26 +03:00
|
|
|
|
2023-09-01 23:57:25 +02:00
|
|
|
data = CResourceHandler::get()->load(fname);
|
2012-08-21 20:37:06 +03:00
|
|
|
|
|
|
|
static const int BUFFER_SIZE = 4096;
|
2013-02-06 02:11:48 +03:00
|
|
|
|
|
|
|
unsigned char * buffer = (unsigned char *)av_malloc(BUFFER_SIZE);// will be freed by ffmpeg
|
2013-06-26 14:18:27 +03:00
|
|
|
context = avio_alloc_context( buffer, BUFFER_SIZE, 0, (void *)this, lodRead, nullptr, lodSeek);
|
2012-08-21 20:37:06 +03:00
|
|
|
|
|
|
|
format = avformat_alloc_context();
|
|
|
|
format->pb = context;
|
|
|
|
// filename is not needed - file was already open and stored in this->data;
|
|
|
|
int avfopen = avformat_open_input(&format, "dummyFilename", nullptr, nullptr);
|
|
|
|
|
2012-05-13 18:04:21 +03:00
|
|
|
if (avfopen != 0)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2009-06-20 04:34:26 +03:00
|
|
|
// Retrieve stream information
|
2013-06-26 14:18:27 +03:00
|
|
|
if (avformat_find_stream_info(format, nullptr) < 0)
|
2009-06-20 04:34:26 +03:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// Find the first video stream
|
|
|
|
stream = -1;
|
2012-05-13 18:04:21 +03:00
|
|
|
for(ui32 i=0; i<format->nb_streams; i++)
|
|
|
|
{
|
2022-05-10 18:25:48 +02:00
|
|
|
if (format->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
|
2011-07-05 22:06:47 +03:00
|
|
|
{
|
2009-06-20 04:34:26 +03:00
|
|
|
stream = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stream < 0)
|
|
|
|
// No video stream in that file
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Find the decoder for the video stream
|
2022-05-10 18:25:48 +02:00
|
|
|
codec = avcodec_find_decoder(format->streams[stream]->codecpar->codec_id);
|
2009-06-20 04:34:26 +03:00
|
|
|
|
2013-06-26 14:18:27 +03:00
|
|
|
if (codec == nullptr)
|
2012-05-13 18:04:21 +03:00
|
|
|
{
|
2009-06-20 04:34:26 +03:00
|
|
|
// Unsupported codec
|
|
|
|
return false;
|
|
|
|
}
|
2012-09-15 22:16:16 +03:00
|
|
|
|
2022-05-10 18:25:48 +02:00
|
|
|
codecContext = avcodec_alloc_context3(codec);
|
2022-05-14 11:11:57 +02:00
|
|
|
if(!codecContext)
|
|
|
|
return false;
|
2022-05-10 18:25:48 +02:00
|
|
|
// Get a pointer to the codec context for the video stream
|
|
|
|
int ret = avcodec_parameters_to_context(codecContext, format->streams[stream]->codecpar);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
//We cannot get codec from parameters
|
2022-05-14 11:11:57 +02:00
|
|
|
avcodec_free_context(&codecContext);
|
2022-05-10 18:25:48 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2009-06-20 04:34:26 +03:00
|
|
|
// Open codec
|
2013-06-26 14:18:27 +03:00
|
|
|
if ( avcodec_open2(codecContext, codec, nullptr) < 0 )
|
2012-05-13 18:04:21 +03:00
|
|
|
{
|
2009-06-20 04:34:26 +03:00
|
|
|
// Could not open codec
|
2013-06-26 14:18:27 +03:00
|
|
|
codec = nullptr;
|
2009-06-20 04:34:26 +03:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Allocate video frame
|
2015-11-11 10:41:28 +02:00
|
|
|
frame = av_frame_alloc();
|
2016-11-27 16:48:18 +02:00
|
|
|
|
2014-07-08 17:20:22 +03:00
|
|
|
//setup scaling
|
2014-07-10 13:18:21 +03:00
|
|
|
if(scale)
|
2014-07-08 17:20:22 +03:00
|
|
|
{
|
2016-11-27 16:48:18 +02:00
|
|
|
pos.w = screen->w;
|
2014-07-08 17:20:22 +03:00
|
|
|
pos.h = screen->h;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2016-11-27 16:48:18 +02:00
|
|
|
pos.w = codecContext->width;
|
|
|
|
pos.h = codecContext->height;
|
2014-07-08 17:20:22 +03:00
|
|
|
}
|
2009-06-20 04:34:26 +03:00
|
|
|
|
|
|
|
// Allocate a place to put our YUV image on that screen
|
2012-05-13 18:04:21 +03:00
|
|
|
if (useOverlay)
|
|
|
|
{
|
2014-07-08 17:20:22 +03:00
|
|
|
texture = SDL_CreateTexture( mainRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STATIC, pos.w, pos.h);
|
2012-05-13 18:04:21 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2014-07-08 17:20:22 +03:00
|
|
|
dest = CSDL_Ext::newSurface(pos.w, pos.h);
|
2009-06-28 02:33:59 +03:00
|
|
|
destRect.x = destRect.y = 0;
|
2014-07-08 17:20:22 +03:00
|
|
|
destRect.w = pos.w;
|
|
|
|
destRect.h = pos.h;
|
2009-06-28 02:33:59 +03:00
|
|
|
}
|
2009-06-20 04:34:26 +03:00
|
|
|
|
2014-05-24 20:12:07 +03:00
|
|
|
if (texture == nullptr && dest == nullptr)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (texture)
|
|
|
|
{ // Convert the image into YUV format that SDL uses
|
2016-11-27 16:48:18 +02:00
|
|
|
sws = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt,
|
2015-11-05 11:53:44 +02:00
|
|
|
pos.w, pos.h,
|
|
|
|
AV_PIX_FMT_YUV420P,
|
2014-07-08 17:20:22 +03:00
|
|
|
SWS_BICUBIC, nullptr, nullptr, nullptr);
|
2012-05-13 18:04:21 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2015-11-05 11:53:44 +02:00
|
|
|
AVPixelFormat screenFormat = AV_PIX_FMT_NONE;
|
2012-12-03 19:00:17 +03:00
|
|
|
if (screen->format->Bshift > screen->format->Rshift)
|
2010-08-26 19:55:42 +03:00
|
|
|
{
|
2012-12-01 09:30:52 +03:00
|
|
|
// this a BGR surface
|
|
|
|
switch (screen->format->BytesPerPixel)
|
|
|
|
{
|
2015-11-05 11:53:44 +02:00
|
|
|
case 2: screenFormat = AV_PIX_FMT_BGR565; break;
|
|
|
|
case 3: screenFormat = AV_PIX_FMT_BGR24; break;
|
|
|
|
case 4: screenFormat = AV_PIX_FMT_BGR32; break;
|
2012-12-01 09:30:52 +03:00
|
|
|
default: return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// this a RGB surface
|
|
|
|
switch (screen->format->BytesPerPixel)
|
|
|
|
{
|
2015-11-05 11:53:44 +02:00
|
|
|
case 2: screenFormat = AV_PIX_FMT_RGB565; break;
|
|
|
|
case 3: screenFormat = AV_PIX_FMT_RGB24; break;
|
|
|
|
case 4: screenFormat = AV_PIX_FMT_RGB32; break;
|
2012-12-01 09:30:52 +03:00
|
|
|
default: return false;
|
|
|
|
}
|
2010-08-26 19:55:42 +03:00
|
|
|
}
|
|
|
|
|
2016-11-27 16:48:18 +02:00
|
|
|
sws = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt,
|
|
|
|
pos.w, pos.h, screenFormat,
|
2014-07-08 17:20:22 +03:00
|
|
|
SWS_BICUBIC, nullptr, nullptr, nullptr);
|
2009-06-28 02:33:59 +03:00
|
|
|
}
|
|
|
|
|
2013-06-26 14:18:27 +03:00
|
|
|
if (sws == nullptr)
|
2009-06-20 04:34:26 +03:00
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2009-06-28 02:33:59 +03:00
|
|
|
// Read the next frame. Return false on error/end of file.
|
2009-06-20 04:34:26 +03:00
|
|
|
bool CVideoPlayer::nextFrame()
|
|
|
|
{
|
|
|
|
AVPacket packet;
|
|
|
|
int frameFinished = 0;
|
2009-06-28 02:33:59 +03:00
|
|
|
bool gotError = false;
|
2009-06-20 04:34:26 +03:00
|
|
|
|
2013-06-26 14:18:27 +03:00
|
|
|
if (sws == nullptr)
|
2009-06-28 02:33:59 +03:00
|
|
|
return false;
|
2009-06-20 04:34:26 +03:00
|
|
|
|
2012-05-13 18:04:21 +03:00
|
|
|
while(!frameFinished)
|
|
|
|
{
|
2009-06-28 02:33:59 +03:00
|
|
|
int ret = av_read_frame(format, &packet);
|
2012-05-13 18:04:21 +03:00
|
|
|
if (ret < 0)
|
|
|
|
{
|
2009-06-28 02:33:59 +03:00
|
|
|
// Error. It's probably an end of file.
|
2012-05-13 18:04:21 +03:00
|
|
|
if (doLoop && !gotError)
|
|
|
|
{
|
2009-06-28 02:33:59 +03:00
|
|
|
// Rewind
|
2023-01-27 01:52:40 +02:00
|
|
|
frameTime = 0;
|
2015-01-25 15:28:11 +02:00
|
|
|
if (av_seek_frame(format, stream, 0, AVSEEK_FLAG_BYTE) < 0)
|
2009-06-28 02:33:59 +03:00
|
|
|
break;
|
|
|
|
gotError = true;
|
2012-05-13 18:04:21 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2009-06-28 02:33:59 +03:00
|
|
|
break;
|
|
|
|
}
|
2012-05-13 18:04:21 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2009-06-28 02:33:59 +03:00
|
|
|
// Is this a packet from the video stream?
|
2012-05-13 18:04:21 +03:00
|
|
|
if (packet.stream_index == stream)
|
|
|
|
{
|
2009-06-28 02:33:59 +03:00
|
|
|
// Decode video frame
|
2022-05-10 18:25:48 +02:00
|
|
|
int rc = avcodec_send_packet(codecContext, &packet);
|
|
|
|
if (rc >=0)
|
|
|
|
packet.size = 0;
|
|
|
|
rc = avcodec_receive_frame(codecContext, frame);
|
|
|
|
if (rc >= 0)
|
|
|
|
frameFinished = 1;
|
2009-06-28 02:33:59 +03:00
|
|
|
// Did we get a video frame?
|
2012-05-13 18:04:21 +03:00
|
|
|
if (frameFinished)
|
|
|
|
{
|
2022-05-10 18:25:48 +02:00
|
|
|
uint8_t *data[4];
|
|
|
|
int linesize[4];
|
2009-06-28 02:33:59 +03:00
|
|
|
|
2014-05-24 20:12:07 +03:00
|
|
|
if (texture) {
|
2022-05-10 18:25:48 +02:00
|
|
|
av_image_alloc(data, linesize, pos.w, pos.h, AV_PIX_FMT_YUV420P, 1);
|
2014-05-24 20:12:07 +03:00
|
|
|
|
|
|
|
sws_scale(sws, frame->data, frame->linesize,
|
2022-05-10 18:25:48 +02:00
|
|
|
0, codecContext->height, data, linesize);
|
2014-05-24 20:12:07 +03:00
|
|
|
|
2022-05-10 18:25:48 +02:00
|
|
|
SDL_UpdateYUVTexture(texture, NULL, data[0], linesize[0],
|
|
|
|
data[1], linesize[1],
|
|
|
|
data[2], linesize[2]);
|
|
|
|
av_freep(&data[0]);
|
2012-05-13 18:04:21 +03:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
client/CVideoHandler.cpp: fix crash on video playback
Avoid buffer overflow caused by sws_scale():
http://trac.ffmpeg.org/ticket/9254
Currently (ffmpeg-4.4 with SSE3 enabled) sws_scale()
has a few requirements for target data buffers on rescaling:
1. buffer has to be aligned to be usable for SIMD instructions
2. buffer has to be padded to allow small overflow by SIMD instructions
Unfortunately SDL_Surface does not provide these guarantees.
This means that atempt to rescale directly into SDL surface causes
memory corruption. Usually it happens on campaign selection screen
where short video moves start spinning on mouse hover.
To fix [1.] we use av_malloc() for memory allocation.
To fix [2.] we add an `ffmpeg_pad` that provides plenty of space.
We have to use intermdiate buffer and then use memcpy() to land it
to SDL_Surface.
Without the change crash has the following backtrace:
```
(gdb) bt
(c=0x47508940, src=0x1ffeffef50, srcStride=0x1ffeffef30, srcSliceY=0, srcSliceH=116, dst=0x1ffeffef70, dstStride=0x1ffeffef40) at src/libswscale/x86/yuv2rgb_template.c:119
(c=<optimized out>, srcSlice=<optimized out>, srcStride=0x432afa20, srcSliceY=<optimized out>, srcSliceH=116, dst=<optimized out>, dstStride=0x1ffefff0a0) at src/libswscale/swscale.c:969
(this=0x1abaa330, x=90, y=72, dst=0x1a85a4c0, forceRedraw=<optimized out>, update=<optimized out>)
at ../vcmi-9999/client/CVideoHandler.cpp:332
at ../vcmi-9999/client/gui/CIntObject.cpp:83
at ../vcmi-9999/client/gui/CGuiHandler.cpp:462
```
valgrind points to corruption right in sws_scale():
```
Invalid write of size 8
at 0x6C50BD3: ??? (in /usr/lib64/libswscale.so.5.7.100)
by 0x6C4FAE6: yuv420_rgb32_ssse3 (yuv2rgb_template.c:119)
by 0x6C28DF2: sws_scale (swscale.c:969)
by 0x4566F6: CVideoPlayer::nextFrame() (CVideoHandler.cpp:293)
by 0x4573A6: CVideoPlayer::update(int, int, SDL_Surface*, bool, bool) (CVideoHandler.cpp:332)
by 0x25EC94: CIntObject::show(SDL_Surface*) [clone .part.0] (CIntObject.cpp:83)
by 0x34E855: CMainMenu::update() (CMainMenu.cpp:319)
by 0x25D589: CGuiHandler::renderFrame() (CGuiHandler.cpp:462)
by 0x1F7450: mainLoop (CMT.cpp:1387)
by 0x1F7450: main (CMT.cpp:513)
Address 0x475088a8 is 0 bytes after a block of size 92,840 alloc'd
at 0x483F7E5: malloc (vg_replace_malloc.c:380)
by 0x52B4E23: SDL_malloc_REAL (SDL_malloc.c:5387)
by 0x5266237: SDL_SIMDAlloc_REAL (SDL_cpuinfo.c:963)
by 0x52EF042: SDL_CreateRGBSurfaceWithFormat_REAL (SDL_surface.c:123)
by 0x2649AC: CSDL_Ext::newSurface(int, int, SDL_Surface*) (SDL_Extensions.cpp:42)
by 0x457B20: CVideoPlayer::open(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, bool, bool) (CVideoHandler.cpp:182)
by 0x457C60: CVideoPlayer::open(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool) (CVideoHandler.cpp:84)
by 0x35B14E: CCampaignScreen::CCampaignButton::show(SDL_Surface*) (CCampaignScreen.cpp:126)
by 0x25EC94: CIntObject::show(SDL_Surface*) [clone .part.0] (CIntObject.cpp:83)
by 0x34E855: CMainMenu::update() (CMainMenu.cpp:319)
by 0x25D589: CGuiHandler::renderFrame() (CGuiHandler.cpp:462)
by 0x1F7450: mainLoop (CMT.cpp:1387)
by 0x1F7450: main (CMT.cpp:513)
```
Signed-off-by: Sergei Trofimovich <slyfox@gentoo.org>
2021-07-25 12:25:37 +02:00
|
|
|
/* Avoid buffer overflow caused by sws_scale():
|
|
|
|
* http://trac.ffmpeg.org/ticket/9254
|
|
|
|
* Currently (ffmpeg-4.4 with SSE3 enabled) sws_scale()
|
|
|
|
* has a few requirements for target data buffers on rescaling:
|
|
|
|
* 1. buffer has to be aligned to be usable for SIMD instructions
|
|
|
|
* 2. buffer has to be padded to allow small overflow by SIMD instructions
|
|
|
|
* Unfortunately SDL_Surface does not provide these guarantees.
|
|
|
|
* This means that atempt to rescale directly into SDL surface causes
|
|
|
|
* memory corruption. Usually it happens on campaign selection screen
|
|
|
|
* where short video moves start spinning on mouse hover.
|
|
|
|
*
|
|
|
|
* To fix [1.] we use av_malloc() for memory allocation.
|
|
|
|
* To fix [2.] we add an `ffmpeg_pad` that provides plenty of space.
|
|
|
|
* We have to use intermdiate buffer and then use memcpy() to land it
|
|
|
|
* to SDL_Surface.
|
|
|
|
*/
|
|
|
|
size_t pic_bytes = dest->pitch * dest->h;
|
|
|
|
size_t ffmped_pad = 1024; /* a few bytes of overflow will go here */
|
|
|
|
void * for_sws = av_malloc (pic_bytes + ffmped_pad);
|
2022-05-10 18:25:48 +02:00
|
|
|
data[0] = (ui8 *)for_sws;
|
|
|
|
linesize[0] = dest->pitch;
|
2009-06-28 02:33:59 +03:00
|
|
|
|
|
|
|
sws_scale(sws, frame->data, frame->linesize,
|
2022-05-10 18:25:48 +02:00
|
|
|
0, codecContext->height, data, linesize);
|
client/CVideoHandler.cpp: fix crash on video playback
Avoid buffer overflow caused by sws_scale():
http://trac.ffmpeg.org/ticket/9254
Currently (ffmpeg-4.4 with SSE3 enabled) sws_scale()
has a few requirements for target data buffers on rescaling:
1. buffer has to be aligned to be usable for SIMD instructions
2. buffer has to be padded to allow small overflow by SIMD instructions
Unfortunately SDL_Surface does not provide these guarantees.
This means that atempt to rescale directly into SDL surface causes
memory corruption. Usually it happens on campaign selection screen
where short video moves start spinning on mouse hover.
To fix [1.] we use av_malloc() for memory allocation.
To fix [2.] we add an `ffmpeg_pad` that provides plenty of space.
We have to use intermdiate buffer and then use memcpy() to land it
to SDL_Surface.
Without the change crash has the following backtrace:
```
(gdb) bt
(c=0x47508940, src=0x1ffeffef50, srcStride=0x1ffeffef30, srcSliceY=0, srcSliceH=116, dst=0x1ffeffef70, dstStride=0x1ffeffef40) at src/libswscale/x86/yuv2rgb_template.c:119
(c=<optimized out>, srcSlice=<optimized out>, srcStride=0x432afa20, srcSliceY=<optimized out>, srcSliceH=116, dst=<optimized out>, dstStride=0x1ffefff0a0) at src/libswscale/swscale.c:969
(this=0x1abaa330, x=90, y=72, dst=0x1a85a4c0, forceRedraw=<optimized out>, update=<optimized out>)
at ../vcmi-9999/client/CVideoHandler.cpp:332
at ../vcmi-9999/client/gui/CIntObject.cpp:83
at ../vcmi-9999/client/gui/CGuiHandler.cpp:462
```
valgrind points to corruption right in sws_scale():
```
Invalid write of size 8
at 0x6C50BD3: ??? (in /usr/lib64/libswscale.so.5.7.100)
by 0x6C4FAE6: yuv420_rgb32_ssse3 (yuv2rgb_template.c:119)
by 0x6C28DF2: sws_scale (swscale.c:969)
by 0x4566F6: CVideoPlayer::nextFrame() (CVideoHandler.cpp:293)
by 0x4573A6: CVideoPlayer::update(int, int, SDL_Surface*, bool, bool) (CVideoHandler.cpp:332)
by 0x25EC94: CIntObject::show(SDL_Surface*) [clone .part.0] (CIntObject.cpp:83)
by 0x34E855: CMainMenu::update() (CMainMenu.cpp:319)
by 0x25D589: CGuiHandler::renderFrame() (CGuiHandler.cpp:462)
by 0x1F7450: mainLoop (CMT.cpp:1387)
by 0x1F7450: main (CMT.cpp:513)
Address 0x475088a8 is 0 bytes after a block of size 92,840 alloc'd
at 0x483F7E5: malloc (vg_replace_malloc.c:380)
by 0x52B4E23: SDL_malloc_REAL (SDL_malloc.c:5387)
by 0x5266237: SDL_SIMDAlloc_REAL (SDL_cpuinfo.c:963)
by 0x52EF042: SDL_CreateRGBSurfaceWithFormat_REAL (SDL_surface.c:123)
by 0x2649AC: CSDL_Ext::newSurface(int, int, SDL_Surface*) (SDL_Extensions.cpp:42)
by 0x457B20: CVideoPlayer::open(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool, bool, bool) (CVideoHandler.cpp:182)
by 0x457C60: CVideoPlayer::open(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, bool) (CVideoHandler.cpp:84)
by 0x35B14E: CCampaignScreen::CCampaignButton::show(SDL_Surface*) (CCampaignScreen.cpp:126)
by 0x25EC94: CIntObject::show(SDL_Surface*) [clone .part.0] (CIntObject.cpp:83)
by 0x34E855: CMainMenu::update() (CMainMenu.cpp:319)
by 0x25D589: CGuiHandler::renderFrame() (CGuiHandler.cpp:462)
by 0x1F7450: mainLoop (CMT.cpp:1387)
by 0x1F7450: main (CMT.cpp:513)
```
Signed-off-by: Sergei Trofimovich <slyfox@gentoo.org>
2021-07-25 12:25:37 +02:00
|
|
|
memcpy(dest->pixels, for_sws, pic_bytes);
|
|
|
|
av_free(for_sws);
|
2009-06-28 02:33:59 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2009-06-20 04:34:26 +03:00
|
|
|
|
2022-05-10 18:25:48 +02:00
|
|
|
av_packet_unref(&packet);
|
2009-06-28 02:33:59 +03:00
|
|
|
}
|
|
|
|
}
|
2009-06-20 04:34:26 +03:00
|
|
|
|
2009-06-28 02:33:59 +03:00
|
|
|
return frameFinished != 0;
|
|
|
|
}
|
2009-06-20 04:34:26 +03:00
|
|
|
|
2009-06-28 02:33:59 +03:00
|
|
|
void CVideoPlayer::show( int x, int y, SDL_Surface *dst, bool update )
|
|
|
|
{
|
2013-06-26 14:18:27 +03:00
|
|
|
if (sws == nullptr)
|
2009-06-28 02:33:59 +03:00
|
|
|
return;
|
2009-06-20 04:34:26 +03:00
|
|
|
|
2009-06-28 02:33:59 +03:00
|
|
|
pos.x = x;
|
|
|
|
pos.y = y;
|
2023-01-17 22:01:35 +02:00
|
|
|
CSDL_Ext::blitSurface(dest, destRect, dst, pos.topLeft());
|
2012-09-15 22:16:16 +03:00
|
|
|
|
2009-06-28 02:33:59 +03:00
|
|
|
if (update)
|
2023-01-17 22:01:35 +02:00
|
|
|
CSDL_Ext::updateRect(dst, pos);
|
2009-06-28 02:33:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void CVideoPlayer::redraw( int x, int y, SDL_Surface *dst, bool update )
|
|
|
|
{
|
|
|
|
show(x, y, dst, update);
|
|
|
|
}
|
2009-06-20 04:34:26 +03:00
|
|
|
|
2023-09-23 20:41:30 +02:00
|
|
|
void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, bool update, std::function<void()> onVideoRestart)
|
2009-06-28 02:33:59 +03:00
|
|
|
{
|
2013-06-26 14:18:27 +03:00
|
|
|
if (sws == nullptr)
|
2009-06-28 02:33:59 +03:00
|
|
|
return;
|
|
|
|
|
2023-03-10 23:11:17 +02:00
|
|
|
#if (LIBAVUTIL_VERSION_MAJOR < 58)
|
|
|
|
auto packet_duration = frame->pkt_duration;
|
|
|
|
#else
|
|
|
|
auto packet_duration = frame->duration;
|
|
|
|
#endif
|
|
|
|
double frameEndTime = (frame->pts + packet_duration) * av_q2d(format->streams[stream]->time_base);
|
2023-05-18 19:32:29 +02:00
|
|
|
frameTime += GH.framerate().getElapsedMilliseconds() / 1000.0;
|
2023-01-27 01:52:40 +02:00
|
|
|
|
|
|
|
if (frameTime >= frameEndTime )
|
2009-06-28 02:33:59 +03:00
|
|
|
{
|
|
|
|
if (nextFrame())
|
|
|
|
show(x,y,dst,update);
|
2012-05-13 18:04:21 +03:00
|
|
|
else
|
|
|
|
{
|
2023-09-23 20:41:30 +02:00
|
|
|
if(onVideoRestart)
|
|
|
|
onVideoRestart();
|
2023-09-16 23:14:51 +02:00
|
|
|
VideoPath filenameToReopen = fname; // create copy to backup this->fname
|
|
|
|
open(filenameToReopen);
|
2012-09-15 22:16:16 +03:00
|
|
|
nextFrame();
|
|
|
|
|
2011-04-26 16:30:29 +03:00
|
|
|
// The y position is wrong at the first frame.
|
|
|
|
// Note: either the windows player or the linux player is
|
2012-09-15 22:16:16 +03:00
|
|
|
// broken. Compensate here until the bug is found.
|
2011-04-26 16:30:29 +03:00
|
|
|
show(x, y--, dst, update);
|
|
|
|
}
|
2012-09-15 22:16:16 +03:00
|
|
|
}
|
2012-05-13 18:04:21 +03:00
|
|
|
else
|
|
|
|
{
|
2009-06-28 02:33:59 +03:00
|
|
|
redraw(x, y, dst, update);
|
2009-06-20 04:34:26 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void CVideoPlayer::close()
|
|
|
|
{
|
2023-09-01 23:57:25 +02:00
|
|
|
fname = VideoPath();
|
|
|
|
|
2012-05-13 18:04:21 +03:00
|
|
|
if (sws)
|
|
|
|
{
|
2009-06-20 04:34:26 +03:00
|
|
|
sws_freeContext(sws);
|
2013-06-26 14:18:27 +03:00
|
|
|
sws = nullptr;
|
2009-06-20 04:34:26 +03:00
|
|
|
}
|
|
|
|
|
2014-05-24 20:12:07 +03:00
|
|
|
if (texture)
|
|
|
|
{
|
|
|
|
SDL_DestroyTexture(texture);
|
|
|
|
texture = nullptr;
|
|
|
|
}
|
|
|
|
|
2012-05-13 18:04:21 +03:00
|
|
|
if (dest)
|
|
|
|
{
|
2009-06-28 02:33:59 +03:00
|
|
|
SDL_FreeSurface(dest);
|
2013-06-26 14:18:27 +03:00
|
|
|
dest = nullptr;
|
2009-06-28 02:33:59 +03:00
|
|
|
}
|
|
|
|
|
2012-05-13 18:04:21 +03:00
|
|
|
if (frame)
|
|
|
|
{
|
2016-11-27 16:48:18 +02:00
|
|
|
av_frame_free(&frame);//will be set to null
|
2009-06-20 04:34:26 +03:00
|
|
|
}
|
|
|
|
|
2012-05-13 18:04:21 +03:00
|
|
|
if (codec)
|
|
|
|
{
|
2009-06-20 04:34:26 +03:00
|
|
|
avcodec_close(codecContext);
|
2013-06-26 14:18:27 +03:00
|
|
|
codec = nullptr;
|
2022-05-10 18:25:48 +02:00
|
|
|
}
|
|
|
|
if (codecContext)
|
|
|
|
{
|
|
|
|
avcodec_free_context(&codecContext);
|
2009-06-20 04:34:26 +03:00
|
|
|
}
|
|
|
|
|
2012-05-13 18:04:21 +03:00
|
|
|
if (format)
|
|
|
|
{
|
|
|
|
avformat_close_input(&format);
|
2009-06-20 04:34:26 +03:00
|
|
|
}
|
2012-08-21 20:37:06 +03:00
|
|
|
|
|
|
|
if (context)
|
|
|
|
{
|
|
|
|
av_free(context);
|
|
|
|
context = nullptr;
|
|
|
|
}
|
2009-06-20 04:34:26 +03:00
|
|
|
}
|
2009-06-28 02:33:59 +03:00
|
|
|
|
2023-10-07 20:09:40 +02:00
|
|
|
std::pair<std::unique_ptr<ui8 []>, si64> CVideoPlayer::getAudio(const VideoPath & videoToOpen)
|
2023-10-07 17:19:10 +02:00
|
|
|
{
|
2023-10-07 20:09:40 +02:00
|
|
|
std::pair<std::unique_ptr<ui8 []>, si64> dat(std::make_pair(std::make_unique<ui8[]>(0), 0));
|
|
|
|
|
|
|
|
VideoPath fnameAudio;
|
|
|
|
|
|
|
|
if (CResourceHandler::get()->existsResource(videoToOpen))
|
|
|
|
fnameAudio = videoToOpen;
|
|
|
|
else
|
|
|
|
fnameAudio = videoToOpen.addPrefix("VIDEO/");
|
|
|
|
|
|
|
|
if (!CResourceHandler::get()->existsResource(fnameAudio))
|
|
|
|
{
|
|
|
|
logGlobal->error("Error: video %s was not found", fnameAudio.getName());
|
|
|
|
return dat;
|
|
|
|
}
|
|
|
|
|
|
|
|
dataAudio = CResourceHandler::get()->load(fnameAudio);
|
|
|
|
|
|
|
|
static const int BUFFER_SIZE = 4096;
|
|
|
|
|
|
|
|
unsigned char * bufferAudio = (unsigned char *)av_malloc(BUFFER_SIZE);// will be freed by ffmpeg
|
|
|
|
AVIOContext * contextAudio = avio_alloc_context( bufferAudio, BUFFER_SIZE, 0, (void *)this, lodReadAudio, nullptr, lodSeekAudio);
|
|
|
|
|
|
|
|
AVFormatContext * formatAudio = avformat_alloc_context();
|
|
|
|
formatAudio->pb = contextAudio;
|
|
|
|
// filename is not needed - file was already open and stored in this->data;
|
|
|
|
int avfopen = avformat_open_input(&formatAudio, "dummyFilename", nullptr, nullptr);
|
|
|
|
|
|
|
|
if (avfopen != 0)
|
|
|
|
{
|
|
|
|
return dat;
|
|
|
|
}
|
|
|
|
// Retrieve stream information
|
|
|
|
if (avformat_find_stream_info(formatAudio, nullptr) < 0)
|
|
|
|
return dat;
|
|
|
|
|
|
|
|
// Find the first audio stream
|
|
|
|
int streamAudio = -1;
|
|
|
|
for(ui32 i=0; i<formatAudio->nb_streams; i++)
|
|
|
|
{
|
|
|
|
if (formatAudio->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
|
|
|
|
{
|
|
|
|
streamAudio = i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(streamAudio < 0)
|
|
|
|
return dat;
|
|
|
|
|
|
|
|
const AVCodec *codecAudio = avcodec_find_decoder(formatAudio->streams[streamAudio]->codecpar->codec_id);
|
|
|
|
|
|
|
|
AVCodecContext *codecContextAudio;
|
|
|
|
if (codecAudio != nullptr)
|
|
|
|
codecContextAudio = avcodec_alloc_context3(codecAudio);
|
|
|
|
|
|
|
|
// Get a pointer to the codec context for the audio stream
|
|
|
|
if (streamAudio > -1)
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
ret = avcodec_parameters_to_context(codecContextAudio, formatAudio->streams[streamAudio]->codecpar);
|
|
|
|
if (ret < 0)
|
|
|
|
{
|
|
|
|
//We cannot get codec from parameters
|
|
|
|
avcodec_free_context(&codecContextAudio);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open codec
|
|
|
|
AVFrame *frameAudio;
|
|
|
|
if (codecAudio != nullptr)
|
|
|
|
{
|
|
|
|
if ( avcodec_open2(codecContextAudio, codecAudio, nullptr) < 0 )
|
|
|
|
{
|
|
|
|
// Could not open codec
|
|
|
|
codecAudio = nullptr;
|
|
|
|
}
|
|
|
|
// Allocate audio frame
|
|
|
|
frameAudio = av_frame_alloc();
|
|
|
|
}
|
|
|
|
|
2023-10-07 17:19:10 +02:00
|
|
|
AVPacket packet;
|
|
|
|
|
2023-10-07 20:09:40 +02:00
|
|
|
std::vector<ui8> samples;
|
|
|
|
while (av_read_frame(formatAudio, &packet) >= 0)
|
2023-10-07 17:19:10 +02:00
|
|
|
{
|
|
|
|
avcodec_send_packet(codecContextAudio, &packet);
|
|
|
|
avcodec_receive_frame(codecContextAudio, frameAudio);
|
|
|
|
|
|
|
|
for (int s = 0; s < frameAudio->linesize[0]; s+=sizeof(ui8))
|
|
|
|
{
|
|
|
|
ui8 value;
|
|
|
|
memcpy(&value, &frameAudio->data[0][s], sizeof(ui8));
|
|
|
|
samples.push_back(value);
|
|
|
|
}
|
|
|
|
}
|
2023-10-07 20:09:40 +02:00
|
|
|
|
|
|
|
dat = std::pair<std::unique_ptr<ui8 []>, si64>(std::make_pair(std::make_unique<ui8[]>(samples.size()), samples.size()));
|
|
|
|
std::copy(samples.begin(), samples.end(), dat.first.get());
|
|
|
|
|
|
|
|
if (frameAudio)
|
|
|
|
av_frame_free(&frameAudio);
|
|
|
|
|
|
|
|
if (codecAudio)
|
|
|
|
{
|
|
|
|
avcodec_close(codecContextAudio);
|
|
|
|
codecAudio = nullptr;
|
|
|
|
}
|
|
|
|
if (codecContextAudio)
|
|
|
|
avcodec_free_context(&codecContextAudio);
|
|
|
|
|
|
|
|
if (formatAudio)
|
|
|
|
avformat_close_input(&formatAudio);
|
|
|
|
|
|
|
|
if (contextAudio)
|
|
|
|
{
|
|
|
|
av_free(contextAudio);
|
|
|
|
contextAudio = nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
return dat;
|
2023-10-07 17:19:10 +02:00
|
|
|
}
|
|
|
|
|
2009-06-28 02:33:59 +03:00
|
|
|
// Plays a video. Only works for overlays.
|
2018-07-25 00:36:48 +02:00
|
|
|
bool CVideoPlayer::playVideo(int x, int y, bool stopOnKey)
|
2009-06-28 02:33:59 +03:00
|
|
|
{
|
|
|
|
// Note: either the windows player or the linux player is
|
|
|
|
// broken. Compensate here until the bug is found.
|
|
|
|
y--;
|
|
|
|
|
|
|
|
pos.x = x;
|
|
|
|
pos.y = y;
|
2023-05-12 23:15:48 +02:00
|
|
|
frameTime = 0.0;
|
2009-06-28 02:33:59 +03:00
|
|
|
|
2012-05-13 18:04:21 +03:00
|
|
|
while(nextFrame())
|
|
|
|
{
|
2023-05-20 00:30:15 +02:00
|
|
|
if(stopOnKey)
|
|
|
|
{
|
|
|
|
GH.input().fetchEvents();
|
|
|
|
if(GH.input().ignoreEventsUntilInput())
|
|
|
|
return false;
|
|
|
|
}
|
2009-06-28 02:33:59 +03:00
|
|
|
|
2023-01-18 15:50:52 +02:00
|
|
|
SDL_Rect rect = CSDL_Ext::toSDL(pos);
|
2023-01-17 22:01:35 +02:00
|
|
|
|
|
|
|
SDL_RenderCopy(mainRenderer, texture, nullptr, &rect);
|
2014-05-24 20:12:07 +03:00
|
|
|
SDL_RenderPresent(mainRenderer);
|
2009-06-28 02:33:59 +03:00
|
|
|
|
2023-05-12 23:15:48 +02:00
|
|
|
#if (LIBAVUTIL_VERSION_MAJOR < 58)
|
|
|
|
auto packet_duration = frame->pkt_duration;
|
|
|
|
#else
|
|
|
|
auto packet_duration = frame->duration;
|
|
|
|
#endif
|
|
|
|
double frameDurationSec = packet_duration * av_q2d(format->streams[stream]->time_base);
|
|
|
|
uint32_t timeToSleepMillisec = 1000 * (frameDurationSec);
|
|
|
|
|
2023-08-20 22:45:41 +02:00
|
|
|
boost::this_thread::sleep_for(boost::chrono::milliseconds(timeToSleepMillisec));
|
2009-06-28 02:33:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-09-01 23:57:25 +02:00
|
|
|
bool CVideoPlayer::openAndPlayVideo(const VideoPath & name, int x, int y, bool stopOnKey, bool scale)
|
2009-06-28 02:33:59 +03:00
|
|
|
{
|
2014-07-10 13:18:21 +03:00
|
|
|
open(name, false, true, scale);
|
2018-07-25 00:36:48 +02:00
|
|
|
bool ret = playVideo(x, y, stopOnKey);
|
2009-06-28 02:33:59 +03:00
|
|
|
close();
|
|
|
|
return ret;
|
|
|
|
}
|
2012-09-15 22:16:16 +03:00
|
|
|
|
2009-06-20 04:34:26 +03:00
|
|
|
CVideoPlayer::~CVideoPlayer()
|
|
|
|
{
|
|
|
|
close();
|
|
|
|
}
|
|
|
|
|
2011-12-14 00:23:17 +03:00
|
|
|
#endif
|
2012-08-18 13:29:54 +03:00
|
|
|
|