MediaRecorder: support sampling rate adaption in AudioTrackRecorder

This CL adds support for audio coming from the MediaStream
Tracks with a sampling rate not directly supported by Opus
({48k, 24k, 16k, 12k, 8k}samples/s) by adding a AudioConverter
in AudioTrackRecorder::AudioEncoder.

BUG=569089
TEST =
- unit tests, which are beefed up
- content_browsertests
- demo [1]
- corrected bug's file [2]
(correction being: s/opus/webm/ for the mimeType to
use for capture.)
  I used a variety of input wav's, mostly the original
 sfx5.wav in the bug and the "miscellaneous" entries in [3]

[1] https://webrtc.github.io/samples/src/content/getusermedia/record/
[2] https://rawgit.com/Miguelao/demos/master/audiotranscode.html
[3] http://download.wavetlan.com/SVV/Media/HTTP/http-wav.htm

Review URL: https://codereview.chromium.org/1579693006

Cr-Commit-Position: refs/heads/master@{#372491}
diff --git a/content/renderer/media/audio_track_recorder.cc b/content/renderer/media/audio_track_recorder.cc
index 05107b1..dea0c3d 100644
--- a/content/renderer/media/audio_track_recorder.cc
+++ b/content/renderer/media/audio_track_recorder.cc
@@ -12,38 +12,93 @@
 #include "base/stl_util.h"
 #include "media/audio/audio_parameters.h"
 #include "media/base/audio_bus.h"
+#include "media/base/audio_converter.h"
+#include "media/base/audio_fifo.h"
 #include "media/base/bind_to_current_loop.h"
 #include "third_party/opus/src/include/opus.h"
 
 // Note that this code follows the Chrome media convention of defining a "frame"
-// as "one multi-channel sample" as opposed to another common definition
-// meaning "a chunk of samples". Here this second definition of "frame" is
-// called a "buffer"; so what might be called "frame duration" is instead
-// "buffer duration", and so on.
+// as "one multi-channel sample" as opposed to another common definition meaning
+// "a chunk of samples". Here this second definition of "frame" is called a
+// "buffer"; so what might be called "frame duration" is instead "buffer
+// duration", and so on.
 
 namespace content {
 
 namespace {
 
-enum {
-  // This is the recommended value, according to documentation in
+enum : int {
+  // Recommended value for opus_encode_float(), according to documentation in
   // third_party/opus/src/include/opus.h, so that the Opus encoder does not
-  // degrade the audio due to memory constraints.
-  OPUS_MAX_PAYLOAD_SIZE = 4000,
+  // degrade the audio due to memory constraints, and is independent of the
+  // duration of the encoded buffer.
+  kOpusMaxDataBytes = 4000,
 
-  // Support for max sampling rate of 48KHz, 2 channels, 60 ms duration.
-  MAX_SAMPLES_PER_BUFFER = 48 * 2 * 60,
+  // Opus preferred sampling rate for encoding. This is also the one WebM likes
+  // to have: https://wiki.xiph.org/MatroskaOpus.
+  kOpusPreferredSamplingRate = 48000,
+
+  // For quality reasons we try to encode 60ms, the maximum Opus buffer.
+  kOpusPreferredBufferDurationMs = 60,
+
+  // Maximum amount of buffers that can be held in the AudioEncoders' AudioFifo.
+  // Recording is not real time, hence a certain buffering is allowed.
+  kMaxNumberOfFifoBuffers = 2,
 };
 
+// The amount of Frames in a 60 ms buffer @ 48000 samples/second.
+const int kOpusPreferredFramesPerBuffer = kOpusPreferredSamplingRate *
+                                          kOpusPreferredBufferDurationMs /
+                                          base::Time::kMillisecondsPerSecond;
+
+// Tries to encode |data_in|'s |num_samples| into |data_out|.
+bool DoEncode(OpusEncoder* opus_encoder,
+              float* data_in,
+              int num_samples,
+              std::string* data_out) {
+  DCHECK_EQ(kOpusPreferredFramesPerBuffer, num_samples);
+
+  data_out->resize(kOpusMaxDataBytes);
+  const opus_int32 result = opus_encode_float(
+      opus_encoder, data_in, num_samples,
+      reinterpret_cast<uint8_t*>(string_as_array(data_out)), kOpusMaxDataBytes);
+
+  if (result > 1) {
+    // TODO(ajose): Investigate improving this. http://crbug.com/547918
+    data_out->resize(result);
+    return true;
+  }
+  // If |result| in {0,1}, do nothing; the documentation says that a return
+  // value of zero or one means the packet does not need to be transmitted.
+  // Otherwise, we have an error.
+  DLOG_IF(ERROR, result < 0) << " encode failed: " << opus_strerror(result);
+  return false;
+}
+
+// Interleaves |audio_bus| channels() of floats into a single output linear
+// |buffer|.
+// TODO(mcasas) https://crbug.com/580391 use AudioBus::ToInterleavedFloat().
+void ToInterleaved(media::AudioBus* audio_bus, float* buffer) {
+  for (int ch = 0; ch < audio_bus->channels(); ++ch) {
+    const float* src = audio_bus->channel(ch);
+    const float* const src_end = src + audio_bus->frames();
+    float* dest = buffer + ch;
+    for (; src < src_end; ++src, dest += audio_bus->channels())
+      *dest = *src;
+  }
+}
+
 }  // anonymous namespace
 
-// Nested class encapsulating opus-related encoding details.
-// AudioEncoder is created and destroyed on ATR's main thread (usually the
-// main render thread) but otherwise should operate entirely on
-// |encoder_thread_|, which is owned by AudioTrackRecorder. Be sure to delete
-// |encoder_thread_| before deleting the AudioEncoder using it.
+// Nested class encapsulating opus-related encoding details. It contains an
+// AudioConverter to adapt incoming data to the format Opus likes to have.
+// AudioEncoder is created and destroyed on ATR's main thread (usually the main
+// render thread) but otherwise should operate entirely on |encoder_thread_|,
+// which is owned by AudioTrackRecorder. Be sure to delete |encoder_thread_|
+// before deleting the AudioEncoder using it.
 class AudioTrackRecorder::AudioEncoder
-    : public base::RefCountedThreadSafe<AudioEncoder> {
+    : public base::RefCountedThreadSafe<AudioEncoder>,
+      public media::AudioConverter::InputCallback {
  public:
   AudioEncoder(const OnEncodedAudioCB& on_encoded_audio_cb,
                int32_t bits_per_second);
@@ -56,17 +111,15 @@
  private:
   friend class base::RefCountedThreadSafe<AudioEncoder>;
 
-  ~AudioEncoder();
+  ~AudioEncoder() override;
 
   bool is_initialized() const { return !!opus_encoder_; }
 
-  void DestroyExistingOpusEncoder();
+  // media::AudioConverted::InputCallback implementation.
+  double ProvideInput(media::AudioBus* audio_bus,
+                      base::TimeDelta buffer_delay) override;
 
-  void TransferSamplesIntoBuffer(const media::AudioBus* audio_bus,
-                                 int source_offset,
-                                 int buffer_fill_offset,
-                                 int num_samples);
-  bool EncodeFromFilledBuffer(std::string* out);
+  void DestroyExistingOpusEncoder();
 
   const OnEncodedAudioCB on_encoded_audio_cb_;
 
@@ -75,17 +128,15 @@
 
   base::ThreadChecker encoder_thread_checker_;
 
-  // In the case where a call to EncodeAudio() cannot completely fill the
-  // buffer, this points to the position at which to populate data in a later
-  // call.
-  int buffer_fill_end_;
+  // Track Audio (ingress) and Opus encoder input parameters, respectively. They
+  // only differ in their sample_rate() and frames_per_buffer(): output is
+  // 48ksamples/s and 2880, respectively.
+  media::AudioParameters input_params_;
+  media::AudioParameters output_params_;
 
-  int frames_per_buffer_;
-
-  // The duration of one set of frames of encoded audio samples.
-  base::TimeDelta buffer_duration_;
-
-  media::AudioParameters audio_params_;
+  // Sampling rate adapter between an OpusEncoder supported and the provided.
+  scoped_ptr<media::AudioConverter> converter_;
+  scoped_ptr<media::AudioFifo> fifo_;
 
   // Buffer for passing AudioBus data to OpusEncoder.
   scoped_ptr<float[]> buffer_;
@@ -115,50 +166,56 @@
 }
 
 void AudioTrackRecorder::AudioEncoder::OnSetFormat(
-    const media::AudioParameters& params) {
+    const media::AudioParameters& input_params) {
+  DVLOG(1) << __FUNCTION__;
   DCHECK(encoder_thread_checker_.CalledOnValidThread());
-  if (audio_params_.Equals(params))
+  if (input_params_.Equals(input_params))
     return;
 
   DestroyExistingOpusEncoder();
 
-  if (!params.IsValid() || params.channels() > 2) {
-    DLOG(ERROR) << "Invalid audio params: " << params.AsHumanReadableString();
+  if (!input_params.IsValid()) {
+    DLOG(ERROR) << "Invalid params: " << input_params.AsHumanReadableString();
     return;
   }
+  input_params_ = input_params;
+  input_params_.set_frames_per_buffer(input_params_.sample_rate() *
+                                      kOpusPreferredBufferDurationMs /
+                                      base::Time::kMillisecondsPerSecond);
 
-  buffer_duration_ = base::TimeDelta::FromMilliseconds(
-      AudioTrackRecorder::GetOpusBufferDuration(params.sample_rate()));
-  if (buffer_duration_ == base::TimeDelta()) {
-    DLOG(ERROR) << "Could not find a valid |buffer_duration| for the given "
-                << "sample rate: " << params.sample_rate();
-    return;
-  }
+  // third_party/libopus supports up to 2 channels (see implementation of
+  // opus_encoder_create()): force |output_params_| to at most those.
+  output_params_ = media::AudioParameters(
+      media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+      media::GuessChannelLayout(std::min(input_params_.channels(), 2)),
+      kOpusPreferredSamplingRate,
+      input_params_.bits_per_sample(),
+      kOpusPreferredFramesPerBuffer);
+  DVLOG(1) << "|input_params_|:" << input_params_.AsHumanReadableString()
+           << " -->|output_params_|:" << output_params_.AsHumanReadableString();
 
-  frames_per_buffer_ =
-      params.sample_rate() * buffer_duration_.InMilliseconds() / 1000;
-  if (frames_per_buffer_ * params.channels() > MAX_SAMPLES_PER_BUFFER) {
-    DLOG(ERROR) << "Invalid |frames_per_buffer_|: " << frames_per_buffer_;
-    return;
-  }
+  converter_.reset(new media::AudioConverter(input_params_, output_params_,
+                                             false /* disable_fifo */));
+  converter_->AddInput(this);
+  converter_->PrimeWithSilence();
 
-  // Initialize AudioBus buffer for OpusEncoder.
-  buffer_fill_end_ = 0;
-  buffer_.reset(new float[params.channels() * frames_per_buffer_]);
+  fifo_.reset(new media::AudioFifo(
+      input_params_.channels(),
+      kMaxNumberOfFifoBuffers * input_params_.frames_per_buffer()));
+
+  buffer_.reset(new float[output_params_.channels() *
+                          output_params_.frames_per_buffer()]);
 
   // Initialize OpusEncoder.
-  DCHECK((params.sample_rate() != 48000) || (params.sample_rate() != 24000) ||
-         (params.sample_rate() != 16000) || (params.sample_rate() != 12000) ||
-         (params.sample_rate() != 8000))
-      << "Opus supports only sample rates of {48, 24, 16, 12, 8}000, requested "
-      << params.sample_rate();
   int opus_result;
-  opus_encoder_ = opus_encoder_create(params.sample_rate(), params.channels(),
-                                      OPUS_APPLICATION_AUDIO, &opus_result);
+  opus_encoder_ = opus_encoder_create(output_params_.sample_rate(),
+                                      output_params_.channels(),
+                                      OPUS_APPLICATION_AUDIO,
+                                      &opus_result);
   if (opus_result < 0) {
     DLOG(ERROR) << "Couldn't init opus encoder: " << opus_strerror(opus_result)
-                << ", sample rate: " << params.sample_rate()
-                << ", channels: " << params.channels();
+                << ", sample rate: " << output_params_.sample_rate()
+                << ", channels: " << output_params_.channels();
     return;
   }
 
@@ -172,48 +229,52 @@
     DLOG(ERROR) << "Failed to set opus bitrate: " << bitrate;
     return;
   }
-
-  audio_params_ = params;
 }
 
 void AudioTrackRecorder::AudioEncoder::EncodeAudio(
-    scoped_ptr<media::AudioBus> audio_bus,
+    scoped_ptr<media::AudioBus> input_bus,
     const base::TimeTicks& capture_time) {
+  DVLOG(1) << __FUNCTION__ << ", #frames " << input_bus->frames();
   DCHECK(encoder_thread_checker_.CalledOnValidThread());
-  DCHECK_EQ(audio_bus->channels(), audio_params_.channels());
+  DCHECK_EQ(input_bus->channels(), input_params_.channels());
+  DCHECK(!capture_time.is_null());
+  DCHECK(converter_);
 
   if (!is_initialized())
     return;
+  // TODO(mcasas): Consider using a std::deque<scoped_ptr<AudioBus>> instead of
+  // an AudioFifo, to avoid copying data needlessly since we know the sizes of
+  // both input and output and they are multiples.
+  fifo_->Push(input_bus.get());
 
-  base::TimeDelta buffer_fill_duration =
-      buffer_fill_end_ * buffer_duration_ / frames_per_buffer_;
-  base::TimeTicks buffer_capture_time = capture_time - buffer_fill_duration;
-
-  // Encode all audio in |audio_bus| into zero or more packets.
-  int src_pos = 0;
-  while (src_pos < audio_bus->frames()) {
-    const int num_samples_to_xfer = std::min(
-        frames_per_buffer_ - buffer_fill_end_, audio_bus->frames() - src_pos);
-    TransferSamplesIntoBuffer(audio_bus.get(), src_pos, buffer_fill_end_,
-                              num_samples_to_xfer);
-    src_pos += num_samples_to_xfer;
-    buffer_fill_end_ += num_samples_to_xfer;
-
-    if (buffer_fill_end_ < frames_per_buffer_)
-      break;
+  // Wait to have enough |input_bus|s to guarantee a satisfactory conversion.
+  while (fifo_->frames() >= input_params_.frames_per_buffer()) {
+    scoped_ptr<media::AudioBus> audio_bus = media::AudioBus::Create(
+        output_params_.channels(), kOpusPreferredFramesPerBuffer);
+    converter_->Convert(audio_bus.get());
+    ToInterleaved(audio_bus.get(), buffer_.get());
 
     scoped_ptr<std::string> encoded_data(new std::string());
-    if (EncodeFromFilledBuffer(encoded_data.get())) {
-      on_encoded_audio_cb_.Run(audio_params_, std::move(encoded_data),
-                               buffer_capture_time);
+    if (DoEncode(opus_encoder_, buffer_.get(), kOpusPreferredFramesPerBuffer,
+                 encoded_data.get())) {
+      const base::TimeTicks capture_time_of_first_sample =
+          capture_time -
+          base::TimeDelta::FromMicroseconds(fifo_->frames() *
+                                            base::Time::kMicrosecondsPerSecond /
+                                            input_params_.sample_rate());
+      on_encoded_audio_cb_.Run(output_params_, std::move(encoded_data),
+                               capture_time_of_first_sample);
     }
-
-    // Reset the capture timestamp and internal buffer for next set of frames.
-    buffer_capture_time += buffer_duration_;
-    buffer_fill_end_ = 0;
   }
 }
 
+double AudioTrackRecorder::AudioEncoder::ProvideInput(
+    media::AudioBus* audio_bus,
+    base::TimeDelta buffer_delay) {
+  fifo_->Consume(audio_bus, 0, audio_bus->frames());
+  return 1.0;  // Return volume greater than zero to indicate we have more data.
+}
+
 void AudioTrackRecorder::AudioEncoder::DestroyExistingOpusEncoder() {
   // We don't DCHECK that we're on the encoder thread here, as this could be
   // called from the dtor (main thread) or from OnSetForamt() (render thread);
@@ -223,48 +284,6 @@
   }
 }
 
-void AudioTrackRecorder::AudioEncoder::TransferSamplesIntoBuffer(
-    const media::AudioBus* audio_bus,
-    int source_offset,
-    int buffer_fill_offset,
-    int num_samples) {
-  // TODO(ajose): Consider replacing with AudioBus::ToInterleaved().
-  // http://crbug.com/547918
-  DCHECK(encoder_thread_checker_.CalledOnValidThread());
-  DCHECK(is_initialized());
-  // Opus requires channel-interleaved samples in a single array.
-  for (int ch = 0; ch < audio_bus->channels(); ++ch) {
-    const float* src = audio_bus->channel(ch) + source_offset;
-    const float* const src_end = src + num_samples;
-    float* dest =
-        buffer_.get() + buffer_fill_offset * audio_params_.channels() + ch;
-    for (; src < src_end; ++src, dest += audio_params_.channels())
-      *dest = *src;
-  }
-}
-
-bool AudioTrackRecorder::AudioEncoder::EncodeFromFilledBuffer(
-    std::string* out) {
-  DCHECK(encoder_thread_checker_.CalledOnValidThread());
-  DCHECK(is_initialized());
-
-  out->resize(OPUS_MAX_PAYLOAD_SIZE);
-  const opus_int32 result = opus_encode_float(
-      opus_encoder_, buffer_.get(), frames_per_buffer_,
-      reinterpret_cast<uint8_t*>(string_as_array(out)), OPUS_MAX_PAYLOAD_SIZE);
-  if (result > 1) {
-    // TODO(ajose): Investigate improving this. http://crbug.com/547918
-    out->resize(result);
-    return true;
-  }
-  // If |result| in {0,1}, do nothing; the documentation says that a return
-  // value of zero or one means the packet does not need to be transmitted.
-  // Otherwise, we have an error.
-  DLOG_IF(ERROR, result < 0) << __FUNCTION__
-                             << " failed: " << opus_strerror(result);
-  return false;
-}
-
 AudioTrackRecorder::AudioTrackRecorder(
     const blink::WebMediaStreamTrack& track,
     const OnEncodedAudioCB& on_encoded_audio_cb,
@@ -316,22 +335,4 @@
                             base::Passed(&audio_data), capture_time));
 }
 
-int AudioTrackRecorder::GetOpusBufferDuration(int sample_rate) {
-  // Valid buffer durations in millseconds. Note there are other valid
-  // durations for Opus, see https://tools.ietf.org/html/rfc6716#section-2.1.4
-  // Descending order as longer durations can increase compression performance.
-  const std::vector<int> opus_valid_buffer_durations_ms = {60, 40, 20, 10};
-
-  // Search for a duration such that |sample_rate| % |buffers_per_second| == 0,
-  // where |buffers_per_second| = 1000ms / |possible_duration|.
-  for (auto possible_duration : opus_valid_buffer_durations_ms) {
-    if (sample_rate * possible_duration % 1000 == 0) {
-      return possible_duration;
-    }
-  }
-
-  // Otherwise, couldn't find a good duration.
-  return 0;
-}
-
 }  // namespace content
diff --git a/content/renderer/media/audio_track_recorder.h b/content/renderer/media/audio_track_recorder.h
index 1d1a395c..7f7c1f9 100644
--- a/content/renderer/media/audio_track_recorder.h
+++ b/content/renderer/media/audio_track_recorder.h
@@ -17,6 +17,7 @@
 
 namespace media {
 class AudioBus;
+class AudioParameters;
 }  // namespace media
 
 namespace content {
@@ -47,17 +48,10 @@
               base::TimeTicks capture_time) override;
 
  private:
-  friend class AudioTrackRecorderTest;
-  class AudioParameters;
-
   // Forward declaration of nested class for handling encoding.
   // See the implementation file for details.
   class AudioEncoder;
 
-  // Returns the Opus buffer duration in milliseconds, or zero if none will work
-  // for the given |sample_rate|.
-  static int GetOpusBufferDuration(int sample_rate);
-
   // Used to check that we are destroyed on the same thread we were created on.
   base::ThreadChecker main_render_thread_checker_;
 
diff --git a/content/renderer/media/audio_track_recorder_unittest.cc b/content/renderer/media/audio_track_recorder_unittest.cc
index 07c0b53..2c5d23f 100644
--- a/content/renderer/media/audio_track_recorder_unittest.cc
+++ b/content/renderer/media/audio_track_recorder_unittest.cc
@@ -31,14 +31,19 @@
 
 namespace {
 
-// Input audio format.
-const media::AudioParameters::Format kDefaultInputFormat =
-    media::AudioParameters::AUDIO_PCM_LOW_LATENCY;
 const int kDefaultBitsPerSample = 16;
 const int kDefaultSampleRate = 48000;
 // The |frames_per_buffer| field of AudioParameters is not used by ATR.
 const int kIgnoreFramesPerBuffer = 1;
-const int kOpusMaxBufferDurationMS = 120;
+
+// The following parameters replicate those in audio_track_recorder.cc, see this
+// file for explanations.
+const int kMediaStreamAudioTrackBufferDurationMs = 10;
+const int kOpusBufferDurationMs = 60;
+const int kRatioInputToOutputFrames =
+    kOpusBufferDurationMs / kMediaStreamAudioTrackBufferDurationMs;
+
+const int kFramesPerBuffer = kOpusBufferDurationMs * kDefaultSampleRate / 1000;
 
 }  // namespace
 
@@ -62,13 +67,32 @@
      kDefaultSampleRate,                            /* sample rate */
      kDefaultBitsPerSample},                        /* bits per sample */
     // Change to mono:
-    {media::AudioParameters::AUDIO_PCM_LOW_LATENCY, media::CHANNEL_LAYOUT_MONO,
-     kDefaultSampleRate, kDefaultBitsPerSample},
-    // Different sampling rate as well:
-    {media::AudioParameters::AUDIO_PCM_LOW_LATENCY, media::CHANNEL_LAYOUT_MONO,
-     24000, kDefaultBitsPerSample},
     {media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
-     media::CHANNEL_LAYOUT_STEREO, 8000, kDefaultBitsPerSample},
+     media::CHANNEL_LAYOUT_MONO,
+     kDefaultSampleRate,
+     kDefaultBitsPerSample},
+    // Different sampling rate as well:
+    {media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+      media::CHANNEL_LAYOUT_MONO,
+     24000,
+     kDefaultBitsPerSample},
+    {media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+     media::CHANNEL_LAYOUT_STEREO,
+     8000,
+     kDefaultBitsPerSample},
+    // Using a non-default Opus sampling rate (48, 24, 16, 12, or 8 kHz).
+    {media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+     media::CHANNEL_LAYOUT_MONO,
+     22050,
+     kDefaultBitsPerSample},
+    {media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+     media::CHANNEL_LAYOUT_STEREO,
+     44100,
+     kDefaultBitsPerSample},
+    {media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
+     media::CHANNEL_LAYOUT_STEREO,
+     96000,
+     kDefaultBitsPerSample},
 };
 
 class AudioTrackRecorderTest : public TestWithParam<ATRTestParams> {
@@ -81,7 +105,7 @@
                       GetParam().sample_rate,
                       GetParam().bits_per_sample,
                       kIgnoreFramesPerBuffer),
