⛏️ index : haiku.git

/*
 * Copyright 2010, Axel Dörfler, axeld@pinc-software.de.
 * Copyright 2000-2010, Stephan Aßmus <superstippi@gmx.de>,
 * Copyright 2000-2008, Ingo Weinhold <ingo_weinhold@gmx.de>,
 * All Rights Reserved. Distributed under the terms of the MIT license.
 *
 * Copyright (c) 1998-99, Be Incorporated, All Rights Reserved.
 * Distributed under the terms of the Be Sample Code license.
 */


#include "AudioProducer.h"

#include <math.h>
#include <string.h>
#include <stdio.h>

#include <BufferGroup.h>
#include <Buffer.h>
#include <MediaDefs.h>
#include <ParameterWeb.h>
#include <TimeSource.h>

#include "AudioSupplier.h"
#include "EventQueue.h"
#include "MessageEvent.h"

#define DEBUG_TO_FILE 0

#if DEBUG_TO_FILE
# include <Entry.h>
# include <MediaFormats.h>
# include <MediaFile.h>
# include <MediaTrack.h>
#endif // DEBUG_TO_FILE


// debugging
//#define TRACE_AUDIO_PRODUCER
#ifdef TRACE_AUDIO_PRODUCER
#	define TRACE(x...)		printf(x)
#	define TRACE_BUFFER(x...)
#	define ERROR(x...)		fprintf(stderr, x)
#else
#	define TRACE(x...)
#	define TRACE_BUFFER(x...)
#	define ERROR(x...)		fprintf(stderr, x)
#endif


const static bigtime_t kMaxLatency = 150000;
	// 150 ms is the maximum latency we publish


#if DEBUG_TO_FILE
static BMediaFile*
init_media_file(media_format format, BMediaTrack** _track)
{
	static BMediaFile* file = NULL;
	static BMediaTrack* track = NULL;
	if (file == NULL) {
		entry_ref ref;
		get_ref_for_path("/boot/home/Desktop/test.wav", &ref);

		media_file_format fileFormat;
		int32 cookie = 0;
		while (get_next_file_format(&cookie, &fileFormat) == B_OK) {
			if (strcmp(fileFormat.short_name, "wav") == 0)
				break;
		}
		file = new BMediaFile(&ref, &fileFormat);

		media_codec_info info;
		cookie = 0;
		while (get_next_encoder(&cookie, &info) == B_OK) {
			if (strcmp(info.short_name, "raw-audio") == 0
				|| strcmp(info.short_name, "pcm") == 0) {
				break;
			}
		}

		track = file->CreateTrack(&format, &info);
		if (track == NULL)
			printf("failed to create track\n");

		file->CommitHeader();
	}
	*_track = track;
	return track != NULL ? file : NULL;
}
#endif // DEBUG_TO_FILE


static bigtime_t
estimate_internal_latency(const media_format& format)
{
	bigtime_t startTime = system_time();
	// calculate the number of samples per buffer
	int32 sampleSize = format.u.raw_audio.format
		& media_raw_audio_format::B_AUDIO_SIZE_MASK;
	int32 sampleCount = format.u.raw_audio.buffer_size / sampleSize;
	// alloc float buffers of this size
	const int bufferCount = 10;	// number of input buffers
	float* buffers[bufferCount + 1];
	for (int32 i = 0; i < bufferCount + 1; i++)
		buffers[i] = new float[sampleCount];
	float* outBuffer = buffers[bufferCount];
	// fill all buffers save the last one with arbitrary data and merge them
	// into the last one
	for (int32 i = 0; i < bufferCount; i++) {
		for (int32 k = 0; k < sampleCount; k++) {
			buffers[i][k] = ((float)i * (float)k)
				/ float(bufferCount * sampleCount);
		}
	}
	for (int32 k = 0; k < sampleCount; k++) {
		outBuffer[k] = 0;
		for (int32 i = 0; i < bufferCount; i++)
			outBuffer[k] += buffers[i][k];
		outBuffer[k] /= bufferCount;
	}
	// cleanup
	for (int32 i = 0; i < bufferCount + 1; i++)
		delete[] buffers[i];
	return system_time() - startTime;
}


// #pragma mark -


