capture_client.pp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. {
  2. Copyright (C) 2001 Paul Davis
  3. Copyright (C) 2003 Jack O'Quin
  4. This program is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program; if not, write to the Free Software
  14. Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  15. * 2002/08/23 - modify for libsndfile 1.0.0 <[email protected]>
  16. * 2003/05/26 - use ringbuffers - joq
  17. }
  18. program capture_client;
  19. {$MODE ObjFpc}{$H+}
  20. uses
  21. {$ifdef UNIX}
  22. CThreads,
  23. {$endif}
  24. PThreads, CTypes, SysUtils, SndFile, Jack, JackRingBuffer;
  25. const
  26. EIO = 5;
  27. EPIPE = 32;
  28. type
  29. Pjack_thread_info_t = ^jack_thread_info_t;
  30. jack_thread_info_t = record
  31. thread_id: TThreadId;
  32. sf: PSNDFILE;
  33. duration: jack_nframes_t;
  34. rb_size: jack_nframes_t;
  35. client: Pjack_client_t;
  36. channels: cuint;
  37. bitdepth: cint;
  38. path: string;
  39. can_capture: Boolean; {volatile;}
  40. can_process: Boolean; {volatile;}
  41. status: cint; {volatile;}
  42. end;
  43. var
  44. { JACK data }
  45. nports: cuint;
  46. ports: PPjack_port_t;
  47. _in: PPjack_default_audio_sample_t;
  48. nframes: jack_nframes_t;
  49. const
  50. sample_size = SizeOf(jack_default_audio_sample_t);
  51. { Synchronization between process thread and disk thread. }
  52. DEFAULT_RB_SIZE = 16384; { ringbuffer size in frames }
  53. var
  54. rb: Pjack_ringbuffer_t;
  55. disk_thread_lock: TPthreadMutex;
  56. data_ready: TCondVar;
  57. overruns: clong = 0;
  58. total_captured: jack_nframes_t = 0;
  59. function disk_thread (arg: Pointer): PtrInt;
  60. label
  61. done;
  62. var
  63. info: Pjack_thread_info_t;
  64. samples_per_frame: jack_nframes_t;
  65. bytes_per_frame: csize_t;
  66. framebuf: Pointer;
  67. errstr: array [0..255] of Char;
  68. begin
  69. info := arg;
  70. samples_per_frame := info^.channels;
  71. bytes_per_frame := samples_per_frame * sample_size;
  72. framebuf := GetMem (bytes_per_frame);
  73. pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, nil);
  74. pthread_mutex_lock (@disk_thread_lock);
  75. info^.status := 0;
  76. while True do
  77. begin
  78. { Write the data one frame at a time. This is
  79. inefficient, but makes things simpler. }
  80. while info^.can_capture and
  81. (jack_ringbuffer_read_space (rb) >= bytes_per_frame) do
  82. begin
  83. jack_ringbuffer_read (rb, framebuf, bytes_per_frame);
  84. if sf_writef_float (info^.sf, framebuf, 1) <> 1 then
  85. begin
  86. sf_error_str (nil, @errstr, sizeof (errstr) - 1);
  87. Writeln (StdErr,
  88. 'cannot write sndfile (', errstr, ')');
  89. info^.status := EIO; { write failed }
  90. goto done;
  91. end;
  92. Inc(total_captured);
  93. if total_captured >= info^.duration then
  94. begin
  95. Writeln ('disk thread finished');
  96. goto done;
  97. end;
  98. end;
  99. { wait until process() signals more data }
  100. pthread_cond_wait (@data_ready, @disk_thread_lock);
  101. end;
  102. done:
  103. pthread_mutex_unlock (@disk_thread_lock);
  104. FreeMem (framebuf);
  105. Result := 0;
  106. end;
  107. function process (nframes: jack_nframes_t; arg: Pointer): cint; cdecl;
  108. var
  109. chn: cint;
  110. i: csize_t;
  111. info: Pjack_thread_info_t;
  112. begin
  113. info := arg;
  114. { Do nothing until we're ready to begin. }
  115. if (not info^.can_process) or (not info^.can_capture) then
  116. Exit(0);
  117. for chn := 0 to nports - 1 do
  118. _in[chn] := jack_port_get_buffer (ports[chn], nframes);
  119. { Sndfile requires interleaved data. It is simpler here to
  120. just queue interleaved samples to a single ringbuffer. }
  121. for i := 0 to nframes - 1 do
  122. for chn := 0 to nports - 1 do
  123. if jack_ringbuffer_write (rb, Pointer(_in[chn]+i), sample_size) < sample_size then
  124. Inc(overruns);
  125. { Tell the disk thread there is work to do. If it is already
  126. running, the lock will not be available. We can't wait
  127. here in the process() thread, but we don't need to signal
  128. in that case, because the disk thread will read all the
  129. data queued before waiting again. }
  130. if pthread_mutex_trylock (@disk_thread_lock) = 0 then
  131. begin
  132. pthread_cond_signal (@data_ready);
  133. pthread_mutex_unlock (@disk_thread_lock);
  134. end;
  135. Result := 0;
  136. end;
  137. procedure jack_shutdown (arg: Pointer); cdecl;
  138. begin
  139. Writeln (StdErr, 'JACK shutdown');
  140. // Halt (0);
  141. //abort();
  142. end;
  143. procedure setup_disk_thread (info: Pjack_thread_info_t);
  144. var
  145. sf_info: TSF_INFO;
  146. short_mask: cint;
  147. errstr: array [0..255] of Char;
  148. begin
  149. sf_info.samplerate := jack_get_sample_rate (info^.client);
  150. sf_info.channels := info^.channels;
  151. case info^.bitdepth of
  152. 8:
  153. short_mask := SF_FORMAT_PCM_U8;
  154. 16:
  155. short_mask := SF_FORMAT_PCM_16;
  156. 24:
  157. short_mask := SF_FORMAT_PCM_24;
  158. 32:
  159. short_mask := SF_FORMAT_PCM_32;
  160. else
  161. short_mask := SF_FORMAT_PCM_16;
  162. end;
  163. sf_info.format := SF_FORMAT_WAV or short_mask;
  164. info^.sf := sf_open (PChar(info^.path), SFM_WRITE, @sf_info);
  165. if info^.sf = nil then
  166. begin
  167. sf_error_str (nil, @errstr, sizeof (errstr) - 1);
  168. Writeln (StdErr, 'cannot open sndfile ', info^.path, ' for output (', errstr, ')');
  169. jack_client_close (info^.client);
  170. Halt (1);
  171. end;
  172. if info^.duration = 0 then
  173. info^.duration := JACK_MAX_FRAMES
  174. else
  175. info^.duration *= sf_info.samplerate;
  176. info^.can_capture := False;
  177. info^.thread_id := BeginThread(@disk_thread, info);
  178. end;
  179. procedure run_disk_thread (info: Pjack_thread_info_t);
  180. begin
  181. info^.can_capture := True;
  182. WaitForThreadTerminate(info^.thread_id, -1);
  183. sf_close (info^.sf);
  184. if overruns > 0 then
  185. begin
  186. Writeln (StdErr,
  187. 'jackrec failed with ', overruns, ' overruns.');
  188. Writeln (StdErr, ' try a bigger buffer than -B ', info^.rb_size, '.');
  189. info^.status := EPIPE;
  190. end;
  191. end;
  192. procedure setup_ports (sources: cint; first_source_param: Integer; info: Pjack_thread_info_t);
  193. var
  194. i: cuint;
  195. in_size: csize_t;
  196. name, source_name: string;
  197. begin
  198. { Allocate data structures that depend on the number of ports. }
  199. nports := sources;
  200. ports := GetMem (SizeOf (Pjack_port_t) * nports);
  201. in_size := nports * SizeOf (Pjack_default_audio_sample_t);
  202. _in := GetMem (in_size);
  203. rb := jack_ringbuffer_create (nports * sample_size * info^.rb_size);
  204. { When JACK is running realtime, jack_activate() will have
  205. called mlockall() to lock our pages into memory. But, we
  206. still need to touch any newly allocated pages before
  207. process() starts using them. Otherwise, a page fault could
  208. create a delay that would force JACK to shut us down. }
  209. FillChar(_in^, in_size, 0);
  210. FillChar(rb^.buf^, rb^.size, 0);
  211. for i := 0 to nports - 1 do
  212. begin
  213. WriteStr (name, 'input', i+1);
  214. ports[i] := jack_port_register (info^.client, PChar(name), JACK_DEFAULT_AUDIO_TYPE, Ord(JackPortIsInput), 0);
  215. if ports[i] = nil then
  216. begin
  217. Writeln (StdErr, 'cannot register input port "', name, '"!');
  218. jack_client_close (info^.client);
  219. Halt (1);
  220. end;
  221. end;
  222. for i := 0 to nports - 1 do
  223. begin
  224. source_name := ParamStr(first_source_param + i);
  225. if jack_connect (info^.client, PChar(source_name), jack_port_name (ports[i])) <> 0 then
  226. begin
  227. Writeln (StdErr, 'cannot connect input port ', jack_port_name (ports[i]), ' to ', source_name);
  228. jack_client_close (info^.client);
  229. Halt (1);
  230. end;
  231. end;
  232. info^.can_process := True; { process() can start, now }
  233. end;
  234. var
  235. client: Pjack_client_t;
  236. thread_info: jack_thread_info_t;
  237. longopt_index: cint = 0;
  238. show_usage: Boolean = False;
  239. begin
  240. pthread_mutex_init(@disk_thread_lock, nil);
  241. pthread_cond_init(@data_ready, nil);
  242. FillChar (thread_info, SizeOf (thread_info), 0);
  243. thread_info.rb_size := DEFAULT_RB_SIZE;
  244. while longopt_index < ParamCount do
  245. begin
  246. Inc(longopt_index);
  247. case ParamStr(longopt_index) of
  248. '-h', '--help':
  249. show_usage := True;
  250. '-d', '--duration':
  251. begin
  252. Inc(longopt_index);
  253. if longopt_index > ParamCount then
  254. begin
  255. Writeln (StdErr, 'error');
  256. show_usage := True;
  257. break;
  258. end;
  259. thread_info.duration := StrToInt (ParamStr(longopt_index));
  260. end;
  261. '-f', '--file':
  262. begin
  263. Inc(longopt_index);
  264. if longopt_index > ParamCount then
  265. begin
  266. Writeln (StdErr, 'error');
  267. show_usage := True;
  268. break;
  269. end;
  270. thread_info.path := ParamStr(longopt_index);
  271. end;
  272. '-b', '--bitdepth':
  273. begin
  274. Inc(longopt_index);
  275. if longopt_index > ParamCount then
  276. begin
  277. Writeln (StdErr, 'error');
  278. show_usage := True;
  279. break;
  280. end;
  281. thread_info.bitdepth := StrToInt (ParamStr(longopt_index));
  282. end;
  283. '-B', '--bufsize':
  284. begin
  285. Inc(longopt_index);
  286. if longopt_index > ParamCount then
  287. begin
  288. Writeln (StdErr, 'error');
  289. show_usage := True;
  290. break;
  291. end;
  292. thread_info.rb_size := StrToInt (ParamStr(longopt_index));
  293. end;
  294. else
  295. begin
  296. if (Length(ParamStr(longopt_index)) >= 1) and (ParamStr(longopt_index)[1] = '-') then
  297. begin
  298. Writeln (StdErr, 'error');
  299. show_usage := True;
  300. break;
  301. end
  302. else
  303. begin
  304. Dec(longopt_index);
  305. break;
  306. end;
  307. end;
  308. end;
  309. end;
  310. if show_usage or (thread_info.path = '') or (longopt_index >= ParamCount) then
  311. begin
  312. Writeln (StdErr, 'usage: jackrec -f filename [ -d second ] [ -b bitdepth ] [ -B bufsize ] port1 [ port2 ... ]');
  313. Halt (1);
  314. end;
  315. client := jack_client_open ('jackrec', JackNullOption, nil);
  316. if client = nil then
  317. begin
  318. Writeln (StdErr, 'jack server not running?');
  319. Halt (1);
  320. end;
  321. thread_info.client := client;
  322. thread_info.channels := ParamCount - longopt_index;
  323. thread_info.can_process := False;
  324. setup_disk_thread (@thread_info);
  325. jack_set_process_callback (client, @process, @thread_info);
  326. jack_on_shutdown (client, @jack_shutdown, @thread_info);
  327. if jack_activate (client) <> 0 then
  328. begin
  329. Writeln (StdErr, 'cannot activate client');
  330. end;
  331. setup_ports (ParamCount - longopt_index, longopt_index + 1, @thread_info);
  332. run_disk_thread (@thread_info);
  333. jack_client_close (client);
  334. jack_ringbuffer_free (rb);
  335. pthread_cond_destroy(@data_ready);
  336. pthread_mutex_destroy(@disk_thread_lock);
  337. end.