Merge pull request #288 from sbooth/dsf

Added DSF support
This commit is contained in:
Stephen F. Booth 2013-12-02 05:09:26 -08:00
commit d8f5937091
9 changed files with 641 additions and 1 deletions

View File

@ -26,6 +26,7 @@ include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/xm
${CMAKE_CURRENT_SOURCE_DIR}/ebml
${CMAKE_CURRENT_SOURCE_DIR}/ebml/matroska
${CMAKE_CURRENT_SOURCE_DIR}/dsf
)
if(ZLIB_FOUND)
@ -138,6 +139,8 @@ set(tag_HDRS
ebml/matroska/ebmlmatroskafile.h
ebml/matroska/ebmlmatroskaconstants.h
ebml/matroska/ebmlmatroskaaudio.h
dsf/dsffile.h
dsf/dsfproperties.h
)
set(mpeg_SRCS
@ -288,6 +291,11 @@ set(xm_SRCS
xm/xmproperties.cpp
)
set(dsf_SRCS
dsf/dsffile.cpp
dsf/dsfproperties.cpp
)
set(toolkit_SRCS
toolkit/tstring.cpp
toolkit/tstringlist.cpp
@ -320,7 +328,7 @@ set(tag_LIB_SRCS
${vorbis_SRCS} ${oggflacs_SRCS} ${mpc_SRCS} ${ape_SRCS} ${toolkit_SRCS} ${flacs_SRCS}
${wavpack_SRCS} ${speex_SRCS} ${trueaudio_SRCS} ${riff_SRCS} ${aiff_SRCS} ${wav_SRCS}
${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS}
${ebml_SRCS} ${matroska_SRCS}
${ebml_SRCS} ${matroska_SRCS} ${dsf_SRCS}
tag.cpp
tagunion.cpp
fileref.cpp

227
taglib/dsf/dsffile.cpp Normal file
View File

@ -0,0 +1,227 @@
/***************************************************************************
copyright : (C) 2013 by Stephen F. Booth
email : me@sbooth.org
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* 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 *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include <tbytevector.h>
#include <tdebug.h>
#include <id3v2tag.h>
#include <tstringlist.h>
#include <tpropertymap.h>
#include "dsffile.h"
using namespace TagLib;
// The DSF specification is located at http://dsd-guide.com/sites/default/files/white-papers/DSFFileFormatSpec_E.pdf
class DSF::File::FilePrivate
{
public:
FilePrivate() :
properties(0),
tag(0)
{
}
~FilePrivate()
{
delete properties;
delete tag;
}
long long fileSize;
long long metadataOffset;
AudioProperties *properties;
ID3v2::Tag *tag;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
DSF::File::File(FileName file, bool readProperties,
AudioProperties::ReadStyle propertiesStyle) : TagLib::File(file)
{
d = new FilePrivate;
if(isOpen())
read(readProperties, propertiesStyle);
}
DSF::File::File(IOStream *stream, bool readProperties,
AudioProperties::ReadStyle propertiesStyle) : TagLib::File(stream)
{
d = new FilePrivate;
if(isOpen())
read(readProperties, propertiesStyle);
}
DSF::File::~File()
{
delete d;
}
ID3v2::Tag *DSF::File::tag() const
{
return d->tag;
}
PropertyMap DSF::File::properties() const
{
return d->tag->properties();
}
PropertyMap DSF::File::setProperties(const PropertyMap &properties)
{
return d->tag->setProperties(properties);
}
DSF::AudioProperties *DSF::File::audioProperties() const
{
return d->properties;
}
bool DSF::File::save()
{
if(readOnly()) {
debug("DSF::File::save() -- File is read only.");
return false;
}
if(!isValid()) {
debug("DSF::File::save() -- Trying to save invalid file.");
return false;
}
// Three things must be updated: the file size, the tag data, and the metadata offset
if(d->tag->isEmpty()) {
long long newFileSize = d->metadataOffset ? d->metadataOffset : d->fileSize;
// Update the file size
if(d->fileSize != newFileSize) {
insert(ByteVector::fromUInt64LE(newFileSize), 12, 8);
d->fileSize = newFileSize;
}
// Update the metadata offset to 0 since there is no longer a tag
if(d->metadataOffset) {
insert(ByteVector::fromUInt64LE(0ULL), 20, 8);
d->metadataOffset = 0;
}
// Delete the old tag
truncate(newFileSize);
}
else {
ByteVector tagData = d->tag->render();
long long newMetadataOffset = d->metadataOffset ? d->metadataOffset : d->fileSize;
long long newFileSize = newMetadataOffset + tagData.size();
long long oldTagSize = d->fileSize - newMetadataOffset;
// Update the file size
if(d->fileSize != newFileSize) {
insert(ByteVector::fromUInt64LE(newFileSize), 12, 8);
d->fileSize = newFileSize;
}
// Update the metadata offset
if(d->metadataOffset != newMetadataOffset) {
insert(ByteVector::fromUInt64LE(newMetadataOffset), 20, 8);
d->metadataOffset = newMetadataOffset;
}
// Delete the old tag and write the new one
insert(tagData, newMetadataOffset, oldTagSize);
}
return true;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void DSF::File::read(bool readProperties, AudioProperties::ReadStyle propertiesStyle)
{
// A DSF file consists of four chunks: DSD chunk, format chunk, data chunk, and metadata chunk
// The file format is not chunked in the sense of a RIFF File, though
// DSD chunk
ByteVector chunkName = readBlock(4);
if(chunkName != "DSD ") {
debug("DSF::File::read() -- Not a DSF file.");
setValid(false);
return;
}
long long chunkSize = readBlock(8).toInt64LE(0);
// Integrity check
if(28 != chunkSize) {
debug("DSF::File::read() -- File is corrupted.");
setValid(false);
return;
}
d->fileSize = readBlock(8).toInt64LE(0);
// File is malformed or corrupted
if(d->fileSize != length()) {
debug("DSF::File::read() -- File is corrupted.");
setValid(false);
return;
}
d->metadataOffset = readBlock(8).toInt64LE(0);
// File is malformed or corrupted
if(d->metadataOffset > d->fileSize) {
debug("DSF::File::read() -- Invalid metadata offset.");
setValid(false);
return;
}
// Format chunk
chunkName = readBlock(4);
if(chunkName != "fmt ") {
debug("DSF::File::read() -- Missing 'fmt ' chunk.");
setValid(false);
return;
}
chunkSize = readBlock(8).toInt64LE(0);
d->properties = new AudioProperties(readBlock(chunkSize), propertiesStyle);
// Skip the data chunk
// A metadata offset of 0 indicates the absence of an ID3v2 tag
if(0 == d->metadataOffset)
d->tag = new ID3v2::Tag();
else
d->tag = new ID3v2::Tag(this, d->metadataOffset);
}

118
taglib/dsf/dsffile.h Normal file
View File

@ -0,0 +1,118 @@
/***************************************************************************
copyright : (C) 2013 by Stephen F. Booth
email : me@sbooth.org
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* 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 *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_DSFFILE_H
#define TAGLIB_DSFFILE_H
#include "tfile.h"
#include "id3v2tag.h"
#include "dsfproperties.h"
namespace TagLib {
//! An implementation of DSF metadata
/*!
* This is implementation of DSF metadata.
*
* This supports an ID3v2 tag as well as properties from the file.
*/
namespace DSF {
//! An implementation of TagLib::File with DSF specific methods
/*!
* This implements and provides an interface for DSF files to the
* TagLib::Tag and TagLib::AudioProperties interfaces by way of implementing
* the abstract TagLib::File API as well as providing some additional
* information specific to DSF files.
*/
class TAGLIB_EXPORT File : public TagLib::File
{
public:
/*!
* Contructs an DSF file from \a file. If \a readProperties is true the
* file's audio properties will also be read using \a propertiesStyle. If
* false, \a propertiesStyle is ignored.
*/
File(FileName file, bool readProperties = true,
AudioProperties::ReadStyle propertiesStyle = AudioProperties::Average);
/*!
* Contructs an DSF file from \a file. If \a readProperties is true the
* file's audio properties will also be read using \a propertiesStyle. If
* false, \a propertiesStyle is ignored.
*/
File(IOStream *stream, bool readProperties = true,
AudioProperties::ReadStyle propertiesStyle = AudioProperties::Average);
/*!
* Destroys this instance of the File.
*/
virtual ~File();
/*!
* Returns the Tag for this file.
*/
virtual ID3v2::Tag *tag() const;
/*!
* Implements the unified property interface -- export function.
* This method forwards to ID3v2::Tag::properties().
*/
PropertyMap properties() const;
/*!
* Implements the unified property interface -- import function.
* This method forwards to ID3v2::Tag::setProperties().
*/
PropertyMap setProperties(const PropertyMap &);
/*!
* Returns the DSF::AudioProperties for this file. If no audio properties
* were read then this will return a null pointer.
*/
virtual AudioProperties *audioProperties() const;
/*!
* Saves the file.
*/
virtual bool save();
private:
File(const File &);
File &operator=(const File &);
void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle);
class FilePrivate;
FilePrivate *d;
};
}
}
#endif

View File

@ -0,0 +1,149 @@
/***************************************************************************
copyright : (C) 2013 by Stephen F. Booth
email : me@sbooth.org
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* 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 *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#include <tstring.h>
#include <tdebug.h>
#include "dsfproperties.h"
using namespace TagLib;
class DSF::AudioProperties::PropertiesPrivate
{
public:
PropertiesPrivate() :
formatVersion(0),
formatID(0),
channelType(0),
channelNum(0),
samplingFrequency(0),
bitsPerSample(0),
sampleCount(0),
blockSizePerChannel(0),
bitrate(0),
length(0)
{
}
// Nomenclature is from DSF file format specification
uint formatVersion;
uint formatID;
uint channelType;
uint channelNum;
uint samplingFrequency;
uint bitsPerSample;
long long sampleCount;
uint blockSizePerChannel;
// Computed
uint bitrate;
uint length;
};
////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////
DSF::AudioProperties::AudioProperties(const ByteVector &data, ReadStyle style)
{
d = new PropertiesPrivate;
read(data);
}
DSF::AudioProperties::~AudioProperties()
{
delete d;
}
int DSF::AudioProperties::length() const
{
return d->length;
}
int DSF::AudioProperties::bitrate() const
{
return d->bitrate;
}
int DSF::AudioProperties::sampleRate() const
{
return d->samplingFrequency;
}
int DSF::AudioProperties::channels() const
{
return d->channelNum;
}
// DSF specific
int DSF::AudioProperties::formatVersion() const
{
return d->formatVersion;
}
int DSF::AudioProperties::formatID() const
{
return d->formatID;
}
int DSF::AudioProperties::channelType() const
{
return d->channelType;
}
int DSF::AudioProperties::bitsPerSample() const
{
return d->bitsPerSample;
}
long long DSF::AudioProperties::sampleCount() const
{
return d->sampleCount;
}
int DSF::AudioProperties::blockSizePerChannel() const
{
return d->blockSizePerChannel;
}
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////
void DSF::AudioProperties::read(const ByteVector &data)
{
d->formatVersion = data.toUInt32LE(0);
d->formatID = data.toUInt32LE(4);
d->channelType = data.toUInt32LE(8);
d->channelNum = data.toUInt32LE(12);
d->samplingFrequency = data.toUInt32LE(16);
d->bitsPerSample = data.toUInt32LE(20);
d->sampleCount = data.toInt64LE(24);
d->blockSizePerChannel = data.toUInt32LE(32);
d->bitrate = (d->samplingFrequency * d->bitsPerSample * d->channelNum) / 1000.0;
d->length = d->samplingFrequency > 0 ? d->sampleCount / d->samplingFrequency : 0;
}

View File

@ -0,0 +1,84 @@
/***************************************************************************
copyright : (C) 2013 by Stephen F. Booth
email : me@sbooth.org
***************************************************************************/
/***************************************************************************
* This library is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License version *
* 2.1 as published by the Free Software Foundation. *
* *
* 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 *
* *
* Alternatively, this file is available under the Mozilla Public *
* License Version 1.1. You may obtain a copy of the License at *
* http://www.mozilla.org/MPL/ *
***************************************************************************/
#ifndef TAGLIB_DSFPROPERTIES_H
#define TAGLIB_DSFPROPERTIES_H
#include "audioproperties.h"
namespace TagLib {
namespace DSF {
class File;
//! An implementation of audio property reading for DSF
/*!
* This reads the data from a DSF stream found in the AudioProperties
* API.
*/
class TAGLIB_EXPORT AudioProperties : public TagLib::AudioProperties
{
public:
/*!
* Create an instance of DSF::AudioProperties with the data read from the
* ByteVector \a data.
*/
AudioProperties(const ByteVector &data, ReadStyle style);
/*!
* Destroys this DSF::AudioProperties instance.
*/
virtual ~AudioProperties();
// Reimplementations.
virtual int length() const;
virtual int bitrate() const;
virtual int sampleRate() const;
virtual int channels() const;
int formatVersion() const;
int formatID() const;
int channelType() const;
int bitsPerSample() const;
long long sampleCount() const;
int blockSizePerChannel() const;
private:
AudioProperties(const AudioProperties &);
AudioProperties &operator=(const AudioProperties &);
void read(const ByteVector &data);
class PropertiesPrivate;
PropertiesPrivate *d;
};
}
}
#endif

View File

@ -51,6 +51,7 @@
#include "s3mfile.h"
#include "itfile.h"
#include "xmfile.h"
#include "dsffile.h"
using namespace TagLib;
@ -137,6 +138,8 @@ namespace
file.reset(new IT::File(fileName, readAudioProperties, audioPropertiesStyle));
else if(ext == "XM")
file.reset(new XM::File(fileName, readAudioProperties, audioPropertiesStyle));
else if(ext == "DSF")
file.reset(new DSF::File(fileName, readAudioProperties, audioPropertiesStyle));
}
return file;
@ -294,6 +297,7 @@ StringList FileRef::defaultFileExtensions()
l.append("s3m");
l.append("it");
l.append("xm");
l.append("dsf");
return l;
}

