Chromium Code Reviews| 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..3253584ef63ffa1643dd7362a9f991c187b3fdc0 |
| --- /dev/null |
| +++ b/remoting/codec/audio_encoder_opus.cc |
| @@ -0,0 +1,240 @@ |
| +// 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/opus.h" |
| + |
| +namespace remoting { |
| + |
| +namespace { |
| + |
| +// Output 160 kb/s bitrate. |
| +const int kOutputBitrateBps = 160 * 1024; |
| + |
| +// 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 = 20; |
| + |
| +// 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); |
| + if (!encoder_) { |
| + LOG(ERROR) << "Failed to create OPUS encoder. Error code: " << error; |
| + return; |
| + } |
| + |
| + opus_encoder_ctl(encoder_, OPUS_SET_BITRATE(kOutputBitrateBps)); |
| + |
| + 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/22 22:50:21
This appears in codereview still over-indented; no
Sergey Ulanov
2012/10/23 00:43:50
Looks correctly to me both in rietveld and in my e
|
| + channels_, |
| + static_cast<double>(sampling_rate_) / kOpusSamplingRate, |
| + base::Bind(&AudioEncoderOpus::FetchBytesToResampler, |
| + base::Unretained(this)))); |
| + resampler_bus_ = media::AudioBus::Create(channels_, kFrameSamples); |
| + } |
| + |
| + // Drop leftover data because it's for different sampling rate. |
| + leftover_samples_ = 0; |
| + leftover_buffer_size_ = |
| + frame_size_ + media::SincResampler::kMaximumLookAheadSize; |
| + leftover_buffer_.reset( |
| + new int16[leftover_buffer_size_ * channels_]); |
| +} |
| + |
| +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(); |
| + } |
| + |
| + return encoder_ != NULL; |
| +} |
| + |
| +void AudioEncoderOpus::FetchBytesToResampler(media::AudioBus* audio_bus) { |
|
Wez
2012/10/22 22:50:21
nit: FetchBytesToResample or PumpBytesToResampler
Sergey Ulanov
2012/10/23 00:43:50
Done.
|
| + DCHECK(resampling_data_); |
| + int samples_left = (resampling_data_size_ - resampling_data_pos_) / |
| + kBytesPerSample / channels_; |
| + DCHECK_LE(audio_bus->frames(), samples_left); |
| + 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_in_packet = packet->data(0).size() / kBytesPerSample / channels_; |
| + 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 prefetch_samples = |
| + resampler_.get() ? media::SincResampler::kMaximumLookAheadSize : 0; |
| + int samples_wanted = frame_size_ + prefetch_samples; |
| + |
| + while (leftover_samples_ + samples_in_packet >= samples_wanted) { |
| + const int16* pcm_buffer = NULL; |
| + |
| + // Combine the packet with the leftover samples, if any. |
| + 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; |
| + } |
| + |
| + // Resample data if necessary. |
| + 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; |
| + resampler_->Resample(resampler_bus_.get(), kFrameSamples); |
| + resampling_data_ = NULL; |
| + samples_consumed = resampling_data_pos_ / channels_ / kBytesPerSample; |
| + |
| + resampler_bus_->ToInterleaved(kFrameSamples, kBytesPerSample, |
| + resample_buffer_.get()); |
| + pcm_buffer = reinterpret_cast<int16*>(resample_buffer_.get()); |
| + } else { |
| + samples_consumed = frame_size_; |
| + } |
| + |
| + // Initialize output buffer. |
| + std::string* data = encoded_packet->add_data(); |
| + data->resize(kFrameSamples * kBytesPerSample * channels_); |
| + |
| + // Encode. |
| + 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>(); |
| + } |
| + |
| + DCHECK_LE(result, static_cast<int>(data->length())); |
| + data->resize(result); |
| + |
| + // Cleanup leftover buffer. |
| + if (samples_consumed >= leftover_samples_) { |
| + samples_consumed -= leftover_samples_; |
| + leftover_samples_ = 0; |
| + next_sample += samples_consumed * channels_; |
| + samples_in_packet -= 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_in_packet > 0) { |
| + DCHECK_LE(leftover_samples_ + samples_in_packet, leftover_buffer_size_); |
| + memmove(leftover_buffer_.get() + leftover_samples_ * channels_, |
| + next_sample, samples_in_packet * kBytesPerSample * channels_); |
| + leftover_samples_ += samples_in_packet; |
| + } |
| + |
| + // 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 |