Check-in [2dd1f3902d]

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:-- Implement Media Kit : add a 'native' audio-out back-end -- * keep the OSS back-end mothballed, with a tweak to IsAlmostFull(), might return to it later ; * add a 'native' (direct-to-Genode-mixer) back-end which does not rely on signals but polls stream.queued() instead ; * this is being used successfully with AC ; * still have to clean-up/commit a few missing classes yet for the Media Kit to compile ; -- TESTS -- * audio playback is fluid (no 'on-air blanks') with upperlimit_queue_threshold set to 64, on my T410 ; * stability was bad starting a couple months back, but seems to be good now, no more lock-ups for the last few days, after I fixed a mem leak in my vfs-ntfs code (coincidence ?) ; * there remains a few question marks yet (do "grep FIXME" to find them) ;
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 2dd1f3902da2ada7fc52a49a3d71bdd81f357d3b
User & Date: user 2023-03-15 10:44:40
Context
2023-03-20
18:22
-- Fix semaphores -- * acquire_sem() was not calling genode_sem.down() but was instead busy looping (sending EAGAIN back and forth) on 'busy' semaphores ; * we now call down() properly, being careful to first release the giant kernel lock ; * this introduces a race though (what if the sem is deleted inbetween), will look into it soon-ish ; -- TESTS -- * a few months back, we would lock-up or at least slow down to a crawl due to the busy-looping-with-giant-lock-held crazyness ; * been testing this fix for quite a few weeks now, looking good ; check-in: 05b6c4490f user: user tags: trunk
2023-03-15
10:44
-- Implement Media Kit : add a 'native' audio-out back-end -- * keep the OSS back-end mothballed, with a tweak to IsAlmostFull(), might return to it later ; * add a 'native' (direct-to-Genode-mixer) back-end which does not rely on signals but polls stream.queued() instead ; * this is being used successfully with AC ; * still have to clean-up/commit a few missing classes yet for the Media Kit to compile ; -- TESTS -- * audio playback is fluid (no 'on-air blanks') with upperlimit_queue_threshold set to 64, on my T410 ; * stability was bad starting a couple months back, but seems to be good now, no more lock-ups for the last few days, after I fixed a mem leak in my vfs-ntfs code (coincidence ?) ; * there remains a few question marks yet (do "grep FIXME" to find them) ; check-in: 2dd1f3902d user: user tags: trunk
2023-03-01
17:29
-- clean-up: various minor tweaks -- * Jamrules: revert roles of NEEDLIBS / UNDEFS again, seems more compatible ; * BDirectory: comment out fcntl(...CLOEXEC..) to dodge "n/i" log warnings ; * logging tweaks ; * comment tweaks ; * ToDo's ; No functional change intended ; check-in: 314a522e76 user: user tags: trunk
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Added genode-haiku/haiku-on-genode/kits/media/_bmediafile_Temp.cpp.





































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
/*
 * Copyright 2022-2023, ttcoder
 * All rights reserved. Distributed under the terms of the MIT license.
 */


// base.lib.a
#include <base/heap.h>
#include <base/mutex.h>

// libmedia.so
#include "_ge_consumer_base.h"



// must relocate the instantiation due to hai-os headers clashing with Genode headers...
extern hog::ConsumerBase * instantiate_output_consumer( Genode::Env & env );

//xx clean this up xx
/******************/
#include "_ge_AvDecoder.cpp"
//#include "_ge_AudioIn.cpp"
/******************/



//#pragma mark -


class Audio_player::Main
{
private:  // Data

		Genode::Mutex feedLock;  // protect concurrent access of Audio_player data by the decoder/feeder thread and the client thread(s)
		
		Genode::Env & env;
		Genode::Heap  alloc;
		
		//Genode::Signal_handler<Main>  progress_dispatcher;
		hog::ConsumerBase * outputConsumer;  // might be either a ConsumerNative or ConsumerOss
		hog::FramesProducer * libavDecoder;


private:  // Data -- accessed by BMediaFile...

#if 0
	class AdvancerThread : public Genode::Thread
	{
	public:
		AdvancerThread( Genode::Env & env, Audio_player::Main & hub );
		
		void entry() override;
		
	public:  // (!)
		Audio_player::Main & playerMain;
	};
	AdvancerThread feederThread;
	bool threadNeedsJoining;
#else
		thread_id feedThread;
#endif
		bool feedTearDown;
		//bool feedPaused = false;
		//bool is_stopped = false; ///need something like this (but not quite this), to handle "decoder is exhausted" situation
	

private:  // Code

		void feedMixer();
		void accumulateDecodedFrames();


public:

		Main( Genode::Env & env, Genode::String<256> path );
		~Main();
		
		bool IsTearingDown() const;
		
		//void handle_progress();
		void Advance();
		
		void start();//
		void stop();//
		
		uint64_t progress();// const;
		uint64_t current_frame();// const;
		uint64_t duration();// const;
		uint64_t count_frames();// const;
};



static status_t audio_out_entry( void * ptr )
{
	Genode::log( "Entering audio_out_entry with obj=", ptr, " and name: ", Genode::Thread::myself()->name(), " .........." );
	
	if( nullptr == ptr )
	{
		Genode::error( "NULL audio_out ptr" );
		return -1;
	}
	
	auto obj = static_cast<Audio_player::Main*>( ptr );
	
	// With this extra call we make sure the thread starts with *2* back-to-back calls
	// to Advance(), before we ever begin to snooze():
	//
	obj->Advance();
	
	while( false == obj->IsTearingDown() )
	{
		obj->Advance();
		
#if 1
		// Note: we sleep *without* holding the <feedLock> mutex, of course:
		snooze( hog::snooze_micros );
#endif
	}
	
	Genode::log( "-Leaving- audio_out_entry ..........thread name: ", Genode::Thread::myself()->name() );
	
	return 0;
}



