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

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

Powered by Google App Engine
This is Rietveld 408576698