Hi,

On Thu, May 16, 2019 at 2:16 PM Måns Rullgård <m...@mansr.com> wrote:
> 
> Jakub Vaněk <linuxtar...@gmail.com> writes:
> 
> > Hi all,
> > 
> > I've created a patch for supporting for the NXT RSO / EV3 RSF
> > (Robot
> > Sound File) format. It is a format used on the stock firmware of
> > the
> > LEGO NXT and EV3 programmable bricks. I thought that it would be
> > nice
> > to have native support in SoX.
> > 
> > The format is capable of holding U8 PCM or ADPCM samples. Currently
> > decoding of both formats is implemented. However for encoding I
> > have
> > disabled the ADPCM support, because the created files sound very
> > wrong
> > on the real brick. U8 PCM mode on the other hand works well.
> > 
> > RSO support is implemented also in FFmpeg, just the RSF alias
> > isn't.
> > 
> > I am posting the exported commit below. However, it is also
> > available
> > from GitHub: 
> > https://github.com/JakubVanek/sox-rsf/commit/f906852451a2efc5b1129c7b34bfc378d3f4da62.patch
> 
> Is there a specification, preferably official, for this format
> available
> somewhere?  Could you provide a few samples?

I don't think there is an official specification. However, there are
some sources where it is described:

RSO description: https://wiki.multimedia.cx/index.php/RSO
RSF (=RSO) decoder on EV3: 
https://github.com/mindboards/ev3sources/blob/78ebaf5b6f8fe31cc17aa5dce0f8e4916a4fc072/lms2012/c_sound/source/c_sound.c#L695
RSO encoder in FFmpeg: 
https://github.com/FFmpeg/FFmpeg/blob/master/libavformat/rsoenc.c

I found the following samples online.

RSO samples:
http://samples.mplayerhq.hu/A-codecs/rso/

RSF samples:
https://github.com/mindboards/ev3sources/tree/master/lms2012/lms2012/doc/graphics/BITMAPS/SOUNDS

