Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "base/bind.h" | |
| 6 #include "base/stl_util.h" | |
| 7 #include "content/renderer/media/audio_track_recorder.h" | |
|
minyue
2015/11/11 13:40:10
I know this should go before all other include as
ajose
2015/11/12 00:10:40
Done.
| |
| 8 #include "media/audio/audio_parameters.h" | |
| 9 #include "media/base/audio_bus.h" | |
| 10 #include "media/base/bind_to_current_loop.h" | |
| 11 | |
|
minyue
2015/11/11 13:40:10
I don't know if there should be a empty line
ajose
2015/11/12 00:10:40
Done.
| |
| 12 #include "third_party/opus/src/include/opus.h" | |
| 13 | |
| 14 namespace content { | |
| 15 | |
| 16 namespace { | |
| 17 | |
| 18 enum { | |
| 19 // This is the recommended value, according to documentation in | |
| 20 // third_party/opus/src/include/opus.h, so that the Opus encoder does not | |
| 21 // degrade the audio due to memory constraints. | |
| 22 OPUS_MAX_PAYLOAD_SIZE = 4000, | |
| 23 | |
| 24 // Support for max sampling rate of 48KHz, 2 channels, 100 ms duration. | |
|
minyue
2015/11/11 13:40:10
where dose 100 ms duration come from?
ajose
2015/11/12 00:10:40
Lifted this from cast::AudioEncoder, didn't make O
| |
| 25 MAX_SAMPLES_x_CHANNELS_PER_FRAME = 48 * 2 * 100, | |
| 26 }; | |
| 27 | |
| 28 // Returns the Opus frame duration in milliseconds, or zero if none will work | |
| 29 // for the given |sample_rate|. | |
| 30 static int GetOpusFrameDuration(int sample_rate) { | |
|
minyue
2015/11/11 13:40:10
Like it or not, the naming convention in Chrome me
ajose
2015/11/12 00:10:40
Good point, I had unintentionally been using both
| |
| 31 // Valid frame durations in millseconds. Note there are other valid frame | |
| 32 // durations for Opus, see https://tools.ietf.org/html/rfc6716#section-2.1.4 | |
| 33 const std::vector<int> opus_valid_frame_durations_ms = | |
| 34 {10, 20, 40, 60}; | |
| 35 | |
| 36 // Search for a duration such that |sample_rate| % |frames_per_second| == 0, | |
| 37 // where |frames_per_second| = 1000ms / |possible_duration|. | |
| 38 for (auto possible_duration : opus_valid_frame_durations_ms) { | |
|
minyue
2015/11/11 13:40:10
This says that if all are valid, we'd use the shor
ajose
2015/11/12 00:10:40
Switched to descending.
| |
| 39 if ((sample_rate * possible_duration) % 1000 == 0) { | |
| 40 return possible_duration; | |
| 41 } | |
| 42 } | |
| 43 | |
| 44 // Otherwise, couldn't find a good duration. | |
| 45 return 0; | |
| 46 } | |
| 47 | |
| 48 } // anonymous namespace | |
| 49 | |
| 50 // Nested class encapsulating opus-related encoding details. | |
| 51 // AudioEncoder is created and destroyed on ATR's main thread (usually the | |
| 52 // main render thread) but otherwise should operate entirely on | |
| 53 // |encoder_thread_|, which is owned by AudioTrackRecorder. | |
| 54 // Be sure to delete |encoder_thread_| before deleting the AudioEncoder using | |
| 55 // it. | |
| 56 class AudioTrackRecorder::AudioEncoder | |
| 57 : public base::RefCountedThreadSafe<AudioEncoder> { | |
| 58 public: | |
| 59 explicit AudioEncoder(const OnEncodedAudioCB& on_encoded_audio_cb) | |
| 60 : on_encoded_audio_cb_(on_encoded_audio_cb), opus_encoder_(nullptr) { | |
| 61 // AudioEncoder is constructed on the thread that ATR lives on, but should | |
| 62 // operate only on the encoder thread after that. Reset | |
| 63 // |encoder_thread_checker_| here, as the next call to CalledOnValidThread() | |
| 64 // will be from the encoder thread. | |
| 65 encoder_thread_checker_.DetachFromThread(); | |
| 66 } | |
| 67 | |
| 68 void OnSetFormat(const media::AudioParameters& params); | |
| 69 | |
| 70 void EncodeAudio(scoped_ptr<media::AudioBus> audio_bus, | |
| 71 const base::TimeTicks& capture_time); | |
| 72 | |
| 73 private: | |
| 74 friend class base::RefCountedThreadSafe<AudioEncoder>; | |
| 75 | |
| 76 ~AudioEncoder(); | |
| 77 | |
| 78 bool is_initialized() const { return !!opus_encoder_; } | |
| 79 | |
| 80 void DestroyOpus(); | |
| 81 | |
| 82 void TransferSamplesIntoBuffer(const media::AudioBus* audio_bus, | |
| 83 int source_offset, | |
| 84 int buffer_fill_offset, | |
| 85 int num_samples); | |
| 86 bool EncodeFromFilledBuffer(std::string* out); | |
| 87 | |
| 88 int samples_per_frame_; | |
| 89 const OnEncodedAudioCB on_encoded_audio_cb_; | |
| 90 | |
| 91 base::ThreadChecker encoder_thread_checker_; | |
| 92 | |
| 93 // In the case where a call to EncodeAudio() cannot completely fill the | |
| 94 // buffer, this points to the position at which to populate data in a later | |
| 95 // call. | |
| 96 int buffer_fill_end_; | |
| 97 | |
| 98 media::AudioParameters audio_params_; | |
| 99 | |
| 100 // Buffer for passing AudioBus data to OpusEncoder. | |
| 101 scoped_ptr<float[]> buffer_; | |
| 102 | |
| 103 OpusEncoder* opus_encoder_; | |
| 104 | |
| 105 DISALLOW_COPY_AND_ASSIGN(AudioEncoder); | |
| 106 }; | |
| 107 | |
| 108 AudioTrackRecorder::AudioEncoder::~AudioEncoder() { | |
| 109 // We don't DCHECK that we're on the encoder thread here, as it should have | |
| 110 // already been deleted at this point. | |
| 111 DestroyOpus(); | |
| 112 } | |
| 113 | |
| 114 void AudioTrackRecorder::AudioEncoder::OnSetFormat( | |
| 115 const media::AudioParameters& params) { | |
| 116 DCHECK(encoder_thread_checker_.CalledOnValidThread()); | |
| 117 if (audio_params_.Equals(params)) | |
| 118 return; | |
| 119 | |
| 120 // Check for and destroy previous OpusEncoder, if necessary. | |
|
minyue
2015/11/11 13:40:10
"if necessary" seems unnecessary. We seem to delet
ajose
2015/11/12 00:10:40
Done.
| |
| 121 DestroyOpus(); | |
| 122 | |
| 123 if (!params.IsValid()) { | |
| 124 DLOG(ERROR) << "Invalid audio params: " << params.AsHumanReadableString(); | |
| 125 return; | |
| 126 } | |
| 127 | |
| 128 int frame_duration = GetOpusFrameDuration(params.sample_rate()); | |
| 129 if (frame_duration == 0) { | |
| 130 DLOG(ERROR) << "Could not find a valid |frame_duration| for the given " | |
| 131 << "sample rate: " << params.sample_rate(); | |
| 132 return; | |
| 133 } | |
| 134 | |
| 135 // This assumes |sample_rate| * |frame_duration| % 1000 == 0, which is true | |
| 136 // if |frame_duration| was calculated with GetOpusFrameDuration(). | |
| 137 samples_per_frame_ = params.sample_rate() * frame_duration / 1000; | |
| 138 if (samples_per_frame_ <= 0 || | |
|
minyue
2015/11/11 13:40:10
how can "samples_per_frame_ <= 0" happen?
ajose
2015/11/12 00:10:40
Should be covered by params.IsValid().
| |
| 139 params.sample_rate() % samples_per_frame_ != 0 || | |
| 140 samples_per_frame_ * params.channels() > | |
| 141 MAX_SAMPLES_x_CHANNELS_PER_FRAME) { | |
|
minyue
2015/11/11 13:40:10
is MAX_SAMPLES_x_CHANNELS_PER_FRAME a good name? t
ajose
2015/11/12 00:10:40
Changed to |MAX_SAMPLES_PER_BUFFER| to fit new nam
| |
| 142 DLOG(ERROR) << "Invalid |samples_per_frame_|: " << samples_per_frame_; | |
| 143 return; | |
| 144 } | |
| 145 | |
| 146 // Initialize AudioBus buffer for OpusEncoder. | |
| 147 buffer_fill_end_ = 0; | |
| 148 buffer_.reset(new float[params.channels() * samples_per_frame_]); | |
| 149 | |
| 150 // Initialize OpusEncoder. | |
| 151 int opus_result; | |
| 152 opus_encoder_ = opus_encoder_create(params.sample_rate(), params.channels(), | |
| 153 OPUS_APPLICATION_AUDIO, &opus_result); | |
| 154 if (opus_result < 0) { | |
| 155 DLOG(ERROR) << "Couldn't init opus encoder: " << opus_strerror(opus_result); | |
| 156 return; | |
| 157 } | |
| 158 | |
| 159 // Note: As of 2013-10-31, the encoder in "auto bitrate" mode would use a | |
| 160 // variable bitrate up to 102kbps for 2-channel, 48 kHz audio and a 10 ms | |
| 161 // frame size. The opus library authors may, of course, adjust this in | |
| 162 // later versions. | |
| 163 if (opus_encoder_ctl(opus_encoder_, OPUS_SET_BITRATE(OPUS_AUTO)) != OPUS_OK) { | |
|
minyue
2015/11/11 13:40:10
Auto bit rate can be ok. but why not giving AudioT
ajose
2015/11/12 00:10:40
This will likely be added in the near future.
minyue
2015/11/12 16:52:31
Acknowledged.
| |
| 164 DLOG(ERROR) << "Failed to set opus bitrate."; | |
| 165 return; | |
| 166 } | |
| 167 | |
| 168 audio_params_ = params; | |
| 169 } | |
| 170 | |
| 171 void AudioTrackRecorder::AudioEncoder::EncodeAudio( | |
| 172 scoped_ptr<media::AudioBus> audio_bus, | |
| 173 const base::TimeTicks& capture_time) { | |
| 174 DCHECK(encoder_thread_checker_.CalledOnValidThread()); | |
| 175 DCHECK_EQ(audio_bus->channels(), audio_params_.channels()); | |
| 176 DCHECK_EQ(audio_bus->frames(), audio_params_.frames_per_buffer()); | |
| 177 | |
| 178 if (!is_initialized()) | |
| 179 return; | |
| 180 | |
| 181 // Encode all audio in |audio_bus| into zero or more frames. | |
|
minyue
2015/11/11 13:40:10
"frames"->"packets"
ajose
2015/11/12 00:10:41
Done.
| |
| 182 int src_pos = 0; | |
| 183 while (src_pos < audio_bus->frames()) { | |
| 184 const int num_samples_to_xfer = std::min( | |
| 185 samples_per_frame_ - buffer_fill_end_, audio_bus->frames() - src_pos); | |
| 186 TransferSamplesIntoBuffer(audio_bus.get(), src_pos, buffer_fill_end_, | |
| 187 num_samples_to_xfer); | |
| 188 src_pos += num_samples_to_xfer; | |
| 189 buffer_fill_end_ += num_samples_to_xfer; | |
| 190 | |
| 191 if (buffer_fill_end_ < samples_per_frame_) | |
| 192 break; | |
| 193 | |
| 194 scoped_ptr<std::string> encoded_data(new std::string()); | |
| 195 if (EncodeFromFilledBuffer(encoded_data.get())) { | |
| 196 on_encoded_audio_cb_.Run(audio_params_, encoded_data.Pass(), | |
| 197 capture_time); | |
| 198 } | |
| 199 | |
| 200 // Reset the internal buffer for the next frame. | |
| 201 buffer_fill_end_ = 0; | |
| 202 } | |
| 203 } | |
| 204 | |
| 205 void AudioTrackRecorder::AudioEncoder::DestroyOpus() { | |
| 206 // We don't DCHECK that we're on the encoder thread here, as this could be | |
| 207 // called from the dtor (main thread) or from OnSetForamt() (render thread); | |
| 208 if (opus_encoder_) { | |
| 209 opus_encoder_destroy(opus_encoder_); | |
| 210 opus_encoder_ = nullptr; | |
| 211 } | |
| 212 } | |
| 213 | |
| 214 void AudioTrackRecorder::AudioEncoder::TransferSamplesIntoBuffer( | |
| 215 const media::AudioBus* audio_bus, | |
| 216 int source_offset, | |
| 217 int buffer_fill_offset, | |
| 218 int num_samples) { | |
| 219 // TODO(ajose): Consider replacing with AudioBus::ToInterleaved(). | |
| 220 // http://crbug.com/547918 | |
| 221 DCHECK(encoder_thread_checker_.CalledOnValidThread()); | |
| 222 DCHECK(is_initialized()); | |
| 223 // Opus requires channel-interleaved samples in a single array. | |
| 224 for (int ch = 0; ch < audio_bus->channels(); ++ch) { | |
| 225 const float* src = audio_bus->channel(ch) + source_offset; | |
| 226 const float* const src_end = src + num_samples; | |
| 227 float* dest = | |
| 228 buffer_.get() + buffer_fill_offset * audio_params_.channels() + ch; | |
| 229 for (; src < src_end; ++src, dest += audio_params_.channels()) | |
| 230 *dest = *src; | |
| 231 } | |
| 232 } | |
| 233 | |
| 234 bool AudioTrackRecorder::AudioEncoder::EncodeFromFilledBuffer( | |
| 235 std::string* out) { | |
| 236 DCHECK(encoder_thread_checker_.CalledOnValidThread()); | |
| 237 DCHECK(is_initialized()); | |
| 238 | |
| 239 out->resize(OPUS_MAX_PAYLOAD_SIZE); | |
|
minyue
2015/11/11 13:40:10
Unless memory size is very critical, it would be g
ajose
2015/11/12 00:10:40
Could you clarify this? Won't calling out->resize(
minyue
2015/11/12 16:52:31
Ah, I see why there is a confusion. I just focused
| |
| 240 const opus_int32 result = opus_encode_float( | |
| 241 opus_encoder_, buffer_.get(), samples_per_frame_, | |
| 242 reinterpret_cast<uint8*>(string_as_array(out)), OPUS_MAX_PAYLOAD_SIZE); | |
| 243 if (result > 1) { | |
| 244 // TODO(ajose): Investigate improving this. http://crbug.com/547918 | |
| 245 out->resize(result); | |
| 246 return true; | |
| 247 } | |
| 248 // If |result| in {0,1}, do nothing; the documentation says that a return | |
| 249 // value of zero or one means the packet does not need to be transmitted. | |
| 250 // Otherwise, we have an error. | |
| 251 DLOG_IF(ERROR, result < 0) << __FUNCTION__ | |
| 252 << " failed: " << opus_strerror(result); | |
| 253 return false; | |
| 254 } | |
| 255 | |
| 256 AudioTrackRecorder::AudioTrackRecorder( | |
| 257 const blink::WebMediaStreamTrack& track, | |
| 258 const OnEncodedAudioCB& on_encoded_audio_cb) | |
| 259 : track_(track), | |
| 260 encoder_(new AudioEncoder(media::BindToCurrentLoop(on_encoded_audio_cb))), | |
| 261 encoder_thread_("AudioEncoderThread") { | |
| 262 DCHECK(main_render_thread_checker_.CalledOnValidThread()); | |
| 263 DCHECK(!track_.isNull()); | |
| 264 DCHECK(track_.extraData()); | |
| 265 | |
| 266 // Start the |encoder_thread_|. From this point on, |encoder_| should work | |
| 267 // only on |encoder_thread_|, as enforced by DCHECKs. | |
| 268 DCHECK(!encoder_thread_.IsRunning()); | |
| 269 encoder_thread_.Start(); | |
| 270 | |
| 271 // Connect the source provider to the track as a sink. | |
| 272 MediaStreamAudioSink::AddToAudioTrack(this, track_); | |
| 273 } | |
| 274 | |
| 275 AudioTrackRecorder::~AudioTrackRecorder() { | |
| 276 DCHECK(main_render_thread_checker_.CalledOnValidThread()); | |
| 277 MediaStreamAudioSink::RemoveFromAudioTrack(this, track_); | |
| 278 } | |
| 279 | |
| 280 void AudioTrackRecorder::OnSetFormat(const media::AudioParameters& params) { | |
| 281 DCHECK(encoder_thread_.IsRunning()); | |
| 282 // If the source is restarted, might have changed to another capture thread. | |
| 283 capture_thread_checker_.DetachFromThread(); | |
| 284 DCHECK(capture_thread_checker_.CalledOnValidThread()); | |
| 285 | |
| 286 encoder_thread_.task_runner()->PostTask( | |
| 287 FROM_HERE, base::Bind(&AudioEncoder::OnSetFormat, encoder_, params)); | |
| 288 } | |
| 289 | |
| 290 void AudioTrackRecorder::OnData(const media::AudioBus& audio_bus, | |
| 291 base::TimeTicks capture_time) { | |
| 292 DCHECK(encoder_thread_.IsRunning()); | |
| 293 DCHECK(capture_thread_checker_.CalledOnValidThread()); | |
| 294 DCHECK(!capture_time.is_null()); | |
| 295 | |
| 296 scoped_ptr<media::AudioBus> audio_data = | |
| 297 media::AudioBus::Create(audio_bus.channels(), audio_bus.frames()); | |
| 298 audio_bus.CopyTo(audio_data.get()); | |
| 299 | |
| 300 encoder_thread_.task_runner()->PostTask( | |
| 301 FROM_HERE, base::Bind(&AudioEncoder::EncodeAudio, encoder_, | |
| 302 base::Passed(&audio_data), capture_time)); | |
| 303 } | |
| 304 | |
| 305 } // namespace content | |
| OLD | NEW |