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

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

Powered by Google App Engine
This is Rietveld 408576698