Version 4 of my patch to add DSF support to the DSDIFF
decoder plugin.

This time I have taken a different approach and created a new
read_metadata function specific for reading DSF files. This saves an
indent (and for me a lot of indent nightmares) and also useful for
splitting the DSF and DFF decoders later on.

There are still a few lines which exceed the 80 character width limit by
a few chars. I was not able to stay within the limit and create (for me)
readable code.

Jurgen

diff --git a/doc/user.xml b/doc/user.xml
index cd36528..46d9c11 100644
--- a/doc/user.xml
+++ b/doc/user.xml
@@ -782,7 +782,7 @@ systemctl start mpd.socket</programlisting>
         <title><varname>dsdiff</varname></title>
 
         <para>
-          Decodes DFF files containing DSDIFF data (e.g. SACD rips).
+          Decodes DFF and DSF files containing DSDIFF data (e.g. SACD rips).
         </para>
 
         <informaltable>
diff --git a/src/decoder/dsdiff_decoder_plugin.c b/src/decoder/dsdiff_decoder_plugin.c
index ae42002..b9ba4ae 100644
--- a/src/decoder/dsdiff_decoder_plugin.c
+++ b/src/decoder/dsdiff_decoder_plugin.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2003-2011 The Music Player Daemon Project
+ * Copyright (C) 2003-2012 The Music Player Daemon Project
  * http://www.musicpd.org
  *
  * This program is free software; you can redistribute it and/or modify
@@ -19,9 +19,12 @@
 
 /* \file
  *
- * This plugin decodes DSDIFF data (SACD) embedded in DFF files.  It
- * was modeled after the specification found here:
+ * This plugin decodes DSDIFF data (SACD) embedded in DFF and DSF files.
+ * The DFF code was modeled after the specification found here:
  * http://www.sonicstudio.com/pdf/dsd/DSDIFF_1.5_Spec.pdf
+ *
+ * The DSF code was created using the specification found here:
+ * http://dsd-guide.com/sonys-dsf-file-format-spec
  */
 
 #include "config.h"