//#pragma mark -


Audio_player::Main::Main(
		Genode::Env & _env,
		Genode::String<256> path///FIXME-2 !
		)
:	env( _env )
	,alloc( env.ram(), env.rm() )
	//,progress_dispatcher( env.ep(), *this, &Main::handle_progress )
	,outputConsumer( nullptr )
	,libavDecoder( nullptr )
#if 0
	,feederThread( env, *this )
	,threadNeedsJoining( false )
#else
	,feedThread( 0 )
#endif
	,feedTearDown( false )
{
	GeTRACE( "audio_main.Ctor, path: ", path, "   ... parent thread name: ", Genode::Thread::myself()->name() );
	
	outputConsumer = instantiate_output_consumer( env );
	outputConsumer->rl.log( "\n create-decoder (will throw if invalid file); " );
	
	libavDecoder = new (&alloc) hog::FramesProducer( path );
	outputConsumer->rl.log( " dump-info; " );
	Genode::log( "file info: -----------" );
	libavDecoder->PrintToStream( path.string() );
	
	outputConsumer->rl.log( " audio_main.Ctor complete; " );
}


Audio_player::Main::~Main()
{
	Genode::log( "audio_main.DTOR" );
	outputConsumer->rl.log( "\n audio_main.DTOR; " );
	
	// T- (since this calls lock(), so must be done *before* the below lock())
	stop();
	
	// T+ (should only block waiting for thread once the above call has unlocked)
	outputConsumer->rl.log( " tearing *Down* 'feed' thread; " );
#if 0
	if( threadNeedsJoining )
		feederThread.join();
#else
	wait_for_thread( feedThread, NULL );
#endif
	
	// Be extra careful and lock here to prevent accessors being called while we delete ?
	// Though if someone's calling "delete BMediaFile" it wouldn't make sense that they also call accessors
	Genode::Mutex::Guard lock_guard( feedLock );
	
	Genode::destroy( &alloc, libavDecoder );  libavDecoder = nullptr;
	//Genode::log("destroy done");
	
	delete outputConsumer; outputConsumer = nullptr;
	//Genode::log("delete audio-out done");
}


bool Audio_player::Main::Main::IsTearingDown() const
{
	return feedTearDown;
}


void Audio_player::Main::Advance()
{
	//xx no need for this one, get rid of it xx
	if( feedTearDown )
		return;
	
	// goal: we want to go to sleep only after stashing decoded samples to the roof
	// and filling the audio queue to the highest tolerable threshold (i.e. strike
	// a balance between excessive latency and instable/choppy playback)
	
	feedMixer();
	
	// 30 iterations is enough to fill our 256 KB fifo... better now with 30 ?
	///FIXME-2: nope, makes no difference, still getting "impossible queue count"... seems harmless though ?
	for( int i = 0; i < 30; i++ )  // we don't want a (potentially buggy/infinite) loop, but we don't want to call this just once either, hence this arbitrary for():
		accumulateDecodedFrames();
	
	//log( "  bmf.accumulated: ", libavDecoder->DecodedFrames().read_avail() );
	
	// once more for good measure, in case a couple packets were consumed during decoding ? :
	//feedMixer();
}


void Audio_player::Main::feedMixer()
{
	Genode::Mutex::Guard lock_guard( feedLock );
	if( feedTearDown )
		return;
	
	//xx test "> AUDIO_OUT_PACKET_SIZE" instead ? Well ConsumeBuffer() already does just that
	if( libavDecoder->DecodedFrames().read_avail() > 0 )
	{
		outputConsumer->ConsumeBuffer( libavDecoder->DecodedFrames() );
		
		//outputConsumer->rl.Dump();
	}
}


void Audio_player::Main::accumulateDecodedFrames()
{
	Genode::Mutex::Guard lock_guard( feedLock );
	if( feedTearDown )
		return;
	
	if( libavDecoder->DecodedFrames().write_avail() <= hog::writeavail_threshold )
		//
		return;  // not enough room to fix an entire chunk, we're full
		//
	
	outputConsumer->rl.log( "\n prodbuffer; " );
	
	// T0
	//
	const int n = libavDecoder->ProduceBuffer(
		hog::AUDIO_OUT_PACKET_SIZE  // aka outputConsumer.frame_size()
		);
	outputConsumer->rl.log( " produced=" );
	outputConsumer->rl.log( n );
	outputConsumer->rl.log( " bytes; " );
	if( n <= 0 )
	{
		outputConsumer->rl.log( " ** zero production! flush previously produced.. ** " );
		Genode::log( " * zero prod : set Tear Down ! *" );
		
///FIXME-2: once the first stage is solved (all reliability issues), refactor some more, so that we keep decoding to the end, we must not tear down prematurely and discard the tail-end audio samples (which have been decoded by libAV but not yet sent to mixer)
#if 1
		// Seems the decoder blows up sometimes, when called beyond EoF -- as a temporary fix, do a brutal bail out and make sure we do NOT call ProduceBuffer() any more:
		feedTearDown = true;
		// +Halt() ?
		
		outputConsumer->rl.Dump();//
#endif
	}
}



