/* * Copyright 2023 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. */ #include "mmc/codec_client/codec_client.h" #include #include #include #include #include #include #include #include #include #include #include #include "mmc/daemon/constants.h" #include "mmc/metrics/mmc_rtt_logger.h" #include "mmc/proto/mmc_config.pb.h" #include "mmc/proto/mmc_service.pb.h" namespace mmc { namespace { using namespace bluetooth; // Codec param field number in |ConfigParam| const int kUnsupportedType = -1; const int kHfpLc3EncoderId = 1; const int kHfpLc3DecoderId = 2; const int kA2dpAacEncoderId = 5; // Maps |ConfigParam| proto field to int, because proto-lite does not support // reflection. int CodecId(const ConfigParam& config) { if (config.has_hfp_lc3_encoder_param()) { return kHfpLc3EncoderId; } else if (config.has_hfp_lc3_decoder_param()) { return kHfpLc3DecoderId; } else if (config.has_a2dp_aac_encoder_param()) { return kA2dpAacEncoderId; } else { log::warn("Unsupported codec type is used."); return kUnsupportedType; } } } // namespace CodecClient::CodecClient() { skt_fd_ = -1; codec_manager_ = nullptr; record_logger_ = nullptr; // Set up DBus connection. dbus::Bus::Options options; options.bus_type = dbus::Bus::SYSTEM; bus_ = new dbus::Bus(options); if (!bus_->Connect()) { log::error("Failed to connect system bus"); return; } // Get proxy to send DBus method call. codec_manager_ = bus_->GetObjectProxy(mmc::kMmcServiceName, dbus::ObjectPath(mmc::kMmcServicePath)); if (!codec_manager_) { log::error("Failed to get object proxy"); return; } } CodecClient::~CodecClient() { cleanup(); if (bus_) { bus_->ShutdownAndBlock(); } } int CodecClient::init(const ConfigParam config) { cleanup(); // Set up record logger. record_logger_ = std::make_unique(CodecId(config)); dbus::MethodCall method_call(mmc::kMmcServiceInterface, mmc::kCodecInitMethod); dbus::MessageWriter writer(&method_call); mmc::CodecInitRequest request; *request.mutable_config() = config; if (!writer.AppendProtoAsArrayOfBytes(request)) { log::error("Failed to encode CodecInitRequest protobuf"); return -EINVAL; } std::unique_ptr dbus_response = codec_manager_ ->CallMethodAndBlock(&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) // TODO(b/297976471): remove the build flag once libchrome uprev is done. #if BASE_VER >= 1170299 .value_or(nullptr) #endif ; if (!dbus_response) { log::error("CodecInit failed"); return -ECOMM; } dbus::MessageReader reader(dbus_response.get()); mmc::CodecInitResponse response; if (!reader.PopArrayOfBytesAsProto(&response)) { log::error("Failed to parse response protobuf"); return -EINVAL; } if (response.socket_token().empty()) { log::error("CodecInit returned empty socket token"); return -EBADMSG; } if (response.input_frame_size() < 0) { log::error("CodecInit returned negative frame size"); return -EBADMSG; } // Create socket. skt_fd_ = socket(AF_UNIX, SOCK_SEQPACKET, 0); if (skt_fd_ < 0) { log::error("Failed to create socket: {}", strerror(errno)); return -errno; } struct sockaddr_un addr = {}; addr.sun_family = AF_UNIX; strncpy(addr.sun_path, response.socket_token().c_str(), sizeof(addr.sun_path) - 1); // Connect to socket for transcoding. int rc = connect(skt_fd_, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)); if (rc < 0) { log::error("Failed to connect socket: {}", strerror(errno)); return -errno; } unlink(addr.sun_path); return response.input_frame_size(); } void CodecClient::cleanup() { if (skt_fd_ >= 0) { close(skt_fd_); skt_fd_ = -1; } // Upload Rtt statics when the session ends. if (record_logger_.get() != nullptr) { record_logger_->UploadTranscodeRttStatics(); record_logger_.release(); } dbus::MethodCall method_call(mmc::kMmcServiceInterface, mmc::kCodecCleanUpMethod); std::unique_ptr dbus_response = codec_manager_ ->CallMethodAndBlock(&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) // TODO(b/297976471): remove the build flag once libchrome uprev is done. #if BASE_VER >= 1170299 .value_or(nullptr) #endif ; if (!dbus_response) { log::warn("CodecCleanUp failed"); } return; } int CodecClient::transcode(uint8_t* i_buf, int i_len, uint8_t* o_buf, int o_len) { // Start Timer base::ElapsedTimer timer; // i_buf and o_buf cannot be null. if (i_buf == nullptr || o_buf == nullptr) { log::error("Buffer is null"); return -EINVAL; } if (i_len <= 0 || o_len <= 0) { log::error("Non-positive buffer length"); return -EINVAL; } // Use MSG_NOSIGNAL to ignore SIGPIPE. int rc = send(skt_fd_, i_buf, i_len, MSG_NOSIGNAL); if (rc < 0) { log::error("Failed to send data: {}", strerror(errno)); return -errno; } // Full packet should be sent under SOCK_SEQPACKET setting. if (rc < i_len) { log::error("Failed to send full packet"); return -EIO; } struct pollfd pfd; pfd.fd = skt_fd_; pfd.events = POLLIN; int pollret = poll(&pfd, 1, -1); if (pollret < 0) { log::error("Failed to poll: {}", strerror(errno)); return -errno; } if (pfd.revents & (POLLHUP | POLLNVAL)) { log::error("Socket closed remotely."); return -EIO; } // POLLIN is returned.. rc = recv(skt_fd_, o_buf, o_len, MSG_NOSIGNAL); if (rc < 0) { log::error("Failed to recv data: {}", strerror(errno)); return -errno; } // Should be able to recv data when POLLIN is returned. if (rc == 0) { log::error("Failed to recv data"); return -EIO; } // End timer record_logger_->RecordRtt(timer.Elapsed().InMicroseconds()); return rc; } } // namespace mmc