| 21 | } // namespace |
| 22 | |
| 23 | JpegThumbnail encodeThumbnailJpeg(const DecodedFrame& src_in, int max_width, int quality) { |
| 24 | JpegThumbnail out; |
| 25 | // Hardware decode emits NV12; the JPEG path below works in planar YUV420P, so |
| 26 | // deinterleave first. Cheap: only the ~1/N sampled frames reach the thumbnail cache. |
| 27 | DecodedFrame nv12_planar; |
| 28 | const DecodedFrame* src_ptr = &src_in; |
| 29 | if (src_in.format == PixelFormat::kNV12) { |
| 30 | nv12_planar = nv12ToYuv420p(src_in); |
| 31 | src_ptr = &nv12_planar; |
| 32 | } |
| 33 | const DecodedFrame& src = *src_ptr; |
| 34 | if (src.format != PixelFormat::kYUV420P || src.isNull() || src.width <= 0 || src.height <= 0) { |
| 35 | return out; |
| 36 | } |
| 37 | if (max_width <= 0) { |
| 38 | max_width = kThumbnailMaxWidth; |
| 39 | } |
| 40 | |
| 41 | // Target dimensions: cap width, preserve aspect, keep even (YUV420 chroma). |
| 42 | int dst_w = src.width; |
| 43 | int dst_h = src.height; |
| 44 | if (dst_w > max_width) { |
| 45 | dst_h = evenDown(static_cast<int>(static_cast<int64_t>(dst_h) * max_width / dst_w)); |
| 46 | dst_w = evenDown(max_width); |
| 47 | if (dst_h <= 0) { |
| 48 | dst_h = 2; |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | // YUV420P bytes to compress: the source verbatim (no downscale) or an sws copy. |
| 53 | const uint8_t* yuv = nullptr; |
| 54 | std::vector<uint8_t> scaled; |
| 55 | if (dst_w == src.width && dst_h == src.height) { |
| 56 | yuv = src.pixels->data(); |
| 57 | } else { |
| 58 | scaled.resize(expectedBufferSize(dst_w, dst_h, PixelFormat::kYUV420P)); |
| 59 | const int src_uvw = (src.width + 1) / 2; |
| 60 | const int src_uvh = (src.height + 1) / 2; |
| 61 | const int dst_uvw = (dst_w + 1) / 2; |
| 62 | const size_t src_y = static_cast<size_t>(src.width) * static_cast<size_t>(src.height); |
| 63 | const size_t src_uv = static_cast<size_t>(src_uvw) * static_cast<size_t>(src_uvh); |
| 64 | const size_t dst_y = static_cast<size_t>(dst_w) * static_cast<size_t>(dst_h); |
| 65 | const size_t dst_uv = static_cast<size_t>(dst_uvw) * static_cast<size_t>((dst_h + 1) / 2); |
| 66 | const uint8_t* sbase = src.pixels->data(); |
| 67 | const uint8_t* src_planes[3] = {sbase, sbase + src_y, sbase + src_y + src_uv}; |
| 68 | const int src_strides[3] = {src.width, src_uvw, src_uvw}; |
| 69 | uint8_t* dst_planes[3] = {scaled.data(), scaled.data() + dst_y, scaled.data() + dst_y + dst_uv}; |
| 70 | const int dst_strides[3] = {dst_w, dst_uvw, dst_uvw}; |
| 71 | |
| 72 | SwsContext* sws = sws_getContext( |
| 73 | src.width, src.height, AV_PIX_FMT_YUV420P, dst_w, dst_h, AV_PIX_FMT_YUV420P, SWS_FAST_BILINEAR, nullptr, |
| 74 | nullptr, nullptr); |
| 75 | if (sws == nullptr) { |
| 76 | return out; |
| 77 | } |
| 78 | sws_scale(sws, src_planes, src_strides, 0, src.height, dst_planes, dst_strides); |
| 79 | sws_freeContext(sws); |
| 80 | yuv = scaled.data(); |