summaryrefslogtreecommitdiff
path: root/engine/audio/private/snd_wave_mixer_xma.cpp
blob: 13f183c03ff1803fa7cf20b9c7c27b43dc5c5aa0 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: XMA Decoding
//
//=====================================================================================//

#include "audio_pch.h"
#include "tier1/mempool.h"
#include "circularbuffer.h"
#include "tier1/utllinkedlist.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

//#define DEBUG_XMA

// Failed attempt to allow mixer to request data that is immediately discarded
// to support < 0 delay samples
//#define ALLOW_SKIP_SAMPLES

// XMA is supposed to decode at an ideal max of 512 mono samples every 4msec.
// XMA can only peel a max of 1984 stereo samples per poll request (if available).
// Max is not achievable and degrades based on quality settings, stereo, etc, but using these numbers for for calcs.
// 1984 stereo samples should be decoded by xma in 31 msec.
// 1984 stereo samples at 44.1Khz dictates a request every 45 msec.
// GetOutputData() must be clocked faster than 45 msec or samples will not be available.
// However, the XMA decoder must be serviced much faster. It was designed for 5 msec.
// 15 msec seems to be fast enough for XMA to decode enough to keep the smaller buffer sizes satisfied, and have slop for +/- 5 msec swings.

// Need at least this amount of decoded pcm samples before mixing can commence.
// This needs to be able to cover the initial mix request, while a new decode cycle is in flight.
#define MIN_READYTOMIX	( ( 2 * XMA_POLL_RATE ) * 0.001f )

// number of samples that xma decodes
// must be 128 aligned for mono (1984 is hw max for stereo)
#define XMA_MONO_OUTPUT_BUFFER_SAMPLES		2048
#define XMA_STEREO_OUTPUT_BUFFER_SAMPLES	1920

// for decoder input
// xma blocks are fetched from the datacache into one of these hw buffers for decoding
// must be in quantum units of XMA_BLOCK_SIZE
#define XMA_INPUT_BUFFER_SIZE		( 8 * XMA_BLOCK_SIZE )

// circular staging buffer to drain xma decoder and stage until mixer requests
// must be large enough to hold the slowest expected mixing frame worth of samples
#define PCM_STAGING_BUFFER_TIME		200

// xma physical heap, supplies xma input buffers for hw decoder
// each potential channel must be able to peel 2 buffers for driving xma decoder
#define XMA_PHYSICAL_HEAP_SIZE		( 2 * MAX_CHANNELS * XMA_INPUT_BUFFER_SIZE )

// in millseconds
#define MIX_IO_DATA_TIMEOUT			2000	// async i/o from dvd could be very late
#define MIX_DECODER_TIMEOUT			3000	// decoder might be very busy
#define MIX_DECODER_POLLING_LATENCY	5		// not faster than 5ms, or decoder will sputter

// diagnostic errors
#define ERROR_IO_DATA_TIMEOUT		-1	// async i/o taking too long to deliver xma blocks
#define ERROR_IO_TRUNCATED_BLOCK	-2	// async i/o failed to deliver complete blocks
#define ERROR_IO_NO_XMA_DATA		-3	// async i/o failed to deliver any block
#define ERROR_DECODER_TIMEOUT		-4	// decoder taking too long to decode xma blocks
#define ERROR_OUT_OF_MEMORY			-5	// not enough physical memory for xma blocks
#define ERROR_XMA_PARSE				-6	// decoder barfed on xma blocks
#define ERROR_XMA_CANTLOCK			-7	// hw not acting as expected
#define ERROR_XMA_CANTSUBMIT		-8	// hw not acting as expected
#define ERROR_XMA_CANTRESUME		-9	// hw not acting as expected
#define ERROR_XMA_NO_PCM_DATA		-10	// no xma decoded pcm data ready
#define ERROR_NULL_BUFFER			-11	// logic flaw, expected buffer is null 

const char *g_XMAErrorStrings[] =
{
	"Unknown Error Code",
	"Async I/O Data Timeout",		// ERROR_IO_DATA_TIMEOUT
	"Async I/O Truncated Block",	// ERROR_IO_TRUNCATED_BLOCK
	"Async I/O Data Not Ready",		// ERROR_IO_NO_XMA_DATA
	"Decoder Timeout",				// ERROR_DECODER_TIMEOUT
	"Out Of Memory",				// ERROR_OUT_OF_MEMORY
	"XMA Parse",					// ERROR_XMA_PARSE
	"XMA Cannot Lock",				// ERROR_XMA_CANTLOCK
	"XMA Cannot Submit",			// ERROR_XMA_CANTSUBMIT
	"XMA Cannot Resume",			// ERROR_XMA_CANTRESUME
	"XMA No PCM Data Ready",		// ERROR_XMA_NO_PCM_DATA
	"NULL Buffer",					// ERROR_NULL_BUFFER
};