@@ -53,10 +56,57 @@ struct dsdiff_chunk_header {
 
 struct dsdiff_metadata {
 	unsigned sample_rate, channels;
+	bool fileisdff;
+	bool bitreverse;
+	uint64_t chunk_size;
 };
 
 static bool lsbitfirst;
 
+
+struct dsf_header {
+	/** DSF header id: "DSD " */
+	struct dsdiff_id id;
+	/** DSD chunk size, including id = 28 */
+	uint32_t size_low, size_high;
+	/** Total file size */
+	uint32_t fsize_low, fsize_high;
+	/** Pointer to id3v2 metadata, should be at the end of the file */
+	uint32_t pmeta_low, pmeta_high;
+};
+/** DSF file fmt chunk */
+struct dsf_fmt_chunk {
+
+	/** id: "fmt " */
+	struct dsdiff_id id;
+	/** fmt chunk size, including id, normally 52 */
+	uint32_t size_low, size_high;
+	/** Version of this format = 1 */
+	uint32_t version;
+	/** 0: DSD raw */
+	uint32_t formatid;
+	/** Channel Type, 1 = mono, 2 = stereo, 3 = 3 channels, etc */
+	uint32_t channeltype;
+	/** Channel number, 1 = mono, 2 = stereo, ... 6 = 6 channels */
+	uint32_t channelnum;
+	/** Sample frequency: 2822400, 5644800 */
+	uint32_t sample_freq;
+	/** Bits per sample 1 or 8 */
+	uint32_t bitssample;
+	/** Sample count per channel in bytes */
+	uint32_t scnt_low, scnt_high;
+	/** Block size per channel = 4096 */
+	uint32_t block_size;
+	/** Reserved, should be all zero */
+	uint32_t reserved;
+};
+
+struct dsf_data_chunk {
+	struct dsdiff_id id;
+	/** "data" chunk size, includes header (id+size) */
+	uint32_t size_low, size_high;
+};
+
 static bool
 dsdiff_init(const struct config_param *param)
 {
@@ -224,7 +274,7 @@ dsdiff_read_prop_snd(struct decoder *decoder, struct input_stream *is,
 				return false;
 
 			if (!dsdiff_id_equals(&type, "DSD "))
-				/* only uincompressed DSD audio data
+				/* only uncompressed DSD audio data
 				   is implemented */
 				return false;
 		} else {
@@ -278,28 +328,107 @@ dsdiff_read_metadata(struct decoder *decoder, struct input_stream *is,
 		return false;
 
 	while (true) {
-		if (!dsdiff_read_chunk_header(decoder, is, chunk_header))
+		if (!dsdiff_read_chunk_header(decoder, is,
+					      chunk_header))
 			return false;
 
 		if (dsdiff_id_equals(&chunk_header->id, "PROP")) {
 			if (!dsdiff_read_prop(decoder, is, metadata,
 					      chunk_header))
-				return false;
+					return false;
 		} else if (dsdiff_id_equals(&chunk_header->id, "DSD ")) {
-			/* done with metadata */
+			/* done with metadata, mark as DFF */
+			metadata->fileisdff = true;
 			return true;
 		} else {
 			/* ignore unknown chunk */
-
-			uint64_t chunk_size = dsdiff_chunk_size(chunk_header);
+			uint64_t chunk_size;
+			chunk_size = dsdiff_chunk_size(chunk_header);
 			goffset chunk_end_offset = is->offset + chunk_size;
 
-			if (!dsdiff_skip_to(decoder, is, chunk_end_offset))
+			if (!dsdiff_skip_to(decoder, is,
+					    chunk_end_offset))
 				return false;
 		}
 	}
 }
 
+/**
+ * Read and parse all needed metadata chunks for DSF files.
+ */
+static bool
+dsf_read_metadata(struct decoder *decoder, struct input_stream *is,
+		     struct dsdiff_metadata *metadata)
+{
+	/* Reset to beginning of the stream */
+	if (!dsdiff_skip_to(decoder, is, 0))
+		return false;
+
+	uint64_t chunk_size;
+	struct dsf_header dsf_header;
+	if (!dsdiff_read(decoder, is, &dsf_header, sizeof(dsf_header)) ||
+			 !dsdiff_id_equals(&dsf_header.id, "DSD "))
+		return false;
+
+	chunk_size = (((uint64_t)GUINT32_FROM_LE(dsf_header.size_high)) << 32) |
+		      ((uint64_t)GUINT32_FROM_LE(dsf_header.size_low));
+
+	if (sizeof(dsf_header) != chunk_size)
+		return false;
+
+	/* Read the 'fmt ' chunk of the DSF file */
+	struct dsf_fmt_chunk dsf_fmt_chunk;
+	if (!dsdiff_read(decoder, is, &dsf_fmt_chunk, sizeof(dsf_fmt_chunk)) ||
+	    !dsdiff_id_equals(&dsf_fmt_chunk.id, "fmt "))
+		return false;
+
+	uint64_t fmt_chunk_size;
+	fmt_chunk_size = (((uint64_t)GUINT32_FROM_LE(dsf_fmt_chunk.size_high)) << 32) |
+			  ((uint64_t)GUINT32_FROM_LE(dsf_fmt_chunk.size_low));
+
+	if (fmt_chunk_size != sizeof(dsf_fmt_chunk))
+		return false;
+
+	uint32_t samplefreq = (uint32_t)GUINT32_FROM_LE(dsf_fmt_chunk.sample_freq);
+
+	/* For now, only support version 1 of the standard, DSD raw stereo
+	   files with a sample freq of 2822400 Hz */
+
+	if (dsf_fmt_chunk.version != 1 || dsf_fmt_chunk.formatid != 0
+	    || dsf_fmt_chunk.channeltype != 2
+	    || dsf_fmt_chunk.channelnum != 2
+	    || samplefreq != 2822400 )
+		return false;
+
+	uint32_t chblksize = (uint32_t)GUINT32_FROM_LE(dsf_fmt_chunk.block_size);
+	/* According to the spec block size should always be 4096 */
+	if (chblksize != 4096)
+		return false;
+
+	/* Read the 'data' chunk of the DSF file */
+	struct dsf_data_chunk data_chunk;
+	if (!dsdiff_read(decoder, is, &data_chunk, sizeof(data_chunk)) ||
+	    !dsdiff_id_equals(&data_chunk.id, "data"))
+		return false;
+
+	/* Data size of DSF files are padded to multiple of 4096,
+	   we use the actual data size as chunk size */
+
+	uint64_t data_size;
+	data_size = (((uint64_t)GUINT32_FROM_LE(data_chunk.size_high)) << 32) |
+		     ((uint64_t)GUINT32_FROM_LE(data_chunk.size_low));
+	data_size -= sizeof(data_chunk);
+
+	metadata->chunk_size = data_size;
+	metadata->channels = (unsigned) dsf_fmt_chunk.channelnum;
+	metadata->sample_rate = samplefreq;
+
+	/* Check bits per sample format, determine if bitreverse is needed */
+	metadata->bitreverse = dsf_fmt_chunk.bitssample == 1 ?  true : false;
+	metadata->fileisdff = false;
+	return true;
+}
+
 static void
 bit_reverse_buffer(uint8_t *p, uint8_t *end)
 {
@@ -308,14 +437,49 @@ bit_reverse_buffer(uint8_t *p, uint8_t *end)
 }
 
 /**
+ * DSF data is build up of alternating 4096 blocks of DSD samples for left and
+ * right. Convert the buffer holding 1 block of 4096 DSD left samples and 1
+ * block of 4096 DSD right samples to 8k of samples in normal PCM left/right
+ * order.
+ */
+static void
+dsf_to_pcm_order(uint8_t *dest, uint8_t *scratch, size_t nrbytes)
+{
+	unsigned j=0, i=0;
+
+	for (i=0 ; i < (unsigned) nrbytes; i += 2) {
+		scratch[i] = *(dest+j);
+		j++;
+	}
+
+	j=0;
+	for (i=1; i < (unsigned) nrbytes ; i += 2) {
+		scratch[i] = *(dest+4096+j);
+		j++;
+	}
+
+	for (i=0 ; i < (unsigned) nrbytes; i++) {
+		*dest = scratch[i];
+		dest++;
+	}
+}
+
+/**
  * Decode one "DSD" chunk.
  */
 static bool
 dsdiff_decode_chunk(struct decoder *decoder, struct input_stream *is,
 		    unsigned channels,
-		    uint64_t chunk_size)
+		    uint64_t chunk_size,
+		    bool fileisdff,
+		    bool bitreverse)
 {
 	uint8_t buffer[8192];
+
+	/* Scratch buffer for DSF samples to convert to the needed
+	   normal Left/Right regime of samples */
+	uint8_t dsf_scratch_buffer[8192];
+
 	const size_t sample_size = sizeof(buffer[0]);
 	const size_t frame_size = channels * sample_size;
 	const unsigned buffer_frames = sizeof(buffer) / frame_size;
@@ -338,9 +502,12 @@ dsdiff_decode_chunk(struct decoder *decoder, struct input_stream *is,
 
 		chunk_size -= nbytes;
 
-		if (lsbitfirst)
+		if (lsbitfirst || bitreverse)
 			bit_reverse_buffer(buffer, buffer + nbytes);
 
+		if (!fileisdff)
+			dsf_to_pcm_order(buffer, dsf_scratch_buffer, nbytes);
+
 		enum decoder_command cmd =
 			decoder_data(decoder, is, buffer, nbytes, 0);
 		switch (cmd) {
@@ -370,8 +537,13 @@ dsdiff_stream_decode(struct decoder *decoder, struct input_stream *is)
 	};
 
 	struct dsdiff_chunk_header chunk_header;
+	/* First see if it is is a DFF file */
 	if (!dsdiff_read_metadata(decoder, is, &metadata, &chunk_header))
-		return;
+	{
+		/* It was not a DFF file, now check if it is a DSF file */
+		if (!dsf_read_metadata(decoder, is, &metadata))
+			return;
+	}
 
 	GError *error = NULL;
 	struct audio_format audio_format;
@@ -384,32 +556,49 @@ dsdiff_stream_decode(struct decoder *decoder, struct input_stream *is)
 	}
 
 	/* success: file was recognized */
-
 	decoder_initialized(decoder, &audio_format, false, -1);
 
-	/* every iteration of the following loop decodes one "DSD"
-	   chunk */
-
-	while (true) {
-		uint64_t chunk_size = dsdiff_chunk_size(&chunk_header);
-
-		if (dsdiff_id_equals(&chunk_header.id, "DSD ")) {
-			if (!dsdiff_decode_chunk(decoder, is,
-						 metadata.channels,
-						 chunk_size))
-				break;
-		} else {
-			/* ignore other chunks */
-
-			if (!dsdiff_skip(decoder, is, chunk_size))
+	if (!metadata.fileisdff) {
+		uint64_t chunk_size = metadata.chunk_size;
+		if (!dsdiff_decode_chunk(decoder, is,
+					 metadata.channels,
+					 chunk_size,
+					 metadata.fileisdff,
+					 metadata.bitreverse))
+			return;
+
+	} else {
+
+		/* every iteration of the following loop decodes one "DSD"
+		   chunk from a DFF file */
+
+		while (true) {
+
+			uint64_t chunk_size = dsdiff_chunk_size(&chunk_header);
+
+			if (dsdiff_id_equals(&chunk_header.id, "DSD ")) {
+				if (!dsdiff_decode_chunk(decoder, is,
+							 metadata.channels,
+							 chunk_size,
+							 metadata.fileisdff,
+						/* Set bitreverse to
+						   false for DFF files */
+							false))
+					break;
+			} else {
+				/* ignore other chunks */
+
+				if (!dsdiff_skip(decoder, is, chunk_size))
+					break;
+			}
+
+			/* read next chunk header; the first one was read by
+			   dsdiff_read_metadata() */
+
+			if (!dsdiff_read_chunk_header(decoder,
+						      is, &chunk_header))
 				break;
 		}
-
-		/* read next chunk header; the first one was read by
-		   dsdiff_read_metadata() */
-
-		if (!dsdiff_read_chunk_header(decoder, is, &chunk_header))
-			break;
 	}
 }
 
@@ -424,8 +613,13 @@ dsdiff_scan_stream(struct input_stream *is,
 	};
 
 	struct dsdiff_chunk_header chunk_header;
+	/* First check for DFF metadata */
 	if (!dsdiff_read_metadata(NULL, is, &metadata, &chunk_header))
-		return false;
+	{
+		/* It was not an DFF file, now check for DSF metadata */
+		if (!dsf_read_metadata(NULL, is, &metadata))
+			return false;
+	}
 
 	struct audio_format audio_format;
 	if (!audio_format_init_checked(&audio_format, metadata.sample_rate / 8,
@@ -440,11 +634,13 @@ dsdiff_scan_stream(struct input_stream *is,
 
 static const char *const dsdiff_suffixes[] = {
 	"dff",
+	"dsf",
 	NULL
 };
 
 static const char *const dsdiff_mime_types[] = {
 	"application/x-dff",
+	"application/x-dsf",
 	NULL
 };
 
-- 
1.7.6.5

------------------------------------------------------------------------------
Live Security Virtual Conference
Exclusive live event will cover all the ways today's security and 
threat landscape has changed and how IT managers can respond. Discussions 
will include endpoint security, mobile security and the latest in malware 
threats. http://www.accelacomm.com/jaw/sfrnl04242012/114/50122263/
_______________________________________________
Musicpd-dev-team mailing list
Musicpd-dev-team@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/musicpd-dev-team

Reply via email to