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