class CXMAAllocator
{
public:
	static void *Alloc( int bytes )
	{
		MEM_ALLOC_CREDIT();

		return XMemAlloc( bytes, 
			MAKE_XALLOC_ATTRIBUTES( 
				0, 
				false, 
				TRUE, 
				FALSE, 
				eXALLOCAllocatorId_XAUDIO,
				XALLOC_PHYSICAL_ALIGNMENT_4K, 
				XALLOC_MEMPROTECT_WRITECOMBINE_LARGE_PAGES, 
				FALSE,
				XALLOC_MEMTYPE_PHYSICAL ) );
	}

	static void Free( void *p )
	{
		XMemFree( p, 
			MAKE_XALLOC_ATTRIBUTES( 
				0, 
				false, 
				TRUE, 
				FALSE, 
				eXALLOCAllocatorId_XAUDIO,
				XALLOC_PHYSICAL_ALIGNMENT_4K, 
				XALLOC_MEMPROTECT_WRITECOMBINE_LARGE_PAGES, 
				FALSE,
				XALLOC_MEMTYPE_PHYSICAL ) );
	}
};

// for XMA decoding, fixed size allocations aligned to 4K from a single physical heap
CAlignedMemPool< XMA_INPUT_BUFFER_SIZE, 4096, XMA_PHYSICAL_HEAP_SIZE, CXMAAllocator > g_XMAMemoryPool;

ConVar snd_xma_spew_warnings( "snd_xma_spew_warnings", "0" );
ConVar snd_xma_spew_startup( "snd_xma_spew_startup", "0" );
ConVar snd_xma_spew_mixers( "snd_xma_spew_mixers", "0" );
ConVar snd_xma_spew_decode( "snd_xma_spew_decode", "0" );
ConVar snd_xma_spew_drain( "snd_xma_spew_drain", "0" );
#ifdef DEBUG_XMA
ConVar snd_xma_record( "snd_xma_record", "0" );
ConVar snd_xma_spew_errors( "snd_xma_spew_errors", "0" );
#endif

//-----------------------------------------------------------------------------
// Purpose: Mixer for ADPCM encoded audio
//-----------------------------------------------------------------------------
class CAudioMixerWaveXMA : public CAudioMixerWave
{
public:
	typedef CAudioMixerWave BaseClass;

	CAudioMixerWaveXMA( IWaveData *data, int initialStreamPosition );
	~CAudioMixerWaveXMA( void );
	
	virtual void			Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress );

	virtual int				GetOutputData( void **pData, int sampleCount, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] );

	virtual void			SetSampleStart( int newPosition );
	virtual int				GetPositionForSave();
	virtual void			SetPositionFromSaved( int savedPosition );

	virtual int				GetMixSampleSize() { return CalcSampleSize( 16, m_NumChannels ); }

	virtual bool			IsReadyToMix();
	virtual bool			ShouldContinueMixing();

private:
	int						GetXMABlocksAndSubmitToDecoder( bool bDecoderLocked );
	int						UpdatePositionForLooping( int *pNumRequestedSamples );
	int						ServiceXMADecoder( bool bForceUpdate );
	int						GetPCMSamples( int numRequested, char *pData );

	XMAPLAYBACK				*m_pXMAPlayback;

	// input buffers, encoded xma
	byte					*m_pXMABuffers[2];
	int						m_XMABufferIndex;

	// output buffer, decoded pcm samples, a staging circular buffer, waiting for mixer requests
	// due to staging nature, contains decoded samples from multiple input buffers
	CCircularBuffer			*m_pPCMSamples;

	int						m_SampleRate;
	int						m_NumChannels;
	// maximum possible decoded samples
	int						m_SampleCount;

	// decoded sample position
	int						m_SamplePosition;
	// current data marker
	int						m_LastDataOffset;
	int						m_DataOffset;
	// total bytes of data
	int						m_TotalBytes;

#if defined( ALLOW_SKIP_SAMPLES )
	// number of samples to throwaway
	int						m_SkipSamples;
#endif

	// timers
	unsigned int			m_StartTime;
	unsigned int			m_LastDrainTime;
	unsigned int			m_LastPollTime;

	int						m_hMixerList;
	int						m_Error;

	unsigned int			m_bStartedMixing : 1;
	unsigned int			m_bFinished : 1;
	unsigned int			m_bLooped : 1;
};

CUtlFixedLinkedList< CAudioMixerWaveXMA * >	g_XMAMixerList;

