| // Copyright 2017 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_jpeg_encode_accelerator.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/sequence_checker.h" |
| #include "base/task/post_task.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/trace_event/trace_event.h" |
| #include "media/base/bind_to_current_loop.h" |
| #include "media/base/video_frame.h" |
| #include "media/filters/jpeg_parser.h" |
| #include "media/gpu/macros.h" |
| #include "media/gpu/vaapi/vaapi_jpeg_encoder.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| // UMA results that the VaapiJpegEncodeAccelerator class reports. |
| // These values are persisted to logs, and should therefore never be renumbered |
| // nor reused. |
| enum VAJEAEncoderResult { |
| VAAPI_SUCCESS = 0, |
| VAAPI_ERROR, |
| VAJEA_ENCODER_RESULT_MAX = VAAPI_ERROR, |
| }; |
| |
| static void ReportToUMA(VAJEAEncoderResult result) { |
| UMA_HISTOGRAM_ENUMERATION("Media.VAJEA.EncoderResult", result, |
| VAJEAEncoderResult::VAJEA_ENCODER_RESULT_MAX + 1); |
| } |
| } // namespace |
| |
| VaapiJpegEncodeAccelerator::EncodeRequest::EncodeRequest( |
| int32_t buffer_id, |
| scoped_refptr<VideoFrame> video_frame, |
| std::unique_ptr<UnalignedSharedMemory> exif_shm, |
| std::unique_ptr<UnalignedSharedMemory> output_shm, |
| int quality) |
| : buffer_id(buffer_id), |
| video_frame(std::move(video_frame)), |
| exif_shm(std::move(exif_shm)), |
| output_shm(std::move(output_shm)), |
| quality(quality) {} |
| |
| VaapiJpegEncodeAccelerator::EncodeRequest::~EncodeRequest() {} |
| |
| class VaapiJpegEncodeAccelerator::Encoder { |
| public: |
| Encoder(scoped_refptr<VaapiWrapper> vaapi_wrapper, |
| base::RepeatingCallback<void(int32_t, size_t)> video_frame_ready_cb, |
| base::RepeatingCallback<void(int32_t, Status)> notify_error_cb); |
| ~Encoder(); |
| |
| // Processes one encode |request|. |
| void EncodeTask(std::unique_ptr<EncodeRequest> request); |
| |
| private: |
| // |cached_output_buffer_id_| is the last allocated VABuffer during |
| // EncodeTask() and |cached_output_buffer_size_| is the size of it. |
| // If the next call to EncodeTask() does not require a buffer bigger than |
| // |cached_output_buffer_size_|, |cached_output_buffer_id_| will be reused. |
| size_t cached_output_buffer_size_; |
| VABufferID cached_output_buffer_id_; |
| |
| std::unique_ptr<VaapiJpegEncoder> jpeg_encoder_; |
| scoped_refptr<VaapiWrapper> vaapi_wrapper_; |
| |
| base::RepeatingCallback<void(int32_t, size_t)> video_frame_ready_cb_; |
| base::RepeatingCallback<void(int32_t, Status)> notify_error_cb_; |
| |
| // The current VA surface used for encoding. |
| VASurfaceID va_surface_id_; |
| // The size of the surface associated with |va_surface_id_|. |
| gfx::Size surface_size_; |
| |
| SEQUENCE_CHECKER(sequence_checker_); |
| |
| DISALLOW_COPY_AND_ASSIGN(Encoder); |
| }; |
| |
| VaapiJpegEncodeAccelerator::Encoder::Encoder( |
| scoped_refptr<VaapiWrapper> vaapi_wrapper, |
| base::RepeatingCallback<void(int32_t, size_t)> video_frame_ready_cb, |
| base::RepeatingCallback<void(int32_t, Status)> notify_error_cb) |
| : cached_output_buffer_size_(0), |
| jpeg_encoder_(new VaapiJpegEncoder(vaapi_wrapper)), |
| vaapi_wrapper_(std::move(vaapi_wrapper)), |
| video_frame_ready_cb_(std::move(video_frame_ready_cb)), |
| notify_error_cb_(std::move(notify_error_cb)), |
| va_surface_id_(VA_INVALID_SURFACE) { |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| } |
| |
| VaapiJpegEncodeAccelerator::Encoder::~Encoder() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| void VaapiJpegEncodeAccelerator::Encoder::EncodeTask( |
| std::unique_ptr<EncodeRequest> request) { |
| DVLOGF(4); |
| TRACE_EVENT0("jpeg", "EncodeTask"); |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| const int buffer_id = request->buffer_id; |
| gfx::Size input_size = request->video_frame->coded_size(); |
| |
| // Recreate VASurface if the video frame's size changed. |
| if (input_size != surface_size_ || va_surface_id_ == VA_INVALID_SURFACE) { |
| vaapi_wrapper_->DestroyContextAndSurfaces(); |
| va_surface_id_ = VA_INVALID_SURFACE; |
| surface_size_ = gfx::Size(); |
| |
| std::vector<VASurfaceID> va_surfaces; |
| if (!vaapi_wrapper_->CreateContextAndSurfaces( |
| VA_RT_FORMAT_YUV420, input_size, 1, &va_surfaces)) { |
| VLOGF(1) << "Failed to create VA surface"; |
| notify_error_cb_.Run(buffer_id, PLATFORM_FAILURE); |
| return; |
| } |
| va_surface_id_ = va_surfaces[0]; |
| surface_size_ = input_size; |
| } |
| |
| if (!vaapi_wrapper_->UploadVideoFrameToSurface(request->video_frame, |
| va_surface_id_)) { |
| VLOGF(1) << "Failed to upload video frame to VA surface"; |
| notify_error_cb_.Run(buffer_id, PLATFORM_FAILURE); |
| return; |
| } |
| |
| // Create output buffer for encoding result. |
| size_t max_coded_buffer_size = |
| VaapiJpegEncoder::GetMaxCodedBufferSize(input_size); |
| if (max_coded_buffer_size > cached_output_buffer_size_) { |
| vaapi_wrapper_->DestroyVABuffers(); |
| cached_output_buffer_size_ = 0; |
| |
| VABufferID output_buffer_id; |
| if (!vaapi_wrapper_->CreateVABuffer(max_coded_buffer_size, |
| &output_buffer_id)) { |
| VLOGF(1) << "Failed to create VA buffer for encoding output"; |
| notify_error_cb_.Run(buffer_id, PLATFORM_FAILURE); |
| return; |
| } |
| cached_output_buffer_size_ = max_coded_buffer_size; |
| cached_output_buffer_id_ = output_buffer_id; |
| } |
| |
| uint8_t* exif_buffer = nullptr; |
| size_t exif_buffer_size = 0; |
| if (request->exif_shm) { |
| exif_buffer = static_cast<uint8_t*>(request->exif_shm->memory()); |
| exif_buffer_size = request->exif_shm->size(); |
| } |
| |
| // When the exif buffer contains a thumbnail, the VAAPI encoder would |
| // generate a corrupted JPEG. We can work around the problem by supplying an |
| // all-zero buffer with the same size and fill in the real exif buffer after |
| // encoding. |
| // TODO(shenghao): Remove this mechanism after b/79840013 is fixed. |
| std::vector<uint8_t> exif_buffer_dummy(exif_buffer_size, 0); |
| size_t exif_offset = 0; |
| if (!jpeg_encoder_->Encode(input_size, exif_buffer_dummy.data(), |
| exif_buffer_size, request->quality, va_surface_id_, |
| cached_output_buffer_id_, &exif_offset)) { |
| VLOGF(1) << "Encode JPEG failed"; |
| notify_error_cb_.Run(buffer_id, PLATFORM_FAILURE); |
| return; |
| } |
| |
| // Get the encoded output. DownloadFromVABuffer() is a blocking call. It |
| // would wait until encoding is finished. |
| size_t encoded_size = 0; |
| if (!vaapi_wrapper_->DownloadFromVABuffer( |
| cached_output_buffer_id_, va_surface_id_, |
| static_cast<uint8_t*>(request->output_shm->memory()), |
| request->output_shm->size(), &encoded_size)) { |
| VLOGF(1) << "Failed to retrieve output image from VA coded buffer"; |
| notify_error_cb_.Run(buffer_id, PLATFORM_FAILURE); |
| } |
| |
| // Copy the real exif buffer into preserved space. |
| memcpy(static_cast<uint8_t*>(request->output_shm->memory()) + exif_offset, |
| exif_buffer, exif_buffer_size); |
| |
| video_frame_ready_cb_.Run(buffer_id, encoded_size); |
| } |
| |
| VaapiJpegEncodeAccelerator::VaapiJpegEncodeAccelerator( |
| scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) |
| : task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| io_task_runner_(std::move(io_task_runner)), |
| weak_this_factory_(this) { |
| VLOGF(2); |
| weak_this_ = weak_this_factory_.GetWeakPtr(); |
| } |
| |
| VaapiJpegEncodeAccelerator::~VaapiJpegEncodeAccelerator() { |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| VLOGF(2) << "Destroying VaapiJpegEncodeAccelerator"; |
| |
| weak_this_factory_.InvalidateWeakPtrs(); |
| if (encoder_task_runner_) { |
| encoder_task_runner_->DeleteSoon(FROM_HERE, std::move(encoder_)); |
| } |
| } |
| |
| void VaapiJpegEncodeAccelerator::NotifyError(int32_t buffer_id, Status status) { |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| VLOGF(1) << "output_buffer_id=" << buffer_id << ", status=" << status; |
| DCHECK(client_); |
| client_->NotifyError(buffer_id, status); |
| } |
| |
| void VaapiJpegEncodeAccelerator::VideoFrameReady(int32_t buffer_id, |
| size_t encoded_picture_size) { |
| DVLOGF(4) << "output_buffer_id=" << buffer_id |
| << ", size=" << encoded_picture_size; |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| ReportToUMA(VAJEAEncoderResult::VAAPI_SUCCESS); |
| |
| client_->VideoFrameReady(buffer_id, encoded_picture_size); |
| } |
| |
| JpegEncodeAccelerator::Status VaapiJpegEncodeAccelerator::Initialize( |
| JpegEncodeAccelerator::Client* client) { |
| VLOGF(2); |
| DCHECK(task_runner_->BelongsToCurrentThread()); |
| |
| if (!VaapiWrapper::IsJpegEncodeSupported()) { |
| VLOGF(1) << "Jpeg encoder is not supported."; |
| return HW_JPEG_ENCODE_NOT_SUPPORTED; |
| } |
| |
| client_ = client; |
| scoped_refptr<VaapiWrapper> vaapi_wrapper = VaapiWrapper::Create( |
| VaapiWrapper::kEncode, VAProfileJPEGBaseline, |
| base::Bind(&ReportToUMA, VAJEAEncoderResult::VAAPI_ERROR)); |
| |
| if (!vaapi_wrapper) { |
| VLOGF(1) << "Failed initializing VAAPI"; |
| return PLATFORM_FAILURE; |
| } |
| |
| encoder_task_runner_ = base::CreateSingleThreadTaskRunnerWithTraits( |
| {base::MayBlock(), base::TaskPriority::USER_BLOCKING}); |
| if (!encoder_task_runner_) { |
| VLOGF(1) << "Failed to create encoder task runner."; |
| return THREAD_CREATION_FAILED; |
| } |
| |
| encoder_ = std::make_unique<Encoder>( |
| std::move(vaapi_wrapper), |
| BindToCurrentLoop(base::BindRepeating( |
| &VaapiJpegEncodeAccelerator::VideoFrameReady, weak_this_)), |
| BindToCurrentLoop(base::BindRepeating( |
| &VaapiJpegEncodeAccelerator::NotifyError, weak_this_))); |
| |
| return ENCODE_OK; |
| } |
| |
| size_t VaapiJpegEncodeAccelerator::GetMaxCodedBufferSize( |
| const gfx::Size& picture_size) { |
| return VaapiJpegEncoder::GetMaxCodedBufferSize(picture_size); |
| } |
| |
| void VaapiJpegEncodeAccelerator::Encode(scoped_refptr<VideoFrame> video_frame, |
| int quality, |
| const BitstreamBuffer* exif_buffer, |
| const BitstreamBuffer& output_buffer) { |
| DVLOGF(4); |
| DCHECK(io_task_runner_->BelongsToCurrentThread()); |
| |
| int32_t buffer_id = output_buffer.id(); |
| TRACE_EVENT1("jpeg", "Encode", "output_buffer_id", buffer_id); |
| |
| // TODO(shenghao): support other YUV formats. |
| if (video_frame->format() != VideoPixelFormat::PIXEL_FORMAT_I420) { |
| VLOGF(1) << "Unsupported input format: " << video_frame->format(); |
| task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&VaapiJpegEncodeAccelerator::NotifyError, |
| weak_this_, buffer_id, INVALID_ARGUMENT)); |
| return; |
| } |
| |
| std::unique_ptr<UnalignedSharedMemory> exif_shm; |
| if (exif_buffer) { |
| // |exif_shm| will take ownership of the |exif_buffer->handle()|. |
| exif_shm = std::make_unique<UnalignedSharedMemory>( |
| exif_buffer->handle(), exif_buffer->size(), true); |
| if (!exif_shm->MapAt(exif_buffer->offset(), exif_buffer->size())) { |
| VLOGF(1) << "Failed to map exif buffer"; |
| task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&VaapiJpegEncodeAccelerator::NotifyError, |
| weak_this_, buffer_id, PLATFORM_FAILURE)); |
| return; |
| } |
| if (exif_shm->size() > kMaxMarkerSizeAllowed) { |
| VLOGF(1) << "Exif buffer too big: " << exif_shm->size(); |
| task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&VaapiJpegEncodeAccelerator::NotifyError, |
| weak_this_, buffer_id, INVALID_ARGUMENT)); |
| return; |
| } |
| } |
| |
| // |output_shm| will take ownership of the |output_buffer.handle()|. |
| auto output_shm = std::make_unique<UnalignedSharedMemory>( |
| output_buffer.handle(), output_buffer.size(), false); |
| if (!output_shm->MapAt(output_buffer.offset(), output_buffer.size())) { |
| VLOGF(1) << "Failed to map output buffer"; |
| task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&VaapiJpegEncodeAccelerator::NotifyError, weak_this_, |
| buffer_id, INACCESSIBLE_OUTPUT_BUFFER)); |
| return; |
| } |
| |
| auto request = std::make_unique<EncodeRequest>( |
| buffer_id, std::move(video_frame), std::move(exif_shm), |
| std::move(output_shm), quality); |
| |
| encoder_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&VaapiJpegEncodeAccelerator::Encoder::EncodeTask, |
| base::Unretained(encoder_.get()), std::move(request))); |
| } |
| |
| } // namespace media |