Added a delay measurement, measures the time between an incoming captured frame until the frame is being processed. Measures the delay per second.

R=mflodman@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/4249004

git-svn-id: http://webrtc.googlecode.com/svn/trunk@5212 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/webrtc/video_engine/include/vie_base.h b/webrtc/video_engine/include/vie_base.h
index 1801e7b..0ca3182 100644
--- a/webrtc/video_engine/include/vie_base.h
+++ b/webrtc/video_engine/include/vie_base.h
@@ -120,12 +120,30 @@
   virtual int RegisterCpuOveruseObserver(int channel,
                                          CpuOveruseObserver* observer) = 0;
 
-  // Gets the last cpu overuse measure.
+  // Gets cpu overuse measures.
+  // capture_jitter_ms: The current estimated jitter in ms based on incoming
+  //                    captured frames.
+  // avg_encode_time_ms: The average encode time in ms.
+  // encode_usage_percent: The average encode time divided by the average time
+  //                       difference between incoming captured frames.
+  // capture_queue_delay_ms_per_s: The current time delay between an incoming
+  //                               captured frame until the frame is being
+  //                               processed. The delay is expressed in ms
+  //                               delay per second.
   // TODO(asapersson): Remove default implementation.
+  // TODO(asapersson): Remove this function.
   virtual int CpuOveruseMeasures(int channel,
                                  int* capture_jitter_ms,
                                  int* avg_encode_time_ms) { return -1; }
 
+  virtual int CpuOveruseMeasures(int channel,
+                                 int* capture_jitter_ms,
+                                 int* avg_encode_time_ms,
+                                 int* encode_usage_percent,
+                                 int* capture_queue_delay_ms_per_s) {
+    return -1;
+  }
+
   // Specifies the VoiceEngine and VideoEngine channel pair to use for
   // audio/video synchronization.
   virtual int ConnectAudioChannel(const int video_channel,
diff --git a/webrtc/video_engine/overuse_frame_detector.cc b/webrtc/video_engine/overuse_frame_detector.cc
index c284f64..a5e2d6f 100644
--- a/webrtc/video_engine/overuse_frame_detector.cc
+++ b/webrtc/video_engine/overuse_frame_detector.cc
@@ -10,10 +10,12 @@
 
 #include "webrtc/video_engine/overuse_frame_detector.h"
 
-#include <algorithm>
 #include <assert.h>
 #include <math.h>
 
+#include <algorithm>
+#include <list>
+
 #include "webrtc/modules/video_coding/utility/include/exp_filter.h"
 #include "webrtc/system_wrappers/interface/clock.h"
 #include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
@@ -49,6 +51,11 @@
 
 // The initial average encode time (set to a fairly small value).
 const float kInitialAvgEncodeTimeMs = 5.0f;
+
+// The maximum exponent to use in VCMExpFilter.
+const float kSampleDiffMs = 33.0f;
+const float kMaxExp = 7.0f;
+
 }  // namespace
 
 Statistics::Statistics() :
@@ -76,8 +83,8 @@
     return;
   }
 
-  float exp = sample_ms/33.0f;
-  exp = std::min(exp, 7.0f);
+  float exp = sample_ms / kSampleDiffMs;
+  exp = std::min(exp, kMaxExp);
   filtered_samples_->Apply(exp, sample_ms);
   filtered_variance_->Apply(exp, (sample_ms - filtered_samples_->Value()) *
                                  (sample_ms - filtered_samples_->Value()));
@@ -103,6 +110,128 @@
 
 uint64_t Statistics::Count() const { return count_; }
 