void Audio_player::Main::start()
{
	GeTRACE( "main-audio.start" );
	
	Genode::Mutex::Guard lock_guard( feedLock );
	
	if( feedThread > 0 )
		Genode::error( "audio.start() was already called ! (will create a redundant thread ?)" );
	
	/*
	outputConsumer.start_timer();
	// "prime the pump" ?:
	//handle_progress();
	*/
#if 0
	feederThread.start();
	threadNeedsJoining = true;
#else
	// we need a pthread, not a "bare" Genode thread, so that we can execute libav's libc code:
	status_t st = -1;
	feedThread = spawn_thread( audio_out_entry, "audio_out_entry", 10, this );
	if( feedThread > 0 )
		st = resume_thread( feedThread );
	if( st != B_OK )
		Genode::error( "audio_out: could not spawn/resume thread" );
#endif
}


void Audio_player::Main::stop()
{
	Genode::log( "main.stop()" );
	
	feedTearDown = true;
	
	/**********/
	Genode::Mutex::Guard lock_guard( feedLock );
	
	//outputConsumer.stop_timer();  // tell the timer or sigh to no longer call our progress() hook
	
	Genode::log( "main.out.halt()" );
	outputConsumer->Halt();
}



uint64_t Audio_player::Main::progress()// const
{
	GeTRACE( "  bmf.main.progress" );
	Genode::Mutex::Guard lock_guard( feedLock );
	
	return libavDecoder ? libavDecoder->progress() : 0;
}

uint64_t Audio_player::Main::duration()// const
{
	GeTRACE( "  bmf.main.duration" );
	Genode::Mutex::Guard lock_guard( feedLock );
	
	return libavDecoder ? libavDecoder->Duration() : 0;
}

uint64_t Audio_player::Main::current_frame()// const
{
	GeTRACE( "  bmf.main.curframe" );
	Genode::Mutex::Guard lock_guard( feedLock );
	
	return libavDecoder ? libavDecoder->current_frame() : 0;
};

uint64_t Audio_player::Main::count_frames()// const
{
	GeTRACE( "  bmf.main.count_frames" );
	Genode::Mutex::Guard lock_guard( feedLock );
	
	return libavDecoder ? libavDecoder->count_frames() : 0;
};


Added genode-haiku/haiku-on-genode/kits/media/_ge_AudioOut_native.cpp.







































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
/*
 * Copyright 2023, ttcoder
 * All rights reserved. Distributed under the terms of the MIT license.
 */


// Genode *native* back-end for:
// BSoundPlayer

// base.lib.a
#include <base/attached_ram_dataspace.h>  // member
//#include <base/signal.h>

// libmedia.so
#include "_ge_consumer_base.h"  // ancestor



class hog::ConsumerNative : public ConsumerBase
{
	// Consumes audio frames produced by FramesProducer,
	// sends them to Audio_out service (e.g. mixer)
	
public:

		ConsumerNative( Genode::Env & env );
		~ConsumerNative();
		
		// ConsumerBase
		bool IsAlmostFull() override;
		void ConsumeBuffer( Frame_data & frame_data ) override;
		void Halt() override;


private:  // Code

		void packageFloats( const float * const decoded_interleaved );
		
		//void handle_audio_out_progress();
		//size_t frame_size() const;


private:  // Data

		Genode::Attached_ram_dataspace 
			writeBuf;
		Genode::Constructible<Audio_out::Connection>
			outChannels[NUM_CHANNELS];
		
		//Genode::Signal_context_capability  sigHandler; _d_
		//Genode::Signal_handler<ConsumerNative> sigHandler;//  progress_dispatcher;
};


// work-around for Hai-OS's #defines (imported by MediaFile.cpp) interfering with "attached_ram_dataspace.h" (imported by this file)...
/**********************/
hog::ConsumerBase * instantiate_output_consumer( Genode::Env & env )
{
	return new hog::ConsumerNative( env );
}
/**********************/


hog::ConsumerNative::ConsumerNative( Genode::Env & env )
:	ConsumerBase( env )
	,writeBuf( env.ram(), env.rm(), AUDIO_OUT_PACKET_SIZE )
	//,sigHandler( env.ep(), *this, &hog::ConsumerNative::handle_audio_out_progress )
	,outChannels()
{
	GeTRACE( "native_out.Ctor" );
	
	// build channels
	for( int i = 0; i < NUM_CHANNELS; i++ )
	{
		static const char *names[] = { "left", "right" };//+static_assert re. NUM_CHANNELS..
		
		try
		{
			outChannels[i].construct( env, names[i], false, false );
			
			rl.log( " created channel " ); rl.log( i );
		}
		catch( ... )
		{
			Genode::error( "cannot create Audio_out #", i, " aka ", names[i] );
			throw;
		}
	}
	
	// subscribe to mixer signals
	//outChannels[0]->progress_sigh( sigHandler );
	
	// start streaming to mixer
	for( int i = 0; i < NUM_CHANNELS; i++)
		outChannels[i]->start();
	
	rl.log( " ctor complete; " );
}


hog::ConsumerNative::~ConsumerNative()
{
	GeTRACE( "native_out.DTor" );
	
	// unsubscribe
	//outChannels[0]->progress_sigh( Genode::Signal_context_capability() );
	
	// stop streaming to mixer
	Halt();
}


void hog::ConsumerNative::Halt()
{
	for( int i = 0; i < NUM_CHANNELS; i++)
	{
		if( outChannels[i].constructed() )
		{
			outChannels[i]->stop();
			//outChannels[i].destruct();
		}
	}
}



