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

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, trybots 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 "content/renderer/media/audio_track_recorder.h"
8 #include "media/audio/audio_parameters.h"
9 #include "media/base/audio_bus.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 : on_encoded_audio_cb_(on_encoded_audio_cb),
54 opus_encoder_(nullptr) {
55 // AudioEncoder is constructed on the thread that ATR lives on, but should
56 // operate only on the encoder thread after that. Reset
57 // |encoder_thread_checker_| here, as the next call to CalledOnValidThread()
58 // will be from the encoder thread.
59 encoder_thread_checker_.DetachFromThread();
60 }
61
62 void OnSetFormat(const media::AudioParameters& params);
63
64 void EncodeAudio(scoped_ptr<media::AudioBus> audio_bus,
65 const base::TimeTicks& capture_time);
66
67 private:
68 friend class base::RefCountedThreadSafe<AudioEncoder>;
69
70 ~AudioEncoder();
71
72 bool IsInitialized() { return !!opus_encoder_; }
73
74 void DestroyOpus();
75
76 void TransferSamplesIntoBuffer(const media::AudioBus* audio_bus,
77 int source_offset,
78 int buffer_fill_offset,
79 int num_samples);
80 bool EncodeFromFilledBuffer(std::string* out);
81
82 int samples_per_frame_;
83 const OnEncodedAudioCB on_encoded_audio_cb_;
84
85 base::ThreadChecker encoder_thread_checker_;
86
87 // In the case where a call to EncodeAudio() cannot completely fill the
88 // buffer, this points to the position at which to populate data in a later
89 // call.
90 int buffer_fill_end_;
91
92 media::AudioParameters audio_params_;
93
94 // Buffer for passing AudioBus data to OpusEncoder.
95 scoped_ptr<float[]> buffer_;
96
97 OpusEncoder* opus_encoder_;
98
99 DISALLOW_COPY_AND_ASSIGN(AudioEncoder);
100 };
101
102 AudioTrackRecorder::AudioEncoder::~AudioEncoder() {
103 // We don't DCHECK that we're on the encoder thread here, as it should have
104 // already been deleted at this point.
105 DestroyOpus();
106 }
107
108 void AudioTrackRecorder::AudioEncoder::OnSetFormat(
109 const media::AudioParameters& params) {
110 DCHECK(encoder_thread_checker_.CalledOnValidThread());
111 if (audio_params_.Equals(params))
112 return;
113
114 // Check for and destroy previous OpusEncoder, if necessary.
115 DestroyOpus();
116
117 if (!params.IsValid()) {
118 DLOG(ERROR) << "Invalid audio params: " << params.AsHumanReadableString();
119 return;
120 }
121
122 samples_per_frame_ = params.sample_rate() / DEFAULT_FRAMES_PER_SECOND;
123 if (samples_per_frame_ <= 0 ||
124 params.sample_rate() % samples_per_frame_ != 0 ||
125 samples_per_frame_ * params.channels() >
126 MAX_SAMPLES_x_CHANNELS_PER_FRAME) {
127 DLOG(ERROR) << "Invalid |samples_per_frame_|: " << samples_per_frame_;
128 return;
129 }
130
131 const base::TimeDelta frame_duration = base::TimeDelta::FromMicroseconds(
132 base::Time::kMicrosecondsPerSecond * samples_per_frame_ /
133 params.sample_rate());
134 if (!IsValidOpusFrameDuration(frame_duration)) {
135 DLOG(ERROR) << "Invalid |frame_duration|: " << frame_duration;
136 return;
137 }
138
139 // Initialize AudioBus buffer for OpusEncoder.
140 buffer_fill_end_ = 0;
141 buffer_.reset(new float[params.channels() * samples_per_frame_]);
142
143 // Initialize OpusEncoder.
144 int opus_result;
145 opus_encoder_ = opus_encoder_create(params.sample_rate(), params.channels(),
146 OPUS_APPLICATION_AUDIO, &opus_result);
147 if (opus_result < 0) {
148 DLOG(ERROR) << "Couldn't init opus encoder: " << opus_strerror(opus_result);
149 return;
150 }
151
152 // Note: As of 2013-10-31, the encoder in "auto bitrate" mode would use a
153 // variable bitrate up to 102kbps for 2-channel, 48 kHz audio and a 10 ms
154 // frame size. The opus library authors may, of course, adjust this in
155 // later versions.
156 if (opus_encoder_ctl(opus_encoder_, OPUS_SET_BITRATE(OPUS_AUTO)) != OPUS_OK) {
157 DLOG(ERROR) << "Failed to set opus bitrate.";
158 return;
159 }
160
161 audio_params_ = params;
162 }
163
164 void AudioTrackRecorder::AudioEncoder::EncodeAudio(
165 scoped_ptr<media::AudioBus> audio_bus,
166 const base::TimeTicks& capture_time) {
167 DCHECK(encoder_thread_checker_.CalledOnValidThread());
168 DCHECK(IsInitialized());
mcasas 2015/10/28 20:03:00 Actually, since there's no way for OnSetFormat() t
ajose 2015/10/28 21:54:06 Done.
169 DCHECK_EQ(audio_bus->channels(), audio_params_.channels());
170 DCHECK_EQ(audio_bus->frames(), audio_params_.frames_per_buffer());
171
172 // Encode all audio in |audio_bus| into zero or more frames.
173 int src_pos = 0;
174 while (src_pos < audio_bus->frames()) {
175 const int num_samples_to_xfer = std::min(
176 samples_per_frame_ - buffer_fill_end_, audio_bus->frames() - src_pos);
177 TransferSamplesIntoBuffer(audio_bus.get(), src_pos, buffer_fill_end_,
178 num_samples_to_xfer);
179 src_pos += num_samples_to_xfer;
180 buffer_fill_end_ += num_samples_to_xfer;
181
182 if (buffer_fill_end_ < samples_per_frame_)
183 break;
184
185 scoped_ptr<std::string> encoded_data(new std::string());
186 if (EncodeFromFilledBuffer(encoded_data.get())) {
187 on_encoded_audio_cb_.Run(audio_params_, encoded_data.Pass(),
188 capture_time);
189 }
190
191 // Reset the internal buffer for the next frame.
192 buffer_fill_end_ = 0;
193 }
194 }
195
196 void AudioTrackRecorder::AudioEncoder::DestroyOpus() {
197 // We don't DCHECK that we're on the encoder thread here, as this could be
198 // called from the dtor (main thread) or from OnSetForamt (render thread);
199 if (opus_encoder_) {
200 opus_encoder_destroy(opus_encoder_);
201 opus_encoder_ = nullptr;
202 }
203 }
204
205 void AudioTrackRecorder::AudioEncoder::TransferSamplesIntoBuffer(
206 const media::AudioBus* audio_bus,
207 int source_offset,
208 int buffer_fill_offset,
209 int num_samples) {
210 // TODO(ajose): Replace with AudioBus::ToInterleaved()? http://crbug.com/54791 8
mcasas 2015/10/28 20:03:00 This line is > 80 characters :)
ajose 2015/10/28 21:54:06 I was hoping nobody would notice...
211 DCHECK(encoder_thread_checker_.CalledOnValidThread());
212 DCHECK(IsInitialized());
213 // Opus requires channel-interleaved samples in a single array.
214 for (int ch = 0; ch < audio_bus->channels(); ++ch) {
215 const float* src = audio_bus->channel(ch) + source_offset;
216 const float* const src_end = src + num_samples;
217 float* dest =
218 buffer_.get() + buffer_fill_offset * audio_params_.channels() + ch;
219 for (; src < src_end; ++src, dest += audio_params_.channels())
220 *dest = *src;
221 }
222 }
223
224 bool AudioTrackRecorder::AudioEncoder::EncodeFromFilledBuffer(
225 std::string* out) {
226 DCHECK(encoder_thread_checker_.CalledOnValidThread());
227 DCHECK(IsInitialized());
228
229 out->resize(OPUS_MAX_PAYLOAD_SIZE);
230 const opus_int32 result = opus_encode_float(
231 opus_encoder_, buffer_.get(), samples_per_frame_,
232 reinterpret_cast<uint8*>(string_as_array(out)), OPUS_MAX_PAYLOAD_SIZE);
233 if (result > 1) {
234 // TODO(ajose): Investigate improving this. http://crbug.com/547918
235 out->resize(result);
236 return true;
237 }
238 // If |result| in {0,1}, do nothing; the documentation says that a return
239 // value of zero or one means the packet does not need to be transmitted.
240 // Otherwise, we have an error.
241 DLOG_IF(ERROR, result < 0) << __FUNCTION__
242 << " failed: " << opus_strerror(result);
243 return false;
244 }
245
246 AudioTrackRecorder::AudioTrackRecorder(
247 const blink::WebMediaStreamTrack& track,
248 const OnEncodedAudioCB& on_encoded_audio_cb)
249 : track_(track),
250 encoder_(new AudioEncoder(media::BindToCurrentLoop(on_encoded_audio_cb))),
251 encoder_thread_("AudioEncoderThread") {
252 DCHECK(main_render_thread_checker_.CalledOnValidThread());
253 DCHECK(!track_.isNull());
254 DCHECK(track_.extraData());
255
256 // Start the |encoder_thread_|. From this point on, |encoder_| should work
257 // only on |encoder_thread_|, as enforced by DCHECKs.
258 DCHECK(!encoder_thread_.IsRunning());
259 encoder_thread_.Start();
260
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 }
269
270 void AudioTrackRecorder::OnSetFormat(const media::AudioParameters& params) {
271 DCHECK(encoder_thread_.IsRunning());
272 // If the source is restarted, might have changed to another capture thread.
273 capture_thread_checker_.DetachFromThread();
274 DCHECK(capture_thread_checker_.CalledOnValidThread());
275
276 encoder_thread_.task_runner()->PostTask(
277 FROM_HERE, base::Bind(&AudioEncoder::OnSetFormat, encoder_, params));
278 }
279
280 void AudioTrackRecorder::OnData(const media::AudioBus& audio_bus,
281 base::TimeTicks capture_time) {
282 DCHECK(encoder_thread_.IsRunning());
283 DCHECK(capture_thread_checker_.CalledOnValidThread());
284 DCHECK(!capture_time.is_null());
285
286 scoped_ptr<media::AudioBus> audio_data =
287 media::AudioBus::Create(audio_bus.channels(), audio_bus.frames());
288 audio_bus.CopyTo(audio_data.get());
289
290 encoder_thread_.task_runner()->PostTask(
291 FROM_HERE, base::Bind(&AudioEncoder::EncodeAudio, encoder_,
292 base::Passed(&audio_data), capture_time));
293 }
294
295 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698