| Index: content/renderer/media/video_track_recorder.cc
 | 
| diff --git a/content/renderer/media/video_track_recorder.cc b/content/renderer/media/video_track_recorder.cc
 | 
| index 2dc0b667c7251257fe3eb7c8a6b618483bf9576e..86ef0265270b1d7bdba26a4cff2e78eb36910479 100644
 | 
| --- a/content/renderer/media/video_track_recorder.cc
 | 
| +++ b/content/renderer/media/video_track_recorder.cc
 | 
| @@ -25,6 +25,10 @@ extern "C" {
 | 
|  #include "third_party/libvpx/source/libvpx/vpx/vpx_encoder.h"
 | 
|  }
 | 
|  
 | 
| +#include "third_party/openh264/src/codec/api/svc/codec_api.h"
 | 
| +#include "third_party/openh264/src/codec/api/svc/codec_app_def.h"
 | 
| +#include "third_party/openh264/src/codec/api/svc/codec_def.h"
 | 
| +
 | 
|  using media::VideoFrame;
 | 
|  using media::VideoFrameMetadata;
 | 
|  
 | 
| @@ -61,6 +65,20 @@ void OnFrameEncodeCompleted(
 | 
|  
 | 
|  }  // anonymous namespace
 | 
|  
 | 
| +class VideoTrackRecorder::Encoder : public base::RefCountedThreadSafe<Encoder> {
 | 
| + public:
 | 
| +  virtual void StartFrameEncode(const scoped_refptr<VideoFrame>& frame,
 | 
| +                                base::TimeTicks capture_timestamp) = 0;
 | 
| +
 | 
| +  virtual void set_paused(bool paused) = 0;
 | 
| +
 | 
| + protected:
 | 
| +  friend class base::RefCountedThreadSafe<Encoder>;
 | 
| +  virtual ~Encoder() {}
 | 
| +};
 | 
| +
 | 
