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 "base/threading/thread.h" | |
| 8 #include "content/renderer/media/audio_track_recorder.h" | |
| 9 | |
| 10 #include "third_party/opus/src/include/opus.h" | |
| 11 | |
| 12 namespace content { | |
| 13 | |
| 14 namespace { | |
| 15 | |
| 16 // TODO(ajose): This likely shouldn't be hardcoded. | |
| 17 const int kDefaultFramesPerSecond = 100; | |
| 18 const int kDefaultAudioEncoderBitrate = 0; // let opus choose | |
| 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 // | |
| 23 // Note: Whereas other RTP implementations do not, the cast library is | |
| 24 // perfectly capable of transporting larger than MTU-sized audio frames. | |
| 25 static const int kOpusMaxPayloadSize = 4000; | |
| 26 | |
| 27 // TODO(ajose): Removed code that might be useful for ensuring A/V sync. | |
| 28 // See cast/sender/AudioEncoder. | |
| 29 | |
| 30 } // anonymous namespace | |
| 31 | |
| 32 class AudioTrackRecorder::AudioEncoder | |
| 33 : public base::RefCountedThreadSafe<AudioEncoder> { | |
| 34 public: | |
| 35 static void ShutdownEncoder(scoped_ptr<base::Thread> encoding_thread) { | |
| 36 DCHECK(encoding_thread->IsRunning()); | |
| 37 encoding_thread->Stop(); | |
| 38 } | |
| 39 | |
| 40 AudioEncoder(const OnEncodedAudioCB& on_encoded_audio_cb) | |
|
mcasas
2015/10/19 20:02:08
Do not inline large methods such as this ctor and
ajose
2015/10/20 03:21:11
Done.
| |
| 41 : initialized_(false), | |
| 42 on_encoded_audio_cb_(on_encoded_audio_cb), | |
| 43 encoding_thread_(new base::Thread("AudioEncodingThread")), | |
| 44 main_task_runner_(base::MessageLoop::current()->task_runner()) { | |
| 45 DCHECK(!encoding_thread_->IsRunning()); | |
| 46 encoding_thread_->Start(); | |
| 47 } | |
| 48 | |
| 49 void OnSetFormat(const media::AudioParameters& params) { | |
| 50 DCHECK(params.IsValid()); | |
| 51 InitOpus(params); | |
| 52 } | |
| 53 | |
| 54 void InsertAudio(scoped_ptr<media::AudioBus> audio_bus, | |
| 55 const base::TimeTicks& recorded_time); | |
| 56 | |
| 57 bool IsInitialized() { return initialized_; } | |
|
mcasas
2015/10/19 20:02:08
You can also turn |audio_params_| into a scoped_pt
ajose
2015/10/20 03:21:11
Done.
| |
| 58 | |
| 59 private: | |
| 60 friend class base::RefCountedThreadSafe<AudioEncoder>; | |
| 61 | |
| 62 ~AudioEncoder() { | |
| 63 main_task_runner_->PostTask(FROM_HERE, | |
| 64 base::Bind(&AudioEncoder::ShutdownEncoder, | |
| 65 base::Passed(&encoding_thread_))); | |
| 66 } | |
| 67 | |
| 68 void InitOpus(const media::AudioParameters& params); | |
| 69 | |
| 70 void EncodeAudio(scoped_ptr<media::AudioBus> audio_bus, | |
| 71 const base::TimeTicks& recorded_time); | |
| 72 | |
| 73 void TransferSamplesIntoBuffer(const media::AudioBus* audio_bus, | |
| 74 int source_offset, | |
| 75 int buffer_fill_offset, | |
| 76 int num_samples); | |
| 77 bool EncodeFromFilledBuffer(std::string* out); | |
| 78 | |
| 79 static bool IsValidFrameDuration(base::TimeDelta duration); | |
| 80 | |
| 81 bool initialized_; | |
| 82 | |
| 83 int samples_per_frame_; | |
| 84 const OnEncodedAudioCB on_encoded_audio_cb_; | |
| 85 | |
| 86 // In the case where a call to EncodeAudio() cannot completely fill the | |
| 87 // buffer, this points to the position at which to populate data in a later | |
| 88 // call. | |
| 89 int buffer_fill_end_; | |
| 90 | |
| 91 // Do actual opus encoding on a separate thread. | |
| 92 scoped_ptr<base::Thread> encoding_thread_; | |
|
mcasas
2015/10/19 20:02:08
Maybe miu@ will have something to say about the ne
| |
| 93 | |
| 94 // Used to shutdown properly on the same thread we were created on. | |
| 95 const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; | |
| 96 | |
| 97 // Task runner where frames to encode and reply callbacks must happen. | |
| 98 scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_; | |
| 99 | |
| 100 media::AudioParameters audio_params_; | |
| 101 | |
| 102 // OpusEncoder-related. | |
| 103 scoped_ptr<uint8[]> encoder_memory_; | |
| 104 OpusEncoder* opus_encoder_; | |
| 105 scoped_ptr<float[]> buffer_; | |
| 106 | |
| 107 DISALLOW_COPY_AND_ASSIGN(AudioEncoder); | |
| 108 }; | |
| 109 | |
| 110 void AudioTrackRecorder::AudioEncoder::InsertAudio( | |
| 111 scoped_ptr<media::AudioBus> audio_bus, | |
| 112 const base::TimeTicks& recorded_time) { | |
| 113 if (!origin_task_runner_.get()) | |
| 114 origin_task_runner_ = base::MessageLoop::current()->task_runner(); | |
| 115 DCHECK(origin_task_runner_->BelongsToCurrentThread()); | |
| 116 | |
| 117 DCHECK(audio_bus.get()); | |
| 118 DCHECK(initialized_); | |
| 119 | |
| 120 encoding_thread_->task_runner()->PostTask( | |
| 121 FROM_HERE, base::Bind(&AudioEncoder::EncodeAudio, this, | |
| 122 base::Passed(&audio_bus), recorded_time)); | |
| 123 } | |
| 124 | |
| 125 void AudioTrackRecorder::AudioEncoder::InitOpus( | |
| 126 const media::AudioParameters& params) { | |
| 127 int sampling_rate = params.sample_rate(); | |
| 128 int bitrate = kDefaultAudioEncoderBitrate; | |
| 129 int num_channels = params.channels(); | |
| 130 samples_per_frame_ = sampling_rate / kDefaultFramesPerSecond; | |
| 131 base::TimeDelta frame_duration = base::TimeDelta::FromMicroseconds( | |
| 132 base::Time::kMicrosecondsPerSecond * samples_per_frame_ / sampling_rate); | |
| 133 | |
| 134 // Initialize things that OpusEncoder needs. | |
| 135 buffer_fill_end_ = 0; | |
| 136 encoder_memory_.reset(new uint8[opus_encoder_get_size(num_channels)]); | |
| 137 opus_encoder_ = reinterpret_cast<OpusEncoder*>(encoder_memory_.get()); | |
|
mcasas
2015/10/19 20:02:08
Wow this is black magic!
Seriously though, do we a
ajose
2015/10/20 03:21:11
Switched to opus_encoder_create
| |
| 138 buffer_.reset(new float[num_channels * samples_per_frame_]); | |
| 139 | |
| 140 // Support for max sampling rate of 48KHz, 2 channels, 100 ms duration. | |
| 141 const int kMaxSamplesTimesChannelsPerFrame = 48 * 2 * 100; | |
| 142 if (num_channels <= 0 || samples_per_frame_ <= 0 || | |
| 143 frame_duration == base::TimeDelta() || | |
| 144 samples_per_frame_ * num_channels > kMaxSamplesTimesChannelsPerFrame || | |
| 145 sampling_rate % samples_per_frame_ != 0 || | |
| 146 !IsValidFrameDuration(frame_duration)) { | |
|
mcasas
2015/10/19 20:02:08
I'd move the check for IsValidFrameDuration() to r
ajose
2015/10/20 03:21:12
Done.
| |
| 147 DVLOG(1) << __FUNCTION__ << ": bad inputs."; | |
|
mcasas
2015/10/19 20:02:09
Make this msg more meaningful or remove it.
ajose
2015/10/20 03:21:11
Done.
| |
| 148 return; | |
| 149 } | |
| 150 | |
| 151 if (opus_encoder_init(opus_encoder_, sampling_rate, num_channels, | |
| 152 OPUS_APPLICATION_AUDIO) != OPUS_OK) { | |
| 153 DVLOG(1) << __FUNCTION__ << ": couldn't initialize opus encoder."; | |
|
mcasas
2015/10/19 20:02:08
What about caching the result of opus_encoder_init
ajose
2015/10/20 03:21:11
Done.
| |
| 154 return; | |
| 155 } | |
| 156 | |
| 157 if (bitrate <= 0) { | |
| 158 // Note: As of 2013-10-31, the encoder in "auto bitrate" mode would use a | |
| 159 // variable bitrate up to 102kbps for 2-channel, 48 kHz audio and a 10 ms | |
| 160 // frame size. The opus library authors may, of course, adjust this in | |
| 161 // later versions. | |
| 162 bitrate = OPUS_AUTO; | |
|
mcasas
2015/10/19 20:02:08
|bitrate| is initalized to kDefaultAudioEncoderBit
ajose
2015/10/20 03:21:11
Was considering letting user set bitrate but will
| |
| 163 } | |
| 164 | |
| 165 audio_params_ = params; | |
| 166 initialized_ = true; | |
| 167 | |
| 168 CHECK_EQ(opus_encoder_ctl(opus_encoder_, OPUS_SET_BITRATE(bitrate)), OPUS_OK); | |
| 169 } | |
| 170 | |
| 171 void AudioTrackRecorder::AudioEncoder::EncodeAudio( | |
| 172 scoped_ptr<media::AudioBus> audio_bus, | |
| 173 const base::TimeTicks& recorded_time) { | |
| 174 DCHECK(encoding_thread_->task_runner()->BelongsToCurrentThread()); | |
| 175 DCHECK(initialized_); | |
| 176 DCHECK(!recorded_time.is_null()); | |
| 177 | |
| 178 // Encode all audio in |audio_bus| into zero or more frames. | |
| 179 int src_pos = 0; | |
| 180 while (src_pos < audio_bus->frames()) { | |
| 181 const int num_samples_to_xfer = std::min( | |
| 182 samples_per_frame_ - buffer_fill_end_, audio_bus->frames() - src_pos); | |
| 183 DCHECK_EQ(audio_bus->channels(), audio_params_.channels()); | |
|
mcasas
2015/10/19 20:02:08
If this doesn't change as |src_pos| moves along, m
ajose
2015/10/20 03:21:11
Done.
| |
| 184 TransferSamplesIntoBuffer(audio_bus.get(), src_pos, buffer_fill_end_, | |
| 185 num_samples_to_xfer); | |
| 186 src_pos += num_samples_to_xfer; | |
| 187 buffer_fill_end_ += num_samples_to_xfer; | |
| 188 | |
| 189 if (buffer_fill_end_ < samples_per_frame_) | |
| 190 break; | |
| 191 | |
| 192 scoped_ptr<std::string> encoded_data(new std::string()); | |
| 193 if (EncodeFromFilledBuffer(encoded_data.get())) { | |
| 194 origin_task_runner_->PostTask( | |
| 195 FROM_HERE, base::Bind(on_encoded_audio_cb_, audio_params_, | |
| 196 base::Passed(&encoded_data), recorded_time)); | |
| 197 } | |
| 198 | |
| 199 // Reset the internal buffer for the next frame. | |
| 200 buffer_fill_end_ = 0; | |
| 201 } | |
| 202 } | |
| 203 | |
| 204 void AudioTrackRecorder::AudioEncoder::TransferSamplesIntoBuffer( | |
| 205 const media::AudioBus* audio_bus, | |
| 206 int source_offset, | |
| 207 int buffer_fill_offset, | |
| 208 int num_samples) { | |
| 209 // Opus requires channel-interleaved samples in a single array. | |
| 210 for (int ch = 0; ch < audio_bus->channels(); ++ch) { | |
| 211 const float* src = audio_bus->channel(ch) + source_offset; | |
| 212 const float* const src_end = src + num_samples; | |
| 213 float* dest = | |
| 214 buffer_.get() + buffer_fill_offset * audio_params_.channels() + ch; | |
| 215 for (; src < src_end; ++src, dest += audio_params_.channels()) | |
| 216 *dest = *src; | |
| 217 } | |
| 218 } | |
| 219 | |
| 220 bool AudioTrackRecorder::AudioEncoder::EncodeFromFilledBuffer( | |
| 221 std::string* out) { | |
| 222 out->resize(kOpusMaxPayloadSize); | |
| 223 const opus_int32 result = opus_encode_float( | |
| 224 opus_encoder_, buffer_.get(), samples_per_frame_, | |
| 225 reinterpret_cast<uint8*>(string_as_array(out)), kOpusMaxPayloadSize); | |
| 226 if (result > 1) { | |
| 227 out->resize(result); | |
| 228 return true; | |
| 229 } else if (result < 0) { | |
| 230 LOG(ERROR) << __FUNCTION__ | |
| 231 << ": Error code from opus_encode_float(): " << result; | |
|
mcasas
2015/10/19 20:02:08
Suggestion: Use opus_strerror() [1]
[1] https://c
ajose
2015/10/20 03:21:11
Nice
| |
| 232 return false; | |
| 233 } else { | |
| 234 // Do nothing: The documentation says that a return value of zero or | |
| 235 // one byte means the packet does not need to be transmitted. | |
|
mcasas
2015/10/19 20:02:08
nit: remove "byte"
ajose
2015/10/20 03:21:12
Done.
| |
| 236 return false; | |
| 237 } | |
| 238 } | |
| 239 | |
| 240 // static | |
| 241 bool AudioTrackRecorder::AudioEncoder::IsValidFrameDuration( | |
| 242 base::TimeDelta duration) { | |
| 243 // See https://tools.ietf.org/html/rfc6716#section-2.1.4 | |
| 244 return duration == base::TimeDelta::FromMicroseconds(2500) || | |
| 245 duration == base::TimeDelta::FromMilliseconds(5) || | |
| 246 duration == base::TimeDelta::FromMilliseconds(10) || | |
| 247 duration == base::TimeDelta::FromMilliseconds(20) || | |
| 248 duration == base::TimeDelta::FromMilliseconds(40) || | |
| 249 duration == base::TimeDelta::FromMilliseconds(60); | |
| 250 } | |
| 251 | |
| 252 AudioTrackRecorder::AudioTrackRecorder( | |
| 253 const blink::WebMediaStreamTrack& track, | |
| 254 const OnEncodedAudioCB& on_encoded_audio_cb) | |
| 255 : track_(track), | |
| 256 encoder_(new AudioEncoder(on_encoded_audio_cb)), | |
| 257 on_encoded_audio_cb_(on_encoded_audio_cb) { | |
| 258 DCHECK(main_render_thread_checker_.CalledOnValidThread()); | |
| 259 DCHECK(!track_.isNull()); | |
| 260 DCHECK(track_.extraData()); | |
| 261 // Connect the source provider to the track as a sink. | |
| 262 MediaStreamAudioSink::AddToAudioTrack(this, track_); | |
| 263 } | |
| 264 | |
| 265 AudioTrackRecorder::~AudioTrackRecorder() { | |
| 266 DCHECK(main_render_thread_checker_.CalledOnValidThread()); | |
| 267 MediaStreamAudioSink::RemoveFromAudioTrack(this, track_); | |
| 268 track_.reset(); | |
| 269 } | |
| 270 | |
| 271 void AudioTrackRecorder::OnData(const media::AudioBus& audio_bus, | |
| 272 base::TimeTicks estimated_capture_time) { | |
| 273 DCHECK(encoder_->IsInitialized()); | |
|
mcasas
2015/10/19 20:02:08
Thread? Or a comment about it for the method.
ajose
2015/10/20 03:21:11
Looking into this.
| |
| 274 DCHECK_EQ(audio_bus.channels(), audio_params_.channels()); | |
| 275 DCHECK_EQ(audio_bus.frames(), audio_params_.frames_per_buffer()); | |
| 276 DCHECK(!estimated_capture_time.is_null()); | |
| 277 | |
| 278 // TODO(ajose): When will audio_bus be deleted? | |
| 279 scoped_ptr<media::AudioBus> audio_data = | |
| 280 media::AudioBus::Create(audio_params_); | |
| 281 audio_bus.CopyTo(audio_data.get()); | |
| 282 encoder_->InsertAudio(audio_data.Pass(), estimated_capture_time); | |
|
mcasas
2015/10/19 20:02:08
Is a bit confusing that we copy the data from |aud
ajose
2015/10/20 03:21:11
Done.
| |
| 283 } | |
| 284 | |
| 285 void AudioTrackRecorder::OnSetFormat(const media::AudioParameters& params) { | |
| 286 DCHECK(params.IsValid()); | |
| 287 DCHECK_EQ(params.bits_per_sample(), 16); | |
| 288 | |
| 289 if (audio_params_.Equals(params)) | |
| 290 return; | |
| 291 | |
| 292 // TODO(ajose): consider only storing params in ATR _or_ encoder, not both. | |
|
mcasas
2015/10/19 20:02:08
I was thinking the same, and by preference I'd say
ajose
2015/10/20 03:21:11
Done.
| |
| 293 audio_params_ = params; | |
| 294 encoder_->OnSetFormat(params); | |
| 295 } | |
| 296 | |
| 297 } // namespace content | |
| OLD | NEW |