/*
* Copyright 2003-2016 Haiku Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Marcus Overhagen
* Dario Casalinuovo
*/
#include "MixerCore.h"
#include <string.h>
#include <Buffer.h>
#include <BufferGroup.h>
#include <BufferProducer.h>
#include <MediaNode.h>
#include <RealtimeAlloc.h>
#include <StackOrHeapArray.h>
#include <StopWatch.h>
#include <TimeSource.h>
#include "AudioMixer.h"
#include "Interpolate.h"
#include "MixerInput.h"
#include "MixerOutput.h"
#include "MixerUtils.h"
#include "Resampler.h"
#include "RtList.h"
#define DOUBLE_RATE_MIXING 0
#if DEBUG > 1
# define ASSERT_LOCKED() if (fLocker->IsLocked()) {} \
else debugger("core not locked, meltdown occurred")
#else
# define ASSERT_LOCKED() ((void)0)
#endif
/*! Mixer channels are identified by a type number, each type number corresponds
to the one of the channel masks of enum media_multi_channels.
The mixer buffer uses either the same frame rate and same count of frames as
the output buffer, or the double frame rate and frame count.
All mixer input ring buffers must be an exact multiple of the mixer buffer
size, so that we do not get any buffer wrap around during reading from the
input buffers.
The mixer input is told by constructor (or after a format change by
SetMixBufferFormat() of the current mixer buffer propertys, and must
allocate a buffer that is an exact multiple,
*/
struct chan_info {
const char *base;
uint32 sample_offset;
float gain;
};
MixerCore::MixerCore(AudioMixer *node)
:
fLocker(new BLocker("mixer core lock")),
fInputs(new BList),
fOutput(0),
fNextInputID(1),
fRunning(false),
fStarted(false),
fOutputEnabled(true),
fResampler(0),
fMixBuffer(0),
fMixBufferFrameRate(0),
fMixBufferFrameCount(0),
fMixBufferChannelCount(0),
fMixBufferChannelTypes(0),
fDoubleRateMixing(DOUBLE_RATE_MIXING),
fDownstreamLatency(1),
fSettings(new MixerSettings),
fNode(node),
fBufferGroup(0),
fTimeSource(0),
fMixThread(-1),
fMixThreadWaitSem(-1),
fHasEvent(false),
fOutputGain(1.0)
{
}
MixerCore::~MixerCore()
{
delete fSettings;
delete fLocker;
delete fInputs;
ASSERT(fMixThreadWaitSem == -1);
ASSERT(fMixThread == -1);
if (fMixBuffer)
rtm_free(fMixBuffer);
if (fTimeSource)
fTimeSource->Release();
if (fResampler) {
for (int i = 0; i < fMixBufferChannelCount; i++)
delete fResampler[i];
delete[] fResampler;
}
delete fMixBufferChannelTypes;
}
MixerSettings *
MixerCore::Settings()
{
return fSettings;
}
void
MixerCore::UpdateResamplingAlgorithm()
{
ASSERT_LOCKED();
_UpdateResamplers(fOutput->MediaOutput().format.u.raw_audio);
for (int32 i = fInputs->CountItems() - 1; i >= 0; i--) {
MixerInput* input
= reinterpret_cast<MixerInput*>(fInputs->ItemAtFast(i));
input->UpdateResamplingAlgorithm();
}
}
void
MixerCore::SetOutputAttenuation(float gain)
{
ASSERT_LOCKED();
fOutputGain = gain;
}
MixerInput*
MixerCore::AddInput(const media_input& input)
{
ASSERT_LOCKED();
MixerInput* in = new MixerInput(this, input, fMixBufferFrameRate,
fMixBufferFrameCount);
fInputs->AddItem(in);
return in;
}
MixerOutput*
MixerCore::AddOutput(const media_output& output)
{
ASSERT_LOCKED();
if (fOutput) {
ERROR("MixerCore::AddOutput: already connected\n");
return fOutput;
}
fOutput = new MixerOutput(this, output);
// the output format might have been adjusted inside MixerOutput
_ApplyOutputFormat();
ASSERT(!fRunning);
if (fStarted && fOutputEnabled)
StartMixThread();
return fOutput;
}
bool
MixerCore::RemoveInput(int32 inputID)
{
ASSERT_LOCKED();
MixerInput *input;
for (int i = 0; (input = Input(i)) != 0; i++) {
if (input->ID() == inputID) {
fInputs->RemoveItem(i);
delete input;
return true;
}
}
return false;
}
bool
MixerCore::RemoveOutput()
{
ASSERT_LOCKED();
if (!fOutput)
return false;
if (fStarted)
StopMixThread();
delete fOutput;
fOutput = 0;
fOutputEnabled = true;
return true;
}
int32
MixerCore::CreateInputID()
{
ASSERT_LOCKED();
return fNextInputID++;
}
MixerInput *
MixerCore::Input(int i)
{
ASSERT_LOCKED();
return (MixerInput *)fInputs->ItemAt(i);
}
MixerOutput *
MixerCore::Output()
{
ASSERT_LOCKED();
return fOutput;
}
void
MixerCore::BufferReceived(BBuffer *buffer, bigtime_t lateness)
{
ASSERT_LOCKED();
MixerInput *input;
int32 id = buffer->Header()->destination;
for (int i = 0; (input = Input(i)) != 0; i++) {
if (input->ID() == id) {
input->BufferReceived(buffer);
return;
}
}
ERROR("MixerCore::BufferReceived: received buffer for unknown id %ld\n",
id);
}
void
MixerCore::InputFormatChanged(int32 inputID,
const media_multi_audio_format &format)
{
ASSERT_LOCKED();
ERROR("MixerCore::InputFormatChanged not handled\n");
}
void
MixerCore::OutputFormatChanged(const media_multi_audio_format &format)
{
ASSERT_LOCKED();
bool was_started = fStarted;
if (was_started)
Stop();
fOutput->ChangeFormat(format);
_ApplyOutputFormat();
if (was_started)
Start();
}
void
MixerCore::SetOutputBufferGroup(BBufferGroup *group)
{
ASSERT_LOCKED();
fBufferGroup = group;
}
void
MixerCore::SetTimingInfo(BTimeSource *ts, bigtime_t downstream_latency)
{
ASSERT_LOCKED();
if (fTimeSource)
fTimeSource->Release();
fTimeSource = dynamic_cast<BTimeSource *>(ts->Acquire());
fDownstreamLatency = downstream_latency;
TRACE("MixerCore::SetTimingInfo, now = %Ld, downstream latency %Ld\n",
fTimeSource->Now(), fDownstreamLatency);
}
void
MixerCore::EnableOutput(bool enabled)
{
ASSERT_LOCKED();
TRACE("MixerCore::EnableOutput %d\n", enabled);
fOutputEnabled = enabled;
if (fRunning && !fOutputEnabled)
StopMixThread();
if (!fRunning && fOutput && fStarted && fOutputEnabled)
StartMixThread();
}
uint32
MixerCore::OutputChannelCount()
{
return (fOutput) ? fOutput->GetOutputChannelCount() : 0;
}
bool
MixerCore::Start()
{
ASSERT_LOCKED();
TRACE("MixerCore::Start\n");
if (fStarted)
return false;
fStarted = true;
ASSERT(!fRunning);
// only start the mix thread if we have an output
if (fOutput && fOutputEnabled)
StartMixThread();
return true;
}
bool
MixerCore::Stop()
{
ASSERT_LOCKED();
TRACE("MixerCore::Stop\n");
if (!fStarted)
return false;
if (fRunning)
StopMixThread();
fStarted = false;
return true;
}
void
MixerCore::StartMixThread()
{
ASSERT(fOutputEnabled == true);
ASSERT(fRunning == false);
ASSERT(fOutput);
fRunning = true;
fMixThreadWaitSem = create_sem(0, "mix thread wait");
fMixThread = spawn_thread(_MixThreadEntry, "Yeah baby, very shagadelic",
120, this);
resume_thread(fMixThread);
}
void
MixerCore::StopMixThread()
{
ASSERT(fRunning == true);
ASSERT(fMixThread > 0);
ASSERT(fMixThreadWaitSem > 0);
fRunning = false;
status_t unused;
delete_sem(fMixThreadWaitSem);
wait_for_thread(fMixThread, &unused);
fMixThread = -1;
fMixThreadWaitSem = -1;
}
// #pragma mark - private
void
MixerCore::_UpdateResamplers(const media_multi_audio_format& format)
{
ASSERT_LOCKED();
if (fResampler != NULL) {
for (int i = 0; i < fMixBufferChannelCount; i++)
delete fResampler[i];
delete[] fResampler;
}
fResampler = new Resampler*[fMixBufferChannelCount];
for (int i = 0; i < fMixBufferChannelCount; i++) {
switch (Settings()->ResamplingAlgorithm()) {
case 2:
fResampler[i] = new Interpolate(
media_raw_audio_format::B_AUDIO_FLOAT, format.format);
break;
default:
fResampler[i] = new Resampler(
media_raw_audio_format::B_AUDIO_FLOAT, format.format);
}
}
}
void
MixerCore::_ApplyOutputFormat()
{
ASSERT_LOCKED();
const media_multi_audio_format& format
= fOutput->MediaOutput().format.u.raw_audio;
if (fMixBuffer != NULL)
rtm_free(fMixBuffer);
delete fMixBufferChannelTypes;
fMixBufferFrameRate = (int32)(0.5 + format.frame_rate);
fMixBufferFrameCount = frames_per_buffer(format);
if (fDoubleRateMixing) {
fMixBufferFrameRate *= 2;
fMixBufferFrameCount *= 2;
}
fMixBufferChannelCount = format.channel_count;
ASSERT(fMixBufferChannelCount == fOutput->GetOutputChannelCount());
fMixBufferChannelTypes = new int32 [format.channel_count];
for (int i = 0; i < fMixBufferChannelCount; i++) {
fMixBufferChannelTypes[i]
= ChannelMaskToChannelType(GetChannelMask(i, format.channel_mask));
}
fMixBuffer = (float*)rtm_alloc(NULL, sizeof(float) * fMixBufferFrameCount
* fMixBufferChannelCount);
ASSERT(fMixBuffer != NULL);
_UpdateResamplers(format);
TRACE("MixerCore::OutputFormatChanged:\n");
TRACE(" fMixBufferFrameRate %ld\n", fMixBufferFrameRate);
TRACE(" fMixBufferFrameCount %ld\n", fMixBufferFrameCount);
TRACE(" fMixBufferChannelCount %ld\n", fMixBufferChannelCount);
for (int i = 0; i < fMixBufferChannelCount; i++)
TRACE(" fMixBufferChannelTypes[%i] %ld\n", i, fMixBufferChannelTypes[i]);
MixerInput *input;
for (int i = 0; (input = Input(i)); i++)
input->SetMixBufferFormat(fMixBufferFrameRate, fMixBufferFrameCount);
}
int32
MixerCore::_MixThreadEntry(void* arg)
{
static_cast<MixerCore*>(arg)->_MixThread();
return 0;
}
void
MixerCore::_MixThread()
{
// The broken BeOS R5 multiaudio node starts with time 0,
// then publishes negative times for about 50ms, publishes 0
// again until it finally reaches time values > 0
if (!Lock())
return;
bigtime_t start = fTimeSource->Now();
Unlock();
while (start <= 0) {
TRACE("MixerCore: delaying _MixThread start, timesource is at %Ld\n",
start);
snooze(5000);
if (!Lock())
return;
start = fTimeSource->Now();
Unlock();
}
fEventLatency = max((bigtime_t)3600, bigtime_t(0.4 * buffer_duration(
fOutput->MediaOutput().format.u.raw_audio)));
// TODO: when the format changes while running, everything is wrong!
bigtime_t bufferRequestTimeout = buffer_duration(
fOutput->MediaOutput().format.u.raw_audio) / 2;
TRACE("MixerCore: starting _MixThread at %Ld with latency %Ld and "
"downstream latency %Ld, bufferRequestTimeout %Ld\n", start, latency,
fDownstreamLatency, bufferRequestTimeout);
// We must read from the input buffer at a position (pos) that is always
// a multiple of fMixBufferFrameCount.
int64 temp = frames_for_duration(fMixBufferFrameRate, start);
int64 frameBase = ((temp / fMixBufferFrameCount) + 1)
* fMixBufferFrameCount;
bigtime_t timeBase = duration_for_frames(fMixBufferFrameRate, frameBase);
TRACE("MixerCore: starting _MixThread, start %Ld, timeBase %Ld, "
"frameBase %Ld\n", start, timeBase, frameBase);
ASSERT(fMixBufferFrameCount > 0);
#if DEBUG
uint64 bufferIndex = 0;
#endif
typedef RtList<chan_info> chan_info_list;
chan_info_list inputChanInfos[MAX_CHANNEL_TYPES];
BStackOrHeapArray<chan_info_list, 16> mixChanInfos(fMixBufferChannelCount);
// TODO: this does not support changing output channel count
if (!mixChanInfos.IsValid()) {
ERROR("MixerCore::_MixThread mixChanInfos allocation failed\n");
return;
}
fEventTime = timeBase;
int64 framePos = 0;
status_t ret = B_ERROR;
while(fRunning == true) {
if (fHasEvent == false)
goto schedule_next_event;
ret = acquire_sem(fMixThreadWaitSem);
if (ret == B_INTERRUPTED)
continue;
else if (ret != B_OK)
return;
fHasEvent = false;
if (!LockWithTimeout(10000)) {
ERROR("MixerCore: LockWithTimeout failed\n");
continue;
}
// no inputs or output muted, skip further processing and just send an
// empty buffer
if (fInputs->IsEmpty() || fOutput->IsMuted()) {
int size = fOutput->MediaOutput().format.u.raw_audio.buffer_size;
BBuffer* buffer = fBufferGroup->RequestBuffer(size,
bufferRequestTimeout);
if (buffer != NULL) {
memset(buffer->Data(), 0, size);
// fill in the buffer header
media_header* hdr = buffer->Header();
hdr->type = B_MEDIA_RAW_AUDIO;
hdr->size_used = size;
hdr->time_source = fTimeSource->ID();
hdr->start_time = fEventTime;
if (fNode->SendBuffer(buffer, fOutput) != B_OK) {
#if DEBUG
ERROR("MixerCore: SendBuffer failed for buffer %Ld\n",
bufferIndex);
#else
ERROR("MixerCore: SendBuffer failed\n");
#endif
buffer->Recycle();
}
} else {
#if DEBUG
ERROR("MixerCore: RequestBuffer failed for buffer %Ld\n",
bufferIndex);
#else
ERROR("MixerCore: RequestBuffer failed\n");
#endif
}
goto schedule_next_event;
}
int64 currentFramePos;
currentFramePos = frameBase + framePos;
// mix all data from all inputs into the mix buffer
ASSERT(currentFramePos % fMixBufferFrameCount == 0);
PRINT(4, "create new buffer event at %Ld, reading input frames at "
"%Ld\n", fEventTime, currentFramePos);
// Init the channel information for each MixerInput.
for (int i = 0; MixerInput* input = Input(i); i++) {
int count = input->GetMixerChannelCount();
for (int channel = 0; channel < count; channel++) {
int type;
const float* base;
uint32 sampleOffset;
float gain;
if (!input->GetMixerChannelInfo(channel, currentFramePos,
fEventTime, &base, &sampleOffset, &type, &gain)) {
continue;
}
if (type < 0 || type >= MAX_CHANNEL_TYPES)
continue;
chan_info* info = inputChanInfos[type].Create();
info->base = (const char*)base;
info->sample_offset = sampleOffset;
info->gain = gain;
}
}
for (int channel = 0; channel < fMixBufferChannelCount; channel++) {
int sourceCount = fOutput->GetOutputChannelSourceCount(channel);
for (int i = 0; i < sourceCount; i++) {
int type;
float gain;
fOutput->GetOutputChannelSourceInfoAt(channel, i, &type,
&gain);
if (type < 0 || type >= MAX_CHANNEL_TYPES)
continue;
int count = inputChanInfos[type].CountItems();
for (int j = 0; j < count; j++) {
chan_info* info = inputChanInfos[type].ItemAt(j);
chan_info* newInfo = mixChanInfos[channel].Create();
newInfo->base = info->base;
newInfo->sample_offset = info->sample_offset;
newInfo->gain = info->gain * gain;
}
}
}
memset(fMixBuffer, 0,
fMixBufferChannelCount * fMixBufferFrameCount * sizeof(float));
for (int channel = 0; channel < fMixBufferChannelCount; channel++) {
PRINT(5, "_MixThread: channel %d has %d sources\n", channel,
mixChanInfos[channel].CountItems());
int count = mixChanInfos[channel].CountItems();
for (int i = 0; i < count; i++) {
chan_info* info = mixChanInfos[channel].ItemAt(i);
PRINT(5, "_MixThread: base %p, sample-offset %2d, gain %.3f\n",
info->base, info->sample_offset, info->gain);
// This looks slightly ugly, but the current GCC will generate
// the fastest code this way.
// fMixBufferFrameCount is always > 0.
uint32 dstSampleOffset
= fMixBufferChannelCount * sizeof(float);
uint32 srcSampleOffset = info->sample_offset;
register char* dst = (char*)&fMixBuffer[channel];
register char* src = (char*)info->base;
register float gain = info->gain;
register int j = fMixBufferFrameCount;
do {
*(float*)dst += *(const float*)src * gain;
dst += dstSampleOffset;
src += srcSampleOffset;
} while (--j);
}
}
// request a buffer
BBuffer* buffer;
buffer = fBufferGroup->RequestBuffer(
fOutput->MediaOutput().format.u.raw_audio.buffer_size,
bufferRequestTimeout);
if (buffer != NULL) {
// copy data from mix buffer into output buffer
for (int i = 0; i < fMixBufferChannelCount; i++) {
fResampler[i]->Resample(
reinterpret_cast<char*>(fMixBuffer) + i * sizeof(float),
fMixBufferChannelCount * sizeof(float),
fMixBufferFrameCount,
reinterpret_cast<char*>(buffer->Data())
+ (i * bytes_per_sample(
fOutput->MediaOutput().format.u.raw_audio)),
bytes_per_frame(fOutput->MediaOutput().format.u.raw_audio),
frames_per_buffer(
fOutput->MediaOutput().format.u.raw_audio),
fOutputGain * fOutput->GetOutputChannelGain(i));
}
PRINT(4, "send buffer, inframes %ld, outframes %ld\n",
fMixBufferFrameCount,
frames_per_buffer(fOutput->MediaOutput().format.u.raw_audio));
// fill in the buffer header
media_header* hdr = buffer->Header();
hdr->type = B_MEDIA_RAW_AUDIO;
hdr->size_used
= fOutput->MediaOutput().format.u.raw_audio.buffer_size;
hdr->time_source = fTimeSource->ID();
hdr->start_time = fEventTime;
// swap byte order if necessary
fOutput->AdjustByteOrder(buffer);
// send the buffer
status_t res = fNode->SendBuffer(buffer, fOutput);
if (res != B_OK) {
#if DEBUG
ERROR("MixerCore: SendBuffer failed for buffer %Ld\n",
bufferIndex);
#else
ERROR("MixerCore: SendBuffer failed\n");
#endif
buffer->Recycle();
}
} else {
#if DEBUG
ERROR("MixerCore: RequestBuffer failed for buffer %Ld\n",
bufferIndex);
#else
ERROR("MixerCore: RequestBuffer failed\n");
#endif
}
// make all lists empty
for (int i = 0; i < MAX_CHANNEL_TYPES; i++)
inputChanInfos[i].MakeEmpty();
for (int i = 0; i < fOutput->GetOutputChannelCount(); i++)
mixChanInfos[i].MakeEmpty();
schedule_next_event:
Unlock();
// schedule next event
framePos += fMixBufferFrameCount;
fEventTime = timeBase + bigtime_t((1000000LL * framePos)
/ fMixBufferFrameRate);
media_timed_event mixerEvent(PickEvent(),
MIXER_PROCESS_EVENT, 0, BTimedEventQueue::B_NO_CLEANUP);
ret = write_port(fNode->ControlPort(), MIXER_SCHEDULE_EVENT,
&mixerEvent, sizeof(mixerEvent));
if (ret != B_OK)
TRACE("MixerCore::_MixThread: can't write to owner port\n");
fHasEvent = true;
#if DEBUG
bufferIndex++;
#endif
}
}
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fEventTime, fEventLatency.