1 #ifndef AUDIOGRAPHER_TMP_FILE_RT_H 2 #define AUDIOGRAPHER_TMP_FILE_RT_H 3 4 #include <cstdio> 5 #include <string> 6 7 #include <glib.h> 8 9 #include "pbd/gstdio_compat.h" 10 #include "pbd/pthread_utils.h" 11 #include "pbd/ringbuffer.h" 12 13 #include "audiographer/flag_debuggable.h" 14 #include "audiographer/sink.h" 15 #include "sndfile_writer.h" 16 #include "sndfile_reader.h" 17 18 #include "tmp_file.h" 19 20 namespace AudioGrapher 21 { 22 23 static const samplecnt_t rb_chunksize = 8192; // samples 24 25 /** A temporary file deleted after this class is destructed 26 * with realtime safe background thread writer. 27 */ 28 template<typename T = DefaultSampleType> 29 class TmpFileRt 30 : public TmpFile<T> 31 { 32 public: 33 34 /// \a filename_template must match the requirements for mkstemp, i.e. end in "XXXXXX" TmpFileRt(char * filename_template,int format,ChannelCount channels,samplecnt_t samplerate)35 TmpFileRt (char * filename_template, int format, ChannelCount channels, samplecnt_t samplerate) 36 : SndfileHandle (g_mkstemp(filename_template), true, SndfileBase::ReadWrite, format, channels, samplerate) 37 , filename (filename_template) 38 , _chunksize (rb_chunksize * channels) 39 , _rb (std::max (_chunksize * 16, 5 * samplerate * channels)) 40 { 41 init (); 42 } 43 44 using SndfileHandle::operator=; 45 ~TmpFileRt()46 ~TmpFileRt() 47 { 48 end_write (); 49 /* explicitly close first, some OS (yes I'm looking at you windows) 50 * cannot delete files that are still open 51 */ 52 if (!filename.empty()) { 53 SndfileBase::close(); 54 std::remove(filename.c_str()); 55 } 56 pthread_mutex_destroy (&_disk_thread_lock); 57 pthread_cond_destroy (&_data_ready); 58 } 59 60 /// Writes data to file process(ProcessContext<T> const & c)61 void process (ProcessContext<T> const & c) 62 { 63 SndfileWriter<T>::check_flags (*this, c); 64 65 if (SndfileWriter<T>::throw_level (ThrowStrict) && c.channels() != SndfileHandle::channels()) { 66 throw Exception (*this, boost::str (boost::format 67 ("Wrong number of channels given to process(), %1% instead of %2%") 68 % c.channels() % SndfileHandle::channels())); 69 } 70 71 if (SndfileWriter<T>::throw_level (ThrowProcess) && _rb.write_space() < c.samples()) { 72 throw Exception (*this, boost::str (boost::format 73 ("Could not write data to ringbuffer/output file (%1%)") 74 % SndfileHandle::strError())); 75 } 76 77 _rb.write (c.data(), c.samples()); 78 79 if (c.has_flag(ProcessContext<T>::EndOfInput)) { 80 _capture = false; 81 SndfileWriter<T>::FileWritten (filename); 82 } 83 84 if (pthread_mutex_trylock (&_disk_thread_lock) == 0) { 85 pthread_cond_signal (&_data_ready); 86 pthread_mutex_unlock (&_disk_thread_lock); 87 } 88 } 89 90 using Sink<T>::process; 91 disk_thread()92 void disk_thread () 93 { 94 T *framebuf = (T*) malloc (_chunksize * sizeof (T)); 95 96 pthread_mutex_lock (&_disk_thread_lock); 97 98 while (_capture) { 99 if ((samplecnt_t)_rb.read_space () >= _chunksize) { 100 _rb.read (framebuf, _chunksize); 101 samplecnt_t const written = SndfileBase::write (framebuf, _chunksize); 102 assert (written == _chunksize); 103 SndfileWriter<T>::samples_written += written; 104 } 105 if (!_capture) { 106 break; 107 } 108 pthread_cond_wait (&_data_ready, &_disk_thread_lock); 109 } 110 111 // flush ringbuffer 112 while (_rb.read_space () > 0) { 113 size_t remain = std::min ((samplecnt_t)_rb.read_space (), _chunksize); 114 _rb.read (framebuf, remain); 115 samplecnt_t const written = SndfileBase::write (framebuf, remain); 116 SndfileWriter<T>::samples_written += written; 117 } 118 119 SndfileWriter<T>::writeSync(); 120 pthread_mutex_unlock (&_disk_thread_lock); 121 free (framebuf); 122 TmpFile<T>::FileFlushed (); 123 } 124 125 protected: 126 std::string filename; 127 128 bool _capture; 129 samplecnt_t _chunksize; 130 PBD::RingBuffer<T> _rb; 131 132 pthread_mutex_t _disk_thread_lock; 133 pthread_cond_t _data_ready; 134 pthread_t _thread_id; 135 _disk_thread(void * arg)136 static void * _disk_thread (void *arg) 137 { 138 TmpFileRt *d = static_cast<TmpFileRt *>(arg); 139 pthread_set_name ("ExportDiskIO"); 140 d->disk_thread (); 141 pthread_exit (0); 142 return 0; 143 } 144 end_write()145 void end_write () { 146 pthread_mutex_lock (&_disk_thread_lock); 147 _capture = false; 148 pthread_cond_signal (&_data_ready); 149 pthread_mutex_unlock (&_disk_thread_lock); 150 pthread_join (_thread_id, NULL); 151 } 152 init()153 void init() 154 { 155 SndfileWriter<T>::samples_written = 0; 156 _capture = true; 157 SndfileWriter<T>::add_supported_flag (ProcessContext<T>::EndOfInput); 158 pthread_mutex_init (&_disk_thread_lock, 0); 159 pthread_cond_init (&_data_ready, 0); 160 161 if (pthread_create (&_thread_id, NULL, _disk_thread, this)) { 162 _capture = false; 163 if (SndfileWriter<T>::throw_level (ThrowStrict)) { 164 throw Exception (*this, "Cannot create export disk writer"); 165 } 166 } 167 } 168 169 private: TmpFileRt(TmpFileRt const & other)170 TmpFileRt (TmpFileRt const & other) : SndfileHandle (other) {} 171 }; 172 173 } // namespace 174 175 #endif // AUDIOGRAPHER_TMP_FILE_RT_H 176