Loading...
Searching...
No Matches
AudioPlaybackThread.cpp
Go to the documentation of this file.
// Copyright (c) 2008-2019 OpenShot Studios, LLC
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "AudioPlaybackThread.h"
#include "Settings.h"
#include "../ReaderBase.h"
#include "../RendererBase.h"
#include "../AudioReaderSource.h"
#include "../AudioDevices.h"
#include "../Settings.h"
#include "../ZmqLogger.h"
#include <mutex>
#include <thread> // for std::this_thread::sleep_for
#include <chrono> // for std::chrono::milliseconds
#include <sstream>
#include <condition_variable>
#include <mutex>
using namespace juce;
namespace openshot
{
// Global reference to device manager
AudioDeviceManagerSingleton *AudioDeviceManagerSingleton::m_pInstance = NULL;
// Create or Get audio device singleton with default settings (44100, 2)
AudioDeviceManagerSingleton *AudioDeviceManagerSingleton::Instance()
{
return AudioDeviceManagerSingleton::Instance(44100, 2);
}
// Create or Get an instance of the device manager singleton (with custom sample rate & channels)
AudioDeviceManagerSingleton *AudioDeviceManagerSingleton::Instance(int rate, int channels)
{
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
if (!m_pInstance) {
// Create the actual instance of device manager only once
m_pInstance = new AudioDeviceManagerSingleton;
auto* mgr = &m_pInstance->audioDeviceManager;
AudioIODevice *foundAudioIODevice = NULL;
m_pInstance->initialise_error = "";
m_pInstance->currentAudioDevice.name = "";
m_pInstance->currentAudioDevice.type = "";
m_pInstance->defaultSampleRate = 0.0;
std::stringstream constructor_title;
constructor_title << "AudioDeviceManagerSingleton::Instance (default audio device type: " <<
Settings::Instance()->PLAYBACK_AUDIO_DEVICE_TYPE << ", default audio device name: " <<
Settings::Instance()->PLAYBACK_AUDIO_DEVICE_NAME << ")";
ZmqLogger::Instance()->AppendDebugMethod(constructor_title.str(), "channels", channels, "buffer", Settings::Instance()->PLAYBACK_AUDIO_BUFFER_SIZE);
// Get preferred audio device type and name (if any - these can be blank)
openshot::AudioDeviceInfo requested_device = {Settings::Instance()->PLAYBACK_AUDIO_DEVICE_TYPE,
Settings::Instance()->PLAYBACK_AUDIO_DEVICE_NAME};
// Find missing device type (if needed)
if (requested_device.type.isEmpty() && !requested_device.name.isEmpty()) {
for (const auto t : mgr->getAvailableDeviceTypes()) {
t->scanForDevices();
for (const auto n : t->getDeviceNames()) {
if (requested_device.name.trim().equalsIgnoreCase(n.trim())) {
requested_device.type = t->getTypeName();
break;
}
}
}
}
// Populate all possible device types and device names (starting with the user's requested settings)
std::vector<openshot::AudioDeviceInfo> devices{ { requested_device } };
for (const auto t : mgr->getAvailableDeviceTypes()) {
std::stringstream type_debug;
type_debug << "AudioDeviceManagerSingleton::Instance (iterate audio device type: " << t->getTypeName() << ")";
ZmqLogger::Instance()->AppendDebugMethod(type_debug.str(), "rate", rate, "channels", channels);
t->scanForDevices();
for (const auto n : t->getDeviceNames()) {
AudioDeviceInfo device = { t->getTypeName(), n.trim() };
devices.push_back(device);
std::stringstream device_debug;
device_debug << "AudioDeviceManagerSingleton::Instance (iterate audio device name: " << device.name << ", type: " << t->getTypeName() << ")";
ZmqLogger::Instance()->AppendDebugMethod(device_debug.str(), "rate", rate, "channels", channels);
}
}
// Loop through all device combinations (starting with the requested one)
for (auto attempt_device : devices) {
m_pInstance->currentAudioDevice = attempt_device;
// Resets everything to a default device setup
m_pInstance->audioDeviceManager.initialiseWithDefaultDevices(0, channels);
// Set device type (if any)
if (!attempt_device.type.isEmpty()) {
m_pInstance->audioDeviceManager.setCurrentAudioDeviceType(attempt_device.type, true);
}
// Settings for audio device playback
AudioDeviceManager::AudioDeviceSetup deviceSetup = AudioDeviceManager::AudioDeviceSetup();
deviceSetup.inputChannels = 0;
deviceSetup.outputChannels = channels;
deviceSetup.bufferSize = Settings::Instance()->PLAYBACK_AUDIO_BUFFER_SIZE;
// Loop through common sample rates, starting with the user's requested rate
// Not all sample rates are supported by audio devices, for example, many VMs
// do not support 48000 causing no audio device to be found.
int possible_rates[] { rate, 48000, 44100, 22050 };
for(int attempt_rate : possible_rates) {
std::stringstream title_rate;
title_rate << "AudioDeviceManagerSingleton::Instance (attempt audio device name: " << attempt_device.name << ")";
ZmqLogger::Instance()->AppendDebugMethod(title_rate.str(), "rate", attempt_rate, "channels", channels);
// Update the audio device setup for the current sample rate
m_pInstance->defaultSampleRate = attempt_rate;
deviceSetup.sampleRate = attempt_rate;
m_pInstance->audioDeviceManager.setAudioDeviceSetup(deviceSetup, true);
// Open the audio device with specific sample rate (if possible)
// Not all sample rates are supported by audio devices
juce::String audio_error = m_pInstance->audioDeviceManager.initialise(
0, // number of input channels
channels, // number of output channels
nullptr, // no XML settings..
true, // select default device on failure
attempt_device.name, // preferredDefaultDeviceName
&deviceSetup // sample_rate & channels
);
// Persist any errors detected
m_pInstance->initialise_error = audio_error.toStdString();
if (!m_pInstance->initialise_error.empty()) {
std::stringstream title_error;
title_error << "AudioDeviceManagerSingleton::Instance (audio device error: " <<
m_pInstance->initialise_error << ")";
ZmqLogger::Instance()->AppendDebugMethod(title_error.str(), "rate", attempt_rate, "channels", channels);
}
// Determine if audio device was opened successfully, and matches the attempted sample rate
// If all rates fail to match, a default audio device and sample rate will be opened if possible
foundAudioIODevice = m_pInstance->audioDeviceManager.getCurrentAudioDevice();
if (foundAudioIODevice && foundAudioIODevice->getCurrentSampleRate() == attempt_rate) {
// Successfully tested a sample rate
std::stringstream title_found;
title_found << "AudioDeviceManagerSingleton::Instance (successful audio device found: " <<
foundAudioIODevice->getTypeName() << ", name: " << foundAudioIODevice->getName() << ")";
ZmqLogger::Instance()->AppendDebugMethod(title_found.str(), "rate", attempt_rate, "channels", channels);
break;
}
}
if (foundAudioIODevice) {
// Successfully opened an audio device
break;
}
}
ZmqLogger::Instance()->AppendDebugMethod("AudioDeviceManagerSingleton::Instance (audio device initialization completed)");
}
return m_pInstance;
}
// Close audio device
void AudioDeviceManagerSingleton::CloseAudioDevice()
{
// Close Audio Device
audioDeviceManager.closeAudioDevice();
audioDeviceManager.removeAllChangeListeners();
audioDeviceManager.dispatchPendingMessages();
delete m_pInstance;
m_pInstance = NULL;
}
// Constructor
AudioPlaybackThread::AudioPlaybackThread(openshot::VideoCacheThread* cache)
: juce::Thread("audio-playback")
, player()
, transport()
, mixer()
, source(NULL)
, sampleRate(0.0)
, numChannels(0)
, is_playing(false)
, time_thread("audio-buffer")
, videoCache(cache)
{
}
// Destructor
AudioPlaybackThread::~AudioPlaybackThread()
{
}
// Set the reader object
void AudioPlaybackThread::Reader(openshot::ReaderBase *reader) {
if (source)
source->Reader(reader);
else {
// Create new audio source reader
auto starting_frame = 1;
source = new AudioReaderSource(reader, starting_frame);
}
// Set local vars
sampleRate = reader->info.sample_rate;
numChannels = reader->info.channels;
ZmqLogger::Instance()->AppendDebugMethod("AudioPlaybackThread::Reader", "rate", sampleRate, "channel", numChannels);
// Set video cache thread
source->setVideoCache(videoCache);
// Mark as 'playing'
Play();
}
// Get the current frame object (which is filling the buffer)
std::shared_ptr<openshot::Frame> AudioPlaybackThread::getFrame()
{
if (source) return source->getFrame();
return std::shared_ptr<openshot::Frame>();
}
// Seek the audio thread
void AudioPlaybackThread::Seek(int64_t new_position)
{
if (source) {
source->Seek(new_position);
}
}
// Override Play and Stop to notify of state changes
void AudioPlaybackThread::Play() {
is_playing = true;
NotifyTransportStateChanged();
}
void AudioPlaybackThread::Stop() {
is_playing = false;
NotifyTransportStateChanged();
}
void AudioPlaybackThread::NotifyTransportStateChanged()
{
std::lock_guard<std::mutex> lock(transportMutex);
transportCondition.notify_all();
}
// Start audio thread
void AudioPlaybackThread::run()
{
while (!threadShouldExit())
{
if (source && !transport.isPlaying() && is_playing) {
// Start new audio device (or get existing one)
AudioDeviceManagerSingleton *audioInstance =
AudioDeviceManagerSingleton::Instance(sampleRate, numChannels);
// Add callback
audioInstance->audioDeviceManager.addAudioCallback(&player);
// Create TimeSliceThread for audio buffering
time_thread.startThread(Priority::high);
// Connect source to transport
transport.setSource(
source,
0, // No read ahead buffer
&time_thread,
0, // Sample rate correction (none)
numChannels); // max channels
transport.setPosition(0);
transport.setGain(1.0);
// Connect transport to mixer and player
mixer.addInputSource(&transport, false);
player.setSource(&mixer);
// Start the transport
transport.start();
while (!threadShouldExit() && transport.isPlaying() && is_playing) {
// Wait until transport state changes or thread should exit
std::unique_lock<std::mutex> lock(transportMutex);
transportCondition.wait_for(lock, std::chrono::milliseconds(10), [this]() {
return threadShouldExit() || !transport.isPlaying() || !is_playing;
});
}
// Stop audio and shutdown transport
Stop();
transport.stop();
// Kill previous audio
transport.setSource(NULL);
player.setSource(NULL);
audioInstance->audioDeviceManager.removeAudioCallback(&player);
// Remove source
delete source;
source = NULL;
// Stop time slice thread
time_thread.stopThread(-1);
}
}
}
}
AudioDevices.h
Header file for Audio Device Info struct.
AudioPlaybackThread.h
Source file for AudioPlaybackThread class.
AudioReaderSource.h
Header file for AudioReaderSource class.
ReaderBase.h
Header file for ReaderBase class.
RendererBase.h
Header file for RendererBase class.
Settings.h
Header file for global Settings class.
ZmqLogger.h
Header file for ZeroMQ-based Logger class.
openshot::AudioDeviceManagerSingleton
Singleton wrapper for AudioDeviceManager (to prevent multiple instances).
Definition AudioPlaybackThread.h:42
openshot::AudioDeviceManagerSingleton::CloseAudioDevice
void CloseAudioDevice()
Close audio device.
Definition AudioPlaybackThread.cpp:178
openshot::AudioDeviceManagerSingleton::Instance
static AudioDeviceManagerSingleton * Instance()
Override with default sample rate & channels (44100, 2) and no preferred audio device.
Definition AudioPlaybackThread.cpp:39
openshot::AudioDeviceManagerSingleton::audioDeviceManager
juce::AudioDeviceManager audioDeviceManager
Public device manager property.
Definition AudioPlaybackThread.h:68
openshot::ReaderBase::info
openshot::ReaderInfo info
Information about the current media file.
Definition ReaderBase.h:88
openshot::Settings::PLAYBACK_AUDIO_BUFFER_SIZE
int PLAYBACK_AUDIO_BUFFER_SIZE
Size of playback buffer before audio playback starts.
Definition Settings.h:107
openshot::Settings::PLAYBACK_AUDIO_DEVICE_NAME
std::string PLAYBACK_AUDIO_DEVICE_NAME
The audio device name to use during playback.
Definition Settings.h:101
openshot::Settings::PLAYBACK_AUDIO_DEVICE_TYPE
std::string PLAYBACK_AUDIO_DEVICE_TYPE
The device type for the playback audio devices.
Definition Settings.h:104
openshot::Settings::Instance
static Settings * Instance()
Create or get an instance of this logger singleton (invoke the class with this method).
Definition Settings.cpp:23
openshot::Thread
openshot::VideoCacheThread
Handles prefetching and caching of video/audio frames for smooth playback.
Definition VideoCacheThread.h:34
openshot::ZmqLogger::AppendDebugMethod
void AppendDebugMethod(std::string method_name, std::string arg1_name="", float arg1_value=-1.0, std::string arg2_name="", float arg2_value=-1.0, std::string arg3_name="", float arg3_value=-1.0, std::string arg4_name="", float arg4_value=-1.0, std::string arg5_name="", float arg5_value=-1.0, std::string arg6_name="", float arg6_value=-1.0)
Append debug information.
Definition ZmqLogger.cpp:178
openshot::ZmqLogger::Instance
static ZmqLogger * Instance()
Create or get an instance of this logger singleton (invoke the class with this method).
Definition ZmqLogger.cpp:35
juce
Definition Robotization.h:29
openshot
This namespace is the default namespace for all code in the openshot library.
Definition Compressor.h:29
openshot::AudioDeviceInfo
This struct hold information about Audio Devices.
Definition AudioDevices.h:27
openshot::AudioDeviceInfo::name
juce::String name
Definition AudioDevices.h:29
openshot::AudioDeviceInfo::type
juce::String type
Definition AudioDevices.h:28
openshot::ReaderInfo::channels
int channels
The number of audio channels used in the audio stream.
Definition ReaderBase.h:61
openshot::ReaderInfo::sample_rate
int sample_rate
The number of audio samples per second (44100 is a common sample rate).
Definition ReaderBase.h:60