> 
> > diff --git a/src/rso.c b/src/rso.c
> > new file mode 100644
> > index
> > 0000000000000000000000000000000000000000..92256d130c63f79d1a8eb6ddf
> > 5db4197796b5f6b
> > --- /dev/null
> > +++ b/src/rso.c
> > @@ -0,0 +1,233 @@
> > +/* (c) 2019 SoX contributors
> 
> Please use your actual name in copyright tags.  If non-trivial
> portions
> of the code were written by someone else, they should also be
> mentioned,
> along with some kind of assurance that they approve of the
> distribution
> under the specified licence terms (LGPL).
> 
> I will not touch patches lacking clear provenance.

The code was written by me, however I took inspiration from AU and
similar formats. I have changed the header so that it lists my address.
I have also added LEGO(R) MINDSTORMS(R) to some places so that it is
clearer what NXT and EV3 are.

New patch is also at 
https://github.com/JakubVanek/sox-rsf/commit/57dbfa9664d9e3ae2bfb29150934027598e18377.patch

> 
> --
> Måns Rullgård

Jakub Vaněk


From 57dbfa9664d9e3ae2bfb29150934027598e18377 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jakub=20Van=C4=9Bk?= <linuxtar...@gmail.com>
Date: Wed, 15 May 2019 22:33:52 +0200
Subject: [PATCH] rso: add support for NXT RSO / EV3 RSF format

---
 FEATURES.in                   |   1 +
 msvc10/LibSoX.vcxproj         |   1 +
 msvc10/LibSoX.vcxproj.filters |   3 +
 msvc9/LibSoX.vcproj           |   4 +
 soxformat.7                   |   7 +
 src/CMakeLists.txt            |   1 +
 src/Makefile.am               |   2 +-
 src/formats.c                 |   2 +
 src/formats.h                 |   1 +
 src/rso.c                     | 235 ++++++++++++++++++++++++++++++++++
 10 files changed, 256 insertions(+), 1 deletion(-)
 create mode 100644 src/rso.c

diff --git a/FEATURES.in b/FEATURES.in
index 43ab6c69..444a153a 100644
--- a/FEATURES.in
+++ b/FEATURES.in
@@ -42,6 +42,7 @@ The current release handles the following audio file formats:
 * Maxis XA Audio files
 ** EA ADPCM (read support only, for now)
 * Pseudo formats that allow direct playing/recording from most audio devices
+* LEGO® MINDSTORMS® NXT RSO/EV3 RSF files
 * The "null" pseudo-file that reads and writes from/to nowhere
 (:tableend:)
 
diff --git a/msvc10/LibSoX.vcxproj b/msvc10/LibSoX.vcxproj
index b2e9d5a3..d00dbe6a 100644
--- a/msvc10/LibSoX.vcxproj
+++ b/msvc10/LibSoX.vcxproj
@@ -281,6 +281,7 @@
     <ClCompile Include="..\src\prc.c" />
     <ClCompile Include="..\src\pvf.c" />
     <ClCompile Include="..\src\raw-fmt.c" />
+    <ClCompile Include="..\src\rso.c" />
     <ClCompile Include="..\src\s1-fmt.c" />
     <ClCompile Include="..\src\s2-fmt.c" />
     <ClCompile Include="..\src\s3-fmt.c" />
diff --git a/msvc10/LibSoX.vcxproj.filters b/msvc10/LibSoX.vcxproj.filters
index f39449ca..9ac27753 100644
--- a/msvc10/LibSoX.vcxproj.filters
+++ b/msvc10/LibSoX.vcxproj.filters
@@ -435,6 +435,9 @@
     <ClCompile Include="..\src\raw-fmt.c">
       <Filter>Format Sources</Filter>
     </ClCompile>
+    <ClCompile Include="..\src\rso.c">
+      <Filter>Format Sources</Filter>
+    </ClCompile>
     <ClCompile Include="..\src\s1-fmt.c">
       <Filter>Format Sources</Filter>
     </ClCompile>
diff --git a/msvc9/LibSoX.vcproj b/msvc9/LibSoX.vcproj
index 362f8d77..11c94234 100644
--- a/msvc9/LibSoX.vcproj
+++ b/msvc9/LibSoX.vcproj
@@ -869,6 +869,10 @@
                                RelativePath="..\src\au.c"
                                >
                        </File>
+                       <File
+                               RelativePath="..\src\rso.c"
+                               >
+                       </File>
                        <File
                                RelativePath="..\src\avr.c"
                                >
diff --git a/soxformat.7 b/soxformat.7
index 5e53b9f9..4df79e24 100644
--- a/soxformat.7
+++ b/soxformat.7
@@ -652,6 +652,13 @@ and
 .BR sox (1)
 .BR \-d .
 .TP
+\fB.rso\fR, \fB.rsf\fR
+LEGO(R) MINDSTORMS(R) NXT RSO / EV3 RSF (Robot Sound File).
+A simple file format used in the stock NXT and EV3 firmwares.
+The format can store either U8 or IMA ADPCM samples. However only the U8
+mode is enabled, as the IMA ADPCM mode has problems on the EV3.
+Also, only the 8000 Hz sample rate and a single channel are supported.
+.TP
 .B .txw
 Yamaha TX-16W sampler.
 A file format from a Yamaha sampling keyboard which wrote IBM-PC
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index bde10d6a..41a2ee4a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -111,6 +111,7 @@ set(formats_srcs
   prc
   raw
   raw-fmt
+  rso
   s1-fmt
   s2-fmt
   s3-fmt
diff --git a/src/Makefile.am b/src/Makefile.am
index d5f6d125..ed547cd2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -121,7 +121,7 @@ libsox_la_SOURCES += raw-fmt.c s1-fmt.c s2-fmt.c s3-fmt.c \
   lu-fmt.c 8svx.c aiff-fmt.c aifc-fmt.c au.c avr.c cdr.c cvsd-fmt.c \
   dvms-fmt.c dat.c hcom.c htk.c maud.c prc.c sf.c smp.c \
   sounder.c soundtool.c sphere.c tx16w.c voc.c vox-fmt.c ima-fmt.c adpcm.c 
adpcm.h \
-  ima_rw.c ima_rw.h wav.c wve.c xa.c nulfile.c f4-fmt.c f8-fmt.c gsrt.c
+  ima_rw.c ima_rw.h wav.c wve.c xa.c nulfile.c f4-fmt.c f8-fmt.c gsrt.c rso.c
 
 libsox_la_LIBADD += @GSM_LIBS@ @LIBGSM_LIBADD@
 libsox_la_LIBADD += @LPC10_LIBS@ @LIBLPC10_LIBADD@
diff --git a/src/formats.c b/src/formats.c
index f3efe764..a2e2da51 100644
--- a/src/formats.c
+++ b/src/formats.c
@@ -88,6 +88,8 @@ static char const * auto_detect_format(sox_format_t * ft, 
char const * ext)
   CHECK(sf    , 0, 0, ""     , 0,  4, "\144\243\004\0")
   CHECK(sox   , 0, 0, ""     , 0,  4, ".SoX")
   CHECK(sox   , 0, 0, ""     , 0,  4, "XoS.")
+  CHECK(rso   , 0, 0, ""     , 0,  2, "\x01\x00")
+  CHECK(rso   , 0, 0, ""     , 0,  2, "\x01\x01")
 
   if (ext && !strcasecmp(ext, "snd"))
   CHECK(sndr  , 7, 1, ""     , 0,  2, "\0")
diff --git a/src/formats.h b/src/formats.h
index a42ce270..e8341ac0 100644
--- a/src/formats.h
+++ b/src/formats.h
@@ -62,6 +62,7 @@
   FORMAT(wav)
   FORMAT(wve)
   FORMAT(xa)
+  FORMAT(rso)
 
 /*--------------------- Plugin or static format handlers 
---------------------*/
 
