/* Audio File Library Copyright (C) 1998-2000, Michael Pruett Copyright (C) 2000-2001, Silicon Graphics, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA. */ /* wavewrite.c This file contains routines which facilitate writing to WAVE files. */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #ifdef HAVE_INTTYPES_H #include #endif #include "audiofile.h" #include "afinternal.h" #include "byteorder.h" #include "util.h" #include "setup.h" #include "wave.h" status _af_wave_update (AFfilehandle file); static status WriteFormat (AFfilehandle file); static status WriteFrameCount (AFfilehandle file); static status WriteMiscellaneous (AFfilehandle file); static status WriteCues (AFfilehandle file); static status WriteData (AFfilehandle file); _WAVEInfo *waveinfo_new (void) { _WAVEInfo *waveinfo = _af_malloc(sizeof (_WAVEInfo)); waveinfo->factOffset = 0; waveinfo->miscellaneousStartOffset = 0; waveinfo->totalMiscellaneousSize = 0; waveinfo->markOffset = 0; waveinfo->dataSizeOffset = 0; return waveinfo; } static status WriteFormat (AFfilehandle file) { _Track *track = NULL; u_int16_t formatTag, channelCount; u_int32_t sampleRate, averageBytesPerSecond; u_int16_t blockAlign; u_int32_t chunkSize; u_int16_t bitsPerSample; assert(file != NULL); track = _af_filehandle_get_track(file, AF_DEFAULT_TRACK); af_fwrite("fmt ", 4, 1, file->fh); switch (track->f.compressionType) { case AF_COMPRESSION_NONE: chunkSize = 16; if (track->f.sampleFormat == AF_SAMPFMT_FLOAT) { formatTag = WAVE_FORMAT_IEEE_FLOAT; } else if (track->f.sampleFormat == AF_SAMPFMT_TWOSCOMP || track->f.sampleFormat == AF_SAMPFMT_UNSIGNED) { formatTag = WAVE_FORMAT_PCM; } else { _af_error(AF_BAD_COMPTYPE, "bad sample format"); return AF_FAIL; } blockAlign = _af_format_frame_size(&track->f, AF_FALSE); bitsPerSample = 8 * _af_format_sample_size(&track->f, AF_FALSE); break; /* G.711 compression uses eight bits per sample. */ case AF_COMPRESSION_G711_ULAW: chunkSize = 18; formatTag = IBM_FORMAT_MULAW; blockAlign = track->f.channelCount; bitsPerSample = 8; break; case AF_COMPRESSION_G711_ALAW: chunkSize = 18; formatTag = IBM_FORMAT_ALAW; blockAlign = track->f.channelCount; bitsPerSample = 8; break; default: _af_error(AF_BAD_COMPTYPE, "bad compression type"); return AF_FAIL; } chunkSize = HOST_TO_LENDIAN_INT32(chunkSize); af_fwrite(&chunkSize, 4, 1, file->fh); formatTag = HOST_TO_LENDIAN_INT16(formatTag); af_fwrite(&formatTag, 2, 1, file->fh); formatTag = LENDIAN_TO_HOST_INT16(formatTag); channelCount = track->f.channelCount; channelCount = HOST_TO_LENDIAN_INT16(channelCount); af_fwrite(&channelCount, 2, 1, file->fh); sampleRate = track->f.sampleRate; sampleRate = HOST_TO_LENDIAN_INT32(sampleRate); af_fwrite(&sampleRate, 4, 1, file->fh); averageBytesPerSecond = track->f.sampleRate * _af_format_frame_size(&track->f, AF_FALSE); averageBytesPerSecond = HOST_TO_LENDIAN_INT32(averageBytesPerSecond); af_fwrite(&averageBytesPerSecond, 4, 1, file->fh); blockAlign = _af_format_frame_size(&track->f, AF_FALSE); blockAlign = HOST_TO_LENDIAN_INT16(blockAlign); af_fwrite(&blockAlign, 2, 1, file->fh); bitsPerSample = HOST_TO_LENDIAN_INT16(bitsPerSample); af_fwrite(&bitsPerSample, 2, 1, file->fh); if (track->f.compressionType == AF_COMPRESSION_G711_ULAW || track->f.compressionType == AF_COMPRESSION_G711_ALAW) { u_int16_t zero = 0; af_fwrite(&zero, 2, 1, file->fh); } return AF_SUCCEED; } static status WriteFrameCount (AFfilehandle file) { _Track *track = NULL; _WAVEInfo *waveinfo = NULL; u_int32_t factSize = 4; u_int32_t totalFrameCount; assert(file != NULL); track = _af_filehandle_get_track(file, AF_DEFAULT_TRACK); waveinfo = (_WAVEInfo *) file->formatSpecific; /* We only write the fact chunk for compressed audio. */ if (track->f.compressionType == AF_COMPRESSION_NONE) return AF_SUCCEED; /* If the offset for the fact chunk hasn't been set yet, set it to the file's current position. */ if (waveinfo->factOffset == 0) waveinfo->factOffset = af_ftell(file->fh); else af_fseek(file->fh, waveinfo->factOffset, SEEK_SET); af_fwrite("fact", 4, 1, file->fh); factSize = HOST_TO_LENDIAN_INT32(factSize); af_fwrite(&factSize, 4, 1, file->fh); totalFrameCount = HOST_TO_LENDIAN_INT32(track->totalfframes); af_fwrite(&totalFrameCount, 4, 1, file->fh); return AF_SUCCEED; } static status WriteData (AFfilehandle file) { _Track *track; u_int32_t chunkSize; _WAVEInfo *waveinfo; assert(file); waveinfo = file->formatSpecific; track = _af_filehandle_get_track(file, AF_DEFAULT_TRACK); af_fwrite("data", 4, 1, file->fh); waveinfo->dataSizeOffset = af_ftell(file->fh); chunkSize = _af_format_frame_size(&track->f, AF_FALSE) * track->totalfframes; chunkSize = HOST_TO_LENDIAN_INT32(chunkSize); af_fwrite(&chunkSize, 4, 1, file->fh); track->fpos_first_frame = af_ftell(file->fh); return AF_SUCCEED; } status _af_wave_update (AFfilehandle file) { _Track *track; _WAVEInfo *wave = (_WAVEInfo *) file->formatSpecific; track = _af_filehandle_get_track(file, AF_DEFAULT_TRACK); if (track->fpos_first_frame != 0) { u_int32_t dataLength, fileLength; /* Update the frame count chunk if present. */ WriteFrameCount(file); /* Update the length of the data chunk. */ af_fseek(file->fh, wave->dataSizeOffset, SEEK_SET); /* We call _af_format_frame_size to calculate the frame size of normal PCM data or compressed data. */ dataLength = (u_int32_t) track->totalfframes * _af_format_frame_size(&track->f, AF_FALSE); dataLength = HOST_TO_LENDIAN_INT32(dataLength); af_fwrite(&dataLength, 4, 1, file->fh); /* Update the length of the RIFF chunk. */ fileLength = (u_int32_t) af_flength(file->fh); fileLength -= 8; fileLength = HOST_TO_LENDIAN_INT32(fileLength); af_fseek(file->fh, 4, SEEK_SET); af_fwrite(&fileLength, 4, 1, file->fh); } /* Write the actual data that was set after initializing the miscellaneous IDs. The size of the data will be unchanged. */ WriteMiscellaneous(file); /* Write the new positions; the size of the data will be unchanged. */ WriteCues(file); return AF_SUCCEED; } /* Convert an Audio File Library miscellaneous type to a WAVE type. */ static status misc_type_to_wave (int misctype, u_int32_t *miscid) { if (misctype == AF_MISC_AUTH) memcpy(miscid, "IART", 4); else if (misctype == AF_MISC_NAME) memcpy(miscid, "INAM", 4); else if (misctype == AF_MISC_COPY) memcpy(miscid, "ICOP", 4); else if (misctype == AF_MISC_ICMT) memcpy(miscid, "ICMT", 4); else if (misctype == AF_MISC_ICRD) memcpy(miscid, "ICRD", 4); else if (misctype == AF_MISC_ISFT) memcpy(miscid, "ISFT", 4); else return AF_FAIL; return AF_SUCCEED; } status WriteMiscellaneous (AFfilehandle filehandle) { _WAVEInfo *wave = (_WAVEInfo *) filehandle->formatSpecific; if (filehandle->miscellaneousCount != 0) { int i; u_int32_t miscellaneousBytes; u_int32_t chunkSize; /* Start at 12 to account for 'LIST', size, and 'INFO'. */ miscellaneousBytes = 12; /* Then calculate the size of the whole INFO chunk. */ for (i=0; imiscellaneousCount; i++) { u_int32_t miscid; /* Skip miscellaneous data of an unsupported type. */ if (misc_type_to_wave(filehandle->miscellaneous[i].type, &miscid) == AF_FAIL) continue; /* Account for miscellaneous type and size. */ miscellaneousBytes += 8; miscellaneousBytes += filehandle->miscellaneous[i].size; /* Add a pad byte if necessary. */ if (filehandle->miscellaneous[i].size % 2 != 0) miscellaneousBytes++; assert(miscellaneousBytes % 2 == 0); } if (wave->miscellaneousStartOffset == 0) wave->miscellaneousStartOffset = af_ftell(filehandle->fh); else af_fseek(filehandle->fh, wave->miscellaneousStartOffset, SEEK_SET); wave->totalMiscellaneousSize = miscellaneousBytes; /* Write the data. On the first call to this function (from _af_wave_write_init), the data won't be available, af_fseek is used to reserve space until the data has been provided. On subseuent calls to this function (from _af_wave_update), the data will really be written. */ /* Write 'LIST'. */ af_fwrite("LIST", 4, 1, filehandle->fh); /* Write the size of the following chunk. */ chunkSize = miscellaneousBytes-8; chunkSize = HOST_TO_LENDIAN_INT32(chunkSize); af_fwrite(&chunkSize, sizeof (u_int32_t), 1, filehandle->fh); /* Write 'INFO'. */ af_fwrite("INFO", 4, 1, filehandle->fh); /* Write each miscellaneous chunk. */ for (i=0; imiscellaneousCount; i++) { u_int32_t miscsize = HOST_TO_LENDIAN_INT32(filehandle->miscellaneous[i].size); u_int32_t miscid = 0; /* Skip miscellaneous data of an unsupported type. */ if (misc_type_to_wave(filehandle->miscellaneous[i].type, &miscid) == AF_FAIL) continue; af_fwrite(&miscid, 4, 1, filehandle->fh); af_fwrite(&miscsize, 4, 1, filehandle->fh); if (filehandle->miscellaneous[i].buffer != NULL) { u_int8_t zero = 0; af_fwrite(filehandle->miscellaneous[i].buffer, filehandle->miscellaneous[i].size, 1, filehandle->fh); /* Pad if necessary. */ if ((filehandle->miscellaneous[i].size%2) != 0) af_fwrite(&zero, 1, 1, filehandle->fh); } else { int size; size = filehandle->miscellaneous[i].size; /* Pad if necessary. */ if ((size % 2) != 0) size++; af_fseek(filehandle->fh, size, SEEK_CUR); } } } return AF_SUCCEED; } static status WriteCues (AFfilehandle file) { int i, *markids, markCount; u_int32_t numCues, cueChunkSize, listChunkSize; _Track *track = &file->tracks[0]; _WAVEInfo *wave; assert(file); markCount = afGetMarkIDs(file, AF_DEFAULT_TRACK, NULL); if (markCount == 0) return AF_SUCCEED; wave = file->formatSpecific; if (wave->markOffset == 0) wave->markOffset = af_ftell(file->fh); else af_fseek(file->fh, wave->markOffset, SEEK_SET); af_fwrite("cue ", 4, 1, file->fh); /* The cue chunk consists of 4 bytes for the number of cue points followed by 24 bytes for each cue point record. */ cueChunkSize = 4 + markCount * 24; cueChunkSize = HOST_TO_LENDIAN_INT32(cueChunkSize); af_fwrite(&cueChunkSize, sizeof (u_int32_t), 1, file->fh); numCues = HOST_TO_LENDIAN_INT32(markCount); af_fwrite(&numCues, sizeof (u_int32_t), 1, file->fh); markids = _af_calloc(markCount, sizeof (int)); assert(markids != NULL); afGetMarkIDs(file, AF_DEFAULT_TRACK, markids); /* Write each marker to the file. */ for (i=0; i < markCount; i++) { u_int32_t identifier, position, chunkStart, blockStart; u_int32_t sampleOffset; AFframecount markposition; identifier = HOST_TO_LENDIAN_INT32(markids[i]); af_fwrite(&identifier, sizeof (u_int32_t), 1, file->fh); position = HOST_TO_LENDIAN_INT32(i); af_fwrite(&position, sizeof (u_int32_t), 1, file->fh); /* For now the RIFF id is always the first data chunk. */ af_fwrite("data", 4, 1, file->fh); /* For an uncompressed WAVE file which contains only one data chunk, chunkStart and blockStart are zero. */ chunkStart = 0; af_fwrite(&chunkStart, sizeof (u_int32_t), 1, file->fh); blockStart = 0; af_fwrite(&blockStart, sizeof (u_int32_t), 1, file->fh); markposition = afGetMarkPosition(file, AF_DEFAULT_TRACK, markids[i]); /* Sample offsets are stored in the WAVE file as frames. */ sampleOffset = HOST_TO_LENDIAN_INT32(markposition); af_fwrite(&sampleOffset, sizeof (u_int32_t), 1, file->fh); } /* Now write the cue names which is in a master list chunk with a subchunk for each cue's name. */ listChunkSize = 4; for (i=0; ifh); listChunkSize = HOST_TO_LENDIAN_INT32(listChunkSize); af_fwrite(&listChunkSize, sizeof (u_int32_t), 1, file->fh); af_fwrite("adtl", 4, 1, file->fh); for (i=0; ifh); af_fwrite(&labelSize, 4, 1, file->fh); af_fwrite(&cuePointID, 4, 1, file->fh); af_fwrite(name, strlen(name) + 1, 1, file->fh); /* If the name plus the size byte comprises an odd length, add another byte to make the string an even length. */ if (((strlen(name) + 1) % 2) != 0) { u_int8_t c=0; af_fwrite(&c, 1, 1, file->fh); } } free(markids); return AF_SUCCEED; } status _af_wave_write_init (AFfilesetup setup, AFfilehandle filehandle) { u_int32_t zero = 0; if (_af_filesetup_make_handle(setup, filehandle) == AF_FAIL) return AF_FAIL; filehandle->formatSpecific = waveinfo_new(); af_fseek(filehandle->fh, 0, SEEK_SET); af_fwrite("RIFF", 4, 1, filehandle->fh); af_fwrite(&zero, 4, 1, filehandle->fh); af_fwrite("WAVE", 4, 1, filehandle->fh); WriteMiscellaneous(filehandle); WriteCues(filehandle); WriteFormat(filehandle); WriteFrameCount(filehandle); WriteData(filehandle); return AF_SUCCEED; }