bool hog::ConsumerNative::IsAlmostFull()
{
	// RETURNS: (comparison of queued() and <upper_queue_threshold>):
	//
	// - strictly superior:	-> error ! (should not happen, although it does)
	// - equal:				-> return true
	// - strictly inferior:	-> return false (i.e. allow additional queueing up)
	
///FIXME-2: strange, I can't get this message to disappear, even if I enlarge buffer sizes and the queue threshold and decrease the snooze time... I get this situation at the beginning of each mp3, for a second or two, even after configuring to queue up 64 packets... File a ticket ? Or am I using the Audio_out session wrong ?
///FIXME-2: also, why can't I play two mp3s simultaneously? That's the very thing that the mixer ought to support :-P  Yet starting a second mp3 in eg ArmyKnife yields choppy sound or no sound, even as the first mp3 in AC continues to play... What am I doing wrong ?
	auto count = outChannels[0]->stream()->queued();
	if( count > upper_queue_threshold )
	{
		rl.log( " *** Impossible Queue Count (overflow?): " );
		rl.log( count );
		rl.Dump();///
	}
	
	return
		outChannels[0]->stream()->queued()
		>=
		upper_queue_threshold
		;
}



///++ Consume"Frames"
void hog::ConsumerNative::ConsumeBuffer( Frame_data & frame_data )
{
	//const unsigned count_frames = frame_data.read_avail() / (NUM_CHANNELS*sizeof(float));
	GeTRACE( " ConsumeBuffer: need to tx ", frame_data.read_avail(), " bytes (", count_frames, " frames)" );
	
	if( frame_data.read_avail() <= 0 )
	{
		rl.log( " *native sink: empty production, return* " );
		//Genode::log( "** native sink: empty production? failed to decode?" );
		//
		return;
		//
	}
	
///FIXME-2: how come queued() returns 1 (instead of 0) the very first time we're called, before we even start to queue buffers ?
	const auto queued = outChannels[0]->stream()->queued();
	if( queued <= 3 )
	{
		rl.log( "\n Low Queue Count (underrun ? or maybe we're just starting up ?): " );
		rl.log( queued );
		/********/
		rl.Dump();
	}
	
	//get rid of this?:
	{
		rl.log( "\n t- pipeline: " ); rl.log( queued ); rl.log( " packets; " );
		rl.log( frame_data.read_avail() ); rl.log( " bytes in fifo; " );
		
		if( IsAlmostFull() )
		{
			rl.log( " BAIL OUT; " );
			//rl.Dump();
			//Genode::warning( "BAIL OUT, there's already ", queued, " packets queued" );
			//
			return;
			//
		}
	}
	
#if 0
	float tmp[AUDIO_OUT_PACKET_SIZE / sizeof(float)] = { 0 };
	// that's a sizeable buffer at 4096 bytes big, let's use a DS instead:
#else
	float * tmp = writeBuf.local_addr<float>();
#endif
	
	// loop until we exhaust <frame_data> or until we reach the desired buffering level
	//
	while( frame_data.read_avail() >= AUDIO_OUT_PACKET_SIZE
		&& false==IsAlmostFull()
		)
	{
		size_t const n = frame_data.read( tmp, AUDIO_OUT_PACKET_SIZE );
		rl.log( "\n  pop " ); rl.log( n ); rl.log( " bytes; " );
		
		if( n != AUDIO_OUT_PACKET_SIZE )
		{
			rl.log( " ** n != AUDIO_OUT_PACKET_SIZE ** " );
			Genode::warning( "retrieved fewer frames than expected" );
			//
			break;
			//
		}
		
		packageFloats( tmp );
	}
	
	//Genode::log( "  sink.queued: ", outChannels[0]->stream()->queued() );
	
	///FIXME-2: what about the very last few bytes of an mp3 ? Need to send a zero-ed out, "partial" packet
	// and to read exactly what remains, like:
	//
	// frame_data.read( last_buf, frame_data.read_avail() );
}


//++ "pushOneStereoPacketPair()"
void hog::ConsumerNative::packageFloats( const float * const decoded_interleaved )
{
	// INPUT:
	//	- float array, one left packet + one right packet's worth (two channels interleaved)
	// OUTPUT:
	//	- left packet
	//	- right packet
	
	Audio_out::Packet * left_pk = nullptr;
	try
	{
		rl.log(" alloc;");
		
		left_pk = outChannels[0]->stream()->alloc();
	}
	catch ( ... )
	{
		Genode::error( "audio-out: RESET on Under-Run (?)" );
		rl.log(" *RESET On Underrun* SHOULD WE TRY AGAIN ? (in a for() loop) ?\n");
		
		// seems we're more stable(?) without this:
		//outChannels[0]->stream()->reset();
		
		///ToDo: or maybe, to the contrary, the above could reduce the ~3 seconds "blanks" to be much shorter ? Either up there, or in ConsumeBuffer() ? Well I no longer get audio silence/blanks with the queue threshold increased to 64...
	}
	
	Audio_out::Packet * right_pk = nullptr;
	{
		auto pos = outChannels[LEFT ]->stream()->packet_position( left_pk );
		right_pk = outChannels[RIGHT]->stream()->get( pos );
	}
	
#if 0
		// DEBUG: Square wave generator...
		//
		for( unsigned i = 0; i < Audio_out::PERIOD; i++ )
		{
			float tmp_val = 0.;
			
			if( i < Audio_out::PERIOD/2.0 )
				tmp_val = 0.25;
			else
				tmp_val = -0.25;
			
			/**/float * floats[NUM_CHANNELS] = { left_pk->content(), right_pk->content() };
			floats[LEFT ][i] = tmp_val;
			floats[RIGHT][i] = tmp_val;
		}
#else
		//fillFloats( left_pk->content() );
		//fillFloats( right_pk->content() );
		
		// Stereo sample loop (one pair of packets)
		//
		const unsigned frames_per_packet = Audio_out::PERIOD;
		for( unsigned f = 0; f < frames_per_packet; f++ )
		{
			float * chan_pkt[NUM_CHANNELS] = { left_pk->content(), right_pk->content() };
			
			chan_pkt[LEFT ][f] = decoded_interleaved[ f * NUM_CHANNELS +LEFT ];
			chan_pkt[RIGHT][f] = decoded_interleaved[ f * NUM_CHANNELS +RIGHT ];
		}
#endif
	
	rl.log( " submit#" ); rl.log( outChannels[0]->stream()->packet_position((left_pk)) );
	
	// push packets
	outChannels[LEFT ]->submit( left_pk );
	outChannels[RIGHT]->submit( right_pk );
	
	{
		auto queued = outChannels[0]->stream()->queued();
		rl.log( "; t+ pipeline: " ); rl.log( queued ); rl.log( " pkts; " );
	}
}


