CVideoHandler.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587
  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. #ifndef DISABLE_VIDEO
  13. #include "ISoundPlayer.h"
  14. #include "../CGameInfo.h"
  15. #include "../CMT.h"
  16. #include "../CPlayerInterface.h"
  17. #include "../eventsSDL/InputHandler.h"
  18. #include "../gui/CGuiHandler.h"
  19. #include "../gui/FramerateManager.h"
  20. #include "../render/Canvas.h"
  21. #include "../renderSDL/SDL_Extensions.h"
  22. #include "../../lib/filesystem/CInputStream.h"
  23. #include "../../lib/filesystem/Filesystem.h"
  24. #include <SDL_render.h>
  25. extern "C" {
  26. #include <libavformat/avformat.h>
  27. #include <libavcodec/avcodec.h>
  28. #include <libavutil/imgutils.h>
  29. #include <libswscale/swscale.h>
  30. }
  31. // Define a set of functions to read data
  32. static int lodRead(void * opaque, uint8_t * buf, int size)
  33. {
  34. auto * data = static_cast<CInputStream *>(opaque);
  35. int bytes = static_cast<int>(data->read(buf, size));
  36. if(bytes == 0)
  37. return AVERROR_EOF;
  38. return bytes;
  39. }
  40. static si64 lodSeek(void * opaque, si64 pos, int whence)
  41. {
  42. auto * data = static_cast<CInputStream *>(opaque);
  43. if(whence & AVSEEK_SIZE)
  44. return data->getSize();
  45. return data->seek(pos);
  46. }
  47. [[noreturn]] static void throwFFmpegError(int errorCode)
  48. {
  49. std::array<char, AV_ERROR_MAX_STRING_SIZE> errorMessage{};
  50. av_strerror(errorCode, errorMessage.data(), errorMessage.size());
  51. throw std::runtime_error(errorMessage.data());
  52. }
  53. static std::unique_ptr<CInputStream> findVideoData(const VideoPath & videoToOpen)
  54. {
  55. if(CResourceHandler::get()->existsResource(videoToOpen))
  56. return CResourceHandler::get()->load(videoToOpen);
  57. auto highQualityVideoToOpenWithDir = videoToOpen.addPrefix("VIDEO/");
  58. auto lowQualityVideo = videoToOpen.toType<EResType::VIDEO_LOW_QUALITY>();
  59. auto lowQualityVideoWithDir = highQualityVideoToOpenWithDir.toType<EResType::VIDEO_LOW_QUALITY>();
  60. if(CResourceHandler::get()->existsResource(highQualityVideoToOpenWithDir))
  61. return CResourceHandler::get()->load(highQualityVideoToOpenWithDir);
  62. if(CResourceHandler::get()->existsResource(lowQualityVideo))
  63. return CResourceHandler::get()->load(lowQualityVideo);
  64. return CResourceHandler::get()->load(lowQualityVideoWithDir);
  65. }
  66. void CVideoInstance::open(const VideoPath & videoToOpen)
  67. {
  68. input = findVideoData(videoToOpen);
  69. }
  70. void CVideoInstance::openContext(FFMpegStreamState & state)
  71. {
  72. static const int BUFFER_SIZE = 4096;
  73. input->seek(0);
  74. auto * buffer = static_cast<unsigned char *>(av_malloc(BUFFER_SIZE)); // will be freed by ffmpeg
  75. state.context = avio_alloc_context(buffer, BUFFER_SIZE, 0, input.get(), lodRead, nullptr, lodSeek);
  76. state.formatContext = avformat_alloc_context();
  77. state.formatContext->pb = state.context;
  78. // filename is not needed - file was already open and stored in this->data;
  79. int avfopen = avformat_open_input(&state.formatContext, "dummyFilename", nullptr, nullptr);
  80. if(avfopen != 0)
  81. throwFFmpegError(avfopen);
  82. // Retrieve stream information
  83. int findStreamInfo = avformat_find_stream_info(state.formatContext, nullptr);
  84. if(avfopen < 0)
  85. throwFFmpegError(findStreamInfo);
  86. }
  87. void CVideoInstance::openCodec(FFMpegStreamState & state, int streamIndex)
  88. {
  89. state.streamIndex = streamIndex;
  90. // Find the decoder for the stream
  91. state.codec = avcodec_find_decoder(state.formatContext->streams[streamIndex]->codecpar->codec_id);
  92. if(state.codec == nullptr)
  93. throw std::runtime_error("Unsupported codec");
  94. state.codecContext = avcodec_alloc_context3(state.codec);
  95. if(state.codecContext == nullptr)
  96. throw std::runtime_error("Failed to create codec context");
  97. // Get a pointer to the codec context for the video stream
  98. int ret = avcodec_parameters_to_context(state.codecContext, state.formatContext->streams[streamIndex]->codecpar);
  99. if(ret < 0)
  100. {
  101. //We cannot get codec from parameters
  102. avcodec_free_context(&state.codecContext);
  103. throwFFmpegError(ret);
  104. }
  105. // Open codec
  106. ret = avcodec_open2(state.codecContext, state.codec, nullptr);
  107. if(ret < 0)
  108. {
  109. // Could not open codec
  110. state.codec = nullptr;
  111. throwFFmpegError(ret);
  112. }
  113. }
  114. void CVideoInstance::openVideo()
  115. {
  116. openContext(video);
  117. for(int i = 0; i < video.formatContext->nb_streams; i++)
  118. {
  119. if(video.formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
  120. {
  121. openCodec(video, i);
  122. return;
  123. }
  124. }
  125. }
  126. void CVideoInstance::prepareOutput(bool scaleToScreenSize, bool useTextureOutput)
  127. {
  128. if (video.streamIndex == -1)
  129. throw std::runtime_error("Invalid file state! No video stream!");
  130. // Allocate video frame
  131. output.frame = av_frame_alloc();
  132. //setup scaling
  133. if(scaleToScreenSize)
  134. {
  135. output.dimensions.x = screen->w;
  136. output.dimensions.y = screen->h;
  137. }
  138. else
  139. {
  140. output.dimensions.x = video.codecContext->width;
  141. output.dimensions.y = video.codecContext->height;
  142. }
  143. // Allocate a place to put our YUV image on that screen
  144. if (useTextureOutput)
  145. {
  146. std::array potentialFormats = {
  147. AV_PIX_FMT_YUV420P, // -> SDL_PIXELFORMAT_IYUV - most of H3 videos use YUV format, so it is preferred to save some space & conversion time
  148. AV_PIX_FMT_RGB32, // -> SDL_PIXELFORMAT_ARGB8888 - some .smk videos actually use palette, so RGB > YUV. This is also our screen texture format
  149. AV_PIX_FMT_NONE
  150. };
  151. auto preferredFormat = avcodec_find_best_pix_fmt_of_list(potentialFormats.data(), video.codecContext->pix_fmt, false, nullptr);
  152. if (preferredFormat == AV_PIX_FMT_YUV420P)
  153. output.textureYUV = SDL_CreateTexture( mainRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, output.dimensions.x, output.dimensions.y);
  154. else
  155. output.textureRGB = SDL_CreateTexture( mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, output.dimensions.x, output.dimensions.y);
  156. output.sws = sws_getContext(video.codecContext->width, video.codecContext->height, video.codecContext->pix_fmt,
  157. output.dimensions.x, output.dimensions.y, preferredFormat,
  158. SWS_BICUBIC, nullptr, nullptr, nullptr);
  159. }
  160. else
  161. {
  162. output.surface = CSDL_Ext::newSurface(output.dimensions.x, output.dimensions.y);
  163. output.sws = sws_getContext(video.codecContext->width, video.codecContext->height, video.codecContext->pix_fmt,
  164. output.dimensions.x, output.dimensions.y, AV_PIX_FMT_RGB32,
  165. SWS_BICUBIC, nullptr, nullptr, nullptr);
  166. }
  167. if (output.sws == nullptr)
  168. throw std::runtime_error("Failed to create sws");
  169. }
  170. bool CVideoInstance::nextFrame()
  171. {
  172. AVPacket packet;
  173. for(;;)
  174. {
  175. int ret = av_read_frame(video.formatContext, &packet);
  176. if(ret < 0)
  177. {
  178. if(ret == AVERROR_EOF)
  179. return false;
  180. throwFFmpegError(ret);
  181. }
  182. // Is this a packet from the video stream?
  183. if(packet.stream_index == video.streamIndex)
  184. {
  185. // Decode video frame
  186. int rc = avcodec_send_packet(video.codecContext, &packet);
  187. if(rc < 0)
  188. throwFFmpegError(rc);
  189. rc = avcodec_receive_frame(video.codecContext, output.frame);
  190. if(rc < 0)
  191. throwFFmpegError(rc);
  192. uint8_t * data[4] = {};
  193. int linesize[4] = {};
  194. if(output.textureYUV)
  195. {
  196. av_image_alloc(data, linesize, output.dimensions.x, output.dimensions.y, AV_PIX_FMT_YUV420P, 1);
  197. sws_scale(output.sws, output.frame->data, output.frame->linesize, 0, video.codecContext->height, data, linesize);
  198. SDL_UpdateYUVTexture(output.textureYUV, nullptr, data[0], linesize[0], data[1], linesize[1], data[2], linesize[2]);
  199. av_freep(&data[0]);
  200. }
  201. if(output.textureRGB)
  202. {
  203. av_image_alloc(data, linesize, output.dimensions.x, output.dimensions.y, AV_PIX_FMT_RGB32, 1);
  204. sws_scale(output.sws, output.frame->data, output.frame->linesize, 0, video.codecContext->height, data, linesize);
  205. SDL_UpdateTexture(output.textureRGB, nullptr, data[0], linesize[0]);
  206. av_freep(&data[0]);
  207. }
  208. if (output.surface)
  209. {
  210. // Avoid buffer overflow caused by sws_scale():
  211. // http://trac.ffmpeg.org/ticket/9254
  212. size_t pic_bytes = output.surface->pitch * output.surface->h;
  213. size_t ffmped_pad = 1024; /* a few bytes of overflow will go here */
  214. void * for_sws = av_malloc(pic_bytes + ffmped_pad);
  215. data[0] = (ui8 *)for_sws;
  216. linesize[0] = output.surface->pitch;
  217. sws_scale(output.sws, output.frame->data, output.frame->linesize, 0, video.codecContext->height, data, linesize);
  218. memcpy(output.surface->pixels, for_sws, pic_bytes);
  219. av_free(for_sws);
  220. }
  221. av_packet_unref(&packet);
  222. return true;
  223. }
  224. }
  225. }
  226. bool CVideoInstance::videoEnded()
  227. {
  228. return output.videoEnded;
  229. }
  230. void CVideoInstance::close()
  231. {
  232. sws_freeContext(output.sws);
  233. av_frame_free(&output.frame);
  234. SDL_DestroyTexture(output.textureYUV);
  235. SDL_DestroyTexture(output.textureRGB);
  236. SDL_FreeSurface(output.surface);
  237. closeState(video);
  238. }
  239. void CVideoInstance::closeState(FFMpegStreamState & streamState)
  240. {
  241. // state.videoStream.codec???
  242. // state.audioStream.codec???
  243. avcodec_close(video.codecContext);
  244. avcodec_free_context(&video.codecContext);
  245. avcodec_close(video.codecContext);
  246. avcodec_free_context(&video.codecContext);
  247. avformat_close_input(&video.formatContext);
  248. av_free(video.context);
  249. output = FFMpegVideoOutput();
  250. video = FFMpegStreamState();
  251. }
  252. CVideoInstance::~CVideoInstance()
  253. {
  254. close();
  255. }
  256. Point CVideoInstance::size()
  257. {
  258. if(!output.frame)
  259. throw std::runtime_error("Invalid video frame!");
  260. return Point(output.frame->width, output.frame->height);
  261. }
  262. void CVideoInstance::show(const Point & position, Canvas & canvas)
  263. {
  264. if(output.sws == nullptr)
  265. throw std::runtime_error("No video to show!");
  266. CSDL_Ext::blitSurface(output.surface, canvas.getInternalSurface(), position);
  267. }
  268. void CVideoInstance::tick(uint32_t msPassed)
  269. {
  270. if(output.sws == nullptr)
  271. throw std::runtime_error("No video to show!");
  272. if(output.videoEnded)
  273. throw std::runtime_error("Video already ended!");
  274. # if(LIBAVUTIL_VERSION_MAJOR < 58)
  275. auto packet_duration = output.frame->pkt_duration;
  276. # else
  277. auto packet_duration = frame->duration;
  278. # endif
  279. double frameEndTime = (output.frame->pts + packet_duration) * av_q2d(video.formatContext->streams[video.streamIndex]->time_base);
  280. output.frameTime += msPassed / 1000.0;
  281. if(output.frameTime >= frameEndTime)
  282. {
  283. if(!nextFrame())
  284. output.videoEnded = true;
  285. }
  286. }
  287. static int32_t sampleSizeBytes(int audioFormat)
  288. {
  289. switch (audioFormat)
  290. {
  291. case AV_SAMPLE_FMT_U8: ///< unsigned 8 bits
  292. case AV_SAMPLE_FMT_U8P: ///< unsigned 8 bits, planar
  293. return 1;
  294. case AV_SAMPLE_FMT_S16: ///< signed 16 bits
  295. case AV_SAMPLE_FMT_S16P: ///< signed 16 bits, planar
  296. return 2;
  297. case AV_SAMPLE_FMT_S32: ///< signed 32 bits
  298. case AV_SAMPLE_FMT_S32P: ///< signed 32 bits, planar
  299. case AV_SAMPLE_FMT_FLT: ///< float
  300. case AV_SAMPLE_FMT_FLTP: ///< float, planar
  301. return 4;
  302. case AV_SAMPLE_FMT_DBL: ///< double
  303. case AV_SAMPLE_FMT_DBLP: ///< double, planar
  304. case AV_SAMPLE_FMT_S64: ///< signed 64 bits
  305. case AV_SAMPLE_FMT_S64P: ///< signed 64 bits, planar
  306. return 8;
  307. }
  308. throw std::runtime_error("Invalid audio format");
  309. }
  310. static int32_t sampleWavType(int audioFormat)
  311. {
  312. switch (audioFormat)
  313. {
  314. case AV_SAMPLE_FMT_U8: ///< unsigned 8 bits
  315. case AV_SAMPLE_FMT_U8P: ///< unsigned 8 bits, planar
  316. case AV_SAMPLE_FMT_S16: ///< signed 16 bits
  317. case AV_SAMPLE_FMT_S16P: ///< signed 16 bits, planar
  318. case AV_SAMPLE_FMT_S32: ///< signed 32 bits
  319. case AV_SAMPLE_FMT_S32P: ///< signed 32 bits, planar
  320. case AV_SAMPLE_FMT_S64: ///< signed 64 bits
  321. case AV_SAMPLE_FMT_S64P: ///< signed 64 bits, planar
  322. return 1; // PCM
  323. case AV_SAMPLE_FMT_FLT: ///< float
  324. case AV_SAMPLE_FMT_FLTP: ///< float, planar
  325. case AV_SAMPLE_FMT_DBL: ///< double
  326. case AV_SAMPLE_FMT_DBLP: ///< double, planar
  327. return 3; // IEEE float
  328. }
  329. throw std::runtime_error("Invalid audio format");
  330. }
  331. void CVideoInstance::playAudio()
  332. {
  333. FFMpegStreamState audio;
  334. openContext(audio);
  335. for(int i = 0; i < audio.formatContext->nb_streams; i++)
  336. {
  337. if(audio.formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
  338. {
  339. openCodec(audio, i);
  340. break;
  341. }
  342. }
  343. std::pair<std::unique_ptr<ui8 []>, si64> dat(std::make_pair(nullptr, 0));
  344. if (audio.streamIndex < 0)
  345. return; // nothing to play
  346. const auto * codecpar = audio.formatContext->streams[audio.streamIndex]->codecpar;
  347. AVFrame *frameAudio = av_frame_alloc();
  348. AVFrame *frameVideo = av_frame_alloc();
  349. AVPacket packet;
  350. std::vector<ui8> samples;
  351. int32_t sampleSize = sampleSizeBytes(codecpar->format);
  352. samples.reserve(44100 * 5); // arbitrary 5-second buffer
  353. while (av_read_frame(audio.formatContext, &packet) >= 0)
  354. {
  355. if (packet.stream_index == video.streamIndex)
  356. {
  357. // Decode video frame
  358. int rc = avcodec_send_packet(video.codecContext, &packet);
  359. if(rc < 0)
  360. throwFFmpegError(rc);
  361. rc = avcodec_receive_frame(video.codecContext, frameVideo);
  362. if(rc < 0)
  363. throwFFmpegError(rc);
  364. }
  365. if(packet.stream_index == audio.streamIndex)
  366. {
  367. int rc = avcodec_send_packet(audio.codecContext, &packet);
  368. if(rc < 0)
  369. throwFFmpegError(rc);
  370. for (;;)
  371. {
  372. rc = avcodec_receive_frame(audio.codecContext, frameAudio);
  373. if (rc == AVERROR(EAGAIN))
  374. break;
  375. if(rc < 0)
  376. throwFFmpegError(rc);
  377. int bytesToRead = frameAudio->nb_samples * 2 * sampleSize;
  378. samples.insert(samples.end(), frameAudio->data[0], frameAudio->data[0] + bytesToRead);
  379. }
  380. }
  381. av_packet_unref(&packet);
  382. }
  383. typedef struct WAV_HEADER {
  384. ui8 RIFF[4] = {'R', 'I', 'F', 'F'};
  385. ui32 ChunkSize;
  386. ui8 WAVE[4] = {'W', 'A', 'V', 'E'};
  387. ui8 fmt[4] = {'f', 'm', 't', ' '};
  388. ui32 Subchunk1Size = 16;
  389. ui16 AudioFormat = 1;
  390. ui16 NumOfChan = 2;
  391. ui32 SamplesPerSec = 22050;
  392. ui32 bytesPerSec = 22050 * 2;
  393. ui16 blockAlign = 2;
  394. ui16 bitsPerSample = 32;
  395. ui8 Subchunk2ID[4] = {'d', 'a', 't', 'a'};
  396. ui32 Subchunk2Size;
  397. } wav_hdr;
  398. wav_hdr wav;
  399. wav.ChunkSize = samples.size() + sizeof(wav_hdr) - 8;
  400. wav.AudioFormat = sampleWavType(codecpar->format);
  401. wav.NumOfChan = codecpar->channels;
  402. wav.SamplesPerSec = codecpar->sample_rate;
  403. wav.bytesPerSec = codecpar->sample_rate * sampleSize;
  404. wav.bitsPerSample = sampleSize * 8;
  405. wav.Subchunk2Size = samples.size() + sizeof(wav_hdr) - 44;
  406. auto wavPtr = reinterpret_cast<ui8*>(&wav);
  407. dat = std::make_pair(std::make_unique<ui8[]>(samples.size() + sizeof(wav_hdr)), samples.size() + sizeof(wav_hdr));
  408. std::copy(wavPtr, wavPtr + sizeof(wav_hdr), dat.first.get());
  409. std::copy(samples.begin(), samples.end(), dat.first.get() + sizeof(wav_hdr));
  410. if (frameAudio)
  411. av_frame_free(&frameAudio);
  412. CCS->soundh->playSound(dat);
  413. closeState(audio);
  414. }
  415. bool CVideoPlayer::openAndPlayVideoImpl(const VideoPath & name, const Point & position, bool useOverlay, bool scale, bool stopOnKey)
  416. {
  417. CVideoInstance instance;
  418. instance.open(name);
  419. instance.playAudio();
  420. instance.openVideo();
  421. instance.prepareOutput(scale, useOverlay);
  422. auto lastTimePoint = boost::chrono::steady_clock::now();
  423. while(instance.nextFrame())
  424. {
  425. if(stopOnKey)
  426. {
  427. GH.input().fetchEvents();
  428. if(GH.input().ignoreEventsUntilInput())
  429. return false;
  430. }
  431. SDL_Rect rect;
  432. rect.x = position.x;
  433. rect.y = position.y;
  434. rect.w = instance.output.dimensions.x;
  435. rect.h = instance.output.dimensions.y;
  436. if(useOverlay)
  437. SDL_RenderFillRect(mainRenderer, &rect);
  438. else
  439. SDL_RenderClear(mainRenderer);
  440. if (instance.output.textureYUV)
  441. SDL_RenderCopy(mainRenderer, instance.output.textureYUV, nullptr, &rect);
  442. else
  443. SDL_RenderCopy(mainRenderer, instance.output.textureRGB, nullptr, &rect);
  444. SDL_RenderPresent(mainRenderer);
  445. #if (LIBAVUTIL_VERSION_MAJOR < 58)
  446. auto packet_duration = instance.output.frame->pkt_duration;
  447. #else
  448. auto packet_duration = output.frame->duration;
  449. #endif
  450. // Framerate delay
  451. double targetFrameTimeSeconds = packet_duration * av_q2d(instance.video.formatContext->streams[instance.video.streamIndex]->time_base);
  452. auto targetFrameTime = boost::chrono::milliseconds(static_cast<int>(1000 * (targetFrameTimeSeconds)));
  453. auto timePointAfterPresent = boost::chrono::steady_clock::now();
  454. auto timeSpentBusy = boost::chrono::duration_cast<boost::chrono::milliseconds>(timePointAfterPresent - lastTimePoint);
  455. if (targetFrameTime > timeSpentBusy)
  456. boost::this_thread::sleep_for(targetFrameTime - timeSpentBusy);
  457. lastTimePoint = boost::chrono::steady_clock::now();
  458. }
  459. return true;
  460. }
  461. bool CVideoPlayer::playIntroVideo(const VideoPath & name)
  462. {
  463. return openAndPlayVideoImpl(name, Point(0,0), true, true, true);
  464. }
  465. void CVideoPlayer::playSpellbookAnimation(const VideoPath & name, const Point & position)
  466. {
  467. openAndPlayVideoImpl(name, position, false, false, false);
  468. }
  469. std::unique_ptr<IVideoInstance> CVideoPlayer::open(const VideoPath & name, bool scaleToScreen)
  470. {
  471. auto result = std::make_unique<CVideoInstance>();
  472. result->open(name);
  473. result->openVideo();
  474. result->prepareOutput(scaleToScreen, false);
  475. return result;
  476. }
  477. #endif