diff --git a/src/add-ons/media/media-add-ons/mixer/AudioMixer.cpp b/src/add-ons/media/media-add-ons/mixer/AudioMixer.cpp index d017a2d8ed..30135336ca 100644 --- a/src/add-ons/media/media-add-ons/mixer/AudioMixer.cpp +++ b/src/add-ons/media/media-add-ons/mixer/AudioMixer.cpp @@ -1,9 +1,11 @@ /* * Copyright 2002 David Shipman, * Copyright 2003-2007 Marcus Overhagen - * Copyright 2007 Haiku Inc. All rights reserved. + * Copyright 2007-2009 Haiku Inc. All rights reserved. + * * Distributed under the terms of the MIT License. */ + #include "AudioMixer.h" #include "MixerCore.h" #include "MixerInput.h" @@ -31,1061 +33,6 @@ #define USE_MEDIA_FORMAT_WORKAROUND 1 -#if USE_MEDIA_FORMAT_WORKAROUND -static void -multi_audio_format_specialize(media_multi_audio_format *format, - const media_multi_audio_format *other); -#endif - -#define FORMAT_USER_DATA_TYPE 0x7294a8f3 -#define FORMAT_USER_DATA_MAGIC_1 0xc84173bd -#define FORMAT_USER_DATA_MAGIC_2 0x4af62b7d - -AudioMixer::AudioMixer(BMediaAddOn *addOn, bool isSystemMixer) - : BMediaNode("Audio Mixer"), - BBufferConsumer(B_MEDIA_RAW_AUDIO), - BBufferProducer(B_MEDIA_RAW_AUDIO), - BControllable(), - BMediaEventLooper(), - fAddOn(addOn), - fCore(new MixerCore(this)), - fWeb(0), - fBufferGroup(0), - fDownstreamLatency(1), - fInternalLatency(1), - fDisableStop(false) -{ - BMediaNode::AddNodeKind(B_SYSTEM_MIXER); - - // this is the default format used for all wildcard format SpecializeTo()s - fDefaultFormat.type = B_MEDIA_RAW_AUDIO; - fDefaultFormat.u.raw_audio.frame_rate = 96000; - fDefaultFormat.u.raw_audio.channel_count = 2; - fDefaultFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_FLOAT; - fDefaultFormat.u.raw_audio.byte_order = B_MEDIA_HOST_ENDIAN; - fDefaultFormat.u.raw_audio.buffer_size = 4096; - fDefaultFormat.u.raw_audio.channel_mask = 0; - fDefaultFormat.u.raw_audio.valid_bits = 0; - fDefaultFormat.u.raw_audio.matrix_mask = 0; - - if (isSystemMixer) { - // to get persistent settings, assign a settings file - BPath path; - if (B_OK != find_directory (B_USER_SETTINGS_DIRECTORY, &path)) - path.SetTo("/boot/home/config/settings/"); - path.Append("System Audio Mixer"); - fCore->Settings()->SetSettingsFile(path.Path()); - - // disable stop on the auto started (system) mixer - DisableNodeStop(); - } - - ApplySettings(); -} - -AudioMixer::~AudioMixer() -{ - BMediaEventLooper::Quit(); - SetParameterWeb(NULL); - - // stop the mixer - fCore->Lock(); - fCore->Stop(); - fCore->Unlock(); - - // disconnect all nodes from the mixer - // XXX todo - - delete fCore; - delete fBufferGroup; - - DEBUG_ONLY(fCore = 0; fBufferGroup = 0; fWeb = 0); -} - -void -AudioMixer::ApplySettings() -{ - fCore->Lock(); - fCore->SetOutputAttenuation(fCore->Settings()->AttenuateOutput() ? 0.708 : 1.0); - fCore->Unlock(); -} - -void -AudioMixer::DisableNodeStop() -{ - fDisableStop = true; -} - -// -// BMediaNode methods -// - -void AudioMixer::Stop(bigtime_t performance_time, bool immediate) -{ - if (fDisableStop) { - TRACE("AudioMixer STOP is disabled\n"); - return; - } else { - BMediaEventLooper::Stop(performance_time, immediate); - } -} - -BMediaAddOn * -AudioMixer::AddOn(int32 *internal_id) const -{ - *internal_id = 0; - return fAddOn; -} - -// -// 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 *ioFormat) -{ - PRINT_FORMAT("AudioMixer::AcceptFormat: ", *ioFormat); - - // check that the specified format is reasonable for the specified destination, and - // fill in any wildcard fields for which our BBufferConsumer has specific requirements. - - // we have multiple inputs with different IDs, but - // the port number must match our ControlPort() - if (dest.port != ControlPort()) - return B_MEDIA_BAD_DESTINATION; - - // specialize to raw audio format if necessary - if (ioFormat->type == B_MEDIA_UNKNOWN_TYPE) - ioFormat->type = B_MEDIA_RAW_AUDIO; - - // we require a raw audio format - if (ioFormat->type != B_MEDIA_RAW_AUDIO) - return B_MEDIA_BAD_FORMAT; - - // We do not have special requirements, but just in case - // another mixer is connecting to us and may need a hint - // to create a connection at optimal frame rate and - // channel count, we place this information in the user_data - fCore->Lock(); - MixerOutput *output = fCore->Output(); - uint32 channel_count = output ? output->MediaOutput().format.u.raw_audio.channel_count : 0; - float frame_rate = output ? output->MediaOutput().format.u.raw_audio.frame_rate : 0.0; - fCore->Unlock(); - ioFormat->user_data_type = FORMAT_USER_DATA_TYPE; - *(uint32 *)&ioFormat->user_data[0] = FORMAT_USER_DATA_MAGIC_1; - *(uint32 *)&ioFormat->user_data[4] = channel_count; - *(float *)&ioFormat->user_data[20] = frame_rate; - *(uint32 *)&ioFormat->user_data[44] = FORMAT_USER_DATA_MAGIC_2; - - return B_OK; -} - -status_t -AudioMixer::GetNextInput(int32 *cookie, media_input *out_input) -{ - TRACE("AudioMixer::GetNextInput\n"); - - // our 0th input is always a wildcard and free one - if (*cookie == 0) { - out_input->node = Node(); - out_input->source = media_source::null; - out_input->destination.port = ControlPort(); - out_input->destination.id = 0; - memset(&out_input->format, 0, sizeof(out_input->format)); - out_input->format.type = B_MEDIA_RAW_AUDIO; - strcpy(out_input->name, "Free Input"); - *cookie += 1; - return B_OK; - } - - // the other inputs are the currently connected ones - fCore->Lock(); - MixerInput *input; - input = fCore->Input(*cookie - 1); - if (!input) { - fCore->Unlock(); - return B_BAD_INDEX; - } - *out_input = input->MediaInput(); - *cookie += 1; - fCore->Unlock(); - return B_OK; -} - -void -AudioMixer::DisposeInputCookie(int32 cookie) -{ - // nothing to do -} - -void -AudioMixer::BufferReceived(BBuffer *buffer) -{ - - if (buffer->Header()->type == B_MEDIA_PARAMETERS) { - TRACE("Control Buffer Received\n"); - ApplyParameterData(buffer->Data(), buffer->SizeUsed()); - buffer->Recycle(); - return; - } - - //PRINT(4, "buffer received at %12Ld, should arrive at %12Ld, delta %12Ld\n", TimeSource()->Now(), buffer->Header()->start_time, TimeSource()->Now() - buffer->Header()->start_time); - - // Note: The following code is outcommented on purpose - // and is about to be modified at a later point - // HandleInputBuffer(buffer, 0); - // buffer->Recycle(); - // return; - - - // to receive the buffer at the right time, - // push it through the event looper - media_timed_event event(buffer->Header()->start_time, - BTimedEventQueue::B_HANDLE_BUFFER, - buffer, - BTimedEventQueue::B_RECYCLE_BUFFER); - EventQueue()->AddEvent(event); -} - - -void -AudioMixer::HandleInputBuffer(BBuffer *buffer, bigtime_t lateness) -{ - // Note: The following code is outcommented on purpose - // and is about to be modified at a later point - /* - if (lateness > 5000) { - printf("Received buffer with way to high lateness %Ld\n", lateness); - if (RunMode() != B_DROP_DATA) { - printf("sending notify\n"); - NotifyLateProducer(channel->fInput.source, lateness / 2, TimeSource()->Now()); - } else if (RunMode() == B_DROP_DATA) { - printf("dropping buffer\n"); - return; - } - } - */ - - // printf("Received buffer with lateness %Ld\n", lateness); - - fCore->Lock(); - fCore->BufferReceived(buffer, lateness); - fCore->Unlock(); - - // Note: The following code is outcommented on purpose - // and is about to be modified at a later point - /* - 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); - - // FIX_THIS - // the for_whom destination is not being sent correctly - verify in HandleEvent loop - - } -*/ -} - -status_t -AudioMixer::GetLatencyFor(const media_destination &for_whom, - bigtime_t *out_latency, - media_node_id *out_timesource) -{ - // we have multiple inputs with different IDs, but - // the port number must match our ControlPort() - if (for_whom.port != ControlPort()) - return B_MEDIA_BAD_DESTINATION; - - // return our event latency - this includes our internal + downstream - // latency, but _not_ the scheduling latency - *out_latency = EventLatency(); - *out_timesource = TimeSource()->ID(); - - printf("AudioMixer::GetLatencyFor %Ld, timesource is %ld\n", - *out_latency, - *out_timesource); - - return B_OK; -} - -status_t -AudioMixer::Connected(const media_source &producer, const media_destination &where, - const media_format &with_format, media_input *out_input) -{ - PRINT_FORMAT("AudioMixer::Connected: ", with_format); - - // workaround for a crashing bug in RealPlayer. to be proper, RealPlayer's - // BBufferProducer::PrepareToConnect() should have removed all wildcards. - if (out_input->format.u.raw_audio.frame_rate == 0) { - fprintf(stderr, "Audio Mixer Warning: " - "Producer (port %ld, id %ld) connected with frame_rate=0\n", - producer.port, - producer.id); - MixerOutput *output = fCore->Output(); - float frame_rate = output ? output->MediaOutput().format.u.raw_audio.frame_rate : 44100.0f; - out_input->format.u.raw_audio.frame_rate = frame_rate; - } - - // a BBufferProducer is connecting to our BBufferConsumer - - // incoming connections should always have an incoming ID=0, - // and the port number must match our ControlPort() - if (where.id != 0 || where.port != ControlPort()) - return B_MEDIA_BAD_DESTINATION; - - fCore->Lock(); - - // we assign a new id (!= 0) to the newly created input - out_input->destination.id = fCore->CreateInputID(); - - // We need to make sure that the outInput's name field contains a valid name, - // the name given the connection by the producer may still be an empty string. - // if the input has no name, assign one - if (strlen(out_input->name) == 0) - sprintf(out_input->name, "Input %ld", out_input->destination.id); - - // add a new input to the mixer engine - MixerInput *input; - input = fCore->AddInput(*out_input); - - fCore->Settings()->LoadConnectionSettings(input); - - fCore->Unlock(); - - // If we want the producer to use a specific BBufferGroup, we now need to call - // BMediaRoster::SetOutputBuffersFor() here to set the producer's buffer group. - // But we have no special buffer requirements anyway... - - UpdateParameterWeb(); - - return B_OK; -} - -void -AudioMixer::Disconnected(const media_source &producer, const media_destination &where) -{ - // One of our inputs has been disconnected - - // check if it is really belongs to us - if (where.port != ControlPort()) { - TRACE("AudioMixer::Disconnected wrong input port\n"); - return; - } - - fCore->Lock(); - - if (!fCore->RemoveInput(where.id)) { - TRACE("AudioMixer::Disconnected can't remove input\n"); - } - - fCore->Unlock(); - UpdateParameterWeb(); -} - -status_t -AudioMixer::FormatChanged(const media_source &producer, const media_destination &consumer, - int32 change_tag, const media_format &format) -{ - // at some point in the future (indicated by change_tag and RequestCompleted()), - // we will receive buffers in a different format - - TRACE("AudioMixer::FormatChanged\n"); - - if (consumer.port != ControlPort() || consumer.id == 0) - return B_MEDIA_BAD_DESTINATION; - - if (fCore->Settings()->RefuseInputFormatChange()) { - TRACE("AudioMixer::FormatChanged: input format change refused\n"); - return B_ERROR; - } - - // XXX we should not apply the format change at this point - - // tell core about format change - fCore->Lock(); - fCore->InputFormatChanged(consumer.id, format.u.raw_audio); - fCore->Unlock(); - - return B_OK; -} - -// -// BBufferProducer methods -// - -status_t -AudioMixer::FormatSuggestionRequested(media_type type, int32 quality, media_format *format) -{ - TRACE("AudioMixer::FormatSuggestionRequested\n"); - - // BBufferProducer function, a downstream consumer - // is asking for our output format - - if (type != B_MEDIA_RAW_AUDIO && type != B_MEDIA_UNKNOWN_TYPE) - return B_MEDIA_BAD_FORMAT; - - // we can produce any (wildcard) raw audio format - memset(format, 0, sizeof(*format)); - format->type = B_MEDIA_RAW_AUDIO; - return B_OK; -} - -status_t -AudioMixer::FormatProposal(const media_source &output, media_format *ioFormat) -{ - // BBufferProducer function, we implement this function to verify that the - // proposed media_format is suitable for the specified output. If any fields - // in the format are wildcards, and we have a specific requirement, adjust - // those fields to match our requirements before returning. - - TRACE("AudioMixer::FormatProposal\n"); - - // we only have one output (id=0, port=ControlPort()) - if (output.id != 0 || output.port != ControlPort()) - return B_MEDIA_BAD_SOURCE; - - // specialize to raw audio format if necessary - if (ioFormat->type == B_MEDIA_UNKNOWN_TYPE) - ioFormat->type = B_MEDIA_RAW_AUDIO; - - // we require a raw audio format - if (ioFormat->type != B_MEDIA_RAW_AUDIO) - return B_MEDIA_BAD_FORMAT; - - return B_OK; -} - -// 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 -AudioMixer::FormatChangeRequested(const media_source &source, const media_destination &destination, - media_format *io_format, int32 *_deprecated_) -{ - // the downstream consumer node (soundcard) requested that we produce - // another format, we need to check if the format is acceptable and - // remove any wildcards before returning OK. - - TRACE("AudioMixer::FormatChangeRequested\n"); - - if (fCore->Settings()->RefuseOutputFormatChange()) { - TRACE("AudioMixer::FormatChangeRequested: output format change refused\n"); - return B_ERROR; - } - - fCore->Lock(); - - MixerOutput *output = fCore->Output(); - if (!output) { - ERROR("AudioMixer::FormatChangeRequested: no output\n"); - goto err; - } - if (source != output->MediaOutput().source) { - ERROR("AudioMixer::FormatChangeRequested: wrong output source\n"); - goto err; - } - if (destination != output->MediaOutput().destination) { - ERROR("AudioMixer::FormatChangeRequested: wrong output destination (port %ld, id %ld), our is (port %ld, id %ld)\n", destination.port, destination.id, output->MediaOutput().destination.port, output->MediaOutput().destination.id); - if (destination.port == output->MediaOutput().destination.port && destination.id == output->MediaOutput().destination.id + 1) { - ERROR("AudioMixer::FormatChangeRequested: this might be the broken R5 multi audio add-on\n"); - goto err; - } else { - goto err; - } - } - if (io_format->type != B_MEDIA_RAW_AUDIO && io_format->type != B_MEDIA_UNKNOWN_TYPE) { - ERROR("AudioMixer::FormatChangeRequested: wrong format type\n"); - goto err; - } - - /* remove wildcards */ - #if USE_MEDIA_FORMAT_WORKAROUND - multi_audio_format_specialize(&io_format->u.raw_audio, &fDefaultFormat.u.raw_audio); - #else - io_format->SpecializeTo(&fDefaultFormat); - #endif - - media_node_id id; - FindLatencyFor(destination, &fDownstreamLatency, &id); - TRACE("AudioMixer: Downstream Latency is %Ld usecs\n", fDownstreamLatency); - - // SetDuration of one buffer - SetBufferDuration(buffer_duration(io_format->u.raw_audio)); - TRACE("AudioMixer: buffer duration is %Ld usecs\n", BufferDuration()); - - // Our internal latency is at least the length of a full output buffer - fInternalLatency = BufferDuration() + max(4500LL, bigtime_t(0.5 * BufferDuration())); - TRACE("AudioMixer: Internal latency is %Ld usecs\n", fInternalLatency); - - SetEventLatency(fDownstreamLatency + fInternalLatency); - - // we need to inform all connected *inputs* about *our* change in latency - PublishEventLatencyChange(); - - // XXX we might need to create more buffers, to span a larger downstream latency - - // apply latency change - fCore->SetTimingInfo(TimeSource(), fDownstreamLatency); - - // apply format change - fCore->OutputFormatChanged(io_format->u.raw_audio); - - delete fBufferGroup; - fBufferGroup = CreateBufferGroup(); - fCore->SetOutputBufferGroup(fBufferGroup); - - fCore->Unlock(); - return B_OK; - -err: - fCore->Unlock(); - return B_ERROR; -} - -status_t -AudioMixer::GetNextOutput(int32 *cookie, media_output *out_output) -{ - TRACE("AudioMixer::GetNextOutput\n"); - - if (*cookie != 0) - return B_BAD_INDEX; - - fCore->Lock(); - MixerOutput *output = fCore->Output(); - if (output) { - *out_output = output->MediaOutput(); - } else { - out_output->node = Node(); - out_output->source.port = ControlPort(); - out_output->source.id = 0; - out_output->destination = media_destination::null; - memset(&out_output->format, 0, sizeof(out_output->format)); - out_output->format.type = B_MEDIA_RAW_AUDIO; - strcpy(out_output->name, "Mixer Output"); - } - fCore->Unlock(); - - *cookie += 1; - return B_OK; -} - -status_t -AudioMixer::DisposeOutputCookie(int32 cookie) -{ - // nothing to do - return B_OK; -} - -status_t -AudioMixer::SetBufferGroup(const media_source &for_source, BBufferGroup *newGroup) -{ - PRINT("AudioMixer::SetBufferGroup\n"); - // the downstream consumer (soundcard) node asks us to use another - // BBufferGroup (might be NULL). We only have one output (id 0) - if (for_source.port != ControlPort() || for_source.id != 0) - return B_MEDIA_BAD_SOURCE; - - if (newGroup == fBufferGroup) { - // we're already using this buffergroup - return B_OK; - } - - fCore->Lock(); - if (!newGroup) - newGroup = CreateBufferGroup(); - fCore->SetOutputBufferGroup(newGroup); - delete fBufferGroup; - fBufferGroup = newGroup; - fCore->Unlock(); - - return B_OK; -} - -status_t -AudioMixer::GetLatency(bigtime_t *out_latency) -{ - // report our *total* latency: internal plus downstream plus scheduling - *out_latency = EventLatency() + SchedulingLatency(); - - TRACE("AudioMixer::GetLatency %Ld\n", *out_latency); - - return B_OK; -} - -void -AudioMixer::LatencyChanged(const media_source & source, const media_destination & destination, - bigtime_t new_latency, uint32 flags) -{ - if (source.port != ControlPort() || source.id != 0) { - ERROR("AudioMixer::LatencyChanged: received but has wrong source %ld/%ld\n", source.port, source.id); - return; - } - - TRACE("AudioMixer::LatencyChanged: downstream latency from %ld/%ld to %ld/%ld changed from %Ld to %Ld\n", - source.port, source.id, destination.port, destination.id, fDownstreamLatency, new_latency); - -#if DEBUG - { - media_node_id id; - bigtime_t l; - FindLatencyFor(destination, &l, &id); - TRACE("AudioMixer: Reported downstream Latency is %Ld usecs\n", l); - } -#endif - - fDownstreamLatency = new_latency; - SetEventLatency(fDownstreamLatency + fInternalLatency); - - // XXX we might need to create more buffers, to span a larger downstream latency - - fCore->Lock(); - fCore->SetTimingInfo(TimeSource(), fDownstreamLatency); - PublishEventLatencyChange(); - fCore->Unlock(); -} - -status_t -AudioMixer::PrepareToConnect(const media_source &what, - const media_destination &where, - media_format *format, - media_source *out_source, - char *out_name) -{ - TRACE("AudioMixer::PrepareToConnect\n"); - // 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! - // we also create the new output connection and return it in out_source. - - PRINT_FORMAT("AudioMixer::PrepareToConnect: suggested format", *format); - - // avoid loop connections - if (where.port == ControlPort()) - return B_MEDIA_BAD_SOURCE; - - // is the source valid? - if (what.port != ControlPort() || what.id != 0) - return B_MEDIA_BAD_SOURCE; - - // is the format acceptable? - if (format->type != B_MEDIA_RAW_AUDIO && format->type != B_MEDIA_UNKNOWN_TYPE) { - PRINT_FORMAT("AudioMixer::PrepareToConnect: bad format", *format); - return B_MEDIA_BAD_FORMAT; - } - - fCore->Lock(); - - // are we already connected? - if (fCore->Output() != 0) { - fCore->Unlock(); - ERROR("AudioMixer::PrepareToConnect: already connected\n"); - return B_MEDIA_ALREADY_CONNECTED; - } - - // It is possible that another mixer is connecting. - // To avoid using the default format, we use one of - // a) the format that it indicated as hint in the user_data, - // b) the output format of the system audio mixer - // c) the input format of the system DAC device - // d) if everything failes, keep the wildcard - if (format->u.raw_audio.channel_count == 0 - && format->u.raw_audio.frame_rate < 1 - && format->user_data_type == FORMAT_USER_DATA_TYPE - && *(uint32 *)&format->user_data[0] == FORMAT_USER_DATA_MAGIC_1 - && *(uint32 *)&format->user_data[44] == FORMAT_USER_DATA_MAGIC_2) { - // ok, a mixer is connecting - uint32 channel_count = *(uint32 *)&format->user_data[4]; - float frame_rate = *(float *)&format->user_data[20]; - if (channel_count > 0 && frame_rate > 0) { - // format is good, use it - format->u.raw_audio.channel_count = channel_count; - format->u.raw_audio.frame_rate = frame_rate; - } else { - // other mixer's output is probably not connected - media_node node; - BMediaRoster *roster = BMediaRoster::Roster(); - media_output out; - media_input in; - int32 count; - if (B_OK == roster->GetAudioMixer(&node) && B_OK == roster->GetConnectedOutputsFor(node, &out, 1, &count) && count == 1) { - // use mixer output format - format->u.raw_audio.channel_count = out.format.u.raw_audio.channel_count; - format->u.raw_audio.frame_rate = out.format.u.raw_audio.frame_rate; - } else if (B_OK == roster->GetAudioOutput(&node) && B_OK == roster->GetAllInputsFor(node, &in, 1, &count) && count == 1) { - // use DAC input format - format->u.raw_audio.channel_count = in.format.u.raw_audio.channel_count; - format->u.raw_audio.frame_rate = in.format.u.raw_audio.frame_rate; - } - } - } - - /* set source and suggest a name */ - *out_source = what; - strcpy(out_name, "Mixer Output"); - - /* remove wildcards */ - #if USE_MEDIA_FORMAT_WORKAROUND - multi_audio_format_specialize(&format->u.raw_audio, &fDefaultFormat.u.raw_audio); - #else - format->SpecializeTo(&fDefaultFormat); - #endif - - PRINT_FORMAT("AudioMixer::PrepareToConnect: final format", *format); - - /* add output to core */ - media_output output; - output.node = Node(); - output.source = *out_source; - output.destination = where; - output.format = *format; - strcpy(output.name, out_name); - - fCore->EnableOutput(false); - fCore->AddOutput(output); - - fCore->Unlock(); - return B_OK; -} - -void -AudioMixer::Connect(status_t error, const media_source &source, - const media_destination &dest, - const media_format &format, - char *io_name) -{ - TRACE("AudioMixer::Connect\n"); - - fCore->Lock(); - // are we still connected? - if (fCore->Output() == 0) { - fCore->Unlock(); - ERROR("AudioMixer::Connect: no longer connected\n"); - return; - } - fCore->Unlock(); - - if (error != B_OK) { - // if an error occured, remove output from core - ERROR("AudioMixer::Connect failed with error 0x%08lX, removing connection\n", error); - fCore->Lock(); - fCore->RemoveOutput(); - fCore->Unlock(); - return; - } - - /* Switch our prefered format to have the same - * frame_rate and channel count as the output. - */ - fDefaultFormat.u.raw_audio.frame_rate = format.u.raw_audio.frame_rate; - fDefaultFormat.u.raw_audio.channel_count = format.u.raw_audio.channel_count; - - // if the connection has no name, we set it now - if (strlen(io_name) == 0) - strcpy(io_name, "Mixer Output"); - - // Now that we're connected, we can determine our downstream latency. - media_node_id id; - FindLatencyFor(dest, &fDownstreamLatency, &id); - TRACE("AudioMixer: Downstream Latency is %Ld usecs\n", fDownstreamLatency); - - // SetDuration of one buffer - SetBufferDuration(buffer_duration(format.u.raw_audio)); - TRACE("AudioMixer: buffer duration is %Ld usecs\n", BufferDuration()); - - // Our internal latency is at least the length of a full output buffer - fInternalLatency = BufferDuration() + max(4500LL, bigtime_t(0.5 * BufferDuration())); - TRACE("AudioMixer: Internal latency is %Ld usecs\n", fInternalLatency); - - SetEventLatency(fDownstreamLatency + fInternalLatency); - - // we need to inform all connected *inputs* about *our* change in latency - PublishEventLatencyChange(); - - // 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) - fBufferGroup = CreateBufferGroup(); - - fCore->Lock(); - - ASSERT(fCore->Output() != 0); - - // our source should still be valid, too - ASSERT(fCore->Output()->MediaOutput().source.id == 0); - ASSERT(fCore->Output()->MediaOutput().source.port == ControlPort()); - - // BBufferConsumer::Connected() may return a different input for the - // newly created connection. The destination can have changed since - // AudioMixer::PrepareToConnect() and we need to update it. - fCore->Output()->MediaOutput().destination = dest; - - fCore->EnableOutput(true); - fCore->SetTimingInfo(TimeSource(), fDownstreamLatency); - fCore->SetOutputBufferGroup(fBufferGroup); - - fCore->Settings()->LoadConnectionSettings(fCore->Output()); - - fCore->Unlock(); - UpdateParameterWeb(); -} - -void -AudioMixer::Disconnect(const media_source &what, const media_destination &where) -{ - TRACE("AudioMixer::Disconnect\n"); - fCore->Lock(); - - // Make sure that our connection is the one being disconnected - MixerOutput * output = fCore->Output(); - if (!output || output->MediaOutput().node != Node() || output->MediaOutput().source != what || output->MediaOutput().destination != where) { - ERROR("AudioMixer::Disconnect can't disconnect (wrong connection)\n"); - fCore->Unlock(); - return; - } - - // Switch our prefered format back to default - // frame rate and channel count. - fDefaultFormat.u.raw_audio.frame_rate = 96000; - fDefaultFormat.u.raw_audio.channel_count = 2; - - // force a stop - fCore->Stop(); - - fCore->RemoveOutput(); - - // destroy buffer group - delete fBufferGroup; - fBufferGroup = 0; - fCore->SetOutputBufferGroup(0); - - fCore->Unlock(); - UpdateParameterWeb(); -} - - -void -AudioMixer::LateNoticeReceived(const media_source &what, bigtime_t how_much, bigtime_t performance_time) -{ - // We've produced some late buffers... Increase Latency - // is the only runmode in which we can do anything about this - - ERROR("AudioMixer::LateNoticeReceived, %Ld too late at %Ld\n", how_much, performance_time); - // Note: The following code is outcommented on purpose - // and is about to be modified at a later point - /* - if (what == fOutput.source) { - if (RunMode() == B_INCREASE_LATENCY) { - fInternalLatency += how_much; - - if (fInternalLatency > 50000) - fInternalLatency = 50000; - - printf("AudioMixer: increasing internal latency to %Ld usec\n", fInternalLatency); - SetEventLatency(fDownstreamLatency + fInternalLatency); - - PublishEventLatencyChange(); - } - } - */ -} - - -void -AudioMixer::EnableOutput(const media_source &what, bool enabled, int32 *_deprecated_) -{ - // we only have one output - if (what.id != 0 || what.port != ControlPort()) - return; - - fCore->Lock(); - fCore->EnableOutput(enabled); - fCore->Unlock(); -} - - -// -// BMediaEventLooper methods -// - -void -AudioMixer::NodeRegistered() -{ - Run(); - SetPriority(120); - UpdateParameterWeb(); -} - -void -AudioMixer::SetTimeSource(BTimeSource * time_source) -{ - TRACE("AudioMixer::SetTimeSource: timesource is now %ld\n", time_source->ID()); - fCore->Lock(); - fCore->SetTimingInfo(time_source, fDownstreamLatency); - fCore->Unlock(); -} - -void -AudioMixer::HandleEvent(const media_timed_event *event, bigtime_t lateness, bool realTimeEvent) -{ - switch (event->type) - { - - case BTimedEventQueue::B_HANDLE_BUFFER: - { - HandleInputBuffer((BBuffer *)event->pointer, lateness); - ((BBuffer *)event->pointer)->Recycle(); - break; - } - - case BTimedEventQueue::B_START: - { - TRACE("AudioMixer::HandleEvent: B_START\n"); - if (RunState() != B_STARTED) { - fCore->Lock(); - fCore->Start(); - fCore->Unlock(); - } - break; - } - - case BTimedEventQueue::B_STOP: - { - TRACE("AudioMixer::HandleEvent: B_STOP\n"); - // stopped - don't process any more buffers, flush all buffers from eventqueue - EventQueue()->FlushEvents(0, BTimedEventQueue::B_ALWAYS, true, BTimedEventQueue::B_HANDLE_BUFFER); - fCore->Lock(); - fCore->Stop(); - fCore->Unlock(); - break; - } - - case BTimedEventQueue::B_DATA_STATUS: - { - ERROR("DataStatus message\n"); - break; - } - - default: - break; - - } -} - -// -// AudioMixer methods -// - -void -AudioMixer::PublishEventLatencyChange() -{ - // our event (processing + downstream) latency has changed, - // and we need tell all inputs about this - - TRACE("AudioMixer::PublishEventLatencyChange\n"); - - fCore->Lock(); - - MixerInput *input; - for (int i = 0; (input = fCore->Input(i)) != 0; i++) { - TRACE("AudioMixer::PublishEventLatencyChange: SendLatencyChange, connection %ld/%ld to %ld/%ld event latency is now %Ld\n", - input->MediaInput().source.port, input->MediaInput().source.id, - input->MediaInput().destination.port, input->MediaInput().destination.id, - EventLatency()); - SendLatencyChange(input->MediaInput().source, input->MediaInput().destination, EventLatency()); - } - - fCore->Unlock(); -} - -BBufferGroup * -AudioMixer::CreateBufferGroup() -{ - // allocate enough buffers to span our downstream latency - // (plus one for rounding up), plus one extra - int32 count = int32(fDownstreamLatency / BufferDuration()) + 2; - - TRACE("AudioMixer::CreateBufferGroup: fDownstreamLatency %Ld, BufferDuration %Ld, buffer count = %ld\n", fDownstreamLatency, BufferDuration(), count); - - if (count < 3) - count = 3; - - fCore->Lock(); - uint32 size = fCore->Output()->MediaOutput().format.u.raw_audio.buffer_size; - fCore->Unlock(); - - TRACE("AudioMixer: allocating %ld buffers of %ld bytes each\n", count, size); - return new BBufferGroup(size, count); -} - -float -AudioMixer::dB_to_Gain(float db) -{ - TRACE("dB_to_Gain: dB in: %01.2f ", db); - if (fCore->Settings()->NonLinearGainSlider()) { - if (db > 0) { - db = db * (pow(abs(DB_MAX), (1.0 / DB_EXPONENT_POSITIVE)) / abs(DB_MAX)); - db = pow(db, DB_EXPONENT_POSITIVE); - } else { - db = -db; - db = db * (pow(abs(DB_MIN), (1.0 / DB_EXPONENT_NEGATIVE)) / abs(DB_MIN)); - db = pow(db, DB_EXPONENT_NEGATIVE); - db = -db; - } - } - TRACE("dB out: %01.2f\n", db); - return pow(10.0, db / 20.0); -} - -float -AudioMixer::Gain_to_dB(float gain) -{ - float db; - db = 20.0 * log10(gain); - if (fCore->Settings()->NonLinearGainSlider()) { - if (db > 0) { - db = pow(db, (1.0 / DB_EXPONENT_POSITIVE)); - db = db * (abs(DB_MAX) / pow(abs(DB_MAX), (1.0 / DB_EXPONENT_POSITIVE))); - } else { - db = -db; - db = pow(db, (1.0 / DB_EXPONENT_NEGATIVE)); - db = db * (abs(DB_MIN) / pow(abs(DB_MIN), (1.0 / DB_EXPONENT_NEGATIVE))); - db = -db; - } - } - return db; -} - -// -// BControllable methods -// - #define DB_TO_GAIN(db) dB_to_Gain((db)) #define GAIN_TO_DB(gain) Gain_to_dB((gain)) #define PERCENT_TO_GAIN(pct) ((pct) / 100.0) @@ -1125,8 +72,1130 @@ AudioMixer::Gain_to_dB(float gain) #define PARAM_IS_GAIN(id) (((id) & 0xf) == 0x8) #define PARAM_IS_BALANCE(id) (((id) & 0xf) == 0x9) +#if USE_MEDIA_FORMAT_WORKAROUND +static void +multi_audio_format_specialize(media_multi_audio_format *format, + const media_multi_audio_format *other); +#endif -status_t +#define FORMAT_USER_DATA_TYPE 0x7294a8f3 +#define FORMAT_USER_DATA_MAGIC_1 0xc84173bd +#define FORMAT_USER_DATA_MAGIC_2 0x4af62b7d + + +AudioMixer::AudioMixer(BMediaAddOn *addOn, bool isSystemMixer) + : BMediaNode("Audio Mixer"), + BBufferConsumer(B_MEDIA_RAW_AUDIO), + BBufferProducer(B_MEDIA_RAW_AUDIO), + BControllable(), + BMediaEventLooper(), + fAddOn(addOn), + fCore(new MixerCore(this)), + fWeb(0), + fBufferGroup(0), + fDownstreamLatency(1), + fInternalLatency(1), + fDisableStop(false) +{ + BMediaNode::AddNodeKind(B_SYSTEM_MIXER); + + // this is the default format used for all wildcard format SpecializeTo()s + fDefaultFormat.type = B_MEDIA_RAW_AUDIO; + fDefaultFormat.u.raw_audio.frame_rate = 96000; + fDefaultFormat.u.raw_audio.channel_count = 2; + fDefaultFormat.u.raw_audio.format = media_raw_audio_format::B_AUDIO_FLOAT; + fDefaultFormat.u.raw_audio.byte_order = B_MEDIA_HOST_ENDIAN; + fDefaultFormat.u.raw_audio.buffer_size = 4096; + fDefaultFormat.u.raw_audio.channel_mask = 0; + fDefaultFormat.u.raw_audio.valid_bits = 0; + fDefaultFormat.u.raw_audio.matrix_mask = 0; + + if (isSystemMixer) { + // to get persistent settings, assign a settings file + BPath path; + if (B_OK != find_directory (B_USER_SETTINGS_DIRECTORY, &path)) + path.SetTo("/boot/home/config/settings/"); + path.Append("System Audio Mixer"); + fCore->Settings()->SetSettingsFile(path.Path()); + + // disable stop on the auto started (system) mixer + DisableNodeStop(); + } + + ApplySettings(); +} + + +AudioMixer::~AudioMixer() +{ + BMediaEventLooper::Quit(); + SetParameterWeb(NULL); + + // stop the mixer + fCore->Lock(); + fCore->Stop(); + fCore->Unlock(); + + // disconnect all nodes from the mixer + // XXX todo + + delete fCore; + delete fBufferGroup; + + DEBUG_ONLY(fCore = 0; fBufferGroup = 0; fWeb = 0); +} + + +void +AudioMixer::ApplySettings() +{ + fCore->Lock(); + fCore->SetOutputAttenuation(fCore->Settings()->AttenuateOutput() ? 0.708 : 1.0); + fCore->Unlock(); +} + + +void +AudioMixer::DisableNodeStop() +{ + fDisableStop = true; +} + + +// #pragma mark - BMediaNode methods + + +void +AudioMixer::Stop(bigtime_t performance_time, bool immediate) +{ + if (fDisableStop) { + TRACE("AudioMixer STOP is disabled\n"); + return; + } else { + BMediaEventLooper::Stop(performance_time, immediate); + } +} + + +BMediaAddOn* +AudioMixer::AddOn(int32 *internal_id) const +{ + *internal_id = 0; + return fAddOn; +} + + +// #pragma mark - 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 *ioFormat) +{ + PRINT_FORMAT("AudioMixer::AcceptFormat: ", *ioFormat); + + // check that the specified format is reasonable for the specified destination, and + // fill in any wildcard fields for which our BBufferConsumer has specific requirements. + + // we have multiple inputs with different IDs, but + // the port number must match our ControlPort() + if (dest.port != ControlPort()) + return B_MEDIA_BAD_DESTINATION; + + // specialize to raw audio format if necessary + if (ioFormat->type == B_MEDIA_UNKNOWN_TYPE) + ioFormat->type = B_MEDIA_RAW_AUDIO; + + // we require a raw audio format + if (ioFormat->type != B_MEDIA_RAW_AUDIO) + return B_MEDIA_BAD_FORMAT; + + // We do not have special requirements, but just in case + // another mixer is connecting to us and may need a hint + // to create a connection at optimal frame rate and + // channel count, we place this information in the user_data + fCore->Lock(); + MixerOutput *output = fCore->Output(); + uint32 channel_count = output ? output->MediaOutput().format.u.raw_audio.channel_count : 0; + float frame_rate = output ? output->MediaOutput().format.u.raw_audio.frame_rate : 0.0; + fCore->Unlock(); + ioFormat->user_data_type = FORMAT_USER_DATA_TYPE; + *(uint32 *)&ioFormat->user_data[0] = FORMAT_USER_DATA_MAGIC_1; + *(uint32 *)&ioFormat->user_data[4] = channel_count; + *(float *)&ioFormat->user_data[20] = frame_rate; + *(uint32 *)&ioFormat->user_data[44] = FORMAT_USER_DATA_MAGIC_2; + + return B_OK; +} + + +status_t +AudioMixer::GetNextInput(int32 *cookie, media_input *out_input) +{ + TRACE("AudioMixer::GetNextInput\n"); + + // our 0th input is always a wildcard and free one + if (*cookie == 0) { + out_input->node = Node(); + out_input->source = media_source::null; + out_input->destination.port = ControlPort(); + out_input->destination.id = 0; + memset(&out_input->format, 0, sizeof(out_input->format)); + out_input->format.type = B_MEDIA_RAW_AUDIO; + strcpy(out_input->name, "Free Input"); + *cookie += 1; + return B_OK; + } + + // the other inputs are the currently connected ones + fCore->Lock(); + MixerInput *input; + input = fCore->Input(*cookie - 1); + if (!input) { + fCore->Unlock(); + return B_BAD_INDEX; + } + *out_input = input->MediaInput(); + *cookie += 1; + fCore->Unlock(); + return B_OK; +} + + +void +AudioMixer::DisposeInputCookie(int32 cookie) +{ + // nothing to do +} + + +void +AudioMixer::BufferReceived(BBuffer *buffer) +{ + + if (buffer->Header()->type == B_MEDIA_PARAMETERS) { + TRACE("Control Buffer Received\n"); + ApplyParameterData(buffer->Data(), buffer->SizeUsed()); + buffer->Recycle(); + return; + } + + //PRINT(4, "buffer received at %12Ld, should arrive at %12Ld, delta %12Ld\n", TimeSource()->Now(), buffer->Header()->start_time, TimeSource()->Now() - buffer->Header()->start_time); + + // Note: The following code is outcommented on purpose + // and is about to be modified at a later point + // HandleInputBuffer(buffer, 0); + // buffer->Recycle(); + // return; + + + // to receive the buffer at the right time, + // push it through the event looper + media_timed_event event(buffer->Header()->start_time, + BTimedEventQueue::B_HANDLE_BUFFER, buffer, + BTimedEventQueue::B_RECYCLE_BUFFER); + EventQueue()->AddEvent(event); +} + + +void +AudioMixer::HandleInputBuffer(BBuffer *buffer, bigtime_t lateness) +{ + // Note: The following code is outcommented on purpose + // and is about to be modified at a later point + /* + if (lateness > 5000) { + printf("Received buffer with way to high lateness %Ld\n", lateness); + if (RunMode() != B_DROP_DATA) { + printf("sending notify\n"); + NotifyLateProducer(channel->fInput.source, lateness / 2, TimeSource()->Now()); + } else if (RunMode() == B_DROP_DATA) { + printf("dropping buffer\n"); + return; + } + } + */ + + // printf("Received buffer with lateness %Ld\n", lateness); + + fCore->Lock(); + fCore->BufferReceived(buffer, lateness); + fCore->Unlock(); + + // Note: The following code is outcommented on purpose + // and is about to be modified at a later point + /* + 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); + + // FIX_THIS + // the for_whom destination is not being sent correctly - verify in HandleEvent loop + + } +*/ +} + + +status_t +AudioMixer::GetLatencyFor(const media_destination &for_whom, + bigtime_t *out_latency, media_node_id *out_timesource) +{ + // we have multiple inputs with different IDs, but + // the port number must match our ControlPort() + if (for_whom.port != ControlPort()) + return B_MEDIA_BAD_DESTINATION; + + // return our event latency - this includes our internal + downstream + // latency, but _not_ the scheduling latency + *out_latency = EventLatency(); + *out_timesource = TimeSource()->ID(); + + printf("AudioMixer::GetLatencyFor %Ld, timesource is %ld\n", + *out_latency, *out_timesource); + + return B_OK; +} + + +status_t +AudioMixer::Connected(const media_source &producer, + const media_destination &where, const media_format &with_format, + media_input *out_input) +{ + PRINT_FORMAT("AudioMixer::Connected: ", with_format); + + // workaround for a crashing bug in RealPlayer. to be proper, RealPlayer's + // BBufferProducer::PrepareToConnect() should have removed all wildcards. + if (out_input->format.u.raw_audio.frame_rate == 0) { + fprintf(stderr, "Audio Mixer Warning: " + "Producer (port %ld, id %ld) connected with frame_rate=0\n", + producer.port, producer.id); + MixerOutput *output = fCore->Output(); + float frame_rate = output + ? output->MediaOutput().format.u.raw_audio.frame_rate : 44100.0f; + out_input->format.u.raw_audio.frame_rate = frame_rate; + } + + // a BBufferProducer is connecting to our BBufferConsumer + + // incoming connections should always have an incoming ID=0, + // and the port number must match our ControlPort() + if (where.id != 0 || where.port != ControlPort()) + return B_MEDIA_BAD_DESTINATION; + + fCore->Lock(); + + // we assign a new id (!= 0) to the newly created input + out_input->destination.id = fCore->CreateInputID(); + + // We need to make sure that the outInput's name field contains a valid + // name, the name given the connection by the producer may still be an + // empty string. + // if the input has no name, assign one + if (strlen(out_input->name) == 0) + sprintf(out_input->name, "Input %ld", out_input->destination.id); + + // add a new input to the mixer engine + MixerInput *input; + input = fCore->AddInput(*out_input); + + fCore->Settings()->LoadConnectionSettings(input); + + fCore->Unlock(); + + // If we want the producer to use a specific BBufferGroup, we now need + // to call BMediaRoster::SetOutputBuffersFor() here to set the producer's + // buffer group. + // But we have no special buffer requirements anyway... + + UpdateParameterWeb(); + + return B_OK; +} + + +void +AudioMixer::Disconnected(const media_source &producer, + const media_destination &where) +{ + // One of our inputs has been disconnected + + // check if it is really belongs to us + if (where.port != ControlPort()) { + TRACE("AudioMixer::Disconnected wrong input port\n"); + return; + } + + fCore->Lock(); + + if (!fCore->RemoveInput(where.id)) { + TRACE("AudioMixer::Disconnected can't remove input\n"); + } + + fCore->Unlock(); + UpdateParameterWeb(); +} + + +status_t +AudioMixer::FormatChanged(const media_source &producer, + const media_destination &consumer, int32 change_tag, + const media_format &format) +{ + // at some point in the future (indicated by change_tag and RequestCompleted()), + // we will receive buffers in a different format + + TRACE("AudioMixer::FormatChanged\n"); + + if (consumer.port != ControlPort() || consumer.id == 0) + return B_MEDIA_BAD_DESTINATION; + + if (fCore->Settings()->RefuseInputFormatChange()) { + TRACE("AudioMixer::FormatChanged: input format change refused\n"); + return B_ERROR; + } + + // XXX we should not apply the format change at this point + + // tell core about format change + fCore->Lock(); + fCore->InputFormatChanged(consumer.id, format.u.raw_audio); + fCore->Unlock(); + + return B_OK; +} + + +// #pragma mark - BBufferProducer methods + + +status_t +AudioMixer::FormatSuggestionRequested(media_type type, int32 quality, + media_format *format) +{ + TRACE("AudioMixer::FormatSuggestionRequested\n"); + + // BBufferProducer function, a downstream consumer + // is asking for our output format + + if (type != B_MEDIA_RAW_AUDIO && type != B_MEDIA_UNKNOWN_TYPE) + return B_MEDIA_BAD_FORMAT; + + // we can produce any (wildcard) raw audio format + memset(format, 0, sizeof(*format)); + format->type = B_MEDIA_RAW_AUDIO; + return B_OK; +} + + +status_t +AudioMixer::FormatProposal(const media_source &output, media_format *ioFormat) +{ + // BBufferProducer function, we implement this function to verify that the + // proposed media_format is suitable for the specified output. If any fields + // in the format are wildcards, and we have a specific requirement, adjust + // those fields to match our requirements before returning. + + TRACE("AudioMixer::FormatProposal\n"); + + // we only have one output (id=0, port=ControlPort()) + if (output.id != 0 || output.port != ControlPort()) + return B_MEDIA_BAD_SOURCE; + + // specialize to raw audio format if necessary + if (ioFormat->type == B_MEDIA_UNKNOWN_TYPE) + ioFormat->type = B_MEDIA_RAW_AUDIO; + + // we require a raw audio format + if (ioFormat->type != B_MEDIA_RAW_AUDIO) + return B_MEDIA_BAD_FORMAT; + + return B_OK; +} + +/*! 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 +AudioMixer::FormatChangeRequested(const media_source &source, + const media_destination &destination, media_format *io_format, + int32 *_deprecated_) +{ + // the downstream consumer node (soundcard) requested that we produce + // another format, we need to check if the format is acceptable and + // remove any wildcards before returning OK. + + TRACE("AudioMixer::FormatChangeRequested\n"); + + if (fCore->Settings()->RefuseOutputFormatChange()) { + TRACE("AudioMixer::FormatChangeRequested: output format change refused\n"); + return B_ERROR; + } + + fCore->Lock(); + + MixerOutput *output = fCore->Output(); + if (!output) { + ERROR("AudioMixer::FormatChangeRequested: no output\n"); + goto err; + } + if (source != output->MediaOutput().source) { + ERROR("AudioMixer::FormatChangeRequested: wrong output source\n"); + goto err; + } + if (destination != output->MediaOutput().destination) { + ERROR("AudioMixer::FormatChangeRequested: wrong output destination " + "(port %ld, id %ld), our is (port %ld, id %ld)\n", destination.port, + destination.id, output->MediaOutput().destination.port, + output->MediaOutput().destination.id); + if (destination.port == output->MediaOutput().destination.port + && destination.id == output->MediaOutput().destination.id + 1) { + ERROR("AudioMixer::FormatChangeRequested: this might be the broken " + "R5 multi audio add-on\n"); + } + goto err; + } + if (io_format->type != B_MEDIA_RAW_AUDIO + && io_format->type != B_MEDIA_UNKNOWN_TYPE) { + ERROR("AudioMixer::FormatChangeRequested: wrong format type\n"); + goto err; + } + + /* remove wildcards */ +#if USE_MEDIA_FORMAT_WORKAROUND + multi_audio_format_specialize(&io_format->u.raw_audio, + &fDefaultFormat.u.raw_audio); +#else + io_format->SpecializeTo(&fDefaultFormat); +#endif + + media_node_id id; + FindLatencyFor(destination, &fDownstreamLatency, &id); + TRACE("AudioMixer: Downstream Latency is %Ld usecs\n", fDownstreamLatency); + + // SetDuration of one buffer + SetBufferDuration(buffer_duration(io_format->u.raw_audio)); + TRACE("AudioMixer: buffer duration is %Ld usecs\n", BufferDuration()); + + // Our internal latency is at least the length of a full output buffer + fInternalLatency = BufferDuration() + + max(4500LL, bigtime_t(0.5 * BufferDuration())); + TRACE("AudioMixer: Internal latency is %Ld usecs\n", fInternalLatency); + + SetEventLatency(fDownstreamLatency + fInternalLatency); + + // we need to inform all connected *inputs* about *our* change in latency + PublishEventLatencyChange(); + + // TODO: we might need to create more buffers, to span a larger downstream + // latency + + // apply latency change + fCore->SetTimingInfo(TimeSource(), fDownstreamLatency); + + // apply format change + fCore->OutputFormatChanged(io_format->u.raw_audio); + + delete fBufferGroup; + fBufferGroup = CreateBufferGroup(); + fCore->SetOutputBufferGroup(fBufferGroup); + + fCore->Unlock(); + return B_OK; + +err: + fCore->Unlock(); + return B_ERROR; +} + + +status_t +AudioMixer::GetNextOutput(int32 *cookie, media_output *out_output) +{ + TRACE("AudioMixer::GetNextOutput\n"); + + if (*cookie != 0) + return B_BAD_INDEX; + + fCore->Lock(); + MixerOutput *output = fCore->Output(); + if (output) { + *out_output = output->MediaOutput(); + } else { + out_output->node = Node(); + out_output->source.port = ControlPort(); + out_output->source.id = 0; + out_output->destination = media_destination::null; + memset(&out_output->format, 0, sizeof(out_output->format)); + out_output->format.type = B_MEDIA_RAW_AUDIO; + strcpy(out_output->name, "Mixer Output"); + } + fCore->Unlock(); + + *cookie += 1; + return B_OK; +} + + +status_t +AudioMixer::DisposeOutputCookie(int32 cookie) +{ + // nothing to do + return B_OK; +} + + +status_t +AudioMixer::SetBufferGroup(const media_source &for_source, + BBufferGroup *newGroup) +{ + PRINT("AudioMixer::SetBufferGroup\n"); + // the downstream consumer (soundcard) node asks us to use another + // BBufferGroup (might be NULL). We only have one output (id 0) + if (for_source.port != ControlPort() || for_source.id != 0) + return B_MEDIA_BAD_SOURCE; + + if (newGroup == fBufferGroup) { + // we're already using this buffergroup + return B_OK; + } + + fCore->Lock(); + if (!newGroup) + newGroup = CreateBufferGroup(); + fCore->SetOutputBufferGroup(newGroup); + delete fBufferGroup; + fBufferGroup = newGroup; + fCore->Unlock(); + + return B_OK; +} + + +status_t +AudioMixer::GetLatency(bigtime_t *out_latency) +{ + // report our *total* latency: internal plus downstream plus scheduling + *out_latency = EventLatency() + SchedulingLatency(); + + TRACE("AudioMixer::GetLatency %Ld\n", *out_latency); + + return B_OK; +} + + +void +AudioMixer::LatencyChanged(const media_source& source, + const media_destination& destination, bigtime_t new_latency, uint32 flags) +{ + if (source.port != ControlPort() || source.id != 0) { + ERROR("AudioMixer::LatencyChanged: received but has wrong source " + "%ld/%ld\n", source.port, source.id); + return; + } + + TRACE("AudioMixer::LatencyChanged: downstream latency from %ld/%ld to " + "%ld/%ld changed from %Ld to %Ld\n", source.port, source.id, + destination.port, destination.id, fDownstreamLatency, new_latency); + +#if DEBUG + { + media_node_id id; + bigtime_t l; + FindLatencyFor(destination, &l, &id); + TRACE("AudioMixer: Reported downstream Latency is %Ld usecs\n", l); + } +#endif + + fDownstreamLatency = new_latency; + SetEventLatency(fDownstreamLatency + fInternalLatency); + + // XXX we might need to create more buffers, to span a larger downstream + // latency + + fCore->Lock(); + fCore->SetTimingInfo(TimeSource(), fDownstreamLatency); + PublishEventLatencyChange(); + fCore->Unlock(); +} + +status_t +AudioMixer::PrepareToConnect(const media_source &what, + const media_destination &where, media_format *format, + media_source *out_source, char *out_name) +{ + TRACE("AudioMixer::PrepareToConnect\n"); + // 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! + // we also create the new output connection and return it in out_source. + + PRINT_FORMAT("AudioMixer::PrepareToConnect: suggested format", *format); + + // avoid loop connections + if (where.port == ControlPort()) + return B_MEDIA_BAD_SOURCE; + + // is the source valid? + if (what.port != ControlPort() || what.id != 0) + return B_MEDIA_BAD_SOURCE; + + // is the format acceptable? + if (format->type != B_MEDIA_RAW_AUDIO + && format->type != B_MEDIA_UNKNOWN_TYPE) { + PRINT_FORMAT("AudioMixer::PrepareToConnect: bad format", *format); + return B_MEDIA_BAD_FORMAT; + } + + fCore->Lock(); + + // are we already connected? + if (fCore->Output() != 0) { + fCore->Unlock(); + ERROR("AudioMixer::PrepareToConnect: already connected\n"); + return B_MEDIA_ALREADY_CONNECTED; + } + + // It is possible that another mixer is connecting. + // To avoid using the default format, we use one of + // a) the format that it indicated as hint in the user_data, + // b) the output format of the system audio mixer + // c) the input format of the system DAC device + // d) if everything failes, keep the wildcard + if (format->u.raw_audio.channel_count == 0 + && format->u.raw_audio.frame_rate < 1 + && format->user_data_type == FORMAT_USER_DATA_TYPE + && *(uint32 *)&format->user_data[0] == FORMAT_USER_DATA_MAGIC_1 + && *(uint32 *)&format->user_data[44] == FORMAT_USER_DATA_MAGIC_2) { + // ok, a mixer is connecting + uint32 channel_count = *(uint32 *)&format->user_data[4]; + float frame_rate = *(float *)&format->user_data[20]; + if (channel_count > 0 && frame_rate > 0) { + // format is good, use it + format->u.raw_audio.channel_count = channel_count; + format->u.raw_audio.frame_rate = frame_rate; + } else { + // other mixer's output is probably not connected + media_node node; + BMediaRoster *roster = BMediaRoster::Roster(); + media_output out; + media_input in; + int32 count; + if (roster->GetAudioMixer(&node) == B_OK + && roster->GetConnectedOutputsFor(node, &out, 1, &count) + == B_OK + && count == 1) { + // use mixer output format + format->u.raw_audio.channel_count + = out.format.u.raw_audio.channel_count; + format->u.raw_audio.frame_rate + = out.format.u.raw_audio.frame_rate; + } else if (roster->GetAudioOutput(&node) == B_OK + && roster->GetAllInputsFor(node, &in, 1, &count) == B_OK + && count == 1) { + // use DAC input format + format->u.raw_audio.channel_count + = in.format.u.raw_audio.channel_count; + format->u.raw_audio.frame_rate + = in.format.u.raw_audio.frame_rate; + } + } + } + + /* set source and suggest a name */ + *out_source = what; + strcpy(out_name, "Mixer Output"); + + /* remove wildcards */ +#if USE_MEDIA_FORMAT_WORKAROUND + multi_audio_format_specialize(&format->u.raw_audio, + &fDefaultFormat.u.raw_audio); +#else + format->SpecializeTo(&fDefaultFormat); +#endif + + PRINT_FORMAT("AudioMixer::PrepareToConnect: final format", *format); + + /* add output to core */ + media_output output; + output.node = Node(); + output.source = *out_source; + output.destination = where; + output.format = *format; + strcpy(output.name, out_name); + + fCore->EnableOutput(false); + fCore->AddOutput(output); + + fCore->Unlock(); + return B_OK; +} + + +void +AudioMixer::Connect(status_t error, const media_source &source, + const media_destination &dest, const media_format &format, char *io_name) +{ + TRACE("AudioMixer::Connect\n"); + + fCore->Lock(); + // are we still connected? + if (fCore->Output() == 0) { + fCore->Unlock(); + ERROR("AudioMixer::Connect: no longer connected\n"); + return; + } + fCore->Unlock(); + + if (error != B_OK) { + // if an error occured, remove output from core + ERROR("AudioMixer::Connect failed with error 0x%08lX, removing " + "connection\n", error); + fCore->Lock(); + fCore->RemoveOutput(); + fCore->Unlock(); + return; + } + + /* Switch our prefered format to have the same + * frame_rate and channel count as the output. + */ + fDefaultFormat.u.raw_audio.frame_rate = format.u.raw_audio.frame_rate; + fDefaultFormat.u.raw_audio.channel_count = format.u.raw_audio.channel_count; + + // if the connection has no name, we set it now + if (strlen(io_name) == 0) + strcpy(io_name, "Mixer Output"); + + // Now that we're connected, we can determine our downstream latency. + media_node_id id; + FindLatencyFor(dest, &fDownstreamLatency, &id); + TRACE("AudioMixer: Downstream Latency is %Ld usecs\n", fDownstreamLatency); + + // SetDuration of one buffer + SetBufferDuration(buffer_duration(format.u.raw_audio)); + TRACE("AudioMixer: buffer duration is %Ld usecs\n", BufferDuration()); + + // Our internal latency is at least the length of a full output buffer + fInternalLatency = BufferDuration() + + max(4500LL, bigtime_t(0.5 * BufferDuration())); + TRACE("AudioMixer: Internal latency is %Ld usecs\n", fInternalLatency); + + SetEventLatency(fDownstreamLatency + fInternalLatency); + + // we need to inform all connected *inputs* about *our* change in latency + PublishEventLatencyChange(); + + // 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) + fBufferGroup = CreateBufferGroup(); + + fCore->Lock(); + + ASSERT(fCore->Output() != 0); + + // our source should still be valid, too + ASSERT(fCore->Output()->MediaOutput().source.id == 0); + ASSERT(fCore->Output()->MediaOutput().source.port == ControlPort()); + + // BBufferConsumer::Connected() may return a different input for the + // newly created connection. The destination can have changed since + // AudioMixer::PrepareToConnect() and we need to update it. + fCore->Output()->MediaOutput().destination = dest; + + fCore->EnableOutput(true); + fCore->SetTimingInfo(TimeSource(), fDownstreamLatency); + fCore->SetOutputBufferGroup(fBufferGroup); + + fCore->Settings()->LoadConnectionSettings(fCore->Output()); + + fCore->Unlock(); + UpdateParameterWeb(); +} + +void +AudioMixer::Disconnect(const media_source &what, const media_destination &where) +{ + TRACE("AudioMixer::Disconnect\n"); + fCore->Lock(); + + // Make sure that our connection is the one being disconnected + MixerOutput * output = fCore->Output(); + if (!output + || output->MediaOutput().node != Node() + || output->MediaOutput().source != what + || output->MediaOutput().destination != where) { + ERROR("AudioMixer::Disconnect can't disconnect (wrong connection)\n"); + fCore->Unlock(); + return; + } + + // Switch our prefered format back to default + // frame rate and channel count. + fDefaultFormat.u.raw_audio.frame_rate = 96000; + fDefaultFormat.u.raw_audio.channel_count = 2; + + // force a stop + fCore->Stop(); + + fCore->RemoveOutput(); + + // destroy buffer group + delete fBufferGroup; + fBufferGroup = 0; + fCore->SetOutputBufferGroup(0); + + fCore->Unlock(); + UpdateParameterWeb(); +} + + +void +AudioMixer::LateNoticeReceived(const media_source &what, bigtime_t how_much, + bigtime_t performance_time) +{ + // We've produced some late buffers... Increase Latency + // is the only runmode in which we can do anything about this + + ERROR("AudioMixer::LateNoticeReceived, %Ld too late at %Ld\n", how_much, + performance_time); + // Note: The following code is outcommented on purpose + // and is about to be modified at a later point + /* + if (what == fOutput.source) { + if (RunMode() == B_INCREASE_LATENCY) { + fInternalLatency += how_much; + + if (fInternalLatency > 50000) + fInternalLatency = 50000; + + printf("AudioMixer: increasing internal latency to %Ld usec\n", fInternalLatency); + SetEventLatency(fDownstreamLatency + fInternalLatency); + + PublishEventLatencyChange(); + } + } + */ +} + + +void +AudioMixer::EnableOutput(const media_source &what, bool enabled, + int32 *_deprecated_) +{ + // we only have one output + if (what.id != 0 || what.port != ControlPort()) + return; + + fCore->Lock(); + fCore->EnableOutput(enabled); + fCore->Unlock(); +} + + +// #pragma mark - BMediaEventLooper methods + + +void +AudioMixer::NodeRegistered() +{ + Run(); + SetPriority(120); + UpdateParameterWeb(); +} + + +void +AudioMixer::SetTimeSource(BTimeSource * time_source) +{ + TRACE("AudioMixer::SetTimeSource: timesource is now %ld\n", + time_source->ID()); + fCore->Lock(); + fCore->SetTimingInfo(time_source, fDownstreamLatency); + fCore->Unlock(); +} + + +void +AudioMixer::HandleEvent(const media_timed_event *event, bigtime_t lateness, + bool realTimeEvent) +{ + switch (event->type) { + case BTimedEventQueue::B_HANDLE_BUFFER: + { + HandleInputBuffer((BBuffer *)event->pointer, lateness); + ((BBuffer *)event->pointer)->Recycle(); + break; + } + + case BTimedEventQueue::B_START: + { + TRACE("AudioMixer::HandleEvent: B_START\n"); + if (RunState() != B_STARTED) { + fCore->Lock(); + fCore->Start(); + fCore->Unlock(); + } + break; + } + + case BTimedEventQueue::B_STOP: + { + TRACE("AudioMixer::HandleEvent: B_STOP\n"); + // stopped - don't process any more buffers, flush all buffers + // from event queue + EventQueue()->FlushEvents(0, BTimedEventQueue::B_ALWAYS, true, + BTimedEventQueue::B_HANDLE_BUFFER); + fCore->Lock(); + fCore->Stop(); + fCore->Unlock(); + break; + } + + case BTimedEventQueue::B_DATA_STATUS: + { + ERROR("DataStatus message\n"); + break; + } + + default: + break; + } +} + + +// #pragma mark - AudioMixer methods + + +void +AudioMixer::PublishEventLatencyChange() +{ + // our event (processing + downstream) latency has changed, + // and we need tell all inputs about this + + TRACE("AudioMixer::PublishEventLatencyChange\n"); + + fCore->Lock(); + + MixerInput *input; + for (int i = 0; (input = fCore->Input(i)) != 0; i++) { + TRACE("AudioMixer::PublishEventLatencyChange: SendLatencyChange, " + "connection %ld/%ld to %ld/%ld event latency is now %Ld\n", + input->MediaInput().source.port, input->MediaInput().source.id, + input->MediaInput().destination.port, + input->MediaInput().destination.id, EventLatency()); + SendLatencyChange(input->MediaInput().source, + input->MediaInput().destination, EventLatency()); + } + + fCore->Unlock(); +} + + +BBufferGroup* +AudioMixer::CreateBufferGroup() +{ + // allocate enough buffers to span our downstream latency + // (plus one for rounding up), plus one extra + int32 count = int32(fDownstreamLatency / BufferDuration()) + 2; + + TRACE("AudioMixer::CreateBufferGroup: fDownstreamLatency %Ld, " + "BufferDuration %Ld, buffer count = %ld\n", fDownstreamLatency, + BufferDuration(), count); + + if (count < 3) + count = 3; + + fCore->Lock(); + uint32 size = fCore->Output()->MediaOutput().format.u.raw_audio.buffer_size; + fCore->Unlock(); + + TRACE("AudioMixer: allocating %ld buffers of %ld bytes each\n", + count, size); + return new BBufferGroup(size, count); +} + + +float +AudioMixer::dB_to_Gain(float db) +{ + TRACE("dB_to_Gain: dB in: %01.2f ", db); + if (fCore->Settings()->NonLinearGainSlider()) { + if (db > 0) { + db = db * (pow(abs(DB_MAX), (1.0 / DB_EXPONENT_POSITIVE)) + / abs(DB_MAX)); + db = pow(db, DB_EXPONENT_POSITIVE); + } else { + db = -db; + db = db * (pow(abs(DB_MIN), (1.0 / DB_EXPONENT_NEGATIVE)) + / abs(DB_MIN)); + db = pow(db, DB_EXPONENT_NEGATIVE); + db = -db; + } + } + TRACE("dB out: %01.2f\n", db); + return pow(10.0, db / 20.0); +} + + +float +AudioMixer::Gain_to_dB(float gain) +{ + float db; + db = 20.0 * log10(gain); + if (fCore->Settings()->NonLinearGainSlider()) { + if (db > 0) { + db = pow(db, (1.0 / DB_EXPONENT_POSITIVE)); + db = db * (abs(DB_MAX) / pow(abs(DB_MAX), + (1.0 / DB_EXPONENT_POSITIVE))); + } else { + db = -db; + db = pow(db, (1.0 / DB_EXPONENT_NEGATIVE)); + db = db * (abs(DB_MIN) / pow(abs(DB_MIN), + (1.0 / DB_EXPONENT_NEGATIVE))); + db = -db; + } + } + return db; +} + + +// #pragma markß - BControllable methods + + +status_t AudioMixer::GetParameterValue(int32 id, bigtime_t *last_change, void *value, size_t *ioSize) { @@ -1570,7 +1639,7 @@ AudioMixer::SetParameterValue(int32 id, bigtime_t when, const void *value, } else { input->RemoveInputChannelDestination(PARAM_CHAN(id), PARAM_DST(id)); } - // XXX this is really annoying + // TODO: this is really annoying // The slider count of the gain control needs to be changed, // but calling SetChannelCount(input->GetMixerChannelCount()) // on it has no effect on remote BParameterWebs in other apps. @@ -1582,18 +1651,21 @@ AudioMixer::SetParameterValue(int32 id, bigtime_t when, const void *value, } fCore->Settings()->SaveConnectionSettings(input); } + BroadcastNewParameterValue(when, id, const_cast(value), size); + err: fCore->Unlock(); if (update) UpdateParameterWeb(); } + void AudioMixer::UpdateParameterWeb() { fCore->Lock(); - BParameterWeb *web = new BParameterWeb(); + BParameterWeb *web = new BParameterWeb(); BParameterGroup *top; BParameterGroup *outputchannels; BParameterGroup *inputchannels; @@ -1604,12 +1676,12 @@ AudioMixer::UpdateParameterWeb() MixerInput *in; MixerOutput *out; char buf[50]; - + top = web->MakeGroup("Gain Controls"); out = fCore->Output(); group = top->MakeGroup(""); - group->MakeNullParameter(PARAM_STR1(0), B_MEDIA_RAW_AUDIO, "Master Output", B_WEB_BUFFER_INPUT); + group->MakeNullParameter(PARAM_STR1(0), B_MEDIA_RAW_AUDIO, "Master Output", B_WEB_BUFFER_INPUT); if (!out) { group->MakeNullParameter(PARAM_STR2(0), B_MEDIA_RAW_AUDIO, "not connected", B_GENERIC); } else { @@ -1622,14 +1694,14 @@ AudioMixer::UpdateParameterWeb() } else { // multi channel control group->MakeContinuousParameter(PARAM_GAIN(0), B_MEDIA_RAW_AUDIO, "Gain", B_MASTER_GAIN, "dB", DB_MIN, DB_MAX, 0.1) - ->SetChannelCount(out->GetOutputChannelCount()); + ->SetChannelCount(out->GetOutputChannelCount()); } - group->MakeNullParameter(PARAM_STR3(0), B_MEDIA_RAW_AUDIO, "To Output", B_WEB_BUFFER_OUTPUT); + group->MakeNullParameter(PARAM_STR3(0), B_MEDIA_RAW_AUDIO, "To Output", B_WEB_BUFFER_OUTPUT); } for (int i = 0; (in = fCore->Input(i)); i++) { group = top->MakeGroup(""); - group->MakeNullParameter(PARAM_STR1(in->ID()), B_MEDIA_RAW_AUDIO, in->MediaInput().name, B_WEB_BUFFER_INPUT); + group->MakeNullParameter(PARAM_STR1(in->ID()), B_MEDIA_RAW_AUDIO, in->MediaInput().name, B_WEB_BUFFER_INPUT); group->MakeNullParameter(PARAM_STR2(in->ID()), B_MEDIA_RAW_AUDIO, StringForFormat(buf, in), B_GENERIC); group->MakeDiscreteParameter(PARAM_MUTE(in->ID()), B_MEDIA_RAW_AUDIO, "Mute", B_MUTE); // XXX the gain control is ugly once you have more than two channels, @@ -1642,7 +1714,7 @@ AudioMixer::UpdateParameterWeb() group->MakeContinuousParameter(PARAM_BALANCE(in->ID()), B_MEDIA_RAW_AUDIO, "", B_BALANCE, "", 0, 100, 1); } else { // multi channel control - group->MakeContinuousParameter(PARAM_GAIN(in->ID()), B_MEDIA_RAW_AUDIO, "Gain", B_GAIN, "dB", DB_MIN, DB_MAX, 0.1) + group->MakeContinuousParameter(PARAM_GAIN(in->ID()), B_MEDIA_RAW_AUDIO, "Gain", B_GAIN, "dB", DB_MIN, DB_MAX, 0.1) ->SetChannelCount(in->GetInputChannelCount()); } } else { @@ -1653,38 +1725,43 @@ AudioMixer::UpdateParameterWeb() group->MakeContinuousParameter(PARAM_BALANCE(in->ID()), B_MEDIA_RAW_AUDIO, "", B_BALANCE, "", 0, 100, 1); } else { // multi channel control - group->MakeContinuousParameter(PARAM_GAIN(in->ID()), B_MEDIA_RAW_AUDIO, "Gain", B_GAIN, "dB", DB_MIN, DB_MAX, 0.1) + group->MakeContinuousParameter(PARAM_GAIN(in->ID()), B_MEDIA_RAW_AUDIO, "Gain", B_GAIN, "dB", DB_MIN, DB_MAX, 0.1) ->SetChannelCount(in->GetMixerChannelCount()); } } - group->MakeNullParameter(PARAM_STR3(in->ID()), B_MEDIA_RAW_AUDIO, "To Master", B_WEB_BUFFER_OUTPUT); + group->MakeNullParameter(PARAM_STR3(in->ID()), B_MEDIA_RAW_AUDIO, "To Master", B_WEB_BUFFER_OUTPUT); } - + if (fCore->Settings()->AllowOutputChannelRemapping()) { top = web->MakeGroup("Output Mapping"); // top level group outputchannels = top->MakeGroup(""); outputchannels->MakeNullParameter(PARAM_STR4(0), B_MEDIA_RAW_AUDIO, "Output Channel Sources", B_GENERIC); group = outputchannels->MakeGroup(""); - group->MakeNullParameter(PARAM_STR5(0), B_MEDIA_RAW_AUDIO, "Master Output", B_GENERIC); + group->MakeNullParameter(PARAM_STR5(0), B_MEDIA_RAW_AUDIO, "Master Output", B_GENERIC); group = group->MakeGroup(""); if (!out) { group->MakeNullParameter(PARAM_STR6(0), B_MEDIA_RAW_AUDIO, "not connected", B_GENERIC); } else { for (int chan = 0; chan < out->GetOutputChannelCount(); chan++) { subgroup = group->MakeGroup(""); - subgroup->MakeNullParameter(PARAM_SRC_STR(0, chan), B_MEDIA_RAW_AUDIO, - StringForChannelType(buf, out->GetOutputChannelType(chan)), B_GENERIC); + subgroup->MakeNullParameter(PARAM_SRC_STR(0, chan), + B_MEDIA_RAW_AUDIO, StringForChannelType(buf, + out->GetOutputChannelType(chan)), B_GENERIC); for (int src = 0; src < MAX_CHANNEL_TYPES; src++) { subsubgroup = subgroup->MakeGroup(""); - subsubgroup->MakeDiscreteParameter(PARAM_SRC_ENABLE(0, chan, src), B_MEDIA_RAW_AUDIO, "", B_ENABLE); - subsubgroup->MakeContinuousParameter(PARAM_SRC_GAIN(0, chan, src), B_MEDIA_RAW_AUDIO, - StringForChannelType(buf, src), B_GAIN, "%", 0.0, 100.0, 0.1); + subsubgroup->MakeDiscreteParameter( + PARAM_SRC_ENABLE(0, chan, src), B_MEDIA_RAW_AUDIO, "", + B_ENABLE); + subsubgroup->MakeContinuousParameter( + PARAM_SRC_GAIN(0, chan, src), B_MEDIA_RAW_AUDIO, + StringForChannelType(buf, src), B_GAIN, "%", 0.0, + 100.0, 0.1); } } } } - + if (fCore->Settings()->AllowInputChannelRemapping()) { top = web->MakeGroup("Input Mapping"); // top level group inputchannels = top->MakeGroup(""); @@ -1692,7 +1769,7 @@ AudioMixer::UpdateParameterWeb() for (int i = 0; (in = fCore->Input(i)); i++) { group = inputchannels->MakeGroup(""); - group->MakeNullParameter(PARAM_STR4(in->ID()), B_MEDIA_RAW_AUDIO, in->MediaInput().name, B_GENERIC); + group->MakeNullParameter(PARAM_STR4(in->ID()), B_MEDIA_RAW_AUDIO, in->MediaInput().name, B_GENERIC); group = group->MakeGroup(""); for (int chan = 0; chan < in->GetInputChannelCount(); chan++) { @@ -1700,7 +1777,7 @@ AudioMixer::UpdateParameterWeb() subgroup->MakeNullParameter(PARAM_DST_STR(in->ID(), chan), B_MEDIA_RAW_AUDIO, StringForChannelType(buf, in->GetInputChannelType(chan)), B_GENERIC); for (int dst = 0; dst < MAX_CHANNEL_TYPES; dst++) { - subgroup->MakeDiscreteParameter(PARAM_DST_ENABLE(in->ID(), chan, dst), B_MEDIA_RAW_AUDIO, StringForChannelType(buf, dst), B_ENABLE); + subgroup->MakeDiscreteParameter(PARAM_DST_ENABLE(in->ID(), chan, dst), B_MEDIA_RAW_AUDIO, StringForChannelType(buf, dst), B_ENABLE); } } } @@ -1708,7 +1785,7 @@ AudioMixer::UpdateParameterWeb() top = web->MakeGroup("Setup"); // top level group group = top->MakeGroup(""); - + group->MakeDiscreteParameter(PARAM_ETC(10), B_MEDIA_RAW_AUDIO, "Attenuate mixer output by 3dB (like BeOS R5)", B_ENABLE); group->MakeDiscreteParameter(PARAM_ETC(20), B_MEDIA_RAW_AUDIO, "Use non linear gain sliders (like BeOS R5)", B_ENABLE); group->MakeDiscreteParameter(PARAM_ETC(30), B_MEDIA_RAW_AUDIO, "Display balance control for stereo connections", B_ENABLE); @@ -1737,9 +1814,11 @@ AudioMixer::UpdateParameterWeb() SetParameterWeb(web); } + #if USE_MEDIA_FORMAT_WORKAROUND static void -raw_audio_format_specialize(media_raw_audio_format *format, const media_raw_audio_format *other) +raw_audio_format_specialize(media_raw_audio_format *format, + const media_raw_audio_format *other) { if (format->frame_rate == 0) format->frame_rate = other->frame_rate; @@ -1755,8 +1834,10 @@ raw_audio_format_specialize(media_raw_audio_format *format, const media_raw_audi format->frame_rate = other->frame_rate; } + static void -multi_audio_info_specialize(media_multi_audio_info *format, const media_multi_audio_info *other) +multi_audio_info_specialize(media_multi_audio_info *format, + const media_multi_audio_info *other) { if (format->channel_mask == 0) format->channel_mask = other->channel_mask; @@ -1766,8 +1847,10 @@ multi_audio_info_specialize(media_multi_audio_info *format, const media_multi_au format->matrix_mask = other->matrix_mask; } + static void -multi_audio_format_specialize(media_multi_audio_format *format, const media_multi_audio_format *other) +multi_audio_format_specialize(media_multi_audio_format *format, + const media_multi_audio_format *other) { raw_audio_format_specialize(format, other); multi_audio_info_specialize(format, other);