-        second_params_(kDefaultInputFormat,
+        second_params_(media::AudioParameters::AUDIO_PCM_LOW_LATENCY,
                        media::CHANNEL_LAYOUT_STEREO,
                        kDefaultSampleRate,
                        kDefaultBitsPerSample,
@@ -104,9 +128,11 @@
   ~AudioTrackRecorderTest() {
     opus_decoder_destroy(opus_decoder_);
     opus_decoder_ = nullptr;
-    audio_track_recorder_.reset();
     blink_track_.reset();
     blink::WebHeap::collectAllGarbageForTesting();
+    audio_track_recorder_.reset();
+    // Let the message loop run to finish destroying the recorder properly.
+    base::RunLoop().RunUntilIdle();
   }
 
   void ResetDecoder(const media::AudioParameters& params) {
@@ -117,31 +143,25 @@
 
     int error;
     opus_decoder_ =
-        opus_decoder_create(params.sample_rate(), params.channels(), &error);
+        opus_decoder_create(kDefaultSampleRate, params.channels(), &error);
     EXPECT_TRUE(error == OPUS_OK && opus_decoder_);
 
-    max_frames_per_buffer_ =
-        kOpusMaxBufferDurationMS * params.sample_rate() / 1000;
-    buffer_.reset(new float[max_frames_per_buffer_ * params.channels()]);
+    buffer_.reset(new float[kFramesPerBuffer * params.channels()]);
   }
 
   scoped_ptr<media::AudioBus> GetFirstSourceAudioBus() {
     scoped_ptr<media::AudioBus> bus(media::AudioBus::Create(
-        first_params_.channels(),
-        first_params_.sample_rate() *
-            audio_track_recorder_->GetOpusBufferDuration(
-                first_params_.sample_rate()) /
-            1000));
+        first_params_.channels(), first_params_.sample_rate() *
+                                      kMediaStreamAudioTrackBufferDurationMs /
+                                      base::Time::kMillisecondsPerSecond));
     first_source_.OnMoreData(bus.get(), 0, 0);
     return bus;
   }
   scoped_ptr<media::AudioBus> GetSecondSourceAudioBus() {
     scoped_ptr<media::AudioBus> bus(media::AudioBus::Create(
-        second_params_.channels(),
-        second_params_.sample_rate() *
-            audio_track_recorder_->GetOpusBufferDuration(
-                second_params_.sample_rate()) /
-            1000));
+        second_params_.channels(), second_params_.sample_rate() *
+                                       kMediaStreamAudioTrackBufferDurationMs /
+                                       base::Time::kMillisecondsPerSecond));
     second_source_.OnMoreData(bus.get(), 0, 0);
     return bus;
   }
