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