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

Powered by Google App Engine
This is Rietveld 408576698