Bochs/bochs/iodev/sound/soundosx.cc
Volker Ruppert ee6bfaa4c4 Added separate thread for the PCM data conversion. Only in case of dual output
sendwavepacket() directly calls the conversion method and output(). In all other
cases it puts the unmodified data into a buffer and the conversion thread is
doing the main job and puts the data into the output buffer chain.
TODO: resampling could also be done by the new thread.
2015-06-04 08:58:02 +00:00

567 lines
18 KiB
C++

/////////////////////////////////////////////////////////////////////////
// $Id$
/////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2004-2015 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_conversion_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)