video_sampler.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. #include "video_sampler.h"
  2. #include <c10/util/Logging.h>
  3. #include "util.h"
  4. // www.ffmpeg.org/doxygen/0.5/swscale-example_8c-source.html
  5. namespace ffmpeg {
  6. namespace {
  7. // Setup the data pointers and linesizes based on the specified image
  8. // parameters and the provided array. This sets up "planes" to point to a
  9. // "buffer"
  10. // NOTE: this is most likely culprit behind #3534
  11. //
  12. // Args:
  13. // fmt: desired output video format
  14. // buffer: source constant image buffer (in different format) that will contain
  15. // the final image after SWScale planes: destination data pointer to be filled
  16. // lineSize: target destination linesize (always {0})
  17. int preparePlanes(
  18. const VideoFormat& fmt,
  19. const uint8_t* buffer,
  20. uint8_t** planes,
  21. int* lineSize) {
  22. int result;
  23. // NOTE: 1 at the end of av_fill_arrays is the value used for alignment
  24. if ((result = av_image_fill_arrays(
  25. planes,
  26. lineSize,
  27. buffer,
  28. (AVPixelFormat)fmt.format,
  29. fmt.width,
  30. fmt.height,
  31. 1)) < 0) {
  32. LOG(ERROR) << "av_image_fill_arrays failed, err: "
  33. << Util::generateErrorDesc(result);
  34. }
  35. return result;
  36. }
  37. // Scale (and crop) the image slice in srcSlice and put the resulting scaled
  38. // slice to `planes` buffer, which is mapped to be `out` via preparePlanes as
  39. // `sws_scale` cannot access buffers directly.
  40. //
  41. // Args:
  42. // context: SWSContext allocated on line 119 (if crop, optional) or 163 (if
  43. // scale) srcSlice: frame data in YUV420P srcStride: the array containing the
  44. // strides for each plane of the source
  45. // image (from AVFrame->linesize[0])
  46. // out: destination buffer
  47. // planes: indirect destination buffer (mapped to "out" via preparePlanes)
  48. // lines: destination linesize; constant {0}
  49. int transformImage(
  50. SwsContext* context,
  51. const uint8_t* const srcSlice[],
  52. int srcStride[],
  53. VideoFormat inFormat,
  54. VideoFormat outFormat,
  55. uint8_t* out,
  56. uint8_t* planes[],
  57. int lines[]) {
  58. int result;
  59. if ((result = preparePlanes(outFormat, out, planes, lines)) < 0) {
  60. return result;
  61. }
  62. if (context) {
  63. // NOTE: srcY stride always 0: this is a parameter of YUV format
  64. if ((result = sws_scale(
  65. context, srcSlice, srcStride, 0, inFormat.height, planes, lines)) <
  66. 0) {
  67. LOG(ERROR) << "sws_scale failed, err: "
  68. << Util::generateErrorDesc(result);
  69. return result;
  70. }
  71. } else if (
  72. inFormat.width == outFormat.width &&
  73. inFormat.height == outFormat.height &&
  74. inFormat.format == outFormat.format) {
  75. // Copy planes without using sws_scale if sws_getContext failed.
  76. av_image_copy(
  77. planes,
  78. lines,
  79. (const uint8_t**)srcSlice,
  80. srcStride,
  81. (AVPixelFormat)inFormat.format,
  82. inFormat.width,
  83. inFormat.height);
  84. } else {
  85. LOG(ERROR) << "Invalid scale context format " << inFormat.format;
  86. return AVERROR(EINVAL);
  87. }
  88. return 0;
  89. }
  90. } // namespace
  91. VideoSampler::VideoSampler(int swsFlags, int64_t loggingUuid)
  92. : swsFlags_(swsFlags), loggingUuid_(loggingUuid) {}
  93. VideoSampler::~VideoSampler() {
  94. cleanUp();
  95. }
  96. void VideoSampler::shutdown() {
  97. cleanUp();
  98. }
  99. bool VideoSampler::init(const SamplerParameters& params) {
  100. cleanUp();
  101. if (params.out.video.cropImage != 0) {
  102. if (!Util::validateVideoFormat(params.out.video)) {
  103. LOG(ERROR) << "Invalid video format"
  104. << ", width: " << params.out.video.width
  105. << ", height: " << params.out.video.height
  106. << ", format: " << params.out.video.format
  107. << ", minDimension: " << params.out.video.minDimension
  108. << ", crop: " << params.out.video.cropImage;
  109. return false;
  110. }
  111. scaleFormat_.format = params.out.video.format;
  112. Util::setFormatDimensions(
  113. scaleFormat_.width,
  114. scaleFormat_.height,
  115. params.out.video.width,
  116. params.out.video.height,
  117. params.in.video.width,
  118. params.in.video.height,
  119. 0,
  120. 0,
  121. 1);
  122. if (!(scaleFormat_ == params_.out.video)) { // crop required
  123. cropContext_ = sws_getContext(
  124. params.out.video.width,
  125. params.out.video.height,
  126. (AVPixelFormat)params.out.video.format,
  127. params.out.video.width,
  128. params.out.video.height,
  129. (AVPixelFormat)params.out.video.format,
  130. swsFlags_,
  131. nullptr,
  132. nullptr,
  133. nullptr);
  134. if (!cropContext_) {
  135. LOG(ERROR) << "sws_getContext failed for crop context";
  136. return false;
  137. }
  138. const auto scaleImageSize = av_image_get_buffer_size(
  139. (AVPixelFormat)scaleFormat_.format,
  140. scaleFormat_.width,
  141. scaleFormat_.height,
  142. 1);
  143. scaleBuffer_.resize(scaleImageSize);
  144. }
  145. } else {
  146. scaleFormat_ = params.out.video;
  147. }
  148. VLOG(1) << "Input format #" << loggingUuid_ << ", width "
  149. << params.in.video.width << ", height " << params.in.video.height
  150. << ", format " << params.in.video.format << ", minDimension "
  151. << params.in.video.minDimension << ", cropImage "
  152. << params.in.video.cropImage;
  153. VLOG(1) << "Scale format #" << loggingUuid_ << ", width "
  154. << scaleFormat_.width << ", height " << scaleFormat_.height
  155. << ", format " << scaleFormat_.format << ", minDimension "
  156. << scaleFormat_.minDimension << ", cropImage "
  157. << scaleFormat_.cropImage;
  158. VLOG(1) << "Crop format #" << loggingUuid_ << ", width "
  159. << params.out.video.width << ", height " << params.out.video.height
  160. << ", format " << params.out.video.format << ", minDimension "
  161. << params.out.video.minDimension << ", cropImage "
  162. << params.out.video.cropImage;
  163. // set output format
  164. params_ = params;
  165. if (params.in.video.format == AV_PIX_FMT_YUV420P) {
  166. /* When the video width and height are not multiples of 8,
  167. * and there is no size change in the conversion,
  168. * a blurry screen will appear on the right side
  169. * This problem was discovered in 2012 and
  170. * continues to exist in version 4.1.3 in 2019
  171. * This problem can be avoided by increasing SWS_ACCURATE_RND
  172. * details https://trac.ffmpeg.org/ticket/1582
  173. */
  174. if ((params.in.video.width & 0x7) || (params.in.video.height & 0x7)) {
  175. VLOG(1) << "The width " << params.in.video.width << " and height "
  176. << params.in.video.height << " the image is not a multiple of 8, "
  177. << "the decoding speed may be reduced";
  178. swsFlags_ |= SWS_ACCURATE_RND;
  179. }
  180. }
  181. scaleContext_ = sws_getContext(
  182. params.in.video.width,
  183. params.in.video.height,
  184. (AVPixelFormat)params.in.video.format,
  185. scaleFormat_.width,
  186. scaleFormat_.height,
  187. (AVPixelFormat)scaleFormat_.format,
  188. swsFlags_,
  189. nullptr,
  190. nullptr,
  191. nullptr);
  192. // sws_getContext might fail if in/out format == AV_PIX_FMT_PAL8 (png format)
  193. // Return true if input and output formats/width/height are identical
  194. // Check scaleContext_ for nullptr in transformImage to copy planes directly
  195. if (params.in.video.width == scaleFormat_.width &&
  196. params.in.video.height == scaleFormat_.height &&
  197. params.in.video.format == scaleFormat_.format) {
  198. return true;
  199. }
  200. return scaleContext_ != nullptr;
  201. }
  202. // Main body of the sample function called from one of the overloads below
  203. //
  204. // Args:
  205. // srcSlice: decoded AVFrame->data perpared buffer
  206. // srcStride: linesize (usually obtained from AVFrame->linesize)
  207. // out: return buffer (ByteStorage*)
  208. int VideoSampler::sample(
  209. const uint8_t* const srcSlice[],
  210. int srcStride[],
  211. ByteStorage* out) {
  212. int result;
  213. // scaled and cropped image
  214. int outImageSize = av_image_get_buffer_size(
  215. (AVPixelFormat)params_.out.video.format,
  216. params_.out.video.width,
  217. params_.out.video.height,
  218. 1);
  219. out->ensure(outImageSize);
  220. uint8_t* scalePlanes[4] = {nullptr};
  221. int scaleLines[4] = {0};
  222. // perform scale first
  223. if ((result = transformImage(
  224. scaleContext_,
  225. srcSlice,
  226. srcStride,
  227. params_.in.video,
  228. scaleFormat_,
  229. // for crop use internal buffer
  230. cropContext_ ? scaleBuffer_.data() : out->writableTail(),
  231. scalePlanes,
  232. scaleLines))) {
  233. return result;
  234. }
  235. // is crop required?
  236. if (cropContext_) {
  237. uint8_t* cropPlanes[4] = {nullptr};
  238. int cropLines[4] = {0};
  239. if (params_.out.video.height < scaleFormat_.height) {
  240. // Destination image is wider of source image: cut top and bottom
  241. for (size_t i = 0; i < 4 && scalePlanes[i] != nullptr; ++i) {
  242. scalePlanes[i] += scaleLines[i] *
  243. (scaleFormat_.height - params_.out.video.height) / 2;
  244. }
  245. } else {
  246. // Source image is wider of destination image: cut sides
  247. for (size_t i = 0; i < 4 && scalePlanes[i] != nullptr; ++i) {
  248. scalePlanes[i] += scaleLines[i] *
  249. (scaleFormat_.width - params_.out.video.width) / 2 /
  250. scaleFormat_.width;
  251. }
  252. }
  253. // crop image
  254. if ((result = transformImage(
  255. cropContext_,
  256. scalePlanes,
  257. scaleLines,
  258. params_.out.video,
  259. params_.out.video,
  260. out->writableTail(),
  261. cropPlanes,
  262. cropLines))) {
  263. return result;
  264. }
  265. }
  266. out->append(outImageSize);
  267. return outImageSize;
  268. }
  269. // Call from `video_stream.cpp::114` - occurs during file reads
  270. int VideoSampler::sample(AVFrame* frame, ByteStorage* out) {
  271. if (!frame) {
  272. return 0; // no flush for videos
  273. }
  274. return sample(frame->data, frame->linesize, out);
  275. }
  276. // Call from `video_stream.cpp::114` - not sure when this occurs
  277. int VideoSampler::sample(const ByteStorage* in, ByteStorage* out) {
  278. if (!in) {
  279. return 0; // no flush for videos
  280. }
  281. int result;
  282. uint8_t* inPlanes[4] = {nullptr};
  283. int inLineSize[4] = {0};
  284. if ((result = preparePlanes(
  285. params_.in.video, in->data(), inPlanes, inLineSize)) < 0) {
  286. return result;
  287. }
  288. return sample(inPlanes, inLineSize, out);
  289. }
  290. void VideoSampler::cleanUp() {
  291. if (scaleContext_) {
  292. sws_freeContext(scaleContext_);
  293. scaleContext_ = nullptr;
  294. }
  295. if (cropContext_) {
  296. sws_freeContext(cropContext_);
  297. cropContext_ = nullptr;
  298. scaleBuffer_.clear();
  299. }
  300. }
  301. } // namespace ffmpeg