| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/fuchsia/video/fuchsia_video_encode_accelerator.h" |
| |
| #include <fuchsia/media/cpp/fidl.h> |
| #include <fuchsia/mediacodec/cpp/fidl.h> |
| #include <fuchsia/sysmem/cpp/fidl.h> |
| #include <lib/fit/function.h> |
| #include <lib/sys/cpp/component_context.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| #include <zircon/errors.h> |
| #include <zircon/types.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bits.h" |
| #include "base/check_op.h" |
| #include "base/containers/queue.h" |
| #include "base/fuchsia/fuchsia_logging.h" |
| #include "base/fuchsia/process_context.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/logging.h" |
| #include "base/memory/shared_memory_mapping.h" |
| #include "base/memory/unsafe_shared_memory_region.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/notreached.h" |
| #include "base/sequence_checker.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/time/time.h" |
| #include "media/base/bitrate.h" |
| #include "media/base/bitstream_buffer.h" |
| #include "media/base/video_codecs.h" |
| #include "media/base/video_frame.h" |
| #include "media/base/video_types.h" |
| #include "media/fuchsia/common/stream_processor_helper.h" |
| #include "media/fuchsia/common/vmo_buffer.h" |
| #include "media/video/video_encode_accelerator.h" |
| #include "third_party/libyuv/include/libyuv/convert.h" |
| #include "ui/gfx/geometry/size.h" |
| |
| namespace media { |
| namespace { |
| |
| // Hardcoded constants defined in the Amlogic driver. |
| // TODO(crbug.com/1373287): Get this values from platform API rather than |
| // hardcoding them. |
| constexpr int kMaxResolutionWidth = 1920; |
| constexpr int kMaxResolutionHeight = 1088; |
| constexpr size_t kMaxFrameRate = 60; |
| constexpr size_t kWidthAlignment = 16; |
| constexpr size_t kHeightAlignment = 2; |
| constexpr uint32_t kBytesPerRowAlignment = 32; |
| |
| // Use 2 buffers for encoder input. Allocating more than one buffers ensures |
| // that when the decoder is done working on one packet it will have another one |
| // waiting in the queue. Limiting number of buffers to 2 allows to minimize |
| // required memory, without significant effect on performance. |
| constexpr size_t kInputBufferCount = 2; |
| constexpr uint32_t kOutputBufferCount = 1; |
| |
| // Allocate 128KiB for SEI/SPS/PPS. (note that the same size is used for all |
| // codecs, not just H264). |
| constexpr size_t kOutputFrameConfigSize = 128 * 1024; |
| |
| const VideoCodecProfile kSupportedProfiles[] = { |
| H264PROFILE_BASELINE, |
| // TODO(crbug.com/1373293): Support HEVC codec. |
| }; |
| |
| fuchsia::sysmem::PixelFormatType GetPixelFormatType( |
| VideoPixelFormat pixel_format) { |
| switch (pixel_format) { |
| case PIXEL_FORMAT_I420: |
| return fuchsia::sysmem::PixelFormatType::I420; |
| case PIXEL_FORMAT_NV12: |
| return fuchsia::sysmem::PixelFormatType::NV12; |
| default: |
| return fuchsia::sysmem::PixelFormatType::INVALID; |
| } |
| } |
| |
| } // namespace |
| |
| // Stores a queue of VideoFrames to be copied to VmoBuffers. VideoFrames can be |
| // queued before VmoBuffers are available. Queue will not start processing |
| // before Initialize() is called. |
| class FuchsiaVideoEncodeAccelerator::VideoFrameWriterQueue { |
| public: |
| using ProcessCB = |
| base::RepeatingCallback<void(StreamProcessorHelper::IoPacket)>; |
| |
| VideoFrameWriterQueue() = default; |
| |
| VideoFrameWriterQueue(const VideoFrameWriterQueue&) = delete; |
| VideoFrameWriterQueue& operator=(const VideoFrameWriterQueue&) = delete; |
| |
| // Enqueues a VideoFrame. Can be called before `Start()`. Immediately |
| // processes `frame` if a VmoBuffer is available. |
| void Enqueue(scoped_refptr<VideoFrame> frame, bool force_keyframe); |
| |
| // Initialize the queue and starts processing if possible. `process_cb` is |
| // called after each VideoFrame is copied. |
| void Initialize(std::vector<VmoBuffer> buffers, |
| fuchsia::sysmem::SingleBufferSettings buffer_settings, |
| fuchsia::media::FormatDetails initial_format_details, |
| gfx::Size coded_size, |
| ProcessCB process_cb); |
| |
| private: |
| struct Item { |
| Item(scoped_refptr<VideoFrame> frame, bool force_keyframe) |
| : frame(std::move(frame)), force_keyframe(force_keyframe) { |
| DCHECK(this->frame); |
| } |
| |
| // Item is move-constructible for popping from the queue. |
| Item(const Item&) = delete; |
| Item& operator=(const Item&) = delete; |
| |
| Item(Item&&) = default; |
| Item& operator=(Item&&) = delete; |
| |
| scoped_refptr<VideoFrame> frame; |
| const bool force_keyframe; |
| }; |
| |
| void ProcessQueue(); |
| |
| // Marks the VmoBuffer at `buffer_index` to be available for copying. |
| void ReleaseBuffer(size_t buffer_index); |
| |
| // Copies a VideoFrame from `item` to VmoBuffer at `buffer_index`. |
| void CopyFrameToBuffer(const Item& item, size_t buffer_index); |
| |
| base::queue<Item> queue_; |
| std::vector<VmoBuffer> buffers_; |
| base::queue<size_t> free_buffer_indices_; |
| fuchsia::media::FormatDetails format_details_; |
| ProcessCB process_cb_; |
| |
| gfx::Size coded_size_; |
| uint32_t dst_y_stride_ = 0; |
| uint32_t dst_uv_stride_ = 0; |
| uint32_t dst_y_plane_size_ = 0; |
| size_t dst_size_ = 0; |
| |
| base::WeakPtrFactory<VideoFrameWriterQueue> weak_factory_{this}; |
| }; |
| |
| // Stores a queue of IoPackets, whose data will be written to BitstreamBuffers. |
| // Packets can be queued before VmoBuffers are available and before any |
| // BitstreamBuffers are ready to be used. BitstreamBuffers can become ready |
| // before VmoBuffers are available. Queue will not start processing before |
| // Initialize() is called. |
| class FuchsiaVideoEncodeAccelerator::OutputPacketsQueue { |
| public: |
| using ProcessCB = |
| base::RepeatingCallback<void(int32_t buffer_index, |
| const BitstreamBufferMetadata& metadata)>; |
| using ErrorCB = base::OnceCallback<void(EncoderStatus status)>; |
| |
| OutputPacketsQueue() = default; |
| |
| OutputPacketsQueue(const OutputPacketsQueue&) = delete; |
| OutputPacketsQueue& operator=(const OutputPacketsQueue&) = delete; |
| |
| // Initialize the queue and starts processing if possible. `process_cb` is |
| // called after data in each IoPacket is copied to BitstreamBuffer. |
| void Initialize(std::vector<VmoBuffer> vmo_buffers, |
| ProcessCB process_cb, |
| ErrorCB error_cb); |
| |
| // Enqueues an IoPacket. Cannot be called before AcquireVmoBuffers(). Can be |
| // called before BitstreamBuffers are ready. Immediately processes `packet` if |
| // a BitstreamBuffer is available. |
| void Enqueue(StreamProcessorHelper::IoPacket packet); |
| |
| // Add an available BitstreamBuffer. Starts processing the next packet in the |
| // queue, if exists. Can be called before AcquireVmoBuffers(). |
| void UseBitstreamBuffer(BitstreamBuffer&& bitstream_buffer); |
| |
| private: |
| void ProcessQueue(); |
| |
| // Copies the data stored in VmoBuffer referred by `packet` to a |
| // BitstreamBuffer. `metadata` is written with information from `packet`. |
| // Returns `true` if no errors occurred. |
| bool CopyPacketDataToBitstream(StreamProcessorHelper::IoPacket& packet, |
| BitstreamBuffer& bitstream_buffer, |
| BitstreamBufferMetadata* metadata); |
| |
| base::queue<StreamProcessorHelper::IoPacket> queue_; |
| base::queue<BitstreamBuffer> bitstream_buffers_; |
| std::vector<VmoBuffer> vmo_buffers_; |
| ProcessCB process_cb_; |
| ErrorCB error_cb_; |
| }; |
| |
| void FuchsiaVideoEncodeAccelerator::VideoFrameWriterQueue::Enqueue( |
| scoped_refptr<VideoFrame> frame, |
| bool force_keyframe) { |
| queue_.emplace(std::move(frame), force_keyframe); |
| |
| if (!buffers_.empty()) { |
| ProcessQueue(); |
| } |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::VideoFrameWriterQueue::Initialize( |
| std::vector<VmoBuffer> buffers, |
| fuchsia::sysmem::SingleBufferSettings buffer_settings, |
| fuchsia::media::FormatDetails initial_format_details, |
| gfx::Size coded_size, |
| ProcessCB process_cb) { |
| DCHECK(buffers_.empty()); |
| DCHECK(!buffers.empty()); |
| |
| buffers_ = std::move(buffers); |
| format_details_ = std::move(initial_format_details); |
| coded_size_ = coded_size; |
| process_cb_ = std::move(process_cb); |
| |
| // Calculate the stride and size of each frame based on `buffer_settings`. |
| // Frames must fit within the buffer. |
| auto& constraints = buffer_settings.image_format_constraints; |
| dst_y_stride_ = |
| base::bits::AlignUp(std::max(constraints.min_bytes_per_row, |
| static_cast<uint32_t>(coded_size_.width())), |
| constraints.bytes_per_row_divisor); |
| dst_uv_stride_ = (dst_y_stride_ + 1) / 2; |
| dst_y_plane_size_ = coded_size_.height() * dst_y_stride_; |
| dst_size_ = dst_y_plane_size_ + dst_y_plane_size_ / 2; |
| |
| // Initialially, all buffers are free to use. |
| for (size_t i = 0; i < buffers_.size(); i++) { |
| free_buffer_indices_.push(i); |
| } |
| |
| ProcessQueue(); |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::VideoFrameWriterQueue::ProcessQueue() { |
| DCHECK(!buffers_.empty()); |
| |
| while (!queue_.empty() && !free_buffer_indices_.empty()) { |
| Item item = std::move(queue_.front()); |
| queue_.pop(); |
| size_t buffer_index = std::move(free_buffer_indices_.front()); |
| free_buffer_indices_.pop(); |
| |
| CopyFrameToBuffer(item, buffer_index); |
| |
| auto packet = StreamProcessorHelper::IoPacket( |
| buffer_index, /*offset=*/0, dst_size_, item.frame->timestamp(), |
| /*unit_end=*/false, /*key_frame=*/false, |
| base::BindOnce(&VideoFrameWriterQueue::ReleaseBuffer, |
| weak_factory_.GetWeakPtr(), buffer_index)); |
| if (item.force_keyframe) { |
| fuchsia::media::FormatDetails format_details; |
| zx_status_t status = format_details_.Clone(&format_details); |
| ZX_DCHECK(status == ZX_OK, status) << "Clone FormatDetails"; |
| |
| format_details.mutable_encoder_settings()->h264().set_force_key_frame( |
| true); |
| packet.set_format(std::move(format_details)); |
| } |
| |
| process_cb_.Run(std::move(packet)); |
| } |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::VideoFrameWriterQueue::ReleaseBuffer( |
| size_t free_buffer_index) { |
| DCHECK(!buffers_.empty()); |
| |
| free_buffer_indices_.push(free_buffer_index); |
| ProcessQueue(); |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::VideoFrameWriterQueue::CopyFrameToBuffer( |
| const Item& item, |
| size_t buffer_index) { |
| DCHECK_LE(dst_size_, buffers_[buffer_index].size()); |
| |
| uint8_t* dst_y = buffers_[buffer_index].GetWritableMemory().data(); |
| uint8_t* dst_u = dst_y + dst_y_plane_size_; |
| uint8_t* dst_v = dst_u + dst_y_plane_size_ / 4; |
| |
| auto& frame = item.frame; |
| CHECK_LE(frame->coded_size().width(), coded_size_.width()); |
| CHECK_LE(frame->coded_size().height(), coded_size_.height()); |
| |
| int result = libyuv::I420Copy( |
| frame->data(VideoFrame::kYPlane), frame->stride(VideoFrame::kYPlane), |
| frame->data(VideoFrame::kUPlane), frame->stride(VideoFrame::kUPlane), |
| frame->data(VideoFrame::kVPlane), frame->stride(VideoFrame::kVPlane), |
| dst_y, dst_y_stride_, dst_u, dst_uv_stride_, dst_v, dst_uv_stride_, |
| frame->coded_size().width(), frame->coded_size().height()); |
| DCHECK_EQ(result, 0); |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::OutputPacketsQueue::Enqueue( |
| StreamProcessorHelper::IoPacket packet) { |
| queue_.push(std::move(packet)); |
| |
| if (!bitstream_buffers_.empty() && !vmo_buffers_.empty()) { |
| ProcessQueue(); |
| } |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::OutputPacketsQueue::UseBitstreamBuffer( |
| BitstreamBuffer&& buffer) { |
| bitstream_buffers_.push(std::move(buffer)); |
| if (!queue_.empty()) { |
| ProcessQueue(); |
| } |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::OutputPacketsQueue::Initialize( |
| std::vector<VmoBuffer> vmo_buffers, |
| ProcessCB process_cb, |
| ErrorCB error_cb) { |
| DCHECK(vmo_buffers_.empty()); |
| DCHECK(!vmo_buffers.empty()); |
| |
| vmo_buffers_ = std::move(vmo_buffers); |
| process_cb_ = std::move(process_cb); |
| error_cb_ = std::move(error_cb); |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::OutputPacketsQueue::ProcessQueue() { |
| DCHECK(!vmo_buffers_.empty()); |
| |
| while (!queue_.empty() && !bitstream_buffers_.empty()) { |
| int bitstream_buffer_id = bitstream_buffers_.front().id(); |
| |
| BitstreamBufferMetadata metadata; |
| bool success = CopyPacketDataToBitstream( |
| queue_.front(), bitstream_buffers_.front(), &metadata); |
| if (!success) |
| return; |
| |
| queue_.pop(); |
| bitstream_buffers_.pop(); |
| |
| process_cb_.Run(bitstream_buffer_id, metadata); |
| } |
| } |
| |
| bool FuchsiaVideoEncodeAccelerator::OutputPacketsQueue:: |
| CopyPacketDataToBitstream(StreamProcessorHelper::IoPacket& packet, |
| BitstreamBuffer& bitstream_buffer, |
| BitstreamBufferMetadata* metadata) { |
| if (packet.size() > bitstream_buffer.size()) { |
| std::move(error_cb_).Run( |
| {EncoderStatus::Codes::kEncoderFailedEncode, |
| base::StringPrintf("Encoded output is too large. Packet size: %zu " |
| "Bitstream buffer size: %zu", |
| packet.size(), bitstream_buffer.size())}); |
| return false; |
| } |
| |
| base::UnsafeSharedMemoryRegion region = bitstream_buffer.TakeRegion(); |
| base::WritableSharedMemoryMapping mapping = |
| region.MapAt(bitstream_buffer.offset(), packet.size()); |
| if (!mapping.IsValid()) { |
| std::move(error_cb_).Run({EncoderStatus::Codes::kSystemAPICallError, |
| "Failed to map BitstreamBuffer memory."}); |
| return false; |
| } |
| |
| VmoBuffer& vmo_buffer = vmo_buffers_[packet.buffer_index()]; |
| |
| metadata->payload_size_bytes = |
| vmo_buffer.Read(packet.offset(), mapping.GetMemoryAsSpan<uint8_t>()); |
| metadata->key_frame = packet.key_frame(); |
| metadata->timestamp = packet.timestamp(); |
| return true; |
| } |
| |
| FuchsiaVideoEncodeAccelerator::FuchsiaVideoEncodeAccelerator() |
| : sysmem_allocator_("CrFuchsiaHWVideoEncoder") {} |
| |
| FuchsiaVideoEncodeAccelerator::~FuchsiaVideoEncodeAccelerator() { |
| DCHECK(!encoder_); |
| } |
| |
| VideoEncodeAccelerator::SupportedProfiles |
| FuchsiaVideoEncodeAccelerator::GetSupportedProfiles() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| SupportedProfiles profiles; |
| |
| SupportedProfile profile; |
| profile.max_framerate_numerator = kMaxFrameRate; |
| profile.max_framerate_denominator = 1; |
| profile.rate_control_modes = VideoEncodeAccelerator::kConstantMode | |
| VideoEncodeAccelerator::kVariableMode; |
| profile.max_resolution = gfx::Size(kMaxResolutionWidth, kMaxResolutionHeight); |
| for (const auto& supported_profile : kSupportedProfiles) { |
| profile.profile = supported_profile; |
| profiles.push_back(profile); |
| } |
| return profiles; |
| } |
| |
| bool FuchsiaVideoEncodeAccelerator::Initialize( |
| const VideoEncodeAccelerator::Config& config, |
| VideoEncodeAccelerator::Client* client, |
| std::unique_ptr<MediaLog> media_log) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| int width = config.input_visible_size.width(), |
| height = config.input_visible_size.height(); |
| if (width % kWidthAlignment != 0 || height % kHeightAlignment != 0) { |
| MEDIA_LOG(ERROR, media_log) |
| << "Fuchsia MediaCodec is only tested with resolutions that have width " |
| "alignment " |
| << kWidthAlignment << " and height alignment " << kHeightAlignment; |
| return false; |
| } |
| |
| if (width <= 0 || height <= 0) { |
| return false; |
| } |
| if (width > kMaxResolutionWidth || height > kMaxResolutionHeight) { |
| return false; |
| } |
| |
| // TODO(crbug.com/1373291): Support NV12 pixel format. |
| if (config.input_format != PIXEL_FORMAT_I420) { |
| return false; |
| } |
| // TODO(crbug.com/1373293): Support HEVC codec. |
| if (config.output_profile != H264PROFILE_BASELINE) { |
| return false; |
| } |
| |
| vea_client_ = client; |
| media_log_ = std::move(media_log); |
| config_ = std::make_unique<Config>(config); |
| |
| input_queue_ = std::make_unique<VideoFrameWriterQueue>(); |
| output_queue_ = std::make_unique<OutputPacketsQueue>(); |
| |
| fuchsia::mediacodec::CodecFactoryPtr codec_factory = |
| base::ComponentContextForProcess() |
| ->svc() |
| ->Connect<fuchsia::mediacodec::CodecFactory>(); |
| |
| fuchsia::mediacodec::CreateEncoder_Params encoder_params; |
| encoder_params.set_require_hw(true); |
| encoder_params.set_input_details(CreateFormatDetails(*config_)); |
| |
| fuchsia::media::StreamProcessorPtr stream_processor; |
| codec_factory->CreateEncoder(std::move(encoder_params), |
| stream_processor.NewRequest()); |
| encoder_ = std::make_unique<StreamProcessorHelper>( |
| std::move(stream_processor), this); |
| |
| // Output buffer size is calculated based on the input size with MinCR of 2, |
| // plus config size. |
| size_t allocation_size = VideoFrame::AllocationSize( |
| config.input_format, config_->input_visible_size); |
| auto output_buffer_size = allocation_size / 2 + kOutputFrameConfigSize; |
| |
| vea_client_->RequireBitstreamBuffers( |
| /*input_count=*/1, /*input_coded_size=*/config_->input_visible_size, |
| output_buffer_size); |
| return true; |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::UseOutputBitstreamBuffer( |
| BitstreamBuffer buffer) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| output_queue_->UseBitstreamBuffer(std::move(buffer)); |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::Encode(scoped_refptr<VideoFrame> frame, |
| bool force_keyframe) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(config_); |
| DCHECK_EQ(frame->format(), PIXEL_FORMAT_I420); |
| DCHECK(!frame->coded_size().IsEmpty()); |
| CHECK(frame->IsMappable()); |
| |
| // Fuchsia VEA ignores the frame's `visible_rect` and encodes the whole |
| // `coded_size`. So we need to check that `coded_size` fits in the allocated |
| // buffer based on `input_visible_size`. This check should not fail due to |
| // the frame's alignment, as `input_visible_size.width()` must be aligned to |
| // `kWidthAlignment`. |
| // |
| // TODO(crbug.com/1381293): Encode only the `visible_rect` of a frame. |
| if (frame->coded_size().width() > config_->input_visible_size.width() || |
| frame->coded_size().height() > config_->input_visible_size.height()) { |
| OnError({EncoderStatus::Codes::kInvalidInputFrame, |
| base::StringPrintf( |
| "Input frame size %s is larger than configured size %s", |
| frame->coded_size().ToString().c_str(), |
| config_->input_visible_size.ToString().c_str())}); |
| return; |
| } |
| |
| input_queue_->Enqueue(std::move(frame), force_keyframe); |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::RequestEncodingParametersChange( |
| const Bitrate& bitrate, |
| uint32_t framerate, |
| const std::optional<gfx::Size>& size) { |
| // TODO(crbug.com/1373298): Implement RequestEncodingParameterChange. |
| NOTIMPLEMENTED(); |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::Destroy() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| ReleaseEncoder(); |
| delete this; |
| } |
| |
| bool FuchsiaVideoEncodeAccelerator::IsFlushSupported() { |
| // TODO(crbug.com/1375924): Implement Flush. |
| return false; |
| } |
| |
| bool FuchsiaVideoEncodeAccelerator::IsGpuFrameResizeSupported() { |
| return false; |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::OnStreamProcessorAllocateInputBuffers( |
| const fuchsia::media::StreamBufferConstraints& stream_constraints) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| input_buffer_collection_ = sysmem_allocator_.AllocateNewCollection(); |
| input_buffer_collection_->CreateSharedToken( |
| base::BindOnce(&StreamProcessorHelper::SetInputBufferCollectionToken, |
| base::Unretained(encoder_.get()))); |
| |
| fuchsia::sysmem::BufferCollectionConstraints constraints = |
| VmoBuffer::GetRecommendedConstraints(kInputBufferCount, |
| /*min_buffer_size=*/std::nullopt, |
| /*writable=*/true); |
| input_buffer_collection_->Initialize(constraints, "VideoEncoderInput"); |
| input_buffer_collection_->AcquireBuffers( |
| base::BindOnce(&FuchsiaVideoEncodeAccelerator::OnInputBuffersAcquired, |
| base::Unretained(this))); |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::OnInputBuffersAcquired( |
| std::vector<VmoBuffer> buffers, |
| const fuchsia::sysmem::SingleBufferSettings& buffer_settings) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(config_); |
| |
| auto& constraints = buffer_settings.image_format_constraints; |
| int coded_width = |
| base::bits::AlignUp(std::max(constraints.min_coded_width, |
| constraints.required_max_coded_width), |
| constraints.coded_width_divisor); |
| int coded_height = |
| base::bits::AlignUp(std::max(constraints.min_coded_height, |
| constraints.required_max_coded_height), |
| constraints.coded_height_divisor); |
| CHECK_GE(coded_width, config_->input_visible_size.width()); |
| CHECK_GE(coded_height, config_->input_visible_size.height()); |
| |
| input_queue_->Initialize( |
| std::move(buffers), buffer_settings, CreateFormatDetails(*config_), |
| gfx::Size(coded_width, coded_height), |
| base::BindRepeating(&StreamProcessorHelper::Process, |
| base::Unretained(encoder_.get()))); |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::OnStreamProcessorAllocateOutputBuffers( |
| const fuchsia::media::StreamBufferConstraints& stream_constraints) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| output_buffer_collection_ = sysmem_allocator_.AllocateNewCollection(); |
| output_buffer_collection_->CreateSharedToken( |
| base::BindOnce(&StreamProcessorHelper::CompleteOutputBuffersAllocation, |
| base::Unretained(encoder_.get()))); |
| |
| fuchsia::sysmem::BufferCollectionConstraints constraints; |
| constraints.usage.cpu = fuchsia::sysmem::cpuUsageRead; |
| constraints.min_buffer_count_for_shared_slack = kOutputBufferCount; |
| output_buffer_collection_->Initialize(constraints, "VideoEncoderOutput"); |
| output_buffer_collection_->AcquireBuffers( |
| base::BindOnce(&FuchsiaVideoEncodeAccelerator::OnOutputBuffersAcquired, |
| base::Unretained(this))); |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::OnOutputBuffersAcquired( |
| std::vector<VmoBuffer> buffers, |
| const fuchsia::sysmem::SingleBufferSettings& buffer_settings) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| output_queue_->Initialize( |
| std::move(buffers), |
| base::BindRepeating(&VideoEncodeAccelerator::Client::BitstreamBufferReady, |
| base::Unretained(vea_client_.get())), |
| base::BindOnce(&FuchsiaVideoEncodeAccelerator::OnError, |
| base::Unretained(this))); |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::OnStreamProcessorOutputFormat( |
| fuchsia::media::StreamOutputFormat format) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| auto* format_details = format.mutable_format_details(); |
| if (!format_details->has_domain() || !format_details->domain().is_video() || |
| !format_details->domain().video().is_compressed()) { |
| OnError({EncoderStatus::Codes::kEncoderFailedEncode, |
| "Received invalid format from stream processor."}); |
| } |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::OnStreamProcessorEndOfStream() { |
| // StreamProcessor should not return EoS when Flush is not supported. |
| NOTIMPLEMENTED(); |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::OnStreamProcessorOutputPacket( |
| StreamProcessorHelper::IoPacket packet) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| output_queue_->Enqueue(std::move(packet)); |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::OnStreamProcessorNoKey() { |
| // This method is only used for decryption. |
| NOTREACHED(); |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::OnStreamProcessorError() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| OnError({EncoderStatus::Codes::kEncoderFailedEncode, |
| "Encountered stream processor error."}); |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::ReleaseEncoder() { |
| // Drop queues and buffers before encoder, as their callbacks can reference |
| // the encoder. |
| input_queue_.reset(); |
| output_queue_.reset(); |
| input_buffer_collection_.reset(); |
| output_buffer_collection_.reset(); |
| |
| encoder_.reset(); |
| } |
| |
| void FuchsiaVideoEncodeAccelerator::OnError(EncoderStatus status) { |
| CHECK(!status.is_ok()); |
| LOG(ERROR) << "FuchsiaVideoEncodeAccelerator failed, error_code=" |
| << static_cast<int>(status.code()) |
| << ", message=" << status.message(); |
| if (media_log_) { |
| MEDIA_LOG(ERROR, media_log_) << status.message(); |
| } |
| ReleaseEncoder(); |
| if (vea_client_) { |
| vea_client_->NotifyErrorStatus(status); |
| } |
| } |
| |
| fuchsia::media::FormatDetails |
| FuchsiaVideoEncodeAccelerator::CreateFormatDetails( |
| VideoEncodeAccelerator::Config& config) { |
| DCHECK(config.input_visible_size.width() > 0); |
| DCHECK(config.input_visible_size.height() > 0); |
| |
| uint32_t width = static_cast<uint32_t>(config.input_visible_size.width()), |
| height = static_cast<uint32_t>(config.input_visible_size.height()); |
| |
| DCHECK(width % kWidthAlignment == 0); |
| DCHECK(height % kHeightAlignment == 0); |
| |
| fuchsia::media::FormatDetails format_details; |
| format_details.set_format_details_version_ordinal(1); |
| |
| fuchsia::media::VideoUncompressedFormat uncompressed; |
| uncompressed.image_format = fuchsia::sysmem::ImageFormat_2{ |
| .pixel_format = fuchsia::sysmem::PixelFormat{.type = GetPixelFormatType( |
| config.input_format)}, |
| .coded_width = width, |
| .coded_height = height, |
| .bytes_per_row = base::bits::AlignUp(width, kBytesPerRowAlignment), |
| .display_width = width, |
| .display_height = height, |
| }; |
| fuchsia::media::VideoFormat video_format; |
| video_format.set_uncompressed(std::move(uncompressed)); |
| fuchsia::media::DomainFormat domain; |
| domain.set_video(std::move(video_format)); |
| format_details.set_domain(std::move(domain)); |
| |
| // For now, hardcode mime type for H264. |
| // TODO(crbug.com/1373293): Support HEVC codec. |
| DCHECK(config.output_profile == H264PROFILE_BASELINE); |
| format_details.set_mime_type("video/h264"); |
| fuchsia::media::H264EncoderSettings h264_settings; |
| if (config.bitrate.target_bps() != 0) { |
| h264_settings.set_bit_rate(config.bitrate.target_bps()); |
| } |
| if (config.initial_framerate.has_value()) { |
| h264_settings.set_frame_rate(config.initial_framerate.value()); |
| format_details.set_timebase(base::Time::kNanosecondsPerSecond / |
| config.initial_framerate.value()); |
| } |
| if (config.gop_length.has_value()) { |
| h264_settings.set_gop_size(config.gop_length.value()); |
| } |
| h264_settings.set_force_key_frame(false); |
| |
| fuchsia::media::EncoderSettings encoder_settings; |
| encoder_settings.set_h264(std::move(h264_settings)); |
| format_details.set_encoder_settings(std::move(encoder_settings)); |
| |
| return format_details; |
| } |
| |
| } // namespace media |