AudioProducer::AudioProducer(const char* name, AudioSupplier* supplier,
		bool lowLatency)
	:
	BMediaNode(name),
	BBufferProducer(B_MEDIA_RAW_AUDIO),
	BMediaEventLooper(),

	fBufferGroup(NULL),
	fLatency(0),
	fInternalLatency(0),
	fLastLateNotice(0),
	fNextScheduledBuffer(0),
	fLowLatency(lowLatency),
	fOutputEnabled(true),
	fFramesSent(0),
	fStartTime(0),
	fSupplier(supplier),

	fPeakListener(NULL)
{
	TRACE("%p->AudioProducer::AudioProducer(%s, %p, %d)\n", this, name,
		supplier, lowLatency);

	// initialize our preferred format object
	fPreferredFormat.type = B_MEDIA_RAW_AUDIO;
	fPreferredFormat.u.raw_audio.format
		= media_raw_audio_format::B_AUDIO_FLOAT;
//		= media_raw_audio_format::B_AUDIO_SHORT;
	fPreferredFormat.u.raw_audio.byte_order
		= (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN;
#if 0
	fPreferredFormat.u.raw_audio.channel_count = 2;
	fPreferredFormat.u.raw_audio.frame_rate = 44100.0;

	// NOTE: the (buffer_size * 1000000) needs to be dividable by
	// fPreferredFormat.u.raw_audio.frame_rate!
	fPreferredFormat.u.raw_audio.buffer_size = 441 * 4
		* (fPreferredFormat.u.raw_audio.format
			& media_raw_audio_format::B_AUDIO_SIZE_MASK);

	if (!fLowLatency)
		fPreferredFormat.u.raw_audio.buffer_size *= 3;
#else
	fPreferredFormat.u.raw_audio.channel_count = 0;
	fPreferredFormat.u.raw_audio.frame_rate = 0.0;
	fPreferredFormat.u.raw_audio.buffer_size = 0;
#endif

	// we're not connected yet
	fOutput.destination = media_destination::null;
	fOutput.format = fPreferredFormat;

	// init the audio supplier
	if (fSupplier != NULL) {
		fSupplier->SetAudioProducer(this);
		SetInitialLatency(fSupplier->InitialLatency());
	}
}


AudioProducer::~AudioProducer()
{
	TRACE("%p->AudioProducer::~AudioProducer()\n", this);

#if DEBUG_TO_FILE
	BMediaTrack* track;
	if (BMediaFile* file = init_media_file(fPreferredFormat, &track)) {
		printf("deleting file...\n");
		track->Flush();
		file->ReleaseTrack(track);
		file->CloseFile();
		delete file;
	}
#endif // DEBUG_TO_FILE

	// Stop the BMediaEventLooper thread
	Quit();
	TRACE("AudioProducer::~AudioProducer() done\n");
}


BMediaAddOn*
AudioProducer::AddOn(int32* internalId) const
{
	return NULL;
}


status_t
AudioProducer::FormatSuggestionRequested(media_type type, int32 quality,
	media_format* _format)
{
	TRACE("%p->AudioProducer::FormatSuggestionRequested()\n", this);

	if (!_format)
		return B_BAD_VALUE;

	// This is the format we'll be returning (our preferred format)
	*_format = fPreferredFormat;

	// A wildcard type is okay; we can specialize it, otherwise only raw audio
	// is supported
	if (type != B_MEDIA_UNKNOWN_TYPE && type != B_MEDIA_RAW_AUDIO)
		return B_MEDIA_BAD_FORMAT;

	return B_OK;
}

status_t
AudioProducer::FormatProposal(const media_source& output, media_format* format)
{
	TRACE("%p->AudioProducer::FormatProposal()\n", this);

	// is this a proposal for our one output?
	if (output != fOutput.source) {
		TRACE("  -> B_MEDIA_BAD_SOURCE\n");
		return B_MEDIA_BAD_SOURCE;
	}

	// Raw audio or wildcard type, either is okay by us. If the format is
	// anything else, overwrite it with our preferred format. Also, we only support
	// floating point audio in the host native byte order at the moment.
	if ((format->type != B_MEDIA_UNKNOWN_TYPE
			&& format->type != B_MEDIA_RAW_AUDIO)
		|| (format->u.raw_audio.format
				!= media_raw_audio_format::wildcard.format
			&& format->u.raw_audio.format
				!= fPreferredFormat.u.raw_audio.format)
		|| (format->u.raw_audio.byte_order
				!= media_raw_audio_format::wildcard.byte_order
			&& format->u.raw_audio.byte_order
				!= fPreferredFormat.u.raw_audio.byte_order)) {
		TRACE("  -> B_MEDIA_BAD_FORMAT\n");
		*format = fPreferredFormat;
		return B_MEDIA_BAD_FORMAT;
	}

	format->type = B_MEDIA_RAW_AUDIO;
	format->u.raw_audio.format = fPreferredFormat.u.raw_audio.format;
	format->u.raw_audio.byte_order = fPreferredFormat.u.raw_audio.byte_order;

	return B_OK;
}


status_t
AudioProducer::FormatChangeRequested(const media_source& source,
	const media_destination& destination, media_format* ioFormat,
	int32* _deprecated_)
{
	TRACE("%p->AudioProducer::FormatChangeRequested()\n", this);

	if (destination != fOutput.destination) {
		TRACE("  -> B_MEDIA_BAD_DESTINATION\n");
		return B_MEDIA_BAD_DESTINATION;
	}

	if (source != fOutput.source) {
		TRACE("  -> B_MEDIA_BAD_SOURCE\n");
		return B_MEDIA_BAD_SOURCE;
	}

// TODO: Maybe we are supposed to specialize here only and not actually change yet?
//	status_t ret = _SpecializeFormat(ioFormat);

	return ChangeFormat(ioFormat);
}


status_t
AudioProducer::GetNextOutput(int32* cookie, media_output* _output)
{
	TRACE("%p->AudioProducer::GetNextOutput(%ld)\n", this, *cookie);

	// we have only a single output; if we supported multiple outputs, we'd
	// iterate over whatever data structure we were using to keep track of
	// them.
	if (0 == *cookie) {
		*_output = fOutput;
		*cookie += 1;
		return B_OK;
	}

	return B_BAD_INDEX;
}


status_t
AudioProducer::DisposeOutputCookie(int32 cookie)
{
	// do nothing because we don't use the cookie for anything special
	return B_OK;
}


status_t
AudioProducer::SetBufferGroup(const media_source& forSource,
	BBufferGroup* newGroup)
{
	TRACE("%p->AudioProducer::SetBufferGroup()\n", this);

	if (forSource != fOutput.source)
		return B_MEDIA_BAD_SOURCE;

	if (newGroup == fBufferGroup)
		return B_OK;

	if (fUsingOurBuffers && fBufferGroup)
		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;
		fUsingOurBuffers = false;
	} else {
		// we were passed a NULL group pointer; that means we construct
		// our own buffer group to use from now on
		size_t size = fOutput.format.u.raw_audio.buffer_size;
		int32 count = int32(fLatency / BufferDuration() + 1 + 1);
		fBufferGroup = new BBufferGroup(size, count);
		fUsingOurBuffers = true;
	}

	return B_OK;
}


