CVideoHandler.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  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. #ifdef ENABLE_VIDEO
  13. #include "ISoundPlayer.h"
  14. #include "../CMT.h"
  15. #include "../eventsSDL/InputHandler.h"
  16. #include "../GameEngine.h"
  17. #include "../render/Canvas.h"
  18. #include "../render/IScreenHandler.h"
  19. #include "../renderSDL/SDL_Extensions.h"
  20. #include "../../lib/filesystem/CInputStream.h"
  21. #include "../../lib/filesystem/Filesystem.h"
  22. #include "../../lib/texts/CGeneralTextHandler.h"
  23. #include "../../lib/texts/Languages.h"
  24. #include "../../lib/GameLibrary.h"
  25. #include <SDL_render.h>
  26. extern "C" {
  27. #include <libavformat/avformat.h>
  28. #include <libavcodec/avcodec.h>
  29. #include <libavutil/imgutils.h>
  30. #include <libavutil/opt.h>
  31. #include <libswscale/swscale.h>
  32. #include <libswresample/swresample.h>
  33. }
  34. // Define a set of functions to read data
  35. static int lodRead(void * opaque, uint8_t * buf, int size)
  36. {
  37. auto * data = static_cast<CInputStream *>(opaque);
  38. auto bytesRead = data->read(buf, size);
  39. if(bytesRead == 0)
  40. return AVERROR_EOF;
  41. return bytesRead;
  42. }
  43. static si64 lodSeek(void * opaque, si64 pos, int whence)
  44. {
  45. auto * data = static_cast<CInputStream *>(opaque);
  46. if(whence & AVSEEK_SIZE)
  47. return data->getSize();
  48. return data->seek(pos);
  49. }
  50. static void logFFmpegError(int errorCode)
  51. {
  52. std::array<char, AV_ERROR_MAX_STRING_SIZE> errorMessage{};
  53. av_strerror(errorCode, errorMessage.data(), errorMessage.size());
  54. logGlobal->warn("Failed to open video file! Reason: %s", errorMessage.data());
  55. }
  56. [[noreturn]] static void throwFFmpegError(int errorCode)
  57. {
  58. logFFmpegError(errorCode);
  59. std::array<char, AV_ERROR_MAX_STRING_SIZE> errorMessage{};
  60. av_strerror(errorCode, errorMessage.data(), errorMessage.size());
  61. throw std::runtime_error(errorMessage.data());
  62. }
  63. static std::unique_ptr<CInputStream> findVideoData(const VideoPath & videoToOpen)
  64. {
  65. if(CResourceHandler::get()->existsResource(videoToOpen))
  66. return CResourceHandler::get()->load(videoToOpen);
  67. auto highQualityVideoToOpenWithDir = videoToOpen.addPrefix("VIDEO/");
  68. auto lowQualityVideo = videoToOpen.toType<EResType::VIDEO_LOW_QUALITY>();
  69. auto lowQualityVideoWithDir = highQualityVideoToOpenWithDir.toType<EResType::VIDEO_LOW_QUALITY>();
  70. if(CResourceHandler::get()->existsResource(highQualityVideoToOpenWithDir))
  71. return CResourceHandler::get()->load(highQualityVideoToOpenWithDir);
  72. if(CResourceHandler::get()->existsResource(lowQualityVideo))
  73. return CResourceHandler::get()->load(lowQualityVideo);
  74. if(CResourceHandler::get()->existsResource(lowQualityVideoWithDir))
  75. return CResourceHandler::get()->load(lowQualityVideoWithDir);
  76. return nullptr;
  77. }
  78. bool FFMpegStream::openInput(const VideoPath & videoToOpen)
  79. {
  80. input = findVideoData(videoToOpen);
  81. return input != nullptr;
  82. }
  83. bool FFMpegStream::openContext()
  84. {
  85. static const int BUFFER_SIZE = 4096;
  86. input->seek(0);
  87. auto * buffer = static_cast<unsigned char *>(av_malloc(BUFFER_SIZE)); // will be freed by ffmpeg
  88. context = avio_alloc_context(buffer, BUFFER_SIZE, 0, input.get(), lodRead, nullptr, lodSeek);
  89. formatContext = avformat_alloc_context();
  90. formatContext->pb = context;
  91. // filename is not needed - file was already open and stored in this->data;
  92. int avfopen = avformat_open_input(&formatContext, "dummyFilename", nullptr, nullptr);
  93. if(avfopen != 0)
  94. {
  95. logFFmpegError(avfopen);
  96. return false;
  97. }
  98. // Retrieve stream information
  99. int findStreamInfo = avformat_find_stream_info(formatContext, nullptr);
  100. if(avfopen < 0)
  101. {
  102. logFFmpegError(findStreamInfo);
  103. return false;
  104. }
  105. return true;
  106. }
  107. void FFMpegStream::openCodec(int desiredStreamIndex)
  108. {
  109. streamIndex = desiredStreamIndex;
  110. // Find the decoder for the stream
  111. codec = avcodec_find_decoder(formatContext->streams[streamIndex]->codecpar->codec_id);
  112. if(codec == nullptr)
  113. throw std::runtime_error("Unsupported codec");
  114. codecContext = avcodec_alloc_context3(codec);
  115. if(codecContext == nullptr)
  116. throw std::runtime_error("Failed to create codec context");
  117. // Get a pointer to the codec context for the video stream
  118. int ret = avcodec_parameters_to_context(codecContext, formatContext->streams[streamIndex]->codecpar);
  119. if(ret < 0)
  120. {
  121. //We cannot get codec from parameters
  122. avcodec_free_context(&codecContext);
  123. throwFFmpegError(ret);
  124. }
  125. // Open codec
  126. ret = avcodec_open2(codecContext, codec, nullptr);
  127. if(ret < 0)
  128. {
  129. // Could not open codec
  130. codec = nullptr;
  131. throwFFmpegError(ret);
  132. }
  133. // Allocate video frame
  134. frame = av_frame_alloc();
  135. }
  136. const AVCodecParameters * FFMpegStream::getCodecParameters() const
  137. {
  138. return formatContext->streams[streamIndex]->codecpar;
  139. }
  140. const AVCodecContext * FFMpegStream::getCodecContext() const
  141. {
  142. return codecContext;
  143. }
  144. const AVFrame * FFMpegStream::getCurrentFrame() const
  145. {
  146. return frame;
  147. }
  148. bool CVideoInstance::openVideo()
  149. {
  150. if (!openContext())
  151. return false;
  152. openCodec(findVideoStream());
  153. return true;
  154. }
  155. void CVideoInstance::prepareOutput(float scaleFactor, bool useTextureOutput)
  156. {
  157. //setup scaling
  158. dimensions = Point(getCodecContext()->width * scaleFactor, getCodecContext()->height * scaleFactor) * ENGINE->screenHandler().getScalingFactor();
  159. // Allocate a place to put our YUV image on that screen
  160. if (useTextureOutput)
  161. {
  162. std::array potentialFormats = {
  163. AV_PIX_FMT_YUV420P, // -> SDL_PIXELFORMAT_IYUV - most of H3 videos use YUV format, so it is preferred to save some space & conversion time
  164. AV_PIX_FMT_RGB32, // -> SDL_PIXELFORMAT_ARGB8888 - some .smk videos actually use palette, so RGB > YUV. This is also our screen texture format
  165. AV_PIX_FMT_NONE
  166. };
  167. auto preferredFormat = avcodec_find_best_pix_fmt_of_list(potentialFormats.data(), getCodecContext()->pix_fmt, false, nullptr);
  168. if (preferredFormat == AV_PIX_FMT_YUV420P)
  169. textureYUV = SDL_CreateTexture( mainRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING, dimensions.x, dimensions.y);
  170. else
  171. textureRGB = SDL_CreateTexture( mainRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, dimensions.x, dimensions.y);
  172. sws = sws_getContext(getCodecContext()->width, getCodecContext()->height, getCodecContext()->pix_fmt,
  173. dimensions.x, dimensions.y, preferredFormat,
  174. SWS_BICUBIC, nullptr, nullptr, nullptr);
  175. }
  176. else
  177. {
  178. surface = CSDL_Ext::newSurface(dimensions);
  179. sws = sws_getContext(getCodecContext()->width, getCodecContext()->height, getCodecContext()->pix_fmt,
  180. dimensions.x, dimensions.y, AV_PIX_FMT_RGB32,
  181. SWS_BICUBIC, nullptr, nullptr, nullptr);
  182. }
  183. if (sws == nullptr)
  184. throw std::runtime_error("Failed to create sws");
  185. }
  186. void FFMpegStream::decodeNextFrame()
  187. {
  188. int rc = avcodec_receive_frame(codecContext, frame);
  189. // frame extracted - data that was sent to codecContext before was sufficient
  190. if (rc == 0)
  191. return;
  192. // returning AVERROR(EAGAIN) is legal - this indicates that codec requires more data from input stream to decode next frame
  193. if(rc != AVERROR(EAGAIN))
  194. throwFFmpegError(rc);
  195. for(;;)
  196. {
  197. AVPacket packet;
  198. // codecContext does not have enough input data - read next packet from input stream
  199. int ret = av_read_frame(formatContext, &packet);
  200. if(ret < 0)
  201. {
  202. if(ret == AVERROR_EOF)
  203. {
  204. av_packet_unref(&packet);
  205. av_frame_free(&frame);
  206. frame = nullptr;
  207. return;
  208. }
  209. throwFFmpegError(ret);
  210. }
  211. // Is this a packet from the stream that needs decoding?
  212. if(packet.stream_index == streamIndex)
  213. {
  214. // Decode read packet
  215. // Note: this method may return AVERROR(EAGAIN). However this should never happen with ffmpeg API
  216. // since there is guaranteed call to avcodec_receive_frame and ffmpeg API promises that *both* of these methods will never return AVERROR(EAGAIN).
  217. int rc = avcodec_send_packet(codecContext, &packet);
  218. if(rc < 0)
  219. throwFFmpegError(rc);
  220. rc = avcodec_receive_frame(codecContext, frame);
  221. if(rc == AVERROR(EAGAIN))
  222. {
  223. // still need more data - read next packet
  224. av_packet_unref(&packet);
  225. continue;
  226. }
  227. else if(rc < 0)
  228. {
  229. throwFFmpegError(rc);
  230. }
  231. else
  232. {
  233. // read succesful. Exit the loop
  234. av_packet_unref(&packet);
  235. return;
  236. }
  237. }
  238. av_packet_unref(&packet);
  239. }
  240. }
  241. bool CVideoInstance::loadNextFrame()
  242. {
  243. decodeNextFrame();
  244. const AVFrame * frame = getCurrentFrame();
  245. if(!frame)
  246. return false;
  247. uint8_t * data[4] = {};
  248. int linesize[4] = {};
  249. if(textureYUV)
  250. {
  251. av_image_alloc(data, linesize, dimensions.x, dimensions.y, AV_PIX_FMT_YUV420P, 1);
  252. sws_scale(sws, frame->data, frame->linesize, 0, getCodecContext()->height, data, linesize);
  253. SDL_UpdateYUVTexture(textureYUV, nullptr, data[0], linesize[0], data[1], linesize[1], data[2], linesize[2]);
  254. av_freep(&data[0]);
  255. }
  256. if(textureRGB)
  257. {
  258. av_image_alloc(data, linesize, dimensions.x, dimensions.y, AV_PIX_FMT_RGB32, 1);
  259. sws_scale(sws, frame->data, frame->linesize, 0, getCodecContext()->height, data, linesize);
  260. SDL_UpdateTexture(textureRGB, nullptr, data[0], linesize[0]);
  261. av_freep(&data[0]);
  262. }
  263. if(surface)
  264. {
  265. // Avoid buffer overflow caused by sws_scale():
  266. // http://trac.ffmpeg.org/ticket/9254
  267. size_t pic_bytes = surface->pitch * surface->h;
  268. size_t ffmped_pad = 1024; /* a few bytes of overflow will go here */
  269. void * for_sws = av_malloc(pic_bytes + ffmped_pad);
  270. data[0] = (ui8 *)for_sws;
  271. linesize[0] = surface->pitch;
  272. sws_scale(sws, frame->data, frame->linesize, 0, getCodecContext()->height, data, linesize);
  273. memcpy(surface->pixels, for_sws, pic_bytes);
  274. av_free(for_sws);
  275. }
  276. return true;
  277. }
  278. double CVideoInstance::timeStamp()
  279. {
  280. return getCurrentFrameEndTime();
  281. }
  282. bool CVideoInstance::videoEnded()
  283. {
  284. return getCurrentFrame() == nullptr;
  285. }
  286. CVideoInstance::CVideoInstance()
  287. : startTimeInitialized(false), deactivationStartTimeHandling(false)
  288. {
  289. }
  290. CVideoInstance::~CVideoInstance()
  291. {
  292. sws_freeContext(sws);
  293. SDL_DestroyTexture(textureYUV);
  294. SDL_DestroyTexture(textureRGB);
  295. SDL_FreeSurface(surface);
  296. }
  297. FFMpegStream::~FFMpegStream()
  298. {
  299. av_frame_free(&frame);
  300. #if (LIBAVCODEC_VERSION_MAJOR < 61 )
  301. // deprecated, apparently no longer necessary - avcodec_free_context should suffice
  302. avcodec_close(codecContext);
  303. #endif
  304. avcodec_free_context(&codecContext);
  305. avformat_close_input(&formatContext);
  306. // for some reason, buffer is managed (e.g. reallocated) by FFmpeg
  307. // however, it must still be freed manually by user
  308. if (context && context->buffer)
  309. av_free(context->buffer);
  310. av_free(context);
  311. }
  312. Point CVideoInstance::size()
  313. {
  314. return dimensions / ENGINE->screenHandler().getScalingFactor();
  315. }
  316. void CVideoInstance::show(const Point & position, SDL_Surface * to)
  317. {
  318. if(sws == nullptr)
  319. throw std::runtime_error("No video to show!");
  320. CSDL_Ext::blitSurface(surface, to, position * ENGINE->screenHandler().getScalingFactor());
  321. }
  322. double FFMpegStream::getCurrentFrameEndTime() const
  323. {
  324. #if(LIBAVUTIL_VERSION_MAJOR < 58)
  325. auto packet_duration = frame->pkt_duration;
  326. #else
  327. auto packet_duration = frame->duration;
  328. #endif
  329. return (frame->pts + packet_duration) * av_q2d(formatContext->streams[streamIndex]->time_base);
  330. }
  331. double FFMpegStream::getCurrentFrameDuration() const
  332. {
  333. #if(LIBAVUTIL_VERSION_MAJOR < 58)
  334. auto packet_duration = frame->pkt_duration;
  335. #else
  336. auto packet_duration = frame->duration;
  337. #endif
  338. return packet_duration * av_q2d(formatContext->streams[streamIndex]->time_base);
  339. }
  340. void CVideoInstance::tick(uint32_t msPassed)
  341. {
  342. if(sws == nullptr)
  343. throw std::runtime_error("No video to show!");
  344. if(videoEnded())
  345. throw std::runtime_error("Video already ended!");
  346. if(!startTimeInitialized)
  347. {
  348. startTime = std::chrono::steady_clock::now();
  349. startTimeInitialized = true;
  350. }
  351. auto nowTime = std::chrono::steady_clock::now();
  352. double difference = std::chrono::duration_cast<std::chrono::milliseconds>(nowTime - startTime).count() / 1000.0;
  353. int frameskipCounter = 0;
  354. while(!videoEnded() && difference >= getCurrentFrameEndTime() + getCurrentFrameDuration() && frameskipCounter < MAX_FRAMESKIP) // Frameskip
  355. {
  356. decodeNextFrame();
  357. frameskipCounter++;
  358. }
  359. if(!videoEnded() && difference >= getCurrentFrameEndTime())
  360. loadNextFrame();
  361. }
  362. void CVideoInstance::activate()
  363. {
  364. if(deactivationStartTimeHandling)
  365. {
  366. auto pauseDuration = std::chrono::steady_clock::now() - deactivationStartTime;
  367. startTime += pauseDuration;
  368. deactivationStartTimeHandling = false;
  369. }
  370. }
  371. void CVideoInstance::deactivate()
  372. {
  373. deactivationStartTime = std::chrono::steady_clock::now();
  374. deactivationStartTimeHandling = true;
  375. }
  376. struct FFMpegFormatDescription
  377. {
  378. uint8_t sampleSizeBytes;
  379. uint8_t wavFormatID;
  380. bool isPlanar;
  381. };
  382. static FFMpegFormatDescription getAudioFormatProperties(int audioFormat)
  383. {
  384. switch (audioFormat)
  385. {
  386. case AV_SAMPLE_FMT_U8: return { 1, 1, false};
  387. case AV_SAMPLE_FMT_U8P: return { 1, 1, true};
  388. case AV_SAMPLE_FMT_S16: return { 2, 1, false};
  389. case AV_SAMPLE_FMT_S16P: return { 2, 1, true};
  390. case AV_SAMPLE_FMT_S32: return { 4, 1, false};
  391. case AV_SAMPLE_FMT_S32P: return { 4, 1, true};
  392. case AV_SAMPLE_FMT_S64: return { 8, 1, false};
  393. case AV_SAMPLE_FMT_S64P: return { 8, 1, true};
  394. case AV_SAMPLE_FMT_FLT: return { 4, 3, false};
  395. case AV_SAMPLE_FMT_FLTP: return { 4, 3, true};
  396. case AV_SAMPLE_FMT_DBL: return { 8, 3, false};
  397. case AV_SAMPLE_FMT_DBLP: return { 8, 3, true};
  398. }
  399. throw std::runtime_error("Invalid audio format");
  400. }
  401. int FFMpegStream::findAudioStream() const
  402. {
  403. std::vector<int> audioStreamIndices;
  404. for(int i = 0; i < formatContext->nb_streams; i++)
  405. if(formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
  406. audioStreamIndices.push_back(i);
  407. if (audioStreamIndices.empty())
  408. return -1;
  409. if (audioStreamIndices.size() == 1)
  410. return audioStreamIndices.front();
  411. // multiple audio streams - try to pick best one based on language settings
  412. std::map<int, std::string> streamToLanguage;
  413. // Approach 1 - check if stream has language set in metadata
  414. for (auto const & index : audioStreamIndices)
  415. {
  416. const AVDictionaryEntry *e = av_dict_get(formatContext->streams[index]->metadata, "language", nullptr, 0);
  417. if (e)
  418. streamToLanguage[index] = e->value;
  419. }
  420. // Approach 2 - no metadata found. This may be video from Chronicles which have predefined (presumably hardcoded) list of languages
  421. if (streamToLanguage.empty())
  422. {
  423. if (audioStreamIndices.size() == 2)
  424. {
  425. streamToLanguage[audioStreamIndices[0]] = Languages::getLanguageOptions(Languages::ELanguages::ENGLISH).tagISO2;
  426. streamToLanguage[audioStreamIndices[1]] = Languages::getLanguageOptions(Languages::ELanguages::GERMAN).tagISO2;
  427. }
  428. if (audioStreamIndices.size() == 5)
  429. {
  430. streamToLanguage[audioStreamIndices[0]] = Languages::getLanguageOptions(Languages::ELanguages::ENGLISH).tagISO2;
  431. streamToLanguage[audioStreamIndices[1]] = Languages::getLanguageOptions(Languages::ELanguages::FRENCH).tagISO2;
  432. streamToLanguage[audioStreamIndices[2]] = Languages::getLanguageOptions(Languages::ELanguages::GERMAN).tagISO2;
  433. streamToLanguage[audioStreamIndices[3]] = Languages::getLanguageOptions(Languages::ELanguages::ITALIAN).tagISO2;
  434. streamToLanguage[audioStreamIndices[4]] = Languages::getLanguageOptions(Languages::ELanguages::SPANISH).tagISO2;
  435. }
  436. }
  437. std::string preferredLanguageName = LIBRARY->generaltexth->getPreferredLanguage();
  438. std::string preferredTag = Languages::getLanguageOptions(preferredLanguageName).tagISO2;
  439. for (auto const & entry : streamToLanguage)
  440. if (entry.second == preferredTag)
  441. return entry.first;
  442. return audioStreamIndices.front();
  443. }
  444. int FFMpegStream::findVideoStream() const
  445. {
  446. for(int i = 0; i < formatContext->nb_streams; i++)
  447. if(formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
  448. return i;
  449. return -1;
  450. }
  451. std::pair<std::unique_ptr<ui8 []>, si64> CAudioInstance::extractAudio(const VideoPath & videoToOpen)
  452. {
  453. if (!openInput(videoToOpen))
  454. return { nullptr, 0};
  455. if (!openContext())
  456. return { nullptr, 0};
  457. int audioStreamIndex = findAudioStream();
  458. if (audioStreamIndex == -1)
  459. return { nullptr, 0};
  460. openCodec(audioStreamIndex);
  461. const auto * codecpar = getCodecParameters();
  462. std::vector<ui8> samples;
  463. auto formatProperties = getAudioFormatProperties(codecpar->format);
  464. #if(LIBAVUTIL_VERSION_MAJOR < 58)
  465. int numChannels = codecpar->channels;
  466. #else
  467. int numChannels = codecpar->ch_layout.nb_channels;
  468. #endif
  469. samples.reserve(44100 * 5); // arbitrary 5-second buffer to reduce reallocations
  470. if (formatProperties.isPlanar && numChannels > 1)
  471. {
  472. // Format is 'planar', which is not supported by wav / SDL
  473. // Use swresample part of ffmpeg to deplanarize audio into format supported by wav / SDL
  474. auto sourceFormat = static_cast<AVSampleFormat>(codecpar->format);
  475. auto targetFormat = av_get_alt_sample_fmt(sourceFormat, false);
  476. SwrContext * swr_ctx = swr_alloc();
  477. #if (LIBAVUTIL_VERSION_MAJOR < 58)
  478. av_opt_set_channel_layout(swr_ctx, "in_chlayout", codecpar->channel_layout, 0);
  479. av_opt_set_channel_layout(swr_ctx, "out_chlayout", codecpar->channel_layout, 0);
  480. #else
  481. av_opt_set_chlayout(swr_ctx, "in_chlayout", &codecpar->ch_layout, 0);
  482. av_opt_set_chlayout(swr_ctx, "out_chlayout", &codecpar->ch_layout, 0);
  483. #endif
  484. av_opt_set_int(swr_ctx, "in_sample_rate", codecpar->sample_rate, 0);
  485. av_opt_set_sample_fmt(swr_ctx, "in_sample_fmt", sourceFormat, 0);
  486. av_opt_set_int(swr_ctx, "out_sample_rate", codecpar->sample_rate, 0);
  487. av_opt_set_sample_fmt(swr_ctx, "out_sample_fmt", targetFormat, 0);
  488. int initResult = swr_init(swr_ctx);
  489. if (initResult < 0)
  490. throwFFmpegError(initResult);
  491. std::vector<uint8_t> frameSamplesBuffer;
  492. for (;;)
  493. {
  494. decodeNextFrame();
  495. const AVFrame * frame = getCurrentFrame();
  496. if (!frame)
  497. break;
  498. size_t samplesToRead = frame->nb_samples * numChannels;
  499. size_t bytesToRead = samplesToRead * formatProperties.sampleSizeBytes;
  500. frameSamplesBuffer.resize(std::max(frameSamplesBuffer.size(), bytesToRead));
  501. uint8_t * frameSamplesPtr = frameSamplesBuffer.data();
  502. int result = swr_convert(swr_ctx, &frameSamplesPtr, frame->nb_samples, const_cast<const uint8_t **>(frame->data), frame->nb_samples);
  503. if (result < 0)
  504. throwFFmpegError(result);
  505. size_t samplesToCopy = result * numChannels;
  506. size_t bytesToCopy = samplesToCopy * formatProperties.sampleSizeBytes;
  507. samples.insert(samples.end(), frameSamplesBuffer.begin(), frameSamplesBuffer.begin() + bytesToCopy);
  508. }
  509. swr_free(&swr_ctx);
  510. }
  511. else
  512. {
  513. for (;;)
  514. {
  515. decodeNextFrame();
  516. const AVFrame * frame = getCurrentFrame();
  517. if (!frame)
  518. break;
  519. size_t samplesToRead = frame->nb_samples * numChannels;
  520. size_t bytesToRead = samplesToRead * formatProperties.sampleSizeBytes;
  521. samples.insert(samples.end(), frame->data[0], frame->data[0] + bytesToRead);
  522. }
  523. }
  524. struct WavHeader {
  525. ui8 RIFF[4] = {'R', 'I', 'F', 'F'};
  526. ui32 ChunkSize;
  527. ui8 WAVE[4] = {'W', 'A', 'V', 'E'};
  528. ui8 fmt[4] = {'f', 'm', 't', ' '};
  529. ui32 Subchunk1Size = 16;
  530. ui16 AudioFormat = 1;
  531. ui16 NumOfChan = 2;
  532. ui32 SamplesPerSec = 22050;
  533. ui32 bytesPerSec = 22050 * 2;
  534. ui16 blockAlign = 1;
  535. ui16 bitsPerSample = 32;
  536. ui8 Subchunk2ID[4] = {'d', 'a', 't', 'a'};
  537. ui32 Subchunk2Size;
  538. };
  539. WavHeader wav;
  540. wav.ChunkSize = samples.size() + sizeof(WavHeader) - 8;
  541. wav.AudioFormat = formatProperties.wavFormatID; // 1 = PCM, 3 = IEEE float
  542. wav.NumOfChan = numChannels;
  543. wav.SamplesPerSec = codecpar->sample_rate;
  544. wav.bytesPerSec = codecpar->sample_rate * formatProperties.sampleSizeBytes;
  545. wav.bitsPerSample = formatProperties.sampleSizeBytes * 8;
  546. wav.Subchunk2Size = samples.size() + sizeof(WavHeader) - 44;
  547. auto * wavPtr = reinterpret_cast<ui8*>(&wav);
  548. auto dat = std::make_pair(std::make_unique<ui8[]>(samples.size() + sizeof(WavHeader)), samples.size() + sizeof(WavHeader));
  549. std::copy(wavPtr, wavPtr + sizeof(WavHeader), dat.first.get());
  550. std::copy(samples.begin(), samples.end(), dat.first.get() + sizeof(WavHeader));
  551. return dat;
  552. }
  553. std::unique_ptr<IVideoInstance> CVideoPlayer::open(const VideoPath & name, float scaleFactor)
  554. {
  555. logGlobal->trace("Opening video: %s", name.getOriginalName());
  556. auto result = std::make_unique<CVideoInstance>();
  557. if (!result->openInput(name))
  558. return nullptr;
  559. if (!result->openVideo())
  560. return nullptr;
  561. result->prepareOutput(scaleFactor, false);
  562. result->loadNextFrame(); // prepare 1st frame
  563. return result;
  564. }
  565. std::pair<std::unique_ptr<ui8[]>, si64> CVideoPlayer::getAudio(const VideoPath & videoToOpen)
  566. {
  567. logGlobal->trace("Opening video: %s", videoToOpen.getOriginalName());
  568. AudioPath audioPath = videoToOpen.toType<EResType::SOUND>();
  569. AudioPath audioPathVideoDir = audioPath.addPrefix("VIDEO/");
  570. if(CResourceHandler::get()->existsResource(audioPath))
  571. return CResourceHandler::get()->load(audioPath)->readAll();
  572. if(CResourceHandler::get()->existsResource(audioPathVideoDir))
  573. return CResourceHandler::get()->load(audioPathVideoDir)->readAll();
  574. CAudioInstance audio;
  575. return audio.extractAudio(videoToOpen);
  576. }
  577. #endif