Add Chromium-only Blender WebEngine parity work
This commit is contained in:
370
blender-5.2.0/extern/audaspace/plugins/coreaudio/CoreAudioDevice.cpp
vendored
Normal file
370
blender-5.2.0/extern/audaspace/plugins/coreaudio/CoreAudioDevice.cpp
vendored
Normal file
@@ -0,0 +1,370 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2021 Jörg Müller
|
||||
*
|
||||
* 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 "CoreAudioDevice.h"
|
||||
#include "devices/DeviceManager.h"
|
||||
#include "devices/IDeviceFactory.h"
|
||||
#include "Exception.h"
|
||||
#include "IReader.h"
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
OSStatus CoreAudioDevice::CoreAudio_mix(void* data, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time_stamp, UInt32 bus_number, UInt32 number_frames, AudioBufferList* buffer_list)
|
||||
{
|
||||
CoreAudioDevice* device = (CoreAudioDevice*)data;
|
||||
|
||||
size_t sample_size = AUD_DEVICE_SAMPLE_SIZE(device->m_specs);
|
||||
|
||||
for(int i = 0; i < buffer_list->mNumberBuffers; i++)
|
||||
{
|
||||
auto& buffer = buffer_list->mBuffers[i];
|
||||
|
||||
size_t readsamples = device->getRingBuffer().getReadSize();
|
||||
|
||||
size_t num_bytes = size_t(buffer.mDataByteSize);
|
||||
|
||||
readsamples = std::min(readsamples, num_bytes) / sample_size;
|
||||
|
||||
device->getRingBuffer().read((data_t*) buffer.mData, readsamples * sample_size);
|
||||
|
||||
if(readsamples * sample_size < num_bytes)
|
||||
std::memset((data_t*) buffer.mData + readsamples * sample_size, 0, num_bytes - readsamples * sample_size);
|
||||
|
||||
device->notifyMixingThread();
|
||||
}
|
||||
|
||||
if (!device->m_audio_clock_ready) {
|
||||
// Workaround CoreAudio quirk that corrupts the clock time data when the first mix callback occurs.
|
||||
// Both the start time and current time will be invalid. We need to reset them.
|
||||
if(device->isSynchronizerPlaying())
|
||||
CAClockStop(device->m_clock_ref);
|
||||
|
||||
CAClockTime clock_time;
|
||||
clock_time.format = kCAClockTimeFormat_Seconds;
|
||||
clock_time.time.seconds = device->m_synchronizerStartTime;
|
||||
CAClockSetCurrentTime(device->m_clock_ref, &clock_time);
|
||||
|
||||
if(device->isSynchronizerPlaying())
|
||||
CAClockStart(device->m_clock_ref);
|
||||
device->m_audio_clock_ready = true;
|
||||
}
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
void CoreAudioDevice::preMixingWork(bool playing)
|
||||
{
|
||||
if(!playing)
|
||||
{
|
||||
if((getRingBuffer().getReadSize() == 0) && m_active)
|
||||
{
|
||||
AudioOutputUnitStop(m_audio_unit);
|
||||
m_active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CoreAudioDevice::playing(bool playing)
|
||||
{
|
||||
MixingThreadDevice::playing(playing);
|
||||
|
||||
if(m_playback != playing)
|
||||
{
|
||||
if(playing)
|
||||
{
|
||||
AudioOutputUnitStart(m_audio_unit);
|
||||
m_active = true;
|
||||
}
|
||||
}
|
||||
|
||||
m_playback = playing;
|
||||
}
|
||||
|
||||
CoreAudioDevice::CoreAudioDevice(DeviceSpecs specs, int buffersize) : m_buffersize(uint32_t(buffersize)), m_playback(false), m_audio_unit(nullptr)
|
||||
{
|
||||
if(specs.channels == CHANNELS_INVALID)
|
||||
specs.channels = CHANNELS_STEREO;
|
||||
if(specs.format == FORMAT_INVALID)
|
||||
specs.format = FORMAT_FLOAT32;
|
||||
if(specs.rate == static_cast<SampleRate>(RATE_INVALID))
|
||||
specs.rate = RATE_48000;
|
||||
|
||||
m_specs = specs;
|
||||
|
||||
AudioComponentDescription component_description = {};
|
||||
|
||||
component_description.componentType = kAudioUnitType_Output;
|
||||
component_description.componentSubType = kAudioUnitSubType_DefaultOutput;
|
||||
component_description.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
|
||||
AudioComponent component = AudioComponentFindNext(nullptr, &component_description);
|
||||
|
||||
if(!component)
|
||||
AUD_THROW(DeviceException, "The audio device couldn't be opened with CoreAudio.");
|
||||
|
||||
OSStatus status = AudioComponentInstanceNew(component, &m_audio_unit);
|
||||
|
||||
if(status != noErr)
|
||||
AUD_THROW(DeviceException, "The audio device couldn't be opened with CoreAudio.");
|
||||
|
||||
AudioStreamBasicDescription stream_basic_description = {};
|
||||
|
||||
switch(m_specs.format)
|
||||
{
|
||||
case FORMAT_U8:
|
||||
stream_basic_description.mFormatFlags = 0;
|
||||
stream_basic_description.mBitsPerChannel = 8;
|
||||
break;
|
||||
case FORMAT_S16:
|
||||
stream_basic_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
|
||||
stream_basic_description.mBitsPerChannel = 16;
|
||||
break;
|
||||
case FORMAT_S24:
|
||||
stream_basic_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
|
||||
stream_basic_description.mBitsPerChannel = 24;
|
||||
break;
|
||||
case FORMAT_S32:
|
||||
stream_basic_description.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger;
|
||||
stream_basic_description.mBitsPerChannel = 32;
|
||||
break;
|
||||
case FORMAT_FLOAT32:
|
||||
stream_basic_description.mFormatFlags = kLinearPCMFormatFlagIsFloat;
|
||||
stream_basic_description.mBitsPerChannel = 32;
|
||||
break;
|
||||
case FORMAT_FLOAT64:
|
||||
stream_basic_description.mFormatFlags = kLinearPCMFormatFlagIsFloat;
|
||||
stream_basic_description.mBitsPerChannel = 64;
|
||||
break;
|
||||
default:
|
||||
m_specs.format = FORMAT_FLOAT32;
|
||||
stream_basic_description.mFormatFlags = kLinearPCMFormatFlagIsFloat;
|
||||
stream_basic_description.mBitsPerChannel = 32;
|
||||
break;
|
||||
}
|
||||
|
||||
stream_basic_description.mSampleRate = m_specs.rate;
|
||||
stream_basic_description.mFormatID = kAudioFormatLinearPCM;
|
||||
stream_basic_description.mFormatFlags |= AudioFormatFlags(kAudioFormatFlagsNativeEndian) | AudioFormatFlags(kLinearPCMFormatFlagIsPacked);
|
||||
stream_basic_description.mBytesPerPacket = stream_basic_description.mBytesPerFrame = AUD_DEVICE_SAMPLE_SIZE(m_specs);
|
||||
stream_basic_description.mFramesPerPacket = 1;
|
||||
stream_basic_description.mChannelsPerFrame = m_specs.channels;
|
||||
|
||||
status = AudioUnitSetProperty(m_audio_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &stream_basic_description, sizeof(stream_basic_description));
|
||||
|
||||
if(status != noErr)
|
||||
{
|
||||
AudioComponentInstanceDispose(m_audio_unit);
|
||||
AUD_THROW(DeviceException, "The audio device couldn't be opened with CoreAudio.");
|
||||
}
|
||||
|
||||
AURenderCallbackStruct render_callback_struct;
|
||||
render_callback_struct.inputProc = CoreAudioDevice::CoreAudio_mix;
|
||||
render_callback_struct.inputProcRefCon = this;
|
||||
|
||||
status = AudioUnitSetProperty(m_audio_unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &render_callback_struct, sizeof(render_callback_struct));
|
||||
|
||||
if(status != noErr)
|
||||
{
|
||||
AudioComponentInstanceDispose(m_audio_unit);
|
||||
AUD_THROW(DeviceException, "The audio device couldn't be opened with CoreAudio.");
|
||||
}
|
||||
|
||||
status = AudioUnitSetProperty(m_audio_unit, kAudioDevicePropertyBufferFrameSize, kAudioUnitScope_Input, 0, &m_buffersize, sizeof(m_buffersize));
|
||||
|
||||
if(status != noErr)
|
||||
{
|
||||
AudioComponentInstanceDispose(m_audio_unit);
|
||||
AUD_THROW(DeviceException, "Could not set the buffer size for the audio device.");
|
||||
}
|
||||
|
||||
status = AudioUnitInitialize(m_audio_unit);
|
||||
|
||||
if(status != noErr)
|
||||
{
|
||||
AudioComponentInstanceDispose(m_audio_unit);
|
||||
AUD_THROW(DeviceException, "The audio device couldn't be opened with CoreAudio.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
OSStatus status = CAClockNew(0, &m_clock_ref);
|
||||
|
||||
if(status != noErr)
|
||||
AUD_THROW(DeviceException, "Could not create a CoreAudio clock.");
|
||||
|
||||
CAClockTimebase timebase = kCAClockTimebase_AudioOutputUnit;
|
||||
|
||||
status = CAClockSetProperty(m_clock_ref, kCAClockProperty_InternalTimebase, sizeof(timebase), &timebase);
|
||||
|
||||
if(status != noErr)
|
||||
{
|
||||
CAClockDispose(m_clock_ref);
|
||||
AUD_THROW(DeviceException, "Could not create a CoreAudio clock.");
|
||||
}
|
||||
|
||||
status = CAClockSetProperty(m_clock_ref, kCAClockProperty_TimebaseSource, sizeof(m_audio_unit), &m_audio_unit);
|
||||
|
||||
if(status != noErr)
|
||||
{
|
||||
CAClockDispose(m_clock_ref);
|
||||
AUD_THROW(DeviceException, "Could not create a CoreAudio clock.");
|
||||
}
|
||||
|
||||
CAClockSyncMode sync_mode = kCAClockSyncMode_Internal;
|
||||
|
||||
status = CAClockSetProperty(m_clock_ref, kCAClockProperty_SyncMode, sizeof(sync_mode), &sync_mode);
|
||||
|
||||
if(status != noErr)
|
||||
{
|
||||
CAClockDispose(m_clock_ref);
|
||||
AUD_THROW(DeviceException, "Could not create a CoreAudio clock.");
|
||||
}
|
||||
}
|
||||
catch(Exception&)
|
||||
{
|
||||
AudioComponentInstanceDispose(m_audio_unit);
|
||||
throw;
|
||||
}
|
||||
|
||||
create();
|
||||
|
||||
startMixingThread(buffersize * 2 * AUD_DEVICE_SAMPLE_SIZE(specs));
|
||||
}
|
||||
|
||||
CoreAudioDevice::~CoreAudioDevice()
|
||||
{
|
||||
stopMixingThread();
|
||||
|
||||
destroy();
|
||||
|
||||
CAClockDispose(m_clock_ref);
|
||||
AudioOutputUnitStop(m_audio_unit);
|
||||
AudioUnitUninitialize(m_audio_unit);
|
||||
AudioComponentInstanceDispose(m_audio_unit);
|
||||
}
|
||||
|
||||
void CoreAudioDevice::seekSynchronizer(double time)
|
||||
{
|
||||
if(isSynchronizerPlaying())
|
||||
CAClockStop(m_clock_ref);
|
||||
|
||||
CAClockTime clock_time;
|
||||
clock_time.format = kCAClockTimeFormat_Seconds;
|
||||
clock_time.time.seconds = time;
|
||||
CAClockSetCurrentTime(m_clock_ref, &clock_time);
|
||||
m_synchronizerStartTime = time;
|
||||
|
||||
if(isSynchronizerPlaying())
|
||||
CAClockStart(m_clock_ref);
|
||||
|
||||
SoftwareDevice::seekSynchronizer(time);
|
||||
}
|
||||
|
||||
double CoreAudioDevice::getSynchronizerPosition()
|
||||
{
|
||||
CAClockTime clock_time;
|
||||
|
||||
OSStatus status;
|
||||
|
||||
if(isSynchronizerPlaying() && m_audio_clock_ready)
|
||||
status = CAClockGetCurrentTime(m_clock_ref, kCAClockTimeFormat_Seconds, &clock_time);
|
||||
else
|
||||
status = CAClockGetStartTime(m_clock_ref, kCAClockTimeFormat_Seconds, &clock_time);
|
||||
|
||||
if(status != noErr)
|
||||
return 0;
|
||||
|
||||
return clock_time.time.seconds;
|
||||
}
|
||||
|
||||
void CoreAudioDevice::playSynchronizer()
|
||||
{
|
||||
if(isSynchronizerPlaying())
|
||||
return;
|
||||
|
||||
CAClockStart(m_clock_ref);
|
||||
|
||||
SoftwareDevice::playSynchronizer();
|
||||
}
|
||||
|
||||
void CoreAudioDevice::stopSynchronizer()
|
||||
{
|
||||
if(!isSynchronizerPlaying())
|
||||
return;
|
||||
|
||||
CAClockStop(m_clock_ref);
|
||||
|
||||
SoftwareDevice::stopSynchronizer();
|
||||
}
|
||||
|
||||
class CoreAudioDeviceFactory : public IDeviceFactory
|
||||
{
|
||||
private:
|
||||
DeviceSpecs m_specs;
|
||||
int m_buffersize;
|
||||
|
||||
public:
|
||||
CoreAudioDeviceFactory() :
|
||||
m_buffersize(AUD_DEFAULT_BUFFER_SIZE)
|
||||
{
|
||||
m_specs.format = FORMAT_FLOAT32;
|
||||
m_specs.channels = CHANNELS_STEREO;
|
||||
m_specs.rate = RATE_48000;
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<IDevice> openDevice()
|
||||
{
|
||||
return std::shared_ptr<IDevice>(new CoreAudioDevice(m_specs, m_buffersize));
|
||||
}
|
||||
|
||||
virtual int getPriority()
|
||||
{
|
||||
return 1 << 15;
|
||||
}
|
||||
|
||||
virtual void setSpecs(DeviceSpecs specs)
|
||||
{
|
||||
m_specs = specs;
|
||||
}
|
||||
|
||||
virtual void setBufferSize(int buffersize)
|
||||
{
|
||||
m_buffersize = buffersize;
|
||||
}
|
||||
|
||||
virtual void setName(const std::string &name)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
void CoreAudioDevice::registerPlugin()
|
||||
{
|
||||
DeviceManager::registerDevice("CoreAudio", std::shared_ptr<IDeviceFactory>(new CoreAudioDeviceFactory));
|
||||
}
|
||||
|
||||
#ifdef COREAUDIO_PLUGIN
|
||||
extern "C" AUD_PLUGIN_API void registerPlugin()
|
||||
{
|
||||
CoreAudioDevice::registerPlugin();
|
||||
}
|
||||
|
||||
extern "C" AUD_PLUGIN_API const char* getName()
|
||||
{
|
||||
return "CoreAudio";
|
||||
}
|
||||
#endif
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
107
blender-5.2.0/extern/audaspace/plugins/coreaudio/CoreAudioDevice.h
vendored
Normal file
107
blender-5.2.0/extern/audaspace/plugins/coreaudio/CoreAudioDevice.h
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2021 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef COREAUDIO_PLUGIN
|
||||
#define AUD_BUILD_PLUGIN
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file CoreAudioDevice.h
|
||||
* @ingroup plugin
|
||||
* The CoreAudioDevice class.
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
#include <AudioToolbox/CoreAudioClock.h>
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
|
||||
#include "devices/MixingThreadDevice.h"
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* This device plays back through CoreAudio, the Apple audio API.
|
||||
*/
|
||||
class AUD_PLUGIN_API CoreAudioDevice : public MixingThreadDevice
|
||||
{
|
||||
private:
|
||||
uint32_t m_buffersize;
|
||||
|
||||
/**
|
||||
* Whether there is currently playback.
|
||||
*/
|
||||
bool m_playback;
|
||||
|
||||
/**
|
||||
* The CoreAudio AudioUnit.
|
||||
*/
|
||||
AudioUnit m_audio_unit;
|
||||
bool m_active{false};
|
||||
|
||||
/// The CoreAudio clock referene.
|
||||
CAClockRef m_clock_ref;
|
||||
bool m_audio_clock_ready{false};
|
||||
double m_synchronizerStartTime{0};
|
||||
|
||||
/**
|
||||
* Mixes the next bytes into the buffer.
|
||||
* \param data The CoreAudio device.
|
||||
* \param flags Unused flags.
|
||||
* \param time_stamp Unused time stamp.
|
||||
* \param bus_number Unused bus number.
|
||||
* \param number_frames Unused number of frames.
|
||||
* \param buffer_list The list of buffers to be filled.
|
||||
*/
|
||||
AUD_LOCAL static OSStatus CoreAudio_mix(void* data, AudioUnitRenderActionFlags* flags, const AudioTimeStamp* time_stamp, UInt32 bus_number, UInt32 number_frames, AudioBufferList* buffer_list);
|
||||
|
||||
// delete copy constructor and operator=
|
||||
CoreAudioDevice(const CoreAudioDevice&) = delete;
|
||||
CoreAudioDevice& operator=(const CoreAudioDevice&) = delete;
|
||||
|
||||
AUD_LOCAL void preMixingWork(bool playing) override;
|
||||
void playing(bool playing) override;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Opens the CoreAudio audio device for playback.
|
||||
* \param specs The wanted audio specification.
|
||||
* \param buffersize The size of the internal buffer.
|
||||
* \note The specification really used for opening the device may differ.
|
||||
* \exception Exception Thrown if the audio device cannot be opened.
|
||||
*/
|
||||
CoreAudioDevice(DeviceSpecs specs, int buffersize = AUD_DEFAULT_BUFFER_SIZE);
|
||||
|
||||
/**
|
||||
* Closes the CoreAudio audio device.
|
||||
*/
|
||||
virtual ~CoreAudioDevice();
|
||||
|
||||
virtual void seekSynchronizer(double time);
|
||||
virtual double getSynchronizerPosition();
|
||||
virtual void playSynchronizer();
|
||||
virtual void stopSynchronizer();
|
||||
|
||||
/**
|
||||
* Registers this plugin.
|
||||
*/
|
||||
static void registerPlugin();
|
||||
};
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
75
blender-5.2.0/extern/audaspace/plugins/ffmpeg/FFMPEG.cpp
vendored
Normal file
75
blender-5.2.0/extern/audaspace/plugins/ffmpeg/FFMPEG.cpp
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2024 Jörg Müller
|
||||
*
|
||||
* 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 "FFMPEG.h"
|
||||
#include "FFMPEGReader.h"
|
||||
#include "FFMPEGWriter.h"
|
||||
#include "file/FileManager.h"
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
FFMPEG::FFMPEG()
|
||||
{
|
||||
#if LIBAVCODEC_VERSION_MAJOR < 58
|
||||
av_register_all();
|
||||
#endif
|
||||
}
|
||||
|
||||
void FFMPEG::registerPlugin()
|
||||
{
|
||||
std::shared_ptr<FFMPEG> plugin = std::shared_ptr<FFMPEG>(new FFMPEG);
|
||||
FileManager::registerInput(plugin);
|
||||
FileManager::registerOutput(plugin);
|
||||
}
|
||||
|
||||
std::shared_ptr<IReader> FFMPEG::createReader(const std::string &filename, int stream)
|
||||
{
|
||||
return std::shared_ptr<IReader>(new FFMPEGReader(filename, stream));
|
||||
}
|
||||
|
||||
std::shared_ptr<IReader> FFMPEG::createReader(std::shared_ptr<Buffer> buffer, int stream)
|
||||
{
|
||||
return std::shared_ptr<IReader>(new FFMPEGReader(buffer, stream));
|
||||
}
|
||||
|
||||
std::vector<StreamInfo> FFMPEG::queryStreams(const std::string &filename)
|
||||
{
|
||||
return FFMPEGReader(filename).queryStreams();
|
||||
}
|
||||
|
||||
std::vector<StreamInfo> FFMPEG::queryStreams(std::shared_ptr<Buffer> buffer)
|
||||
{
|
||||
return FFMPEGReader(buffer).queryStreams();
|
||||
}
|
||||
|
||||
std::shared_ptr<IWriter> FFMPEG::createWriter(const std::string &filename, DeviceSpecs specs, Container format, Codec codec, unsigned int bitrate)
|
||||
{
|
||||
return std::shared_ptr<IWriter>(new FFMPEGWriter(filename, specs, format, codec, bitrate));
|
||||
}
|
||||
|
||||
#ifdef FFMPEG_PLUGIN
|
||||
extern "C" AUD_PLUGIN_API void registerPlugin()
|
||||
{
|
||||
FFMPEG::registerPlugin();
|
||||
}
|
||||
|
||||
extern "C" AUD_PLUGIN_API const char* getName()
|
||||
{
|
||||
return "FFMPEG";
|
||||
}
|
||||
#endif
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
62
blender-5.2.0/extern/audaspace/plugins/ffmpeg/FFMPEG.h
vendored
Normal file
62
blender-5.2.0/extern/audaspace/plugins/ffmpeg/FFMPEG.h
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2024 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef FFMPEG_PLUGIN
|
||||
#define AUD_BUILD_PLUGIN
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file FFMPEG.h
|
||||
* @ingroup plugin
|
||||
* The FFMPEG class.
|
||||
*/
|
||||
|
||||
#include "file/IFileInput.h"
|
||||
#include "file/IFileOutput.h"
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* This plugin class reads and writes sounds via ffmpeg.
|
||||
*/
|
||||
class AUD_PLUGIN_API FFMPEG : public IFileInput, public IFileOutput
|
||||
{
|
||||
private:
|
||||
// delete copy constructor and operator=
|
||||
FFMPEG(const FFMPEG&) = delete;
|
||||
FFMPEG& operator=(const FFMPEG&) = delete;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Creates a new ffmpeg plugin.
|
||||
*/
|
||||
FFMPEG();
|
||||
|
||||
/**
|
||||
* Registers this plugin.
|
||||
*/
|
||||
static void registerPlugin();
|
||||
|
||||
virtual std::shared_ptr<IReader> createReader(const std::string &filename, int stream = 0);
|
||||
virtual std::shared_ptr<IReader> createReader(std::shared_ptr<Buffer> buffer, int stream = 0);
|
||||
virtual std::vector<StreamInfo> queryStreams(const std::string &filename);
|
||||
virtual std::vector<StreamInfo> queryStreams(std::shared_ptr<Buffer> buffer);
|
||||
virtual std::shared_ptr<IWriter> createWriter(const std::string &filename, DeviceSpecs specs, Container format, Codec codec, unsigned int bitrate);
|
||||
};
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
583
blender-5.2.0/extern/audaspace/plugins/ffmpeg/FFMPEGReader.cpp
vendored
Normal file
583
blender-5.2.0/extern/audaspace/plugins/ffmpeg/FFMPEGReader.cpp
vendored
Normal file
@@ -0,0 +1,583 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2024 Jörg Müller
|
||||
*
|
||||
* 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 "FFMPEGReader.h"
|
||||
#include "Exception.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avio.h>
|
||||
#include <libavutil/avutil.h>
|
||||
}
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
/* FFmpeg < 4.0 */
|
||||
#if LIBAVCODEC_VERSION_MAJOR < 58
|
||||
#define FFMPEG_OLD_CODE
|
||||
#endif
|
||||
/* FFmpeg < 5.0 */
|
||||
#if LIBAVCODEC_VERSION_MAJOR < 59
|
||||
#define FFMPEG_OLD_CH_LAYOUT
|
||||
#endif
|
||||
|
||||
SampleFormat FFMPEGReader::convertSampleFormat(AVSampleFormat format)
|
||||
{
|
||||
switch(av_get_packed_sample_fmt(format))
|
||||
{
|
||||
case AV_SAMPLE_FMT_U8:
|
||||
return FORMAT_U8;
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
return FORMAT_S16;
|
||||
case AV_SAMPLE_FMT_S32:
|
||||
return FORMAT_S32;
|
||||
case AV_SAMPLE_FMT_FLT:
|
||||
return FORMAT_FLOAT32;
|
||||
case AV_SAMPLE_FMT_DBL:
|
||||
return FORMAT_FLOAT64;
|
||||
default:
|
||||
AUD_THROW(FileException, "FFMPEG sample format unknown.");
|
||||
}
|
||||
}
|
||||
|
||||
int FFMPEGReader::decode(AVPacket& packet, Buffer& buffer)
|
||||
{
|
||||
int buf_size = buffer.getSize();
|
||||
int buf_pos = 0;
|
||||
|
||||
#ifdef FFMPEG_OLD_CODE
|
||||
int got_frame;
|
||||
int read_length;
|
||||
uint8_t* orig_data = packet.data;
|
||||
int orig_size = packet.size;
|
||||
|
||||
while(packet.size > 0)
|
||||
{
|
||||
got_frame = 0;
|
||||
|
||||
read_length = avcodec_decode_audio4(m_codecCtx, m_frame, &got_frame, &packet);
|
||||
if(read_length < 0)
|
||||
break;
|
||||
|
||||
if(got_frame)
|
||||
{
|
||||
int data_size = av_samples_get_buffer_size(nullptr, m_codecCtx->channels, m_frame->nb_samples, m_codecCtx->sample_fmt, 1);
|
||||
|
||||
if(buf_size - buf_pos < data_size)
|
||||
{
|
||||
buffer.resize(buf_size + data_size, true);
|
||||
buf_size += data_size;
|
||||
}
|
||||
|
||||
if(m_tointerleave)
|
||||
{
|
||||
int single_size = data_size / m_codecCtx->channels / m_frame->nb_samples;
|
||||
for(int channel = 0; channel < m_codecCtx->channels; channel++)
|
||||
{
|
||||
for(int i = 0; i < m_frame->nb_samples; i++)
|
||||
{
|
||||
std::memcpy(((data_t*)buffer.getBuffer()) + buf_pos + ((m_codecCtx->channels * i) + channel) * single_size,
|
||||
m_frame->data[channel] + i * single_size, single_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
std::memcpy(((data_t*)buffer.getBuffer()) + buf_pos, m_frame->data[0], data_size);
|
||||
|
||||
buf_pos += data_size;
|
||||
}
|
||||
packet.size -= read_length;
|
||||
packet.data += read_length;
|
||||
}
|
||||
|
||||
packet.data = orig_data;
|
||||
packet.size = orig_size;
|
||||
#else
|
||||
avcodec_send_packet(m_codecCtx, &packet);
|
||||
|
||||
while(true)
|
||||
{
|
||||
auto ret = avcodec_receive_frame(m_codecCtx, m_frame);
|
||||
|
||||
if(ret != 0)
|
||||
break;
|
||||
|
||||
#ifdef FFMPEG_OLD_CH_LAYOUT
|
||||
int channels = m_codecCtx->channels;
|
||||
#else
|
||||
int channels = m_codecCtx->ch_layout.nb_channels;
|
||||
#endif
|
||||
|
||||
int data_size = av_samples_get_buffer_size(nullptr, channels, m_frame->nb_samples, m_codecCtx->sample_fmt, 1);
|
||||
|
||||
if(buf_size - buf_pos < data_size)
|
||||
{
|
||||
buffer.resize(buf_size + data_size, true);
|
||||
buf_size += data_size;
|
||||
}
|
||||
|
||||
if(m_tointerleave)
|
||||
{
|
||||
int single_size = data_size / channels / m_frame->nb_samples;
|
||||
for(int channel = 0; channel < channels; channel++)
|
||||
{
|
||||
for(int i = 0; i < m_frame->nb_samples; i++)
|
||||
{
|
||||
std::memcpy(((data_t*)buffer.getBuffer()) + buf_pos + ((channels * i) + channel) * single_size,
|
||||
m_frame->data[channel] + i * single_size, single_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
std::memcpy(((data_t*)buffer.getBuffer()) + buf_pos, m_frame->data[0], data_size);
|
||||
|
||||
buf_pos += data_size;
|
||||
}
|
||||
#endif
|
||||
|
||||
return buf_pos;
|
||||
}
|
||||
|
||||
void FFMPEGReader::init(int stream)
|
||||
{
|
||||
m_position = 0;
|
||||
m_pkgbuf_left = 0;
|
||||
|
||||
if(avformat_find_stream_info(m_formatCtx, nullptr) < 0)
|
||||
AUD_THROW(FileException, "File couldn't be read, ffmpeg couldn't find the stream info.");
|
||||
|
||||
// find audio stream and codec
|
||||
m_stream = -1;
|
||||
|
||||
for(unsigned int i = 0; i < m_formatCtx->nb_streams; i++)
|
||||
{
|
||||
#ifdef FFMPEG_OLD_CODE
|
||||
if((m_formatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
|
||||
#else
|
||||
if((m_formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
|
||||
#endif
|
||||
&& (m_stream < 0))
|
||||
{
|
||||
if(stream == 0)
|
||||
{
|
||||
m_stream=i;
|
||||
break;
|
||||
}
|
||||
else
|
||||
stream--;
|
||||
}
|
||||
}
|
||||
|
||||
if(m_stream == -1)
|
||||
AUD_THROW(FileException, "File couldn't be read, no audio stream found by ffmpeg.");
|
||||
|
||||
// get a decoder and open it
|
||||
#ifndef FFMPEG_OLD_CODE
|
||||
const AVCodec* aCodec = avcodec_find_decoder(m_formatCtx->streams[m_stream]->codecpar->codec_id);
|
||||
|
||||
if(!aCodec)
|
||||
AUD_THROW(FileException, "File couldn't be read, no decoder found with ffmpeg.");
|
||||
#endif
|
||||
|
||||
m_frame = av_frame_alloc();
|
||||
|
||||
if(!m_frame)
|
||||
AUD_THROW(FileException, "File couldn't be read, ffmpeg frame couldn't be allocated.");
|
||||
|
||||
#ifdef FFMPEG_OLD_CODE
|
||||
m_codecCtx = m_formatCtx->streams[m_stream]->codec;
|
||||
|
||||
AVCodec* aCodec = avcodec_find_decoder(m_codecCtx->codec_id);
|
||||
#else
|
||||
m_codecCtx = avcodec_alloc_context3(aCodec);
|
||||
#endif
|
||||
|
||||
if(!m_codecCtx)
|
||||
AUD_THROW(FileException, "File couldn't be read, ffmpeg context couldn't be allocated.");
|
||||
|
||||
#ifndef FFMPEG_OLD_CODE
|
||||
if(avcodec_parameters_to_context(m_codecCtx, m_formatCtx->streams[m_stream]->codecpar) < 0)
|
||||
AUD_THROW(FileException, "File couldn't be read, ffmpeg decoder parameters couldn't be copied to decoder context.");
|
||||
#endif
|
||||
|
||||
if(avcodec_open2(m_codecCtx, aCodec, nullptr) < 0)
|
||||
AUD_THROW(FileException, "File couldn't be read, ffmpeg codec couldn't be opened.");
|
||||
|
||||
#ifdef FFMPEG_OLD_CH_LAYOUT
|
||||
int channels = m_codecCtx->channels;
|
||||
#else
|
||||
int channels = m_codecCtx->ch_layout.nb_channels;
|
||||
#endif
|
||||
m_specs.channels = (Channels) channels;
|
||||
m_tointerleave = av_sample_fmt_is_planar(m_codecCtx->sample_fmt);
|
||||
|
||||
switch(av_get_packed_sample_fmt(m_codecCtx->sample_fmt))
|
||||
{
|
||||
case AV_SAMPLE_FMT_U8:
|
||||
m_convert = convert_u8_float;
|
||||
m_specs.format = FORMAT_U8;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
m_convert = convert_s16_float;
|
||||
m_specs.format = FORMAT_S16;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_S32:
|
||||
m_convert = convert_s32_float;
|
||||
m_specs.format = FORMAT_S32;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_FLT:
|
||||
m_convert = convert_copy<float>;
|
||||
m_specs.format = FORMAT_FLOAT32;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_DBL:
|
||||
m_convert = convert_double_float;
|
||||
m_specs.format = FORMAT_FLOAT64;
|
||||
break;
|
||||
default:
|
||||
AUD_THROW(FileException, "File couldn't be read, ffmpeg sample format unknown.");
|
||||
}
|
||||
|
||||
m_specs.rate = (SampleRate) m_codecCtx->sample_rate;
|
||||
}
|
||||
|
||||
FFMPEGReader::FFMPEGReader(const std::string& filename, int stream) : m_pkgbuf(), m_formatCtx(nullptr), m_codecCtx(nullptr), m_frame(nullptr), m_aviocontext(nullptr)
|
||||
{
|
||||
// open file
|
||||
if(avformat_open_input(&m_formatCtx, filename.c_str(), nullptr, nullptr)!=0)
|
||||
AUD_THROW(FileException, "File couldn't be opened with ffmpeg.");
|
||||
|
||||
try
|
||||
{
|
||||
init(stream);
|
||||
}
|
||||
catch(Exception&)
|
||||
{
|
||||
avformat_close_input(&m_formatCtx);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
FFMPEGReader::FFMPEGReader(std::shared_ptr<Buffer> buffer, int stream) :
|
||||
m_pkgbuf(),
|
||||
m_codecCtx(nullptr),
|
||||
m_frame(nullptr),
|
||||
m_membuffer(buffer),
|
||||
m_membufferpos(0)
|
||||
{
|
||||
constexpr int BUFFER_SIZE{4096};
|
||||
|
||||
auto membuf = reinterpret_cast<data_t*>(av_malloc(BUFFER_SIZE));
|
||||
|
||||
m_aviocontext = avio_alloc_context(membuf, BUFFER_SIZE, 0, this, read_packet, nullptr, seek_packet);
|
||||
|
||||
if(!m_aviocontext)
|
||||
{
|
||||
av_free(membuf);
|
||||
AUD_THROW(FileException, "Buffer reading context couldn't be created with ffmpeg.");
|
||||
}
|
||||
|
||||
m_formatCtx = avformat_alloc_context();
|
||||
m_formatCtx->pb = m_aviocontext;
|
||||
if(avformat_open_input(&m_formatCtx, "", nullptr, nullptr)!=0)
|
||||
{
|
||||
if(m_aviocontext->buffer)
|
||||
av_free(m_aviocontext->buffer);
|
||||
av_free(m_aviocontext);
|
||||
AUD_THROW(FileException, "Buffer couldn't be read with ffmpeg.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
init(stream);
|
||||
}
|
||||
catch(Exception&)
|
||||
{
|
||||
avformat_close_input(&m_formatCtx);
|
||||
if(m_aviocontext->buffer)
|
||||
av_free(m_aviocontext->buffer);
|
||||
av_free(m_aviocontext);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
FFMPEGReader::~FFMPEGReader()
|
||||
{
|
||||
if(m_frame)
|
||||
av_frame_free(&m_frame);
|
||||
#ifdef FFMPEG_OLD_CODE
|
||||
avcodec_close(m_codecCtx);
|
||||
#else
|
||||
if(m_codecCtx)
|
||||
avcodec_free_context(&m_codecCtx);
|
||||
#endif
|
||||
if(m_aviocontext)
|
||||
{
|
||||
if(m_aviocontext->buffer)
|
||||
av_free(m_aviocontext->buffer);
|
||||
av_free(m_aviocontext);
|
||||
}
|
||||
|
||||
avformat_close_input(&m_formatCtx);
|
||||
}
|
||||
|
||||
std::vector<StreamInfo> FFMPEGReader::queryStreams()
|
||||
{
|
||||
std::vector<StreamInfo> result;
|
||||
|
||||
for(unsigned int i = 0; i < m_formatCtx->nb_streams; i++)
|
||||
{
|
||||
#ifdef FFMPEG_OLD_CODE
|
||||
if(m_formatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
|
||||
#else
|
||||
if(m_formatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
|
||||
#endif
|
||||
{
|
||||
StreamInfo info;
|
||||
|
||||
double time_base = av_q2d(m_formatCtx->streams[i]->time_base);
|
||||
|
||||
if(m_formatCtx->streams[i]->start_time != AV_NOPTS_VALUE)
|
||||
info.start = m_formatCtx->streams[i]->start_time * time_base;
|
||||
else
|
||||
info.start = 0;
|
||||
|
||||
if(m_formatCtx->streams[i]->duration != AV_NOPTS_VALUE)
|
||||
info.duration = m_formatCtx->streams[i]->duration * time_base;
|
||||
else if(m_formatCtx->duration != AV_NOPTS_VALUE)
|
||||
info.duration = double(m_formatCtx->duration) / AV_TIME_BASE - info.start;
|
||||
else
|
||||
info.duration = 0;
|
||||
|
||||
#ifdef FFMPEG_OLD_CODE
|
||||
info.specs.channels = Channels(m_formatCtx->streams[i]->codec->channels);
|
||||
info.specs.rate = m_formatCtx->streams[i]->codec->sample_rate;
|
||||
info.specs.format = convertSampleFormat(m_formatCtx->streams[i]->codec->sample_fmt);
|
||||
#else
|
||||
#ifdef FFMPEG_OLD_CH_LAYOUT
|
||||
int channels = m_formatCtx->streams[i]->codecpar->channels;
|
||||
#else
|
||||
int channels = m_formatCtx->streams[i]->codecpar->ch_layout.nb_channels;
|
||||
#endif
|
||||
info.specs.channels = Channels(channels);
|
||||
info.specs.rate = m_formatCtx->streams[i]->codecpar->sample_rate;
|
||||
info.specs.format = convertSampleFormat(AVSampleFormat(m_formatCtx->streams[i]->codecpar->format));
|
||||
#endif
|
||||
|
||||
result.emplace_back(info);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int FFMPEGReader::read_packet(void* opaque, uint8_t* buf, int buf_size)
|
||||
{
|
||||
FFMPEGReader* reader = reinterpret_cast<FFMPEGReader*>(opaque);
|
||||
|
||||
long long size = std::min(static_cast<long long>(buf_size), reader->m_membuffer->getSize() - reader->m_membufferpos);
|
||||
|
||||
if(size <= 0)
|
||||
return AVERROR_EOF;
|
||||
|
||||
std::memcpy(buf, ((data_t*)reader->m_membuffer->getBuffer()) + reader->m_membufferpos, size);
|
||||
reader->m_membufferpos += size;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
int64_t FFMPEGReader::seek_packet(void* opaque, int64_t offset, int whence)
|
||||
{
|
||||
FFMPEGReader* reader = reinterpret_cast<FFMPEGReader*>(opaque);
|
||||
|
||||
switch(whence)
|
||||
{
|
||||
case SEEK_SET:
|
||||
reader->m_membufferpos = 0;
|
||||
break;
|
||||
case SEEK_END:
|
||||
reader->m_membufferpos = reader->m_membuffer->getSize();
|
||||
break;
|
||||
case AVSEEK_SIZE:
|
||||
return reader->m_membuffer->getSize();
|
||||
}
|
||||
|
||||
int64_t position = reader->m_membufferpos + offset;
|
||||
|
||||
if(position > reader->m_membuffer->getSize())
|
||||
position = reader->m_membuffer->getSize();
|
||||
|
||||
reader->m_membufferpos = int(position);
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
bool FFMPEGReader::isSeekable() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void FFMPEGReader::seek(int position)
|
||||
{
|
||||
if(position >= 0)
|
||||
{
|
||||
double pts_time_base = av_q2d(m_formatCtx->streams[m_stream]->time_base);
|
||||
|
||||
int64_t st_time = m_formatCtx->streams[m_stream]->start_time;
|
||||
uint64_t seek_pos = (uint64_t)(position / (pts_time_base * m_specs.rate));
|
||||
|
||||
if(st_time != AV_NOPTS_VALUE)
|
||||
seek_pos += st_time;
|
||||
|
||||
// a value < 0 tells us that seeking failed
|
||||
if(av_seek_frame(m_formatCtx, m_stream, seek_pos, AVSEEK_FLAG_BACKWARD | AVSEEK_FLAG_ANY) >= 0)
|
||||
{
|
||||
avcodec_flush_buffers(m_codecCtx);
|
||||
m_position = position;
|
||||
|
||||
AVPacket packet;
|
||||
bool search = true;
|
||||
|
||||
while(search && av_read_frame(m_formatCtx, &packet) >= 0)
|
||||
{
|
||||
// is it a frame from the audio stream?
|
||||
if(packet.stream_index == m_stream)
|
||||
{
|
||||
// decode the package
|
||||
m_pkgbuf_left = decode(packet, m_pkgbuf);
|
||||
search = false;
|
||||
|
||||
// check position
|
||||
if(packet.pts != AV_NOPTS_VALUE)
|
||||
{
|
||||
// calculate real position, and read to frame!
|
||||
m_position = (packet.pts - (st_time != AV_NOPTS_VALUE ? st_time : 0)) * pts_time_base * m_specs.rate;
|
||||
|
||||
if(m_position < position)
|
||||
{
|
||||
// read until we're at the right position
|
||||
int length = AUD_DEFAULT_BUFFER_SIZE;
|
||||
Buffer buffer(length * AUD_SAMPLE_SIZE(m_specs));
|
||||
bool eos;
|
||||
for(int len = position - m_position; len > 0; len -= AUD_DEFAULT_BUFFER_SIZE)
|
||||
{
|
||||
if(len < AUD_DEFAULT_BUFFER_SIZE)
|
||||
length = len;
|
||||
read(length, eos, buffer.getBuffer());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
av_packet_unref(&packet);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "seeking failed!\n");
|
||||
// Seeking failed, do nothing.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int FFMPEGReader::getLength() const
|
||||
{
|
||||
auto stream = m_formatCtx->streams[m_stream];
|
||||
|
||||
double time_base = av_q2d(stream->time_base);
|
||||
double duration;
|
||||
|
||||
if(stream->duration != AV_NOPTS_VALUE)
|
||||
duration = stream->duration * time_base;
|
||||
else if(m_formatCtx->duration != AV_NOPTS_VALUE)
|
||||
{
|
||||
duration = float(m_formatCtx->duration) / AV_TIME_BASE;
|
||||
|
||||
if(stream->start_time != AV_NOPTS_VALUE)
|
||||
duration -= stream->start_time * time_base;
|
||||
}
|
||||
else
|
||||
duration = -1;
|
||||
|
||||
// return approximated remaning size
|
||||
return (int)(duration * m_codecCtx->sample_rate) - m_position;
|
||||
}
|
||||
|
||||
int FFMPEGReader::getPosition() const
|
||||
{
|
||||
return m_position;
|
||||
}
|
||||
|
||||
Specs FFMPEGReader::getSpecs() const
|
||||
{
|
||||
return m_specs.specs;
|
||||
}
|
||||
|
||||
void FFMPEGReader::read(int& length, bool& eos, sample_t* buffer)
|
||||
{
|
||||
// read packages and decode them
|
||||
AVPacket packet = {};
|
||||
int data_size = 0;
|
||||
int pkgbuf_pos;
|
||||
int left = length;
|
||||
int sample_size = AUD_DEVICE_SAMPLE_SIZE(m_specs);
|
||||
|
||||
sample_t* buf = buffer;
|
||||
pkgbuf_pos = m_pkgbuf_left;
|
||||
m_pkgbuf_left = 0;
|
||||
|
||||
// there may still be data in the buffer from the last call
|
||||
if(pkgbuf_pos > 0)
|
||||
{
|
||||
data_size = std::min(pkgbuf_pos, left * sample_size);
|
||||
m_convert((data_t*) buf, (data_t*) m_pkgbuf.getBuffer(), data_size / AUD_FORMAT_SIZE(m_specs.format));
|
||||
buf += data_size / AUD_FORMAT_SIZE(m_specs.format);
|
||||
left -= data_size / sample_size;
|
||||
}
|
||||
|
||||
// for each frame read as long as there isn't enough data already
|
||||
while((left > 0) && (av_read_frame(m_formatCtx, &packet) >= 0))
|
||||
{
|
||||
// is it a frame from the audio stream?
|
||||
if(packet.stream_index == m_stream)
|
||||
{
|
||||
// decode the package
|
||||
pkgbuf_pos = decode(packet, m_pkgbuf);
|
||||
|
||||
// copy to output buffer
|
||||
data_size = std::min(pkgbuf_pos, left * sample_size);
|
||||
m_convert((data_t*) buf, (data_t*) m_pkgbuf.getBuffer(), data_size / AUD_FORMAT_SIZE(m_specs.format));
|
||||
buf += data_size / AUD_FORMAT_SIZE(m_specs.format);
|
||||
left -= data_size / sample_size;
|
||||
}
|
||||
av_packet_unref(&packet);
|
||||
}
|
||||
// read more data than necessary?
|
||||
if(pkgbuf_pos > data_size)
|
||||
{
|
||||
m_pkgbuf_left = pkgbuf_pos-data_size;
|
||||
memmove(m_pkgbuf.getBuffer(),
|
||||
((data_t*)m_pkgbuf.getBuffer())+data_size,
|
||||
pkgbuf_pos-data_size);
|
||||
}
|
||||
|
||||
if((eos = (left > 0)))
|
||||
length -= left;
|
||||
|
||||
m_position += length;
|
||||
}
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
203
blender-5.2.0/extern/audaspace/plugins/ffmpeg/FFMPEGReader.h
vendored
Normal file
203
blender-5.2.0/extern/audaspace/plugins/ffmpeg/FFMPEGReader.h
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2024 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef FFMPEG_PLUGIN
|
||||
#define AUD_BUILD_PLUGIN
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file FFMPEGReader.h
|
||||
* @ingroup plugin
|
||||
* The FFMPEGReader class.
|
||||
*/
|
||||
|
||||
#include "respec/ConverterFunctions.h"
|
||||
#include "IReader.h"
|
||||
#include "util/Buffer.h"
|
||||
#include "file/FileInfo.h"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
struct AVCodecContext;
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
}
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* This class reads a sound file via ffmpeg.
|
||||
* \warning Seeking may not be accurate! Moreover the position is updated after
|
||||
* a buffer reading call. So calling getPosition right after seek
|
||||
* normally results in a wrong value.
|
||||
*/
|
||||
class AUD_PLUGIN_API FFMPEGReader : public IReader
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* The current position in samples.
|
||||
*/
|
||||
int m_position;
|
||||
|
||||
/**
|
||||
* The specification of the audio data.
|
||||
*/
|
||||
DeviceSpecs m_specs;
|
||||
|
||||
/**
|
||||
* The buffer for package reading.
|
||||
*/
|
||||
Buffer m_pkgbuf;
|
||||
|
||||
/**
|
||||
* The count of samples still available from the last read package.
|
||||
*/
|
||||
int m_pkgbuf_left;
|
||||
|
||||
/**
|
||||
* The AVFormatContext structure for using ffmpeg.
|
||||
*/
|
||||
AVFormatContext* m_formatCtx;
|
||||
|
||||
/**
|
||||
* The AVCodecContext structure for using ffmpeg.
|
||||
*/
|
||||
AVCodecContext* m_codecCtx;
|
||||
|
||||
/**
|
||||
* The AVFrame structure for using ffmpeg.
|
||||
*/
|
||||
AVFrame* m_frame;
|
||||
|
||||
/**
|
||||
* The AVIOContext to read the data from.
|
||||
*/
|
||||
AVIOContext* m_aviocontext;
|
||||
|
||||
/**
|
||||
* The stream ID in the file.
|
||||
*/
|
||||
int m_stream;
|
||||
|
||||
/**
|
||||
* Converter function.
|
||||
*/
|
||||
convert_f m_convert;
|
||||
|
||||
/**
|
||||
* The memory file to read from.
|
||||
*/
|
||||
std::shared_ptr<Buffer> m_membuffer;
|
||||
|
||||
/**
|
||||
* Reading position of the buffer.
|
||||
*/
|
||||
long long m_membufferpos;
|
||||
|
||||
/**
|
||||
* Whether the audio data has to be interleaved after reading.
|
||||
*/
|
||||
bool m_tointerleave;
|
||||
|
||||
/**
|
||||
* Converts an ffmpeg sample format to an audaspace one.
|
||||
* \param format The AVSampleFormat sample format.
|
||||
* \return The sample format as SampleFormat.
|
||||
*/
|
||||
AUD_LOCAL static SampleFormat convertSampleFormat(AVSampleFormat format);
|
||||
|
||||
/**
|
||||
* Decodes a packet into the given buffer.
|
||||
* \param packet The AVPacket to decode.
|
||||
* \param buffer The target buffer.
|
||||
* \return The count of read bytes.
|
||||
*/
|
||||
AUD_LOCAL int decode(AVPacket& packet, Buffer& buffer);
|
||||
|
||||
/**
|
||||
* Initializes the object.
|
||||
* \param stream The index of the audio stream within the file if it contains multiple audio streams.
|
||||
*/
|
||||
AUD_LOCAL void init(int stream);
|
||||
|
||||
// delete copy constructor and operator=
|
||||
FFMPEGReader(const FFMPEGReader&) = delete;
|
||||
FFMPEGReader& operator=(const FFMPEGReader&) = delete;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Creates a new reader.
|
||||
* \param filename The path to the file to be read.
|
||||
* \param stream The index of the audio stream within the file if it contains multiple audio streams.
|
||||
* \exception Exception Thrown if the file specified does not exist or
|
||||
* cannot be read with ffmpeg.
|
||||
*/
|
||||
FFMPEGReader(const std::string &filename, int stream = 0);
|
||||
|
||||
/**
|
||||
* Creates a new reader.
|
||||
* \param buffer The buffer to read from.
|
||||
* \param stream The index of the audio stream within the file if it contains multiple audio streams.
|
||||
* \exception Exception Thrown if the buffer specified cannot be read
|
||||
* with ffmpeg.
|
||||
*/
|
||||
FFMPEGReader(std::shared_ptr<Buffer> buffer, int stream = 0);
|
||||
|
||||
/**
|
||||
* Destroys the reader and closes the file.
|
||||
*/
|
||||
virtual ~FFMPEGReader();
|
||||
|
||||
/**
|
||||
* Queries the streams of a sound file.
|
||||
* \return A vector with as many streams as there are in the file.
|
||||
* \exception Exception Thrown if the file specified cannot be read.
|
||||
*/
|
||||
virtual std::vector<StreamInfo> queryStreams();
|
||||
|
||||
/**
|
||||
* Reads data to a memory buffer.
|
||||
* This function is used for avio only.
|
||||
* @param opaque The FFMPEGReader.
|
||||
* @param buf The buffer to read to.
|
||||
* @param buf_size The size of the buffer.
|
||||
* @return How many bytes have been read.
|
||||
*/
|
||||
static int read_packet(void* opaque, uint8_t* buf, int buf_size);
|
||||
|
||||
/**
|
||||
* Seeks within data.
|
||||
* This function is used for avio only.
|
||||
* @param opaque The FFMPEGReader.
|
||||
* @param offset The byte offset to seek to.
|
||||
* @param whence The seeking action.
|
||||
* @return The current position or the size of the data if requested.
|
||||
*/
|
||||
static int64_t seek_packet(void* opaque, int64_t offset, int whence);
|
||||
|
||||
virtual bool isSeekable() const;
|
||||
virtual void seek(int position);
|
||||
virtual int getLength() const;
|
||||
virtual int getPosition() const;
|
||||
virtual Specs getSpecs() const;
|
||||
virtual void read(int& length, bool& eos, sample_t* buffer);
|
||||
};
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
552
blender-5.2.0/extern/audaspace/plugins/ffmpeg/FFMPEGWriter.cpp
vendored
Normal file
552
blender-5.2.0/extern/audaspace/plugins/ffmpeg/FFMPEGWriter.cpp
vendored
Normal file
@@ -0,0 +1,552 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2024 Jörg Müller
|
||||
*
|
||||
* 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 "FFMPEGWriter.h"
|
||||
#include "Exception.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avio.h>
|
||||
#if LIBAVCODEC_VERSION_MAJOR >= 59
|
||||
#include <libavutil/channel_layout.h>
|
||||
#endif
|
||||
}
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
/* FFmpeg < 4.0 */
|
||||
#if LIBAVCODEC_VERSION_MAJOR < 58
|
||||
#define FFMPEG_OLD_CODE
|
||||
#endif
|
||||
/* FFmpeg < 5.0 */
|
||||
#if LIBAVCODEC_VERSION_MAJOR < 59
|
||||
#define FFMPEG_OLD_CH_LAYOUT
|
||||
#endif
|
||||
|
||||
void FFMPEGWriter::encode()
|
||||
{
|
||||
sample_t* data = m_input_buffer.getBuffer();
|
||||
|
||||
if(m_deinterleave)
|
||||
{
|
||||
m_deinterleave_buffer.assureSize(m_input_buffer.getSize());
|
||||
|
||||
sample_t* dbuf = m_deinterleave_buffer.getBuffer();
|
||||
// deinterleave
|
||||
int single_size = sizeof(sample_t);
|
||||
for(int channel = 0; channel < m_specs.channels; channel++)
|
||||
{
|
||||
for(int i = 0; i < m_input_buffer.getSize() / AUD_SAMPLE_SIZE(m_specs); i++)
|
||||
{
|
||||
std::memcpy(((data_t*)dbuf) + (m_input_samples * channel + i) * single_size,
|
||||
((data_t*)data) + ((m_specs.channels * i) + channel) * single_size, single_size);
|
||||
}
|
||||
}
|
||||
|
||||
// convert first
|
||||
if(m_input_size)
|
||||
m_convert(reinterpret_cast<data_t*>(data), reinterpret_cast<data_t*>(dbuf), m_input_samples * m_specs.channels);
|
||||
else
|
||||
std::memcpy(data, dbuf, m_input_buffer.getSize());
|
||||
}
|
||||
else
|
||||
// convert first
|
||||
if(m_input_size)
|
||||
m_convert(reinterpret_cast<data_t*>(data), reinterpret_cast<data_t*>(data), m_input_samples * m_specs.channels);
|
||||
|
||||
#ifdef FFMPEG_OLD_CODE
|
||||
m_packet->data = nullptr;
|
||||
m_packet->size = 0;
|
||||
|
||||
av_init_packet(m_packet);
|
||||
|
||||
av_frame_unref(m_frame);
|
||||
int got_packet;
|
||||
#endif
|
||||
|
||||
m_frame->nb_samples = m_input_samples;
|
||||
m_frame->format = m_codecCtx->sample_fmt;
|
||||
#ifdef FFMPEG_OLD_CH_LAYOUT
|
||||
m_frame->channel_layout = m_codecCtx->channel_layout;
|
||||
m_frame->channels = m_specs.channels;
|
||||
#else
|
||||
if(av_channel_layout_copy(&m_frame->ch_layout, &m_codecCtx->ch_layout) < 0)
|
||||
AUD_THROW(FileException, "File couldn't be written, couldn't copy audio channel layout.");
|
||||
#endif
|
||||
|
||||
if(avcodec_fill_audio_frame(m_frame, m_specs.channels, m_codecCtx->sample_fmt, reinterpret_cast<data_t*>(data), m_input_buffer.getSize(), 0) < 0)
|
||||
AUD_THROW(FileException, "File couldn't be written, filling the audio frame failed with ffmpeg.");
|
||||
|
||||
AVRational sample_time = { 1, static_cast<int>(m_specs.rate) };
|
||||
m_frame->pts = av_rescale_q(m_position - m_input_samples, m_codecCtx->time_base, sample_time);
|
||||
|
||||
#ifdef FFMPEG_OLD_CODE
|
||||
if(avcodec_encode_audio2(m_codecCtx, m_packet, m_frame, &got_packet))
|
||||
{
|
||||
AUD_THROW(FileException, "File couldn't be written, audio encoding failed with ffmpeg.");
|
||||
}
|
||||
|
||||
if(got_packet)
|
||||
{
|
||||
m_packet->flags |= AV_PKT_FLAG_KEY;
|
||||
m_packet->stream_index = m_stream->index;
|
||||
if(av_write_frame(m_formatCtx, m_packet) < 0)
|
||||
{
|
||||
av_free_packet(m_packet);
|
||||
AUD_THROW(FileException, "Frame couldn't be writen to the file with ffmpeg.");
|
||||
}
|
||||
av_free_packet(m_packet);
|
||||
}
|
||||
#else
|
||||
if(avcodec_send_frame(m_codecCtx, m_frame) < 0)
|
||||
AUD_THROW(FileException, "File couldn't be written, audio encoding failed with ffmpeg.");
|
||||
|
||||
while(avcodec_receive_packet(m_codecCtx, m_packet) == 0)
|
||||
{
|
||||
m_packet->stream_index = m_stream->index;
|
||||
av_packet_rescale_ts(m_packet, m_codecCtx->time_base, m_stream->time_base);
|
||||
if(av_write_frame(m_formatCtx, m_packet) < 0)
|
||||
AUD_THROW(FileException, "Frame couldn't be writen to the file with ffmpeg.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void FFMPEGWriter::close()
|
||||
{
|
||||
#ifdef FFMPEG_OLD_CODE
|
||||
int got_packet = true;
|
||||
|
||||
while(got_packet)
|
||||
{
|
||||
m_packet->data = nullptr;
|
||||
m_packet->size = 0;
|
||||
|
||||
av_init_packet(m_packet);
|
||||
|
||||
if(avcodec_encode_audio2(m_codecCtx, m_packet, nullptr, &got_packet))
|
||||
AUD_THROW(FileException, "File end couldn't be written, audio encoding failed with ffmpeg.");
|
||||
|
||||
if(got_packet)
|
||||
{
|
||||
m_packet->flags |= AV_PKT_FLAG_KEY;
|
||||
m_packet->stream_index = m_stream->index;
|
||||
if(av_write_frame(m_formatCtx, m_packet))
|
||||
{
|
||||
av_free_packet(m_packet);
|
||||
AUD_THROW(FileException, "Final frames couldn't be writen to the file with ffmpeg.");
|
||||
}
|
||||
av_free_packet(m_packet);
|
||||
}
|
||||
}
|
||||
#else
|
||||
if(avcodec_send_frame(m_codecCtx, nullptr) < 0)
|
||||
AUD_THROW(FileException, "File couldn't be written, audio encoding failed with ffmpeg.");
|
||||
|
||||
while(avcodec_receive_packet(m_codecCtx, m_packet) == 0)
|
||||
{
|
||||
m_packet->stream_index = m_stream->index;
|
||||
av_packet_rescale_ts(m_packet, m_codecCtx->time_base, m_stream->time_base);
|
||||
if(av_write_frame(m_formatCtx, m_packet) < 0)
|
||||
AUD_THROW(FileException, "Frame couldn't be writen to the file with ffmpeg.");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
FFMPEGWriter::FFMPEGWriter(const std::string &filename, DeviceSpecs specs, Container format, Codec codec, unsigned int bitrate) :
|
||||
m_position(0),
|
||||
m_specs(specs),
|
||||
m_formatCtx(nullptr),
|
||||
m_codecCtx(nullptr),
|
||||
m_stream(nullptr),
|
||||
m_packet(nullptr),
|
||||
m_frame(nullptr),
|
||||
m_input_samples(0),
|
||||
m_deinterleave(false)
|
||||
{
|
||||
static const char* formats[] = { nullptr, "ac3", "flac", "matroska", "mp2", "mp3", "ogg", "wav", "adts" };
|
||||
|
||||
if(avformat_alloc_output_context2(&m_formatCtx, nullptr, formats[format], filename.c_str()) < 0)
|
||||
AUD_THROW(FileException, "File couldn't be written, format couldn't be found with ffmpeg.");
|
||||
|
||||
const AVOutputFormat* outputFmt = m_formatCtx->oformat;
|
||||
|
||||
if(!outputFmt) {
|
||||
avformat_free_context(m_formatCtx);
|
||||
AUD_THROW(FileException, "File couldn't be written, output format couldn't be found with ffmpeg.");
|
||||
}
|
||||
|
||||
AVCodecID audio_codec = AV_CODEC_ID_NONE;
|
||||
|
||||
switch(codec)
|
||||
{
|
||||
case CODEC_AAC:
|
||||
audio_codec = AV_CODEC_ID_AAC;
|
||||
break;
|
||||
case CODEC_AC3:
|
||||
audio_codec = AV_CODEC_ID_AC3;
|
||||
break;
|
||||
case CODEC_FLAC:
|
||||
audio_codec = AV_CODEC_ID_FLAC;
|
||||
break;
|
||||
case CODEC_MP2:
|
||||
audio_codec = AV_CODEC_ID_MP2;
|
||||
break;
|
||||
case CODEC_MP3:
|
||||
audio_codec = AV_CODEC_ID_MP3;
|
||||
break;
|
||||
case CODEC_OPUS:
|
||||
audio_codec = AV_CODEC_ID_OPUS;
|
||||
break;
|
||||
case CODEC_PCM:
|
||||
switch(specs.format)
|
||||
{
|
||||
case FORMAT_U8:
|
||||
audio_codec = AV_CODEC_ID_PCM_U8;
|
||||
break;
|
||||
case FORMAT_S16:
|
||||
audio_codec = AV_CODEC_ID_PCM_S16LE;
|
||||
break;
|
||||
case FORMAT_S24:
|
||||
audio_codec = AV_CODEC_ID_PCM_S24LE;
|
||||
break;
|
||||
case FORMAT_S32:
|
||||
audio_codec = AV_CODEC_ID_PCM_S32LE;
|
||||
break;
|
||||
case FORMAT_FLOAT32:
|
||||
audio_codec = AV_CODEC_ID_PCM_F32LE;
|
||||
break;
|
||||
case FORMAT_FLOAT64:
|
||||
audio_codec = AV_CODEC_ID_PCM_F64LE;
|
||||
break;
|
||||
default:
|
||||
audio_codec = AV_CODEC_ID_NONE;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case CODEC_VORBIS:
|
||||
audio_codec = AV_CODEC_ID_VORBIS;
|
||||
break;
|
||||
default:
|
||||
audio_codec = AV_CODEC_ID_NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
uint64_t channel_layout = 0;
|
||||
|
||||
switch(m_specs.channels)
|
||||
{
|
||||
case CHANNELS_MONO:
|
||||
channel_layout = AV_CH_LAYOUT_MONO;
|
||||
break;
|
||||
case CHANNELS_STEREO:
|
||||
channel_layout = AV_CH_LAYOUT_STEREO;
|
||||
break;
|
||||
case CHANNELS_STEREO_LFE:
|
||||
channel_layout = AV_CH_LAYOUT_2POINT1;
|
||||
break;
|
||||
case CHANNELS_SURROUND4:
|
||||
channel_layout = AV_CH_LAYOUT_QUAD;
|
||||
break;
|
||||
case CHANNELS_SURROUND5:
|
||||
channel_layout = AV_CH_LAYOUT_5POINT0_BACK;
|
||||
break;
|
||||
case CHANNELS_SURROUND51:
|
||||
channel_layout = AV_CH_LAYOUT_5POINT1_BACK;
|
||||
break;
|
||||
case CHANNELS_SURROUND61:
|
||||
channel_layout = AV_CH_LAYOUT_6POINT1_BACK;
|
||||
break;
|
||||
case CHANNELS_SURROUND71:
|
||||
channel_layout = AV_CH_LAYOUT_7POINT1;
|
||||
break;
|
||||
default:
|
||||
AUD_THROW(FileException, "File couldn't be written, channel layout not supported.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if(audio_codec == AV_CODEC_ID_NONE)
|
||||
AUD_THROW(FileException, "File couldn't be written, audio codec not found with ffmpeg.");
|
||||
|
||||
const AVCodec* codec = avcodec_find_encoder(audio_codec);
|
||||
if(!codec)
|
||||
AUD_THROW(FileException, "File couldn't be written, audio encoder couldn't be found with ffmpeg.");
|
||||
|
||||
m_stream = avformat_new_stream(m_formatCtx, codec);
|
||||
if(!m_stream)
|
||||
AUD_THROW(FileException, "File couldn't be written, stream creation failed with ffmpeg.");
|
||||
|
||||
m_stream->id = m_formatCtx->nb_streams - 1;
|
||||
|
||||
#ifdef FFMPEG_OLD_CODE
|
||||
m_codecCtx = m_stream->codec;
|
||||
#else
|
||||
m_codecCtx = avcodec_alloc_context3(codec);
|
||||
#endif
|
||||
|
||||
if(!m_codecCtx)
|
||||
AUD_THROW(FileException, "File couldn't be written, context creation failed with ffmpeg.");
|
||||
|
||||
switch(m_specs.format)
|
||||
{
|
||||
case FORMAT_U8:
|
||||
m_convert = convert_float_u8;
|
||||
m_codecCtx->sample_fmt = AV_SAMPLE_FMT_U8;
|
||||
break;
|
||||
case FORMAT_S16:
|
||||
m_convert = convert_float_s16;
|
||||
m_codecCtx->sample_fmt = AV_SAMPLE_FMT_S16;
|
||||
break;
|
||||
case FORMAT_S32:
|
||||
m_convert = convert_float_s32;
|
||||
m_codecCtx->sample_fmt = AV_SAMPLE_FMT_S32;
|
||||
break;
|
||||
case FORMAT_FLOAT64:
|
||||
m_convert = convert_float_double;
|
||||
m_codecCtx->sample_fmt = AV_SAMPLE_FMT_DBL;
|
||||
break;
|
||||
default:
|
||||
m_convert = convert_copy<sample_t>;
|
||||
m_codecCtx->sample_fmt = AV_SAMPLE_FMT_FLT;
|
||||
break;
|
||||
}
|
||||
|
||||
if(m_formatCtx->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
m_codecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
|
||||
|
||||
bool format_supported = false;
|
||||
|
||||
for(int i = 0; codec->sample_fmts[i] != -1; i++)
|
||||
{
|
||||
if(av_get_alt_sample_fmt(codec->sample_fmts[i], false) == m_codecCtx->sample_fmt)
|
||||
{
|
||||
m_deinterleave = av_sample_fmt_is_planar(codec->sample_fmts[i]);
|
||||
m_codecCtx->sample_fmt = codec->sample_fmts[i];
|
||||
format_supported = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!format_supported)
|
||||
{
|
||||
int chosen_index = 0;
|
||||
auto chosen = av_get_alt_sample_fmt(codec->sample_fmts[chosen_index], false);
|
||||
for(int i = 1; codec->sample_fmts[i] != -1; i++)
|
||||
{
|
||||
auto fmt = av_get_alt_sample_fmt(codec->sample_fmts[i], false);
|
||||
if((fmt > chosen && chosen < m_codecCtx->sample_fmt) || (fmt > m_codecCtx->sample_fmt && fmt < chosen))
|
||||
{
|
||||
chosen = fmt;
|
||||
chosen_index = i;
|
||||
}
|
||||
}
|
||||
|
||||
m_codecCtx->sample_fmt = codec->sample_fmts[chosen_index];
|
||||
m_deinterleave = av_sample_fmt_is_planar(m_codecCtx->sample_fmt);
|
||||
switch(av_get_alt_sample_fmt(m_codecCtx->sample_fmt, false))
|
||||
{
|
||||
case AV_SAMPLE_FMT_U8:
|
||||
specs.format = FORMAT_U8;
|
||||
m_convert = convert_float_u8;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_S16:
|
||||
specs.format = FORMAT_S16;
|
||||
m_convert = convert_float_s16;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_S32:
|
||||
specs.format = FORMAT_S32;
|
||||
m_convert = convert_float_s32;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_FLT:
|
||||
specs.format = FORMAT_FLOAT32;
|
||||
m_convert = convert_copy<sample_t>;
|
||||
break;
|
||||
case AV_SAMPLE_FMT_DBL:
|
||||
specs.format = FORMAT_FLOAT64;
|
||||
m_convert = convert_float_double;
|
||||
break;
|
||||
default:
|
||||
AUD_THROW(FileException, "File couldn't be written, sample format not supported with ffmpeg.");
|
||||
}
|
||||
}
|
||||
|
||||
m_codecCtx->sample_rate = 0;
|
||||
|
||||
if(codec->supported_samplerates)
|
||||
{
|
||||
for(int i = 0; codec->supported_samplerates[i]; i++)
|
||||
{
|
||||
if(codec->supported_samplerates[i] == m_specs.rate)
|
||||
{
|
||||
m_codecCtx->sample_rate = codec->supported_samplerates[i];
|
||||
break;
|
||||
}
|
||||
else if((codec->supported_samplerates[i] > m_codecCtx->sample_rate && m_specs.rate > m_codecCtx->sample_rate) ||
|
||||
(codec->supported_samplerates[i] < m_codecCtx->sample_rate && m_specs.rate < codec->supported_samplerates[i]))
|
||||
{
|
||||
m_codecCtx->sample_rate = codec->supported_samplerates[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(m_codecCtx->sample_rate == 0)
|
||||
m_codecCtx->sample_rate = m_specs.rate;
|
||||
|
||||
m_specs.rate = m_codecCtx->sample_rate;
|
||||
|
||||
#ifdef FFMPEG_OLD_CODE
|
||||
m_codecCtx->codec_id = audio_codec;
|
||||
#endif
|
||||
|
||||
m_codecCtx->codec_type = AVMEDIA_TYPE_AUDIO;
|
||||
m_codecCtx->bit_rate = bitrate;
|
||||
#ifdef FFMPEG_OLD_CH_LAYOUT
|
||||
m_codecCtx->channel_layout = channel_layout;
|
||||
m_codecCtx->channels = m_specs.channels;
|
||||
#else
|
||||
av_channel_layout_uninit(&m_codecCtx->ch_layout);
|
||||
av_channel_layout_from_mask(&m_codecCtx->ch_layout, channel_layout);
|
||||
#endif
|
||||
m_stream->time_base.num = m_codecCtx->time_base.num = 1;
|
||||
m_stream->time_base.den = m_codecCtx->time_base.den = m_codecCtx->sample_rate;
|
||||
|
||||
if(avcodec_open2(m_codecCtx, codec, nullptr) < 0)
|
||||
AUD_THROW(FileException, "File couldn't be written, encoder couldn't be opened with ffmpeg.");
|
||||
|
||||
#ifndef FFMPEG_OLD_CODE
|
||||
if(avcodec_parameters_from_context(m_stream->codecpar, m_codecCtx) < 0)
|
||||
AUD_THROW(FileException, "File couldn't be written, codec parameters couldn't be copied to the context.");
|
||||
#endif
|
||||
|
||||
int samplesize = std::max(int(AUD_SAMPLE_SIZE(m_specs)), AUD_DEVICE_SAMPLE_SIZE(m_specs));
|
||||
|
||||
if((m_input_size = m_codecCtx->frame_size))
|
||||
m_input_buffer.resize(m_input_size * samplesize);
|
||||
|
||||
if(avio_open(&m_formatCtx->pb, filename.c_str(), AVIO_FLAG_WRITE))
|
||||
AUD_THROW(FileException, "File couldn't be written, file opening failed with ffmpeg.");
|
||||
|
||||
if(avformat_write_header(m_formatCtx, nullptr) < 0)
|
||||
AUD_THROW(FileException, "File couldn't be written, writing the header failed.");
|
||||
}
|
||||
catch(Exception&)
|
||||
{
|
||||
#ifndef FFMPEG_OLD_CODE
|
||||
if(m_codecCtx)
|
||||
avcodec_free_context(&m_codecCtx);
|
||||
#endif
|
||||
avformat_free_context(m_formatCtx);
|
||||
throw;
|
||||
}
|
||||
|
||||
#ifdef FFMPEG_OLD_CODE
|
||||
m_packet = new AVPacket({});
|
||||
#else
|
||||
m_packet = av_packet_alloc();
|
||||
#endif
|
||||
|
||||
m_frame = av_frame_alloc();
|
||||
}
|
||||
|
||||
FFMPEGWriter::~FFMPEGWriter()
|
||||
{
|
||||
// writte missing data
|
||||
if(m_input_samples)
|
||||
encode();
|
||||
|
||||
close();
|
||||
|
||||
av_write_trailer(m_formatCtx);
|
||||
|
||||
if(m_frame)
|
||||
av_frame_free(&m_frame);
|
||||
|
||||
if(m_packet)
|
||||
{
|
||||
#ifdef FFMPEG_OLD_CODE
|
||||
delete m_packet;
|
||||
#else
|
||||
av_packet_free(&m_packet);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef FFMPEG_OLD_CODE
|
||||
avcodec_close(m_codecCtx);
|
||||
#else
|
||||
if(m_codecCtx)
|
||||
avcodec_free_context(&m_codecCtx);
|
||||
#endif
|
||||
|
||||
avio_closep(&m_formatCtx->pb);
|
||||
avformat_free_context(m_formatCtx);
|
||||
}
|
||||
|
||||
int FFMPEGWriter::getPosition() const
|
||||
{
|
||||
return m_position;
|
||||
}
|
||||
|
||||
DeviceSpecs FFMPEGWriter::getSpecs() const
|
||||
{
|
||||
return m_specs;
|
||||
}
|
||||
|
||||
void FFMPEGWriter::write(unsigned int length, sample_t* buffer)
|
||||
{
|
||||
unsigned int samplesize = AUD_SAMPLE_SIZE(m_specs);
|
||||
|
||||
if(m_input_size)
|
||||
{
|
||||
sample_t* inbuf = m_input_buffer.getBuffer();
|
||||
|
||||
while(length)
|
||||
{
|
||||
unsigned int len = std::min(m_input_size - m_input_samples, length);
|
||||
|
||||
std::memcpy(inbuf + m_input_samples * m_specs.channels, buffer, len * samplesize);
|
||||
|
||||
buffer += len * m_specs.channels;
|
||||
m_input_samples += len;
|
||||
m_position += len;
|
||||
length -= len;
|
||||
|
||||
if(m_input_samples == m_input_size)
|
||||
{
|
||||
encode();
|
||||
|
||||
m_input_samples = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
else // PCM data, can write directly!
|
||||
{
|
||||
int samplesize = AUD_SAMPLE_SIZE(m_specs);
|
||||
m_input_buffer.assureSize(length * std::max(AUD_DEVICE_SAMPLE_SIZE(m_specs), samplesize));
|
||||
|
||||
sample_t* buf = m_input_buffer.getBuffer();
|
||||
m_convert(reinterpret_cast<data_t*>(buf), reinterpret_cast<data_t*>(buffer), length * m_specs.channels);
|
||||
|
||||
m_input_samples = length;
|
||||
|
||||
m_position += length;
|
||||
|
||||
encode();
|
||||
}
|
||||
}
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
150
blender-5.2.0/extern/audaspace/plugins/ffmpeg/FFMPEGWriter.h
vendored
Normal file
150
blender-5.2.0/extern/audaspace/plugins/ffmpeg/FFMPEGWriter.h
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2024 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef FFMPEG_PLUGIN
|
||||
#define AUD_BUILD_PLUGIN
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file FFMPEGWriter.h
|
||||
* @ingroup plugin
|
||||
* The FFMPEGWriter class.
|
||||
*/
|
||||
|
||||
#include "respec/ConverterFunctions.h"
|
||||
#include "util/Buffer.h"
|
||||
#include "file/IWriter.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
struct AVCodecContext;
|
||||
extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
}
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* This class writes a sound file via ffmpeg.
|
||||
*/
|
||||
class AUD_PLUGIN_API FFMPEGWriter : public IWriter
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* The current position in samples.
|
||||
*/
|
||||
int m_position;
|
||||
|
||||
/**
|
||||
* The specification of the audio data.
|
||||
*/
|
||||
DeviceSpecs m_specs;
|
||||
|
||||
/**
|
||||
* The AVFormatContext structure for using ffmpeg.
|
||||
*/
|
||||
AVFormatContext* m_formatCtx;
|
||||
|
||||
/**
|
||||
* The AVCodecContext structure for using ffmpeg.
|
||||
*/
|
||||
AVCodecContext* m_codecCtx;
|
||||
|
||||
/**
|
||||
* The AVStream structure for using ffmpeg.
|
||||
*/
|
||||
AVStream* m_stream;
|
||||
|
||||
/**
|
||||
* The AVPacket structure for using ffmpeg.
|
||||
*/
|
||||
AVPacket* m_packet;
|
||||
|
||||
/**
|
||||
* The AVFrame structure for using ffmpeg.
|
||||
*/
|
||||
AVFrame* m_frame;
|
||||
|
||||
/**
|
||||
* The input buffer for the format converted data before encoding.
|
||||
*/
|
||||
Buffer m_input_buffer;
|
||||
|
||||
/**
|
||||
* The buffer used for deinterleaving.
|
||||
*/
|
||||
Buffer m_deinterleave_buffer;
|
||||
|
||||
/**
|
||||
* The count of input samples we have so far.
|
||||
*/
|
||||
unsigned int m_input_samples;
|
||||
|
||||
/**
|
||||
* The count of input samples necessary to encode a packet.
|
||||
*/
|
||||
unsigned int m_input_size;
|
||||
|
||||
/**
|
||||
* Whether the ouput has to be deinterleaved before writing.
|
||||
*/
|
||||
bool m_deinterleave;
|
||||
|
||||
/**
|
||||
* Converter function.
|
||||
*/
|
||||
convert_f m_convert;
|
||||
|
||||
// delete copy constructor and operator=
|
||||
FFMPEGWriter(const FFMPEGWriter&) = delete;
|
||||
FFMPEGWriter& operator=(const FFMPEGWriter&) = delete;
|
||||
|
||||
/**
|
||||
* Encodes to the output buffer.
|
||||
*/
|
||||
AUD_LOCAL void encode();
|
||||
|
||||
/**
|
||||
* Finishes writing to the file.
|
||||
*/
|
||||
AUD_LOCAL void close();
|
||||
|
||||
public:
|
||||
/**
|
||||
* Creates a new writer.
|
||||
* \param filename The path to the file to be read.
|
||||
* \param specs The file's audio specification.
|
||||
* \param format The file's container format.
|
||||
* \param codec The codec used for encoding the audio data.
|
||||
* \param bitrate The bitrate for encoding.
|
||||
* \exception Exception Thrown if the file specified does not exist or
|
||||
* cannot be read with ffmpeg.
|
||||
*/
|
||||
FFMPEGWriter(const std::string &filename, DeviceSpecs specs, Container format, Codec codec, unsigned int bitrate);
|
||||
|
||||
/**
|
||||
* Destroys the writer and closes the file.
|
||||
*/
|
||||
virtual ~FFMPEGWriter();
|
||||
|
||||
virtual int getPosition() const;
|
||||
virtual DeviceSpecs getSpecs() const;
|
||||
virtual void write(unsigned int length, sample_t* buffer);
|
||||
};
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
353
blender-5.2.0/extern/audaspace/plugins/jack/JackDevice.cpp
vendored
Normal file
353
blender-5.2.0/extern/audaspace/plugins/jack/JackDevice.cpp
vendored
Normal file
@@ -0,0 +1,353 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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 "JackDevice.h"
|
||||
#include "JackLibrary.h"
|
||||
#include "devices/DeviceManager.h"
|
||||
#include "devices/IDeviceFactory.h"
|
||||
#include "Exception.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
int JackDevice::jack_mix(jack_nframes_t length, void* data)
|
||||
{
|
||||
JackDevice* device = (JackDevice*) data;
|
||||
int count = device->m_specs.channels;
|
||||
float* buffer;
|
||||
|
||||
jack_position_t position;
|
||||
jack_transport_state_t state = AUD_jack_transport_query(device->m_client, &position);
|
||||
|
||||
if(state == JackTransportStarting)
|
||||
{
|
||||
// play silence while syncing
|
||||
for(int i = 0; i < count; i++)
|
||||
std::memset(AUD_jack_port_get_buffer(device->m_ports[i], length), 0, length * sizeof(float));
|
||||
}
|
||||
else
|
||||
{
|
||||
// ensure that if two consecutive seeks to exactly the same position result in a sync callback call in jack_sync
|
||||
if((state == JackTransportRolling) && (device->m_lastMixState != JackTransportRolling))
|
||||
++device->m_rollingSyncRevision;
|
||||
|
||||
size_t sample_size = AUD_DEVICE_SAMPLE_SIZE(device->m_specs);
|
||||
|
||||
size_t readsamples = device->getRingBuffer().getReadSize();
|
||||
|
||||
readsamples = std::min(readsamples / sample_size, static_cast<size_t>(length));
|
||||
|
||||
data_t* deinterleave_buffer = reinterpret_cast<data_t*>(device->m_deinterleavebuf.getBuffer());
|
||||
|
||||
device->getRingBuffer().read(deinterleave_buffer, readsamples * sample_size);
|
||||
|
||||
if(readsamples < length)
|
||||
std::memset(deinterleave_buffer + readsamples * sample_size, 0, (length - readsamples) * sample_size);
|
||||
|
||||
for(int i = 0; i < count; i++)
|
||||
{
|
||||
buffer = reinterpret_cast<float*>(AUD_jack_port_get_buffer(device->m_ports[i], length));
|
||||
|
||||
for(int j = 0; j < length; j++)
|
||||
buffer[j] = reinterpret_cast<float*>(deinterleave_buffer)[i + j * count];
|
||||
}
|
||||
|
||||
// if we are stopped and the jack transport position changes, we need to notify the mixing thread to call the sync callback
|
||||
if(state == JackTransportStopped)
|
||||
{
|
||||
float syncTime = position.frame / (float) position.frame_rate;
|
||||
|
||||
if(syncTime != device->m_syncTime)
|
||||
{
|
||||
device->m_syncTime = syncTime;
|
||||
++device->m_syncCallRevision;
|
||||
}
|
||||
}
|
||||
|
||||
device->notifyMixingThread();
|
||||
}
|
||||
|
||||
device->m_lastMixState = state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int JackDevice::jack_sync(jack_transport_state_t state, jack_position_t* pos, void* data)
|
||||
{
|
||||
JackDevice* device = (JackDevice*)data;
|
||||
|
||||
// we return immediately when the state is stopped as this is handled in the mixing thread separately, as not all stops result in a call here from jack.
|
||||
if(state == JackTransportStopped)
|
||||
return 1;
|
||||
|
||||
float syncTime = pos->frame / (float) pos->frame_rate;
|
||||
|
||||
// We need to call the sync callback in the mixing thread if
|
||||
// - the sync time is different, i.e., a new sync to a different time is done
|
||||
// - if the last state is stopped, i.e., we are starting playback
|
||||
// - if the sync time is the same but the rolling revision is increased, i.e., we are syncing repeatedly to the same time (happens especially when jumping back to the start)
|
||||
if((syncTime != device->m_syncTime) || (device->m_lastMixState == JackTransportStopped) || (device->m_rollingSyncRevision != device->m_lastRollingSyncRevision))
|
||||
{
|
||||
device->m_syncTime = syncTime;
|
||||
++device->m_syncCallRevision;
|
||||
device->notifyMixingThread();
|
||||
device->m_lastRollingSyncRevision = device->m_rollingSyncRevision;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return device->m_syncCallRevision == device->m_lastSyncCallRevision;
|
||||
}
|
||||
|
||||
void JackDevice::preMixingWork([[maybe_unused]] bool playing)
|
||||
{
|
||||
jack_transport_state_t state;
|
||||
jack_position_t position;
|
||||
|
||||
state = AUD_jack_transport_query(m_client, &position);
|
||||
|
||||
// we sync either when:
|
||||
// - there was a jack sync callback that requests a playing sync (either start playback or seek during playback) - caused by a m_syncCallRevision change in jack_sync
|
||||
// - the jack transport state changed to stop from not stopped (i.e. external stopping) - checked here
|
||||
// - the sync time changes when seeking during the stopped state - caused by a m_syncCallRevision change in jack_mix
|
||||
if((m_syncCallRevision != m_lastSyncCallRevision) || (state == JackTransportStopped && m_lastState != JackTransportStopped))
|
||||
{
|
||||
int syncRevision = m_syncCallRevision;
|
||||
float syncTime = m_syncTime;
|
||||
|
||||
if(m_syncFunc)
|
||||
m_syncFunc(m_syncFuncData, state != JackTransportStopped, syncTime);
|
||||
|
||||
// we reset the ring buffer when we sync to start from the correct position
|
||||
getRingBuffer().reset();
|
||||
|
||||
m_lastSyncCallRevision = syncRevision;
|
||||
}
|
||||
|
||||
m_lastState = state;
|
||||
}
|
||||
|
||||
void JackDevice::jack_shutdown(void* data)
|
||||
{
|
||||
JackDevice* device = (JackDevice*)data;
|
||||
device->stopMixingThread();
|
||||
}
|
||||
|
||||
JackDevice::JackDevice(const std::string& name, DeviceSpecs specs, int buffersize)
|
||||
{
|
||||
if(specs.channels == CHANNELS_INVALID)
|
||||
specs.channels = CHANNELS_STEREO;
|
||||
|
||||
// jack uses floats
|
||||
m_specs = specs;
|
||||
m_specs.format = FORMAT_FLOAT32;
|
||||
|
||||
jack_options_t options = JackNullOption;
|
||||
jack_status_t status;
|
||||
|
||||
// open client
|
||||
m_client = AUD_jack_client_open(name.c_str(), options, &status);
|
||||
if(m_client == nullptr)
|
||||
AUD_THROW(DeviceException, "Connecting to the JACK server failed.");
|
||||
|
||||
// set callbacks
|
||||
AUD_jack_set_process_callback(m_client, JackDevice::jack_mix, this);
|
||||
AUD_jack_on_shutdown(m_client, JackDevice::jack_shutdown, this);
|
||||
AUD_jack_set_sync_callback(m_client, JackDevice::jack_sync, this);
|
||||
|
||||
// register our output channels which are called ports in jack
|
||||
m_ports = new jack_port_t*[m_specs.channels];
|
||||
|
||||
try
|
||||
{
|
||||
char portname[64];
|
||||
for(int i = 0; i < m_specs.channels; i++)
|
||||
{
|
||||
sprintf(portname, "out %d", i+1);
|
||||
m_ports[i] = AUD_jack_port_register(m_client, portname, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
|
||||
if(m_ports[i] == nullptr)
|
||||
AUD_THROW(DeviceException, "Registering output port with JACK failed.");
|
||||
}
|
||||
}
|
||||
catch(Exception&)
|
||||
{
|
||||
AUD_jack_client_close(m_client);
|
||||
delete[] m_ports;
|
||||
throw;
|
||||
}
|
||||
|
||||
m_specs.rate = (SampleRate)AUD_jack_get_sample_rate(m_client);
|
||||
|
||||
if(buffersize < 0)
|
||||
buffersize = AUD_jack_get_buffer_size(m_client) * 2;
|
||||
|
||||
buffersize *= AUD_SAMPLE_SIZE(m_specs);
|
||||
m_deinterleavebuf.resize(buffersize);
|
||||
|
||||
create();
|
||||
|
||||
m_lastState = JackTransportStopped;
|
||||
m_lastMixState = JackTransportStopped;
|
||||
m_syncFunc = nullptr;
|
||||
m_syncTime = 0;
|
||||
m_syncCallRevision = 0;
|
||||
m_lastSyncCallRevision = 0;
|
||||
m_rollingSyncRevision = 0;
|
||||
m_lastRollingSyncRevision = 0;
|
||||
|
||||
// activate the client
|
||||
if(AUD_jack_activate(m_client))
|
||||
{
|
||||
AUD_jack_client_close(m_client);
|
||||
delete[] m_ports;
|
||||
destroy();
|
||||
|
||||
AUD_THROW(DeviceException, "Client activation with JACK failed.");
|
||||
}
|
||||
|
||||
const char** ports = AUD_jack_get_ports(m_client, nullptr, nullptr,
|
||||
JackPortIsPhysical | JackPortIsInput);
|
||||
if(ports != nullptr)
|
||||
{
|
||||
for(int i = 0; i < m_specs.channels && ports[i]; i++)
|
||||
AUD_jack_connect(m_client, AUD_jack_port_name(m_ports[i]), ports[i]);
|
||||
|
||||
AUD_jack_free(ports);
|
||||
}
|
||||
|
||||
startMixingThread(buffersize);
|
||||
}
|
||||
|
||||
JackDevice::~JackDevice()
|
||||
{
|
||||
if(isMixingThreadRunning())
|
||||
{
|
||||
stopMixingThread();
|
||||
AUD_jack_client_close(m_client);
|
||||
}
|
||||
|
||||
delete[] m_ports;
|
||||
|
||||
destroy();
|
||||
}
|
||||
|
||||
void JackDevice::playing(bool playing)
|
||||
{
|
||||
MixingThreadDevice::playing(playing);
|
||||
}
|
||||
|
||||
void JackDevice::playSynchronizer()
|
||||
{
|
||||
AUD_jack_transport_start(m_client);
|
||||
}
|
||||
|
||||
void JackDevice::stopSynchronizer()
|
||||
{
|
||||
AUD_jack_transport_stop(m_client);
|
||||
}
|
||||
|
||||
void JackDevice::seekSynchronizer(double time)
|
||||
{
|
||||
if(time >= 0.0f)
|
||||
AUD_jack_transport_locate(m_client, time * m_specs.rate);
|
||||
}
|
||||
|
||||
void JackDevice::setSyncCallback(syncFunction sync, void* data)
|
||||
{
|
||||
m_syncFunc = sync;
|
||||
m_syncFuncData = data;
|
||||
}
|
||||
|
||||
double JackDevice::getSynchronizerPosition()
|
||||
{
|
||||
jack_position_t position;
|
||||
jack_transport_state_t state = AUD_jack_transport_query(m_client, &position);
|
||||
double result = position.frame / (double) position.frame_rate;
|
||||
|
||||
if(state == JackTransportRolling)
|
||||
{
|
||||
result += AUD_jack_frames_since_cycle_start(m_client) / (double) position.frame_rate;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int JackDevice::isSynchronizerPlaying()
|
||||
{
|
||||
return AUD_jack_transport_query(m_client, nullptr);
|
||||
}
|
||||
|
||||
class JackDeviceFactory : public IDeviceFactory
|
||||
{
|
||||
private:
|
||||
DeviceSpecs m_specs;
|
||||
int m_buffersize;
|
||||
std::string m_name;
|
||||
|
||||
public:
|
||||
JackDeviceFactory() : m_buffersize(-1), m_name("Audaspace")
|
||||
{
|
||||
m_specs.format = FORMAT_FLOAT32;
|
||||
m_specs.channels = CHANNELS_STEREO;
|
||||
m_specs.rate = RATE_48000;
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<IDevice> openDevice()
|
||||
{
|
||||
return std::shared_ptr<IDevice>(new JackDevice(m_name, m_specs, m_buffersize));
|
||||
}
|
||||
|
||||
virtual int getPriority()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual void setSpecs(DeviceSpecs specs)
|
||||
{
|
||||
m_specs = specs;
|
||||
}
|
||||
|
||||
virtual void setBufferSize(int buffersize)
|
||||
{
|
||||
m_buffersize = buffersize;
|
||||
}
|
||||
|
||||
virtual void setName(const std::string &name)
|
||||
{
|
||||
m_name = name;
|
||||
}
|
||||
};
|
||||
|
||||
void JackDevice::registerPlugin()
|
||||
{
|
||||
if(loadJACK())
|
||||
DeviceManager::registerDevice("JACK", std::shared_ptr<IDeviceFactory>(new JackDeviceFactory));
|
||||
}
|
||||
|
||||
#ifdef JACK_PLUGIN
|
||||
extern "C" AUD_PLUGIN_API void registerPlugin()
|
||||
{
|
||||
JackDevice::registerPlugin();
|
||||
}
|
||||
|
||||
extern "C" AUD_PLUGIN_API const char* getName()
|
||||
{
|
||||
return "JACK";
|
||||
}
|
||||
#endif
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
191
blender-5.2.0/extern/audaspace/plugins/jack/JackDevice.h
vendored
Normal file
191
blender-5.2.0/extern/audaspace/plugins/jack/JackDevice.h
vendored
Normal file
@@ -0,0 +1,191 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef JACK_PLUGIN
|
||||
#define AUD_BUILD_PLUGIN
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file JackDevice.h
|
||||
* @ingroup plugin
|
||||
* The JackDevice class.
|
||||
*/
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <jack/jack.h>
|
||||
|
||||
#include "devices/MixingThreadDevice.h"
|
||||
#include "util/Buffer.h"
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* This device plays back through JACK.
|
||||
*/
|
||||
class AUD_PLUGIN_API JackDevice : public MixingThreadDevice
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* The output ports of jack.
|
||||
*/
|
||||
jack_port_t** m_ports;
|
||||
|
||||
/**
|
||||
* The jack client.
|
||||
*/
|
||||
jack_client_t* m_client;
|
||||
|
||||
/**
|
||||
* The deinterleaving buffer.
|
||||
*/
|
||||
Buffer m_deinterleavebuf;
|
||||
|
||||
/**
|
||||
* Invalidates the jack device.
|
||||
* \param data The jack device that gets invalidet by jack.
|
||||
*/
|
||||
AUD_LOCAL static void jack_shutdown(void* data);
|
||||
|
||||
/**
|
||||
* Mixes the next bytes into the buffer.
|
||||
* \param length The length in samples to be filled.
|
||||
* \param data A pointer to the jack device.
|
||||
* \return 0 what shows success.
|
||||
*/
|
||||
AUD_LOCAL static int jack_mix(jack_nframes_t length, void* data);
|
||||
|
||||
AUD_LOCAL static int jack_sync(jack_transport_state_t state, jack_position_t* pos, void* data);
|
||||
|
||||
/**
|
||||
* Last known JACK Transport state used for stop callbacks.
|
||||
*/
|
||||
jack_transport_state_t m_lastState;
|
||||
|
||||
/**
|
||||
* Last known JACK Transport state used for stop callbacks.
|
||||
*/
|
||||
jack_transport_state_t m_lastMixState;
|
||||
|
||||
/**
|
||||
* Time for a synchronisation request.
|
||||
*/
|
||||
std::atomic<float> m_syncTime;
|
||||
|
||||
/**
|
||||
* Sync revision used to notify the mixing thread that a sync call is necessary.
|
||||
*/
|
||||
std::atomic<int> m_syncCallRevision;
|
||||
|
||||
/**
|
||||
* The sync revision that the last sync call in the mixing thread handled.
|
||||
*/
|
||||
std::atomic<int> m_lastSyncCallRevision;
|
||||
|
||||
/**
|
||||
* Sync revision that is increased every time jack transport enters the rolling state.
|
||||
*/
|
||||
int m_rollingSyncRevision;
|
||||
|
||||
/**
|
||||
* The last time the jack_sync callback saw the rolling sync revision.
|
||||
*
|
||||
* Used to ensure the sync callback will be called when consecutive syncs target the same sync time.
|
||||
*/
|
||||
int m_lastRollingSyncRevision;
|
||||
|
||||
/**
|
||||
* External syncronisation callback function.
|
||||
*/
|
||||
syncFunction m_syncFunc;
|
||||
|
||||
/**
|
||||
* Data for the sync function.
|
||||
*/
|
||||
void* m_syncFuncData;
|
||||
|
||||
AUD_LOCAL void preMixingWork(bool playing) override;
|
||||
|
||||
// delete copy constructor and operator=
|
||||
JackDevice(const JackDevice&) = delete;
|
||||
JackDevice& operator=(const JackDevice&) = delete;
|
||||
|
||||
protected:
|
||||
virtual void playing(bool playing);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Creates a JACK client for audio output.
|
||||
* \param name The client name.
|
||||
* \param specs The wanted audio specification, where only the channel count
|
||||
* is important.
|
||||
* \param buffersize The size of the internal buffer.
|
||||
* \exception Exception Thrown if the audio device cannot be opened.
|
||||
*/
|
||||
JackDevice(const std::string &name, DeviceSpecs specs, int buffersize = AUD_DEFAULT_BUFFER_SIZE);
|
||||
|
||||
/**
|
||||
* Closes the JACK client.
|
||||
*/
|
||||
virtual ~JackDevice();
|
||||
|
||||
/**
|
||||
* Starts jack transport playback.
|
||||
*/
|
||||
void playSynchronizer();
|
||||
|
||||
/**
|
||||
* Stops jack transport playback.
|
||||
*/
|
||||
void stopSynchronizer();
|
||||
|
||||
/**
|
||||
* Seeks jack transport playback.
|
||||
* \param time The time to seek to.
|
||||
*/
|
||||
void seekSynchronizer(double time);
|
||||
|
||||
/**
|
||||
* Sets the sync callback for jack transport playback.
|
||||
* \param sync The callback function.
|
||||
* \param data The data for the function.
|
||||
*/
|
||||
void setSyncCallback(syncFunction sync, void* data);
|
||||
|
||||
/**
|
||||
* Retrieves the jack transport playback time.
|
||||
* \return The current time position.
|
||||
*/
|
||||
double getSynchronizerPosition();
|
||||
|
||||
/**
|
||||
* Returns whether jack transport plays back.
|
||||
* \return Whether jack transport plays back.
|
||||
*/
|
||||
int isSynchronizerPlaying();
|
||||
|
||||
/**
|
||||
* Registers this plugin.
|
||||
*/
|
||||
static void registerPlugin();
|
||||
};
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
59
blender-5.2.0/extern/audaspace/plugins/jack/JackLibrary.cpp
vendored
Normal file
59
blender-5.2.0/extern/audaspace/plugins/jack/JackLibrary.cpp
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#define JACK_LIBRARY_IMPLEMENTATION
|
||||
|
||||
#include <string>
|
||||
#include <array>
|
||||
|
||||
#include "JackLibrary.h"
|
||||
|
||||
#ifdef DYNLOAD_JACK
|
||||
#include "plugin/PluginManager.h"
|
||||
#endif
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
bool loadJACK()
|
||||
{
|
||||
#ifdef DYNLOAD_JACK
|
||||
std::array<const std::string, 5> names = {"libjack.so", "libjack.so.0", "libjack.so.1", "libjack.so.2", "libjack.dll"};
|
||||
|
||||
void* handle = nullptr;
|
||||
|
||||
for(auto& name : names)
|
||||
{
|
||||
handle = PluginManager::openLibrary(name);
|
||||
if(handle)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!handle)
|
||||
return false;
|
||||
|
||||
#define JACK_SYMBOL(sym) AUD_##sym = reinterpret_cast<decltype(&sym)>(PluginManager::lookupLibrary(handle, #sym))
|
||||
#else
|
||||
#define JACK_SYMBOL(sym) AUD_##sym = &sym
|
||||
#endif
|
||||
|
||||
#include "JackSymbols.h"
|
||||
|
||||
#undef JACK_SYMBOL
|
||||
|
||||
return AUD_jack_client_open != nullptr;
|
||||
}
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
47
blender-5.2.0/extern/audaspace/plugins/jack/JackLibrary.h
vendored
Normal file
47
blender-5.2.0/extern/audaspace/plugins/jack/JackLibrary.h
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef JACK_PLUGIN
|
||||
#define AUD_BUILD_PLUGIN
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file JackLibrary.h
|
||||
* @ingroup plugin
|
||||
*/
|
||||
|
||||
#include "Audaspace.h"
|
||||
|
||||
#include <jack/jack.h>
|
||||
#include <jack/ringbuffer.h>
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
#ifdef JACK_LIBRARY_IMPLEMENTATION
|
||||
#define JACK_SYMBOL(sym) decltype(&sym) AUD_##sym
|
||||
#else
|
||||
#define JACK_SYMBOL(sym) extern decltype(&sym) AUD_##sym
|
||||
#endif
|
||||
|
||||
#include "JackSymbols.h"
|
||||
|
||||
#undef JACK_SYMBOL
|
||||
|
||||
bool loadJACK();
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
47
blender-5.2.0/extern/audaspace/plugins/jack/JackSymbols.h
vendored
Normal file
47
blender-5.2.0/extern/audaspace/plugins/jack/JackSymbols.h
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
JACK_SYMBOL(jack_frames_since_cycle_start);
|
||||
JACK_SYMBOL(jack_transport_query);
|
||||
JACK_SYMBOL(jack_transport_locate);
|
||||
|
||||
JACK_SYMBOL(jack_transport_start);
|
||||
JACK_SYMBOL(jack_transport_stop);
|
||||
|
||||
JACK_SYMBOL(jack_ringbuffer_reset);
|
||||
JACK_SYMBOL(jack_ringbuffer_write);
|
||||
JACK_SYMBOL(jack_ringbuffer_write_space);
|
||||
JACK_SYMBOL(jack_ringbuffer_write_advance);
|
||||
JACK_SYMBOL(jack_ringbuffer_read);
|
||||
JACK_SYMBOL(jack_ringbuffer_create);
|
||||
JACK_SYMBOL(jack_ringbuffer_free);
|
||||
JACK_SYMBOL(jack_ringbuffer_read_space);
|
||||
JACK_SYMBOL(jack_set_sync_callback);
|
||||
|
||||
JACK_SYMBOL(jack_port_get_buffer);
|
||||
|
||||
JACK_SYMBOL(jack_client_open);
|
||||
JACK_SYMBOL(jack_set_process_callback);
|
||||
JACK_SYMBOL(jack_on_shutdown);
|
||||
JACK_SYMBOL(jack_port_register);
|
||||
JACK_SYMBOL(jack_client_close);
|
||||
JACK_SYMBOL(jack_get_sample_rate);
|
||||
JACK_SYMBOL(jack_get_buffer_size);
|
||||
JACK_SYMBOL(jack_activate);
|
||||
JACK_SYMBOL(jack_get_ports);
|
||||
JACK_SYMBOL(jack_port_name);
|
||||
JACK_SYMBOL(jack_connect);
|
||||
JACK_SYMBOL(jack_free);
|
||||
72
blender-5.2.0/extern/audaspace/plugins/libsndfile/SndFile.cpp
vendored
Normal file
72
blender-5.2.0/extern/audaspace/plugins/libsndfile/SndFile.cpp
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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 "SndFile.h"
|
||||
#include "SndFileReader.h"
|
||||
#include "SndFileWriter.h"
|
||||
#include "file/FileManager.h"
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
SndFile::SndFile()
|
||||
{
|
||||
}
|
||||
|
||||
void SndFile::registerPlugin()
|
||||
{
|
||||
std::shared_ptr<SndFile> plugin = std::shared_ptr<SndFile>(new SndFile);
|
||||
FileManager::registerInput(plugin);
|
||||
FileManager::registerOutput(plugin);
|
||||
}
|
||||
|
||||
std::shared_ptr<IReader> SndFile::createReader(const std::string &filename, int stream)
|
||||
{
|
||||
return std::shared_ptr<IReader>(new SndFileReader(filename));
|
||||
}
|
||||
|
||||
std::shared_ptr<IReader> SndFile::createReader(std::shared_ptr<Buffer> buffer, int stream)
|
||||
{
|
||||
return std::shared_ptr<IReader>(new SndFileReader(buffer));
|
||||
}
|
||||
|
||||
std::vector<StreamInfo> SndFile::queryStreams(const std::string &filename)
|
||||
{
|
||||
return SndFileReader(filename).queryStreams();
|
||||
}
|
||||
|
||||
std::vector<StreamInfo> SndFile::queryStreams(std::shared_ptr<Buffer> buffer)
|
||||
{
|
||||
return SndFileReader(buffer).queryStreams();
|
||||
}
|
||||
|
||||
std::shared_ptr<IWriter> SndFile::createWriter(const std::string &filename, DeviceSpecs specs, Container format, Codec codec, unsigned int bitrate)
|
||||
{
|
||||
return std::shared_ptr<IWriter>(new SndFileWriter(filename, specs, format, codec, bitrate));
|
||||
}
|
||||
|
||||
#ifdef LIBSNDFILE_PLUGIN
|
||||
extern "C" AUD_PLUGIN_API void registerPlugin()
|
||||
{
|
||||
SndFile::registerPlugin();
|
||||
}
|
||||
|
||||
extern "C" AUD_PLUGIN_API const char* getName()
|
||||
{
|
||||
return "LibSndFile";
|
||||
}
|
||||
#endif
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
62
blender-5.2.0/extern/audaspace/plugins/libsndfile/SndFile.h
vendored
Normal file
62
blender-5.2.0/extern/audaspace/plugins/libsndfile/SndFile.h
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef LIBSNDFILE_PLUGIN
|
||||
#define AUD_BUILD_PLUGIN
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file SndFile.h
|
||||
* @ingroup plugin
|
||||
* The SndFile class.
|
||||
*/
|
||||
|
||||
#include "file/IFileInput.h"
|
||||
#include "file/IFileOutput.h"
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* This plugin class reads and writes sounds via libsndfile.
|
||||
*/
|
||||
class AUD_PLUGIN_API SndFile : public IFileInput, public IFileOutput
|
||||
{
|
||||
private:
|
||||
// delete copy constructor and operator=
|
||||
SndFile(const SndFile&) = delete;
|
||||
SndFile& operator=(const SndFile&) = delete;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Creates a new libsndfile plugin.
|
||||
*/
|
||||
SndFile();
|
||||
|
||||
/**
|
||||
* Registers this plugin.
|
||||
*/
|
||||
static void registerPlugin();
|
||||
|
||||
virtual std::shared_ptr<IReader> createReader(const std::string &filename, int stream = 0);
|
||||
virtual std::shared_ptr<IReader> createReader(std::shared_ptr<Buffer> buffer, int stream = 0);
|
||||
virtual std::vector<StreamInfo> queryStreams(const std::string &filename);
|
||||
virtual std::vector<StreamInfo> queryStreams(std::shared_ptr<Buffer> buffer);
|
||||
virtual std::shared_ptr<IWriter> createWriter(const std::string &filename, DeviceSpecs specs, Container format, Codec codec, unsigned int bitrate);
|
||||
};
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
176
blender-5.2.0/extern/audaspace/plugins/libsndfile/SndFileReader.cpp
vendored
Normal file
176
blender-5.2.0/extern/audaspace/plugins/libsndfile/SndFileReader.cpp
vendored
Normal file
@@ -0,0 +1,176 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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 "SndFileReader.h"
|
||||
#include "util/Buffer.h"
|
||||
#include "Exception.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
sf_count_t SndFileReader::vio_get_filelen(void* user_data)
|
||||
{
|
||||
SndFileReader* reader = (SndFileReader*)user_data;
|
||||
return reader->m_membuffer->getSize();
|
||||
}
|
||||
|
||||
sf_count_t SndFileReader::vio_seek(sf_count_t offset, int whence,
|
||||
void* user_data)
|
||||
{
|
||||
SndFileReader* reader = (SndFileReader*)user_data;
|
||||
|
||||
switch(whence)
|
||||
{
|
||||
case SEEK_SET:
|
||||
reader->m_memoffset = offset;
|
||||
break;
|
||||
case SEEK_CUR:
|
||||
reader->m_memoffset = reader->m_memoffset + offset;
|
||||
break;
|
||||
case SEEK_END:
|
||||
reader->m_memoffset = reader->m_membuffer->getSize() + offset;
|
||||
break;
|
||||
}
|
||||
|
||||
return reader->m_memoffset;
|
||||
}
|
||||
|
||||
sf_count_t SndFileReader::vio_read(void* ptr, sf_count_t count,
|
||||
void* user_data)
|
||||
{
|
||||
SndFileReader* reader = (SndFileReader*)user_data;
|
||||
|
||||
if(reader->m_memoffset + count > reader->m_membuffer->getSize())
|
||||
count = reader->m_membuffer->getSize() - reader->m_memoffset;
|
||||
|
||||
std::memcpy(ptr, ((data_t*)reader->m_membuffer->getBuffer()) +
|
||||
reader->m_memoffset, count);
|
||||
reader->m_memoffset += count;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
sf_count_t SndFileReader::vio_tell(void* user_data)
|
||||
{
|
||||
SndFileReader* reader = (SndFileReader*)user_data;
|
||||
|
||||
return reader->m_memoffset;
|
||||
}
|
||||
|
||||
SndFileReader::SndFileReader(const std::string &filename) :
|
||||
m_position(0)
|
||||
{
|
||||
SF_INFO sfinfo;
|
||||
|
||||
sfinfo.format = 0;
|
||||
m_sndfile = sf_open(filename.c_str(), SFM_READ, &sfinfo);
|
||||
|
||||
if(!m_sndfile)
|
||||
AUD_THROW(FileException, "The file couldn't be opened with libsndfile.");
|
||||
|
||||
m_specs.channels = (Channels) sfinfo.channels;
|
||||
m_specs.rate = (SampleRate) sfinfo.samplerate;
|
||||
m_length = sfinfo.frames;
|
||||
m_seekable = sfinfo.seekable;
|
||||
}
|
||||
|
||||
SndFileReader::SndFileReader(std::shared_ptr<Buffer> buffer) :
|
||||
m_position(0),
|
||||
m_membuffer(buffer),
|
||||
m_memoffset(0)
|
||||
{
|
||||
m_vio.get_filelen = vio_get_filelen;
|
||||
m_vio.read = vio_read;
|
||||
m_vio.seek = vio_seek;
|
||||
m_vio.tell = vio_tell;
|
||||
m_vio.write = nullptr;
|
||||
|
||||
SF_INFO sfinfo;
|
||||
|
||||
sfinfo.format = 0;
|
||||
m_sndfile = sf_open_virtual(&m_vio, SFM_READ, &sfinfo, this);
|
||||
|
||||
if(!m_sndfile)
|
||||
AUD_THROW(FileException, "The buffer couldn't be read with libsndfile.");
|
||||
|
||||
m_specs.channels = (Channels) sfinfo.channels;
|
||||
m_specs.rate = (SampleRate) sfinfo.samplerate;
|
||||
m_length = sfinfo.frames;
|
||||
m_seekable = sfinfo.seekable;
|
||||
}
|
||||
|
||||
SndFileReader::~SndFileReader()
|
||||
{
|
||||
sf_close(m_sndfile);
|
||||
}
|
||||
|
||||
std::vector<StreamInfo> SndFileReader::queryStreams()
|
||||
{
|
||||
std::vector<StreamInfo> result;
|
||||
|
||||
StreamInfo info;
|
||||
info.start = 0;
|
||||
info.duration = double(getLength()) / m_specs.rate;
|
||||
info.specs.specs = m_specs;
|
||||
info.specs.format = FORMAT_FLOAT32;
|
||||
|
||||
result.emplace_back(info);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SndFileReader::isSeekable() const
|
||||
{
|
||||
return m_seekable;
|
||||
}
|
||||
|
||||
void SndFileReader::seek(int position)
|
||||
{
|
||||
if(m_seekable)
|
||||
{
|
||||
position = sf_seek(m_sndfile, position, SEEK_SET);
|
||||
m_position = position;
|
||||
}
|
||||
}
|
||||
|
||||
int SndFileReader::getLength() const
|
||||
{
|
||||
return m_length;
|
||||
}
|
||||
|
||||
int SndFileReader::getPosition() const
|
||||
{
|
||||
return m_position;
|
||||
}
|
||||
|
||||
Specs SndFileReader::getSpecs() const
|
||||
{
|
||||
return m_specs;
|
||||
}
|
||||
|
||||
void SndFileReader::read(int& length, bool& eos, sample_t* buffer)
|
||||
{
|
||||
int olen = length;
|
||||
|
||||
length = sf_readf_float(m_sndfile, buffer, length);
|
||||
|
||||
m_position += length;
|
||||
|
||||
eos = length < olen;
|
||||
}
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
137
blender-5.2.0/extern/audaspace/plugins/libsndfile/SndFileReader.h
vendored
Normal file
137
blender-5.2.0/extern/audaspace/plugins/libsndfile/SndFileReader.h
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IReader.h"
|
||||
|
||||
#ifdef LIBSNDFILE_PLUGIN
|
||||
#define AUD_BUILD_PLUGIN
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file SndFileReader.h
|
||||
* @ingroup plugin
|
||||
* The SndFileReader class.
|
||||
*/
|
||||
|
||||
#include "file/FileInfo.h"
|
||||
|
||||
#include <string>
|
||||
#include <sndfile.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
class Buffer;
|
||||
|
||||
/**
|
||||
* This class reads a sound file via libsndfile.
|
||||
*/
|
||||
class AUD_PLUGIN_API SndFileReader : public IReader
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* The current position in samples.
|
||||
*/
|
||||
int m_position;
|
||||
|
||||
/**
|
||||
* The sample count in the file.
|
||||
*/
|
||||
int m_length;
|
||||
|
||||
/**
|
||||
* Whether the file is seekable.
|
||||
*/
|
||||
bool m_seekable;
|
||||
|
||||
/**
|
||||
* The specification of the audio data.
|
||||
*/
|
||||
Specs m_specs;
|
||||
|
||||
/**
|
||||
* The sndfile.
|
||||
*/
|
||||
SNDFILE* m_sndfile;
|
||||
|
||||
/**
|
||||
* The virtual IO structure for memory file reading.
|
||||
*/
|
||||
SF_VIRTUAL_IO m_vio;
|
||||
|
||||
/**
|
||||
* The pointer to the memory file.
|
||||
*/
|
||||
std::shared_ptr<Buffer> m_membuffer;
|
||||
|
||||
/**
|
||||
* The current reading pointer of the memory file.
|
||||
*/
|
||||
int m_memoffset;
|
||||
|
||||
// Functions for libsndfile virtual IO functionality
|
||||
AUD_LOCAL static sf_count_t vio_get_filelen(void* user_data);
|
||||
AUD_LOCAL static sf_count_t vio_seek(sf_count_t offset, int whence, void* user_data);
|
||||
AUD_LOCAL static sf_count_t vio_read(void* ptr, sf_count_t count, void* user_data);
|
||||
AUD_LOCAL static sf_count_t vio_tell(void* user_data);
|
||||
|
||||
// delete copy constructor and operator=
|
||||
SndFileReader(const SndFileReader&) = delete;
|
||||
SndFileReader& operator=(const SndFileReader&) = delete;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Creates a new reader.
|
||||
* \param filename The path to the file to be read.
|
||||
* \param stream The index of the audio stream within the file if it contains multiple audio streams.
|
||||
* \exception Exception Thrown if the file specified does not exist or
|
||||
* cannot be read with libsndfile.
|
||||
*/
|
||||
SndFileReader(const std::string &filename);
|
||||
|
||||
/**
|
||||
* Creates a new reader.
|
||||
* \param buffer The buffer to read from.
|
||||
* \param stream The index of the audio stream within the file if it contains multiple audio streams.
|
||||
* \exception Exception Thrown if the buffer specified cannot be read
|
||||
* with libsndfile.
|
||||
*/
|
||||
SndFileReader(std::shared_ptr<Buffer> buffer);
|
||||
|
||||
/**
|
||||
* Destroys the reader and closes the file.
|
||||
*/
|
||||
virtual ~SndFileReader();
|
||||
|
||||
/**
|
||||
* Queries the streams of a sound file.
|
||||
* \return A vector with as many streams as there are in the file.
|
||||
* \exception Exception Thrown if the file specified cannot be read.
|
||||
*/
|
||||
virtual std::vector<StreamInfo> queryStreams();
|
||||
|
||||
virtual bool isSeekable() const;
|
||||
virtual void seek(int position);
|
||||
virtual int getLength() const;
|
||||
virtual int getPosition() const;
|
||||
virtual Specs getSpecs() const;
|
||||
virtual void read(int& length, bool& eos, sample_t* buffer);
|
||||
};
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
128
blender-5.2.0/extern/audaspace/plugins/libsndfile/SndFileWriter.cpp
vendored
Normal file
128
blender-5.2.0/extern/audaspace/plugins/libsndfile/SndFileWriter.cpp
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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 "SndFileWriter.h"
|
||||
#include "Exception.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
SndFileWriter::SndFileWriter(const std::string &filename, DeviceSpecs specs,
|
||||
Container format, Codec codec, unsigned int bitrate) :
|
||||
m_position(0), m_specs(specs)
|
||||
{
|
||||
SF_INFO sfinfo;
|
||||
|
||||
sfinfo.channels = specs.channels;
|
||||
sfinfo.samplerate = int(specs.rate);
|
||||
|
||||
switch(format)
|
||||
{
|
||||
case CONTAINER_FLAC:
|
||||
sfinfo.format = SF_FORMAT_FLAC;
|
||||
switch(specs.format)
|
||||
{
|
||||
case FORMAT_S16:
|
||||
sfinfo.format |= SF_FORMAT_PCM_16;
|
||||
break;
|
||||
case FORMAT_S24:
|
||||
sfinfo.format |= SF_FORMAT_PCM_24;
|
||||
break;
|
||||
case FORMAT_S32:
|
||||
sfinfo.format |= SF_FORMAT_PCM_32;
|
||||
break;
|
||||
case FORMAT_FLOAT32:
|
||||
sfinfo.format |= SF_FORMAT_FLOAT;
|
||||
break;
|
||||
case FORMAT_FLOAT64:
|
||||
sfinfo.format |= SF_FORMAT_DOUBLE;
|
||||
break;
|
||||
default:
|
||||
sfinfo.format = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case CONTAINER_OGG:
|
||||
if(codec == CODEC_VORBIS)
|
||||
sfinfo.format = SF_FORMAT_OGG | SF_FORMAT_VORBIS;
|
||||
else
|
||||
sfinfo.format = 0;
|
||||
break;
|
||||
case CONTAINER_WAV:
|
||||
sfinfo.format = SF_FORMAT_WAV;
|
||||
switch(specs.format)
|
||||
{
|
||||
case FORMAT_U8:
|
||||
sfinfo.format |= SF_FORMAT_PCM_U8;
|
||||
break;
|
||||
case FORMAT_S16:
|
||||
sfinfo.format |= SF_FORMAT_PCM_16;
|
||||
break;
|
||||
case FORMAT_S24:
|
||||
sfinfo.format |= SF_FORMAT_PCM_24;
|
||||
break;
|
||||
case FORMAT_S32:
|
||||
sfinfo.format |= SF_FORMAT_PCM_32;
|
||||
break;
|
||||
case FORMAT_FLOAT32:
|
||||
sfinfo.format |= SF_FORMAT_FLOAT;
|
||||
break;
|
||||
case FORMAT_FLOAT64:
|
||||
sfinfo.format |= SF_FORMAT_DOUBLE;
|
||||
break;
|
||||
default:
|
||||
sfinfo.format = 0;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
sfinfo.format = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if(sfinfo.format == 0)
|
||||
AUD_THROW(FileException, "This format couldn't be written with libsndfile.");
|
||||
|
||||
m_sndfile = sf_open(filename.c_str(), SFM_WRITE, &sfinfo);
|
||||
|
||||
if(!m_sndfile)
|
||||
AUD_THROW(FileException, "The file couldn't be written with libsndfile.");
|
||||
}
|
||||
|
||||
SndFileWriter::~SndFileWriter()
|
||||
{
|
||||
sf_close(m_sndfile);
|
||||
}
|
||||
|
||||
int SndFileWriter::getPosition() const
|
||||
{
|
||||
return m_position;
|
||||
}
|
||||
|
||||
DeviceSpecs SndFileWriter::getSpecs() const
|
||||
{
|
||||
return m_specs;
|
||||
}
|
||||
|
||||
void SndFileWriter::write(unsigned int length, sample_t* buffer)
|
||||
{
|
||||
length = sf_writef_float(m_sndfile, buffer, length);
|
||||
|
||||
m_position += length;
|
||||
}
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
84
blender-5.2.0/extern/audaspace/plugins/libsndfile/SndFileWriter.h
vendored
Normal file
84
blender-5.2.0/extern/audaspace/plugins/libsndfile/SndFileWriter.h
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef LIBSNDFILE_PLUGIN
|
||||
#define AUD_BUILD_PLUGIN
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file SndFileWriter.h
|
||||
* @ingroup plugin
|
||||
* The SndFileWriter class.
|
||||
*/
|
||||
|
||||
#include "file/IWriter.h"
|
||||
|
||||
#include <string>
|
||||
#include <sndfile.h>
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* This class writes a sound file via libsndfile.
|
||||
*/
|
||||
class AUD_PLUGIN_API SndFileWriter : public IWriter
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* The current position in samples.
|
||||
*/
|
||||
int m_position;
|
||||
|
||||
/**
|
||||
* The specification of the audio data.
|
||||
*/
|
||||
DeviceSpecs m_specs;
|
||||
|
||||
/**
|
||||
* The sndfile.
|
||||
*/
|
||||
SNDFILE* m_sndfile;
|
||||
|
||||
// delete copy constructor and operator=
|
||||
SndFileWriter(const SndFileWriter&) = delete;
|
||||
SndFileWriter& operator=(const SndFileWriter&) = delete;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Creates a new writer.
|
||||
* \param filename The path to the file to be read.
|
||||
* \param specs The file's audio specification.
|
||||
* \param format The file's container format.
|
||||
* \param codec The codec used for encoding the audio data.
|
||||
* \param bitrate The bitrate for encoding.
|
||||
* \exception Exception Thrown if the file specified cannot be written
|
||||
* with libsndfile.
|
||||
*/
|
||||
SndFileWriter(const std::string &filename, DeviceSpecs specs, Container format, Codec codec, unsigned int bitrate);
|
||||
|
||||
/**
|
||||
* Destroys the writer and closes the file.
|
||||
*/
|
||||
virtual ~SndFileWriter();
|
||||
|
||||
virtual int getPosition() const;
|
||||
virtual DeviceSpecs getSpecs() const;
|
||||
virtual void write(unsigned int length, sample_t* buffer);
|
||||
};
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
1707
blender-5.2.0/extern/audaspace/plugins/openal/OpenALDevice.cpp
vendored
Normal file
1707
blender-5.2.0/extern/audaspace/plugins/openal/OpenALDevice.cpp
vendored
Normal file
File diff suppressed because it is too large
Load Diff
317
blender-5.2.0/extern/audaspace/plugins/openal/OpenALDevice.h
vendored
Normal file
317
blender-5.2.0/extern/audaspace/plugins/openal/OpenALDevice.h
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef OPENAL_PLUGIN
|
||||
#define AUD_BUILD_PLUGIN
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file OpenALDevice.h
|
||||
* @ingroup plugin
|
||||
* The OpenALDevice class.
|
||||
*/
|
||||
|
||||
#include "devices/IDevice.h"
|
||||
#include "devices/IHandle.h"
|
||||
#include "devices/I3DDevice.h"
|
||||
#include "devices/I3DHandle.h"
|
||||
#include "util/Buffer.h"
|
||||
|
||||
#include <al.h>
|
||||
#include <alc.h>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <string>
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* This device plays through OpenAL.
|
||||
*/
|
||||
class AUD_PLUGIN_API OpenALDevice : public IDevice, public I3DDevice
|
||||
{
|
||||
private:
|
||||
/// Saves the data for playback.
|
||||
class OpenALHandle : public IHandle, public I3DHandle
|
||||
{
|
||||
private:
|
||||
friend class OpenALDevice;
|
||||
|
||||
static const int CYCLE_BUFFERS = 3;
|
||||
|
||||
/// Whether it's a buffered or a streamed source.
|
||||
bool m_isBuffered;
|
||||
|
||||
/// The reader source.
|
||||
std::shared_ptr<IReader> m_reader;
|
||||
|
||||
/// Whether to keep the source if end of it is reached.
|
||||
bool m_keep;
|
||||
|
||||
/// OpenAL sample format.
|
||||
ALenum m_format;
|
||||
|
||||
/// OpenAL source.
|
||||
ALuint m_source;
|
||||
|
||||
/// OpenAL buffers.
|
||||
ALuint m_buffers[CYCLE_BUFFERS];
|
||||
|
||||
/// The first buffer to be read next.
|
||||
int m_current;
|
||||
|
||||
/// Whether the stream doesn't return any more data.
|
||||
bool m_eos;
|
||||
|
||||
/// The loop count of the source.
|
||||
int m_loopcount;
|
||||
|
||||
/// The stop callback.
|
||||
stopCallback m_stop;
|
||||
|
||||
/// Stop callback data.
|
||||
void* m_stop_data;
|
||||
|
||||
/// Orientation.
|
||||
Quaternion m_orientation;
|
||||
|
||||
/// Current status of the handle
|
||||
Status m_status;
|
||||
|
||||
/// Whether the source is relative or not.
|
||||
ALint m_relative;
|
||||
|
||||
/// Own device.
|
||||
OpenALDevice* m_device;
|
||||
|
||||
AUD_LOCAL bool pause(bool keep);
|
||||
|
||||
AUD_LOCAL bool reinitialize();
|
||||
|
||||
// delete copy constructor and operator=
|
||||
OpenALHandle(const OpenALHandle&) = delete;
|
||||
OpenALHandle& operator=(const OpenALHandle&) = delete;
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* Creates a new OpenAL handle.
|
||||
* \param device The OpenAL device the handle belongs to.
|
||||
* \param format The AL format.
|
||||
* \param reader The reader this handle plays.
|
||||
* \param keep Whether to keep the handle alive when the reader ends.
|
||||
*/
|
||||
OpenALHandle(OpenALDevice* device, ALenum format, std::shared_ptr<IReader> reader, bool keep);
|
||||
|
||||
virtual ~OpenALHandle() {}
|
||||
virtual bool pause();
|
||||
virtual bool resume();
|
||||
virtual bool stop();
|
||||
virtual bool getKeep();
|
||||
virtual bool setKeep(bool keep);
|
||||
virtual bool seek(double position);
|
||||
virtual double getPosition();
|
||||
virtual Status getStatus();
|
||||
virtual float getVolume();
|
||||
virtual bool setVolume(float volume);
|
||||
virtual float getPitch();
|
||||
virtual bool setPitch(float pitch);
|
||||
virtual int getLoopCount();
|
||||
virtual bool setLoopCount(int count);
|
||||
virtual bool setStopCallback(stopCallback callback = 0, void* data = 0);
|
||||
|
||||
virtual Vector3 getLocation();
|
||||
virtual bool setLocation(const Vector3& location);
|
||||
virtual Vector3 getVelocity();
|
||||
virtual bool setVelocity(const Vector3& velocity);
|
||||
virtual Quaternion getOrientation();
|
||||
virtual bool setOrientation(const Quaternion& orientation);
|
||||
virtual bool isRelative();
|
||||
virtual bool setRelative(bool relative);
|
||||
virtual float getVolumeMaximum();
|
||||
virtual bool setVolumeMaximum(float volume);
|
||||
virtual float getVolumeMinimum();
|
||||
virtual bool setVolumeMinimum(float volume);
|
||||
virtual float getDistanceMaximum();
|
||||
virtual bool setDistanceMaximum(float distance);
|
||||
virtual float getDistanceReference();
|
||||
virtual bool setDistanceReference(float distance);
|
||||
virtual float getAttenuation();
|
||||
virtual bool setAttenuation(float factor);
|
||||
virtual float getConeAngleOuter();
|
||||
virtual bool setConeAngleOuter(float angle);
|
||||
virtual float getConeAngleInner();
|
||||
virtual bool setConeAngleInner(float angle);
|
||||
virtual float getConeVolumeOuter();
|
||||
virtual bool setConeVolumeOuter(float volume);
|
||||
};
|
||||
|
||||
/**
|
||||
* The OpenAL device handle.
|
||||
*/
|
||||
ALCdevice* m_device;
|
||||
|
||||
/**
|
||||
* The OpenAL context.
|
||||
*/
|
||||
ALCcontext* m_context;
|
||||
|
||||
/**
|
||||
* The specification of the device.
|
||||
*/
|
||||
DeviceSpecs m_specs;
|
||||
|
||||
/**
|
||||
* The device name.
|
||||
*/
|
||||
std::string m_name;
|
||||
|
||||
/**
|
||||
* Whether the device has the AL_EXT_MCFORMATS extension.
|
||||
*/
|
||||
bool m_useMC;
|
||||
|
||||
/**
|
||||
* Whether the ALC_EXT_disconnect extension is present and device disconnect should be checked repeatedly.
|
||||
*/
|
||||
bool m_checkDisconnect;
|
||||
|
||||
/**
|
||||
* The list of sounds that are currently playing.
|
||||
*/
|
||||
std::list<std::shared_ptr<OpenALHandle> > m_playingSounds;
|
||||
|
||||
/**
|
||||
* The list of sounds that are currently paused.
|
||||
*/
|
||||
std::list<std::shared_ptr<OpenALHandle> > m_pausedSounds;
|
||||
|
||||
/**
|
||||
* The mutex for locking.
|
||||
*/
|
||||
std::recursive_mutex m_mutex;
|
||||
|
||||
/**
|
||||
* The streaming thread.
|
||||
*/
|
||||
std::thread m_thread;
|
||||
|
||||
/**
|
||||
* The condition for streaming thread wakeup.
|
||||
*/
|
||||
bool m_playing;
|
||||
|
||||
/**
|
||||
* Buffer size.
|
||||
*/
|
||||
int m_buffersize;
|
||||
|
||||
/**
|
||||
* Device buffer.
|
||||
*/
|
||||
Buffer m_buffer;
|
||||
|
||||
/**
|
||||
* Orientation.
|
||||
*/
|
||||
Quaternion m_orientation;
|
||||
|
||||
/// Synchronizer.
|
||||
uint64_t m_synchronizerPosition{0};
|
||||
std::shared_ptr<IHandle> m_silenceHandle;
|
||||
|
||||
/**
|
||||
* Starts the streaming thread.
|
||||
* \param Whether the previous thread should be joined.
|
||||
*/
|
||||
AUD_LOCAL void start();
|
||||
|
||||
/**
|
||||
* Streaming thread main function.
|
||||
*/
|
||||
AUD_LOCAL void updateStreams();
|
||||
|
||||
/**
|
||||
* Gets the format according to the specs.
|
||||
* \param format The variable to put the format into.
|
||||
* \param specs The specs to read the channel count from.
|
||||
* \return Whether the format is valid or not.
|
||||
*/
|
||||
AUD_LOCAL bool getFormat(ALenum &format, Specs specs);
|
||||
|
||||
// delete copy constructor and operator=
|
||||
OpenALDevice(const OpenALDevice&) = delete;
|
||||
OpenALDevice& operator=(const OpenALDevice&) = delete;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Opens the OpenAL audio device for playback.
|
||||
* \param specs The wanted audio specification.
|
||||
* \param buffersize The size of the internal buffer.
|
||||
* \param name The name of the device to be opened.
|
||||
* \note The specification really used for opening the device may differ.
|
||||
* \note The buffersize will be multiplicated by three for this device.
|
||||
* \exception DeviceException Thrown if the audio device cannot be opened.
|
||||
*/
|
||||
OpenALDevice(DeviceSpecs specs, int buffersize = AUD_DEFAULT_BUFFER_SIZE, const std::string &name = "");
|
||||
|
||||
virtual ~OpenALDevice();
|
||||
|
||||
virtual DeviceSpecs getSpecs() const;
|
||||
virtual std::shared_ptr<IHandle> play(std::shared_ptr<IReader> reader, bool keep = false);
|
||||
virtual std::shared_ptr<IHandle> play(std::shared_ptr<ISound> sound, bool keep = false);
|
||||
virtual void stopAll();
|
||||
virtual void lock();
|
||||
virtual void unlock();
|
||||
virtual float getVolume() const;
|
||||
virtual void setVolume(float volume);
|
||||
|
||||
virtual void seekSynchronizer(double time);
|
||||
virtual double getSynchronizerPosition();
|
||||
virtual void playSynchronizer();
|
||||
virtual void stopSynchronizer();
|
||||
virtual void setSyncCallback(syncFunction function, void* data);
|
||||
virtual int isSynchronizerPlaying();
|
||||
|
||||
virtual Vector3 getListenerLocation() const;
|
||||
virtual void setListenerLocation(const Vector3& location);
|
||||
virtual Vector3 getListenerVelocity() const;
|
||||
virtual void setListenerVelocity(const Vector3& velocity);
|
||||
virtual Quaternion getListenerOrientation() const;
|
||||
virtual void setListenerOrientation(const Quaternion& orientation);
|
||||
virtual float getSpeedOfSound() const;
|
||||
virtual void setSpeedOfSound(float speed);
|
||||
virtual float getDopplerFactor() const;
|
||||
virtual void setDopplerFactor(float factor);
|
||||
virtual DistanceModel getDistanceModel() const;
|
||||
virtual void setDistanceModel(DistanceModel model);
|
||||
|
||||
/**
|
||||
* Retrieves a list of available hardware devices to open with OpenAL.
|
||||
* @return The list of devices to open.
|
||||
*/
|
||||
static std::list<std::string> getDeviceNames();
|
||||
|
||||
/**
|
||||
* Registers this plugin.
|
||||
*/
|
||||
static void registerPlugin();
|
||||
};
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
138
blender-5.2.0/extern/audaspace/plugins/openal/OpenALReader.cpp
vendored
Normal file
138
blender-5.2.0/extern/audaspace/plugins/openal/OpenALReader.cpp
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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 "OpenALReader.h"
|
||||
#include "respec/ConverterFunctions.h"
|
||||
#include "Exception.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <al.h>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
OpenALReader::OpenALReader(Specs specs, int buffersize, const std::string& name) :
|
||||
m_specs(specs),
|
||||
m_position(0),
|
||||
m_device(nullptr)
|
||||
{
|
||||
if((specs.channels != CHANNELS_MONO) && (specs.channels != CHANNELS_STEREO))
|
||||
specs.channels = CHANNELS_MONO;
|
||||
m_specs.channels = specs.channels;
|
||||
|
||||
const char* device_name = name.empty() ? nullptr : name.c_str();
|
||||
m_device = alcCaptureOpenDevice(device_name, specs.rate,
|
||||
specs.channels == CHANNELS_MONO ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16,
|
||||
buffersize * specs.channels * 2);
|
||||
|
||||
if(!m_device)
|
||||
AUD_THROW(DeviceException, "The capture device couldn't be opened with OpenAL.");
|
||||
|
||||
alcCaptureStart(m_device);
|
||||
}
|
||||
|
||||
OpenALReader::~OpenALReader()
|
||||
{
|
||||
if(m_device)
|
||||
{
|
||||
alcCaptureStop(m_device);
|
||||
alcCaptureCloseDevice(m_device);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> OpenALReader::getDeviceNames()
|
||||
{
|
||||
std::vector<std::string> names;
|
||||
|
||||
/* ALC_CAPTURE_DEVICE_SPECIFIER requires ALC_ENUMERATION_EXT.
|
||||
* Without it alcGetString may return garbage on some drivers. */
|
||||
if(!alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT"))
|
||||
return names;
|
||||
|
||||
const ALCchar* devices = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER);
|
||||
if(devices != nullptr)
|
||||
{
|
||||
const ALCchar* cursor = devices;
|
||||
while(*cursor != '\0')
|
||||
{
|
||||
names.push_back(cursor);
|
||||
cursor += std::strlen(cursor) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
const ALCchar* default_device = alcGetString(nullptr, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER);
|
||||
if(default_device != nullptr && default_device[0] != '\0')
|
||||
{
|
||||
const std::string default_name(default_device);
|
||||
auto it = std::find(names.begin(), names.end(), default_name);
|
||||
if(it == names.end())
|
||||
names.insert(names.begin(), default_name);
|
||||
else if(it != names.begin())
|
||||
{
|
||||
names.erase(it);
|
||||
names.insert(names.begin(), default_name);
|
||||
}
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
bool OpenALReader::isSeekable() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void OpenALReader::seek(int position)
|
||||
{
|
||||
m_position = position;
|
||||
}
|
||||
|
||||
int OpenALReader::getLength() const
|
||||
{
|
||||
int length;
|
||||
alcGetIntegerv(m_device, ALC_CAPTURE_SAMPLES, 1, &length);
|
||||
return length;
|
||||
}
|
||||
|
||||
int OpenALReader::getPosition() const
|
||||
{
|
||||
return m_position;
|
||||
}
|
||||
|
||||
Specs OpenALReader::getSpecs() const
|
||||
{
|
||||
return m_specs;
|
||||
}
|
||||
|
||||
void OpenALReader::read(int & length, bool& eos, sample_t* buffer)
|
||||
{
|
||||
int len = getLength();
|
||||
length = std::min(length, len);
|
||||
|
||||
if(length > 0)
|
||||
{
|
||||
alcCaptureSamples(m_device, buffer, length);
|
||||
convert_s16_float((data_t*)buffer, (data_t*)buffer, length * m_specs.channels);
|
||||
}
|
||||
|
||||
eos = false;
|
||||
|
||||
m_position += length;
|
||||
}
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
92
blender-5.2.0/extern/audaspace/plugins/openal/OpenALReader.h
vendored
Normal file
92
blender-5.2.0/extern/audaspace/plugins/openal/OpenALReader.h
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef OPENAL_PLUGIN
|
||||
#define AUD_BUILD_PLUGIN
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file OpenALReader.h
|
||||
* @ingroup plugin
|
||||
* The OpenALReader class.
|
||||
*/
|
||||
|
||||
#include "IReader.h"
|
||||
|
||||
#include <alc.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* This class is used for sine tone playback.
|
||||
* The output format is in the 16 bit format and stereo, the sample rate can be
|
||||
* specified.
|
||||
* As the two channels both play the same the output could also be mono, but
|
||||
* in most cases this will result in having to resample for output, so stereo
|
||||
* sound is created directly.
|
||||
*/
|
||||
class AUD_PLUGIN_API OpenALReader : public IReader
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* The specs of the reader.
|
||||
*/
|
||||
Specs m_specs;
|
||||
|
||||
/**
|
||||
* The current position in samples.
|
||||
*/
|
||||
int m_position;
|
||||
|
||||
/**
|
||||
* The capture device.
|
||||
*/
|
||||
ALCdevice* m_device;
|
||||
|
||||
// delete copy constructor and operator=
|
||||
OpenALReader(const OpenALReader&) = delete;
|
||||
OpenALReader& operator=(const OpenALReader&) = delete;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Creates a new reader.
|
||||
* \param specs The desired specification of the output samples.
|
||||
* \param buffersize The buffer size used to read from the device.
|
||||
* \param name The name of the capture device.
|
||||
*/
|
||||
OpenALReader(Specs specs, int buffersize = AUD_DEFAULT_BUFFER_SIZE, const std::string& name = "");
|
||||
|
||||
virtual ~OpenALReader();
|
||||
|
||||
/**
|
||||
* Lists all available OpenAL capture devices.
|
||||
* \return Device names.
|
||||
*/
|
||||
static std::vector<std::string> getDeviceNames();
|
||||
|
||||
virtual bool isSeekable() const;
|
||||
virtual void seek(int position);
|
||||
virtual int getLength() const;
|
||||
virtual int getPosition() const;
|
||||
virtual Specs getSpecs() const;
|
||||
virtual void read(int & length, bool& eos, sample_t* buffer);
|
||||
};
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
307
blender-5.2.0/extern/audaspace/plugins/pipewire/PipeWireDevice.cpp
vendored
Normal file
307
blender-5.2.0/extern/audaspace/plugins/pipewire/PipeWireDevice.cpp
vendored
Normal file
@@ -0,0 +1,307 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2024 Jörg Müller
|
||||
*
|
||||
* 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 "PipeWireDevice.h"
|
||||
|
||||
#include <spa/param/audio/format-utils.h>
|
||||
|
||||
#include "Exception.h"
|
||||
#include "PipeWireLibrary.h"
|
||||
|
||||
#include "devices/DeviceManager.h"
|
||||
#include "devices/IDeviceFactory.h"
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
void PipeWireDevice::handleStateChanged(void* device_ptr, enum pw_stream_state old, enum pw_stream_state state, const char* error)
|
||||
{
|
||||
PipeWireDevice* device = (PipeWireDevice*) device_ptr;
|
||||
// fprintf(stderr, "stream state: \"%s\"\n", pw_stream_state_as_string(state));
|
||||
if(state == PW_STREAM_STATE_PAUSED)
|
||||
{
|
||||
AUD_pw_stream_flush(device->m_stream, false);
|
||||
}
|
||||
}
|
||||
|
||||
void PipeWireDevice::mixAudioBuffer(void* device_ptr)
|
||||
{
|
||||
PipeWireDevice* device = (PipeWireDevice*) device_ptr;
|
||||
|
||||
pw_buffer* pw_buf = AUD_pw_stream_dequeue_buffer(device->m_stream);
|
||||
if(!pw_buf)
|
||||
{
|
||||
/* Couldn't get any buffer from PipeWire...*/
|
||||
return;
|
||||
}
|
||||
|
||||
/* We compute this here as the tick is not guaranteed to be up to date
|
||||
* until the "process" callback is triggered.
|
||||
*/
|
||||
if(device->m_getSynchronizerStartTime)
|
||||
{
|
||||
pw_time tm;
|
||||
AUD_pw_stream_get_time_n(device->m_stream, &tm, sizeof(tm));
|
||||
device->m_synchronizerStartTime = tm.ticks;
|
||||
device->m_getSynchronizerStartTime = false;
|
||||
}
|
||||
|
||||
spa_data& spa_data = pw_buf->buffer->datas[0];
|
||||
spa_chunk* chunk = spa_data.chunk;
|
||||
|
||||
chunk->offset = 0;
|
||||
chunk->stride = AUD_DEVICE_SAMPLE_SIZE(device->m_specs);
|
||||
int n_frames = spa_data.maxsize / chunk->stride;
|
||||
if(pw_buf->requested)
|
||||
{
|
||||
n_frames = SPA_MIN(pw_buf->requested, n_frames);
|
||||
}
|
||||
|
||||
size_t readsamples = device->getRingBuffer().getReadSize() / chunk->stride;
|
||||
|
||||
if(readsamples < n_frames)
|
||||
n_frames = readsamples;
|
||||
|
||||
chunk->size = n_frames * chunk->stride;
|
||||
|
||||
device->getRingBuffer().read(reinterpret_cast<data_t*>(spa_data.data), chunk->size);
|
||||
|
||||
device->notifyMixingThread();
|
||||
AUD_pw_stream_queue_buffer(device->m_stream, pw_buf);
|
||||
}
|
||||
|
||||
void PipeWireDevice::preMixingWork(bool playing)
|
||||
{
|
||||
if(!playing)
|
||||
{
|
||||
if((getRingBuffer().getReadSize() == 0) && m_active)
|
||||
{
|
||||
AUD_pw_thread_loop_lock(m_thread);
|
||||
AUD_pw_stream_set_active(m_stream, false);
|
||||
AUD_pw_thread_loop_unlock(m_thread);
|
||||
m_active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PipeWireDevice::playing(bool playing)
|
||||
{
|
||||
std::lock_guard<ILockable> lock(*this);
|
||||
|
||||
MixingThreadDevice::playing(playing);
|
||||
|
||||
if(playing)
|
||||
{
|
||||
AUD_pw_thread_loop_lock(m_thread);
|
||||
AUD_pw_stream_set_active(m_stream, playing);
|
||||
AUD_pw_thread_loop_unlock(m_thread);
|
||||
m_active = true;
|
||||
}
|
||||
}
|
||||
|
||||
PipeWireDevice::PipeWireDevice(const std::string& name, DeviceSpecs specs, int buffersize)
|
||||
{
|
||||
if(specs.channels == CHANNELS_INVALID)
|
||||
specs.channels = CHANNELS_STEREO;
|
||||
if(specs.format == FORMAT_INVALID)
|
||||
specs.format = FORMAT_FLOAT32;
|
||||
if(specs.rate == RATE_INVALID)
|
||||
specs.rate = RATE_48000;
|
||||
|
||||
m_specs = specs;
|
||||
spa_audio_format format = SPA_AUDIO_FORMAT_F32;
|
||||
switch(m_specs.format)
|
||||
{
|
||||
case FORMAT_U8:
|
||||
format = SPA_AUDIO_FORMAT_U8;
|
||||
break;
|
||||
case FORMAT_S16:
|
||||
format = SPA_AUDIO_FORMAT_S16;
|
||||
break;
|
||||
case FORMAT_S24:
|
||||
format = SPA_AUDIO_FORMAT_S24;
|
||||
break;
|
||||
case FORMAT_S32:
|
||||
format = SPA_AUDIO_FORMAT_S32;
|
||||
break;
|
||||
case FORMAT_FLOAT32:
|
||||
format = SPA_AUDIO_FORMAT_F32;
|
||||
break;
|
||||
case FORMAT_FLOAT64:
|
||||
format = SPA_AUDIO_FORMAT_F64;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
AUD_pw_init(nullptr, nullptr);
|
||||
|
||||
m_thread = AUD_pw_thread_loop_new(name.c_str(), nullptr);
|
||||
if(!m_thread)
|
||||
{
|
||||
AUD_THROW(DeviceException, "Could not create PipeWire thread.");
|
||||
}
|
||||
|
||||
m_events = std::make_unique<pw_stream_events>();
|
||||
m_events->version = PW_VERSION_STREAM_EVENTS;
|
||||
m_events->state_changed = PipeWireDevice::handleStateChanged;
|
||||
m_events->process = PipeWireDevice::mixAudioBuffer;
|
||||
|
||||
pw_properties* stream_props = AUD_pw_properties_new(PW_KEY_MEDIA_TYPE, "Audio", PW_KEY_MEDIA_CATEGORY, "Playback", PW_KEY_MEDIA_ROLE, "Production", NULL);
|
||||
|
||||
/* Set the requested sample rate and latency. */
|
||||
AUD_pw_properties_setf(stream_props, PW_KEY_NODE_RATE, "1/%u", uint(m_specs.rate));
|
||||
AUD_pw_properties_setf(stream_props, PW_KEY_NODE_LATENCY, "%u/%u", buffersize, uint(m_specs.rate));
|
||||
|
||||
m_stream = AUD_pw_stream_new_simple(AUD_pw_thread_loop_get_loop(m_thread), name.c_str(), stream_props, m_events.get(), this);
|
||||
if(!m_stream)
|
||||
{
|
||||
AUD_pw_thread_loop_destroy(m_thread);
|
||||
AUD_THROW(DeviceException, "Could not create PipeWire stream.");
|
||||
}
|
||||
|
||||
spa_audio_info_raw info{};
|
||||
info.channels = m_specs.channels;
|
||||
info.format = format;
|
||||
info.rate = m_specs.rate;
|
||||
|
||||
uint8_t buffer[1024];
|
||||
spa_pod_builder b = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer));
|
||||
const spa_pod* param = spa_format_audio_raw_build(&b, SPA_PARAM_EnumFormat, &info);
|
||||
|
||||
AUD_pw_stream_connect(m_stream, PW_DIRECTION_OUTPUT, PW_ID_ANY,
|
||||
static_cast<pw_stream_flags>(PW_STREAM_FLAG_AUTOCONNECT | PW_STREAM_FLAG_MAP_BUFFERS | PW_STREAM_FLAG_INACTIVE | PW_STREAM_FLAG_RT_PROCESS), ¶m, 1);
|
||||
AUD_pw_thread_loop_start(m_thread);
|
||||
|
||||
create();
|
||||
|
||||
startMixingThread(buffersize * 2 * AUD_DEVICE_SAMPLE_SIZE(m_specs));
|
||||
}
|
||||
|
||||
PipeWireDevice::~PipeWireDevice()
|
||||
{
|
||||
stopMixingThread();
|
||||
|
||||
/* Ensure that we are not playing back anything anymore. */
|
||||
destroy();
|
||||
|
||||
/* Destruct all PipeWire data. */
|
||||
AUD_pw_thread_loop_stop(m_thread);
|
||||
AUD_pw_stream_destroy(m_stream);
|
||||
AUD_pw_thread_loop_destroy(m_thread);
|
||||
AUD_pw_deinit();
|
||||
}
|
||||
|
||||
void PipeWireDevice::seekSynchronizer(double time)
|
||||
{
|
||||
/* Update start time here as we might update the seek position while playing back. */
|
||||
m_getSynchronizerStartTime = true;
|
||||
m_synchronizerStartPosition = time;
|
||||
|
||||
SoftwareDevice::seekSynchronizer(time);
|
||||
}
|
||||
|
||||
double PipeWireDevice::getSynchronizerPosition()
|
||||
{
|
||||
if(!isSynchronizerPlaying() || m_getSynchronizerStartTime)
|
||||
{
|
||||
return m_synchronizerStartPosition;
|
||||
}
|
||||
|
||||
pw_time tm;
|
||||
AUD_pw_stream_get_time_n(m_stream, &tm, sizeof(tm));
|
||||
uint64_t now = AUD_pw_stream_get_nsec(m_stream);
|
||||
int64_t diff = now - tm.now;
|
||||
/* Elapsed time since the last sample was queued. */
|
||||
int64_t elapsed = (tm.rate.denom * diff) / (tm.rate.num * SPA_NSEC_PER_SEC);
|
||||
|
||||
/* Calculate the elapsed time in seconds from the last seek position. */
|
||||
double elapsed_time = (tm.ticks - m_synchronizerStartTime + elapsed) * tm.rate.num / double(tm.rate.denom);
|
||||
return elapsed_time + m_synchronizerStartPosition;
|
||||
}
|
||||
|
||||
void PipeWireDevice::playSynchronizer()
|
||||
{
|
||||
/* Make sure that our start time is up to date. */
|
||||
m_getSynchronizerStartTime = true;
|
||||
SoftwareDevice::playSynchronizer();
|
||||
}
|
||||
|
||||
void PipeWireDevice::stopSynchronizer()
|
||||
{
|
||||
m_synchronizerStartPosition = getSynchronizerPosition();
|
||||
SoftwareDevice::stopSynchronizer();
|
||||
}
|
||||
|
||||
class PipeWireDeviceFactory : public IDeviceFactory
|
||||
{
|
||||
private:
|
||||
DeviceSpecs m_specs;
|
||||
int m_buffersize;
|
||||
std::string m_name;
|
||||
|
||||
public:
|
||||
PipeWireDeviceFactory() : m_buffersize(AUD_DEFAULT_BUFFER_SIZE)
|
||||
{
|
||||
m_specs.format = FORMAT_S16;
|
||||
m_specs.channels = CHANNELS_STEREO;
|
||||
m_specs.rate = RATE_48000;
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<IDevice> openDevice()
|
||||
{
|
||||
return std::shared_ptr<IDevice>(new PipeWireDevice(m_name, m_specs, m_buffersize));
|
||||
}
|
||||
|
||||
virtual int getPriority()
|
||||
{
|
||||
return 1 << 16;
|
||||
}
|
||||
|
||||
virtual void setSpecs(DeviceSpecs specs)
|
||||
{
|
||||
m_specs = specs;
|
||||
}
|
||||
|
||||
virtual void setBufferSize(int buffersize)
|
||||
{
|
||||
m_buffersize = buffersize;
|
||||
}
|
||||
|
||||
virtual void setName(const std::string& name)
|
||||
{
|
||||
m_name = name;
|
||||
}
|
||||
};
|
||||
|
||||
void PipeWireDevice::registerPlugin()
|
||||
{
|
||||
if(loadPipeWire())
|
||||
DeviceManager::registerDevice("PipeWire", std::shared_ptr<IDeviceFactory>(new PipeWireDeviceFactory));
|
||||
}
|
||||
|
||||
#ifdef PIPEWIRE_PLUGIN
|
||||
extern "C" AUD_PLUGIN_API void registerPlugin()
|
||||
{
|
||||
PipeWireDevice::registerPlugin();
|
||||
}
|
||||
|
||||
extern "C" AUD_PLUGIN_API const char* getName()
|
||||
{
|
||||
return "Pipewire";
|
||||
}
|
||||
#endif
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
93
blender-5.2.0/extern/audaspace/plugins/pipewire/PipeWireDevice.h
vendored
Normal file
93
blender-5.2.0/extern/audaspace/plugins/pipewire/PipeWireDevice.h
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2024 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef PIPEWIRE_PLUGIN
|
||||
#define AUD_BUILD_PLUGIN
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file PipeWireDevice.h
|
||||
* @ingroup plugin
|
||||
* The PipeWireDevice class.
|
||||
*/
|
||||
|
||||
#include <pipewire/pipewire.h>
|
||||
|
||||
#include "devices/MixingThreadDevice.h"
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* This device plays back through PipeWire, the simple direct media layer.
|
||||
*/
|
||||
class AUD_PLUGIN_API PipeWireDevice : public MixingThreadDevice
|
||||
{
|
||||
private:
|
||||
pw_stream* m_stream;
|
||||
pw_thread_loop* m_thread;
|
||||
std::unique_ptr<pw_stream_events> m_events;
|
||||
bool m_active{false};
|
||||
|
||||
/// Synchronizer.
|
||||
bool m_getSynchronizerStartTime{false};
|
||||
int64_t m_synchronizerStartTime{0};
|
||||
double m_synchronizerStartPosition{0.0};
|
||||
|
||||
AUD_LOCAL static void handleStateChanged(void* device_ptr, enum pw_stream_state old, enum pw_stream_state state, const char* error);
|
||||
|
||||
/**
|
||||
* Mixes the next bytes into the buffer.
|
||||
* \param data The PipeWire device.
|
||||
*/
|
||||
AUD_LOCAL static void mixAudioBuffer(void* device_ptr);
|
||||
|
||||
// delete copy constructor and operator=
|
||||
PipeWireDevice(const PipeWireDevice&) = delete;
|
||||
PipeWireDevice& operator=(const PipeWireDevice&) = delete;
|
||||
|
||||
protected:
|
||||
void preMixingWork(bool playing);
|
||||
virtual void playing(bool playing);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Opens the PipeWire audio device for playback.
|
||||
* \param specs The wanted audio specification.
|
||||
* \param buffersize The size of the internal buffer.
|
||||
* \note The specification really used for opening the device may differ.
|
||||
* \exception Exception Thrown if the audio device cannot be opened.
|
||||
*/
|
||||
PipeWireDevice(const std::string& name, DeviceSpecs specs, int buffersize = AUD_DEFAULT_BUFFER_SIZE);
|
||||
|
||||
/**
|
||||
* Closes the PipeWire audio device.
|
||||
*/
|
||||
virtual ~PipeWireDevice();
|
||||
|
||||
virtual void seekSynchronizer(double time);
|
||||
virtual double getSynchronizerPosition();
|
||||
virtual void playSynchronizer();
|
||||
virtual void stopSynchronizer();
|
||||
|
||||
/**
|
||||
* Registers this plugin.
|
||||
*/
|
||||
static void registerPlugin();
|
||||
};
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
59
blender-5.2.0/extern/audaspace/plugins/pipewire/PipeWireLibrary.cpp
vendored
Normal file
59
blender-5.2.0/extern/audaspace/plugins/pipewire/PipeWireLibrary.cpp
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2024 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#define PIPEWIRE_LIBRARY_IMPLEMENTATION
|
||||
|
||||
#include <string>
|
||||
#include <array>
|
||||
|
||||
#include "PipeWireLibrary.h"
|
||||
|
||||
#ifdef DYNLOAD_PIPEWIRE
|
||||
#include "plugin/PluginManager.h"
|
||||
#endif
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
bool loadPipeWire()
|
||||
{
|
||||
#ifdef DYNLOAD_PIPEWIRE
|
||||
std::array<const std::string, 2> names = {"libpipewire-0.3.so", "libpipewire-0.3.so.0"};
|
||||
|
||||
void* handle = nullptr;
|
||||
|
||||
for(auto& name : names)
|
||||
{
|
||||
handle = PluginManager::openLibrary(name);
|
||||
if(handle)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!handle)
|
||||
return false;
|
||||
|
||||
#define PIPEWIRE_SYMBOL(sym) AUD_##sym = reinterpret_cast<decltype(&sym)>(PluginManager::lookupLibrary(handle, #sym))
|
||||
#else
|
||||
#define PIPEWIRE_SYMBOL(sym) AUD_##sym = &sym
|
||||
#endif
|
||||
|
||||
#include "PipeWireSymbols.h"
|
||||
|
||||
#undef PIPEWIRE_SYMBOL
|
||||
|
||||
return AUD_pw_check_library_version != nullptr && AUD_pw_check_library_version(1, 1, 0);
|
||||
}
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
47
blender-5.2.0/extern/audaspace/plugins/pipewire/PipeWireLibrary.h
vendored
Normal file
47
blender-5.2.0/extern/audaspace/plugins/pipewire/PipeWireLibrary.h
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2024 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef PIPEWIRE_PLUGIN
|
||||
#define AUD_BUILD_PLUGIN
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file PipeWireLibrary.h
|
||||
* @ingroup plugin
|
||||
*/
|
||||
|
||||
#include "Audaspace.h"
|
||||
|
||||
#include <pipewire/pipewire.h>
|
||||
#include <pipewire/stream.h>
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
#ifdef PIPEWIRE_LIBRARY_IMPLEMENTATION
|
||||
#define PIPEWIRE_SYMBOL(sym) decltype(&sym) AUD_##sym
|
||||
#else
|
||||
#define PIPEWIRE_SYMBOL(sym) extern decltype(&sym) AUD_##sym
|
||||
#endif
|
||||
|
||||
#include "PipeWireSymbols.h"
|
||||
|
||||
#undef PIPEWIRE_SYMBOL
|
||||
|
||||
bool loadPipeWire();
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
41
blender-5.2.0/extern/audaspace/plugins/pipewire/PipeWireSymbols.h
vendored
Normal file
41
blender-5.2.0/extern/audaspace/plugins/pipewire/PipeWireSymbols.h
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2024 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
PIPEWIRE_SYMBOL(pw_init);
|
||||
PIPEWIRE_SYMBOL(pw_deinit);
|
||||
|
||||
PIPEWIRE_SYMBOL(pw_properties_new);
|
||||
PIPEWIRE_SYMBOL(pw_properties_setf);
|
||||
|
||||
PIPEWIRE_SYMBOL(pw_stream_connect);
|
||||
PIPEWIRE_SYMBOL(pw_stream_destroy);
|
||||
PIPEWIRE_SYMBOL(pw_stream_get_nsec);
|
||||
PIPEWIRE_SYMBOL(pw_stream_get_time_n);
|
||||
PIPEWIRE_SYMBOL(pw_stream_new_simple);
|
||||
PIPEWIRE_SYMBOL(pw_stream_queue_buffer);
|
||||
PIPEWIRE_SYMBOL(pw_stream_dequeue_buffer);
|
||||
PIPEWIRE_SYMBOL(pw_stream_set_active);
|
||||
PIPEWIRE_SYMBOL(pw_stream_flush);
|
||||
|
||||
PIPEWIRE_SYMBOL(pw_thread_loop_destroy);
|
||||
PIPEWIRE_SYMBOL(pw_thread_loop_get_loop);
|
||||
PIPEWIRE_SYMBOL(pw_thread_loop_lock);
|
||||
PIPEWIRE_SYMBOL(pw_thread_loop_unlock);
|
||||
PIPEWIRE_SYMBOL(pw_thread_loop_new);
|
||||
PIPEWIRE_SYMBOL(pw_thread_loop_start);
|
||||
PIPEWIRE_SYMBOL(pw_thread_loop_stop);
|
||||
|
||||
PIPEWIRE_SYMBOL(pw_check_library_version);
|
||||
361
blender-5.2.0/extern/audaspace/plugins/pulseaudio/PulseAudioDevice.cpp
vendored
Normal file
361
blender-5.2.0/extern/audaspace/plugins/pulseaudio/PulseAudioDevice.cpp
vendored
Normal file
@@ -0,0 +1,361 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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 "PulseAudioDevice.h"
|
||||
|
||||
#include "Exception.h"
|
||||
#include "PulseAudioLibrary.h"
|
||||
|
||||
#include "devices/DeviceManager.h"
|
||||
#include "devices/IDeviceFactory.h"
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
void PulseAudioDevice::preMixingWork(bool playing)
|
||||
{
|
||||
if(!playing)
|
||||
{
|
||||
if(getRingBuffer().getReadSize() == 0 && !m_corked)
|
||||
{
|
||||
AUD_pa_threaded_mainloop_lock(m_mainloop);
|
||||
AUD_pa_stream_cork(m_stream, 1, nullptr, nullptr);
|
||||
AUD_pa_stream_flush(m_stream, nullptr, nullptr);
|
||||
AUD_pa_threaded_mainloop_unlock(m_mainloop);
|
||||
m_corked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PulseAudioDevice::PulseAudio_state_callback(pa_context* context, void* data)
|
||||
{
|
||||
PulseAudioDevice* device = (PulseAudioDevice*) data;
|
||||
|
||||
device->m_state = AUD_pa_context_get_state(context);
|
||||
|
||||
AUD_pa_threaded_mainloop_signal(device->m_mainloop, 0);
|
||||
}
|
||||
|
||||
void PulseAudioDevice::PulseAudio_request(pa_stream* stream, size_t total_bytes, void* data)
|
||||
{
|
||||
PulseAudioDevice* device = (PulseAudioDevice*) data;
|
||||
|
||||
data_t* buffer;
|
||||
|
||||
size_t sample_size = AUD_DEVICE_SAMPLE_SIZE(device->m_specs);
|
||||
|
||||
while(total_bytes > 0)
|
||||
{
|
||||
size_t num_bytes = total_bytes;
|
||||
|
||||
AUD_pa_stream_begin_write(stream, reinterpret_cast<void**>(&buffer), &num_bytes);
|
||||
|
||||
size_t readsamples = device->getRingBuffer().getReadSize();
|
||||
|
||||
readsamples = std::min(readsamples, size_t(num_bytes)) / sample_size;
|
||||
|
||||
device->getRingBuffer().read(buffer, readsamples * sample_size);
|
||||
|
||||
if(readsamples * sample_size < num_bytes)
|
||||
std::memset(buffer + readsamples * sample_size, 0, num_bytes - readsamples * sample_size);
|
||||
|
||||
device->notifyMixingThread();
|
||||
|
||||
AUD_pa_stream_write(stream, reinterpret_cast<void*>(buffer), num_bytes, nullptr, 0, PA_SEEK_RELATIVE);
|
||||
|
||||
total_bytes -= num_bytes;
|
||||
}
|
||||
AUD_pa_threaded_mainloop_signal(device->m_mainloop, 0);
|
||||
}
|
||||
|
||||
void PulseAudioDevice::playing(bool playing)
|
||||
{
|
||||
std::lock_guard<ILockable> lock(*this);
|
||||
|
||||
MixingThreadDevice::playing(playing);
|
||||
|
||||
if(playing)
|
||||
{
|
||||
AUD_pa_threaded_mainloop_lock(m_mainloop);
|
||||
AUD_pa_stream_cork(m_stream, 0, nullptr, nullptr);
|
||||
AUD_pa_threaded_mainloop_unlock(m_mainloop);
|
||||
m_corked = false;
|
||||
}
|
||||
}
|
||||
|
||||
PulseAudioDevice::PulseAudioDevice(const std::string& name, DeviceSpecs specs, int buffersize) : m_corked(true), m_state(PA_CONTEXT_UNCONNECTED), m_underflows(0)
|
||||
{
|
||||
m_mainloop = AUD_pa_threaded_mainloop_new();
|
||||
|
||||
AUD_pa_threaded_mainloop_lock(m_mainloop);
|
||||
|
||||
m_context = AUD_pa_context_new(AUD_pa_threaded_mainloop_get_api(m_mainloop), name.c_str());
|
||||
|
||||
if(!m_context)
|
||||
{
|
||||
AUD_pa_threaded_mainloop_unlock(m_mainloop);
|
||||
AUD_pa_threaded_mainloop_free(m_mainloop);
|
||||
|
||||
AUD_THROW(DeviceException, "Could not connect to PulseAudio.");
|
||||
}
|
||||
|
||||
AUD_pa_context_set_state_callback(m_context, PulseAudio_state_callback, this);
|
||||
|
||||
AUD_pa_context_connect(m_context, nullptr, PA_CONTEXT_NOFLAGS, nullptr);
|
||||
|
||||
AUD_pa_threaded_mainloop_start(m_mainloop);
|
||||
|
||||
while(m_state != PA_CONTEXT_READY)
|
||||
{
|
||||
switch(m_state)
|
||||
{
|
||||
case PA_CONTEXT_FAILED:
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
AUD_pa_threaded_mainloop_unlock(m_mainloop);
|
||||
AUD_pa_threaded_mainloop_stop(m_mainloop);
|
||||
|
||||
AUD_pa_context_disconnect(m_context);
|
||||
AUD_pa_context_unref(m_context);
|
||||
|
||||
AUD_pa_threaded_mainloop_free(m_mainloop);
|
||||
|
||||
AUD_THROW(DeviceException, "Could not connect to PulseAudio.");
|
||||
break;
|
||||
default:
|
||||
AUD_pa_threaded_mainloop_wait(m_mainloop);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(specs.channels == CHANNELS_INVALID)
|
||||
specs.channels = CHANNELS_STEREO;
|
||||
if(specs.format == FORMAT_INVALID)
|
||||
specs.format = FORMAT_FLOAT32;
|
||||
if(specs.rate == RATE_INVALID)
|
||||
specs.rate = RATE_48000;
|
||||
|
||||
m_specs = specs;
|
||||
|
||||
pa_sample_spec sample_spec;
|
||||
|
||||
sample_spec.channels = specs.channels;
|
||||
sample_spec.format = PA_SAMPLE_FLOAT32;
|
||||
sample_spec.rate = specs.rate;
|
||||
|
||||
switch(m_specs.format)
|
||||
{
|
||||
case FORMAT_U8:
|
||||
sample_spec.format = PA_SAMPLE_U8;
|
||||
break;
|
||||
case FORMAT_S16:
|
||||
sample_spec.format = PA_SAMPLE_S16NE;
|
||||
break;
|
||||
case FORMAT_S24:
|
||||
sample_spec.format = PA_SAMPLE_S24NE;
|
||||
break;
|
||||
case FORMAT_S32:
|
||||
sample_spec.format = PA_SAMPLE_S32NE;
|
||||
break;
|
||||
case FORMAT_FLOAT32:
|
||||
sample_spec.format = PA_SAMPLE_FLOAT32;
|
||||
break;
|
||||
case FORMAT_FLOAT64:
|
||||
m_specs.format = FORMAT_FLOAT32;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
m_stream = AUD_pa_stream_new(m_context, "Playback", &sample_spec, nullptr);
|
||||
|
||||
if(!m_stream)
|
||||
{
|
||||
AUD_pa_threaded_mainloop_unlock(m_mainloop);
|
||||
AUD_pa_threaded_mainloop_stop(m_mainloop);
|
||||
|
||||
AUD_pa_context_disconnect(m_context);
|
||||
AUD_pa_context_unref(m_context);
|
||||
|
||||
AUD_pa_threaded_mainloop_free(m_mainloop);
|
||||
|
||||
AUD_THROW(DeviceException, "Could not create PulseAudio stream.");
|
||||
}
|
||||
|
||||
AUD_pa_stream_set_write_callback(m_stream, PulseAudio_request, this);
|
||||
|
||||
buffersize *= AUD_DEVICE_SAMPLE_SIZE(m_specs);
|
||||
m_buffersize = buffersize;
|
||||
|
||||
pa_buffer_attr buffer_attr;
|
||||
|
||||
buffer_attr.fragsize = -1U;
|
||||
buffer_attr.maxlength = -1U;
|
||||
buffer_attr.minreq = -1U;
|
||||
buffer_attr.prebuf = -1U;
|
||||
buffer_attr.tlength = buffersize;
|
||||
|
||||
if(AUD_pa_stream_connect_playback(m_stream, nullptr, &buffer_attr, static_cast<pa_stream_flags_t>(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_START_CORKED), nullptr, nullptr) < 0)
|
||||
{
|
||||
AUD_pa_threaded_mainloop_unlock(m_mainloop);
|
||||
AUD_pa_threaded_mainloop_stop(m_mainloop);
|
||||
|
||||
AUD_pa_context_disconnect(m_context);
|
||||
AUD_pa_context_unref(m_context);
|
||||
|
||||
AUD_pa_threaded_mainloop_free(m_mainloop);
|
||||
|
||||
AUD_THROW(DeviceException, "Could not connect PulseAudio stream.");
|
||||
}
|
||||
|
||||
/* Make sure that the stream is ready to be used before we proceed. */
|
||||
int stream_state;
|
||||
while((stream_state = AUD_pa_stream_get_state(m_stream)) != PA_STREAM_READY)
|
||||
{
|
||||
switch(stream_state)
|
||||
{
|
||||
case PA_STREAM_FAILED:
|
||||
case PA_STREAM_TERMINATED:
|
||||
AUD_pa_threaded_mainloop_unlock(m_mainloop);
|
||||
AUD_pa_threaded_mainloop_stop(m_mainloop);
|
||||
|
||||
AUD_pa_context_disconnect(m_context);
|
||||
AUD_pa_context_unref(m_context);
|
||||
|
||||
AUD_pa_threaded_mainloop_free(m_mainloop);
|
||||
|
||||
AUD_THROW(DeviceException, "Could not connect to PulseAudio.");
|
||||
break;
|
||||
default:
|
||||
AUD_pa_threaded_mainloop_wait(m_mainloop);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
AUD_pa_threaded_mainloop_unlock(m_mainloop);
|
||||
|
||||
create();
|
||||
|
||||
startMixingThread(buffersize);
|
||||
}
|
||||
|
||||
PulseAudioDevice::~PulseAudioDevice()
|
||||
{
|
||||
stopMixingThread();
|
||||
|
||||
AUD_pa_threaded_mainloop_stop(m_mainloop);
|
||||
|
||||
AUD_pa_context_disconnect(m_context);
|
||||
AUD_pa_context_unref(m_context);
|
||||
|
||||
AUD_pa_threaded_mainloop_free(m_mainloop);
|
||||
|
||||
destroy();
|
||||
}
|
||||
|
||||
void PulseAudioDevice::seekSynchronizer(double time)
|
||||
{
|
||||
/* Update start time here as we might update the seek position while playing back. */
|
||||
AUD_pa_stream_get_time(m_stream, &m_synchronizerStartTime);
|
||||
m_synchronizerStartPosition = time;
|
||||
|
||||
SoftwareDevice::seekSynchronizer(time);
|
||||
}
|
||||
|
||||
double PulseAudioDevice::getSynchronizerPosition()
|
||||
{
|
||||
pa_usec_t time;
|
||||
if(!isSynchronizerPlaying())
|
||||
{
|
||||
return m_synchronizerStartPosition;
|
||||
}
|
||||
AUD_pa_stream_get_time(m_stream, &time);
|
||||
return (time - m_synchronizerStartTime) * 1.0e-6 + m_synchronizerStartPosition;
|
||||
}
|
||||
|
||||
void PulseAudioDevice::playSynchronizer()
|
||||
{
|
||||
/* Make sure that our start time is up to date. */
|
||||
AUD_pa_stream_get_time(m_stream, &m_synchronizerStartTime);
|
||||
SoftwareDevice::playSynchronizer();
|
||||
}
|
||||
|
||||
void PulseAudioDevice::stopSynchronizer()
|
||||
{
|
||||
m_synchronizerStartPosition = getSynchronizerPosition();
|
||||
SoftwareDevice::stopSynchronizer();
|
||||
}
|
||||
|
||||
class PulseAudioDeviceFactory : public IDeviceFactory
|
||||
{
|
||||
private:
|
||||
DeviceSpecs m_specs;
|
||||
int m_buffersize;
|
||||
std::string m_name;
|
||||
|
||||
public:
|
||||
PulseAudioDeviceFactory() :
|
||||
m_buffersize(AUD_DEFAULT_BUFFER_SIZE),
|
||||
m_name("Audaspace")
|
||||
{
|
||||
m_specs.format = FORMAT_FLOAT32;
|
||||
m_specs.channels = CHANNELS_STEREO;
|
||||
m_specs.rate = RATE_48000;
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<IDevice> openDevice()
|
||||
{
|
||||
return std::shared_ptr<IDevice>(new PulseAudioDevice(m_name, m_specs, m_buffersize));
|
||||
}
|
||||
|
||||
virtual int getPriority()
|
||||
{
|
||||
return 1 << 15;
|
||||
}
|
||||
|
||||
virtual void setSpecs(DeviceSpecs specs)
|
||||
{
|
||||
m_specs = specs;
|
||||
}
|
||||
|
||||
virtual void setBufferSize(int buffersize)
|
||||
{
|
||||
m_buffersize = buffersize;
|
||||
}
|
||||
|
||||
virtual void setName(const std::string &name)
|
||||
{
|
||||
m_name = name;
|
||||
}
|
||||
};
|
||||
|
||||
void PulseAudioDevice::registerPlugin()
|
||||
{
|
||||
if(loadPulseAudio())
|
||||
DeviceManager::registerDevice("PulseAudio", std::shared_ptr<IDeviceFactory>(new PulseAudioDeviceFactory));
|
||||
}
|
||||
|
||||
#ifdef PULSEAUDIO_PLUGIN
|
||||
extern "C" AUD_PLUGIN_API void registerPlugin()
|
||||
{
|
||||
PulseAudioDevice::registerPlugin();
|
||||
}
|
||||
|
||||
extern "C" AUD_PLUGIN_API const char* getName()
|
||||
{
|
||||
return "PulseAudio";
|
||||
}
|
||||
#endif
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
105
blender-5.2.0/extern/audaspace/plugins/pulseaudio/PulseAudioDevice.h
vendored
Normal file
105
blender-5.2.0/extern/audaspace/plugins/pulseaudio/PulseAudioDevice.h
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef PULSEAUDIO_PLUGIN
|
||||
#define AUD_BUILD_PLUGIN
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file PulseAudioDevice.h
|
||||
* @ingroup plugin
|
||||
* The PulseAudioDevice class.
|
||||
*/
|
||||
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
#include "devices/MixingThreadDevice.h"
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* This device plays back through PulseAudio, the simple direct media layer.
|
||||
*/
|
||||
class AUD_PLUGIN_API PulseAudioDevice : public MixingThreadDevice
|
||||
{
|
||||
private:
|
||||
bool m_corked;
|
||||
|
||||
pa_threaded_mainloop* m_mainloop;
|
||||
pa_context* m_context;
|
||||
pa_stream* m_stream;
|
||||
pa_context_state_t m_state;
|
||||
|
||||
int m_buffersize;
|
||||
uint32_t m_underflows;
|
||||
|
||||
/// Synchronizer.
|
||||
pa_usec_t m_synchronizerStartTime{0};
|
||||
double m_synchronizerStartPosition{0.0};
|
||||
|
||||
AUD_LOCAL void preMixingWork(bool playing) override;
|
||||
|
||||
/**
|
||||
* Reports the state of the PulseAudio server connection.
|
||||
* \param context The PulseAudio context.
|
||||
* \param data The PulseAudio device.
|
||||
*/
|
||||
AUD_LOCAL static void PulseAudio_state_callback(pa_context* context, void* data);
|
||||
|
||||
/**
|
||||
* Supplies the next samples to PulseAudio.
|
||||
* \param stream The PulseAudio stream.
|
||||
* \param num_bytes The length in bytes to be supplied.
|
||||
* \param data The PulseAudio device.
|
||||
*/
|
||||
AUD_LOCAL static void PulseAudio_request(pa_stream* stream, size_t total_bytes, void* data);
|
||||
|
||||
// delete copy constructor and operator=
|
||||
PulseAudioDevice(const PulseAudioDevice&) = delete;
|
||||
PulseAudioDevice& operator=(const PulseAudioDevice&) = delete;
|
||||
|
||||
protected:
|
||||
virtual void playing(bool playing);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Opens the PulseAudio audio device for playback.
|
||||
* \param specs The wanted audio specification.
|
||||
* \param buffersize The size of the internal buffer.
|
||||
* \note The specification really used for opening the device may differ.
|
||||
* \exception Exception Thrown if the audio device cannot be opened.
|
||||
*/
|
||||
PulseAudioDevice(const std::string &name, DeviceSpecs specs, int buffersize = AUD_DEFAULT_BUFFER_SIZE);
|
||||
|
||||
/**
|
||||
* Closes the PulseAudio audio device.
|
||||
*/
|
||||
virtual ~PulseAudioDevice();
|
||||
|
||||
virtual void seekSynchronizer(double time);
|
||||
virtual double getSynchronizerPosition();
|
||||
virtual void playSynchronizer();
|
||||
virtual void stopSynchronizer();
|
||||
|
||||
/**
|
||||
* Registers this plugin.
|
||||
*/
|
||||
static void registerPlugin();
|
||||
};
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
59
blender-5.2.0/extern/audaspace/plugins/pulseaudio/PulseAudioLibrary.cpp
vendored
Normal file
59
blender-5.2.0/extern/audaspace/plugins/pulseaudio/PulseAudioLibrary.cpp
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#define PULSEAUDIO_LIBRARY_IMPLEMENTATION
|
||||
|
||||
#include <string>
|
||||
#include <array>
|
||||
|
||||
#include "PulseAudioLibrary.h"
|
||||
|
||||
#ifdef DYNLOAD_PULSEAUDIO
|
||||
#include "plugin/PluginManager.h"
|
||||
#endif
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
bool loadPulseAudio()
|
||||
{
|
||||
#ifdef DYNLOAD_PULSEAUDIO
|
||||
std::array<const std::string, 2> names = {"libpulse.so", "libpulse.so.0"};
|
||||
|
||||
void* handle = nullptr;
|
||||
|
||||
for(auto& name : names)
|
||||
{
|
||||
handle = PluginManager::openLibrary(name);
|
||||
if(handle)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!handle)
|
||||
return false;
|
||||
|
||||
#define PULSEAUDIO_SYMBOL(sym) AUD_##sym = reinterpret_cast<decltype(&sym)>(PluginManager::lookupLibrary(handle, #sym))
|
||||
#else
|
||||
#define PULSEAUDIO_SYMBOL(sym) AUD_##sym = &sym
|
||||
#endif
|
||||
|
||||
#include "PulseAudioSymbols.h"
|
||||
|
||||
#undef PULSEAUDIO_SYMBOL
|
||||
|
||||
return AUD_pa_context_new != nullptr;
|
||||
}
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
46
blender-5.2.0/extern/audaspace/plugins/pulseaudio/PulseAudioLibrary.h
vendored
Normal file
46
blender-5.2.0/extern/audaspace/plugins/pulseaudio/PulseAudioLibrary.h
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef PULSEAUDIO_PLUGIN
|
||||
#define AUD_BUILD_PLUGIN
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file PulseAudioLibrary.h
|
||||
* @ingroup plugin
|
||||
*/
|
||||
|
||||
#include "Audaspace.h"
|
||||
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
#ifdef PULSEAUDIO_LIBRARY_IMPLEMENTATION
|
||||
#define PULSEAUDIO_SYMBOL(sym) decltype(&sym) AUD_##sym
|
||||
#else
|
||||
#define PULSEAUDIO_SYMBOL(sym) extern decltype(&sym) AUD_##sym
|
||||
#endif
|
||||
|
||||
#include "PulseAudioSymbols.h"
|
||||
|
||||
#undef PULSEAUDIO_SYMBOL
|
||||
|
||||
bool loadPulseAudio();
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
53
blender-5.2.0/extern/audaspace/plugins/pulseaudio/PulseAudioSymbols.h
vendored
Normal file
53
blender-5.2.0/extern/audaspace/plugins/pulseaudio/PulseAudioSymbols.h
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
PULSEAUDIO_SYMBOL(pa_context_connect);
|
||||
PULSEAUDIO_SYMBOL(pa_context_disconnect);
|
||||
PULSEAUDIO_SYMBOL(pa_context_get_state);
|
||||
PULSEAUDIO_SYMBOL(pa_context_new);
|
||||
PULSEAUDIO_SYMBOL(pa_context_set_state_callback);
|
||||
PULSEAUDIO_SYMBOL(pa_context_unref);
|
||||
|
||||
PULSEAUDIO_SYMBOL(pa_stream_begin_write);
|
||||
PULSEAUDIO_SYMBOL(pa_stream_connect_playback);
|
||||
PULSEAUDIO_SYMBOL(pa_stream_cork);
|
||||
PULSEAUDIO_SYMBOL(pa_stream_flush);
|
||||
PULSEAUDIO_SYMBOL(pa_stream_get_state);
|
||||
PULSEAUDIO_SYMBOL(pa_stream_get_time);
|
||||
PULSEAUDIO_SYMBOL(pa_stream_is_corked);
|
||||
PULSEAUDIO_SYMBOL(pa_stream_new);
|
||||
PULSEAUDIO_SYMBOL(pa_stream_set_buffer_attr);
|
||||
PULSEAUDIO_SYMBOL(pa_stream_set_underflow_callback);
|
||||
PULSEAUDIO_SYMBOL(pa_stream_set_write_callback);
|
||||
PULSEAUDIO_SYMBOL(pa_stream_write);
|
||||
|
||||
PULSEAUDIO_SYMBOL(pa_mainloop_free);
|
||||
PULSEAUDIO_SYMBOL(pa_mainloop_get_api);
|
||||
PULSEAUDIO_SYMBOL(pa_mainloop_new);
|
||||
PULSEAUDIO_SYMBOL(pa_mainloop_iterate);
|
||||
PULSEAUDIO_SYMBOL(pa_mainloop_prepare);
|
||||
PULSEAUDIO_SYMBOL(pa_mainloop_poll);
|
||||
PULSEAUDIO_SYMBOL(pa_mainloop_dispatch);
|
||||
|
||||
PULSEAUDIO_SYMBOL(pa_threaded_mainloop_free);
|
||||
PULSEAUDIO_SYMBOL(pa_threaded_mainloop_get_api);
|
||||
PULSEAUDIO_SYMBOL(pa_threaded_mainloop_lock);
|
||||
PULSEAUDIO_SYMBOL(pa_threaded_mainloop_new);
|
||||
PULSEAUDIO_SYMBOL(pa_threaded_mainloop_signal);
|
||||
PULSEAUDIO_SYMBOL(pa_threaded_mainloop_start);
|
||||
PULSEAUDIO_SYMBOL(pa_threaded_mainloop_stop);
|
||||
PULSEAUDIO_SYMBOL(pa_threaded_mainloop_unlock);
|
||||
PULSEAUDIO_SYMBOL(pa_threaded_mainloop_wait);
|
||||
169
blender-5.2.0/extern/audaspace/plugins/sdl/SDLDevice.cpp
vendored
Normal file
169
blender-5.2.0/extern/audaspace/plugins/sdl/SDLDevice.cpp
vendored
Normal file
@@ -0,0 +1,169 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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 "SDLDevice.h"
|
||||
#include "devices/DeviceManager.h"
|
||||
#include "devices/IDeviceFactory.h"
|
||||
#include "Exception.h"
|
||||
#include "IReader.h"
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
void SDLDevice::SDL_mix(void* userdata, SDL_AudioStream* stream, int additional_amount, int /*total_amount*/)
|
||||
{
|
||||
SDLDevice* device = (SDLDevice*)userdata;
|
||||
|
||||
if(!device->m_playback)
|
||||
return;
|
||||
|
||||
const int sample_size = AUD_DEVICE_SAMPLE_SIZE(device->m_specs);
|
||||
const int num_samples = additional_amount / sample_size;
|
||||
data_t* buffer = (data_t*)SDL_stack_alloc(Uint8, additional_amount);
|
||||
device->mix(buffer, num_samples);
|
||||
SDL_PutAudioStreamData(stream, buffer, additional_amount);
|
||||
SDL_stack_free(buffer);
|
||||
}
|
||||
|
||||
void SDLDevice::playing(bool playing)
|
||||
{
|
||||
if(playing)
|
||||
SDL_ResumeAudioStreamDevice(m_stream);
|
||||
else
|
||||
SDL_PauseAudioStreamDevice(m_stream);
|
||||
|
||||
m_playback = playing;
|
||||
}
|
||||
|
||||
SDL_AudioSpec SDLDevice::sdl_audiospec_from_device_specs(const DeviceSpecs &specs)
|
||||
{
|
||||
SDL_AudioSpec audiospec;
|
||||
|
||||
switch(specs.format)
|
||||
{
|
||||
case FORMAT_U8:
|
||||
audiospec.format = SDL_AUDIO_U8;
|
||||
break;
|
||||
case FORMAT_S16:
|
||||
audiospec.format = SDL_AUDIO_S16;
|
||||
break;
|
||||
case FORMAT_S32:
|
||||
audiospec.format = SDL_AUDIO_S32;
|
||||
break;
|
||||
case FORMAT_FLOAT32:
|
||||
audiospec.format = SDL_AUDIO_F32;
|
||||
break;
|
||||
default:
|
||||
audiospec.format = SDL_AUDIO_F32;
|
||||
break;
|
||||
}
|
||||
|
||||
audiospec.channels = specs.channels;
|
||||
audiospec.freq = specs.rate;
|
||||
|
||||
return audiospec;
|
||||
}
|
||||
|
||||
SDLDevice::SDLDevice(DeviceSpecs specs, int buffersize) :
|
||||
m_playback(false),
|
||||
m_stream(nullptr)
|
||||
{
|
||||
if(specs.channels == CHANNELS_INVALID)
|
||||
specs.channels = CHANNELS_STEREO;
|
||||
if(specs.format == FORMAT_INVALID)
|
||||
specs.format = FORMAT_FLOAT32;
|
||||
if(specs.rate == static_cast<SampleRate>(RATE_INVALID))
|
||||
specs.rate = RATE_48000;
|
||||
|
||||
m_specs = specs;
|
||||
|
||||
if(!SDL_InitSubSystem(SDL_INIT_AUDIO))
|
||||
AUD_THROW(DeviceException, "Failed to initialize SDL Audio subsystem.");
|
||||
|
||||
const SDL_AudioSpec audiospec = sdl_audiospec_from_device_specs(specs);
|
||||
m_stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, &audiospec, SDLDevice::SDL_mix, this);
|
||||
|
||||
if(!m_stream)
|
||||
AUD_THROW(DeviceException, "The audio device couldn't be opened with SDL.");
|
||||
|
||||
create();
|
||||
}
|
||||
|
||||
SDLDevice::~SDLDevice()
|
||||
{
|
||||
destroy();
|
||||
|
||||
SDL_DestroyAudioStream(m_stream);
|
||||
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
||||
}
|
||||
|
||||
class SDLDeviceFactory : public IDeviceFactory
|
||||
{
|
||||
private:
|
||||
DeviceSpecs m_specs;
|
||||
int m_buffersize;
|
||||
|
||||
public:
|
||||
SDLDeviceFactory() :
|
||||
m_buffersize(AUD_DEFAULT_BUFFER_SIZE)
|
||||
{
|
||||
m_specs.format = FORMAT_S16;
|
||||
m_specs.channels = CHANNELS_STEREO;
|
||||
m_specs.rate = RATE_48000;
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<IDevice> openDevice()
|
||||
{
|
||||
return std::shared_ptr<IDevice>(new SDLDevice(m_specs, m_buffersize));
|
||||
}
|
||||
|
||||
virtual int getPriority()
|
||||
{
|
||||
return 1 << 5;
|
||||
}
|
||||
|
||||
virtual void setSpecs(DeviceSpecs specs)
|
||||
{
|
||||
m_specs = specs;
|
||||
}
|
||||
|
||||
virtual void setBufferSize(int buffersize)
|
||||
{
|
||||
m_buffersize = buffersize;
|
||||
}
|
||||
|
||||
virtual void setName(const std::string &name)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
void SDLDevice::registerPlugin()
|
||||
{
|
||||
DeviceManager::registerDevice("SDL", std::shared_ptr<IDeviceFactory>(new SDLDeviceFactory));
|
||||
}
|
||||
|
||||
#ifdef SDL_PLUGIN
|
||||
extern "C" AUD_PLUGIN_API void registerPlugin()
|
||||
{
|
||||
SDLDevice::registerPlugin();
|
||||
}
|
||||
|
||||
extern "C" AUD_PLUGIN_API const char* getName()
|
||||
{
|
||||
return "SDL";
|
||||
}
|
||||
#endif
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
94
blender-5.2.0/extern/audaspace/plugins/sdl/SDLDevice.h
vendored
Normal file
94
blender-5.2.0/extern/audaspace/plugins/sdl/SDLDevice.h
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef SDL_PLUGIN
|
||||
#define AUD_BUILD_PLUGIN
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file SDLDevice.h
|
||||
* @ingroup plugin
|
||||
* The SDLDevice class.
|
||||
*/
|
||||
|
||||
#include "devices/SoftwareDevice.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
/**
|
||||
* This device plays back through SDL, the simple direct media layer.
|
||||
*/
|
||||
class AUD_PLUGIN_API SDLDevice : public SoftwareDevice
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* Whether there is currently playback.
|
||||
*/
|
||||
bool m_playback;
|
||||
|
||||
/**
|
||||
* The SDL audio stream.
|
||||
*/
|
||||
SDL_AudioStream* m_stream;
|
||||
|
||||
/**
|
||||
* SDL callback to mix the next bytes into the audio stream.
|
||||
* Uses the SDL_AudioStreamCallback signature.
|
||||
* \param userdata The SDL device.
|
||||
* \param stream The target audio stream.
|
||||
* \param additional_amount The number of bytes needed.
|
||||
* \param total_amount The total amount of data buffered.
|
||||
*/
|
||||
AUD_LOCAL static void SDL_mix(void* userdata, SDL_AudioStream* stream, int additional_amount, int total_amount);
|
||||
|
||||
/**
|
||||
* Helper function to convert Audaspace DeviceSpecs structs to SDL SDL_AudioSpec structs.
|
||||
*/
|
||||
AUD_LOCAL static SDL_AudioSpec sdl_audiospec_from_device_specs(const DeviceSpecs &specs);
|
||||
|
||||
// delete copy constructor and operator=
|
||||
SDLDevice(const SDLDevice&) = delete;
|
||||
SDLDevice& operator=(const SDLDevice&) = delete;
|
||||
|
||||
protected:
|
||||
virtual void playing(bool playing);
|
||||
|
||||
public:
|
||||
/**
|
||||
* Opens the SDL audio device for playback.
|
||||
* \param specs The wanted audio specification.
|
||||
* \param buffersize The size of the internal buffer.
|
||||
* \note The specification really used for opening the device may differ.
|
||||
* \exception Exception Thrown if the audio device cannot be opened.
|
||||
*/
|
||||
SDLDevice(DeviceSpecs specs, int buffersize = AUD_DEFAULT_BUFFER_SIZE);
|
||||
|
||||
/**
|
||||
* Closes the SDL audio device.
|
||||
*/
|
||||
virtual ~SDLDevice();
|
||||
|
||||
/**
|
||||
* Registers this plugin.
|
||||
*/
|
||||
static void registerPlugin();
|
||||
};
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
463
blender-5.2.0/extern/audaspace/plugins/wasapi/WASAPIDevice.cpp
vendored
Normal file
463
blender-5.2.0/extern/audaspace/plugins/wasapi/WASAPIDevice.cpp
vendored
Normal file
@@ -0,0 +1,463 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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 "WASAPIDevice.h"
|
||||
#include "devices/DeviceManager.h"
|
||||
#include "devices/IDeviceFactory.h"
|
||||
#include "Exception.h"
|
||||
#include "IReader.h"
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
HRESULT WASAPIDevice::setupRenderClient(IAudioRenderClient*& render_client, UINT32& buffer_size)
|
||||
{
|
||||
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
|
||||
|
||||
UINT32 padding;
|
||||
UINT32 length;
|
||||
data_t* buffer;
|
||||
|
||||
HRESULT result;
|
||||
|
||||
if(FAILED(result = m_audio_client->GetBufferSize(&buffer_size)))
|
||||
return result;
|
||||
|
||||
if(FAILED(result = m_audio_client->GetService(IID_IAudioRenderClient, reinterpret_cast<void**>(&render_client))))
|
||||
return result;
|
||||
|
||||
if(FAILED(result = m_audio_client->GetCurrentPadding(&padding)))
|
||||
return result;
|
||||
|
||||
length = buffer_size - padding;
|
||||
|
||||
if(FAILED(result = render_client->GetBuffer(length, &buffer)))
|
||||
return result;
|
||||
|
||||
mix((data_t*)buffer, length);
|
||||
|
||||
if(FAILED(result = render_client->ReleaseBuffer(length, 0)))
|
||||
return result;
|
||||
|
||||
m_audio_client->Start();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void WASAPIDevice::runMixingThread()
|
||||
{
|
||||
UINT32 buffer_size;
|
||||
|
||||
IAudioRenderClient* render_client = nullptr;
|
||||
|
||||
std::chrono::milliseconds sleep_duration(0);
|
||||
|
||||
bool run_init = true;
|
||||
|
||||
for(;;)
|
||||
{
|
||||
HRESULT result = S_OK;
|
||||
|
||||
{
|
||||
UINT32 padding;
|
||||
UINT32 length;
|
||||
data_t* buffer;
|
||||
std::lock_guard<ILockable> lock(*this);
|
||||
|
||||
if(run_init)
|
||||
{
|
||||
result = setupRenderClient(render_client, buffer_size);
|
||||
|
||||
if(FAILED(result))
|
||||
goto stop_thread;
|
||||
|
||||
sleep_duration = std::chrono::milliseconds(buffer_size * 1000 / int(m_specs.rate) / 2);
|
||||
}
|
||||
|
||||
if(m_default_device_changed)
|
||||
{
|
||||
m_default_device_changed = false;
|
||||
result = AUDCLNT_E_DEVICE_INVALIDATED;
|
||||
goto stop_thread;
|
||||
}
|
||||
|
||||
if(FAILED(result = m_audio_client->GetCurrentPadding(&padding)))
|
||||
goto stop_thread;
|
||||
|
||||
length = buffer_size - padding;
|
||||
|
||||
if(FAILED(result = render_client->GetBuffer(length, &buffer)))
|
||||
goto stop_thread;
|
||||
|
||||
mix((data_t*)buffer, length);
|
||||
|
||||
if(FAILED(result = render_client->ReleaseBuffer(length, 0)))
|
||||
goto stop_thread;
|
||||
|
||||
// stop thread
|
||||
if(shouldStop())
|
||||
{
|
||||
stop_thread:
|
||||
m_audio_client->Stop();
|
||||
|
||||
if(result == AUDCLNT_E_DEVICE_INVALIDATED)
|
||||
{
|
||||
DeviceSpecs specs = m_specs;
|
||||
if(!setupDevice(specs))
|
||||
result = S_FALSE;
|
||||
else
|
||||
{
|
||||
setSpecs(specs);
|
||||
|
||||
run_init = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(result != AUDCLNT_E_DEVICE_INVALIDATED)
|
||||
{
|
||||
doStop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(sleep_duration);
|
||||
}
|
||||
}
|
||||
|
||||
bool WASAPIDevice::setupDevice(DeviceSpecs &specs)
|
||||
{
|
||||
const IID IID_IAudioClient = __uuidof(IAudioClient);
|
||||
|
||||
if(FAILED(m_imm_device_enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &m_imm_device)))
|
||||
return false;
|
||||
|
||||
if(FAILED(m_imm_device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, reinterpret_cast<void**>(m_audio_client.GetAddressOf()))))
|
||||
return false;
|
||||
|
||||
WAVEFORMATEXTENSIBLE wave_format_extensible_closest_match;
|
||||
WAVEFORMATEXTENSIBLE* closest_match_pointer = &wave_format_extensible_closest_match;
|
||||
|
||||
REFERENCE_TIME minimum_time = 0;
|
||||
REFERENCE_TIME buffer_duration;
|
||||
|
||||
switch(specs.format)
|
||||
{
|
||||
case FORMAT_U8:
|
||||
case FORMAT_S16:
|
||||
case FORMAT_S24:
|
||||
case FORMAT_S32:
|
||||
m_wave_format_extensible.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
||||
break;
|
||||
case FORMAT_FLOAT32:
|
||||
m_wave_format_extensible.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
|
||||
break;
|
||||
default:
|
||||
m_wave_format_extensible.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
|
||||
specs.format = FORMAT_FLOAT32;
|
||||
break;
|
||||
}
|
||||
|
||||
switch(specs.channels)
|
||||
{
|
||||
case CHANNELS_MONO:
|
||||
m_wave_format_extensible.dwChannelMask = SPEAKER_FRONT_CENTER;
|
||||
break;
|
||||
case CHANNELS_STEREO:
|
||||
m_wave_format_extensible.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
|
||||
break;
|
||||
case CHANNELS_STEREO_LFE:
|
||||
m_wave_format_extensible.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY;
|
||||
break;
|
||||
case CHANNELS_SURROUND4:
|
||||
m_wave_format_extensible.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
|
||||
break;
|
||||
case CHANNELS_SURROUND5:
|
||||
m_wave_format_extensible.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
|
||||
break;
|
||||
case CHANNELS_SURROUND51:
|
||||
m_wave_format_extensible.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
|
||||
break;
|
||||
case CHANNELS_SURROUND61:
|
||||
m_wave_format_extensible.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
|
||||
break;
|
||||
case CHANNELS_SURROUND71:
|
||||
m_wave_format_extensible.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT;
|
||||
break;
|
||||
default:
|
||||
specs.channels = CHANNELS_STEREO;
|
||||
m_wave_format_extensible.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
|
||||
break;
|
||||
}
|
||||
|
||||
m_wave_format_extensible.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
||||
m_wave_format_extensible.Format.nChannels = specs.channels;
|
||||
m_wave_format_extensible.Format.nSamplesPerSec = specs.rate;
|
||||
m_wave_format_extensible.Format.nAvgBytesPerSec = specs.rate * AUD_DEVICE_SAMPLE_SIZE(specs);
|
||||
m_wave_format_extensible.Format.nBlockAlign = AUD_DEVICE_SAMPLE_SIZE(specs);
|
||||
m_wave_format_extensible.Format.wBitsPerSample = AUD_FORMAT_SIZE(specs.format) * 8;
|
||||
m_wave_format_extensible.Format.cbSize = 22;
|
||||
m_wave_format_extensible.Samples.wValidBitsPerSample = m_wave_format_extensible.Format.wBitsPerSample;
|
||||
|
||||
HRESULT result = m_audio_client->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, reinterpret_cast<const WAVEFORMATEX*>(&m_wave_format_extensible), reinterpret_cast<WAVEFORMATEX**>(&closest_match_pointer));
|
||||
|
||||
if(result == S_FALSE)
|
||||
{
|
||||
bool errored = false;
|
||||
|
||||
if(closest_match_pointer->Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE)
|
||||
goto closest_match_error;
|
||||
|
||||
specs.channels = Channels(closest_match_pointer->Format.nChannels);
|
||||
specs.rate = closest_match_pointer->Format.nSamplesPerSec;
|
||||
|
||||
if(closest_match_pointer->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)
|
||||
{
|
||||
if(closest_match_pointer->Format.wBitsPerSample == 32)
|
||||
specs.format = FORMAT_FLOAT32;
|
||||
else if(closest_match_pointer->Format.wBitsPerSample == 64)
|
||||
specs.format = FORMAT_FLOAT64;
|
||||
else
|
||||
goto closest_match_error;
|
||||
}
|
||||
else if(closest_match_pointer->SubFormat == KSDATAFORMAT_SUBTYPE_PCM)
|
||||
{
|
||||
switch(closest_match_pointer->Format.wBitsPerSample)
|
||||
{
|
||||
case 8:
|
||||
specs.format = FORMAT_U8;
|
||||
break;
|
||||
case 16:
|
||||
specs.format = FORMAT_S16;
|
||||
break;
|
||||
case 24:
|
||||
specs.format = FORMAT_S24;
|
||||
break;
|
||||
case 32:
|
||||
specs.format = FORMAT_S32;
|
||||
break;
|
||||
default:
|
||||
goto closest_match_error;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
goto closest_match_error;
|
||||
|
||||
m_wave_format_extensible = *closest_match_pointer;
|
||||
|
||||
if(false)
|
||||
{
|
||||
closest_match_error:
|
||||
errored = true;
|
||||
}
|
||||
|
||||
if(closest_match_pointer != &wave_format_extensible_closest_match)
|
||||
{
|
||||
CoTaskMemFree(closest_match_pointer);
|
||||
closest_match_pointer = &wave_format_extensible_closest_match;
|
||||
}
|
||||
|
||||
if(errored)
|
||||
return false;
|
||||
}
|
||||
else if(FAILED(result))
|
||||
return false;
|
||||
|
||||
if(FAILED(m_audio_client->GetDevicePeriod(nullptr, &minimum_time)))
|
||||
return false;
|
||||
|
||||
buffer_duration = REFERENCE_TIME(m_buffersize) * REFERENCE_TIME(10000000) / REFERENCE_TIME(specs.rate);
|
||||
|
||||
if(minimum_time > buffer_duration)
|
||||
buffer_duration = minimum_time;
|
||||
|
||||
if(FAILED(m_audio_client->Initialize(AUDCLNT_SHAREMODE_SHARED, 0, buffer_duration, 0, reinterpret_cast<WAVEFORMATEX*>(&m_wave_format_extensible), nullptr)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ULONG WASAPIDevice::AddRef()
|
||||
{
|
||||
return InterlockedIncrement(&m_reference_count);
|
||||
}
|
||||
|
||||
ULONG WASAPIDevice::Release()
|
||||
{
|
||||
ULONG reference_count = InterlockedDecrement(&m_reference_count);
|
||||
|
||||
if(0 == reference_count)
|
||||
delete this;
|
||||
|
||||
return reference_count;
|
||||
}
|
||||
|
||||
HRESULT WASAPIDevice::QueryInterface(REFIID riid, void **ppvObject)
|
||||
{
|
||||
if(riid == __uuidof(IMMNotificationClient))
|
||||
{
|
||||
*ppvObject = reinterpret_cast<IMMNotificationClient*>(this);
|
||||
AddRef();
|
||||
}
|
||||
else if(riid == IID_IUnknown)
|
||||
{
|
||||
*ppvObject = reinterpret_cast<IUnknown*>(this);
|
||||
AddRef();
|
||||
}
|
||||
else
|
||||
{
|
||||
*ppvObject = nullptr;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT WASAPIDevice::OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT WASAPIDevice::OnDeviceAdded(LPCWSTR pwstrDeviceId)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT WASAPIDevice::OnDeviceRemoved(LPCWSTR pwstrDeviceId)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT WASAPIDevice::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId)
|
||||
{
|
||||
if(flow != EDataFlow::eCapture)
|
||||
m_default_device_changed = true;
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT WASAPIDevice::OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
WASAPIDevice::WASAPIDevice(DeviceSpecs specs, int buffersize) :
|
||||
m_buffersize(buffersize),
|
||||
m_imm_device_enumerator(nullptr),
|
||||
m_imm_device(nullptr),
|
||||
m_audio_client(nullptr),
|
||||
m_wave_format_extensible({}),
|
||||
m_default_device_changed(false),
|
||||
m_reference_count(1)
|
||||
{
|
||||
// initialize COM if it hasn't happened yet
|
||||
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
|
||||
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
|
||||
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
|
||||
|
||||
if(specs.channels == CHANNELS_INVALID)
|
||||
specs.channels = CHANNELS_STEREO;
|
||||
if(specs.format == FORMAT_INVALID)
|
||||
specs.format = FORMAT_FLOAT32;
|
||||
if(int(specs.rate) == RATE_INVALID)
|
||||
specs.rate = RATE_48000;
|
||||
|
||||
if(FAILED(CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL, IID_IMMDeviceEnumerator, reinterpret_cast<void**>(m_imm_device_enumerator.GetAddressOf()))))
|
||||
goto error;
|
||||
|
||||
if(!setupDevice(specs))
|
||||
goto error;
|
||||
|
||||
m_specs = specs;
|
||||
|
||||
create();
|
||||
|
||||
m_imm_device_enumerator->RegisterEndpointNotificationCallback(this);
|
||||
|
||||
return;
|
||||
|
||||
error:
|
||||
AUD_THROW(DeviceException, "The audio device couldn't be opened with WASAPI.");
|
||||
}
|
||||
|
||||
WASAPIDevice::~WASAPIDevice()
|
||||
{
|
||||
stopMixingThread();
|
||||
|
||||
m_imm_device_enumerator->UnregisterEndpointNotificationCallback(this);
|
||||
|
||||
destroy();
|
||||
}
|
||||
|
||||
class WASAPIDeviceFactory : public IDeviceFactory
|
||||
{
|
||||
private:
|
||||
DeviceSpecs m_specs;
|
||||
int m_buffersize;
|
||||
|
||||
public:
|
||||
WASAPIDeviceFactory() :
|
||||
m_buffersize(AUD_DEFAULT_BUFFER_SIZE)
|
||||
{
|
||||
m_specs.format = FORMAT_S16;
|
||||
m_specs.channels = CHANNELS_STEREO;
|
||||
m_specs.rate = RATE_48000;
|
||||
}
|
||||
|
||||
virtual std::shared_ptr<IDevice> openDevice()
|
||||
{
|
||||
return std::shared_ptr<IDevice>(new WASAPIDevice(m_specs, m_buffersize));
|
||||
}
|
||||
|
||||
virtual int getPriority()
|
||||
{
|
||||
return 1 << 15;
|
||||
}
|
||||
|
||||
virtual void setSpecs(DeviceSpecs specs)
|
||||
{
|
||||
m_specs = specs;
|
||||
}
|
||||
|
||||
virtual void setBufferSize(int buffersize)
|
||||
{
|
||||
m_buffersize = buffersize;
|
||||
}
|
||||
|
||||
virtual void setName(const std::string &name)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
void WASAPIDevice::registerPlugin()
|
||||
{
|
||||
DeviceManager::registerDevice("WASAPI", std::shared_ptr<IDeviceFactory>(new WASAPIDeviceFactory));
|
||||
}
|
||||
|
||||
#ifdef WASAPI_PLUGIN
|
||||
extern "C" AUD_PLUGIN_API void registerPlugin()
|
||||
{
|
||||
WASAPIDevice::registerPlugin();
|
||||
}
|
||||
|
||||
extern "C" AUD_PLUGIN_API const char* getName()
|
||||
{
|
||||
return "WASAPI";
|
||||
}
|
||||
#endif
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
102
blender-5.2.0/extern/audaspace/plugins/wasapi/WASAPIDevice.h
vendored
Normal file
102
blender-5.2.0/extern/audaspace/plugins/wasapi/WASAPIDevice.h
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2009-2016 Jörg Müller
|
||||
*
|
||||
* 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.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef WASAPI_PLUGIN
|
||||
#define AUD_BUILD_PLUGIN
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @file WASAPIDevice.h
|
||||
* @ingroup plugin
|
||||
* The WASAPIDevice class.
|
||||
*/
|
||||
|
||||
#include "devices/ThreadedDevice.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include <windows.h>
|
||||
#include <audioclient.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <mmreg.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
AUD_NAMESPACE_BEGIN
|
||||
|
||||
using Microsoft::WRL::ComPtr;
|
||||
|
||||
/**
|
||||
* This device plays back through WASAPI, the Windows audio API.
|
||||
*/
|
||||
class AUD_PLUGIN_API WASAPIDevice : IMMNotificationClient, public ThreadedDevice
|
||||
{
|
||||
private:
|
||||
int m_buffersize;
|
||||
ComPtr<IMMDeviceEnumerator> m_imm_device_enumerator;
|
||||
ComPtr<IMMDevice> m_imm_device;
|
||||
ComPtr<IAudioClient> m_audio_client;
|
||||
WAVEFORMATEXTENSIBLE m_wave_format_extensible;
|
||||
bool m_default_device_changed;
|
||||
LONG m_reference_count;
|
||||
|
||||
AUD_LOCAL HRESULT setupRenderClient(IAudioRenderClient*& render_client, UINT32& buffer_size);
|
||||
|
||||
/**
|
||||
* Streaming thread main function.
|
||||
*/
|
||||
AUD_LOCAL void runMixingThread();
|
||||
|
||||
AUD_LOCAL bool setupDevice(DeviceSpecs& specs);
|
||||
|
||||
// IUnknown implementation
|
||||
ULONG STDMETHODCALLTYPE AddRef();
|
||||
ULONG STDMETHODCALLTYPE Release();
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject);
|
||||
// IMMNotificationClient implementation
|
||||
HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState);
|
||||
HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId);
|
||||
HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId);
|
||||
HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId);
|
||||
HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key);
|
||||
|
||||
// delete copy constructor and operator=
|
||||
WASAPIDevice(const WASAPIDevice&) = delete;
|
||||
WASAPIDevice& operator=(const WASAPIDevice&) = delete;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Opens the WASAPI audio device for playback.
|
||||
* \param specs The wanted audio specification.
|
||||
* \param buffersize The size of the internal buffer.
|
||||
* \note The specification really used for opening the device may differ.
|
||||
* \exception Exception Thrown if the audio device cannot be opened.
|
||||
*/
|
||||
WASAPIDevice(DeviceSpecs specs, int buffersize = AUD_DEFAULT_BUFFER_SIZE);
|
||||
|
||||
/**
|
||||
* Closes the WASAPI audio device.
|
||||
*/
|
||||
virtual ~WASAPIDevice();
|
||||
|
||||
/**
|
||||
* Registers this plugin.
|
||||
*/
|
||||
static void registerPlugin();
|
||||
};
|
||||
|
||||
AUD_NAMESPACE_END
|
||||
Reference in New Issue
Block a user