View File

@ -24,6 +24,7 @@ INCLUDE_DIRECTORIES(
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/s3m
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/it
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/xm
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/dsf
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ebml
${CMAKE_CURRENT_SOURCE_DIR}/../taglib/ebml/matroska
)
@ -67,6 +68,7 @@ SET(test_runner_SRCS
test_xm.cpp
test_mpc.cpp
test_opus.cpp
test_dsf.cpp
test_matroska.cpp
)

BIN
tests/data/empty.dsf Normal file

Binary file not shown.

48
tests/test_dsf.cpp Normal file
View File

@ -0,0 +1,48 @@
#include <cppunit/extensions/HelperMacros.h>
#include <string>
#include <stdio.h>
#include <tag.h>
#include <tbytevectorlist.h>
#include <dsffile.h>
#include "utils.h"
using namespace std;
using namespace TagLib;
class TestDSF : public CppUnit::TestFixture
{
CPPUNIT_TEST_SUITE(TestDSF);
CPPUNIT_TEST(testBasic);
CPPUNIT_TEST(testTags);
CPPUNIT_TEST_SUITE_END();
public:
void testBasic()
{
DSF::File f(TEST_FILE_PATH_C("empty.dsf"));
DSF::AudioProperties *props = f.audioProperties();
CPPUNIT_ASSERT_EQUAL(2822400, props->sampleRate());
CPPUNIT_ASSERT_EQUAL(1, props->channels());
CPPUNIT_ASSERT_EQUAL(1, props->bitsPerSample());
}
void testTags()
{
ScopedFileCopy copy("empty", ".dsf");
string newname = copy.fileName();
DSF::File *f = new DSF::File(newname.c_str());
CPPUNIT_ASSERT_EQUAL(String(""), f->tag()->artist());
f->tag()->setArtist("The Artist");
f->save();
delete f;
f = new DSF::File(newname.c_str());
CPPUNIT_ASSERT_EQUAL(String("The Artist"), f->tag()->artist());
delete f;
}
};
CPPUNIT_TEST_SUITE_REGISTRATION(TestDSF);