Bochs/bochs/iodev/sound/soundosx.cc
Volker Ruppert f844e846b7 Added PCM output data resampling in a separate thread. The resampler requires
either libsamplerate or the SoX resampler library installed. If not installed,
the data is only copied to the output buffer and sample rate of the output
driver is changed similar to legacy code. Related changes:
- Added check for libsamplerate or SoX resampler header files in configure
  script.
- Added functions for converting source format to float (requied by resampler)
  and float to output format.
- Added support for float type data in the audio buffer code. Buffer chain #0
  receives float data from sendwavepacket() and buffer #1 receives data for
  mixing and output in format "16-bit signed little endian stereo".
- ALSA: Disable builtin resampling feature if resampler is present to avoid
  doing it twice.
2017-02-05 08:33:03 +00:00

567 lines
18 KiB
C++

/////////////////////////////////////////////////////////////////////////
// $Id$
/////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2004-2017 The Bochs Project
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
// This file (SOUNDOSX.CC) written and donated by Brian Huffman
#ifdef PARANOID
#include <MacTypes.h>
#endif
#include "iodev.h"
#include "soundlow.h"
#include "soundosx.h"
#if BX_HAVE_SOUND_OSX && BX_SUPPORT_SOUNDLOW
#define LOG_THIS
#if BX_WITH_MACOS
#include <QuickTimeMusic.h>
#else
#include <CoreServices/CoreServices.h>
#include <CoreAudio/CoreAudio.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/DefaultAudioOutput.h>
#include <AudioToolbox/AudioConverter.h>
#include <AudioToolbox/AUGraph.h>
#include <QuickTime/QuickTimeMusic.h>
#endif
#include <string.h>
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= 1030)
#define BX_SOUND_OSX_CONVERTER_NEW_API 1
#endif
#ifdef BX_SOUND_OSX_use_converter
#ifndef BX_SOUND_OSX_CONVERTER_NEW_API
OSStatus MyRenderer (void *inRefCon, AudioUnitRenderActionFlags inActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, AudioBuffer *ioData);
OSStatus MyACInputProc (AudioConverterRef inAudioConverter, UInt32* outDataSize, void** outData, void* inUserData);
#else
OSStatus MyRenderer (void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData);
OSStatus MyACInputProc (AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData);
#endif
#endif
// Global variables
#ifdef BX_SOUND_OSX_use_converter
AUGraph MidiGraph;
AudioUnit synthUnit;
#endif
#ifdef BX_SOUND_OSX_use_quicktime
SndChannelPtr WaveChannel;
ExtSoundHeader WaveInfo;
ExtSoundHeader WaveHeader[BX_SOUND_OSX_NBUF];
#endif
#ifdef BX_SOUND_OSX_use_converter
AudioUnit WaveOutputUnit = NULL;
AudioConverterRef WaveConverter = NULL;
#endif
// bx_soundlow_waveout_osx_c class implemenzation
bx_soundlow_waveout_osx_c::bx_soundlow_waveout_osx_c()
:bx_soundlow_waveout_c()
{
WaveOpen = 0;
head = 0;
tail = 0;
for (int i=0; i<BX_SOUND_OSX_NBUF; i++)
WaveLength[i] = 0;
}
bx_soundlow_waveout_osx_c::~bx_soundlow_waveout_osx_c()
{
#ifdef BX_SOUND_OSX_use_converter
if (WavePlaying) AudioOutputUnitStop (WaveOutputUnit);
if (WaveConverter) AudioConverterDispose (WaveConverter);
if (WaveOutputUnit) CloseComponent (WaveOutputUnit);
WavePlaying = 0;
WaveOpen = 0;
WaveConverter = NULL;
WaveOutputUnit = NULL;
#endif
}
#ifdef BX_SOUND_OSX_use_quicktime
#if BX_WITH_MACOS
pascal
#endif
void WaveCallbackProc(SndChannelPtr chan, SndCommand *cmd)
{
// a new buffer is available, so increment tail pointer
int *tail = (int *) (cmd->param2);
(*tail)++;
}
#endif
int bx_soundlow_waveout_osx_c::openwaveoutput(const char *wavedev)
{
OSStatus err;
BX_DEBUG(("openwaveoutput(%s)", wavedev));
// open the default output unit
#ifdef BX_SOUND_OSX_use_quicktime
err = SndNewChannel (&WaveChannel, sampledSynth, 0, NewSndCallBackUPP(WaveCallbackProc));
if (err != noErr) return BX_SOUNDLOW_ERR;
#endif
#ifdef BX_SOUND_OSX_use_converter
#ifndef BX_SOUND_OSX_CONVERTER_NEW_API
err = OpenDefaultAudioOutput (&WaveOutputUnit);
if (err != noErr) return BX_SOUNDLOW_ERR;
AudioUnitInputCallback input;
input.inputProc = MyRenderer;
input.inputProcRefCon = (void *) this;
AudioUnitSetProperty (WaveOutputUnit, kAudioUnitProperty_SetInputCallback,
kAudioUnitScope_Global, 0, &input, sizeof(input));
#else
ComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
Component c = FindNextComponent(NULL, &desc);
if (c == NULL) {
BX_ERROR(("Core Audio: Unable to find default audio output component\n"));
return BX_SOUNDLOW_ERR;
}
err = OpenAComponent(c, &WaveOutputUnit);
if (err) {
BX_ERROR(("Core Audio: Unable to open audio output (err=%X)\n", (unsigned int)err));
return BX_SOUNDLOW_ERR;
}
AURenderCallbackStruct input;
input.inputProc = MyRenderer;
input.inputProcRefCon = (void *) this;
err = AudioUnitSetProperty (WaveOutputUnit,
kAudioUnitProperty_SetRenderCallback,
kAudioUnitScope_Input, 0, &input, sizeof(input));
if (err) {
BX_ERROR(("Core Audio: AudioUnitSetProperty error (err=%X)\n", (unsigned int)err));
return BX_SOUNDLOW_ERR;
}
#endif
err = AudioUnitInitialize (WaveOutputUnit);
if (err) {
BX_ERROR(("Core Audio: AudioUnitInitialize error (err=%X)\n", (unsigned int)err));
return BX_SOUNDLOW_ERR;
}
#endif
set_pcm_params(&real_pcm_param);
pcm_callback_id = register_wave_callback(this, pcm_callback);
start_resampler_thread();
start_mixer_thread();
WaveOpen = 1;
return BX_SOUNDLOW_OK;
}
#ifdef BX_SOUND_OSX_use_converter
OSStatus bx_soundlow_waveout_osx_c::core_audio_pause()
{
OSStatus err = noErr;
if (WaveOutputUnit) {
err = AudioOutputUnitStop (WaveOutputUnit);
if (err) {
BX_ERROR(("Core Audio: nextbuffer(): AudioOutputUnitStop (err=%X)\n", (unsigned int)err));
}
WavePlaying = 0;
}
return err;
}
OSStatus bx_soundlow_waveout_osx_c::core_audio_resume()
{
OSStatus err = noErr;
if (WaveConverter) {
err = AudioConverterReset (WaveConverter);
if (err) {
BX_ERROR(("Core Audio: core_audio_resume(): AudioConverterReset (err=%X)\n", (unsigned int)err));
return err;
}
}
if (WaveOutputUnit) {
err = AudioOutputUnitStart (WaveOutputUnit);
if (err) {
BX_ERROR(("Core Audio: core_audio_resume(): AudioOutputUnitStart (err=%X)\n", (unsigned int)err));
return err;
}
WavePlaying = 1;
}
return err;
}
#endif
int bx_soundlow_waveout_osx_c::set_pcm_params(bx_pcm_param_t *param)
{
#ifdef BX_SOUND_OSX_use_converter
AudioStreamBasicDescription srcFormat, dstFormat;
UInt32 formatSize = sizeof(AudioStreamBasicDescription);
OSStatus err;
#endif
BX_DEBUG(("set_pcm_params(): %u, %u, %u, %02x", param->samplerate, param->bits,
param->channels, param->format));
#ifdef BX_SOUND_OSX_use_quicktime
WaveInfo.samplePtr = NULL;
WaveInfo.numChannels = param->channels;
WaveInfo.sampleRate = param->samplerate << 16; // sampleRate is a 16.16 fixed-point value
WaveInfo.loopStart = 0;
WaveInfo.loopEnd = 0;
WaveInfo.encode = extSH; // WaveInfo has type ExtSoundHeader
WaveInfo.baseFrequency = 1; // not sure what means. It's only a Uint8.
WaveInfo.numFrames = 0;
//WaveInfo.AIFFSampleRate = param->samplerate; // frequency as float80
WaveInfo.markerChunk = NULL;
WaveInfo.instrumentChunks = NULL;
WaveInfo.AESRecording = NULL;
WaveInfo.sampleSize = param->bits * WaveInfo.numChannels;
#endif
#ifdef BX_SOUND_OSX_use_converter
// update the source audio format
UInt32 bytes = param->bits / 8;
UInt32 channels = param->channels;
srcFormat.mSampleRate = (Float64) param->samplerate;
srcFormat.mFormatID = kAudioFormatLinearPCM;
srcFormat.mFormatFlags = kLinearPCMFormatFlagIsPacked;
if (param->format & 1) srcFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
srcFormat.mBytesPerPacket = channels * bytes;
srcFormat.mFramesPerPacket = 1;
srcFormat.mBytesPerFrame = channels * bytes;
srcFormat.mChannelsPerFrame = channels;
srcFormat.mBitsPerChannel = bytes * 8;
if (WavePlaying) {
err = AudioOutputUnitStop (WaveOutputUnit);
if (err)
BX_ERROR(("Core Audio: set_pcm_params(): AudioOutputUnitStop (err=%X)\n", (unsigned int)err));
}
if (WaveConverter) {
err = AudioConverterDispose (WaveConverter);
if (err)
BX_ERROR(("Core Audio: set_pcm_params(): AudioConverterDispose (err=%X)\n", (unsigned int)err));
}
err = AudioUnitGetProperty (WaveOutputUnit, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Output, 0, &dstFormat, &formatSize);
if (err) {
BX_ERROR(("Core Audio: set_pcm_params(): AudioUnitGetProperty (err=%X)\n", (unsigned int)err));
return BX_SOUNDLOW_ERR;
}
#ifdef BX_SOUND_OSX_CONVERTER_NEW_API
// force interleaved mode
dstFormat.mFormatFlags |= kAudioFormatFlagIsNonInterleaved;
dstFormat.mBytesPerPacket = dstFormat.mBytesPerFrame = (dstFormat.mBitsPerChannel + 7) / 8;
err = AudioUnitSetProperty (WaveOutputUnit, kAudioUnitProperty_StreamFormat,
kAudioUnitScope_Input, 0, &dstFormat, sizeof(dstFormat));
if (err) {
BX_ERROR(("Core Audio: set_pcm_params(): AudioUnitSetProperty (err=%X)\n", (unsigned int)err));
return BX_SOUNDLOW_ERR;
}
#endif
err = AudioConverterNew (&srcFormat, &dstFormat, &WaveConverter);
if (err) {
BX_ERROR(("Core Audio: set_pcm_params(): AudioConverterNew (err=%X)\n", (unsigned int)err));
return BX_SOUNDLOW_ERR;
}
if (srcFormat.mChannelsPerFrame == 1 && dstFormat.mChannelsPerFrame == 2) {
// map single-channel input to both output channels
SInt32 map[2] = {0,0};
err = AudioConverterSetProperty (WaveConverter,
kAudioConverterChannelMap,
sizeof(map), (void*) map);
if (err) {
BX_ERROR(("Core Audio: set_pcm_params(): AudioConverterSetProperty (err=%X)\n", (unsigned int)err));
return BX_SOUNDLOW_ERR;
}
}
if (WavePlaying) {
if (core_audio_resume() != noErr)
return BX_SOUNDLOW_ERR;
}
#endif
return BX_SOUNDLOW_OK;
}
int bx_soundlow_waveout_osx_c::output(int length, Bit8u data[])
{
#ifdef BX_SOUND_OSX_use_quicktime
SndCommand mySndCommand;
#endif
BX_DEBUG(("output(%d, %p), head=%u", length, data, head));
// sanity check
if ((!WaveOpen) || (head - tail >= BX_SOUND_OSX_NBUF))
return BX_SOUNDLOW_ERR;
// find next available buffer
int n = head++ % BX_SOUND_OSX_NBUF;
// put data in buffer
memcpy(WaveData[n], data, length);
WaveLength[n] = length;
#ifdef BX_SOUND_OSX_use_quicktime
memcpy(&WaveHeader[n], &WaveInfo, sizeof(WaveInfo));
WaveHeader[n].samplePtr = (char *) (WaveData[n]);
WaveHeader[n].numFrames = length * 8 / WaveInfo.sampleSize;
#endif
#ifdef BX_SOUND_OSX_use_converter
// make sure that the sound is playing
if (!WavePlaying) {
if (core_audio_resume() != noErr)
return BX_SOUNDLOW_ERR;
}
#endif
#ifdef BX_SOUND_OSX_use_quicktime
// queue buffer to play
mySndCommand.cmd = bufferCmd;
mySndCommand.param1 = 0;
mySndCommand.param2 = (long)(&WaveHeader[n]);
SndDoCommand(WaveChannel, &mySndCommand, TRUE);
// queue callback for when buffer finishes
mySndCommand.cmd = callBackCmd;
mySndCommand.param1 = 0;
mySndCommand.param2 = (long)(&tail);
SndDoCommand(WaveChannel, &mySndCommand, TRUE);
#endif
return BX_SOUNDLOW_OK;
}
#ifdef BX_SOUND_OSX_use_converter
#ifndef BX_SOUND_OSX_CONVERTER_NEW_API
OSStatus MyRenderer (void *inRefCon, AudioUnitRenderActionFlags inActionFlags,
const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, AudioBuffer *ioData)
{
OSStatus err;
UInt32 size = ioData->mDataByteSize;
err = AudioConverterFillBuffer (WaveConverter, MyACInputProc, inRefCon, &size, ioData->mData);
return err;
}
OSStatus MyACInputProc (AudioConverterRef inAudioConverter,
UInt32* outDataSize, void** outData, void* inUserData)
{
bx_soundlow_waveout_osx_c *self = (bx_soundlow_waveout_osx_c*) inUserData;
self->nextbuffer ((int*) outDataSize, outData);
return noErr;
}
#else
OSStatus MyRenderer (void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
{
UInt32 packets;
AudioStreamBasicDescription dstFormat;
UInt32 formatSize = sizeof(AudioStreamBasicDescription);
OSStatus err = noErr;
err = AudioConverterGetProperty (WaveConverter,
kAudioConverterCurrentOutputStreamDescription,
&formatSize, &dstFormat);
if (err) {
return err;
}
packets = inNumberFrames / dstFormat.mFramesPerPacket;
err = AudioConverterFillComplexBuffer(WaveConverter,
MyACInputProc, inRefCon, &packets, ioData, NULL);
return err;
}
OSStatus MyACInputProc (AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void *inUserData)
{
OSStatus err;
bx_soundlow_waveout_osx_c *self = (bx_soundlow_waveout_osx_c*) inUserData;
AudioStreamBasicDescription srcFormat;
UInt32 formatSize = sizeof(AudioStreamBasicDescription);
err = AudioConverterGetProperty (inAudioConverter,
kAudioConverterCurrentInputStreamDescription,
&formatSize, &srcFormat);
if (err) {
*ioNumberDataPackets = 0;
return err;
}
int outDataSize = *ioNumberDataPackets * srcFormat.mBytesPerPacket;
void *outData = ioData->mBuffers[0].mData;
self->nextbuffer ((int*) &outDataSize, &outData);
*ioNumberDataPackets = outDataSize / srcFormat.mBytesPerPacket;
ioData->mBuffers[0].mDataByteSize = outDataSize;
ioData->mBuffers[0].mData = outData;
return noErr;
}
#endif
void bx_soundlow_waveout_osx_c::nextbuffer (int *outDataSize, void **outData)
{
BX_DEBUG(("nextbuffer(), tail=%u", tail));
if (head - tail <= 0) {
*outData = NULL;
*outDataSize = 0;
// We are getting behind, so stop the output for now
core_audio_pause();
}
else {
int n = tail % BX_SOUND_OSX_NBUF;
*outData = (void *) (WaveData[n]);
*outDataSize = WaveLength[n];
tail++;
}
}
#endif
// bx_soundlow_midiout_osx_c class implemenzation
bx_soundlow_midiout_osx_c::bx_soundlow_midiout_osx_c()
:bx_soundlow_midiout_c()
{
MidiOpen = 0;
}
bx_soundlow_midiout_osx_c::~bx_soundlow_midiout_osx_c()
{
closemidioutput();
}
int bx_soundlow_midiout_osx_c::openmidioutput(const char *mididev)
{
#ifdef BX_SOUND_OSX_use_converter
ComponentDescription description;
AUNode synthNode, outputNode;
// Create the graph
NewAUGraph (&MidiGraph);
// Open the DLS Synth
description.componentType = kAudioUnitType_MusicDevice;
description.componentSubType = kAudioUnitSubType_DLSSynth;
description.componentManufacturer = kAudioUnitManufacturer_Apple;
description.componentFlags = 0;
description.componentFlagsMask = 0;
AUGraphNewNode (MidiGraph, &description, 0, NULL, &synthNode);
// Open the output device
description.componentType = kAudioUnitType_Output;
description.componentSubType = kAudioUnitSubType_DefaultOutput;
description.componentManufacturer = kAudioUnitManufacturer_Apple;
description.componentFlags = 0;
description.componentFlagsMask = 0;
AUGraphNewNode (MidiGraph, &description, 0, NULL, &outputNode);
// Connect the devices up
AUGraphConnectNodeInput (MidiGraph, synthNode, 1, outputNode, 0);
AUGraphUpdate (MidiGraph, NULL);
// Open and initialize the audio units
AUGraphOpen (MidiGraph);
AUGraphInitialize (MidiGraph);
// Turn off the reverb on the synth
AUGraphGetNodeInfo (MidiGraph, synthNode, NULL, NULL, NULL, &synthUnit);
UInt32 usesReverb = 0;
AudioUnitSetProperty (synthUnit, kMusicDeviceProperty_UsesInternalReverb,
kAudioUnitScope_Global, 0, &usesReverb, sizeof (usesReverb));
// Start playing
AUGraphStart (MidiGraph);
#endif
BX_DEBUG(("openmidioutput(%s)", mididev));
MidiOpen = 1;
return BX_SOUNDLOW_OK;
}
int bx_soundlow_midiout_osx_c::sendmidicommand(int delta, int command, int length, Bit8u data[])
{
BX_DEBUG(("sendmidicommand(%i,%02x,%i)", delta, command, length));
if (!MidiOpen) return BX_SOUNDLOW_ERR;
#ifdef BX_SOUND_OSX_use_converter
if (length <= 2) {
Bit8u arg1 = (length >=1) ? data[0] : 0;
Bit8u arg2 = (length >=2) ? data[1] : 0;
MusicDeviceMIDIEvent (synthUnit, command, arg1, arg2, delta);
}
else {
MusicDeviceSysEx (synthUnit, data, length);
}
#endif
return BX_SOUNDLOW_OK;
}
int bx_soundlow_midiout_osx_c::closemidioutput()
{
MidiOpen = 0;
#ifdef BX_SOUND_OSX_use_converter
AUGraphStop(MidiGraph);
AUGraphClose(MidiGraph);
#endif
return BX_SOUNDLOW_OK;
}
// bx_sound_osx_c class implemenzation
bx_sound_osx_c::bx_sound_osx_c()
:bx_sound_lowlevel_c()
{
BX_INFO(("Sound lowlevel module 'osx' initialized"));
}
bx_soundlow_waveout_c* bx_sound_osx_c::get_waveout()
{
if (waveout == NULL) {
waveout = new bx_soundlow_waveout_osx_c();
}
return waveout;
}
bx_soundlow_midiout_c* bx_sound_osx_c::get_midiout()
{
if (midiout == NULL) {
midiout = new bx_soundlow_midiout_osx_c();
}
return midiout;
}
#endif // defined(macintosh)