xref: /haiku/src/kits/game/GameProducer.cpp (revision d5cd5d63ff0ad395989db6cf4841a64d5b545d1d)
1 /*****************************************************************************/
2 // GameProdcure.h
3 //
4 // This produce creates audio buffer on behalf of the GameKit.
5 //
6 // Copyright (c) 2001 OpenBeOS Project
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a
9 // copy of this software and associated documentation files (the "Software"),
10 // to deal in the Software without restriction, including without limitation
11 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 // and/or sell copies of the Software, and to permit persons to whom the
13 // Software is furnished to do so, subject to the following conditions:
14 //
15 // The above copyright notice and this permission notice shall be included
16 // in all copies or substantial portions of the Software.
17 //
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 // DEALINGS IN THE SOFTWARE.
25 //
26 //	File Name:		GameProducer.cpp
27 //	Author:			Christopher ML Zumwalt May (zummy@users.sf.net)
28 //	Description:	A MediaKit producer node which mixes sound from the GameKit
29 //					and sends them to the audio mixer
30 /*****************************************************************************/
31 
32 // Standard Includes -----------------------------------------------------------
33 #include <string.h>
34 #include <stdio.h>
35 
36 // System Includes -------------------------------------------------------------
37 #include <ByteOrder.h>
38 #include <BufferGroup.h>
39 #include <Buffer.h>
40 #include <List.h>
41 #include <TimeSource.h>
42 #include <MediaDefs.h>
43 
44 // Project Includes ------------------------------------------------------------
45 #include <GameSoundBuffer.h>
46 #include <GameSoundDevice.h>
47 #include <GSUtility.h>
48 
49 // Local Includes --------------------------------------------------------------
50 #include <GameProducer.h>
51 
52 // Local Defines ---------------------------------------------------------------
53 struct _gs_play
54 {
55 	gs_id sound;
56 	bool * hook;
57 
58 	_gs_play * next;
59 	_gs_play * previous;
60 };
61 
62 GameProducer::GameProducer(GameSoundBuffer * object,
63 							const gs_audio_format * format)
64 		:	BMediaNode("GameProducer.h"),
65 			BBufferProducer(B_MEDIA_RAW_AUDIO),
66 			BMediaEventLooper(),
67 			fBufferGroup(NULL),
68 			fLatency(0),
69 			fInternalLatency(0),
70 			fOutputEnabled(true)
71 {
72 	// initialize our preferred format object
73 	fPreferredFormat.type = B_MEDIA_RAW_AUDIO;
74 	fPreferredFormat.u.raw_audio.format = format->format;
75 	fPreferredFormat.u.raw_audio.channel_count = format->channel_count;
76 	fPreferredFormat.u.raw_audio.frame_rate = format->frame_rate;		// measured in Hertz
77 	fPreferredFormat.u.raw_audio.byte_order = format->byte_order;
78 //	fPreferredFormat.u.raw_audio.channel_mask = B_CHANNEL_LEFT & B_CHANNEL_RIGHT;
79 //	fPreferredFormat.u.raw_audio.valid_bits = 32;
80 //	fPreferredFormat.u.raw_audio.matrix_mask = B_MATRIX_AMBISONIC_WXYZ;
81 
82 	// we'll use the consumer's preferred buffer size, if any
83 	fPreferredFormat.u.raw_audio.buffer_size = media_raw_audio_format::wildcard.buffer_size;
84 
85 	// we're not connected yet
86 	fOutput.destination = media_destination::null;
87 	fOutput.format = fPreferredFormat;
88 
89 	fFrameSize = get_sample_size(format->format) * format->channel_count;
90 	fObject = object;
91 }
92 
93 
94 GameProducer::~GameProducer()
95 {
96 	// Stop the BMediaEventLooper thread
97 	Quit();
98 }
99 
100 
101 // BMediaNode methods
102 BMediaAddOn *
103 GameProducer::AddOn(int32 *internal_id) const
104 {
105 	return NULL;
106 }
107 
108 
109 // BBufferProducer methods
110 status_t
111 GameProducer::GetNextOutput(int32* cookie, media_output* out_output)
112 {
113 	// we currently support only one output
114 	if (0 != *cookie) return B_BAD_INDEX;
115 
116 	*out_output = fOutput;
117 	*cookie += 1;
118 	return B_OK;
119 }
120 
121 
122 status_t
123 GameProducer::DisposeOutputCookie(int32 cookie)
124 {
125 	// do nothing because our cookie is only an integer
126 	return B_OK;
127 }
128 
129 
130 void
131 GameProducer::EnableOutput(const media_source& what, bool enabled, int32* _deprecated_)
132 {
133 	// If I had more than one output, I'd have to walk my list of output records to see
134 	// which one matched the given source, and then enable/disable that one.  But this
135 	// node only has one output, so I just make sure the given source matches, then set
136 	// the enable state accordingly.
137 	if (what == fOutput.source)
138 	{
139 		fOutputEnabled = enabled;
140 	}
141 }
142 
143 
144 status_t
145 GameProducer::FormatSuggestionRequested(media_type type, int32 /*quality*/, media_format* format)
146 {
147 	// insure that we received a format
148 	if (!format)
149 		return B_BAD_VALUE;
150 
151 	// returning our preferred format
152 	*format = fPreferredFormat;
153 
154 	// our format is supported
155 	if (type == B_MEDIA_UNKNOWN_TYPE) return B_OK;
156 
157 	// we only support raw audo
158 	return (type != B_MEDIA_RAW_AUDIO) ? B_MEDIA_BAD_FORMAT : B_OK;
159 }
160 
161 
162 status_t
163 GameProducer::FormatProposal(const media_source& output, media_format* format)
164 {
165 	// doest the proposed output match our output?
166 	if (output != fOutput.source) return B_MEDIA_BAD_SOURCE;
167 
168 	// return our preferred format
169 	*format = fPreferredFormat;
170 
171 	// we will reject the proposal if the format is not audio
172 	media_type requestedType = format->type;
173 	if ((requestedType != B_MEDIA_UNKNOWN_TYPE) && (requestedType != B_MEDIA_RAW_AUDIO))
174 		return B_MEDIA_BAD_FORMAT;
175 
176 	return B_OK;		// raw audio or wildcard type, either is okay by us
177 }
178 
179 
180 status_t
181 GameProducer::PrepareToConnect(const media_source& what, const media_destination& where, media_format* format, media_source* out_source, char* out_name)
182 {
183 	// The format has been processed by the consumer at this point. We need
184 	// to insure the format is still acceptable and any wild care are filled in.
185 
186 	// trying to connect something that isn't our source?
187 	if (what != fOutput.source) return B_MEDIA_BAD_SOURCE;
188 
189 	// are we already connected?
190 	if (fOutput.destination != media_destination::null) return B_MEDIA_ALREADY_CONNECTED;
191 
192 	// the format may not yet be fully specialized (the consumer might have
193 	// passed back some wildcards).  Finish specializing it now, and return an
194 	// error if we don't support the requested format.
195 	if (format->type != B_MEDIA_RAW_AUDIO)
196 		return B_MEDIA_BAD_FORMAT;
197 
198 	if (format->u.raw_audio.format != fPreferredFormat.u.raw_audio.format)
199 		return B_MEDIA_BAD_FORMAT;
200 
201 	// check the buffer size, which may still be wildcarded
202 	if (format->u.raw_audio.buffer_size == media_raw_audio_format::wildcard.buffer_size)
203 		format->u.raw_audio.buffer_size = 2048;		// pick something comfortable to suggest
204 
205 	// Now reserve the connection, and return information about it
206 	fOutput.destination = where;
207 	fOutput.format = *format;
208 	*out_source = fOutput.source;
209 	strncpy(out_name, fOutput.name, B_MEDIA_NAME_LENGTH);
210 	return B_OK;
211 }
212 
213 
214 void
215 GameProducer::Connect(status_t error, const media_source& source, const media_destination& destination, const media_format& format, char* io_name)
216 {
217 	// If something earlier failed, Connect() might still be called, but with a non-zero
218 	// error code.  When that happens we simply unreserve the connection and do
219 	// nothing else.
220 	if (error)
221 	{
222 		fOutput.destination = media_destination::null;
223 		fOutput.format = fPreferredFormat;
224 	}
225 	else
226 	{
227 		// Okay, the connection has been confirmed.  Record the destination and format
228 		// that we agreed on, and report our connection name again.
229 		fOutput.destination = destination;
230 		fOutput.format = format;
231 		strncpy(io_name, fOutput.name, B_MEDIA_NAME_LENGTH);
232 
233 		// Now that we're connected, we can determine our downstream latency.
234 		// Do so, then make sure we get our events early enough.
235 		media_node_id id;
236 		FindLatencyFor(fOutput.destination, &fLatency, &id);
237 
238 		// Use a dry run to see how long it takes me to fill a buffer of data
239 
240 		// The first step to setup the buffer
241 		bigtime_t start, produceLatency;
242 		int32 frames = int32(fOutput.format.u.raw_audio.buffer_size / fFrameSize);
243 		float* data = new float[frames * 2];
244 
245 		// Second, fill the buffer
246 		start = ::system_time();
247 		for(int32 i = 0; i < frames; i++)
248 		{
249 			data[i*2] = 0.8 * float(i/frames);
250 			data[i*2+1] = 0.8 * float(i/frames);
251 		}
252 		produceLatency = ::system_time();
253 
254 		// Thid, calculate the latency
255 		fInternalLatency = produceLatency - start;
256 		SetEventLatency(fLatency + fInternalLatency);
257 
258 		// Finaily, clean up
259 		delete [] data;
260 
261 		// reset our buffer duration, etc. to avoid later calculations
262 		bigtime_t duration = bigtime_t(1000000) * frames / bigtime_t(fOutput.format.u.raw_audio.frame_rate);
263 		SetBufferDuration(duration);
264 
265 		// Set up the buffer group for our connection, as long as nobody handed us a
266 		// buffer group (via SetBufferGroup()) prior to this.
267 		if (!fBufferGroup)
268 		{
269 			size_t size = fOutput.format.u.raw_audio.buffer_size;
270 			int32 count = int32(fLatency / BufferDuration() + 2);
271 			fBufferGroup = new BBufferGroup(size, count);
272 		}
273 	}
274 }
275 
276 
277 void
278 GameProducer::Disconnect(const media_source& what, const media_destination& where)
279 {
280 	// Make sure that our connection is the one being disconnected
281 	if ((where == fOutput.destination) && (what == fOutput.source))
282 	{
283 		fOutput.destination = media_destination::null;
284 		fOutput.format = fPreferredFormat;
285 		delete fBufferGroup;
286 		fBufferGroup = NULL;
287 	}
288 }
289 
290 
291 status_t
292 GameProducer::FormatChangeRequested(const media_source& source, const media_destination& destination, media_format* io_format, int32* _deprecated_)
293 {
294 	// we don't support any other formats, so we just reject any format changes.
295 	return B_ERROR;
296 }
297 
298 
299 status_t
300 GameProducer::SetBufferGroup(const media_source& for_source, BBufferGroup* newGroup)
301 {
302 	// verify that we didn't get bogus arguments before we proceed
303 	if (for_source != fOutput.source) return B_MEDIA_BAD_SOURCE;
304 
305 	// Are we being passed the buffer group we're already using?
306 	if (newGroup == fBufferGroup) return B_OK;
307 
308 	// Ahh, someone wants us to use a different buffer group.  At this point we delete
309 	// the one we are using and use the specified one instead.  If the specified group is
310 	// NULL, we need to recreate one ourselves, and use *that*.  Note that if we're
311 	// caching a BBuffer that we requested earlier, we have to Recycle() that buffer
312 	// *before* deleting the buffer group, otherwise we'll deadlock waiting for that
313 	// buffer to be recycled!
314 	delete fBufferGroup;		// waits for all buffers to recycle
315 	if (newGroup != NULL)
316 	{
317 		// we were given a valid group; just use that one from now on
318 		fBufferGroup = newGroup;
319 	}
320 	else
321 	{
322 		// we were passed a NULL group pointer; that means we construct
323 		// our own buffer group to use from now on
324 		size_t size = fOutput.format.u.raw_audio.buffer_size;
325 		int32 count = int32(fLatency / BufferDuration() + 2);
326 		fBufferGroup = new BBufferGroup(size, count);
327 	}
328 
329 	return B_OK;
330 }
331 
332 
333 status_t
334 GameProducer::GetLatency(bigtime_t* out_latency)
335 {
336 	// report our *total* latency:  internal plus downstream plus scheduling
337 	*out_latency = EventLatency() + SchedulingLatency();
338 	return B_OK;
339 }
340 
341 
342 void
343 GameProducer::LateNoticeReceived(const media_source& what, bigtime_t how_much, bigtime_t performance_time)
344 {
345 	// If we're late, we need to catch up.  Respond in a manner appropriate to our
346 	// current run mode.
347 	if (what == fOutput.source)
348 	{
349 		if (RunMode() == B_RECORDING)
350 		{
351 			// A hardware capture node can't adjust; it simply emits buffers at
352 			// appropriate points.  We (partially) simulate this by not adjusting
353 			// our behavior upon receiving late notices -- after all, the hardware
354 			// can't choose to capture "sooner"....
355 		}
356 		else if (RunMode() == B_INCREASE_LATENCY)
357 		{
358 			// We're late, and our run mode dictates that we try to produce buffers
359 			// earlier in order to catch up.  This argues that the downstream nodes are
360 			// not properly reporting their latency, but there's not much we can do about
361 			// that at the moment, so we try to start producing buffers earlier to
362 			// compensate.
363 			fInternalLatency += how_much;
364 			SetEventLatency(fLatency + fInternalLatency);
365 		}
366 		else
367 		{
368 			// The other run modes dictate various strategies for sacrificing data quality
369 			// in the interests of timely data delivery.  The way *we* do this is to skip
370 			// a buffer, which catches us up in time by one buffer duration.
371 			size_t nSamples = fOutput.format.u.raw_audio.buffer_size / fFrameSize;
372 			fFramesSent += nSamples;
373 		}
374 	}
375 }
376 
377 
378 void
379 GameProducer::LatencyChanged(const media_source& source, const media_destination& destination, bigtime_t new_latency, uint32 flags)
380 {
381 	// something downstream changed latency, so we need to start producing
382 	// buffers earlier (or later) than we were previously.  Make sure that the
383 	// connection that changed is ours, and adjust to the new downstream
384 	// latency if so.
385 	if ((source == fOutput.source) && (destination == fOutput.destination))
386 	{
387 		fLatency = new_latency;
388 		SetEventLatency(fLatency + fInternalLatency);
389 	}
390 }
391 
392 
393 status_t
394 GameProducer::SetPlayRate(int32 numer, int32 denom)
395 {
396 	// Play rates are weird.  We don't support them
397 	return B_ERROR;
398 }
399 
400 
401 status_t
402 GameProducer::HandleMessage(int32 message, const void* data, size_t size)
403 {
404 	// We currently do not handle private messages
405 	return B_ERROR;
406 }
407 
408 
409 void
410 GameProducer::AdditionalBufferRequested(const media_source& source, media_buffer_id prev_buffer, bigtime_t prev_time, const media_seek_tag* prev_tag)
411 {
412 	// we don't support offline mode (yet...)
413 	return;
414 }
415 
416 
417 // BMediaEventLooper methods
418 void
419 GameProducer::NodeRegistered()
420 {
421 	// Start the BMediaEventLooper thread
422 	SetPriority(B_REAL_TIME_PRIORITY);
423 	Run();
424 
425 	// set up as much information about our output as we can
426 	fOutput.source.port = ControlPort();
427 	fOutput.source.id = 0;
428 	fOutput.node = Node();
429 	::strcpy(fOutput.name, "GameProducer Output");
430 }
431 
432 
433 void
434 GameProducer::SetRunMode(run_mode mode)
435 {
436 	// We don't support offline run mode, so broadcast an error if we're set to
437 	// B_OFFLINE.  Unfortunately, we can't actually reject the mode change...
438 	if (B_OFFLINE == mode)
439 	{
440 		ReportError(B_NODE_FAILED_SET_RUN_MODE);
441 	}
442 }
443 
444 
445 void
446 GameProducer::HandleEvent(const media_timed_event* event, bigtime_t lateness, bool realTimeEvent)
447 {
448 //	FPRINTF(stderr, "ToneProducer::HandleEvent\n");
449 	switch (event->type)
450 	{
451 	case BTimedEventQueue::B_START:
452 		// don't do anything if we're already running
453 		if (RunState() != B_STARTED)
454 		{
455 			// We are going to start sending buffers so setup the needed bookkeeping
456 			fFramesSent = 0;
457 			fStartTime = event->event_time;
458 			media_timed_event firstBufferEvent(fStartTime, BTimedEventQueue::B_HANDLE_BUFFER);
459 
460 			// Alternatively, we could call HandleEvent() directly with this event, to avoid a trip through
461 			// the event queue, like this:
462 			//
463 			//		this->HandleEvent(&firstBufferEvent, 0, false);
464 			//
465 			EventQueue()->AddEvent(firstBufferEvent);
466 		}
467 		break;
468 
469 	case BTimedEventQueue::B_STOP:
470 		// When we handle a stop, we must ensure that downstream consumers don't
471 		// get any more buffers from us.  This means we have to flush any pending
472 		// buffer-producing events from the queue.
473 		EventQueue()->FlushEvents(0, BTimedEventQueue::B_ALWAYS, true, BTimedEventQueue::B_HANDLE_BUFFER);
474 		break;
475 
476 	case BTimedEventQueue::B_HANDLE_BUFFER:
477 		{
478 			// make sure we're both started *and* connected before delivering a buffer
479 			if ((RunState() == BMediaEventLooper::B_STARTED) && (fOutput.destination != media_destination::null))
480 			{
481 				// Get the next buffer of data
482 				BBuffer* buffer = FillNextBuffer(event->event_time);
483 				if (buffer)
484 				{
485 					// send the buffer downstream if and only if output is enabled
486 					status_t err = B_ERROR;
487 					if (fOutputEnabled) err = SendBuffer(buffer, fOutput.destination);
488 					if (err)
489 					{
490 						// we need to recycle the buffer ourselves if output is disabled or
491 						// if the call to SendBuffer() fails
492 						buffer->Recycle();
493 					}
494 				}
495 
496 				// track how much media we've delivered so far
497 				size_t nFrames = fOutput.format.u.raw_audio.buffer_size / fFrameSize;
498 				fFramesSent += nFrames;
499 
500 				// The buffer is on its way; now schedule the next one to go
501 				bigtime_t nextEvent = fStartTime + bigtime_t(double(fFramesSent) / double(fOutput.format.u.raw_audio.frame_rate) * 1000000.0);
502 				media_timed_event nextBufferEvent(nextEvent, BTimedEventQueue::B_HANDLE_BUFFER);
503 				EventQueue()->AddEvent(nextBufferEvent);
504 			}
505 		}
506 		break;
507 
508 	default:
509 		break;
510 	}
511 }
512 
513 
514 // --------------------------------
515 //  GameProducer
516 BBuffer*
517 GameProducer::FillNextBuffer(bigtime_t event_time)
518 {
519 	// get a buffer from our buffer group
520 	BBuffer* buf = fBufferGroup->RequestBuffer(fOutput.format.u.raw_audio.buffer_size, BufferDuration());
521 
522 	// if we fail to get a buffer (for example, if the request times out), we skip this
523 	// buffer and go on to the next, to avoid locking up the control thread
524 	if (!buf) return NULL;
525 
526 	// we need to discribe the buffer
527 	int64 frames = int64(fOutput.format.u.raw_audio.buffer_size / fFrameSize);
528 	memset(buf->Data(), 0, fOutput.format.u.raw_audio.buffer_size);
529 
530 	// now fill the buffer with data, continuing where the last buffer left off
531 	fObject->Play(buf->Data(), frames);
532 
533 	// fill in the buffer header
534 	media_header* hdr = buf->Header();
535 	hdr->type = B_MEDIA_RAW_AUDIO;
536 	hdr->size_used = fOutput.format.u.raw_audio.buffer_size;
537 	hdr->time_source = TimeSource()->ID();
538 
539 	bigtime_t stamp;
540 	if (RunMode() == B_RECORDING)
541 	{
542 		// In B_RECORDING mode, we stamp with the capture time.  We're not
543 		// really a hardware capture node, but we simulate it by using the (precalculated)
544 		// time at which this buffer "should" have been created.
545 		stamp = event_time;
546 	}
547 	else
548 	{
549 		// okay, we're in one of the "live" performance run modes.  in these modes, we
550 		// stamp the buffer with the time at which the buffer should be rendered to the
551 		// output, not with the capture time.  fStartTime is the cached value of the
552 		// first buffer's performance time; we calculate this buffer's performance time as
553 		// an offset from that time, based on the amount of media we've created so far.
554 		// Recalculating every buffer like this avoids accumulation of error.
555 		stamp = fStartTime + bigtime_t(double(fFramesSent) / double(fOutput.format.u.raw_audio.frame_rate) * 1000000.0);
556 	}
557 	hdr->start_time = stamp;
558 
559 	return buf;
560 }
561 
562 
563