| +namespace {
 | 
| +
 | 
|  // Inner class encapsulating all libvpx interactions and the encoding+delivery
 | 
|  // of received frames. Limitation: Only VP8 is supported for the time being.
 | 
|  // This class must be ref-counted because the MediaStreamVideoTrack will hold a
 | 
| @@ -73,24 +91,23 @@ void OnFrameEncodeCompleted(
 | 
|  // thread, but this is not enforced;
 | 
|  // - uses an internal |encoding_thread_| for libvpx interactions, notably for
 | 
|  // encoding (which might take some time).
 | 
| -class VideoTrackRecorder::VpxEncoder final
 | 
| -    : public base::RefCountedThreadSafe<VpxEncoder> {
 | 
| +class VpxEncoder final : public VideoTrackRecorder::Encoder {
 | 
|   public:
 | 
|    static void ShutdownEncoder(std::unique_ptr<base::Thread> encoding_thread,
 | 
|                                ScopedVpxCodecCtxPtr encoder);
 | 
|  
 | 
| -  VpxEncoder(bool use_vp9,
 | 
| -             const OnEncodedVideoCB& on_encoded_video_callback,
 | 
| -             int32_t bits_per_second);
 | 
| +  VpxEncoder(
 | 
| +      bool use_vp9,
 | 
| +      const VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_callback,
 | 
| +      int32_t bits_per_second);
 | 
|  
 | 
|    void StartFrameEncode(const scoped_refptr<VideoFrame>& frame,
 | 
| -                        base::TimeTicks capture_timestamp);
 | 
| +                        base::TimeTicks capture_timestamp) override;
 | 
|  
 | 
| -  void set_paused(bool paused) { paused_ = paused; }
 | 
| +  void set_paused(bool paused) override { paused_ = paused; }
 | 
|  
 | 
|   private:
 | 
| -  friend class base::RefCountedThreadSafe<VpxEncoder>;
 | 
| -  ~VpxEncoder();
 | 
| +  ~VpxEncoder() override;
 | 
|  
 | 
|    void EncodeOnEncodingThread(const scoped_refptr<VideoFrame>& frame,
 | 
|                                base::TimeTicks capture_timestamp);
 | 
| @@ -111,7 +128,7 @@ class VideoTrackRecorder::VpxEncoder final
 | 
|    const bool use_vp9_;
 | 
|  
 | 
|    // This callback should be exercised on IO thread.
 | 
| -  const OnEncodedVideoCB on_encoded_video_callback_;
 | 
| +  const VideoTrackRecorder::OnEncodedVideoCB on_encoded_video_callback_;
 | 
|  
 | 
|    // Target bitrate or video encoding. If 0, a standard bitrate is used.
 | 
|    const int32_t bits_per_second_;
 | 
| @@ -131,6 +148,7 @@ class VideoTrackRecorder::VpxEncoder final
 | 
|    // Again, it should only be accessed on |encoding_thread_|.
 | 
|    ScopedVpxCodecCtxPtr encoder_;
 | 
|  
 | 
| +
 | 
|    // The |VideoFrame::timestamp()| of the last encoded frame.  This is used to
 | 
|    // predict the duration of the next frame.
 | 
|    base::TimeDelta last_frame_timestamp_;
 | 
| @@ -138,18 +156,254 @@ class VideoTrackRecorder::VpxEncoder final
 | 
|    DISALLOW_COPY_AND_ASSIGN(VpxEncoder);
 | 
|  };
 | 
|  
 | 
| +class H264Encoder final : public VideoTrackRecorder::Encoder {
 | 
| + public:
 | 
| +  static void ShutdownEncoder(std::unique_ptr<base::Thread> encoding_thread,
 | 
| +                              scoped_ptr<ISVCEncoder> encoder);
 | 
| +
 | 
| +  H264Encoder(
 | 
| +      const VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_callback,
 | 
| +      int32_t bits_per_second);
 | 
| +
 | 
| +  void StartFrameEncode(const scoped_refptr<VideoFrame>& frame,
 | 
| +                        base::TimeTicks capture_timestamp) override;
 | 
| +
 | 
| +  void set_paused(bool paused) override { paused_ = paused; }
 | 
| +
 | 
| + private:
 | 
| +  ~H264Encoder() override;
 | 
| +
 | 
| +  void EncodeOnEncodingThread(const scoped_refptr<VideoFrame>& frame,
 | 
| +                              base::TimeTicks capture_timestamp);
 | 
| +
 | 
| +  void ConfigureEncoding(const gfx::Size& size);
 | 
| +
 | 
| +  // While |paused_|, frames are not encoded.
 | 
| +  bool paused_;
 | 
| +
 | 
| +  // This callback should be exercised on IO thread.
 | 
| +  const VideoTrackRecorder::OnEncodedVideoCB on_encoded_video_callback_;
 | 
| +
 | 
| +  // Target bitrate or video encoding. If 0, a standard bitrate is used.
 | 
| +  const int32_t bits_per_second_;
 | 
| +
 | 
| +  // Used to shutdown properly on the same thread we were created.
 | 
| +  const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
 | 
| +
 | 
| +  // Task runner where frames to encode and reply callbacks must happen.
 | 
| +  scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_;
 | 
| +
 | 
| +  // Thread for encoding. Active for the lifetime of VpxEncoder. All variables
 | 
| +  // below this are used in this thread.
 | 
| +  std::unique_ptr<base::Thread> encoding_thread_;
 | 
| +
 | 
| +  scoped_ptr<ISVCEncoder> openh264_encoder_;
 | 
| +  gfx::Size configured_size_;
 | 
| +
 | 
| +  // The |VideoFrame::timestamp()| of the first received frame.
 | 
| +  base::TimeTicks first_frame_timestamp_;
 | 
| +
 | 
| +  DISALLOW_COPY_AND_ASSIGN(H264Encoder);
 | 
| +};
 | 
| +
 | 
| +// static
 | 
| +void H264Encoder::ShutdownEncoder(std::unique_ptr<base::Thread> encoding_thread,
 | 
| +                                  scoped_ptr<ISVCEncoder> encoder) {
 | 
| +  DVLOG(1) << __FUNCTION__;
 | 
| +  DCHECK(encoding_thread->IsRunning());
 | 
| +  encoding_thread->Stop();
 | 
| +
 | 
| +  if (encoder) {
 | 
| +    const int uninit_ret = encoder->Uninitialize();
 | 
| +    DLOG_IF(ERROR, uninit_ret != 0)  << "OpenH264 Uninitialize()";
 | 
| +
 | 
| +    WelsDestroySVCEncoder(encoder.release());
 | 
| +  }
 | 
| +}
 | 
| +
 | 
| +H264Encoder::H264Encoder(
 | 
| +    const VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_callback,
 | 
| +    int32_t bits_per_second)
 | 
| +    : paused_(false),
 | 
| +      on_encoded_video_callback_(on_encoded_video_callback),
 | 
| +      bits_per_second_(bits_per_second),
 | 
| +      main_task_runner_(base::MessageLoop::current()->task_runner()),
 | 
| +      encoding_thread_(new base::Thread("EncodingThread")) {
 | 
| +  DCHECK(!on_encoded_video_callback_.is_null());
 | 
| +
 | 
| +  DCHECK(!encoding_thread_->IsRunning());
 | 
| +  encoding_thread_->Start();
 | 
| +}
 | 
| +
 | 
| +void H264Encoder::StartFrameEncode(const scoped_refptr<VideoFrame>& frame,
 | 
| +                                   base::TimeTicks capture_timestamp) {
 | 
| +  DVLOG(1) << __FUNCTION__;
 | 
| +  // Cache the thread sending frames on first frame arrival.
 | 
| +  if (!origin_task_runner_.get())
 | 
| +    origin_task_runner_ = base::MessageLoop::current()->task_runner();
 | 
| +  DCHECK(origin_task_runner_->BelongsToCurrentThread());
 | 
| +  if (paused_)
 | 
| +    return;
 | 
| +  encoding_thread_->task_runner()->PostTask(
 | 
| +      FROM_HERE, base::Bind(&H264Encoder::EncodeOnEncodingThread,
 | 
| +                            this, frame, capture_timestamp));
 | 
| +}
 | 
| +
 | 
| +
 | 
| +void H264Encoder::EncodeOnEncodingThread(
 | 
| +    const scoped_refptr<VideoFrame>& video_frame,
 | 
| +    base::TimeTicks capture_timestamp) {
 | 
| +  DVLOG(1) << __FUNCTION__;
 | 
| +  DCHECK(encoding_thread_->task_runner()->BelongsToCurrentThread());
 | 
| +
 | 
| +  if (!(video_frame->format() == media::PIXEL_FORMAT_I420 ||
 | 
| +        video_frame->format() == media::PIXEL_FORMAT_YV12 ||
 | 
| +        video_frame->format() == media::PIXEL_FORMAT_YV12A)) {
 | 
| +    NOTREACHED();
 | 
| +    return;
 | 
| +  }
 | 
| +  scoped_refptr<media::VideoFrame> frame = video_frame;
 | 
| +  // Drop alpha channel since we do not support it yet.
 | 
| +  if (frame->format() == media::PIXEL_FORMAT_YV12A)
 | 
| +    frame = media::WrapAsI420VideoFrame(video_frame);
 | 
| +
 | 
| +  const gfx::Size frame_size = frame->visible_rect().size();
 | 
| +  if (!openh264_encoder_ || configured_size_ != frame_size) {
 | 
| +    ConfigureEncoding(frame_size);
 | 
| +    first_frame_timestamp_ = capture_timestamp;
 | 
| +  }
 | 
| +
 | 
| +  DCHECK(openh264_encoder_);
 | 
| +
 | 
| +  // EncodeFrame input.
 | 
| +  SSourcePicture picture = {};
 | 
| +  picture.iPicWidth = frame_size.width();
 | 
| +  picture.iPicHeight = frame_size.height();
 | 
| +  picture.iColorFormat = EVideoFormatType::videoFormatI420;
 | 
| +  picture.uiTimeStamp =
 | 
| +      (capture_timestamp - first_frame_timestamp_).InMilliseconds();
 | 
| +  picture.iStride[0] = video_frame->stride(VideoFrame::kYPlane);
 | 
| +  picture.iStride[1] = video_frame->stride(VideoFrame::kUPlane);
 | 
| +  picture.iStride[2] = video_frame->stride(VideoFrame::kVPlane);
 | 
| +  picture.pData[0] = video_frame->data(VideoFrame::kYPlane);
 | 
| +  picture.pData[1] = video_frame->data(VideoFrame::kUPlane);
 | 
| +  picture.pData[2] = video_frame->data(VideoFrame::kVPlane);
 | 
| +
 | 
| +  // EncodeFrame output.
 | 
| +  SFrameBSInfo info;
 | 
| +  memset(&info, 0, sizeof(SFrameBSInfo));
 | 
| +
 | 
| +  // Encode!
 | 
| +  int enc_ret = openh264_encoder_->EncodeFrame(&picture, &info);
 | 
| +  if (enc_ret != cmResultSuccess) {
 | 
| +    DLOG(ERROR) << "OpenH264 encoding failed, EncodeFrame says " << enc_ret;
 | 
| +    NOTREACHED();
 | 
| +    return;
 | 
| +  }
 | 
| +
 | 
| +  std::unique_ptr<std::string> data(new std::string);
 | 
| +  const uint8_t kNALStartCode[4] = {0, 0, 0, 1};
 | 
| +  for (int layer = 0; layer < info.iLayerNum; ++layer) {
 | 
| +    const SLayerBSInfo& layerInfo = info.sLayerInfo[layer];
 | 
| +    // Iterate NAL units making up this layer, noting fragments.
 | 
| +    size_t layer_len = 0;
 | 
| +    for (int nal = 0; nal < layerInfo.iNalCount; ++nal) {
 | 
| +
 | 
| +      DCHECK_GE(layerInfo.pNalLengthInByte[nal], 4);
 | 
| +      DCHECK_EQ(kNALStartCode[0], layerInfo.pBsBuf[layer_len+0]);
 | 
| +      DCHECK_EQ(kNALStartCode[1], layerInfo.pBsBuf[layer_len+1]);
 | 
| +      DCHECK_EQ(kNALStartCode[2], layerInfo.pBsBuf[layer_len+2]);
 | 
| +      DCHECK_EQ(kNALStartCode[3], layerInfo.pBsBuf[layer_len+3]);
 | 
| +
 | 
| +      layer_len += layerInfo.pNalLengthInByte[nal];
 | 
| +    }
 | 
| +    // Copy the entire layer's data (including start codes).
 | 
| +    data->append(reinterpret_cast<char*>(layerInfo.pBsBuf), layer_len);
 | 
| +  }
 | 
| +
 | 
| +  const bool is_key_frame = info.eFrameType == videoFrameTypeIDR;
 | 
| +
 | 
| +  origin_task_runner_->PostTask(
 | 
| +      FROM_HERE,
 | 
| +      base::Bind(OnFrameEncodeCompleted, on_encoded_video_callback_, frame,
 | 
| +                 base::Passed(&data), capture_timestamp, is_key_frame));
 | 
| +}
 | 
| +
 | 
| +void H264Encoder::ConfigureEncoding(const gfx::Size& size) {
 | 
| +  DVLOG(1) << __FUNCTION__;
 | 
| +  configured_size_ = size;
 | 
| +
 | 
| +  ISVCEncoder* temp_encoder = nullptr;
 | 
| +  int result = WelsCreateSVCEncoder(&temp_encoder);
 | 
| +  if (result != 0) {
 | 
| +    DLOG(ERROR) << "Failed to create OpenH264 encoder";
 | 
| +    NOTREACHED();
 | 
| +    return;
 | 
| +  }
 | 
| +  openh264_encoder_.reset(temp_encoder);
 | 
| +
 | 
| +#if DCHECK_IS_ON()
 | 
| +  int trace_level = WELS_LOG_DETAIL;
 | 
| +  openh264_encoder_->SetOption(ENCODER_OPTION_TRACE_LEVEL, &trace_level);
 | 
| +#endif
 | 
| +
 | 
| +  SEncParamExt init_params;
 | 
| +  openh264_encoder_->GetDefaultParams(&init_params);
 | 
| +  init_params.iUsageType = CAMERA_VIDEO_REAL_TIME;
 | 
| +
 | 
| +  init_params.iPicWidth = size.width();
 | 
| +  init_params.iPicHeight = size.height();
 | 
| +  if (bits_per_second_ > 0) {
 | 
| +    init_params.iRCMode = RC_BITRATE_MODE;
 | 
| +    init_params.iTargetBitrate = bits_per_second_;
 | 
| +  } else {
 | 
| +    init_params.iRCMode = RC_OFF_MODE;
 | 
| +  }
 | 
| +
 | 
| +  // Do not saturate CPU utilization just for encoding. On a lower-end system
 | 
| +  // with only 1 or 2 cores, use only one thread for encoding. On systems with
 | 
| +  // more cores, allow half of the cores to be used for encoding.
 | 
| +  init_params.iMultipleThreadIdc =
 | 
| +      std::min(8, (base::SysInfo::NumberOfProcessors() + 1) / 2);
 | 
| +
 | 
| +  // The base spatial layer 0 is the only one we use.
 | 
| +  DCHECK_EQ(1, init_params.iSpatialLayerNum);
 | 
| +  init_params.sSpatialLayers[0].iVideoWidth        = init_params.iPicWidth;
 | 
| +  init_params.sSpatialLayers[0].iVideoHeight       = init_params.iPicHeight;
 | 
| +  init_params.sSpatialLayers[0].iSpatialBitrate    = init_params.iTargetBitrate;
 | 
| +
 | 
| +  result = openh264_encoder_->InitializeExt(&init_params);
 | 
| +  if (result != cmResultSuccess) {
 | 
| +    DLOG(ERROR) << "Failed to initialize OpenH264 encoder";
 | 
| +    NOTREACHED();
 | 
| +    return;
 | 
| +  }
 | 
| +
 | 
| +  int video_format = EVideoFormatType::videoFormatI420;
 | 
| +  openh264_encoder_->SetOption(ENCODER_OPTION_DATAFORMAT,
 | 
| +                               &video_format);
 | 
| +}
 | 
| +
 | 
| +H264Encoder::~H264Encoder() {
 | 
| +  main_task_runner_->PostTask(FROM_HERE,
 | 
| +                              base::Bind(&H264Encoder::ShutdownEncoder,
 | 
| +                                         base::Passed(&encoding_thread_),
 | 
| +                                         base::Passed(&openh264_encoder_)));
 | 
| +}
 | 
| +
 | 
| +//
 | 
| +//
 | 
|  // static
 | 
| -void VideoTrackRecorder::VpxEncoder::ShutdownEncoder(
 | 
| -    std::unique_ptr<base::Thread> encoding_thread,
 | 
| -    ScopedVpxCodecCtxPtr encoder) {
 | 
| +void VpxEncoder::ShutdownEncoder(std::unique_ptr<base::Thread> encoding_thread,
 | 
| +                                 ScopedVpxCodecCtxPtr encoder) {
 | 
|    DCHECK(encoding_thread->IsRunning());
 | 
|    encoding_thread->Stop();
 | 
|    // Both |encoding_thread| and |encoder| will be destroyed at end-of-scope.
 | 
|  }
 | 
|  
 | 
| -VideoTrackRecorder::VpxEncoder::VpxEncoder(
 | 
| +VpxEncoder::VpxEncoder(
 | 
|      bool use_vp9,
 | 
| -    const OnEncodedVideoCB& on_encoded_video_callback,
 | 
| +    const VideoTrackRecorder::OnEncodedVideoCB& on_encoded_video_callback,
 | 
|      int32_t bits_per_second)
 | 
|      : paused_(false),
 | 
|        use_vp9_(use_vp9),
 | 
| @@ -165,16 +419,15 @@ VideoTrackRecorder::VpxEncoder::VpxEncoder(
 | 
|    encoding_thread_->Start();
 | 
|  }
 | 
|  
 | 
| -VideoTrackRecorder::VpxEncoder::~VpxEncoder() {
 | 
| +VpxEncoder::~VpxEncoder() {
 | 
|    main_task_runner_->PostTask(FROM_HERE,
 | 
|                                base::Bind(&VpxEncoder::ShutdownEncoder,
 | 
|                                           base::Passed(&encoding_thread_),
 | 
|                                           base::Passed(&encoder_)));
 | 
|  }
 | 
|  
 | 
| -void VideoTrackRecorder::VpxEncoder::StartFrameEncode(
 | 
| -    const scoped_refptr<VideoFrame>& frame,
 | 
| -    base::TimeTicks capture_timestamp) {
 | 
| +void VpxEncoder::StartFrameEncode(const scoped_refptr<VideoFrame>& frame,
 | 
| +                                  base::TimeTicks capture_timestamp) {
 | 
|    // Cache the thread sending frames on first frame arrival.
 | 
|    if (!origin_task_runner_.get())
 | 
|      origin_task_runner_ = base::MessageLoop::current()->task_runner();
 | 
| @@ -186,11 +439,11 @@ void VideoTrackRecorder::VpxEncoder::StartFrameEncode(
 | 
|                              this, frame, capture_timestamp));
 | 
|  }
 | 
|  
 | 
| -void VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread(
 | 
| +void VpxEncoder::EncodeOnEncodingThread(
 | 
|      const scoped_refptr<VideoFrame>& video_frame,
 | 
|      base::TimeTicks capture_timestamp) {
 | 
|    TRACE_EVENT0("video",
 | 
| -               "VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread");
 | 
| +               "VpxEncoder::EncodeOnEncodingThread");
 | 
|    DCHECK(encoding_thread_->task_runner()->BelongsToCurrentThread());
 | 
|  
 | 
|    if (!(video_frame->format() == media::PIXEL_FORMAT_I420 ||
 | 
| @@ -259,7 +512,7 @@ void VideoTrackRecorder::VpxEncoder::EncodeOnEncodingThread(
 | 
|                   keyframe));
 | 
|  }
 | 
|  
 | 
| -void VideoTrackRecorder::VpxEncoder::ConfigureEncoding(const gfx::Size& size) {
 | 
| +void VpxEncoder::ConfigureEncoding(const gfx::Size& size) {
 | 
|    if (IsInitialized()) {
 | 
|      // TODO(mcasas) VP8 quirk/optimisation: If the new |size| is strictly less-
 | 
|      // than-or-equal than the old size, in terms of area, the existing encoder
 | 
| @@ -351,12 +604,12 @@ void VideoTrackRecorder::VpxEncoder::ConfigureEncoding(const gfx::Size& size) {
 | 
|    }
 | 
|  }
 | 
|  
 | 
| -bool VideoTrackRecorder::VpxEncoder::IsInitialized() const {
 | 
| +bool VpxEncoder::IsInitialized() const {
 | 
|    DCHECK(encoding_thread_->task_runner()->BelongsToCurrentThread());
 | 
|    return codec_config_.g_timebase.den != 0;
 | 
|  }
 | 
|  
 | 
| -base::TimeDelta VideoTrackRecorder::VpxEncoder::CalculateFrameDuration(
 | 
| +base::TimeDelta VpxEncoder::CalculateFrameDuration(
 | 
|      const scoped_refptr<VideoFrame>& frame) {
 | 
|    DCHECK(encoding_thread_->task_runner()->BelongsToCurrentThread());
 | 
|  
 | 
| @@ -381,22 +634,35 @@ base::TimeDelta VideoTrackRecorder::VpxEncoder::CalculateFrameDuration(
 | 
|                                                kMinFrameDuration));
 | 
|  }
 | 
|  
 | 
| +}  // anonymous namespace
 | 
| +
 | 
|  VideoTrackRecorder::VideoTrackRecorder(
 | 
| -    bool use_vp9,
 | 
| +    CodecId codec,
 | 
|      const blink::WebMediaStreamTrack& track,
 | 
|      const OnEncodedVideoCB& on_encoded_video_callback,
 | 
|      int32_t bits_per_second)
 | 
| -    : track_(track),
 | 
| -      encoder_(
 | 
| -          new VpxEncoder(use_vp9, on_encoded_video_callback, bits_per_second)) {
 | 
| +    : track_(track) {
 | 
|    DCHECK(main_render_thread_checker_.CalledOnValidThread());
 | 
|    DCHECK(!track_.isNull());
 | 
|    DCHECK(track_.getExtraData());
 | 
|  
 | 
| +  switch (codec) {
 | 
| +    case CodecId::H264:
 | 
| +      encoder_ = new H264Encoder(on_encoded_video_callback, bits_per_second);
 | 
| +      break;
 | 
| +    case CodecId::VP8:
 | 
| +    case CodecId::VP9:
 | 
| +      encoder_ = new VpxEncoder(codec == CodecId::VP9,
 | 
| +                                on_encoded_video_callback, bits_per_second);
 | 
| +      break;
 | 
| +    default:
 | 
| +      NOTREACHED();
 | 
| +  }
 | 
| +
 | 
|    // StartFrameEncode() will be called on Render IO thread.
 | 
|    MediaStreamVideoSink::ConnectToTrack(
 | 
|        track_,
 | 
| -      base::Bind(&VideoTrackRecorder::VpxEncoder::StartFrameEncode, encoder_));
 | 
| +      base::Bind(&VideoTrackRecorder::Encoder::StartFrameEncode, encoder_));
 | 
|  }
 | 
|  
 | 
|  VideoTrackRecorder::~VideoTrackRecorder() {
 | 
| 
 |