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

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: miu's 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 // This is the recommended value, according to documentation in
20 // third_party/opus/src/include/opus.h, so that the Opus encoder does not
21 // degrade the audio due to memory constraints.
22 OPUS_MAX_PAYLOAD_SIZE = 4000,
23
24 // Support for max sampling rate of 48KHz, 2 channels, 100 ms duration.
25 MAX_SAMPLES_x_CHANNELS_PER_FRAME = 48 * 2 * 100,
26 };
27
28 static bool GetOpusFrameDuration(int sample_rate, int* frame_duration) {
miu 2015/10/29 23:16:42 nit: You could simplify by not taking an int* outp
ajose 2015/11/02 20:12:51 Done.
29 // Valid frame durations in millseconds. Note there are other valid frame
30 // durations for Opus, see https://tools.ietf.org/html/rfc6716#section-2.1.4
31 const std::vector<int> opus_valid_frame_durations_ms =
32 {10, 20, 40, 60};
33
34 // Search for a duration such that |sample_rate| % |frames_per_second| == 0,
35 // where |frames_per_second| = 1000ms / |possible_duration|.
36 for (auto possible_duration : opus_valid_frame_durations_ms) {
37 if (sample_rate % (1000 / possible_duration) == 0) {
miu 2015/10/29 23:16:42 1000 does not divide evenly by 60. Suggest you ch
ajose 2015/11/02 20:12:51 Done.
38 *frame_duration = possible_duration;
39 return true;
40 }
41 }
42
43 // Otherwise, couldn't find a good duration.
44 return false;
45 }
46
47 } // anonymous namespace
48
49 // Nested class encapsulating opus-related encoding details.
50 // AudioEncoder is created and destroyed on ATR's main thread (usually the
51 // main render thread) but otherwise should operate entirely on
52 // |encoder_thread_|, which is owned by AudioTrackRecorder.
53 // Be sure to delete |encoder_thread_| before deleting the AudioEncoder using
54 // it.
55 class AudioTrackRecorder::AudioEncoder
56 : public base::RefCountedThreadSafe<AudioEncoder> {
57 public:
58 explicit AudioEncoder(const OnEncodedAudioCB& on_encoded_audio_cb)
59 : on_encoded_audio_cb_(on_encoded_audio_cb), opus_encoder_(nullptr) {
60 // AudioEncoder is constructed on the thread that ATR lives on, but should
61 // operate only on the encoder thread after that. Reset
62 // |encoder_thread_checker_| here, as the next call to CalledOnValidThread()
63 // will be from the encoder thread.
64 encoder_thread_checker_.DetachFromThread();
65 }
66
67 void OnSetFormat(const media::AudioParameters& params);
68
69 void EncodeAudio(scoped_ptr<media::AudioBus> audio_bus,
70 const base::TimeTicks& capture_time);
71
72 private:
73 friend class base::RefCountedThreadSafe<AudioEncoder>;
74
75 ~AudioEncoder();
76
77 bool is_initialized() const { return !!opus_encoder_; }
78
79 void DestroyOpus();
80
81 void TransferSamplesIntoBuffer(const media::AudioBus* audio_bus,
82 int source_offset,
83 int buffer_fill_offset,
84 int num_samples);
85 bool EncodeFromFilledBuffer(std::string* out);
86
87 int samples_per_frame_;
88 const OnEncodedAudioCB on_encoded_audio_cb_;
89
90 base::ThreadChecker encoder_thread_checker_;
91
92 // In the case where a call to EncodeAudio() cannot completely fill the
93 // buffer, this points to the position at which to populate data in a later
94 // call.
95 int buffer_fill_end_;
96
97 media::AudioParameters audio_params_;
98
99 // Buffer for passing AudioBus data to OpusEncoder.
100 scoped_ptr<float[]> buffer_;
101
102 OpusEncoder* opus_encoder_;
103
104 DISALLOW_COPY_AND_ASSIGN(AudioEncoder);
105 };
106
107 AudioTrackRecorder::AudioEncoder::~AudioEncoder() {
108 // We don't DCHECK that we're on the encoder thread here, as it should have
109 // already been deleted at this point.
110 DestroyOpus();
111 }
112
113 void AudioTrackRecorder::AudioEncoder::OnSetFormat(
114 const media::AudioParameters& params) {
115 DCHECK(encoder_thread_checker_.CalledOnValidThread());
116 if (audio_params_.Equals(params))
117 return;
118
119 // Check for and destroy previous OpusEncoder, if necessary.
120 DestroyOpus();
121
122 if (!params.IsValid()) {
123 DLOG(ERROR) << "Invalid audio params: " << params.AsHumanReadableString();
124 return;
125 }
126
127 int frame_duration;
128 if (!GetOpusFrameDuration(params.sample_rate(), &frame_duration)) {
129 DLOG(ERROR) << "Could not find a valid |frame_duration| for the given "
130 << "sample rate: " << params.sample_rate();
131 return;
132 }
133
134 // |samples_per_frame_| = |sample_rate| / |frames_per_second|, where
135 // |frames_per_second| = 1000ms / frame_duration.
136 samples_per_frame_ = params.sample_rate() / (1000 / frame_duration);
miu 2015/10/29 23:16:42 Like above, this math could break if 1000 is not e
ajose 2015/11/02 20:12:51 Done.
137 if (samples_per_frame_ <= 0 ||
138 params.sample_rate() % samples_per_frame_ != 0 ||
139 samples_per_frame_ * params.channels() >
140 MAX_SAMPLES_x_CHANNELS_PER_FRAME) {
141 DLOG(ERROR) << "Invalid |samples_per_frame_|: " << samples_per_frame_;
142 return;
143 }
144
145 // Initialize AudioBus buffer for OpusEncoder.
146 buffer_fill_end_ = 0;
147 buffer_.reset(new float[params.channels() * samples_per_frame_]);
148
149 // Initialize OpusEncoder.
150 int opus_result;
151 opus_encoder_ = opus_encoder_create(params.sample_rate(), params.channels(),
152 OPUS_APPLICATION_AUDIO, &opus_result);
153 if (opus_result < 0) {
154 DLOG(ERROR) << "Couldn't init opus encoder: " << opus_strerror(opus_result);
155 return;
156 }
157
158 // Note: As of 2013-10-31, the encoder in "auto bitrate" mode would use a
159 // variable bitrate up to 102kbps for 2-channel, 48 kHz audio and a 10 ms
160 // frame size. The opus library authors may, of course, adjust this in
161 // later versions.
162 if (opus_encoder_ctl(opus_encoder_, OPUS_SET_BITRATE(OPUS_AUTO)) != OPUS_OK) {
163 DLOG(ERROR) << "Failed to set opus bitrate.";
164 return;
165 }
166
167 audio_params_ = params;
168 }
169
170 void AudioTrackRecorder::AudioEncoder::EncodeAudio(
171 scoped_ptr<media::AudioBus> audio_bus,
172 const base::TimeTicks& capture_time) {
173 DCHECK(encoder_thread_checker_.CalledOnValidThread());
174 DCHECK_EQ(audio_bus->channels(), audio_params_.channels());
175 DCHECK_EQ(audio_bus->frames(), audio_params_.frames_per_buffer());
176
177 if (!is_initialized())
178 return;
179
180 // Encode all audio in |audio_bus| into zero or more frames.
181 int src_pos = 0;
182 while (src_pos < audio_bus->frames()) {
183 const int num_samples_to_xfer = std::min(
184 samples_per_frame_ - buffer_fill_end_, audio_bus->frames() - src_pos);
185 TransferSamplesIntoBuffer(audio_bus.get(), src_pos, buffer_fill_end_,
186 num_samples_to_xfer);
187 src_pos += num_samples_to_xfer;
188 buffer_fill_end_ += num_samples_to_xfer;
189
190 if (buffer_fill_end_ < samples_per_frame_)
191 break;
192
193 scoped_ptr<std::string> encoded_data(new std::string());
194 if (EncodeFromFilledBuffer(encoded_data.get())) {
195 on_encoded_audio_cb_.Run(audio_params_, encoded_data.Pass(),
196 capture_time);
197 }
198
199 // Reset the internal buffer for the next frame.
200 buffer_fill_end_ = 0;
201 }
202 }
203
204 void AudioTrackRecorder::AudioEncoder::DestroyOpus() {
205 // We don't DCHECK that we're on the encoder thread here, as this could be
206 // called from the dtor (main thread) or from OnSetForamt (render thread);
mcasas 2015/10/29 22:58:21 nit: OnSetFormat()
ajose 2015/11/02 20:12:51 Done.
207 if (opus_encoder_) {
208 opus_encoder_destroy(opus_encoder_);
209 opus_encoder_ = nullptr;
210 }
211 }
212
213 void AudioTrackRecorder::AudioEncoder::TransferSamplesIntoBuffer(
214 const media::AudioBus* audio_bus,
215 int source_offset,
216 int buffer_fill_offset,
217 int num_samples) {
218 // TODO(ajose): Consider replacing with AudioBus::ToInterleaved().
219 // http://crbug.com/547918
220 DCHECK(encoder_thread_checker_.CalledOnValidThread());
221 DCHECK(is_initialized());
222 // Opus requires channel-interleaved samples in a single array.
223 for (int ch = 0; ch < audio_bus->channels(); ++ch) {
224 const float* src = audio_bus->channel(ch) + source_offset;
225 const float* const src_end = src + num_samples;
226 float* dest =
227 buffer_.get() + buffer_fill_offset * audio_params_.channels() + ch;
228 for (; src < src_end; ++src, dest += audio_params_.channels())
229 *dest = *src;
230 }
231 }
232
233 bool AudioTrackRecorder::AudioEncoder::EncodeFromFilledBuffer(
234 std::string* out) {
235 DCHECK(encoder_thread_checker_.CalledOnValidThread());
236 DCHECK(is_initialized());
237
238 out->resize(OPUS_MAX_PAYLOAD_SIZE);
239 const opus_int32 result = opus_encode_float(
240 opus_encoder_, buffer_.get(), samples_per_frame_,
241 reinterpret_cast<uint8*>(string_as_array(out)), OPUS_MAX_PAYLOAD_SIZE);
242 if (result > 1) {
243 // TODO(ajose): Investigate improving this. http://crbug.com/547918
244 out->resize(result);
245 return true;
246 }
247 // If |result| in {0,1}, do nothing; the documentation says that a return
248 // value of zero or one means the packet does not need to be transmitted.
249 // Otherwise, we have an error.
250 DLOG_IF(ERROR, result < 0) << __FUNCTION__
251 << " failed: " << opus_strerror(result);
252 return false;
253 }
254
255 AudioTrackRecorder::AudioTrackRecorder(
256 const blink::WebMediaStreamTrack& track,
257 const OnEncodedAudioCB& on_encoded_audio_cb)
258 : track_(track),
259 encoder_(new AudioEncoder(media::BindToCurrentLoop(on_encoded_audio_cb))),
260 encoder_thread_("AudioEncoderThread") {
261 DCHECK(main_render_thread_checker_.CalledOnValidThread());
262 DCHECK(!track_.isNull());
263 DCHECK(track_.extraData());
264
265 // Start the |encoder_thread_|. From this point on, |encoder_| should work
266 // only on |encoder_thread_|, as enforced by DCHECKs.
267 DCHECK(!encoder_thread_.IsRunning());
268 encoder_thread_.Start();
269
270 // Connect the source provider to the track as a sink.
271 MediaStreamAudioSink::AddToAudioTrack(this, track_);
272 }
273
274 AudioTrackRecorder::~AudioTrackRecorder() {
275 DCHECK(main_render_thread_checker_.CalledOnValidThread());
276 MediaStreamAudioSink::RemoveFromAudioTrack(this, track_);
277 }
278
279 void AudioTrackRecorder::OnSetFormat(const media::AudioParameters& params) {
280 DCHECK(encoder_thread_.IsRunning());
281 // If the source is restarted, might have changed to another capture thread.
282 capture_thread_checker_.DetachFromThread();
283 DCHECK(capture_thread_checker_.CalledOnValidThread());
284
285 encoder_thread_.task_runner()->PostTask(
286 FROM_HERE, base::Bind(&AudioEncoder::OnSetFormat, encoder_, params));
287 }
288
289 void AudioTrackRecorder::OnData(const media::AudioBus& audio_bus,
290 base::TimeTicks capture_time) {
291 DCHECK(encoder_thread_.IsRunning());
292 DCHECK(capture_thread_checker_.CalledOnValidThread());
293 DCHECK(!capture_time.is_null());
294
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 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698