@@ -155,17 +175,13 @@
                       scoped_ptr<std::string> encoded_data,
                       base::TimeTicks timestamp) {
     EXPECT_TRUE(!encoded_data->empty());
-
     // Decode |encoded_data| and check we get the expected number of frames
     // per buffer.
-    EXPECT_EQ(
-        params.sample_rate() *
-            audio_track_recorder_->GetOpusBufferDuration(params.sample_rate()) /
-            1000,
-        opus_decode_float(
-            opus_decoder_,
-            reinterpret_cast<uint8_t*>(string_as_array(encoded_data.get())),
-            encoded_data->size(), buffer_.get(), max_frames_per_buffer_, 0));
+    EXPECT_EQ(kDefaultSampleRate * kOpusBufferDurationMs / 1000,
+              opus_decode_float(
+                  opus_decoder_, reinterpret_cast<uint8_t*>(
+                                     string_as_array(encoded_data.get())),
+                  encoded_data->size(), buffer_.get(), kFramesPerBuffer, 0));
 
     DoOnEncodedAudio(params, *encoded_data, timestamp);
   }
@@ -186,7 +202,6 @@
 
   // Decoder for verifying data was properly encoded.
   OpusDecoder* opus_decoder_;
-  int max_frames_per_buffer_;
   scoped_ptr<float[]> buffer_;
 
  private:
