// Copyright 2014 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.

#ifndef MEDIA_CAST_SENDER_H264_VT_ENCODER_H_
#define MEDIA_CAST_SENDER_H264_VT_ENCODER_H_

#include <stdint.h>

#include "base/mac/scoped_cftyperef.h"
#include "base/macros.h"
#include "base/power_monitor/power_observer.h"
#include "base/threading/thread_checker.h"
#include "media/base/mac/videotoolbox_glue.h"
#include "media/base/mac/videotoolbox_helpers.h"
#include "media/cast/sender/size_adaptable_video_encoder_base.h"
#include "media/cast/sender/video_encoder.h"

namespace media {
namespace cast {

// VideoToolbox implementation of the media::cast::VideoEncoder interface.
// VideoToolbox makes no guarantees that it is thread safe, so this object is
// pinned to the thread on which it is constructed. Supports changing frame
// sizes directly. Implements the base::PowerObserver interface to reset the
// compression session when the host process is suspended.
class H264VideoToolboxEncoder : public VideoEncoder,
                                public base::PowerObserver {
  typedef CoreMediaGlue::CMSampleBufferRef CMSampleBufferRef;
  typedef VideoToolboxGlue::VTCompressionSessionRef VTCompressionSessionRef;
  typedef VideoToolboxGlue::VTEncodeInfoFlags VTEncodeInfoFlags;

 public:
  // Returns true if the current platform and system configuration supports
  // using H264VideoToolboxEncoder with the given |video_config|.
  static bool IsSupported(const FrameSenderConfig& video_config);

  H264VideoToolboxEncoder(
      const scoped_refptr<CastEnvironment>& cast_environment,
      const FrameSenderConfig& video_config,
      const StatusChangeCallback& status_change_cb);
  ~H264VideoToolboxEncoder() final;

  // media::cast::VideoEncoder implementation
  bool EncodeVideoFrame(
      const scoped_refptr<media::VideoFrame>& video_frame,
      const base::TimeTicks& reference_time,
      const FrameEncodedCallback& frame_encoded_callback) final;
  void SetBitRate(int new_bit_rate) final;
  void GenerateKeyFrame() final;
  std::unique_ptr<VideoFrameFactory> CreateVideoFrameFactory() final;
  void EmitFrames() final;

  // base::PowerObserver
  void OnSuspend() final;
  void OnResume() final;

 private:
  // VideoFrameFactory tied to the VideoToolbox encoder.
  class VideoFrameFactoryImpl;

  // Reset the encoder's compression session by destroying the existing one
  // using DestroyCompressionSession() and creating a new one. The new session
  // is configured using ConfigureCompressionSession().
  void ResetCompressionSession();

  // Configure the current compression session using current encoder settings.
  void ConfigureCompressionSession();

  // Destroy the current compression session if any. Blocks until all pending
  // frames have been flushed out (similar to EmitFrames without doing any
  // encoding work).
  void DestroyCompressionSession();

  // Update the encoder's target frame size by resetting the compression
  // session. This will also update the video frame factory.
  void UpdateFrameSize(const gfx::Size& size_needed);

  // Compression session callback function to handle compressed frames.
  static void CompressionCallback(void* encoder_opaque,
                                  void* request_opaque,
                                  OSStatus status,
                                  VTEncodeInfoFlags info,
                                  CMSampleBufferRef sbuf);

  // The cast environment (contains worker threads & more).
  const scoped_refptr<CastEnvironment> cast_environment_;

  // VideoToolboxGlue provides access to VideoToolbox at runtime.
  const VideoToolboxGlue* const videotoolbox_glue_;

  // VideoSenderConfig copy so we can create compression sessions on demand.
  // This is needed to recover from backgrounding and other events that can
  // invalidate compression sessions.
  const FrameSenderConfig video_config_;

  // Frame size of the current compression session. Can be changed by submitting
  // a frame of a different size, which will cause a compression session reset.
  gfx::Size frame_size_;

  // Callback used to report initialization status and runtime errors.
  const StatusChangeCallback status_change_cb_;

  // Thread checker to enforce that this object is used on a specific thread.
  base::ThreadChecker thread_checker_;

  // The compression session.
  base::ScopedCFTypeRef<VTCompressionSessionRef> compression_session_;

  // Video frame factory tied to the encoder.
  scoped_refptr<VideoFrameFactoryImpl> video_frame_factory_;

  // The ID for the next frame to be emitted.
  FrameId next_frame_id_;

  // Force next frame to be a keyframe.
  bool encode_next_frame_as_keyframe_;

  // Power suspension state.
  bool power_suspended_;

  // NOTE: Weak pointers must be invalidated before all other member variables.
  base::WeakPtrFactory<H264VideoToolboxEncoder> weak_factory_;

  DISALLOW_COPY_AND_ASSIGN(H264VideoToolboxEncoder);
};

}  // namespace cast
}  // namespace media

#endif  // MEDIA_CAST_SENDER_H264_VT_ENCODER_H_
