diff --git a/src/add-ons/media/media-add-ons/mixer/AudioMixer.cpp b/src/add-ons/media/media-add-ons/mixer/AudioMixer.cpp new file mode 100644 index 0000000000..831f92c321 --- /dev/null +++ b/src/add-ons/media/media-add-ons/mixer/AudioMixer.cpp @@ -0,0 +1,1183 @@ +// AudioMixer.cpp +/* + + By David Shipman, 2002 + +*/ + +#include "AudioMixer.h" +#include "IOStructures.h" + +#include +#include +#include +#include +#include + +// AudioMixer support classes + +bool +AudioMixer::IsValidDest( media_destination dest) +{ + + int32 input_cookie = 0; + media_input input; + + while (GetNextInput(&input_cookie, &input) == B_OK) + { + + if (dest == input.destination) + return true; + + } + + return false; + +} + +bool +AudioMixer::IsValidSource( media_source source) +{ + + int32 output_cookie = 0; + media_output output; + + while (GetNextOutput(&output_cookie, &output) == B_OK) + { + + if (source == output.source) + return true; + + } + + return false; + +} + +// 'structors... + +AudioMixer::AudioMixer(BMediaAddOn *addOn) + : BMediaNode("OBOS Mixer"), + BBufferConsumer(B_MEDIA_RAW_AUDIO), + BBufferProducer(B_MEDIA_RAW_AUDIO), + BControllable(), + BMediaEventLooper(), + fAddOn(addOn), + fWeb(NULL), + fLatency(1), + fInternalLatency(1), + fStartTime(0), fNextEventTime(0), + fFramesSent(0), + fOutputEnabled(true), + fBufferGroup(NULL) + + +{ + + BMediaNode::AddNodeKind(B_SYSTEM_MIXER); + + // set up the preferred output format + + fPrefOutputFormat.type = B_MEDIA_RAW_AUDIO; + + fPrefOutputFormat.u.raw_audio.format = media_raw_audio_format::wildcard.format; // B_AUDIO_FLOAT + fPrefOutputFormat.u.raw_audio.frame_rate = 44100; //media_raw_audio_format::wildcard.frame_rate; + fPrefOutputFormat.u.raw_audio.byte_order = (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN; + + fPrefOutputFormat.u.raw_audio.buffer_size = media_raw_audio_format::wildcard.buffer_size; //2048 + fPrefOutputFormat.u.raw_audio.channel_count = 2; //media_raw_audio_format::wildcard.channel_count; + + // set up the preferred input format + + fPrefInputFormat.type = B_MEDIA_RAW_AUDIO; + + fPrefInputFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_FLOAT; + fPrefInputFormat.u.raw_audio.frame_rate = media_raw_audio_format::wildcard.frame_rate;//44100; // + fPrefInputFormat.u.raw_audio.byte_order = (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN; + + fPrefInputFormat.u.raw_audio.buffer_size = media_raw_audio_format::wildcard.buffer_size;//1024; // + fPrefInputFormat.u.raw_audio.channel_count = 2; //media_raw_audio_format::wildcard.channel_count; + + //fInput.source = media_source::null; + + // init i/o + + fOutput.destination = media_destination::null; + fOutput.format = fPrefOutputFormat; + fOutput.source.port = ControlPort(); + fOutput.source.id = 0; + + strcpy(fOutput.name, "Audio Mix"); + + input_channel *freechannel = new input_channel; + + freechannel->DataAvailable = false; + + media_input freeinput; + + freeinput.format = fPrefInputFormat; + freeinput.source = media_source::null; + freeinput.destination.port = ControlPort(); + freeinput.destination.id = 0; + + strcpy(freeinput.name, "Free Input"); + + freechannel->fInput = freeinput; + + fInputChannels.AddItem(freechannel); + +} + +AudioMixer::~AudioMixer() +{ + + BMediaEventLooper::Quit(); + SetParameterWeb(NULL); + fWeb = NULL; + + // any other cleanup goes here + +} + +// +// BMediaNode methods +// + +BMediaAddOn * +AudioMixer::AddOn(int32 *internal_id) const +{ + if(fAddOn) + { + *internal_id = 0; + return fAddOn; + } + else + return NULL; +} + +// +// BControllable methods +// + +status_t +AudioMixer::GetParameterValue( int32 id, bigtime_t* last_change, + void* value, size_t* ioSize) +{ + + switch (id) + { + + default: + + return B_ERROR; + + } + + return B_OK; + +} + +void +AudioMixer::SetParameterValue( int32 id, bigtime_t performance_time, + const void* value, size_t size) +{ + +} + +// +// BBufferConsumer methods +// + +status_t +AudioMixer::HandleMessage( int32 message, const void *data, size_t size) +{ + + // since we're using a mediaeventlooper, there shouldn't be any messages + + return B_ERROR; + +} + +status_t +AudioMixer::AcceptFormat(const media_destination &dest, media_format *format) +{ + + // we accept any raw audio + + if (IsValidDest(dest)) + { + if (format->type == B_MEDIA_RAW_AUDIO) + return B_OK; + else + return B_MEDIA_BAD_FORMAT; + } + else + return B_MEDIA_BAD_DESTINATION; + +} + +status_t +AudioMixer::GetNextInput(int32 *cookie, media_input *out_input) +{ + + if (*cookie < fInputChannels.CountItems() && *cookie >= 0) + { + input_channel *channel = (input_channel *)fInputChannels.ItemAt(*cookie); + *out_input = channel->fInput; + *cookie += 1; + return B_OK; + } + else + return B_BAD_INDEX; + +} + +void +AudioMixer::DisposeInputCookie(int32 cookie) +{ + + // nothing yet + +} + +void +AudioMixer::BufferReceived(BBuffer *buffer) +{ + + if (buffer) + { + if (buffer->Header()->type == B_MEDIA_PARAMETERS) + { + ApplyParameterData(buffer->Data(), buffer->SizeUsed()); + buffer->Recycle(); + } + else + { + + media_header *hdr = buffer->Header(); + + // check input + + input_channel *channel; + + int inputcount = fInputChannels.CountItems(); + + for (int i = 0; i < inputcount; i++) + { + channel = (input_channel *)fInputChannels.ItemAt(i); + if (channel->fInput.destination.id == hdr->destination) + { + + i = inputcount; + + bigtime_t now = TimeSource()->Now(); + bigtime_t perf_time = hdr->start_time; + bigtime_t how_early = perf_time - fLatency - now; + + if ((RunMode() != B_OFFLINE) && + (RunMode() != B_RECORDING) && + (how_early < 0)) + { + + NotifyLateProducer(channel->fInput.source, -how_early, perf_time); + + } + else + { + + size_t sample_size; + + switch (channel->fInput.format.u.raw_audio.format) + { + + case (media_raw_audio_format::B_AUDIO_FLOAT): + case (media_raw_audio_format::B_AUDIO_INT): + + sample_size = 4; + break; + + case (media_raw_audio_format::B_AUDIO_SHORT): + + sample_size = 2; + break; + + case (media_raw_audio_format::B_AUDIO_CHAR): + case (media_raw_audio_format::B_AUDIO_UCHAR): + + sample_size = 1; + break; + + } + + // calculate total byte offset for writing to the ringbuffer + // this takes account of the Event offset, as well as the buffer's start time + + size_t total_offset = int(channel->fEventOffset + ((((perf_time - fNextEventTime) / 1000000) * + channel->fInput.format.u.raw_audio.frame_rate) * + sample_size * channel->fInput.format.u.raw_audio.channel_count)) % int(channel->fDataSize); + + char *indata = (char *)buffer->Data(); + + if (buffer->SizeUsed() > (channel->fDataSize - total_offset)) + { + memcpy(channel->fData + total_offset, indata, channel->fDataSize - total_offset); + memcpy(channel->fData, indata + (channel->fDataSize - total_offset), buffer->SizeUsed() - (channel->fDataSize - total_offset)); + } + else + memcpy(channel->fData + total_offset, indata, buffer->SizeUsed()); + + } + + } + + buffer->Recycle(); + + if ((B_OFFLINE == RunMode()) && (B_DATA_AVAILABLE == channel->fProducerDataStatus)) + { + RequestAdditionalBuffer(channel->fInput.source, buffer); + } + + } + + } + + } +} + +void +AudioMixer::ProducerDataStatus( const media_destination &for_whom, + int32 status, bigtime_t at_performance_time) +{ + + if (IsValidDest(for_whom)) + { + media_timed_event event(at_performance_time, BTimedEventQueue::B_DATA_STATUS, + (void *)&for_whom, BTimedEventQueue::B_NO_CLEANUP, status, 0, NULL); + EventQueue()->AddEvent(event); + } + +} + +status_t +AudioMixer::GetLatencyFor( const media_destination &for_whom, bigtime_t *out_latency, + media_node_id *out_timesource) +{ + + if (! IsValidDest(for_whom)) + return B_MEDIA_BAD_DESTINATION; + + *out_latency = EventLatency() + SchedulingLatency(); + *out_timesource = TimeSource()->ID(); + + return B_OK; + +} + +status_t +AudioMixer::Connected( const media_source &producer, const media_destination &where, + const media_format &with_format, media_input *out_input) +{ + if (! IsValidDest(where)) + return B_MEDIA_BAD_DESTINATION; + + input_channel *channel = new input_channel; + + media_input freeinput; + + freeinput.format = with_format; + freeinput.source = producer; + freeinput.destination.port = ControlPort(); + freeinput.destination.id = fInputChannels.CountItems(); + + strcpy(freeinput.name, "Used Input"); // should be the name of the upstream producer + + channel->fInput = freeinput; + + fInputChannels.AddItem(channel); + + // update connection info + + *out_input = channel->fInput; + + channel->fData = (char *)malloc(131072); // CHANGE_THIS - we only need to allocate space for as many + channel->fDataSize = 131072; // input buffers as would fill an output - plus one + + memset(channel->fData, 0, channel->fDataSize); + + channel->DataAvailable = true; + + return B_OK; + +} + +void +AudioMixer::Disconnected( const media_source &producer, const media_destination &where) +{ + + input_channel *channel; + + // disconnected - remove the input from our list + + int inputcount = fInputChannels.CountItems(); + + for (int i = 0; i < inputcount; i++) + { + channel = (input_channel *)fInputChannels.ItemAt(i); + if (channel->fInput.destination == where) + { + + i = inputcount; + + // channel->DataAvailable = false; + // memset(channel->fData, 0, channel->fDataSize); + + delete channel->fData; + + delete channel; + + fInputChannels.RemoveItem(channel); + + } + + } + +} + +status_t +AudioMixer::FormatChanged( const media_source &producer, const media_destination &consumer, + int32 change_tag, const media_format &format) +{ + + // Format changed, we need to update our local info + // and change our ringbuffer size if needs be + // Not supported (yet) + + return B_ERROR; + +} + +// +// BBufferProducer methods +// + +status_t +AudioMixer::FormatSuggestionRequested( media_type type, int32 quality, media_format *format) +{ + + // downstream consumers are asking for a preferred format + // if suitable, return our previously constructed format def + + if (!format) + return B_BAD_VALUE; + + *format = fPrefOutputFormat; + + if (type == B_MEDIA_UNKNOWN_TYPE) + type = B_MEDIA_RAW_AUDIO; + + if (type != B_MEDIA_RAW_AUDIO) + return B_MEDIA_BAD_FORMAT; + else return B_OK; + +} + +status_t +AudioMixer::FormatProposal( const media_source &output, media_format *format) +{ + + // check to see if format is valid for the specified output + + if (! IsValidSource(output)) + return B_MEDIA_BAD_SOURCE; + + if ((format->type != B_MEDIA_UNKNOWN_TYPE) && (format->type != B_MEDIA_RAW_AUDIO)) + return B_MEDIA_BAD_FORMAT; + else return B_OK; + +} + +status_t +AudioMixer::FormatChangeRequested( const media_source &source, const media_destination &destination, + media_format *io_format, int32 *_deprecated_) +{ + + // try to change the format of the specified source to io_format + // not supported (yet) + + return B_ERROR; + +} + +status_t +AudioMixer::GetNextOutput( int32 *cookie, media_output *out_output) +{ + + // iterate through available outputs + + if (*cookie == 0) + { + *out_output = fOutput; + *cookie = 1; + return B_OK; + } + else return B_BAD_INDEX; + +} + +status_t +AudioMixer::DisposeOutputCookie( int32 cookie) +{ + + // we don't need to do anything here, since we haven't + // used the cookie for anything special... + + return B_OK; + +} + +status_t +AudioMixer::SetBufferGroup(const media_source &for_source, BBufferGroup *newGroup) +{ + + if (! IsValidSource(for_source)) + return B_MEDIA_BAD_SOURCE; + + if (newGroup == fBufferGroup) // we're already using this buffergroup + 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; + } + else + { + // we were passed a NULL group pointer; that means we construct + // our own buffer group to use from now on + AllocateBuffers(); + } + + + return B_OK; +} + +status_t +AudioMixer::GetLatency(bigtime_t *out_latency) +{ + + // report our *total* latency: internal plus downstream plus scheduling + *out_latency = EventLatency() + SchedulingLatency(); + return B_OK; + +} + +status_t +AudioMixer::PrepareToConnect(const media_source &what, const media_destination &where, + media_format *format, media_source *out_source, char *out_name) +{ + // PrepareToConnect() is the second stage of format negotiations that happens + // inside BMediaRoster::Connect(). At this point, the consumer's AcceptFormat() + // method has been called, and that node has potentially changed the proposed + // format. It may also have left wildcards in the format. PrepareToConnect() + // *must* fully specialize the format before returning! + + // trying to connect something that isn't our source? + if (! IsValidSource(what)) + 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; + + else if ((format->u.raw_audio.format != media_raw_audio_format::B_AUDIO_FLOAT) && // currently support floating point + (format->u.raw_audio.format != media_raw_audio_format::B_AUDIO_SHORT)) // and 16bit int audio + return B_MEDIA_BAD_FORMAT; + + // all fields MUST be validated here + // or else the consumer is prone to divide-by-zero errors + + if(format->u.raw_audio.frame_rate == media_raw_audio_format::wildcard.frame_rate) + format->u.raw_audio.frame_rate = fPrefOutputFormat.u.raw_audio.frame_rate; + + if(format->u.raw_audio.channel_count == media_raw_audio_format::wildcard.channel_count) + format->u.raw_audio.channel_count = fPrefOutputFormat.u.raw_audio.channel_count; +// format->u.raw_audio.channel_count = 2; + + if(format->u.raw_audio.byte_order == media_raw_audio_format::wildcard.byte_order) + format->u.raw_audio.byte_order = fPrefOutputFormat.u.raw_audio.byte_order; + + if (format->u.raw_audio.buffer_size == media_raw_audio_format::wildcard.buffer_size) + format->u.raw_audio.buffer_size = 1024; // pick something comfortable to suggest + + + // Now reserve the connection, and return information about it + fOutput.destination = where; + fOutput.format = *format; + *out_source = fOutput.source; + strncpy(out_name, fOutput.name, B_MEDIA_NAME_LENGTH); // strncpy? + + return B_OK; +} + +void +AudioMixer::Connect( status_t error, const media_source &source, const media_destination &dest, + const media_format &format, char *io_name) +{ + //FPRINTF(stderr, "AudioMixer::Connect\n"); + + // 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. + + // we need to check which output dest refers to - we only have one for now + + if (error) + { + fOutput.destination = media_destination::null; + fOutput.format = fPrefOutputFormat; + return; + } + + // connection is confirmed, record information and send output name + + fOutput.destination = dest; + fOutput.format = format; + strncpy(io_name, 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); + + // we need at least the length of a full output buffer's latency (I think?) + + size_t sample_size; + + switch (fOutput.format.u.raw_audio.format) + { + + case (media_raw_audio_format::B_AUDIO_FLOAT): + case (media_raw_audio_format::B_AUDIO_INT): + + sample_size = 4; + break; + + case (media_raw_audio_format::B_AUDIO_SHORT): + + sample_size = 2; + break; + + case (media_raw_audio_format::B_AUDIO_CHAR): + case (media_raw_audio_format::B_AUDIO_UCHAR): + + sample_size = 1; + break; + + } + + size_t framesPerBuffer = (fOutput.format.u.raw_audio.buffer_size / sample_size) / fOutput.format.u.raw_audio.channel_count; + + fInternalLatency = (framesPerBuffer / fOutput.format.u.raw_audio.frame_rate) * 1000000.0; + + // might need to tweak the latency + + SetEventLatency(fLatency + fInternalLatency); + + // reset our buffer duration, etc. to avoid later calculations + // crashes w/ divide-by-zero when connecting to a variety of nodes... + if (fOutput.format.u.raw_audio.frame_rate == media_raw_audio_format::wildcard.frame_rate) + { + fOutput.format.u.raw_audio.frame_rate = 44100; + } + + bigtime_t duration = bigtime_t(1000000) * framesPerBuffer / 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. That can happen, for example, + // if the consumer calls SetOutputBuffersFor() on us from within its Connected() + // method. + if (!fBufferGroup) AllocateBuffers(); +} + +void +AudioMixer::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)) // change later for multisource outputs + { + fOutput.destination = media_destination::null; + fOutput.format = fPrefOutputFormat; + delete fBufferGroup; + fBufferGroup = NULL; + } + +} + +void +AudioMixer::LateNoticeReceived(const media_source &what, bigtime_t how_much, bigtime_t performance_time) +{ + + // 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 += how_much; + 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. + // this should be the "drop data" mode - decrease precision could repeat the buffer ? + + //size_t nSamples = fOutput.format.u.raw_audio.buffer_size / sizeof(float); + //fFramesSent += (nSamples / 2); + + } + } +} + +void +AudioMixer::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; + } +} + + +// +// BMediaEventLooper methods +// + +void +AudioMixer::NodeRegistered() +{ + + Run(); + + fOutput.node = Node(); + fInput.node = Node(); + + SetPriority(120); + + SetParameterWeb(fWeb); + +} + +void +AudioMixer::HandleEvent( const media_timed_event *event, bigtime_t lateness, + bool realTimeEvent = false) +{ + + switch (event->type) + { + + case BTimedEventQueue::B_HANDLE_BUFFER: + { + + // check output + if (fOutput.destination != media_destination::null && fOutputEnabled) // this is in the wrong order too + { + + BBuffer *outbuffer = fBufferGroup->RequestBuffer(fOutput.format.u.raw_audio.buffer_size, BufferDuration()); + + if (outbuffer) + { + + int32 outChannels = fOutput.format.u.raw_audio.channel_count; + + // we have an output buffer - now it needs to be filled! + + switch (fOutput.format.u.raw_audio.format) + { + case media_raw_audio_format::B_AUDIO_FLOAT: + { + float *outdata = (float*)outbuffer->Data(); + + memset(outdata, 0, sizeof(outdata)); + + int numsamples = int(fOutput.format.u.raw_audio.buffer_size / sizeof(float)); + + for (int c = 0; c < fInputChannels.CountItems(); c++) + { + input_channel *channel = (input_channel *)fInputChannels.ItemAt(c); + + if (channel->DataAvailable == true) // formerly DataAvailable + { + int32 inChannels = channel->fInput.format.u.raw_audio.channel_count; + bool split = outChannels > inChannels; + bool mix = outChannels < inChannels; + + + if (fOutput.format.u.raw_audio.frame_rate == channel->fInput.format.u.raw_audio.frame_rate) + { + switch(channel->fInput.format.u.raw_audio.format) + { + + case media_raw_audio_format::B_AUDIO_FLOAT: + + if (split) + { } + else if (mix) + { + for (int s = 0; s < numsamples; s++) + { + // outdata[s] = (float *)(channel->fData)[s] + } + } + else + { + for (int s = 0; s < numsamples; s++) + { + float *indata = (float *)channel->fData; + int os = ((channel->fEventOffset / 4) + s) % int(channel->fDataSize / 4); + //outdata[s] = indata[ int(((channel->fEventOffset) * 0.25) + s) % int(channel->fDataSize / 4)]; + outdata[s] = float(indata[os]); + // fHost->PostMessage(int32(32767 * indata[os])); + //*(float *)channel->fData[channel->fEventOffset/4) + s) % channel->fDataSize]; + } + + channel->fEventOffset = (channel->fEventOffset + fOutput.format.u.raw_audio.buffer_size) % + channel->fDataSize; + + } + + break; + + case media_raw_audio_format::B_AUDIO_SHORT: + + if (split) + { } + else if (mix) + { + + } + else + { + for (int s = 0; s < numsamples; s++) + { + int16 *indata = (int16 *)channel->fData; + int os = ((channel->fEventOffset / 2) + s) % int(channel->fDataSize / 2); + //outdata[s] = indata[ int(((channel->fEventOffset) * 0.25) + s) % int(channel->fDataSize / 4)]; + outdata[s] = float(indata[os] / 32767.0); + //*(float *)channel->fData[channel->fEventOffset/4) + s) % channel->fDataSize]; + } + + channel->fEventOffset = (channel->fEventOffset + fOutput.format.u.raw_audio.buffer_size / 2) % + channel->fDataSize; + + } + + break; + + } // end formatswitch + + } // end same-samplerate condition + + } // data + + } // chan-loop + } // cur-case + + case media_raw_audio_format::B_AUDIO_SHORT: + + int16 *outdata = (int16*)outbuffer->Data(); + int numsamples = int(fOutput.format.u.raw_audio.buffer_size / sizeof(int16)); + + memset(outdata, 0, outbuffer->SizeAvailable()); + + for (int c = 0; c < fInputChannels.CountItems(); c++) + { + input_channel *channel = (input_channel *)fInputChannels.ItemAt(c); + + if (channel->DataAvailable == true) // only use if there are buffers waiting + { // CHANGE_THIS - we still get looped audio when there are no buffers + int32 inChannels = channel->fInput.format.u.raw_audio.channel_count; + bool split = outChannels > inChannels; + bool mix = outChannels < inChannels; + + if (fOutput.format.u.raw_audio.frame_rate == channel->fInput.format.u.raw_audio.frame_rate) + { + switch(channel->fInput.format.u.raw_audio.format) + { + + case media_raw_audio_format::B_AUDIO_FLOAT: + + if (split) + { } + else if (mix) + { + for (int s = 0; s < numsamples; s++) + { + // outdata[s] = (float *)(channel->fData)[s] + } + } + else + { + for (int s = 0; s < numsamples; s++) + { + float *indata = (float *)channel->fData; + int os = ((channel->fEventOffset / 4) + s) % int(channel->fDataSize / 4); + outdata[s] = outdata[s] + int16(32767 * indata[os]); + } + + channel->fEventOffset = (channel->fEventOffset + fOutput.format.u.raw_audio.buffer_size * 2) % + channel->fDataSize; + + } + + break; + + case media_raw_audio_format::B_AUDIO_SHORT: + + if (split) + { } + else if (mix) + { + + } + else + { + for (int s = 0; s < numsamples; s++) + { + int16 *indata = (int16 *)channel->fData; + int os = ((channel->fEventOffset / 2) + s) % int(channel->fDataSize / 2); + outdata[s] = outdata[s] + indata[os]; + } + + channel->fEventOffset = (channel->fEventOffset + (fOutput.format.u.raw_audio.buffer_size / 2)) % + channel->fDataSize; + + } + + break; + + } + } + + } + + } + } + // for each input, check the format, then mix into output buffer + + + media_header *outheader = outbuffer->Header(); + outheader->type = B_MEDIA_RAW_AUDIO; + outheader->size_used = fOutput.format.u.raw_audio.buffer_size; + outheader->time_source = TimeSource()->ID(); + + // if this is the first buffer, mark with the start time + // we need this to calculate the other buffer times + + if (fStartTime == 0) { + fStartTime = event->event_time; + fNextEventTime = fStartTime; + } + + bigtime_t stamp; + if (RunMode() == B_RECORDING) + stamp = event->event_time; // this is actually the same as the other modes, since we're using + else // a timedevent queue and adding events at + { + + // we're in a live performance mode + // use the start time we calculate at the end of the the mix loop + // this time is based on the offset of all media produced so far, + // plus fStartTime, which is the recorded time of our first event + + stamp = fNextEventTime; + + } + + outheader->start_time = stamp; + + status_t err = SendBuffer(outbuffer, fOutput.destination); + + if(err != B_OK) + outbuffer->Recycle(); + + } + + // even if we didn't get a buffer allocated, we still need to send the next event + + size_t sample_size; + + switch (fOutput.format.u.raw_audio.format) + { + + case (media_raw_audio_format::B_AUDIO_FLOAT): + case (media_raw_audio_format::B_AUDIO_INT): + + sample_size = 4; + break; + + case (media_raw_audio_format::B_AUDIO_SHORT): + + sample_size = 2; + break; + + case (media_raw_audio_format::B_AUDIO_CHAR): + case (media_raw_audio_format::B_AUDIO_UCHAR): + + sample_size = 1; + break; + + } + + int framesperbuffer = (fOutput.format.u.raw_audio.buffer_size / (sample_size * fOutput.format.u.raw_audio.channel_count)); + + fFramesSent += (framesperbuffer); + + // calculate the start time for the next event + + fNextEventTime = fStartTime + (double(fFramesSent / fOutput.format.u.raw_audio.frame_rate) * 1000000.0); + + media_timed_event nextBufferEvent(fNextEventTime, BTimedEventQueue::B_HANDLE_BUFFER); + EventQueue()->AddEvent(nextBufferEvent); + + + + } + + + break; + + } + + case BTimedEventQueue::B_START: + + if (RunState() != B_STARTED) + { + // We want to start sending buffers now, so we set up the buffer-sending bookkeeping + // and fire off the first "produce a buffer" event. + fStartTime = 0; + fFramesSent = 0; + + //fThread = spawn_thread(_mix_thread_, "audio mixer thread", B_REAL_TIME_PRIORITY, this); + + media_timed_event firstBufferEvent(event->event_time, 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); + + // fStartTime = event->event_time; + + } + + break; + + case BTimedEventQueue::B_STOP: + { + // stopped - don't process any more buffers, flush all buffers from eventqueue + + EventQueue()->FlushEvents(0, BTimedEventQueue::B_ALWAYS, true, BTimedEventQueue::B_HANDLE_BUFFER); + break; + } + + case BTimedEventQueue::B_DATA_STATUS: + { + + input_channel *channel; + + int inputcount = fInputChannels.CountItems(); + + for (int i = 0; i < inputcount; i++) + { + channel = (input_channel *)fInputChannels.ItemAt(i); + if (channel->fInput.destination == (media_destination &)event->pointer) + { + i = inputcount; + channel->fProducerDataStatus = event->data; + if (channel->fProducerDataStatus == B_DATA_AVAILABLE) + channel->DataAvailable = true; + else + channel->DataAvailable = false; + } + } + break; + } + + default: + + break; + + } + +} + +// +// AudioMixer methods +// + +void +AudioMixer::AllocateBuffers() +{ + + // allocate enough buffers to span our downstream latency, plus one + size_t size = fOutput.format.u.raw_audio.buffer_size; + int32 count = int32(fLatency / BufferDuration() + 1 + 1); + + fBufferGroup = new BBufferGroup(size, count); + +} + +// use this later for separate threads + +int32 +AudioMixer::_mix_thread_(void *data) +{ + return ((AudioMixer *)data)->MixThread(); +} + +int32 +AudioMixer::MixThread() +{ + while (1) + { + snooze(500000); + } + +} diff --git a/src/add-ons/media/media-add-ons/mixer/AudioMixer.h b/src/add-ons/media/media-add-ons/mixer/AudioMixer.h new file mode 100644 index 0000000000..d8e266c1ca --- /dev/null +++ b/src/add-ons/media/media-add-ons/mixer/AudioMixer.h @@ -0,0 +1,208 @@ +// AudioMixer.h +/* + + By David Shipman, 2002 + +*/ + +#ifndef _AUDIOMIXER_H +#define _AUDIOMIXER_H + +// forward declarations + +class input_channel; + +// includes + +#include +#include +#include +#include +#include +#include + +// ---------------- +// AudioMixer class + +class AudioMixer : + public BBufferConsumer, + public BBufferProducer, + public BControllable, + public BMediaEventLooper +{ + + public: + + AudioMixer(BMediaAddOn *addOn); + ~AudioMixer(); + + // Our own (AudioMixer) methods + + bool IsValidDest( media_destination dest); + bool IsValidSource( media_source source); + + status_t ResolveDest(int32 dest, input_channel *outchannel); + status_t ResolveDest(media_destination dest, input_channel *outchannel); + + void AllocateBuffers(); + + // BMediaNode methods + + BMediaAddOn * AddOn(int32*) const; + // void SetRunMode(run_mode); + // void Preroll(); + // void SetTimeSource(BTimeSource* time_source); + // status_t RequestCompleted(const media_request_info & info); + + protected: + + // BControllable methods + + status_t GetParameterValue( int32 id, bigtime_t* last_change, + void* value, + size_t* ioSize); + + void SetParameterValue( int32 id, bigtime_t when, + const void* value, + size_t size); + + // BBufferConsumer methods + + status_t HandleMessage( int32 message, + const void* data, + size_t size); + + status_t AcceptFormat( const media_destination &dest, media_format *format); + + status_t GetNextInput( int32 *cookie, + media_input *out_input); + + void DisposeInputCookie( int32 cookie); + + void BufferReceived( BBuffer *buffer); + + void ProducerDataStatus( const media_destination &for_whom, + int32 status, + bigtime_t at_performance_time); + + status_t GetLatencyFor( const media_destination &for_whom, + bigtime_t *out_latency, + media_node_id *out_timesource); + + status_t Connected( const media_source &producer, + const media_destination &where, + const media_format &with_format, + media_input *out_input); + + void Disconnected( const media_source &producer, + const media_destination &where); + + status_t FormatChanged( const media_source &producer, + const media_destination &consumer, + int32 change_tag, + const media_format &format); + + + // + // BBufferProducer methods + // + + status_t FormatSuggestionRequested( media_type type, + int32 quality, + media_format *format); + + status_t FormatProposal( + const media_source& output, + media_format* format); +// +// /* If the format isn't good, put a good format into *io_format and return error */ +// /* If format has wildcard, specialize to what you can do (and change). */ +// /* If you can change the format, return OK. */ +// /* The request comes from your destination sychronously, so you cannot ask it */ +// /* whether it likes it -- you should assume it will since it asked. */ + status_t FormatChangeRequested( + const media_source& source, + const media_destination& destination, + media_format* io_format, + int32* _deprecated_); + + status_t GetNextOutput( /* cookie starts as 0 */ + int32* cookie, + media_output* out_output); + + status_t DisposeOutputCookie( + int32 cookie); + + status_t SetBufferGroup( + const media_source& for_source, + BBufferGroup* group); + + status_t GetLatency( + bigtime_t* out_latency); + + status_t PrepareToConnect( + const media_source& what, + const media_destination& where, + media_format* format, + media_source* out_source, + char* out_name); + + void Connect( + status_t error, + const media_source& source, + const media_destination& destination, + const media_format& format, + char* io_name); + + void Disconnect( + const media_source& what, + const media_destination& where); + + void LateNoticeReceived( + const media_source& what, + bigtime_t how_much, + bigtime_t performance_time); + + void EnableOutput( + const media_source & what, + bool enabled, + int32* _deprecated_); + + // BMediaEventLooper methods + + virtual void NodeRegistered(); + + void HandleEvent( const media_timed_event *event, + bigtime_t lateness, + bool realTimeEvent = false); + + // handle mixing in separate thread + // not implemented (yet) + + static int32 _mix_thread_(void *data); + int32 MixThread(); + + private: + + thread_id fThread; // for launching mixthread + + media_input fInput; // descriptor of single input (temp) + media_output fOutput; // single output (temp) + + BMediaAddOn * fAddOn; + BParameterWeb * fWeb; // local pointer to parameterweb + + bigtime_t fLatency, fInternalLatency; // latency (total and internal) + bigtime_t fStartTime, fNextEventTime; // time node started, time of next (output) event + uint64 fFramesSent; // audio frames sent + bool fOutputEnabled; + + media_format fPrefInputFormat, fPrefOutputFormat; + BBufferGroup * fBufferGroup; + + + BList fInputChannels; + +}; + +#endif \ No newline at end of file diff --git a/src/add-ons/media/media-add-ons/mixer/IOStructures.h b/src/add-ons/media/media-add-ons/mixer/IOStructures.h new file mode 100644 index 0000000000..5d54ca71f5 --- /dev/null +++ b/src/add-ons/media/media-add-ons/mixer/IOStructures.h @@ -0,0 +1,27 @@ +// IOStructures.h +/* + + Structures to represent IO channels in the AudioMixer node + By David Shipman, 2002 + +*/ + +#ifndef _IO_STRUCT_H +#define _IO_STRUCT_H + +struct input_channel +{ + + public: + + media_input fInput; + int32 fProducerDataStatus; // status of upstream data + bool DataAvailable; // there is a buffer ready for mixing + char * fData; + + size_t fEventOffset; // offset (in bytes) + size_t fDataSize; // size of ringbuffer + +}; + +#endif; diff --git a/src/add-ons/media/media-add-ons/mixer/MixerAddOn.cpp b/src/add-ons/media/media-add-ons/mixer/MixerAddOn.cpp new file mode 100644 index 0000000000..dfa22e5517 --- /dev/null +++ b/src/add-ons/media/media-add-ons/mixer/MixerAddOn.cpp @@ -0,0 +1,84 @@ +// MixerAddOn.cpp +// +// David Shipman, 2002 +// allows AudioMixer to be used as an addon +// + +#include "AudioMixer.h" +#include "MixerAddOn.h" +#include +#include + +// instantiation function +extern "C" _EXPORT BMediaAddOn* make_media_addon(image_id image) { + return new AudioMixerAddon(image); +} + +// -------------------------------------------------------- // +// ctor/dtor +// -------------------------------------------------------- // + +AudioMixerAddon::~AudioMixerAddon() {} +AudioMixerAddon::AudioMixerAddon(image_id image) : + BMediaAddOn(image) {} + +// -------------------------------------------------------- // +// BMediaAddOn impl +// -------------------------------------------------------- // + +status_t AudioMixerAddon::InitCheck( + const char** out_failure_text) { + return B_OK; +} + +int32 AudioMixerAddon::CountFlavors() { + return 1; +} + +status_t AudioMixerAddon::GetFlavorAt( + int32 n, + const flavor_info** out_info) { + if(n) + return B_ERROR; + + flavor_info* pInfo = new flavor_info; + pInfo->internal_id = n; + pInfo->name = "AudioMixer"; + pInfo->info = + "AudioMixer media addon\n"; + "By David Shipman, 2002"; + pInfo->kinds = B_BUFFER_PRODUCER | B_BUFFER_CONSUMER | B_SYSTEM_MIXER | B_CONTROLLABLE; + pInfo->flavor_flags = 0; + pInfo->possible_count = 0; + + media_format* pFormat = new media_format; + pFormat->type = B_MEDIA_RAW_AUDIO; + pFormat->u.raw_audio = media_raw_audio_format::wildcard; + + pInfo->in_format_count = 1; + pInfo->in_formats = pFormat; + + pInfo->out_format_count = 1; + pInfo->out_formats = pFormat; + + *out_info = pInfo; + return B_OK; +} + +BMediaNode* AudioMixerAddon::InstantiateNodeFor( + const flavor_info* info, + BMessage* config, + status_t* out_error) { + + return new AudioMixer(this); +} + +status_t AudioMixerAddon::GetConfigurationFor( + BMediaNode* your_node, + BMessage* into_message) { + + // no config yet + return B_OK; +} + +// end \ No newline at end of file diff --git a/src/add-ons/media/media-add-ons/mixer/MixerAddOn.h b/src/add-ons/media/media-add-ons/mixer/MixerAddOn.h new file mode 100644 index 0000000000..d5551516d4 --- /dev/null +++ b/src/add-ons/media/media-add-ons/mixer/MixerAddOn.h @@ -0,0 +1,45 @@ +// MixerAddon.h +// David Shipman, 2002 +// +// Quick addon header for the Audio Mixer +// + +#ifndef __AudioMixerAddOn_H_ +#define __AudioMixerAddOn_H_ + +#include + +// -------------------------------------------------------- // + +class AudioMixerAddon : + public BMediaAddOn { + typedef BMediaAddOn _inherited; + +public: // ctor/dtor + virtual ~AudioMixerAddon(); + explicit AudioMixerAddon(image_id image); + +public: // BMediaAddOn impl +virtual status_t InitCheck( + const char** out_failure_text); +virtual int32 CountFlavors(); +virtual status_t GetFlavorAt( + int32 n, + const flavor_info ** out_info); +virtual BMediaNode * InstantiateNodeFor( + const flavor_info * info, + BMessage * config, + status_t * out_error); +virtual status_t GetConfigurationFor( + BMediaNode * your_node, + BMessage * into_message); + +virtual bool WantsAutoStart() { return false; } +virtual status_t AutoStart( + int in_count, + BMediaNode ** out_node, + int32 * out_internal_id, + bool * out_has_more) { return B_OK; } +}; + +#endif /*__AudioMixerAddOn_H_*/ diff --git a/src/add-ons/media/media-add-ons/mixer/license.txt b/src/add-ons/media/media-add-ons/mixer/license.txt new file mode 100644 index 0000000000..86a4268fa9 --- /dev/null +++ b/src/add-ons/media/media-add-ons/mixer/license.txt @@ -0,0 +1,31 @@ +---------------------- +Be Sample Code License +---------------------- + +Copyright 1991-1999, Be Incorporated. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions, and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions, and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR +TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/add-ons/media/media-add-ons/mixer/readme.txt b/src/add-ons/media/media-add-ons/mixer/readme.txt new file mode 100644 index 0000000000..c89f3979b6 --- /dev/null +++ b/src/add-ons/media/media-add-ons/mixer/readme.txt @@ -0,0 +1,41 @@ +OpenBeOS Audio Mixer +David Shipman, 2002 + +Overview + +The node is based on a mediaeventlooper, using the HandleEvent loop to compile +the mixed buffer output. +Each input creates a ringbuffer (fixed size "looped" buffer) for its input - this way +buffer contents can be placed in the ringbuffer and recycled immediately. + +Inputs are maintained using a list of input_channel structs - inputs are created/ +destroyed dynamically when producers connect/disconnect. + +Done + +- node functions as a replacement for the BeOS R5 mixer.media-addon +- IO/conversion between stereo B_AUDIO_FLOAT and B_AUDIO_SHORT streams + +Tested + +- Output to emu10k1 (SBLive) +- Input from CL-Amp and emu10k1-in + +Bugs + +- Soundplay, extractor nodes, and a variety of others connect, but sound really bad! +- When input stops, the current ringbuffer contents continue to be played + +To Do (lots) (vaguely in order of importance) + +- fix bugs +- support for remaining formats +- samplerate conversion +- Interface (BControllable) +- multithreading (separate mix thread) +- tidy up code + +Notes : + +Parts of this program are based on code under the Be Sample Code License. +This is included in the archive, in accordance with the license. \ No newline at end of file