@@ -223,30 +238,49 @@
 
   // Give ATR initial audio parameters.
   audio_track_recorder_->OnSetFormat(first_params_);
+
   // TODO(ajose): consider adding WillOnce(SaveArg...) and inspecting, as done
   // in VTR unittests. http://crbug.com/548856
-  const base::TimeTicks time1 = base::TimeTicks::Now();
-  EXPECT_CALL(*this, DoOnEncodedAudio(_, _, time1)).Times(1);
-  audio_track_recorder_->OnData(*GetFirstSourceAudioBus(), time1);
+  EXPECT_CALL(*this, DoOnEncodedAudio(_, _, _)).Times(1);
+  audio_track_recorder_->OnData(*GetFirstSourceAudioBus(),
+                                base::TimeTicks::Now());
+  for (int i = 0; i < kRatioInputToOutputFrames - 1; ++i) {
+    audio_track_recorder_->OnData(*GetFirstSourceAudioBus(),
+                                  base::TimeTicks::Now());
+  }
 
-  // Send more audio.
-  const base::TimeTicks time2 = base::TimeTicks::Now();
   EXPECT_CALL(*this, DoOnEncodedAudio(_, _, _))
       .Times(1)
       // Only reset the decoder once we've heard back:
       .WillOnce(RunClosure(base::Bind(&AudioTrackRecorderTest::ResetDecoder,
                                       base::Unretained(this), second_params_)));
