| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef CHROMEOS_ASH_SERVICES_RECORDING_RECORDING_SERVICE_H_ |
| #define CHROMEOS_ASH_SERVICES_RECORDING_RECORDING_SERVICE_H_ |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/task/bind_post_task.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/thread_annotations.h" |
| #include "base/threading/sequence_bound.h" |
| #include "base/threading/thread_checker.h" |
| #include "base/timer/timer.h" |
| #include "chromeos/ash/services/recording/public/mojom/recording_service.mojom.h" |
| #include "chromeos/ash/services/recording/recording_encoder.h" |
| #include "chromeos/ash/services/recording/video_capture_params.h" |
| #include "media/base/audio_bus.h" |
| #include "media/base/audio_capturer_source.h" |
| #include "media/base/audio_parameters.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "services/viz/privileged/mojom/compositing/frame_sink_video_capture.mojom.h" |
| #include "ui/gfx/image/image_skia.h" |
| |
| namespace recording { |
| |
| class AudioStreamMixer; |
| class RecordingServiceTestApi; |
| |
| // Implements the mojo interface of the recording service which handles |
| // recording audio and video of the screen or portion of it, and writes the |
| // encoded video chunks directly to a file at a path provided to the Record*() |
| // functions. |
| class RecordingService : public mojom::RecordingService, |
| public viz::mojom::FrameSinkVideoConsumer { |
| public: |
| explicit RecordingService( |
| mojo::PendingReceiver<mojom::RecordingService> receiver); |
| RecordingService(const RecordingService&) = delete; |
| RecordingService& operator=(const RecordingService&) = delete; |
| ~RecordingService() override; |
| |
| // mojom::RecordingService: |
| void RecordFullscreen( |
| mojo::PendingRemote<mojom::RecordingServiceClient> client, |
| mojo::PendingRemote<viz::mojom::FrameSinkVideoCapturer> video_capturer, |
| mojo::PendingRemote<media::mojom::AudioStreamFactory> |
| microphone_stream_factory, |
| mojo::PendingRemote<media::mojom::AudioStreamFactory> |
| system_audio_stream_factory, |
| mojo::PendingRemote<mojom::DriveFsQuotaDelegate> drive_fs_quota_delegate, |
| const base::FilePath& output_file_path, |
| const viz::FrameSinkId& frame_sink_id, |
| const gfx::Size& frame_sink_size_dip, |
| float device_scale_factor) override; |
| void RecordWindow( |
| mojo::PendingRemote<mojom::RecordingServiceClient> client, |
| mojo::PendingRemote<viz::mojom::FrameSinkVideoCapturer> video_capturer, |
| mojo::PendingRemote<media::mojom::AudioStreamFactory> |
| microphone_stream_factory, |
| mojo::PendingRemote<media::mojom::AudioStreamFactory> |
| system_audio_stream_factory, |
| mojo::PendingRemote<mojom::DriveFsQuotaDelegate> drive_fs_quota_delegate, |
| const base::FilePath& output_file_path, |
| const viz::FrameSinkId& frame_sink_id, |
| const gfx::Size& frame_sink_size_dip, |
| float device_scale_factor, |
| const viz::SubtreeCaptureId& subtree_capture_id, |
| const gfx::Size& window_size_dip) override; |
| void RecordRegion( |
| mojo::PendingRemote<mojom::RecordingServiceClient> client, |
| mojo::PendingRemote<viz::mojom::FrameSinkVideoCapturer> video_capturer, |
| mojo::PendingRemote<media::mojom::AudioStreamFactory> |
| microphone_stream_factory, |
| mojo::PendingRemote<media::mojom::AudioStreamFactory> |
| system_audio_stream_factory, |
| mojo::PendingRemote<mojom::DriveFsQuotaDelegate> drive_fs_quota_delegate, |
| const base::FilePath& output_file_path, |
| const viz::FrameSinkId& frame_sink_id, |
| const gfx::Size& frame_sink_size_dip, |
| float device_scale_factor, |
| const gfx::Rect& crop_region_dip) override; |
| void StopRecording() override; |
| void OnRecordedWindowChangingRoot(const viz::FrameSinkId& new_frame_sink_id, |
| const gfx::Size& new_frame_sink_size_dip, |
| float new_device_scale_factor) override; |
| void OnRecordedWindowSizeChanged( |
| const gfx::Size& new_window_size_dip) override; |
| void OnFrameSinkSizeChanged(const gfx::Size& new_frame_sink_size_dip, |
| float new_device_scale_factor) override; |
| |
| // viz::mojom::FrameSinkVideoConsumer: |
| void OnFrameCaptured( |
| media::mojom::VideoBufferHandlePtr data, |
| media::mojom::VideoFrameInfoPtr info, |
| const gfx::Rect& content_rect, |
| mojo::PendingRemote<viz::mojom::FrameSinkVideoConsumerFrameCallbacks> |
| callbacks) override; |
| void OnNewCaptureVersion( |
| const media::CaptureVersion& capture_version) override; |
| void OnFrameWithEmptyRegionCapture() override; |
| void OnStopped() override; |
| void OnLog(const std::string& message) override; |
| |
| private: |
| friend class RecordingServiceTestApi; |
| |
| void StartNewRecording( |
| mojo::PendingRemote<mojom::RecordingServiceClient> client, |
| mojo::PendingRemote<viz::mojom::FrameSinkVideoCapturer> video_capturer, |
| mojo::PendingRemote<media::mojom::AudioStreamFactory> |
| microphone_stream_factory, |
| mojo::PendingRemote<media::mojom::AudioStreamFactory> |
| system_audio_stream_factory, |
| mojo::PendingRemote<mojom::DriveFsQuotaDelegate> drive_fs_quota_delegate, |
| const base::FilePath& output_file_path, |
| std::unique_ptr<VideoCaptureParams> capture_params); |
| |
| // Called asynchronously by the encoder to provide the `callback` that will be |
| // called repeatedly by the `audio_stream_mixer_` to provide the mixed audio |
| // buses along with their timestamps to the encoder. This happens only if we |
| // are recording audio. |
| void OnEncodeAudioCallbackReady(EncodeAudioCallback callback); |
| |
| // Called on the main thread during an on-going recording to reconfigure an |
| // existing video encoder. |
| void ReconfigureVideoEncoder(); |
| |
| // Called on the main thread to end the recording with the given |status|. |
| void TerminateRecording(mojom::RecordingStatus status); |
| |
| // Binds the given |video_capturer| to |video_capturer_remote_| and starts |
| // video according to the current |current_video_capture_params_|. |
| void ConnectAndStartVideoCapturer( |
| mojo::PendingRemote<viz::mojom::FrameSinkVideoCapturer> video_capturer); |
| |
| // If the video capturer gets disconnected (e.g. Viz crashes) during an |
| // ongoing recording, this attempts to reconnect to a new capturer and resumes |
| // capturing with the same |current_video_capture_params_|. |
| void OnVideoCapturerDisconnected(); |
| |
| // This is called by |encoder_muxer_| on the main thread (since we bound it as |
| // a callback to be invoked on the main thread. See BindOnceToMainThread()), |
| // when a failure of type |status| occurs during audio or video encoding. This |
| // ends the ongoing recording and signals to the client that a failure |
| // occurred. |
| void OnEncodingFailure(mojom::RecordingStatus status); |
| |
| // At the end of recording we ask the |encoder_muxer_| to flush and process |
| // any buffered frames. When this completes this function is called on the |
| // main thread (since it's bound as a callback to be invoked on the main |
| // thread. See BindOnceToMainThread()). |status| indicates the recording |
| // termination status that lead to this flushing of the encoders and muxer. |
| void OnEncoderMuxerFlushed(mojom::RecordingStatus status); |
| |
| // Called on the main thread to tell the client that recording ended, |
| // providing it with the |status| of the recording as well as a chached |
| // thumbnail of the video (if available). |
| void SignalRecordingEndedToClient(mojom::RecordingStatus status); |
| |
| // Called when `refresh_timer_` fires, which means it has been more than |
| // `kVideoFramesRefreshInterval` since the last video frame was delivered, at |
| // which point we request a new refresh video frame. |
| void OnRefreshTimerFired(); |
| |
| // Stops audio recording if any is being done. |
| void MaybeStopAudioRecording(); |
| |
| // By default, the `encoder_muxer_` will invoke any callback we provide it |
| // with to notify us of certain events (such as failure errors, or flush done) |
| // on the `encoding_task_runner_`'s sequence. But since these callbacks are |
| // invoked asynchronously from other threads, they may get invoked after this |
| // RecordingService instance had been destroyed. Therefore, we need to bind |
| // these callbacks to weak ptrs, to prevent them from invoking after this |
| // object's destruction. However, this won't work, since weak ptrs cannot be |
| // invalidated except on the sequence on which they were invoked on. Hence, we |
| // must make sure these callbacks are invoked on the main thread. |
| // |
| // The below is a convenience method to bind once callbacks to weak ptrs that |
| // would only be invoked on the main thread. |
| template <typename Functor, typename... Args> |
| auto BindOnceToMainThread(Functor&& functor, Args&&... args) { |
| return base::BindPostTask(main_task_runner_, |
| base::BindOnce(std::forward<Functor>(functor), |
| weak_ptr_factory_.GetWeakPtr(), |
| std::forward<Args>(args)...)); |
| } |
| |
| THREAD_CHECKER(main_thread_checker_); |
| |
| // The audio parameters that will be used when recording audio. |
| const media::AudioParameters audio_parameters_; |
| |
| // The mojo receiving end of the service. |
| mojo::Receiver<mojom::RecordingService> receiver_; |
| |
| // The mojo receiving end of the service as a FrameSinkVideoConsumer. |
| mojo::Receiver<viz::mojom::FrameSinkVideoConsumer> consumer_receiver_ |
| GUARDED_BY_CONTEXT(main_thread_checker_); |
| |
| // A task runner to post tasks on the main thread of the recording service. |
| // It can be accessed from any thread. |
| scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; |
| |
| // A sequenced blocking pool task runner used to run all encoding and muxing |
| // tasks on. Can be accessed from any thread. |
| scoped_refptr<base::SequencedTaskRunner> encoding_task_runner_; |
| |
| // A mojo remote end of client of this service (e.g. Ash). There can only be |
| // a single client of this service. |
| mojo::Remote<mojom::RecordingServiceClient> client_remote_ |
| GUARDED_BY_CONTEXT(main_thread_checker_); |
| |
| // A callback used for testing, which will be triggered when a video frame is |
| // delivered to the service from the Viz capturer. |
| using OnVideoFrameDeliveredCallback = |
| base::OnceCallback<void(const media::VideoFrame& frame, |
| const gfx::Rect& content_rect)>; |
| OnVideoFrameDeliveredCallback on_video_frame_delivered_callback_for_testing_ |
| GUARDED_BY_CONTEXT(main_thread_checker_); |
| |
| // A timer used to request refresh video frames if none was delivered within |
| // a certain time interval (which can happen when the contents of the surface |
| // being recorded is static, resulting in no damage). |
| base::RepeatingTimer refresh_timer_ GUARDED_BY_CONTEXT(main_thread_checker_); |
| |
| // A cached scaled down rgb image of the first valid video frame which will be |
| // used to provide the client with an image thumbnail representing the |
| // recorded video. |
| gfx::ImageSkia video_thumbnail_ GUARDED_BY_CONTEXT(main_thread_checker_); |
| |
| // True if a failure has been propagated from |encoder_muxer_| that we will |
| // end recording abruptly and ignore any incoming audio/video frames. |
| bool did_failure_occur_ GUARDED_BY_CONTEXT(main_thread_checker_) = false; |
| |
| // The parameters of the current ongoing video capture. This object knows how |
| // to initialize the video capturer depending on which capture source |
| // (fullscreen, window, or region) is currently being recorded. It is set to |
| // a nullptr when there's no recording happening. |
| std::unique_ptr<VideoCaptureParams> current_video_capture_params_ |
| GUARDED_BY_CONTEXT(main_thread_checker_); |
| |
| // The mojo remote end used to interact with a video capturer living on Viz. |
| mojo::Remote<viz::mojom::FrameSinkVideoCapturer> video_capturer_remote_ |
| GUARDED_BY_CONTEXT(main_thread_checker_); |
| |
| // The audio stream mixer that will be created only if we are capturing any |
| // audio stream. The mixer creates and owns the audio capturers that we |
| // require. If the mixer exists, it must contain at least a single capturer; |
| // if either microphone or system audio recording was requested, or contains |
| // two capturers if both are desired to be recorded an mixed together in one |
| // stream. |
| // All the operations performed by the mixer (including its construction and |
| // destruction) are done on `encoding_task_runner_` to avoid stalling the main |
| // thread (on which the video frames are received). |
| base::SequenceBound<AudioStreamMixer> audio_stream_mixer_ |
| GUARDED_BY_CONTEXT(main_thread_checker_); |
| |
| // Abstracts querying the supported capabilities of the currently used encoder |
| // type. |
| std::unique_ptr<RecordingEncoder::Capabilities> encoder_capabilities_ |
| GUARDED_BY_CONTEXT(main_thread_checker_); |
| |
| // Performs all encoding and muxing operations asynchronously on the |
| // |encoding_task_runner_|. However, the |encoder_muxer_| object itself is |
| // constructed, used, and destroyed on the main thread sequence. |
| base::SequenceBound<RecordingEncoder> encoder_muxer_ |
| GUARDED_BY_CONTEXT(main_thread_checker_); |
| |
| // The number of times the video encoder was reconfigured as a result of a |
| // change in the video size in the middle of recording (See |
| // ReconfigureVideoEncoder()). |
| int number_of_video_encoder_reconfigures_ |
| GUARDED_BY_CONTEXT(main_thread_checker_) = 0; |
| |
| base::WeakPtrFactory<RecordingService> weak_ptr_factory_{this}; |
| }; |
| |
| } // namespace recording |
| #endif // CHROMEOS_ASH_SERVICES_RECORDING_RECORDING_SERVICE_H_ |