#if 0
void hog::ConsumerNative::handle_audio_out_progress()
{
	Genode::log( "handle_audio_out_progress" );
	/*
	if constructed..
	auto queued = outChannels[0].stream()->queued();
	log( queued );
	*/
}
#endif



Changes to genode-haiku/haiku-on-genode/kits/media/_ge_AudioOut_oss.cpp.

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

// libc.lib.so
#include <fcntl.h>  // ioctl
#include <sys/soundcard.h>  // ioctl codes: OSS_GETVERSION, SNDCTL_DSP_HALT, etc




class hog::Consumer
{
	
public:

		Consumer(
			Genode::Env & env
	//		Genode::Signal_handler<Audio_player::Main>  & sigh//progress_dispatcher;
	//		Genode::Signal_context_capability sigh
			);
		~Consumer();
		
	//	void start_timer();
	//	void stop_timer();
		void Halt();
		void ConsumeBuffer( Frame_data & frame_data );




private:  // Constants

	//xxx or:  sizeof(short) * NUM_CHANNELS * PERIOD * how_many_periods_to_batch_together
	//enum { BUFSIZE = 128 * 1024 };


private:  // Data

		int fdOss;
		
///FIXME: use a DS instead of (smaller/riskier) stack-allocated buf (careful, that class gets "clashed" by hai-libc, make this a separate compilation unit)
#if 0
		Attached_ram_dataspace  writeBuf;
#endif

#if 0
		///ToDo: any way to get a signal, when OSS is ready to receive new buffers ?
		void _handle_timeout( Genode::Duration );







>
















>
>












|







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

// libc.lib.so
#include <fcntl.h>  // ioctl
#include <sys/soundcard.h>  // ioctl codes: OSS_GETVERSION, SNDCTL_DSP_HALT, etc



//++ "ConsumerOSS"
class hog::Consumer
{
	
public:

		Consumer(
			Genode::Env & env
	//		Genode::Signal_handler<Audio_player::Main>  & sigh//progress_dispatcher;
	//		Genode::Signal_context_capability sigh
			);
		~Consumer();
		
	//	void start_timer();
	//	void stop_timer();
		void Halt();
		void ConsumeBuffer( Frame_data & frame_data );
		
		bool IsAlmostFull();


private:  // Constants

	//xxx or:  sizeof(short) * NUM_CHANNELS * PERIOD * how_many_periods_to_batch_together
	//enum { BUFSIZE = 128 * 1024 };


private:  // Data

		int fdOss;
		
///ToDo: use a DS, instead of a (smaller/riskier) stack-allocated buf (careful, that class gets "clashed" by hai-libc, make this a separate compilation unit)
#if 0
		Attached_ram_dataspace  writeBuf;
#endif

#if 0
		///ToDo: any way to get a signal, when OSS is ready to receive new buffers ?
		void _handle_timeout( Genode::Duration );
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114


#if 0
void hog::Consumer::start_timer()
{
	//GeTRACE( "oss.start" );
	
	// initiate timer (it will self-sustain afterwards)
	Genode::Microseconds delay { 9000 };
	_timeout.schedule( delay );
}

void hog::Consumer::stop_timer()
{
	//GeTRACE( "oss.stop" );







|







103
104
105
106
107
108
109
110
111
112
113
114
115
116
117


#if 0
void hog::Consumer::start_timer()
{
	//GeTRACE( "oss.start" );
	
	// initiate timer (it will self-sustain afterwards with repeated calls to schedule())
	Genode::Microseconds delay { 9000 };
	_timeout.schedule( delay );
}

void hog::Consumer::stop_timer()
{
	//GeTRACE( "oss.stop" );
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
	{
		Genode::error( "oss-out: buffer overflow, bailing out" );
		//
		return;
		//
	}
#else
	///FIXME: uh ? can this get "too low" (yes: for WAV files eg) or "too high" and overflow ? :
#endif
	unsigned count_frames = frame_data.read_avail() / (NUM_CHANNELS*sizeof(float));
	//Genode::log( "    chunk size: ", count_frames, " frames" );  // typically: 1152 frames

#if 0
	short * buf = writeBuf.local_addr<short>();
	///ToDo: should use a ram_dataspace for tmp[] as well, not just buf[]







|







200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
	{
		Genode::error( "oss-out: buffer overflow, bailing out" );
		//
		return;
		//
	}
#else
	///ToDo: uh ? can this get "too low" (yes: for WAV files eg) or "too high" and overflow ? :
#endif
	unsigned count_frames = frame_data.read_avail() / (NUM_CHANNELS*sizeof(float));
	//Genode::log( "    chunk size: ", count_frames, " frames" );  // typically: 1152 frames

#if 0
	short * buf = writeBuf.local_addr<short>();
	///ToDo: should use a ram_dataspace for tmp[] as well, not just buf[]
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
			buf[i * NUM_CHANNELS +RIGHT] = tmp[i * NUM_CHANNELS + RIGHT] * 32700.;
		}
	}
#endif
	