status_t
AudioProducer::GetLatency(bigtime_t* _latency)
{
	TRACE("%p->AudioProducer::GetLatency()\n", this);

	// report our *total* latency:  internal plus downstream plus scheduling
	*_latency = EventLatency() + SchedulingLatency();
	return B_OK;
}


status_t
AudioProducer::PrepareToConnect(const media_source& what,
	const media_destination& where, media_format* format,
	media_source* _source, char* _name)
{
	TRACE("%p->AudioProducer::PrepareToConnect()\n", this);

	// trying to connect something that isn't our source?
	if (what != fOutput.source) {
		TRACE("  -> B_MEDIA_BAD_SOURCE\n");
		return B_MEDIA_BAD_SOURCE;
	}

	// are we already connected?
	if (fOutput.destination != media_destination::null) {
		TRACE("  -> B_MEDIA_ALREADY_CONNECTED\n");
		return B_MEDIA_ALREADY_CONNECTED;
	}

	status_t ret = _SpecializeFormat(format);
	if (ret != B_OK) {
		TRACE("  -> format error: %s\n", strerror(ret));
		return ret;
	}

	// Now reserve the connection, and return information about it
	fOutput.destination = where;
	fOutput.format = *format;

	if (fSupplier != NULL)
		fSupplier->SetFormat(fOutput.format);

	*_source = fOutput.source;
	strncpy(_name, fOutput.name, B_MEDIA_NAME_LENGTH);
	TRACE("  -> B_OK\n");
	return B_OK;
}


