Files
2025-05-21 21:20:08 +03:00

467 lines
12 KiB
C

/*===============================================================================================
OUTPUT_MP3.DLL
Copyright (c), Firelight Technologies Pty, Ltd 2004-2016.
Shows how to write an FMOD output plugin that writes the output to an mp3 file using
lame_enc.dll.
Most of this source code that does the encoding is taken from the LAME encoder example.
An FMOD Ex output plugin is created by declaring the FMODGetOutputDescription function,
then filling out a FMOD_OUTPUT_DESCRIPTION and returning a pointer to it.
FMOD will then call those functions when appropriate.
To get the output from FMOD so that you can write it to your sound device (or LAME encoder
function in this case), FMOD_OUTPUT_STATE::readfrommixer is called to run the mixer.
We acknowledge that we are using LAME, which originates from www.mp3dev.org.
LAME is under the LGPL and as an external FMOD plugin with full source code for the interface
it is allowable under the LGPL to be distributed in this fashion.
===============================================================================================*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>
#include "../../../api/inc/fmod.h"
#include "../../../api/inc/fmod_output.h"
#include "../../../api/inc/fmod_errors.h"
#include "BladeMP3EncDll.h"
typedef struct
{
FILE *mFP;
HINSTANCE mDLL;
HBE_STREAM hbeStream;
PBYTE pMP3Buffer;
PSHORT pWAVBuffer;
BE_VERSION Version;
DWORD dwMP3Buffer;
DWORD dwSamples;
BEINITSTREAM beInitStream;
BEENCODECHUNK beEncodeChunk;
BEDEINITSTREAM beDeinitStream;
BECLOSESTREAM beCloseStream;
BEVERSION beVersion;
BEWRITEVBRHEADER beWriteVBRHeader;
} outputmp3_state;
FMOD_OUTPUT_DESCRIPTION mp3output;
FMOD_RESULT F_CALLBACK OutputMP3_GetNumDriversCallback(FMOD_OUTPUT_STATE *output, int *numdrivers);
FMOD_RESULT F_CALLBACK OutputMP3_GetDriverNameCallback(FMOD_OUTPUT_STATE *output, int id, char *name, int namelen);
FMOD_RESULT F_CALLBACK OutputMP3_InitCallback(FMOD_OUTPUT_STATE *output, int selecteddriver, FMOD_INITFLAGS flags, int *outputrate, int outputchannels, FMOD_SOUND_FORMAT *outputformat, int dspbufferlength, int dspnumbuffers, const void *hwnd);
FMOD_RESULT F_CALLBACK OutputMP3_CloseCallback(FMOD_OUTPUT_STATE *output);
FMOD_RESULT F_CALLBACK OutputMP3_UpdateCallback(FMOD_OUTPUT_STATE *output);
FMOD_RESULT F_CALLBACK OutputMP3_GetHandleCallback(FMOD_OUTPUT_STATE *output, void **handle);
#ifdef __cplusplus
extern "C" {
#endif
/*
FMODGetOutputDescription is mandantory for every fmod plugin. This is the symbol the registerplugin function searches for.
Must be declared with F_API to make it export as stdcall.
*/
F_DECLSPEC F_DLLEXPORT FMOD_OUTPUT_DESCRIPTION * F_API FMODGetOutputDescription()
{
memset(&mp3output, 0, sizeof(FMOD_OUTPUT_DESCRIPTION));
mp3output.name = "FMOD MP3 Output";
mp3output.version = 0x00010000;
mp3output.polling = 0; /* False = no thread is created. Plugin must drive FMOD's mixer */
mp3output.getnumdrivers = OutputMP3_GetNumDriversCallback;
mp3output.getdrivername = OutputMP3_GetDriverNameCallback;
mp3output.getdrivercaps = 0;
mp3output.init = OutputMP3_InitCallback;
mp3output.close = OutputMP3_CloseCallback;
mp3output.update = OutputMP3_UpdateCallback;
mp3output.gethandle = OutputMP3_GetHandleCallback;
mp3output.getposition = 0; /* Not a polling output so getposition is never called */
mp3output.lock = 0; /* Not a polling output so lock is never called */
mp3output.unlock = 0; /* Not a polling output so unlock is never called */
return &mp3output;
}
#ifdef __cplusplus
}
#endif
/*
[
[DESCRIPTION]
[PARAMETERS]
[RETURN_VALUE]
FMOD_OK
[REMARKS]
[PLATFORMS]
Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube
[SEE_ALSO]
]
*/
FMOD_RESULT F_CALLBACK OutputMP3_GetNumDriversCallback(FMOD_OUTPUT_STATE *output, int *numdrivers)
{
*numdrivers = 1;
return FMOD_OK;
}
/*
[
[DESCRIPTION]
[PARAMETERS]
[RETURN_VALUE]
FMOD_OK
[REMARKS]
[PLATFORMS]
Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube
[SEE_ALSO]
]
*/
FMOD_RESULT F_CALLBACK OutputMP3_GetDriverNameCallback(FMOD_OUTPUT_STATE *output, int id, char *name, int namelen)
{
strncpy(name, "fmodoutput.mp3", namelen);
return FMOD_OK;
}
/*
[
[DESCRIPTION]
[PARAMETERS]
[RETURN_VALUE]
FMOD_OK
[REMARKS]
[PLATFORMS]
Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube
[SEE_ALSO]
]
*/
FMOD_RESULT F_CALLBACK OutputMP3_InitCallback(FMOD_OUTPUT_STATE *output, int selecteddriver, FMOD_INITFLAGS flags, int *outputrate, int outputchannels, FMOD_SOUND_FORMAT *outputformat, int dspbufferlength, int dspnumbuffers, const void *hwnd)
{
outputmp3_state *state;
BE_CONFIG beConfig = {0,};
BE_ERR err = 0;
char filename[256];
if (outputchannels > 2)
{
return FMOD_ERR_TOOMANYCHANNELS;
}
/*
Create a structure that we can attach to the plugin instance.
*/
state = (outputmp3_state *)calloc(sizeof(outputmp3_state), 1);
if (!state)
{
return FMOD_ERR_MEMORY;
}
output->plugindata = state;
*outputformat = FMOD_SOUND_FORMAT_PCM16;
#ifdef _WIN64
state->mDLL = LoadLibrary("lame_enc64.dll");
#else
state->mDLL = LoadLibrary("lame_enc.dll");
#endif
if (!state->mDLL)
{
return FMOD_ERR_PLUGIN_RESOURCE;
}
/*
Get Interface functions from the DLL
*/
state->beInitStream = (BEINITSTREAM) GetProcAddress(state->mDLL, TEXT_BEINITSTREAM);
state->beEncodeChunk = (BEENCODECHUNK) GetProcAddress(state->mDLL, TEXT_BEENCODECHUNK);
state->beDeinitStream = (BEDEINITSTREAM) GetProcAddress(state->mDLL, TEXT_BEDEINITSTREAM);
state->beCloseStream = (BECLOSESTREAM) GetProcAddress(state->mDLL, TEXT_BECLOSESTREAM);
state->beVersion = (BEVERSION) GetProcAddress(state->mDLL, TEXT_BEVERSION);
state->beWriteVBRHeader = (BEWRITEVBRHEADER) GetProcAddress(state->mDLL, TEXT_BEWRITEVBRHEADER);
// Check if all interfaces are present
if(!state->beInitStream || !state->beEncodeChunk || !state->beDeinitStream || !state->beCloseStream || !state->beVersion || !state->beWriteVBRHeader)
{
return FMOD_ERR_PLUGIN;
}
// Get the version number
state->beVersion( &state->Version );
memset(&beConfig,0,sizeof(beConfig)); // clear all fields
// use the LAME config structure
beConfig.dwConfig = BE_CONFIG_LAME;
// this are the default settings for testcase.wav
// FMOD NOTE : The 'hwnd' param could be used to pass in info about the bitrate and encoding settings.
beConfig.format.LHV1.dwStructVersion = 1;
beConfig.format.LHV1.dwStructSize = sizeof(beConfig);
beConfig.format.LHV1.dwSampleRate = *outputrate; // INPUT FREQUENCY
beConfig.format.LHV1.dwReSampleRate = 0; // DON"T RESAMPLE
beConfig.format.LHV1.nMode = outputchannels == 1 ? BE_MP3_MODE_MONO : BE_MP3_MODE_JSTEREO; // OUTPUT IN STREO
beConfig.format.LHV1.dwBitrate = 128; // MINIMUM BIT RATE
beConfig.format.LHV1.nPreset = LQP_R3MIX; // QUALITY PRESET SETTING
beConfig.format.LHV1.dwMpegVersion = MPEG1; // MPEG VERSION (I or II)
beConfig.format.LHV1.dwPsyModel = 0; // USE DEFAULT PSYCHOACOUSTIC MODEL
beConfig.format.LHV1.dwEmphasis = 0; // NO EMPHASIS TURNED ON
beConfig.format.LHV1.bOriginal = TRUE; // SET ORIGINAL FLAG
beConfig.format.LHV1.bWriteVBRHeader = TRUE; // Write INFO tag
// beConfig.format.LHV1.dwMaxBitrate = 320; // MAXIMUM BIT RATE
// beConfig.format.LHV1.bCRC = TRUE; // INSERT CRC
// beConfig.format.LHV1.bCopyright = TRUE; // SET COPYRIGHT FLAG
// beConfig.format.LHV1.bPrivate = TRUE; // SET PRIVATE FLAG
// beConfig.format.LHV1.bWriteVBRHeader = TRUE; // YES, WRITE THE XING VBR HEADER
// beConfig.format.LHV1.bEnableVBR = TRUE; // USE VBR
// beConfig.format.LHV1.nVBRQuality = 5; // SET VBR QUALITY
beConfig.format.LHV1.bNoRes = TRUE; // No Bit resorvoir
// Preset Test
// beConfig.format.LHV1.nPreset = LQP_PHONE;
// Init the MP3 Stream
err = state->beInitStream(&beConfig, &state->dwSamples, &state->dwMP3Buffer, &state->hbeStream);
// Check result
if(err != BE_ERR_SUCCESSFUL)
{
return FMOD_ERR_PLUGIN;
}
// Allocate MP3 buffer
state->pMP3Buffer = malloc(state->dwMP3Buffer);
if(!state->pMP3Buffer)
{
return FMOD_ERR_MEMORY;
}
// Allocate WAV buffer
state->pWAVBuffer = malloc(state->dwSamples * sizeof(SHORT));
if (!state->pWAVBuffer)
{
return FMOD_ERR_MEMORY;
}
if (!hwnd)
{
strncpy(filename, "fmodoutput.mp3", 256);
}
else
{
strncpy(filename, (char *)hwnd, 256);
}
state->mFP = fopen(filename, "wb");
if (!state->mFP)
{
return FMOD_ERR_FILE_NOTFOUND;
}
return FMOD_OK;
}
/*
[
[DESCRIPTION]
[PARAMETERS]
[RETURN_VALUE]
FMOD_OK
[REMARKS]
[PLATFORMS]
Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube
[SEE_ALSO]
]
*/
FMOD_RESULT F_CALLBACK OutputMP3_CloseCallback(FMOD_OUTPUT_STATE *output)
{
outputmp3_state *state = output->plugindata;
DWORD dwRead=0;
DWORD dwWrite=0;
DWORD dwDone=0;
DWORD dwFileSize=0;
BE_ERR err;
if (!state)
{
return FMOD_OK;
}
// Deinit the stream
err = state->beDeinitStream(state->hbeStream, state->pMP3Buffer, &dwWrite);
// Check result
if(err != BE_ERR_SUCCESSFUL)
{
state->beCloseStream(state->hbeStream);
return FMOD_ERR_PLUGIN;
}
// Are there any bytes returned from the DeInit call?
// If so, write them to disk
if( dwWrite )
{
if( fwrite( state->pMP3Buffer, 1, dwWrite, state->mFP ) != dwWrite )
{
return FMOD_ERR_FILE_BAD;
}
}
// close the MP3 Stream
state->beCloseStream( state->hbeStream );
if (state->pWAVBuffer)
{
free(state->pWAVBuffer);
state->pWAVBuffer = 0;
}
if (state->pMP3Buffer)
{
free(state->pMP3Buffer);
state->pMP3Buffer = 0;
}
if (state->mFP)
{
fclose(state->mFP);
state->mFP = 0;
}
if (state)
{
free(state);
output->plugindata = 0;
}
return FMOD_OK;
}
/*
[
[DESCRIPTION]
[PARAMETERS]
[RETURN_VALUE]
FMOD_OK
[REMARKS]
[PLATFORMS]
Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube
[SEE_ALSO]
]
*/
FMOD_RESULT F_CALLBACK OutputMP3_UpdateCallback(FMOD_OUTPUT_STATE *output)
{
FMOD_RESULT result;
outputmp3_state *state = output->plugindata;
DWORD dwRead=0;
DWORD dwWrite=0;
DWORD dwDone=0;
DWORD dwFileSize=0;
BE_ERR err;
/*
Update the mixer to the interleaved buffer.
*/
result = output->readfrommixer(output, state->pWAVBuffer, state->dwSamples / sizeof(SHORT));
if (result != FMOD_OK)
{
return FMOD_OK;
}
dwRead = state->dwSamples * sizeof(SHORT);
{
// Encode samples
err = state->beEncodeChunk(state->hbeStream, dwRead / sizeof(SHORT), state->pWAVBuffer, state->pMP3Buffer, &dwWrite);
// Check result
if(err != BE_ERR_SUCCESSFUL)
{
state->beCloseStream(state->hbeStream);
return FMOD_ERR_PLUGIN;
}
// write dwWrite bytes that are returned in tehe pMP3Buffer to disk
if (fwrite(state->pMP3Buffer, 1, dwWrite, state->mFP) != dwWrite)
{
return FMOD_ERR_FILE_BAD;
}
dwDone += dwRead*sizeof(SHORT);
}
return FMOD_OK;
}
/*
[
[DESCRIPTION]
[PARAMETERS]
[RETURN_VALUE]
FMOD_OK
[REMARKS]
[PLATFORMS]
Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube
[SEE_ALSO]
]
*/
FMOD_RESULT F_CALLBACK OutputMP3_GetHandleCallback(FMOD_OUTPUT_STATE *output, void **handle)
{
outputmp3_state *state = output->plugindata;
*handle = state->mFP;
return FMOD_OK;
}