-  audio_track_recorder_->OnData(*GetFirstSourceAudioBus(), time2);
+  audio_track_recorder_->OnData(*GetFirstSourceAudioBus(),
+                                base::TimeTicks::Now());
+  for (int i = 0; i < kRatioInputToOutputFrames - 1; ++i) {
+    audio_track_recorder_->OnData(*GetFirstSourceAudioBus(),
+                                  base::TimeTicks::Now());
+  }
+
+  // If the amount of samples/10ms buffer is not an integer (e.g. 22050Hz) we
+  // need an extra OnData() to account for the round-off error.
+  if (GetParam().sample_rate % 100) {
+    audio_track_recorder_->OnData(*GetFirstSourceAudioBus(),
+                                  base::TimeTicks::Now());
+  }
 
   // Give ATR new audio parameters.
   audio_track_recorder_->OnSetFormat(second_params_);
 
   // Send audio with different params.
-  const base::TimeTicks time3 = base::TimeTicks::Now();
   EXPECT_CALL(*this, DoOnEncodedAudio(_, _, _))
       .Times(1)
       .WillOnce(RunClosure(quit_closure));
-  audio_track_recorder_->OnData(*GetSecondSourceAudioBus(), time3);
+  audio_track_recorder_->OnData(*GetSecondSourceAudioBus(),
+                                base::TimeTicks::Now());
+  for (int i = 0; i < kRatioInputToOutputFrames - 1; ++i) {
+    audio_track_recorder_->OnData(*GetSecondSourceAudioBus(),
+                                  base::TimeTicks::Now());
+  }
 
   run_loop.Run();
   Mock::VerifyAndClearExpectations(this);