CON_COMMAND( snd_xma_info, "Spew XMA Info" )
{
	Msg( "XMA Memory:\n" );
	Msg( " Blocks Allocated: %d\n", g_XMAMemoryPool.NumAllocated() );
	Msg( " Blocks Free:      %d\n", g_XMAMemoryPool.NumFree() );
	Msg( " Total Bytes:      %d\n", g_XMAMemoryPool.BytesTotal() );
	Msg( "Active XMA Mixers: %d\n", g_XMAMixerList.Count() );
	for ( int hMixer = g_XMAMixerList.Head(); hMixer != g_XMAMixerList.InvalidIndex(); hMixer = g_XMAMixerList.Next( hMixer ) )
	{
		CAudioMixerWaveXMA *pXMAMixer = g_XMAMixerList[hMixer];
		Msg( "  rate:%5d ch:%1d '%s'\n", pXMAMixer->GetSource()->SampleRate(), pXMAMixer->GetSource()->IsStereoWav() ? 2 : 1, pXMAMixer->GetSource()->GetFileName() );
	}
}

CAudioMixerWaveXMA::CAudioMixerWaveXMA( IWaveData *data, int initialStreamPosition ) : CAudioMixerWave( data ) 
{
	Assert( dynamic_cast<CAudioSourceWave *>(&m_pData->Source()) != NULL );

	m_Error = 0;

	m_NumChannels = m_pData->Source().IsStereoWav() ? 2 : 1;
	m_SampleRate = m_pData->Source().SampleRate();
	m_bLooped = m_pData->Source().IsLooped();
	m_SampleCount = m_pData->Source().SampleCount();
	m_TotalBytes = m_pData->Source().DataSize();

#if defined( ALLOW_SKIP_SAMPLES )
	m_SkipSamples = 0;
#endif

	m_LastDataOffset = initialStreamPosition;
	m_DataOffset = initialStreamPosition;
	m_SamplePosition = 0;
	if ( initialStreamPosition )
	{
		m_SamplePosition = m_pData->Source().StreamToSamplePosition( initialStreamPosition );

		CAudioMixerWave::m_sample_loaded_index = m_SamplePosition;
		CAudioMixerWave::m_sample_max_loaded = m_SamplePosition + 1;
	}

	m_bStartedMixing = false;
	m_bFinished = false;

	m_StartTime = 0;
	m_LastPollTime = 0;
	m_LastDrainTime = 0;

	m_pXMAPlayback = NULL;
	m_pPCMSamples = NULL;

	m_pXMABuffers[0] = NULL;
	m_pXMABuffers[1] = NULL;
	m_XMABufferIndex = 0;

	m_hMixerList = g_XMAMixerList.AddToTail( this );

#ifdef DEBUG_XMA
	if ( snd_xma_record.GetBool() )
	{
		WaveCreateTmpFile( "debug.wav", m_SampleRate, 16, m_NumChannels );
	}
#endif

	if ( snd_xma_spew_mixers.GetBool() )
	{
		Msg( "XMA: 0x%8.8x (%2d), Mixer Alloc, '%s'\n", (unsigned int)this, g_XMAMixerList.Count(), m_pData->Source().GetFileName() );
	}
}

CAudioMixerWaveXMA::~CAudioMixerWaveXMA( void )
{
	if ( m_pXMAPlayback )
	{
		XMAPlaybackDestroy( m_pXMAPlayback );

		g_XMAMemoryPool.Free( m_pXMABuffers[0] );
		if ( m_pXMABuffers[1] )
		{
			g_XMAMemoryPool.Free( m_pXMABuffers[1] );
		}
	}

	if ( m_pPCMSamples )
	{
		FreeCircularBuffer( m_pPCMSamples );
	}

	g_XMAMixerList.Remove( m_hMixerList );

	if ( snd_xma_spew_mixers.GetBool() )
	{
		Msg( "XMA: 0x%8.8x (%2d), Mixer Freed, '%s'\n", (unsigned int)this, g_XMAMixerList.Count(), m_pData->Source().GetFileName() );
	}
}

