CVideoHandler.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672
  1. /*
  2. * CVideoHandler.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "CVideoHandler.h"
  12. #include "CMT.h"
  13. #include "gui/CGuiHandler.h"
  14. #include "eventsSDL/InputHandler.h"
  15. #include "gui/FramerateManager.h"
  16. #include "renderSDL/SDL_Extensions.h"
  17. #include "CPlayerInterface.h"
  18. #include "../lib/filesystem/Filesystem.h"
  19. #include "../lib/filesystem/CInputStream.h"
  20. #include <SDL_render.h>
  21. #ifndef DISABLE_VIDEO
  22. extern "C" {
  23. #include <libavformat/avformat.h>
  24. #include <libavcodec/avcodec.h>
  25. #include <libavutil/imgutils.h>
  26. #include <libswscale/swscale.h>
  27. }
  28. #ifdef _MSC_VER
  29. #pragma comment(lib, "avcodec.lib")
  30. #pragma comment(lib, "avutil.lib")
  31. #pragma comment(lib, "avformat.lib")
  32. #pragma comment(lib, "swscale.lib")
  33. #endif // _MSC_VER
  34. // Define a set of functions to read data
  35. static int lodRead(void* opaque, uint8_t* buf, int size)
  36. {
  37. auto video = reinterpret_cast<CVideoPlayer *>(opaque);
  38. int bytes = static_cast<int>(video->data->read(buf, size));
  39. if(bytes == 0)
  40. return AVERROR_EOF;
  41. return bytes;
  42. }
  43. static si64 lodSeek(void * opaque, si64 pos, int whence)
  44. {
  45. auto video = reinterpret_cast<CVideoPlayer *>(opaque);
  46. if (whence & AVSEEK_SIZE)
  47. return video->data->getSize();
  48. return video->data->seek(pos);
  49. }
  50. // Define a set of functions to read data
  51. static int lodReadAudio(void* opaque, uint8_t* buf, int size)
  52. {
  53. auto video = reinterpret_cast<CVideoPlayer *>(opaque);
  54. int bytes = static_cast<int>(video->dataAudio->read(buf, size));
  55. if(bytes == 0)
  56. return AVERROR_EOF;
  57. return bytes;
  58. }
  59. static si64 lodSeekAudio(void * opaque, si64 pos, int whence)
  60. {
  61. auto video = reinterpret_cast<CVideoPlayer *>(opaque);
  62. if (whence & AVSEEK_SIZE)
  63. return video->dataAudio->getSize();
  64. return video->dataAudio->seek(pos);
  65. }
  66. CVideoPlayer::CVideoPlayer()
  67. : stream(-1)
  68. , format (nullptr)
  69. , codecContext(nullptr)
  70. , codec(nullptr)
  71. , frame(nullptr)
  72. , sws(nullptr)
  73. , context(nullptr)
  74. , texture(nullptr)
  75. , dest(nullptr)
  76. , destRect(0,0,0,0)
  77. , pos(0,0,0,0)
  78. , frameTime(0)
  79. , doLoop(false)
  80. {}
  81. bool CVideoPlayer::open(const VideoPath & fname, bool scale)
  82. {
  83. return open(fname, true, false);
  84. }
  85. // loop = to loop through the video
  86. // useOverlay = directly write to the screen.
  87. bool CVideoPlayer::open(const VideoPath & videoToOpen, bool loop, bool useOverlay, bool scale)
  88. {
  89. close();
  90. doLoop = loop;
  91. frameTime = 0;
  92. if (CResourceHandler::get()->existsResource(videoToOpen))
  93. fname = videoToOpen;
  94. else
  95. fname = videoToOpen.addPrefix("VIDEO/");
  96. if (!CResourceHandler::get()->existsResource(fname))
  97. {
  98. logGlobal->error("Error: video %s was not found", fname.getName());
  99. return false;
  100. }
  101. data = CResourceHandler::get()->load(fname);
  102. static const int BUFFER_SIZE = 4096;
  103. unsigned char * buffer = (unsigned char *)av_malloc(BUFFER_SIZE);// will be freed by ffmpeg
  104. context = avio_alloc_context( buffer, BUFFER_SIZE, 0, (void *)this, lodRead, nullptr, lodSeek);
  105. format = avformat_alloc_context();
  106. format->pb = context;
  107. // filename is not needed - file was already open and stored in this->data;
  108. int avfopen = avformat_open_input(&format, "dummyFilename", nullptr, nullptr);
  109. if (avfopen != 0)
  110. {
  111. return false;
  112. }
  113. // Retrieve stream information
  114. if (avformat_find_stream_info(format, nullptr) < 0)
  115. return false;
  116. // Find the first video stream
  117. stream = -1;
  118. for(ui32 i=0; i<format->nb_streams; i++)
  119. {
  120. if (format->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
  121. {
  122. stream = i;
  123. break;
  124. }
  125. }
  126. if (stream < 0)
  127. // No video stream in that file
  128. return false;
  129. // Find the decoder for the video stream
  130. codec = avcodec_find_decoder(format->streams[stream]->codecpar->codec_id);
  131. if (codec == nullptr)
  132. {
  133. // Unsupported codec
  134. return false;
  135. }
  136. codecContext = avcodec_alloc_context3(codec);
  137. if(!codecContext)
  138. return false;
  139. // Get a pointer to the codec context for the video stream
  140. int ret = avcodec_parameters_to_context(codecContext, format->streams[stream]->codecpar);
  141. if (ret < 0)
  142. {
  143. //We cannot get codec from parameters
  144. avcodec_free_context(&codecContext);
  145. return false;
  146. }
  147. // Open codec
  148. if ( avcodec_open2(codecContext, codec, nullptr) < 0 )
  149. {
  150. // Could not open codec
  151. codec = nullptr;
  152. return false;
  153. }
  154. // Allocate video frame
  155. frame = av_frame_alloc();
  156. //setup scaling
  157. if(scale)
  158. {
  159. pos.w = screen->w;
  160. pos.h = screen->h;
  161. }
  162. else
  163. {
  164. pos.w = codecContext->width;
  165. pos.h = codecContext->height;
  166. }
  167. // Allocate a place to put our YUV image on that screen
  168. if (useOverlay)
  169. {
  170. texture = SDL_CreateTexture( mainRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STATIC, pos.w, pos.h);
  171. }
  172. else
  173. {
  174. dest = CSDL_Ext::newSurface(pos.w, pos.h);
  175. destRect.x = destRect.y = 0;
  176. destRect.w = pos.w;
  177. destRect.h = pos.h;
  178. }
  179. if (texture == nullptr && dest == nullptr)
  180. return false;
  181. if (texture)
  182. { // Convert the image into YUV format that SDL uses
  183. sws = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt,
  184. pos.w, pos.h,
  185. AV_PIX_FMT_YUV420P,
  186. SWS_BICUBIC, nullptr, nullptr, nullptr);
  187. }
  188. else
  189. {
  190. AVPixelFormat screenFormat = AV_PIX_FMT_NONE;
  191. if (screen->format->Bshift > screen->format->Rshift)
  192. {
  193. // this a BGR surface
  194. switch (screen->format->BytesPerPixel)
  195. {
  196. case 2: screenFormat = AV_PIX_FMT_BGR565; break;
  197. case 3: screenFormat = AV_PIX_FMT_BGR24; break;
  198. case 4: screenFormat = AV_PIX_FMT_BGR32; break;
  199. default: return false;
  200. }
  201. }
  202. else
  203. {
  204. // this a RGB surface
  205. switch (screen->format->BytesPerPixel)
  206. {
  207. case 2: screenFormat = AV_PIX_FMT_RGB565; break;
  208. case 3: screenFormat = AV_PIX_FMT_RGB24; break;
  209. case 4: screenFormat = AV_PIX_FMT_RGB32; break;
  210. default: return false;
  211. }
  212. }
  213. sws = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt,
  214. pos.w, pos.h, screenFormat,
  215. SWS_BICUBIC, nullptr, nullptr, nullptr);
  216. }
  217. if (sws == nullptr)
  218. return false;
  219. return true;
  220. }
  221. // Read the next frame. Return false on error/end of file.
  222. bool CVideoPlayer::nextFrame()
  223. {
  224. AVPacket packet;
  225. int frameFinished = 0;
  226. bool gotError = false;
  227. if (sws == nullptr)
  228. return false;
  229. while(!frameFinished)
  230. {
  231. int ret = av_read_frame(format, &packet);
  232. if (ret < 0)
  233. {
  234. // Error. It's probably an end of file.
  235. if (doLoop && !gotError)
  236. {
  237. // Rewind
  238. frameTime = 0;
  239. if (av_seek_frame(format, stream, 0, AVSEEK_FLAG_BYTE) < 0)
  240. break;
  241. gotError = true;
  242. }
  243. else
  244. {
  245. break;
  246. }
  247. }
  248. else
  249. {
  250. // Is this a packet from the video stream?
  251. if (packet.stream_index == stream)
  252. {
  253. // Decode video frame
  254. int rc = avcodec_send_packet(codecContext, &packet);
  255. if (rc >=0)
  256. packet.size = 0;
  257. rc = avcodec_receive_frame(codecContext, frame);
  258. if (rc >= 0)
  259. frameFinished = 1;
  260. // Did we get a video frame?
  261. if (frameFinished)
  262. {
  263. uint8_t *data[4];
  264. int linesize[4];
  265. if (texture) {
  266. av_image_alloc(data, linesize, pos.w, pos.h, AV_PIX_FMT_YUV420P, 1);
  267. sws_scale(sws, frame->data, frame->linesize,
  268. 0, codecContext->height, data, linesize);
  269. SDL_UpdateYUVTexture(texture, nullptr, data[0], linesize[0],
  270. data[1], linesize[1],
  271. data[2], linesize[2]);
  272. av_freep(&data[0]);
  273. }
  274. else
  275. {
  276. /* Avoid buffer overflow caused by sws_scale():
  277. * http://trac.ffmpeg.org/ticket/9254
  278. * Currently (ffmpeg-4.4 with SSE3 enabled) sws_scale()
  279. * has a few requirements for target data buffers on rescaling:
  280. * 1. buffer has to be aligned to be usable for SIMD instructions
  281. * 2. buffer has to be padded to allow small overflow by SIMD instructions
  282. * Unfortunately SDL_Surface does not provide these guarantees.
  283. * This means that atempt to rescale directly into SDL surface causes
  284. * memory corruption. Usually it happens on campaign selection screen
  285. * where short video moves start spinning on mouse hover.
  286. *
  287. * To fix [1.] we use av_malloc() for memory allocation.
  288. * To fix [2.] we add an `ffmpeg_pad` that provides plenty of space.
  289. * We have to use intermdiate buffer and then use memcpy() to land it
  290. * to SDL_Surface.
  291. */
  292. size_t pic_bytes = dest->pitch * dest->h;
  293. size_t ffmped_pad = 1024; /* a few bytes of overflow will go here */
  294. void * for_sws = av_malloc (pic_bytes + ffmped_pad);
  295. data[0] = (ui8 *)for_sws;
  296. linesize[0] = dest->pitch;
  297. sws_scale(sws, frame->data, frame->linesize,
  298. 0, codecContext->height, data, linesize);
  299. memcpy(dest->pixels, for_sws, pic_bytes);
  300. av_free(for_sws);
  301. }
  302. }
  303. }
  304. av_packet_unref(&packet);
  305. }
  306. }
  307. return frameFinished != 0;
  308. }
  309. void CVideoPlayer::show( int x, int y, SDL_Surface *dst, bool update )
  310. {
  311. if (sws == nullptr)
  312. return;
  313. pos.x = x;
  314. pos.y = y;
  315. CSDL_Ext::blitSurface(dest, destRect, dst, pos.topLeft());
  316. if (update)
  317. CSDL_Ext::updateRect(dst, pos);
  318. }
  319. void CVideoPlayer::redraw( int x, int y, SDL_Surface *dst, bool update )
  320. {
  321. show(x, y, dst, update);
  322. }
  323. void CVideoPlayer::update( int x, int y, SDL_Surface *dst, bool forceRedraw, bool update, std::function<void()> onVideoRestart)
  324. {
  325. if (sws == nullptr)
  326. return;
  327. #if (LIBAVUTIL_VERSION_MAJOR < 58)
  328. auto packet_duration = frame->pkt_duration;
  329. #else
  330. auto packet_duration = frame->duration;
  331. #endif
  332. double frameEndTime = (frame->pts + packet_duration) * av_q2d(format->streams[stream]->time_base);
  333. frameTime += GH.framerate().getElapsedMilliseconds() / 1000.0;
  334. if (frameTime >= frameEndTime )
  335. {
  336. if (nextFrame())
  337. show(x,y,dst,update);
  338. else
  339. {
  340. if(onVideoRestart)
  341. onVideoRestart();
  342. VideoPath filenameToReopen = fname; // create copy to backup this->fname
  343. open(filenameToReopen);
  344. nextFrame();
  345. // The y position is wrong at the first frame.
  346. // Note: either the windows player or the linux player is
  347. // broken. Compensate here until the bug is found.
  348. show(x, y--, dst, update);
  349. }
  350. }
  351. else
  352. {
  353. redraw(x, y, dst, update);
  354. }
  355. }
  356. void CVideoPlayer::close()
  357. {
  358. fname = VideoPath();
  359. if (sws)
  360. {
  361. sws_freeContext(sws);
  362. sws = nullptr;
  363. }
  364. if (texture)
  365. {
  366. SDL_DestroyTexture(texture);
  367. texture = nullptr;
  368. }
  369. if (dest)
  370. {
  371. SDL_FreeSurface(dest);
  372. dest = nullptr;
  373. }
  374. if (frame)
  375. {
  376. av_frame_free(&frame);//will be set to null
  377. }
  378. if (codec)
  379. {
  380. avcodec_close(codecContext);
  381. codec = nullptr;
  382. }
  383. if (codecContext)
  384. {
  385. avcodec_free_context(&codecContext);
  386. }
  387. if (format)
  388. {
  389. avformat_close_input(&format);
  390. }
  391. if (context)
  392. {
  393. av_free(context);
  394. context = nullptr;
  395. }
  396. }
  397. std::pair<std::unique_ptr<ui8 []>, si64> CVideoPlayer::getAudio(const VideoPath & videoToOpen)
  398. {
  399. std::pair<std::unique_ptr<ui8 []>, si64> dat(std::make_pair(nullptr, 0));
  400. VideoPath fnameAudio;
  401. if (CResourceHandler::get()->existsResource(videoToOpen))
  402. fnameAudio = videoToOpen;
  403. else
  404. fnameAudio = videoToOpen.addPrefix("VIDEO/");
  405. if (!CResourceHandler::get()->existsResource(fnameAudio))
  406. {
  407. logGlobal->error("Error: video %s was not found", fnameAudio.getName());
  408. return dat;
  409. }
  410. dataAudio = CResourceHandler::get()->load(fnameAudio);
  411. static const int BUFFER_SIZE = 4096;
  412. unsigned char * bufferAudio = (unsigned char *)av_malloc(BUFFER_SIZE);// will be freed by ffmpeg
  413. AVIOContext * contextAudio = avio_alloc_context( bufferAudio, BUFFER_SIZE, 0, (void *)this, lodReadAudio, nullptr, lodSeekAudio);
  414. AVFormatContext * formatAudio = avformat_alloc_context();
  415. formatAudio->pb = contextAudio;
  416. // filename is not needed - file was already open and stored in this->data;
  417. int avfopen = avformat_open_input(&formatAudio, "dummyFilename", nullptr, nullptr);
  418. if (avfopen != 0)
  419. {
  420. return dat;
  421. }
  422. // Retrieve stream information
  423. if (avformat_find_stream_info(formatAudio, nullptr) < 0)
  424. return dat;
  425. // Find the first audio stream
  426. int streamAudio = -1;
  427. for(ui32 i = 0; i < formatAudio->nb_streams; i++)
  428. {
  429. if (formatAudio->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
  430. {
  431. streamAudio = i;
  432. break;
  433. }
  434. }
  435. if(streamAudio < 0)
  436. return dat;
  437. const AVCodec *codecAudio = avcodec_find_decoder(formatAudio->streams[streamAudio]->codecpar->codec_id);
  438. AVCodecContext *codecContextAudio;
  439. if (codecAudio != nullptr)
  440. codecContextAudio = avcodec_alloc_context3(codecAudio);
  441. // Get a pointer to the codec context for the audio stream
  442. if (streamAudio > -1)
  443. {
  444. int ret = avcodec_parameters_to_context(codecContextAudio, formatAudio->streams[streamAudio]->codecpar);
  445. if (ret < 0)
  446. {
  447. //We cannot get codec from parameters
  448. avcodec_free_context(&codecContextAudio);
  449. }
  450. }
  451. // Open codec
  452. AVFrame *frameAudio;
  453. if (codecAudio != nullptr)
  454. {
  455. if ( avcodec_open2(codecContextAudio, codecAudio, nullptr) < 0 )
  456. {
  457. // Could not open codec
  458. codecAudio = nullptr;
  459. }
  460. // Allocate audio frame
  461. frameAudio = av_frame_alloc();
  462. }
  463. AVPacket packet;
  464. std::vector<ui8> samples;
  465. while (av_read_frame(formatAudio, &packet) >= 0)
  466. {
  467. if(packet.stream_index == streamAudio)
  468. {
  469. int rc = avcodec_send_packet(codecContextAudio, &packet);
  470. if (rc >= 0)
  471. packet.size = 0;
  472. rc = avcodec_receive_frame(codecContextAudio, frameAudio);
  473. int bytesToRead = (frameAudio->nb_samples * 2 * (formatAudio->streams[streamAudio]->codecpar->bits_per_coded_sample / 8));
  474. if (rc >= 0)
  475. for (int s = 0; s < bytesToRead; s += sizeof(ui8))
  476. {
  477. ui8 value;
  478. memcpy(&value, &frameAudio->data[0][s], sizeof(ui8));
  479. samples.push_back(value);
  480. }
  481. }
  482. av_packet_unref(&packet);
  483. }
  484. typedef struct WAV_HEADER {
  485. ui8 RIFF[4] = {'R', 'I', 'F', 'F'};
  486. ui32 ChunkSize;
  487. ui8 WAVE[4] = {'W', 'A', 'V', 'E'};
  488. ui8 fmt[4] = {'f', 'm', 't', ' '};
  489. ui32 Subchunk1Size = 16;
  490. ui16 AudioFormat = 1;
  491. ui16 NumOfChan = 2;
  492. ui32 SamplesPerSec = 22050;
  493. ui32 bytesPerSec = 22050 * 2;
  494. ui16 blockAlign = 2;
  495. ui16 bitsPerSample = 16;
  496. ui8 Subchunk2ID[4] = {'d', 'a', 't', 'a'};
  497. ui32 Subchunk2Size;
  498. } wav_hdr;
  499. wav_hdr wav;
  500. wav.ChunkSize = samples.size() + sizeof(wav_hdr) - 8;
  501. wav.Subchunk2Size = samples.size() + sizeof(wav_hdr) - 44;
  502. wav.SamplesPerSec = formatAudio->streams[streamAudio]->codecpar->sample_rate;
  503. wav.bitsPerSample = formatAudio->streams[streamAudio]->codecpar->bits_per_coded_sample;
  504. auto wavPtr = reinterpret_cast<ui8*>(&wav);
  505. dat = std::make_pair(std::make_unique<ui8[]>(samples.size() + sizeof(wav_hdr)), samples.size() + sizeof(wav_hdr));
  506. std::copy(wavPtr, wavPtr + sizeof(wav_hdr), dat.first.get());
  507. std::copy(samples.begin(), samples.end(), dat.first.get() + sizeof(wav_hdr));
  508. if (frameAudio)
  509. av_frame_free(&frameAudio);
  510. if (codecAudio)
  511. {
  512. avcodec_close(codecContextAudio);
  513. codecAudio = nullptr;
  514. }
  515. if (codecContextAudio)
  516. avcodec_free_context(&codecContextAudio);
  517. if (formatAudio)
  518. avformat_close_input(&formatAudio);
  519. if (contextAudio)
  520. {
  521. av_free(contextAudio);
  522. contextAudio = nullptr;
  523. }
  524. return dat;
  525. }
  526. // Plays a video. Only works for overlays.
  527. bool CVideoPlayer::playVideo(int x, int y, bool stopOnKey)
  528. {
  529. // Note: either the windows player or the linux player is
  530. // broken. Compensate here until the bug is found.
  531. y--;
  532. pos.x = x;
  533. pos.y = y;
  534. frameTime = 0.0;
  535. while(nextFrame())
  536. {
  537. if(stopOnKey)
  538. {
  539. GH.input().fetchEvents();
  540. if(GH.input().ignoreEventsUntilInput())
  541. return false;
  542. }
  543. SDL_Rect rect = CSDL_Ext::toSDL(pos);
  544. SDL_RenderFillRect(mainRenderer, &rect);
  545. SDL_RenderCopy(mainRenderer, texture, nullptr, &rect);
  546. SDL_RenderPresent(mainRenderer);
  547. #if (LIBAVUTIL_VERSION_MAJOR < 58)
  548. auto packet_duration = frame->pkt_duration;
  549. #else
  550. auto packet_duration = frame->duration;
  551. #endif
  552. double frameDurationSec = packet_duration * av_q2d(format->streams[stream]->time_base);
  553. uint32_t timeToSleepMillisec = 1000 * (frameDurationSec);
  554. boost::this_thread::sleep_for(boost::chrono::milliseconds(timeToSleepMillisec));
  555. }
  556. return true;
  557. }
  558. bool CVideoPlayer::openAndPlayVideo(const VideoPath & name, int x, int y, bool stopOnKey, bool scale)
  559. {
  560. open(name, false, true, scale);
  561. bool ret = playVideo(x, y, stopOnKey);
  562. close();
  563. return ret;
  564. }
  565. CVideoPlayer::~CVideoPlayer()
  566. {
  567. close();
  568. }
  569. #endif