// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved. // For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md #include "PlaybackOperation.hpp" #include "Audio/decoder/Decoder.hpp" #include "Audio/Profiles/Profile.hpp" #include "Audio/StreamFactory.hpp" #include "Audio/AudioCommon.hpp" #include namespace audio { using namespace AudioServiceMessage; PlaybackOperation::PlaybackOperation(const std::string &filePath, const audio::PlaybackType &playbackType, Callback callback) : Operation(std::move(callback), playbackType), dec(nullptr) { // order defines priority AddProfile(Profile::Type::PlaybackHeadphones, playbackType, false); AddProfile(Profile::Type::PlaybackBluetoothA2DP, playbackType, false); AddProfile(Profile::Type::PlaybackLoudspeaker, playbackType, true); endOfFileCallback = [this]() { state = State::Idle; const auto req = AudioServiceMessage::EndOfFile(operationToken); serviceCallback(&req); return std::string(); }; dec = Decoder::Create(filePath); if (dec == nullptr) { throw AudioInitException("Error during initializing decoder", RetCode::FileDoesntExist); } auto format = dec->getSourceFormat(); LOG_DEBUG("Source format: %s", format.toString().c_str()); auto retCode = SwitchToPriorityProfile(playbackType); if (retCode != RetCode::Success) { throw AudioInitException("Failed to switch audio profile", retCode); } } audio::RetCode PlaybackOperation::Start(audio::Token token) { if (state == State::Active || (state == State::Paused && outputConnection != nullptr)) { return RetCode::InvokedInIncorrectState; } // create stream StreamFactory streamFactory(playbackTimeConstraint); try { dataStreamOut = streamFactory.makeStream(*dec, *audioDevice, currentProfile->getAudioFormat()); } catch (std::invalid_argument &e) { LOG_FATAL("Cannot create audio stream: %s", e.what()); return audio::RetCode::Failed; } // create audio connection outputConnection = std::make_unique(dec.get(), audioDevice.get(), dataStreamOut.get()); // decoder worker soft start - must be called after connection setup dec->startDecodingWorker(endOfFileCallback); // start output device and enable audio connection auto ret = audioDevice->Start(); outputConnection->enable(); // update state and token state = State::Active; operationToken = token; return GetDeviceError(ret); } audio::RetCode PlaybackOperation::Stop() { state = State::Idle; if (!audioDevice) { return audio::RetCode::DeviceFailure; } // stop playback by destroying audio connection outputConnection.reset(); dec->stopDecodingWorker(); dataStreamOut.reset(); return GetDeviceError(audioDevice->Stop()); } audio::RetCode PlaybackOperation::Pause() { if (state == State::Paused || state == State::Idle || outputConnection == nullptr) { return RetCode::InvokedInIncorrectState; } const auto retCode = GetDeviceError(audioDevice->Pause()); if (retCode == audio::RetCode::Success) { state = State::Paused; outputConnection->disable(); } return retCode; } audio::RetCode PlaybackOperation::Resume() { if (state == State::Active || state == State::Idle) { return RetCode::InvokedInIncorrectState; } if (outputConnection == nullptr) { Start(operationToken); } state = State::Active; outputConnection->enable(); return GetDeviceError(audioDevice->Resume()); } audio::RetCode PlaybackOperation::SetOutputVolume(float vol) { currentProfile->SetOutputVolume(vol); auto ret = audioDevice->setOutputVolume(vol); return GetDeviceError(ret); } audio::RetCode PlaybackOperation::SetInputGain(float gain) { currentProfile->SetInputGain(gain); auto ret = audioDevice->setInputGain(gain); return GetDeviceError(ret); } Position PlaybackOperation::GetPosition() { return dec->getCurrentPosition(); } audio::RetCode PlaybackOperation::SwitchToPriorityProfile(audio::PlaybackType playbackType) { for (const auto &p : supportedProfiles) { const auto profileType = p.profile->GetType(); if (profileType == audio::Profile::Type::PlaybackBluetoothA2DP && playbackType == audio::PlaybackType::CallRingtone) { continue; } if (p.isAvailable) { return SwitchProfile(profileType); } } return audio::RetCode::ProfileNotSet; } audio::RetCode PlaybackOperation::SendEvent(std::shared_ptr evt) { const auto isAvailable = evt->getDeviceState() == Event::DeviceState::Connected; switch (evt->getType()) { case EventType::JackState: SetProfileAvailability({Profile::Type::PlaybackHeadphones}, isAvailable); Operation::SwitchToPriorityProfile(); break; case EventType::BlutoothA2DPDeviceState: SetProfileAvailability({Profile::Type::PlaybackBluetoothA2DP}, isAvailable); Operation::SwitchToPriorityProfile(); break; default: return RetCode::UnsupportedEvent; } return RetCode::Success; } audio::RetCode PlaybackOperation::SwitchProfile(const Profile::Type type) { auto newProfile = GetProfile(type); if (newProfile == nullptr) { LOG_ERROR("Unsupported profile"); return RetCode::UnsupportedProfile; } if (currentProfile && currentProfile->GetType() == newProfile->GetType()) { return RetCode::Success; } // adjust new profile with information from file's tags newProfile->SetSampleRate(dec->getSourceFormat().getSampleRate()); newProfile->SetInOutFlags(static_cast(audio::codec::Flags::OutputStereo)); /// profile change - (re)create output device; stop audio first by /// killing audio connection outputConnection.reset(); dec->stopDecodingWorker(); audioDevice.reset(); dataStreamOut.reset(); audioDevice = CreateDevice(*newProfile); if (audioDevice == nullptr) { LOG_ERROR("Error creating AudioDevice"); return RetCode::Failed; } // check if audio device supports Decoder's profile if (auto format = dec->getSourceFormat(); !audioDevice->isFormatSupportedBySink(format)) { LOG_ERROR("Format unsupported by the audio device: %s", format.toString().c_str()); return RetCode::Failed; } // store profile currentProfile = newProfile; if (state == State::Active) { // playback in progress, restart state = State::Idle; Start(operationToken); } return audio::RetCode::Success; } PlaybackOperation::~PlaybackOperation() { Stop(); } } // namespace audio