Replace SoundCounsumer with modified BMediaRecorder.
Signed-off-by: Hamish Morrison <[email protected]>
This commit is contained in:
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2014, Haiku, Inc. All rights reserved.
|
||||||
|
* Distributed under the terms of the MIT License.
|
||||||
|
*/
|
||||||
|
#ifndef _MEDIA_RECORDER_H
|
||||||
|
#define _MEDIA_RECORDER_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <MediaNode.h>
|
||||||
|
#include <TimeSource.h>
|
||||||
|
|
||||||
|
#include "MediaRecorderNode.h"
|
||||||
|
#include "SoundUtils.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace BPrivate { namespace media {
|
||||||
|
|
||||||
|
class BMediaRecorder {
|
||||||
|
public:
|
||||||
|
BMediaRecorder(const char* name,
|
||||||
|
media_type type
|
||||||
|
= B_MEDIA_UNKNOWN_TYPE);
|
||||||
|
|
||||||
|
virtual ~BMediaRecorder();
|
||||||
|
|
||||||
|
status_t InitCheck() const;
|
||||||
|
|
||||||
|
typedef void (*ProcessFunc)(void* cookie,
|
||||||
|
bigtime_t timestamp, void* data,
|
||||||
|
size_t datasize,
|
||||||
|
const media_format& format);
|
||||||
|
|
||||||
|
typedef void (*NotifyFunc)(void* cookie,
|
||||||
|
int32 code, ...);
|
||||||
|
|
||||||
|
status_t SetHooks(ProcessFunc recordFunc = NULL,
|
||||||
|
NotifyFunc notifyFunc = NULL,
|
||||||
|
void* cookie = NULL);
|
||||||
|
|
||||||
|
void SetAcceptedFormat(
|
||||||
|
const media_format& format);
|
||||||
|
|
||||||
|
virtual status_t Start(bool force = false);
|
||||||
|
virtual status_t Stop(bool force = false);
|
||||||
|
|
||||||
|
virtual status_t Connect(const media_format& format,
|
||||||
|
uint32 flags = 0);
|
||||||
|
|
||||||
|
virtual status_t Connect(const dormant_node_info& dormantInfo,
|
||||||
|
const media_format* format = NULL,
|
||||||
|
uint32 flags = 0);
|
||||||
|
|
||||||
|
virtual status_t Connect(const media_node& node,
|
||||||
|
const media_output* useOutput = NULL,
|
||||||
|
const media_format* format = NULL,
|
||||||
|
uint32 flags = 0);
|
||||||
|
|
||||||
|
virtual status_t Disconnect();
|
||||||
|
|
||||||
|
bool IsConnected() const;
|
||||||
|
bool IsRunning() const;
|
||||||
|
|
||||||
|
const media_format& Format() const;
|
||||||
|
|
||||||
|
const media_output& MediaOutput() const;
|
||||||
|
const media_input& MediaInput() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
virtual void BufferReceived(void* buffer,
|
||||||
|
size_t size,
|
||||||
|
const media_header& header);
|
||||||
|
private:
|
||||||
|
|
||||||
|
status_t _Connect(
|
||||||
|
const media_format* format,
|
||||||
|
uint32 flags,
|
||||||
|
const dormant_node_info* dormantNode,
|
||||||
|
const media_node* mediaNode,
|
||||||
|
const media_output* output);
|
||||||
|
|
||||||
|
status_t fInitErr;
|
||||||
|
|
||||||
|
bool fConnected;
|
||||||
|
bool fRunning;
|
||||||
|
|
||||||
|
BTimeSource* fTimeSource;
|
||||||
|
|
||||||
|
ProcessFunc fRecordHook;
|
||||||
|
NotifyFunc fNotifyHook;
|
||||||
|
|
||||||
|
media_node fOutputNode;
|
||||||
|
media_output fOutput;
|
||||||
|
|
||||||
|
BMediaRecorderNode* fNode;
|
||||||
|
media_input fInput;
|
||||||
|
|
||||||
|
void* fBufferCookie;
|
||||||
|
|
||||||
|
friend class BMediaRecorderNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace BPrivate::media;
|
||||||
|
|
||||||
|
#endif // _MEDIA_RECORDER_H
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 1999, Be Incorporated
|
||||||
|
* Copyright 2014, Dario Casalinuovo
|
||||||
|
* All Rights Reserved.
|
||||||
|
* This file may be used under the terms of the Be Sample Code License.
|
||||||
|
*/
|
||||||
|
#ifndef _MEDIA_RECORDER_NODE_H
|
||||||
|
#define _MEDIA_RECORDER_NODE_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <BufferConsumer.h>
|
||||||
|
#include <MediaEventLooper.h>
|
||||||
|
#include <String.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace BPrivate { namespace media {
|
||||||
|
|
||||||
|
class BMediaRecorder;
|
||||||
|
|
||||||
|
class BMediaRecorderNode : public BMediaEventLooper,
|
||||||
|
public BBufferConsumer {
|
||||||
|
public:
|
||||||
|
BMediaRecorderNode(const char* name,
|
||||||
|
BMediaRecorder* recorder,
|
||||||
|
media_type type
|
||||||
|
= B_MEDIA_UNKNOWN_TYPE);
|
||||||
|
|
||||||
|
// TODO these are not thread safe; we should fix that...
|
||||||
|
void SetAcceptedFormat(const media_format& format);
|
||||||
|
|
||||||
|
status_t GetInput(media_input* outInput);
|
||||||
|
|
||||||
|
void SetDataEnabled(bool enabled);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
virtual BMediaAddOn* AddOn(int32* id) const;
|
||||||
|
|
||||||
|
virtual void NodeRegistered();
|
||||||
|
|
||||||
|
virtual void SetRunMode(run_mode mode);
|
||||||
|
|
||||||
|
virtual void HandleEvent(const media_timed_event* event,
|
||||||
|
bigtime_t lateness,
|
||||||
|
bool realTimeEvent);
|
||||||
|
|
||||||
|
virtual void Start(bigtime_t performanceTime);
|
||||||
|
|
||||||
|
virtual void Stop(bigtime_t performanceTime,
|
||||||
|
bool immediate);
|
||||||
|
|
||||||
|
virtual void Seek(bigtime_t mediaTime,
|
||||||
|
bigtime_t performanceTime);
|
||||||
|
|
||||||
|
virtual void TimeWarp(bigtime_t realTime,
|
||||||
|
bigtime_t performanceTime);
|
||||||
|
|
||||||
|
virtual status_t HandleMessage(int32 message,
|
||||||
|
const void* data,
|
||||||
|
size_t size);
|
||||||
|
|
||||||
|
// Someone, probably the producer, is asking you about
|
||||||
|
// this format. Give your honest opinion, possibly
|
||||||
|
// modifying *format. Do not ask upstream producer about
|
||||||
|
// the format, since he's synchronously waiting for your
|
||||||
|
// reply.
|
||||||
|
|
||||||
|
virtual status_t AcceptFormat(const media_destination& dest,
|
||||||
|
media_format* format);
|
||||||
|
|
||||||
|
virtual status_t GetNextInput(int32* cookie,
|
||||||
|
media_input* outInput);
|
||||||
|
|
||||||
|
virtual void DisposeInputCookie(int32 cookie);
|
||||||
|
|
||||||
|
virtual void BufferReceived(BBuffer* buffer);
|
||||||
|
|
||||||
|
virtual void ProducerDataStatus(
|
||||||
|
const media_destination& forWhom,
|
||||||
|
int32 status,
|
||||||
|
bigtime_t performanceTime);
|
||||||
|
|
||||||
|
virtual status_t GetLatencyFor(
|
||||||
|
const media_destination& forWhom,
|
||||||
|
bigtime_t* outLatency,
|
||||||
|
media_node_id* outTimesource);
|
||||||
|
|
||||||
|
virtual status_t Connected(
|
||||||
|
const media_source& producer,
|
||||||
|
const media_destination& where,
|
||||||
|
const media_format& withFormat,
|
||||||
|
media_input* outInput);
|
||||||
|
|
||||||
|
virtual void Disconnected(
|
||||||
|
const media_source& producer,
|
||||||
|
const media_destination& where);
|
||||||
|
|
||||||
|
virtual status_t FormatChanged(
|
||||||
|
const media_source& producer,
|
||||||
|
const media_destination& consumer,
|
||||||
|
int32 tag,
|
||||||
|
const media_format& format);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
virtual ~BMediaRecorderNode();
|
||||||
|
|
||||||
|
BMediaRecorder* fRecorder;
|
||||||
|
media_format fOKFormat;
|
||||||
|
media_input fInput;
|
||||||
|
BString fName;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using namespace BPrivate::media;
|
||||||
|
|
||||||
|
#endif // _MEDIA_RECORDER_NODE_H
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
/*******************************************************************************
|
|
||||||
/
|
|
||||||
/ File: SoundConsumer.h
|
|
||||||
/
|
|
||||||
/ Description: Record sound from some sound-producing Node.
|
|
||||||
/
|
|
||||||
/ Copyright 1998, Be Incorporated, All Rights Reserved
|
|
||||||
/
|
|
||||||
*******************************************************************************/
|
|
||||||
#ifndef SOUND_CONSUMER_H
|
|
||||||
#define SOUND_CONSUMER_H
|
|
||||||
|
|
||||||
// To use this Consumer:
|
|
||||||
// 1. Create Record and Notify hooks, or subclass SoundConsumer
|
|
||||||
// if you'd rather use the inheritance hierarchy.
|
|
||||||
// * The Record function should do whatever you want to do
|
|
||||||
// when you receive a buffer.
|
|
||||||
// * The Notify function should handle whatever events
|
|
||||||
// you wish to handle (defined in SoundUtil.h).
|
|
||||||
// 2: Create an instance of SoundConsumer, giving it the
|
|
||||||
// appropriate hook functions. Or, create an instance of an
|
|
||||||
// appropriate subclass if you've made one.
|
|
||||||
// 3: Register your new Consumer with the MediaRoster.
|
|
||||||
// 4: Connect your Consumer to some Producer.
|
|
||||||
// 5: Start or Stop the Consumer if your hook functions
|
|
||||||
// implement behavior for these kinds of events.
|
|
||||||
// Seek the Consumer to set the offset of the timestamps that
|
|
||||||
// your Record function will see.
|
|
||||||
// 6: When you're done, disconnect the Consumer, then delete it.
|
|
||||||
|
|
||||||
#include <BufferConsumer.h>
|
|
||||||
#include "SoundUtils.h"
|
|
||||||
|
|
||||||
namespace BPrivate {
|
|
||||||
|
|
||||||
|
|
||||||
class SoundConsumer : public BBufferConsumer {
|
|
||||||
public:
|
|
||||||
SoundConsumer(const char* name,
|
|
||||||
SoundProcessFunc recordFunc = NULL,
|
|
||||||
SoundNotifyFunc notifyFunc = NULL,
|
|
||||||
void* cookie = NULL);
|
|
||||||
virtual ~SoundConsumer();
|
|
||||||
|
|
||||||
//This function is OK to call from any thread.
|
|
||||||
status_t SetHooks(SoundProcessFunc recordFunc = NULL,
|
|
||||||
SoundNotifyFunc notifyFunc = NULL,
|
|
||||||
void* cookie = NULL);
|
|
||||||
|
|
||||||
// The MediaNode interface
|
|
||||||
public:
|
|
||||||
virtual port_id ControlPort() const;
|
|
||||||
virtual BMediaAddOn* AddOn(int32* internalID) const;
|
|
||||||
// Who instantiated you -- or NULL for app class
|
|
||||||
|
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual void Start(bigtime_t performanceTime);
|
|
||||||
virtual void Stop(bigtime_t performanceTime, bool immediate);
|
|
||||||
virtual void Seek(bigtime_t mediaTime,
|
|
||||||
bigtime_t performanceTime);
|
|
||||||
virtual void SetRunMode(run_mode mode);
|
|
||||||
virtual void TimeWarp(bigtime_t atRealTime,
|
|
||||||
bigtime_t to_performanceTime);
|
|
||||||
virtual void Preroll();
|
|
||||||
virtual void SetTimeSource(BTimeSource* timeSource);
|
|
||||||
virtual status_t HandleMessage(int32 message, const void* data,
|
|
||||||
size_t size);
|
|
||||||
|
|
||||||
// The BufferConsumer interface
|
|
||||||
virtual status_t AcceptFormat(const media_destination& dest,
|
|
||||||
media_format* format);
|
|
||||||
virtual status_t GetNextInput(int32* cookie,
|
|
||||||
media_input* _input);
|
|
||||||
virtual void DisposeInputCookie(int32 cookie);
|
|
||||||
virtual void BufferReceived(BBuffer* buffer);
|
|
||||||
virtual void ProducerDataStatus(
|
|
||||||
const media_destination& forWhom,
|
|
||||||
int32 status, bigtime_t atMediaTime);
|
|
||||||
virtual status_t GetLatencyFor(const media_destination& forWhom,
|
|
||||||
bigtime_t* _latency,
|
|
||||||
media_node_id* _timesource);
|
|
||||||
virtual status_t Connected(const media_source& producer,
|
|
||||||
const media_destination& where,
|
|
||||||
const media_format& format,
|
|
||||||
media_input* _input);
|
|
||||||
virtual void Disconnected(const media_source& producer,
|
|
||||||
const media_destination& where);
|
|
||||||
virtual status_t FormatChanged(const media_source& producer,
|
|
||||||
const media_destination& consumer,
|
|
||||||
int32 fromChangeCount,
|
|
||||||
const media_format& format);
|
|
||||||
|
|
||||||
protected:
|
|
||||||
// Functions called when no hooks are installed.
|
|
||||||
// OK to override instead of installing hooks.
|
|
||||||
virtual void Record(bigtime_t time, const void* data,
|
|
||||||
size_t size,
|
|
||||||
const media_raw_audio_format& format);
|
|
||||||
virtual void Notify(int32 cause, ...);
|
|
||||||
|
|
||||||
private:
|
|
||||||
SoundProcessFunc m_recordHook;
|
|
||||||
SoundNotifyFunc m_notifyHook;
|
|
||||||
void* m_cookie;
|
|
||||||
media_input m_input;
|
|
||||||
thread_id m_thread;
|
|
||||||
port_id m_port;
|
|
||||||
|
|
||||||
// The times we need to deal with
|
|
||||||
// My notation for times: tr = real time,
|
|
||||||
// tp = performance time, tm = media time.
|
|
||||||
bigtime_t m_trTimeout;
|
|
||||||
// how long to wait on the input port
|
|
||||||
bigtime_t m_tpSeekAt;
|
|
||||||
// when we Seek
|
|
||||||
bigtime_t m_tmSeekTo;
|
|
||||||
// target time for Seek
|
|
||||||
|
|
||||||
// The transformation from media to peformance time.
|
|
||||||
// d = p - m, so m + d = p.
|
|
||||||
// Media time is generally governed by the Seek
|
|
||||||
// function. In our node, we simply use media time as
|
|
||||||
// the time that we report to the record hook function.
|
|
||||||
// If we were a producer node, we might use media time
|
|
||||||
// to track where we were in playing a certain piece
|
|
||||||
// of media. But we aren't.
|
|
||||||
bigtime_t m_delta;
|
|
||||||
|
|
||||||
// State variables
|
|
||||||
bool m_seeking;
|
|
||||||
// a Seek is pending
|
|
||||||
|
|
||||||
// Functions to calculate timing values. OK to override.
|
|
||||||
// ProcessingLatency is the time it takes to process a buffer;
|
|
||||||
// TotalLatency is returned to producer; Timeout is passed
|
|
||||||
// to call to read_port_etc() in service thread loop.
|
|
||||||
virtual bigtime_t Timeout();
|
|
||||||
virtual bigtime_t ProcessingLatency();
|
|
||||||
virtual bigtime_t TotalLatency();
|
|
||||||
// The actual thread doing the work
|
|
||||||
static status_t ThreadEntry(void* cookie);
|
|
||||||
void ServiceThread();
|
|
||||||
void DoHookChange(void* message);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif // SOUND_CONSUMER_H
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2005, Jérôme Duval. All rights reserved.
|
* Copyright 2005, Jérôme Duval. All rights reserved.
|
||||||
|
* Copyright 2014, Dario Casalinuovo. All rights reserved.
|
||||||
* Distributed under the terms of the MIT License.
|
* Distributed under the terms of the MIT License.
|
||||||
*
|
*
|
||||||
* Inspired by SoundCapture from Be newsletter (Media Kit Basics:
|
* Inspired by SoundCapture from Be newsletter (Media Kit Basics:
|
||||||
@@ -42,8 +43,8 @@
|
|||||||
#include <TimeSource.h>
|
#include <TimeSource.h>
|
||||||
#include <NodeInfo.h>
|
#include <NodeInfo.h>
|
||||||
|
|
||||||
|
#include "SoundUtils.h"
|
||||||
#include "RecorderWindow.h"
|
#include "RecorderWindow.h"
|
||||||
#include "SoundConsumer.h"
|
|
||||||
#include "FileUtils.h"
|
#include "FileUtils.h"
|
||||||
|
|
||||||
#if ! NDEBUG
|
#if ! NDEBUG
|
||||||
@@ -123,7 +124,7 @@ RecorderWindow::RecorderWindow()
|
|||||||
fSaveButton = NULL;
|
fSaveButton = NULL;
|
||||||
fLoopButton = NULL;
|
fLoopButton = NULL;
|
||||||
fInputField = NULL;
|
fInputField = NULL;
|
||||||
fRecordNode = 0;
|
fRecorder = NULL;
|
||||||
fRecording = false;
|
fRecording = false;
|
||||||
fTempCount = -1;
|
fTempCount = -1;
|
||||||
fButtonState = btnPaused;
|
fButtonState = btnPaused;
|
||||||
@@ -146,14 +147,15 @@ RecorderWindow::RecorderWindow()
|
|||||||
|
|
||||||
RecorderWindow::~RecorderWindow()
|
RecorderWindow::~RecorderWindow()
|
||||||
{
|
{
|
||||||
// The sound consumer and producer are Nodes; it has to be released and the Roster
|
// The MediaRecorder have to be deleted, the dtor
|
||||||
// will reap it when it's done.
|
// disconnect it from the media_kit.
|
||||||
if (fRecordNode)
|
delete fRecorder;
|
||||||
fRecordNode->Release();
|
|
||||||
delete fPlayer;
|
delete fPlayer;
|
||||||
|
|
||||||
if (fPlayTrack && fPlayFile)
|
if (fPlayTrack && fPlayFile)
|
||||||
fPlayFile->ReleaseTrack(fPlayTrack);
|
fPlayFile->ReleaseTrack(fPlayTrack);
|
||||||
|
|
||||||
if (fPlayFile)
|
if (fPlayFile)
|
||||||
delete fPlayFile;
|
delete fPlayFile;
|
||||||
fPlayTrack = NULL;
|
fPlayTrack = NULL;
|
||||||
@@ -232,17 +234,25 @@ RecorderWindow::InitWindow()
|
|||||||
if (error < B_OK) // there's no mixer?
|
if (error < B_OK) // there's no mixer?
|
||||||
goto bad_mojo;
|
goto bad_mojo;
|
||||||
|
|
||||||
// Create our internal Node which records sound, and register it.
|
fRecorder = new BMediaRecorder("Sound Recorder",
|
||||||
fRecordNode = new SoundConsumer("Sound Recorder");
|
B_MEDIA_RAW_AUDIO);
|
||||||
error = fRoster->RegisterNode(fRecordNode);
|
|
||||||
if (error < B_OK)
|
if (fRecorder->InitCheck() < B_OK)
|
||||||
goto bad_mojo;
|
goto bad_mojo;
|
||||||
|
|
||||||
|
// Set the node to accept only audio data
|
||||||
|
media_format output_format;
|
||||||
|
output_format.type = B_MEDIA_RAW_AUDIO;
|
||||||
|
output_format.u.raw_audio = media_raw_audio_format::wildcard;
|
||||||
|
fRecorder->SetAcceptedFormat(output_format);
|
||||||
|
|
||||||
// Create the window header with controls
|
// Create the window header with controls
|
||||||
BRect r(Bounds());
|
BRect r(Bounds());
|
||||||
r.bottom = r.top + 175;
|
r.bottom = r.top + 175;
|
||||||
BBox *background = new BBox(r, "_background", B_FOLLOW_LEFT_RIGHT
|
BBox *background = new BBox(r, "_background",
|
||||||
| B_FOLLOW_TOP, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE_JUMP, B_NO_BORDER);
|
B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP, B_WILL_DRAW
|
||||||
|
| B_FRAME_EVENTS | B_NAVIGABLE_JUMP, B_NO_BORDER);
|
||||||
|
|
||||||
AddChild(background);
|
AddChild(background);
|
||||||
|
|
||||||
r = background->Bounds();
|
r = background->Bounds();
|
||||||
@@ -451,9 +461,7 @@ RecorderWindow::InitWindow()
|
|||||||
dormant_node_info dni[maxInputCount];
|
dormant_node_info dni[maxInputCount];
|
||||||
|
|
||||||
int32 real_count = maxInputCount;
|
int32 real_count = maxInputCount;
|
||||||
media_format output_format;
|
|
||||||
output_format.type = B_MEDIA_RAW_AUDIO;
|
|
||||||
output_format.u.raw_audio = media_raw_audio_format::wildcard;
|
|
||||||
error = fRoster->GetDormantNodes(dni, &real_count, 0, &output_format,
|
error = fRoster->GetDormantNodes(dni, &real_count, 0, &output_format,
|
||||||
0, B_BUFFER_PRODUCER | B_PHYSICAL_INPUT);
|
0, B_BUFFER_PRODUCER | B_PHYSICAL_INPUT);
|
||||||
if (real_count > maxInputCount) {
|
if (real_count > maxInputCount) {
|
||||||
@@ -514,8 +522,8 @@ RecorderWindow::InitWindow()
|
|||||||
bad_mojo:
|
bad_mojo:
|
||||||
if (error >= 0)
|
if (error >= 0)
|
||||||
error = B_ERROR;
|
error = B_ERROR;
|
||||||
if (fRecordNode)
|
if (fRecorder)
|
||||||
fRecordNode->Release();
|
delete fRecorder;
|
||||||
|
|
||||||
delete fPlayer;
|
delete fPlayer;
|
||||||
if (!fInputField)
|
if (!fInputField)
|
||||||
@@ -696,14 +704,7 @@ RecorderWindow::Record(BMessage * message)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// And get it going...
|
fRecorder->Start();
|
||||||
bigtime_t then = fRecordNode->TimeSource()->Now() + 50000LL;
|
|
||||||
fRoster->StartNode(fRecordNode->Node(), then);
|
|
||||||
if (fAudioInputNode.kind & B_TIME_SOURCE) {
|
|
||||||
fRoster->StartNode(fAudioInputNode,
|
|
||||||
fRecordNode->TimeSource()->RealTimeFor(then, 0));
|
|
||||||
} else
|
|
||||||
fRoster->StartNode(fAudioInputNode, then);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -871,93 +872,60 @@ RecorderWindow::MakeRecordConnection(const media_node & input)
|
|||||||
{
|
{
|
||||||
CONNECT((stderr, "RecorderWindow::MakeRecordConnection()\n"));
|
CONNECT((stderr, "RecorderWindow::MakeRecordConnection()\n"));
|
||||||
|
|
||||||
// Find an available output for the given input node.
|
status_t err = B_OK;
|
||||||
int32 count = 0;
|
media_output audioOutput;
|
||||||
status_t err = fRoster->GetFreeOutputsFor(input, &fAudioOutput, 1, &count, B_MEDIA_RAW_AUDIO);
|
|
||||||
if (err < B_OK) {
|
if (!fRecorder->IsConnected()) {
|
||||||
|
// Find an available output for the given input node.
|
||||||
|
int32 count = 0;
|
||||||
|
err = fRoster->GetFreeOutputsFor(input, &audioOutput, 1,
|
||||||
|
&count, B_MEDIA_RAW_AUDIO);
|
||||||
|
|
||||||
|
if (err < B_OK) {
|
||||||
|
CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
|
||||||
|
" couldn't get free outputs from audio input node\n"));
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count < 1) {
|
||||||
|
CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
|
||||||
|
" no free outputs from audio input node\n"));
|
||||||
|
return B_BUSY;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
|
CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
|
||||||
" couldn't get free outputs from audio input node\n"));
|
" audio input node already connected\n"));
|
||||||
return err;
|
|
||||||
}
|
|
||||||
if (count < 1) {
|
|
||||||
CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
|
|
||||||
" no free outputs from audio input node\n"));
|
|
||||||
return B_BUSY;
|
return B_BUSY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find an available input for our own Node. Note that we go through the
|
|
||||||
// MediaRoster; calling Media Kit methods directly on Nodes in our app is
|
|
||||||
// not OK (because synchronization happens in the service thread, not in
|
|
||||||
// the calling thread).
|
|
||||||
// TODO: explain this
|
|
||||||
err = fRoster->GetFreeInputsFor(fRecordNode->Node(), &fRecInput, 1, &count, B_MEDIA_RAW_AUDIO);
|
|
||||||
if (err < B_OK) {
|
|
||||||
CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
|
|
||||||
" couldn't get free inputs for sound recorder\n"));
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
if (count < 1) {
|
|
||||||
CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
|
|
||||||
" no free inputs for sound recorder\n"));
|
|
||||||
return B_BUSY;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find out what the time source of the input is.
|
|
||||||
// For most nodes, we just use the preferred time source (the DAC) for synchronization.
|
|
||||||
// However, nodes that record from an input need to synchronize to the audio input node
|
|
||||||
// instead for best results.
|
|
||||||
// MakeTimeSourceFor gives us a "clone" of the time source node that we can manipulate
|
|
||||||
// to our heart's content. When we're done with it, though, we need to call Release()
|
|
||||||
// on the time source node, so that it keeps an accurate reference count and can delete
|
|
||||||
// itself when it's no longer needed.
|
|
||||||
// TODO: what about filters connected to audio input?
|
|
||||||
media_node use_time_source;
|
|
||||||
BTimeSource * tsobj = fRoster->MakeTimeSourceFor(input);
|
|
||||||
if (! tsobj) {
|
|
||||||
CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
|
|
||||||
" couldn't clone time source from audio input node\n"));
|
|
||||||
return B_MEDIA_BAD_NODE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the time source in effect to our own Node.
|
|
||||||
err = fRoster->SetTimeSourceFor(fRecordNode->Node().node, tsobj->Node().node);
|
|
||||||
if (err < B_OK) {
|
|
||||||
CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
|
|
||||||
" couldn't set the sound recorder's time source\n"));
|
|
||||||
tsobj->Release();
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a format, any format.
|
// Get a format, any format.
|
||||||
fRecordFormat.u.raw_audio = fAudioOutput.format.u.raw_audio;
|
fRecordFormat.u.raw_audio = audioOutput.format.u.raw_audio;
|
||||||
fRecordFormat.type = B_MEDIA_RAW_AUDIO;
|
fRecordFormat.type = B_MEDIA_RAW_AUDIO;
|
||||||
|
|
||||||
// Tell the consumer where we want data to go.
|
// Tell the consumer where we want data to go.
|
||||||
err = fRecordNode->SetHooks(RecordFile, NotifyRecordFile, this);
|
err = fRecorder->SetHooks(RecordFile, NotifyRecordFile, this);
|
||||||
|
|
||||||
if (err < B_OK) {
|
if (err < B_OK) {
|
||||||
CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
|
CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
|
||||||
" couldn't set the sound recorder's hook functions\n"));
|
" couldn't set the sound recorder's hook functions\n"));
|
||||||
tsobj->Release();
|
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using the same structs for input and output is OK in
|
if (!fRecorder->IsConnected()) {
|
||||||
// BMediaRoster::Connect().
|
|
||||||
err = fRoster->Connect(fAudioOutput.source, fRecInput.destination,
|
err = fRecorder->Connect(input, &audioOutput, &fRecordFormat);
|
||||||
&fRecordFormat, &fAudioOutput, &fRecInput);
|
|
||||||
if (err < B_OK) {
|
if (err < B_OK) {
|
||||||
CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
|
CONNECT((stderr, "RecorderWindow::MakeRecordConnection():"
|
||||||
" failed to connect sound recorder to audio input node.\n"));
|
" failed to connect sound recorder to audio input node.\n"));
|
||||||
tsobj->Release();
|
|
||||||
fRecordNode->SetHooks(0, 0, 0);
|
fRecorder->SetHooks(NULL, NULL, NULL);
|
||||||
return err;
|
return err;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start the time source if it's not running.
|
|
||||||
if ((tsobj->Node() != input) && !tsobj->IsRunning())
|
|
||||||
fRoster->StartNode(tsobj->Node(), BTimeSource::RealTime());
|
|
||||||
|
|
||||||
tsobj->Release(); // we're done with this time source instance!
|
|
||||||
return B_OK;
|
return B_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -965,16 +933,13 @@ RecorderWindow::MakeRecordConnection(const media_node & input)
|
|||||||
status_t
|
status_t
|
||||||
RecorderWindow::BreakRecordConnection()
|
RecorderWindow::BreakRecordConnection()
|
||||||
{
|
{
|
||||||
status_t err;
|
status_t err = B_OK;
|
||||||
|
|
||||||
// If we are the last connection, the Node will stop automatically since it
|
err = fRecorder->Stop(true);
|
||||||
// has nowhere to send data to.
|
if (err < B_OK)
|
||||||
err = fRoster->StopNode(fRecInput.node, 0);
|
return err;
|
||||||
err = fRoster->Disconnect(fAudioOutput.node.node, fAudioOutput.source,
|
|
||||||
fRecInput.node.node, fRecInput.destination);
|
return fRecorder->Disconnect();
|
||||||
fAudioOutput.source = media_source::null;
|
|
||||||
fRecInput.destination = media_destination::null;
|
|
||||||
return err;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -984,8 +949,11 @@ RecorderWindow::StopRecording()
|
|||||||
if (!fRecording)
|
if (!fRecording)
|
||||||
return B_OK;
|
return B_OK;
|
||||||
fRecording = false;
|
fRecording = false;
|
||||||
|
|
||||||
BreakRecordConnection();
|
BreakRecordConnection();
|
||||||
fRecordNode->SetHooks(NULL,NULL,NULL);
|
|
||||||
|
fRecorder->SetHooks(NULL, NULL, NULL);
|
||||||
|
|
||||||
if (fRecSize > 0) {
|
if (fRecSize > 0) {
|
||||||
|
|
||||||
wave_struct header;
|
wave_struct header;
|
||||||
@@ -1240,7 +1208,7 @@ RecorderWindow::RemoveCurrentSoundItem() {
|
|||||||
|
|
||||||
void
|
void
|
||||||
RecorderWindow::RecordFile(void* cookie, bigtime_t timestamp,
|
RecorderWindow::RecordFile(void* cookie, bigtime_t timestamp,
|
||||||
void* data, size_t size, const media_raw_audio_format &format)
|
void* data, size_t size, const media_format &format)
|
||||||
{
|
{
|
||||||
// Callback called from the SoundConsumer when receiving buffers.
|
// Callback called from the SoundConsumer when receiving buffers.
|
||||||
RecorderWindow * window = (RecorderWindow *)cookie;
|
RecorderWindow * window = (RecorderWindow *)cookie;
|
||||||
@@ -1249,7 +1217,7 @@ RecorderWindow::RecordFile(void* cookie, bigtime_t timestamp,
|
|||||||
// Write the data to file (we don't buffer or guard file access
|
// Write the data to file (we don't buffer or guard file access
|
||||||
// or anything)
|
// or anything)
|
||||||
window->fRecFile.WriteAt(window->fRecSize, data, size);
|
window->fRecFile.WriteAt(window->fRecSize, data, size);
|
||||||
window->fVUView->ComputeLevels(data, size, format.format);
|
window->fVUView->ComputeLevels(data, size, format.u.raw_audio.format);
|
||||||
window->fRecSize += size;
|
window->fRecSize += size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
#include <Window.h>
|
#include <Window.h>
|
||||||
|
|
||||||
#include "DrawButton.h"
|
#include "DrawButton.h"
|
||||||
|
#include "MediaRecorder.h"
|
||||||
#include "ScopeView.h"
|
#include "ScopeView.h"
|
||||||
#include "SoundListView.h"
|
#include "SoundListView.h"
|
||||||
#include "TransportButton.h"
|
#include "TransportButton.h"
|
||||||
@@ -40,12 +41,8 @@ class BScrollView;
|
|||||||
class BSlider;
|
class BSlider;
|
||||||
class BStringView;
|
class BStringView;
|
||||||
|
|
||||||
namespace BPrivate {
|
|
||||||
class SoundConsumer;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
using BPrivate::media::BMediaRecorder;
|
||||||
using BPrivate::SoundConsumer;
|
|
||||||
|
|
||||||
|
|
||||||
class RecorderWindow : public BWindow {
|
class RecorderWindow : public BWindow {
|
||||||
@@ -78,6 +75,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
void AddSoundItem(const BEntry& entry, bool temp = false);
|
void AddSoundItem(const BEntry& entry, bool temp = false);
|
||||||
|
|
||||||
void RemoveCurrentSoundItem();
|
void RemoveCurrentSoundItem();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -95,7 +93,7 @@ private:
|
|||||||
TrackSlider *fTrackSlider;
|
TrackSlider *fTrackSlider;
|
||||||
UpDownButton * fUpDownButton;
|
UpDownButton * fUpDownButton;
|
||||||
BMenuField * fInputField;
|
BMenuField * fInputField;
|
||||||
SoundConsumer * fRecordNode;
|
BMediaRecorder * fRecorder;
|
||||||
BSoundPlayer * fPlayer;
|
BSoundPlayer * fPlayer;
|
||||||
bool fRecording;
|
bool fRecording;
|
||||||
SoundListView * fSoundList;
|
SoundListView * fSoundList;
|
||||||
@@ -128,8 +126,6 @@ private:
|
|||||||
off_t fRecSize;
|
off_t fRecSize;
|
||||||
|
|
||||||
media_node fAudioInputNode;
|
media_node fAudioInputNode;
|
||||||
media_output fAudioOutput;
|
|
||||||
media_input fRecInput;
|
|
||||||
|
|
||||||
BMediaFile *fPlayFile;
|
BMediaFile *fPlayFile;
|
||||||
media_format fPlayFormat;
|
media_format fPlayFormat;
|
||||||
@@ -171,7 +167,7 @@ private:
|
|||||||
status_t UpdatePlayFile(SoundListItem *item, bool updateDisplay = false);
|
status_t UpdatePlayFile(SoundListItem *item, bool updateDisplay = false);
|
||||||
void ErrorAlert(const char * action, status_t err);
|
void ErrorAlert(const char * action, status_t err);
|
||||||
|
|
||||||
static void RecordFile(void * cookie, bigtime_t timestamp, void * data, size_t size, const media_raw_audio_format & format);
|
static void RecordFile(void * cookie, bigtime_t timestamp, void * data, size_t size, const media_format & format);
|
||||||
static void NotifyRecordFile(void * cookie, int32 code, ...);
|
static void NotifyRecordFile(void * cookie, int32 code, ...);
|
||||||
|
|
||||||
static void PlayFile(void * cookie, void * data, size_t size, const media_raw_audio_format & format);
|
static void PlayFile(void * cookie, void * data, size_t size, const media_raw_audio_format & format);
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ for architectureObject in [ MultiArchSubDirSetup ] {
|
|||||||
SharedLibrary [ MultiArchDefaultGristFiles libmedia.so ] :
|
SharedLibrary [ MultiArchDefaultGristFiles libmedia.so ] :
|
||||||
# Private Media Kit
|
# Private Media Kit
|
||||||
!missing_symbols.cpp
|
!missing_symbols.cpp
|
||||||
SoundConsumer.cpp
|
MediaRecorder.cpp
|
||||||
|
MediaRecorderNode.cpp
|
||||||
|
|
||||||
# Public Media Kit
|
# Public Media Kit
|
||||||
Buffer.cpp
|
Buffer.cpp
|
||||||
|
|||||||
@@ -0,0 +1,473 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 1999, Be Incorporated
|
||||||
|
* Copyright 2014, Dario Casalinuovo
|
||||||
|
* All Rights Reserved.
|
||||||
|
* This file may be used under the terms of the Be Sample Code License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "MediaRecorder.h"
|
||||||
|
|
||||||
|
#include <MediaAddOn.h>
|
||||||
|
#include <MediaRoster.h>
|
||||||
|
#include <TimeSource.h>
|
||||||
|
|
||||||
|
#include "MediaDebug.h"
|
||||||
|
#include "MediaRecorderNode.h"
|
||||||
|
|
||||||
|
|
||||||
|
class MediaNodeReleaser {
|
||||||
|
public:
|
||||||
|
MediaNodeReleaser(media_node& mediaNode,
|
||||||
|
bool release = true)
|
||||||
|
:
|
||||||
|
fNode(mediaNode),
|
||||||
|
fRelease(release)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~MediaNodeReleaser()
|
||||||
|
{
|
||||||
|
if (fRelease)
|
||||||
|
BMediaRoster::Roster()->ReleaseNode(fNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetTo(media_node& node)
|
||||||
|
{
|
||||||
|
fNode = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetRelease(bool release)
|
||||||
|
{
|
||||||
|
fRelease = release;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
media_node& fNode;
|
||||||
|
bool fRelease;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
BMediaRecorder::BMediaRecorder(const char* name, media_type type)
|
||||||
|
:
|
||||||
|
fInitErr(B_OK),
|
||||||
|
fConnected(false),
|
||||||
|
fRunning(false),
|
||||||
|
fTimeSource(NULL),
|
||||||
|
fRecordHook(NULL),
|
||||||
|
fNotifyHook(NULL),
|
||||||
|
fNode(NULL),
|
||||||
|
fBufferCookie(NULL)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
BMediaRoster::Roster(&fInitErr);
|
||||||
|
|
||||||
|
if (fInitErr == B_OK) {
|
||||||
|
fNode = new BMediaRecorderNode(name, this, type);
|
||||||
|
fInitErr = BMediaRoster::CurrentRoster()->RegisterNode(fNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BMediaRecorder::~BMediaRecorder()
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
if (fNode != NULL) {
|
||||||
|
Stop();
|
||||||
|
Disconnect();
|
||||||
|
fNode->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fTimeSource != NULL)
|
||||||
|
fTimeSource->Release();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
BMediaRecorder::InitCheck() const
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
return fInitErr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
BMediaRecorder::SetAcceptedFormat(const media_format& format)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
fNode->SetAcceptedFormat(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
BMediaRecorder::SetHooks(ProcessFunc recordFunc, NotifyFunc notifyFunc,
|
||||||
|
void* cookie)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
fRecordHook = recordFunc;
|
||||||
|
fNotifyHook = notifyFunc;
|
||||||
|
fBufferCookie = cookie;
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
BMediaRecorder::BufferReceived(void* buffer, size_t size,
|
||||||
|
const media_header& header)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
if (fRecordHook) {
|
||||||
|
(*fRecordHook)(fBufferCookie, header.start_time,
|
||||||
|
buffer, size, Format());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
BMediaRecorder::Connect(const media_format& format, uint32 flags)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
if (fInitErr != B_OK)
|
||||||
|
return fInitErr;
|
||||||
|
|
||||||
|
if (fConnected)
|
||||||
|
return B_MEDIA_ALREADY_CONNECTED;
|
||||||
|
|
||||||
|
return _Connect(&format, flags, NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
BMediaRecorder::Connect(const dormant_node_info& dormantInfo,
|
||||||
|
const media_format* format, uint32 flags)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
if (fInitErr != B_OK)
|
||||||
|
return fInitErr;
|
||||||
|
|
||||||
|
if (fConnected)
|
||||||
|
return B_MEDIA_ALREADY_CONNECTED;
|
||||||
|
|
||||||
|
return _Connect(format, flags, &dormantInfo, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
BMediaRecorder::Connect(const media_node& node,
|
||||||
|
const media_output* useOutput, const media_format* format,
|
||||||
|
uint32 flags)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
if (fInitErr != B_OK)
|
||||||
|
return fInitErr;
|
||||||
|
|
||||||
|
if (fConnected)
|
||||||
|
return B_MEDIA_ALREADY_CONNECTED;
|
||||||
|
|
||||||
|
return _Connect(format, flags, NULL, &node, useOutput);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
BMediaRecorder::Disconnect()
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
status_t err = B_OK;
|
||||||
|
|
||||||
|
if (fInitErr != B_OK)
|
||||||
|
return fInitErr;
|
||||||
|
|
||||||
|
if (!fConnected)
|
||||||
|
return B_MEDIA_NOT_CONNECTED;
|
||||||
|
|
||||||
|
if (!fNode)
|
||||||
|
return B_ERROR;
|
||||||
|
|
||||||
|
if (fRunning)
|
||||||
|
err = Stop();
|
||||||
|
|
||||||
|
if (err != B_OK)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// do the disconnect
|
||||||
|
err = BMediaRoster::CurrentRoster()->Disconnect(
|
||||||
|
fOutputNode.node, fOutput.source,
|
||||||
|
fNode->Node().node, fInput.destination);
|
||||||
|
|
||||||
|
BMediaRoster::Roster()->ReleaseNode(fOutputNode);
|
||||||
|
|
||||||
|
if (fTimeSource != NULL) {
|
||||||
|
fTimeSource->Release();
|
||||||
|
fTimeSource = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
fConnected = false;
|
||||||
|
fRunning = false;
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
BMediaRecorder::Start(bool force)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
if (fInitErr != B_OK)
|
||||||
|
return fInitErr;
|
||||||
|
|
||||||
|
if (!fConnected)
|
||||||
|
return B_MEDIA_NOT_CONNECTED;
|
||||||
|
|
||||||
|
if (fRunning && !force)
|
||||||
|
return EALREADY;
|
||||||
|
|
||||||
|
if (!fNode)
|
||||||
|
return B_ERROR;
|
||||||
|
|
||||||
|
// start node here
|
||||||
|
status_t err = B_OK;
|
||||||
|
|
||||||
|
if (fNode->Node().kind & B_TIME_SOURCE)
|
||||||
|
err = BMediaRoster::CurrentRoster()->StartTimeSource(
|
||||||
|
fOutputNode, BTimeSource::RealTime());
|
||||||
|
else
|
||||||
|
err = BMediaRoster::CurrentRoster()->StartNode(
|
||||||
|
fOutputNode, fTimeSource->Now());
|
||||||
|
|
||||||
|
// then un-mute it
|
||||||
|
if (err == B_OK) {
|
||||||
|
fNode->SetDataEnabled(true);
|
||||||
|
fRunning = true;
|
||||||
|
} else
|
||||||
|
fRunning = false;
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
BMediaRecorder::Stop(bool force)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
if (fInitErr != B_OK)
|
||||||
|
return fInitErr;
|
||||||
|
|
||||||
|
if (!fRunning && !force)
|
||||||
|
return EALREADY;
|
||||||
|
|
||||||
|
if (!fNode)
|
||||||
|
return B_ERROR;
|
||||||
|
|
||||||
|
// should have the Node mute the output here
|
||||||
|
fNode->SetDataEnabled(false);
|
||||||
|
|
||||||
|
fRunning = false;
|
||||||
|
|
||||||
|
return BMediaRoster::CurrentRoster()->StopNode(fNode->Node(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
BMediaRecorder::IsRunning() const
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
return fRunning;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
BMediaRecorder::IsConnected() const
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
return fConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const media_output&
|
||||||
|
BMediaRecorder::MediaOutput() const
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
return fOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const media_input&
|
||||||
|
BMediaRecorder::MediaInput() const
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
return fInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const media_format&
|
||||||
|
BMediaRecorder::Format() const
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
return fOutput.format;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
BMediaRecorder::_Connect(const media_format* format,
|
||||||
|
uint32 flags, const dormant_node_info* dormantNode,
|
||||||
|
const media_node* mediaNode, const media_output* output)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
status_t err = B_OK;
|
||||||
|
media_format ourFormat;
|
||||||
|
media_node node;
|
||||||
|
MediaNodeReleaser away(node, false);
|
||||||
|
media_output ourOutput;
|
||||||
|
|
||||||
|
// argument checking and set-up
|
||||||
|
if (format != NULL)
|
||||||
|
ourFormat = *format;
|
||||||
|
|
||||||
|
if (fNode == NULL)
|
||||||
|
return B_ERROR;
|
||||||
|
|
||||||
|
if (mediaNode == NULL && output != NULL)
|
||||||
|
return B_MISMATCHED_VALUES;
|
||||||
|
|
||||||
|
if (dormantNode != NULL && (mediaNode != NULL || output != NULL))
|
||||||
|
return B_MISMATCHED_VALUES;
|
||||||
|
|
||||||
|
if (format == NULL && output != NULL)
|
||||||
|
ourFormat = output->format;
|
||||||
|
|
||||||
|
fNode->SetAcceptedFormat(ourFormat);
|
||||||
|
|
||||||
|
// figure out the node to instantiate
|
||||||
|
if (dormantNode != NULL) {
|
||||||
|
|
||||||
|
err = BMediaRoster::Roster()->InstantiateDormantNode(
|
||||||
|
*dormantNode, &node, B_FLAVOR_IS_GLOBAL);
|
||||||
|
|
||||||
|
away.SetRelease(true);
|
||||||
|
|
||||||
|
} else if (mediaNode != NULL) {
|
||||||
|
node = *mediaNode;
|
||||||
|
} else {
|
||||||
|
switch (ourFormat.type) {
|
||||||
|
|
||||||
|
// switch on format for default
|
||||||
|
case B_MEDIA_RAW_AUDIO:
|
||||||
|
err = BMediaRoster::Roster()->GetAudioInput(&node);
|
||||||
|
away.SetRelease(true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case B_MEDIA_RAW_VIDEO:
|
||||||
|
case B_MEDIA_ENCODED_VIDEO:
|
||||||
|
err = BMediaRoster::Roster()->GetVideoInput(&node);
|
||||||
|
away.SetRelease(true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// give up?
|
||||||
|
default:
|
||||||
|
return B_MEDIA_BAD_FORMAT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fOutputNode = node;
|
||||||
|
|
||||||
|
// figure out the output provided
|
||||||
|
|
||||||
|
if (output != NULL) {
|
||||||
|
ourOutput = *output;
|
||||||
|
} else if (err == B_OK) {
|
||||||
|
|
||||||
|
media_output outputs[10];
|
||||||
|
int32 count = 10;
|
||||||
|
|
||||||
|
err = BMediaRoster::Roster()->GetFreeOutputsFor(node,
|
||||||
|
outputs, count, &count, ourFormat.type);
|
||||||
|
|
||||||
|
if (err != B_OK)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
err = B_MEDIA_BAD_SOURCE;
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
if (format_is_compatible(outputs[i].format, ourFormat)) {
|
||||||
|
ourOutput = outputs[i];
|
||||||
|
err = B_OK;
|
||||||
|
ourFormat = outputs[i].format;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ourOutput.source == media_source::null)
|
||||||
|
return B_MEDIA_BAD_SOURCE;
|
||||||
|
|
||||||
|
// find our Node's free input
|
||||||
|
media_input ourInput;
|
||||||
|
|
||||||
|
err = fNode->GetInput(&ourInput);
|
||||||
|
if (err != B_OK)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
media_node time_source;
|
||||||
|
if (node.kind & B_TIME_SOURCE)
|
||||||
|
time_source = node;
|
||||||
|
else
|
||||||
|
BMediaRoster::Roster()->GetSystemTimeSource(&time_source);
|
||||||
|
|
||||||
|
// set time source
|
||||||
|
BMediaRoster::Roster()->SetTimeSourceFor(
|
||||||
|
fNode->Node().node, time_source.node);
|
||||||
|
|
||||||
|
fTimeSource = BMediaRoster::CurrentRoster()->MakeTimeSourceFor(
|
||||||
|
fNode->Node());
|
||||||
|
|
||||||
|
if (err != B_OK)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// start the recorder node (it's always running)
|
||||||
|
err = BMediaRoster::CurrentRoster()->StartNode(
|
||||||
|
fOutputNode, fTimeSource->Now());
|
||||||
|
|
||||||
|
if (err != B_OK)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
// perform the connection
|
||||||
|
fOutput = ourOutput;
|
||||||
|
fInput = ourInput;
|
||||||
|
|
||||||
|
err = BMediaRoster::CurrentRoster()->Connect(fOutput.source,
|
||||||
|
fInput.destination, &ourFormat, &fOutput, &fInput,
|
||||||
|
BMediaRoster::B_CONNECT_MUTED);
|
||||||
|
|
||||||
|
if (err != B_OK)
|
||||||
|
return err;
|
||||||
|
|
||||||
|
fConnected = true;
|
||||||
|
away.SetRelease(false);
|
||||||
|
|
||||||
|
return err;
|
||||||
|
}
|
||||||
@@ -0,0 +1,324 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 1999, Be Incorporated
|
||||||
|
* Copyright 2014, Dario Casalinuovo
|
||||||
|
* All Rights Reserved.
|
||||||
|
* This file may be used under the terms of the Be Sample Code License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include "MediaRecorderNode.h"
|
||||||
|
|
||||||
|
#include <Buffer.h>
|
||||||
|
#include <scheduler.h>
|
||||||
|
#include <TimedEventQueue.h>
|
||||||
|
#include <TimeSource.h>
|
||||||
|
|
||||||
|
#include "MediaDebug.h"
|
||||||
|
#include "MediaRecorder.h"
|
||||||
|
|
||||||
|
|
||||||
|
BMediaRecorderNode::BMediaRecorderNode(const char* name,
|
||||||
|
BMediaRecorder* recorder, media_type type)
|
||||||
|
:
|
||||||
|
BMediaNode(name),
|
||||||
|
BMediaEventLooper(),
|
||||||
|
BBufferConsumer(type),
|
||||||
|
fRecorder(recorder)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
fInput.destination.id = 1;
|
||||||
|
fInput.destination.port = ControlPort();
|
||||||
|
|
||||||
|
fName.SetTo(name);
|
||||||
|
|
||||||
|
BString str(name);
|
||||||
|
str << " Input";
|
||||||
|
strcpy(fInput.name, str.String());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BMediaRecorderNode::~BMediaRecorderNode()
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BMediaAddOn*
|
||||||
|
BMediaRecorderNode::AddOn(int32* id) const
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
if (id)
|
||||||
|
*id = -1;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
BMediaRecorderNode::NodeRegistered()
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
Run();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
BMediaRecorderNode::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
|
||||||
|
BMediaRecorderNode::SetAcceptedFormat(const media_format& format)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
fOKFormat = format;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
BMediaRecorderNode::GetInput(media_input* outInput)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
fInput.node = Node();
|
||||||
|
*outInput = fInput;
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
BMediaRecorderNode::SetDataEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
int32 tag;
|
||||||
|
|
||||||
|
SetOutputEnabled(fInput.source,
|
||||||
|
fInput.destination, enabled, NULL, &tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
BMediaRecorderNode::HandleEvent(const media_timed_event* event,
|
||||||
|
bigtime_t lateness, bool realTimeEvent)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
// we ignore them all!
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
BMediaRecorderNode::Start(bigtime_t performanceTime)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
if (fRecorder->fNotifyHook)
|
||||||
|
(*fRecorder->fNotifyHook)(fRecorder->fBufferCookie,
|
||||||
|
B_WILL_START, performanceTime);
|
||||||
|
|
||||||
|
fRecorder->fRunning = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
BMediaRecorderNode::Stop(bigtime_t performanceTime, bool immediate)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
if (fRecorder->fNotifyHook)
|
||||||
|
(*fRecorder->fNotifyHook)(fRecorder->fBufferCookie,
|
||||||
|
B_WILL_STOP, performanceTime, immediate);
|
||||||
|
|
||||||
|
fRecorder->fRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
BMediaRecorderNode::Seek(bigtime_t mediaTime, bigtime_t performanceTime)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
if (fRecorder->fNotifyHook)
|
||||||
|
(*fRecorder->fNotifyHook)(fRecorder->fBufferCookie,
|
||||||
|
B_WILL_SEEK, performanceTime, mediaTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
BMediaRecorderNode::TimeWarp(bigtime_t realTime, bigtime_t performanceTime)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
// Since buffers will come pre-time-stamped, we only need to look
|
||||||
|
// at them, so we can ignore the time warp as a consumer.
|
||||||
|
if (fRecorder->fNotifyHook)
|
||||||
|
(*fRecorder->fNotifyHook)(fRecorder->fBufferCookie,
|
||||||
|
B_WILL_TIMEWARP, realTime, performanceTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
BMediaRecorderNode::HandleMessage(int32 message,
|
||||||
|
const void* data, size_t size)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
if (BBufferConsumer::HandleMessage(message, data, size) < 0
|
||||||
|
&& BMediaEventLooper::HandleMessage(message, data, size) < 0
|
||||||
|
&& BMediaNode::HandleMessage(message, data, size) < 0) {
|
||||||
|
HandleBadMessage(message, data, size);
|
||||||
|
return B_ERROR;
|
||||||
|
}
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
BMediaRecorderNode::AcceptFormat(const media_destination& dest,
|
||||||
|
media_format* format)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
if (format_is_compatible(*format, fOKFormat))
|
||||||
|
return B_OK;
|
||||||
|
|
||||||
|
*format = fOKFormat;
|
||||||
|
|
||||||
|
return B_MEDIA_BAD_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
BMediaRecorderNode::GetNextInput(int32* cookie, media_input* outInput)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
if (*cookie == 0) {
|
||||||
|
*cookie = -1;
|
||||||
|
*outInput = fInput;
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return B_BAD_INDEX;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
BMediaRecorderNode::DisposeInputCookie(int32 cookie)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
BMediaRecorderNode::BufferReceived(BBuffer* buffer)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
fRecorder->BufferReceived(buffer->Data(), buffer->SizeUsed(),
|
||||||
|
*buffer->Header());
|
||||||
|
|
||||||
|
buffer->Recycle();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
BMediaRecorderNode::ProducerDataStatus(
|
||||||
|
const media_destination& forWhom, int32 status,
|
||||||
|
bigtime_t performanceTime)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
BMediaRecorderNode::GetLatencyFor(const media_destination& forWhom,
|
||||||
|
bigtime_t* outLatency, media_node_id* outTimesource)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
*outLatency = 0;
|
||||||
|
*outTimesource = TimeSource()->ID();
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
BMediaRecorderNode::Connected(const media_source &producer,
|
||||||
|
const media_destination &where, const media_format &withFormat,
|
||||||
|
media_input* outInput)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
fInput.source = producer;
|
||||||
|
fInput.format = withFormat;
|
||||||
|
*outInput = fInput;
|
||||||
|
|
||||||
|
fRecorder->fConnected = true;
|
||||||
|
fRecorder->fInput = fInput;
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
BMediaRecorderNode::Disconnected(const media_source& producer,
|
||||||
|
const media_destination& where)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
fInput.source = media_source::null;
|
||||||
|
|
||||||
|
fRecorder->fConnected = false;
|
||||||
|
|
||||||
|
fRecorder->fInput.format = fOKFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
status_t
|
||||||
|
BMediaRecorderNode::FormatChanged(const media_source& producer,
|
||||||
|
const media_destination& consumer, int32 tag,
|
||||||
|
const media_format& format)
|
||||||
|
{
|
||||||
|
CALLED();
|
||||||
|
|
||||||
|
if (!format_is_compatible(format, fOKFormat))
|
||||||
|
return B_MEDIA_BAD_FORMAT;
|
||||||
|
|
||||||
|
fInput.format = format;
|
||||||
|
|
||||||
|
return B_OK;
|
||||||
|
}
|
||||||
@@ -1,700 +0,0 @@
|
|||||||
/******************************************************************************
|
|
||||||
/
|
|
||||||
/ File: SoundConsumer.cpp
|
|
||||||
/
|
|
||||||
/ Description: Record sound from some sound-producing Node.
|
|
||||||
/
|
|
||||||
/ Copyright 1998-1999, Be Incorporated, All Rights Reserved
|
|
||||||
/
|
|
||||||
******************************************************************************/
|
|
||||||
#include "SoundConsumer.h"
|
|
||||||
|
|
||||||
#include <new>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#include <OS.h>
|
|
||||||
#include <scheduler.h>
|
|
||||||
#include <Buffer.h>
|
|
||||||
#include <TimeSource.h>
|
|
||||||
|
|
||||||
#include "AutoDeleter.h"
|
|
||||||
|
|
||||||
|
|
||||||
using std::nothrow;
|
|
||||||
|
|
||||||
|
|
||||||
namespace BPrivate {
|
|
||||||
|
|
||||||
|
|
||||||
// If we don't mind the format changing to another format while
|
|
||||||
// running, we can define this to 1. Look for the symbol down in the source.
|
|
||||||
#define ACCEPT_ANY_FORMAT_CHANGE 0
|
|
||||||
|
|
||||||
|
|
||||||
// Compiling with NDEBUG means "release" -- it also turns off assert() and
|
|
||||||
// other such debugging aids. By contrast, DEBUG turns on extra debugging
|
|
||||||
// in some programs.
|
|
||||||
#if !NDEBUG
|
|
||||||
#define TRACE_SOUNDCONSUMER
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Comment out the FPRINTF part of these lines to reduce verbiage.
|
|
||||||
// Enabling MESSAGE will kill performance on slower machines, because it
|
|
||||||
// prints for each message received (including each buffer).
|
|
||||||
#ifdef TRACE_SOUNDCONSUMER
|
|
||||||
#define NODE fprintf
|
|
||||||
#define MESSAGE fprintf
|
|
||||||
#else
|
|
||||||
#define NODE(x...)
|
|
||||||
#define MESSAGE(x...)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
// This structure is the body of a request that we use to
|
|
||||||
// implement SetHooks().
|
|
||||||
struct set_hooks_q {
|
|
||||||
port_id reply;
|
|
||||||
void * cookie;
|
|
||||||
SoundProcessFunc process;
|
|
||||||
SoundNotifyFunc notify;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// All incoming buffers and Media Kit requests arrive at a
|
|
||||||
// media node in the form of messages (which are generally
|
|
||||||
// dispatched for you by your superclasses' HandleMessage
|
|
||||||
// implementations). Each message has a 'type' which is
|
|
||||||
// analagous to a BMessage's 'what' field. We'll define our
|
|
||||||
// own private message types for our SoundConsumer and
|
|
||||||
// SoundProducer to use. The BeOS reserves a range,
|
|
||||||
// 0x60000000 to 0x7fffffff, for us to use.
|
|
||||||
enum {
|
|
||||||
MSG_QUIT_NOW = 0x60000000L,
|
|
||||||
MSG_CHANGE_HOOKS
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
SoundConsumer::SoundConsumer(
|
|
||||||
const char * name,
|
|
||||||
SoundProcessFunc recordFunc,
|
|
||||||
SoundNotifyFunc notifyFunc,
|
|
||||||
void * cookie) :
|
|
||||||
BMediaNode(name ? name : "SoundConsumer"),
|
|
||||||
BBufferConsumer(B_MEDIA_RAW_AUDIO)
|
|
||||||
{
|
|
||||||
NODE(stderr, "SoundConsumer::SoundConsumer(%p, %p, %p, %p)\n", name,
|
|
||||||
recordFunc, notifyFunc, cookie);
|
|
||||||
|
|
||||||
if (!name) name = "SoundConsumer";
|
|
||||||
|
|
||||||
// Set up the hook functions.
|
|
||||||
m_recordHook = recordFunc;
|
|
||||||
m_notifyHook = notifyFunc;
|
|
||||||
m_cookie = cookie;
|
|
||||||
|
|
||||||
// Create the port that we publish as our Control Port.
|
|
||||||
char pname[32];
|
|
||||||
sprintf(pname, "%.20s Control", name);
|
|
||||||
m_port = create_port(10, pname);
|
|
||||||
|
|
||||||
// Initialize our single media_input. Make sure it knows
|
|
||||||
// the Control Port associated with the destination, and
|
|
||||||
// the index of the destination (since we only have one,
|
|
||||||
// that's trivial).
|
|
||||||
m_input.destination.port = m_port;
|
|
||||||
m_input.destination.id = 1;
|
|
||||||
sprintf(m_input.name, "%.20s Input", name);
|
|
||||||
|
|
||||||
// Set up the timing variables that we'll be using.
|
|
||||||
m_trTimeout = 0LL;
|
|
||||||
m_tpSeekAt = 0;
|
|
||||||
m_tmSeekTo = 0;
|
|
||||||
m_delta = 0;
|
|
||||||
m_seeking = false;
|
|
||||||
|
|
||||||
// Create, and run, the thread that we use to service
|
|
||||||
// the Control Port.
|
|
||||||
sprintf(pname, "%.20s Service", name);
|
|
||||||
m_thread = spawn_thread(ThreadEntry, pname, 110, this);
|
|
||||||
resume_thread(m_thread);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
SoundConsumer::~SoundConsumer()
|
|
||||||
{
|
|
||||||
NODE(stderr, "SoundConsumer::~SoundConsumer()\n");
|
|
||||||
// Signal to our thread that it's time to go home.
|
|
||||||
write_port(m_port, MSG_QUIT_NOW, 0, 0);
|
|
||||||
status_t s;
|
|
||||||
while (wait_for_thread(m_thread, &s) == B_INTERRUPTED)
|
|
||||||
NODE(stderr, "wait_for_thread() B_INTERRUPTED\n");
|
|
||||||
delete_port(m_port);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
status_t
|
|
||||||
SoundConsumer::SetHooks(SoundProcessFunc recordFunc, SoundNotifyFunc notifyFunc,
|
|
||||||
void* cookie)
|
|
||||||
{
|
|
||||||
// SetHooks needs to be synchronized with the service thread, else we may
|
|
||||||
// call the wrong hook function with the wrong cookie, which would be bad.
|
|
||||||
// Rather than do locking, which is expensive, we streamline the process
|
|
||||||
// by sending our service thread a request to change the hooks, and waiting
|
|
||||||
// for the acknowledge.
|
|
||||||
status_t err = B_OK;
|
|
||||||
set_hooks_q cmd;
|
|
||||||
cmd.process = recordFunc;
|
|
||||||
cmd.notify = notifyFunc;
|
|
||||||
cmd.cookie = cookie;
|
|
||||||
// If we're not in the service thread, we need to round-trip a message.
|
|
||||||
if (find_thread(0) != m_thread) {
|
|
||||||
cmd.reply = create_port(1, "SetHooks reply");
|
|
||||||
// Send the private message to our service thread.
|
|
||||||
err = write_port(ControlPort(), MSG_CHANGE_HOOKS, &cmd, sizeof(cmd));
|
|
||||||
if (err >= 0) {
|
|
||||||
int32 code;
|
|
||||||
// Wait for acknowledge from the service thread.
|
|
||||||
err = read_port_etc(cmd.reply, &code, 0, 0, B_TIMEOUT, 6000000LL);
|
|
||||||
if (err > 0) err = 0;
|
|
||||||
NODE(stderr, "SoundConsumer::SetHooks read reply: %#" B_PRIx32
|
|
||||||
"\n", err);
|
|
||||||
}
|
|
||||||
// Clean up.
|
|
||||||
delete_port(cmd.reply);
|
|
||||||
} else {
|
|
||||||
// Within the service thread, it's OK to just go ahead and do the
|
|
||||||
// change.
|
|
||||||
DoHookChange(&cmd);
|
|
||||||
}
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// #pragma mark -BMediaNode-derived methods
|
|
||||||
|
|
||||||
|
|
||||||
port_id
|
|
||||||
SoundConsumer::ControlPort() const
|
|
||||||
{
|
|
||||||
return m_port;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
BMediaAddOn*
|
|
||||||
SoundConsumer::AddOn(int32 * internal_id) const
|
|
||||||
{
|
|
||||||
// This object is instantiated inside an application.
|
|
||||||
// Therefore, it has no add-on.
|
|
||||||
if (internal_id) *internal_id = 0;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
SoundConsumer::Start(bigtime_t performance_time)
|
|
||||||
{
|
|
||||||
// Since we are a consumer and just blindly accept buffers that are
|
|
||||||
// thrown at us, we don't need to do anything special in Start()/Stop().
|
|
||||||
// If we were (also) a producer, we'd have to be more elaborate.
|
|
||||||
// The only thing we do is immediately perform any queued Seek based on
|
|
||||||
// the start time, which is the right thing to do (seeing as we were
|
|
||||||
// Seek()-ed when we weren't started).
|
|
||||||
if (m_seeking) {
|
|
||||||
m_delta = performance_time - m_tmSeekTo;
|
|
||||||
m_seeking = false;
|
|
||||||
}
|
|
||||||
if (m_notifyHook)
|
|
||||||
(*m_notifyHook)(m_cookie, B_WILL_START, performance_time);
|
|
||||||
else
|
|
||||||
Notify(B_WILL_START, performance_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
SoundConsumer::Stop(bigtime_t performance_time, bool immediate)
|
|
||||||
{
|
|
||||||
// Since we are a consumer and just blindly accept buffers that are
|
|
||||||
// thrown at us, we don't need to do anything special in Start()/Stop().
|
|
||||||
// If we were (also) a producer, we'd have to be more elaborate.
|
|
||||||
// Note that this is not strictly in conformance with The Rules,
|
|
||||||
// but since this is not an add-on Node for use with any application;
|
|
||||||
// it's a Node over which we have complete control, we can live with
|
|
||||||
// treating buffers received before the start time or after the stop
|
|
||||||
// time as any other buffer.
|
|
||||||
if (m_notifyHook)
|
|
||||||
(*m_notifyHook)(m_cookie, B_WILL_STOP, performance_time, immediate);
|
|
||||||
else
|
|
||||||
Notify(B_WILL_STOP, performance_time, immediate);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
SoundConsumer::Seek(bigtime_t media_time, bigtime_t performance_time)
|
|
||||||
{
|
|
||||||
// Seek() on a consumer just serves to offset the time stamp
|
|
||||||
// of received buffers passed to our Record hook function.
|
|
||||||
// In the hook function, you may wish to save those time stamps
|
|
||||||
// to disk or otherwise store them. You may also want to
|
|
||||||
// synchronize this node's media time with an upstream
|
|
||||||
// producer's media time to make this offset meaningful.
|
|
||||||
if (m_notifyHook)
|
|
||||||
(*m_notifyHook)(m_cookie, B_WILL_SEEK, performance_time, media_time);
|
|
||||||
else
|
|
||||||
Notify(B_WILL_SEEK, performance_time, media_time);
|
|
||||||
|
|
||||||
m_tpSeekAt = performance_time;
|
|
||||||
m_tmSeekTo = media_time;
|
|
||||||
m_seeking = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
SoundConsumer::SetRunMode(run_mode mode)
|
|
||||||
{
|
|
||||||
if (mode == BMediaNode::B_OFFLINE) {
|
|
||||||
// BMediaNode::B_OFFLINE means we don't need to run in
|
|
||||||
// real time. So, we shouldn't run as a real time
|
|
||||||
// thread.
|
|
||||||
int32 new_prio = suggest_thread_priority(B_OFFLINE_PROCESSING);
|
|
||||||
set_thread_priority(m_thread, new_prio);
|
|
||||||
} else {
|
|
||||||
// We're running in real time, so we'd better have
|
|
||||||
// a big enough thread priority to handle it!
|
|
||||||
// Here's where those magic scheduler values
|
|
||||||
// come from:
|
|
||||||
//
|
|
||||||
// * In the worst case, we process one buffer per
|
|
||||||
// reschedule (we get rescheduled when we go to
|
|
||||||
// look for a message on our Control Port), so
|
|
||||||
// in order to keep up with the incoming buffers,
|
|
||||||
// the duration of one buffer becomes our
|
|
||||||
// scheduling period. If we don't know anything
|
|
||||||
// about the buffers, we pick a reasonable
|
|
||||||
// default.
|
|
||||||
// * We're a simple consumer, so we don't have to
|
|
||||||
// be too picky about the jitter. Half a period
|
|
||||||
// of jitter means that we'd have to get two
|
|
||||||
// consecutive worst-case reschedules before
|
|
||||||
// we'd fall behind.
|
|
||||||
// * The amount of time we spend processing is
|
|
||||||
// our ProcessingLatency().
|
|
||||||
bigtime_t period = 10000;
|
|
||||||
if (buffer_duration(m_input.format.u.raw_audio) > 0)
|
|
||||||
period = buffer_duration(m_input.format.u.raw_audio);
|
|
||||||
|
|
||||||
// assuming we're running for 500 us or less per buffer
|
|
||||||
int32 new_prio = suggest_thread_priority(B_AUDIO_RECORDING,
|
|
||||||
period, period / 2, ProcessingLatency());
|
|
||||||
set_thread_priority(m_thread, new_prio);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
SoundConsumer::TimeWarp(bigtime_t at_real_time, bigtime_t to_performance_time)
|
|
||||||
{
|
|
||||||
// Since buffers will come pre-time-stamped, we only need to look
|
|
||||||
// at them, so we can ignore the time warp as a consumer.
|
|
||||||
if (m_notifyHook) {
|
|
||||||
(*m_notifyHook)(m_cookie, B_WILL_TIMEWARP, at_real_time,
|
|
||||||
to_performance_time);
|
|
||||||
} else
|
|
||||||
Notify(B_WILL_TIMEWARP, at_real_time, to_performance_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
SoundConsumer::Preroll()
|
|
||||||
{
|
|
||||||
// There is nothing for us to do in Preroll()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
SoundConsumer::SetTimeSource(BTimeSource* /* time_source */)
|
|
||||||
{
|
|
||||||
// We don't need to do anything special to take note of the
|
|
||||||
// fact that the time source changed, because we get our timing
|
|
||||||
// information from the buffers we receive.
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
status_t
|
|
||||||
SoundConsumer::HandleMessage(int32 message, const void* data, size_t size)
|
|
||||||
{
|
|
||||||
// Check with each of our superclasses to see if they
|
|
||||||
// understand the message. If none of them do, call
|
|
||||||
// BMediaNode::HandleBadMessage().
|
|
||||||
if (BBufferConsumer::HandleMessage(message, data, size) < 0
|
|
||||||
&& BMediaNode::HandleMessage(message, data, size) < 0) {
|
|
||||||
HandleBadMessage(message, data, size);
|
|
||||||
return B_ERROR;
|
|
||||||
}
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// #pragma mark - BBufferConsumer-derived methods
|
|
||||||
|
|
||||||
|
|
||||||
status_t
|
|
||||||
SoundConsumer::AcceptFormat(const media_destination& dest, media_format* format)
|
|
||||||
{
|
|
||||||
// We only accept formats aimed at our single input.
|
|
||||||
if (dest != m_input.destination)
|
|
||||||
return B_MEDIA_BAD_DESTINATION;
|
|
||||||
|
|
||||||
if (format->type <= 0) {
|
|
||||||
// If no format is specified, we say we want raw audio.
|
|
||||||
format->type = B_MEDIA_RAW_AUDIO;
|
|
||||||
format->u.raw_audio = media_raw_audio_format::wildcard;
|
|
||||||
} else if (format->type != B_MEDIA_RAW_AUDIO) {
|
|
||||||
// If a non-raw-audio format is specified, we tell the world what
|
|
||||||
// we want, and that the specified format was unacceptable to us.
|
|
||||||
format->type = B_MEDIA_RAW_AUDIO;
|
|
||||||
format->u.raw_audio = media_raw_audio_format::wildcard;
|
|
||||||
return B_MEDIA_BAD_FORMAT;
|
|
||||||
}
|
|
||||||
#if !ACCEPT_ANY_FORMAT_CHANGE
|
|
||||||
// If we're already connected, and this format doesn't go with the
|
|
||||||
// format in effect, we dont' accept this new format.
|
|
||||||
if (!format_is_compatible(*format, m_input.format)) {
|
|
||||||
*format = m_input.format;
|
|
||||||
return B_MEDIA_BAD_FORMAT;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
// I guess we're OK by now, because we have no particular needs as
|
|
||||||
// far as frame rate, sample format, etc go.
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
status_t
|
|
||||||
SoundConsumer::GetNextInput(int32* cookie, media_input* out_input)
|
|
||||||
{
|
|
||||||
NODE(stderr, "SoundConsumer: GetNextInput()\n");
|
|
||||||
// The "next" is kind of misleading, since it's also used for
|
|
||||||
// getting the first (and only) input.
|
|
||||||
if (*cookie == 0) {
|
|
||||||
if (m_input.source == media_source::null) {
|
|
||||||
// If there's no current connection, make sure we return a
|
|
||||||
// reasonable format telling the world we accept any raw audio.
|
|
||||||
m_input.format.type = B_MEDIA_RAW_AUDIO;
|
|
||||||
m_input.format.u.raw_audio = media_raw_audio_format::wildcard;
|
|
||||||
m_input.node = Node();
|
|
||||||
m_input.destination.port = ControlPort();
|
|
||||||
m_input.destination.id = 1;
|
|
||||||
}
|
|
||||||
*out_input = m_input;
|
|
||||||
*cookie = 1;
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
// There's only one input.
|
|
||||||
return B_BAD_INDEX;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
SoundConsumer::DisposeInputCookie(int32 /* cookie */)
|
|
||||||
{
|
|
||||||
// We didn't allocate any memory or set any state in GetNextInput()
|
|
||||||
// so this function is a no-op.
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
SoundConsumer::BufferReceived(BBuffer* buffer)
|
|
||||||
{
|
|
||||||
NODE(stderr, "SoundConsumer::BufferReceived()\n");
|
|
||||||
// Whee, a buffer! Update the seek info, if necessary.
|
|
||||||
if (m_seeking && buffer->Header()->start_time >= m_tpSeekAt) {
|
|
||||||
m_delta = m_tpSeekAt - m_tmSeekTo;
|
|
||||||
m_seeking = false;
|
|
||||||
}
|
|
||||||
// If there is a record hook, let the interested party have at it!
|
|
||||||
if (m_recordHook) {
|
|
||||||
(*m_recordHook)(m_cookie, buffer->Header()->start_time-m_delta,
|
|
||||||
buffer->Data(), buffer->Header()->size_used,
|
|
||||||
m_input.format.u.raw_audio);
|
|
||||||
} else {
|
|
||||||
Record(buffer->Header()->start_time-m_delta, buffer->Data(),
|
|
||||||
buffer->Header()->size_used, m_input.format.u.raw_audio);
|
|
||||||
}
|
|
||||||
// Buffers should ALWAYS be recycled, else whomever is producing them
|
|
||||||
// will starve.
|
|
||||||
buffer->Recycle();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
SoundConsumer::ProducerDataStatus(const media_destination& for_whom,
|
|
||||||
int32 status, bigtime_t at_media_time)
|
|
||||||
{
|
|
||||||
if (for_whom != m_input.destination)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Tell whomever is interested that the upstream producer will or won't
|
|
||||||
// send more data in the immediate future.
|
|
||||||
if (m_notifyHook) {
|
|
||||||
(*m_notifyHook)(m_cookie, B_PRODUCER_DATA_STATUS, status,
|
|
||||||
at_media_time);
|
|
||||||
} else
|
|
||||||
Notify(B_PRODUCER_DATA_STATUS, status, at_media_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
status_t
|
|
||||||
SoundConsumer::GetLatencyFor(const media_destination& for_whom,
|
|
||||||
bigtime_t* out_latency, media_node_id* out_timesource)
|
|
||||||
{
|
|
||||||
// We only accept requests for the one-and-only input of our Node.
|
|
||||||
if (for_whom != m_input.destination)
|
|
||||||
return B_MEDIA_BAD_DESTINATION;
|
|
||||||
|
|
||||||
// Tell the world about our latency information (overridable by user).
|
|
||||||
*out_latency = TotalLatency();
|
|
||||||
*out_timesource = TimeSource()->Node().node;
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
status_t
|
|
||||||
SoundConsumer::Connected(const media_source& producer,
|
|
||||||
const media_destination& where, const media_format& with_format,
|
|
||||||
media_input* out_input)
|
|
||||||
{
|
|
||||||
NODE(stderr, "SoundConsumer::Connected()\n");
|
|
||||||
// Only accept connection requests when we're not already connected.
|
|
||||||
if (m_input.source != media_source::null)
|
|
||||||
return B_MEDIA_BAD_DESTINATION;
|
|
||||||
|
|
||||||
// Only accept connection requests on the one-and-only available input.
|
|
||||||
if (where != m_input.destination)
|
|
||||||
return B_MEDIA_BAD_DESTINATION;
|
|
||||||
|
|
||||||
// Other than that, we accept pretty much anything. The format has been
|
|
||||||
// pre-cleared through AcceptFormat(), and we accept any format anyway.
|
|
||||||
m_input.source = producer;
|
|
||||||
m_input.format = with_format;
|
|
||||||
// Tell whomever is interested that there's now a connection.
|
|
||||||
if (m_notifyHook)
|
|
||||||
(*m_notifyHook)(m_cookie, B_CONNECTED, m_input.name);
|
|
||||||
else
|
|
||||||
Notify(B_CONNECTED, m_input.name);
|
|
||||||
|
|
||||||
// This is the most important line -- return our connection information
|
|
||||||
// to the world so it can use it!
|
|
||||||
*out_input = m_input;
|
|
||||||
return B_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
SoundConsumer::Disconnected(const media_source& producer,
|
|
||||||
const media_destination& where)
|
|
||||||
{
|
|
||||||
// We can't disconnect something which isn't us.
|
|
||||||
if (where != m_input.destination)
|
|
||||||
return;
|
|
||||||
// We can't disconnect from someone who isn't connected to us.
|
|
||||||
if (producer != m_input.source)
|
|
||||||
return;
|
|
||||||
// Tell the interested party that it's time to leave.
|
|
||||||
if (m_notifyHook)
|
|
||||||
(*m_notifyHook)(m_cookie, B_DISCONNECTED);
|
|
||||||
else
|
|
||||||
Notify(B_DISCONNECTED);
|
|
||||||
// Mark ourselves as not-connected.
|
|
||||||
m_input.source = media_source::null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
status_t
|
|
||||||
SoundConsumer::FormatChanged(const media_source& producer,
|
|
||||||
const media_destination& consumer, int32 from_change_count,
|
|
||||||
const media_format& format)
|
|
||||||
{
|
|
||||||
NODE(stderr, "SoundConsumer::Connected()\n");
|
|
||||||
// The up-stream guy feels like changing the format. If we can accept
|
|
||||||
// arbitrary format changes, we just say "OK". If, however, we're recording
|
|
||||||
// to a file, that's not such a good idea; we only accept format changes
|
|
||||||
// that are compatible with the format we're already using. You set this
|
|
||||||
// behaviour at compile time by defining ACCEPT_ANY_FORMAT_CHANGE to 1 or
|
|
||||||
// 0.
|
|
||||||
status_t err = B_OK;
|
|
||||||
#if ACCEPT_ANY_FORMAT_CHANGE
|
|
||||||
media_format fmt(format);
|
|
||||||
err = AcceptFormat(m_input.destination, &fmt);
|
|
||||||
#else
|
|
||||||
if (m_input.source != media_source::null) {
|
|
||||||
err = format_is_compatible(format, m_input.format) ? B_OK
|
|
||||||
: B_MEDIA_BAD_FORMAT;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (err >= B_OK) {
|
|
||||||
m_input.format = format;
|
|
||||||
if (m_notifyHook) {
|
|
||||||
(*m_notifyHook)(m_cookie, B_FORMAT_CHANGED,
|
|
||||||
&m_input.format.u.raw_audio);
|
|
||||||
} else
|
|
||||||
Notify(B_FORMAT_CHANGED, &m_input.format.u.raw_audio);
|
|
||||||
}
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
SoundConsumer::DoHookChange(void* msg)
|
|
||||||
{
|
|
||||||
// Tell the old guy we're changing the hooks ...
|
|
||||||
if (m_notifyHook)
|
|
||||||
(*m_notifyHook)(m_cookie, B_HOOKS_CHANGED);
|
|
||||||
else
|
|
||||||
Notify(B_HOOKS_CHANGED);
|
|
||||||
|
|
||||||
// ... and then do it.
|
|
||||||
set_hooks_q * ptr = (set_hooks_q *)msg;
|
|
||||||
m_recordHook = ptr->process;
|
|
||||||
m_notifyHook = ptr->notify;
|
|
||||||
m_cookie = ptr->cookie;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
status_t
|
|
||||||
SoundConsumer::ThreadEntry(void* cookie)
|
|
||||||
{
|
|
||||||
SoundConsumer* consumer = (SoundConsumer*)cookie;
|
|
||||||
consumer->ServiceThread();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
SoundConsumer::ServiceThread()
|
|
||||||
{
|
|
||||||
// The Big Bad ServiceThread receives messages aimed at this
|
|
||||||
// Node and dispatches them (typically to HandleMessage()).
|
|
||||||
// If we were a Producer, we might have to do finicky timing and
|
|
||||||
// queued Start()/Stop() processing in here. But we ain't.
|
|
||||||
|
|
||||||
// A media kit message will never be bigger than B_MEDIA_MESSAGE_SIZE.
|
|
||||||
// Avoid wasing stack space by dynamically allocating at start.
|
|
||||||
char* msg = new (nothrow) char[B_MEDIA_MESSAGE_SIZE];
|
|
||||||
if (msg == NULL)
|
|
||||||
return;
|
|
||||||
// Make sure we clean up this data when we exit the function.
|
|
||||||
ArrayDeleter<char> _(msg);
|
|
||||||
int bad = 0;
|
|
||||||
while (true) {
|
|
||||||
// Call read_port_etc() with a timeout derived from a virtual function,
|
|
||||||
// to allow clients to do special processing if necessary.
|
|
||||||
bigtime_t timeout = Timeout();
|
|
||||||
int32 code = 0;
|
|
||||||
status_t err = read_port_etc(m_port, &code, msg, B_MEDIA_MESSAGE_SIZE,
|
|
||||||
B_TIMEOUT, timeout);
|
|
||||||
MESSAGE(stderr, "SoundConsumer::ServiceThread() port %" B_PRId32
|
|
||||||
" message %#" B_PRIx32 "\n", m_port, code);
|
|
||||||
// If we received a message, err will be the size of the message
|
|
||||||
// (including 0).
|
|
||||||
if (err >= B_OK) {
|
|
||||||
// Real messages reset the timeout time.
|
|
||||||
m_trTimeout = 0;
|
|
||||||
bad = 0;
|
|
||||||
if (code == MSG_QUIT_NOW) {
|
|
||||||
// Check for our private stop message.
|
|
||||||
if (m_notifyHook)
|
|
||||||
(*m_notifyHook)(m_cookie, B_NODE_DIES, 0);
|
|
||||||
else
|
|
||||||
Notify(B_NODE_DIES, 0);
|
|
||||||
break;
|
|
||||||
} else if (code == MSG_CHANGE_HOOKS) {
|
|
||||||
// Else check for our private change-hooks message.
|
|
||||||
DoHookChange(msg);
|
|
||||||
// Write acknowledge to waiting thread.
|
|
||||||
write_port(((set_hooks_q *)msg)->reply, 0, 0, 0);
|
|
||||||
} else {
|
|
||||||
// Else it has to be a regular media kit message;
|
|
||||||
// go ahead and dispatch it.
|
|
||||||
HandleMessage(code, msg, err);
|
|
||||||
}
|
|
||||||
} else if (err == B_TIMED_OUT) {
|
|
||||||
// Timing out means that there was no buffer. Tell the interested
|
|
||||||
// party.
|
|
||||||
if (m_notifyHook)
|
|
||||||
(*m_notifyHook)(m_cookie, B_OP_TIMED_OUT, timeout);
|
|
||||||
else
|
|
||||||
Notify(B_OP_TIMED_OUT, timeout);
|
|
||||||
} else {
|
|
||||||
// Other errors are bad.
|
|
||||||
MESSAGE(stderr, "SoundConsumer: error %#" B_PRIx32 "\n", err);
|
|
||||||
bad++;
|
|
||||||
// If we receive three bad reads with no good messages inbetween,
|
|
||||||
// things are probably not going to improve (like the port
|
|
||||||
// disappeared or something) so we call it a day.
|
|
||||||
if (bad > 3) {
|
|
||||||
if (m_notifyHook)
|
|
||||||
(*m_notifyHook)(m_cookie, B_NODE_DIES, bad, err, code, msg);
|
|
||||||
else
|
|
||||||
Notify(B_NODE_DIES, bad, err, code, msg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bigtime_t
|
|
||||||
SoundConsumer::Timeout()
|
|
||||||
{
|
|
||||||
// Timeout() is called for each call to read_port_etc() in the service
|
|
||||||
// thread to figure out a reasonable time-out value. The default behaviour
|
|
||||||
// we've picked is to exponentially back off from one second and upwards.
|
|
||||||
// While it's true that 44 back-offs will run us out of precision in a
|
|
||||||
// bigtime_t, the time to actually reach 44 consecutive back-offs is longer
|
|
||||||
// than the expected market longevity of just about any piece of real
|
|
||||||
// estate. Is that the sound of an impending year-fifteen-million software
|
|
||||||
// problem? :-)
|
|
||||||
m_trTimeout = (m_trTimeout < 1000000) ? 1000000 : m_trTimeout*2;
|
|
||||||
return m_trTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bigtime_t
|
|
||||||
SoundConsumer::ProcessingLatency()
|
|
||||||
{
|
|
||||||
// We're saying it takes us 500 us to process each buffer. If all we do is
|
|
||||||
// copy the data, it probably takes much less than that, but it doesn't
|
|
||||||
// hurt to be slightly conservative.
|
|
||||||
return 500LL;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bigtime_t
|
|
||||||
SoundConsumer::TotalLatency()
|
|
||||||
{
|
|
||||||
// Had we been a producer that passes buffers on, we'd have to
|
|
||||||
// include downstream latency in this value. But we are not.
|
|
||||||
return ProcessingLatency();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// #pragma mark -
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
SoundConsumer::Record(bigtime_t /*time*/, const void* /*data*/,
|
|
||||||
size_t /*size*/, const media_raw_audio_format& /*format*/)
|
|
||||||
{
|
|
||||||
// If there is no record hook installed, we instead call this function
|
|
||||||
// for received buffers.
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
SoundConsumer::Notify(int32 /*cause*/, ...)
|
|
||||||
{
|
|
||||||
// If there is no notification hook installed, we instead call this
|
|
||||||
// function for giving notification of various events.
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user