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; | |
|
miu
2015/10/21 20:35:38
Consider using enums for these constants rather th
ajose
2015/10/23 22:46:33
Done.
| |
| 18 // This is the recommended value, according to documentation in | |
| 19 // third_party/opus/src/include/opus.h, so that the Opus encoder does not | |
| 20 // degrade the audio due to memory constraints. | |
| 21 // | |
| 22 // Note: Whereas other RTP implementations do not, the cast library is | |
|
miu
2015/10/21 20:35:38
This comment seems....out of place? ;-)
ajose
2015/10/23 22:46:33
Whoops...
| |
| 23 // perfectly capable of transporting larger than MTU-sized audio frames. | |
| 24 static const int kOpusMaxPayloadSize = 4000; | |
| 25 | |
| 26 // TODO(ajose): Removed code that might be useful for ensuring A/V sync. | |
| 27 // See cast/sender/AudioEncoder. | |
| 28 | |
| 29 } // anonymous namespace | |
| 30 | |
| 31 class AudioTrackRecorder::AudioEncoder | |
|
miu
2015/10/21 20:35:38
General statement about the entire AudioEncoder cl
ajose
2015/10/23 22:46:33
Done.
| |
| 32 : public base::RefCountedThreadSafe<AudioEncoder> { | |
| 33 public: | |
| 34 AudioEncoder(const OnEncodedAudioCB& on_encoded_audio_cb) | |
|
miu
2015/10/21 20:35:38
Need explicit keyword here.
ajose
2015/10/23 22:46:33
Done.
| |
| 35 : on_encoded_audio_cb_(on_encoded_audio_cb), | |
|
mcasas
2015/10/21 19:19:51
Don't inline complex ctor.
miu
2015/10/21 20:35:38
It doesn't matter for classes private to .cc modul
ajose
2015/10/23 22:46:33
Acknowledged.
ajose
2015/10/23 22:46:33
Done.
| |
| 36 main_task_runner_(base::MessageLoop::current()->task_runner()) {} | |
| 37 | |
| 38 void OnSetFormat(const media::AudioParameters& params) { InitOpus(params); } | |
| 39 | |
| 40 void EncodeAudio(const media::AudioBus& audio_bus, | |
| 41 const base::TimeTicks& recorded_time); | |
| 42 | |
| 43 bool IsInitialized() { return audio_params_.get() != nullptr; } | |
| 44 | |
| 45 private: | |
| 46 friend class base::RefCountedThreadSafe<AudioEncoder>; | |
| 47 | |
| 48 ~AudioEncoder(); | |
| 49 | |
| 50 void InitOpus(const media::AudioParameters& params); | |
| 51 | |
| 52 void TransferSamplesIntoBuffer(const media::AudioBus& audio_bus, | |
| 53 int source_offset, | |
| 54 int buffer_fill_offset, | |
| 55 int num_samples); | |
| 56 bool EncodeFromFilledBuffer(std::string* out); | |
| 57 | |
| 58 static bool IsValidFrameDuration(base::TimeDelta duration); | |
| 59 | |
| 60 int samples_per_frame_; | |
| 61 const OnEncodedAudioCB on_encoded_audio_cb_; | |
| 62 | |
| 63 // In the case where a call to EncodeAudio() cannot completely fill the | |
| 64 // buffer, this points to the position at which to populate data in a later | |
| 65 // call. | |
| 66 int buffer_fill_end_; | |
| 67 | |
| 68 // Used to shutdown properly on the same thread we were created on. | |
| 69 const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; | |
|
mcasas
2015/10/21 19:19:52
I think this fellow is not used now, perhaps you w
ajose
2015/10/23 22:46:33
ThreadChecker already does something similar to th
| |
| 70 | |
| 71 // Task runner where frames to encode and reply callbacks must happen. | |
| 72 scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_; | |
| 73 | |
| 74 scoped_ptr<media::AudioParameters> audio_params_; | |
|
miu
2015/10/21 20:35:38
No need for separate heap allocation here. Just:
ajose
2015/10/23 22:46:33
Done.
| |
| 75 | |
| 76 // Buffer for passing AudioBus data to OpusEncoder. | |
| 77 scoped_ptr<float[]> buffer_; | |
| 78 | |
| 79 OpusEncoder* opus_encoder_; | |
|
mcasas
2015/10/21 19:19:52
Hmm this smells like a base::WeakPtr<>. However, t
ajose
2015/10/23 22:46:33
Think this is resolved with new structure - AudioE
| |
| 80 | |
| 81 DISALLOW_COPY_AND_ASSIGN(AudioEncoder); | |
| 82 }; | |
| 83 | |
| 84 AudioTrackRecorder::AudioEncoder::~AudioEncoder() { | |
| 85 LOG(INFO) << "AudioEncoder dtor"; | |
| 86 if (opus_encoder_) { | |
| 87 LOG(INFO) << "destroying opus"; | |
| 88 opus_encoder_destroy(opus_encoder_); | |
| 89 opus_encoder_ = nullptr; | |
| 90 } | |
| 91 } | |
| 92 | |
| 93 void AudioTrackRecorder::AudioEncoder::EncodeAudio( | |
| 94 const media::AudioBus& audio_bus, | |
| 95 const base::TimeTicks& recorded_time) { | |
| 96 DCHECK(IsInitialized()); | |
| 97 DCHECK(!recorded_time.is_null()); | |
| 98 DCHECK_EQ(audio_bus.channels(), audio_params_->channels()); | |
| 99 DCHECK_EQ(audio_bus.frames(), audio_params_->frames_per_buffer()); | |
| 100 | |
| 101 if (!origin_task_runner_.get()) | |
| 102 origin_task_runner_ = base::MessageLoop::current()->task_runner(); | |
|
miu
2015/10/21 20:35:38
This doesn't seem safe to me. Usually encoding sh
ajose
2015/10/23 22:46:33
Done.
| |
| 103 DCHECK(origin_task_runner_->BelongsToCurrentThread()); | |
| 104 | |
| 105 // Encode all audio in |audio_bus| into zero or more frames. | |
| 106 int src_pos = 0; | |
| 107 while (src_pos < audio_bus.frames()) { | |
| 108 const int num_samples_to_xfer = std::min( | |
| 109 samples_per_frame_ - buffer_fill_end_, audio_bus.frames() - src_pos); | |
| 110 TransferSamplesIntoBuffer(audio_bus, src_pos, buffer_fill_end_, | |
| 111 num_samples_to_xfer); | |
| 112 src_pos += num_samples_to_xfer; | |
| 113 buffer_fill_end_ += num_samples_to_xfer; | |
| 114 | |
| 115 if (buffer_fill_end_ < samples_per_frame_) | |
| 116 break; | |
| 117 | |
| 118 scoped_ptr<std::string> encoded_data(new std::string()); | |
| 119 if (EncodeFromFilledBuffer(encoded_data.get())) { | |
| 120 origin_task_runner_->PostTask( | |
| 121 FROM_HERE, base::Bind(on_encoded_audio_cb_, *audio_params_, | |
| 122 base::Passed(&encoded_data), recorded_time)); | |
| 123 } | |
| 124 | |
| 125 // Reset the internal buffer for the next frame. | |
| 126 buffer_fill_end_ = 0; | |
| 127 } | |
| 128 } | |
| 129 | |
| 130 void AudioTrackRecorder::AudioEncoder::InitOpus( | |
| 131 const media::AudioParameters& params) { | |
|
mcasas
2015/10/21 19:19:52
Thread check plz, here and elsewhere.
ajose
2015/10/23 22:46:33
Done.
| |
| 132 DCHECK(params.IsValid()); | |
| 133 DCHECK_EQ(params.bits_per_sample(), 16); | |
| 134 if (audio_params_.get() && audio_params_->Equals(params)) | |
|
mcasas
2015/10/21 19:19:52
How can |audio_params_.get()| be nullptr if it's a
| |
| 135 return; | |
| 136 | |
| 137 const int sampling_rate = params.sample_rate(); | |
| 138 const int num_channels = params.channels(); | |
| 139 samples_per_frame_ = sampling_rate / kDefaultFramesPerSecond; | |
| 140 const base::TimeDelta frame_duration = base::TimeDelta::FromMicroseconds( | |
| 141 base::Time::kMicrosecondsPerSecond * samples_per_frame_ / sampling_rate); | |
| 142 | |
| 143 if (frame_duration == base::TimeDelta() || | |
| 144 !IsValidFrameDuration(frame_duration)) { | |
| 145 DVLOG(1) << __FUNCTION__ << ": bad frame duration: " << frame_duration; | |
| 146 } | |
| 147 | |
| 148 // Support for max sampling rate of 48KHz, 2 channels, 100 ms duration. | |
| 149 const int kMaxSamplesTimesChannelsPerFrame = 48 * 2 * 100; | |
| 150 if (num_channels <= 0 || samples_per_frame_ <= 0 || | |
| 151 samples_per_frame_ * num_channels > kMaxSamplesTimesChannelsPerFrame || | |
| 152 sampling_rate % samples_per_frame_ != 0) { | |
|
mcasas
2015/10/21 19:19:52
I see what you do here but is more customary to
c
ajose
2015/10/23 22:46:33
Might have gone overboard...
| |
| 153 DVLOG(1) << __FUNCTION__ << ": bad inputs:" | |
| 154 << "\nnum channels: " << num_channels | |
| 155 << "\nsamples per frame: " << samples_per_frame_ | |
| 156 << "\nsampling rate: " << sampling_rate; | |
| 157 return; | |
| 158 } | |
| 159 | |
| 160 // Initialize AudioBus buffer for OpusEncoder. | |
| 161 buffer_fill_end_ = 0; | |
| 162 buffer_.reset(new float[num_channels * samples_per_frame_]); | |
| 163 | |
| 164 // Initialize OpusEncoder. | |
| 165 int opus_result; | |
| 166 opus_encoder_ = opus_encoder_create(sampling_rate, num_channels, | |
|
miu
2015/10/21 20:35:38
Can this InitOpus() method be called multiple time
ajose
2015/10/23 22:46:33
Done.
| |
| 167 OPUS_APPLICATION_AUDIO, &opus_result); | |
| 168 if (opus_result < 0) { | |
| 169 DVLOG(1) << __FUNCTION__ << ": couldn't initialize opus encoder: " | |
|
mcasas
2015/10/21 19:19:52
There's no real need for __FUNCTION__ here, and I'
ajose
2015/10/23 22:46:33
Done.
| |
| 170 << opus_strerror(opus_result); | |
| 171 return; | |
| 172 } | |
| 173 | |
| 174 audio_params_.reset(new media::AudioParameters(params)); | |
| 175 | |
| 176 // Note: As of 2013-10-31, the encoder in "auto bitrate" mode would use a | |
| 177 // variable bitrate up to 102kbps for 2-channel, 48 kHz audio and a 10 ms | |
| 178 // frame size. The opus library authors may, of course, adjust this in | |
| 179 // later versions. | |
|
mcasas
2015/10/21 19:19:52
Could we codify this assumption?
int bitrate;
| |
| 180 CHECK_EQ(opus_encoder_ctl(opus_encoder_, OPUS_SET_BITRATE(OPUS_AUTO)), | |
|
mcasas
2015/10/21 19:19:51
Here you say: crash the renderer tab if opus_encod
ajose
2015/10/23 22:46:33
Done.
| |
| 181 OPUS_OK); | |
| 182 } | |
| 183 | |
| 184 void AudioTrackRecorder::AudioEncoder::TransferSamplesIntoBuffer( | |
| 185 // const media::AudioBus* audio_bus, | |
| 186 const media::AudioBus& audio_bus, | |
| 187 int source_offset, | |
| 188 int buffer_fill_offset, | |
| 189 int num_samples) { | |
| 190 // Opus requires channel-interleaved samples in a single array. | |
| 191 for (int ch = 0; ch < audio_bus.channels(); ++ch) { | |
| 192 const float* src = audio_bus.channel(ch) + source_offset; | |
| 193 const float* const src_end = src + num_samples; | |
| 194 float* dest = | |
| 195 buffer_.get() + buffer_fill_offset * audio_params_->channels() + ch; | |
| 196 for (; src < src_end; ++src, dest += audio_params_->channels()) | |
| 197 *dest = *src; | |
| 198 } | |
| 199 } | |
| 200 | |
| 201 bool AudioTrackRecorder::AudioEncoder::EncodeFromFilledBuffer( | |
| 202 std::string* out) { | |
| 203 out->resize(kOpusMaxPayloadSize); | |
| 204 const opus_int32 result = opus_encode_float( | |
| 205 opus_encoder_, buffer_.get(), samples_per_frame_, | |
| 206 reinterpret_cast<uint8*>(string_as_array(out)), kOpusMaxPayloadSize); | |
|
mcasas
2015/10/21 19:19:52
What about
s/reinterpret_cast<uint8*>(string_as_a
ajose
2015/10/23 22:46:33
out->data() is const char*, need unsigned char* bu
| |
| 207 if (result > 1) { | |
| 208 out->resize(result); | |
| 209 return true; | |
| 210 } else if (result < 0) { | |
| 211 LOG(ERROR) << __FUNCTION__ << ": Error code from opus_encode_float(): " | |
| 212 << opus_strerror(result); | |
| 213 return false; | |
| 214 } else { | |
| 215 // Do nothing: The documentation says that a return value of zero or | |
| 216 // one means the packet does not need to be transmitted. | |
| 217 return false; | |
| 218 } | |
|
mcasas
2015/10/21 19:19:52
What about:
if (result > 1) {
out->resize(resul
ajose
2015/10/23 22:46:33
Done.
| |
| 219 } | |
| 220 | |
| 221 // static | |
| 222 bool AudioTrackRecorder::AudioEncoder::IsValidFrameDuration( | |
| 223 base::TimeDelta duration) { | |
| 224 // See https://tools.ietf.org/html/rfc6716#section-2.1.4 | |
|
mcasas
2015/10/21 19:19:52
Maybe also mention opus.h? [1]
[1] https://code.g
| |
| 225 return duration == base::TimeDelta::FromMicroseconds(2500) || | |
| 226 duration == base::TimeDelta::FromMilliseconds(5) || | |
| 227 duration == base::TimeDelta::FromMilliseconds(10) || | |
| 228 duration == base::TimeDelta::FromMilliseconds(20) || | |
| 229 duration == base::TimeDelta::FromMilliseconds(40) || | |
| 230 duration == base::TimeDelta::FromMilliseconds(60); | |
|
mcasas
2015/10/21 19:19:52
It sucks that we have to check for this duration o
ajose
2015/10/23 22:46:33
Not that I can find :(
| |
| 231 } | |
| 232 | |
| 233 AudioTrackRecorder::AudioTrackRecorder( | |
| 234 const blink::WebMediaStreamTrack& track, | |
| 235 const OnEncodedAudioCB& on_encoded_audio_cb) | |
| 236 : track_(track), | |
| 237 encoder_(new AudioEncoder(on_encoded_audio_cb)), | |
|
miu
2015/10/21 20:35:38
Note: If AudioEncoder was only used on a separate
ajose
2015/10/23 22:46:33
Nice!
| |
| 238 on_encoded_audio_cb_(on_encoded_audio_cb) { | |
| 239 DCHECK(main_render_thread_checker_.CalledOnValidThread()); | |
| 240 DCHECK(!track_.isNull()); | |
| 241 DCHECK(track_.extraData()); | |
| 242 // Connect the source provider to the track as a sink. | |
| 243 MediaStreamAudioSink::AddToAudioTrack(this, track_); | |
| 244 } | |
| 245 | |
| 246 AudioTrackRecorder::~AudioTrackRecorder() { | |
| 247 DCHECK(main_render_thread_checker_.CalledOnValidThread()); | |
| 248 MediaStreamAudioSink::RemoveFromAudioTrack(this, track_); | |
| 249 track_.reset(); | |
| 250 LOG(INFO) << "ATR dtor"; | |
| 251 } | |
| 252 | |
| 253 void AudioTrackRecorder::OnData(const media::AudioBus& audio_bus, | |
| 254 base::TimeTicks estimated_capture_time) { | |
| 255 DCHECK(capture_thread_checker_.CalledOnValidThread()); | |
| 256 encoder_->EncodeAudio(audio_bus, estimated_capture_time); | |
|
miu
2015/10/21 20:35:38
Is this method being called on a real-time audio t
ajose
2015/10/23 22:46:33
Done.
| |
| 257 } | |
| 258 | |
| 259 void AudioTrackRecorder::OnSetFormat(const media::AudioParameters& params) { | |
| 260 // If the source is restarted, might have changed to another capture thread. | |
| 261 capture_thread_checker_.DetachFromThread(); | |
| 262 DCHECK(capture_thread_checker_.CalledOnValidThread()); | |
| 263 encoder_->OnSetFormat(params); | |
| 264 } | |
| 265 | |
| 266 } // namespace content | |
| OLD | NEW |