+
+// Class for calculating the average encode time.
+class OveruseFrameDetector::EncodeTimeAvg {
+ public:
+  EncodeTimeAvg()
+      : kWeightFactor(0.5f),
+        filtered_encode_time_ms_(new VCMExpFilter(kWeightFactor)) {
+    filtered_encode_time_ms_->Apply(1.0f, kInitialAvgEncodeTimeMs);
+  }
+  ~EncodeTimeAvg() {}
+
+  void AddEncodeSample(float encode_time_ms, int64_t diff_last_sample_ms) {
+    float exp =  diff_last_sample_ms / kSampleDiffMs;
+    exp = std::min(exp, kMaxExp);
+    filtered_encode_time_ms_->Apply(exp, encode_time_ms);
+  }
+
+  int filtered_encode_time_ms() const {
+    return static_cast<int>(filtered_encode_time_ms_->Value() + 0.5);
+  }
+
+ private:
+  const float kWeightFactor;
+  scoped_ptr<VCMExpFilter> filtered_encode_time_ms_;
+};
+
+// Class for calculating the encode usage.
+class OveruseFrameDetector::EncodeUsage {
+ public:
+  EncodeUsage()
+      : kWeightFactorFrameDiff(0.998f),
+        kWeightFactorEncodeTime(0.995f),
+        filtered_encode_time_ms_(new VCMExpFilter(kWeightFactorEncodeTime)),
+        filtered_frame_diff_ms_(new VCMExpFilter(kWeightFactorFrameDiff)) {
+    filtered_encode_time_ms_->Apply(1.0f, kInitialAvgEncodeTimeMs);
+    filtered_frame_diff_ms_->Apply(1.0f, kSampleDiffMs);
+  }
+  ~EncodeUsage() {}
+
+  void AddSample(float sample_ms) {
+    float exp = sample_ms / kSampleDiffMs;
+    exp = std::min(exp, kMaxExp);
+    filtered_frame_diff_ms_->Apply(exp, sample_ms);
+  }
+
+  void AddEncodeSample(float encode_time_ms, int64_t diff_last_sample_ms) {
+    float exp = diff_last_sample_ms / kSampleDiffMs;
+    exp = std::min(exp, kMaxExp);
+    filtered_encode_time_ms_->Apply(exp, encode_time_ms);
+  }
+
+  int UsageInPercent() const {
+    float frame_diff_ms = std::max(filtered_frame_diff_ms_->Value(), 1.0f);
+    float encode_usage_percent =
+        100.0f * filtered_encode_time_ms_->Value() / frame_diff_ms;
+    return static_cast<int>(encode_usage_percent + 0.5);
+  }
+
+ private:
+  const float kWeightFactorFrameDiff;
+  const float kWeightFactorEncodeTime;
+  scoped_ptr<VCMExpFilter> filtered_encode_time_ms_;
+  scoped_ptr<VCMExpFilter> filtered_frame_diff_ms_;
+};
+
+// Class for calculating the capture queue delay change.
+class OveruseFrameDetector::CaptureQueueDelay {
+ public:
+  CaptureQueueDelay()
+      : kWeightFactor(0.5f),
+        delay_ms_(0),
+        filtered_delay_ms_per_s_(new VCMExpFilter(kWeightFactor)) {
+    filtered_delay_ms_per_s_->Apply(1.0f, 0.0f);
+  }
+  ~CaptureQueueDelay() {}
+
+  void FrameCaptured(int64_t now) {
+    const size_t kMaxSize = 200;
+    if (frames_.size() > kMaxSize) {
+      frames_.pop_front();
+    }
+    frames_.push_back(now);
+  }
+
+  void FrameProcessingStarted(int64_t now) {
+    if (frames_.empty()) {
+      return;
+    }
+    delay_ms_ = now - frames_.front();
+    frames_.pop_front();
+  }
+
+  void CalculateDelayChange(int64_t diff_last_sample_ms) {
+    if (diff_last_sample_ms <= 0) {
+      return;
+    }
+    float exp = static_cast<float>(diff_last_sample_ms) / kProcessIntervalMs;
+    exp = std::min(exp, kMaxExp);
+    filtered_delay_ms_per_s_->Apply(exp,
+                                    delay_ms_ * 1000.0f / diff_last_sample_ms);
+    ClearFrames();
+  }
+
+  void ClearFrames() {
+    frames_.clear();
+  }
+
+  int delay_ms() const {
+    return delay_ms_;
+  }
+
+  int filtered_delay_ms_per_s() const {
+    return static_cast<int>(filtered_delay_ms_per_s_->Value() + 0.5);
+  }
+
+ private:
+  const float kWeightFactor;
+  std::list<int64_t> frames_;
+  int delay_ms_;
+  scoped_ptr<VCMExpFilter> filtered_delay_ms_per_s_;
+};
+
 OveruseFrameDetector::OveruseFrameDetector(Clock* clock,
                                            float normaluse_stddev_ms,
                                            float overuse_stddev_ms)