diff --git a/content/renderer/media/media_recorder_handler_unittest.cc b/content/renderer/media/media_recorder_handler_unittest.cc
index 4a92b22..2a99ccb 100644
--- a/content/renderer/media/media_recorder_handler_unittest.cc
+++ b/content/renderer/media/media_recorder_handler_unittest.cc
@@ -22,6 +22,7 @@
 using ::testing::_;
 using ::testing::AtLeast;
 using ::testing::InSequence;
+using ::testing::Gt;
 using ::testing::Lt;
 using ::testing::Mock;
 using ::testing::TestWithParam;
@@ -41,25 +42,24 @@
 static const int kTestAudioChannels = 2;
 static const int kTestAudioBitsPerSample = 16;
 static const int kTestAudioSampleRate = 48000;
-static const int kTestAudioBufferDurationMS = 60;
+static const int kTestAudioBufferDurationMs = 10;
+// Opus works with 60ms buffers, so 6 MediaStreamAudioTrack Buffers are needed
+// to encode one output buffer.
+static const int kRatioOpusToTestAudioBuffers = 6;
 
 struct MediaRecorderTestParams {
   const bool has_video;
   const bool has_audio;
   const char* const mime_type;
   const char* const codecs;
-  const size_t first_encoded_video_frame_size;
-  const size_t second_encoded_video_frame_size;
-  const size_t first_encoded_audio_frame_size;
-  const size_t second_encoded_audio_frame_size;
 };
 
 // Array of valid combinations of video/audio/codecs and expected collected
 // encoded sizes to use for parameterizing MediaRecorderHandlerTest.
 static const MediaRecorderTestParams kMediaRecorderTestParams[] = {
-    {true, false, "video/webm", "vp8", 52, 32, 0, 0},
-    {true, false, "video/webm", "vp9", 33, 18, 0, 0},
-    {false, true, "video/webm", "vp8", 0, 0, 990, 706}};
+    {true, false, "video/webm", "vp8"},
+    {true, false, "video/webm", "vp9"},
+    {false, true, "video/webm", "vp8"}};
 
 class MediaRecorderHandlerTest : public TestWithParam<MediaRecorderTestParams>,
                                  public blink::WebMediaRecorderHandlerClient {
@@ -113,7 +113,7 @@
   scoped_ptr<media::AudioBus> NextAudioBus() {
     scoped_ptr<media::AudioBus> bus(media::AudioBus::Create(
         kTestAudioChannels,
-        kTestAudioSampleRate * kTestAudioBufferDurationMS / 1000));
+        kTestAudioSampleRate * kTestAudioBufferDurationMs / 1000));
     audio_source_.OnMoreData(bus.get(), 0, 0);
     return bus;
   }
