Index: remoting/codec/audio_encoder_opus.cc |
diff --git a/remoting/codec/audio_encoder_opus.cc b/remoting/codec/audio_encoder_opus.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..96440d67b7289568cf6bddfbcd3b4b6d83bffa42 |
--- /dev/null |
+++ b/remoting/codec/audio_encoder_opus.cc |
@@ -0,0 +1,238 @@ |
+// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "remoting/codec/audio_encoder_opus.h" |
+ |
+#include "base/bind.h" |
+#include "base/logging.h" |
+#include "base/time.h" |
+#include "media/base/audio_bus.h" |
+#include "media/base/multi_channel_resampler.h" |
+#include "third_party/opus/src/include/opus.h" |
+ |
+namespace remoting { |
+ |
+namespace { |
+ |
+// Use 160 kb/s bitrate. |
Wez
2012/10/19 01:51:32
nit: When you say "use", do you mean "output"?
Wez
2012/10/19 01:51:32
You mean kilobits/second?
Sergey Ulanov
2012/10/19 20:54:30
Yes, It would be kB/s for kilobytes :)
Sergey Ulanov
2012/10/19 20:54:30
Done.
|
+const int kBitrateBps = 160 * 1024; |
Wez
2012/10/19 01:51:32
nit: kOutputBps or kOutputBitRateBps
Sergey Ulanov
2012/10/19 20:54:30
Done.
|
+ |
+// Encoded buffer size. |
+const int kFrameDefaultBufferSize = 4096; |
+ |
+// Maximum buffer size we'll allocate when encoding before giving up. |
+const int kMaxBufferSize = 65536; |
+ |
+// Opus doesn't support 44100 sampling rate so we always resample to 48kHz. |
+const AudioPacket::SamplingRate kOpusSamplingRate = |
+ AudioPacket::SAMPLING_RATE_48000; |
+ |
+// Opus supports frame sizes of 2.5, 5, 10, 20, 40 and 60 ms. We use 20 ms |
+// frames to balance latency and efficiency. |
+const int kFrameSizeMs = 60; |
+ |
+// Number of samples per frame when using default sampling rate. |
+const int kFrameSamples = |
+ kOpusSamplingRate * kFrameSizeMs / base::Time::kMillisecondsPerSecond; |
+ |
+const AudioPacket::BytesPerSample kBytesPerSample = |
+ AudioPacket::BYTES_PER_SAMPLE_2; |
+ |
+bool IsSupportedSampleRate(int rate) { |
+ return rate == 44100 || rate == 48000; |
+} |
+ |
+} // namespace |
+ |
+AudioEncoderOpus::AudioEncoderOpus() |
+ : sampling_rate_(0), |
+ channels_(AudioPacket::CHANNELS_STEREO), |
+ encoder_(NULL), |
+ frame_size_(0), |
+ resampling_data_(NULL), |
+ resampling_data_size_(0), |
+ resampling_data_pos_(0) { |
+} |
+ |
+AudioEncoderOpus::~AudioEncoderOpus() { |
+ DestroyEncoder(); |
+} |
+ |
+void AudioEncoderOpus::InitEncoder() { |
+ DCHECK(!encoder_); |
+ int error; |
+ encoder_ = opus_encoder_create(kOpusSamplingRate, channels_, |
+ OPUS_APPLICATION_AUDIO, &error); |
+ CHECK(!error); |
Wez
2012/10/19 01:51:32
Don't CHECK on error; just cope w/ |encoder_| bein
Sergey Ulanov
2012/10/19 20:54:30
Done.
|
+ CHECK(encoder_); |
+ |
+ opus_encoder_ctl(encoder_, OPUS_SET_BITRATE(kBitrateBps)); |
+ |
+ frame_size_ = sampling_rate_ * kFrameSizeMs / |
+ base::Time::kMillisecondsPerSecond; |
+ |
+ if (sampling_rate_ != kOpusSamplingRate) { |
+ resample_buffer_.reset( |
+ new char[kFrameSamples * kBytesPerSample * channels_]); |
+ resampler_.reset(new media::MultiChannelResampler( |
Wez
2012/10/19 01:51:32
nit: Indentation.
Sergey Ulanov
2012/10/19 20:54:30
Done.
|
+ channels_, |
+ static_cast<double>(sampling_rate_) / kOpusSamplingRate, |
+ base::Bind(&AudioEncoderOpus::ResamplerRead, |
+ base::Unretained(this)))); |
+ } |
+ |
+ // Drop leftover data because it's for different sampling rate. |
+ leftover_samples_ = 0; |
+ leftover_buffer_size_ = frame_size_ + media::SincResampler::kLookAheadSize; |
+ leftover_buffer_.reset( |
+ new int16[leftover_buffer_size_ * channels_]); |
+ |
Wez
2012/10/19 01:51:32
nit: Lose blank line.
Sergey Ulanov
2012/10/19 20:54:30
Done.
|
+} |
+ |
+void AudioEncoderOpus::DestroyEncoder() { |
+ if (encoder_) { |
+ opus_encoder_destroy(encoder_); |
+ encoder_ = NULL; |
+ } |
+ |
+ resampler_.reset(); |
+} |
+ |
+bool AudioEncoderOpus::ResetForPacket(AudioPacket* packet) { |
+ if (packet->channels() != channels_ || |
+ packet->sampling_rate() != sampling_rate_) { |
+ DestroyEncoder(); |
+ |
+ channels_ = packet->channels(); |
+ sampling_rate_ = packet->sampling_rate(); |
+ |
+ if (channels_ <= 0 || channels_ > 2 || |
+ !IsSupportedSampleRate(sampling_rate_)) { |
+ LOG(WARNING) << "Unsupported OPUS parameters: " |
+ << channels_ << " channels with " |
+ << sampling_rate_ << " samples per second."; |
+ return false; |
+ } |
+ |
+ InitEncoder(); |
+ DCHECK(encoder_); |
Wez
2012/10/19 01:51:32
nit: Remove DCHECK
Sergey Ulanov
2012/10/19 20:54:30
Done.
|
+ } |
+ |
+ return encoder_ != NULL; |
+} |
+ |
+void AudioEncoderOpus::ResamplerRead(media::AudioBus* audio_bus) { |
Wez
2012/10/19 01:51:32
This needs a better name, e.g. SupplyResampler / F
Sergey Ulanov
2012/10/19 20:54:30
Done.
|
+ CHECK(resampling_data_); |
Wez
2012/10/19 01:51:32
nit: DCHECK, and add a line after the check
Sergey Ulanov
2012/10/19 20:54:30
Done.
|
+ int samples_left = (resampling_data_size_ - resampling_data_pos_) / |
+ kBytesPerSample / channels_; |
+ CHECK_LE(audio_bus->frames(), samples_left); |
Wez
2012/10/19 01:51:32
nit: DCHECK_LE and add a line after the check
Sergey Ulanov
2012/10/19 20:54:30
Done.
|
+ audio_bus->FromInterleaved( |
+ resampling_data_ + resampling_data_pos_, |
+ audio_bus->frames(), kBytesPerSample); |
+ resampling_data_pos_ += audio_bus->frames() * kBytesPerSample * channels_; |
+ DCHECK_LE(resampling_data_pos_, static_cast<int>(resampling_data_size_)); |
+} |
+ |
+scoped_ptr<AudioPacket> AudioEncoderOpus::Encode( |
+ scoped_ptr<AudioPacket> packet) { |
+ DCHECK_EQ(AudioPacket::ENCODING_RAW, packet->encoding()); |
+ DCHECK_EQ(1, packet->data_size()); |
+ DCHECK_EQ(kBytesPerSample, packet->bytes_per_sample()); |
+ |
+ if (!ResetForPacket(packet.get())) { |
+ LOG(ERROR) << "Encoder initialization failed"; |
+ return scoped_ptr<AudioPacket>(); |
+ } |
+ |
+ int samples_left = packet->data(0).size() / kBytesPerSample / channels_; |
Wez
2012/10/19 01:51:32
nit: samples_in_packet?
Sergey Ulanov
2012/10/19 20:54:30
Done.
|
+ const int16* next_sample = |
+ reinterpret_cast<const int16*>(packet->data(0).data()); |
+ |
+ // Create a new packet of encoded data. |
+ scoped_ptr<AudioPacket> encoded_packet(new AudioPacket()); |
+ encoded_packet->set_encoding(AudioPacket::ENCODING_OPUS); |
+ encoded_packet->set_sampling_rate(kOpusSamplingRate); |
+ encoded_packet->set_channels(channels_); |
+ |
+ int preferch_samples = |
Wez
2012/10/19 01:51:32
typo: prefetch_samples
Sergey Ulanov
2012/10/19 20:54:30
Done.
|
+ resampler_.get() ? media::SincResampler::kLookAheadSize : 0; |
+ int samples_wanted = frame_size_ + preferch_samples; |
+ |
+ while (leftover_samples_ + samples_left >= samples_wanted) { |
Wez
2012/10/19 01:51:32
This loop could use a liberal sprinkling of concis
Sergey Ulanov
2012/10/19 20:54:30
Done.
|
+ const int16* pcm_buffer = NULL; |
+ |
+ if (leftover_samples_ > 0) { |
+ pcm_buffer = leftover_buffer_.get(); |
+ int samples_to_copy = samples_wanted - leftover_samples_; |
+ memcpy(leftover_buffer_.get() + leftover_samples_ * channels_, |
+ next_sample, samples_to_copy * kBytesPerSample * channels_); |
+ } else { |
+ pcm_buffer = next_sample; |
+ } |
+ |
+ int samples_consumed = 0; |
+ if (resampler_.get()) { |
+ resampling_data_ = reinterpret_cast<const char*>(pcm_buffer); |
+ resampling_data_pos_ = 0; |
+ resampling_data_size_ = samples_wanted * channels_ * kBytesPerSample; |
+ |
+ scoped_ptr<media::AudioBus> bus = |
+ media::AudioBus::Create(channels_, kFrameSamples); |
Wez
2012/10/19 01:51:32
Do we really want to create a new AudioBus for eac
Sergey Ulanov
2012/10/19 20:54:30
Done.
|
+ |
+ resampler_->Resample(bus.get(), kFrameSamples); |
+ resampling_data_ = NULL; |
+ samples_consumed = resampling_data_pos_ / channels_ / kBytesPerSample; |
+ |
+ bus->ToInterleaved(kFrameSamples, kBytesPerSample, |
+ resample_buffer_.get()); |
+ pcm_buffer = reinterpret_cast<int16*>(resample_buffer_.get()); |
+ } else { |
+ samples_consumed = frame_size_; |
+ } |
+ |
+ std::string* data = encoded_packet->add_data(); |
+ |
+ data->resize(kFrameSamples * kBytesPerSample * channels_); |
+ |
+ unsigned char* buffer = |
+ reinterpret_cast<unsigned char*>(string_as_array(data)); |
+ int result = opus_encode(encoder_, pcm_buffer, kFrameSamples, |
+ buffer, data->length()); |
+ if (result < 0) { |
+ LOG(ERROR) << "opus_encode() failed with error code: " << result; |
+ return scoped_ptr<AudioPacket>(); |
+ } |
+ |
+ CHECK_LE(result, static_cast<int>(data->length())); |
Wez
2012/10/19 01:51:32
DCHECK_LE?
Sergey Ulanov
2012/10/19 20:54:30
Done.
|
+ data->resize(result); |
+ |
+ if (samples_consumed >= leftover_samples_) { |
+ samples_consumed -= leftover_samples_; |
+ leftover_samples_ = 0; |
+ next_sample += samples_consumed * channels_; |
+ samples_left -= samples_consumed; |
+ } else { |
+ leftover_samples_ -= samples_consumed; |
+ memmove(leftover_buffer_.get(), |
+ leftover_buffer_.get() + samples_consumed * channels_, |
+ leftover_samples_ * channels_ * kBytesPerSample); |
+ } |
+ } |
+ |
+ // Store the leftover samples. |
+ if (samples_left > 0) { |
+ CHECK_LE(leftover_samples_ + samples_left, leftover_buffer_size_); |
+ memmove(leftover_buffer_.get() + leftover_samples_ * channels_, |
+ next_sample, samples_left * kBytesPerSample * channels_); |
+ leftover_samples_ += samples_left; |
+ } |
+ |
+ // Return NULL if there's nothing in the packet. |
+ if (encoded_packet->data_size() == 0) |
+ return scoped_ptr<AudioPacket>(); |
+ |
+ return encoded_packet.Pass(); |
+} |
+ |
+} // namespace remoting |