make midiplayer more fancy with pipes for communication

This commit is contained in:
Tim Felgentreff 2022-04-28 22:57:01 +02:00
parent e4c4b29805
commit 94617fb1cc
2 changed files with 372 additions and 75 deletions

View file

@ -35,6 +35,8 @@
-- Includes
----------------------------------------------------------------------------*/
#include <numeric>
#include "stratagus.h"
#include "sound_server.h"
@ -63,104 +65,154 @@ static int MusicVolume = 0;
static void (*MusicFinishedCallback)();
#ifdef USE_WIN32
static volatile bool threadWaiting = false;
static std::string externalFile;
static HANDLE hWaitingThread;
static bool externalPlayerIsPlaying = false;
static HANDLE g_hStatusThread;
static HANDLE g_hDebugThread;
static HANDLE g_hChildStd_IN_Wr;
static PROCESS_INFORMATION pi;
DWORD WINAPI MyThreadFunction(LPVOID lpParam) {
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
if (threadWaiting) {
MusicFinishedCallback();
threadWaiting = false;
static DWORD WINAPI StatusThreadFunction(LPVOID lpParam) {
CHAR chStatus;
DWORD dwRead = 1;
while (1) {
if (!ReadFile((HANDLE)lpParam, &chStatus, 1, &dwRead, NULL) || dwRead == 0) {
CloseHandle((HANDLE)lpParam);
break;
}
// any write means we finished
if (externalPlayerIsPlaying) {
externalPlayerIsPlaying = false;
MusicFinishedCallback();
}
}
return 0;
}
static void killPlayingProcess() {
if (threadWaiting) {
threadWaiting = false;
TerminateProcess(pi.hProcess, 0);
WaitForSingleObject(hWaitingThread, INFINITE);
threadWaiting = false;
} else {
static DWORD WINAPI DebugThreadFunction(LPVOID lpParam) {
DWORD dwRead = 1;
while (1) {
char *chStatus[1024] = {'\0'};
if (!ReadFile((HANDLE)lpParam, &chStatus, 1024, &dwRead, NULL) || dwRead == 0) {
CloseHandle((HANDLE)lpParam);
break;
}
DebugPrint("%s" _C_ chStatus);
}
return 0;
}
static void KillPlayingProcess() {
externalPlayerIsPlaying = false;
if (g_hChildStd_IN_Wr) {
TerminateProcess(pi.hProcess, 0);
CloseHandle(g_hChildStd_IN_Wr);
g_hChildStd_IN_Wr = NULL;
WaitForSingleObject(StatusThreadFunction, 0);
WaitForSingleObject(DebugThreadFunction, 0);
}
}
static bool External_Play(const std::string &file) {
if (threadWaiting && file == externalFile) {
return true;
}
static std::string midi = ".mid";
auto it = midi.begin();
if (file.size() > midi.size() &&
std::all_of(std::next(file.begin(), file.size() - midi.size()), file.end(), [&it](const char & c) { return c == ::tolower(*(it++)); })) {
// midi file, use external player, since windows vista+ does not allow midi volume control independent of process volume
std::string full_filename = LibraryFileName(file.c_str());
static const char* midiplayerExe = "stratagus-midiplayer.exe";
static const int midiplayerExeSz = strlen(midiplayerExe);
// set up a job so our children die with us
static bool firstRun = true;
static HANDLE hJob;
if (firstRun) {
hJob = CreateJobObject(NULL, NULL);
JOBOBJECT_BASIC_LIMIT_INFORMATION limitInfo;
ZeroMemory(&limitInfo, sizeof(limitInfo));
limitInfo.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
SetInformationJobObject(hJob, JobObjectBasicLimitInformation, &limitInfo, sizeof(limitInfo));
AssignProcessToJobObject(hJob, GetCurrentProcess());
firstRun = false;
// try to communicate with the running midiplayer if we can
if (g_hChildStd_IN_Wr != NULL) {
// already playing, just send the new song
// XXX: timfel: disabled, since the midiplayer behaves weirdly when it receives the next file, just kill and restart
KillPlayingProcess();
/*
// negative value signals a new filename
int fileSize = full_filename.size() & 0xffff;
char loSize = fileSize & 0xff;
char hiSize = (fileSize >> 8) & 0xff;
char buf[2] = {loSize, hiSize};
externalPlayerIsPlaying = true;
if (!WriteFile(g_hChildStd_IN_Wr, buf, 2, NULL, NULL)) {
KillPlayingProcess();
} else {
// then write the filename
if (!WriteFile(g_hChildStd_IN_Wr, full_filename.c_str(), fileSize, NULL, NULL)) {
KillPlayingProcess();
} else {
return true;
}
}
*/
}
// need to start an external player first
int sz = midiplayerExeSz + 2 + 3 + 2 + full_filename.size() + 1; // exe + 2 spaces + 3 volume + 2 quotes + filename + nullbyte
char *cmdline = new char[sz];
snprintf(cmdline, sz, "%s %3d \"%s\"", midiplayerExe, std::min(MusicVolume, 127), full_filename.c_str());
DebugPrint("Using external command to play midi on windows: %s\n" _C_ cmdline);
killPlayingProcess();
// setup pipes to player
HANDLE hChildStd_IN_Rd = NULL;
HANDLE hChildStd_OUT_Rd = NULL;
HANDLE hChildStd_OUT_Wr = NULL;
HANDLE hChildStd_ERR_Rd = NULL;
HANDLE hChildStd_ERR_Wr = NULL;
SECURITY_ATTRIBUTES saAttr;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
CreatePipe(&hChildStd_OUT_Rd, &hChildStd_OUT_Wr, &saAttr, 0);
CreatePipe(&hChildStd_ERR_Rd, &hChildStd_ERR_Wr, &saAttr, 0);
CreatePipe(&hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0);
SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0);
// start the process
std::vector<std::string> args = QuoteArguments({ "stratagus-midiplayer.exe", std::to_string(std::min(MusicVolume, 127)), full_filename });
std::string cmd = std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, std::string b) { return a + " " + b; });
DebugPrint("Using external command to play midi on windows: %s\n" _C_ cmd.c_str());
STARTUPINFO si;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
si.hStdError = hChildStd_ERR_Wr;
si.hStdOutput = hChildStd_OUT_Wr;
si.hStdInput = hChildStd_IN_Rd;
si.dwFlags |= STARTF_USESTDHANDLES;
ZeroMemory(&pi, sizeof(pi));
bool result = true;
if (CreateProcess(NULL, cmdline, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
AssignProcessToJobObject(hJob, pi.hProcess);
externalFile = file;
hWaitingThread = CreateThread(NULL, 0, MyThreadFunction, NULL, 0, NULL);
threadWaiting = true;
char* cmdline = strdup(cmd.c_str());
if (CreateProcess(NULL, cmdline, NULL, NULL, TRUE, /* Handles are inherited */ CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
CloseHandle(hChildStd_OUT_Wr);
CloseHandle(hChildStd_ERR_Wr);
CloseHandle(hChildStd_IN_Rd);
externalPlayerIsPlaying = true;
g_hStatusThread = CreateThread(NULL, 0, StatusThreadFunction, hChildStd_OUT_Rd, 0, NULL);
g_hDebugThread = CreateThread(NULL, 0, DebugThreadFunction, hChildStd_ERR_Rd, 0, NULL);
} else {
result = false;
DebugPrint("CreateProcess failed (%d).\n" _C_ GetLastError());
}
delete[] cmdline;
free(cmdline);
return result;
}
killPlayingProcess();
KillPlayingProcess();
return false;
}
static bool External_IsPlaying() {
return threadWaiting;
return externalPlayerIsPlaying;
}
static bool External_Stop() {
if (External_IsPlaying()) {
killPlayingProcess();
KillPlayingProcess();
return true;
}
return false;
}
static bool External_Volume(int volume, int oldVolume) {
if (External_IsPlaying() && externalFile.size() > 0) {
if (oldVolume != volume) {
if (External_IsPlaying()) {
char buf[2] = {0, volume & 0xFF};
if (!WriteFile(g_hChildStd_IN_Wr, buf, 2, NULL, NULL)) {
External_Stop();
External_Play(externalFile);
return false;
}
return true;
}

View file

@ -16,6 +16,16 @@
#include <mmsystem.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <io.h>
HMIXER sMixerHandle;
HMIDISTRM sOut;
HANDLE hWaitingThread;
int sVolume = 127;
char *sFilename = NULL;
unsigned int sDeviceId = 0;
#define MAX_BUFFER_SIZE (512 * 12)
HANDLE event;
@ -142,7 +152,7 @@ static int is_track_end(const struct evt* e)
static void CALLBACK example9_callback(HMIDIOUT out, UINT msg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
{
switch (msg)
switch (msg)
{
case MOM_DONE:
SetEvent(event);
@ -283,7 +293,7 @@ static unsigned int get_buffer(struct trk* tracks, unsigned int ntracks, unsigne
return 1;
}
unsigned int example9(char* filename, int volume)
unsigned int example9(char *filename)
{
unsigned char* midibuf = NULL;
unsigned int midilen = 0;
@ -299,15 +309,14 @@ unsigned int example9(char* filename, int volume)
unsigned int* streambuf = NULL;
unsigned int streamlen = 0;
HMIDISTRM out;
MIDIPROPTIMEDIV prop;
MIDIHDR mhdr;
unsigned int device = 0;
midibuf = load_file((unsigned char*)filename, &midilen);
if(midibuf == NULL)
{
printf("could not open %s\n", filename);
fprintf(stderr, "could not open %s\n", filename);
fflush(stderr);
return 0;
}
@ -338,52 +347,57 @@ unsigned int example9(char* filename, int volume)
if ((event = CreateEvent(0, FALSE, FALSE, 0)) == NULL)
goto error3;
if (midiStreamOpen(&out, &device, 1, (DWORD)example9_callback, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
if (midiStreamOpen(&sOut, &sDeviceId, 1, (DWORD)example9_callback, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR)
goto error4;
prop.cbStruct = sizeof(MIDIPROPTIMEDIV);
prop.dwTimeDiv = swap_bytes_short(hdr->ticks);
if(midiStreamProperty(out, (LPBYTE)&prop, MIDIPROP_SET|MIDIPROP_TIMEDIV) != MMSYSERR_NOERROR)
if(midiStreamProperty(sOut, (LPBYTE)&prop, MIDIPROP_SET|MIDIPROP_TIMEDIV) != MMSYSERR_NOERROR)
goto error5;
mhdr.lpData = (char*)streambuf;
mhdr.dwBufferLength = mhdr.dwBytesRecorded = streambufsize;
mhdr.dwFlags = 0;
if(midiOutPrepareHeader((HMIDIOUT)out, &mhdr, sizeof(MIDIHDR)) != MMSYSERR_NOERROR)
if(midiOutPrepareHeader((HMIDIOUT)sOut, &mhdr, sizeof(MIDIHDR)) != MMSYSERR_NOERROR)
goto error5;
if(midiStreamRestart(out) != MMSYSERR_NOERROR)
if(midiStreamRestart(sOut) != MMSYSERR_NOERROR)
goto error6;
if (midiOutSetVolume((HMIDIOUT)out, (DWORD)((volume & 0xFF) << 8) | (volume & 0xFF)) != MMSYSERR_NOERROR) {
printf("Cannot set volume, will have to use windows application volume control");
}
printf("buffering...\n");
fprintf(stderr, "buffering...\n");
fflush(stderr);
get_buffer(tracks, ntracks, streambuf, &streamlen);
while(streamlen > 0)
{
mhdr.dwBytesRecorded = streamlen;
if(midiStreamOut(out, &mhdr, sizeof(MIDIHDR)) != MMSYSERR_NOERROR)
if(midiStreamOut(sOut, &mhdr, sizeof(MIDIHDR)) != MMSYSERR_NOERROR)
goto error7;
WaitForSingleObject(event, INFINITE);
printf("buffering...\n");
if (sFilename != filename) {
fprintf(stderr, "switch to new file %s.\n", sFilename);
fflush(stderr);
break;
}
fprintf(stderr, "buffering...\n");
fflush(stderr);
get_buffer(tracks, ntracks, streambuf, &streamlen);
}
printf("done.\n");
fprintf(stderr, "done.\n");
fflush(stderr);
error7:
midiOutReset((HMIDIOUT)out);
midiOutReset((HMIDIOUT)sOut);
error6:
midiOutUnprepareHeader((HMIDIOUT)out, &mhdr, sizeof(MIDIHDR));
midiOutUnprepareHeader((HMIDIOUT)sOut, &mhdr, sizeof(MIDIHDR));
error5:
midiStreamClose(out);
midiStreamClose(sOut);
error4:
CloseHandle(event);
@ -400,13 +414,244 @@ error1:
return(0);
}
DWORD WINAPI MyThreadFunction(LPVOID lpParam) {
while (1) {
MMRESULT r = midiOutSetVolume((HMIDIOUT)sOut, (DWORD)((sVolume & 0xFF) << 8) | (sVolume & 0xFF));
if (r != MMSYSERR_NOERROR) {
fprintf(stderr, "Cannot set volume via midi...");
switch (r) {
case MMSYSERR_INVALHANDLE:
fprintf(stderr, "Reason: handle not valid\n");
break;
case MMSYSERR_NOMEM:
fprintf(stderr, "Reason: memory error\n");
break;
case MMSYSERR_NOTSUPPORTED:
fprintf(stderr, "Reason: not supported\n");
break;
default:
fprintf(stderr, "Reason: unknown\n");
}
fflush(stderr);
MMRESULT err = mixerOpen(&sMixerHandle, sDeviceId, 0, 0, 0);
if (err) {
fprintf(stderr, "ERROR: Can't open Mixer Device! -- %08X\n", err);
switch (err) {
case MMSYSERR_ALLOCATED:
fprintf(stderr, "MMSYSERR_ALLOCATED\n");
break;
case MMSYSERR_BADDEVICEID:
fprintf(stderr, "MMSYSERR_BADDEVICEID\n");
break;
case MMSYSERR_INVALFLAG:
fprintf(stderr, "MMSYSERR_INVALFLAG\n");
break;
case MMSYSERR_INVALHANDLE:
fprintf(stderr, "MMSYSERR_INVALHANDLE\n");
break;
case MMSYSERR_INVALPARAM:
fprintf(stderr, "MMSYSERR_INVALPARAM\n");
break;
case MMSYSERR_NODRIVER:
fprintf(stderr, "MMSYSERR_NODRIVER\n");
break;
case MMSYSERR_NOMEM:
fprintf(stderr, "MMSYSERR_NOMEM\n");
break;
default:
fprintf(stderr, "Reason: unknown\n");
}
fflush(stderr);
} else {
MIXERCAPS mixcaps;
MIXERLINE mixerline;
MMRESULT err;
unsigned long i;
/* Get info about the first Mixer Device */
if (!(err = mixerGetDevCaps((UINT)sMixerHandle, &mixcaps, sizeof(MIXERCAPS)))) {
/* Print out the name of each destination line */
for (i = 0; i < mixcaps.cDestinations; i++) {
mixerline.cbStruct = sizeof(MIXERLINE);
mixerline.dwDestination = i;
if (!(err = mixerGetLineInfo((HMIXEROBJ)sMixerHandle, &mixerline, MIXER_GETLINEINFOF_DESTINATION)))
{
fprintf(stderr, "Destination #%lu = %s\n", i, mixerline.szName);
fflush(stderr);
}
}
}
mixerline.cbStruct = sizeof(MIXERLINE);
mixerline.dwDestination = 0;
mixerline.dwSource = 0;
if ((err = mixerGetLineInfo((HMIXEROBJ)sMixerHandle, &mixerline, MIXER_GETLINEINFOF_DESTINATION))) {
/* An error */
fprintf(stderr, "Error #%d calling mixerGetLineInfo()\n", err);
switch (err) {
case MIXERR_INVALLINE:
fprintf(stderr, "MIXERR_INVALLINE\n");
break;
case MMSYSERR_BADDEVICEID:
fprintf(stderr, "MMSYSERR_BADDEVICEID\n");
break;
case MMSYSERR_INVALFLAG:
fprintf(stderr, "MMSYSERR_INVALFLAG\n");
break;
case MMSYSERR_INVALHANDLE:
fprintf(stderr, "MMSYSERR_INVALHANDLE\n");
break;
case MMSYSERR_INVALPARAM:
fprintf(stderr, "MMSYSERR_INVALPARAM\n");
break;
case MMSYSERR_NODRIVER:
fprintf(stderr, "MMSYSERR_NODRIVER\n");
break;
default:
fprintf(stderr, "Reason: unknown\n");
}
fflush(stderr);
} else {
fprintf(stderr, "Mixerline: %s\n", mixerline.szName);
MIXERCONTROL mixerControlArray;
MIXERLINECONTROLS mixerLineControls;
mixerLineControls.cbStruct = sizeof(MIXERLINECONTROLS);
/* Tell mixerGetLineControls() for which line we're retrieving info.
We do this by putting the desired line's ID number in dwLineID */
mixerLineControls.dwLineID = mixerline.dwLineID;
/* We want to fetch info on only 1 control */
mixerLineControls.cControls = 1;
/* Tell mixerGetLineControls() for which type of control we're
retrieving info. We do this by putting the desired control type
in dwControlType */
mixerLineControls.dwControlType = MIXERCONTROL_CONTROLTYPE_VOLUME;
/* Give mixerGetLineControls() the address of the MIXERCONTROL
struct to hold info */
mixerLineControls.pamxctrl = &mixerControlArray;
/* Tell mixerGetLineControls() how big the MIXERCONTROL is. This
saves having to initialize the cbStruct of the MIXERCONTROL itself */
mixerLineControls.cbmxctrl = sizeof(MIXERCONTROL);
/* Retrieve info on only any volume slider control for this line */
if ((err = mixerGetLineControls((HMIXEROBJ)sMixerHandle, &mixerLineControls, MIXER_GETLINECONTROLSF_ONEBYTYPE))) {
/* An error */
fprintf(stderr, "Error #%d calling mixerGetLineControls()\n", err);
fflush(stderr);
} else {
fprintf(stderr, "Mixercontrol: %s\n", mixerControlArray.szName);
fflush(stderr);
// try via mixer controls
MIXERCONTROLDETAILS_UNSIGNED value;
MIXERCONTROLDETAILS mixerControlDetails;
MMRESULT err;
mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
/* Tell mixerSetControlDetails() which control whose value we
want to set. We do this by putting the desired control's
ID number in dwControlID. The "Speaker Out" line's
volume slider has an ID of 0x00000000 */
mixerControlDetails.dwControlID = mixerControlArray.dwControlID;
/* This is always 1 for a MIXERCONTROL_CONTROLF_UNIFORM control */
mixerControlDetails.cChannels = 1;
/* This is always 0 except for a MIXERCONTROL_CONTROLF_MULTIPLE control */
mixerControlDetails.cMultipleItems = 0;
/* Give mixerSetControlDetails() the address of the
MIXERCONTROLDETAILS_UNSIGNED struct into which we place the value */
mixerControlDetails.paDetails = &value;
/* Tell mixerSetControlDetails() how big the MIXERCONTROLDETAILS_UNSIGNED is */
mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
/* Store the value */
value.dwValue = (sVolume & 0xFF) << 8;
/* Set the value of the volume slider control for this line */
if ((err = mixerSetControlDetails((HMIXEROBJ)sMixerHandle, &mixerControlDetails, MIXER_SETCONTROLDETAILSF_VALUE))) {
fprintf(stderr, "Error #%d calling mixerSetControlDetails() ", err);
switch (err) {
case MIXERR_INVALCONTROL:
fprintf(stderr, "Invalid control\n");
break;
case MMSYSERR_BADDEVICEID:
fprintf(stderr, "bad device id\n");
break;
case MMSYSERR_INVALFLAG:
fprintf(stderr, "invalid flag\n");
break;
case MMSYSERR_INVALHANDLE:
fprintf(stderr, "Invalid handle\n");
break;
case MMSYSERR_INVALPARAM:
fprintf(stderr, "Invalid param\n");
break;
case MMSYSERR_NODRIVER:
fprintf(stderr, "No driver\n");
break;
default:
fprintf(stderr, "\n");
}
fprintf(stderr, "Will have to use windows application volume control.\n");
fflush(stderr);
}
}
}
}
}
char data1, data2;
fprintf(stderr, "read from stdin...");
fflush(stderr);
if (fread(&data1, sizeof(char), 1, stdin) != 1) {
exit(0);
} else {
if (fread(&data2, sizeof(char), 1, stdin) != 1) {
exit(0);
}
}
fprintf(stderr, "got %x %x\n", data1, data2);
fflush(stderr);
if (data1 > 0) {
// new filename
short length = data1 | (data2 << 8);
fprintf(stderr, "New filename sending: %d bytes\n", length);
fflush(stderr);
static char buf[0xffff];
int lastRead, totalRead = 0;
while ((lastRead = fread(buf + totalRead, sizeof(char), length - totalRead, stdin)) > 0 && totalRead < length) {
totalRead += lastRead;
}
if (totalRead < length) {
fprintf(stderr, "not enough data, got %d, expected %d bytes\n", totalRead, length);
fflush(stderr);
exit(0);
}
buf[length] = '\0';
sFilename = buf;
fprintf(stderr, "New filename received: %s\n", sFilename);
fflush(stderr);
SetEvent(event);
} else {
fprintf(stderr, "New volume received: %d\n", data2);
fflush(stderr);
sVolume = data2;
}
}
return 0;
}
int main(int argc, char* argv[])
{
if(argc != 3) {
return printf("Usage: %s <volume 0-255> <filename.mid>\n", argv[0]);
return fprintf(stderr, "Usage: %s <volume 0-255> <filename.mid>\n", argv[0]);
}
sVolume = atoi(argv[1]);
_setmode(_fileno(stdin), _O_BINARY);
hWaitingThread = CreateThread(NULL, 0, MyThreadFunction, NULL, 0, NULL);
sFilename = argv[2];
while (1) {
example9(sFilename);
fprintf(stdout, "1");
fflush(stdout);
WaitForSingleObject(event, INFINITE);
}
example9(argv[2], atoi(argv[1]));
return 0;
}