	int res = 0;
	







	/*******/
























	//int oss_ver = 0;
	struct audio_errinfo  err_info = {};
	oss_count_t  optr = {};
	struct audio_buf_info  o_inf = {};
	Libc::with_libc([&] ()
	{
		//res = ioctl( fdOss, OSS_GETVERSION, &oss_ver );
		res = ioctl( fdOss, SNDCTL_DSP_GETERROR, &err_info );
		res = ioctl( fdOss, SNDCTL_DSP_CURRENT_OPTR, &optr );
		res = ioctl( fdOss, SNDCTL_DSP_GETOSPACE, &o_inf );
	});
#if 0
	//Genode::log("ioctl -> ", res, " ver ", oss_ver );
	Genode::log("ioctl -> ", res, " play_underruns ", err_info.play_underruns );
	Genode::log("ioctl -> ", res, " samples ", optr.samples, " fifo_samples ", optr.fifo_samples );
	Genode::log("ioctl -> ", res, " fragments ", o_inf.fragments, " fragstotal ", o_inf.fragstotal, " fragsize ", o_inf.fragsize, " bytes ", o_inf.bytes );
#endif
	/*******/
	
	Libc::with_libc([&] () {
		// This might hang when used against *black_hole* (e.g. in Qemu), if the write rate isn't sustained, after a few seconds BH gives up and write() never returns ; seems rock solid against pci_audio_drv however.
		///FIXME: it seems write() hangs on (bare metal) HDA too, in fact, re-enable log() lines to confirm ?
		///UPD: maybe the hang(s) was not in the OSS plug-in, but the whole client-side being jammed due to crashed vfs-server, and thus not proceeding to my oss.write() call, and thus fixed now that I fixed the vfs server crash ?

		
		//Genode::log( "  ..write ", sizeof(buf), " bytes:" );
		res = write(
			fdOss,
			buf,
			sizeof( buf )
			);
		//Genode::log( "  ..done with write -> ", res );
	});

	
	if( res != signed(sizeof(buf)) )
		Genode::warning( "OSS.write got ", res, " instead of ", sizeof(buf) );

	
	GeTRACE( " consume done!" );






}









>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

















<
|
<
<
<
<
>
|
<
|
<
<
<
<
<
<
>
|
<
<
>

<
>
>
>
>
>
>



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
			buf[i * NUM_CHANNELS +RIGHT] = tmp[i * NUM_CHANNELS + RIGHT] * 32700.;
		}
	}
#endif
	
	int res = 0;
	
	Libc::with_libc([&] () {
		// This might hang when used against *black_hole* (e.g. in Qemu), if the write rate isn't sustained,
		// after a few seconds BH gives up and write() never returns ; seems rock solid against pci_audio_drv however.
		///ToDo: it seems write() hangs on (bare metal) HDA too, not just with black_hole, in fact,
		// re-enable log() lines to confirm ?
		//UPD: could the "hang" actually be caused by a vfs server crash ? Nope, other apps (AK, Lightning etc) continue to work with vfs, even after AC locks-up.
		//UPD: seems the lock-ups are unrelated to audio/OSS, but to the scheduler thread instead ?
		
		// Heisen-Bug: T410 no longer reproduces the write() hang, after adding this log()s ?
		// UPD: yea it does, though now it takes 2+ hours
	//	Genode::log( "  ..write ", sizeof(buf), " bytes:" );
	//	GENODE_LOG_TSC_NAMED( 600, "oss.write" );
		
		res = write(
			fdOss,
			buf,
			sizeof( buf )
			);
		//Genode::log( "  ..done with write -> ", res );
	});
	
	if( res != signed(sizeof(buf)) )
		Genode::warning( "OSS.write got ", res, " instead of ", sizeof(buf) );
	
	GeTRACE( " consume done!" );
}


bool hog::Consumer::IsAlmostFull()
{
	int res = 0;
	
	//int oss_ver = 0;
	struct audio_errinfo  err_info = {};
	oss_count_t  optr = {};
	struct audio_buf_info  o_inf = {};
	Libc::with_libc([&] ()
	{
		//res = ioctl( fdOss, OSS_GETVERSION, &oss_ver );
		res = ioctl( fdOss, SNDCTL_DSP_GETERROR, &err_info );
		res = ioctl( fdOss, SNDCTL_DSP_CURRENT_OPTR, &optr );
		res = ioctl( fdOss, SNDCTL_DSP_GETOSPACE, &o_inf );
	});
#if 0
	//Genode::log("ioctl -> ", res, " ver ", oss_ver );
	Genode::log("ioctl -> ", res, " play_underruns ", err_info.play_underruns );
	Genode::log("ioctl -> ", res, " samples ", optr.samples, " fifo_samples ", optr.fifo_samples );
	Genode::log("ioctl -> ", res, " fragments ", o_inf.fragments, " fragstotal ", o_inf.fragstotal, " fragsize ", o_inf.fragsize, " bytes ", o_inf.bytes );
#endif

	int free_perc = -1;




	if( o_inf.fragstotal > 0 )
	{

		free_perc = 100 * o_inf.fragments;






		free_perc /= o_inf.fragstotal;
	}


	GeTRACE( "    audio queue free space: ", free_perc, " %" );
	

	// We draw the line for "almost full" at 10% free space (90% full) : beyond that,
	// we ask the caller to snooze for a bit before submitting more audio data..
	// Might spare us the blockage that occurs when doing "blocking writes" that never un-block?
	// UPD: no change, the lock-ups still occur, but now they occur at 90% full instead :-) :-/
	//
	return free_perc <= 10;
}