@@ -119,7 +248,12 @@
       in_quick_rampup_(false),
       current_rampup_delay_ms_(kStandardRampUpDelayMs),
       num_pixels_(0),
-      avg_encode_time_ms_(kInitialAvgEncodeTimeMs) {}
+      last_capture_jitter_ms_(-1),
+      last_encode_sample_ms_(0),
+      encode_time_(new EncodeTimeAvg()),
+      encode_usage_(new EncodeUsage()),
+      capture_queue_delay_(new CaptureQueueDelay()) {
+}
 
 OveruseFrameDetector::~OveruseFrameDetector() {
 }
@@ -129,6 +263,31 @@
   observer_ = observer;
 }
 
+int OveruseFrameDetector::AvgEncodeTimeMs() const {
+  CriticalSectionScoped cs(crit_.get());
+  return encode_time_->filtered_encode_time_ms();
+}
+
+int OveruseFrameDetector::EncodeUsagePercent() const {
+  CriticalSectionScoped cs(crit_.get());
+  return encode_usage_->UsageInPercent();
+}
+
+int OveruseFrameDetector::AvgCaptureQueueDelayMsPerS() const {
+  CriticalSectionScoped cs(crit_.get());
+  return capture_queue_delay_->filtered_delay_ms_per_s();
+}
+
+int OveruseFrameDetector::CaptureQueueDelayMsPerS() const {
+  CriticalSectionScoped cs(crit_.get());
+  return capture_queue_delay_->delay_ms();
+}
+
+int32_t OveruseFrameDetector::TimeUntilNextProcess() {
+  CriticalSectionScoped cs(crit_.get());
+  return next_process_time_ - clock_->TimeInMilliseconds();
+}
+
 void OveruseFrameDetector::FrameCaptured(int width, int height) {
   CriticalSectionScoped cs(crit_.get());
 
@@ -138,35 +297,38 @@
     num_pixels_ = num_pixels;
     capture_deltas_.Reset();
     last_capture_time_ = 0;
+    capture_queue_delay_->ClearFrames();
   }
 
   int64_t time = clock_->TimeInMilliseconds();
   if (last_capture_time_ != 0) {
     capture_deltas_.AddSample(time - last_capture_time_);
+    encode_usage_->AddSample(time - last_capture_time_);
   }
   last_capture_time_ = time;
+
+  capture_queue_delay_->FrameCaptured(time);
+}
+
+void OveruseFrameDetector::FrameProcessingStarted() {
+  CriticalSectionScoped cs(crit_.get());
+  capture_queue_delay_->FrameProcessingStarted(clock_->TimeInMilliseconds());
 }
 
 void OveruseFrameDetector::FrameEncoded(int encode_time_ms) {
   CriticalSectionScoped cs(crit_.get());
-  const float kWeight = 0.1f;
-  avg_encode_time_ms_ = kWeight * encode_time_ms +
-                        (1.0f - kWeight) * avg_encode_time_ms_;
+  int64_t time = clock_->TimeInMilliseconds();
+  if (last_encode_sample_ms_ != 0) {
+    int64_t diff_ms = time - last_encode_sample_ms_;
+    encode_time_->AddEncodeSample(encode_time_ms, diff_ms);
+    encode_usage_->AddEncodeSample(encode_time_ms, diff_ms);
+  }
+  last_encode_sample_ms_ = time;
 }
 
 int OveruseFrameDetector::last_capture_jitter_ms() const {
   CriticalSectionScoped cs(crit_.get());
-  return static_cast<int>(capture_deltas_.StdDev() + 0.5);
-}
-
-int OveruseFrameDetector::avg_encode_time_ms() const {
-  CriticalSectionScoped cs(crit_.get());
-  return static_cast<int>(avg_encode_time_ms_ + 0.5);
-}
-
-int32_t OveruseFrameDetector::TimeUntilNextProcess() {
-  CriticalSectionScoped cs(crit_.get());
-  return next_process_time_ - clock_->TimeInMilliseconds();
+  return last_capture_jitter_ms_;
 }
 
 int32_t OveruseFrameDetector::Process() {
@@ -178,12 +340,15 @@
   if (now < next_process_time_)
     return 0;
 
+  int64_t diff_ms = now - next_process_time_ + kProcessIntervalMs;
   next_process_time_ = now + kProcessIntervalMs;
 
   // Don't trigger overuse unless we've seen a certain number of frames.
   if (capture_deltas_.Count() < kMinFrameSampleCount)
     return 0;
 
+  capture_queue_delay_->CalculateDelayChange(diff_ms);
+
   if (IsOverusing()) {
     // If the last thing we did was going up, and now have to back down, we need
     // to check if this peak was short. If so we should back off to avoid going
@@ -228,6 +393,7 @@
       overuse_stddev_ms_,
       normaluse_stddev_ms_);
 
+  last_capture_jitter_ms_ = static_cast<int>(capture_deltas_.StdDev() + 0.5);
   return 0;
 }
 
