* Copyright 2002-2010, Haiku.
* Distributed under the terms of the MIT License.
*
* Authors:
* Marcus Overhagen
* Jérôme Duval
*/
*/
#include "SoundPlayNode.h"
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <TimeSource.h>
#include <MediaRoster.h>
#include "MediaDebug.h"
#define SEND_NEW_BUFFER_EVENT (BTimedEventQueue::B_USER_EVENT + 1)
namespace BPrivate {
SoundPlayNode::SoundPlayNode(const char* name, BSoundPlayer* player)
:
BMediaNode(name),
BBufferProducer(B_MEDIA_RAW_AUDIO),
BMediaEventLooper(),
fPlayer(player),
fInitStatus(B_OK),
fOutputEnabled(true),
fBufferGroup(NULL),
fFramesSent(0),
fTooEarlyCount(0)
{
CALLED();
fOutput.format.type = B_MEDIA_RAW_AUDIO;
fOutput.format.u.raw_audio = media_multi_audio_format::wildcard;
}
SoundPlayNode::~SoundPlayNode()
{
CALLED();
Quit();
}
bool
SoundPlayNode::IsPlaying()
{
return RunState() == B_STARTED;
}
bigtime_t
SoundPlayNode::CurrentTime()
{
int frameRate = (int)fOutput.format.u.raw_audio.frame_rate;
return frameRate == 0 ? 0
: bigtime_t((1000000LL * fFramesSent) / frameRate);
}
media_multi_audio_format
SoundPlayNode::Format() const
{
return fOutput.format.u.raw_audio;
}
BMediaAddOn*
SoundPlayNode::AddOn(int32* _internalID) const
{
CALLED();
return NULL;
}
void
SoundPlayNode::Preroll()
{
CALLED();
BMediaNode::Preroll();
}
status_t
SoundPlayNode::HandleMessage(int32 message, const void* data, size_t size)
{
CALLED();
return B_ERROR;
}
void
SoundPlayNode::NodeRegistered()
{
CALLED();
if (fInitStatus != B_OK) {
ReportError(B_NODE_IN_DISTRESS);
return;
}
SetPriority(B_URGENT_PRIORITY);
fOutput.format.type = B_MEDIA_RAW_AUDIO;
fOutput.format.u.raw_audio = media_multi_audio_format::wildcard;
fOutput.destination = media_destination::null;
fOutput.source.port = ControlPort();
fOutput.source.id = 0;
fOutput.node = Node();
strcpy(fOutput.name, Name());
Run();
}
status_t
SoundPlayNode::RequestCompleted(const media_request_info& info)
{
CALLED();
return B_OK;
}
void
SoundPlayNode::SetTimeSource(BTimeSource* timeSource)
{
CALLED();
BMediaNode::SetTimeSource(timeSource);
}
void
SoundPlayNode::SetRunMode(run_mode mode)
{
TRACE("SoundPlayNode::SetRunMode mode:%i\n", mode);
BMediaNode::SetRunMode(mode);
}
status_t
SoundPlayNode::FormatSuggestionRequested(media_type type, int32 ,
media_format* format)
{
CALLED();
if (type != B_MEDIA_RAW_AUDIO && type != B_MEDIA_UNKNOWN_TYPE)
return B_MEDIA_BAD_FORMAT;
format->type = B_MEDIA_RAW_AUDIO;
format->u.raw_audio = media_multi_audio_format::wildcard;
return B_OK;
}
status_t
SoundPlayNode::FormatProposal(const media_source& output, media_format* format)
{
CALLED();
if (output != fOutput.source) {
TRACE("SoundPlayNode::FormatProposal returning B_MEDIA_BAD_SOURCE\n");
return B_MEDIA_BAD_SOURCE;
}
if (format->type == B_MEDIA_UNKNOWN_TYPE)
format->type = B_MEDIA_RAW_AUDIO;
if (format->type != B_MEDIA_RAW_AUDIO) {
TRACE("SoundPlayNode::FormatProposal returning B_MEDIA_BAD_FORMAT\n");
return B_MEDIA_BAD_FORMAT;
}
#if DEBUG >0
char buf[100];
string_for_format(*format, buf, sizeof(buf));
TRACE("SoundPlayNode::FormatProposal: format %s\n", buf);
#endif
return B_OK;
}
status_t
SoundPlayNode::FormatChangeRequested(const media_source& source,
const media_destination& destination, media_format* _format,
int32* )
{
CALLED();
return B_ERROR;
}
status_t
SoundPlayNode::GetNextOutput(int32* cookie, media_output* _output)
{
CALLED();
if (*cookie == 0) {
*_output = fOutput;
*cookie += 1;
return B_OK;
} else {
return B_BAD_INDEX;
}
}
status_t
SoundPlayNode::DisposeOutputCookie(int32 cookie)
{
CALLED();
return B_OK;
}
status_t
SoundPlayNode::SetBufferGroup(const media_source& forSource,
BBufferGroup* newGroup)
{
CALLED();
if (forSource != fOutput.source) {
TRACE("SoundPlayNode::SetBufferGroup returning B_MEDIA_BAD_SOURCE\n");
return B_MEDIA_BAD_SOURCE;
}
if (newGroup == fBufferGroup)
return B_OK;
delete fBufferGroup;
if (newGroup != NULL) {
fBufferGroup = newGroup;
return B_OK;
}
return AllocateBuffers();
}
status_t
SoundPlayNode::GetLatency(bigtime_t* _latency)
{
CALLED();
*_latency = EventLatency() + SchedulingLatency();
return B_OK;
}
status_t
SoundPlayNode::PrepareToConnect(const media_source& what,
const media_destination& where, media_format* format,
media_source* _source, char* _name)
{
CALLED();
if (what != fOutput.source) {
TRACE("SoundPlayNode::PrepareToConnect returning "
"B_MEDIA_BAD_SOURCE\n");
return B_MEDIA_BAD_SOURCE;
}
if (fOutput.destination != media_destination::null)
return B_MEDIA_ALREADY_CONNECTED;
#if DEBUG > 0
char buf[100];
string_for_format(*format, buf, sizeof(buf));
TRACE("SoundPlayNode::PrepareToConnect: input format %s\n", buf);
#endif
if (format->type != B_MEDIA_UNKNOWN_TYPE
&& format->type != B_MEDIA_RAW_AUDIO) {
TRACE("SoundPlayNode::PrepareToConnect: non raw format, returning "
"B_MEDIA_BAD_FORMAT\n");
return B_MEDIA_BAD_FORMAT;
}
#define FORMAT_USER_DATA_TYPE 0x7294a8f3
#define FORMAT_USER_DATA_MAGIC_1 0xc84173bd
#define FORMAT_USER_DATA_MAGIC_2 0x4af62b7d
uint32 channel_count = 0;
float frame_rate = 0;
if (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) {
channel_count = *(uint32 *)&format->user_data[4];
frame_rate = *(float *)&format->user_data[20];
TRACE("SoundPlayNode::PrepareToConnect: found mixer info: "
"channel_count %" B_PRId32 " , frame_rate %.1f\n", channel_count, frame_rate);
}
media_format default_format;
default_format.type = B_MEDIA_RAW_AUDIO;
default_format.u.raw_audio.frame_rate = frame_rate > 0 ? frame_rate : 44100;
default_format.u.raw_audio.channel_count = channel_count > 0
? channel_count : 2;
default_format.u.raw_audio.format = media_raw_audio_format::B_AUDIO_FLOAT;
default_format.u.raw_audio.byte_order = B_MEDIA_HOST_ENDIAN;
default_format.u.raw_audio.buffer_size = 0;
format->SpecializeTo(&default_format);
if (format->u.raw_audio.buffer_size == 0) {
format->u.raw_audio.buffer_size
= BMediaRoster::Roster()->AudioBufferSizeFor(
format->u.raw_audio.channel_count, format->u.raw_audio.format,
format->u.raw_audio.frame_rate);
}
#if DEBUG > 0
string_for_format(*format, buf, sizeof(buf));
TRACE("SoundPlayNode::PrepareToConnect: output format %s\n", buf);
#endif
fOutput.destination = where;
fOutput.format = *format;
*_source = fOutput.source;
strcpy(_name, Name());
return B_OK;
}
void
SoundPlayNode::Connect(status_t error, const media_source& source,
const media_destination& destination, const media_format& format,
char* name)
{
CALLED();
if (source != fOutput.source) {
TRACE("SoundPlayNode::Connect returning\n");
return;
}
if (error) {
fOutput.destination = media_destination::null;
fOutput.format.type = B_MEDIA_RAW_AUDIO;
fOutput.format.u.raw_audio = media_multi_audio_format::wildcard;
return;
}
fOutput.destination = destination;
fOutput.format = format;
strcpy(name, Name());
media_node_id id;
FindLatencyFor(fOutput.destination, &fLatency, &id);
TRACE("SoundPlayNode::Connect: downstream latency = %" B_PRId64 "\n",
fLatency);
bigtime_t duration = ((fOutput.format.u.raw_audio.buffer_size * 1000000LL)
/ ((fOutput.format.u.raw_audio.format
& media_raw_audio_format::B_AUDIO_SIZE_MASK)
* fOutput.format.u.raw_audio.channel_count))
/ (int32)fOutput.format.u.raw_audio.frame_rate;
SetBufferDuration(duration);
TRACE("SoundPlayNode::Connect: buffer duration is %" B_PRId64 "\n",
duration);
fInternalLatency = (3 * BufferDuration()) / 4;
TRACE("SoundPlayNode::Connect: using %" B_PRId64 " as internal latency\n",
fInternalLatency);
SetEventLatency(fLatency + fInternalLatency);
if (!fBufferGroup)
AllocateBuffers();
}
void
SoundPlayNode::Disconnect(const media_source& what,
const media_destination& where)
{
CALLED();
if (what != fOutput.source) {
TRACE("SoundPlayNode::Disconnect returning\n");
return;
}
if (where == fOutput.destination && what == fOutput.source) {
fOutput.destination = media_destination::null;
fOutput.format.type = B_MEDIA_RAW_AUDIO;
fOutput.format.u.raw_audio = media_multi_audio_format::wildcard;
delete fBufferGroup;
fBufferGroup = NULL;
} else {
fprintf(stderr, "\tDisconnect() called with wrong source/destination "
"(%" B_PRId32 "/%" B_PRId32 "), ours is (%" B_PRId32 "/%" B_PRId32
")\n", what.id, where.id, fOutput.source.id,
fOutput.destination.id);
}
}
void
SoundPlayNode::LateNoticeReceived(const media_source& what, bigtime_t howMuch,
bigtime_t performanceTime)
{
CALLED();
TRACE("SoundPlayNode::LateNoticeReceived, %" B_PRId64 " too late at %"
B_PRId64 "\n", howMuch, performanceTime);
if (what != fOutput.source) {
TRACE("SoundPlayNode::LateNoticeReceived returning\n");
return;
}
if (RunMode() != B_DROP_DATA) {
fInternalLatency += howMuch;
if (fInternalLatency > 30000)
fInternalLatency = 30000;
SetEventLatency(fLatency + fInternalLatency);
TRACE("SoundPlayNode::LateNoticeReceived: increasing latency to %"
B_PRId64 "\n", fLatency + fInternalLatency);
} else {
size_t nFrames = fOutput.format.u.raw_audio.buffer_size
/ ((fOutput.format.u.raw_audio.format & media_raw_audio_format::B_AUDIO_SIZE_MASK)
* fOutput.format.u.raw_audio.channel_count);
fFramesSent += nFrames;
TRACE("SoundPlayNode::LateNoticeReceived: skipping a buffer to try to catch up\n");
}
}
void
SoundPlayNode::EnableOutput(const media_source& what, bool enabled,
int32* )
{
CALLED();
if (what != fOutput.source) {
fprintf(stderr, "SoundPlayNode::EnableOutput returning\n");
return;
}
fOutputEnabled = enabled;
}
void
SoundPlayNode::AdditionalBufferRequested(const media_source& source,
media_buffer_id previousBuffer, bigtime_t previousTime,
const media_seek_tag* previousTag)
{
CALLED();
return;
}
void
SoundPlayNode::LatencyChanged(const media_source& source,
const media_destination& destination, bigtime_t newLatency, uint32 flags)
{
CALLED();
TRACE("SoundPlayNode::LatencyChanged: new_latency %" B_PRId64 "\n",
newLatency);
if (source == fOutput.source && destination == fOutput.destination) {
fLatency = newLatency;
SetEventLatency(fLatency + fInternalLatency);
} else {
TRACE("SoundPlayNode::LatencyChanged: ignored\n");
}
}
void
SoundPlayNode::HandleEvent(const media_timed_event* event, bigtime_t lateness,
bool realTimeEvent)
{
CALLED();
switch (event->type) {
case BTimedEventQueue::B_START:
HandleStart(event,lateness,realTimeEvent);
break;
case BTimedEventQueue::B_SEEK:
HandleSeek(event,lateness,realTimeEvent);
break;
case BTimedEventQueue::B_WARP:
HandleWarp(event,lateness,realTimeEvent);
break;
case BTimedEventQueue::B_STOP:
HandleStop(event,lateness,realTimeEvent);
break;
case BTimedEventQueue::B_HANDLE_BUFFER:
break;
case SEND_NEW_BUFFER_EVENT:
if (RunState() == BMediaEventLooper::B_STARTED)
SendNewBuffer(event, lateness, realTimeEvent);
break;
case BTimedEventQueue::B_DATA_STATUS:
HandleDataStatus(event,lateness,realTimeEvent);
break;
case BTimedEventQueue::B_PARAMETER:
HandleParameter(event,lateness,realTimeEvent);
break;
default:
fprintf(stderr," unknown event type: %" B_PRId32 "\n", event->type);
break;
}
}
status_t
SoundPlayNode::SendNewBuffer(const media_timed_event* event,
bigtime_t lateness, bool realTimeEvent)
{
CALLED();
if (RunState() != BMediaEventLooper::B_STARTED
|| fOutput.destination == media_destination::null)
return B_OK;
if (lateness > (BufferDuration() / 3) ) {
TRACE("SoundPlayNode::SendNewBuffer, event scheduled much too late, "
"lateness is %" B_PRId64 "\n", lateness);
}
if (fOutputEnabled) {
BBuffer* buffer = FillNextBuffer(event->event_time);
if (buffer) {
bigtime_t how_early = event->event_time - TimeSource()->Now() - fLatency - fInternalLatency;
if (how_early > 5000) {
TRACE("SoundPlayNode::SendNewBuffer, event scheduled too early, how_early is %lld\n", how_early);
if (fTooEarlyCount++ == 5) {
fInternalLatency -= how_early;
if (fInternalLatency < 500)
fInternalLatency = 500;
TRACE("SoundPlayNode::SendNewBuffer setting internal latency to %lld\n", fInternalLatency);
SetEventLatency(fLatency + fInternalLatency);
fTooEarlyCount = 0;
}
}
*/
if (SendBuffer(buffer, fOutput.source, fOutput.destination)
!= B_OK) {
TRACE("SoundPlayNode::SendNewBuffer: Buffer sending "
"failed\n");
buffer->Recycle();
}
}
}
size_t nFrames = fOutput.format.u.raw_audio.buffer_size
/ ((fOutput.format.u.raw_audio.format
& media_raw_audio_format::B_AUDIO_SIZE_MASK)
* fOutput.format.u.raw_audio.channel_count);
fFramesSent += nFrames;
bigtime_t nextEvent = fStartTime + bigtime_t((1000000LL * fFramesSent)
/ (int32)fOutput.format.u.raw_audio.frame_rate);
media_timed_event nextBufferEvent(nextEvent, SEND_NEW_BUFFER_EVENT);
EventQueue()->AddEvent(nextBufferEvent);
return B_OK;
}
status_t
SoundPlayNode::HandleDataStatus(const media_timed_event* event,
bigtime_t lateness, bool realTimeEvent)
{
TRACE("SoundPlayNode::HandleDataStatus status: %" B_PRId32 ", lateness: %"
B_PRId64 "\n", event->data, lateness);
switch (event->data) {
case B_DATA_NOT_AVAILABLE:
break;
case B_DATA_AVAILABLE:
break;
case B_PRODUCER_STOPPED:
break;
default:
break;
}
return B_OK;
}
status_t
SoundPlayNode::HandleStart(const media_timed_event* event, bigtime_t lateness,
bool realTimeEvent)
{
CALLED();
if (RunState() != B_STARTED) {
fFramesSent = 0;
fStartTime = event->event_time;
media_timed_event firstBufferEvent(event->event_time,
SEND_NEW_BUFFER_EVENT);
EventQueue()->AddEvent(firstBufferEvent);
}
return B_OK;
}
status_t
SoundPlayNode::HandleSeek(const media_timed_event* event, bigtime_t lateness,
bool realTimeEvent)
{
CALLED();
TRACE("SoundPlayNode::HandleSeek(t=%" B_PRId64 ", d=%" B_PRId32 ", bd=%"
B_PRId64 ")\n", event->event_time, event->data, event->bigdata);
return B_OK;
}
status_t
SoundPlayNode::HandleWarp(const media_timed_event* event, bigtime_t lateness,
bool realTimeEvent)
{
CALLED();
return B_OK;
}
status_t
SoundPlayNode::HandleStop(const media_timed_event* event, bigtime_t lateness,
bool realTimeEvent)
{
CALLED();
EventQueue()->FlushEvents(0, BTimedEventQueue::B_ALWAYS, true,
SEND_NEW_BUFFER_EVENT);
return B_OK;
}
status_t
SoundPlayNode::HandleParameter(const media_timed_event* event,
bigtime_t lateness, bool realTimeEvent)
{
CALLED();
return B_OK;
}
status_t
SoundPlayNode::AllocateBuffers()
{
CALLED();
size_t size = fOutput.format.u.raw_audio.buffer_size;
int32 count = int32(fLatency / BufferDuration() + 1 + 1);
TRACE("SoundPlayNode::AllocateBuffers: latency = %" B_PRId64 ", buffer "
"duration = %" B_PRId64 ", count %" B_PRId32 "\n", fLatency,
BufferDuration(), count);
if (count < 3)
count = 3;
TRACE("SoundPlayNode::AllocateBuffers: creating group of %" B_PRId32
" buffers, size = %" B_PRIuSIZE "\n", count, size);
fBufferGroup = new BBufferGroup(size, count);
if (fBufferGroup->InitCheck() != B_OK) {
ERROR("SoundPlayNode::AllocateBuffers: BufferGroup::InitCheck() "
"failed\n");
}
return fBufferGroup->InitCheck();
}
BBuffer*
SoundPlayNode::FillNextBuffer(bigtime_t eventTime)
{
CALLED();
BBuffer* buffer = fBufferGroup->RequestBuffer(
fOutput.format.u.raw_audio.buffer_size, BufferDuration() / 2);
if (buffer == NULL) {
ERROR("SoundPlayNode::FillNextBuffer: RequestBuffer failed\n");
return NULL;
}
if (fPlayer->HasData()) {
fPlayer->PlayBuffer(buffer->Data(),
fOutput.format.u.raw_audio.buffer_size, fOutput.format.u.raw_audio);
} else
memset(buffer->Data(), 0, fOutput.format.u.raw_audio.buffer_size);
media_header* header = buffer->Header();
header->type = B_MEDIA_RAW_AUDIO;
header->size_used = fOutput.format.u.raw_audio.buffer_size;
header->time_source = TimeSource()->ID();
header->start_time = eventTime;
return buffer;
}
}