Ticket #8911: 0001-Replace-SoundCounsumer-with-modified-BMediaRecorder.patch
File 0001-Replace-SoundCounsumer-with-modified-BMediaRecorder.patch, 59.9 KB (added by , 10 years ago) |
---|
-
new file headers/private/media/MediaRecorder.h
From 337b20d3672e656bc612334466e07dc0ee287883 Mon Sep 17 00:00:00 2001 From: Barrett <b.vitruvio@gmail.com> Date: Sun, 4 May 2014 21:44:00 +0200 Subject: [PATCH] Replace SoundCounsumer with modified BMediaRecorder. --- headers/private/media/MediaRecorder.h | 109 +++++ headers/private/media/MediaRecorderNode.h | 119 +++++ headers/private/media/SoundConsumer.h | 151 ------- src/apps/soundrecorder/RecorderWindow.cpp | 180 ++++---- src/apps/soundrecorder/RecorderWindow.h | 14 +- src/kits/media/Jamfile | 3 +- src/kits/media/MediaRecorder.cpp | 473 ++++++++++++++++++++ src/kits/media/MediaRecorderNode.cpp | 324 ++++++++++++++ src/kits/media/SoundConsumer.cpp | 700 ------------------------------ 9 files changed, 1106 insertions(+), 967 deletions(-) create mode 100644 headers/private/media/MediaRecorder.h create mode 100644 headers/private/media/MediaRecorderNode.h delete mode 100644 headers/private/media/SoundConsumer.h create mode 100644 src/kits/media/MediaRecorder.cpp create mode 100644 src/kits/media/MediaRecorderNode.cpp delete mode 100644 src/kits/media/SoundConsumer.cpp diff --git a/headers/private/media/MediaRecorder.h b/headers/private/media/MediaRecorder.h new file mode 100644 index 0000000..8b6d54e
- + 1 /* 2 * Copyright 2014, Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 */ 5 #ifndef _MEDIA_RECORDER_H 6 #define _MEDIA_RECORDER_H 7 8 9 #include <MediaNode.h> 10 #include <TimeSource.h> 11 12 #include "MediaRecorderNode.h" 13 #include "SoundUtils.h" 14 15 16 namespace BPrivate { namespace media { 17 18 class BMediaRecorder { 19 public: 20 BMediaRecorder(const char* name, 21 media_type type 22 = B_MEDIA_UNKNOWN_TYPE); 23 24 virtual ~BMediaRecorder(); 25 26 status_t InitCheck() const; 27 28 typedef void (*ProcessFunc)(void* cookie, 29 bigtime_t timestamp, void* data, 30 size_t datasize, 31 const media_format& format); 32 33 typedef void (*NotifyFunc)(void* cookie, 34 int32 code, ...); 35 36 status_t SetHooks(ProcessFunc recordFunc = NULL, 37 NotifyFunc notifyFunc = NULL, 38 void* cookie = NULL); 39 40 void SetAcceptedFormat( 41 const media_format& format); 42 43 virtual status_t Start(bool force = false); 44 virtual status_t Stop(bool force = false); 45 46 virtual status_t Connect(const media_format& format, 47 uint32 flags = 0); 48 49 virtual status_t Connect(const dormant_node_info& dormantInfo, 50 const media_format* format = NULL, 51 uint32 flags = 0); 52 53 virtual status_t Connect(const media_node& node, 54 const media_output* useOutput = NULL, 55 const media_format* format = NULL, 56 uint32 flags = 0); 57 58 virtual status_t Disconnect(); 59 60 bool IsConnected() const; 61 bool IsRunning() const; 62 63 const media_format& Format() const; 64 65 const media_output& MediaOutput() const; 66 const media_input& MediaInput() const; 67 68 protected: 69 70 virtual void BufferReceived(void* buffer, 71 size_t size, 72 const media_header& header); 73 private: 74 75 status_t _Connect( 76 const media_format* format, 77 uint32 flags, 78 const dormant_node_info* dormantNode, 79 const media_node* mediaNode, 80 const media_output* output); 81 82 status_t fInitErr; 83 84 bool fConnected; 85 bool fRunning; 86 87 BTimeSource* fTimeSource; 88 89 ProcessFunc fRecordHook; 90 NotifyFunc fNotifyHook; 91 92 media_node fOutputNode; 93 media_output fOutput; 94 95 BMediaRecorderNode* fNode; 96 media_input fInput; 97 98 void* fBufferCookie; 99 100 friend class BMediaRecorderNode; 101 }; 102 103 } 104 105 } 106 107 using namespace BPrivate::media; 108 109 #endif // _MEDIA_RECORDER_H -
new file headers/private/media/MediaRecorderNode.h
diff --git a/headers/private/media/MediaRecorderNode.h b/headers/private/media/MediaRecorderNode.h new file mode 100644 index 0000000..b1e421e
- + 1 /* 2 * Copyright 1999, Be Incorporated 3 * Copyright 2014, Dario Casalinuovo 4 * All Rights Reserved. 5 * This file may be used under the terms of the Be Sample Code License. 6 */ 7 #ifndef _MEDIA_RECORDER_NODE_H 8 #define _MEDIA_RECORDER_NODE_H 9 10 11 #include <BufferConsumer.h> 12 #include <MediaEventLooper.h> 13 #include <String.h> 14 15 16 namespace BPrivate { namespace media { 17 18 class BMediaRecorder; 19 20 class BMediaRecorderNode : public BMediaEventLooper, 21 public BBufferConsumer { 22 public: 23 BMediaRecorderNode(const char* name, 24 BMediaRecorder* recorder, 25 media_type type 26 = B_MEDIA_UNKNOWN_TYPE); 27 28 // TODO these are not thread safe; we should fix that... 29 void SetAcceptedFormat(const media_format& format); 30 31 status_t GetInput(media_input* outInput); 32 33 void SetDataEnabled(bool enabled); 34 35 protected: 36 37 virtual BMediaAddOn* AddOn(int32* id) const; 38 39 virtual void NodeRegistered(); 40 41 virtual void SetRunMode(run_mode mode); 42 43 virtual void HandleEvent(const media_timed_event* event, 44 bigtime_t lateness, 45 bool realTimeEvent); 46 47 virtual void Start(bigtime_t performanceTime); 48 49 virtual void Stop(bigtime_t performanceTime, 50 bool immediate); 51 52 virtual void Seek(bigtime_t mediaTime, 53 bigtime_t performanceTime); 54 55 virtual void TimeWarp(bigtime_t realTime, 56 bigtime_t performanceTime); 57 58 virtual status_t HandleMessage(int32 message, 59 const void* data, 60 size_t size); 61 62 // Someone, probably the producer, is asking you about 63 // this format. Give your honest opinion, possibly 64 // modifying *format. Do not ask upstream producer about 65 // the format, since he's synchronously waiting for your 66 // reply. 67 68 virtual status_t AcceptFormat(const media_destination& dest, 69 media_format* format); 70 71 virtual status_t GetNextInput(int32* cookie, 72 media_input* outInput); 73 74 virtual void DisposeInputCookie(int32 cookie); 75 76 virtual void BufferReceived(BBuffer* buffer); 77 78 virtual void ProducerDataStatus( 79 const media_destination& forWhom, 80 int32 status, 81 bigtime_t performanceTime); 82 83 virtual status_t GetLatencyFor( 84 const media_destination& forWhom, 85 bigtime_t* outLatency, 86 media_node_id* outTimesource); 87 88 virtual status_t Connected( 89 const media_source& producer, 90 const media_destination& where, 91 const media_format& withFormat, 92 media_input* outInput); 93 94 virtual void Disconnected( 95 const media_source& producer, 96 const media_destination& where); 97 98 virtual status_t FormatChanged( 99 const media_source& producer, 100 const media_destination& consumer, 101 int32 tag, 102 const media_format& format); 103 104 protected: 105 106 virtual ~BMediaRecorderNode(); 107 108 BMediaRecorder* fRecorder; 109 media_format fOKFormat; 110 media_input fInput; 111 BString fName; 112 }; 113 114 } 115 } 116 117 using namespace BPrivate::media; 118 119 #endif // _MEDIA_RECORDER_NODE_H -
deleted file headers/private/media/SoundConsumer.h
diff --git a/headers/private/media/SoundConsumer.h b/headers/private/media/SoundConsumer.h deleted file mode 100644 index c51dcc3..0000000
+ - 1 /*******************************************************************************2 /3 / File: SoundConsumer.h4 /5 / Description: Record sound from some sound-producing Node.6 /7 / Copyright 1998, Be Incorporated, All Rights Reserved8 /9 *******************************************************************************/10 #ifndef SOUND_CONSUMER_H11 #define SOUND_CONSUMER_H12 13 // To use this Consumer:14 // 1. Create Record and Notify hooks, or subclass SoundConsumer15 // if you'd rather use the inheritance hierarchy.16 // * The Record function should do whatever you want to do17 // when you receive a buffer.18 // * The Notify function should handle whatever events19 // you wish to handle (defined in SoundUtil.h).20 // 2: Create an instance of SoundConsumer, giving it the21 // appropriate hook functions. Or, create an instance of an22 // appropriate subclass if you've made one.23 // 3: Register your new Consumer with the MediaRoster.24 // 4: Connect your Consumer to some Producer.25 // 5: Start or Stop the Consumer if your hook functions26 // implement behavior for these kinds of events.27 // Seek the Consumer to set the offset of the timestamps that28 // your Record function will see.29 // 6: When you're done, disconnect the Consumer, then delete it.30 31 #include <BufferConsumer.h>32 #include "SoundUtils.h"33 34 namespace BPrivate {35 36 37 class SoundConsumer : public BBufferConsumer {38 public:39 SoundConsumer(const char* name,40 SoundProcessFunc recordFunc = NULL,41 SoundNotifyFunc notifyFunc = NULL,42 void* cookie = NULL);43 virtual ~SoundConsumer();44 45 //This function is OK to call from any thread.46 status_t SetHooks(SoundProcessFunc recordFunc = NULL,47 SoundNotifyFunc notifyFunc = NULL,48 void* cookie = NULL);49 50 // The MediaNode interface51 public:52 virtual port_id ControlPort() const;53 virtual BMediaAddOn* AddOn(int32* internalID) const;54 // Who instantiated you -- or NULL for app class55 56 57 protected:58 virtual void Start(bigtime_t performanceTime);59 virtual void Stop(bigtime_t performanceTime, bool immediate);60 virtual void Seek(bigtime_t mediaTime,61 bigtime_t performanceTime);62 virtual void SetRunMode(run_mode mode);63 virtual void TimeWarp(bigtime_t atRealTime,64 bigtime_t to_performanceTime);65 virtual void Preroll();66 virtual void SetTimeSource(BTimeSource* timeSource);67 virtual status_t HandleMessage(int32 message, const void* data,68 size_t size);69 70 // The BufferConsumer interface71 virtual status_t AcceptFormat(const media_destination& dest,72 media_format* format);73 virtual status_t GetNextInput(int32* cookie,74 media_input* _input);75 virtual void DisposeInputCookie(int32 cookie);76 virtual void BufferReceived(BBuffer* buffer);77 virtual void ProducerDataStatus(78 const media_destination& forWhom,79 int32 status, bigtime_t atMediaTime);80 virtual status_t GetLatencyFor(const media_destination& forWhom,81 bigtime_t* _latency,82 media_node_id* _timesource);83 virtual status_t Connected(const media_source& producer,84 const media_destination& where,85 const media_format& format,86 media_input* _input);87 virtual void Disconnected(const media_source& producer,88 const media_destination& where);89 virtual status_t FormatChanged(const media_source& producer,90 const media_destination& consumer,91 int32 fromChangeCount,92 const media_format& format);93 94 protected:95 // Functions called when no hooks are installed.96 // OK to override instead of installing hooks.97 virtual void Record(bigtime_t time, const void* data,98 size_t size,99 const media_raw_audio_format& format);100 virtual void Notify(int32 cause, ...);101 102 private:103 SoundProcessFunc m_recordHook;104 SoundNotifyFunc m_notifyHook;105 void* m_cookie;106 media_input m_input;107 thread_id m_thread;108 port_id m_port;109 110 // The times we need to deal with111 // My notation for times: tr = real time,112 // tp = performance time, tm = media time.113 bigtime_t m_trTimeout;114 // how long to wait on the input port115 bigtime_t m_tpSeekAt;116 // when we Seek117 bigtime_t m_tmSeekTo;118 // target time for Seek119 120 // The transformation from media to peformance time.121 // d = p - m, so m + d = p.122 // Media time is generally governed by the Seek123 // function. In our node, we simply use media time as124 // the time that we report to the record hook function.125 // If we were a producer node, we might use media time126 // to track where we were in playing a certain piece127 // of media. But we aren't.128 bigtime_t m_delta;129 130 // State variables131 bool m_seeking;132 // a Seek is pending133 134 // Functions to calculate timing values. OK to override.135 // ProcessingLatency is the time it takes to process a buffer;136 // TotalLatency is returned to producer; Timeout is passed137 // to call to read_port_etc() in service thread loop.138 virtual bigtime_t Timeout();139 virtual bigtime_t ProcessingLatency();140 virtual bigtime_t TotalLatency();141 // The actual thread doing the work142 static status_t ThreadEntry(void* cookie);143 void ServiceThread();144 void DoHookChange(void* message);145 };146 147 148 }149 150 151 #endif // SOUND_CONSUMER_H -
src/apps/soundrecorder/RecorderWindow.cpp
diff --git a/src/apps/soundrecorder/RecorderWindow.cpp b/src/apps/soundrecorder/RecorderWindow.cpp index f2fb731..dad953a 100644
a b 1 1 /* 2 2 * Copyright 2005, Jérôme Duval. All rights reserved. 3 * Copyright 2014, Dario Casalinuovo. All rights reserved. 3 4 * Distributed under the terms of the MIT License. 4 5 * 5 6 * Inspired by SoundCapture from Be newsletter (Media Kit Basics: … … 42 43 #include <TimeSource.h> 43 44 #include <NodeInfo.h> 44 45 46 #include "SoundUtils.h" 45 47 #include "RecorderWindow.h" 46 #include "SoundConsumer.h"47 48 #include "FileUtils.h" 48 49 49 50 #if ! NDEBUG … … RecorderWindow::RecorderWindow() 123 124 fSaveButton = NULL; 124 125 fLoopButton = NULL; 125 126 fInputField = NULL; 126 fRecord Node = 0;127 fRecorder = NULL; 127 128 fRecording = false; 128 129 fTempCount = -1; 129 130 fButtonState = btnPaused; … … RecorderWindow::RecorderWindow() 146 147 147 148 RecorderWindow::~RecorderWindow() 148 149 { 149 // The sound consumer and producer are Nodes; it has to be released and the Roster150 // will reap it when it's done.151 if (fRecordNode)152 fRecordNode->Release(); 150 // The MediaRecorder have to be deleted, the dtor 151 // disconnect it from the media_kit. 152 delete fRecorder; 153 153 154 delete fPlayer; 154 155 155 156 if (fPlayTrack && fPlayFile) 156 157 fPlayFile->ReleaseTrack(fPlayTrack); 158 157 159 if (fPlayFile) 158 160 delete fPlayFile; 159 161 fPlayTrack = NULL; … … RecorderWindow::InitWindow() 232 234 if (error < B_OK) // there's no mixer? 233 235 goto bad_mojo; 234 236 235 // Create our internal Node which records sound, and register it.236 fRecordNode = new SoundConsumer("Sound Recorder");237 error = fRoster->RegisterNode(fRecordNode); 238 if ( error< B_OK)237 fRecorder = new BMediaRecorder("Sound Recorder", 238 B_MEDIA_RAW_AUDIO); 239 240 if (fRecorder->InitCheck() < B_OK) 239 241 goto bad_mojo; 240 242 243 // Set the node to accept only audio data 244 media_format output_format; 245 output_format.type = B_MEDIA_RAW_AUDIO; 246 output_format.u.raw_audio = media_raw_audio_format::wildcard; 247 fRecorder->SetAcceptedFormat(output_format); 248 241 249 // Create the window header with controls 242 250 BRect r(Bounds()); 243 251 r.bottom = r.top + 175; 244 BBox *background = new BBox(r, "_background", B_FOLLOW_LEFT_RIGHT 245 | B_FOLLOW_TOP, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE_JUMP, B_NO_BORDER); 252 BBox *background = new BBox(r, "_background", 253 B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP, B_WILL_DRAW 254 | B_FRAME_EVENTS | B_NAVIGABLE_JUMP, B_NO_BORDER); 255 246 256 AddChild(background); 247 257 248 258 r = background->Bounds(); … … RecorderWindow::InitWindow() 451 461 dormant_node_info dni[maxInputCount]; 452 462 453 463 int32 real_count = maxInputCount; 454 media_format output_format; 455 output_format.type = B_MEDIA_RAW_AUDIO; 456 output_format.u.raw_audio = media_raw_audio_format::wildcard; 464 457 465 error = fRoster->GetDormantNodes(dni, &real_count, 0, &output_format, 458 466 0, B_BUFFER_PRODUCER | B_PHYSICAL_INPUT); 459 467 if (real_count > maxInputCount) { … … RecorderWindow::InitWindow() 514 522 bad_mojo: 515 523 if (error >= 0) 516 524 error = B_ERROR; 517 if (fRecord Node)518 fRecordNode->Release();525 if (fRecorder) 526 delete fRecorder; 519 527 520 528 delete fPlayer; 521 529 if (!fInputField) … … RecorderWindow::Record(BMessage * message) 696 704 return; 697 705 } 698 706 699 // And get it going... 700 bigtime_t then = fRecordNode->TimeSource()->Now() + 50000LL; 701 fRoster->StartNode(fRecordNode->Node(), then); 702 if (fAudioInputNode.kind & B_TIME_SOURCE) { 703 fRoster->StartNode(fAudioInputNode, 704 fRecordNode->TimeSource()->RealTimeFor(then, 0)); 705 } else 706 fRoster->StartNode(fAudioInputNode, then); 707 fRecorder->Start(); 707 708 } 708 709 709 710 … … RecorderWindow::MakeRecordConnection(const media_node & input) 871 872 { 872 873 CONNECT((stderr, "RecorderWindow::MakeRecordConnection()\n")); 873 874 874 // Find an available output for the given input node. 875 int32 count = 0; 876 status_t err = fRoster->GetFreeOutputsFor(input, &fAudioOutput, 1, &count, B_MEDIA_RAW_AUDIO); 877 if (err < B_OK) { 878 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 879 " couldn't get free outputs from audio input node\n")); 880 return err; 881 } 882 if (count < 1) { 883 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 884 " no free outputs from audio input node\n")); 885 return B_BUSY; 886 } 875 status_t err = B_OK; 876 media_output audioOutput; 887 877 888 // Find an available input for our own Node. Note that we go through the 889 // MediaRoster; calling Media Kit methods directly on Nodes in our app is 890 // not OK (because synchronization happens in the service thread, not in 891 // the calling thread). 892 // TODO: explain this 893 err = fRoster->GetFreeInputsFor(fRecordNode->Node(), &fRecInput, 1, &count, B_MEDIA_RAW_AUDIO); 894 if (err < B_OK) { 895 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 896 " couldn't get free inputs for sound recorder\n")); 897 return err; 898 } 899 if (count < 1) { 900 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 901 " no free inputs for sound recorder\n")); 902 return B_BUSY; 903 } 878 if (!fRecorder->IsConnected()) { 879 // Find an available output for the given input node. 880 int32 count = 0; 881 err = fRoster->GetFreeOutputsFor(input, &audioOutput, 1, 882 &count, B_MEDIA_RAW_AUDIO); 904 883 905 // Find out what the time source of the input is. 906 // For most nodes, we just use the preferred time source (the DAC) for synchronization. 907 // However, nodes that record from an input need to synchronize to the audio input node 908 // instead for best results. 909 // MakeTimeSourceFor gives us a "clone" of the time source node that we can manipulate 910 // to our heart's content. When we're done with it, though, we need to call Release() 911 // on the time source node, so that it keeps an accurate reference count and can delete 912 // itself when it's no longer needed. 913 // TODO: what about filters connected to audio input? 914 media_node use_time_source; 915 BTimeSource * tsobj = fRoster->MakeTimeSourceFor(input); 916 if (! tsobj) { 917 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 918 " couldn't clone time source from audio input node\n")); 919 return B_MEDIA_BAD_NODE; 920 } 884 if (err < B_OK) { 885 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 886 " couldn't get free outputs from audio input node\n")); 887 return err; 888 } 921 889 922 // Apply the time source in effect to our own Node. 923 err = fRoster->SetTimeSourceFor(fRecordNode->Node().node, tsobj->Node().node); 924 if (err < B_OK) { 890 if (count < 1) { 891 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 892 " no free outputs from audio input node\n")); 893 return B_BUSY; 894 } 895 896 } else { 925 897 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 926 " couldn't set the sound recorder's time source\n"));927 tsobj->Release(); 928 return err;898 " audio input node already connected\n")); 899 900 return B_BUSY; 929 901 } 930 902 931 903 // Get a format, any format. 932 fRecordFormat.u.raw_audio = fAudioOutput.format.u.raw_audio;904 fRecordFormat.u.raw_audio = audioOutput.format.u.raw_audio; 933 905 fRecordFormat.type = B_MEDIA_RAW_AUDIO; 934 906 935 907 // Tell the consumer where we want data to go. 936 err = fRecordNode->SetHooks(RecordFile, NotifyRecordFile, this); 908 err = fRecorder->SetHooks(RecordFile, NotifyRecordFile, this); 909 937 910 if (err < B_OK) { 938 911 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 939 912 " couldn't set the sound recorder's hook functions\n")); 940 tsobj->Release();941 913 return err; 942 914 } 943 915 944 // Using the same structs for input and output is OK in 945 // BMediaRoster::Connect(). 946 err = fRoster->Connect(fAudioOutput.source, fRecInput.destination, 947 &fRecordFormat, &fAudioOutput, &fRecInput); 948 if (err < B_OK) { 949 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 950 " failed to connect sound recorder to audio input node.\n")); 951 tsobj->Release(); 952 fRecordNode->SetHooks(0, 0, 0); 953 return err; 954 } 916 if (!fRecorder->IsConnected()) { 917 918 err = fRecorder->Connect(input, &audioOutput, &fRecordFormat); 919 920 if (err < B_OK) { 921 CONNECT((stderr, "RecorderWindow::MakeRecordConnection():" 922 " failed to connect sound recorder to audio input node.\n")); 955 923 956 // Start the time source if it's not running. 957 if ((tsobj->Node() != input) && !tsobj->IsRunning()) 958 fRoster->StartNode(tsobj->Node(), BTimeSource::RealTime()); 924 fRecorder->SetHooks(NULL, NULL, NULL); 925 return err; 926 } 927 } 959 928 960 tsobj->Release(); // we're done with this time source instance!961 929 return B_OK; 962 930 } 963 931 … … RecorderWindow::MakeRecordConnection(const media_node & input) 965 933 status_t 966 934 RecorderWindow::BreakRecordConnection() 967 935 { 968 status_t err; 936 status_t err = B_OK; 937 938 err = fRecorder->Stop(true); 939 if (err < B_OK) 940 return err; 969 941 970 // If we are the last connection, the Node will stop automatically since it 971 // has nowhere to send data to. 972 err = fRoster->StopNode(fRecInput.node, 0); 973 err = fRoster->Disconnect(fAudioOutput.node.node, fAudioOutput.source, 974 fRecInput.node.node, fRecInput.destination); 975 fAudioOutput.source = media_source::null; 976 fRecInput.destination = media_destination::null; 977 return err; 942 return fRecorder->Disconnect(); 978 943 } 979 944 980 945 … … RecorderWindow::StopRecording() 984 949 if (!fRecording) 985 950 return B_OK; 986 951 fRecording = false; 952 987 953 BreakRecordConnection(); 988 fRecordNode->SetHooks(NULL,NULL,NULL); 954 955 fRecorder->SetHooks(NULL, NULL, NULL); 956 989 957 if (fRecSize > 0) { 990 958 991 959 wave_struct header; … … RecorderWindow::RemoveCurrentSoundItem() { 1240 1208 1241 1209 void 1242 1210 RecorderWindow::RecordFile(void* cookie, bigtime_t timestamp, 1243 void* data, size_t size, const media_ raw_audio_format &format)1211 void* data, size_t size, const media_format &format) 1244 1212 { 1245 1213 // Callback called from the SoundConsumer when receiving buffers. 1246 1214 RecorderWindow * window = (RecorderWindow *)cookie; … … RecorderWindow::RecordFile(void* cookie, bigtime_t timestamp, 1249 1217 // Write the data to file (we don't buffer or guard file access 1250 1218 // or anything) 1251 1219 window->fRecFile.WriteAt(window->fRecSize, data, size); 1252 window->fVUView->ComputeLevels(data, size, format. format);1220 window->fVUView->ComputeLevels(data, size, format.u.raw_audio.format); 1253 1221 window->fRecSize += size; 1254 1222 } 1255 1223 } -
src/apps/soundrecorder/RecorderWindow.h
diff --git a/src/apps/soundrecorder/RecorderWindow.h b/src/apps/soundrecorder/RecorderWindow.h index 1688334..d3e3087 100644
a b 21 21 #include <Window.h> 22 22 23 23 #include "DrawButton.h" 24 #include "MediaRecorder.h" 24 25 #include "ScopeView.h" 25 26 #include "SoundListView.h" 26 27 #include "TransportButton.h" … … class BScrollView; 40 41 class BSlider; 41 42 class BStringView; 42 43 43 namespace BPrivate {44 class SoundConsumer;45 }46 44 47 48 using BPrivate::SoundConsumer; 45 using BPrivate::media::BMediaRecorder; 49 46 50 47 51 48 class RecorderWindow : public BWindow { … … public: 78 75 }; 79 76 80 77 void AddSoundItem(const BEntry& entry, bool temp = false); 78 81 79 void RemoveCurrentSoundItem(); 82 80 83 81 private: … … private: 95 93 TrackSlider *fTrackSlider; 96 94 UpDownButton * fUpDownButton; 97 95 BMenuField * fInputField; 98 SoundConsumer * fRecordNode;96 BMediaRecorder * fRecorder; 99 97 BSoundPlayer * fPlayer; 100 98 bool fRecording; 101 99 SoundListView * fSoundList; … … private: 128 126 off_t fRecSize; 129 127 130 128 media_node fAudioInputNode; 131 media_output fAudioOutput;132 media_input fRecInput;133 129 134 130 BMediaFile *fPlayFile; 135 131 media_format fPlayFormat; … … private: 171 167 status_t UpdatePlayFile(SoundListItem *item, bool updateDisplay = false); 172 168 void ErrorAlert(const char * action, status_t err); 173 169 174 static void RecordFile(void * cookie, bigtime_t timestamp, void * data, size_t size, const media_ raw_audio_format & format);170 static void RecordFile(void * cookie, bigtime_t timestamp, void * data, size_t size, const media_format & format); 175 171 static void NotifyRecordFile(void * cookie, int32 code, ...); 176 172 177 173 static void PlayFile(void * cookie, void * data, size_t size, const media_raw_audio_format & format); -
src/kits/media/Jamfile
diff --git a/src/kits/media/Jamfile b/src/kits/media/Jamfile index 907794c..9c17f9e 100644
a b for architectureObject in [ MultiArchSubDirSetup ] { 19 19 SharedLibrary [ MultiArchDefaultGristFiles libmedia.so ] : 20 20 # Private Media Kit 21 21 !missing_symbols.cpp 22 SoundConsumer.cpp 22 MediaRecorder.cpp 23 MediaRecorderNode.cpp 23 24 24 25 # Public Media Kit 25 26 Buffer.cpp -
new file src/kits/media/MediaRecorder.cpp
diff --git a/src/kits/media/MediaRecorder.cpp b/src/kits/media/MediaRecorder.cpp new file mode 100644 index 0000000..9c92909
- + 1 /* 2 * Copyright 1999, Be Incorporated 3 * Copyright 2014, Dario Casalinuovo 4 * All Rights Reserved. 5 * This file may be used under the terms of the Be Sample Code License. 6 */ 7 8 9 #include "MediaRecorder.h" 10 11 #include <MediaAddOn.h> 12 #include <MediaRoster.h> 13 #include <TimeSource.h> 14 15 #include "MediaDebug.h" 16 #include "MediaRecorderNode.h" 17 18 19 class MediaNodeReleaser { 20 public: 21 MediaNodeReleaser(media_node& mediaNode, 22 bool release = true) 23 : 24 fNode(mediaNode), 25 fRelease(release) 26 { 27 } 28 29 ~MediaNodeReleaser() 30 { 31 if (fRelease) 32 BMediaRoster::Roster()->ReleaseNode(fNode); 33 } 34 35 void SetTo(media_node& node) 36 { 37 fNode = node; 38 } 39 40 void SetRelease(bool release) 41 { 42 fRelease = release; 43 } 44 45 private: 46 media_node& fNode; 47 bool fRelease; 48 }; 49 50 51 BMediaRecorder::BMediaRecorder(const char* name, media_type type) 52 : 53 fInitErr(B_OK), 54 fConnected(false), 55 fRunning(false), 56 fTimeSource(NULL), 57 fRecordHook(NULL), 58 fNotifyHook(NULL), 59 fNode(NULL), 60 fBufferCookie(NULL) 61 { 62 CALLED(); 63 64 BMediaRoster::Roster(&fInitErr); 65 66 if (fInitErr == B_OK) { 67 fNode = new BMediaRecorderNode(name, this, type); 68 fInitErr = BMediaRoster::CurrentRoster()->RegisterNode(fNode); 69 } 70 } 71 72 73 BMediaRecorder::~BMediaRecorder() 74 { 75 CALLED(); 76 77 if (fNode != NULL) { 78 Stop(); 79 Disconnect(); 80 fNode->Release(); 81 } 82 83 if (fTimeSource != NULL) 84 fTimeSource->Release(); 85 86 } 87 88 89 status_t 90 BMediaRecorder::InitCheck() const 91 { 92 CALLED(); 93 94 return fInitErr; 95 } 96 97 98 void 99 BMediaRecorder::SetAcceptedFormat(const media_format& format) 100 { 101 CALLED(); 102 103 fNode->SetAcceptedFormat(format); 104 } 105 106 107 status_t 108 BMediaRecorder::SetHooks(ProcessFunc recordFunc, NotifyFunc notifyFunc, 109 void* cookie) 110 { 111 CALLED(); 112 113 fRecordHook = recordFunc; 114 fNotifyHook = notifyFunc; 115 fBufferCookie = cookie; 116 117 return B_OK; 118 } 119 120 121 void 122 BMediaRecorder::BufferReceived(void* buffer, size_t size, 123 const media_header& header) 124 { 125 CALLED(); 126 127 if (fRecordHook) { 128 (*fRecordHook)(fBufferCookie, header.start_time, 129 buffer, size, Format()); 130 } 131 } 132 133 134 status_t 135 BMediaRecorder::Connect(const media_format& format, uint32 flags) 136 { 137 CALLED(); 138 139 if (fInitErr != B_OK) 140 return fInitErr; 141 142 if (fConnected) 143 return B_MEDIA_ALREADY_CONNECTED; 144 145 return _Connect(&format, flags, NULL, NULL, NULL); 146 } 147 148 149 status_t 150 BMediaRecorder::Connect(const dormant_node_info& dormantInfo, 151 const media_format* format, uint32 flags) 152 { 153 CALLED(); 154 155 if (fInitErr != B_OK) 156 return fInitErr; 157 158 if (fConnected) 159 return B_MEDIA_ALREADY_CONNECTED; 160 161 return _Connect(format, flags, &dormantInfo, NULL, NULL); 162 } 163 164 165 status_t 166 BMediaRecorder::Connect(const media_node& node, 167 const media_output* useOutput, const media_format* format, 168 uint32 flags) 169 { 170 CALLED(); 171 172 if (fInitErr != B_OK) 173 return fInitErr; 174 175 if (fConnected) 176 return B_MEDIA_ALREADY_CONNECTED; 177 178 return _Connect(format, flags, NULL, &node, useOutput); 179 } 180 181 182 status_t 183 BMediaRecorder::Disconnect() 184 { 185 CALLED(); 186 187 status_t err = B_OK; 188 189 if (fInitErr != B_OK) 190 return fInitErr; 191 192 if (!fConnected) 193 return B_MEDIA_NOT_CONNECTED; 194 195 if (!fNode) 196 return B_ERROR; 197 198 if (fRunning) 199 err = Stop(); 200 201 if (err != B_OK) 202 return err; 203 204 // do the disconnect 205 err = BMediaRoster::CurrentRoster()->Disconnect( 206 fOutputNode.node, fOutput.source, 207 fNode->Node().node, fInput.destination); 208 209 BMediaRoster::Roster()->ReleaseNode(fOutputNode); 210 211 if (fTimeSource != NULL) { 212 fTimeSource->Release(); 213 fTimeSource = NULL; 214 } 215 216 fConnected = false; 217 fRunning = false; 218 219 return err; 220 } 221 222 223 status_t 224 BMediaRecorder::Start(bool force) 225 { 226 CALLED(); 227 228 if (fInitErr != B_OK) 229 return fInitErr; 230 231 if (!fConnected) 232 return B_MEDIA_NOT_CONNECTED; 233 234 if (fRunning && !force) 235 return EALREADY; 236 237 if (!fNode) 238 return B_ERROR; 239 240 // start node here 241 status_t err = B_OK; 242 243 if (fNode->Node().kind & B_TIME_SOURCE) 244 err = BMediaRoster::CurrentRoster()->StartTimeSource( 245 fOutputNode, BTimeSource::RealTime()); 246 else 247 err = BMediaRoster::CurrentRoster()->StartNode( 248 fOutputNode, fTimeSource->Now()); 249 250 // then un-mute it 251 if (err == B_OK) { 252 fNode->SetDataEnabled(true); 253 fRunning = true; 254 } else 255 fRunning = false; 256 257 return err; 258 } 259 260 261 status_t 262 BMediaRecorder::Stop(bool force) 263 { 264 CALLED(); 265 266 if (fInitErr != B_OK) 267 return fInitErr; 268 269 if (!fRunning && !force) 270 return EALREADY; 271 272 if (!fNode) 273 return B_ERROR; 274 275 // should have the Node mute the output here 276 fNode->SetDataEnabled(false); 277 278 fRunning = false; 279 280 return BMediaRoster::CurrentRoster()->StopNode(fNode->Node(), 0); 281 } 282 283 284 bool 285 BMediaRecorder::IsRunning() const 286 { 287 CALLED(); 288 289 return fRunning; 290 } 291 292 293 bool 294 BMediaRecorder::IsConnected() const 295 { 296 CALLED(); 297 298 return fConnected; 299 } 300 301 302 const media_output& 303 BMediaRecorder::MediaOutput() const 304 { 305 CALLED(); 306 307 return fOutput; 308 } 309 310 311 const media_input& 312 BMediaRecorder::MediaInput() const 313 { 314 CALLED(); 315 316 return fInput; 317 } 318 319 320 const media_format& 321 BMediaRecorder::Format() const 322 { 323 CALLED(); 324 325 return fOutput.format; 326 } 327 328 329 status_t 330 BMediaRecorder::_Connect(const media_format* format, 331 uint32 flags, const dormant_node_info* dormantNode, 332 const media_node* mediaNode, const media_output* output) 333 { 334 CALLED(); 335 336 status_t err = B_OK; 337 media_format ourFormat; 338 media_node node; 339 MediaNodeReleaser away(node, false); 340 media_output ourOutput; 341 342 // argument checking and set-up 343 if (format != NULL) 344 ourFormat = *format; 345 346 if (fNode == NULL) 347 return B_ERROR; 348 349 if (mediaNode == NULL && output != NULL) 350 return B_MISMATCHED_VALUES; 351 352 if (dormantNode != NULL && (mediaNode != NULL || output != NULL)) 353 return B_MISMATCHED_VALUES; 354 355 if (format == NULL && output != NULL) 356 ourFormat = output->format; 357 358 fNode->SetAcceptedFormat(ourFormat); 359 360 // figure out the node to instantiate 361 if (dormantNode != NULL) { 362 363 err = BMediaRoster::Roster()->InstantiateDormantNode( 364 *dormantNode, &node, B_FLAVOR_IS_GLOBAL); 365 366 away.SetRelease(true); 367 368 } else if (mediaNode != NULL) { 369 node = *mediaNode; 370 } else { 371 switch (ourFormat.type) { 372 373 // switch on format for default 374 case B_MEDIA_RAW_AUDIO: 375 err = BMediaRoster::Roster()->GetAudioInput(&node); 376 away.SetRelease(true); 377 break; 378 379 case B_MEDIA_RAW_VIDEO: 380 case B_MEDIA_ENCODED_VIDEO: 381 err = BMediaRoster::Roster()->GetVideoInput(&node); 382 away.SetRelease(true); 383 break; 384 385 // give up? 386 default: 387 return B_MEDIA_BAD_FORMAT; 388 break; 389 } 390 } 391 392 fOutputNode = node; 393 394 // figure out the output provided 395 396 if (output != NULL) { 397 ourOutput = *output; 398 } else if (err == B_OK) { 399 400 media_output outputs[10]; 401 int32 count = 10; 402 403 err = BMediaRoster::Roster()->GetFreeOutputsFor(node, 404 outputs, count, &count, ourFormat.type); 405 406 if (err != B_OK) 407 return err; 408 409 err = B_MEDIA_BAD_SOURCE; 410 411 for (int i = 0; i < count; i++) { 412 if (format_is_compatible(outputs[i].format, ourFormat)) { 413 ourOutput = outputs[i]; 414 err = B_OK; 415 ourFormat = outputs[i].format; 416 break; 417 } 418 419 } 420 421 } else { 422 return err; 423 } 424 425 if (ourOutput.source == media_source::null) 426 return B_MEDIA_BAD_SOURCE; 427 428 // find our Node's free input 429 media_input ourInput; 430 431 err = fNode->GetInput(&ourInput); 432 if (err != B_OK) 433 return err; 434 435 media_node time_source; 436 if (node.kind & B_TIME_SOURCE) 437 time_source = node; 438 else 439 BMediaRoster::Roster()->GetSystemTimeSource(&time_source); 440 441 // set time source 442 BMediaRoster::Roster()->SetTimeSourceFor( 443 fNode->Node().node, time_source.node); 444 445 fTimeSource = BMediaRoster::CurrentRoster()->MakeTimeSourceFor( 446 fNode->Node()); 447 448 if (err != B_OK) 449 return err; 450 451 // start the recorder node (it's always running) 452 err = BMediaRoster::CurrentRoster()->StartNode( 453 fOutputNode, fTimeSource->Now()); 454 455 if (err != B_OK) 456 return err; 457 458 // perform the connection 459 fOutput = ourOutput; 460 fInput = ourInput; 461 462 err = BMediaRoster::CurrentRoster()->Connect(fOutput.source, 463 fInput.destination, &ourFormat, &fOutput, &fInput, 464 BMediaRoster::B_CONNECT_MUTED); 465 466 if (err != B_OK) 467 return err; 468 469 fConnected = true; 470 away.SetRelease(false); 471 472 return err; 473 } -
new file src/kits/media/MediaRecorderNode.cpp
diff --git a/src/kits/media/MediaRecorderNode.cpp b/src/kits/media/MediaRecorderNode.cpp new file mode 100644 index 0000000..b8c3f97
- + 1 /* 2 * Copyright 1999, Be Incorporated 3 * Copyright 2014, Dario Casalinuovo 4 * All Rights Reserved. 5 * This file may be used under the terms of the Be Sample Code License. 6 */ 7 8 9 #include "MediaRecorderNode.h" 10 11 #include <Buffer.h> 12 #include <scheduler.h> 13 #include <TimedEventQueue.h> 14 #include <TimeSource.h> 15 16 #include "MediaDebug.h" 17 #include "MediaRecorder.h" 18 19 20 BMediaRecorderNode::BMediaRecorderNode(const char* name, 21 BMediaRecorder* recorder, media_type type) 22 : 23 BMediaNode(name), 24 BMediaEventLooper(), 25 BBufferConsumer(type), 26 fRecorder(recorder) 27 { 28 CALLED(); 29 30 fInput.destination.id = 1; 31 fInput.destination.port = ControlPort(); 32 33 fName.SetTo(name); 34 35 BString str(name); 36 str << " Input"; 37 strcpy(fInput.name, str.String()); 38 } 39 40 41 BMediaRecorderNode::~BMediaRecorderNode() 42 { 43 CALLED(); 44 } 45 46 47 BMediaAddOn* 48 BMediaRecorderNode::AddOn(int32* id) const 49 { 50 CALLED(); 51 52 if (id) 53 *id = -1; 54 55 return NULL; 56 } 57 58 59 void 60 BMediaRecorderNode::NodeRegistered() 61 { 62 CALLED(); 63 Run(); 64 } 65 66 67 void 68 BMediaRecorderNode::SetRunMode(run_mode mode) 69 { 70 CALLED(); 71 72 int32 priority; 73 74 if (mode == BMediaNode::B_OFFLINE) 75 priority = B_OFFLINE_PROCESSING; 76 else { 77 switch(ConsumerType()) { 78 case B_MEDIA_RAW_AUDIO: 79 case B_MEDIA_ENCODED_AUDIO: 80 priority = B_AUDIO_RECORDING; 81 break; 82 83 case B_MEDIA_RAW_VIDEO: 84 case B_MEDIA_ENCODED_VIDEO: 85 priority = B_VIDEO_RECORDING; 86 break; 87 88 default: 89 priority = B_DEFAULT_MEDIA_PRIORITY; 90 } 91 } 92 93 SetPriority(suggest_thread_priority(priority)); 94 95 BMediaNode::SetRunMode(mode); 96 } 97 98 99 void 100 BMediaRecorderNode::SetAcceptedFormat(const media_format& format) 101 { 102 CALLED(); 103 104 fOKFormat = format; 105 } 106 107 108 status_t 109 BMediaRecorderNode::GetInput(media_input* outInput) 110 { 111 CALLED(); 112 113 fInput.node = Node(); 114 *outInput = fInput; 115 116 return B_OK; 117 } 118 119 120 void 121 BMediaRecorderNode::SetDataEnabled(bool enabled) 122 { 123 CALLED(); 124 125 int32 tag; 126 127 SetOutputEnabled(fInput.source, 128 fInput.destination, enabled, NULL, &tag); 129 } 130 131 132 void 133 BMediaRecorderNode::HandleEvent(const media_timed_event* event, 134 bigtime_t lateness, bool realTimeEvent) 135 { 136 CALLED(); 137 138 // we ignore them all! 139 } 140 141 142 void 143 BMediaRecorderNode::Start(bigtime_t performanceTime) 144 { 145 CALLED(); 146 147 if (fRecorder->fNotifyHook) 148 (*fRecorder->fNotifyHook)(fRecorder->fBufferCookie, 149 B_WILL_START, performanceTime); 150 151 fRecorder->fRunning = true; 152 } 153 154 155 void 156 BMediaRecorderNode::Stop(bigtime_t performanceTime, bool immediate) 157 { 158 CALLED(); 159 160 if (fRecorder->fNotifyHook) 161 (*fRecorder->fNotifyHook)(fRecorder->fBufferCookie, 162 B_WILL_STOP, performanceTime, immediate); 163 164 fRecorder->fRunning = false; 165 } 166 167 168 void 169 BMediaRecorderNode::Seek(bigtime_t mediaTime, bigtime_t performanceTime) 170 { 171 CALLED(); 172 173 if (fRecorder->fNotifyHook) 174 (*fRecorder->fNotifyHook)(fRecorder->fBufferCookie, 175 B_WILL_SEEK, performanceTime, mediaTime); 176 } 177 178 179 void 180 BMediaRecorderNode::TimeWarp(bigtime_t realTime, bigtime_t performanceTime) 181 { 182 CALLED(); 183 184 // Since buffers will come pre-time-stamped, we only need to look 185 // at them, so we can ignore the time warp as a consumer. 186 if (fRecorder->fNotifyHook) 187 (*fRecorder->fNotifyHook)(fRecorder->fBufferCookie, 188 B_WILL_TIMEWARP, realTime, performanceTime); 189 } 190 191 192 status_t 193 BMediaRecorderNode::HandleMessage(int32 message, 194 const void* data, size_t size) 195 { 196 CALLED(); 197 198 if (BBufferConsumer::HandleMessage(message, data, size) < 0 199 && BMediaEventLooper::HandleMessage(message, data, size) < 0 200 && BMediaNode::HandleMessage(message, data, size) < 0) { 201 HandleBadMessage(message, data, size); 202 return B_ERROR; 203 } 204 return B_OK; 205 } 206 207 208 status_t 209 BMediaRecorderNode::AcceptFormat(const media_destination& dest, 210 media_format* format) 211 { 212 CALLED(); 213 214 if (format_is_compatible(*format, fOKFormat)) 215 return B_OK; 216 217 *format = fOKFormat; 218 219 return B_MEDIA_BAD_FORMAT; 220 } 221 222 223 status_t 224 BMediaRecorderNode::GetNextInput(int32* cookie, media_input* outInput) 225 { 226 CALLED(); 227 228 if (*cookie == 0) { 229 *cookie = -1; 230 *outInput = fInput; 231 return B_OK; 232 } 233 234 return B_BAD_INDEX; 235 } 236 237 238 void 239 BMediaRecorderNode::DisposeInputCookie(int32 cookie) 240 { 241 CALLED(); 242 } 243 244 245 void 246 BMediaRecorderNode::BufferReceived(BBuffer* buffer) 247 { 248 CALLED(); 249 250 fRecorder->BufferReceived(buffer->Data(), buffer->SizeUsed(), 251 *buffer->Header()); 252 253 buffer->Recycle(); 254 } 255 256 257 void 258 BMediaRecorderNode::ProducerDataStatus( 259 const media_destination& forWhom, int32 status, 260 bigtime_t performanceTime) 261 { 262 CALLED(); 263 } 264 265 266 status_t 267 BMediaRecorderNode::GetLatencyFor(const media_destination& forWhom, 268 bigtime_t* outLatency, media_node_id* outTimesource) 269 { 270 CALLED(); 271 272 *outLatency = 0; 273 *outTimesource = TimeSource()->ID(); 274 275 return B_OK; 276 } 277 278 279 status_t 280 BMediaRecorderNode::Connected(const media_source &producer, 281 const media_destination &where, const media_format &withFormat, 282 media_input* outInput) 283 { 284 CALLED(); 285 286 fInput.source = producer; 287 fInput.format = withFormat; 288 *outInput = fInput; 289 290 fRecorder->fConnected = true; 291 fRecorder->fInput = fInput; 292 293 return B_OK; 294 } 295 296 297 void 298 BMediaRecorderNode::Disconnected(const media_source& producer, 299 const media_destination& where) 300 { 301 CALLED(); 302 303 fInput.source = media_source::null; 304 305 fRecorder->fConnected = false; 306 307 fRecorder->fInput.format = fOKFormat; 308 } 309 310 311 status_t 312 BMediaRecorderNode::FormatChanged(const media_source& producer, 313 const media_destination& consumer, int32 tag, 314 const media_format& format) 315 { 316 CALLED(); 317 318 if (!format_is_compatible(format, fOKFormat)) 319 return B_MEDIA_BAD_FORMAT; 320 321 fInput.format = format; 322 323 return B_OK; 324 } -
deleted file src/kits/media/SoundConsumer.cpp
diff --git a/src/kits/media/SoundConsumer.cpp b/src/kits/media/SoundConsumer.cpp deleted file mode 100644 index e2ca10f..0000000
+ - 1 /******************************************************************************2 /3 / File: SoundConsumer.cpp4 /5 / Description: Record sound from some sound-producing Node.6 /7 / Copyright 1998-1999, Be Incorporated, All Rights Reserved8 /9 ******************************************************************************/10 #include "SoundConsumer.h"11 12 #include <new>13 #include <stdio.h>14 15 #include <OS.h>16 #include <scheduler.h>17 #include <Buffer.h>18 #include <TimeSource.h>19 20 #include "AutoDeleter.h"21 22 23 using std::nothrow;24 25 26 namespace BPrivate {27 28 29 // If we don't mind the format changing to another format while30 // running, we can define this to 1. Look for the symbol down in the source.31 #define ACCEPT_ANY_FORMAT_CHANGE 032 33 34 // Compiling with NDEBUG means "release" -- it also turns off assert() and35 // other such debugging aids. By contrast, DEBUG turns on extra debugging36 // in some programs.37 #if !NDEBUG38 #define TRACE_SOUNDCONSUMER39 #endif40 41 // Comment out the FPRINTF part of these lines to reduce verbiage.42 // Enabling MESSAGE will kill performance on slower machines, because it43 // prints for each message received (including each buffer).44 #ifdef TRACE_SOUNDCONSUMER45 #define NODE fprintf46 #define MESSAGE fprintf47 #else48 #define NODE(x...)49 #define MESSAGE(x...)50 #endif51 52 53 // This structure is the body of a request that we use to54 // implement SetHooks().55 struct set_hooks_q {56 port_id reply;57 void * cookie;58 SoundProcessFunc process;59 SoundNotifyFunc notify;60 };61 62 63 // All incoming buffers and Media Kit requests arrive at a64 // media node in the form of messages (which are generally65 // dispatched for you by your superclasses' HandleMessage66 // implementations). Each message has a 'type' which is67 // analagous to a BMessage's 'what' field. We'll define our68 // own private message types for our SoundConsumer and69 // SoundProducer to use. The BeOS reserves a range,70 // 0x60000000 to 0x7fffffff, for us to use.71 enum {72 MSG_QUIT_NOW = 0x60000000L,73 MSG_CHANGE_HOOKS74 };75 76 77 SoundConsumer::SoundConsumer(78 const char * name,79 SoundProcessFunc recordFunc,80 SoundNotifyFunc notifyFunc,81 void * cookie) :82 BMediaNode(name ? name : "SoundConsumer"),83 BBufferConsumer(B_MEDIA_RAW_AUDIO)84 {85 NODE(stderr, "SoundConsumer::SoundConsumer(%p, %p, %p, %p)\n", name,86 recordFunc, notifyFunc, cookie);87 88 if (!name) name = "SoundConsumer";89 90 // Set up the hook functions.91 m_recordHook = recordFunc;92 m_notifyHook = notifyFunc;93 m_cookie = cookie;94 95 // Create the port that we publish as our Control Port.96 char pname[32];97 sprintf(pname, "%.20s Control", name);98 m_port = create_port(10, pname);99 100 // Initialize our single media_input. Make sure it knows101 // the Control Port associated with the destination, and102 // the index of the destination (since we only have one,103 // that's trivial).104 m_input.destination.port = m_port;105 m_input.destination.id = 1;106 sprintf(m_input.name, "%.20s Input", name);107 108 // Set up the timing variables that we'll be using.109 m_trTimeout = 0LL;110 m_tpSeekAt = 0;111 m_tmSeekTo = 0;112 m_delta = 0;113 m_seeking = false;114 115 // Create, and run, the thread that we use to service116 // the Control Port.117 sprintf(pname, "%.20s Service", name);118 m_thread = spawn_thread(ThreadEntry, pname, 110, this);119 resume_thread(m_thread);120 }121 122 123 SoundConsumer::~SoundConsumer()124 {125 NODE(stderr, "SoundConsumer::~SoundConsumer()\n");126 // Signal to our thread that it's time to go home.127 write_port(m_port, MSG_QUIT_NOW, 0, 0);128 status_t s;129 while (wait_for_thread(m_thread, &s) == B_INTERRUPTED)130 NODE(stderr, "wait_for_thread() B_INTERRUPTED\n");131 delete_port(m_port);132 }133 134 135 status_t136 SoundConsumer::SetHooks(SoundProcessFunc recordFunc, SoundNotifyFunc notifyFunc,137 void* cookie)138 {139 // SetHooks needs to be synchronized with the service thread, else we may140 // call the wrong hook function with the wrong cookie, which would be bad.141 // Rather than do locking, which is expensive, we streamline the process142 // by sending our service thread a request to change the hooks, and waiting143 // for the acknowledge.144 status_t err = B_OK;145 set_hooks_q cmd;146 cmd.process = recordFunc;147 cmd.notify = notifyFunc;148 cmd.cookie = cookie;149 // If we're not in the service thread, we need to round-trip a message.150 if (find_thread(0) != m_thread) {151 cmd.reply = create_port(1, "SetHooks reply");152 // Send the private message to our service thread.153 err = write_port(ControlPort(), MSG_CHANGE_HOOKS, &cmd, sizeof(cmd));154 if (err >= 0) {155 int32 code;156 // Wait for acknowledge from the service thread.157 err = read_port_etc(cmd.reply, &code, 0, 0, B_TIMEOUT, 6000000LL);158 if (err > 0) err = 0;159 NODE(stderr, "SoundConsumer::SetHooks read reply: %#" B_PRIx32160 "\n", err);161 }162 // Clean up.163 delete_port(cmd.reply);164 } else {165 // Within the service thread, it's OK to just go ahead and do the166 // change.167 DoHookChange(&cmd);168 }169 return err;170 }171 172 173 // #pragma mark -BMediaNode-derived methods174 175 176 port_id177 SoundConsumer::ControlPort() const178 {179 return m_port;180 }181 182 183 BMediaAddOn*184 SoundConsumer::AddOn(int32 * internal_id) const185 {186 // This object is instantiated inside an application.187 // Therefore, it has no add-on.188 if (internal_id) *internal_id = 0;189 return 0;190 }191 192 193 void194 SoundConsumer::Start(bigtime_t performance_time)195 {196 // Since we are a consumer and just blindly accept buffers that are197 // thrown at us, we don't need to do anything special in Start()/Stop().198 // If we were (also) a producer, we'd have to be more elaborate.199 // The only thing we do is immediately perform any queued Seek based on200 // the start time, which is the right thing to do (seeing as we were201 // Seek()-ed when we weren't started).202 if (m_seeking) {203 m_delta = performance_time - m_tmSeekTo;204 m_seeking = false;205 }206 if (m_notifyHook)207 (*m_notifyHook)(m_cookie, B_WILL_START, performance_time);208 else209 Notify(B_WILL_START, performance_time);210 }211 212 213 void214 SoundConsumer::Stop(bigtime_t performance_time, bool immediate)215 {216 // Since we are a consumer and just blindly accept buffers that are217 // thrown at us, we don't need to do anything special in Start()/Stop().218 // If we were (also) a producer, we'd have to be more elaborate.219 // Note that this is not strictly in conformance with The Rules,220 // but since this is not an add-on Node for use with any application;221 // it's a Node over which we have complete control, we can live with222 // treating buffers received before the start time or after the stop223 // time as any other buffer.224 if (m_notifyHook)225 (*m_notifyHook)(m_cookie, B_WILL_STOP, performance_time, immediate);226 else227 Notify(B_WILL_STOP, performance_time, immediate);228 }229 230 231 void232 SoundConsumer::Seek(bigtime_t media_time, bigtime_t performance_time)233 {234 // Seek() on a consumer just serves to offset the time stamp235 // of received buffers passed to our Record hook function.236 // In the hook function, you may wish to save those time stamps237 // to disk or otherwise store them. You may also want to238 // synchronize this node's media time with an upstream239 // producer's media time to make this offset meaningful.240 if (m_notifyHook)241 (*m_notifyHook)(m_cookie, B_WILL_SEEK, performance_time, media_time);242 else243 Notify(B_WILL_SEEK, performance_time, media_time);244 245 m_tpSeekAt = performance_time;246 m_tmSeekTo = media_time;247 m_seeking = true;248 }249 250 251 void252 SoundConsumer::SetRunMode(run_mode mode)253 {254 if (mode == BMediaNode::B_OFFLINE) {255 // BMediaNode::B_OFFLINE means we don't need to run in256 // real time. So, we shouldn't run as a real time257 // thread.258 int32 new_prio = suggest_thread_priority(B_OFFLINE_PROCESSING);259 set_thread_priority(m_thread, new_prio);260 } else {261 // We're running in real time, so we'd better have262 // a big enough thread priority to handle it!263 // Here's where those magic scheduler values264 // come from:265 //266 // * In the worst case, we process one buffer per267 // reschedule (we get rescheduled when we go to268 // look for a message on our Control Port), so269 // in order to keep up with the incoming buffers,270 // the duration of one buffer becomes our271 // scheduling period. If we don't know anything272 // about the buffers, we pick a reasonable273 // default.274 // * We're a simple consumer, so we don't have to275 // be too picky about the jitter. Half a period276 // of jitter means that we'd have to get two277 // consecutive worst-case reschedules before278 // we'd fall behind.279 // * The amount of time we spend processing is280 // our ProcessingLatency().281 bigtime_t period = 10000;282 if (buffer_duration(m_input.format.u.raw_audio) > 0)283 period = buffer_duration(m_input.format.u.raw_audio);284 285 // assuming we're running for 500 us or less per buffer286 int32 new_prio = suggest_thread_priority(B_AUDIO_RECORDING,287 period, period / 2, ProcessingLatency());288 set_thread_priority(m_thread, new_prio);289 }290 }291 292 293 void294 SoundConsumer::TimeWarp(bigtime_t at_real_time, bigtime_t to_performance_time)295 {296 // Since buffers will come pre-time-stamped, we only need to look297 // at them, so we can ignore the time warp as a consumer.298 if (m_notifyHook) {299 (*m_notifyHook)(m_cookie, B_WILL_TIMEWARP, at_real_time,300 to_performance_time);301 } else302 Notify(B_WILL_TIMEWARP, at_real_time, to_performance_time);303 }304 305 306 void307 SoundConsumer::Preroll()308 {309 // There is nothing for us to do in Preroll()310 }311 312 313 void314 SoundConsumer::SetTimeSource(BTimeSource* /* time_source */)315 {316 // We don't need to do anything special to take note of the317 // fact that the time source changed, because we get our timing318 // information from the buffers we receive.319 }320 321 322 status_t323 SoundConsumer::HandleMessage(int32 message, const void* data, size_t size)324 {325 // Check with each of our superclasses to see if they326 // understand the message. If none of them do, call327 // BMediaNode::HandleBadMessage().328 if (BBufferConsumer::HandleMessage(message, data, size) < 0329 && BMediaNode::HandleMessage(message, data, size) < 0) {330 HandleBadMessage(message, data, size);331 return B_ERROR;332 }333 return B_OK;334 }335 336 337 // #pragma mark - BBufferConsumer-derived methods338 339 340 status_t341 SoundConsumer::AcceptFormat(const media_destination& dest, media_format* format)342 {343 // We only accept formats aimed at our single input.344 if (dest != m_input.destination)345 return B_MEDIA_BAD_DESTINATION;346 347 if (format->type <= 0) {348 // If no format is specified, we say we want raw audio.349 format->type = B_MEDIA_RAW_AUDIO;350 format->u.raw_audio = media_raw_audio_format::wildcard;351 } else if (format->type != B_MEDIA_RAW_AUDIO) {352 // If a non-raw-audio format is specified, we tell the world what353 // we want, and that the specified format was unacceptable to us.354 format->type = B_MEDIA_RAW_AUDIO;355 format->u.raw_audio = media_raw_audio_format::wildcard;356 return B_MEDIA_BAD_FORMAT;357 }358 #if !ACCEPT_ANY_FORMAT_CHANGE359 // If we're already connected, and this format doesn't go with the360 // format in effect, we dont' accept this new format.361 if (!format_is_compatible(*format, m_input.format)) {362 *format = m_input.format;363 return B_MEDIA_BAD_FORMAT;364 }365 #endif366 // I guess we're OK by now, because we have no particular needs as367 // far as frame rate, sample format, etc go.368 return B_OK;369 }370 371 372 status_t373 SoundConsumer::GetNextInput(int32* cookie, media_input* out_input)374 {375 NODE(stderr, "SoundConsumer: GetNextInput()\n");376 // The "next" is kind of misleading, since it's also used for377 // getting the first (and only) input.378 if (*cookie == 0) {379 if (m_input.source == media_source::null) {380 // If there's no current connection, make sure we return a381 // reasonable format telling the world we accept any raw audio.382 m_input.format.type = B_MEDIA_RAW_AUDIO;383 m_input.format.u.raw_audio = media_raw_audio_format::wildcard;384 m_input.node = Node();385 m_input.destination.port = ControlPort();386 m_input.destination.id = 1;387 }388 *out_input = m_input;389 *cookie = 1;390 return B_OK;391 }392 // There's only one input.393 return B_BAD_INDEX;394 }395 396 397 void398 SoundConsumer::DisposeInputCookie(int32 /* cookie */)399 {400 // We didn't allocate any memory or set any state in GetNextInput()401 // so this function is a no-op.402 }403 404 405 void406 SoundConsumer::BufferReceived(BBuffer* buffer)407 {408 NODE(stderr, "SoundConsumer::BufferReceived()\n");409 // Whee, a buffer! Update the seek info, if necessary.410 if (m_seeking && buffer->Header()->start_time >= m_tpSeekAt) {411 m_delta = m_tpSeekAt - m_tmSeekTo;412 m_seeking = false;413 }414 // If there is a record hook, let the interested party have at it!415 if (m_recordHook) {416 (*m_recordHook)(m_cookie, buffer->Header()->start_time-m_delta,417 buffer->Data(), buffer->Header()->size_used,418 m_input.format.u.raw_audio);419 } else {420 Record(buffer->Header()->start_time-m_delta, buffer->Data(),421 buffer->Header()->size_used, m_input.format.u.raw_audio);422 }423 // Buffers should ALWAYS be recycled, else whomever is producing them424 // will starve.425 buffer->Recycle();426 }427 428 429 void430 SoundConsumer::ProducerDataStatus(const media_destination& for_whom,431 int32 status, bigtime_t at_media_time)432 {433 if (for_whom != m_input.destination)434 return;435 436 // Tell whomever is interested that the upstream producer will or won't437 // send more data in the immediate future.438 if (m_notifyHook) {439 (*m_notifyHook)(m_cookie, B_PRODUCER_DATA_STATUS, status,440 at_media_time);441 } else442 Notify(B_PRODUCER_DATA_STATUS, status, at_media_time);443 }444 445 446 status_t447 SoundConsumer::GetLatencyFor(const media_destination& for_whom,448 bigtime_t* out_latency, media_node_id* out_timesource)449 {450 // We only accept requests for the one-and-only input of our Node.451 if (for_whom != m_input.destination)452 return B_MEDIA_BAD_DESTINATION;453 454 // Tell the world about our latency information (overridable by user).455 *out_latency = TotalLatency();456 *out_timesource = TimeSource()->Node().node;457 return B_OK;458 }459 460 461 status_t462 SoundConsumer::Connected(const media_source& producer,463 const media_destination& where, const media_format& with_format,464 media_input* out_input)465 {466 NODE(stderr, "SoundConsumer::Connected()\n");467 // Only accept connection requests when we're not already connected.468 if (m_input.source != media_source::null)469 return B_MEDIA_BAD_DESTINATION;470 471 // Only accept connection requests on the one-and-only available input.472 if (where != m_input.destination)473 return B_MEDIA_BAD_DESTINATION;474 475 // Other than that, we accept pretty much anything. The format has been476 // pre-cleared through AcceptFormat(), and we accept any format anyway.477 m_input.source = producer;478 m_input.format = with_format;479 // Tell whomever is interested that there's now a connection.480 if (m_notifyHook)481 (*m_notifyHook)(m_cookie, B_CONNECTED, m_input.name);482 else483 Notify(B_CONNECTED, m_input.name);484 485 // This is the most important line -- return our connection information486 // to the world so it can use it!487 *out_input = m_input;488 return B_OK;489 }490 491 492 void493 SoundConsumer::Disconnected(const media_source& producer,494 const media_destination& where)495 {496 // We can't disconnect something which isn't us.497 if (where != m_input.destination)498 return;499 // We can't disconnect from someone who isn't connected to us.500 if (producer != m_input.source)501 return;502 // Tell the interested party that it's time to leave.503 if (m_notifyHook)504 (*m_notifyHook)(m_cookie, B_DISCONNECTED);505 else506 Notify(B_DISCONNECTED);507 // Mark ourselves as not-connected.508 m_input.source = media_source::null;509 }510 511 512 status_t513 SoundConsumer::FormatChanged(const media_source& producer,514 const media_destination& consumer, int32 from_change_count,515 const media_format& format)516 {517 NODE(stderr, "SoundConsumer::Connected()\n");518 // The up-stream guy feels like changing the format. If we can accept519 // arbitrary format changes, we just say "OK". If, however, we're recording520 // to a file, that's not such a good idea; we only accept format changes521 // that are compatible with the format we're already using. You set this522 // behaviour at compile time by defining ACCEPT_ANY_FORMAT_CHANGE to 1 or523 // 0.524 status_t err = B_OK;525 #if ACCEPT_ANY_FORMAT_CHANGE526 media_format fmt(format);527 err = AcceptFormat(m_input.destination, &fmt);528 #else529 if (m_input.source != media_source::null) {530 err = format_is_compatible(format, m_input.format) ? B_OK531 : B_MEDIA_BAD_FORMAT;532 }533 #endif534 if (err >= B_OK) {535 m_input.format = format;536 if (m_notifyHook) {537 (*m_notifyHook)(m_cookie, B_FORMAT_CHANGED,538 &m_input.format.u.raw_audio);539 } else540 Notify(B_FORMAT_CHANGED, &m_input.format.u.raw_audio);541 }542 return err;543 }544 545 546 void547 SoundConsumer::DoHookChange(void* msg)548 {549 // Tell the old guy we're changing the hooks ...550 if (m_notifyHook)551 (*m_notifyHook)(m_cookie, B_HOOKS_CHANGED);552 else553 Notify(B_HOOKS_CHANGED);554 555 // ... and then do it.556 set_hooks_q * ptr = (set_hooks_q *)msg;557 m_recordHook = ptr->process;558 m_notifyHook = ptr->notify;559 m_cookie = ptr->cookie;560 }561 562 563 status_t564 SoundConsumer::ThreadEntry(void* cookie)565 {566 SoundConsumer* consumer = (SoundConsumer*)cookie;567 consumer->ServiceThread();568 return 0;569 }570 571 572 void573 SoundConsumer::ServiceThread()574 {575 // The Big Bad ServiceThread receives messages aimed at this576 // Node and dispatches them (typically to HandleMessage()).577 // If we were a Producer, we might have to do finicky timing and578 // queued Start()/Stop() processing in here. But we ain't.579 580 // A media kit message will never be bigger than B_MEDIA_MESSAGE_SIZE.581 // Avoid wasing stack space by dynamically allocating at start.582 char* msg = new (nothrow) char[B_MEDIA_MESSAGE_SIZE];583 if (msg == NULL)584 return;585 // Make sure we clean up this data when we exit the function.586 ArrayDeleter<char> _(msg);587 int bad = 0;588 while (true) {589 // Call read_port_etc() with a timeout derived from a virtual function,590 // to allow clients to do special processing if necessary.591 bigtime_t timeout = Timeout();592 int32 code = 0;593 status_t err = read_port_etc(m_port, &code, msg, B_MEDIA_MESSAGE_SIZE,594 B_TIMEOUT, timeout);595 MESSAGE(stderr, "SoundConsumer::ServiceThread() port %" B_PRId32596 " message %#" B_PRIx32 "\n", m_port, code);597 // If we received a message, err will be the size of the message598 // (including 0).599 if (err >= B_OK) {600 // Real messages reset the timeout time.601 m_trTimeout = 0;602 bad = 0;603 if (code == MSG_QUIT_NOW) {604 // Check for our private stop message.605 if (m_notifyHook)606 (*m_notifyHook)(m_cookie, B_NODE_DIES, 0);607 else608 Notify(B_NODE_DIES, 0);609 break;610 } else if (code == MSG_CHANGE_HOOKS) {611 // Else check for our private change-hooks message.612 DoHookChange(msg);613 // Write acknowledge to waiting thread.614 write_port(((set_hooks_q *)msg)->reply, 0, 0, 0);615 } else {616 // Else it has to be a regular media kit message;617 // go ahead and dispatch it.618 HandleMessage(code, msg, err);619 }620 } else if (err == B_TIMED_OUT) {621 // Timing out means that there was no buffer. Tell the interested622 // party.623 if (m_notifyHook)624 (*m_notifyHook)(m_cookie, B_OP_TIMED_OUT, timeout);625 else626 Notify(B_OP_TIMED_OUT, timeout);627 } else {628 // Other errors are bad.629 MESSAGE(stderr, "SoundConsumer: error %#" B_PRIx32 "\n", err);630 bad++;631 // If we receive three bad reads with no good messages inbetween,632 // things are probably not going to improve (like the port633 // disappeared or something) so we call it a day.634 if (bad > 3) {635 if (m_notifyHook)636 (*m_notifyHook)(m_cookie, B_NODE_DIES, bad, err, code, msg);637 else638 Notify(B_NODE_DIES, bad, err, code, msg);639 break;640 }641 }642 }643 }644 645 646 bigtime_t647 SoundConsumer::Timeout()648 {649 // Timeout() is called for each call to read_port_etc() in the service650 // thread to figure out a reasonable time-out value. The default behaviour651 // we've picked is to exponentially back off from one second and upwards.652 // While it's true that 44 back-offs will run us out of precision in a653 // bigtime_t, the time to actually reach 44 consecutive back-offs is longer654 // than the expected market longevity of just about any piece of real655 // estate. Is that the sound of an impending year-fifteen-million software656 // problem? :-)657 m_trTimeout = (m_trTimeout < 1000000) ? 1000000 : m_trTimeout*2;658 return m_trTimeout;659 }660 661 662 bigtime_t663 SoundConsumer::ProcessingLatency()664 {665 // We're saying it takes us 500 us to process each buffer. If all we do is666 // copy the data, it probably takes much less than that, but it doesn't667 // hurt to be slightly conservative.668 return 500LL;669 }670 671 672 bigtime_t673 SoundConsumer::TotalLatency()674 {675 // Had we been a producer that passes buffers on, we'd have to676 // include downstream latency in this value. But we are not.677 return ProcessingLatency();678 }679 680 681 // #pragma mark -682 683 684 void685 SoundConsumer::Record(bigtime_t /*time*/, const void* /*data*/,686 size_t /*size*/, const media_raw_audio_format& /*format*/)687 {688 // If there is no record hook installed, we instead call this function689 // for received buffers.690 }691 692 693 void694 SoundConsumer::Notify(int32 /*cause*/, ...)695 {696 // If there is no notification hook installed, we instead call this697 // function for giving notification of various events.698 }699 700 }