//
// Wma2Mp3: A basic audio file converter that uses the Windows Media Format SDK
// and the GPL "Lame" MPEG library to convert a Windows Media Audio file to an
// MP3. This was quickly hacked together from all kinds of sample code from all
// kinds of places. I take no credit for anything... and no responsibility
// either :).
//
#include <windows.h>
#include <string.h>
#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>
//
// The wmsdk.h header file comes with the Windows Media Format SDK.
// This code compiles with the Windows Media Format SDK v9. It will
// probably compile fine with later versions as well. You need
// to have installed Windows Media Player 9 or the Windows Media
// Format Runtime v9 to use this utilit.
//
#include <wmsdk.h>
//
// The BladeMP3EncDll.h header file comes with the Lame MP3 library.
// More info can be found at http://www.mp3dev.org/.
//
#include "BladeMP3EncDll.h"
BEINITSTREAM beInitStream=NULL;
BEENCODECHUNK beEncodeChunk=NULL;
BEDEINITSTREAM beDeinitStream=NULL;
BECLOSESTREAM beCloseStream=NULL;
BEVERSION beVersion=NULL;
BEWRITEVBRHEADER beWriteVBRHeader=NULL;
BEWRITEINFOTAG beWriteInfoTag=NULL;
IWMSyncReader* gpSyncReader=NULL;
INSSBuffer* gpWmaBuffer=NULL;
PSHORT gpBufferHead=NULL;
PSHORT gpBufferNow=NULL;
DWORD gdwBytesRemaining=0;
bool gWmaDone=false;
WCHAR g_wszSourceFileName[1024];
WCHAR g_wszDestFileName[1024];
bool CreateAndOpenSyncReader();
void CloseSyncReader();
DWORD FillAudioBuffer( PSHORT pAudioBuffer, DWORD dwSamplesWanted );
void TransferMetadata();
HRESULT TransferSingleItem( IWMHeaderInfo *pSrc, IWMHeaderInfo *pDest, WCHAR *pwszName );
////////////////////////////////////////////////////////////////////////////////
int main( int argc, char *argv[] )
{
HINSTANCE hDLL =NULL;
FILE* pFileIn =NULL;
FILE* pFileOut =NULL;
BE_VERSION Version ={0,};
BE_CONFIG beConfig ={0,};
CHAR strFileIn[255] ={'0',};
CHAR strFileOut[255] ={'0',};
DWORD dwBitrate =0;
DWORD dwSamples =0;
DWORD dwMP3Buffer =0;
HBE_STREAM hbeStream =0;
BE_ERR err =0;
PBYTE pMP3Buffer =NULL;
PSHORT pWAVBuffer =NULL;
HRESULT hr = S_OK;
CoInitialize(NULL);
if(argc != 3)
{
fprintf(stderr, "Wma2Mp3: Please specify a WMA file to convert./n");
fprintf(stderr, "Usage: %s <filename> <target bitrate in kbs>/n", argv[0] );
return -1;
}
//
// Get the bitrate and make it something useful.
//
dwBitrate = atoi(argv[2]);
if( dwBitrate <= 56 ) dwBitrate = 56;
else if( (56 < dwBitrate) && (dwBitrate <= 128) ) dwBitrate = 128;
else if( (128 < dwBitrate) && (dwBitrate <= 256) ) dwBitrate = 256;
else if( dwBitrate > 256 ) dwBitrate = 320;
//
// Set up the file names. If there's an extention on the input
// file, replace it with mp3. Otherwise, just append mp3.
//
ZeroMemory( g_wszSourceFileName, sizeof( g_wszSourceFileName ) );
ZeroMemory( g_wszDestFileName, sizeof( g_wszDestFileName ) );
strcpy(strFileIn ,argv[1]);
if( 0 == MultiByteToWideChar( CP_ACP, 0, strFileIn, (int)strlen(strFileIn) + 1,
g_wszSourceFileName, sizeof( g_wszSourceFileName ) / sizeof(WCHAR) ) )
{
return -1;
}
char* cCurrent = &(strFileOut[254]);
ZeroMemory( strFileOut, sizeof( strFileOut ) );
strcpy(strFileOut,argv[1]);
while( *cCurrent != '.' && (cCurrent >= strFileOut) ) cCurrent--;
if( *cCurrent == '.' ) *cCurrent = 0;
strcat(strFileOut,".mp3");
if( 0 == MultiByteToWideChar( CP_ACP, 0, strFileOut, (int)strlen(strFileOut) + 1,
g_wszDestFileName, sizeof( g_wszDestFileName ) / sizeof(WCHAR) ) )
{
return -1;
}
//
// Load the lame_enc.dll library.
//
hDLL = LoadLibrary("lame_enc.dll");
if( NULL == hDLL )
{
fprintf(stderr,"Error loading lame_enc.DLL - you can probably find this somewhere on the web./n");
return -1;
}
beInitStream = (BEINITSTREAM) GetProcAddress(hDLL, TEXT_BEINITSTREAM);
beEncodeChunk = (BEENCODECHUNK) GetProcAddress(hDLL, TEXT_BEENCODECHUNK);
beDeinitStream = (BEDEINITSTREAM) GetProcAddress(hDLL, TEXT_BEDEINITSTREAM);
beCloseStream = (BECLOSESTREAM) GetProcAddress(hDLL, TEXT_BECLOSESTREAM);
beVersion = (BEVERSION) GetProcAddress(hDLL, TEXT_BEVERSION);
beWriteVBRHeader= (BEWRITEVBRHEADER) GetProcAddress(hDLL,TEXT_BEWRITEVBRHEADER);
beWriteInfoTag = (BEWRITEINFOTAG) GetProcAddress(hDLL,TEXT_BEWRITEINFOTAG);
if( !beInitStream || !beEncodeChunk || !beDeinitStream ||
!beCloseStream || !beVersion || !beWriteVBRHeader )
{
printf("Unable to get LAME interfaces: your lame_enc.dll is bad?/n");
return -1;
}
beVersion( &Version );
//
// Print out an informative welcome.
//
printf( "Wma2Mp3 v1.0/n/n"
"lame_enc.dll version %u.%02u (%u/%u/%u)/n"
"lame_enc Engine %u.%02u/n"
"lame_enc homepage at %s/n/n"
"source file: %s/n"
"dest file: %s/n"
"bitrate: %d kbs/n/n",
Version.byDLLMajorVersion, Version.byDLLMinorVersion,
Version.byDay, Version.byMonth, Version.wYear,
Version.byMajorVersion, Version.byMinorVersion,
Version.zHomepage,
strFileIn,
strFileOut,
dwBitrate );
if( !CreateAndOpenSyncReader() )
{
fprintf( stderr, "Error opening input file %s", argv[1] );
return -1;
}
//
// Open the output MP3 file.
//
pFileOut= fopen(strFileOut,"wb+");
if(pFileOut == NULL)
{
fprintf( stderr, "Error creating output file %s/n", strFileOut );
return -1;
}
//
// Configure the LAME encoder the to do the conversion.
//
memset(&beConfig,0,sizeof(beConfig));
beConfig.dwConfig = BE_CONFIG_LAME;
beConfig.format.LHV1.dwStructVersion = 1;
beConfig.format.LHV1.dwStructSize = sizeof(beConfig);
beConfig.format.LHV1.dwSampleRate = 44100; // INPUT FREQUENCY
beConfig.format.LHV1.dwReSampleRate = 0; // DON'T RESAMPLE
beConfig.format.LHV1.nMode = BE_MP3_MODE_JSTEREO; // OUTPUT IN STREO
beConfig.format.LHV1.dwBitrate = dwBitrate; // MINIMUM BIT RATE
beConfig.format.LHV1.nPreset = LQP_VERYHIGH_QUALITY; // 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.bNoRes = TRUE; // No Bit resorvoir
beConfig.format.LHV1.bCRC = TRUE; // INSERT CRC
// Other possibilities?
// beConfig.format.LHV1.dwMaxBitrate = 320; // MAXIMUM BIT RATE
// 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
err = beInitStream(&beConfig, &dwSamples, &dwMP3Buffer, &hbeStream);
if(err != BE_ERR_SUCCESSFUL)
{
fprintf(stderr,"Error opening encoding stream (%lu)/n", err);
return -1;
}
//
// Allocate the work buffers and do the conversion.
//
pMP3Buffer = new BYTE[dwMP3Buffer];
pWAVBuffer = new SHORT[dwSamples];
if( !pMP3Buffer || !pWAVBuffer )
{
printf("Out of memory/n");
return -1;
}
DWORD dwRead=0;
DWORD dwWrite=0;
DWORD dwDone=0;
DWORD dwClick = 80;
while ( (dwRead = FillAudioBuffer( pWAVBuffer, dwSamples )) > 0 )
{
dwClick--;
if( !dwClick )
{
printf(".");
dwClick = 80;
}
err = beEncodeChunk( hbeStream, dwRead, pWAVBuffer, pMP3Buffer, &dwWrite );
if(err != BE_ERR_SUCCESSFUL)
{
beCloseStream(hbeStream);
fprintf(stderr,"beEncodeChunk() failed (%lu)/n", err);
return -1;
}
if(fwrite(pMP3Buffer,1,dwWrite,pFileOut) != dwWrite)
{
fprintf(stderr,"Output file write error/n");
return -1;
}
dwDone += dwRead*sizeof(SHORT);
}
printf("/n");
//
// Complete the stream encode and shut down. Note that de-initializing the
// stream may flush out a few final bytes.
//
err = beDeinitStream(hbeStream, pMP3Buffer, &dwWrite);
if(err != BE_ERR_SUCCESSFUL)
{
beCloseStream(hbeStream);
fprintf(stderr,"beExitStream failed (%lu)/n", err);
return -1;
}
if( dwWrite )
{
if( fwrite( pMP3Buffer, 1, dwWrite, pFileOut ) != dwWrite )
{
fprintf(stderr,"Output file write error/n");
return -1;
}
}
beCloseStream( hbeStream );
delete [] pWAVBuffer;
delete [] pMP3Buffer;
fclose( pFileOut );
if ( beWriteInfoTag )
{
beWriteInfoTag( hbeStream, strFileOut );
}
else
{
beWriteVBRHeader( strFileOut );
}
CloseSyncReader();
TransferMetadata();
return 0;
}
////////////////////////////////////////////////////////////////////////////////
bool
CreateAndOpenSyncReader(void)
{
HRESULT hr = S_OK;
//
// Create a sync reader object and open the file.
// We just assume we're going to have only a single ASF stream,
// and that it's an audio stream. This could be smarter,
// of course...
//
hr = WMCreateSyncReader( NULL, 0, &gpSyncReader );
if( (S_OK != hr) || !gpSyncReader )
{
hr = E_UNEXPECTED;
goto CleanExit;
}
hr = gpSyncReader->Open( g_wszSourceFileName );
if( S_OK != hr )
{
hr = E_UNEXPECTED;
goto CleanExit;
}
IWMOutputMediaProps *pProps = NULL;
hr = gpSyncReader->GetOutputFormat( 0, 0, &pProps );
if( (S_OK != hr) || !pProps )
{
hr = E_UNEXPECTED;
goto CleanExit;
}
BYTE rgMediaProps[2048];
DWORD dwMediaType = 2048;
ZeroMemory( rgMediaProps, sizeof( rgMediaProps ) );
WM_MEDIA_TYPE *pMT = (WM_MEDIA_TYPE*)rgMediaProps;
hr = pProps->GetMediaType( pMT, &dwMediaType );
if( S_OK != hr )
{
hr = E_UNEXPECTED;
goto CleanExit;
}
//
// Validate the wave format. Should we try and set the output
// format if it's not what we're expecting? After all, the format
// SDK is capable of resampling both the sample rate and the
// bit depth if necessary.
//
WAVEFORMATEX *pWfEx = (WAVEFORMATEX*)pMT->pbFormat;
if( (pWfEx->wFormatTag != WAVE_FORMAT_PCM) ||
(pWfEx->nChannels != 2) ||
(pWfEx->nSamplesPerSec != 44100) ||
(pWfEx->wBitsPerSample != 16) )
{
hr = E_UNEXPECTED;
goto CleanExit;
}
DWORD dwMaxSampleSize = 0;
hr = gpSyncReader->GetMaxOutputSampleSize( 0, &dwMaxSampleSize );
if( S_OK != hr )
{
hr = E_UNEXPECTED;
goto CleanExit;
}
CleanExit:
if( pProps ) pProps->Release();
if( S_OK != hr )
{
if( gpSyncReader ) gpSyncReader->Release();
return( false );
}
return( true );
}
////////////////////////////////////////////////////////////////////////////////
DWORD
FillAudioBuffer(
PSHORT pAudioBuffer,
DWORD dwSamplesWanted )
{
HRESULT hr = S_OK;
DWORD dwBytesRemaining = dwSamplesWanted*sizeof(SHORT);
PSHORT pCurrentSample = pAudioBuffer;
DWORD dwSamplesWritten = 0;
if( gWmaDone ) return( 0 );
while( dwBytesRemaining )
{
//
// If we've consumed the sample we're currently working on,
// or if this is the first time around, grab the next sample.
//
if( !gdwBytesRemaining || !gpWmaBuffer )
{
QWORD qwSampleTime = 0;
QWORD qwDuration = 0;
DWORD dwFlags = 0;
DWORD dwOutputNum = 0;
WORD wStream = 0;
if( gpWmaBuffer )
gpWmaBuffer->Release();
hr = gpSyncReader->GetNextSample( 0, &gpWmaBuffer, &qwSampleTime, &qwDuration,
&dwFlags, &dwOutputNum, &wStream );
if( (S_OK != hr) || !gpWmaBuffer )
{
if( NS_E_NO_MORE_SAMPLES == hr )
{
gWmaDone = true;
}
else
{
hr = E_UNEXPECTED;
}
goto CleanExit;
}
hr = gpWmaBuffer->GetBufferAndLength( (BYTE**)&gpBufferHead, &gdwBytesRemaining );
if( S_OK != hr )
{
hr = E_UNEXPECTED;
goto CleanExit;
}
gpBufferNow = gpBufferHead;
}
//
// There are samples available so process them.
//
DWORD dwInputBytesReady = gdwBytesRemaining;
if( dwInputBytesReady > dwBytesRemaining )
{
dwInputBytesReady = dwBytesRemaining;
}
memcpy( (BYTE*)pCurrentSample, (BYTE*)gpBufferNow, dwInputBytesReady );
dwBytesRemaining -= dwInputBytesReady;
gdwBytesRemaining -= dwInputBytesReady;
gpBufferNow += (dwInputBytesReady/sizeof(SHORT));
pCurrentSample += (dwInputBytesReady/sizeof(SHORT));
dwSamplesWritten += (dwInputBytesReady/sizeof(SHORT));
}
CleanExit:
return( dwSamplesWritten );
}
////////////////////////////////////////////////////////////////////////////////
void
CloseSyncReader()
{
if( gpSyncReader )
{
gpSyncReader->Close();
gpSyncReader->Release();
}
}
////////////////////////////////////////////////////////////////////////////////
void TransferMetadata()
{
HRESULT hr = S_OK;
IWMMetadataEditor *pSrc = NULL;
IWMMetadataEditor *pDest = NULL;
IWMHeaderInfo *pHISrc = NULL;
IWMHeaderInfo *pHIDest = NULL;
do
{
//
// Open the source and destination file.
//
hr = WMCreateEditor( &pSrc );
if( SUCCEEDED( hr ) )
{
hr = pSrc->Open( g_wszSourceFileName );
if( SUCCEEDED( hr ) )
{
hr = pSrc->QueryInterface( IID_IWMHeaderInfo, (void**)&pHISrc );
}
}
if( FAILED( hr ) )
{
printf( "Failed to open source file for metadata transfer./n" );
break;
}
hr = WMCreateEditor( &pDest );
if( SUCCEEDED( hr ) )
{
hr = pDest->Open( g_wszDestFileName );
if( SUCCEEDED( hr ) )
{
hr = pDest->QueryInterface( IID_IWMHeaderInfo, (void**)&pHIDest );
}
}
if( FAILED( hr ) )
{
printf( "Failed to open destination file for metadata transfer./n" );
break;
}
//
// Migrate over the metadata items that the portable player can display.
//
TransferSingleItem( pHISrc, pHIDest, (WCHAR*)g_wszWMTitle );
TransferSingleItem( pHISrc, pHIDest, (WCHAR*)g_wszWMAuthor );
TransferSingleItem( pHISrc, pHIDest, (WCHAR*)g_wszWMDescription );
TransferSingleItem( pHISrc, pHIDest, (WCHAR*)g_wszWMAlbumTitle );
TransferSingleItem( pHISrc, pHIDest, (WCHAR*)g_wszWMTrack );
TransferSingleItem( pHISrc, pHIDest, (WCHAR*)g_wszWMTrackNumber );
}
while( FALSE );
if( pSrc )
{
pSrc->Close();
pSrc->Release();
}
if( pDest )
{
pDest->Flush();
pDest->Close();
pDest->Release();
}
return;
}
////////////////////////////////////////////////////////////////////////////////
HRESULT TransferSingleItem( IWMHeaderInfo *pSrc, IWMHeaderInfo *pDest, WCHAR *pwszName )
{
HRESULT hr = S_OK;
WORD wStream = -1;
WMT_ATTR_DATATYPE wmType = WMT_TYPE_STRING;
BYTE rgData[2048];
WORD wLen = 2048;
hr = pSrc->GetAttributeByName( &wStream,
pwszName,
&wmType,
rgData,
&wLen );
if( SUCCEEDED( hr ) )
{
hr = pDest->SetAttribute( wStream,
pwszName,
wmType,
rgData,
wLen );
}
return( hr );
}
////////////////////////////////////////////////////////////////////////////////