diff --git a/webrtc/video_engine/overuse_frame_detector.h b/webrtc/video_engine/overuse_frame_detector.h
index 356e27f..7cbb21c 100644
--- a/webrtc/video_engine/overuse_frame_detector.h
+++ b/webrtc/video_engine/overuse_frame_detector.h
@@ -71,19 +71,43 @@
   // Called for each captured frame.
   void FrameCaptured(int width, int height);
 
+  // Called when the processing of a captured frame is started.
+  void FrameProcessingStarted();
+
+  // Called for each encoded frame.
   void FrameEncoded(int encode_time_ms);
 
+  // Accessors.
+  // The last estimated jitter based on the incoming captured frames.
   int last_capture_jitter_ms() const;
 
   // Running average of reported encode time (FrameEncoded()).
   // Only used for stats.
-  int avg_encode_time_ms() const;
+  int AvgEncodeTimeMs() const;
+
+  // The average encode time divided by the average time difference between
+  // incoming captured frames.
+  // This variable is currently only used for statistics.
+  int EncodeUsagePercent() const;
+
+  // The current time delay between an incoming captured frame (FrameCaptured())
+  // until the frame is being processed (FrameProcessingStarted()).
+  // (Note: if a new frame is received before an old frame has been processed,
+  // the old frame is skipped).
+  // The delay is returned as the delay in ms per second.
+  // This variable is currently only used for statistics.
+  int AvgCaptureQueueDelayMsPerS() const;
+  int CaptureQueueDelayMsPerS() const;
 
   // Implements Module.
   virtual int32_t TimeUntilNextProcess() OVERRIDE;
   virtual int32_t Process() OVERRIDE;
 
  private:
+  class EncodeTimeAvg;
+  class EncodeUsage;
+  class CaptureQueueDelay;
+
   bool IsOverusing();
   bool IsUnderusing(int64_t time_now);
 
@@ -113,7 +137,13 @@
   // Number of pixels of last captured frame.
   int num_pixels_;
 
-  float avg_encode_time_ms_;
+  int last_capture_jitter_ms_;
+
+  int64_t last_encode_sample_ms_;
+  scoped_ptr<EncodeTimeAvg> encode_time_;
+  scoped_ptr<EncodeUsage> encode_usage_;
+
+  scoped_ptr<CaptureQueueDelay> capture_queue_delay_;
 
   DISALLOW_COPY_AND_ASSIGN(OveruseFrameDetector);
 };
diff --git a/webrtc/video_engine/overuse_frame_detector_unittest.cc b/webrtc/video_engine/overuse_frame_detector_unittest.cc
index 131bbfd..f974f28 100644
--- a/webrtc/video_engine/overuse_frame_detector_unittest.cc
+++ b/webrtc/video_engine/overuse_frame_detector_unittest.cc
@@ -96,17 +96,75 @@
 }
 
 TEST_F(OveruseFrameDetectorTest, LastCaptureJitter) {
-  EXPECT_EQ(0, overuse_detector_->last_capture_jitter_ms());
+  EXPECT_EQ(-1, overuse_detector_->last_capture_jitter_ms());
   TriggerOveruse();
   EXPECT_GT(overuse_detector_->last_capture_jitter_ms(), 0);
 }
 
