AudioCoderWMA.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. #include <windows.h>
  2. #include <mmreg.h>
  3. #include <msacm.h>
  4. #include "../nu/ns_wc.h"
  5. #include "resource.h"
  6. #include "wmsdk.h" // for IWMWriterSink
  7. #include "AudioCoderWMA.h"
  8. #include <cassert>
  9. #include <exception>
  10. #include "../nu/AutoLock.h"
  11. #include "../nu/AutoWide.h"
  12. #include "../Winamp/strutil.h"
  13. #include "../Agave/Language/api_language.h"
  14. /* TODO: implement 2-pass encoding via IWMWriterPreprocess */
  15. int config_bitrate, config_samplerate, config_nch;
  16. // New globals for encoder query
  17. class CustomIndexStatus : public IWMStatusCallback
  18. {
  19. public:
  20. CustomIndexStatus( HANDLE _done ) : done(_done), IWMStatusCallback(), refcount(1)
  21. {}
  22. // IUnknown methods
  23. public:
  24. virtual ULONG STDMETHODCALLTYPE AddRef()
  25. {
  26. return ++refcount;
  27. }
  28. virtual ULONG STDMETHODCALLTYPE Release()
  29. {
  30. // If we go to zero, who cares?
  31. return --refcount;
  32. }
  33. virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **ppvObject)
  34. {
  35. HRESULT hRetval = E_NOINTERFACE;
  36. if (IID_IWMStatusCallback == iid)
  37. {
  38. *ppvObject = static_cast<IWMStatusCallback *>(this);
  39. hRetval = S_OK;
  40. }
  41. else
  42. {
  43. *ppvObject = NULL;
  44. }
  45. return hRetval;
  46. }
  47. // IWMStatusCallback methods
  48. public:
  49. HRESULT STDMETHODCALLTYPE OnStatus( WMT_STATUS Status, HRESULT hr, WMT_ATTR_DATATYPE dwType, BYTE* pValue, void* pvContext )
  50. {
  51. switch ( Status )
  52. {
  53. case WMT_CLOSED:
  54. // You may want to deal with the HRESULT value passed with the status.
  55. // If you do, you should do it here.
  56. // Signal the event.
  57. SetEvent(done);
  58. break;
  59. }
  60. return S_OK;
  61. }
  62. protected:
  63. ULONG refcount;
  64. HANDLE done;
  65. };
  66. // Our custom buffer object, used by the writer sink.
  67. AudioCoderWMA::AudioCoderWMA(int numchannels, int samplerate, int bitspersamp, configtype *cfg, char *configfile) : AudioCoder()
  68. {
  69. lastByteCount=0;
  70. writerAdvanced=0;
  71. begin_writing = false;
  72. error = WMA_NO_ERROR;
  73. sink = NULL;
  74. // Get globals from Winamp.ini config file
  75. config_bitrate = cfg->config_bitrate;
  76. config_samplerate = cfg->config_samplesSec;
  77. config_nch = cfg->config_nch;
  78. timeunits_per_byte = ( ( (10000000.0) / (double)samplerate ) / (double)numchannels ) / ( (double)bitspersamp / 8.0 );
  79. //char t[100] = {0};
  80. //wsprintf(t,"%d", timeunits_per_byte);
  81. //::MessageBox(NULL, t, t, MB_OK);
  82. input_bytecount = 0;
  83. HRESULT hr = CreateAndConfigureWriter(numchannels, samplerate, bitspersamp, configfile);
  84. if ( FAILED(hr) )
  85. {
  86. error = WMA_CANT_CREATE_WRITER;
  87. }
  88. }
  89. AudioCoderWMA::~AudioCoderWMA()
  90. {
  91. if (writer)
  92. {
  93. if ( begin_writing )
  94. {
  95. begin_writing = false;
  96. writer->EndWriting();
  97. }
  98. writer->Release();
  99. writer = NULL;
  100. }
  101. if (writerAdvanced)
  102. {
  103. writerAdvanced->Release();
  104. writerAdvanced=0;
  105. }
  106. if (sink)
  107. {
  108. sink->Release();
  109. sink=0;
  110. }
  111. }
  112. int AudioCoderWMA::GetLastError()
  113. {
  114. return error;
  115. }
  116. void AudioCoderWMA::PrepareToFinish()
  117. {
  118. // We don't want to kill the objects here, because there might still be data in the pipeline.
  119. if (writer && begin_writing)
  120. {
  121. begin_writing = false;
  122. // Tell WM that we're done giving it input data.
  123. writer->EndWriting();
  124. // TODO: do we have to wait for this to finish?
  125. }
  126. }
  127. void AudioCoderWMA::OnFinished(const wchar_t *wfname)
  128. {
  129. //
  130. // Okay, here we need to go back and index the file we just wrote so it's seekable.
  131. //
  132. // From: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmform/htm/toconfiguretheindexer.asp
  133. // And: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmform/htm/toindexanasffile.asp
  134. IWMIndexer* pBaseIndexer = NULL;
  135. IWMIndexer2* pMyIndexer = NULL;
  136. // Create an indexer.
  137. WMCreateIndexer(&pBaseIndexer);
  138. // Retrieve an IWMIndexer2 interface pointer for the indexer just created.
  139. pBaseIndexer->QueryInterface(IID_IWMIndexer2, (void **) & pMyIndexer);
  140. // Release the base indexer.
  141. pBaseIndexer->Release();
  142. pBaseIndexer = NULL;
  143. // Configure the indexer to create a timecode-based index.
  144. pMyIndexer->Configure(0, // Stream Number, use all.
  145. WMT_IT_PRESENTATION_TIME, // Indexer type.
  146. NULL, // Index interval, use default.
  147. NULL); // Index type, use default.
  148. // Create an event for asynchronous calls.
  149. HANDLE done = CreateEvent(NULL, TRUE, FALSE, NULL);
  150. // Give that to the status object
  151. CustomIndexStatus status( done );
  152. // Start the indexer.
  153. pMyIndexer->StartIndexing(tempFilename, &status, NULL);
  154. // Wait for the indexer to finish.
  155. WaitForSingleObject(done, INFINITE);
  156. // Release the remaining interface.
  157. pMyIndexer->Release();
  158. pMyIndexer = NULL;
  159. // Cleanup
  160. CloseHandle( done );
  161. DeleteFileW(wfname);
  162. MoveFileW(tempFilename, wfname);
  163. }
  164. int AudioCoderWMA::Encode(int framepos, void *in, int in_avail, int *in_used, void *out, int out_avail)
  165. {
  166. HRESULT hr = S_OK;
  167. int retval = 0; // number of bytes written into "out"
  168. *in_used = 0; // number of bytes read from "in"
  169. if ( !framepos && !in_avail )
  170. {
  171. int x = 0;
  172. x++;
  173. }
  174. assert(writer);
  175. // Hopefully, at the end of the stream, we still get called with "Encode" until we return 0?
  176. if (in_avail)
  177. {
  178. // Allocate an INSSBuffer of the proper size to hold all the data.
  179. INSSBuffer* pSample = NULL;
  180. if (FAILED(writer->AllocateSample(in_avail, &pSample))) return -1;
  181. // Get its internal memory buffer
  182. DWORD newBufferLength;
  183. pSample->GetLength(&newBufferLength);
  184. assert(newBufferLength == in_avail);
  185. BYTE *pdwBuffer = NULL;
  186. if (FAILED(pSample->GetBuffer(&pdwBuffer))) return -1;
  187. memcpy(pdwBuffer, in, in_avail); // Send all the available bytes in the input buffer into the IWMWriter,
  188. pSample->SetLength(in_avail); // Tell the buffer object how much we used
  189. QWORD timeunits = (QWORD)( (double)input_bytecount * timeunits_per_byte ); // Compute the current timecode
  190. // And stuff it into the writer
  191. hr = writer->WriteSample(0, timeunits, 0, pSample);
  192. if (FAILED(hr))
  193. {
  194. }
  195. else
  196. {
  197. // Increment the bytecount to be able to calculate the next timecode
  198. input_bytecount += in_avail;
  199. // And tell the host we used up all the available input data.
  200. *in_used = in_avail;
  201. }
  202. // Release immediately
  203. pSample->Release();
  204. }
  205. WM_WRITER_STATISTICS stats;
  206. writerAdvanced->GetStatistics(0, &stats);
  207. retval = (int)(stats.qwByteCount - lastByteCount);
  208. retval = min(retval, out_avail);
  209. lastByteCount+=retval;
  210. memset(out, 0, retval); // so we don't write random memory to disk
  211. return retval;
  212. }
  213. HRESULT AudioCoderWMA::SelectAndLoadResampler(int numchannels, int samplerate, int bitspersamp)
  214. {
  215. DWORD inCount = 0;
  216. BOOL success = false;
  217. //wsprintf(junk,"IN Chan=%d, SRate=%d, BPS=%d", numchannels, samplerate,bitspersamp);
  218. //MessageBox(NULL, junk, "INPUT FMT", MB_OK);
  219. // First get the number of input streams
  220. HRESULT hr = writer->GetInputCount(&inCount);
  221. if(!FAILED(hr)){
  222. //wsprintf(junk, "Input Count = %d", inCount);
  223. //MessageBox(NULL, junk, "DEBUG", MB_OK);
  224. // Now get the number of input formats we can resample for
  225. DWORD fmtCount = 0;
  226. hr = writer->GetInputFormatCount(0, &fmtCount);
  227. if(!FAILED(hr)){
  228. //wsprintf(junk, "Format Count = %d", fmtCount);
  229. //MessageBox(NULL, junk, "DEBUG", MB_OK);
  230. // Now cycle through and find the one that matches our input fmt
  231. for(size_t i = 0;i < fmtCount;i++){
  232. IWMInputMediaProps* pProps = NULL;
  233. hr = writer->GetInputFormat(0, (DWORD)i, &pProps);
  234. if(!FAILED(hr)){
  235. DWORD cbSize = 0;
  236. // Get the size of the media type structure.
  237. pProps->GetMediaType(NULL, &cbSize);
  238. // Allocate memory for the media type structure.
  239. WM_MEDIA_TYPE* pType = (WM_MEDIA_TYPE*) new BYTE[cbSize];
  240. if(pType != NULL){
  241. WAVEFORMATEX* pwave = NULL;
  242. // Get the media type structure.
  243. hr = pProps->GetMediaType(pType, &cbSize);
  244. // Check that the format data is present.
  245. if (pType->cbFormat >= sizeof(WAVEFORMATEX)){
  246. pwave = (WAVEFORMATEX*)pType->pbFormat;
  247. //wsprintf(junk, "Cnannels = %d, SPerSec = %d, AvgBPS = %d, BPS = %d BALIGN = %d",
  248. // pwave->nChannels,
  249. // pwave->nSamplesPerSec,
  250. // pwave->nAvgBytesPerSec,
  251. // pwave->wBitsPerSample,
  252. // pwave->nBlockAlign);
  253. //MessageBox(NULL, junk, "DEBUG", MB_OK);
  254. }
  255. else{
  256. break;
  257. }
  258. // Try to match the channels/samplerate/and bits/samp
  259. if((pwave->nChannels == numchannels) && (pwave->nSamplesPerSec == samplerate) && (pwave->wBitsPerSample == bitspersamp)){
  260. writer->SetInputProps(0, pProps);
  261. success = true;
  262. break;
  263. }
  264. }
  265. }
  266. }
  267. }
  268. }
  269. if(success != 1){
  270. wchar_t junk[FILETITLE_SIZE] = {0};
  271. wsprintfW(junk,WASABI_API_LNGSTRINGW(IDS_CANNOT_FIND_INPUT_FORMATTER),
  272. numchannels, samplerate,bitspersamp);
  273. MessageBoxW(NULL, junk, WASABI_API_LNGSTRINGW(IDS_WARNING), MB_OK);
  274. }
  275. if (success)
  276. return S_OK;
  277. else if (FAILED(hr)) // if we have an error code, return it
  278. return hr;
  279. else
  280. return E_FAIL;
  281. }
  282. HRESULT AudioCoderWMA::CreateAndConfigureWriter(WORD numchannels, WORD samplerate, WORD bitspersamp, char *configfile)
  283. {
  284. // First, create the writer.
  285. HRESULT hr = WMCreateWriter( NULL, &writer );
  286. if ( !FAILED(hr) )
  287. {
  288. // Create and Configure a stream profile with the given wave limits.
  289. WAVEFORMATEX WaveLimits =
  290. {
  291. WAVE_FORMAT_PCM,
  292. numchannels,
  293. samplerate,
  294. samplerate * numchannels * bitspersamp / (DWORD)8,
  295. numchannels * bitspersamp / (DWORD)8,
  296. bitspersamp,
  297. 0
  298. };
  299. IWMProfile* pProfile = NULL;
  300. hr = CreateAndConfigureProfile(&WaveLimits, &pProfile, configfile);
  301. if ( !FAILED(hr) )
  302. {
  303. // Set the profile into the writer
  304. hr = writer->SetProfile( pProfile );
  305. if ( !FAILED(hr) )
  306. {
  307. // Go get the input resampler and load it to the profile
  308. hr = SelectAndLoadResampler(numchannels, samplerate, bitspersamp);
  309. if (!FAILED(hr))
  310. {
  311. wchar_t tempPath[MAX_PATH] = {0};
  312. GetTempPathW(MAX_PATH,tempPath);
  313. GetTempFileNameW(tempPath, L"wma", 0, tempFilename);
  314. // Make the custom data sink object
  315. WMCreateWriterFileSink(&sink);
  316. //sink = new CustomWMWriterSink;
  317. if ( sink )
  318. {
  319. sink->Open(tempFilename);
  320. HRESULT hr;
  321. // From MSDN: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmform/htm/addingsinkstothewriter.asp
  322. IWMWriterSink* pSinkBase = NULL;
  323. hr = writer->QueryInterface( IID_IWMWriterAdvanced, (void **) & writerAdvanced );
  324. if ( !FAILED(hr) )
  325. {
  326. hr = sink->QueryInterface( IID_IWMWriterSink, (void**) & pSinkBase );
  327. if ( !FAILED(hr) )
  328. {
  329. // Stuff the custom data sink into the writer.
  330. hr = writerAdvanced->AddSink(pSinkBase);
  331. if ( !FAILED(hr) )
  332. {
  333. // And let the writer initialize itself for output.
  334. hr = writer->BeginWriting();
  335. if ( !FAILED(hr) )
  336. {
  337. begin_writing = true;
  338. }
  339. }
  340. else
  341. {
  342. error = WMA_CANT_ADD_SINK;
  343. }
  344. }
  345. else
  346. {
  347. error = WMA_CANT_QUERY_SINK_INTERFACE;
  348. }
  349. }
  350. else
  351. {
  352. error = WMA_CANT_QUERY_WRITER_INTERFACE;
  353. }
  354. }
  355. else
  356. {
  357. error = WMA_CANT_MAKE_CUSTOM_SINK;
  358. }
  359. }
  360. }
  361. }
  362. }
  363. return hr;
  364. }
  365. HRESULT AudioCoderWMA::CreateAndConfigureProfile(WAVEFORMATEX* pWaveLimits, IWMProfile** ppProfile, char *configfile)
  366. {
  367. IWMProfileManager* pProfileMgr = NULL;
  368. // Instantiate a profile manager object.
  369. HRESULT hr = WMCreateProfileManager(&pProfileMgr);
  370. if ( !FAILED(hr) )
  371. {
  372. /* SAVE
  373. // Create the empty profile.
  374. //hr = pProfileMgr->CreateEmptyProfile(WMT_VER_9_0, ppProfile);
  375. if ( !FAILED(hr) ){
  376. IWMCodecInfo3 *codecInfo = NULL;
  377. hr = pProfileMgr->QueryInterface(&codecInfo);
  378. if(!FAILED(hr)){
  379. // Find the proper IWMStreamConfig that matches the WAVEFORMATEX data.
  380. IWMStreamConfig* pStreamConfig = NULL;
  381. //hr = FindAudioFormat(WMMEDIASUBTYPE_WMAudioV2, pProfileMgr, pWaveLimits, config_bitrate * 1000, FALSE, &pStreamConfig);
  382. hr = codecInfo->GetCodecFormat(WMMEDIATYPE_Audio, config_encOffset, config_formatOffset, &pStreamConfig);
  383. if ( !FAILED(hr) ){
  384. // Config the stream.
  385. // hr = pStreamConfig->SetBitrate( config_bitrate );
  386. hr = pStreamConfig->SetConnectionName( L"enc_wma" );
  387. hr = pStreamConfig->SetStreamName( L"enc_wma" );
  388. hr = pStreamConfig->SetStreamNumber( 1 );
  389. // Stuff it into the profile
  390. hr = (*ppProfile)->AddStream( pStreamConfig );
  391. }
  392. }
  393. }
  394. */
  395. if ( !FAILED(hr) ){
  396. // Load the .prx file into the writer
  397. if(configfile == NULL){
  398. hr = E_FAIL;
  399. }
  400. else{
  401. wchar_t cstring[4000] = {0};
  402. GetPrivateProfileStructW(L"audio_wma", L"profile", cstring, sizeof(cstring)/sizeof(*cstring), AutoWide(configfile));
  403. hr = pProfileMgr->LoadProfileByData(cstring, ppProfile);
  404. if(hr != S_OK){
  405. hr = E_FAIL;
  406. }
  407. }
  408. }
  409. pProfileMgr->Release();
  410. }
  411. return hr;
  412. }