123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- {
- Copyright (C) 2001 Paul Davis
- Copyright (C) 2003 Jack O'Quin
-
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- * 2002/08/23 - modify for libsndfile 1.0.0 <[email protected]>
- * 2003/05/26 - use ringbuffers - joq
-
- }
- program capture_client;
- {$MODE ObjFpc}{$H+}
- uses
- {$ifdef UNIX}
- CThreads,
- {$endif}
- PThreads, CTypes, SysUtils, SndFile, Jack, JackRingBuffer;
- const
- EIO = 5;
- EPIPE = 32;
- type
- Pjack_thread_info_t = ^jack_thread_info_t;
- jack_thread_info_t = record
- thread_id: TThreadId;
- sf: PSNDFILE;
- duration: jack_nframes_t;
- rb_size: jack_nframes_t;
- client: Pjack_client_t;
- channels: cuint;
- bitdepth: cint;
- path: string;
- can_capture: Boolean; {volatile;}
- can_process: Boolean; {volatile;}
- status: cint; {volatile;}
- end;
- var
- { JACK data }
- nports: cuint;
- ports: PPjack_port_t;
- _in: PPjack_default_audio_sample_t;
- nframes: jack_nframes_t;
- const
- sample_size = SizeOf(jack_default_audio_sample_t);
- { Synchronization between process thread and disk thread. }
- DEFAULT_RB_SIZE = 16384; { ringbuffer size in frames }
- var
- rb: Pjack_ringbuffer_t;
- disk_thread_lock: TPthreadMutex;
- data_ready: TCondVar;
- overruns: clong = 0;
- total_captured: jack_nframes_t = 0;
- function disk_thread (arg: Pointer): PtrInt;
- label
- done;
- var
- info: Pjack_thread_info_t;
- samples_per_frame: jack_nframes_t;
- bytes_per_frame: csize_t;
- framebuf: Pointer;
- errstr: array [0..255] of Char;
- begin
- info := arg;
- samples_per_frame := info^.channels;
- bytes_per_frame := samples_per_frame * sample_size;
- framebuf := GetMem (bytes_per_frame);
- pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, nil);
- pthread_mutex_lock (@disk_thread_lock);
- info^.status := 0;
- while True do
- begin
- { Write the data one frame at a time. This is
- inefficient, but makes things simpler. }
- while info^.can_capture and
- (jack_ringbuffer_read_space (rb) >= bytes_per_frame) do
- begin
- jack_ringbuffer_read (rb, framebuf, bytes_per_frame);
- if sf_writef_float (info^.sf, framebuf, 1) <> 1 then
- begin
- sf_error_str (nil, @errstr, sizeof (errstr) - 1);
- Writeln (StdErr,
- 'cannot write sndfile (', errstr, ')');
- info^.status := EIO; { write failed }
- goto done;
- end;
- Inc(total_captured);
- if total_captured >= info^.duration then
- begin
- Writeln ('disk thread finished');
- goto done;
- end;
- end;
- { wait until process() signals more data }
- pthread_cond_wait (@data_ready, @disk_thread_lock);
- end;
- done:
- pthread_mutex_unlock (@disk_thread_lock);
- FreeMem (framebuf);
- Result := 0;
- end;
-
- function process (nframes: jack_nframes_t; arg: Pointer): cint; cdecl;
- var
- chn: cint;
- i: csize_t;
- info: Pjack_thread_info_t;
- begin
- info := arg;
- { Do nothing until we're ready to begin. }
- if (not info^.can_process) or (not info^.can_capture) then
- Exit(0);
- for chn := 0 to nports - 1 do
- _in[chn] := jack_port_get_buffer (ports[chn], nframes);
- { Sndfile requires interleaved data. It is simpler here to
- just queue interleaved samples to a single ringbuffer. }
- for i := 0 to nframes - 1 do
- for chn := 0 to nports - 1 do
- if jack_ringbuffer_write (rb, Pointer(_in[chn]+i), sample_size) < sample_size then
- Inc(overruns);
- { Tell the disk thread there is work to do. If it is already
- running, the lock will not be available. We can't wait
- here in the process() thread, but we don't need to signal
- in that case, because the disk thread will read all the
- data queued before waiting again. }
- if pthread_mutex_trylock (@disk_thread_lock) = 0 then
- begin
- pthread_cond_signal (@data_ready);
- pthread_mutex_unlock (@disk_thread_lock);
- end;
- Result := 0;
- end;
- procedure jack_shutdown (arg: Pointer); cdecl;
- begin
- Writeln (StdErr, 'JACK shutdown');
- // Halt (0);
- //abort();
- end;
- procedure setup_disk_thread (info: Pjack_thread_info_t);
- var
- sf_info: TSF_INFO;
- short_mask: cint;
- errstr: array [0..255] of Char;
- begin
- sf_info.samplerate := jack_get_sample_rate (info^.client);
- sf_info.channels := info^.channels;
- case info^.bitdepth of
- 8:
- short_mask := SF_FORMAT_PCM_U8;
- 16:
- short_mask := SF_FORMAT_PCM_16;
- 24:
- short_mask := SF_FORMAT_PCM_24;
- 32:
- short_mask := SF_FORMAT_PCM_32;
- else
- short_mask := SF_FORMAT_PCM_16;
- end;
- sf_info.format := SF_FORMAT_WAV or short_mask;
- info^.sf := sf_open (PChar(info^.path), SFM_WRITE, @sf_info);
- if info^.sf = nil then
- begin
- sf_error_str (nil, @errstr, sizeof (errstr) - 1);
- Writeln (StdErr, 'cannot open sndfile ', info^.path, ' for output (', errstr, ')');
- jack_client_close (info^.client);
- Halt (1);
- end;
- if info^.duration = 0 then
- info^.duration := JACK_MAX_FRAMES
- else
- info^.duration *= sf_info.samplerate;
- info^.can_capture := False;
- info^.thread_id := BeginThread(@disk_thread, info);
- end;
- procedure run_disk_thread (info: Pjack_thread_info_t);
- begin
- info^.can_capture := True;
- WaitForThreadTerminate(info^.thread_id, -1);
- sf_close (info^.sf);
- if overruns > 0 then
- begin
- Writeln (StdErr,
- 'jackrec failed with ', overruns, ' overruns.');
- Writeln (StdErr, ' try a bigger buffer than -B ', info^.rb_size, '.');
- info^.status := EPIPE;
- end;
- end;
- procedure setup_ports (sources: cint; first_source_param: Integer; info: Pjack_thread_info_t);
- var
- i: cuint;
- in_size: csize_t;
- name, source_name: string;
- begin
- { Allocate data structures that depend on the number of ports. }
- nports := sources;
- ports := GetMem (SizeOf (Pjack_port_t) * nports);
- in_size := nports * SizeOf (Pjack_default_audio_sample_t);
- _in := GetMem (in_size);
- rb := jack_ringbuffer_create (nports * sample_size * info^.rb_size);
- { When JACK is running realtime, jack_activate() will have
- called mlockall() to lock our pages into memory. But, we
- still need to touch any newly allocated pages before
- process() starts using them. Otherwise, a page fault could
- create a delay that would force JACK to shut us down. }
- FillChar(_in^, in_size, 0);
- FillChar(rb^.buf^, rb^.size, 0);
- for i := 0 to nports - 1 do
- begin
- WriteStr (name, 'input', i+1);
- ports[i] := jack_port_register (info^.client, PChar(name), JACK_DEFAULT_AUDIO_TYPE, Ord(JackPortIsInput), 0);
- if ports[i] = nil then
- begin
- Writeln (StdErr, 'cannot register input port "', name, '"!');
- jack_client_close (info^.client);
- Halt (1);
- end;
- end;
- for i := 0 to nports - 1 do
- begin
- source_name := ParamStr(first_source_param + i);
- if jack_connect (info^.client, PChar(source_name), jack_port_name (ports[i])) <> 0 then
- begin
- Writeln (StdErr, 'cannot connect input port ', jack_port_name (ports[i]), ' to ', source_name);
- jack_client_close (info^.client);
- Halt (1);
- end;
- end;
- info^.can_process := True; { process() can start, now }
- end;
- var
- client: Pjack_client_t;
- thread_info: jack_thread_info_t;
- longopt_index: cint = 0;
- show_usage: Boolean = False;
- begin
- pthread_mutex_init(@disk_thread_lock, nil);
- pthread_cond_init(@data_ready, nil);
- FillChar (thread_info, SizeOf (thread_info), 0);
- thread_info.rb_size := DEFAULT_RB_SIZE;
- while longopt_index < ParamCount do
- begin
- Inc(longopt_index);
- case ParamStr(longopt_index) of
- '-h', '--help':
- show_usage := True;
- '-d', '--duration':
- begin
- Inc(longopt_index);
- if longopt_index > ParamCount then
- begin
- Writeln (StdErr, 'error');
- show_usage := True;
- break;
- end;
- thread_info.duration := StrToInt (ParamStr(longopt_index));
- end;
- '-f', '--file':
- begin
- Inc(longopt_index);
- if longopt_index > ParamCount then
- begin
- Writeln (StdErr, 'error');
- show_usage := True;
- break;
- end;
- thread_info.path := ParamStr(longopt_index);
- end;
- '-b', '--bitdepth':
- begin
- Inc(longopt_index);
- if longopt_index > ParamCount then
- begin
- Writeln (StdErr, 'error');
- show_usage := True;
- break;
- end;
- thread_info.bitdepth := StrToInt (ParamStr(longopt_index));
- end;
- '-B', '--bufsize':
- begin
- Inc(longopt_index);
- if longopt_index > ParamCount then
- begin
- Writeln (StdErr, 'error');
- show_usage := True;
- break;
- end;
- thread_info.rb_size := StrToInt (ParamStr(longopt_index));
- end;
- else
- begin
- if (Length(ParamStr(longopt_index)) >= 1) and (ParamStr(longopt_index)[1] = '-') then
- begin
- Writeln (StdErr, 'error');
- show_usage := True;
- break;
- end
- else
- begin
- Dec(longopt_index);
- break;
- end;
- end;
- end;
- end;
- if show_usage or (thread_info.path = '') or (longopt_index >= ParamCount) then
- begin
- Writeln (StdErr, 'usage: jackrec -f filename [ -d second ] [ -b bitdepth ] [ -B bufsize ] port1 [ port2 ... ]');
- Halt (1);
- end;
- client := jack_client_open ('jackrec', JackNullOption, nil);
- if client = nil then
- begin
- Writeln (StdErr, 'jack server not running?');
- Halt (1);
- end;
- thread_info.client := client;
- thread_info.channels := ParamCount - longopt_index;
- thread_info.can_process := False;
- setup_disk_thread (@thread_info);
- jack_set_process_callback (client, @process, @thread_info);
- jack_on_shutdown (client, @jack_shutdown, @thread_info);
- if jack_activate (client) <> 0 then
- begin
- Writeln (StdErr, 'cannot activate client');
- end;
- setup_ports (ParamCount - longopt_index, longopt_index + 1, @thread_info);
- run_disk_thread (@thread_info);
- jack_client_close (client);
- jack_ringbuffer_free (rb);
- pthread_cond_destroy(@data_ready);
- pthread_mutex_destroy(@disk_thread_lock);
- end.
|