Bochs/bochs/iodev/sound/soundfile.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

387 lines
10 KiB
C++

/////////////////////////////////////////////////////////////////////////
// $Id$
/////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2001-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
/////////////////////////////////////////////////////////////////////////
// Support for sound output to files (based on SB16 code)
// Define BX_PLUGGABLE in files that can be compiled into plugins. For
// platforms that require a special tag on exported symbols, BX_PLUGGABLE
// is used to know when we are exporting symbols and when we are importing.
#define BX_PLUGGABLE
#include "iodev.h"
#include "soundlow.h"
#include "soundfile.h"
#if BX_SUPPORT_SOUNDLOW
#define BX_SOUNDFILE_RAW 0
#define BX_SOUNDFILE_VOC 1
#define BX_SOUNDFILE_WAV 2
#define BX_SOUNDFILE_MID 3
#define LOG_THIS
// bx_soundlow_waveout_file_c class implemenzation
bx_soundlow_waveout_file_c::bx_soundlow_waveout_file_c()
:bx_soundlow_waveout_c()
{
wavefile = NULL;
type = BX_SOUNDFILE_RAW;
}
bx_soundlow_waveout_file_c::~bx_soundlow_waveout_file_c()
{
closewaveoutput();
}
void bx_soundlow_waveout_file_c::initwavfile()
{
Bit8u waveheader[44] =
{0x52, 0x49, 0x46, 0x46, 0, 0, 0, 0, 0x57, 0x41, 0x56, 0x45, 0x66, 0x6d,
0x74, 0x20, 0x10, 0, 0, 0, 0x1, 0, 0x2, 0, 0x44, 0xac, 0, 0, 0x10, 0xb1,
0x2, 0, 0x4, 0, 0x10, 0, 0x64, 0x61, 0x74, 0x61, 0, 0, 0, 0};
fwrite(waveheader, 1, 44, wavefile);
}
void bx_soundlow_waveout_file_c::write_32bit(Bit32u pos, Bit32u value)
{
Bit8u size[4] = {(Bit8u)(value & 0xff), (Bit8u)(value >> 8),
(Bit8u)(value >> 16), (Bit8u)(value >> 24)};
fseek(wavefile, pos, SEEK_SET);
fwrite(size, 1, 4, wavefile);
}
int bx_soundlow_waveout_file_c::openwaveoutput(const char *wavedev)
{
size_t len = strlen(wavedev);
char ext[4];
if ((wavefile == NULL) && (len > 0)) {
if (len > 4) {
if (wavedev[len-4] == '.') {
strcpy(ext, wavedev+len-3);
if (!stricmp(ext, "voc")) {
type = BX_SOUNDFILE_VOC;
} else if (!stricmp(ext, "wav")) {
type = BX_SOUNDFILE_WAV;
}
}
}
wavefile = fopen(wavedev, "wb");
if (wavefile == NULL) {
BX_ERROR(("Failed to open WAVE output file %s.", wavedev));
} else if (type == BX_SOUNDFILE_VOC) {
VOC_init_file();
} else if (type == BX_SOUNDFILE_WAV) {
initwavfile();
}
set_pcm_params(&real_pcm_param);
if (resampler_control != 1) {
start_resampler_thread();
}
if (mixer_control != 1) {
pcm_callback_id = register_wave_callback(this, pcm_callback);
start_mixer_thread();
}
return BX_SOUNDLOW_OK;
} else {
return BX_SOUNDLOW_ERR;
}
}
int bx_soundlow_waveout_file_c::set_pcm_params(bx_pcm_param_t *param)
{
return BX_SOUNDLOW_OK;
}
int bx_soundlow_waveout_file_c::output(int length, Bit8u data[])
{
Bit8u temparray[12] =
{ (Bit8u)(real_pcm_param.samplerate & 0xff), (Bit8u)(real_pcm_param.samplerate >> 8), 0, 0,
(Bit8u)real_pcm_param.bits, real_pcm_param.channels, 0, 0, 0, 0, 0, 0 };
if (wavefile != NULL) {
if (type == BX_SOUNDFILE_VOC) {
switch ((real_pcm_param.format >> 1) & 7) {
case 2:
temparray[6] = 3;
break;
case 3:
temparray[6] = 2;
break;
case 4:
temparray[6] = 1;
break;
}
if (real_pcm_param.bits == 16)
temparray[6] = 4;
VOC_write_block(9, 12, temparray, length, data);
} else {
fwrite(data, 1, length, wavefile);
}
if (pcm_callback_id >= 0) {
BX_MSLEEP(100);
}
}
return BX_SOUNDLOW_OK;
}
int bx_soundlow_waveout_file_c::closewaveoutput()
{
if (wavefile != NULL) {
if (type == BX_SOUNDFILE_VOC) {
fputc(0, wavefile);
} else if (type == BX_SOUNDFILE_WAV) {
Bit32u tracklen = ftell(wavefile);
write_32bit(4, tracklen - 8);
write_32bit(24, real_pcm_param.samplerate);
write_32bit(28, (Bit32u)real_pcm_param.samplerate * 4);
write_32bit(40, tracklen - 44);
}
fclose(wavefile);
wavefile = NULL;
}
return BX_SOUNDLOW_OK;
}
/* Handlers for the VOC file output */
// Write the header of the VOC file.
void bx_soundlow_waveout_file_c::VOC_init_file()
{
struct {
char id[20];
Bit16u headerlen; // All in LITTLE Endian!
Bit16u version;
Bit16u chksum;
} vocheader =
{ "Creative Voice File",
#ifdef BX_LITTLE_ENDIAN
0x1a, 0x0114, 0x111f };
#else
0x1a00, 0x1401, 0x1f11 };
#endif
vocheader.id[19] = 26; // Replace string end with 26
fwrite(&vocheader, 1, sizeof vocheader, wavefile);
}
// write one block to the VOC file
void bx_soundlow_waveout_file_c::VOC_write_block(int block, Bit32u headerlen,
Bit8u header[], Bit32u datalen, Bit8u data[])
{
Bit32u i;
if (block > 9) {
BX_ERROR(("VOC Block %d not recognized, ignored.", block));
return;
}
fputc(block, wavefile);
i = headerlen + datalen;
#ifdef BX_LITTLE_ENDIAN
fwrite(&i, 1, 3, wavefile); // write the length in 24-bit little endian
#else
Bit8u lengthbytes[3];
lengthbytes[0] = i & 0xff; i >>= 8;
lengthbytes[1] = i & 0xff; i >>= 8;
lengthbytes[2] = i & 0xff;
fwrite(lengthbytes, 1, 3, wavefile);
#endif
BX_DEBUG(("Voc block %d; Headerlen %d; Datalen %d",
block, headerlen, datalen));
if (headerlen > 0)
fwrite(header, 1, headerlen, wavefile);
if (datalen > 0)
fwrite(data, 1, datalen, wavefile);
}
// bx_soundlow_midiout_file_c class implemenzation
bx_soundlow_midiout_file_c::bx_soundlow_midiout_file_c()
:bx_soundlow_midiout_c()
{
midifile = NULL;
type = BX_SOUNDFILE_RAW;
}
bx_soundlow_midiout_file_c::~bx_soundlow_midiout_file_c()
{
closemidioutput();
}
int bx_soundlow_midiout_file_c::openmidioutput(const char *mididev)
{
struct {
Bit8u chunk[4];
Bit32u chunklen; // all values in BIG Endian!
Bit16u smftype;
Bit16u tracknum;
Bit16u timecode; // 0x80 + deltatimesperquarter << 8
} midiheader =
#ifdef BX_LITTLE_ENDIAN
{ "MTh", 0x06000000, 0, 0x0100, 0x8001 };
#else
{ "MTh", 6, 0, 1, 0x180 };
#endif
midiheader.chunk[3] = 'd';
struct {
Bit8u chunk[4];
Bit32u chunklen;
Bit8u data[15];
} trackheader =
#ifdef BX_LITTLE_ENDIAN
{ "MTr", 0xffffff7f,
#else
{ "MTr", 0x7fffffff,
#endif
{ 0x00,0xff,0x51,3,0x07,0xa1,0x20, // set tempo 120 (0x7a120 us per quarter)
0x00,0xff,0x58,4,4,2,0x18,0x08 }}; // time sig 4/4
trackheader.chunk[3] = 'k';
size_t len = strlen(mididev);
char ext[4];
if ((midifile == NULL) && (len > 0)) {
if (len > 4) {
if (mididev[len-4] == '.') {
strcpy(ext, mididev+len-3);
if (!stricmp(ext, "mid")) {
type = BX_SOUNDFILE_MID;
}
}
}
midifile = fopen(mididev, "wb");
if (midifile == NULL) {
BX_ERROR(("Failed to open MIDI output file %s.", mididev));
return BX_SOUNDLOW_ERR;
} else if (type == BX_SOUNDFILE_MID) {
fwrite(&midiheader, 1, 14, midifile);
fwrite(&trackheader, 1, 23, midifile);
}
return BX_SOUNDLOW_OK;
} else {
return BX_SOUNDLOW_ERR;
}
}
int bx_soundlow_midiout_file_c::sendmidicommand(int delta, int command, int length, Bit8u data[])
{
if (midifile != NULL) {
if (type == BX_SOUNDFILE_MID) {
writedeltatime(delta);
}
fputc(command, midifile);
if ((command == 0xf0) ||
(command == 0xf7)) // write event length for sysex/meta events
writedeltatime(length);
fwrite(data, 1, length, midifile);
}
return BX_SOUNDLOW_OK;
}
int bx_soundlow_midiout_file_c::closemidioutput()
{
struct {
Bit8u delta, statusbyte, metaevent, length;
} metatrackend = { 0, 0xff, 0x2f, 0 };
if (midifile != NULL) {
if (type == BX_SOUNDFILE_MID) {
// Meta event track end (0xff 0x2f 0x00) plus leading delta time
fwrite(&metatrackend, 1, sizeof(metatrackend), midifile);
int trlen = ftell(midifile);
if (trlen < 0)
BX_PANIC (("ftell failed in closemidioutput()"));
Bit32u tracklen = (Bit32u)trlen;
if (tracklen < 22)
BX_PANIC (("MIDI track length too short"));
tracklen -= 22; // subtract the midi file and track header
fseek(midifile, 22 - 4, SEEK_SET);
// value has to be in big endian
#ifdef BX_LITTLE_ENDIAN
tracklen = bx_bswap32(tracklen);
#endif
fwrite(&tracklen, 4, 1, midifile);
}
fclose(midifile);
midifile = NULL;
}
return BX_SOUNDLOW_OK;
}
void bx_soundlow_midiout_file_c::writedeltatime(Bit32u deltatime)
{
int i, count = 0;
Bit8u outbytes[4], value[4];
if (deltatime == 0) {
count = 1;
value[0] = 0;
} else {
while ((deltatime > 0) && (count < 4)) { // split into parts of seven bits
outbytes[count++] = deltatime & 0x7f;
deltatime >>= 7;
}
for (i=0; i<count; i++) // reverse order and
value[i] = outbytes[count - i - 1] | 0x80; // set eighth bit on
value[count - 1] &= 0x7f; // all but last byte
}
for (int i=0; i<count; i++)
fputc(value[i], midifile);
}
// bx_sound_oss_c class implemenzation
bx_sound_file_c::bx_sound_file_c()
:bx_sound_lowlevel_c()
{
BX_INFO(("Sound lowlevel module 'file' initialized"));
}
bx_soundlow_waveout_c* bx_sound_file_c::get_waveout()
{
if (waveout == NULL) {
waveout = new bx_soundlow_waveout_file_c();
}
return waveout;
}
bx_soundlow_midiout_c* bx_sound_file_c::get_midiout()
{
if (midiout == NULL) {
midiout = new bx_soundlow_midiout_file_c();
}
return midiout;
}
#endif