@@ -221,31 +221,31 @@
   const scoped_refptr<media::VideoFrame> video_frame =
       media::VideoFrame::CreateBlackFrame(gfx::Size(160, 80));
 
+  const size_t kEncodedSizeThreshold = 16;
   {
     base::RunLoop run_loop;
     base::Closure quit_closure = run_loop.QuitClosure();
     // writeData() is pinged a number of times as the WebM header is written;
     // the last time it is called it has the encoded data.
-    const size_t encoded_data_size = GetParam().first_encoded_video_frame_size;
-    EXPECT_CALL(*this, writeData(_, Lt(encoded_data_size), _))
+    EXPECT_CALL(*this, writeData(_, Lt(kEncodedSizeThreshold), _))
         .Times(AtLeast(1));
-    EXPECT_CALL(*this, writeData(_, encoded_data_size, _))
+    EXPECT_CALL(*this, writeData(_, Gt(kEncodedSizeThreshold), _))
         .Times(1)
         .WillOnce(RunClosure(quit_closure));
 
     OnVideoFrameForTesting(video_frame);
     run_loop.Run();
   }
+  Mock::VerifyAndClearExpectations(this);
 
   {
     base::RunLoop run_loop;
     base::Closure quit_closure = run_loop.QuitClosure();
     // The second time around writeData() is called a number of times to write
     // the WebM frame header, and then is pinged with the encoded data.
-    const size_t encoded_data_size = GetParam().second_encoded_video_frame_size;
-    EXPECT_CALL(*this, writeData(_, Lt(encoded_data_size), _))
+    EXPECT_CALL(*this, writeData(_, Lt(kEncodedSizeThreshold), _))
         .Times(AtLeast(1));
-    EXPECT_CALL(*this, writeData(_, encoded_data_size, _))
+    EXPECT_CALL(*this, writeData(_, Gt(kEncodedSizeThreshold), _))
         .Times(1)
         .WillOnce(RunClosure(quit_closure));
 
@@ -284,38 +284,40 @@
   media::AudioParameters params(
       media::AudioParameters::AUDIO_PCM_LINEAR, media::CHANNEL_LAYOUT_STEREO,
       kTestAudioSampleRate, kTestAudioBitsPerSample,
-      kTestAudioSampleRate * kTestAudioBufferDurationMS / 1000);
+      kTestAudioSampleRate * kTestAudioBufferDurationMs / 1000);
   SetAudioFormatForTesting(params);
 
