From 3c3f1b7ff27c13b817a7a94ef7605e06c93e4ff2 Mon Sep 17 00:00:00 2001 From: Jurgen Kramer Date: Sat, 28 Apr 2012 15:40:38 +0200 Subject: Add support for DSF files to DSDIFF decoder - v4 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 --- doc/user.xml | 2 +- src/decoder/dsdiff_decoder_plugin.c | 265 +++++++++++++++++++++++++++++++----- 2 files changed, 230 insertions(+), 37 deletions(-) diff --git a/doc/user.xml b/doc/user.xml index cd36528d..46d9c11c 100644 --- a/doc/user.xml +++ b/doc/user.xml @@ -782,7 +782,7 @@ systemctl start mpd.socket <varname>dsdiff</varname> - Decodes DFF files containing DSDIFF data (e.g. SACD rips). + Decodes DFF and DSF files containing DSDIFF data (e.g. SACD rips). diff --git a/src/decoder/dsdiff_decoder_plugin.c b/src/decoder/dsdiff_decoder_plugin.c index ae42002d..a7aa6017 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) { @@ -307,15 +436,47 @@ bit_reverse_buffer(uint8_t *p, uint8_t *end) *p = bit_reverse(*p); } +/** + * 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) +{ + for (unsigned i = 0, j = 0; i < (unsigned)nrbytes; i += 2) { + scratch[i] = *(dest+j); + j++; + } + + for (unsigned i = 1, j = 0; i < (unsigned) nrbytes; i += 2) { + scratch[i] = *(dest+4096+j); + j++; + } + + for (unsigned 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 +499,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 +534,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 +553,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 +610,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 +631,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 }; -- cgit v1.2.3