void
AudioProducer::Connect(status_t error, const media_source& source,
	 const media_destination& destination, const media_format& format,
	 char* _name)
{
	TRACE("AudioProducer::Connect(%s)\n", strerror(error));

	// If something earlier failed, Connect() might still be called, but with
	// a non-zero error code.  When that happens we simply unreserve the
	// connection and do nothing else.
	if (error != B_OK) {
		fOutput.destination = media_destination::null;
		fOutput.format = fPreferredFormat;
		return;
	}

	// Okay, the connection has been confirmed.  Record the destination and
	// format that we agreed on, and report our connection name again.
	fOutput.destination = destination;
	fOutput.format = format;
	strncpy(_name, fOutput.name, B_MEDIA_NAME_LENGTH);

	// tell our audio supplier about the format
	if (fSupplier) {
		TRACE("AudioProducer::Connect() fSupplier->SetFormat()\n");
		fSupplier->SetFormat(fOutput.format);
	}

	TRACE("AudioProducer::Connect() FindLatencyFor()\n");

	// 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);

	// Use a dry run to see how long it takes me to fill a buffer of data
	size_t sampleSize = fOutput.format.u.raw_audio.format
		& media_raw_audio_format::B_AUDIO_SIZE_MASK;
	size_t samplesPerBuffer
		= fOutput.format.u.raw_audio.buffer_size / sampleSize;
	fInternalLatency = estimate_internal_latency(fOutput.format);
	if (!fLowLatency)
		fInternalLatency *= 32;
	SetEventLatency(fLatency + fInternalLatency);

	// reset our buffer duration, etc. to avoid later calculations
	bigtime_t duration = bigtime_t(1000000)
		* samplesPerBuffer / bigtime_t(fOutput.format.u.raw_audio.frame_rate
		* fOutput.format.u.raw_audio.channel_count);
	TRACE("AudioProducer::Connect() SetBufferDuration(%lld)\n", duration);
	SetBufferDuration(duration);

	TRACE("AudioProducer::Connect() _AllocateBuffers()\n");

	// 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 == NULL)
		_AllocateBuffers(fOutput.format);

	TRACE("AudioProducer::Connect() done\n");
}


void
AudioProducer::Disconnect(const media_source& what,
	const media_destination& where)
{
	TRACE("%p->AudioProducer::Disconnect()\n", this);

	// Make sure that our connection is the one being disconnected
	if (where == fOutput.destination && what == fOutput.source) {
		fOutput.destination = media_destination::null;
		fOutput.format = fPreferredFormat;
		TRACE("AudioProducer:  deleting buffer group...\n");
		// Always delete the buffer group, even if it is not ours.
		// (See BeBook::SetBufferGroup()).
		delete fBufferGroup;
		TRACE("AudioProducer:  buffer group deleted\n");
		fBufferGroup = NULL;
	}

	TRACE("%p->AudioProducer::Disconnect() done\n", this);
}


void
AudioProducer::LateNoticeReceived(const media_source& what, bigtime_t howMuch,
	bigtime_t performanceTime)
{
	TRACE("%p->AudioProducer::LateNoticeReceived(%lld, %lld)\n", this, howMuch,
		performanceTime);
	// If we're late, we need to catch up. Respond in a manner appropriate
	// to our current run mode.
	if (what == fOutput.source) {
		// Ignore the notices for buffers we already send out (or scheduled
		// their event) before we processed the last notice
		if (fLastLateNotice > performanceTime)
			return;

		fLastLateNotice = fNextScheduledBuffer;

		if (RunMode() == B_RECORDING) {
			// ...
		} else if (RunMode() == B_INCREASE_LATENCY) {
			fInternalLatency += howMuch;

			// At some point a too large latency can get annoying
			if (fInternalLatency > kMaxLatency)
				fInternalLatency = kMaxLatency;

			SetEventLatency(fLatency + fInternalLatency);
		} else {
			// Skip one buffer ahead in the audio data.
			size_t sampleSize
				= fOutput.format.u.raw_audio.format
					& media_raw_audio_format::B_AUDIO_SIZE_MASK;
			size_t samplesPerBuffer
				= fOutput.format.u.raw_audio.buffer_size / sampleSize;
			size_t framesPerBuffer
				= samplesPerBuffer / fOutput.format.u.raw_audio.channel_count;
			fFramesSent += framesPerBuffer;
		}
	}
}


