Files
MuditaOS/module-audio/Audio/Operation/PlaybackOperation.cpp
Lefucjusz 9a8ffff654 [BH-1863] Fix deleted file popup showing in Relaxation
* Fix of the issue that 'File has been
deleted' popup would show in Relaxation
app at the end of playback if the
playback was paused at least once,
even though the file wasn't actually
deleted.
* Added very basic audio decoder error
handling and propagation mechanism.
* Minor refactor around several
audio-related parts.
2024-02-01 12:09:32 +01:00

233 lines
7.6 KiB
C++

// Copyright (c) 2017-2024, 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 <log/log.hpp>
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 msg = AudioServiceMessage::EndOfFile(operationToken);
serviceCallback(&msg);
};
fileDeletedCallback = [this]() {
state = State::Idle;
const auto msg = AudioServiceMessage::FileDeleted(operationToken);
serviceCallback(&msg);
};
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<StreamConnection>(dec.get(), audioDevice.get(), dataStreamOut.get());
// decoder worker soft start - must be called after connection setup
dec->startDecodingWorker(endOfFileCallback, fileDeletedCallback);
// 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<Event> 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::BluetoothA2DPDeviceState:
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<std::uint32_t>(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