+TEST_F(OveruseFrameDetectorTest, NoCaptureQueueDelay) {
+  EXPECT_EQ(overuse_detector_->CaptureQueueDelayMsPerS(), 0);
+  overuse_detector_->FrameCaptured(320, 180);
+  overuse_detector_->FrameProcessingStarted();
+  EXPECT_EQ(overuse_detector_->CaptureQueueDelayMsPerS(), 0);
+}
+
+TEST_F(OveruseFrameDetectorTest, CaptureQueueDelay) {
+  overuse_detector_->FrameCaptured(320, 180);
+  clock_->AdvanceTimeMilliseconds(100);
+  overuse_detector_->FrameProcessingStarted();
+  EXPECT_EQ(overuse_detector_->CaptureQueueDelayMsPerS(), 100);
+}
+
+TEST_F(OveruseFrameDetectorTest, CaptureQueueDelayMultipleFrames) {
+  overuse_detector_->FrameCaptured(320, 180);
+  clock_->AdvanceTimeMilliseconds(10);
+  overuse_detector_->FrameCaptured(320, 180);
+  clock_->AdvanceTimeMilliseconds(20);
+
+  overuse_detector_->FrameProcessingStarted();
+  EXPECT_EQ(overuse_detector_->CaptureQueueDelayMsPerS(), 30);
+  overuse_detector_->FrameProcessingStarted();
+  EXPECT_EQ(overuse_detector_->CaptureQueueDelayMsPerS(), 20);
+}
+
+TEST_F(OveruseFrameDetectorTest, CaptureQueueDelayResetAtResolutionSwitch) {
+  overuse_detector_->FrameCaptured(320, 180);
+  clock_->AdvanceTimeMilliseconds(10);
+  overuse_detector_->FrameCaptured(321, 180);
+  clock_->AdvanceTimeMilliseconds(20);
+
+  overuse_detector_->FrameProcessingStarted();
+  EXPECT_EQ(overuse_detector_->CaptureQueueDelayMsPerS(), 20);
+}
+
+TEST_F(OveruseFrameDetectorTest, CaptureQueueDelayNoMatchingCapturedFrame) {
+  overuse_detector_->FrameCaptured(320, 180);
+  clock_->AdvanceTimeMilliseconds(100);
+  overuse_detector_->FrameProcessingStarted();
+  EXPECT_EQ(overuse_detector_->CaptureQueueDelayMsPerS(), 100);
+  // No new captured frame. The last delay should be reported.
+  overuse_detector_->FrameProcessingStarted();
+  EXPECT_EQ(overuse_detector_->CaptureQueueDelayMsPerS(), 100);
+}
+
 TEST_F(OveruseFrameDetectorTest, EncodedFrame) {
   const int kInitialAvgEncodeTimeInMs = 5;
-  EXPECT_EQ(kInitialAvgEncodeTimeInMs, overuse_detector_->avg_encode_time_ms());
-  for (int i = 0; i < 30; i++)
+  EXPECT_EQ(kInitialAvgEncodeTimeInMs, overuse_detector_->AvgEncodeTimeMs());
+  for (int i = 0; i < 30; i++) {
+    clock_->AdvanceTimeMilliseconds(33);
     overuse_detector_->FrameEncoded(2);
-  EXPECT_EQ(2, overuse_detector_->avg_encode_time_ms());
+  }
+  EXPECT_EQ(2, overuse_detector_->AvgEncodeTimeMs());
+}
+
+TEST_F(OveruseFrameDetectorTest, EncodedUsage) {
+  for (int i = 0; i < 30; i++) {
+    overuse_detector_->FrameCaptured(320, 180);
+    clock_->AdvanceTimeMilliseconds(5);
+    overuse_detector_->FrameEncoded(5);
+    clock_->AdvanceTimeMilliseconds(33-5);
+  }
+  EXPECT_EQ(15, overuse_detector_->EncodeUsagePercent());
 }
 
 }  // namespace webrtc