void
AudioProducer::EnableOutput(const media_source& what, bool enabled,
	int32* _deprecated_)
{
	TRACE("%p->AudioProducer::EnableOutput(%d)\n", this, enabled);

	if (what == fOutput.source)
		fOutputEnabled = enabled;
}


status_t
AudioProducer::SetPlayRate(int32 numer, int32 denom)
{
	return B_ERROR;
}


status_t
AudioProducer::HandleMessage(int32 message, const void *data, size_t size)
{
	TRACE("%p->AudioProducer::HandleMessage()\n", this);
	return B_ERROR;
}


void
AudioProducer::AdditionalBufferRequested(const media_source& source,
	media_buffer_id prevBuffer, bigtime_t prevTime,
	const media_seek_tag *prevTag)
{
	TRACE("%p->AudioProducer::AdditionalBufferRequested()\n", this);
}


void
AudioProducer::LatencyChanged(const media_source& source,
	const media_destination& destination, bigtime_t newLatency, uint32 flags)
{
	TRACE("%p->AudioProducer::LatencyChanged(%lld)\n", this, newLatency);

	if (source == fOutput.source && destination == fOutput.destination) {
		fLatency = newLatency;
		SetEventLatency(fLatency + fInternalLatency);
	}
}


void
AudioProducer::NodeRegistered()
{
	TRACE("%p->AudioProducer::NodeRegistered()\n", this);

	// set up as much information about our output as we can
	fOutput.source.port = ControlPort();
	fOutput.source.id = 0;
	fOutput.node = Node();
	::strcpy(fOutput.name, Name());

	// Start the BMediaEventLooper thread
	SetPriority(B_REAL_TIME_PRIORITY);
	Run();
}


void
AudioProducer::SetRunMode(run_mode mode)
{
	TRACE("%p->AudioProducer::SetRunMode()\n", this);

	if (B_OFFLINE == mode)
		ReportError(B_NODE_FAILED_SET_RUN_MODE);
	else
		BBufferProducer::SetRunMode(mode);
}


void
AudioProducer::HandleEvent(const media_timed_event* event, bigtime_t lateness,
	bool realTimeEvent)
{
	TRACE_BUFFER("%p->AudioProducer::HandleEvent()\n", this);

	switch (event->type) {
		case BTimedEventQueue::B_START:
			TRACE("AudioProducer::HandleEvent(B_START)\n");
			if (RunState() != B_STARTED) {
				fFramesSent = 0;
				fStartTime = event->event_time + fSupplier->InitialLatency();
				media_timed_event firstBufferEvent(
					fStartTime - fSupplier->InitialLatency(),
					BTimedEventQueue::B_HANDLE_BUFFER);
				EventQueue()->AddEvent(firstBufferEvent);
			}
			TRACE("AudioProducer::HandleEvent(B_START) done\n");
			break;

		case BTimedEventQueue::B_STOP:
			TRACE("AudioProducer::HandleEvent(B_STOP)\n");
			EventQueue()->FlushEvents(0, BTimedEventQueue::B_ALWAYS, true,
				BTimedEventQueue::B_HANDLE_BUFFER);
			TRACE("AudioProducer::HandleEvent(B_STOP) done\n");
			break;

		case BTimedEventQueue::B_HANDLE_BUFFER:
		{
			TRACE_BUFFER("AudioProducer::HandleEvent(B_HANDLE_BUFFER)\n");
			if (RunState() == BMediaEventLooper::B_STARTED
				&& fOutput.destination != media_destination::null) {
				BBuffer* buffer = _FillNextBuffer(event->event_time);
				if (buffer != NULL) {
					status_t err = B_ERROR;
					if (fOutputEnabled) {
						err = SendBuffer(buffer, fOutput.source,
							fOutput.destination);
					}
					if (err != B_OK)
						buffer->Recycle();
				}
				size_t sampleSize = fOutput.format.u.raw_audio.format
						& media_raw_audio_format::B_AUDIO_SIZE_MASK;

				size_t nFrames = fOutput.format.u.raw_audio.buffer_size
					/ (sampleSize * fOutput.format.u.raw_audio.channel_count);
				fFramesSent += nFrames;

				fNextScheduledBuffer = fStartTime
					+ bigtime_t(double(fFramesSent) * 1000000.0
						/ double(fOutput.format.u.raw_audio.frame_rate));
				media_timed_event nextBufferEvent(fNextScheduledBuffer,
					BTimedEventQueue::B_HANDLE_BUFFER);
				EventQueue()->AddEvent(nextBufferEvent);
			} else {
				ERROR("B_HANDLE_BUFFER, but not started!\n");
			}
			TRACE_BUFFER("AudioProducer::HandleEvent(B_HANDLE_BUFFER) done\n");
			break;
		}
		default:
			break;
	}
}


