/*
* Copyright 2015, Dario Casalinuovo. All rights reserved.
* Distributed under the terms of the MIT License.
*/
#include "MediaClientNode.h"
#include <MediaClient.h>
#include <MediaConnection.h>
#include <MediaRoster.h>
#include <scheduler.h>
#include <TimeSource.h>
#include <string.h>
#include "MediaDebug.h"
#define B_NEW_BUFFER (BTimedEventQueue::B_USER_EVENT + 1)
BMediaClientNode::BMediaClientNode(const char* name,
BMediaClient* owner, media_type type)
:
BMediaNode(name),
BBufferConsumer(type),
BBufferProducer(type),
BMediaEventLooper(),
fOwner(owner)
{
CALLED();
// Configure the node to do the requested jobs
if (fOwner->Kinds() & B_MEDIA_PLAYER)
AddNodeKind(B_BUFFER_PRODUCER);
if (fOwner->Kinds() & B_MEDIA_RECORDER)
AddNodeKind(B_BUFFER_CONSUMER);
if (fOwner->Kinds() & B_MEDIA_CONTROLLABLE)
AddNodeKind(B_CONTROLLABLE);
}
status_t
BMediaClientNode::SendBuffer(BBuffer* buffer, BMediaConnection* conn)
{
return BBufferProducer::SendBuffer(buffer, conn->_Source(), conn->_Destination());
}
BMediaAddOn*
BMediaClientNode::AddOn(int32* id) const
{
CALLED();
return fOwner->AddOn(id);
}
void
BMediaClientNode::NodeRegistered()
{
CALLED();
fOwner->ClientRegistered();
Run();
}
void
BMediaClientNode::SetRunMode(run_mode mode)
{
CALLED();
int32 priority;
if (mode == BMediaNode::B_OFFLINE)
priority = B_OFFLINE_PROCESSING;
else {
switch(ConsumerType()) {
case B_MEDIA_RAW_AUDIO:
case B_MEDIA_ENCODED_AUDIO:
priority = B_AUDIO_RECORDING;
break;
case B_MEDIA_RAW_VIDEO:
case B_MEDIA_ENCODED_VIDEO:
priority = B_VIDEO_RECORDING;
break;
default:
priority = B_DEFAULT_MEDIA_PRIORITY;
}
}
SetPriority(suggest_thread_priority(priority));
BMediaNode::SetRunMode(mode);
}
void
BMediaClientNode::Start(bigtime_t performanceTime)
{
CALLED();
BMediaEventLooper::Start(performanceTime);
}
void
BMediaClientNode::Stop(bigtime_t performanceTime, bool immediate)
{
CALLED();
BMediaEventLooper::Stop(performanceTime, immediate);
}
void
BMediaClientNode::Seek(bigtime_t mediaTime, bigtime_t performanceTime)
{
CALLED();
BMediaEventLooper::Seek(mediaTime, performanceTime);
}
void
BMediaClientNode::TimeWarp(bigtime_t realTime, bigtime_t performanceTime)
{
CALLED();
BMediaEventLooper::TimeWarp(realTime, performanceTime);
}
status_t
BMediaClientNode::HandleMessage(int32 message,
const void* data, size_t size)
{
CALLED();
return B_ERROR;
}
status_t
BMediaClientNode::AcceptFormat(const media_destination& dest,
media_format* format)
{
CALLED();
BMediaInput* conn = fOwner->_FindInput(dest);
if (conn == NULL)
return B_MEDIA_BAD_DESTINATION;
return conn->AcceptFormat(format);
}
status_t
BMediaClientNode::GetNextInput(int32* cookie,
media_input* input)
{
CALLED();
if (fOwner->CountInputs() == 0)
return B_BAD_INDEX;
if (*cookie < 0 || *cookie >= fOwner->CountInputs()) {
*cookie = -1;
input = NULL;
} else {
BMediaInput* conn = fOwner->InputAt(*cookie);
if (conn != NULL) {
*input = conn->fConnection._BuildMediaInput();
*cookie += 1;
return B_OK;
}
}
return B_BAD_INDEX;
}
void
BMediaClientNode::DisposeInputCookie(int32 cookie)
{
CALLED();
}
void
BMediaClientNode::BufferReceived(BBuffer* buffer)
{
CALLED();
EventQueue()->AddEvent(media_timed_event(buffer->Header()->start_time,
BTimedEventQueue::B_HANDLE_BUFFER, buffer,
BTimedEventQueue::B_RECYCLE_BUFFER));
}
status_t
BMediaClientNode::GetLatencyFor(const media_destination& dest,
bigtime_t* latency, media_node_id* timesource)
{
CALLED();
BMediaInput* conn = fOwner->_FindInput(dest);
if (conn == NULL)
return B_MEDIA_BAD_DESTINATION;
//*latency = conn->fLatency;
*timesource = TimeSource()->ID();
return B_OK;
}
status_t
BMediaClientNode::Connected(const media_source& source,
const media_destination& dest, const media_format& format,
media_input* outInput)
{
CALLED();
BMediaInput* conn = fOwner->_FindInput(dest);
if (conn == NULL)
return B_MEDIA_BAD_DESTINATION;
conn->fConnection.source = source;
conn->fConnection.format = format;
// Retrieve the node without using GetNodeFor that's pretty inefficient.
// Unfortunately we don't have an alternative which doesn't require us
// to release the cloned node.
// However, our node will not have flags set. Keep in mind this.
conn->fConnection.remote_node.node
= BMediaRoster::CurrentRoster()->NodeIDFor(source.port);
conn->fConnection.remote_node.port = source.port;
conn->Connected(format);
*outInput = conn->fConnection._BuildMediaInput();
return B_OK;
}
void
BMediaClientNode::Disconnected(const media_source& source,
const media_destination& dest)
{
CALLED();
BMediaInput* conn = fOwner->_FindInput(dest);
if (conn == NULL)
return;
if (conn->_Source() == source) {
// Cleanup the connection
conn->fConnection.source = media_source::null;
conn->fConnection.format = media_format();
conn->fConnection.remote_node.node = -1;
conn->fConnection.remote_node.port = -1;
conn->Disconnected();
}
}
status_t
BMediaClientNode::FormatChanged(const media_source& source,
const media_destination& dest,
int32 tag, const media_format& format)
{
CALLED();
return B_ERROR;
}
status_t
BMediaClientNode::FormatSuggestionRequested(media_type type,
int32 quality, media_format* format)
{
CALLED();
if (type != ConsumerType()
&& type != ProducerType()) {
return B_MEDIA_BAD_FORMAT;
}
status_t ret = fOwner->FormatSuggestion(type, quality, format);
if (ret != B_OK) {
// In that case we return just a very generic format.
media_format outFormat;
outFormat.type = fOwner->MediaType();
*format = outFormat;
return B_OK;
}
return ret;
}
status_t
BMediaClientNode::FormatProposal(const media_source& source,
media_format* format)
{
CALLED();
BMediaOutput* conn = fOwner->_FindOutput(source);
if (conn == NULL)
return B_MEDIA_BAD_DESTINATION;
return conn->FormatProposal(format);
}
status_t
BMediaClientNode::FormatChangeRequested(const media_source& source,
const media_destination& dest, media_format* format,
int32* _deprecated_)
{
CALLED();
return B_ERROR;
}
void
BMediaClientNode::LateNoticeReceived(const media_source& source,
bigtime_t late, bigtime_t when)
{
CALLED();
}
status_t
BMediaClientNode::GetNextOutput(int32* cookie, media_output* output)
{
CALLED();
if (fOwner->CountOutputs() == 0)
return B_BAD_INDEX;
if (*cookie < 0 || *cookie >= fOwner->CountOutputs()) {
*cookie = -1;
output = NULL;
} else {
BMediaOutput* conn = fOwner->OutputAt(*cookie);
if (conn != NULL) {
*output = conn->fConnection._BuildMediaOutput();
*cookie += 1;
return B_OK;
}
}
return B_BAD_INDEX;
}
status_t
BMediaClientNode::DisposeOutputCookie(int32 cookie)
{
CALLED();
return B_OK;
}
status_t
BMediaClientNode::SetBufferGroup(const media_source& source, BBufferGroup* group)
{
CALLED();
BMediaOutput* conn = fOwner->_FindOutput(source);
if (conn == NULL)
return B_MEDIA_BAD_SOURCE;
if (group == conn->fBufferGroup)
return B_OK;
delete conn->fBufferGroup;
if (group != NULL) {
conn->fBufferGroup = group;
return B_OK;
}
conn->fBufferGroup = new BBufferGroup(conn->BufferSize(), 3);
if (conn->fBufferGroup == NULL)
return B_NO_MEMORY;
return conn->fBufferGroup->InitCheck();
}
status_t
BMediaClientNode::PrepareToConnect(const media_source& source,
const media_destination& dest, media_format* format,
media_source* out_source, char *name)
{
CALLED();
BMediaOutput* conn = fOwner->_FindOutput(source);
if (conn == NULL)
return B_MEDIA_BAD_SOURCE;
if (conn->_Destination() != media_destination::null)
return B_MEDIA_ALREADY_CONNECTED;
if (fOwner->MediaType() != B_MEDIA_UNKNOWN_TYPE
&& format->type != fOwner->MediaType()) {
return B_MEDIA_BAD_FORMAT;
}
conn->fConnection.destination = dest;
status_t err = conn->PrepareToConnect(format);
if (err != B_OK)
return err;
*out_source = conn->_Source();
strcpy(name, conn->Name());
return B_OK;
}
void
BMediaClientNode::Connect(status_t status, const media_source& source,
const media_destination& dest, const media_format& format,
char* name)
{
CALLED();
BMediaOutput* conn = fOwner->_FindOutput(source);
if (conn == NULL)
return;
// Connection failed, return.
if (status != B_OK)
return;
conn->fConnection.destination = dest;
conn->fConnection.format = format;
// Retrieve the node without using GetNodeFor that's pretty inefficient.
// Unfortunately we don't have an alternative which doesn't require us
// to release the cloned node.
// However, our node will not have flags set. Keep in mind this.
conn->fConnection.remote_node.node
= BMediaRoster::CurrentRoster()->NodeIDFor(dest.port);
conn->fConnection.remote_node.port = dest.port;
strcpy(name, conn->Name());
// TODO: add correct latency estimate
SetEventLatency(1000);
conn->fBufferGroup = new BBufferGroup(conn->BufferSize(), 3);
if (conn->fBufferGroup == NULL)
TRACE("Can't allocate the buffer group\n");
conn->Connected(format);
}
void
BMediaClientNode::Disconnect(const media_source& source,
const media_destination& dest)
{
CALLED();
BMediaOutput* conn = fOwner->_FindOutput(source);
if (conn == NULL)
return;
if (conn->_Destination() == dest) {
// Cleanup the connection
delete conn->fBufferGroup;
conn->fBufferGroup = NULL;
conn->fConnection.destination = media_destination::null;
conn->fConnection.format = media_format();
conn->fConnection.remote_node.node = -1;
conn->fConnection.remote_node.port = -1;
conn->Disconnected();
}
}
void
BMediaClientNode::EnableOutput(const media_source& source,
bool enabled, int32* _deprecated_)
{
CALLED();
BMediaOutput* conn = fOwner->_FindOutput(source);
if (conn != NULL)
conn->_SetEnabled(enabled);
}
status_t
BMediaClientNode::GetLatency(bigtime_t* outLatency)
{
CALLED();
return BBufferProducer::GetLatency(outLatency);
}
void
BMediaClientNode::LatencyChanged(const media_source& source,
const media_destination& dest, bigtime_t latency, uint32 flags)
{
CALLED();
}
void
BMediaClientNode::ProducerDataStatus(const media_destination& dest,
int32 status, bigtime_t when)
{
CALLED();
}
void
BMediaClientNode::HandleEvent(const media_timed_event* event,
bigtime_t late, bool realTimeEvent)
{
CALLED();
switch (event->type) {
// This event is used for inputs which consumes buffers
// or binded connections which also send them to an output.
case BTimedEventQueue::B_HANDLE_BUFFER:
_HandleBuffer((BBuffer*)event->pointer);
break;
// This is used for connections which produce buffers only.
case B_NEW_BUFFER:
_ProduceNewBuffer(event, late);
break;
case BTimedEventQueue::B_START:
{
if (RunState() != B_STARTED)
fOwner->HandleStart(event->event_time);
fStartTime = event->event_time;
_ScheduleConnections(event->event_time);
break;
}
case BTimedEventQueue::B_STOP:
{
fOwner->HandleStop(event->event_time);
EventQueue()->FlushEvents(0, BTimedEventQueue::B_ALWAYS, true,
BTimedEventQueue::B_HANDLE_BUFFER);
break;
}
case BTimedEventQueue::B_SEEK:
fOwner->HandleSeek(event->event_time, event->bigdata);
break;
case BTimedEventQueue::B_WARP:
// NOTE: We have no need to handle it
break;
}
}
BMediaClientNode::~BMediaClientNode()
{
CALLED();
Quit();
}
void
BMediaClientNode::_ScheduleConnections(bigtime_t eventTime)
{
for (int32 i = 0; i < fOwner->CountOutputs(); i++) {
BMediaOutput* output = fOwner->OutputAt(i);
if (output->HasBinding())
continue;
media_timed_event firstBufferEvent(eventTime,
B_NEW_BUFFER);
output->fFramesSent = 0;
firstBufferEvent.pointer = (void*) output;
EventQueue()->AddEvent(firstBufferEvent);
}
}
void
BMediaClientNode::_HandleBuffer(BBuffer* buffer)
{
CALLED();
media_destination dest;
dest.id = buffer->Header()->destination;
BMediaInput* conn = fOwner->_FindInput(dest);
if (conn != NULL)
conn->HandleBuffer(buffer);
// TODO: Investigate system level latency logging
if (conn->HasBinding()) {
BMediaOutput* output = dynamic_cast<BMediaOutput*>(conn->Binding());
output->SendBuffer(buffer);
}
}
void
BMediaClientNode::_ProduceNewBuffer(const media_timed_event* event,
bigtime_t late)
{
CALLED();
if (RunState() != BMediaEventLooper::B_STARTED)
return;
// The connection is get through the event
BMediaOutput* output
= dynamic_cast<BMediaOutput*>((BMediaConnection*)event->pointer);
if (output == NULL)
return;
if (output->_IsEnabled()) {
BBuffer* buffer = _GetNextBuffer(output, event->event_time);
if (buffer != NULL) {
if (output->SendBuffer(buffer) != B_OK) {
TRACE("BMediaClientNode: Failed to send buffer\n");
// The output failed, let's recycle the buffer
buffer->Recycle();
}
}
}
bigtime_t time = 0;
media_format format = output->fConnection.format;
if (format.IsAudio()) {
size_t nFrames = format.u.raw_audio.buffer_size
/ ((format.u.raw_audio.format
& media_raw_audio_format::B_AUDIO_SIZE_MASK)
* format.u.raw_audio.channel_count);
output->fFramesSent += nFrames;
time = fStartTime + bigtime_t((1000000LL * output->fFramesSent)
/ (int32)format.u.raw_audio.frame_rate);
}
media_timed_event nextEvent(time, B_NEW_BUFFER);
EventQueue()->AddEvent(nextEvent);
}
BBuffer*
BMediaClientNode::_GetNextBuffer(BMediaOutput* output, bigtime_t eventTime)
{
CALLED();
BBuffer* buffer = NULL;
if (output->fBufferGroup->RequestBuffer(buffer, 0) != B_OK) {
TRACE("MediaClientNode:::_GetNextBuffer: Failed to get the buffer\n");
return NULL;
}
media_header* header = buffer->Header();
header->type = output->fConnection.format.type;
header->size_used = output->BufferSize();
header->time_source = TimeSource()->ID();
header->start_time = eventTime;
return buffer;
}
↑ V522 Dereferencing of the null pointer 'buffer' might take place.
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fStartTime.