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

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: Created 5 years, 2 months 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 "base/threading/thread.h"
8 #include "content/renderer/media/audio_track_recorder.h"
9
10 #include "third_party/opus/src/include/opus.h"
11
12 namespace content {
13
14 namespace {
15
16 // TODO(ajose): This likely shouldn't be hardcoded.
17 const int kDefaultFramesPerSecond = 100;
18 const int kDefaultAudioEncoderBitrate = 0; // let opus choose
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 //
23 // Note: Whereas other RTP implementations do not, the cast library is
24 // perfectly capable of transporting larger than MTU-sized audio frames.
25 static const int kOpusMaxPayloadSize = 4000;
26
27 // TODO(ajose): Removed code that might be useful for ensuring A/V sync.
28 // See cast/sender/AudioEncoder.
29
30 } // anonymous namespace
31
32 class AudioTrackRecorder::AudioEncoder
33 : public base::RefCountedThreadSafe<AudioEncoder> {
34 public:
35 static void ShutdownEncoder(scoped_ptr<base::Thread> encoding_thread) {
36 DCHECK(encoding_thread->IsRunning());
37 encoding_thread->Stop();
38 }
39
40 AudioEncoder(const OnEncodedAudioCB& on_encoded_audio_cb)
mcasas 2015/10/19 20:02:08 Do not inline large methods such as this ctor and
ajose 2015/10/20 03:21:11 Done.
41 : initialized_(false),
42 on_encoded_audio_cb_(on_encoded_audio_cb),
43 encoding_thread_(new base::Thread("AudioEncodingThread")),
44 main_task_runner_(base::MessageLoop::current()->task_runner()) {
45 DCHECK(!encoding_thread_->IsRunning());
46 encoding_thread_->Start();
47 }
48
49 void OnSetFormat(const media::AudioParameters& params) {
50 DCHECK(params.IsValid());
51 InitOpus(params);
52 }
53
54 void InsertAudio(scoped_ptr<media::AudioBus> audio_bus,
55 const base::TimeTicks& recorded_time);
56
57 bool IsInitialized() { return initialized_; }
mcasas 2015/10/19 20:02:08 You can also turn |audio_params_| into a scoped_pt
ajose 2015/10/20 03:21:11 Done.
58
59 private:
60 friend class base::RefCountedThreadSafe<AudioEncoder>;
61
62 ~AudioEncoder() {
63 main_task_runner_->PostTask(FROM_HERE,
64 base::Bind(&AudioEncoder::ShutdownEncoder,
65 base::Passed(&encoding_thread_)));
66 }
67
68 void InitOpus(const media::AudioParameters& params);
69
70 void EncodeAudio(scoped_ptr<media::AudioBus> audio_bus,
71 const base::TimeTicks& recorded_time);
72
73 void TransferSamplesIntoBuffer(const media::AudioBus* audio_bus,
74 int source_offset,
75 int buffer_fill_offset,
76 int num_samples);
77 bool EncodeFromFilledBuffer(std::string* out);
78
79 static bool IsValidFrameDuration(base::TimeDelta duration);
80
81 bool initialized_;
82
83 int samples_per_frame_;
84 const OnEncodedAudioCB on_encoded_audio_cb_;
85
86 // In the case where a call to EncodeAudio() cannot completely fill the
87 // buffer, this points to the position at which to populate data in a later
88 // call.
89 int buffer_fill_end_;
90
91 // Do actual opus encoding on a separate thread.
92 scoped_ptr<base::Thread> encoding_thread_;
mcasas 2015/10/19 20:02:08 Maybe miu@ will have something to say about the ne
93
94 // Used to shutdown properly on the same thread we were created on.
95 const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
96
97 // Task runner where frames to encode and reply callbacks must happen.
98 scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_;
99
100 media::AudioParameters audio_params_;
101
102 // OpusEncoder-related.
103 scoped_ptr<uint8[]> encoder_memory_;
104 OpusEncoder* opus_encoder_;
105 scoped_ptr<float[]> buffer_;
106
107 DISALLOW_COPY_AND_ASSIGN(AudioEncoder);
108 };
109
110 void AudioTrackRecorder::AudioEncoder::InsertAudio(
111 scoped_ptr<media::AudioBus> audio_bus,
112 const base::TimeTicks& recorded_time) {
113 if (!origin_task_runner_.get())
114 origin_task_runner_ = base::MessageLoop::current()->task_runner();
115 DCHECK(origin_task_runner_->BelongsToCurrentThread());
116
117 DCHECK(audio_bus.get());
118 DCHECK(initialized_);
119
120 encoding_thread_->task_runner()->PostTask(
121 FROM_HERE, base::Bind(&AudioEncoder::EncodeAudio, this,
122 base::Passed(&audio_bus), recorded_time));
123 }
124
125 void AudioTrackRecorder::AudioEncoder::InitOpus(
126 const media::AudioParameters& params) {
127 int sampling_rate = params.sample_rate();
128 int bitrate = kDefaultAudioEncoderBitrate;
129 int num_channels = params.channels();
130 samples_per_frame_ = sampling_rate / kDefaultFramesPerSecond;
131 base::TimeDelta frame_duration = base::TimeDelta::FromMicroseconds(
132 base::Time::kMicrosecondsPerSecond * samples_per_frame_ / sampling_rate);
133
134 // Initialize things that OpusEncoder needs.
135 buffer_fill_end_ = 0;
136 encoder_memory_.reset(new uint8[opus_encoder_get_size(num_channels)]);
137 opus_encoder_ = reinterpret_cast<OpusEncoder*>(encoder_memory_.get());
mcasas 2015/10/19 20:02:08 Wow this is black magic! Seriously though, do we a
ajose 2015/10/20 03:21:11 Switched to opus_encoder_create
138 buffer_.reset(new float[num_channels * samples_per_frame_]);
139
140 // Support for max sampling rate of 48KHz, 2 channels, 100 ms duration.
141 const int kMaxSamplesTimesChannelsPerFrame = 48 * 2 * 100;
142 if (num_channels <= 0 || samples_per_frame_ <= 0 ||
143 frame_duration == base::TimeDelta() ||
144 samples_per_frame_ * num_channels > kMaxSamplesTimesChannelsPerFrame ||
145 sampling_rate % samples_per_frame_ != 0 ||
146 !IsValidFrameDuration(frame_duration)) {
mcasas 2015/10/19 20:02:08 I'd move the check for IsValidFrameDuration() to r
ajose 2015/10/20 03:21:12 Done.
147 DVLOG(1) << __FUNCTION__ << ": bad inputs.";
mcasas 2015/10/19 20:02:09 Make this msg more meaningful or remove it.
ajose 2015/10/20 03:21:11 Done.
148 return;
149 }
150
151 if (opus_encoder_init(opus_encoder_, sampling_rate, num_channels,
152 OPUS_APPLICATION_AUDIO) != OPUS_OK) {
153 DVLOG(1) << __FUNCTION__ << ": couldn't initialize opus encoder.";
mcasas 2015/10/19 20:02:08 What about caching the result of opus_encoder_init
ajose 2015/10/20 03:21:11 Done.
154 return;
155 }
156
157 if (bitrate <= 0) {
158 // Note: As of 2013-10-31, the encoder in "auto bitrate" mode would use a
159 // variable bitrate up to 102kbps for 2-channel, 48 kHz audio and a 10 ms
160 // frame size. The opus library authors may, of course, adjust this in
161 // later versions.
162 bitrate = OPUS_AUTO;
mcasas 2015/10/19 20:02:08 |bitrate| is initalized to kDefaultAudioEncoderBit
ajose 2015/10/20 03:21:11 Was considering letting user set bitrate but will
163 }
164
165 audio_params_ = params;
166 initialized_ = true;
167
168 CHECK_EQ(opus_encoder_ctl(opus_encoder_, OPUS_SET_BITRATE(bitrate)), OPUS_OK);
169 }
170
171 void AudioTrackRecorder::AudioEncoder::EncodeAudio(
172 scoped_ptr<media::AudioBus> audio_bus,
173 const base::TimeTicks& recorded_time) {
174 DCHECK(encoding_thread_->task_runner()->BelongsToCurrentThread());
175 DCHECK(initialized_);
176 DCHECK(!recorded_time.is_null());
177
178 // Encode all audio in |audio_bus| into zero or more frames.
179 int src_pos = 0;
180 while (src_pos < audio_bus->frames()) {
181 const int num_samples_to_xfer = std::min(
182 samples_per_frame_ - buffer_fill_end_, audio_bus->frames() - src_pos);
183 DCHECK_EQ(audio_bus->channels(), audio_params_.channels());
mcasas 2015/10/19 20:02:08 If this doesn't change as |src_pos| moves along, m
ajose 2015/10/20 03:21:11 Done.
184 TransferSamplesIntoBuffer(audio_bus.get(), src_pos, buffer_fill_end_,
185 num_samples_to_xfer);
186 src_pos += num_samples_to_xfer;
187 buffer_fill_end_ += num_samples_to_xfer;
188
189 if (buffer_fill_end_ < samples_per_frame_)
190 break;
191
192 scoped_ptr<std::string> encoded_data(new std::string());
193 if (EncodeFromFilledBuffer(encoded_data.get())) {
194 origin_task_runner_->PostTask(
195 FROM_HERE, base::Bind(on_encoded_audio_cb_, audio_params_,
196 base::Passed(&encoded_data), recorded_time));
197 }
198
199 // Reset the internal buffer for the next frame.
200 buffer_fill_end_ = 0;
201 }
202 }
203
204 void AudioTrackRecorder::AudioEncoder::TransferSamplesIntoBuffer(
205 const media::AudioBus* audio_bus,
206 int source_offset,
207 int buffer_fill_offset,
208 int num_samples) {
209 // Opus requires channel-interleaved samples in a single array.
210 for (int ch = 0; ch < audio_bus->channels(); ++ch) {
211 const float* src = audio_bus->channel(ch) + source_offset;
212 const float* const src_end = src + num_samples;
213 float* dest =
214 buffer_.get() + buffer_fill_offset * audio_params_.channels() + ch;
215 for (; src < src_end; ++src, dest += audio_params_.channels())
216 *dest = *src;
217 }
218 }
219
220 bool AudioTrackRecorder::AudioEncoder::EncodeFromFilledBuffer(
221 std::string* out) {
222 out->resize(kOpusMaxPayloadSize);
223 const opus_int32 result = opus_encode_float(
224 opus_encoder_, buffer_.get(), samples_per_frame_,
225 reinterpret_cast<uint8*>(string_as_array(out)), kOpusMaxPayloadSize);
226 if (result > 1) {
227 out->resize(result);
228 return true;
229 } else if (result < 0) {
230 LOG(ERROR) << __FUNCTION__
231 << ": Error code from opus_encode_float(): " << result;
mcasas 2015/10/19 20:02:08 Suggestion: Use opus_strerror() [1] [1] https://c
ajose 2015/10/20 03:21:11 Nice
232 return false;
233 } else {
234 // Do nothing: The documentation says that a return value of zero or
235 // one byte means the packet does not need to be transmitted.
mcasas 2015/10/19 20:02:08 nit: remove "byte"
ajose 2015/10/20 03:21:12 Done.
236 return false;
237 }
238 }
239
240 // static
241 bool AudioTrackRecorder::AudioEncoder::IsValidFrameDuration(
242 base::TimeDelta duration) {
243 // See https://tools.ietf.org/html/rfc6716#section-2.1.4
244 return duration == base::TimeDelta::FromMicroseconds(2500) ||
245 duration == base::TimeDelta::FromMilliseconds(5) ||
246 duration == base::TimeDelta::FromMilliseconds(10) ||
247 duration == base::TimeDelta::FromMilliseconds(20) ||
248 duration == base::TimeDelta::FromMilliseconds(40) ||
249 duration == base::TimeDelta::FromMilliseconds(60);
250 }
251
252 AudioTrackRecorder::AudioTrackRecorder(
253 const blink::WebMediaStreamTrack& track,
254 const OnEncodedAudioCB& on_encoded_audio_cb)
255 : track_(track),
256 encoder_(new AudioEncoder(on_encoded_audio_cb)),
257 on_encoded_audio_cb_(on_encoded_audio_cb) {
258 DCHECK(main_render_thread_checker_.CalledOnValidThread());
259 DCHECK(!track_.isNull());
260 DCHECK(track_.extraData());
261 // Connect the source provider to the track as a sink.
262 MediaStreamAudioSink::AddToAudioTrack(this, track_);
263 }
264
265 AudioTrackRecorder::~AudioTrackRecorder() {
266 DCHECK(main_render_thread_checker_.CalledOnValidThread());
267 MediaStreamAudioSink::RemoveFromAudioTrack(this, track_);
268 track_.reset();
269 }
270
271 void AudioTrackRecorder::OnData(const media::AudioBus& audio_bus,
272 base::TimeTicks estimated_capture_time) {
273 DCHECK(encoder_->IsInitialized());
mcasas 2015/10/19 20:02:08 Thread? Or a comment about it for the method.
ajose 2015/10/20 03:21:11 Looking into this.
274 DCHECK_EQ(audio_bus.channels(), audio_params_.channels());
275 DCHECK_EQ(audio_bus.frames(), audio_params_.frames_per_buffer());
276 DCHECK(!estimated_capture_time.is_null());
277
278 // TODO(ajose): When will audio_bus be deleted?
279 scoped_ptr<media::AudioBus> audio_data =
280 media::AudioBus::Create(audio_params_);
281 audio_bus.CopyTo(audio_data.get());
282 encoder_->InsertAudio(audio_data.Pass(), estimated_capture_time);
mcasas 2015/10/19 20:02:08 Is a bit confusing that we copy the data from |aud
ajose 2015/10/20 03:21:11 Done.
283 }
284
285 void AudioTrackRecorder::OnSetFormat(const media::AudioParameters& params) {
286 DCHECK(params.IsValid());
287 DCHECK_EQ(params.bits_per_sample(), 16);
288
289 if (audio_params_.Equals(params))
290 return;
291
292 // TODO(ajose): consider only storing params in ATR _or_ encoder, not both.
mcasas 2015/10/19 20:02:08 I was thinking the same, and by preference I'd say
ajose 2015/10/20 03:21:11 Done.
293 audio_params_ = params;
294 encoder_->OnSetFormat(params);
295 }
296
297 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698