Anklang-0.3.0.dev712+gdc4e642f anklang-0.3.0.dev712+gdc4e642f
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
wave.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include "wave.hh"
3#include "utils.hh"
4#include "datautils.hh"
5#include "atquit.hh"
6#include "platform.hh"
7#include "randomhash.hh"
8#include "internal.hh"
9#include <cstring>
10#include <sys/types.h>
11#include <sys/stat.h>
12#include <sys/types.h>
13#include <fcntl.h>
14#include <unistd.h>
15#include <opus.h>
16#include <ogg/ogg.h>
17#include <FLAC/all.h>
18
19namespace Ase {
20
21// == WaveWriter ==
22WaveWriter::~WaveWriter ()
23{}
24
25// == WAV ==
27wav_header (const uint8_t n_bits, const uint32_t n_channels, const uint32_t sample_freq, const uint32_t n_samples)
28{
29 union ByteStream {
30 void *v; char *c; uint8_t *u8;
31 uint16_t *u16;
32 uint32_t *u32;
33 char*
34 puts (const char *str)
35 {
36 strncpy (c, str, 1024);
37 c += strlen (str);
38 return c;
39 }
40 };
41 const uint32_t byte_per_sample = n_channels * n_bits / 8;
42 const uint32_t byte_per_second = byte_per_sample * sample_freq;
43 const uint32_t n_data_bytes = (n_samples * byte_per_sample + 1) / 2 * 2; // round odd
45 buffer.resize (1024);
46 ByteStream b { &buffer[0] };
47 b.puts ("RIFF"); // main chunk
48 const ByteStream lpos = b;
49 b.u32++; // skip file length
50 const ByteStream cpos = b; // save chunk pos
51 b.puts ("WAVE"); // chunk type
52 b.puts ("fmt "); // sub chunk
53 const uint16_t fmt = n_bits == 32 ? 3 : 1;
54 const bool extensible = n_channels > 2 || fmt == 3;
55 const uint32_t fmtsz = extensible ? 18 : 16;
56 *b.u32++ = htole32 (fmtsz); // sub chunk length
57 *b.u16++ = htole16 (fmt); // format, 1=PCM, 3=FLOAT
58 *b.u16++ = htole16 (n_channels);
59 *b.u32++ = htole32 (sample_freq);
60 *b.u32++ = htole32 (byte_per_second);
61 *b.u16++ = htole16 (byte_per_sample); // block align
62 *b.u16++ = htole16 (n_bits);
63 if (extensible)
64 *b.u16++ = htole16 (0); // extension size
65 if (fmt == 3)
66 {
67 b.puts ("fact"); // sub chunk
68 *b.u32++ = htole32 (4); // sub chunk length
69 *b.u32++ = htole32 (n_samples); // frames
70 }
71 b.puts ("data"); // data chunk
72 *b.u32++ = htole32 (n_data_bytes);
73 const uint32_t length = b.c - cpos.c + n_data_bytes;
74 *lpos.u32 = htole32 (length); // fix file length
75 buffer.resize (b.u8 - &buffer[0]);
76 return buffer;
77}
78
79static int
80wav_write (int fd, uint8_t n_bits, uint32_t n_channels, uint32_t sample_freq, const float *samples, size_t n_frames)
81{
82 const size_t n_samples = n_channels * n_frames;
83 if (n_bits == 8) // unsigned WAV
84 {
85 uint8_t buffer[16384], *const e = buffer + sizeof (buffer) / sizeof (buffer[0]), *b = buffer;
86 for (size_t n = 0; n < n_samples; )
87 {
88 const uint8_t u8 = 127.5 + 127.5 * samples[n++];
89 *b++ = u8;
90 if (b + 1 >= e || n >= n_samples)
91 {
92 if (n >= n_samples && (n_channels & 1) && (n_samples & 1)) // && (byte_per_sample & 1)
93 *b++ = 0; // final pad byte
94 if (write (fd, buffer, (b - buffer) * sizeof (buffer[0])) < 0)
95 return -errno;
96 b = buffer;
97 }
98 }
99 }
100 if (n_bits == 24)
101 {
102 uint8_t buffer[16384], *const e = buffer + sizeof (buffer) / sizeof (buffer[0]), *b = buffer;
103 for (size_t n = 0; n < n_samples; )
104 {
105 const int32_t i24 = samples[n++] * 8388607.5 - 0.5;
106 *b++ = i24;
107 *b++ = i24 >> 8;
108 *b++ = i24 >> 16;
109 if (b + 4 >= e || n >= n_samples)
110 {
111 if (n >= n_samples && (n_channels & 1) && (n_samples & 1)) // && (byte_per_sample & 1)
112 *b++ = 0; // final pad byte
113 if (write (fd, buffer, (b - buffer) * sizeof (buffer[0])) < 0)
114 return -errno;
115 b = buffer;
116 }
117 }
118 }
119 if (n_bits == 16)
120 {
121 uint16_t buffer[16384], *const e = buffer + sizeof (buffer) / sizeof (buffer[0]), *b = buffer;
122 for (size_t n = 0; n < n_samples; )
123 {
124 const int16_t i16 = samples[n++] * 32767.5 - 0.5;
125 *b++ = htole16 (i16);
126 if (b >= e || n >= n_samples)
127 {
128 if (write (fd, buffer, (b - buffer) * sizeof (buffer[0])) < 0)
129 return -errno;
130 b = buffer;
131 }
132 }
133 }
134 if (n_bits == 32)
135 {
136 uint32_t buffer[16384], *const e = buffer + sizeof (buffer) / sizeof (buffer[0]), *b = buffer;
137 for (size_t n = 0; n < n_samples; )
138 {
139 union { float f; uint32_t u32; } u { samples[n++] };
140 *b++ = htole32 (u.u32);
141 if (b >= e || n >= n_samples)
142 {
143 if (write (fd, buffer, (b - buffer) * sizeof (buffer[0])) < 0)
144 return -errno;
145 b = buffer;
146 }
147 }
148 }
149 return 0;
150}
151
152class WavWriterImpl final : public WaveWriter {
153 String filename_;
154 uint32_t n_channels_ = 0;
155 uint32_t sample_freq_ = 0;
156 uint8_t n_bits_ = 0;
157 int fd_ = -1;
158 size_t n_samples_ = 0;
159 std::function<void()> flush_atquit;
160public:
162 {
163 flush_atquit = [this] () { close(); };
164 atquit_add (&flush_atquit);
165 }
167 {
168 atquit_del (&flush_atquit);
169 close();
170 }
171 bool
172 open (const String &filename, uint8_t n_bits, uint32_t n_channels, uint32_t sample_freq)
173 {
174 assert_return (fd_ == -1, false);
175 assert_return (!filename.empty(), false);
176 assert_return (n_bits == 8 || n_bits == 16 || n_bits == 24 || n_bits == 32, false);
177 assert_return (n_channels > 0, false);
178 assert_return (sample_freq > 0, false);
179 // open, must be seekable
180 fd_ = ::open (filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0644);
181 if (fd_ < 0)
182 return false;
183 if (lseek (fd_, 0, SEEK_SET) < 0)
184 {
185 ::close (fd_);
186 fd_ = -1;
187 return false;
188 }
189 // setup fields
190 filename_ = filename;
191 n_bits_ = n_bits;
192 n_channels_ = n_channels;
193 sample_freq_ = sample_freq;
194 n_samples_ = 0;
195 // write header
196 std::vector<unsigned char> header = wav_header (n_bits_, n_channels_, sample_freq_, 4294967168);
197 if (::write (fd_, header.data(), header.size()) < 0)
198 {
199 ::close (fd_);
200 fd_ = -1;
201 return false;
202 }
203 return true;
204 }
205 String
206 name () const override
207 {
208 return filename_;
209 }
210 ssize_t
211 write (const float *frames, size_t n_frames) override
212 {
213 assert_return (n_bits_ == 32, false);
214 assert_return (fd_ >= 0, false);
215 return_unless (n_frames, false);
216
217 n_samples_ += n_frames * n_channels_;
218
219 return wav_write (fd_, n_bits_, n_channels_, sample_freq_, frames, n_frames) >= 0;
220 }
221 bool
222 close () override
223 {
224 return_unless (fd_ >= 0, false);
225 std::vector<unsigned char> header = wav_header (n_bits_, n_channels_, sample_freq_, n_samples_ / n_channels_);
226 bool ok = true;
227 if (lseek (fd_, 0, SEEK_SET) >= 0)
228 ok &= ::write (fd_, header.data(), header.size()) >= 0;
229 ok &=::close (fd_) >= 0;
230 fd_ = -1;
231 return ok;
232 }
233};
234
236wave_writer_create_wav (int rate, int channels, const String &filename, int mode, uint8_t n_bits)
237{
239 if (wavw->open (filename, n_bits, channels, rate) == false)
240 return nullptr;
241 return wavw;
242}
243
244// == OpusWriter ==
245String
246wave_writer_opus_version()
247{
248 return opus_get_version_string();
249}
250
251class OpusWriter final : public WaveWriter {
252 String name_;
253 OpusEncoder *enc_ = nullptr;
254 int fd_ = -1;
255 float *bmark_ = nullptr;
256 std::vector<float> buffer_;
257 ogg_stream_state ostream_ = { 0, };
258 uint32_t rate_ = 0;
259 uint8_t n_channels_ = 0;
260 bool eos_ = false;
261 uint packetno_ = 0;
262 int64_t granulepos_ = 0;
263 std::function<void()> flush_atquit;
264public:
265 OpusWriter (const String &filename)
266 {
267 name_ = filename;
268 flush_atquit = [this] () { finish_and_close (true); };
269 atquit_add (&flush_atquit);
270 }
272 {
273 atquit_del (&flush_atquit);
274 close();
275 opus_encoder_destroy (enc_);
276 enc_ = nullptr;
277 ogg_stream_clear (&ostream_);
278 }
279 String
280 name() const override
281 {
282 return name_;
283 }
284 bool
285 create (int mode)
286 {
287 errno = EBUSY;
288 assert_return (fd_ < 0, false);
289 fd_ = open (name_.c_str(), O_CREAT | O_TRUNC | O_WRONLY, mode);
290 return fd_ >= 0;
291 }
292 bool
293 setup_encoder (int rate, int channels, int complexity, float bitrate)
294 {
295 assert_return (fd_ >= 0, false);
296 errno = EINVAL;
297 assert_return (!enc_, false);
298 assert_return (channels == 1 || channels == 2, false);
299 assert_return (rate > 24000, false); // lets opus operate at 48000
300 rate_ = rate;
301 int error = 0;
302 n_channels_ = channels;
303 enc_ = opus_encoder_create (rate_, n_channels_, OPUS_APPLICATION_AUDIO, &error);
304 if (error == OPUS_OK)
305 {
306 bitrate = n_channels_ * bitrate * 1000;
307 const int serialno = random_int64();
308 if (OPUS_OK == opus_encoder_ctl (enc_, OPUS_SET_BITRATE (MIN (256000 * n_channels_, bitrate))) &&
309 OPUS_OK == opus_encoder_ctl (enc_, OPUS_SET_VBR (1)) &&
310 OPUS_OK == opus_encoder_ctl (enc_, OPUS_SET_VBR_CONSTRAINT (0)) &&
311 OPUS_OK == opus_encoder_ctl (enc_, OPUS_SET_FORCE_CHANNELS (n_channels_)) &&
312 OPUS_OK == opus_encoder_ctl (enc_, OPUS_SET_COMPLEXITY (complexity)) &&
313 ogg_stream_init (&ostream_, serialno) >= 0)
314 {
315 const int std_fragment_size = 20 * 48000 / 1000; // 960
316 int fragment_size = std_fragment_size * rate_ / 48000;
317 buffer_.resize (fragment_size * n_channels_);
318 bmark_ = buffer_.data();
319 if (write_header() > 0)
320 return true;
321 }
322 }
323 finish_and_close (false);
324 errno = EINVAL;
325 return false;
326 }
327 ssize_t
328 write_packet (ogg_packet *op, bool force_flush)
329 {
330 ogg_stream_packetin (&ostream_, op);
331 ogg_page opage;
332 auto stream_pageout = force_flush ? ogg_stream_flush : ogg_stream_pageout;
333 while (stream_pageout (&ostream_, &opage))
334 {
335 ssize_t n;
336 do
337 n = ::write (fd_, opage.header, opage.header_len);
338 while (n == -1 && errno == EINTR);
339 if (n != opage.header_len) return -1;
340 do
341 n = ::write (fd_, opage.body, opage.body_len);
342 while (n == -1 && errno == EINTR);
343 if (n != opage.body_len) return -1;
344 }
345 return 1;
346 }
347 ssize_t
348 write_header ()
349 {
350 int lookahead = 0;
351 if (OPUS_OK != opus_encoder_ctl (enc_, OPUS_GET_LOOKAHEAD (&lookahead)))
352 lookahead = 0;
353 // header packet
354 struct OpusHeader {
355 // https://www.rfc-editor.org/rfc/rfc7845.html#section-5
356 char magic[8] = { 'O', 'p', 'u', 's', 'H', 'e', 'a', 'd' };
357 uint8_t version = 1;
358 uint8_t channels = 0;
359 uint16_t pre_skip_le = 0;
360 uint32_t rate_le = 0;
361 int16_t gain_le = 0; // in dB, stored as 20*log10(gainfactor) * 256; sample *= pow (10, gain_le/256./20)
362 uint8_t chmapping = 0;
363 // uint8_t mappings...
364 } __attribute__ ((packed));
365 OpusHeader oh = {
366 .channels = n_channels_,
367 .pre_skip_le = htole16 (lookahead),
368 .rate_le = htole32 (rate_),
369 };
370 static_assert (sizeof (oh) == 19);
371 ogg_packet op0 = {
372 .packet = (uint8_t*) &oh,
373 .bytes = sizeof (oh),
374 .b_o_s = 0, .e_o_s = 0,
375 .granulepos = 0,
376 .packetno = packetno_++
377 };
378 if (write_packet (&op0, true) < 0)
379 return -1;
380 // comment packet
381 uint32_t u32_le;
382 std::string cmtheader;
383 cmtheader += "OpusTags"; // 8, magic
384 const String opus_version = opus_get_version_string();
385 u32_le = htole32 (opus_version.size());
386 cmtheader += String ((char*) &u32_le, 4); // 4, vendor string length
387 cmtheader += opus_version; // vendor string
388 StringS tags;
389 tags.insert (tags.begin(), "ENCODER=Anklang-" + String (ase_version_short));
390 // R128_TRACK_GAIN, R128_ALBUM_GAIN, BPM, ARTIST, TITLE, DATE, ALBUM
391 u32_le = htole32 (tags.size());
392 cmtheader += String ((char*) &u32_le, 4); // 4, number of tags
393 for (const auto &tag : tags)
394 {
395 u32_le = htole32 (tag.size());
396 cmtheader += String ((char*) &u32_le, 4); // 4, tag length
397 cmtheader += tag; // tag string
398 }
399 ogg_packet op1 = {
400 .packet = (uint8_t*) cmtheader.data(),
401 .bytes = long (cmtheader.size()),
402 .b_o_s = 0, .e_o_s = 0,
403 .granulepos = 0,
404 .packetno = packetno_++
405 };
406 if (write_packet (&op1, true) < 0)
407 return -1;
408 return 2;
409 }
410 ssize_t
411 write_ogg (uint8_t *data, long l, bool force_flush)
412 {
413 ogg_packet op = {
414 .packet = data,
415 .bytes = l,
416 .b_o_s = 0, .e_o_s = eos_,
417 .granulepos = granulepos_,
418 .packetno = packetno_++
419 };
420 if (write_packet (&op, force_flush) < 0)
421 return -1;
422 return 1;
423 }
424 ssize_t
425 write (const float *frames, size_t n_frames) override
426 {
427 return write_opus (frames, n_frames, false);
428 }
429 ssize_t
430 write_opus (const float *frames, size_t n_frames, bool force_flush)
431 {
432 errno = EINVAL;
433 return_unless (fd_ >= 0, -1);
434 return_unless (n_frames, 0);
435 assert_return (enc_, -1);
436 assert_return (frames, -1);
437 const float *fend = frames + n_frames * n_channels_;
438 const float *bmax = buffer_.data() + buffer_.size();
439 while (frames < fend)
440 {
441 ssize_t l = MIN (bmax - bmark_, fend - frames);
442 fast_copy (l, bmark_, frames);
443 frames += l;
444 bmark_ += l;
445 if (bmark_ == bmax)
446 {
447 uint8_t pmem[16384];
448 bmark_ = buffer_.data();
449 const uint n_frames = buffer_.size() / n_channels_;
450 granulepos_ += n_frames;
451 l = opus_encode_float (enc_, bmark_, n_frames, pmem, sizeof (pmem));
452 if (l < 0)
453 {
454 printerr ("%s: OpusWriter encoding error: %s\n", name_, opus_strerror (l));
455 finish_and_close (false);
456 errno = EIO;
457 return -1;
458 }
459 if (l > 0 && write_ogg (pmem, l, force_flush) < 0)
460 {
461 const int serrno = errno;
462 finish_and_close (false);
463 errno = serrno;
464 return -1;
465 }
466 }
467 }
468 return n_frames;
469 }
470 bool
471 finish_and_close (bool flush)
472 {
473 return_unless (fd_ >= 0, false);
474 bool err = false;
475 if (flush && enc_)
476 {
477 const size_t n_filled = bmark_ - buffer_.data();
478 // flush fragment and ogg stream
479 const size_t n_floats = buffer_.size() - n_filled;
480 float zeros[n_floats];
481 floatfill (zeros, 0, n_floats);
482 eos_ = true;
483 err &= write_opus (zeros, n_floats / n_channels_, true) < 0;
484 }
485 // flush file descroiptor
486 err &= ::close (fd_) < 0;
487 fd_ = -1;
488 if (err)
489 printerr ("%s: OpusWriter close error: %s\n", name_, strerror (errno ? errno : EIO));
490 return !err;
491 }
492 bool
493 close() override
494 {
495 return finish_and_close (true);
496 }
497};
498
500wave_writer_create_opus (int rate, int channels, const String &filename, int mode, int complexity, float bitrate)
501{
502 auto ow = std::make_shared<OpusWriter> (filename);
503 if (ow->create (mode) == false)
504 return nullptr;
505 if (!ow->setup_encoder (rate, channels, complexity, bitrate))
506 return nullptr;
507 return ow;
508}
509
510// == FlacWriter ==
511String
512wave_writer_flac_version()
513{
514 return FLAC__VERSION_STRING;
515}
516
517class FlacWriter final : public WaveWriter {
518 String name_;
519 FLAC__StreamEncoder *enc_ = nullptr;
520 FLAC__StreamMetadata *metadata_[2] = { nullptr, nullptr };
521 uint32_t rate_ = 0;
522 uint8_t n_channels_ = 0;
523 std::vector<int32_t> ibuffer_;
524 std::function<void()> flush_atquit;
525public:
526 FlacWriter (const String &filename)
527 {
528 name_ = filename;
529 ibuffer_.reserve (65536);
530 flush_atquit = [this] () { if (enc_) close(); };
531 atquit_add (&flush_atquit);
532 }
534 {
535 atquit_del (&flush_atquit);
536 close();
537 }
538 void
539 cleanup()
540 {
541 if (enc_)
542 FLAC__stream_encoder_delete (enc_);
543 enc_ = nullptr;
544 if (metadata_[0])
545 FLAC__metadata_object_delete (metadata_[0]);
546 metadata_[0] = nullptr;
547 }
548 bool
549 close() override
550 {
551 errno = EINVAL;
552 assert_return (enc_, false);
553 const bool ok = FLAC__stream_encoder_finish (enc_);
554 const int saved_errno = errno;
555 // const FLAC__StreamEncoderState state = FLAC__stream_encoder_get_state (enc_);
556 cleanup();
557 if (!ok)
558 printerr ("%s: FlacWriter error: %s\n", name_, strerror (saved_errno ? saved_errno : EIO));
559 return ok;
560 }
561 bool
562 create (int mode, int rate, int channels, int compresion)
563 {
564 errno = EINVAL;
565 assert_return (channels == 1 || channels == 2, false);
566 assert_return (rate > 24000, false); // lets flac operate at 48000
567 errno = EBUSY;
568 assert_return (enc_ == nullptr, false);
569 const int fd = open (name_.c_str(), O_CREAT | O_TRUNC | O_RDWR, mode);
570 return_unless (fd >= 0, false);
571 FILE *file = fdopen (fd, "w+b");
572 if (!file)
573 {
574 ::close (fd);
575 return false;
576 }
577 enc_ = FLAC__stream_encoder_new();
578 if (!enc_)
579 {
580 fclose (file); // closes fd
581 return false;
582 }
583 rate_ = rate;
584 n_channels_ = channels;
585 bool setup_ok = true;
586 setup_ok &= FLAC__stream_encoder_set_channels (enc_, n_channels_);
587 setup_ok &= FLAC__stream_encoder_set_bits_per_sample (enc_, 24);
588 setup_ok &= FLAC__stream_encoder_set_sample_rate (enc_, rate_);
589 setup_ok &= FLAC__stream_encoder_set_compression_level (enc_, compresion);
590 StringS tags;
591 tags.insert (tags.begin(), "ENCODER=Anklang-" + String (ase_version_short));
592 metadata_[0] = FLAC__metadata_object_new (FLAC__METADATA_TYPE_VORBIS_COMMENT);
593 for (const auto &tag : tags)
594 {
595 FLAC__StreamMetadata_VorbisComment_Entry entry = { uint32_t (tag.size()), (uint8_t*) tag.data() };
596 setup_ok &= FLAC__metadata_object_vorbiscomment_append_comment (metadata_[0], entry, true);
597 }
598 setup_ok &= FLAC__stream_encoder_set_metadata (enc_, metadata_, 1);
599 printerr ("%s:%d: ok=%d\n", __FILE__, __LINE__, setup_ok);
600 setup_ok &= FLAC__STREAM_ENCODER_INIT_STATUS_OK == FLAC__stream_encoder_init_FILE (enc_, file, nullptr, nullptr);
601 printerr ("%s:%d: ok=%d\n", __FILE__, __LINE__, setup_ok);
602 if (setup_ok)
603 return true;
604 cleanup(); // deletes enc_ which closes file
605 errno = EINVAL;
606 return false;
607 }
608 ssize_t
609 write (const float *frames, size_t n_frames) override
610 {
611 errno = EINVAL;
612 return_unless (enc_ != nullptr, -1);
613 return_unless (n_frames, 0);
614 assert_return (frames, -1);
615 const size_t n_samples = n_frames * n_channels_;
616 if (ibuffer_.size() < n_samples)
617 ibuffer_.resize (n_samples);
618 for (size_t i = 0; i < n_samples; i++)
619 {
620 int32_t v = frames[i] * 8388607.5 - 0.5;
621 ibuffer_[i] = CLAMP (v, -8388608, +8388607);
622 }
623 const bool ok = FLAC__stream_encoder_process_interleaved (enc_, ibuffer_.data(), n_frames);
624 return ok;
625 }
626 String
627 name() const override
628 {
629 return name_;
630 }
631};
632
634wave_writer_create_flac (int rate, int channels, const String &filename, int mode, int compresion)
635{
636 auto ow = std::make_shared<FlacWriter> (filename);
637 if (ow->create (mode, rate, channels, compresion) == false)
638 return nullptr;
639 return ow;
640}
641
642} // Ase
#define EBUSY
T begin(T... args)
T c_str(T... args)
T data(T... args)
T empty(T... args)
errno
fclose
fdopen
T insert(T... args)
#define assert_return(expr,...)
Return from the current function if expr is unmet and issue an assertion warning.
Definition internal.hh:29
#define MIN(a, b)
Yield minimum of a and b.
Definition internal.hh:57
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
Definition internal.hh:73
#define CLAMP(v, mi, ma)
Yield v clamped to [mi … ma].
Definition internal.hh:60
lseek
The Anklang C++ API namespace.
Definition api.hh:9
void floatfill(float *dst, float f, size_t n)
Fill n values of dst with f.
Definition datautils.hh:29
std::string String
Convenience alias for std::string.
Definition cxxaux.hh:35
uint32_t uint
Provide 'uint' as convenience type.
Definition cxxaux.hh:18
void fast_copy(size_t n, float *d, const float *s)
Copy a block of floats.
Definition datautils.hh:37
uint64_t random_int64()
Generate a non-deterministic, uniformly distributed 64 bit pseudo-random number.
open
puts
T size(T... args)
typedef uint8_t
strncpy
strerror
strlen
typedef ssize_t