Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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);

View 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

View 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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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), &param, 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

View 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

View 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

View 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

View 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);

View 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

View 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

View 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

View 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

View 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);

View 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

View 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

View 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

View 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