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

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

Powered by Google App Engine
This is Rietveld 408576698