Changes to genode-haiku/haiku-on-genode/kits/media/_ge_AvDecoder.cpp.

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

// Genode back-end for:
// BMediaTrack






// Adapted (slightly) from original cnuke@genode -----------

// avcodec.so avformat.so avutil.so avresample.so
extern "C" {
#	include <libavcodec/avcodec.h>
#	include <libavformat/avformat.h>
#	include <libavutil/dict.h>
#	include <libavutil/frame.h>
#	include <libavutil/opt.h>
#	include <libavresample/avresample.h>
};  // ~extern "C"


template <size_t CAPACITY>
class hog::SimpleRingBuffer
{
	// AKA "trivial ring buffer" AKA "not really a *ring* buffer at all", simulated a ring using memmove()
	
public:
		SimpleRingBuffer()
		:	currentSize( 0 )
		{
		}
		
		size_t write( const void * src, size_t len )
		{
			const size_t write_avail =
				CAPACITY - currentSize;
			
			if( len > write_avail )
			{
				Genode::error( "ringbuffer: attempting to write ", len, " bytes but only ", write_avail, " free" );
				return 0;
			}
			
			// T0
			memcpy(
				bufData + currentSize,
				src,
				len
				);
			
			// T+
			currentSize += len;
			
			return len;
		}
		
		size_t read( void * dst, size_t len )
		{
			if( len > currentSize )
			{
				Genode::error( "ringbuffer: attempting to read ", len, " bytes, but only ", currentSize, " available" );
				return 0;
			}
			
			// T-
			memcpy( dst, bufData, len );
			
			// T0
			memmove( bufData, bufData+len, currentSize-len );
			currentSize -= len;
			
			// T+
			memset( bufData+currentSize, '\0', len );
			
			return len;
		}
		
		size_t read_avail() const
		{
			return currentSize;
		}


private:  // Data

		uint8 bufData[ CAPACITY ];
		size_t currentSize;
};



//#pragma mark -


class hog::FramesProducer
{
	// Decodes mp3 ..etc files, extracting/producing audio frames
	// to be fed to FramesConsumer.
	
public:  // Xtors

		FramesProducer( Genode::String<MAXPATHLEN> trackpath );
		~FramesProducer();
		



>
>
>
>
>














<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


|







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

// Genode back-end for:
// BMediaTrack


// libc.lib.so
#include <libc/component.h>  // with_libc


// Adapted (slightly) from original cnuke@genode -----------

// avcodec.so avformat.so avutil.so avresample.so
extern "C" {
#	include <libavcodec/avcodec.h>
#	include <libavformat/avformat.h>
#	include <libavutil/dict.h>
#	include <libavutil/frame.h>
#	include <libavutil/opt.h>
#	include <libavresample/avresample.h>
};  // ~extern "C"











































































class hog::FramesProducer
{
	// Decodes mp3 ..etc audio files, extracting/producing audio frames
	// to be fed to FramesConsumer.
	
public:  // Xtors

		FramesProducer( Genode::String<MAXPATHLEN> trackpath );
		~FramesProducer();
		
402
403
404
405
406
407
408

409
410
411
412
413
414
415
				//
				break;
				//
			}
			
			if( _packet.stream_index != _stream->index )
			{

				Genode::log( "  unexpected stream packet -- video stream maybe??" );
				
				//
				continue;
			}
			
			int finished = 0; //++ "got_full_frame"







>







335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
				//
				break;
				//
			}
			
			if( _packet.stream_index != _stream->index )
			{
				///later: I sometimes get this "unexpected stream packet" message:
				Genode::log( "  unexpected stream packet -- video stream maybe??" );
				
				//
				continue;
			}
			
			int finished = 0; //++ "got_full_frame"
479
480
481
482
483
484
485
486


487
488
489
490
491
492
493
					
					//_d_ samplesDecoded += bytes / frame_size;
					samplesDecoded +=
						_conv_frame->nb_samples;//xx uh if "nb_samples" is actually a nb_frames, we should *2 to get the nb_samples..??
					;
					written +=
						decodedFrames.write( data, bytes );
					


					GeTRACE(" producer: frame_date.write(", bytes, ") returns ", written);
					
					// This trace'ing exhibits the problem (also in e.g. audio_player) when playing 22050 Hz etc mp3s:
					// ! <nb_samples> is zero after a while !
					//	Genode::log( "   _conv_frame->linesize = ", bytes, "  t+ samplesDecoded = ", samplesDecoded, "  nb_samples: ", _conv_frame->nb_samples);
					
					in = nullptr;  // !







|
>
>







