/* * Copyright 2002-2014 Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Christopher ML Zumwalt May (zummy@users.sf.net) */ /* A MediaKit producer node which mixes sound from the GameKit and sends them to the audio mixer */ #include "GameProducer.h" #include #include #include #include #include #include #include #include #include "GameSoundBuffer.h" #include "GameSoundDevice.h" #include "GSUtility.h" struct _gs_play { gs_id sound; bool* hook; _gs_play* next; _gs_play* previous; }; GameProducer::GameProducer(GameSoundBuffer* object, const gs_audio_format* format) : BMediaNode("GameProducer.h"), BBufferProducer(B_MEDIA_RAW_AUDIO), BMediaEventLooper(), fBufferGroup(NULL), fLatency(0), fInternalLatency(0), fOutputEnabled(true) { // initialize our preferred format object fPreferredFormat.type = B_MEDIA_RAW_AUDIO; fPreferredFormat.u.raw_audio.format = format->format; fPreferredFormat.u.raw_audio.channel_count = format->channel_count; fPreferredFormat.u.raw_audio.frame_rate = format->frame_rate; // Hertz fPreferredFormat.u.raw_audio.byte_order = format->byte_order; // fPreferredFormat.u.raw_audio.channel_mask // = B_CHANNEL_LEFT | B_CHANNEL_RIGHT; // fPreferredFormat.u.raw_audio.valid_bits = 32; // fPreferredFormat.u.raw_audio.matrix_mask = B_MATRIX_AMBISONIC_WXYZ; // we'll use the consumer's preferred buffer size, if any fPreferredFormat.u.raw_audio.buffer_size = media_raw_audio_format::wildcard.buffer_size; // we're not connected yet fOutput.destination = media_destination::null; fOutput.format = fPreferredFormat; fFrameSize = get_sample_size(format->format) * format->channel_count; fObject = object; } GameProducer::~GameProducer() { // Stop the BMediaEventLooper thread Quit(); } // BMediaNode methods BMediaAddOn* GameProducer::AddOn(int32* internal_id) const { return NULL; } // BBufferProducer methods status_t GameProducer::GetNextOutput(int32* cookie, media_output* _output) { // we currently support only one output if (0 != *cookie) return B_BAD_INDEX; *_output = fOutput; *cookie += 1; return B_OK; } status_t GameProducer::DisposeOutputCookie(int32 cookie) { // do nothing because our cookie is only an integer return B_OK; } void GameProducer::EnableOutput(const media_source& what, bool enabled, int32* _deprecated_) { // If I had more than one output, I'd have to walk my list of output records // to see which one matched the given source, and then enable/disable that // one. But this node only has one output, so I just make sure the given // source matches, then set the enable state accordingly. if (what == fOutput.source) { fOutputEnabled = enabled; } } status_t GameProducer::FormatSuggestionRequested(media_type type, int32 /*quality*/, media_format* format) { // insure that we received a format if (!format) return B_BAD_VALUE; // returning our preferred format *format = fPreferredFormat; // our format is supported if (type == B_MEDIA_UNKNOWN_TYPE) return B_OK; // we only support raw audo return (type != B_MEDIA_RAW_AUDIO) ? B_MEDIA_BAD_FORMAT : B_OK; } status_t GameProducer::FormatProposal(const media_source& output, media_format* format) { // doest the proposed output match our output? if (output != fOutput.source) return B_MEDIA_BAD_SOURCE; // return our preferred format *format = fPreferredFormat; // we will reject the proposal if the format is not audio media_type requestedType = format->type; if ((requestedType != B_MEDIA_UNKNOWN_TYPE) && (requestedType != B_MEDIA_RAW_AUDIO)) { return B_MEDIA_BAD_FORMAT; } return B_OK; // raw audio or wildcard type, either is okay by us } status_t GameProducer::PrepareToConnect(const media_source& what, const media_destination& where, media_format* format, media_source* _source, char* out_name) { // The format has been processed by the consumer at this point. We need // to insure the format is still acceptable and any wild care are filled in. // trying to connect something that isn't our source? if (what != fOutput.source) return B_MEDIA_BAD_SOURCE; // are we already connected? if (fOutput.destination != media_destination::null) return B_MEDIA_ALREADY_CONNECTED; // the format may not yet be fully specialized (the consumer might have // passed back some wildcards). Finish specializing it now, and return an // error if we don't support the requested format. if (format->type != B_MEDIA_RAW_AUDIO) return B_MEDIA_BAD_FORMAT; if (format->u.raw_audio.format != fPreferredFormat.u.raw_audio.format) return B_MEDIA_BAD_FORMAT; // check the buffer size, which may still be wildcarded if (format->u.raw_audio.buffer_size == media_raw_audio_format::wildcard.buffer_size) { format->u.raw_audio.buffer_size = 4096; // pick something comfortable to suggest } // Now reserve the connection, and return information about it fOutput.destination = where; fOutput.format = *format; *_source = fOutput.source; strlcpy(out_name, fOutput.name, B_MEDIA_NAME_LENGTH); return B_OK; } void GameProducer::Connect(status_t error, const media_source& source, const media_destination& destination, const media_format& format, char* ioName) { // If something earlier failed, Connect() might still be called, but with a // non-zero error code. When that happens we simply unreserve the // connection and do nothing else. if (error) { fOutput.destination = media_destination::null; fOutput.format = fPreferredFormat; return; } // Okay, the connection has been confirmed. Record the destination and // format that we agreed on, and report our connection name again. fOutput.destination = destination; fOutput.format = format; strlcpy(ioName, fOutput.name, B_MEDIA_NAME_LENGTH); // Now that we're connected, we can determine our downstream latency. // Do so, then make sure we get our events early enough. media_node_id id; FindLatencyFor(fOutput.destination, &fLatency, &id); if (!fBufferGroup) fBufferSize = fOutput.format.u.raw_audio.buffer_size; // Have to set it before latency calculating // Use a dry run to see how long it takes me to fill a buffer of data // The first step to setup the buffer bigtime_t start, produceLatency; int32 frames = int32(fBufferSize / fFrameSize); float* data = new float[frames * 2]; // Second, fill the buffer start = ::system_time(); for (int32 i = 0; i < frames; i++) { data[i * 2] = 0.8 * float(i / frames); data[i * 2 + 1] = 0.8 * float(i / frames); } produceLatency = ::system_time(); // Third, calculate the latency fInternalLatency = produceLatency - start; SetEventLatency(fLatency + fInternalLatency); // Finaily, clean up delete [] data; // reset our buffer duration, etc. to avoid later calculations bigtime_t duration = bigtime_t(1000000) * frames / bigtime_t(fOutput.format.u.raw_audio.frame_rate); SetBufferDuration(duration); // Set up the buffer group for our connection, as long as nobody handed us a // buffer group (via SetBufferGroup()) prior to this. if (!fBufferGroup) { int32 count = int32(fLatency / BufferDuration() + 2); fBufferGroup = new BBufferGroup(fBufferSize, count); } } void GameProducer::Disconnect(const media_source& what, const media_destination& where) { // Make sure that our connection is the one being disconnected if ((where == fOutput.destination) && (what == fOutput.source)) { fOutput.destination = media_destination::null; fOutput.format = fPreferredFormat; delete fBufferGroup; fBufferGroup = NULL; } } status_t GameProducer::FormatChangeRequested(const media_source& source, const media_destination& destination, media_format* io_format, int32* _deprecated_) { // we don't support any other formats, so we just reject any format changes. return B_ERROR; } status_t GameProducer::SetBufferGroup(const media_source& forSource, BBufferGroup* newGroup) { // verify that we didn't get bogus arguments before we proceed if (forSource != fOutput.source) return B_MEDIA_BAD_SOURCE; // Are we being passed the buffer group we're already using? if (newGroup == fBufferGroup) return B_OK; // Ahh, someone wants us to use a different buffer group. At this point we // delete the one we are using and use the specified one instead. If the // specified group is NULL, we need to recreate one ourselves, and use // *that*. Note that if we're caching a BBuffer that we requested earlier, // we have to Recycle() that buffer *before* deleting the buffer group, // otherwise we'll deadlock waiting for that buffer to be recycled! delete fBufferGroup; // waits for all buffers to recycle if (newGroup != NULL) { // we were given a valid group; just use that one from now on fBufferGroup = newGroup; // get buffer length from the first buffer BBuffer* buffers[1]; if (newGroup->GetBufferList(1, buffers) != B_OK) return B_BAD_VALUE; fBufferSize = buffers[0]->SizeAvailable(); } else { // we were passed a NULL group pointer; that means we construct // our own buffer group to use from now on fBufferSize = fOutput.format.u.raw_audio.buffer_size; int32 count = int32(fLatency / BufferDuration() + 2); fBufferGroup = new BBufferGroup(fBufferSize, count); } return B_OK; } status_t GameProducer::GetLatency(bigtime_t* _latency) { // report our *total* latency: internal plus downstream plus scheduling *_latency = EventLatency() + SchedulingLatency(); return B_OK; } void GameProducer::LateNoticeReceived(const media_source& what, bigtime_t howMuch, bigtime_t performanceDuration) { // If we're late, we need to catch up. Respond in a manner appropriate to // our current run mode. if (what == fOutput.source) { if (RunMode() == B_RECORDING) { // A hardware capture node can't adjust; it simply emits buffers at // appropriate points. We (partially) simulate this by not // adjusting our behavior upon receiving late notices -- after all, // the hardware can't choose to capture "sooner"... } else if (RunMode() == B_INCREASE_LATENCY) { // We're late, and our run mode dictates that we try to produce // buffers earlier in order to catch up. This argues that the // downstream nodes are not properly reporting their latency, but // there's not much we can do about that at the moment, so we try // to start producing buffers earlier to compensate. fInternalLatency += howMuch; SetEventLatency(fLatency + fInternalLatency); } else { // The other run modes dictate various strategies for sacrificing // data quality in the interests of timely data delivery. The way we // do this is to skip a buffer, which catches us up in time by one // buffer duration. size_t nSamples = fBufferSize / fFrameSize; fFramesSent += nSamples; } } } void GameProducer::LatencyChanged(const media_source& source, const media_destination& destination, bigtime_t new_latency, uint32 flags) { // something downstream changed latency, so we need to start producing // buffers earlier (or later) than we were previously. Make sure that the // connection that changed is ours, and adjust to the new downstream // latency if so. if ((source == fOutput.source) && (destination == fOutput.destination)) { fLatency = new_latency; SetEventLatency(fLatency + fInternalLatency); } } status_t GameProducer::SetPlayRate(int32 numerator, int32 denominator) { // Play rates are weird. We don't support them return B_ERROR; } status_t GameProducer::HandleMessage(int32 message, const void* data, size_t size) { // We currently do not handle private messages return B_ERROR; } void GameProducer::AdditionalBufferRequested(const media_source& source, media_buffer_id prev_buffer, bigtime_t prev_time, const media_seek_tag* prev_tag) { // we don't support offline mode (yet...) return; } // BMediaEventLooper methods void GameProducer::NodeRegistered() { // set up as much information about our output as we can fOutput.source.port = ControlPort(); fOutput.source.id = 0; fOutput.node = Node(); strlcpy(fOutput.name, "GameProducer Output", B_MEDIA_NAME_LENGTH); // Start the BMediaEventLooper thread SetPriority(B_REAL_TIME_PRIORITY); Run(); } void GameProducer::SetRunMode(run_mode mode) { // We don't support offline run mode, so broadcast an error if we're set to // B_OFFLINE. Unfortunately, we can't actually reject the mode change... if (B_OFFLINE == mode) { ReportError(B_NODE_FAILED_SET_RUN_MODE); } } void GameProducer::HandleEvent(const media_timed_event* event, bigtime_t lateness, bool realTimeEvent) { // FPRINTF(stderr, "ToneProducer::HandleEvent\n"); switch (event->type) { case BTimedEventQueue::B_START: // don't do anything if we're already running if (RunState() != B_STARTED) { // Going to start sending buffers so setup the needed bookkeeping fFramesSent = 0; fStartTime = event->event_time; media_timed_event firstBufferEvent(fStartTime, BTimedEventQueue::B_HANDLE_BUFFER); // Alternatively, we could call HandleEvent() directly with this // event, to avoid a trip through the event queue like this: // this->HandleEvent(&firstBufferEvent, 0, false); EventQueue()->AddEvent(firstBufferEvent); } break; case BTimedEventQueue::B_STOP: // When we handle a stop, we must ensure that downstream consumers don't // get any more buffers from us. This means we have to flush any // pending buffer-producing events from the queue. EventQueue()->FlushEvents(0, BTimedEventQueue::B_ALWAYS, true, BTimedEventQueue::B_HANDLE_BUFFER); break; case BTimedEventQueue::B_HANDLE_BUFFER: { // Ensure we're both started and connected before delivering buffer if ((RunState() == BMediaEventLooper::B_STARTED) && (fOutput.destination != media_destination::null)) { // Get the next buffer of data BBuffer* buffer = FillNextBuffer(event->event_time); if (buffer) { // Send the buffer downstream if output is enabled status_t err = B_ERROR; if (fOutputEnabled) { err = SendBuffer(buffer, fOutput.source, fOutput.destination); } if (err) { // we need to recycle the buffer ourselves if output is // disabled or if the call to SendBuffer() fails buffer->Recycle(); } } // track how much media we've delivered so far size_t nFrames = fBufferSize / fFrameSize; fFramesSent += nFrames; // The buffer is on its way; now schedule the next one to go bigtime_t nextEvent = fStartTime + bigtime_t(double(fFramesSent) / double(fOutput.format.u.raw_audio.frame_rate) * 1000000.0); media_timed_event nextBufferEvent(nextEvent, BTimedEventQueue::B_HANDLE_BUFFER); EventQueue()->AddEvent(nextBufferEvent); } } break; default: break; } } BBuffer* GameProducer::FillNextBuffer(bigtime_t event_time) { // get a buffer from our buffer group BBuffer* buf = fBufferGroup->RequestBuffer(fBufferSize, BufferDuration()); // if we fail to get a buffer (for example, if the request times out), we // skip this buffer and go on to the next, to avoid locking up the control // thread. if (!buf) return NULL; // we need to discribe the buffer int64 frames = int64(fBufferSize / fFrameSize); memset(buf->Data(), 0, fBufferSize); // now fill the buffer with data, continuing where the last buffer left off fObject->Play(buf->Data(), frames); // fill in the buffer header media_header* hdr = buf->Header(); hdr->type = B_MEDIA_RAW_AUDIO; hdr->size_used = fBufferSize; hdr->time_source = TimeSource()->ID(); bigtime_t stamp; if (RunMode() == B_RECORDING) { // In B_RECORDING mode, we stamp with the capture time. We're not // really a hardware capture node, but we simulate it by using the // (precalculated) time at which this buffer "should" have been created. stamp = event_time; } else { // okay, we're in one of the "live" performance run modes. in these // modes, we stamp the buffer with the time at which the buffer should // be rendered to the output, not with the capture time. fStartTime is // the cached value of the first buffer's performance time; we calculate // this buffer's performance time as an offset from that time, based on // the amount of media we've created so far. // Recalculating every buffer like this avoids accumulation of error. stamp = fStartTime + bigtime_t(double(fFramesSent) / double(fOutput.format.u.raw_audio.frame_rate) * 1000000.0); } hdr->start_time = stamp; return buf; }