Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "content/renderer/media_recorder/audio_track_recorder.h" | 5 #include "content/renderer/media_recorder/audio_track_recorder.h" |
| 6 | 6 |
| 7 #include <stdint.h> | 7 #include <stdint.h> |
| 8 #include <utility> | 8 #include <utility> |
| 9 | 9 |
| 10 #include "base/bind.h" | 10 #include "base/bind.h" |
| 11 #include "base/macros.h" | 11 #include "base/macros.h" |
| 12 #include "base/stl_util.h" | 12 #include "base/stl_util.h" |
| 13 #include "content/renderer/media/media_stream_audio_track.h" | 13 #include "content/renderer/media/media_stream_audio_track.h" |
| 14 #include "media/base/audio_bus.h" | 14 #include "media/base/audio_bus.h" |
| 15 #include "media/base/audio_converter.h" | 15 #include "media/base/audio_converter.h" |
| 16 #include "media/base/audio_fifo.h" | |
| 17 #include "media/base/audio_parameters.h" | 16 #include "media/base/audio_parameters.h" |
| 18 #include "media/base/audio_sample_types.h" | 17 #include "media/base/audio_sample_types.h" |
| 19 #include "media/base/bind_to_current_loop.h" | 18 #include "media/base/bind_to_current_loop.h" |
| 20 #include "third_party/opus/src/include/opus.h" | 19 #include "third_party/opus/src/include/opus.h" |
| 21 | 20 |
| 22 // Note that this code follows the Chrome media convention of defining a "frame" | 21 // Note that this code follows the Chrome media convention of defining a "frame" |
| 23 // as "one multi-channel sample" as opposed to another common definition meaning | 22 // as "one multi-channel sample" as opposed to another common definition meaning |
| 24 // "a chunk of samples". Here this second definition of "frame" is called a | 23 // "a chunk of samples". Here this second definition of "frame" is called a |
| 25 // "buffer"; so what might be called "frame duration" is instead "buffer | 24 // "buffer"; so what might be called "frame duration" is instead "buffer |
| 26 // duration", and so on. | 25 // duration", and so on. |
| 27 | 26 |
| 28 namespace content { | 27 namespace content { |
| 29 | 28 |
| 30 namespace { | 29 namespace { |
| 31 | 30 |
| 32 enum : int { | 31 enum : int { |
| 33 // Recommended value for opus_encode_float(), according to documentation in | 32 // Recommended value for opus_encode_float(), according to documentation in |
| 34 // third_party/opus/src/include/opus.h, so that the Opus encoder does not | 33 // third_party/opus/src/include/opus.h, so that the Opus encoder does not |
| 35 // degrade the audio due to memory constraints, and is independent of the | 34 // degrade the audio due to memory constraints, and is independent of the |
| 36 // duration of the encoded buffer. | 35 // duration of the encoded buffer. |
| 37 kOpusMaxDataBytes = 4000, | 36 kOpusMaxDataBytes = 4000, |
| 38 | 37 |
| 39 // Opus preferred sampling rate for encoding. This is also the one WebM likes | 38 // Opus preferred sampling rate for encoding. This is also the one WebM likes |
| 40 // to have: https://wiki.xiph.org/MatroskaOpus. | 39 // to have: https://wiki.xiph.org/MatroskaOpus. |
| 41 kOpusPreferredSamplingRate = 48000, | 40 kOpusPreferredSamplingRate = 48000, |
| 42 | 41 |
| 43 // For quality reasons we try to encode 60ms, the maximum Opus buffer. | 42 // For quality reasons we try to encode 60ms, the maximum Opus buffer. |
| 44 kOpusPreferredBufferDurationMs = 60, | 43 kOpusPreferredBufferDurationMs = 60, |
| 45 | |
| 46 // Maximum amount of buffers that can be held in the AudioEncoders' AudioFifo. | |
| 47 // Recording is not real time, hence a certain buffering is allowed. | |
| 48 kMaxNumberOfFifoBuffers = 2, | |
|
miu
2017/05/09 19:53:19
Keep this. (See comment below.)
Chandan
2017/05/10 09:35:07
Done.
| |
| 49 }; | 44 }; |
| 50 | 45 |
| 51 // The amount of Frames in a 60 ms buffer @ 48000 samples/second. | 46 // The amount of Frames in a 60 ms buffer @ 48000 samples/second. |
| 52 const int kOpusPreferredFramesPerBuffer = kOpusPreferredSamplingRate * | 47 const int kOpusPreferredFramesPerBuffer = kOpusPreferredSamplingRate * |
| 53 kOpusPreferredBufferDurationMs / | 48 kOpusPreferredBufferDurationMs / |
| 54 base::Time::kMillisecondsPerSecond; | 49 base::Time::kMillisecondsPerSecond; |
| 55 | 50 |
| 56 // Tries to encode |data_in|'s |num_samples| into |data_out|. | 51 // Tries to encode |data_in|'s |num_samples| into |data_out|. |
| 57 bool DoEncode(OpusEncoder* opus_encoder, | 52 bool DoEncode(OpusEncoder* opus_encoder, |
| 58 float* data_in, | 53 float* data_in, |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 120 base::ThreadChecker encoder_thread_checker_; | 115 base::ThreadChecker encoder_thread_checker_; |
| 121 | 116 |
| 122 // Track Audio (ingress) and Opus encoder input parameters, respectively. They | 117 // Track Audio (ingress) and Opus encoder input parameters, respectively. They |
| 123 // only differ in their sample_rate() and frames_per_buffer(): output is | 118 // only differ in their sample_rate() and frames_per_buffer(): output is |
| 124 // 48ksamples/s and 2880, respectively. | 119 // 48ksamples/s and 2880, respectively. |
| 125 media::AudioParameters input_params_; | 120 media::AudioParameters input_params_; |
| 126 media::AudioParameters output_params_; | 121 media::AudioParameters output_params_; |
| 127 | 122 |
| 128 // Sampling rate adapter between an OpusEncoder supported and the provided. | 123 // Sampling rate adapter between an OpusEncoder supported and the provided. |
| 129 std::unique_ptr<media::AudioConverter> converter_; | 124 std::unique_ptr<media::AudioConverter> converter_; |
| 130 std::unique_ptr<media::AudioFifo> fifo_; | 125 std::deque<std::unique_ptr<media::AudioBus>> audio_bus_queue_; |
| 131 | 126 |
| 132 // Buffer for passing AudioBus data to OpusEncoder. | 127 // Buffer for passing AudioBus data to OpusEncoder. |
| 133 std::unique_ptr<float[]> buffer_; | 128 std::unique_ptr<float[]> buffer_; |
| 134 | 129 |
| 135 // While |paused_|, AudioBuses are not encoded. | 130 // While |paused_|, AudioBuses are not encoded. |
| 136 bool paused_; | 131 bool paused_; |
| 137 | 132 |
| 133 int frames_in_; | |
|
miu
2017/05/09 19:53:19
Since these values always increase, they are at ri
Chandan
2017/05/10 09:35:07
Acknowledged.
| |
| 134 int frames_out_; | |
| 135 | |
| 138 OpusEncoder* opus_encoder_; | 136 OpusEncoder* opus_encoder_; |
| 139 | 137 |
| 140 DISALLOW_COPY_AND_ASSIGN(AudioEncoder); | 138 DISALLOW_COPY_AND_ASSIGN(AudioEncoder); |
| 141 }; | 139 }; |
| 142 | 140 |
| 143 AudioTrackRecorder::AudioEncoder::AudioEncoder( | 141 AudioTrackRecorder::AudioEncoder::AudioEncoder( |
| 144 const OnEncodedAudioCB& on_encoded_audio_cb, | 142 const OnEncodedAudioCB& on_encoded_audio_cb, |
| 145 int32_t bits_per_second) | 143 int32_t bits_per_second) |
| 146 : on_encoded_audio_cb_(on_encoded_audio_cb), | 144 : on_encoded_audio_cb_(on_encoded_audio_cb), |
| 147 bits_per_second_(bits_per_second), | 145 bits_per_second_(bits_per_second), |
| 148 paused_(false), | 146 paused_(false), |
| 147 frames_in_(0), | |
| 148 frames_out_(0), | |
| 149 opus_encoder_(nullptr) { | 149 opus_encoder_(nullptr) { |
| 150 // AudioEncoder is constructed on the thread that ATR lives on, but should | 150 // AudioEncoder is constructed on the thread that ATR lives on, but should |
| 151 // operate only on the encoder thread after that. Reset | 151 // operate only on the encoder thread after that. Reset |
| 152 // |encoder_thread_checker_| here, as the next call to CalledOnValidThread() | 152 // |encoder_thread_checker_| here, as the next call to CalledOnValidThread() |
| 153 // will be from the encoder thread. | 153 // will be from the encoder thread. |
| 154 encoder_thread_checker_.DetachFromThread(); | 154 encoder_thread_checker_.DetachFromThread(); |
| 155 } | 155 } |
| 156 | 156 |
| 157 AudioTrackRecorder::AudioEncoder::~AudioEncoder() { | 157 AudioTrackRecorder::AudioEncoder::~AudioEncoder() { |
| 158 // We don't DCHECK that we're on the encoder thread here, as it should have | 158 // We don't DCHECK that we're on the encoder thread here, as it should have |
| 159 // already been deleted at this point. | 159 // already been deleted at this point. |
| 160 DestroyExistingOpusEncoder(); | 160 DestroyExistingOpusEncoder(); |
| 161 } | 161 } |
| 162 | 162 |
| 163 void AudioTrackRecorder::AudioEncoder::OnSetFormat( | 163 void AudioTrackRecorder::AudioEncoder::OnSetFormat( |
| 164 const media::AudioParameters& input_params) { | 164 const media::AudioParameters& input_params) { |
| 165 DVLOG(1) << __func__; | 165 DVLOG(1) << __func__; |
| 166 DCHECK(encoder_thread_checker_.CalledOnValidThread()); | 166 DCHECK(encoder_thread_checker_.CalledOnValidThread()); |
| 167 if (input_params_.Equals(input_params)) | 167 if (input_params_.Equals(input_params)) |
| 168 return; | 168 return; |
| 169 | 169 |
| 170 DestroyExistingOpusEncoder(); | 170 DestroyExistingOpusEncoder(); |
| 171 | 171 |
| 172 if (!input_params.IsValid()) { | 172 if (!input_params.IsValid()) { |
| 173 DLOG(ERROR) << "Invalid params: " << input_params.AsHumanReadableString(); | 173 DLOG(ERROR) << "Invalid params: " << input_params.AsHumanReadableString(); |
| 174 return; | 174 return; |
| 175 } | 175 } |
| 176 | |
| 176 input_params_ = input_params; | 177 input_params_ = input_params; |
| 177 input_params_.set_frames_per_buffer(input_params_.sample_rate() * | 178 input_params_.set_frames_per_buffer(input_params_.sample_rate() * |
| 178 kOpusPreferredBufferDurationMs / | 179 kOpusPreferredBufferDurationMs / |
| 179 base::Time::kMillisecondsPerSecond); | 180 base::Time::kMillisecondsPerSecond); |
| 180 | 181 |
| 181 // third_party/libopus supports up to 2 channels (see implementation of | 182 // third_party/libopus supports up to 2 channels (see implementation of |
| 182 // opus_encoder_create()): force |output_params_| to at most those. | 183 // opus_encoder_create()): force |output_params_| to at most those. |
| 183 output_params_ = media::AudioParameters( | 184 output_params_ = media::AudioParameters( |
| 184 media::AudioParameters::AUDIO_PCM_LOW_LATENCY, | 185 media::AudioParameters::AUDIO_PCM_LOW_LATENCY, |
| 185 media::GuessChannelLayout(std::min(input_params_.channels(), 2)), | 186 media::GuessChannelLayout(std::min(input_params_.channels(), 2)), |
| 186 kOpusPreferredSamplingRate, | 187 kOpusPreferredSamplingRate, |
| 187 input_params_.bits_per_sample(), | 188 input_params_.bits_per_sample(), |
| 188 kOpusPreferredFramesPerBuffer); | 189 kOpusPreferredFramesPerBuffer); |
| 189 DVLOG(1) << "|input_params_|:" << input_params_.AsHumanReadableString() | 190 DVLOG(1) << "|input_params_|:" << input_params_.AsHumanReadableString() |
| 190 << " -->|output_params_|:" << output_params_.AsHumanReadableString(); | 191 << " -->|output_params_|:" << output_params_.AsHumanReadableString(); |
| 191 | 192 |
| 192 converter_.reset(new media::AudioConverter(input_params_, output_params_, | 193 converter_.reset(new media::AudioConverter(input_params_, output_params_, |
| 193 false /* disable_fifo */)); | 194 false /* disable_fifo */)); |
| 194 converter_->AddInput(this); | 195 converter_->AddInput(this); |
| 195 converter_->PrimeWithSilence(); | 196 converter_->PrimeWithSilence(); |
| 196 | 197 |
| 197 fifo_.reset(new media::AudioFifo( | 198 frames_in_ = 0; |
| 198 input_params_.channels(), | 199 frames_out_ = 0; |
| 199 kMaxNumberOfFifoBuffers * input_params_.frames_per_buffer())); | 200 audio_bus_queue_.clear(); |
| 200 | 201 |
| 201 buffer_.reset(new float[output_params_.channels() * | 202 buffer_.reset(new float[output_params_.channels() * |
| 202 output_params_.frames_per_buffer()]); | 203 output_params_.frames_per_buffer()]); |
| 203 | 204 |
| 204 // Initialize OpusEncoder. | 205 // Initialize OpusEncoder. |
| 205 int opus_result; | 206 int opus_result; |
| 206 opus_encoder_ = opus_encoder_create(output_params_.sample_rate(), | 207 opus_encoder_ = opus_encoder_create(output_params_.sample_rate(), |
| 207 output_params_.channels(), | 208 output_params_.channels(), |
| 208 OPUS_APPLICATION_AUDIO, | 209 OPUS_APPLICATION_AUDIO, |
| 209 &opus_result); | 210 &opus_result); |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 230 std::unique_ptr<media::AudioBus> input_bus, | 231 std::unique_ptr<media::AudioBus> input_bus, |
| 231 const base::TimeTicks& capture_time) { | 232 const base::TimeTicks& capture_time) { |
| 232 DVLOG(3) << __func__ << ", #frames " << input_bus->frames(); | 233 DVLOG(3) << __func__ << ", #frames " << input_bus->frames(); |
| 233 DCHECK(encoder_thread_checker_.CalledOnValidThread()); | 234 DCHECK(encoder_thread_checker_.CalledOnValidThread()); |
| 234 DCHECK_EQ(input_bus->channels(), input_params_.channels()); | 235 DCHECK_EQ(input_bus->channels(), input_params_.channels()); |
| 235 DCHECK(!capture_time.is_null()); | 236 DCHECK(!capture_time.is_null()); |
| 236 DCHECK(converter_); | 237 DCHECK(converter_); |
| 237 | 238 |
| 238 if (!is_initialized() || paused_) | 239 if (!is_initialized() || paused_) |
| 239 return; | 240 return; |
| 240 // TODO(mcasas): Consider using a std::deque<std::unique_ptr<AudioBus>> | 241 |
| 241 // instead of | 242 frames_in_ += input_bus->frames(); |
| 242 // an AudioFifo, to avoid copying data needlessly since we know the sizes of | 243 audio_bus_queue_.push_back(std::move(input_bus)); |
|
miu
2017/05/09 19:53:19
The old code had a limit on the maximum amount of
Chandan
2017/05/10 09:35:07
Done.
| |
| 243 // both input and output and they are multiples. | |
| 244 fifo_->Push(input_bus.get()); | |
| 245 | 244 |
| 246 // Wait to have enough |input_bus|s to guarantee a satisfactory conversion. | 245 // Wait to have enough |input_bus|s to guarantee a satisfactory conversion. |
| 247 while (fifo_->frames() >= input_params_.frames_per_buffer()) { | 246 while ((frames_in_ - frames_out_) >= input_params_.frames_per_buffer()) { |
| 248 std::unique_ptr<media::AudioBus> audio_bus = media::AudioBus::Create( | 247 std::unique_ptr<media::AudioBus> audio_bus = media::AudioBus::Create( |
| 249 output_params_.channels(), kOpusPreferredFramesPerBuffer); | 248 output_params_.channels(), kOpusPreferredFramesPerBuffer); |
| 250 converter_->Convert(audio_bus.get()); | 249 converter_->Convert(audio_bus.get()); |
| 251 audio_bus->ToInterleaved<media::Float32SampleTypeTraits>( | 250 audio_bus->ToInterleaved<media::Float32SampleTypeTraits>( |
| 252 audio_bus->frames(), buffer_.get()); | 251 audio_bus->frames(), buffer_.get()); |
| 253 | 252 |
| 254 std::unique_ptr<std::string> encoded_data(new std::string()); | 253 std::unique_ptr<std::string> encoded_data(new std::string()); |
| 255 if (DoEncode(opus_encoder_, buffer_.get(), kOpusPreferredFramesPerBuffer, | 254 if (DoEncode(opus_encoder_, buffer_.get(), kOpusPreferredFramesPerBuffer, |
| 256 encoded_data.get())) { | 255 encoded_data.get())) { |
| 257 const base::TimeTicks capture_time_of_first_sample = | 256 const base::TimeTicks capture_time_of_first_sample = |
| 258 capture_time - | 257 capture_time - |
| 259 base::TimeDelta::FromMicroseconds(fifo_->frames() * | 258 base::TimeDelta::FromMicroseconds((frames_in_ - frames_out_) * |
| 260 base::Time::kMicrosecondsPerSecond / | 259 base::Time::kMicrosecondsPerSecond / |
| 261 input_params_.sample_rate()); | 260 input_params_.sample_rate()); |
| 262 on_encoded_audio_cb_.Run(output_params_, std::move(encoded_data), | 261 on_encoded_audio_cb_.Run(output_params_, std::move(encoded_data), |
| 263 capture_time_of_first_sample); | 262 capture_time_of_first_sample); |
| 264 } | 263 } |
| 265 } | 264 } |
| 266 } | 265 } |
| 267 | 266 |
| 268 double AudioTrackRecorder::AudioEncoder::ProvideInput( | 267 double AudioTrackRecorder::AudioEncoder::ProvideInput( |
| 269 media::AudioBus* audio_bus, | 268 media::AudioBus* audio_bus, |
| 270 uint32_t frames_delayed) { | 269 uint32_t frames_delayed) { |
| 271 fifo_->Consume(audio_bus, 0, audio_bus->frames()); | 270 if (!audio_bus_queue_.empty()) { |
| 271 frames_out_ += audio_bus->frames(); | |
| 272 audio_bus_queue_.front()->CopyTo(audio_bus); | |
| 273 audio_bus_queue_.pop_front(); | |
| 274 } | |
| 272 return 1.0; // Return volume greater than zero to indicate we have more data. | 275 return 1.0; // Return volume greater than zero to indicate we have more data. |
|
miu
2017/05/09 19:53:19
It seems that if |audio_bus_queue_| is empty, we s
Chandan
2017/05/10 09:35:07
Done.
| |
| 273 } | 276 } |
| 274 | 277 |
| 275 void AudioTrackRecorder::AudioEncoder::DestroyExistingOpusEncoder() { | 278 void AudioTrackRecorder::AudioEncoder::DestroyExistingOpusEncoder() { |
| 276 // We don't DCHECK that we're on the encoder thread here, as this could be | 279 // We don't DCHECK that we're on the encoder thread here, as this could be |
| 277 // called from the dtor (main thread) or from OnSetFormat() (encoder thread). | 280 // called from the dtor (main thread) or from OnSetFormat() (encoder thread). |
| 278 if (opus_encoder_) { | 281 if (opus_encoder_) { |
| 279 opus_encoder_destroy(opus_encoder_); | 282 opus_encoder_destroy(opus_encoder_); |
| 280 opus_encoder_ = nullptr; | 283 opus_encoder_ = nullptr; |
| 281 } | 284 } |
| 282 } | 285 } |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 337 } | 340 } |
| 338 | 341 |
| 339 void AudioTrackRecorder::Resume() { | 342 void AudioTrackRecorder::Resume() { |
| 340 DCHECK(main_render_thread_checker_.CalledOnValidThread()); | 343 DCHECK(main_render_thread_checker_.CalledOnValidThread()); |
| 341 DCHECK(encoder_); | 344 DCHECK(encoder_); |
| 342 encoder_thread_.task_runner()->PostTask( | 345 encoder_thread_.task_runner()->PostTask( |
| 343 FROM_HERE, base::Bind(&AudioEncoder::set_paused, encoder_, false)); | 346 FROM_HERE, base::Bind(&AudioEncoder::set_paused, encoder_, false)); |
| 344 } | 347 } |
| 345 | 348 |
| 346 } // namespace content | 349 } // namespace content |
| OLD | NEW |