hrev48668 adds 2 changesets to branch 'master' old head: 3395fdcd6ab08d881ee3bfbb50413eb1c445595d new head: 683cf2ff58b85b9224cd8a7e08310c2b1041b952 overview: http://cgit.haiku-os.org/haiku/log/?qt=range&q=683cf2f+%5E3395fdc ---------------------------------------------------------------------------- 739fd34: Replace SoundCounsumer with modified BMediaRecorder. Signed-off-by: Hamish Morrison <hamishm53@xxxxxxxxx> [ Barrett <b.vitruvio@xxxxxxxxx> ] 683cf2f: BMediaRecorder: fix a few issues, fix style, remove unused SoundUtils * Use the preferred time source (GetTimeSource) for the node * Fix node releasing when creating the connection fails * Add virtual slots and padding * Refactor _Connect method [ Hamish Morrison <hamishm53@xxxxxxxxx> ] ---------------------------------------------------------------------------- 11 files changed, 1075 insertions(+), 1134 deletions(-) headers/private/media/MediaRecorder.h | 118 +++++ headers/private/media/MediaRecorderNode.h | 115 +++++ headers/private/media/SoundConsumer.h | 151 ------ headers/private/media/SoundUtils.h | 56 --- src/apps/soundrecorder/RecorderWindow.cpp | 184 +++---- src/apps/soundrecorder/RecorderWindow.h | 16 +- src/kits/media/Jamfile | 4 +- src/kits/media/MediaRecorder.cpp | 434 ++++++++++++++++ src/kits/media/MediaRecorderNode.cpp | 324 ++++++++++++ src/kits/media/SoundConsumer.cpp | 700 -------------------------- src/kits/media/SoundUtils.cpp | 107 ---- ############################################################################ Commit: 739fd34cf534560c5bf171ec6e414b85532c1bda URL: http://cgit.haiku-os.org/haiku/commit/?id=739fd34 Author: Barrett <b.vitruvio@xxxxxxxxx> Date: Sun May 4 19:44:00 2014 UTC Committer: Hamish Morrison <hamishm53@xxxxxxxxx> Commit-Date: Mon Jan 12 18:31:20 2015 UTC Replace SoundCounsumer with modified BMediaRecorder. Signed-off-by: Hamish Morrison <hamishm53@xxxxxxxxx> ---------------------------------------------------------------------------- diff --git a/headers/private/media/MediaRecorder.h b/headers/private/media/MediaRecorder.h new file mode 100644 index 0000000..8b6d54e --- /dev/null +++ b/headers/private/media/MediaRecorder.h @@ -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 diff --git a/headers/private/media/MediaRecorderNode.h b/headers/private/media/MediaRecorderNode.h new file mode 100644 index 0000000..b1e421e --- /dev/null +++ b/headers/private/media/MediaRecorderNode.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 diff --git a/headers/private/media/SoundConsumer.h b/headers/private/media/SoundConsumer.h deleted file mode 100644 index c51dcc3..0000000 --- a/headers/private/media/SoundConsumer.h +++ /dev/null @@ -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 diff --git a/src/apps/soundrecorder/RecorderWindow.cpp b/src/apps/soundrecorder/RecorderWindow.cpp index 7445ff9..a4dbf8a 100644 --- a/src/apps/soundrecorder/RecorderWindow.cpp +++ b/src/apps/soundrecorder/RecorderWindow.cpp @@ -1,5 +1,6 @@ /* * Copyright 2005, Jérôme Duval. All rights reserved. + * Copyright 2014, Dario Casalinuovo. All rights reserved. * Distributed under the terms of the MIT License. * * Inspired by SoundCapture from Be newsletter (Media Kit Basics: @@ -42,8 +43,8 @@ #include <TimeSource.h> #include <NodeInfo.h> +#include "SoundUtils.h" #include "RecorderWindow.h" -#include "SoundConsumer.h" #include "FileUtils.h" #if ! NDEBUG @@ -123,7 +124,7 @@ RecorderWindow::RecorderWindow() fSaveButton = NULL; fLoopButton = NULL; fInputField = NULL; - fRecordNode = 0; + fRecorder = NULL; fRecording = false; fTempCount = -1; fButtonState = btnPaused; @@ -146,14 +147,15 @@ RecorderWindow::RecorderWindow() RecorderWindow::~RecorderWindow() { - // The sound consumer and producer are Nodes; it has to be released and the Roster - // will reap it when it's done. - if (fRecordNode) - fRecordNode->Release(); + // The MediaRecorder have to be deleted, the dtor + // disconnect it from the media_kit. + delete fRecorder; + delete fPlayer; if (fPlayTrack && fPlayFile) fPlayFile->ReleaseTrack(fPlayTrack); + if (fPlayFile) delete fPlayFile; fPlayTrack = NULL; @@ -232,17 +234,25 @@ RecorderWindow::InitWindow() if (error < B_OK) // there's no mixer? goto bad_mojo; - // Create our internal Node which records sound, and register it. - fRecordNode = new SoundConsumer("Sound Recorder"); - error = fRoster->RegisterNode(fRecordNode); - if (error < B_OK) + fRecorder = new BMediaRecorder("Sound Recorder", + B_MEDIA_RAW_AUDIO); + + if (fRecorder->InitCheck() < B_OK) 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 BRect r(Bounds()); r.bottom = r.top + 175; - BBox *background = new BBox(r, "_background", B_FOLLOW_LEFT_RIGHT - | B_FOLLOW_TOP, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE_JUMP, B_NO_BORDER); + BBox *background = new BBox(r, "_background", + B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP, B_WILL_DRAW + | B_FRAME_EVENTS | B_NAVIGABLE_JUMP, B_NO_BORDER); + AddChild(background); r = background->Bounds(); @@ -451,9 +461,7 @@ RecorderWindow::InitWindow() dormant_node_info dni[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, 0, B_BUFFER_PRODUCER | B_PHYSICAL_INPUT); if (real_count > maxInputCount) { @@ -514,8 +522,8 @@ RecorderWindow::InitWindow() bad_mojo: if (error >= 0) error = B_ERROR; - if (fRecordNode) - fRecordNode->Release(); + if (fRecorder) + delete fRecorder; delete fPlayer; if (!fInputField) @@ -696,14 +704,7 @@ RecorderWindow::Record(BMessage * message) return; } - // And get it going... - 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); + fRecorder->Start(); } @@ -871,93 +872,60 @@ RecorderWindow::MakeRecordConnection(const media_node & input) { CONNECT((stderr, "RecorderWindow::MakeRecordConnection()\n")); - // Find an available output for the given input node. - int32 count = 0; - status_t err = fRoster->GetFreeOutputsFor(input, &fAudioOutput, 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; - } + status_t err = B_OK; + media_output audioOutput; - // 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; - } + 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); - // 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; - } + if (err < B_OK) { + CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" + " couldn't get free outputs from audio input node\n")); + return err; + } - // Apply the time source in effect to our own Node. - err = fRoster->SetTimeSourceFor(fRecordNode->Node().node, tsobj->Node().node); - if (err < B_OK) { + if (count < 1) { + CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" + " no free outputs from audio input node\n")); + return B_BUSY; + } + + } else { CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" - " couldn't set the sound recorder's time source\n")); - tsobj->Release(); - return err; + " audio input node already connected\n")); + + return B_BUSY; } // 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; // 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) { CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" " couldn't set the sound recorder's hook functions\n")); - tsobj->Release(); return err; } - // Using the same structs for input and output is OK in - // BMediaRoster::Connect(). - err = fRoster->Connect(fAudioOutput.source, fRecInput.destination, - &fRecordFormat, &fAudioOutput, &fRecInput); - if (err < B_OK) { - CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" - " failed to connect sound recorder to audio input node.\n")); - tsobj->Release(); - fRecordNode->SetHooks(0, 0, 0); - return err; - } + if (!fRecorder->IsConnected()) { + + err = fRecorder->Connect(input, &audioOutput, &fRecordFormat); + + if (err < B_OK) { + CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" + " failed to connect sound recorder to audio input node.\n")); - // Start the time source if it's not running. - if ((tsobj->Node() != input) && !tsobj->IsRunning()) - fRoster->StartNode(tsobj->Node(), BTimeSource::RealTime()); + fRecorder->SetHooks(NULL, NULL, NULL); + return err; + } + } - tsobj->Release(); // we're done with this time source instance! return B_OK; } @@ -965,16 +933,13 @@ RecorderWindow::MakeRecordConnection(const media_node & input) status_t RecorderWindow::BreakRecordConnection() { - status_t err; + status_t err = B_OK; + + err = fRecorder->Stop(true); + if (err < B_OK) + return err; - // If we are the last connection, the Node will stop automatically since it - // has nowhere to send data to. - err = fRoster->StopNode(fRecInput.node, 0); - err = fRoster->Disconnect(fAudioOutput.node.node, fAudioOutput.source, - fRecInput.node.node, fRecInput.destination); - fAudioOutput.source = media_source::null; - fRecInput.destination = media_destination::null; - return err; + return fRecorder->Disconnect(); } @@ -984,8 +949,11 @@ RecorderWindow::StopRecording() if (!fRecording) return B_OK; fRecording = false; + BreakRecordConnection(); - fRecordNode->SetHooks(NULL,NULL,NULL); + + fRecorder->SetHooks(NULL, NULL, NULL); + if (fRecSize > 0) { wave_struct header; @@ -1240,7 +1208,7 @@ RecorderWindow::RemoveCurrentSoundItem() { void 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. 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 // or anything) 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; } } diff --git a/src/apps/soundrecorder/RecorderWindow.h b/src/apps/soundrecorder/RecorderWindow.h index 1688334..d3e3087 100644 --- a/src/apps/soundrecorder/RecorderWindow.h +++ b/src/apps/soundrecorder/RecorderWindow.h @@ -21,6 +21,7 @@ #include <Window.h> #include "DrawButton.h" +#include "MediaRecorder.h" #include "ScopeView.h" #include "SoundListView.h" #include "TransportButton.h" @@ -40,12 +41,8 @@ class BScrollView; class BSlider; class BStringView; -namespace BPrivate { -class SoundConsumer; -} - -using BPrivate::SoundConsumer; +using BPrivate::media::BMediaRecorder; class RecorderWindow : public BWindow { @@ -78,6 +75,7 @@ public: }; void AddSoundItem(const BEntry& entry, bool temp = false); + void RemoveCurrentSoundItem(); private: @@ -95,7 +93,7 @@ private: TrackSlider *fTrackSlider; UpDownButton * fUpDownButton; BMenuField * fInputField; - SoundConsumer * fRecordNode; + BMediaRecorder * fRecorder; BSoundPlayer * fPlayer; bool fRecording; SoundListView * fSoundList; @@ -128,8 +126,6 @@ private: off_t fRecSize; media_node fAudioInputNode; - media_output fAudioOutput; - media_input fRecInput; BMediaFile *fPlayFile; media_format fPlayFormat; @@ -171,7 +167,7 @@ private: status_t UpdatePlayFile(SoundListItem *item, bool updateDisplay = false); 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 PlayFile(void * cookie, void * data, size_t size, const media_raw_audio_format & format); diff --git a/src/kits/media/Jamfile b/src/kits/media/Jamfile index 5f049a1..583b12b 100644 --- a/src/kits/media/Jamfile +++ b/src/kits/media/Jamfile @@ -19,7 +19,8 @@ for architectureObject in [ MultiArchSubDirSetup ] { SharedLibrary [ MultiArchDefaultGristFiles libmedia.so ] : # Private Media Kit !missing_symbols.cpp - SoundConsumer.cpp + MediaRecorder.cpp + MediaRecorderNode.cpp # Public Media Kit Buffer.cpp diff --git a/src/kits/media/MediaRecorder.cpp b/src/kits/media/MediaRecorder.cpp new file mode 100644 index 0000000..9c92909 --- /dev/null +++ b/src/kits/media/MediaRecorder.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; +} diff --git a/src/kits/media/MediaRecorderNode.cpp b/src/kits/media/MediaRecorderNode.cpp new file mode 100644 index 0000000..b8c3f97 --- /dev/null +++ b/src/kits/media/MediaRecorderNode.cpp @@ -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; +} diff --git a/src/kits/media/SoundConsumer.cpp b/src/kits/media/SoundConsumer.cpp deleted file mode 100644 index e2ca10f..0000000 --- a/src/kits/media/SoundConsumer.cpp +++ /dev/null @@ -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. -} - -} ############################################################################ Revision: hrev48668 Commit: 683cf2ff58b85b9224cd8a7e08310c2b1041b952 URL: http://cgit.haiku-os.org/haiku/commit/?id=683cf2f Author: Hamish Morrison <hamishm53@xxxxxxxxx> Date: Fri Jan 9 20:56:32 2015 UTC BMediaRecorder: fix a few issues, fix style, remove unused SoundUtils * Use the preferred time source (GetTimeSource) for the node * Fix node releasing when creating the connection fails * Add virtual slots and padding * Refactor _Connect method ---------------------------------------------------------------------------- diff --git a/headers/private/media/MediaRecorder.h b/headers/private/media/MediaRecorder.h index 8b6d54e..417be03 100644 --- a/headers/private/media/MediaRecorder.h +++ b/headers/private/media/MediaRecorder.h @@ -6,17 +6,32 @@ #define _MEDIA_RECORDER_H +#include <MediaDefs.h> #include <MediaNode.h> -#include <TimeSource.h> - -#include "MediaRecorderNode.h" -#include "SoundUtils.h" namespace BPrivate { namespace media { + +class BMediaRecorderNode; + class BMediaRecorder { public: + enum notification { + B_WILL_START = 1, // performance_time + B_WILL_STOP, // performance_time immediate + B_WILL_SEEK, // performance_time media_time + B_WILL_TIMEWARP, // real_time performance_time + }; + + typedef void (*ProcessFunc)(void* cookie, + bigtime_t timestamp, void* data, + size_t size, const media_format& format); + + typedef void (*NotifyFunc)(void* cookie, + notification what, ...); + +public: BMediaRecorder(const char* name, media_type type = B_MEDIA_UNKNOWN_TYPE); @@ -25,14 +40,6 @@ public: 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); @@ -43,17 +50,14 @@ public: 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 media_format& format); virtual status_t Connect(const dormant_node_info& dormantInfo, - const media_format* format = NULL, - uint32 flags = 0); + const media_format& format); virtual status_t Connect(const media_node& node, - const media_output* useOutput = NULL, - const media_format* format = NULL, - uint32 flags = 0); + const media_output* output = NULL, + const media_format* format = NULL); virtual status_t Disconnect(); @@ -67,37 +71,42 @@ public: protected: - virtual void BufferReceived(void* buffer, - size_t size, + 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 _Connect(const media_node& mediaNode, + const media_output* output, + const media_format& format); - status_t fInitErr; + virtual void _ReservedMediaRecorder0(); + virtual void _ReservedMediaRecorder1(); + virtual void _ReservedMediaRecorder2(); + virtual void _ReservedMediaRecorder3(); + virtual void _ReservedMediaRecorder4(); + virtual void _ReservedMediaRecorder5(); + virtual void _ReservedMediaRecorder6(); + virtual void _ReservedMediaRecorder7(); - bool fConnected; - bool fRunning; + status_t fInitErr; - BTimeSource* fTimeSource; + bool fConnected; + bool fRunning; + bool fReleaseOutputNode; - ProcessFunc fRecordHook; - NotifyFunc fNotifyHook; + ProcessFunc fRecordHook; + NotifyFunc fNotifyHook; - media_node fOutputNode; - media_output fOutput; + media_node fOutputNode; + media_output fOutput; - BMediaRecorderNode* fNode; - media_input fInput; + BMediaRecorderNode* fNode; + media_input fInput; - void* fBufferCookie; + void* fBufferCookie; + uint32 fPadding[32]; - friend class BMediaRecorderNode; + friend class BMediaRecorderNode; }; } diff --git a/headers/private/media/MediaRecorderNode.h b/headers/private/media/MediaRecorderNode.h index b1e421e..8a4f2fe 100644 --- a/headers/private/media/MediaRecorderNode.h +++ b/headers/private/media/MediaRecorderNode.h @@ -1,6 +1,6 @@ /* - * Copyright 1999, Be Incorporated * Copyright 2014, Dario Casalinuovo + * Copyright 1999, Be Incorporated * All Rights Reserved. * This file may be used under the terms of the Be Sample Code License. */ @@ -76,27 +76,23 @@ protected: virtual void BufferReceived(BBuffer* buffer); virtual void ProducerDataStatus( - const media_destination& forWhom, + const media_destination& destination, int32 status, bigtime_t performanceTime); - virtual status_t GetLatencyFor( - const media_destination& forWhom, - bigtime_t* outLatency, - media_node_id* outTimesource); + virtual status_t GetLatencyFor(const media_destination& destination, + bigtime_t* outLatency, + media_node_id* outTimesource); - virtual status_t Connected( - const media_source& producer, + virtual status_t Connected(const media_source& producer, const media_destination& where, - const media_format& withFormat, + const media_format& format, media_input* outInput); - virtual void Disconnected( - const media_source& producer, + virtual void Disconnected(const media_source& producer, const media_destination& where); - virtual status_t FormatChanged( - const media_source& producer, + virtual status_t FormatChanged(const media_source& producer, const media_destination& consumer, int32 tag, const media_format& format); diff --git a/headers/private/media/SoundUtils.h b/headers/private/media/SoundUtils.h deleted file mode 100644 index c9e5ec8..0000000 --- a/headers/private/media/SoundUtils.h +++ /dev/null @@ -1,56 +0,0 @@ -/******************************************************************************* -/ -/ File: SoundUtils.h -/ -/ Description: Utility functions for handling audio data. -/ -/ Copyright 1998-1999, Be Incorporated, All Rights Reserved -/ -*******************************************************************************/ - -#if ! defined( _SoundUtils_h ) -#define _SoundUtils_h - -#include <MediaDefs.h> [ *** diff truncated: 674 lines dropped *** ]