Stream opus instead of raw pcm16
Bug: 120082384
Test: stream youtube on pie
Change-Id: I7b166722b75b9ee95d4ab77d9eb0d4710fd46391
diff --git a/host/frontend/stream_audio/Android.bp b/host/frontend/stream_audio/Android.bp
index ba9388e..ab5ff8e 100644
--- a/host/frontend/stream_audio/Android.bp
+++ b/host/frontend/stream_audio/Android.bp
@@ -23,12 +23,14 @@
"libbase",
"libcuttlefish_utils",
"libcuttlefish_fs",
+ "libopus",
"vsoc_lib",
],
static_libs: [
"libcuttlefish_host_config",
"libjsoncpp",
"libgflags",
+ "libopuscpp",
],
cpp_std: "c++17",
defaults: ["cuttlefish_host_only"],
diff --git a/host/frontend/stream_audio/main.cpp b/host/frontend/stream_audio/main.cpp
index 53621f2..f63e315 100644
--- a/host/frontend/stream_audio/main.cpp
+++ b/host/frontend/stream_audio/main.cpp
@@ -15,20 +15,20 @@
* limitations under the License.
*/
-// TODO(b/120082384) I plan on changing this to use something better than pcm
-// this is just to get something minimal running.
-//
// For each client that connects initially a header is sent with the following,
// in this order, all as uint16_t in network-byte-order:
-// sample_width, number of channels, frame rate
+// number of channels, frame rate
//
// Following, audio packets are sent as a uint32_t length (network byte order)
+// indicating the number of bytes
+// followed by the (opus) frame_size as a uint32_t
// followed by <length> bytes.
#include "common/libs/tcp_socket/tcp_socket.h"
#include "common/vsoc/lib/audio_data_region_view.h"
#include "common/vsoc/lib/circqueue_impl.h"
#include "common/vsoc/lib/vsoc_audio_message.h"
+#include "host/frontend/stream_audio/opuscpp/opus_wrapper.h"
#include "host/libs/config/cuttlefish_config.h"
#include <android-base/logging.h>
@@ -60,11 +60,25 @@
buffer_cv_.wait(guard);
}
- const size_t num_channels = header_.frame_size / sizeof(int16_t);
- return cvd::CreateMessage(
- static_cast<std::uint16_t>(header_.frame_size / num_channels),
- static_cast<std::uint16_t>(num_channels),
- static_cast<std::uint16_t>(header_.frame_rate));
+ const size_t num_channels = header_.frame_size / sizeof(opus_int16);
+ return cvd::CreateMessage(static_cast<std::uint16_t>(num_channels),
+ static_cast<std::uint16_t>(header_.frame_rate));
+ }
+
+ std::uint32_t frame_rate() const {
+ std::unique_lock guard(buffer_lock_);
+ while (!audio_buffer_) {
+ buffer_cv_.wait(guard);
+ }
+ return header_.frame_rate;
+ }
+
+ std::uint32_t num_channels() const {
+ std::unique_lock guard(buffer_lock_);
+ while (!audio_buffer_) {
+ buffer_cv_.wait(guard);
+ }
+ return header_.frame_size / sizeof(opus_int16);
}
// Returns the frame id and audio frame
@@ -188,18 +202,35 @@
void HandleClient(AudioStreamer* audio_streamer,
cvd::ClientSocket client_socket) {
+ auto num_channels = audio_streamer->num_channels();
+ opus::Encoder enc(audio_streamer->frame_rate(),
+ audio_streamer->num_channels(), OPUS_APPLICATION_AUDIO);
+ CHECK(enc.valid()) << "Could not construct Encoder. Maybe bad frame_rate ("
+ << audio_streamer->frame_rate() <<") or num_channels ("
+ << audio_streamer->num_channels() << ")?";
+
auto header = audio_streamer->MakeAudioDescriptionHeader();
client_socket.SendNoSignal(header);
std::int64_t previous_frame_num = 0;
while (!client_socket.closed()) {
+ CHECK(enc.valid()) << "encoder in invalid state";
auto [frame_num, audio_data] =
audio_streamer->audio_buffer(previous_frame_num);
- auto length_message =
- cvd::CreateMessage(static_cast<std::uint32_t>(audio_data->size()));
- client_socket.SendNoSignal(length_message);
- client_socket.SendNoSignal(*audio_data);
previous_frame_num = frame_num;
+
+ std::vector<opus_int16> pcm(audio_data->size() / sizeof(opus_int16));
+ std::memcpy(pcm.data(), audio_data->data(), audio_data->size());
+ // in opus terms "frame_size" is the number of unencoded samples per frame
+ const std::uint32_t frame_size = pcm.size() / num_channels;
+ auto encoded = enc.Encode(pcm, frame_size);
+ for (auto&& p : encoded) {
+ auto length_message =
+ cvd::CreateMessage(static_cast<std::uint32_t>(p.size()));
+ client_socket.SendNoSignal(length_message);
+ client_socket.SendNoSignal(cvd::CreateMessage(frame_size));
+ client_socket.SendNoSignal(p);
+ }
}
}
diff --git a/host/frontend/stream_audio/opuscpp/Android.bp b/host/frontend/stream_audio/opuscpp/Android.bp
new file mode 100644
index 0000000..e0b97ac
--- /dev/null
+++ b/host/frontend/stream_audio/opuscpp/Android.bp
@@ -0,0 +1,27 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_library_host_static {
+ name: "libopuscpp",
+ srcs: [
+ "opus_wrapper.cc",
+ ],
+ shared_libs: [
+ "libbase",
+ "libopus",
+ ],
+ cpp_std: "c++17",
+ defaults: ["cuttlefish_host_only"],
+}
diff --git a/host/frontend/stream_audio/opuscpp/opus_wrapper.cc b/host/frontend/stream_audio/opuscpp/opus_wrapper.cc
new file mode 100644
index 0000000..538bee8
--- /dev/null
+++ b/host/frontend/stream_audio/opuscpp/opus_wrapper.cc
@@ -0,0 +1,177 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// https://github.com/google/opuscpp
+
+#include <iterator>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <android-base/logging.h>
+#include "host/frontend/stream_audio/opuscpp/opus_wrapper.h"
+
+std::string opus::ErrorToString(int error) {
+ switch (error) {
+ case OPUS_OK:
+ return "OK";
+ case OPUS_BAD_ARG:
+ return "One or more invalid/out of range arguments.";
+ case OPUS_BUFFER_TOO_SMALL:
+ return "The mode struct passed is invalid.";
+ case OPUS_INTERNAL_ERROR:
+ return "An internal error was detected.";
+ case OPUS_INVALID_PACKET:
+ return "The compressed data passed is corrupted.";
+ case OPUS_UNIMPLEMENTED:
+ return "Invalid/unsupported request number.";
+ case OPUS_INVALID_STATE:
+ return "An encoder or decoder structure is invalid or already freed.";
+ default:
+ return "Unknown error code: " + std::to_string(error);
+ }
+}
+
+void opus::internal::OpusDestroyer::operator()(OpusEncoder* encoder) const
+ noexcept {
+ opus_encoder_destroy(encoder);
+}
+
+void opus::internal::OpusDestroyer::operator()(OpusDecoder* decoder) const
+ noexcept {
+ opus_decoder_destroy(decoder);
+}
+
+opus::Encoder::Encoder(opus_int32 sample_rate, int num_channels,
+ int application, int expected_loss_percent)
+ : num_channels_{num_channels} {
+ int error{};
+ encoder_.reset(
+ opus_encoder_create(sample_rate, num_channels, application, &error));
+ valid_ = error == OPUS_OK;
+ if (!valid()) {
+ LOG(INFO) << "Could not construct encoder. Error: " << ErrorToString(error);
+ return;
+ }
+ if (expected_loss_percent > 0) {
+ LOG(INFO) << "Enabling FEC in the encoder.";
+ Ctl(OPUS_SET_INBAND_FEC(1));
+ Ctl(OPUS_SET_PACKET_LOSS_PERC(expected_loss_percent));
+ }
+}
+
+bool opus::Encoder::ResetState() {
+ valid_ = Ctl(OPUS_RESET_STATE) == OPUS_OK;
+ return valid_;
+}
+
+bool opus::Encoder::SetBitrate(int bitrate) {
+ valid_ = Ctl(OPUS_SET_BITRATE(bitrate)) == OPUS_OK;
+ return valid_;
+}
+
+bool opus::Encoder::SetVariableBitrate(int vbr) {
+ valid_ = Ctl(OPUS_SET_VBR(vbr)) == OPUS_OK;
+ return valid_;
+}
+
+bool opus::Encoder::SetComplexity(int complexity) {
+ valid_ = Ctl(OPUS_SET_COMPLEXITY(complexity)) == OPUS_OK;
+ return valid_;
+}
+
+int opus::Encoder::GetLookahead() {
+ opus_int32 skip{};
+ valid_ = Ctl(OPUS_GET_LOOKAHEAD(&skip)) == OPUS_OK;
+ return skip;
+}
+
+std::vector<std::vector<unsigned char>> opus::Encoder::Encode(
+ const std::vector<opus_int16>& pcm, int frame_size) {
+ constexpr auto sample_size = sizeof(pcm[0]);
+ const auto frame_length = frame_size * num_channels_ * sample_size;
+ auto data_length = pcm.size() * sample_size;
+ if (data_length % frame_length != 0u) {
+ LOG(WARNING) << "PCM samples contain an incomplete frame. Ignoring the "
+ "incomplete frame.";
+ data_length -= (data_length % frame_length);
+ }
+
+ std::vector<std::vector<unsigned char>> encoded;
+ for (std::size_t i{}; i < data_length; i += frame_length) {
+ encoded.push_back(EncodeFrame(pcm.begin() + (i / sample_size), frame_size));
+ }
+ return encoded;
+}
+
+std::vector<unsigned char> opus::Encoder::EncodeFrame(
+ const std::vector<opus_int16>::const_iterator& frame_start,
+ int frame_size) {
+ const auto frame_length = (frame_size * num_channels_ * sizeof(*frame_start));
+ std::vector<unsigned char> encoded(frame_length);
+ auto num_bytes = opus_encode(encoder_.get(), &*frame_start, frame_size,
+ encoded.data(), encoded.size());
+ if (num_bytes < 0) {
+ LOG(ERROR) << "Encode error: " << opus::ErrorToString(num_bytes);
+ return {};
+ }
+ encoded.resize(num_bytes);
+ return encoded;
+}
+
+opus::Decoder::Decoder(opus_uint32 sample_rate, int num_channels)
+ : num_channels_(num_channels) {
+ int error{};
+ decoder_.reset(opus_decoder_create(sample_rate, num_channels, &error));
+ valid_ = error == OPUS_OK;
+}
+
+std::vector<opus_int16> opus::Decoder::Decode(
+ const std::vector<std::vector<unsigned char>>& packets, int frame_size,
+ bool decode_fec) {
+ std::vector<opus_int16> decoded;
+ for (const auto& enc : packets) {
+ auto just_decoded = Decode(enc, frame_size, decode_fec);
+ decoded.insert(std::end(decoded), std::begin(just_decoded),
+ std::end(just_decoded));
+ }
+ return decoded;
+}
+
+std::vector<opus_int16> opus::Decoder::Decode(
+ const std::vector<unsigned char>& packet, int frame_size, bool decode_fec) {
+ const auto frame_length = (frame_size * num_channels_ * sizeof(opus_int16));
+ std::vector<opus_int16> decoded(frame_length);
+ auto num_samples = opus_decode(decoder_.get(), packet.data(), packet.size(),
+ decoded.data(), frame_size, decode_fec);
+ if (num_samples < 0) {
+ LOG(ERROR) << "Decode error: " << opus::ErrorToString(num_samples);
+ return {};
+ }
+ decoded.resize(num_samples * num_channels_);
+ return decoded;
+}
+
+std::vector<opus_int16> opus::Decoder::DecodeDummy(int frame_size) {
+ const auto frame_length = (frame_size * num_channels_ * sizeof(opus_int16));
+ std::vector<opus_int16> decoded(frame_length);
+ auto num_samples =
+ opus_decode(decoder_.get(), nullptr, 0, decoded.data(), frame_size, true);
+ if (num_samples < 0) {
+ LOG(ERROR) << "Decode error: " << opus::ErrorToString(num_samples);
+ return {};
+ }
+ decoded.resize(num_samples * num_channels_);
+ return decoded;
+}
diff --git a/host/frontend/stream_audio/opuscpp/opus_wrapper.h b/host/frontend/stream_audio/opuscpp/opus_wrapper.h
new file mode 100644
index 0000000..07e932e
--- /dev/null
+++ b/host/frontend/stream_audio/opuscpp/opus_wrapper.h
@@ -0,0 +1,133 @@
+// Copyright 2018 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// https://github.com/google/opuscpp
+
+#ifndef OPUSCPP_OPUS_WRAPPER_H_
+#define OPUSCPP_OPUS_WRAPPER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "opus.h"
+
+namespace opus {
+
+std::string ErrorToString(int error);
+
+namespace internal {
+// Deleter for OpusEncoders and OpusDecoders
+struct OpusDestroyer {
+ void operator()(OpusEncoder* encoder) const noexcept;
+ void operator()(OpusDecoder* decoder) const noexcept;
+};
+template <typename T>
+using opus_uptr = std::unique_ptr<T, OpusDestroyer>;
+} // namespace internal
+
+class Encoder {
+ public:
+ // see documentation at:
+ // https://mf4.xiph.org/jenkins/view/opus/job/opus/ws/doc/html/group__opus__encoder.html#gaa89264fd93c9da70362a0c9b96b9ca88
+ // Fs corresponds to sample_rate
+ //
+ // If expected_loss_percent is positive, FEC will be enabled
+ Encoder(opus_int32 sample_rate, int num_channels, int application,
+ int expected_loss_percent = 0);
+
+ // Resets internal state of encoder. This should be called between encoding
+ // different streams so that back-to-back decoding and one-at-a-time decoding
+ // give the same result. Returns true on success.
+ bool ResetState();
+
+ // Sets the desired bitrate. Rates from 500 to 512000 are meaningful as well
+ // as the special values OPUS_AUTO and OPUS_BITRATE_MAX. If this method
+ // is not called, the default value of OPUS_AUTO is used.
+ // Returns true on success.
+ bool SetBitrate(int bitrate);
+
+ // Enables or disables variable bitrate in the encoder. By default, variable
+ // bitrate is enabled. Returns true on success.
+ bool SetVariableBitrate(int vbr);
+
+ // Sets the computational complexity of the encoder, in the range of 0 to 10,
+ // inclusive, with 10 being the highest complexity. Returns true on success.
+ bool SetComplexity(int complexity);
+
+ // Gets the total samples of delay added by the entire codec. This value
+ // is the minimum amount of 'preskip' that has to be specified in an
+ // ogg-stream that encapsulates the encoded audio.
+ int GetLookahead();
+
+ // Takes audio data and encodes it. Returns a sequence of encoded packets.
+ // pcm.size() must be divisible by frame_size * (number of channels);
+ // pcm must not contain any incomplete packets.
+ // see documentation for pcm and frame_size at:
+ // https://mf4.xiph.org/jenkins/view/opus/job/opus/ws/doc/html/group__opus__encoder.html#gad2d6bf6a9ffb6674879d7605ed073e25
+ std::vector<std::vector<unsigned char>> Encode(
+ const std::vector<opus_int16>& pcm, int frame_size);
+
+ int valid() const { return valid_; }
+
+ private:
+ std::vector<unsigned char> EncodeFrame(
+ const std::vector<opus_int16>::const_iterator& frame_start,
+ int frame_size);
+
+ template <typename... Ts>
+ int Ctl(int request, Ts... args) const {
+ return opus_encoder_ctl(encoder_.get(), request, args...);
+ }
+
+ int num_channels_{};
+ bool valid_{};
+ internal::opus_uptr<OpusEncoder> encoder_;
+};
+
+class Decoder {
+ public:
+ // see documentation at:
+ // https://mf4.xiph.org/jenkins/view/opus/job/opus/ws/doc/html/group__opus__decoder.html#ga753f6fe0b699c81cfd47d70c8e15a0bd
+ // Fs corresponds to sample_rate
+ Decoder(opus_uint32 sample_rate, int num_channels);
+
+ // Takes a sequence of encoded packets and decodes them. Returns the decoded
+ // audio.
+ // see documentation at:
+ // https://mf4.xiph.org/jenkins/view/opus/job/opus/ws/doc/html/group__opus__decoder.html#ga7d1111f64c36027ddcb81799df9b3fc9
+ std::vector<opus_int16> Decode(
+ const std::vector<std::vector<unsigned char>>& packets, int frame_size,
+ bool decode_fec);
+
+ int valid() const { return valid_; }
+
+ // Takes an encoded packet and decodes it. Returns the decoded audio
+ // see documentation at:
+ // https://mf4.xiph.org/jenkins/view/opus/job/opus/ws/doc/html/group__opus__decoder.html#ga7d1111f64c36027ddcb81799df9b3fc9
+ std::vector<opus_int16> Decode(const std::vector<unsigned char>& packet,
+ int frame_size, bool decode_fec);
+
+ // Generates a dummy frame by passing nullptr to the underlying opus decode.
+ std::vector<opus_int16> DecodeDummy(int frame_size);
+
+ private:
+ int num_channels_{};
+ bool valid_{};
+ internal::opus_uptr<OpusDecoder> decoder_;
+};
+
+} // namespace opus
+
+#endif