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

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