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

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: trybots 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;
miu 2015/10/21 20:35:38 Consider using enums for these constants rather th
ajose 2015/10/23 22:46:33 Done.
18 // This is the recommended value, according to documentation in
19 // third_party/opus/src/include/opus.h, so that the Opus encoder does not
20 // degrade the audio due to memory constraints.
21 //
22 // Note: Whereas other RTP implementations do not, the cast library is
miu 2015/10/21 20:35:38 This comment seems....out of place? ;-)
ajose 2015/10/23 22:46:33 Whoops...
23 // perfectly capable of transporting larger than MTU-sized audio frames.
24 static const int kOpusMaxPayloadSize = 4000;
25
26 // TODO(ajose): Removed code that might be useful for ensuring A/V sync.
27 // See cast/sender/AudioEncoder.
28
29 } // anonymous namespace
30
31 class AudioTrackRecorder::AudioEncoder
miu 2015/10/21 20:35:38 General statement about the entire AudioEncoder cl
ajose 2015/10/23 22:46:33 Done.
32 : public base::RefCountedThreadSafe<AudioEncoder> {
33 public:
34 AudioEncoder(const OnEncodedAudioCB& on_encoded_audio_cb)
miu 2015/10/21 20:35:38 Need explicit keyword here.
ajose 2015/10/23 22:46:33 Done.
35 : on_encoded_audio_cb_(on_encoded_audio_cb),
mcasas 2015/10/21 19:19:51 Don't inline complex ctor.
miu 2015/10/21 20:35:38 It doesn't matter for classes private to .cc modul
ajose 2015/10/23 22:46:33 Acknowledged.
ajose 2015/10/23 22:46:33 Done.
36 main_task_runner_(base::MessageLoop::current()->task_runner()) {}
37
38 void OnSetFormat(const media::AudioParameters& params) { InitOpus(params); }
39
40 void EncodeAudio(const media::AudioBus& audio_bus,
41 const base::TimeTicks& recorded_time);
42
43 bool IsInitialized() { return audio_params_.get() != nullptr; }
44
45 private:
46 friend class base::RefCountedThreadSafe<AudioEncoder>;
47
48 ~AudioEncoder();
49
50 void InitOpus(const media::AudioParameters& params);
51
52 void TransferSamplesIntoBuffer(const media::AudioBus& audio_bus,
53 int source_offset,
54 int buffer_fill_offset,
55 int num_samples);
56 bool EncodeFromFilledBuffer(std::string* out);
57
58 static bool IsValidFrameDuration(base::TimeDelta duration);
59
60 int samples_per_frame_;
61 const OnEncodedAudioCB on_encoded_audio_cb_;
62
63 // In the case where a call to EncodeAudio() cannot completely fill the
64 // buffer, this points to the position at which to populate data in a later
65 // call.
66 int buffer_fill_end_;
67
68 // Used to shutdown properly on the same thread we were created on.
69 const scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
mcasas 2015/10/21 19:19:52 I think this fellow is not used now, perhaps you w
ajose 2015/10/23 22:46:33 ThreadChecker already does something similar to th
70
71 // Task runner where frames to encode and reply callbacks must happen.
72 scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_;
73
74 scoped_ptr<media::AudioParameters> audio_params_;
miu 2015/10/21 20:35:38 No need for separate heap allocation here. Just:
ajose 2015/10/23 22:46:33 Done.
75
76 // Buffer for passing AudioBus data to OpusEncoder.
77 scoped_ptr<float[]> buffer_;
78
79 OpusEncoder* opus_encoder_;
mcasas 2015/10/21 19:19:52 Hmm this smells like a base::WeakPtr<>. However, t
ajose 2015/10/23 22:46:33 Think this is resolved with new structure - AudioE
80
81 DISALLOW_COPY_AND_ASSIGN(AudioEncoder);
82 };
83
84 AudioTrackRecorder::AudioEncoder::~AudioEncoder() {
85 LOG(INFO) << "AudioEncoder dtor";
86 if (opus_encoder_) {
87 LOG(INFO) << "destroying opus";
88 opus_encoder_destroy(opus_encoder_);
89 opus_encoder_ = nullptr;
90 }
91 }
92
93 void AudioTrackRecorder::AudioEncoder::EncodeAudio(
94 const media::AudioBus& audio_bus,
95 const base::TimeTicks& recorded_time) {
96 DCHECK(IsInitialized());
97 DCHECK(!recorded_time.is_null());
98 DCHECK_EQ(audio_bus.channels(), audio_params_->channels());
99 DCHECK_EQ(audio_bus.frames(), audio_params_->frames_per_buffer());
100
101 if (!origin_task_runner_.get())
102 origin_task_runner_ = base::MessageLoop::current()->task_runner();
miu 2015/10/21 20:35:38 This doesn't seem safe to me. Usually encoding sh
ajose 2015/10/23 22:46:33 Done.
103 DCHECK(origin_task_runner_->BelongsToCurrentThread());
104
105 // Encode all audio in |audio_bus| into zero or more frames.
106 int src_pos = 0;
107 while (src_pos < audio_bus.frames()) {
108 const int num_samples_to_xfer = std::min(
109 samples_per_frame_ - buffer_fill_end_, audio_bus.frames() - src_pos);
110 TransferSamplesIntoBuffer(audio_bus, src_pos, buffer_fill_end_,
111 num_samples_to_xfer);
112 src_pos += num_samples_to_xfer;
113 buffer_fill_end_ += num_samples_to_xfer;
114
115 if (buffer_fill_end_ < samples_per_frame_)
116 break;
117
118 scoped_ptr<std::string> encoded_data(new std::string());
119 if (EncodeFromFilledBuffer(encoded_data.get())) {
120 origin_task_runner_->PostTask(
121 FROM_HERE, base::Bind(on_encoded_audio_cb_, *audio_params_,
122 base::Passed(&encoded_data), recorded_time));
123 }
124
125 // Reset the internal buffer for the next frame.
126 buffer_fill_end_ = 0;
127 }
128 }
129
130 void AudioTrackRecorder::AudioEncoder::InitOpus(
131 const media::AudioParameters& params) {
mcasas 2015/10/21 19:19:52 Thread check plz, here and elsewhere.
ajose 2015/10/23 22:46:33 Done.
132 DCHECK(params.IsValid());
133 DCHECK_EQ(params.bits_per_sample(), 16);
134 if (audio_params_.get() && audio_params_->Equals(params))
mcasas 2015/10/21 19:19:52 How can |audio_params_.get()| be nullptr if it's a
135 return;
136
137 const int sampling_rate = params.sample_rate();
138 const int num_channels = params.channels();
139 samples_per_frame_ = sampling_rate / kDefaultFramesPerSecond;
140 const base::TimeDelta frame_duration = base::TimeDelta::FromMicroseconds(
141 base::Time::kMicrosecondsPerSecond * samples_per_frame_ / sampling_rate);
142
143 if (frame_duration == base::TimeDelta() ||
144 !IsValidFrameDuration(frame_duration)) {
145 DVLOG(1) << __FUNCTION__ << ": bad frame duration: " << frame_duration;
146 }
147
148 // Support for max sampling rate of 48KHz, 2 channels, 100 ms duration.
149 const int kMaxSamplesTimesChannelsPerFrame = 48 * 2 * 100;
150 if (num_channels <= 0 || samples_per_frame_ <= 0 ||
151 samples_per_frame_ * num_channels > kMaxSamplesTimesChannelsPerFrame ||
152 sampling_rate % samples_per_frame_ != 0) {
mcasas 2015/10/21 19:19:52 I see what you do here but is more customary to c
ajose 2015/10/23 22:46:33 Might have gone overboard...
153 DVLOG(1) << __FUNCTION__ << ": bad inputs:"
154 << "\nnum channels: " << num_channels
155 << "\nsamples per frame: " << samples_per_frame_
156 << "\nsampling rate: " << sampling_rate;
157 return;
158 }
159
160 // Initialize AudioBus buffer for OpusEncoder.
161 buffer_fill_end_ = 0;
162 buffer_.reset(new float[num_channels * samples_per_frame_]);
163
164 // Initialize OpusEncoder.
165 int opus_result;
166 opus_encoder_ = opus_encoder_create(sampling_rate, num_channels,
miu 2015/10/21 20:35:38 Can this InitOpus() method be called multiple time
ajose 2015/10/23 22:46:33 Done.
167 OPUS_APPLICATION_AUDIO, &opus_result);
168 if (opus_result < 0) {
169 DVLOG(1) << __FUNCTION__ << ": couldn't initialize opus encoder: "
mcasas 2015/10/21 19:19:52 There's no real need for __FUNCTION__ here, and I'
ajose 2015/10/23 22:46:33 Done.
170 << opus_strerror(opus_result);
171 return;
172 }
173
174 audio_params_.reset(new media::AudioParameters(params));
175
176 // Note: As of 2013-10-31, the encoder in "auto bitrate" mode would use a
177 // variable bitrate up to 102kbps for 2-channel, 48 kHz audio and a 10 ms
178 // frame size. The opus library authors may, of course, adjust this in
179 // later versions.
mcasas 2015/10/21 19:19:52 Could we codify this assumption? int bitrate;
180 CHECK_EQ(opus_encoder_ctl(opus_encoder_, OPUS_SET_BITRATE(OPUS_AUTO)),
mcasas 2015/10/21 19:19:51 Here you say: crash the renderer tab if opus_encod
ajose 2015/10/23 22:46:33 Done.
181 OPUS_OK);
182 }
183
184 void AudioTrackRecorder::AudioEncoder::TransferSamplesIntoBuffer(
185 // const media::AudioBus* audio_bus,
186 const media::AudioBus& audio_bus,
187 int source_offset,
188 int buffer_fill_offset,
189 int num_samples) {
190 // Opus requires channel-interleaved samples in a single array.
191 for (int ch = 0; ch < audio_bus.channels(); ++ch) {
192 const float* src = audio_bus.channel(ch) + source_offset;
193 const float* const src_end = src + num_samples;
194 float* dest =
195 buffer_.get() + buffer_fill_offset * audio_params_->channels() + ch;
196 for (; src < src_end; ++src, dest += audio_params_->channels())
197 *dest = *src;
198 }
199 }
200
201 bool AudioTrackRecorder::AudioEncoder::EncodeFromFilledBuffer(
202 std::string* out) {
203 out->resize(kOpusMaxPayloadSize);
204 const opus_int32 result = opus_encode_float(
205 opus_encoder_, buffer_.get(), samples_per_frame_,
206 reinterpret_cast<uint8*>(string_as_array(out)), kOpusMaxPayloadSize);
mcasas 2015/10/21 19:19:52 What about s/reinterpret_cast<uint8*>(string_as_a
ajose 2015/10/23 22:46:33 out->data() is const char*, need unsigned char* bu
207 if (result > 1) {
208 out->resize(result);
209 return true;
210 } else if (result < 0) {
211 LOG(ERROR) << __FUNCTION__ << ": Error code from opus_encode_float(): "
212 << opus_strerror(result);
213 return false;
214 } else {
215 // Do nothing: The documentation says that a return value of zero or
216 // one means the packet does not need to be transmitted.
217 return false;
218 }
mcasas 2015/10/21 19:19:52 What about: if (result > 1) { out->resize(resul
ajose 2015/10/23 22:46:33 Done.
219 }
220
221 // static
222 bool AudioTrackRecorder::AudioEncoder::IsValidFrameDuration(
223 base::TimeDelta duration) {
224 // See https://tools.ietf.org/html/rfc6716#section-2.1.4
mcasas 2015/10/21 19:19:52 Maybe also mention opus.h? [1] [1] https://code.g
225 return duration == base::TimeDelta::FromMicroseconds(2500) ||
226 duration == base::TimeDelta::FromMilliseconds(5) ||
227 duration == base::TimeDelta::FromMilliseconds(10) ||
228 duration == base::TimeDelta::FromMilliseconds(20) ||
229 duration == base::TimeDelta::FromMilliseconds(40) ||
230 duration == base::TimeDelta::FromMilliseconds(60);
mcasas 2015/10/21 19:19:52 It sucks that we have to check for this duration o
ajose 2015/10/23 22:46:33 Not that I can find :(
231 }
232
233 AudioTrackRecorder::AudioTrackRecorder(
234 const blink::WebMediaStreamTrack& track,
235 const OnEncodedAudioCB& on_encoded_audio_cb)
236 : track_(track),
237 encoder_(new AudioEncoder(on_encoded_audio_cb)),
miu 2015/10/21 20:35:38 Note: If AudioEncoder was only used on a separate
ajose 2015/10/23 22:46:33 Nice!
238 on_encoded_audio_cb_(on_encoded_audio_cb) {
239 DCHECK(main_render_thread_checker_.CalledOnValidThread());
240 DCHECK(!track_.isNull());
241 DCHECK(track_.extraData());
242 // Connect the source provider to the track as a sink.
243 MediaStreamAudioSink::AddToAudioTrack(this, track_);
244 }
245
246 AudioTrackRecorder::~AudioTrackRecorder() {
247 DCHECK(main_render_thread_checker_.CalledOnValidThread());
248 MediaStreamAudioSink::RemoveFromAudioTrack(this, track_);
249 track_.reset();
250 LOG(INFO) << "ATR dtor";
251 }
252
253 void AudioTrackRecorder::OnData(const media::AudioBus& audio_bus,
254 base::TimeTicks estimated_capture_time) {
255 DCHECK(capture_thread_checker_.CalledOnValidThread());
256 encoder_->EncodeAudio(audio_bus, estimated_capture_time);
miu 2015/10/21 20:35:38 Is this method being called on a real-time audio t
ajose 2015/10/23 22:46:33 Done.
257 }
258
259 void AudioTrackRecorder::OnSetFormat(const media::AudioParameters& params) {
260 // If the source is restarted, might have changed to another capture thread.
261 capture_thread_checker_.DetachFromThread();
262 DCHECK(capture_thread_checker_.CalledOnValidThread());
263 encoder_->OnSetFormat(params);
264 }
265
266 } // namespace content
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698