void CAudioMixerWaveXMA::Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress )
{
	if ( m_NumChannels == 1 )
	{
		pDevice->Mix16Mono( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
	}
	else
	{
		pDevice->Mix16Stereo( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress );
	}
}

//-----------------------------------------------------------------------------
// Looping is achieved in two passes to provide a circular view of the linear data.
// Pass1: Clamps a sample request to the end of data.
// Pass2: Snaps to the loop start, and returns the number of samples to discard, could be 0,
// up to the expected loop sample position.
// Returns the number of samples to discard, or 0.
//-----------------------------------------------------------------------------
int CAudioMixerWaveXMA::UpdatePositionForLooping( int *pNumRequestedSamples )
{
	if ( !m_bLooped )
	{
		// not looping, no fixups
		return 0;
	}

	int numLeadingSamples;
	int numTrailingSamples;
	CAudioSourceWave &source = reinterpret_cast<CAudioSourceWave &>(m_pData->Source());
	int loopSampleStart = source.GetLoopingInfo( NULL, &numLeadingSamples, &numTrailingSamples );

	int numRemainingSamples = ( m_SampleCount - numTrailingSamples ) - m_SamplePosition;

	// possibly straddling the end of data (and thus about to loop)
	// want to split the straddle into two regions, due to loops possibly requiring a trailer and leader of discarded samples
	if ( numRemainingSamples > 0 )
	{
		// first region, all the remaining samples, clamped until end of desired data
		*pNumRequestedSamples = min( *pNumRequestedSamples, numRemainingSamples );

		// nothing to discard
		return 0;
	}
	else if ( numRemainingSamples == 0 )
	{
		// at exact end of desired data, snap the sample position back
		// the position will be correct AFTER discarding decoded trailing and leading samples
		m_SamplePosition = loopSampleStart;

		// clamp the request
		numRemainingSamples = ( m_SampleCount - numTrailingSamples ) - m_SamplePosition;
		*pNumRequestedSamples = min( *pNumRequestedSamples, numRemainingSamples );

		// flush these samples so the sample position is the real loop sample starting position
		return numTrailingSamples + numLeadingSamples;
	}

	return 0;
}

//-----------------------------------------------------------------------------
// Get and submit XMA block(s). The decoder must stay blocks ahead of mixer
// so the decoded samples are available for peeling.
// An XMA file is thus treated as a series of fixed size large buffers (multiple xma blocks),
// which are streamed in sequentially. The XMA buffers may be delayed from the 
// audio data cache due to async i/o latency.
// Returns < 0 if error, 0 if no decode started, 1 if decode submitted.
//-----------------------------------------------------------------------------
int CAudioMixerWaveXMA::GetXMABlocksAndSubmitToDecoder( bool bDecoderIsLocked )
{
	int status = 0;

	if ( m_DataOffset >= m_TotalBytes )
	{
		if ( !m_bLooped )
		{
			// end of file, no more data to decode
			// not an error, because decoder finishes long before samples drained
			return 0;
		}

		// start from beginning of loop
		CAudioSourceWave &source = reinterpret_cast<CAudioSourceWave &>(m_pData->Source());
		source.GetLoopingInfo( &m_DataOffset, NULL, NULL ); 
		m_DataOffset *= XMA_BLOCK_SIZE;
	}

	HRESULT hr;
	bool bLocked = false;
	if ( !bDecoderIsLocked )
	{
		// decoder must be locked before any access
		hr = XMAPlaybackRequestModifyLock( m_pXMAPlayback );
		if ( FAILED( hr ) )
		{
			status = ERROR_XMA_CANTLOCK;
			goto cleanUp;
		}

		hr = XMAPlaybackWaitUntilModifyLockObtained( m_pXMAPlayback );
		if ( FAILED( hr ) )
		{
			status = ERROR_XMA_CANTLOCK;
			goto cleanUp;
		}
		bLocked = true;
	}

	// the input buffer can never be less than a single xma block (buffer size is multiple blocks)
	int bufferSize = min( m_TotalBytes - m_DataOffset, XMA_INPUT_BUFFER_SIZE );
	if ( !bufferSize )
	{
		// EOF
		goto cleanUp;
	}
	Assert( !( bufferSize % XMA_BLOCK_SIZE ) );

	byte *pXMABuffer = m_pXMABuffers[m_XMABufferIndex & 0x01];
	if ( !pXMABuffer )
	{
		// shouldn't happen, buffer should have been allocated
		Assert( 0 );
		status = ERROR_NULL_BUFFER;
		goto cleanUp;
	}

	if ( !XMAPlaybackQueryReadyForMoreData( m_pXMAPlayback, 0 ) || XMAPlaybackQueryInputDataPending( m_pXMAPlayback, 0, pXMABuffer ) )
	{
		// decoder too saturated for more data or 
		// decoder still decoding from input hw buffer
		goto cleanUp;
	}

	// get xma block(s)
	// pump to get all of requested data
	char *pData;
	int total = 0;
	while ( total < bufferSize )
	{
		int available = m_pData->ReadSourceData( (void **)&pData, m_DataOffset, bufferSize - total, NULL );
		if ( !available )
			break;

		// aggregate into hw buffer
		V_memcpy( pXMABuffer + total, pData, available );

		m_DataOffset += available;
		total += available;
	}	
	if ( total != bufferSize )
	{
		if ( !total )
		{
			// failed to get any data, could be async latency or file error
			status = ERROR_IO_NO_XMA_DATA;
		}
		else
		{
			// failed to get complete xma block(s)
			status = ERROR_IO_TRUNCATED_BLOCK;
		}
		goto cleanUp;
	}

	// track the currently submitted offset
	// this is used as a cheap method for save/restore because an XMA seek table is not available
	m_LastDataOffset = m_DataOffset - total;

	// start decoding the block(s) in the hw buffer
	hr = XMAPlaybackSubmitData( m_pXMAPlayback, 0, pXMABuffer, bufferSize );
	if ( FAILED( hr ) )
	{
		// failed to start decoder
		status = ERROR_XMA_CANTSUBMIT;
		goto cleanUp;
	}

	// decode submitted
	status = 1;

	// advance to next buffer
	m_XMABufferIndex++;

	if ( snd_xma_spew_decode.GetBool() )
	{
		Msg( "XMA: 0x%8.8x, XMABuffer: 0x%8.8x, BufferSize: %d, NextDataOffset: %d, %s\n", (unsigned int)this, pXMABuffer, bufferSize, m_DataOffset, m_pData->Source().GetFileName() );
	}

cleanUp:
	if ( bLocked )
	{
		// release the lock and let the decoder run
		hr = XMAPlaybackResumePlayback( m_pXMAPlayback );
 		if ( FAILED( hr ) )
		{
			status = ERROR_XMA_CANTRESUME;
		}
	}
	
	return status;
}

//-----------------------------------------------------------------------------
// Drain the XMA Decoder into the staging circular buffer of PCM for mixer.
// Fetch new XMA samples for the decoder.
//-----------------------------------------------------------------------------
int CAudioMixerWaveXMA::ServiceXMADecoder( bool bForceUpdate )
{
	// allow decoder to work without being polled (lock causes a decoding stall)
	// decoder must be allowed minimum operating latency
	// the buffers are sized to compensate for the operating latency
	if ( !bForceUpdate && ( Plat_MSTime() - m_LastPollTime <= MIX_DECODER_POLLING_LATENCY ) )
	{
		return 0;
	}
	m_LastPollTime = Plat_MSTime();

	// lock and pause the decoder to gain access
	HRESULT hr = XMAPlaybackRequestModifyLock( m_pXMAPlayback );
	if ( FAILED( hr ) )
	{
		m_Error = ERROR_XMA_CANTLOCK;
		return -1;
	}

	hr = XMAPlaybackWaitUntilModifyLockObtained( m_pXMAPlayback );
	if ( FAILED( hr ) )
	{
		m_Error = ERROR_XMA_CANTLOCK;
		return -1;
	}

	DWORD dwParseError = XMAPlaybackGetParseError( m_pXMAPlayback, 0 );
	if ( dwParseError )
	{
		if ( snd_xma_spew_warnings.GetBool() )
		{
			Warning( "XMA: 0x%8.8x, Decoder Error, Parse: %d, '%s'\n", (unsigned int)this, dwParseError, m_pData->Source().GetFileName() );
		}
		m_Error = ERROR_XMA_PARSE;
		return -1;
	}

#ifdef DEBUG_XMA
	if ( snd_xma_spew_errors.GetBool() )
	{
		DWORD dwError = XMAPlaybackGetErrorBits( m_pXMAPlayback, 0 );
		if ( dwError )
		{
			Warning( "XMA: 0x%8.8x, Playback Error: %d, '%s'\n", (unsigned int)this, dwError, m_pData->Source().GetFileName() );
		}
	}
#endif

	int numNewSamples = XMAPlaybackQueryAvailableData( m_pXMAPlayback, 0 );
	int numMaxSamples = m_pPCMSamples->GetWriteAvailable()/( m_NumChannels*sizeof( short ) );
	int numSamples = min( numNewSamples, numMaxSamples );
	while ( numSamples )
	{
		char *pPCMData = NULL;
		int numSamplesDecoded = XMAPlaybackConsumeDecodedData( m_pXMAPlayback, 0, numSamples, (void**)&pPCMData );

		// put into staging buffer, ready for mixer to drain
		m_pPCMSamples->Write( pPCMData, numSamplesDecoded*m_NumChannels*sizeof( short ) );
		
		numSamples -= numSamplesDecoded;
		numNewSamples -= numSamplesDecoded;
	}

	// queue up more blocks for the decoder
	// the decoder will always finish ahead of the mixer, submit nothing, and the mixer will still be draining
	int decodeStatus = GetXMABlocksAndSubmitToDecoder( true );
	if ( decodeStatus < 0 )
	{
		m_Error = decodeStatus;
		return -1;
	}

	m_bFinished = ( numNewSamples == 0 ) && ( decodeStatus == 0 ) && XMAPlaybackIsIdle( m_pXMAPlayback, 0 );

	// decoder was paused for access, let the decoder run
	hr = XMAPlaybackResumePlayback( m_pXMAPlayback );
 	if ( FAILED( hr ) )
	{
		m_Error = ERROR_XMA_CANTRESUME;
		return -1;
	}

	return 1;
}

//-----------------------------------------------------------------------------
// Drain the PCM staging buffer.
// Copy samples (numSamplesToCopy && pData). Return actual copied.
// Flush Samples (numSamplesToCopy && !pData). Return actual flushed.
// Query available number of samples (!numSamplesToCopy && !pData). Returns available.
//-----------------------------------------------------------------------------
int CAudioMixerWaveXMA::GetPCMSamples( int numSamplesToCopy, char *pData )
{
	int numReadySamples = m_pPCMSamples->GetReadAvailable()/( m_NumChannels*sizeof( short ) );

	// peel sequential samples from the stream's staging buffer
	int numCopiedSamples = 0;
	int numRequestedSamples = min( numSamplesToCopy, numReadySamples );
	if ( numRequestedSamples )
	{
		if ( pData )
		{
			// copy to caller
			m_pPCMSamples->Read( pData, numRequestedSamples*m_NumChannels*sizeof( short ) );
			pData += numRequestedSamples*m_NumChannels*sizeof( short );
		}
		else
		{
			// flush
			m_pPCMSamples->Advance( numRequestedSamples*m_NumChannels*sizeof( short ) );
		}

		numCopiedSamples += numRequestedSamples;
	}

	if ( snd_xma_spew_drain.GetBool() )
	{
		char *pOperation = ( numSamplesToCopy && !pData ) ? "Flushed" : "Copied";
		Msg( "XMA: 0x%8.8x, SamplePosition: %d, Ready: %d, Requested: %d, %s: %d, Elapsed: %d ms '%s'\n", 
			(unsigned int)this, m_SamplePosition, numReadySamples, numSamplesToCopy, pOperation, numCopiedSamples, Plat_MSTime() - m_LastDrainTime, m_pData->Source().GetFileName() );
	}
	m_LastDrainTime = Plat_MSTime();

	if ( numSamplesToCopy )
	{
		// could be actual flushed or actual copied
		return numCopiedSamples;
	}
	
	if ( !pData )
	{
		// satify query for available
		return numReadySamples;
	}

	return 0;
}

//-----------------------------------------------------------------------------
// Stall mixing until initial buffer of decoded samples are available.
//-----------------------------------------------------------------------------
bool CAudioMixerWaveXMA::IsReadyToMix()
{
	// XMA mixing cannot be driven from the main thread
	Assert( ThreadInMainThread() == false );

	if ( m_Error )
	{
		// error has been set
		// let mixer try to get unavailable samples, which casues the real abort
		return true;
	}

	if ( m_bStartedMixing )
	{
		// decoding process has started
		return true;
	}

	if ( !m_pXMAPlayback )
	{
		// first time, finish setup
		int numBuffers;
		if ( m_bLooped || m_TotalBytes > XMA_INPUT_BUFFER_SIZE )
		{
			// data will cascade through multiple buffers
			numBuffers = 2;
		}
		else
		{
			// data can fit into a single buffer
			numBuffers = 1;
		}

		// xma data must be decoded from a hw friendly buffer
		// pool should have buffers available
		if ( g_XMAMemoryPool.BytesAllocated() != numBuffers * g_XMAMemoryPool.ChunkSize() )
		{
			for ( int i = 0; i < numBuffers; i++ )
			{
				m_pXMABuffers[i] = (byte*)g_XMAMemoryPool.Alloc();
			}

			XMA_PLAYBACK_INIT xmaPlaybackInit = { 0 };
			xmaPlaybackInit.sampleRate = m_SampleRate;
			xmaPlaybackInit.channelCount = m_NumChannels;
			xmaPlaybackInit.subframesToDecode = 4;
			xmaPlaybackInit.outputBufferSizeInSamples = ( m_NumChannels == 2 ) ? XMA_STEREO_OUTPUT_BUFFER_SAMPLES : XMA_MONO_OUTPUT_BUFFER_SAMPLES;
			XMAPlaybackCreate( 1, &xmaPlaybackInit, 0, &m_pXMAPlayback );

			int stagingSize = PCM_STAGING_BUFFER_TIME * m_SampleRate * m_NumChannels * sizeof( short ) * 0.001f;
			m_pPCMSamples = AllocateCircularBuffer( AlignValue( stagingSize, 4 ) );
		}
		else
		{
			// too many sounds playing, no xma buffers free
			m_Error = ERROR_OUT_OF_MEMORY;
			return true;
		}

		m_StartTime = Plat_MSTime();
	}

	// waiting for samples
	// allow decoder to work without being polled (lock causes a decoding stall)
	if ( Plat_MSTime() - m_LastPollTime <= MIX_DECODER_POLLING_LATENCY )
	{
		return false;
	}
	m_LastPollTime = Plat_MSTime();

	// must have buffers in flight before mixing can begin
	if ( m_DataOffset == m_LastDataOffset )
	{
		// keep trying to get data, async i/o has some allowable latency
		int decodeStatus = GetXMABlocksAndSubmitToDecoder( false );
		if ( decodeStatus < 0 && decodeStatus != ERROR_IO_NO_XMA_DATA )
		{
			m_Error = decodeStatus;
			return true;
		}
		else if ( !decodeStatus || decodeStatus == ERROR_IO_NO_XMA_DATA )
		{
			// async streaming latency could be to blame, check watchdog
			if ( Plat_MSTime() - m_StartTime >= MIX_IO_DATA_TIMEOUT )
			{
				m_Error = ERROR_IO_DATA_TIMEOUT;
			}
			return false;
		}
	}

	// get the available samples ready for immediate mixing
	if ( ServiceXMADecoder( true ) < 0 )
	{
		return true;
	}

	// can't mix until we have a minimum threshold of data or the decoder is finished
	int minSamplesNeeded = m_bFinished ? 0 : MIN_READYTOMIX * m_SampleRate;
#if defined( ALLOW_SKIP_SAMPLES )
	minSamplesNeeded += m_bFinished ? 0 : m_SkipSamples;
#endif

	int numReadySamples = GetPCMSamples( 0, NULL );
	if ( numReadySamples > minSamplesNeeded )
	{
		// decoder has samples ready for draining
		m_bStartedMixing = true;
		if ( snd_xma_spew_startup.GetBool() )
		{
			Msg( "XMA: 0x%8.8x, Startup Latency: %d ms, Samples Ready: %d, '%s'\n", (unsigned int)this, Plat_MSTime() - m_StartTime, numReadySamples, m_pData->Source().GetFileName() );
		}
		return true;
	}

	if ( Plat_MSTime() - m_StartTime >= MIX_DECODER_TIMEOUT )
	{
		m_Error = ERROR_DECODER_TIMEOUT;
	}

	// on startup error, let mixer start and get unavailable samples, and abort
	// otherwise hold off mixing until samples arrive
	return ( m_Error != 0 );
}

//-----------------------------------------------------------------------------
// Returns true to mix, false to stop mixer completely. Called after
// mixer requests samples.
//-----------------------------------------------------------------------------
bool CAudioMixerWaveXMA::ShouldContinueMixing()
{
	if ( !IsRetail() && m_Error && snd_xma_spew_warnings.GetBool() )
	{
		const char *pErrorString;
		if ( m_Error < 0 && -m_Error < ARRAYSIZE( g_XMAErrorStrings ) )
		{
			pErrorString = g_XMAErrorStrings[-m_Error];
		}
		else
		{
			pErrorString = g_XMAErrorStrings[0];
		}
		Warning( "XMA: 0x%8.8x, Mixer Aborted: %s, SamplePosition: %d/%d, DataOffset: %d/%d, '%s'\n", 
			(unsigned int)this, pErrorString, m_SamplePosition, m_SampleCount, m_DataOffset, m_TotalBytes, m_pData->Source().GetFileName() );
	}

	// an error condition is fatal to mixer
	return ( m_Error == 0 && BaseClass::ShouldContinueMixing() );
}

//-----------------------------------------------------------------------------
// Read existing buffer or decompress a new block when necessary.
// If no samples can be fetched, returns 0, which hints the mixer to a pending shutdown state.
// This routines operates in large buffer quantums, and nothing smaller.
// XMA decode performance severly degrades if the lock is too frequent.
//-----------------------------------------------------------------------------
int CAudioMixerWaveXMA::GetOutputData( void **pData, int numSamplesToCopy, char copyBuf[AUDIOSOURCE_COPYBUF_SIZE] )
{
	if ( m_Error )
	{
		// mixer will eventually shutdown
		return 0;
	}

	if ( !m_bStartedMixing )
	{
#if defined( ALLOW_SKIP_SAMPLES )
		int numMaxSamples =  AUDIOSOURCE_COPYBUF_SIZE/( m_NumChannels * sizeof( short ) );
		numSamplesToCopy = min( numSamplesToCopy, numMaxSamples );
		m_SkipSamples += numSamplesToCopy;

		// caller requesting data before mixing has commenced
		V_memset( copyBuf, 0, numSamplesToCopy );
		*pData = (void*)copyBuf;
		return numSamplesToCopy;
#else
		// not allowed, GetOutputData() should only be called by the mixing loop
		Assert( 0 );
		return 0;
#endif
	}

	// XMA mixing cannot be driven from the main thread
	Assert( ThreadInMainThread() == false );

	// needs to be clocked at regular intervals
	if ( ServiceXMADecoder( false ) < 0 )
	{
		return 0;
	}

#if defined( ALLOW_SKIP_SAMPLES )
	if ( m_SkipSamples > 0 )
	{		
		// flush whatever is available
		// ignore
		m_SkipSamples -= GetPCMSamples( m_SkipSamples, NULL );
		if ( m_SkipSamples != 0 )
		{
			// not enough decoded data ready to flush
			// must flush these samples to maintain proper position
			m_Error = ERROR_XMA_NO_PCM_DATA;
			return 0;
		}
	}
#endif

	// loopback may require flushing some decoded samples
	int numRequestedSamples = numSamplesToCopy;
	int numDiscardSamples = UpdatePositionForLooping( &numRequestedSamples );
	if ( numDiscardSamples > 0 )
	{
		// loopback requires discarding samples to converge to expected looppoint
		numDiscardSamples -= GetPCMSamples( numDiscardSamples, NULL );
		if ( numDiscardSamples != 0 )
		{
			// not enough decoded data ready to flush
			// must flush these samples to achieve looping
			m_Error = ERROR_XMA_NO_PCM_DATA;
			return 0;
		}
	}

	// can only drain as much as can be copied to caller		
	int numMaxSamples =  AUDIOSOURCE_COPYBUF_SIZE/( m_NumChannels * sizeof( short ) );
	numRequestedSamples = min( numRequestedSamples, numMaxSamples );

	int numCopiedSamples = GetPCMSamples( numRequestedSamples, copyBuf );
	if ( numCopiedSamples )
	{
		CAudioMixerWave::m_sample_max_loaded += numCopiedSamples;
		CAudioMixerWave::m_sample_loaded_index += numCopiedSamples;

		// advance position by valid samples
		m_SamplePosition += numCopiedSamples;

		*pData = (void*)copyBuf;

#ifdef DEBUG_XMA
		if ( snd_xma_record.GetBool() )
		{
			WaveAppendTmpFile( "debug.wav", copyBuf, 16, numCopiedSamples * m_NumChannels );
			WaveFixupTmpFile( "debug.wav" );
		}
#endif
	}
	else
	{
		// no samples copied
		if ( !m_bFinished && numRequestedSamples )
		{
			// XMA latency error occurs when decoder not finished (not at EOF) and caller wanted samples but can't get any
			if ( snd_xma_spew_warnings.GetInt() )
			{
				Warning( "XMA: 0x%8.8x, No Decoded Data Ready: %d samples needed, '%s'\n", (unsigned int)this, numSamplesToCopy, m_pData->Source().GetFileName() );
			}
			m_Error = ERROR_XMA_NO_PCM_DATA;
		}
	}

	return numCopiedSamples;
}

//-----------------------------------------------------------------------------
// Purpose: Seek to a new position in the file
//			NOTE: In most cases, only call this once, and call it before playing
//			any data.
// Input  : newPosition - new position in the sample clocks of this sample
//-----------------------------------------------------------------------------
void CAudioMixerWaveXMA::SetSampleStart( int newPosition )
{
	// cannot support this
	// this should be unused and thus not supporting
	Assert( 0 );
}


int CAudioMixerWaveXMA::GetPositionForSave()
{
	if ( m_bLooped )
	{
		// A looped sample cannot be saved/restored because the decoded sample position,
		// which is needed for loop calc, cannot ever be correctly restored without
		// the XMA seek table. 
		return 0;
	}

	// This is silly and totally wrong, but doing it anyways.
	// The correct thing was to have the XMA seek table and use
	// that to determine the correct packet. This is just a hopeful
	// nearby approximation. Music did not have the seek table at
	// the time of this code. The Seek table was added for vo
	// restoration later.
	return m_LastDataOffset;
}

void CAudioMixerWaveXMA::SetPositionFromSaved( int savedPosition )
{
	// Not used here. The Mixer creation will be given the initial startup offset.
}

//-----------------------------------------------------------------------------
// Purpose: Abstract factory function for XMA mixers
//-----------------------------------------------------------------------------
CAudioMixer *CreateXMAMixer( IWaveData *data, int initialStreamPosition )
{
	return new CAudioMixerWaveXMA( data, initialStreamPosition );
}