diff --git a/webrtc/video_engine/vie_base_impl.cc b/webrtc/video_engine/vie_base_impl.cc
index 8d6aeb2..17aab2e 100644
--- a/webrtc/video_engine/vie_base_impl.cc
+++ b/webrtc/video_engine/vie_base_impl.cc
@@ -120,6 +120,20 @@
 int ViEBaseImpl::CpuOveruseMeasures(int video_channel,
                                     int* capture_jitter_ms,
                                     int* avg_encode_time_ms) {
+  int encode_usage_percent;
+  int capture_queue_delay_ms_per_s;
+  return CpuOveruseMeasures(video_channel,
+                            capture_jitter_ms,
+                            avg_encode_time_ms,
+                            &encode_usage_percent,
+                            &capture_queue_delay_ms_per_s);
+}
+
+int ViEBaseImpl::CpuOveruseMeasures(int video_channel,
+                                    int* capture_jitter_ms,
+                                    int* avg_encode_time_ms,
+                                    int* encode_usage_percent,
+                                    int* capture_queue_delay_ms_per_s) {
   ViEChannelManagerScoped cs(*(shared_data_.channel_manager()));
   ViEChannel* vie_channel = cs.Channel(video_channel);
   if (!vie_channel) {
@@ -140,7 +154,10 @@
   if (provider) {
     ViECapturer* capturer = is.Capture(provider->Id());
     if (capturer) {
-      capturer->CpuOveruseMeasures(capture_jitter_ms, avg_encode_time_ms);
+      capturer->CpuOveruseMeasures(capture_jitter_ms,
+                                   avg_encode_time_ms,
+                                   encode_usage_percent,
+                                   capture_queue_delay_ms_per_s);
       return 0;
     }
   }
diff --git a/webrtc/video_engine/vie_base_impl.h b/webrtc/video_engine/vie_base_impl.h
index 1411caf..779042b 100644
--- a/webrtc/video_engine/vie_base_impl.h
+++ b/webrtc/video_engine/vie_base_impl.h
@@ -36,6 +36,11 @@
   virtual int CpuOveruseMeasures(int channel,
                                  int* capture_jitter_ms,
                                  int* avg_encode_time_ms);
+  virtual int CpuOveruseMeasures(int channel,
+                                 int* capture_jitter_ms,
+                                 int* avg_encode_time_ms,
+                                 int* encode_usage_percent,
+                                 int* capture_queue_delay_ms_per_s);
   virtual int CreateChannel(int& video_channel);  // NOLINT
   virtual int CreateChannel(int& video_channel,  // NOLINT
                             int original_channel);
diff --git a/webrtc/video_engine/vie_capturer.cc b/webrtc/video_engine/vie_capturer.cc
index d7e33e7..9a2e3fc 100644
--- a/webrtc/video_engine/vie_capturer.cc
+++ b/webrtc/video_engine/vie_capturer.cc
@@ -268,9 +268,14 @@
 }
 
 void ViECapturer::CpuOveruseMeasures(int* capture_jitter_ms,
-                                     int* avg_encode_time_ms) const {
+                                     int* avg_encode_time_ms,
+                                     int* encode_usage_percent,
+                                     int* capture_queue_delay_ms_per_s) const {
   *capture_jitter_ms = overuse_detector_->last_capture_jitter_ms();
-  *avg_encode_time_ms = overuse_detector_->avg_encode_time_ms();
+  *avg_encode_time_ms = overuse_detector_->AvgEncodeTimeMs();
+  *encode_usage_percent = overuse_detector_->EncodeUsagePercent();
+  *capture_queue_delay_ms_per_s =
+      overuse_detector_->AvgCaptureQueueDelayMsPerS();
 }
 
 int32_t ViECapturer::SetCaptureDelay(int32_t delay_ms) {
@@ -534,6 +539,7 @@
 
 bool ViECapturer::ViECaptureProcess() {
   if (capture_event_.Wait(kThreadWaitTimeMs) == kEventSignaled) {
+    overuse_detector_->FrameProcessingStarted();
     int64_t encode_start_time = -1;
     deliver_cs_->Enter();
     if (SwapCapturedAndDeliverFrameIfAvailable()) {
diff --git a/webrtc/video_engine/vie_capturer.h b/webrtc/video_engine/vie_capturer.h
index e47700f..9da187a 100644
--- a/webrtc/video_engine/vie_capturer.h
+++ b/webrtc/video_engine/vie_capturer.h
@@ -105,7 +105,9 @@
   void RegisterCpuOveruseObserver(CpuOveruseObserver* observer);
 
   void CpuOveruseMeasures(int* capture_jitter_ms,
-                          int* avg_encode_time_ms) const;
+                          int* avg_encode_time_ms,
+                          int* encode_usage_percent,
+                          int* capture_queue_delay_ms_per_s) const;
 
  protected:
   ViECapturer(int capture_id,