1
0
mirror of https://github.com/cookiengineer/audacity synced 2026-02-16 07:47:58 +01:00
Files
audacity/src/import/ImportFLAC.cpp
2010-04-04 08:19:53 +00:00

558 lines
15 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
ImportFLAC.cpp
Copyright 2004 Sami Liedes
Leland Lucius
Based on ImportPCM.cpp by Dominic Mazzoni
Licensed under the GNU General Public License v2 or later
*//****************************************************************//**
\class FLACImportFileHandle
\brief An ImportFileHandle for FLAC data
*//****************************************************************//**
\class FLACImportPlugin
\brief An ImportPlugin for FLAC data
*//*******************************************************************/
// For compilers that support precompilation, includes "wx/wx.h".
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
// Include your minimal set of headers here, or wx.h
#include <wx/window.h>
#endif
#include <wx/defs.h>
#include <wx/intl.h> // needed for _("translated stings") even if we
// don't have libflac available
#include "../Audacity.h"
#include "Import.h"
#include "ImportPlugin.h"
#include "../Tags.h"
#define FLAC_HEADER "fLaC"
#define DESC _("FLAC files")
static const wxChar *exts[] =
{
wxT("flac"),
wxT("flc")
};
#ifndef USE_LIBFLAC
void GetFLACImportPlugin(ImportPluginList *importPluginList,
UnusableImportPluginList *unusableImportPluginList)
{
UnusableImportPlugin* flacIsUnsupported =
new UnusableImportPlugin(DESC, wxArrayString(WXSIZEOF(exts), exts));
unusableImportPluginList->Append(flacIsUnsupported);
}
#else /* USE_LIBFLAC */
#include "../Internat.h"
#include "ImportFLAC.h"
#include <wx/string.h>
#include <wx/utils.h>
#include <wx/file.h>
#include <wx/ffile.h>
#include "FLAC++/decoder.h"
#include "../FileFormats.h"
#include "../Prefs.h"
#include "../WaveTrack.h"
#include "ImportPlugin.h"
#include "../ondemand/ODDecodeFlacTask.h"
#include "../ondemand/ODManager.h"
#ifdef USE_LIBID3TAG
extern "C" {
#include <id3tag.h>
}
#endif
/* FLACPP_API_VERSION_CURRENT is 6 for libFLAC++ from flac-1.1.3 (see <FLAC++/export.h>) */
#if !defined FLACPP_API_VERSION_CURRENT || FLACPP_API_VERSION_CURRENT < 6
#define LEGACY_FLAC
#else
#undef LEGACY_FLAC
#endif
//#define EXPERIMENTAL_OD_FLAC
class FLACImportFileHandle;
class MyFLACFile : public FLAC::Decoder::File
{
public:
MyFLACFile(FLACImportFileHandle *handle) : mFile(handle)
{
mWasError = false;
set_metadata_ignore_all();
set_metadata_respond(FLAC__METADATA_TYPE_VORBIS_COMMENT);
set_metadata_respond(FLAC__METADATA_TYPE_STREAMINFO);
}
bool get_was_error() const
{
return mWasError;
}
private:
friend class FLACImportFileHandle;
FLACImportFileHandle *mFile;
bool mWasError;
wxArrayString mComments;
protected:
virtual FLAC__StreamDecoderWriteStatus write_callback(const FLAC__Frame *frame,
const FLAC__int32 * const buffer[]);
virtual void metadata_callback(const FLAC__StreamMetadata *metadata);
virtual void error_callback(FLAC__StreamDecoderErrorStatus status);
};
class FLACImportPlugin : public ImportPlugin
{
public:
FLACImportPlugin():
ImportPlugin(wxArrayString(WXSIZEOF(exts), exts))
{
}
~FLACImportPlugin() { }
wxString GetPluginStringID() { return wxT("libflac"); }
wxString GetPluginFormatDescription();
ImportFileHandle *Open(wxString Filename);
};
class FLACImportFileHandle : public ImportFileHandle
{
friend class MyFLACFile;
public:
FLACImportFileHandle(const wxString & name);
~FLACImportFileHandle();
bool Init();
wxString GetFileDescription();
int GetFileUncompressedBytes();
int Import(TrackFactory *trackFactory, Track ***outTracks,
int *outNumTracks, Tags *tags);
wxInt32 GetStreamCount(){ return 1; }
wxArrayString *GetStreamInfo(){ return NULL; }
void SetStreamUsage(wxInt32 StreamID, bool Use){}
private:
sampleFormat mFormat;
MyFLACFile *mFile;
wxFFile mHandle;
unsigned long mSampleRate;
unsigned long mNumChannels;
unsigned long mBitsPerSample;
FLAC__uint64 mNumSamples;
FLAC__uint64 mSamplesDone;
bool mStreamInfoDone;
int mUpdateResult;
WaveTrack **mChannels;
ODDecodeFlacTask *mDecoderTask;
};
void MyFLACFile::metadata_callback(const FLAC__StreamMetadata *metadata)
{
switch (metadata->type)
{
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
for (FLAC__uint32 i = 0; i < metadata->data.vorbis_comment.num_comments; i++) {
mComments.Add(UTF8CTOWX((char *)metadata->data.vorbis_comment.comments[i].entry));
}
break;
case FLAC__METADATA_TYPE_STREAMINFO:
mFile->mSampleRate=metadata->data.stream_info.sample_rate;
mFile->mNumChannels=metadata->data.stream_info.channels;
mFile->mBitsPerSample=metadata->data.stream_info.bits_per_sample;
mFile->mNumSamples=metadata->data.stream_info.total_samples;
if (mFile->mBitsPerSample<=16) {
if (mFile->mFormat<int16Sample) {
mFile->mFormat=int16Sample;
}
} else if (mFile->mBitsPerSample<=24) {
if (mFile->mFormat<int24Sample) {
mFile->mFormat=int24Sample;
}
} else {
mFile->mFormat=floatSample;
}
mFile->mStreamInfoDone=true;
break;
// handle the other types we do nothing with to avoid a warning
case FLAC__METADATA_TYPE_PADDING: // do nothing with padding
case FLAC__METADATA_TYPE_APPLICATION: // no idea what to do with this
case FLAC__METADATA_TYPE_SEEKTABLE: // don't need a seektable here
case FLAC__METADATA_TYPE_CUESHEET: // convert this to labels?
case FLAC__METADATA_TYPE_PICTURE: // ignore pictures
case FLAC__METADATA_TYPE_UNDEFINED: // do nothing with this either
break;
}
}
void MyFLACFile::error_callback(FLAC__StreamDecoderErrorStatus status)
{
mWasError = true;
/*
switch (status)
{
case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC:
wxPrintf(wxT("Flac Error: Lost sync\n"));
break;
case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH:
wxPrintf(wxT("Flac Error: Crc mismatch\n"));
break;
case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER:
wxPrintf(wxT("Flac Error: Bad Header\n"));
break;
default:
wxPrintf(wxT("Flac Error: Unknown error code\n"));
break;
}*/
}
FLAC__StreamDecoderWriteStatus MyFLACFile::write_callback(const FLAC__Frame *frame,
const FLAC__int32 * const buffer[])
{
short *tmp=new short[frame->header.blocksize];
for (unsigned int chn=0; chn<mFile->mNumChannels; chn++) {
if (frame->header.bits_per_sample == 16) {
for (unsigned int s=0; s<frame->header.blocksize; s++) {
tmp[s]=buffer[chn][s];
}
mFile->mChannels[chn]->Append((samplePtr)tmp,
int16Sample,
frame->header.blocksize);
}
else {
mFile->mChannels[chn]->Append((samplePtr)buffer[chn],
int24Sample,
frame->header.blocksize);
}
}
delete [] tmp;
mFile->mSamplesDone += frame->header.blocksize;
mFile->mUpdateResult = mFile->mProgress->Update((wxULongLong_t) mFile->mSamplesDone, mFile->mNumSamples != 0 ? (wxULongLong_t)mFile->mNumSamples : 1);
if (mFile->mUpdateResult != eProgressSuccess)
{
return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
}
return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}
void GetFLACImportPlugin(ImportPluginList *importPluginList,
UnusableImportPluginList *unusableImportPluginList)
{
importPluginList->Append(new FLACImportPlugin);
}
wxString FLACImportPlugin::GetPluginFormatDescription()
{
return DESC;
}
ImportFileHandle *FLACImportPlugin::Open(wxString filename)
{
// First check if it really is a FLAC file
int cnt;
wxFile binaryFile;
if (!binaryFile.Open(filename)) {
return false; // File not found
}
#ifdef USE_LIBID3TAG
// Skip any ID3 tags that might be present
id3_byte_t query[ID3_TAG_QUERYSIZE];
cnt = binaryFile.Read(query, sizeof(query));
cnt = id3_tag_query(query, cnt);
binaryFile.Seek(cnt);
#endif
char buf[5];
cnt = binaryFile.Read(buf, 4);
binaryFile.Close();
if (cnt == wxInvalidOffset || strncmp(buf, FLAC_HEADER, 4) != 0) {
// File is not a FLAC file
return false;
}
// Open the file for import
FLACImportFileHandle *handle = new FLACImportFileHandle(filename);
bool success = handle->Init();
if (!success) {
delete handle;
return NULL;
}
return handle;
}
FLACImportFileHandle::FLACImportFileHandle(const wxString & name)
: ImportFileHandle(name),
mSamplesDone(0),
mStreamInfoDone(false),
mUpdateResult(eProgressSuccess)
{
mFormat = (sampleFormat)
gPrefs->Read(wxT("/SamplingRate/DefaultProjectSampleFormat"), floatSample);
mFile = new MyFLACFile(this);
}
bool FLACImportFileHandle::Init()
{
#ifdef EXPERIMENTAL_OD_FLAC
mDecoderTask=new ODDecodeFlacTask;
ODFlacDecoder* odDecoder = (ODFlacDecoder*)mDecoderTask->CreateFileDecoder(mFilename);
if(!odDecoder || !odDecoder->ReadHeader())
{
//delete the task only if it failed to read - otherwise the OD man takes care of it.
delete mDecoderTask;
return false;
}
//copy the meta data over to the class
mSampleRate=odDecoder->mSampleRate;
mNumChannels=odDecoder->mNumChannels;
mBitsPerSample=odDecoder->mBitsPerSample;
mNumSamples=odDecoder->mNumSamples;
mBitsPerSample=odDecoder->mBitsPerSample;
mFormat=odDecoder->mFormat;
mStreamInfoDone=true;
return true;
#endif
#ifdef LEGACY_FLAC
bool success = mFile->set_filename(OSINPUT(mFilename));
if (!success) {
return false;
}
mFile->set_metadata_respond(FLAC__METADATA_TYPE_STREAMINFO);
mFile->set_metadata_respond(FLAC__METADATA_TYPE_VORBIS_COMMENT);
FLAC::Decoder::File::State state = mFile->init();
if (state != FLAC__FILE_DECODER_OK) {
return false;
}
#else
if (!mHandle.Open(mFilename, wxT("rb"))) {
return false;
}
// Even though there is an init() method that takes a filename, use the one that
// takes a file handle because wxWidgets can open a file with a Unicode name and
// libflac can't (under Windows).
//
// Responsibility for closing the file is passed to libflac.
// (it happens when mFile->finish() is called)
bool result = mFile->init(mHandle.fp())?true:false;
mHandle.Detach();
if (result != FLAC__STREAM_DECODER_INIT_STATUS_OK) {
return false;
}
#endif
mFile->process_until_end_of_metadata();
#ifdef LEGACY_FLAC
state = mFile->get_state();
if (state != FLAC__FILE_DECODER_OK) {
return false;
}
#else
// not necessary to check state, error callback will catch errors, but here's how:
if (mFile->get_state() > FLAC__STREAM_DECODER_READ_FRAME) {
return false;
}
#endif
if (!mFile->is_valid() || mFile->get_was_error()) {
// This probably is not a FLAC file at all
return false;
}
return true;
}
wxString FLACImportFileHandle::GetFileDescription()
{
return DESC;
}
int FLACImportFileHandle::GetFileUncompressedBytes()
{
// TODO: Get Uncompressed byte count.
return 0;
}
int FLACImportFileHandle::Import(TrackFactory *trackFactory,
Track ***outTracks,
int *outNumTracks,
Tags *tags)
{
wxASSERT(mStreamInfoDone);
CreateProgress();
mChannels = new WaveTrack *[mNumChannels];
unsigned long c;
for (c = 0; c < mNumChannels; c++) {
mChannels[c] = trackFactory->NewWaveTrack(mFormat, mSampleRate);
if (mNumChannels == 2) {
switch (c) {
case 0:
mChannels[c]->SetChannel(Track::LeftChannel);
mChannels[c]->SetLinked(true);
break;
case 1:
mChannels[c]->SetChannel(Track::RightChannel);
break;
}
}
else {
mChannels[c]->SetChannel(Track::MonoChannel);
}
}
//Start OD
bool useOD = false;
#ifdef EXPERIMENTAL_OD_FLAC
useOD=true;
#endif
#ifdef LEGACY_FLAC
bool res = (mFile->process_until_end_of_file() != 0);
#else
bool res = true;
if(!useOD)
res = (mFile->process_until_end_of_stream() != 0);
#endif
//add the task to the ODManager
if(useOD)
{
sampleCount fileTotalFrames = mNumSamples;
sampleCount maxBlockSize = mChannels[0]->GetMaxBlockSize();
for (sampleCount i = 0; i < fileTotalFrames; i += maxBlockSize) {
sampleCount blockLen = maxBlockSize;
if (i + blockLen > fileTotalFrames)
blockLen = fileTotalFrames - i;
for (c = 0; c < mNumChannels; c++)
mChannels[c]->AppendCoded(mFilename, i, blockLen, c,ODTask::eODFLAC);
mUpdateResult = mProgress->Update(i, fileTotalFrames);
if (mUpdateResult != eProgressSuccess)
break;
}
bool moreThanStereo = mNumChannels>2;
for (c = 0; c < mNumChannels; c++)
{
mDecoderTask->AddWaveTrack(mChannels[c]);
if(moreThanStereo)
{
//if we have 3 more channels, they get imported on seperate tracks, so we add individual tasks for each.
ODManager::Instance()->AddNewTask(mDecoderTask);
mDecoderTask=new ODDecodeFlacTask; //TODO: see if we need to use clone to keep the metadata.
}
}
//if we have mono or a linked track (stereo), we add ONE task for the one linked wave track
if(!moreThanStereo)
ODManager::Instance()->AddNewTask(mDecoderTask);
}
//END OD
if (mUpdateResult == eProgressFailed || mUpdateResult == eProgressCancelled) {
for(c = 0; c < mNumChannels; c++) {
delete mChannels[c];
}
delete[] mChannels;
return mUpdateResult;
}
*outNumTracks = mNumChannels;
*outTracks = new Track *[mNumChannels];
for (c = 0; c < mNumChannels; c++) {
mChannels[c]->Flush();
(*outTracks)[c] = mChannels[c];
}
delete[] mChannels;
tags->Clear();
size_t cnt = mFile->mComments.GetCount();
for (c = 0; c < cnt; c++) {
wxString name = mFile->mComments[c].BeforeFirst(wxT('='));
wxString value = mFile->mComments[c].AfterFirst(wxT('='));
if (name.Upper() == wxT("DATE") && !tags->HasTag(TAG_YEAR)) {
long val;
if (value.Length() == 4 && value.ToLong(&val)) {
name = TAG_YEAR;
}
}
tags->SetTag(name, value);
}
return mUpdateResult;
}
FLACImportFileHandle::~FLACImportFileHandle()
{
//don't delete mFile if we are using OD.
#ifndef EXPERIMENTAL_OD_FLAC
mFile->finish();
delete mFile;
#endif
}
#endif /* USE_LIBFLAC */