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

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 comment 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 media::AudioParameters audio_params_;
88
89 // Buffer for passing AudioBus data to OpusEncoder.
90 scoped_ptr<float[]> buffer_;
91
92 OpusEncoder* opus_encoder_;
93
94 DISALLOW_COPY_AND_ASSIGN(AudioEncoder);
95 };
96
97 AudioTrackRecorder::AudioEncoder::~AudioEncoder() {
98 // We don't DCHECK that we're on the encoder thread here, as it should have
99 // already been deleted at this point.
100 DestroyExistingOpusEncoder();
101 }
102
103 void AudioTrackRecorder::AudioEncoder::OnSetFormat(
104 const media::AudioParameters& params) {
105 DCHECK(encoder_thread_checker_.CalledOnValidThread());
106 if (audio_params_.Equals(params))
107 return;
108
109 DestroyExistingOpusEncoder();
110
111 if (!params.IsValid()) {
112 DLOG(ERROR) << "Invalid audio params: " << params.AsHumanReadableString();
113 return;
114 }
115
116 buffer_duration_ = base::TimeDelta::FromMilliseconds(
117 AudioTrackRecorder::GetOpusBufferDuration(params.sample_rate()));
118 if (buffer_duration_ == base::TimeDelta()) {
119 DLOG(ERROR) << "Could not find a valid |buffer_duration| for the given "
120 << "sample rate: " << params.sample_rate();
121 return;
122 }
123
124 frames_per_buffer_ =
125 params.sample_rate() * buffer_duration_.InMilliseconds() / 1000;
126 if (frames_per_buffer_ * params.channels() > MAX_SAMPLES_PER_BUFFER) {
127 DLOG(ERROR) << "Invalid |frames_per_buffer_|: " << frames_per_buffer_;
128 return;
129 }
130
131 // Initialize AudioBus buffer for OpusEncoder.
132 buffer_fill_end_ = 0;
133 buffer_.reset(new float[params.channels() * frames_per_buffer_]);
134
135 // Initialize OpusEncoder.
136 int opus_result;
137 opus_encoder_ = opus_encoder_create(params.sample_rate(), params.channels(),
138 OPUS_APPLICATION_AUDIO, &opus_result);
139 if (opus_result < 0) {
140 DLOG(ERROR) << "Couldn't init opus encoder: " << opus_strerror(opus_result);
141 return;
142 }
143
144 // Note: As of 2013-10-31, the encoder in "auto bitrate" mode would use a
145 // variable bitrate up to 102kbps for 2-channel, 48 kHz audio and a 10 ms
146 // buffer duration. The opus library authors may, of course, adjust this in
147 // later versions.
148 if (opus_encoder_ctl(opus_encoder_, OPUS_SET_BITRATE(OPUS_AUTO)) != OPUS_OK) {
149 DLOG(ERROR) << "Failed to set opus bitrate.";
150 return;
151 }
152
153 audio_params_ = params;
154 }
155
156 void AudioTrackRecorder::AudioEncoder::EncodeAudio(
157 scoped_ptr<media::AudioBus> audio_bus,
158 const base::TimeTicks& capture_time) {
159 DCHECK(encoder_thread_checker_.CalledOnValidThread());
160 DCHECK_EQ(audio_bus->channels(), audio_params_.channels());
161
162 if (!is_initialized())
163 return;
164
165 base::TimeDelta buffer_fill_duration =
166 buffer_fill_end_ * buffer_duration_ / frames_per_buffer_;
167 base::TimeTicks buffer_capture_time = capture_time - buffer_fill_duration;
168
169 // Encode all audio in |audio_bus| into zero or more packets.
170 int src_pos = 0;
171 while (src_pos < audio_bus->frames()) {
172 const int num_samples_to_xfer = std::min(
173 frames_per_buffer_ - buffer_fill_end_, audio_bus->frames() - src_pos);
174 TransferSamplesIntoBuffer(audio_bus.get(), src_pos, buffer_fill_end_,
175 num_samples_to_xfer);
176 src_pos += num_samples_to_xfer;
177 buffer_fill_end_ += num_samples_to_xfer;
178
179 if (buffer_fill_end_ < frames_per_buffer_)
180 break;
181
182 scoped_ptr<std::string> encoded_data(new std::string());
183 if (EncodeFromFilledBuffer(encoded_data.get())) {
184 on_encoded_audio_cb_.Run(audio_params_, encoded_data.Pass(),
185 buffer_capture_time);
186 }
187
188 // Reset the capture timestamp and internal buffer for next set of frames.
189 buffer_capture_time += buffer_duration_;
190 buffer_fill_end_ = 0;
191 }
192 }
193
194 void AudioTrackRecorder::AudioEncoder::DestroyExistingOpusEncoder() {
195 // We don't DCHECK that we're on the encoder thread here, as this could be
196 // called from the dtor (main thread) or from OnSetForamt() (render thread);
197 if (opus_encoder_) {
198 opus_encoder_destroy(opus_encoder_);
199 opus_encoder_ = nullptr;
200 }
201 }
202
203 void AudioTrackRecorder::AudioEncoder::TransferSamplesIntoBuffer(
204 const media::AudioBus* audio_bus,
205 int source_offset,
206 int buffer_fill_offset,
207 int num_samples) {
208 // TODO(ajose): Consider replacing with AudioBus::ToInterleaved().
209 // http://crbug.com/547918
210 DCHECK(encoder_thread_checker_.CalledOnValidThread());
211 DCHECK(is_initialized());
212 // Opus requires channel-interleaved samples in a single array.
213 for (int ch = 0; ch < audio_bus->channels(); ++ch) {
214 const float* src = audio_bus->channel(ch) + source_offset;
215 const float* const src_end = src + num_samples;
216 float* dest =
217 buffer_.get() + buffer_fill_offset * audio_params_.channels() + ch;
218 for (; src < src_end; ++src, dest += audio_params_.channels())
219 *dest = *src;
220 }
221 }
222
223 bool AudioTrackRecorder::AudioEncoder::EncodeFromFilledBuffer(
224 std::string* out) {
225 DCHECK(encoder_thread_checker_.CalledOnValidThread());
226 DCHECK(is_initialized());
227
228 out->resize(OPUS_MAX_PAYLOAD_SIZE);
229 const opus_int32 result = opus_encode_float(
230 opus_encoder_, buffer_.get(), frames_per_buffer_,
231 reinterpret_cast<uint8*>(string_as_array(out)), OPUS_MAX_PAYLOAD_SIZE);
232 if (result > 1) {
233 // TODO(ajose): Investigate improving this. http://crbug.com/547918
234 out->resize(result);
235 return true;
236 }
237 // If |result| in {0,1}, do nothing; the documentation says that a return
238 // value of zero or one means the packet does not need to be transmitted.
239 // Otherwise, we have an error.
240 DLOG_IF(ERROR, result < 0) << __FUNCTION__
241 << " failed: " << opus_strerror(result);
242 return false;
243 }
244
245 AudioTrackRecorder::AudioTrackRecorder(
246 const blink::WebMediaStreamTrack& track,
247 const OnEncodedAudioCB& on_encoded_audio_cb)
248 : track_(track),
249 encoder_(new AudioEncoder(media::BindToCurrentLoop(on_encoded_audio_cb))),
250 encoder_thread_("AudioEncoderThread") {
251 DCHECK(main_render_thread_checker_.CalledOnValidThread());
252 DCHECK(!track_.isNull());
253 DCHECK(track_.extraData());
254
255 // Start the |encoder_thread_|. From this point on, |encoder_| should work
256 // only on |encoder_thread_|, as enforced by DCHECKs.
257 DCHECK(!encoder_thread_.IsRunning());
258 encoder_thread_.Start();
259
260 // Connect the source provider to the track as a sink.
261 MediaStreamAudioSink::AddToAudioTrack(this, track_);
262 }
263
264 AudioTrackRecorder::~AudioTrackRecorder() {
265 DCHECK(main_render_thread_checker_.CalledOnValidThread());
266 MediaStreamAudioSink::RemoveFromAudioTrack(this, track_);
267 }
268
269 void AudioTrackRecorder::OnSetFormat(const media::AudioParameters& params) {
270 DCHECK(encoder_thread_.IsRunning());
271 // If the source is restarted, might have changed to another capture thread.
272 capture_thread_checker_.DetachFromThread();
273 DCHECK(capture_thread_checker_.CalledOnValidThread());
274
275 encoder_thread_.task_runner()->PostTask(
276 FROM_HERE, base::Bind(&AudioEncoder::OnSetFormat, encoder_, params));
277 }
278
279 void AudioTrackRecorder::OnData(const media::AudioBus& audio_bus,
280 base::TimeTicks capture_time) {
281 DCHECK(encoder_thread_.IsRunning());
282 DCHECK(capture_thread_checker_.CalledOnValidThread());
283 DCHECK(!capture_time.is_null());
284
285 scoped_ptr<media::AudioBus> audio_data =
286 media::AudioBus::Create(audio_bus.channels(), audio_bus.frames());
287 audio_bus.CopyTo(audio_data.get());
288
289 encoder_thread_.task_runner()->PostTask(
290 FROM_HERE, base::Bind(&AudioEncoder::EncodeAudio, encoder_,
291 base::Passed(&audio_data), capture_time));
292 }
293
294 int AudioTrackRecorder::GetOpusBufferDuration(int sample_rate) {
295 // Valid buffer durations in millseconds. Note there are other valid
296 // durations for Opus, see https://tools.ietf.org/html/rfc6716#section-2.1.4
297 // Descending order as longer durations can increase compression performance.
298 const std::vector<int> opus_valid_buffer_durations_ms = {60, 40, 20, 10};
299
300 // Search for a duration such that |sample_rate| % |buffers_per_second| == 0,
301 // where |buffers_per_second| = 1000ms / |possible_duration|.
302 for (auto possible_duration : opus_valid_buffer_durations_ms) {
303 if (sample_rate * possible_duration % 1000 == 0) {
304 return possible_duration;
305 }
306 }
307
308 // Otherwise, couldn't find a good duration.
309 return 0;
310 }
311
312 } // 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