void
AudioProducer::SetPeakListener(BHandler* handler)
{
	fPeakListener = handler;
}


status_t
AudioProducer::ChangeFormat(media_format* format)
{
	TRACE("AudioProducer::ChangeFormat()\n");

	format->u.raw_audio.buffer_size
		= media_raw_audio_format::wildcard.buffer_size;

	status_t ret = _SpecializeFormat(format);
	if (ret != B_OK) {
		TRACE("  _SpecializeFormat(): %s\n", strerror(ret));
		return ret;
	}

	ret = BBufferProducer::ProposeFormatChange(format, fOutput.destination);
	if (ret != B_OK) {
		TRACE("  ProposeFormatChange(): %s\n", strerror(ret));
		return ret;
	}

	ret = BBufferProducer::ChangeFormat(fOutput.source, fOutput.destination,
		format);
	if (ret != B_OK) {
		TRACE("  ChangeFormat(): %s\n", strerror(ret));
		return ret;
	}

	return _ChangeFormat(*format);
}


// #pragma mark -


status_t
AudioProducer::_SpecializeFormat(media_format* format)
{
	// 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) {
		TRACE("  not raw audio\n");
		return B_MEDIA_BAD_FORMAT;
// TODO: we might want to support different audio formats
	} else if (format->u.raw_audio.format
			!= fPreferredFormat.u.raw_audio.format) {
		TRACE("  format does not match\n");
		return B_MEDIA_BAD_FORMAT;
	}

	if (format->u.raw_audio.channel_count
			== media_raw_audio_format::wildcard.channel_count) {
		format->u.raw_audio.channel_count = 2;
		TRACE("  -> adjusting channel count, it was wildcard\n");
	}

	if (format->u.raw_audio.frame_rate
			== media_raw_audio_format::wildcard.frame_rate) {
		format->u.raw_audio.frame_rate = 44100.0;
		TRACE("  -> adjusting frame rate, it was wildcard\n");
	}

	// check the buffer size, which may still be wildcarded
	if (format->u.raw_audio.buffer_size
			== media_raw_audio_format::wildcard.buffer_size) {
		// pick something comfortable to suggest
		TRACE("  -> adjusting buffer size, it was wildcard\n");

		// NOTE: the (buffer_size * 1000000) needs to be dividable by
		// format->u.raw_audio.frame_rate! (We assume frame rate is a multiple of
		// 25, which it usually is.)
		format->u.raw_audio.buffer_size
			= uint32(format->u.raw_audio.frame_rate / 25.0)
				* format->u.raw_audio.channel_count
				* (format->u.raw_audio.format
					& media_raw_audio_format::B_AUDIO_SIZE_MASK);

		if (!fLowLatency)
			format->u.raw_audio.buffer_size *= 3;
	}

	return B_OK;
}


status_t
AudioProducer::_ChangeFormat(const media_format& format)
{
	fOutput.format = format;

	// notify our audio supplier of the format change
	if (fSupplier)
		fSupplier->SetFormat(format);

	return _AllocateBuffers(format);
}