413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
					
					//_d_ samplesDecoded += bytes / frame_size;
					samplesDecoded +=
						_conv_frame->nb_samples;//xx uh if "nb_samples" is actually a nb_frames, we should *2 to get the nb_samples..??
					;
					written +=
						decodedFrames.write( data, bytes );
					;
					if( written != bytes )
						Genode::warning( "some decoded frames were discarded/lost in the ether, we're decoding faster than Audio-out consumes ?!" );
					GeTRACE(" producer: frame_date.write(", bytes, ") returns ", written);
					
					// This trace'ing exhibits the problem (also in e.g. audio_player) when playing 22050 Hz etc mp3s:
					// ! <nb_samples> is zero after a while !
					//	Genode::log( "   _conv_frame->linesize = ", bytes, "  t+ samplesDecoded = ", samplesDecoded, "  nb_samples: ", _conv_frame->nb_samples);
					
					in = nullptr;  // !

Added genode-haiku/haiku-on-genode/kits/media/_ge_consumer_base.h.































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
/*
 * Copyright 2023, ttcoder
 * All rights reserved. Distributed under the terms of the MIT license.
 */
#ifndef _ge_consumer_base_H
#define _ge_consumer_base_H


// base.lib.a
#include <audio_out_session/connection.h>

// libmedia.so
#include "_RingLog.h"


/**************/
//#define GeTRACE Genode::log
#define GeTRACE(x...) ;


namespace hog
{
	// constants
	//
	enum { LEFT, RIGHT, NUM_CHANNELS };
	enum { AUDIO_OUT_PACKET_SIZE = Audio_out::PERIOD * NUM_CHANNELS * sizeof(float) };
		// i.e.  "audio_pkt_size = frames_per_packet * samples_per_frame * bytes_per_sample"
	
	enum { snooze_micros = 25000/*55000*/ };  // ca. 40 Hz
		// pause for that many µs between each "wake-up -> decode -> fill queue" step.
	enum { upper_queue_threshold = 64 /*32*/ /*16*/ };
		// upper limit of how many mixer/audio_drv-side packets to fill before snoozing.
		// max 254 (for ca. 3 seconds worth of audio buffering).
		// min circa 16 (otherwise it's hard to avoid under-runs, i.e. hard to be certain to keep up and queue packets in time)
	enum { writeavail_threshold = 16384 };
		// keep filling the frames ring until there's only that many free bytes left
		// (we don't try to fill to the brim as the AV decoder deals in "chunks", and if the last chunk does not fit in full it's too late to "rewind" the decoder)
		// (otherwise we get errors like: << attempting to write 9216 bytes but only 5120 free >>
	enum { ringbuf_size_kb = 256 };
		// 256 KB -> enough to package <upper_queue_threshold> packets (64 packets)
	
	// types
	//
	class FramesProducer;
	class ConsumerBase;
	class ConsumerNative;
	
	template <size_t> class SimpleRingBuffer;
	typedef SimpleRingBuffer< ringbuf_size_kb/*256*/ * 1024 > Frame_data;
	
	
	/****** Latency config ******
	To increase performance/reliability:
		- increase queue threshold (higher latency)
		- decrease snooze (faster responsivness by more aggressive polling)
	To stress-test:
		- decrease queue threshold (lower latency)
		- increase snooze
	
	Changing the size of the decoded-frames FIFO plays a lesser role than the other settings.
	
	Latency tests (on T410, with snooze(119000)):
		- 24 packets deep: we frequently "miss a beat", with a "blank" of ca. 2 second (why so long ? full 255 packets cycle maybe ?)
		- 32 packets deep: same as 24 deep
		- 64 packets deep: looks good, no need to go up to 128 it seems
	
	The "blanks" get logged as "Low Queue Count"...
	Bug in queued() implementation ? Maybe when empty it tends to not return "0" but rather "255" ?
	Let's add a "Impossible Queue Count" logger.
	*/
};



template <size_t CAPACITY>
class hog::SimpleRingBuffer
{
	// AKA "trivial ring buffer" AKA "not really a *ring* buffer at all, using memmove() instead"
	
public:
		SimpleRingBuffer()
		:	currentSize( 0 )
		{
		}
		
		size_t write_avail() const
		{
			return CAPACITY - currentSize;
		}
		
		size_t write( const void * src, size_t len )
		{
			if( len > write_avail() )
			{
				///ToDo: instead of rejecting new frames I should discard some oldest frames, just enough to make room for newest frames, so as to minimize the damage...
				
				Genode::warning( "frames overflow -- ringbuffer: attempting to write ", len, " bytes but only ", write_avail(), " free" );
				//
				return 0;
				//
			}
			
			// T0
			memcpy(
				bufData + currentSize,
				src,
				len
				);
			
			// T+
			currentSize += len;
			
			return len;
		}
		
		size_t read( void * dst, size_t len )
		{
			if( len > currentSize )
			{
				Genode::error( "ringbuffer: attempting to read ", len, " bytes, but only ", currentSize, " available" );
				//
				return 0;
				//
			}
			
			// T-
			memcpy( dst, bufData, len );
			
			// T0
			memmove( bufData, bufData+len, currentSize-len );
			currentSize -= len;
			
			// T+
			memset( bufData+currentSize, '\0', len );
			
			return len;
		}
		
		size_t read_avail() const
		{
			return currentSize;
		}


private:  // Data

		uint8 bufData[ CAPACITY ];
		size_t currentSize;
};



class hog::ConsumerBase
{
public:
		ConsumerBase( Genode::Env & env )
		:	rl( env, false/**/, false )
		{
		}
		virtual ~ConsumerBase()
		{
		}
		
			//+ "IsBelowThreshold()"...
		virtual bool IsAlmostFull() = 0;
		virtual void ConsumeBuffer( Frame_data & frame_data ) = 0;
		virtual void Halt() = 0;

public:  // Data (!)
		RingLog rl;
};


#endif // _ge_consumer_base_H