| // 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/android/video_frame_factory_impl.h" |
| |
| #include "base/android/android_image_reader_compat.h" |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/callback.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/stl_util.h" |
| #include "base/task_runner_util.h" |
| #include "gpu/command_buffer/common/constants.h" |
| #include "gpu/command_buffer/service/gles2_cmd_decoder.h" |
| #include "gpu/command_buffer/service/mailbox_manager.h" |
| #include "gpu/command_buffer/service/texture_manager.h" |
| #include "gpu/ipc/service/command_buffer_stub.h" |
| #include "gpu/ipc/service/gpu_channel.h" |
| #include "gpu/ipc/service/gpu_channel_manager.h" |
| #include "media/base/bind_to_current_loop.h" |
| #include "media/base/media_switches.h" |
| #include "media/base/video_frame.h" |
| #include "media/gpu/android/codec_image.h" |
| #include "media/gpu/android/codec_image_group.h" |
| #include "media/gpu/android/codec_wrapper.h" |
| #include "media/gpu/android/texture_pool.h" |
| #include "media/gpu/command_buffer_helper.h" |
| #include "mojo/public/cpp/bindings/callback_helpers.h" |
| #include "ui/gl/android/surface_texture.h" |
| #include "ui/gl/gl_bindings.h" |
| #include "ui/gl/scoped_make_current.h" |
| |
| namespace media { |
| namespace { |
| |
| bool MakeContextCurrent(gpu::CommandBufferStub* stub) { |
| return stub && stub->decoder_context()->MakeCurrent(); |
| } |
| |
| TextureOwner::Mode GetTextureOwnerMode( |
| VideoFrameFactory::OverlayMode overlay_mode) { |
| const bool a_image_reader_supported = |
| base::android::AndroidImageReader::GetInstance().IsSupported(); |
| |
| switch (overlay_mode) { |
| case VideoFrameFactory::OverlayMode::kDontRequestPromotionHints: |
| case VideoFrameFactory::OverlayMode::kRequestPromotionHints: |
| return a_image_reader_supported && base::FeatureList::IsEnabled( |
| media::kAImageReaderVideoOutput) |
| ? TextureOwner::Mode::kAImageReaderInsecure |
| : TextureOwner::Mode::kSurfaceTextureInsecure; |
| case VideoFrameFactory::OverlayMode::kSurfaceControlSecure: |
| DCHECK(a_image_reader_supported); |
| return TextureOwner::Mode::kAImageReaderSecure; |
| case VideoFrameFactory::OverlayMode::kSurfaceControlInsecure: |
| DCHECK(a_image_reader_supported); |
| return TextureOwner::Mode::kAImageReaderInsecure; |
| } |
| |
| NOTREACHED(); |
| return TextureOwner::Mode::kSurfaceTextureInsecure; |
| } |
| |
| void ContextStateResultUMA(gpu::ContextResult result) { |
| UMA_HISTOGRAM_ENUMERATION( |
| "Media.GpuVideoFrameFactory.SharedContextStateResult", result); |
| } |
| |
| } // namespace |
| |
| using gpu::gles2::AbstractTexture; |
| |
| VideoFrameFactoryImpl::VideoFrameFactoryImpl( |
| scoped_refptr<base::SingleThreadTaskRunner> gpu_task_runner, |
| GetStubCb get_stub_cb) |
| : gpu_task_runner_(std::move(gpu_task_runner)), |
| get_stub_cb_(std::move(get_stub_cb)) {} |
| |
| VideoFrameFactoryImpl::~VideoFrameFactoryImpl() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (gpu_video_frame_factory_) |
| gpu_task_runner_->DeleteSoon(FROM_HERE, gpu_video_frame_factory_.release()); |
| } |
| |
| void VideoFrameFactoryImpl::Initialize(OverlayMode overlay_mode, |
| InitCb init_cb) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!gpu_video_frame_factory_); |
| gpu_video_frame_factory_ = std::make_unique<GpuVideoFrameFactory>(); |
| base::PostTaskAndReplyWithResult( |
| gpu_task_runner_.get(), FROM_HERE, |
| base::Bind(&GpuVideoFrameFactory::Initialize, |
| base::Unretained(gpu_video_frame_factory_.get()), overlay_mode, |
| get_stub_cb_), |
| std::move(init_cb)); |
| } |
| |
| void VideoFrameFactoryImpl::SetSurfaceBundle( |
| scoped_refptr<AVDASurfaceBundle> surface_bundle) { |
| scoped_refptr<CodecImageGroup> image_group; |
| if (!surface_bundle) { |
| // Clear everything, just so we're not holding a reference. |
| texture_owner_ = nullptr; |
| } else { |
| // If |surface_bundle| is using a TextureOwner, then get it. |
| texture_owner_ = |
| surface_bundle->overlay ? nullptr : surface_bundle->texture_owner_; |
| |
| // Start a new image group. Note that there's no reason that we can't have |
| // more than one group per surface bundle; it's okay if we're called |
| // mulitiple times with the same surface bundle. It just helps to combine |
| // the callbacks if we don't, especially since AndroidOverlay doesn't know |
| // how to remove destruction callbacks. That's one reason why we don't just |
| // make the CodecImage register itself. The other is that the threading is |
| // easier if we do it this way, since the image group is constructed on the |
| // proper thread to talk to the overlay. |
| image_group = |
| base::MakeRefCounted<CodecImageGroup>(gpu_task_runner_, surface_bundle); |
| } |
| |
| gpu_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&GpuVideoFrameFactory::SetImageGroup, |
| base::Unretained(gpu_video_frame_factory_.get()), |
| std::move(image_group))); |
| } |
| |
| void VideoFrameFactoryImpl::CreateVideoFrame( |
| std::unique_ptr<CodecOutputBuffer> output_buffer, |
| base::TimeDelta timestamp, |
| gfx::Size natural_size, |
| PromotionHintAggregator::NotifyPromotionHintCB promotion_hint_cb, |
| OnceOutputCb output_cb) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| gpu_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&GpuVideoFrameFactory::CreateVideoFrame, |
| base::Unretained(gpu_video_frame_factory_.get()), |
| base::Passed(&output_buffer), texture_owner_, timestamp, |
| natural_size, std::move(promotion_hint_cb), |
| std::move(output_cb), |
| base::ThreadTaskRunnerHandle::Get())); |
| } |
| |
| void VideoFrameFactoryImpl::RunAfterPendingVideoFrames( |
| base::OnceClosure closure) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // Hop through |gpu_task_runner_| to ensure it comes after pending frames. |
| gpu_task_runner_->PostTaskAndReply(FROM_HERE, base::DoNothing(), |
| std::move(closure)); |
| } |
| |
| GpuVideoFrameFactory::GpuVideoFrameFactory() : weak_factory_(this) { |
| DETACH_FROM_THREAD(thread_checker_); |
| } |
| |
| GpuVideoFrameFactory::~GpuVideoFrameFactory() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (stub_) |
| stub_->RemoveDestructionObserver(this); |
| } |
| |
| scoped_refptr<TextureOwner> GpuVideoFrameFactory::Initialize( |
| VideoFrameFactoryImpl::OverlayMode overlay_mode, |
| VideoFrameFactoryImpl::GetStubCb get_stub_cb) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| overlay_mode_ = overlay_mode; |
| stub_ = get_stub_cb.Run(); |
| if (!MakeContextCurrent(stub_)) |
| return nullptr; |
| stub_->AddDestructionObserver(this); |
| |
| texture_pool_ = new TexturePool(CommandBufferHelper::Create(stub_)); |
| |
| decoder_helper_ = GLES2DecoderHelper::Create(stub_->decoder_context()); |
| |
| gpu::ContextResult result; |
| scoped_refptr<gpu::SharedContextState> shared_context = |
| stub_->channel()->gpu_channel_manager()->GetSharedContextState(&result); |
| if (result != gpu::ContextResult::kSuccess) { |
| LOG(ERROR) << "Unable to get a shared context."; |
| ContextStateResultUMA(result); |
| return nullptr; |
| } |
| |
| // Make the shared context current. |
| auto scoped_current = std::make_unique<ui::ScopedMakeCurrent>( |
| shared_context->context(), shared_context->surface()); |
| if (!shared_context->IsCurrent(nullptr)) { |
| result = gpu::ContextResult::kTransientFailure; |
| LOG(ERROR) << "Unable to make shared context current."; |
| ContextStateResultUMA(result); |
| return nullptr; |
| } |
| return TextureOwner::Create(TextureOwner::CreateTexture(shared_context.get()), |
| GetTextureOwnerMode(overlay_mode_)); |
| } |
| |
| void GpuVideoFrameFactory::CreateVideoFrame( |
| std::unique_ptr<CodecOutputBuffer> output_buffer, |
| scoped_refptr<TextureOwner> texture_owner_, |
| base::TimeDelta timestamp, |
| gfx::Size natural_size, |
| PromotionHintAggregator::NotifyPromotionHintCB promotion_hint_cb, |
| VideoFrameFactory::OnceOutputCb output_cb, |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| scoped_refptr<VideoFrame> frame; |
| std::unique_ptr<AbstractTexture> texture; |
| CodecImage* codec_image = nullptr; |
| CreateVideoFrameInternal(std::move(output_buffer), std::move(texture_owner_), |
| timestamp, natural_size, |
| std::move(promotion_hint_cb), &frame, &texture, |
| &codec_image); |
| TRACE_EVENT0("media", "GpuVideoFrameFactory::CreateVideoFrame"); |
| if (!frame || !texture) |
| return; |
| |
| // Try to render this frame if possible. |
| internal::MaybeRenderEarly(&images_); |
| |
| // Callback to notify us when |texture| is going to drop its ref to the |
| // underlying texture. This happens when we (a) are notified that |frame| |
| // has been released by the renderer and the sync token has cleared, or (b) |
| // when the stub is destroyed. In the former case, we want to release any |
| // codec resources as quickly as possible so that we can re-use them. In |
| // the latter case, decoding has stopped and we want to release any buffers |
| // so that the MediaCodec instance can clean up. Note that the texture will |
| // remain renderable, but it won't necessarily refer to the frame it was |
| // supposed to; it'll be the most recently rendered frame. |
| auto cleanup_cb = base::BindOnce([](AbstractTexture* texture) { |
| gl::GLImage* image = texture->GetImage(); |
| if (image) |
| static_cast<CodecImage*>(image)->ReleaseCodecBuffer(); |
| }); |
| texture->SetCleanupCallback(std::move(cleanup_cb)); |
| |
| // Note that this keeps the pool around while any texture is. |
| auto drop_texture_ref = base::BindOnce( |
| [](scoped_refptr<TexturePool> texture_pool, AbstractTexture* texture, |
| const gpu::SyncToken& sync_token) { |
| texture_pool->ReleaseTexture(texture, sync_token); |
| }, |
| texture_pool_, base::Unretained(texture.get())); |
| texture_pool_->AddTexture(std::move(texture)); |
| |
| // Guarantee that the AbstractTexture is released even if the VideoFrame is |
| // dropped. Otherwise we could keep TextureRefs we don't need alive. |
| auto release_cb = mojo::WrapCallbackWithDefaultInvokeIfNotRun( |
| BindToCurrentLoop(std::move(drop_texture_ref)), gpu::SyncToken()); |
| frame->SetReleaseMailboxCB(std::move(release_cb)); |
| task_runner->PostTask(FROM_HERE, |
| base::BindOnce(std::move(output_cb), std::move(frame))); |
| } |
| |
| void GpuVideoFrameFactory::CreateVideoFrameInternal( |
| std::unique_ptr<CodecOutputBuffer> output_buffer, |
| scoped_refptr<TextureOwner> texture_owner_, |
| base::TimeDelta timestamp, |
| gfx::Size natural_size, |
| PromotionHintAggregator::NotifyPromotionHintCB promotion_hint_cb, |
| scoped_refptr<VideoFrame>* video_frame_out, |
| std::unique_ptr<AbstractTexture>* texture_out, |
| CodecImage** codec_image_out) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (!MakeContextCurrent(stub_)) |
| return; |
| |
| gpu::gles2::ContextGroup* group = stub_->decoder_context()->GetContextGroup(); |
| if (!group) |
| return; |
| gpu::gles2::TextureManager* texture_manager = group->texture_manager(); |
| if (!texture_manager) |
| return; |
| |
| gfx::Size size = output_buffer->size(); |
| gfx::Rect visible_rect(size); |
| // The pixel format doesn't matter as long as it's valid for texture frames. |
| VideoPixelFormat pixel_format = PIXEL_FORMAT_ARGB; |
| |
| // Check that we can create a VideoFrame for this config before creating the |
| // TextureRef so that we don't have to clean up the TextureRef if creating the |
| // frame fails. |
| if (!VideoFrame::IsValidConfig(pixel_format, VideoFrame::STORAGE_OPAQUE, size, |
| visible_rect, natural_size)) { |
| return; |
| } |
| |
| // Create a Texture and a CodecImage to back it. |
| std::unique_ptr<AbstractTexture> texture = decoder_helper_->CreateTexture( |
| GL_TEXTURE_EXTERNAL_OES, GL_RGBA, size.width(), size.height(), GL_RGBA, |
| GL_UNSIGNED_BYTE); |
| auto image = base::MakeRefCounted<CodecImage>( |
| std::move(output_buffer), texture_owner_, std::move(promotion_hint_cb)); |
| images_.push_back(image.get()); |
| *codec_image_out = image.get(); |
| |
| // Add |image| to our current image group. This makes sure that any overlay |
| // lasts as long as the images. For TextureOwner, it doesn't do much. |
| image_group_->AddCodecImage(image.get()); |
| |
| // Attach the image to the texture. |
| // Either way, we expect this to be UNBOUND (i.e., decoder-managed). For |
| // overlays, BindTexImage will return true, causing it to transition to the |
| // BOUND state, and thus receive ScheduleOverlayPlane calls. For TextureOwner |
| // backed images, BindTexImage will return false, and CopyTexImage will be |
| // tried next. |
| // TODO(liberato): consider not binding this as a StreamTextureImage if we're |
| // using an overlay. There's no advantage. We'd likely want to create (and |
| // initialize to a 1x1 texture) a 2D texture above in that case, in case |
| // somebody tries to sample from it. Be sure that promotion hints still |
| // work properly, though -- they might require a stream texture image. |
| GLuint texture_owner_service_id = |
| texture_owner_ ? texture_owner_->GetTextureId() : 0; |
| texture->BindStreamTextureImage(image.get(), texture_owner_service_id); |
| |
| gpu::Mailbox mailbox = decoder_helper_->CreateMailbox(texture.get()); |
| gpu::MailboxHolder mailbox_holders[VideoFrame::kMaxPlanes]; |
| mailbox_holders[0] = |
| gpu::MailboxHolder(mailbox, gpu::SyncToken(), GL_TEXTURE_EXTERNAL_OES); |
| |
| auto frame = VideoFrame::WrapNativeTextures( |
| pixel_format, mailbox_holders, VideoFrame::ReleaseMailboxCB(), size, |
| visible_rect, natural_size, timestamp); |
| |
| // The frames must be copied when threaded texture mailboxes are in use |
| // (http://crbug.com/582170). |
| if (group->gpu_preferences().enable_threaded_texture_mailboxes) |
| frame->metadata()->SetBoolean(VideoFrameMetadata::COPY_REQUIRED, true); |
| |
| const bool is_surface_control = |
| overlay_mode_ == VideoFrameFactory::OverlayMode::kSurfaceControlSecure || |
| overlay_mode_ == VideoFrameFactory::OverlayMode::kSurfaceControlInsecure; |
| const bool wants_promotion_hints = |
| overlay_mode_ == VideoFrameFactory::OverlayMode::kRequestPromotionHints; |
| |
| bool allow_overlay = false; |
| if (is_surface_control) { |
| DCHECK(texture_owner_); |
| allow_overlay = true; |
| } else { |
| // We unconditionally mark the picture as overlayable, even if |
| // |!texture_owner_|, if we want to get hints. It's required, else we won't |
| // get hints. |
| allow_overlay = !texture_owner_ || wants_promotion_hints; |
| } |
| |
| frame->metadata()->SetBoolean(VideoFrameMetadata::ALLOW_OVERLAY, |
| allow_overlay); |
| frame->metadata()->SetBoolean(VideoFrameMetadata::WANTS_PROMOTION_HINT, |
| wants_promotion_hints); |
| frame->metadata()->SetBoolean(VideoFrameMetadata::TEXTURE_OWNER, |
| !!texture_owner_); |
| |
| *video_frame_out = std::move(frame); |
| *texture_out = std::move(texture); |
| } |
| |
| void GpuVideoFrameFactory::OnWillDestroyStub(bool have_context) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK(stub_); |
| stub_ = nullptr; |
| decoder_helper_ = nullptr; |
| } |
| |
| void GpuVideoFrameFactory::OnImageDestructed(CodecImage* image) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| base::Erase(images_, image); |
| internal::MaybeRenderEarly(&images_); |
| } |
| |
| void GpuVideoFrameFactory::SetImageGroup( |
| scoped_refptr<CodecImageGroup> image_group) { |
| image_group_ = std::move(image_group); |
| |
| if (!image_group_) |
| return; |
| |
| image_group_->SetDestructionCb(base::BindRepeating( |
| &GpuVideoFrameFactory::OnImageDestructed, weak_factory_.GetWeakPtr())); |
| } |
| |
| } // namespace media |