diff --git a/src/rso.c b/src/rso.c
new file mode 100644
index 00000000..8332b290
--- /dev/null
+++ b/src/rso.c
@@ -0,0 +1,235 @@
+/* LEGO® MINDSTORMS® NXT RSO and EV3 RSF sound format
+ * (c) 2019 Jakub Vanek <linuxtar...@gmail.com>
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or (at
+ * your option) any later version.
+ *
+ * This library 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 Lesser
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include "sox_i.h"
+#include "adpcms.h"
+#include "sox.h"
+#include "stdio.h"
+
+typedef struct {
+    adpcm_io_t adpcm;
+    sox_bool is_adpcm;
+} priv_t;
+
+#define RSO_MAGIC_U8     0x0100
+#define RSO_MAGIC_ADPCM  0x0101
+#define RSO_LENGTH_EMPTY 0x0000
+#define RSO_MODE_DEFAULT 0x0000
+
+enum {
+    LENGTH_ERROR = -1,
+    LENGTH_READY = 0,
+    LENGTH_MISSING = 1
+};
+
+static int lsx_rso_calc_length(sox_format_t *ft, priv_t *priv, uint16_t 
*for_header) {
+    sox_uint64_t samples = ft->olength ? ft->olength : ft->signal.length;
+    sox_uint64_t length = 0;
+
+    if (samples) {
+        if (priv->is_adpcm) {
+            length = (samples + 1) / 2;
+        } else {
+            length = samples;
+        }
+        if (length > 65535) {
+            return LENGTH_ERROR;
+        } else {
+            *for_header = (uint16_t)length;
+            return LENGTH_READY;
+        }
+    } else {
+        return LENGTH_MISSING;
+    }
+}
+
+static int lsx_rso_startread(sox_format_t *ft) {
+    priv_t *priv = (priv_t*) ft->priv;
+    uint16_t hdr_magic = 0;
+    uint16_t hdr_bytes = 0;
+    uint16_t hdr_rate = 0;
+    uint16_t hdr_mode = 0;
+
+    sox_encoding_t sample_type;
+    uint64_t sample_count;
+    unsigned bits_per_sample;
+    int rval;
+
+    if (lsx_readw(ft, &hdr_magic) != SOX_SUCCESS ||
+        lsx_readw(ft, &hdr_bytes) != SOX_SUCCESS ||
+        lsx_readw(ft, &hdr_rate) != SOX_SUCCESS ||
+        lsx_readw(ft, &hdr_mode) != SOX_SUCCESS) {
+        return SOX_EOF;
+    }
+
+    if (hdr_magic == RSO_MAGIC_U8) {
+        priv->is_adpcm = sox_false;
+
+        sample_count = hdr_bytes * 1;
+        sample_type = SOX_ENCODING_UNSIGNED;
+        bits_per_sample = 8;
+
+    } else if (hdr_magic == RSO_MAGIC_ADPCM) {
+        priv->is_adpcm = sox_true;
+
+        sample_count = hdr_bytes * 2;
+        sample_type = SOX_ENCODING_IMA_ADPCM;
+        bits_per_sample = 4;
+
+    } else {
+        lsx_fail_errno(ft, SOX_EHDR, "`%s': unknown sample format", 
ft->filename);
+        return SOX_EOF;
+    }
+
+    rval = lsx_check_read_params(ft,
+                                 1 /* channels */,
+                                 1.0 * hdr_rate,
+                                 sample_type,
+                                 bits_per_sample,
+                                 sample_count,
+                                 sox_true /* verify length */);
+    if (rval != SOX_SUCCESS) {
+        return rval;
+    }
+
+    if (priv->is_adpcm) {
+        return lsx_adpcm_ima_start(ft, &priv->adpcm);
+    } else {
+        return lsx_rawstartread(ft);
+    }
+}
+
+static size_t lsx_rso_read(sox_format_t *ft, sox_sample_t *buf, size_t len) {
+    priv_t *priv = (priv_t*) ft->priv;
+
+    if (priv->is_adpcm) {
+        return lsx_adpcm_read(ft, &priv->adpcm, buf, len);
+    } else {
+        return lsx_rawread(ft, buf, len);
+    }
+}
+
+static int lsx_rso_stopread(sox_format_t *ft) {
+    priv_t *priv = (priv_t*) ft->priv;
+
+    if (priv->is_adpcm) {
+        return lsx_adpcm_stopread(ft, &priv->adpcm);
+    }
+
+    return SOX_SUCCESS;
+}
+
+
+static int lsx_rso_startwrite(sox_format_t *ft) {
+    priv_t *priv = (priv_t*) ft->priv;
+    uint16_t magic;
+
+    if (ft->encoding.encoding == SOX_ENCODING_UNSIGNED) {
+        priv->is_adpcm = sox_false;
+        magic = RSO_MAGIC_U8;
+
+    } else if (ft->encoding.encoding == SOX_ENCODING_IMA_ADPCM) {
+        priv->is_adpcm = sox_true;
+        magic = RSO_MAGIC_ADPCM;
+
+    } else {
+        lsx_fail_errno(ft, SOX_EFMT, "`%s': sample encoding is not supported", 
ft->filename);
+        return SOX_EOF;
+    }
+
+    if (lsx_writew(ft, magic)                     != SOX_SUCCESS ||
+        lsx_writew(ft, RSO_LENGTH_EMPTY)          != SOX_SUCCESS ||
+        lsx_writew(ft, (uint16_t)ft->signal.rate) != SOX_SUCCESS ||
+        lsx_writew(ft, RSO_MODE_DEFAULT)          != SOX_SUCCESS) {
+        return SOX_EOF;
+    }
+
+    ft->data_start = lsx_tell(ft);
+
+    if (priv->is_adpcm) {
+        return lsx_adpcm_ima_start(ft, &priv->adpcm);
+    } else {
+        return lsx_rawstartwrite(ft);
+    }
+}
+
+static size_t lsx_rso_write(sox_format_t *ft, const sox_sample_t *buf, size_t 
len) {
+    priv_t *priv = (priv_t*) ft->priv;
+
+    if (priv->is_adpcm) {
+        return lsx_adpcm_write(ft, &priv->adpcm, buf, len);
+    } else {
+        return lsx_rawwrite(ft, buf, len);
+    }
+}
+
+static int lsx_rso_stopwrite(sox_format_t *ft) {
+    priv_t *priv = (priv_t*) ft->priv;
+    off_t target = (off_t)ft->data_start - 6;
+    uint16_t hdr_bytes = 0;
+
+    if (priv->is_adpcm) {
+        if (lsx_adpcm_stopwrite(ft, &priv->adpcm) != SOX_SUCCESS) {
+            lsx_warn("IMA ADPCM stop failed");
+            return SOX_EOF;
+        }
+    }
+
+    if (ft->seekable) {
+        int rval = lsx_rso_calc_length(ft, priv, &hdr_bytes);
+
+        if (rval == LENGTH_ERROR) {
+            lsx_warn("`%s': output file is too long, length header will be 
truncated", ft->filename);
+            hdr_bytes = 0xFFFF;
+
+        } else if (rval == LENGTH_MISSING) {
+            lsx_warn("`%s': file length not available, file header will 
indicate zero length", ft->filename);
+            hdr_bytes = 0x0000;
+        }
+
+        if (lsx_seeki(ft, target, SEEK_SET)  != SOX_SUCCESS ||
+            lsx_writew(ft, hdr_bytes)        != SOX_SUCCESS) {
+            lsx_warn("`%s': seek or header write failed", ft->filename);
+            return SOX_EOF;
+        }
+    } else {
+        lsx_warn("`%s': seeking is not possible, the output file will have 
broken length header", ft->filename);
+    }
+
+    return SOX_SUCCESS;
+}
+
+LSX_FORMAT_HANDLER(rso)
+{
+  static char const * const names[] = {"rso", "rsf", NULL};
+  static unsigned const write_encodings[] = {
+      SOX_ENCODING_UNSIGNED, 8, 0,
+//    SOX_ENCODING_IMA_ADPCM, 4, 0, // playback broken on EV3?
+      0};
+  static sox_rate_t const sample_rates[] = {
+      8000.0, 0.0
+  };
+  static sox_format_handler_t handler = {SOX_LIB_VERSION_CODE,
+    "LEGO(R) MINDSTORMS(R) NXT RSO / EV3 RSF", names,
+    SOX_FILE_MONO | SOX_FILE_BIG_END,
+    lsx_rso_startread, lsx_rso_read, lsx_rso_stopread,
+    lsx_rso_startwrite, lsx_rso_write, lsx_rso_stopwrite,
+    lsx_rawseek, write_encodings, sample_rates, sizeof(priv_t)
+  };
+  return &handler;
+}
-- 
2.17.1




_______________________________________________
SoX-devel mailing list
SoX-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/sox-devel

Reply via email to