status_t
AudioProducer::_AllocateBuffers(const media_format& format)
{
	TRACE("%p->AudioProducer::_AllocateBuffers()\n", this);

	if (fBufferGroup && fUsingOurBuffers) {
		delete fBufferGroup;
		fBufferGroup = NULL;
	}
	size_t size = format.u.raw_audio.buffer_size;
	int32 bufferDuration = BufferDuration();
	int32 count = 0;
	if (bufferDuration > 0) {
		count = (int32)((fLatency + fInternalLatency)
			/ bufferDuration + 2);
	}

	fBufferGroup = new BBufferGroup(size, count);
	fUsingOurBuffers = true;
	return fBufferGroup->InitCheck();
}


BBuffer*
AudioProducer::_FillNextBuffer(bigtime_t eventTime)
{
	fBufferGroup->WaitForBuffers();
	BBuffer* buffer = fBufferGroup->RequestBuffer(
		fOutput.format.u.raw_audio.buffer_size, BufferDuration());

	if (buffer == NULL) {
		static bool errorPrinted = false;
		if (!errorPrinted) {
			ERROR("AudioProducer::_FillNextBuffer() - no buffer "
				"(size: %" B_PRIuSIZE ", duration: %" B_PRIiBIGTIME ")\n",
				fOutput.format.u.raw_audio.buffer_size, BufferDuration());
			errorPrinted = true;
		}
		return NULL;
	}

	size_t sampleSize = fOutput.format.u.raw_audio.format
		& media_raw_audio_format::B_AUDIO_SIZE_MASK;
	size_t numSamples = fOutput.format.u.raw_audio.buffer_size / sampleSize;
		// number of sample in the buffer

	// fill in the buffer header
	media_header* header = buffer->Header();
	header->type = B_MEDIA_RAW_AUDIO;
	header->time_source = TimeSource()->ID();
	buffer->SetSizeUsed(fOutput.format.u.raw_audio.buffer_size);

	// fill in data from audio supplier
	int64 frameCount = numSamples / fOutput.format.u.raw_audio.channel_count;
	bigtime_t startTime = bigtime_t(double(fFramesSent)
		* 1000000.0 / fOutput.format.u.raw_audio.frame_rate);
	bigtime_t endTime = bigtime_t(double(fFramesSent + frameCount)
		* 1000000.0 / fOutput.format.u.raw_audio.frame_rate);

	if (fSupplier == NULL || fSupplier->InitCheck() != B_OK
		|| fSupplier->GetFrames(buffer->Data(), frameCount, startTime,
			endTime) != B_OK) {
		ERROR("AudioProducer::_FillNextBuffer() - supplier error -> silence\n");
		memset(buffer->Data(), 0, buffer->SizeUsed());
	}

	// stamp buffer
	if (RunMode() == B_RECORDING)
		header->start_time = eventTime;
	else
		header->start_time = fStartTime + startTime;

#if DEBUG_TO_FILE
	BMediaTrack* track;
	if (init_media_file(fOutput.format, &track) != NULL)
		track->WriteFrames(buffer->Data(), frameCount);
#endif // DEBUG_TO_FILE

	if (fPeakListener
		&& fOutput.format.u.raw_audio.format
			== media_raw_audio_format::B_AUDIO_FLOAT) {
		// TODO: extend the peak notifier for other sample formats
		int32 channels = fOutput.format.u.raw_audio.channel_count;
		float max[channels];
		float min[channels];
		for (int32 i = 0; i < channels; i++) {
			max[i] = -1.0;
			min[i] = 1.0;
		}
		float* sample = (float*)buffer->Data();
		for (uint32 i = 0; i < frameCount; i++) {
			for (int32 k = 0; k < channels; k++) {
				if (*sample < min[k])
					min[k] = *sample;
				if (*sample > max[k])
					max[k] = *sample;
				sample++;
			}
		}
		BMessage message(MSG_PEAK_NOTIFICATION);
		for (int32 i = 0; i < channels; i++) {
			float maxAbs = max_c(fabs(min[i]), fabs(max[i]));
			message.AddFloat("max", maxAbs);
		}
		bigtime_t realTime = TimeSource()->RealTimeFor(
			fStartTime + startTime, 0);
		MessageEvent* event = new (std::nothrow) MessageEvent(realTime,
			fPeakListener, message);
		if (event != NULL)
			EventQueue::Default().AddEvent(event);
	}

	return buffer;
}