Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(345)

Side by Side Diff: content/renderer/media/audio_track_recorder.cc

Issue 1406113002: Add AudioTrackRecorder for audio component of MediaStream recording. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: address comments Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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"
8 #include "media/base/bind_to_current_loop.h"
9
10 #include "third_party/opus/src/include/opus.h"
11
12 namespace content {
13
14 namespace {
15
16 enum {
17 // TODO(ajose): This likely shouldn't be hardcoded.
18 DEFAULT_FRAMES_PER_SECOND = 100,
19
20 // This is the recommended value, according to documentation in
21 // third_party/opus/src/include/opus.h, so that the Opus encoder does not
22 // degrade the audio due to memory constraints.
23 OPUS_MAX_PAYLOAD_SIZE = 4000,
24
25 // Support for max sampling rate of 48KHz, 2 channels, 100 ms duration.
26 MAX_SAMPLES_x_CHANNELS_PER_FRAME = 48 * 2 * 100,
27 };
28
29 } // anonymous namespace
30
31 // Nested class encapsulating opus-related encoding details.
32 // AudioEncoder is created and destroyed on ATR's main thread (usually the
33 // main render thread) but otherwise should operate entirely on
34 // |encoder_thread_|, which is owned by AudioTrackRecorder.
35 // Be sure to delete |encoder_thread_| before deleting the AudioEncoder using
36 // it.
37 class AudioTrackRecorder::AudioEncoder
38 : public base::RefCountedThreadSafe<AudioEncoder> {
39 public:
40 explicit AudioEncoder(const OnEncodedAudioCB& on_encoded_audio_cb)
41 : initialized_(false),
42 on_encoded_audio_cb_(on_encoded_audio_cb),
43 opus_encoder_(nullptr) {}
44
45 bool IsInitialized() { return initialized_; }
mcasas 2015/10/26 18:56:25 Since IsInitialized() is only used for DCHECK()s,
ajose 2015/10/26 20:26:46 I kinda like explicit initialization (doesn't hide
46
47 void OnSetFormat(const media::AudioParameters& params);
48
49 void EncodeAudio(scoped_ptr<media::AudioBus> audio_bus,
50 const base::TimeTicks& capture_time);
51
52 private:
53 friend class base::RefCountedThreadSafe<AudioEncoder>;
54
55 ~AudioEncoder();
56
57 bool InitOpus(const media::AudioParameters& params);
58 void DestroyOpus();
59
60 void TransferSamplesIntoBuffer(const media::AudioBus* audio_bus,
61 int source_offset,
62 int buffer_fill_offset,
63 int num_samples);
64 bool EncodeFromFilledBuffer(std::string* out);
65
66 static bool IsValidFrameDuration(base::TimeDelta duration);
67
68 bool initialized_;
69
70 int samples_per_frame_;
71 const OnEncodedAudioCB on_encoded_audio_cb_;
72
73 base::ThreadChecker encoder_thread_checker_;
74
75 // In the case where a call to EncodeAudio() cannot completely fill the
76 // buffer, this points to the position at which to populate data in a later
77 // call.
78 int buffer_fill_end_;
79
80 media::AudioParameters audio_params_;
81
82 // Buffer for passing AudioBus data to OpusEncoder.
83 scoped_ptr<float[]> buffer_;
84
85 OpusEncoder* opus_encoder_;
86
87 DISALLOW_COPY_AND_ASSIGN(AudioEncoder);
88 };
89
90 AudioTrackRecorder::AudioEncoder::~AudioEncoder() {
mcasas 2015/10/26 18:56:25 Order of method definition should follow the decla
ajose 2015/10/26 20:26:46 Done.
91 // We don't DCHECK that we're on the encoder thread here, as it should have
92 // already been deleted at this point.
93 DestroyOpus();
94 }
95
96 void AudioTrackRecorder::AudioEncoder::OnSetFormat(
97 const media::AudioParameters& params) {
98 // AudioEncoder is constructed on the thread that ATR lives on, but should
99 // operate only on the encoder thread after that. Reset
100 // |encoder_thread_checker_| here to reflect this.
101 encoder_thread_checker_.DetachFromThread();
mcasas 2015/10/26 18:56:25 Hmm this would reset the checker every time OnSetF
ajose 2015/10/26 20:26:46 Done.
102 DCHECK(encoder_thread_checker_.CalledOnValidThread());
103 if (!InitOpus(params)) {
104 DLOG(ERROR) << "Couldn't initialize opus.";
mcasas 2015/10/26 18:56:25 No need for more DLOG()s here, InitOpus() is infor
ajose 2015/10/26 20:26:46 Hmm, tricky because ATR::OnSetFormat() is calling
105 }
106 }
107
108 bool AudioTrackRecorder::AudioEncoder::InitOpus(
109 const media::AudioParameters& params) {
110 DCHECK(encoder_thread_checker_.CalledOnValidThread());
111 if (audio_params_.Equals(params))
112 return true;
113
114 if (!params.IsValid()) {
115 DLOG(ERROR) << "Invalid audio params: " << params.AsHumanReadableString();
116 return false;
117 }
118
119 if (params.bits_per_sample() != 16) {
120 DLOG(ERROR) << "Invalid bits per sample: " << params.bits_per_sample();
121 return false;
122 }
123
124 samples_per_frame_ = params.sample_rate() / DEFAULT_FRAMES_PER_SECOND;
125 if (samples_per_frame_ <= 0 ||
126 params.sample_rate() % samples_per_frame_ != 0 ||
127 samples_per_frame_ * params.channels() >
128 MAX_SAMPLES_x_CHANNELS_PER_FRAME) {
129 DLOG(ERROR) << "Invalid |samples_per_frame_|: " << samples_per_frame_;
130 return false;
131 }
132
133 const base::TimeDelta frame_duration = base::TimeDelta::FromMicroseconds(
134 base::Time::kMicrosecondsPerSecond * samples_per_frame_ /
135 params.sample_rate());
136 if (frame_duration == base::TimeDelta() ||
137 !IsValidFrameDuration(frame_duration)) {
138 DLOG(ERROR) << "Invalid |frame_duration|: " << frame_duration;
139 return false;
140 }
141
142 // Initialize AudioBus buffer for OpusEncoder.
143 buffer_fill_end_ = 0;
144 buffer_.reset(new float[params.channels() * samples_per_frame_]);
145
146 // Check for and destroy previous OpusEncoder, if necessary.
147 DestroyOpus();
148 // Initialize OpusEncoder.
149 int opus_result;
150 opus_encoder_ = opus_encoder_create(params.sample_rate(), params.channels(),
151 OPUS_APPLICATION_AUDIO, &opus_result);
152 if (opus_result < 0) {
153 DLOG(ERROR) << "Couldn't init opus encoder: " << opus_strerror(opus_result);
154 return false;
155 }
156
157 // Note: As of 2013-10-31, the encoder in "auto bitrate" mode would use a
158 // variable bitrate up to 102kbps for 2-channel, 48 kHz audio and a 10 ms
159 // frame size. The opus library authors may, of course, adjust this in
160 // later versions.
161 if (opus_encoder_ctl(opus_encoder_, OPUS_SET_BITRATE(OPUS_AUTO)) != OPUS_OK) {
162 DLOG(ERROR) << "Failed to set opus bitrate.";
163 return false;
164 }
165
166 audio_params_ = params;
167 initialized_ = true;
168 return true;
169 }
170
171 void AudioTrackRecorder::AudioEncoder::DestroyOpus() {
172 // We don't DCHECK that we're on the encoder thread here, as this could be
173 // called from the dtor (main thread) or from OnSetFormat (render thread);
174 if (opus_encoder_) {
175 opus_encoder_destroy(opus_encoder_);
176 opus_encoder_ = nullptr;
177 }
178 }
179
180 void AudioTrackRecorder::AudioEncoder::EncodeAudio(
181 scoped_ptr<media::AudioBus> audio_bus,
182 const base::TimeTicks& capture_time) {
183 DCHECK(encoder_thread_checker_.CalledOnValidThread());
184 DCHECK(IsInitialized());
185 DCHECK_EQ(audio_bus->channels(), audio_params_.channels());
186 DCHECK_EQ(audio_bus->frames(), audio_params_.frames_per_buffer());
187
188 // Encode all audio in |audio_bus| into zero or more frames.
189 int src_pos = 0;
190 while (src_pos < audio_bus->frames()) {
191 const int num_samples_to_xfer = std::min(
192 samples_per_frame_ - buffer_fill_end_, audio_bus->frames() - src_pos);
193 TransferSamplesIntoBuffer(audio_bus.get(), src_pos, buffer_fill_end_,
mcasas 2015/10/26 18:56:25 AudioBus has some method called ToInterleaved() [1
ajose 2015/10/26 20:26:46 I'll look into it, added a bug.
194 num_samples_to_xfer);
195 src_pos += num_samples_to_xfer;
196 buffer_fill_end_ += num_samples_to_xfer;
197
198 if (buffer_fill_end_ < samples_per_frame_)
199 break;
200
201 scoped_ptr<std::string> encoded_data(new std::string());
202 if (EncodeFromFilledBuffer(encoded_data.get())) {
203 on_encoded_audio_cb_.Run(audio_params_, encoded_data.Pass(),
204 capture_time);
205 }
206
207 // Reset the internal buffer for the next frame.
208 buffer_fill_end_ = 0;
209 }
210 }
211
212 void AudioTrackRecorder::AudioEncoder::TransferSamplesIntoBuffer(
213 const media::AudioBus* audio_bus,
214 int source_offset,
215 int buffer_fill_offset,
216 int num_samples) {
217 DCHECK(encoder_thread_checker_.CalledOnValidThread());
218 DCHECK(IsInitialized());
219 // Opus requires channel-interleaved samples in a single array.
220 for (int ch = 0; ch < audio_bus->channels(); ++ch) {
221 const float* src = audio_bus->channel(ch) + source_offset;
222 const float* const src_end = src + num_samples;
223 float* dest =
224 buffer_.get() + buffer_fill_offset * audio_params_.channels() + ch;
225 for (; src < src_end; ++src, dest += audio_params_.channels())
226 *dest = *src;
227 }
228 }
229
230 bool AudioTrackRecorder::AudioEncoder::EncodeFromFilledBuffer(
231 std::string* out) {
232 DCHECK(encoder_thread_checker_.CalledOnValidThread());
233 DCHECK(IsInitialized());
234
235 out->resize(OPUS_MAX_PAYLOAD_SIZE);
236 const opus_int32 result = opus_encode_float(
237 opus_encoder_, buffer_.get(), samples_per_frame_,
238 reinterpret_cast<uint8*>(string_as_array(out)), OPUS_MAX_PAYLOAD_SIZE);
239 if (result > 1) {
240 out->resize(result);
mcasas 2015/10/26 18:56:25 This is not your fault, but are we allocating an a
ajose 2015/10/26 20:26:46 I'll look into it, added a bug.
241 return true;
242 }
243 // If |result| in {0,1}, do nothing; the documentation says that a return
244 // value of zero or one means the packet does not need to be transmitted.
245 // Otherwise, we have an error.
246 DLOG_IF(ERROR, result < 0) << __FUNCTION__
247 << " failed: " << opus_strerror(result);
248 return false;
249 }
250
251 // static
252 bool AudioTrackRecorder::AudioEncoder::IsValidFrameDuration(
mcasas 2015/10/26 18:56:25 Move this method to an anonymous namespace and out
ajose 2015/10/26 20:26:46 Done.
253 base::TimeDelta duration) {
254 // See https://tools.ietf.org/html/rfc6716#section-2.1.4
255 return duration == base::TimeDelta::FromMicroseconds(2500) ||
256 duration == base::TimeDelta::FromMilliseconds(5) ||
257 duration == base::TimeDelta::FromMilliseconds(10) ||
258 duration == base::TimeDelta::FromMilliseconds(20) ||
259 duration == base::TimeDelta::FromMilliseconds(40) ||
260 duration == base::TimeDelta::FromMilliseconds(60);
261 }
262
263 AudioTrackRecorder::AudioTrackRecorder(
264 const blink::WebMediaStreamTrack& track,
265 const OnEncodedAudioCB& on_encoded_audio_cb)
266 : track_(track),
267 encoder_(new AudioEncoder(media::BindToCurrentLoop(on_encoded_audio_cb))),
268 encoder_thread_(new base::Thread("AudioEncoderThread")) {
269 DCHECK(main_render_thread_checker_.CalledOnValidThread());
270 DCHECK(!track_.isNull());
271 DCHECK(track_.extraData());
272
273 // Start the |encoder_thread_|. From this point on, |encoder_| should work
274 // only on |encoder_thread_|, as enforced by DCHECKs.
275 DCHECK(!encoder_thread_->IsRunning());
276 encoder_thread_->Start();
277
278 // Connect the source provider to the track as a sink.
279 MediaStreamAudioSink::AddToAudioTrack(this, track_);
280 }
281
282 AudioTrackRecorder::~AudioTrackRecorder() {
283 DCHECK(main_render_thread_checker_.CalledOnValidThread());
284 MediaStreamAudioSink::RemoveFromAudioTrack(this, track_);
285 track_.reset();
mcasas 2015/10/26 18:56:25 I don't think you need to reset() it explicitly si
ajose 2015/10/26 20:26:46 Done.
286 }
287
288 void AudioTrackRecorder::OnData(const media::AudioBus& audio_bus,
289 base::TimeTicks capture_time) {
290 DCHECK(encoder_thread_->IsRunning());
291 DCHECK(capture_thread_checker_.CalledOnValidThread());
292 DCHECK(!capture_time.is_null());
293
294 // TODO(ajose): When will audio_bus be deleted?
295 scoped_ptr<media::AudioBus> audio_data =
296 media::AudioBus::Create(audio_bus.channels(), audio_bus.frames());
297 audio_bus.CopyTo(audio_data.get());
298
299 encoder_thread_->task_runner()->PostTask(
300 FROM_HERE, base::Bind(&AudioEncoder::EncodeAudio, encoder_,
301 base::Passed(&audio_data), capture_time));
302 }
303
304 void AudioTrackRecorder::OnSetFormat(const media::AudioParameters& params) {
305 DCHECK(encoder_thread_->IsRunning());
306 // If the source is restarted, might have changed to another capture thread.
307 capture_thread_checker_.DetachFromThread();
308 DCHECK(capture_thread_checker_.CalledOnValidThread());
309
310 encoder_thread_->task_runner()->PostTask(
311 FROM_HERE, base::Bind(&AudioEncoder::OnSetFormat, encoder_, params));
312 }
313
314 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698