changeset 27414:5283c505f92b stable

Fix numerous errors in audiowrite/audioread (bug #56889). Fix functions so that "y == audioread (audiowrite (y))", i.e., no innaccuracy nor loss of precision when saving and reading audio data from a file. * audioread.cc (Faudiowrite): Use correct bias and scale factor of 127.5 for uint8 input data. Use hardcoded scale factors at compile-time for int16, int32 rather than calculating factors at runtime with std::pow(). Configure libsndfile options SFC_SET_NORM_DOUBLE and SFC_SET_CLIPPING to true which stops libsndfile from oddly changing written data. Use doubles and sf_write_double to represent audio data (no loss of accuracy even with 32-bit PCM). Add BIST tests. * audioread.cc (Faudioread): Use doubles and sf_read_double to represent audio data (no loss of accuracy even with 32-bit PCM). Correct scale factors for PCM_S8, PCM_U8, and PCM_16 in order to correctly reproduce written data.
author Rik <rik@octave.org>
date Mon, 16 Sep 2019 10:24:13 -0700
parents d9d10a49926d
children 0a19330a9e01 6bbbfba55989
files libinterp/dldfcn/audioread.cc
diffstat 1 files changed, 74 insertions(+), 10 deletions(-) [+]
line wrap: on
line diff
--- a/libinterp/dldfcn/audioread.cc	Tue Sep 03 18:00:25 2019 +0900
+++ b/libinterp/dldfcn/audioread.cc	Mon Sep 16 10:24:13 2019 -0700
@@ -98,9 +98,9 @@
 
   frame.add_fcn (safe_close, file);
 
-  OCTAVE_LOCAL_BUFFER (float, data, info.frames * info.channels);
+  OCTAVE_LOCAL_BUFFER (double, data, info.frames * info.channels);
 
-  sf_read_float (file, data, info.frames * info.channels);
+  sf_read_double (file, data, info.frames * info.channels);
 
   sf_count_t start = 0;
   sf_count_t end = info.frames;
@@ -153,13 +153,13 @@
           switch (info.format & SF_FORMAT_SUBMASK)
             {
             case SF_FORMAT_PCM_S8:
-              ret_audio = int8NDArray (audio * 127);
+              ret_audio = int8NDArray (audio * 128);
               break;
             case SF_FORMAT_PCM_U8:
-              ret_audio = uint8NDArray (audio * 127 + 127);
+              ret_audio = uint8NDArray (audio * 128 + 128);
               break;
             case SF_FORMAT_PCM_16:
-              ret_audio = int16NDArray (audio * 32767);
+              ret_audio = int16NDArray (audio * 32768);
               break;
             case SF_FORMAT_PCM_24:
               ret_audio = int32NDArray (audio * 8388608);
@@ -288,11 +288,11 @@
   double scale = 1.0;
 
   if (args(1).is_uint8_type ())
-    bias = scale = std::pow (2.0, 7);
+    bias = scale = 127.5;
   else if (args(1).is_int16_type ())
-    scale = std::pow (2.0, 15);
+    scale = 32768;       // 2^15
   else if (args(1).is_int32_type ())
-    scale = std::pow (2.0, 31);
+    scale = 2147483648;  // 2^31
   else if (args(1).isinteger ())
     err_wrong_type_arg ("audiowrite", args(1));
 
@@ -313,7 +313,7 @@
   if (audio.rows () == 1)
     audio = audio.transpose ();
 
-  OCTAVE_LOCAL_BUFFER (float, data, items_to_write);
+  OCTAVE_LOCAL_BUFFER (double, data, items_to_write);
 
   sf_count_t idx = 0;
   for (int i = 0; i < audio.rows (); i++)
@@ -431,6 +431,8 @@
 
   frame.add_fcn (safe_close, file);
 
+  sf_command (file, SFC_SET_NORM_DOUBLE, NULL, SF_TRUE);
+  sf_command (file, SFC_SET_CLIPPING, NULL, SF_TRUE) ;
   sf_command (file, SFC_SET_VBR_ENCODING_QUALITY, &quality, sizeof (quality));
 
   if (title != "")
@@ -453,7 +455,7 @@
       if (items_to_write - offset < chunk_size)
         chunk_size = items_to_write - offset;
 
-      sf_count_t items_written = sf_write_float (file, data+offset, chunk_size);
+      sf_count_t items_written = sf_write_double (file, data+offset, chunk_size);
 
       if (items_written != chunk_size)
         error ("audiowrite: write failed, wrote %" PRId64 " of %" PRId64
@@ -478,6 +480,68 @@
 }
 
 /*
+## Joint audiowrite/audioread tests
+## 8-bit Unsigned PCM
+%!testif HAVE_SNDFILE <*56889>
+%! fname = [tempname() ".wav"];
+%! unwind_protect
+%!   y1 = uint8 ([0, 1, 2, 253, 254, 255]);
+%!   audiowrite (fname, y1, 8000, "BitsPerSample", 8);
+%!   y2 = audioread (fname, "native");
+%! unwind_protect_cleanup
+%!   unlink (fname);
+%! end_unwind_protect
+%! assert (y1(:), y2);
+
+## 8-bit Signed PCM
+%!testif HAVE_SNDFILE <*56889>
+%! fname = [tempname() ".au"];
+%! unwind_protect
+%!   y1 = uint8 ([0, 1, 2, 253, 254, 255]);
+%!   audiowrite (fname, y1, 8000, "BitsPerSample", 8);
+%!   y2 = audioread (fname, "native");
+%! unwind_protect_cleanup
+%!   unlink (fname);
+%! end_unwind_protect
+%! assert (y2, int8 ([-128; -127; -126; 125; 126; 127]));
+
+## 16-bit Signed PCM
+%!testif HAVE_SNDFILE <*56889>
+%! fname = [tempname() ".wav"];
+%! unwind_protect
+%!   y1 = int16 ([-32768, -32767, -32766, 32765, 32766, 32767]);
+%!   audiowrite (fname, y1, 8000, "BitsPerSample", 16);
+%!   y2 = audioread (fname, "native");
+%! unwind_protect_cleanup
+%!   unlink (fname);
+%! end_unwind_protect
+%! assert (y1(:), y2);
+
+## 24-bit Signed PCM
+%!testif HAVE_SNDFILE <*56889>
+%! fname = [tempname() ".au"];
+%! unwind_protect
+%!   y1 = [-8388608, -8388607, -8388606, 8388605, 8388606, 8388607] / 8388608;
+%!   audiowrite (fname, y1, 8000, "BitsPerSample", 24);
+%!   y2 = audioread (fname, "native");
+%! unwind_protect_cleanup
+%!   unlink (fname);
+%! end_unwind_protect
+%! assert (int32 ([-8388608; -8388607; -8388606; 8388605; 8388606; 8388607]),
+%!         y2);
+
+## 32-bit Signed PCM
+%!testif HAVE_SNDFILE <*56889>
+%! fname = [tempname() ".wav"];
+%! unwind_protect
+%!   y1 = int32 ([-2147483648, -2147483647, -2147483646, 2147483645, 2147483646, 2147483647 ]);
+%!   audiowrite (fname, y1, 8000, "BitsPerSample", 32);
+%!   y2 = audioread (fname, "native");
+%! unwind_protect_cleanup
+%!   unlink (fname);
+%! end_unwind_protect
+%! assert (y1(:), y2);
+
 ## Test input validation
 %!testif HAVE_SNDFILE
 %! fail ("audiowrite (1, 1, 8e3)", "FILENAME must be a string");