+  const size_t kEncodedSizeThreshold = 24;
   {
     base::RunLoop run_loop;
     base::Closure quit_closure = run_loop.QuitClosure();
     // writeData() is pinged a number of times as the WebM header is written;
     // the last time it is called it has the encoded data.
-    const size_t kEncodedDataSize = GetParam().first_encoded_audio_frame_size;
-    EXPECT_CALL(*this, writeData(_, Lt(kEncodedDataSize), _)).Times(AtLeast(1));
-    EXPECT_CALL(*this, writeData(_, kEncodedDataSize, _))
+    EXPECT_CALL(*this, writeData(_, Lt(kEncodedSizeThreshold), _))
+        .Times(AtLeast(1));
+    EXPECT_CALL(*this, writeData(_, Gt(kEncodedSizeThreshold), _))
         .Times(1)
         .WillOnce(RunClosure(quit_closure));
 
-    OnAudioBusForTesting(*audio_bus1);
+    for (int i = 0; i < kRatioOpusToTestAudioBuffers; ++i)
+      OnAudioBusForTesting(*audio_bus1);
     run_loop.Run();
   }
+  Mock::VerifyAndClearExpectations(this);
 
   {
     base::RunLoop run_loop;
     base::Closure quit_closure = run_loop.QuitClosure();
     // The second time around writeData() is called a number of times to write
     // the WebM frame header, and then is pinged with the encoded data.
-    const size_t kSecondEncodedDataSize =
-        GetParam().second_encoded_audio_frame_size;
-    EXPECT_CALL(*this, writeData(_, Lt(kSecondEncodedDataSize), _))
+    EXPECT_CALL(*this, writeData(_, Lt(kEncodedSizeThreshold), _))
         .Times(AtLeast(1));
-    EXPECT_CALL(*this, writeData(_, kSecondEncodedDataSize, _))
+    EXPECT_CALL(*this, writeData(_, Gt(kEncodedSizeThreshold), _))
         .Times(1)
         .WillOnce(RunClosure(quit_closure));
 
-    OnAudioBusForTesting(*audio_bus2);
+    for (int i = 0; i < kRatioOpusToTestAudioBuffers; ++i)
+      OnAudioBusForTesting(*audio_bus2);
     run_loop.Run();
   }