| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/gpu/vaapi/vaapi_image_decode_accelerator_worker.h" |
| |
| #include "base/task/thread_pool.h" |
| #include "string.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/containers/span.h" |
| #include "base/feature_list.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/task/post_task.h" |
| #include "base/trace_event/trace_event.h" |
| #include "gpu/config/gpu_finch_features.h" |
| #include "media/gpu/macros.h" |
| #include "media/gpu/vaapi/va_surface.h" |
| #include "media/gpu/vaapi/vaapi_image_decoder.h" |
| #include "media/gpu/vaapi/vaapi_jpeg_decoder.h" |
| #include "media/gpu/vaapi/vaapi_webp_decoder.h" |
| #include "media/gpu/vaapi/vaapi_wrapper.h" |
| #include "media/parsers/webp_parser.h" |
| #include "mojo/public/cpp/bindings/callback_helpers.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "ui/gfx/gpu_memory_buffer.h" |
| #include "ui/gfx/linux/native_pixmap_dmabuf.h" |
| #include "ui/gfx/native_pixmap_handle.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| bool IsJpegImage(base::span<const uint8_t> encoded_data) { |
| if (encoded_data.size() < 3u) |
| return false; |
| return memcmp("\xFF\xD8\xFF", encoded_data.data(), 3u) == 0; |
| } |
| |
| // Uses |decoder| to decode the image corresponding to |encoded_data|. |
| // |decode_cb| is called when finished or when an error is encountered. We don't |
| // support decoding to scale, so |output_size| is only used for tracing. |
| void DecodeTask( |
| VaapiImageDecoder* decoder, |
| std::vector<uint8_t> encoded_data, |
| const gfx::Size& output_size, |
| gpu::ImageDecodeAcceleratorWorker::CompletedDecodeCB decode_cb) { |
| TRACE_EVENT2("jpeg", "VaapiImageDecodeAcceleratorWorker::DecodeTask", |
| "encoded_bytes", encoded_data.size(), "output_size", |
| output_size.ToString()); |
| gpu::ImageDecodeAcceleratorWorker::CompletedDecodeCB scoped_decode_callback = |
| mojo::WrapCallbackWithDefaultInvokeIfNotRun(std::move(decode_cb), |
| nullptr); |
| |
| // Decode into a VAAPI surface. |
| if (!decoder) { |
| DVLOGF(1) << "No decoder is available for supplied image"; |
| return; |
| } |
| VaapiImageDecodeStatus status = decoder->Decode(encoded_data); |
| if (status != VaapiImageDecodeStatus::kSuccess) { |
| DVLOGF(1) << "Failed to decode - status = " |
| << static_cast<uint32_t>(status); |
| return; |
| } |
| |
| // Export the decode result as a NativePixmap. |
| std::unique_ptr<NativePixmapAndSizeInfo> exported_pixmap = |
| decoder->ExportAsNativePixmapDmaBuf(&status); |
| if (status != VaapiImageDecodeStatus::kSuccess) { |
| DVLOGF(1) << "Failed to export surface - status = " |
| << static_cast<uint32_t>(status); |
| return; |
| } |
| DCHECK(exported_pixmap); |
| DCHECK(exported_pixmap->pixmap); |
| if (exported_pixmap->pixmap->GetBufferSize() != output_size) { |
| DVLOGF(1) << "Scaling is not supported"; |
| return; |
| } |
| |
| // Output the decoded data. |
| gfx::NativePixmapHandle pixmap_handle = |
| exported_pixmap->pixmap->ExportHandle(); |
| // If a dup() failed while exporting the handle, we would get no planes. |
| if (pixmap_handle.planes.empty()) { |
| DVLOGF(1) << "Could not export the NativePixmapHandle"; |
| return; |
| } |
| auto result = |
| std::make_unique<gpu::ImageDecodeAcceleratorWorker::DecodeResult>(); |
| result->handle.type = gfx::GpuMemoryBufferType::NATIVE_PIXMAP; |
| result->handle.native_pixmap_handle = std::move(pixmap_handle); |
| result->visible_size = exported_pixmap->pixmap->GetBufferSize(); |
| result->buffer_format = exported_pixmap->pixmap->GetBufferFormat(); |
| result->buffer_byte_size = exported_pixmap->byte_size; |
| result->yuv_color_space = decoder->GetYUVColorSpace(); |
| std::move(scoped_decode_callback).Run(std::move(result)); |
| } |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<VaapiImageDecodeAcceleratorWorker> |
| VaapiImageDecodeAcceleratorWorker::Create() { |
| // TODO(crbug.com/988123): revisit the |
| // Media.VaapiImageDecodeAcceleratorWorker.VAAPIError UMA to be able to record |
| // WebP and JPEG failures separately. |
| const auto uma_cb = |
| base::BindRepeating(&ReportVaapiErrorToUMA, |
| "Media.VaapiImageDecodeAcceleratorWorker.VAAPIError"); |
| VaapiImageDecoderVector decoders; |
| |
| auto jpeg_decoder = std::make_unique<VaapiJpegDecoder>(); |
| // TODO(crbug.com/974438): we can't advertise accelerated image decoding in |
| // AMD until we support VAAPI surfaces with multiple buffer objects. |
| if (VaapiWrapper::GetImplementationType() != VAImplementation::kMesaGallium && |
| jpeg_decoder->Initialize(uma_cb)) { |
| decoders.push_back(std::move(jpeg_decoder)); |
| } |
| |
| auto webp_decoder = std::make_unique<VaapiWebPDecoder>(); |
| if (webp_decoder->Initialize(uma_cb)) |
| decoders.push_back(std::move(webp_decoder)); |
| |
| // If there are no decoders due to disabled flags or initialization failure, |
| // return nullptr. |
| if (decoders.empty()) |
| return nullptr; |
| |
| return base::WrapUnique( |
| new VaapiImageDecodeAcceleratorWorker(std::move(decoders))); |
| } |
| |
| VaapiImageDecodeAcceleratorWorker::VaapiImageDecodeAcceleratorWorker( |
| VaapiImageDecoderVector decoders) { |
| DETACH_FROM_SEQUENCE(io_sequence_checker_); |
| decoder_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner({}); |
| DCHECK(decoder_task_runner_); |
| |
| DCHECK(!decoders.empty()); |
| for (auto& decoder : decoders) { |
| supported_profiles_.push_back(decoder->GetSupportedProfile()); |
| const gpu::ImageDecodeAcceleratorType type = decoder->GetType(); |
| decoders_[type] = std::move(decoder); |
| } |
| } |
| |
| VaapiImageDecodeAcceleratorWorker::~VaapiImageDecodeAcceleratorWorker() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(main_sequence_checker_); |
| DCHECK(decoder_task_runner_); |
| for (auto& decoder : decoders_) |
| decoder_task_runner_->DeleteSoon(FROM_HERE, std::move(decoder.second)); |
| } |
| |
| gpu::ImageDecodeAcceleratorSupportedProfiles |
| VaapiImageDecodeAcceleratorWorker::GetSupportedProfiles() { |
| return supported_profiles_; |
| } |
| |
| VaapiImageDecoder* VaapiImageDecodeAcceleratorWorker::GetDecoderForImage( |
| const std::vector<uint8_t>& encoded_data) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(io_sequence_checker_); |
| auto result = decoders_.end(); |
| |
| if (base::FeatureList::IsEnabled( |
| features::kVaapiJpegImageDecodeAcceleration) && |
| IsJpegImage(encoded_data)) { |
| result = decoders_.find(gpu::ImageDecodeAcceleratorType::kJpeg); |
| } else if (base::FeatureList::IsEnabled( |
| features::kVaapiWebPImageDecodeAcceleration) && |
| IsLossyWebPImage(encoded_data)) { |
| result = decoders_.find(gpu::ImageDecodeAcceleratorType::kWebP); |
| } |
| |
| return result == decoders_.end() ? nullptr : result->second.get(); |
| } |
| |
| void VaapiImageDecodeAcceleratorWorker::Decode( |
| std::vector<uint8_t> encoded_data, |
| const gfx::Size& output_size, |
| CompletedDecodeCB decode_cb) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(io_sequence_checker_); |
| DCHECK(decoder_task_runner_); |
| |
| // We defer checking for a null |decoder| until DecodeTask() because the |
| // gpu::ImageDecodeAcceleratorWorker interface mandates that the callback be |
| // called asynchronously. |
| VaapiImageDecoder* decoder = GetDecoderForImage(encoded_data); |
| decoder_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&DecodeTask, decoder, std::move(encoded_data), |
| output_size, std::move(decode_cb))); |
| } |
| |
| } // namespace media |