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

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

Powered by Google App Engine
This is Rietveld 408576698